@tonyclaw/llm-inspector 1.7.8 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/index-BLVa7n9b.css +1 -0
  3. package/.output/public/assets/index-DH3FOgcK.js +97 -0
  4. package/.output/public/assets/main-Beo3LJDa.js +17 -0
  5. package/.output/server/_libs/dequal.mjs +27 -0
  6. package/.output/server/_libs/lucide-react.mjs +98 -91
  7. package/.output/server/_libs/swr.mjs +938 -0
  8. package/.output/server/_libs/use-sync-external-store.mjs +64 -1
  9. package/.output/server/_libs/zod.mjs +1 -0
  10. package/.output/server/_ssr/{index-hNquJMfH.mjs → index-HkueJ4Un.mjs} +218 -71
  11. package/.output/server/_ssr/index.mjs +2 -2
  12. package/.output/server/_ssr/{router-MmnX-LYh.mjs → router-DTswxb7l.mjs} +264 -52
  13. package/.output/server/_tanstack-start-manifest_v-DhUuivt-.mjs +4 -0
  14. package/.output/server/index.mjs +26 -26
  15. package/package.json +2 -1
  16. package/src/components/ProxyViewer.tsx +2 -0
  17. package/src/components/providers/ProviderCard.tsx +38 -33
  18. package/src/components/providers/ProviderLogo.tsx +6 -1
  19. package/src/components/providers/ProvidersPanel.tsx +144 -43
  20. package/src/components/providers/SettingsDialog.tsx +5 -3
  21. package/src/components/proxy-viewer/ConversationGroup.tsx +3 -3
  22. package/src/components/proxy-viewer/LogEntry.tsx +6 -3
  23. package/src/components/proxy-viewer/LogEntryHeader.tsx +3 -2
  24. package/src/lib/useProviders.ts +30 -0
  25. package/src/proxy/formats/anthropic/stream.ts +3 -2
  26. package/src/proxy/formats/openai/stream.ts +3 -2
  27. package/src/proxy/handler.ts +5 -0
  28. package/src/proxy/providers.ts +98 -0
  29. package/src/routes/__root.tsx +4 -1
  30. package/src/routes/api/providers.export.ts +26 -0
  31. package/src/routes/api/providers.import.ts +47 -0
  32. package/.output/public/assets/index-B3RwBPLW.css +0 -1
  33. package/.output/public/assets/index-C8o6bEv6.js +0 -97
  34. package/.output/public/assets/main-Bxc5pKCu.js +0 -17
  35. package/.output/server/_tanstack-start-manifest_v-CYKtU_9S.mjs +0 -4
@@ -1,5 +1,6 @@
1
1
  import { c as createRouter, a as createRootRoute, b as createFileRoute, l as lazyRouteComponent, O as Outlet, H as HeadContent, S as Scripts } from "../_libs/tanstack__react-router.mjs";
2
2
  import { j as jsxRuntimeExports } from "../_libs/react.mjs";
3
+ import { S as SWRConfig } from "../_libs/swr.mjs";
3
4
  import { mkdirSync, writeFileSync, renameSync, copyFileSync, unlinkSync, existsSync, readFileSync } from "node:fs";
4
5
  import path, { join, dirname, isAbsolute } from "node:path";
5
6
  import { readFile, mkdir, writeFile, appendFile } from "node:fs/promises";
@@ -7,7 +8,7 @@ import { C as Conf } from "../_libs/conf.mjs";
7
8
  import { randomUUID } from "crypto";
8
9
  import { exec } from "node:child_process";
9
10
  import { promisify } from "node:util";
10
- import { o as object, _ as _enum, s as string, a as array, n as number, d as discriminatedUnion, l as literal, b as boolean, r as record, c as lazy, e as _null, u as union } from "../_libs/zod.mjs";
11
+ import { o as object, _ as _enum, s as string, u as union, a as array, n as number, d as discriminatedUnion, l as literal, b as boolean, r as record, c as lazy, e as _null, f as unknown } from "../_libs/zod.mjs";
11
12
  import "../_libs/tiny-warning.mjs";
12
13
  import "../_libs/tanstack__router-core.mjs";
13
14
  import "../_libs/cookie-es.mjs";
@@ -22,6 +23,8 @@ import "util";
22
23
  import "async_hooks";
23
24
  import "stream";
24
25
  import "../_libs/isbot.mjs";
26
+ import "../_libs/use-sync-external-store.mjs";
27
+ import "../_libs/dequal.mjs";
25
28
  import "node:process";
26
29
  import "node:crypto";
27
30
  import "node:assert";
@@ -41,8 +44,8 @@ import "../_libs/debounce-fn.mjs";
41
44
  import "../_libs/mimic-function.mjs";
42
45
  import "../_libs/semver.mjs";
43
46
  import "../_libs/uint8array-extras.mjs";
44
- const appCss = "/assets/index-B3RwBPLW.css";
45
- const Route$e = createRootRoute({
47
+ const appCss = "/assets/index-BLVa7n9b.css";
48
+ const Route$g = createRootRoute({
46
49
  head: () => ({
47
50
  meta: [
48
51
  { charSet: "utf-8" },
@@ -60,13 +63,13 @@ function RootDocument({ children }) {
60
63
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("html", { lang: "en", className: "dark", children: [
61
64
  /* @__PURE__ */ jsxRuntimeExports.jsx("head", { children: /* @__PURE__ */ jsxRuntimeExports.jsx(HeadContent, {}) }),
62
65
  /* @__PURE__ */ jsxRuntimeExports.jsxs("body", { children: [
63
- children,
66
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SWRConfig, { value: { revalidateOnFocus: false, revalidateIfStale: false }, children }),
64
67
  /* @__PURE__ */ jsxRuntimeExports.jsx(Scripts, {})
65
68
  ] })
66
69
  ] });
67
70
  }
68
- const $$splitComponentImporter = () => import("./index-hNquJMfH.mjs");
69
- const Route$d = createFileRoute("/")({
71
+ const $$splitComponentImporter = () => import("./index-HkueJ4Un.mjs");
72
+ const Route$f = createFileRoute("/")({
70
73
  component: lazyRouteComponent($$splitComponentImporter, "component")
71
74
  });
72
75
  const LOG_DIR_ENV = process.env["LOG_DIR"];
@@ -939,9 +942,10 @@ function extractAnthropicStream(raw, log, fallbackModel, collectChunks) {
939
942
  let streamStartMs = 0;
940
943
  const chunks = [];
941
944
  for (const line of raw.split("\n")) {
942
- if (!line.startsWith("data: ")) continue;
945
+ const trimmedLine = line.trim();
946
+ if (!trimmedLine.startsWith("data: ")) continue;
943
947
  try {
944
- const json = JSON.parse(line.slice(6));
948
+ const json = JSON.parse(trimmedLine.slice(6));
945
949
  const parsed = SseEventSchema.safeParse(json);
946
950
  if (!parsed.success) continue;
947
951
  const data = parsed.data;
@@ -1175,8 +1179,9 @@ function extractOpenAIStream(raw, log, fallbackModel, collectChunks = true) {
1175
1179
  let streamStartMs = 0;
1176
1180
  const chunks = [];
1177
1181
  for (const line of raw.split("\n")) {
1178
- if (!line.startsWith("data: ")) continue;
1179
- const dataStr = line.slice(6).trim();
1182
+ const trimmedLine = line.trim();
1183
+ if (!trimmedLine.startsWith("data: ")) continue;
1184
+ const dataStr = trimmedLine.slice(6);
1180
1185
  if (dataStr === "[DONE]") break;
1181
1186
  try {
1182
1187
  const parsed = JSON.parse(dataStr);
@@ -1518,6 +1523,74 @@ function deleteProvider(id) {
1518
1523
  store.set("providers", filtered);
1519
1524
  return true;
1520
1525
  }
1526
+ function exportProviders() {
1527
+ const providers = getProviders();
1528
+ const safeProviders = providers.map(({ apiKey, ...rest }) => ({
1529
+ ...rest,
1530
+ apiKey: maskApiKey(apiKey)
1531
+ }));
1532
+ return JSON.stringify(safeProviders, null, 2);
1533
+ }
1534
+ function exportProvidersWithKeys() {
1535
+ return JSON.stringify(getProviders(), null, 2);
1536
+ }
1537
+ function importProviders(json) {
1538
+ const errors = [];
1539
+ let imported = 0;
1540
+ try {
1541
+ const parsed = JSON.parse(json);
1542
+ const ImportSchema = union([
1543
+ array(ProviderConfigSchema),
1544
+ object({
1545
+ providers: array(ProviderConfigSchema)
1546
+ })
1547
+ ]);
1548
+ const parseResult = ImportSchema.safeParse(parsed);
1549
+ if (!parseResult.success) {
1550
+ return {
1551
+ imported: 0,
1552
+ errors: [`Invalid format: ${parseResult.error.message}`]
1553
+ };
1554
+ }
1555
+ const providerArray = Array.isArray(parseResult.data) ? parseResult.data : parseResult.data.providers;
1556
+ for (let i = 0; i < providerArray.length; i++) {
1557
+ const item = providerArray[i];
1558
+ const result = ProviderConfigSchema.safeParse(item);
1559
+ if (!result.success) {
1560
+ errors.push(`Provider ${i + 1}: ${result.error.message}`);
1561
+ continue;
1562
+ }
1563
+ const provider = result.data;
1564
+ const existing = getProviders().find(
1565
+ (p) => p.name === provider.name && p.anthropicBaseUrl === provider.anthropicBaseUrl
1566
+ );
1567
+ if (existing) {
1568
+ errors.push(`Provider "${provider.name}" already exists, skipping`);
1569
+ continue;
1570
+ }
1571
+ const newProvider = {
1572
+ ...provider,
1573
+ id: randomUUID(),
1574
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1575
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1576
+ };
1577
+ const providers = getProviders();
1578
+ providers.push(newProvider);
1579
+ store.set("providers", providers);
1580
+ imported++;
1581
+ }
1582
+ } catch (err) {
1583
+ return {
1584
+ imported: 0,
1585
+ errors: [`Failed to parse JSON: ${err instanceof Error ? err.message : String(err)}`]
1586
+ };
1587
+ }
1588
+ return { imported, errors };
1589
+ }
1590
+ function maskApiKey(apiKey) {
1591
+ if (apiKey.length <= 8) return "••••••••";
1592
+ return apiKey.slice(0, 4) + "••••••••" + apiKey.slice(-4);
1593
+ }
1521
1594
  function getModelUsageName(model, providerName) {
1522
1595
  if (providerName !== void 0 && providerName !== "" && providerName.toLowerCase().includes("minimax")) {
1523
1596
  return model.replace(/ /g, "-");
@@ -1694,6 +1767,9 @@ function parseRequestPath(req, url) {
1694
1767
  };
1695
1768
  }
1696
1769
  function buildUpstreamUrl$1(upstreamBase, normalizedPath) {
1770
+ if (upstreamBase.endsWith("/v1") && normalizedPath.startsWith("/v1/")) {
1771
+ return upstreamBase + normalizedPath.slice(3);
1772
+ }
1697
1773
  return upstreamBase + normalizedPath;
1698
1774
  }
1699
1775
  function selectUpstreamBase$1(isChatCompletions, matchedProviderConfig) {
@@ -1875,7 +1951,7 @@ function findProviderByModelFromConfig(requestBody) {
1875
1951
  if (model === null) return null;
1876
1952
  return findProviderByModel(model);
1877
1953
  }
1878
- const Route$c = createFileRoute("/proxy/$")({
1954
+ const Route$e = createFileRoute("/proxy/$")({
1879
1955
  server: {
1880
1956
  handlers: {
1881
1957
  GET: ({ request }) => handleProxy(request),
@@ -1887,7 +1963,7 @@ const Route$c = createFileRoute("/proxy/$")({
1887
1963
  }
1888
1964
  }
1889
1965
  });
1890
- const Route$b = createFileRoute("/api/sessions")({
1966
+ const Route$d = createFileRoute("/api/sessions")({
1891
1967
  server: {
1892
1968
  handlers: {
1893
1969
  GET: () => Response.json(getSessions())
@@ -1902,7 +1978,7 @@ const ProviderInputSchema = object({
1902
1978
  model: string().min(1, "Model is required"),
1903
1979
  authHeader: _enum(["bearer", "x-api-key"]).optional().default("bearer")
1904
1980
  });
1905
- const Route$a = createFileRoute("/api/providers")({
1981
+ const Route$c = createFileRoute("/api/providers")({
1906
1982
  server: {
1907
1983
  handlers: {
1908
1984
  GET: () => {
@@ -1926,14 +2002,14 @@ const Route$a = createFileRoute("/api/providers")({
1926
2002
  }
1927
2003
  }
1928
2004
  });
1929
- const Route$9 = createFileRoute("/api/models")({
2005
+ const Route$b = createFileRoute("/api/models")({
1930
2006
  server: {
1931
2007
  handlers: {
1932
2008
  GET: () => Response.json(getModels())
1933
2009
  }
1934
2010
  }
1935
2011
  });
1936
- const Route$8 = createFileRoute("/api/logs")({
2012
+ const Route$a = createFileRoute("/api/logs")({
1937
2013
  server: {
1938
2014
  handlers: {
1939
2015
  GET: ({ request }) => {
@@ -1958,7 +2034,7 @@ const Route$8 = createFileRoute("/api/logs")({
1958
2034
  }
1959
2035
  }
1960
2036
  });
1961
- const Route$7 = createFileRoute("/api/health")({
2037
+ const Route$9 = createFileRoute("/api/health")({
1962
2038
  server: {
1963
2039
  handlers: {
1964
2040
  GET: () => {
@@ -1967,6 +2043,58 @@ const Route$7 = createFileRoute("/api/health")({
1967
2043
  }
1968
2044
  }
1969
2045
  });
2046
+ union([string(), object({ providers: unknown() })]);
2047
+ const Route$8 = createFileRoute("/api/providers/import")({
2048
+ server: {
2049
+ handlers: {
2050
+ POST: async ({ request }) => {
2051
+ try {
2052
+ const rawBody = await request.text();
2053
+ let jsonContent;
2054
+ try {
2055
+ const parsedBody = JSON.parse(rawBody);
2056
+ jsonContent = typeof parsedBody === "string" ? parsedBody : JSON.stringify(parsedBody);
2057
+ } catch {
2058
+ jsonContent = rawBody;
2059
+ }
2060
+ if (!jsonContent || jsonContent.trim() === "") {
2061
+ return Response.json({ error: "No JSON content provided" }, { status: 400 });
2062
+ }
2063
+ const result = importProviders(jsonContent);
2064
+ return Response.json({
2065
+ success: result.imported > 0,
2066
+ imported: result.imported,
2067
+ errors: result.errors,
2068
+ message: result.imported > 0 ? `Successfully imported ${result.imported} provider(s)` : result.errors.length > 0 ? `Import completed with ${result.errors.length} error(s)` : "No providers imported"
2069
+ });
2070
+ } catch (err) {
2071
+ return Response.json(
2072
+ { error: `Failed to import: ${err instanceof Error ? err.message : String(err)}` },
2073
+ { status: 400 }
2074
+ );
2075
+ }
2076
+ }
2077
+ }
2078
+ }
2079
+ });
2080
+ const Route$7 = createFileRoute("/api/providers/export")({
2081
+ server: {
2082
+ handlers: {
2083
+ GET: ({ request }) => {
2084
+ const url = new URL(request.url);
2085
+ const includeKeys = url.searchParams.get("includeKeys") === "true";
2086
+ const json = includeKeys ? exportProvidersWithKeys() : exportProviders();
2087
+ const filename = includeKeys ? `llm-inspector-providers-${Date.now()}.json` : `llm-inspector-providers-safe-${Date.now()}.json`;
2088
+ return new Response(json, {
2089
+ headers: {
2090
+ "Content-Type": "application/json",
2091
+ "Content-Disposition": `attachment; filename="${filename}"`
2092
+ }
2093
+ });
2094
+ }
2095
+ }
2096
+ }
2097
+ });
1970
2098
  const ProviderUpdateSchema = object({
1971
2099
  name: string().min(1, "Name is required").optional(),
1972
2100
  apiKey: string().min(1, "API key is required").optional(),
@@ -2104,30 +2232,76 @@ const ERROR_HINTS = {
2104
2232
  const MAX_ERROR_DETAILS_LENGTH = 200;
2105
2233
  function classifyError(error, responseStatus) {
2106
2234
  const errorStr = String(error);
2235
+ const errorLower = errorStr.toLowerCase();
2107
2236
  if (error instanceof Error && error.name === "AbortError") {
2108
- return { type: "timeout", details: "Request was aborted due to timeout" };
2237
+ return { type: "timeout", details: "Request was aborted (timeout)" };
2238
+ }
2239
+ if (errorLower.includes("timeout") || errorLower.includes("timed out") || errorLower.includes("etimedout") || errorLower.includes("esockettimedout") || errorLower.includes("socket hang up") || errorLower.includes("peer terminating")) {
2240
+ return { type: "timeout", details: extractErrorDetails(errorStr) };
2109
2241
  }
2110
- if (errorStr.includes("timeout") || errorStr.includes("timed out") || errorStr.includes("Timeout")) {
2111
- return { type: "timeout", details: truncateErrorDetails(errorStr) };
2242
+ if (errorLower.includes("econnrefused") || errorLower.includes("connection refused") || errorLower.includes("econrefused")) {
2243
+ return { type: "connection_refused", details: extractErrorDetails(errorStr) };
2112
2244
  }
2113
- if (errorStr.includes("ECONNREFUSED") || errorStr.includes("connection refused")) {
2114
- return { type: "connection_refused", details: truncateErrorDetails(errorStr) };
2245
+ if (errorLower.includes("enotfound") || errorLower.includes("getaddrinfo") || errorLower.includes("dns lookup") || errorLower.includes("name or service not known") || errorLower.includes("network is unreachable") || errorLower.includes("enetunreach") || errorLower.includes("fetch failed") || errorLower.includes("failed to fetch") || errorLower.includes("networkerror")) {
2246
+ return { type: "network_unreachable", details: extractErrorDetails(errorStr) };
2115
2247
  }
2116
- if (errorStr.includes("ENOTFOUND") || errorStr.includes("getaddrinfo") || errorStr.includes("DNS") || errorStr.includes("network") || errorStr.includes("fetch failed")) {
2117
- return { type: "network_unreachable", details: truncateErrorDetails(errorStr) };
2248
+ if (errorLower.includes("econnreset") || errorLower.includes("connection reset") || errorLower.includes("ECONNRESET")) {
2249
+ return { type: "connection_refused", details: "Connection was reset by server" };
2250
+ }
2251
+ if (errorLower.includes("tls") || errorLower.includes("ssl") || errorLower.includes("certificate") || errorLower.includes("self signed") || errorLower.includes("unable to verify") || errorLower.includes("sec_error") || errorLower.includes("securitypolicy")) {
2252
+ return { type: "network_unreachable", details: "SSL/TLS certificate error" };
2253
+ }
2254
+ if (errorLower.includes("proxy") || errorLower.includes("tunneling") || errorLower.includes("eproto")) {
2255
+ return { type: "network_unreachable", details: extractErrorDetails(errorStr) };
2256
+ }
2257
+ if (errorLower.includes("cors") || errorLower.includes("cross-origin") || errorLower.includes("access-control")) {
2258
+ return {
2259
+ type: "network_unreachable",
2260
+ details: "CORS error - check your server's CORS configuration"
2261
+ };
2262
+ }
2263
+ if (errorLower.includes("invalid url") || errorLower.includes("url invalid") || errorLower.includes("etype") || errorLower.includes("err_invalid_url")) {
2264
+ return { type: "network_unreachable", details: "Invalid URL - check your provider base URL" };
2265
+ }
2266
+ if (errorLower.includes("empty response") || errorLower.includes("aborted") || errorLower.includes("premature close") || errorLower.includes("incomplete read")) {
2267
+ return { type: "invalid_response", details: "Server closed connection prematurely" };
2268
+ }
2269
+ if (errorLower.includes("bad gateway") || errorLower.includes("gateway timeout")) {
2270
+ return { type: "server_error", details: extractErrorDetails(errorStr) };
2118
2271
  }
2119
2272
  if (responseStatus !== void 0) {
2120
2273
  if (responseStatus === 401 || responseStatus === 403) {
2121
- return { type: "auth_failed", details: `HTTP ${responseStatus}` };
2274
+ return { type: "auth_failed", details: `Authentication failed (HTTP ${responseStatus})` };
2275
+ }
2276
+ if (responseStatus === 407) {
2277
+ return { type: "auth_failed", details: "Proxy authentication required (HTTP 407)" };
2122
2278
  }
2123
2279
  if (responseStatus === 429) {
2124
- return { type: "rate_limited", details: `HTTP ${responseStatus}` };
2280
+ return { type: "rate_limited", details: "Rate limited - try again later (HTTP 429)" };
2125
2281
  }
2126
2282
  if (responseStatus >= 500 && responseStatus < 600) {
2127
- return { type: "server_error", details: `HTTP ${responseStatus}` };
2283
+ return { type: "server_error", details: `Server error (HTTP ${responseStatus})` };
2284
+ }
2285
+ if (responseStatus === 400) {
2286
+ return {
2287
+ type: "invalid_response",
2288
+ details: "Bad request (HTTP 400) - check your model name and parameters"
2289
+ };
2290
+ }
2291
+ if (responseStatus === 404) {
2292
+ return {
2293
+ type: "network_unreachable",
2294
+ details: "Endpoint not found (HTTP 404) - check your base URL"
2295
+ };
2128
2296
  }
2129
2297
  }
2130
- return { type: "unknown", details: truncateErrorDetails(errorStr) };
2298
+ return { type: "unknown", details: extractErrorDetails(errorStr) };
2299
+ }
2300
+ function extractErrorDetails(errorStr) {
2301
+ let details = errorStr.replace(/^TypeError:\s*/i, "");
2302
+ details = details.replace(/^Error:\s*/i, "");
2303
+ details = details.replace(/^fetch failed:\s*/i, "");
2304
+ return truncateErrorDetails(details);
2131
2305
  }
2132
2306
  function truncateErrorDetails(details) {
2133
2307
  if (details.length <= MAX_ERROR_DETAILS_LENGTH) {
@@ -2135,7 +2309,7 @@ function truncateErrorDetails(details) {
2135
2309
  }
2136
2310
  return details.slice(0, MAX_ERROR_DETAILS_LENGTH) + "...";
2137
2311
  }
2138
- function createErrorResult(error, latencyMs, streaming, responseStatus) {
2312
+ function createErrorResult(error, latencyMs, streaming, responseStatus, requestHeaders) {
2139
2313
  const { type, details } = classifyError(error, responseStatus);
2140
2314
  return {
2141
2315
  success: false,
@@ -2146,7 +2320,8 @@ function createErrorResult(error, latencyMs, streaming, responseStatus) {
2146
2320
  hint: ERROR_HINTS[type]
2147
2321
  },
2148
2322
  latencyMs,
2149
- streaming
2323
+ streaming,
2324
+ requestHeaders
2150
2325
  };
2151
2326
  }
2152
2327
  function getErrorMessage(type) {
@@ -2223,7 +2398,8 @@ async function testEndpoint(baseUrl, apiKey, path2, model, isOpenAI) {
2223
2398
  `HTTP ${response.status}: ${response.statusText}`,
2224
2399
  latencyMs,
2225
2400
  false,
2226
- response.status
2401
+ response.status,
2402
+ requestHeaders
2227
2403
  );
2228
2404
  }
2229
2405
  const responseText = await response.text();
@@ -2280,7 +2456,7 @@ async function testEndpoint(baseUrl, apiKey, path2, model, isOpenAI) {
2280
2456
  }
2281
2457
  } catch (err) {
2282
2458
  clearTimeout(timeoutId);
2283
- return createErrorResult(err, Date.now() - startTime, false);
2459
+ return createErrorResult(err, Date.now() - startTime, false, void 0, requestHeaders);
2284
2460
  }
2285
2461
  }
2286
2462
  async function testStreamingEndpoint(baseUrl, apiKey, path2, model, isOpenAI) {
@@ -2312,7 +2488,8 @@ async function testStreamingEndpoint(baseUrl, apiKey, path2, model, isOpenAI) {
2312
2488
  `HTTP ${response.status}: ${response.statusText}`,
2313
2489
  latencyMs,
2314
2490
  true,
2315
- response.status
2491
+ response.status,
2492
+ requestHeaders
2316
2493
  );
2317
2494
  }
2318
2495
  const chunks = [];
@@ -2330,14 +2507,37 @@ async function testStreamingEndpoint(baseUrl, apiKey, path2, model, isOpenAI) {
2330
2507
  };
2331
2508
  }
2332
2509
  const decoder = new TextDecoder();
2510
+ const MAX_CHUNKS_SIZE = 10 * 1024 * 1024;
2511
+ let totalSize = 0;
2333
2512
  try {
2334
2513
  while (true) {
2335
2514
  const { done, value } = await reader.read();
2336
2515
  if (done) break;
2337
- chunks.push(decoder.decode(value, { stream: true }));
2516
+ if (totalSize >= MAX_CHUNKS_SIZE) {
2517
+ return createErrorResult(
2518
+ "Response too large (exceeded 10MB limit)",
2519
+ Date.now() - startTime,
2520
+ true,
2521
+ void 0,
2522
+ requestHeaders
2523
+ );
2524
+ }
2525
+ const decoded = decoder.decode(value, { stream: true });
2526
+ totalSize += decoded.length;
2527
+ chunks.push(decoded);
2528
+ }
2529
+ const finalChunk = decoder.decode();
2530
+ if (finalChunk) {
2531
+ chunks.push(finalChunk);
2338
2532
  }
2339
2533
  } catch (readErr) {
2340
- return createErrorResult(`Stream read error: ${readErr}`, latencyMs, true);
2534
+ return createErrorResult(
2535
+ `Stream read error: ${readErr}`,
2536
+ latencyMs,
2537
+ true,
2538
+ void 0,
2539
+ requestHeaders
2540
+ );
2341
2541
  }
2342
2542
  const fullResponse = chunks.join("");
2343
2543
  const mockLog = {
@@ -2421,7 +2621,7 @@ async function testStreamingEndpoint(baseUrl, apiKey, path2, model, isOpenAI) {
2421
2621
  }
2422
2622
  } catch (err) {
2423
2623
  clearTimeout(timeoutId);
2424
- return createErrorResult(err, Date.now() - startTime, true);
2624
+ return createErrorResult(err, Date.now() - startTime, true, void 0, requestHeaders);
2425
2625
  }
2426
2626
  }
2427
2627
  function createTestLogEntry(providerName, path2, body, upstreamUrl, result, isTest) {
@@ -2851,40 +3051,50 @@ const Route = createFileRoute("/api/logs/$id/chunks")({
2851
3051
  }
2852
3052
  }
2853
3053
  });
2854
- const IndexRoute = Route$d.update({
3054
+ const IndexRoute = Route$f.update({
2855
3055
  id: "/",
2856
3056
  path: "/",
2857
- getParentRoute: () => Route$e
3057
+ getParentRoute: () => Route$g
2858
3058
  });
2859
- const ProxySplatRoute = Route$c.update({
3059
+ const ProxySplatRoute = Route$e.update({
2860
3060
  id: "/proxy/$",
2861
3061
  path: "/proxy/$",
2862
- getParentRoute: () => Route$e
3062
+ getParentRoute: () => Route$g
2863
3063
  });
2864
- const ApiSessionsRoute = Route$b.update({
3064
+ const ApiSessionsRoute = Route$d.update({
2865
3065
  id: "/api/sessions",
2866
3066
  path: "/api/sessions",
2867
- getParentRoute: () => Route$e
3067
+ getParentRoute: () => Route$g
2868
3068
  });
2869
- const ApiProvidersRoute = Route$a.update({
3069
+ const ApiProvidersRoute = Route$c.update({
2870
3070
  id: "/api/providers",
2871
3071
  path: "/api/providers",
2872
- getParentRoute: () => Route$e
3072
+ getParentRoute: () => Route$g
2873
3073
  });
2874
- const ApiModelsRoute = Route$9.update({
3074
+ const ApiModelsRoute = Route$b.update({
2875
3075
  id: "/api/models",
2876
3076
  path: "/api/models",
2877
- getParentRoute: () => Route$e
3077
+ getParentRoute: () => Route$g
2878
3078
  });
2879
- const ApiLogsRoute = Route$8.update({
3079
+ const ApiLogsRoute = Route$a.update({
2880
3080
  id: "/api/logs",
2881
3081
  path: "/api/logs",
2882
- getParentRoute: () => Route$e
3082
+ getParentRoute: () => Route$g
2883
3083
  });
2884
- const ApiHealthRoute = Route$7.update({
3084
+ const ApiHealthRoute = Route$9.update({
2885
3085
  id: "/api/health",
2886
3086
  path: "/api/health",
2887
- getParentRoute: () => Route$e
3087
+ getParentRoute: () => Route$g
3088
+ });
3089
+ const ApiProvidersImportRoute = Route$8.update({
3090
+ id: "/import",
3091
+ path: "/import",
3092
+ getParentRoute: () => ApiProvidersRoute
3093
+ });
3094
+ const ApiProvidersExportRoute = Route$7.update({
3095
+ id: "/export",
3096
+ path: "/export",
3097
+ getParentRoute: () => ApiProvidersRoute
2888
3098
  });
2889
3099
  const ApiProvidersProviderIdRoute = Route$6.update({
2890
3100
  id: "/$providerId",
@@ -2904,7 +3114,7 @@ const ApiLogsIdRoute = Route$4.update({
2904
3114
  const ApiConfigPathsRoute = Route$3.update({
2905
3115
  id: "/api/config/paths",
2906
3116
  path: "/api/config/paths",
2907
- getParentRoute: () => Route$e
3117
+ getParentRoute: () => Route$g
2908
3118
  });
2909
3119
  const ApiProvidersProviderIdTestRoute = Route$2.update({
2910
3120
  id: "/test",
@@ -2940,7 +3150,9 @@ const ApiProvidersProviderIdRouteWithChildren = ApiProvidersProviderIdRoute._add
2940
3150
  ApiProvidersProviderIdRouteChildren
2941
3151
  );
2942
3152
  const ApiProvidersRouteChildren = {
2943
- ApiProvidersProviderIdRoute: ApiProvidersProviderIdRouteWithChildren
3153
+ ApiProvidersProviderIdRoute: ApiProvidersProviderIdRouteWithChildren,
3154
+ ApiProvidersExportRoute,
3155
+ ApiProvidersImportRoute
2944
3156
  };
2945
3157
  const ApiProvidersRouteWithChildren = ApiProvidersRoute._addFileChildren(
2946
3158
  ApiProvidersRouteChildren
@@ -2955,7 +3167,7 @@ const rootRouteChildren = {
2955
3167
  ProxySplatRoute,
2956
3168
  ApiConfigPathsRoute
2957
3169
  };
2958
- const routeTree = Route$e._addFileChildren(rootRouteChildren)._addFileTypes();
3170
+ const routeTree = Route$g._addFileChildren(rootRouteChildren)._addFileTypes();
2959
3171
  function getRouter() {
2960
3172
  const router2 = createRouter({
2961
3173
  routeTree,
@@ -0,0 +1,4 @@
1
+ const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/__root.tsx", "children": ["/", "/api/health", "/api/logs", "/api/models", "/api/providers", "/api/sessions", "/proxy/$", "/api/config/paths"], "preloads": ["/assets/main-Beo3LJDa.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-DH3FOgcK.js"] }, "/api/health": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/health.ts" }, "/api/logs": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.ts", "children": ["/api/logs/$id", "/api/logs/stream"] }, "/api/models": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/models.ts" }, "/api/providers": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.ts", "children": ["/api/providers/$providerId", "/api/providers/export", "/api/providers/import"] }, "/api/sessions": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/sessions.ts" }, "/proxy/$": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/proxy/$.ts" }, "/api/config/paths": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/config.paths.ts" }, "/api/logs/$id": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.ts", "children": ["/api/logs/$id/chunks", "/api/logs/$id/replay"] }, "/api/logs/stream": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.stream.ts" }, "/api/providers/$providerId": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.ts", "children": ["/api/providers/$providerId/test"] }, "/api/providers/export": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.export.ts" }, "/api/providers/import": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.import.ts" }, "/api/logs/$id/chunks": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.chunks.ts" }, "/api/logs/$id/replay": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.replay.ts" }, "/api/providers/$providerId/test": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.test.ts" } }, "clientEntry": "/assets/main-Beo3LJDa.js" });
2
+ export {
3
+ tsrStartManifest
4
+ };
@@ -97,54 +97,54 @@ const headers = ((m) => function headersRouteRule(event) {
97
97
  }
98
98
  });
99
99
  const assets = {
100
- "/assets/index-B3RwBPLW.css": {
101
- "type": "text/css; charset=utf-8",
102
- "etag": '"10c74-aXacU4DRFVsUwcC5jHnjoPRSlTA"',
103
- "mtime": "2026-06-03T12:12:14.334Z",
104
- "size": 68724,
105
- "path": "../public/assets/index-B3RwBPLW.css"
106
- },
107
100
  "/assets/alibaba-TTwafVwX.svg": {
108
101
  "type": "image/svg+xml",
109
102
  "etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
110
- "mtime": "2026-06-03T12:12:14.334Z",
103
+ "mtime": "2026-06-04T01:19:08.282Z",
111
104
  "size": 5915,
112
105
  "path": "../public/assets/alibaba-TTwafVwX.svg"
113
106
  },
114
107
  "/assets/minimax-BPMzvuL-.jpeg": {
115
108
  "type": "image/jpeg",
116
109
  "etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
117
- "mtime": "2026-06-03T12:12:14.334Z",
110
+ "mtime": "2026-06-04T01:19:08.282Z",
118
111
  "size": 6918,
119
112
  "path": "../public/assets/minimax-BPMzvuL-.jpeg"
120
113
  },
121
- "/assets/main-Bxc5pKCu.js": {
114
+ "/assets/main-Beo3LJDa.js": {
122
115
  "type": "text/javascript; charset=utf-8",
123
- "etag": '"4db57-Hyx+4GtcEuVuTT+HldUhPQTOcL8"',
124
- "mtime": "2026-06-03T12:12:14.334Z",
125
- "size": 318295,
126
- "path": "../public/assets/main-Bxc5pKCu.js"
116
+ "etag": '"50591-/XG/Oh/RlDy7LRgwa0Uqfltq6fM"',
117
+ "mtime": "2026-06-04T01:19:08.282Z",
118
+ "size": 329105,
119
+ "path": "../public/assets/main-Beo3LJDa.js"
120
+ },
121
+ "/assets/zhipuai-BPNAnxo-.svg": {
122
+ "type": "image/svg+xml",
123
+ "etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
124
+ "mtime": "2026-06-04T01:19:08.282Z",
125
+ "size": 11256,
126
+ "path": "../public/assets/zhipuai-BPNAnxo-.svg"
127
127
  },
128
128
  "/assets/qwen-CONDcHqt.png": {
129
129
  "type": "image/png",
130
130
  "etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
131
- "mtime": "2026-06-03T12:12:14.334Z",
131
+ "mtime": "2026-06-04T01:19:08.282Z",
132
132
  "size": 357059,
133
133
  "path": "../public/assets/qwen-CONDcHqt.png"
134
134
  },
135
- "/assets/zhipuai-BPNAnxo-.svg": {
136
- "type": "image/svg+xml",
137
- "etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
138
- "mtime": "2026-06-03T12:12:14.334Z",
139
- "size": 11256,
140
- "path": "../public/assets/zhipuai-BPNAnxo-.svg"
135
+ "/assets/index-BLVa7n9b.css": {
136
+ "type": "text/css; charset=utf-8",
137
+ "etag": '"10ce0-rnZGppItQl8rOmyih342MZyZcI0"',
138
+ "mtime": "2026-06-04T01:19:08.282Z",
139
+ "size": 68832,
140
+ "path": "../public/assets/index-BLVa7n9b.css"
141
141
  },
142
- "/assets/index-C8o6bEv6.js": {
142
+ "/assets/index-DH3FOgcK.js": {
143
143
  "type": "text/javascript; charset=utf-8",
144
- "etag": '"83962-iIMvsvRc3BmqDsudqDxFOFuC3xg"',
145
- "mtime": "2026-06-03T12:12:14.334Z",
146
- "size": 538978,
147
- "path": "../public/assets/index-C8o6bEv6.js"
144
+ "etag": '"8421c-gZ3NyuThEQLW3CYFr8ZxAzJ1zq0"',
145
+ "mtime": "2026-06-04T01:19:08.283Z",
146
+ "size": 541212,
147
+ "path": "../public/assets/index-DH3FOgcK.js"
148
148
  }
149
149
  };
150
150
  function readAsset(id) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonyclaw/llm-inspector",
3
- "version": "1.7.8",
3
+ "version": "1.8.0",
4
4
  "type": "module",
5
5
  "description": "LLM API proxy inspector — captures and displays requests/responses from AI coding tools in a web UI",
6
6
  "license": "MIT",
@@ -61,6 +61,7 @@
61
61
  "react": "^19",
62
62
  "react-dom": "^19",
63
63
  "react-markdown": "^10.1.0",
64
+ "swr": "^2.4.1",
64
65
  "tailwind-merge": "^3.4.0",
65
66
  "tw-animate-css": "^1.4.0",
66
67
  "zod": "^4.3.6"