@tonyclaw/llm-inspector 1.8.0 → 1.9.1

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 (30) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{index-DH3FOgcK.js → index-BmEH5jeO.js} +18 -18
  3. package/.output/public/assets/{index-BLVa7n9b.css → index-DdJSLfxK.css} +1 -1
  4. package/.output/public/assets/{main-Beo3LJDa.js → main-GVpFMVGE.js} +1 -1
  5. package/.output/server/_ssr/{index-HkueJ4Un.mjs → index-D0d6QxPt.mjs} +85 -31
  6. package/.output/server/_ssr/index.mjs +2 -2
  7. package/.output/server/_ssr/{router-DTswxb7l.mjs → router-D9uLXa9A.mjs} +287 -67
  8. package/.output/server/{_tanstack-start-manifest_v-DhUuivt-.mjs → _tanstack-start-manifest_v-ByfnNZV_.mjs} +1 -1
  9. package/.output/server/index.mjs +26 -26
  10. package/README.md +8 -209
  11. package/package.json +1 -1
  12. package/src/components/ProxyViewerContainer.tsx +10 -1
  13. package/src/components/providers/ProviderCard.tsx +19 -15
  14. package/src/components/providers/ProviderForm.tsx +21 -0
  15. package/src/components/proxy-viewer/LogEntry.tsx +7 -0
  16. package/src/components/proxy-viewer/ResponseView.tsx +32 -6
  17. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +12 -3
  18. package/src/proxy/chunkStorage.ts +4 -6
  19. package/src/proxy/formats/anthropic/schemas.ts +9 -0
  20. package/src/proxy/formats/anthropic/stream.ts +11 -0
  21. package/src/proxy/formats/openai/stream.ts +15 -0
  22. package/src/proxy/handler.ts +44 -27
  23. package/src/proxy/logIndex.ts +73 -7
  24. package/src/proxy/logger.ts +62 -6
  25. package/src/proxy/providers.ts +5 -0
  26. package/src/proxy/schemas.ts +2 -0
  27. package/src/proxy/socketTracker.ts +90 -36
  28. package/src/proxy/store.ts +32 -18
  29. package/src/routes/api/providers.$providerId.ts +1 -0
  30. package/src/routes/api/providers.ts +2 -0
@@ -1,5 +1,5 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports, a as React } from "../_libs/react.mjs";
2
- import { C as CapturedLogSchema, a as parseRequest, p as parseOpenAIResponse, I as InspectorResponseSchema } from "./router-DTswxb7l.mjs";
2
+ import { C as CapturedLogSchema, a as parseRequest, p as parseOpenAIResponse, I as InspectorResponseSchema } from "./router-D9uLXa9A.mjs";
3
3
  import { u as useVirtualizer } from "../_libs/tanstack__react-virtual.mjs";
4
4
  import { J as JSZip } from "../_libs/jszip.mjs";
5
5
  import { c as clsx } from "../_libs/clsx.mjs";
@@ -199,7 +199,7 @@ async function exportLogsAsZip(logs) {
199
199
  document.body.removeChild(anchor);
200
200
  URL.revokeObjectURL(url);
201
201
  }
202
- const version = "1.8.0";
202
+ const version = "1.9.1";
203
203
  const packageJson = {
204
204
  version
205
205
  };
@@ -1509,22 +1509,37 @@ function ResponseView({
1509
1509
  outputTokens,
1510
1510
  cacheCreationInputTokens,
1511
1511
  cacheReadInputTokens,
1512
- apiFormat
1512
+ apiFormat,
1513
+ error
1513
1514
  }) {
1514
- if (responseText === null) {
1515
+ if (responseText === null && error === void 0) {
1515
1516
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 py-3", children: [
1516
1517
  /* @__PURE__ */ jsxRuntimeExports.jsx(StatusIndicator, { status: responseStatus }),
1517
1518
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-muted-foreground italic", children: "No response" })
1518
1519
  ] });
1519
1520
  }
1520
- const isError = responseStatus !== null && responseStatus >= 400;
1521
- if (isError) {
1521
+ const isHttpError = responseStatus !== null && responseStatus >= 400;
1522
+ if (isHttpError) {
1522
1523
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1523
1524
  /* @__PURE__ */ jsxRuntimeExports.jsx(StatusIndicator, { status: responseStatus }),
1524
- /* @__PURE__ */ jsxRuntimeExports.jsx(ErrorResponseView, { text: responseText })
1525
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ErrorResponseView, { text: responseText ?? "" }),
1526
+ error !== void 0 && error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-destructive/50 bg-destructive/10 p-3 text-xs", children: [
1527
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
1528
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: error })
1529
+ ] })
1530
+ ] });
1531
+ }
1532
+ if (error !== void 0 && error !== null) {
1533
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1534
+ /* @__PURE__ */ jsxRuntimeExports.jsx(StatusIndicator, { status: responseStatus }),
1535
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-destructive/50 bg-destructive/10 p-3 text-xs", children: [
1536
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
1537
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: error })
1538
+ ] }),
1539
+ responseText !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ErrorResponseView, { text: responseText }) })
1525
1540
  ] });
1526
1541
  }
1527
- const parsed = parseResponse(responseText, apiFormat);
1542
+ const parsed = responseText !== null ? parseResponse(responseText, apiFormat) : null;
1528
1543
  if (parsed !== null) {
1529
1544
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1530
1545
  /* @__PURE__ */ jsxRuntimeExports.jsx(StatusIndicator, { status: responseStatus }),
@@ -1553,7 +1568,7 @@ function ResponseView({
1553
1568
  ] })
1554
1569
  ] })
1555
1570
  ] }),
1556
- /* @__PURE__ */ jsxRuntimeExports.jsx(MarkdownFallbackView, { text: responseText })
1571
+ /* @__PURE__ */ jsxRuntimeExports.jsx(MarkdownFallbackView, { text: responseText ?? "" })
1557
1572
  ] });
1558
1573
  }
1559
1574
  const ReplayResultSchema = object({
@@ -1711,6 +1726,7 @@ function StreamingChunkSequence({
1711
1726
  const [expandedIndices, setExpandedIndices] = reactExports.useState(/* @__PURE__ */ new Set());
1712
1727
  reactExports.useEffect(() => {
1713
1728
  if (!containerExpanded || chunkState.status !== "idle") return;
1729
+ let cancelled = false;
1714
1730
  setChunkState({ status: "loading" });
1715
1731
  fetch(`/api/logs/${logId}/chunks`).then((res) => {
1716
1732
  if (!res.ok) {
@@ -1718,11 +1734,18 @@ function StreamingChunkSequence({
1718
1734
  }
1719
1735
  return res.json();
1720
1736
  }).then((data) => {
1721
- setChunkState({ status: "success", chunks: data.chunks });
1737
+ if (!cancelled) {
1738
+ setChunkState({ status: "success", chunks: data.chunks });
1739
+ }
1722
1740
  }).catch(() => {
1723
- setChunkState({ status: "error", message: "Chunk data unavailable" });
1741
+ if (!cancelled) {
1742
+ setChunkState({ status: "error", message: "Chunk data unavailable" });
1743
+ }
1724
1744
  });
1725
- }, [containerExpanded, logId, chunkState.status]);
1745
+ return () => {
1746
+ cancelled = true;
1747
+ };
1748
+ }, [containerExpanded, logId]);
1726
1749
  const groups = reactExports.useMemo(() => {
1727
1750
  if (chunkState.status !== "success") return [];
1728
1751
  const map = /* @__PURE__ */ new Map();
@@ -1936,6 +1959,10 @@ const LogEntry = reactExports.memo(function LogEntry2({
1936
1959
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
1937
1960
  ] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No raw headers captured" }) }) }),
1938
1961
  /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 space-y-3", children: [
1962
+ log.error !== void 0 && log.error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-destructive/50 bg-destructive/10 p-3 text-xs", children: [
1963
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
1964
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: log.error })
1965
+ ] }),
1939
1966
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1940
1967
  CopyButton,
1941
1968
  {
@@ -1964,7 +1991,8 @@ const LogEntry = reactExports.memo(function LogEntry2({
1964
1991
  outputTokens: log.outputTokens,
1965
1992
  cacheCreationInputTokens: log.cacheCreationInputTokens,
1966
1993
  cacheReadInputTokens: log.cacheReadInputTokens,
1967
- apiFormat: log.apiFormat
1994
+ apiFormat: log.apiFormat,
1995
+ error: log.error
1968
1996
  }
1969
1997
  ) }) })
1970
1998
  ] }) })
@@ -2207,25 +2235,25 @@ function ProviderCard({
2207
2235
  onTest
2208
2236
  }) {
2209
2237
  const [showApiKey, setShowApiKey] = reactExports.useState(false);
2238
+ const docsUrl = provider.apiDocsUrl ?? Object.entries(KNOWN_PROVIDER_DOCS).find(
2239
+ ([keyword]) => provider.name.toLowerCase().includes(keyword)
2240
+ )?.[1];
2210
2241
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border rounded-lg p-4 flex flex-col gap-3 bg-card", children: [
2211
2242
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
2212
2243
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-2 min-w-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium truncate", children: provider.model !== void 0 && provider.model !== "" ? `${provider.model} (${provider.name})` : provider.name }) }),
2213
- Object.entries(KNOWN_PROVIDER_DOCS).map(
2214
- ([keyword, url]) => provider.name.toLowerCase().includes(keyword) ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
2215
- "a",
2216
- {
2217
- href: url,
2218
- target: "_blank",
2219
- rel: "noopener noreferrer",
2220
- className: "text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1 text-xs",
2221
- title: "View API documentation",
2222
- children: [
2223
- /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "size-3" }),
2224
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sr-only", children: "Docs" })
2225
- ]
2226
- },
2227
- keyword
2228
- ) : null
2244
+ docsUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
2245
+ "a",
2246
+ {
2247
+ href: docsUrl,
2248
+ target: "_blank",
2249
+ rel: "noopener noreferrer",
2250
+ className: "text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1 text-xs",
2251
+ title: "View API documentation",
2252
+ children: [
2253
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "size-3" }),
2254
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sr-only", children: "Docs" })
2255
+ ]
2256
+ }
2229
2257
  )
2230
2258
  ] }),
2231
2259
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
@@ -2331,6 +2359,7 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
2331
2359
  const [model, setModel] = reactExports.useState(provider?.model ?? "");
2332
2360
  const [format, setFormat] = reactExports.useState(provider?.format ?? "anthropic");
2333
2361
  const [baseUrl, setBaseUrl] = reactExports.useState(provider?.baseUrl ?? "");
2362
+ const [apiDocsUrl, setApiDocsUrl] = reactExports.useState(provider?.apiDocsUrl ?? "");
2334
2363
  const [errors, setErrors] = reactExports.useState({});
2335
2364
  const [isSubmitting, setIsSubmitting] = reactExports.useState(false);
2336
2365
  const [manualBaseUrlOverride, setManualBaseUrlOverride] = reactExports.useState(false);
@@ -2343,6 +2372,7 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
2343
2372
  setModel(provider.model ?? "");
2344
2373
  setFormat(provider.format ?? "anthropic");
2345
2374
  setBaseUrl(provider.baseUrl ?? "");
2375
+ setApiDocsUrl(provider.apiDocsUrl ?? "");
2346
2376
  setManualBaseUrlOverride(false);
2347
2377
  }
2348
2378
  }, [provider]);
@@ -2401,7 +2431,8 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
2401
2431
  apiKey: apiKey.trim(),
2402
2432
  model: model.trim() || void 0,
2403
2433
  format,
2404
- baseUrl: baseUrl.trim() || void 0
2434
+ baseUrl: baseUrl.trim() || void 0,
2435
+ apiDocsUrl: apiDocsUrl.trim() || void 0
2405
2436
  });
2406
2437
  } finally {
2407
2438
  setIsSubmitting(false);
@@ -2513,6 +2544,21 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
2513
2544
  errors.baseUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.baseUrl }),
2514
2545
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Base URL for the provider API." })
2515
2546
  ] }),
2547
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
2548
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "provider-api-docs-url", className: "text-sm font-medium", children: "API Docs URL" }),
2549
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2550
+ "input",
2551
+ {
2552
+ id: "provider-api-docs-url",
2553
+ type: "text",
2554
+ value: apiDocsUrl,
2555
+ onChange: (e) => setApiDocsUrl(e.target.value),
2556
+ placeholder: "https://api.example.com/docs",
2557
+ className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
2558
+ }
2559
+ ),
2560
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Optional API documentation URL. If not set, uses known provider docs." })
2561
+ ] }),
2516
2562
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 justify-end pt-2", children: [
2517
2563
  /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: isSubmitting, children: "Cancel" }),
2518
2564
  /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { type: "submit", disabled: isSubmitting, children: isSubmitting ? "Saving..." : provider ? "Update Provider" : "Add Provider" })
@@ -3291,6 +3337,7 @@ function ProxyViewerContainer() {
3291
3337
  const [viewMode, setViewMode] = reactExports.useState("simple");
3292
3338
  const [error, setError] = reactExports.useState(null);
3293
3339
  const eventSourceRef = reactExports.useRef(null);
3340
+ const reconnectTimeoutRef = reactExports.useRef(null);
3294
3341
  const fetchSessionsAndModels = reactExports.useCallback(async () => {
3295
3342
  try {
3296
3343
  const [sessionsRes, modelsRes] = await Promise.all([
@@ -3365,7 +3412,10 @@ function ProxyViewerContainer() {
3365
3412
  es.onerror = () => {
3366
3413
  setError("SSE connection lost, reconnecting...");
3367
3414
  es.close();
3368
- setTimeout(connectSSE, 3e3);
3415
+ if (reconnectTimeoutRef.current !== null) {
3416
+ clearTimeout(reconnectTimeoutRef.current);
3417
+ }
3418
+ reconnectTimeoutRef.current = setTimeout(connectSSE, 3e3);
3369
3419
  };
3370
3420
  void fetchSessionsAndModels();
3371
3421
  }, [selectedSession, selectedModel, fetchSessionsAndModels]);
@@ -3376,6 +3426,10 @@ function ProxyViewerContainer() {
3376
3426
  eventSourceRef.current.close();
3377
3427
  eventSourceRef.current = null;
3378
3428
  }
3429
+ if (reconnectTimeoutRef.current !== null) {
3430
+ clearTimeout(reconnectTimeoutRef.current);
3431
+ reconnectTimeoutRef.current = null;
3432
+ }
3379
3433
  };
3380
3434
  }, [connectSSE]);
3381
3435
  const handleClearAll = reactExports.useCallback(() => {
@@ -197,7 +197,7 @@ function getResponse() {
197
197
  return event.res;
198
198
  }
199
199
  async function getStartManifest(matchedRoutes) {
200
- const { tsrStartManifest } = await import("../_tanstack-start-manifest_v-DhUuivt-.mjs");
200
+ const { tsrStartManifest } = await import("../_tanstack-start-manifest_v-ByfnNZV_.mjs");
201
201
  const startManifest = tsrStartManifest();
202
202
  const rootRoute = startManifest.routes[rootRouteId] = startManifest.routes[rootRouteId] || {};
203
203
  rootRoute.assets = rootRoute.assets || [];
@@ -766,7 +766,7 @@ let entriesPromise;
766
766
  let baseManifestPromise;
767
767
  let cachedFinalManifestPromise;
768
768
  async function loadEntries() {
769
- const routerEntry = await import("./router-DTswxb7l.mjs").then((n) => n.r);
769
+ const routerEntry = await import("./router-D9uLXa9A.mjs").then((n) => n.r);
770
770
  const startEntry = await import("./start-HYkvq4Ni.mjs");
771
771
  return { startEntry, routerEntry };
772
772
  }