@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.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/index-BLVa7n9b.css +1 -0
- package/.output/public/assets/index-DH3FOgcK.js +97 -0
- package/.output/public/assets/main-Beo3LJDa.js +17 -0
- package/.output/server/_libs/dequal.mjs +27 -0
- package/.output/server/_libs/lucide-react.mjs +98 -91
- package/.output/server/_libs/swr.mjs +938 -0
- package/.output/server/_libs/use-sync-external-store.mjs +64 -1
- package/.output/server/_libs/zod.mjs +1 -0
- package/.output/server/_ssr/{index-hNquJMfH.mjs → index-HkueJ4Un.mjs} +218 -71
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-MmnX-LYh.mjs → router-DTswxb7l.mjs} +264 -52
- package/.output/server/_tanstack-start-manifest_v-DhUuivt-.mjs +4 -0
- package/.output/server/index.mjs +26 -26
- package/package.json +2 -1
- package/src/components/ProxyViewer.tsx +2 -0
- package/src/components/providers/ProviderCard.tsx +38 -33
- package/src/components/providers/ProviderLogo.tsx +6 -1
- package/src/components/providers/ProvidersPanel.tsx +144 -43
- package/src/components/providers/SettingsDialog.tsx +5 -3
- package/src/components/proxy-viewer/ConversationGroup.tsx +3 -3
- package/src/components/proxy-viewer/LogEntry.tsx +6 -3
- package/src/components/proxy-viewer/LogEntryHeader.tsx +3 -2
- package/src/lib/useProviders.ts +30 -0
- package/src/proxy/formats/anthropic/stream.ts +3 -2
- package/src/proxy/formats/openai/stream.ts +3 -2
- package/src/proxy/handler.ts +5 -0
- package/src/proxy/providers.ts +98 -0
- package/src/routes/__root.tsx +4 -1
- package/src/routes/api/providers.export.ts +26 -0
- package/src/routes/api/providers.import.ts +47 -0
- package/.output/public/assets/index-B3RwBPLW.css +0 -1
- package/.output/public/assets/index-C8o6bEv6.js +0 -97
- package/.output/public/assets/main-Bxc5pKCu.js +0 -17
- 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,
|
|
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-
|
|
45
|
-
const Route$
|
|
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-
|
|
69
|
-
const Route$
|
|
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
|
-
|
|
945
|
+
const trimmedLine = line.trim();
|
|
946
|
+
if (!trimmedLine.startsWith("data: ")) continue;
|
|
943
947
|
try {
|
|
944
|
-
const json = JSON.parse(
|
|
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
|
-
|
|
1179
|
-
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
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 (
|
|
2111
|
-
return { type: "
|
|
2242
|
+
if (errorLower.includes("econnrefused") || errorLower.includes("connection refused") || errorLower.includes("econrefused")) {
|
|
2243
|
+
return { type: "connection_refused", details: extractErrorDetails(errorStr) };
|
|
2112
2244
|
}
|
|
2113
|
-
if (
|
|
2114
|
-
return { type: "
|
|
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 (
|
|
2117
|
-
return { type: "
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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$
|
|
3054
|
+
const IndexRoute = Route$f.update({
|
|
2855
3055
|
id: "/",
|
|
2856
3056
|
path: "/",
|
|
2857
|
-
getParentRoute: () => Route$
|
|
3057
|
+
getParentRoute: () => Route$g
|
|
2858
3058
|
});
|
|
2859
|
-
const ProxySplatRoute = Route$
|
|
3059
|
+
const ProxySplatRoute = Route$e.update({
|
|
2860
3060
|
id: "/proxy/$",
|
|
2861
3061
|
path: "/proxy/$",
|
|
2862
|
-
getParentRoute: () => Route$
|
|
3062
|
+
getParentRoute: () => Route$g
|
|
2863
3063
|
});
|
|
2864
|
-
const ApiSessionsRoute = Route$
|
|
3064
|
+
const ApiSessionsRoute = Route$d.update({
|
|
2865
3065
|
id: "/api/sessions",
|
|
2866
3066
|
path: "/api/sessions",
|
|
2867
|
-
getParentRoute: () => Route$
|
|
3067
|
+
getParentRoute: () => Route$g
|
|
2868
3068
|
});
|
|
2869
|
-
const ApiProvidersRoute = Route$
|
|
3069
|
+
const ApiProvidersRoute = Route$c.update({
|
|
2870
3070
|
id: "/api/providers",
|
|
2871
3071
|
path: "/api/providers",
|
|
2872
|
-
getParentRoute: () => Route$
|
|
3072
|
+
getParentRoute: () => Route$g
|
|
2873
3073
|
});
|
|
2874
|
-
const ApiModelsRoute = Route$
|
|
3074
|
+
const ApiModelsRoute = Route$b.update({
|
|
2875
3075
|
id: "/api/models",
|
|
2876
3076
|
path: "/api/models",
|
|
2877
|
-
getParentRoute: () => Route$
|
|
3077
|
+
getParentRoute: () => Route$g
|
|
2878
3078
|
});
|
|
2879
|
-
const ApiLogsRoute = Route$
|
|
3079
|
+
const ApiLogsRoute = Route$a.update({
|
|
2880
3080
|
id: "/api/logs",
|
|
2881
3081
|
path: "/api/logs",
|
|
2882
|
-
getParentRoute: () => Route$
|
|
3082
|
+
getParentRoute: () => Route$g
|
|
2883
3083
|
});
|
|
2884
|
-
const ApiHealthRoute = Route$
|
|
3084
|
+
const ApiHealthRoute = Route$9.update({
|
|
2885
3085
|
id: "/api/health",
|
|
2886
3086
|
path: "/api/health",
|
|
2887
|
-
getParentRoute: () => Route$
|
|
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$
|
|
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$
|
|
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
|
+
};
|
package/.output/server/index.mjs
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
114
|
+
"/assets/main-Beo3LJDa.js": {
|
|
122
115
|
"type": "text/javascript; charset=utf-8",
|
|
123
|
-
"etag": '"
|
|
124
|
-
"mtime": "2026-06-
|
|
125
|
-
"size":
|
|
126
|
-
"path": "../public/assets/main-
|
|
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-
|
|
131
|
+
"mtime": "2026-06-04T01:19:08.282Z",
|
|
132
132
|
"size": 357059,
|
|
133
133
|
"path": "../public/assets/qwen-CONDcHqt.png"
|
|
134
134
|
},
|
|
135
|
-
"/assets/
|
|
136
|
-
"type": "
|
|
137
|
-
"etag": '"
|
|
138
|
-
"mtime": "2026-06-
|
|
139
|
-
"size":
|
|
140
|
-
"path": "../public/assets/
|
|
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-
|
|
142
|
+
"/assets/index-DH3FOgcK.js": {
|
|
143
143
|
"type": "text/javascript; charset=utf-8",
|
|
144
|
-
"etag": '"
|
|
145
|
-
"mtime": "2026-06-
|
|
146
|
-
"size":
|
|
147
|
-
"path": "../public/assets/index-
|
|
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.
|
|
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"
|