@tonyclaw/llm-inspector 1.6.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 +17 -0
- package/.output/public/assets/alibaba-TTwafVwX.svg +1 -0
- package/.output/public/assets/index-B3RwBPLW.css +1 -0
- package/.output/public/assets/index-s4lwsWvq.js +97 -0
- package/.output/public/assets/main-Cp8AM0Pa.js +17 -0
- package/.output/public/assets/minimax-BPMzvuL-.jpeg +0 -0
- package/.output/public/assets/qwen-CONDcHqt.png +0 -0
- package/.output/public/assets/zhipuai-BPNAnxo-.svg +219 -0
- package/.output/server/_chunks/ssr-renderer.mjs +17 -0
- package/.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs +210 -0
- package/.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs +11 -0
- package/.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs +69 -0
- package/.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-use-escape-keydown+[...].mjs +17 -0
- package/.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs +6 -0
- package/.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs +34 -0
- package/.output/server/_libs/ajv-formats.mjs +330 -0
- package/.output/server/_libs/ajv.mjs +11444 -0
- package/.output/server/_libs/aria-hidden.mjs +122 -0
- package/.output/server/_libs/atomically.mjs +152 -0
- package/.output/server/_libs/bail.mjs +8 -0
- package/.output/server/_libs/character-entities.mjs +2130 -0
- package/.output/server/_libs/class-variance-authority.mjs +44 -0
- package/.output/server/_libs/clsx.mjs +16 -0
- package/.output/server/_libs/comma-separated-tokens.mjs +10 -0
- package/.output/server/_libs/conf.mjs +635 -0
- package/.output/server/_libs/cookie-es.mjs +58 -0
- package/.output/server/_libs/core-util-is.mjs +75 -0
- package/.output/server/_libs/croner.mjs +1 -0
- package/.output/server/_libs/crossws.mjs +1 -0
- package/.output/server/_libs/debounce-fn.mjs +69 -0
- package/.output/server/_libs/decode-named-character-reference+[...].mjs +8 -0
- package/.output/server/_libs/detect-node-es.mjs +1 -0
- package/.output/server/_libs/devlop.mjs +8 -0
- package/.output/server/_libs/dot-prop.mjs +265 -0
- package/.output/server/_libs/env-paths.mjs +57 -0
- package/.output/server/_libs/estree-util-is-identifier-name.mjs +11 -0
- package/.output/server/_libs/extend.mjs +97 -0
- package/.output/server/_libs/fast-deep-equal.mjs +38 -0
- package/.output/server/_libs/fast-uri.mjs +812 -0
- package/.output/server/_libs/floating-ui__core.mjs +725 -0
- package/.output/server/_libs/floating-ui__dom.mjs +622 -0
- package/.output/server/_libs/floating-ui__react-dom.mjs +292 -0
- package/.output/server/_libs/floating-ui__utils.mjs +320 -0
- package/.output/server/_libs/get-nonce.mjs +9 -0
- package/.output/server/_libs/h3-v2.mjs +276 -0
- package/.output/server/_libs/h3.mjs +400 -0
- package/.output/server/_libs/hast-util-to-jsx-runtime.mjs +388 -0
- package/.output/server/_libs/hast-util-whitespace.mjs +10 -0
- package/.output/server/_libs/hookable.mjs +1 -0
- package/.output/server/_libs/html-url-attributes.mjs +26 -0
- package/.output/server/_libs/immediate.mjs +74 -0
- package/.output/server/_libs/inherits.mjs +50 -0
- package/.output/server/_libs/inline-style-parser.mjs +142 -0
- package/.output/server/_libs/is-plain-obj.mjs +10 -0
- package/.output/server/_libs/isarray.mjs +14 -0
- package/.output/server/_libs/isbot.mjs +20 -0
- package/.output/server/_libs/json-schema-traverse.mjs +180 -0
- package/.output/server/_libs/jszip.mjs +3049 -0
- package/.output/server/_libs/lie.mjs +273 -0
- package/.output/server/_libs/lucide-react.mjs +368 -0
- package/.output/server/_libs/mdast-util-from-markdown.mjs +717 -0
- package/.output/server/_libs/mdast-util-to-hast.mjs +710 -0
- package/.output/server/_libs/mdast-util-to-string.mjs +38 -0
- package/.output/server/_libs/micromark-core-commonmark.mjs +2259 -0
- package/.output/server/_libs/micromark-factory-destination.mjs +94 -0
- package/.output/server/_libs/micromark-factory-label.mjs +63 -0
- package/.output/server/_libs/micromark-factory-space.mjs +24 -0
- package/.output/server/_libs/micromark-factory-title.mjs +65 -0
- package/.output/server/_libs/micromark-factory-whitespace.mjs +22 -0
- package/.output/server/_libs/micromark-util-character.mjs +44 -0
- package/.output/server/_libs/micromark-util-chunked.mjs +36 -0
- package/.output/server/_libs/micromark-util-classify-character+[...].mjs +12 -0
- package/.output/server/_libs/micromark-util-combine-extensions+[...].mjs +41 -0
- package/.output/server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +19 -0
- package/.output/server/_libs/micromark-util-decode-string.mjs +21 -0
- package/.output/server/_libs/micromark-util-encode.mjs +1 -0
- package/.output/server/_libs/micromark-util-html-tag-name.mjs +69 -0
- package/.output/server/_libs/micromark-util-normalize-identifier+[...].mjs +6 -0
- package/.output/server/_libs/micromark-util-resolve-all.mjs +15 -0
- package/.output/server/_libs/micromark-util-sanitize-uri.mjs +41 -0
- package/.output/server/_libs/micromark-util-subtokenize.mjs +346 -0
- package/.output/server/_libs/micromark.mjs +906 -0
- package/.output/server/_libs/mimic-function.mjs +47 -0
- package/.output/server/_libs/ohash.mjs +1 -0
- package/.output/server/_libs/pako.mjs +4223 -0
- package/.output/server/_libs/process-nextick-args.mjs +48 -0
- package/.output/server/_libs/property-information.mjs +1209 -0
- package/.output/server/_libs/radix-ui.mjs +1 -0
- package/.output/server/_libs/radix-ui__number.mjs +6 -0
- package/.output/server/_libs/radix-ui__primitive.mjs +11 -0
- package/.output/server/_libs/radix-ui__react-accordion.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-alert-dialog.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-arrow.mjs +23 -0
- package/.output/server/_libs/radix-ui__react-aspect-ratio.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-avatar.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-checkbox.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-collapsible.mjs +144 -0
- package/.output/server/_libs/radix-ui__react-collection.mjs +69 -0
- package/.output/server/_libs/radix-ui__react-compose-refs.mjs +39 -0
- package/.output/server/_libs/radix-ui__react-context-menu.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-context.mjs +78 -0
- package/.output/server/_libs/radix-ui__react-dialog.mjs +325 -0
- package/.output/server/_libs/radix-ui__react-direction.mjs +9 -0
- package/.output/server/_libs/radix-ui__react-dropdown-menu.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-focus-guards.mjs +29 -0
- package/.output/server/_libs/radix-ui__react-focus-scope.mjs +206 -0
- package/.output/server/_libs/radix-ui__react-form.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-hover-card.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-id.mjs +14 -0
- package/.output/server/_libs/radix-ui__react-label.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-menu.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-menubar.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-popover.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-popper.mjs +286 -0
- package/.output/server/_libs/radix-ui__react-portal.mjs +16 -0
- package/.output/server/_libs/radix-ui__react-presence.mjs +128 -0
- package/.output/server/_libs/radix-ui__react-primitive.mjs +42 -0
- package/.output/server/_libs/radix-ui__react-progress.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-radio-group.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-roving-focus.mjs +224 -0
- package/.output/server/_libs/radix-ui__react-scroll-area.mjs +721 -0
- package/.output/server/_libs/radix-ui__react-select.mjs +1163 -0
- package/.output/server/_libs/radix-ui__react-separator.mjs +28 -0
- package/.output/server/_libs/radix-ui__react-slider.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-slot.mjs +99 -0
- package/.output/server/_libs/radix-ui__react-switch.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-tabs.mjs +189 -0
- package/.output/server/_libs/radix-ui__react-toast.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-toggle-group.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-toggle.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-toolbar.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-tooltip.mjs +495 -0
- package/.output/server/_libs/radix-ui__react-use-previous.mjs +14 -0
- package/.output/server/_libs/radix-ui__react-use-size.mjs +39 -0
- package/.output/server/_libs/react-dom.mjs +9935 -0
- package/.output/server/_libs/react-markdown.mjs +147 -0
- package/.output/server/_libs/react-remove-scroll-bar.mjs +82 -0
- package/.output/server/_libs/react-remove-scroll.mjs +328 -0
- package/.output/server/_libs/react-style-singleton.mjs +69 -0
- package/.output/server/_libs/react.mjs +515 -0
- package/.output/server/_libs/readable-stream.mjs +1518 -0
- package/.output/server/_libs/remark-parse.mjs +19 -0
- package/.output/server/_libs/remark-rehype.mjs +21 -0
- package/.output/server/_libs/rou3.mjs +8 -0
- package/.output/server/_libs/safe-buffer.mjs +64 -0
- package/.output/server/_libs/semver.mjs +1984 -0
- package/.output/server/_libs/seroval-plugins.mjs +58 -0
- package/.output/server/_libs/seroval.mjs +1765 -0
- package/.output/server/_libs/setimmediate.mjs +1 -0
- package/.output/server/_libs/space-separated-tokens.mjs +6 -0
- package/.output/server/_libs/srvx.mjs +334 -0
- package/.output/server/_libs/stubborn-fs.mjs +91 -0
- package/.output/server/_libs/stubborn-utils.mjs +66 -0
- package/.output/server/_libs/style-to-js.mjs +72 -0
- package/.output/server/_libs/style-to-object.mjs +38 -0
- package/.output/server/_libs/tailwind-merge.mjs +3010 -0
- package/.output/server/_libs/tanstack__history.mjs +217 -0
- package/.output/server/_libs/tanstack__react-router.mjs +1480 -0
- package/.output/server/_libs/tanstack__react-store.mjs +1 -0
- package/.output/server/_libs/tanstack__react-virtual.mjs +44 -0
- package/.output/server/_libs/tanstack__router-core.mjs +4827 -0
- package/.output/server/_libs/tanstack__store.mjs +1 -0
- package/.output/server/_libs/tanstack__virtual-core.mjs +1225 -0
- package/.output/server/_libs/tiny-invariant.mjs +12 -0
- package/.output/server/_libs/tiny-warning.mjs +5 -0
- package/.output/server/_libs/trim-lines.mjs +41 -0
- package/.output/server/_libs/trough.mjs +85 -0
- package/.output/server/_libs/tslib.mjs +576 -0
- package/.output/server/_libs/ufo.mjs +54 -0
- package/.output/server/_libs/uint8array-extras.mjs +69 -0
- package/.output/server/_libs/unctx.mjs +1 -0
- package/.output/server/_libs/ungap__structured-clone.mjs +212 -0
- package/.output/server/_libs/unified.mjs +661 -0
- package/.output/server/_libs/unist-util-is.mjs +100 -0
- package/.output/server/_libs/unist-util-position.mjs +27 -0
- package/.output/server/_libs/unist-util-stringify-position.mjs +27 -0
- package/.output/server/_libs/unist-util-visit-parents.mjs +82 -0
- package/.output/server/_libs/unist-util-visit.mjs +24 -0
- package/.output/server/_libs/unstorage.mjs +1 -0
- package/.output/server/_libs/use-callback-ref.mjs +66 -0
- package/.output/server/_libs/use-sidecar.mjs +106 -0
- package/.output/server/_libs/use-sync-external-store.mjs +1 -0
- package/.output/server/_libs/util-deprecate.mjs +12 -0
- package/.output/server/_libs/vfile-message.mjs +138 -0
- package/.output/server/_libs/vfile.mjs +467 -0
- package/.output/server/_libs/when-exit.mjs +53 -0
- package/.output/server/_libs/zod.mjs +4460 -0
- package/.output/server/_ssr/index-ByCLZu7J.mjs +3061 -0
- package/.output/server/_ssr/index.mjs +1176 -0
- package/.output/server/_ssr/router-Bq_mxeNz.mjs +2872 -0
- package/.output/server/_ssr/start-HYkvq4Ni.mjs +4 -0
- package/.output/server/_tanstack-start-manifest_v-C4E0e9my.mjs +4 -0
- package/.output/server/index.mjs +393 -0
- package/README.md +196 -0
- package/package.json +91 -0
- package/src/assets/logos/alibaba.svg +1 -0
- package/src/assets/logos/anthropic.svg +1 -0
- package/src/assets/logos/deepseek.svg +1 -0
- package/src/assets/logos/minimax.jpeg +0 -0
- package/src/assets/logos/openai.svg +1 -0
- package/src/assets/logos/qwen.png +0 -0
- package/src/assets/logos/zhipuai.svg +219 -0
- package/src/cli.ts +68 -0
- package/src/components/ProxyViewer.tsx +325 -0
- package/src/components/ProxyViewerContainer.tsx +211 -0
- package/src/components/providers/ProviderCard.tsx +186 -0
- package/src/components/providers/ProviderForm.tsx +259 -0
- package/src/components/providers/ProviderLogo.tsx +111 -0
- package/src/components/providers/ProvidersPanel.tsx +259 -0
- package/src/components/providers/SettingsDialog.tsx +39 -0
- package/src/components/proxy-viewer/ConversationGroup.tsx +68 -0
- package/src/components/proxy-viewer/ConversationHeader.tsx +141 -0
- package/src/components/proxy-viewer/LogEntry.tsx +225 -0
- package/src/components/proxy-viewer/LogEntryHeader.tsx +250 -0
- package/src/components/proxy-viewer/ReplayDialog.tsx +208 -0
- package/src/components/proxy-viewer/ResponseView.tsx +161 -0
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +171 -0
- package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +139 -0
- package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +64 -0
- package/src/components/proxy-viewer/formats/index.tsx +24 -0
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +80 -0
- package/src/components/proxy-viewer/index.ts +8 -0
- package/src/components/ui/badge.tsx +47 -0
- package/src/components/ui/button.tsx +47 -0
- package/src/components/ui/collapsible.tsx +21 -0
- package/src/components/ui/dialog.tsx +129 -0
- package/src/components/ui/json-viewer.tsx +464 -0
- package/src/components/ui/scroll-area.tsx +54 -0
- package/src/components/ui/select.tsx +178 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/tabs.tsx +88 -0
- package/src/components/ui/tooltip.tsx +51 -0
- package/src/index.css +11 -0
- package/src/lib/export-logs.ts +51 -0
- package/src/lib/utils.ts +22 -0
- package/src/proxy/chunkStorage.ts +118 -0
- package/src/proxy/constants.ts +36 -0
- package/src/proxy/formats/anthropic/anthropicProvider.ts +75 -0
- package/src/proxy/formats/anthropic/handler.ts +74 -0
- package/src/proxy/formats/anthropic/index.ts +14 -0
- package/src/proxy/formats/anthropic/register.ts +4 -0
- package/src/proxy/formats/anthropic/schemas.ts +217 -0
- package/src/proxy/formats/anthropic/stream.ts +167 -0
- package/src/proxy/formats/handler.ts +46 -0
- package/src/proxy/formats/index.ts +12 -0
- package/src/proxy/formats/jsonSchema.ts +24 -0
- package/src/proxy/formats/openai/alibabaProvider.ts +38 -0
- package/src/proxy/formats/openai/handler.ts +70 -0
- package/src/proxy/formats/openai/index.ts +25 -0
- package/src/proxy/formats/openai/provider.ts +50 -0
- package/src/proxy/formats/openai/register.ts +4 -0
- package/src/proxy/formats/openai/schemas.ts +150 -0
- package/src/proxy/formats/openai/stream.ts +153 -0
- package/src/proxy/formats/protocol.ts +50 -0
- package/src/proxy/formats/providerRegistry.ts +51 -0
- package/src/proxy/formats/providers/index.ts +3 -0
- package/src/proxy/formats/registry.ts +61 -0
- package/src/proxy/handler.ts +389 -0
- package/src/proxy/logIndex.ts +187 -0
- package/src/proxy/logger.ts +99 -0
- package/src/proxy/providers.ts +234 -0
- package/src/proxy/schemas.ts +160 -0
- package/src/proxy/socketTracker.ts +158 -0
- package/src/proxy/store.ts +386 -0
- package/src/router.tsx +16 -0
- package/src/routes/__root.tsx +38 -0
- package/src/routes/api/config.paths.ts +14 -0
- package/src/routes/api/health.ts +11 -0
- package/src/routes/api/logs.$id.chunks.ts +36 -0
- package/src/routes/api/logs.$id.replay.ts +262 -0
- package/src/routes/api/logs.$id.ts +22 -0
- package/src/routes/api/logs.stream.ts +64 -0
- package/src/routes/api/logs.ts +30 -0
- package/src/routes/api/models.ts +10 -0
- package/src/routes/api/providers.$providerId.ts +45 -0
- package/src/routes/api/providers.ts +37 -0
- package/src/routes/api/sessions.ts +10 -0
- package/src/routes/index.tsx +6 -0
- package/src/routes/proxy/$.ts +15 -0
- package/styles/globals.css +121 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
3
|
+
import { createReadStream } from "node:fs";
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { appendLogEntry, resolveLogDir } from "./logger";
|
|
7
|
+
import {
|
|
8
|
+
addToIndex,
|
|
9
|
+
findInIndex,
|
|
10
|
+
getNextLogId,
|
|
11
|
+
getCurrentLogFile,
|
|
12
|
+
saveIndex,
|
|
13
|
+
loadIndex,
|
|
14
|
+
} from "./logIndex";
|
|
15
|
+
import { writeChunks } from "./chunkStorage";
|
|
16
|
+
import type { CapturedLog } from "./schemas";
|
|
17
|
+
import { CapturedLogSchema } from "./schemas";
|
|
18
|
+
|
|
19
|
+
export type { CapturedLog };
|
|
20
|
+
|
|
21
|
+
const LooseRequestSchema = z.object({
|
|
22
|
+
model: z.string().optional(),
|
|
23
|
+
metadata: z.object({ user_id: z.string().optional() }).passthrough().optional(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const MAX_MEMORY_CACHE = 100;
|
|
27
|
+
|
|
28
|
+
// Memory cache: id -> CapturedLog (recent logs only)
|
|
29
|
+
const memoryCache: Map<number, CapturedLog> = new Map();
|
|
30
|
+
|
|
31
|
+
// O(1) LRU ordering using Map's insertion order
|
|
32
|
+
// head -> ... -> tail (head = oldest, tail = newest)
|
|
33
|
+
let headId: number | null = null;
|
|
34
|
+
let tailId: number | null = null;
|
|
35
|
+
const prevMap: Map<number, number | null> = new Map(); // id -> previous id (null if head)
|
|
36
|
+
const nextMap: Map<number, number | null> = new Map(); // id -> next id (null if tail)
|
|
37
|
+
|
|
38
|
+
function evictOldestIfNeeded(): void {
|
|
39
|
+
while (memoryCache.size >= MAX_MEMORY_CACHE && headId !== null) {
|
|
40
|
+
const oldest = headId;
|
|
41
|
+
const next = nextMap.get(oldest) ?? null;
|
|
42
|
+
|
|
43
|
+
memoryCache.delete(oldest);
|
|
44
|
+
prevMap.delete(oldest);
|
|
45
|
+
nextMap.delete(oldest);
|
|
46
|
+
|
|
47
|
+
if (next !== null) {
|
|
48
|
+
prevMap.set(next, null);
|
|
49
|
+
headId = next;
|
|
50
|
+
} else {
|
|
51
|
+
headId = null;
|
|
52
|
+
tailId = null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function addToCache(log: CapturedLog): void {
|
|
58
|
+
evictOldestIfNeeded();
|
|
59
|
+
|
|
60
|
+
// Add at tail (newest position)
|
|
61
|
+
if (tailId !== null) {
|
|
62
|
+
nextMap.set(tailId, log.id);
|
|
63
|
+
prevMap.set(log.id, tailId);
|
|
64
|
+
} else {
|
|
65
|
+
// First element
|
|
66
|
+
headId = log.id;
|
|
67
|
+
prevMap.set(log.id, null);
|
|
68
|
+
}
|
|
69
|
+
nextMap.set(log.id, null);
|
|
70
|
+
tailId = log.id;
|
|
71
|
+
|
|
72
|
+
memoryCache.set(log.id, log);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function removeFromCache(id: number): void {
|
|
76
|
+
const prev = prevMap.get(id) ?? null;
|
|
77
|
+
const next = nextMap.get(id) ?? null;
|
|
78
|
+
|
|
79
|
+
if (prev !== null) {
|
|
80
|
+
nextMap.set(prev, next);
|
|
81
|
+
} else {
|
|
82
|
+
headId = next;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (next !== null) {
|
|
86
|
+
prevMap.set(next, prev);
|
|
87
|
+
} else {
|
|
88
|
+
tailId = prev;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
prevMap.delete(id);
|
|
92
|
+
nextMap.delete(id);
|
|
93
|
+
memoryCache.delete(id);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Add a test log entry directly to the in-memory store (for dashboard display).
|
|
98
|
+
* This is used by the provider test endpoint alongside appendLogEntry (file logging).
|
|
99
|
+
*/
|
|
100
|
+
export async function addTestLogEntry(entry: Omit<CapturedLog, "id">): Promise<CapturedLog> {
|
|
101
|
+
const id = await getNextLogId();
|
|
102
|
+
// Update the index with the new maxId so subsequent calls get unique IDs
|
|
103
|
+
const index = await loadIndex();
|
|
104
|
+
if (id > index.maxId) {
|
|
105
|
+
index.maxId = id;
|
|
106
|
+
await saveIndex(index);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Persist streaming chunks to disk if present
|
|
110
|
+
let streamingChunksPath: string | null = null;
|
|
111
|
+
if (entry.streamingChunks !== undefined && entry.streamingChunks.chunks.length > 0) {
|
|
112
|
+
streamingChunksPath = writeChunks(
|
|
113
|
+
id,
|
|
114
|
+
entry.streamingChunks.chunks,
|
|
115
|
+
entry.streamingChunks.truncated,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const log: CapturedLog = {
|
|
120
|
+
id,
|
|
121
|
+
...entry,
|
|
122
|
+
streamingChunksPath,
|
|
123
|
+
};
|
|
124
|
+
addToCache(log);
|
|
125
|
+
emitLogUpdate(log);
|
|
126
|
+
return log;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
type ClientInfo = {
|
|
130
|
+
port: number | null;
|
|
131
|
+
pid: number | null;
|
|
132
|
+
cwd: string | null;
|
|
133
|
+
projectFolder: string | null;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export async function createLog(
|
|
137
|
+
method: string,
|
|
138
|
+
path: string,
|
|
139
|
+
requestBody: string | null,
|
|
140
|
+
headers: Headers,
|
|
141
|
+
clientInfo?: ClientInfo,
|
|
142
|
+
rawHeaders?: Record<string, string>,
|
|
143
|
+
upstreamHeaders?: Record<string, string>,
|
|
144
|
+
apiFormat: "anthropic" | "openai" | "unknown" = "unknown",
|
|
145
|
+
): Promise<CapturedLog> {
|
|
146
|
+
let model: string | null = null;
|
|
147
|
+
let sessionId: string | null = null;
|
|
148
|
+
|
|
149
|
+
if (requestBody !== null) {
|
|
150
|
+
try {
|
|
151
|
+
const json: unknown = JSON.parse(requestBody);
|
|
152
|
+
const loose = LooseRequestSchema.safeParse(json);
|
|
153
|
+
if (loose.success) {
|
|
154
|
+
model = loose.data.model ?? null;
|
|
155
|
+
sessionId = loose.data.metadata?.user_id ?? headers.get("x-session-affinity") ?? null;
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
// request body not valid JSON, skip parsing
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const userAgent = headers.get("user-agent");
|
|
163
|
+
const origin = headers.get("origin");
|
|
164
|
+
|
|
165
|
+
const id = await getNextLogId();
|
|
166
|
+
const log: CapturedLog = {
|
|
167
|
+
id,
|
|
168
|
+
timestamp: new Date().toISOString(),
|
|
169
|
+
method,
|
|
170
|
+
path,
|
|
171
|
+
model,
|
|
172
|
+
sessionId,
|
|
173
|
+
rawRequestBody: requestBody,
|
|
174
|
+
responseStatus: null,
|
|
175
|
+
responseText: null,
|
|
176
|
+
inputTokens: null,
|
|
177
|
+
outputTokens: null,
|
|
178
|
+
cacheCreationInputTokens: null,
|
|
179
|
+
cacheReadInputTokens: null,
|
|
180
|
+
elapsedMs: null,
|
|
181
|
+
streaming: false,
|
|
182
|
+
userAgent,
|
|
183
|
+
origin,
|
|
184
|
+
rawHeaders: rawHeaders,
|
|
185
|
+
headers: upstreamHeaders,
|
|
186
|
+
apiFormat,
|
|
187
|
+
isTest: false,
|
|
188
|
+
providerName: null,
|
|
189
|
+
clientPort: clientInfo?.port ?? null,
|
|
190
|
+
clientPid: clientInfo?.pid ?? null,
|
|
191
|
+
clientCwd: clientInfo?.cwd ?? null,
|
|
192
|
+
clientProjectFolder: clientInfo?.projectFolder ?? null,
|
|
193
|
+
streamingChunksPath: null,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// Write to disk and update index
|
|
197
|
+
const logFile = getCurrentLogFile();
|
|
198
|
+
appendLogEntry(log);
|
|
199
|
+
await addToIndex(id, logFile, -1, -1); // line numbers not tracked precisely
|
|
200
|
+
|
|
201
|
+
// Add to memory cache
|
|
202
|
+
addToCache(log);
|
|
203
|
+
emitLogUpdate(log);
|
|
204
|
+
|
|
205
|
+
return log;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get a log by ID, checking memory cache first then disk.
|
|
210
|
+
* Each request has two entries in the log file: initial (incomplete) and complete (with response).
|
|
211
|
+
* We scan for the complete entry (responseStatus !== null) first, falling back to the last match.
|
|
212
|
+
*/
|
|
213
|
+
export async function getLogById(id: number): Promise<CapturedLog | null> {
|
|
214
|
+
// Check memory cache first
|
|
215
|
+
const cached = memoryCache.get(id);
|
|
216
|
+
if (cached !== undefined) {
|
|
217
|
+
return cached;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Look up in index and load from disk
|
|
221
|
+
const entry = await findInIndex(id);
|
|
222
|
+
if (entry === null) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Load from disk using index entry
|
|
227
|
+
try {
|
|
228
|
+
const filePath = join(resolveLogDir(), entry.file);
|
|
229
|
+
if (!existsSync(filePath)) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const content = readFileSync(filePath, "utf-8");
|
|
234
|
+
const lines = content.split("\n");
|
|
235
|
+
|
|
236
|
+
let lastMatch: CapturedLog | null = null;
|
|
237
|
+
for (const line of lines) {
|
|
238
|
+
if (line.trim() === "") continue;
|
|
239
|
+
try {
|
|
240
|
+
const parsed: unknown = JSON.parse(line);
|
|
241
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
242
|
+
const desc = Object.getOwnPropertyDescriptor(parsed, "id");
|
|
243
|
+
if (desc !== undefined && typeof desc.value === "number" && desc.value === id) {
|
|
244
|
+
const result = CapturedLogSchema.safeParse(parsed);
|
|
245
|
+
if (result.success) {
|
|
246
|
+
lastMatch = result.data;
|
|
247
|
+
// Return immediately if we found a completed entry (has responseStatus)
|
|
248
|
+
if (result.data.responseStatus !== null) {
|
|
249
|
+
return result.data;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
// Skip malformed lines
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Fallback: return the last matching entry (incomplete is better than nothing)
|
|
259
|
+
if (lastMatch !== null) return lastMatch;
|
|
260
|
+
} catch (err) {
|
|
261
|
+
// eslint-disable-next-line no-console
|
|
262
|
+
console.error("[store] Failed to read log from disk:", err);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function getFilteredLogs(sessionId?: string, model?: string): CapturedLog[] {
|
|
269
|
+
// Get all logs from memory cache sorted by ID
|
|
270
|
+
const cachedLogs = Array.from(memoryCache.values()).sort((a, b) => a.id - b.id);
|
|
271
|
+
|
|
272
|
+
return cachedLogs.filter((l) => {
|
|
273
|
+
if (sessionId !== undefined && l.sessionId !== sessionId) return false;
|
|
274
|
+
if (model !== undefined && l.model !== model) return false;
|
|
275
|
+
return true;
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function getSessions(): string[] {
|
|
280
|
+
const set = new Set<string>();
|
|
281
|
+
for (const l of memoryCache.values()) {
|
|
282
|
+
if (l.sessionId !== null && l.sessionId !== "") set.add(l.sessionId);
|
|
283
|
+
}
|
|
284
|
+
return [...set];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function getModels(): string[] {
|
|
288
|
+
const set = new Set<string>();
|
|
289
|
+
for (const l of memoryCache.values()) {
|
|
290
|
+
if (l.model !== null && l.model !== "") set.add(l.model);
|
|
291
|
+
}
|
|
292
|
+
return [...set];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Load recent completed log entries from today's log file into the memory cache.
|
|
297
|
+
* Called on server startup so the frontend shows existing logs.
|
|
298
|
+
*/
|
|
299
|
+
export async function loadLogsIntoMemory(): Promise<void> {
|
|
300
|
+
const logDir = resolveLogDir();
|
|
301
|
+
if (!existsSync(logDir)) return;
|
|
302
|
+
|
|
303
|
+
const today = getCurrentLogFile();
|
|
304
|
+
const filePath = join(logDir, today);
|
|
305
|
+
if (!existsSync(filePath)) return;
|
|
306
|
+
|
|
307
|
+
// Keep track of IDs seen so we only load the completed entry for each
|
|
308
|
+
const seenComplete = new Set<number>();
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const fileStream = createInterface({
|
|
312
|
+
input: createReadStream(filePath),
|
|
313
|
+
crlfDelay: Infinity,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const lines: string[] = [];
|
|
317
|
+
for await (const line of fileStream) {
|
|
318
|
+
lines.push(line);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Process in reverse so we find completed entries first
|
|
322
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
323
|
+
const line = lines[i] ?? "";
|
|
324
|
+
if (line.trim() === "") continue;
|
|
325
|
+
try {
|
|
326
|
+
const parsed: unknown = JSON.parse(line);
|
|
327
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
328
|
+
const desc = Object.getOwnPropertyDescriptor(parsed, "id");
|
|
329
|
+
if (desc !== undefined && typeof desc.value === "number") {
|
|
330
|
+
const entryId = desc.value;
|
|
331
|
+
// Only load completed entries (those with responseStatus)
|
|
332
|
+
const statusDesc = Object.getOwnPropertyDescriptor(parsed, "responseStatus");
|
|
333
|
+
if (
|
|
334
|
+
statusDesc !== undefined &&
|
|
335
|
+
statusDesc.value !== null &&
|
|
336
|
+
!seenComplete.has(entryId)
|
|
337
|
+
) {
|
|
338
|
+
const result = CapturedLogSchema.safeParse(parsed);
|
|
339
|
+
if (result.success) {
|
|
340
|
+
addToCache(result.data);
|
|
341
|
+
seenComplete.add(entryId);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
} catch {
|
|
347
|
+
// Skip malformed lines
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
} catch (err) {
|
|
351
|
+
// eslint-disable-next-line no-console
|
|
352
|
+
console.error("[store] Failed to load logs into memory:", err);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function clearAllLogs(): { cleared: number } {
|
|
357
|
+
const count = memoryCache.size;
|
|
358
|
+
memoryCache.clear();
|
|
359
|
+
headId = null;
|
|
360
|
+
tailId = null;
|
|
361
|
+
prevMap.clear();
|
|
362
|
+
nextMap.clear();
|
|
363
|
+
return { cleared: count };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// SSE event system for real-time log updates
|
|
367
|
+
type LogUpdateHandler = (log: CapturedLog) => void;
|
|
368
|
+
const sseHandlers: Set<LogUpdateHandler> = new Set();
|
|
369
|
+
|
|
370
|
+
export function onLogUpdate(handler: LogUpdateHandler): () => void {
|
|
371
|
+
sseHandlers.add(handler);
|
|
372
|
+
return () => {
|
|
373
|
+
sseHandlers.delete(handler);
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export function emitLogUpdate(log: CapturedLog): void {
|
|
378
|
+
for (const handler of sseHandlers) {
|
|
379
|
+
try {
|
|
380
|
+
handler(log);
|
|
381
|
+
} catch {
|
|
382
|
+
// Remove handler that threw
|
|
383
|
+
sseHandlers.delete(handler);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
package/src/router.tsx
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createRouter } from "@tanstack/react-router";
|
|
2
|
+
import { routeTree } from "./routeTree.gen";
|
|
3
|
+
|
|
4
|
+
export function getRouter() {
|
|
5
|
+
const router = createRouter({
|
|
6
|
+
routeTree,
|
|
7
|
+
scrollRestoration: false,
|
|
8
|
+
});
|
|
9
|
+
return router;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare module "@tanstack/react-router" {
|
|
13
|
+
interface Register {
|
|
14
|
+
router: ReturnType<typeof getRouter>;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
import { Outlet, createRootRoute, HeadContent, Scripts } from "@tanstack/react-router";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import appCss from "../index.css?url";
|
|
5
|
+
|
|
6
|
+
export const Route = createRootRoute({
|
|
7
|
+
head: () => ({
|
|
8
|
+
meta: [
|
|
9
|
+
{ charSet: "utf-8" },
|
|
10
|
+
{ name: "viewport", content: "width=device-width, initial-scale=1" },
|
|
11
|
+
{ title: "llm-inspector" },
|
|
12
|
+
],
|
|
13
|
+
links: [{ rel: "stylesheet", href: appCss }],
|
|
14
|
+
}),
|
|
15
|
+
component: RootComponent,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function RootComponent() {
|
|
19
|
+
return (
|
|
20
|
+
<RootDocument>
|
|
21
|
+
<Outlet />
|
|
22
|
+
</RootDocument>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
|
|
27
|
+
return (
|
|
28
|
+
<html lang="en" className="dark">
|
|
29
|
+
<head>
|
|
30
|
+
<HeadContent />
|
|
31
|
+
</head>
|
|
32
|
+
<body>
|
|
33
|
+
{children}
|
|
34
|
+
<Scripts />
|
|
35
|
+
</body>
|
|
36
|
+
</html>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { store } from "../../proxy/providers";
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute("/api/config/paths")({
|
|
5
|
+
server: {
|
|
6
|
+
handlers: {
|
|
7
|
+
GET: () => {
|
|
8
|
+
return Response.json({
|
|
9
|
+
providerConfig: store.path,
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { getLogById } from "../../proxy/store";
|
|
3
|
+
import { readChunks } from "../../proxy/chunkStorage";
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute("/api/logs/$id/chunks")({
|
|
6
|
+
server: {
|
|
7
|
+
handlers: {
|
|
8
|
+
GET: async ({ params }: { params: { id: string } }) => {
|
|
9
|
+
const id = Number(params.id);
|
|
10
|
+
if (isNaN(id)) {
|
|
11
|
+
return Response.json({ error: "Invalid log ID" }, { status: 400 });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const log = await getLogById(id);
|
|
15
|
+
if (log === null) {
|
|
16
|
+
return Response.json({ error: "Log not found" }, { status: 404 });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Try disk file first (persistent chunks from real proxy streaming)
|
|
20
|
+
if (log.streamingChunksPath !== null && log.streamingChunksPath !== undefined) {
|
|
21
|
+
const chunksData = readChunks(log.streamingChunksPath);
|
|
22
|
+
if (chunksData !== null) {
|
|
23
|
+
return Response.json(chunksData);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Fall back to in-memory streamingChunks (e.g., from provider test)
|
|
28
|
+
if (log.streamingChunks !== undefined && log.streamingChunks.chunks.length > 0) {
|
|
29
|
+
return Response.json(log.streamingChunks);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return Response.json({ error: "Chunks not found" }, { status: 404 });
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
});
|