@tonyclaw/agent-inspector 2.0.27 → 2.0.29

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 (47) hide show
  1. package/.output/cli.js +169 -13
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-B_p6vmMQ.js → CompareDrawer-DcqxlgSQ.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-D650kQlv.js +115 -0
  5. package/.output/public/assets/{ReplayDialog-BmprJ6D3.js → ReplayDialog-CBcnPgx1.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-DqO0_SVJ.js → RequestAnatomy-DSgSqCYt.js} +1 -1
  7. package/.output/public/assets/{ResponseView-FZbPNdHa.js → ResponseView-CjqzBSoF.js} +1 -1
  8. package/.output/public/assets/{StreamingChunkSequence-BZPzfJWj.js → StreamingChunkSequence-DdBBKolI.js} +1 -1
  9. package/.output/public/assets/_sessionId-wctOoGh_.js +1 -0
  10. package/.output/public/assets/index-BhQGSdhG.js +1 -0
  11. package/.output/public/assets/{index-DsiKfWCp.css → index-CwlHPmgL.css} +1 -1
  12. package/.output/public/assets/{main-CzItFZlM.js → main-B6OLZCp9.js} +2 -2
  13. package/.output/server/_libs/lucide-react.mjs +13 -13
  14. package/.output/server/{_sessionId-DZH8SrEZ.mjs → _sessionId-CCfKJJ19.mjs} +2 -2
  15. package/.output/server/_ssr/{CompareDrawer-L0aE1UV1.mjs → CompareDrawer-uaPI5R6e.mjs} +2 -2
  16. package/.output/server/_ssr/{ProxyViewerContainer-B62RB9ER.mjs → ProxyViewerContainer-BV2nIxXy.mjs} +83 -20
  17. package/.output/server/_ssr/{ReplayDialog-FVWnpx2C.mjs → ReplayDialog-eE2Hy2Nl.mjs} +4 -4
  18. package/.output/server/_ssr/{RequestAnatomy-DIyqW-Ny.mjs → RequestAnatomy-B7vwWo57.mjs} +2 -2
  19. package/.output/server/_ssr/{ResponseView-BX4mxEZ5.mjs → ResponseView-Bm6Qp8mo.mjs} +2 -2
  20. package/.output/server/_ssr/{StreamingChunkSequence-Cs3nzOor.mjs → StreamingChunkSequence-CbAJKJQx.mjs} +2 -2
  21. package/.output/server/_ssr/{index-Dik2Mc3h.mjs → index-u14_Aj60.mjs} +2 -2
  22. package/.output/server/_ssr/index.mjs +2 -2
  23. package/.output/server/_ssr/{router-DCPg8ykx.mjs → router-qgeGUp1k.mjs} +161 -18
  24. package/.output/server/{_tanstack-start-manifest_v-BaoL3JCh.mjs → _tanstack-start-manifest_v-BrfUXnre.mjs} +1 -1
  25. package/.output/server/index.mjs +59 -59
  26. package/package.json +1 -1
  27. package/src/cli/networkHints.ts +150 -0
  28. package/src/cli.ts +72 -13
  29. package/src/components/ProxyViewer.tsx +3 -0
  30. package/src/components/providers/ProviderCard.tsx +32 -0
  31. package/src/components/proxy-viewer/LogEntryHeader.tsx +38 -1
  32. package/src/lib/providerTestContract.ts +3 -0
  33. package/src/lib/sessionInfoContract.ts +3 -0
  34. package/src/mcp/toolHandlers.ts +3 -0
  35. package/src/proxy/evidenceAnalysis.ts +5 -0
  36. package/src/proxy/evidenceExporter.ts +8 -3
  37. package/src/proxy/handler.ts +8 -1
  38. package/src/proxy/logFinalizer.ts +36 -0
  39. package/src/proxy/schemas.ts +3 -0
  40. package/src/proxy/sessionInfo.ts +3 -0
  41. package/src/proxy/store.ts +3 -0
  42. package/src/proxy/streamTiming.ts +31 -0
  43. package/src/routes/api/logs.$id.replay.ts +25 -2
  44. package/src/routes/api/providers.$providerId.test.log.ts +34 -2
  45. package/.output/public/assets/ProxyViewerContainer-DItqh1EO.js +0 -115
  46. package/.output/public/assets/_sessionId-y2ATIp51.js +0 -1
  47. package/.output/public/assets/index-CQT4higx.js +0 -1
@@ -50,7 +50,7 @@ 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";
53
+ const appCss = "/assets/index-CwlHPmgL.css";
54
54
  const Route$v = createRootRoute({
55
55
  head: () => ({
56
56
  meta: [
@@ -93,7 +93,7 @@ function RootDocument({ children }) {
93
93
  ] })
94
94
  ] });
95
95
  }
96
- const $$splitComponentImporter$1 = () => import("./index-Dik2Mc3h.mjs");
96
+ const $$splitComponentImporter$1 = () => import("./index-u14_Aj60.mjs");
97
97
  const Route$u = createFileRoute("/")({
98
98
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
99
99
  });
@@ -136,7 +136,7 @@ function decodeSessionIdFromPath(encoded) {
136
136
  function getSessionPath(sessionId) {
137
137
  return `/session/${encodeSessionIdForPath(sessionId)}`;
138
138
  }
139
- const $$splitComponentImporter = () => import("../_sessionId-DZH8SrEZ.mjs");
139
+ const $$splitComponentImporter = () => import("../_sessionId-CCfKJJ19.mjs");
140
140
  const Route$t = createFileRoute("/session/$sessionId")({
141
141
  component: lazyRouteComponent($$splitComponentImporter, "component"),
142
142
  parseParams: (params) => ({
@@ -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,
@@ -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
  },
@@ -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(),
@@ -5616,6 +5682,9 @@ function buildLogSummary(log) {
5616
5682
  isStreaming: log.streaming,
5617
5683
  hasError,
5618
5684
  latencyMs: log.elapsedMs,
5685
+ firstChunkMs: log.firstChunkMs ?? null,
5686
+ totalStreamMs: log.totalStreamMs ?? null,
5687
+ tokensPerSecond: log.tokensPerSecond ?? null,
5619
5688
  tokens: {
5620
5689
  input: log.inputTokens,
5621
5690
  output: log.outputTokens,
@@ -8391,6 +8460,11 @@ function logDetails(log) {
8391
8460
  `streaming=${log.isStreaming ? "yes" : "no"}`,
8392
8461
  `latencyMs=${formatNullable(log.latencyMs)}`
8393
8462
  ];
8463
+ if (log.isStreaming) {
8464
+ parts.push(`firstChunkMs=${formatNullable(log.firstChunkMs ?? null)}`);
8465
+ parts.push(`totalStreamMs=${formatNullable(log.totalStreamMs ?? null)}`);
8466
+ parts.push(`tokensPerSecond=${formatNullable(log.tokensPerSecond ?? null)}`);
8467
+ }
8394
8468
  if (log.error !== null) {
8395
8469
  parts.push(`error=${log.error}`);
8396
8470
  }
@@ -8643,12 +8717,13 @@ function buildMarkdown(document) {
8643
8717
  lines.push("No compact logs were available.", "");
8644
8718
  } else {
8645
8719
  lines.push(
8646
- "| ID | Status | Model | Latency | Path | Error |",
8647
- "| --- | --- | --- | --- | --- | --- |"
8720
+ "| ID | Status | Model | Latency | Stream | Path | Error |",
8721
+ "| --- | --- | --- | --- | --- | --- | --- |"
8648
8722
  );
8649
8723
  for (const log of session.latestLogs) {
8724
+ const streamTiming = log.isStreaming ? `first=${log.firstChunkMs ?? "n/a"}ms total=${log.totalStreamMs ?? "n/a"}ms tps=${log.tokensPerSecond ?? "n/a"}` : "n/a";
8650
8725
  lines.push(
8651
- `| ${log.id} | ${log.status ?? "n/a"} | ${log.model ?? "n/a"} | ${log.latencyMs ?? "n/a"} | ${log.path} | ${log.hasError ? "yes" : "no"} |`
8726
+ `| ${log.id} | ${log.status ?? "n/a"} | ${log.model ?? "n/a"} | ${log.latencyMs ?? "n/a"} | ${streamTiming} | ${log.path} | ${log.hasError ? "yes" : "no"} |`
8652
8727
  );
8653
8728
  }
8654
8729
  lines.push("");
@@ -8998,7 +9073,7 @@ async function readResponseText(response) {
8998
9073
  return `Failed to read response body: ${String(err)}`;
8999
9074
  }
9000
9075
  }
9001
- function createErrorResult(error, latencyMs, streaming, responseStatus, debug) {
9076
+ function createErrorResult(error, latencyMs, streaming, responseStatus, debug, streamTiming) {
9002
9077
  const { type, details } = classifyError(error, responseStatus);
9003
9078
  const result = {
9004
9079
  success: false,
@@ -9011,6 +9086,11 @@ function createErrorResult(error, latencyMs, streaming, responseStatus, debug) {
9011
9086
  latencyMs,
9012
9087
  streaming
9013
9088
  };
9089
+ if (streamTiming !== void 0) {
9090
+ result.firstChunkMs = streamTiming.firstChunkMs;
9091
+ result.totalStreamMs = streamTiming.totalStreamMs;
9092
+ result.tokensPerSecond = streamTiming.tokensPerSecond;
9093
+ }
9014
9094
  if (debug !== void 0) {
9015
9095
  result.debug = debug;
9016
9096
  if (debug.requestHeaders !== void 0) {
@@ -9232,14 +9312,17 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, isOpenAI)
9232
9312
  const decoder = new TextDecoder();
9233
9313
  const MAX_CHUNKS_SIZE = 10 * 1024 * 1024;
9234
9314
  let totalSize = 0;
9315
+ let firstChunkMs = null;
9235
9316
  try {
9236
9317
  while (true) {
9237
9318
  const { done, value } = await reader.read();
9238
9319
  if (done) break;
9320
+ if (firstChunkMs === null) firstChunkMs = Date.now() - startTime;
9239
9321
  if (totalSize >= MAX_CHUNKS_SIZE) {
9322
+ const totalStreamMs2 = Date.now() - startTime;
9240
9323
  return createErrorResult(
9241
9324
  "Response too large (exceeded 10MB limit)",
9242
- Date.now() - startTime,
9325
+ totalStreamMs2,
9243
9326
  true,
9244
9327
  response.status,
9245
9328
  createTestDebug({
@@ -9248,7 +9331,8 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, isOpenAI)
9248
9331
  requestBody: body,
9249
9332
  response,
9250
9333
  responseBody: chunks.join("")
9251
- })
9334
+ }),
9335
+ { firstChunkMs, totalStreamMs: totalStreamMs2, tokensPerSecond: null }
9252
9336
  );
9253
9337
  }
9254
9338
  const decoded = decoder.decode(value, { stream: true });
@@ -9260,6 +9344,7 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, isOpenAI)
9260
9344
  chunks.push(finalChunk);
9261
9345
  }
9262
9346
  } catch (readErr) {
9347
+ const totalStreamMs2 = Date.now() - startTime;
9263
9348
  const error = readErr instanceof Error && readErr.name === "AbortError" ? readErr : `Stream read error: ${String(readErr)}`;
9264
9349
  return createErrorResult(
9265
9350
  error,
@@ -9272,10 +9357,12 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, isOpenAI)
9272
9357
  requestBody: body,
9273
9358
  response,
9274
9359
  responseBody: chunks.join("")
9275
- })
9360
+ }),
9361
+ { firstChunkMs, totalStreamMs: totalStreamMs2, tokensPerSecond: null }
9276
9362
  );
9277
9363
  }
9278
9364
  const fullResponse = chunks.join("");
9365
+ const totalStreamMs = Date.now() - startTime;
9279
9366
  const safeRequestHeaders = redactHeaders(requestHeaders);
9280
9367
  const mockLog = {
9281
9368
  id: 0,
@@ -9336,6 +9423,11 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, isOpenAI)
9336
9423
  }
9337
9424
  }
9338
9425
  }
9426
+ const tokensPerSecond = computeTokensPerSecond({
9427
+ outputTokens,
9428
+ firstChunkMs,
9429
+ totalStreamMs
9430
+ });
9339
9431
  return {
9340
9432
  success: true,
9341
9433
  model: responseModel ?? model,
@@ -9344,6 +9436,9 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, isOpenAI)
9344
9436
  cacheCreationInputTokens,
9345
9437
  cacheReadInputTokens,
9346
9438
  latencyMs,
9439
+ firstChunkMs,
9440
+ totalStreamMs,
9441
+ tokensPerSecond,
9347
9442
  content: content.length > 0 ? content : void 0,
9348
9443
  rawResponse: reconstructedJson,
9349
9444
  streaming: true,
@@ -9362,7 +9457,8 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, isOpenAI)
9362
9457
  requestBody: body,
9363
9458
  response,
9364
9459
  responseBody: fullResponse
9365
- })
9460
+ }),
9461
+ { firstChunkMs, totalStreamMs, tokensPerSecond: null }
9366
9462
  );
9367
9463
  }
9368
9464
  } catch (err) {
@@ -9633,26 +9729,35 @@ const Route$5 = createFileRoute("/api/logs/$id/replay")({
9633
9729
  error: String(err)
9634
9730
  });
9635
9731
  }
9636
- const elapsedMs = Date.now() - startTime;
9637
9732
  const isStream = upstreamRes.headers.get(HEADER_CONTENT_TYPE)?.includes(CONTENT_TYPE_EVENT_STREAM) ?? false;
9638
9733
  if (isStream) {
9639
9734
  const chunks = [];
9640
9735
  const decoder = new TextDecoder();
9641
9736
  const reader = upstreamRes.body?.getReader();
9737
+ let firstChunkMs = null;
9642
9738
  if (reader) {
9643
9739
  try {
9644
9740
  while (true) {
9645
9741
  const { done, value } = await reader.read();
9646
9742
  if (done) break;
9743
+ if (firstChunkMs === null) firstChunkMs = Date.now() - startTime;
9647
9744
  chunks.push(decoder.decode(value, { stream: true }));
9648
9745
  }
9649
9746
  } catch {
9650
9747
  }
9651
9748
  }
9652
9749
  const fullResponse = chunks.join("");
9750
+ const elapsedMs = Date.now() - startTime;
9751
+ const totalStreamMs = elapsedMs;
9653
9752
  const mockLog = { ...log };
9654
9753
  const responseText = formatHandler.extractStream(fullResponse, mockLog, model, false);
9655
9754
  const tokens = formatHandler.extractTokens(responseText);
9755
+ const outputTokens = tokens.outputTokens ?? null;
9756
+ const tokensPerSecond = computeTokensPerSecond({
9757
+ outputTokens,
9758
+ firstChunkMs,
9759
+ totalStreamMs
9760
+ });
9656
9761
  const savedReplayLog = await addTestLogEntry({
9657
9762
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9658
9763
  method: log.method,
@@ -9663,10 +9768,13 @@ const Route$5 = createFileRoute("/api/logs/$id/replay")({
9663
9768
  responseStatus: upstreamRes.status,
9664
9769
  responseText,
9665
9770
  inputTokens: tokens.inputTokens ?? null,
9666
- outputTokens: tokens.outputTokens ?? null,
9771
+ outputTokens,
9667
9772
  cacheCreationInputTokens: tokens.cacheCreationInputTokens ?? null,
9668
9773
  cacheReadInputTokens: tokens.cacheReadInputTokens ?? null,
9669
9774
  elapsedMs,
9775
+ firstChunkMs,
9776
+ totalStreamMs,
9777
+ tokensPerSecond,
9670
9778
  streaming: true,
9671
9779
  userAgent: log.userAgent,
9672
9780
  origin: log.origin,
@@ -9691,11 +9799,15 @@ const Route$5 = createFileRoute("/api/logs/$id/replay")({
9691
9799
  inputTokens: tokens.inputTokens ?? void 0,
9692
9800
  outputTokens: tokens.outputTokens ?? void 0,
9693
9801
  elapsedMs,
9802
+ firstChunkMs,
9803
+ totalStreamMs,
9804
+ tokensPerSecond,
9694
9805
  streaming: true,
9695
9806
  replayLogId: savedReplayLog.id
9696
9807
  });
9697
9808
  } else {
9698
9809
  const responseText = await upstreamRes.text();
9810
+ const elapsedMs = Date.now() - startTime;
9699
9811
  const tokens = formatHandler.extractTokens(responseText);
9700
9812
  const savedReplayLog = await addTestLogEntry({
9701
9813
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -9711,6 +9823,9 @@ const Route$5 = createFileRoute("/api/logs/$id/replay")({
9711
9823
  cacheCreationInputTokens: tokens.cacheCreationInputTokens ?? null,
9712
9824
  cacheReadInputTokens: tokens.cacheReadInputTokens ?? null,
9713
9825
  elapsedMs,
9826
+ firstChunkMs: null,
9827
+ totalStreamMs: null,
9828
+ tokensPerSecond: null,
9714
9829
  streaming: false,
9715
9830
  userAgent: log.userAgent,
9716
9831
  origin: log.origin,
@@ -9846,6 +9961,9 @@ const ProviderTestResultSchema = object({
9846
9961
  cacheCreationInputTokens: number().optional(),
9847
9962
  cacheReadInputTokens: number().optional(),
9848
9963
  latencyMs: number().optional(),
9964
+ firstChunkMs: number().nullable().optional(),
9965
+ totalStreamMs: number().nullable().optional(),
9966
+ tokensPerSecond: number().nullable().optional(),
9849
9967
  content: array(ProviderTestContentSchema).optional(),
9850
9968
  rawResponse: string().optional(),
9851
9969
  streaming: boolean().optional(),
@@ -9915,6 +10033,19 @@ function getResultResponseText(result) {
9915
10033
  function getResultHeaders(result) {
9916
10034
  return result.requestHeaders ?? result.debug?.requestHeaders ?? {};
9917
10035
  }
10036
+ function getResultTotalStreamMs(result) {
10037
+ return result.totalStreamMs ?? (result.streaming === true ? result.latencyMs ?? null : null);
10038
+ }
10039
+ function getResultFirstChunkMs(result) {
10040
+ return result.firstChunkMs ?? firstChunkMsFromChunks(result.streamingChunks?.chunks);
10041
+ }
10042
+ function getResultTokensPerSecond(result) {
10043
+ return result.tokensPerSecond ?? computeTokensPerSecond({
10044
+ outputTokens: result.outputTokens,
10045
+ firstChunkMs: getResultFirstChunkMs(result),
10046
+ totalStreamMs: getResultTotalStreamMs(result)
10047
+ });
10048
+ }
9918
10049
  async function logModelResults(displayName, providerName, anthropicUrl, openaiUrl, modelResults, captureFullDetails) {
9919
10050
  const usageModel = getModelUsageName(displayName, providerName);
9920
10051
  if (anthropicUrl !== void 0) {
@@ -9938,6 +10069,9 @@ async function logModelResults(displayName, providerName, anthropicUrl, openaiUr
9938
10069
  cacheCreationInputTokens: nonStreamingResult.cacheCreationInputTokens ?? null,
9939
10070
  cacheReadInputTokens: nonStreamingResult.cacheReadInputTokens ?? null,
9940
10071
  elapsedMs: nonStreamingResult.latencyMs ?? 0,
10072
+ firstChunkMs: null,
10073
+ totalStreamMs: null,
10074
+ tokensPerSecond: null,
9941
10075
  streaming: false,
9942
10076
  userAgent: "provider-test",
9943
10077
  origin: null,
@@ -9962,7 +10096,10 @@ async function logModelResults(displayName, providerName, anthropicUrl, openaiUr
9962
10096
  outputTokens: streamingResult.outputTokens ?? null,
9963
10097
  cacheCreationInputTokens: streamingResult.cacheCreationInputTokens ?? null,
9964
10098
  cacheReadInputTokens: streamingResult.cacheReadInputTokens ?? null,
9965
- elapsedMs: streamingResult.latencyMs ?? 0,
10099
+ elapsedMs: getResultTotalStreamMs(streamingResult) ?? 0,
10100
+ firstChunkMs: getResultFirstChunkMs(streamingResult),
10101
+ totalStreamMs: getResultTotalStreamMs(streamingResult),
10102
+ tokensPerSecond: getResultTokensPerSecond(streamingResult),
9966
10103
  streaming: true,
9967
10104
  streamingChunks: captureFullDetails ? streamingResult.streamingChunks : void 0,
9968
10105
  userAgent: "provider-test",
@@ -9995,6 +10132,9 @@ async function logModelResults(displayName, providerName, anthropicUrl, openaiUr
9995
10132
  cacheCreationInputTokens: null,
9996
10133
  cacheReadInputTokens: null,
9997
10134
  elapsedMs: nonStreamingResult.latencyMs ?? 0,
10135
+ firstChunkMs: null,
10136
+ totalStreamMs: null,
10137
+ tokensPerSecond: null,
9998
10138
  streaming: false,
9999
10139
  userAgent: "provider-test",
10000
10140
  origin: null,
@@ -10019,7 +10159,10 @@ async function logModelResults(displayName, providerName, anthropicUrl, openaiUr
10019
10159
  outputTokens: streamingResult.outputTokens ?? null,
10020
10160
  cacheCreationInputTokens: null,
10021
10161
  cacheReadInputTokens: null,
10022
- elapsedMs: streamingResult.latencyMs ?? 0,
10162
+ elapsedMs: getResultTotalStreamMs(streamingResult) ?? 0,
10163
+ firstChunkMs: getResultFirstChunkMs(streamingResult),
10164
+ totalStreamMs: getResultTotalStreamMs(streamingResult),
10165
+ tokensPerSecond: getResultTokensPerSecond(streamingResult),
10023
10166
  streaming: true,
10024
10167
  streamingChunks: captureFullDetails ? streamingResult.streamingChunks : void 0,
10025
10168
  userAgent: "provider-test",
@@ -1,4 +1,4 @@
1
- const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/__root.tsx", "children": ["/", "/api/config", "/api/health", "/api/logs", "/api/mcp", "/api/models", "/api/providers", "/api/runs", "/api/sessions", "/proxy/$", "/session/$sessionId", "/api/knowledge/candidates", "/api/knowledge/project-context", "/api/knowledge/search", "/api/knowledge/sessions/$sessionId/candidates"], "preloads": ["/assets/main-CzItFZlM.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-CQT4higx.js", "/assets/ProxyViewerContainer-DItqh1EO.js"] }, "/api/config": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/config.ts", "children": ["/api/config/paths"] }, "/api/health": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/health.ts" }, "/api/logs": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.ts", "children": ["/api/logs/$id", "/api/logs/stream"] }, "/api/mcp": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/mcp.ts" }, "/api/models": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/models.ts" }, "/api/providers": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.ts", "children": ["/api/providers/$providerId", "/api/providers/export", "/api/providers/import", "/api/providers/scan"] }, "/api/runs": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/runs.ts", "children": ["/api/runs/$runId"] }, "/api/sessions": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/sessions.ts" }, "/proxy/$": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/proxy/$.ts" }, "/session/$sessionId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/session/$sessionId.tsx", "assets": [], "preloads": ["/assets/_sessionId-y2ATIp51.js", "/assets/ProxyViewerContainer-DItqh1EO.js"] }, "/api/config/paths": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/config.paths.ts" }, "/api/knowledge/candidates": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.candidates.ts", "children": ["/api/knowledge/candidates/$candidateId"] }, "/api/knowledge/project-context": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.project-context.ts" }, "/api/knowledge/search": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.search.ts" }, "/api/logs/$id": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.$id.ts", "children": ["/api/logs/$id/chunks", "/api/logs/$id/replay"] }, "/api/logs/stream": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.stream.ts" }, "/api/providers/$providerId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.ts", "children": ["/api/providers/$providerId/model-metadata", "/api/providers/$providerId/test"] }, "/api/providers/export": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.export.ts" }, "/api/providers/import": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.import.ts" }, "/api/providers/scan": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.scan.ts" }, "/api/runs/$runId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/runs.$runId.ts", "children": ["/api/runs/$runId/evidence"] }, "/api/knowledge/candidates/$candidateId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.candidates.$candidateId.ts", "children": ["/api/knowledge/candidates/$candidateId/promote"] }, "/api/logs/$id/chunks": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.$id.chunks.ts" }, "/api/logs/$id/replay": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.$id.replay.ts" }, "/api/providers/$providerId/model-metadata": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.model-metadata.ts" }, "/api/providers/$providerId/test": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.test.ts", "children": ["/api/providers/$providerId/test/log"] }, "/api/runs/$runId/evidence": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/runs.$runId.evidence.ts" }, "/api/knowledge/candidates/$candidateId/promote": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.candidates.$candidateId.promote.ts" }, "/api/knowledge/sessions/$sessionId/candidates": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.sessions.$sessionId.candidates.ts" }, "/api/providers/$providerId/test/log": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.test.log.ts" } }, "clientEntry": "/assets/main-CzItFZlM.js" });
1
+ const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/__root.tsx", "children": ["/", "/api/config", "/api/health", "/api/logs", "/api/mcp", "/api/models", "/api/providers", "/api/runs", "/api/sessions", "/proxy/$", "/session/$sessionId", "/api/knowledge/candidates", "/api/knowledge/project-context", "/api/knowledge/search", "/api/knowledge/sessions/$sessionId/candidates"], "preloads": ["/assets/main-B6OLZCp9.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-BhQGSdhG.js", "/assets/ProxyViewerContainer-D650kQlv.js"] }, "/api/config": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/config.ts", "children": ["/api/config/paths"] }, "/api/health": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/health.ts" }, "/api/logs": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.ts", "children": ["/api/logs/$id", "/api/logs/stream"] }, "/api/mcp": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/mcp.ts" }, "/api/models": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/models.ts" }, "/api/providers": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.ts", "children": ["/api/providers/$providerId", "/api/providers/export", "/api/providers/import", "/api/providers/scan"] }, "/api/runs": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/runs.ts", "children": ["/api/runs/$runId"] }, "/api/sessions": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/sessions.ts" }, "/proxy/$": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/proxy/$.ts" }, "/session/$sessionId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/session/$sessionId.tsx", "assets": [], "preloads": ["/assets/_sessionId-wctOoGh_.js", "/assets/ProxyViewerContainer-D650kQlv.js"] }, "/api/config/paths": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/config.paths.ts" }, "/api/knowledge/candidates": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.candidates.ts", "children": ["/api/knowledge/candidates/$candidateId"] }, "/api/knowledge/project-context": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.project-context.ts" }, "/api/knowledge/search": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.search.ts" }, "/api/logs/$id": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.$id.ts", "children": ["/api/logs/$id/chunks", "/api/logs/$id/replay"] }, "/api/logs/stream": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.stream.ts" }, "/api/providers/$providerId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.ts", "children": ["/api/providers/$providerId/model-metadata", "/api/providers/$providerId/test"] }, "/api/providers/export": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.export.ts" }, "/api/providers/import": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.import.ts" }, "/api/providers/scan": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.scan.ts" }, "/api/runs/$runId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/runs.$runId.ts", "children": ["/api/runs/$runId/evidence"] }, "/api/knowledge/candidates/$candidateId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.candidates.$candidateId.ts", "children": ["/api/knowledge/candidates/$candidateId/promote"] }, "/api/logs/$id/chunks": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.$id.chunks.ts" }, "/api/logs/$id/replay": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.$id.replay.ts" }, "/api/providers/$providerId/model-metadata": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.model-metadata.ts" }, "/api/providers/$providerId/test": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.test.ts", "children": ["/api/providers/$providerId/test/log"] }, "/api/runs/$runId/evidence": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/runs.$runId.evidence.ts" }, "/api/knowledge/candidates/$candidateId/promote": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.candidates.$candidateId.promote.ts" }, "/api/knowledge/sessions/$sessionId/candidates": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.sessions.$sessionId.candidates.ts" }, "/api/providers/$providerId/test/log": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.test.log.ts" } }, "clientEntry": "/assets/main-B6OLZCp9.js" });
2
2
  export {
3
3
  tsrStartManifest
4
4
  };