@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,2872 @@
|
|
|
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
|
+
import { j as jsxRuntimeExports } from "../_libs/react.mjs";
|
|
3
|
+
import { mkdirSync, writeFileSync, renameSync, copyFileSync, unlinkSync, existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import path, { join, dirname, isAbsolute } from "node:path";
|
|
5
|
+
import { readFile, mkdir, writeFile, appendFile } from "node:fs/promises";
|
|
6
|
+
import { C as Conf } from "../_libs/conf.mjs";
|
|
7
|
+
import { randomUUID } from "crypto";
|
|
8
|
+
import { exec } from "node:child_process";
|
|
9
|
+
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 "../_libs/tiny-warning.mjs";
|
|
12
|
+
import "../_libs/tanstack__router-core.mjs";
|
|
13
|
+
import "../_libs/cookie-es.mjs";
|
|
14
|
+
import "../_libs/tanstack__history.mjs";
|
|
15
|
+
import "../_libs/tiny-invariant.mjs";
|
|
16
|
+
import "../_libs/seroval.mjs";
|
|
17
|
+
import "../_libs/seroval-plugins.mjs";
|
|
18
|
+
import "node:stream/web";
|
|
19
|
+
import "node:stream";
|
|
20
|
+
import "../_libs/react-dom.mjs";
|
|
21
|
+
import "../_libs/isbot.mjs";
|
|
22
|
+
import "node:process";
|
|
23
|
+
import "node:crypto";
|
|
24
|
+
import "node:assert";
|
|
25
|
+
import "../_libs/dot-prop.mjs";
|
|
26
|
+
import "../_libs/env-paths.mjs";
|
|
27
|
+
import "node:os";
|
|
28
|
+
import "../_libs/atomically.mjs";
|
|
29
|
+
import "../_libs/stubborn-fs.mjs";
|
|
30
|
+
import "../_libs/stubborn-utils.mjs";
|
|
31
|
+
import "../_libs/when-exit.mjs";
|
|
32
|
+
import "../_libs/ajv.mjs";
|
|
33
|
+
import "../_libs/fast-deep-equal.mjs";
|
|
34
|
+
import "../_libs/json-schema-traverse.mjs";
|
|
35
|
+
import "../_libs/fast-uri.mjs";
|
|
36
|
+
import "../_libs/ajv-formats.mjs";
|
|
37
|
+
import "../_libs/debounce-fn.mjs";
|
|
38
|
+
import "../_libs/mimic-function.mjs";
|
|
39
|
+
import "../_libs/semver.mjs";
|
|
40
|
+
import "../_libs/uint8array-extras.mjs";
|
|
41
|
+
const appCss = "/assets/index-B3RwBPLW.css";
|
|
42
|
+
const Route$e = createRootRoute({
|
|
43
|
+
head: () => ({
|
|
44
|
+
meta: [
|
|
45
|
+
{ charSet: "utf-8" },
|
|
46
|
+
{ name: "viewport", content: "width=device-width, initial-scale=1" },
|
|
47
|
+
{ title: "llm-inspector" }
|
|
48
|
+
],
|
|
49
|
+
links: [{ rel: "stylesheet", href: appCss }]
|
|
50
|
+
}),
|
|
51
|
+
component: RootComponent
|
|
52
|
+
});
|
|
53
|
+
function RootComponent() {
|
|
54
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(RootDocument, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Outlet, {}) });
|
|
55
|
+
}
|
|
56
|
+
function RootDocument({ children }) {
|
|
57
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("html", { lang: "en", className: "dark", children: [
|
|
58
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("head", { children: /* @__PURE__ */ jsxRuntimeExports.jsx(HeadContent, {}) }),
|
|
59
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("body", { children: [
|
|
60
|
+
children,
|
|
61
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Scripts, {})
|
|
62
|
+
] })
|
|
63
|
+
] });
|
|
64
|
+
}
|
|
65
|
+
const $$splitComponentImporter = () => import("./index-ByCLZu7J.mjs");
|
|
66
|
+
const Route$d = createFileRoute("/")({
|
|
67
|
+
component: lazyRouteComponent($$splitComponentImporter, "component")
|
|
68
|
+
});
|
|
69
|
+
const LOG_DIR_ENV = process.env["LOG_DIR"];
|
|
70
|
+
Number(process.env["LOG_RETENTION_DAYS"] ?? "7");
|
|
71
|
+
function getUserDataDir() {
|
|
72
|
+
if (process.platform === "win32") {
|
|
73
|
+
return process.env["APPDATA"] ?? path.join(process.env["USERPROFILE"] ?? "C:\\", ".llm-inspector");
|
|
74
|
+
}
|
|
75
|
+
return process.env["HOME"] ?? path.join("/tmp");
|
|
76
|
+
}
|
|
77
|
+
let resolvedLogDir = null;
|
|
78
|
+
function resolveLogDir() {
|
|
79
|
+
if (resolvedLogDir !== null) return resolvedLogDir;
|
|
80
|
+
const base = getUserDataDir();
|
|
81
|
+
if (LOG_DIR_ENV !== void 0) {
|
|
82
|
+
resolvedLogDir = path.isAbsolute(LOG_DIR_ENV) ? LOG_DIR_ENV : path.join(base, LOG_DIR_ENV);
|
|
83
|
+
} else {
|
|
84
|
+
resolvedLogDir = path.join(base, ".llm-inspector", "logs");
|
|
85
|
+
}
|
|
86
|
+
return resolvedLogDir;
|
|
87
|
+
}
|
|
88
|
+
function getLogFilePath() {
|
|
89
|
+
const date = /* @__PURE__ */ new Date();
|
|
90
|
+
const yyyy = date.getUTCFullYear();
|
|
91
|
+
const mm = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
92
|
+
const dd = String(date.getUTCDate()).padStart(2, "0");
|
|
93
|
+
return path.join(resolveLogDir(), `${yyyy}-${mm}-${dd}.jsonl`);
|
|
94
|
+
}
|
|
95
|
+
let writeBuffer = [];
|
|
96
|
+
let writeScheduled = false;
|
|
97
|
+
async function flushWriteBuffer() {
|
|
98
|
+
if (writeBuffer.length === 0) return;
|
|
99
|
+
const toWrite = writeBuffer.join("");
|
|
100
|
+
writeBuffer = [];
|
|
101
|
+
try {
|
|
102
|
+
const filePath = getLogFilePath();
|
|
103
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
104
|
+
await appendFile(filePath, toWrite, "utf-8");
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.error("[logger] Failed to write log entries:", err);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function scheduleFlush() {
|
|
110
|
+
if (writeScheduled) return;
|
|
111
|
+
writeScheduled = true;
|
|
112
|
+
void Promise.resolve().then(() => {
|
|
113
|
+
writeScheduled = false;
|
|
114
|
+
void flushWriteBuffer();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function appendLogEntry(entry) {
|
|
118
|
+
const line = JSON.stringify(entry) + "\n";
|
|
119
|
+
writeBuffer.push(line);
|
|
120
|
+
scheduleFlush();
|
|
121
|
+
}
|
|
122
|
+
const INDEX_VERSION = 1;
|
|
123
|
+
const INDEX_FILE = "logs.idx";
|
|
124
|
+
function getIndexPath() {
|
|
125
|
+
return join(resolveLogDir(), INDEX_FILE);
|
|
126
|
+
}
|
|
127
|
+
let cachedIndex = null;
|
|
128
|
+
function createEmptyIndex() {
|
|
129
|
+
return {
|
|
130
|
+
version: INDEX_VERSION,
|
|
131
|
+
entries: {},
|
|
132
|
+
maxId: 0
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function isLogIndex(obj) {
|
|
136
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) return false;
|
|
137
|
+
const versionDesc = Object.getOwnPropertyDescriptor(obj, "version");
|
|
138
|
+
const entriesDesc = Object.getOwnPropertyDescriptor(obj, "entries");
|
|
139
|
+
const maxIdDesc = Object.getOwnPropertyDescriptor(obj, "maxId");
|
|
140
|
+
return versionDesc !== void 0 && typeof versionDesc.value === "number" && entriesDesc !== void 0 && typeof entriesDesc.value === "object" && entriesDesc.value !== null && maxIdDesc !== void 0 && typeof maxIdDesc.value === "number";
|
|
141
|
+
}
|
|
142
|
+
async function loadIndex() {
|
|
143
|
+
if (cachedIndex !== null) return cachedIndex;
|
|
144
|
+
const indexPath = getIndexPath();
|
|
145
|
+
if (!existsSync(indexPath)) {
|
|
146
|
+
cachedIndex = createEmptyIndex();
|
|
147
|
+
return cachedIndex;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const content = await readFile(indexPath, "utf-8");
|
|
151
|
+
const parsed = JSON.parse(content);
|
|
152
|
+
if (isLogIndex(parsed)) {
|
|
153
|
+
cachedIndex = parsed;
|
|
154
|
+
} else {
|
|
155
|
+
cachedIndex = createEmptyIndex();
|
|
156
|
+
}
|
|
157
|
+
return cachedIndex;
|
|
158
|
+
} catch (err) {
|
|
159
|
+
console.error("[logIndex] Failed to load index:", err);
|
|
160
|
+
cachedIndex = createEmptyIndex();
|
|
161
|
+
return cachedIndex;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async function saveIndex(index) {
|
|
165
|
+
const indexPath = getIndexPath();
|
|
166
|
+
const dir = dirname(indexPath);
|
|
167
|
+
try {
|
|
168
|
+
await mkdir(dir, { recursive: true });
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
await writeFile(indexPath, JSON.stringify(index), "utf-8");
|
|
173
|
+
} catch (err) {
|
|
174
|
+
console.error("[logIndex] Failed to save index:", err);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function addToIndex(id, file, lineStart, lineEnd) {
|
|
178
|
+
const index = await loadIndex();
|
|
179
|
+
index.entries[id] = { id, file, lineStart, lineEnd };
|
|
180
|
+
if (id > index.maxId) {
|
|
181
|
+
index.maxId = id;
|
|
182
|
+
}
|
|
183
|
+
await saveIndex(index);
|
|
184
|
+
}
|
|
185
|
+
async function findInIndex(id) {
|
|
186
|
+
const index = await loadIndex();
|
|
187
|
+
return index.entries[id] ?? null;
|
|
188
|
+
}
|
|
189
|
+
async function getNextLogId() {
|
|
190
|
+
const index = await loadIndex();
|
|
191
|
+
return index.maxId + 1;
|
|
192
|
+
}
|
|
193
|
+
function getCurrentLogFile() {
|
|
194
|
+
const now = /* @__PURE__ */ new Date();
|
|
195
|
+
const yyyy = now.getUTCFullYear();
|
|
196
|
+
const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
197
|
+
const dd = String(now.getUTCDate()).padStart(2, "0");
|
|
198
|
+
return `${yyyy}-${mm}-${dd}.jsonl`;
|
|
199
|
+
}
|
|
200
|
+
const JsonValueSchema = lazy(
|
|
201
|
+
() => union([
|
|
202
|
+
string(),
|
|
203
|
+
number(),
|
|
204
|
+
boolean(),
|
|
205
|
+
_null(),
|
|
206
|
+
array(JsonValueSchema),
|
|
207
|
+
record(string(), JsonValueSchema)
|
|
208
|
+
])
|
|
209
|
+
);
|
|
210
|
+
const CacheControl = object({
|
|
211
|
+
type: string(),
|
|
212
|
+
ttl: string().optional(),
|
|
213
|
+
scope: string().optional()
|
|
214
|
+
});
|
|
215
|
+
const TextContentBlock = object({
|
|
216
|
+
type: literal("text"),
|
|
217
|
+
text: string(),
|
|
218
|
+
cache_control: CacheControl.optional()
|
|
219
|
+
});
|
|
220
|
+
const ThinkingContentBlock = object({
|
|
221
|
+
type: literal("thinking"),
|
|
222
|
+
thinking: string(),
|
|
223
|
+
signature: string().optional()
|
|
224
|
+
});
|
|
225
|
+
const ImageSourceBlock = object({
|
|
226
|
+
type: literal("base64"),
|
|
227
|
+
media_type: string(),
|
|
228
|
+
data: string()
|
|
229
|
+
});
|
|
230
|
+
const ImageContentBlock = object({
|
|
231
|
+
type: literal("image"),
|
|
232
|
+
source: ImageSourceBlock
|
|
233
|
+
});
|
|
234
|
+
const ToolUseContentBlock = object({
|
|
235
|
+
type: literal("tool_use"),
|
|
236
|
+
id: string(),
|
|
237
|
+
name: string(),
|
|
238
|
+
input: record(string(), JsonValueSchema)
|
|
239
|
+
});
|
|
240
|
+
const ToolResultContentItem = discriminatedUnion("type", [TextContentBlock, ImageContentBlock]);
|
|
241
|
+
const ToolResultContentBlock = object({
|
|
242
|
+
type: literal("tool_result"),
|
|
243
|
+
tool_use_id: string().optional(),
|
|
244
|
+
content: union([string(), array(ToolResultContentItem)]),
|
|
245
|
+
is_error: boolean().optional()
|
|
246
|
+
});
|
|
247
|
+
const ContentBlock = discriminatedUnion("type", [
|
|
248
|
+
TextContentBlock,
|
|
249
|
+
ThinkingContentBlock,
|
|
250
|
+
ImageContentBlock,
|
|
251
|
+
ToolUseContentBlock,
|
|
252
|
+
ToolResultContentBlock
|
|
253
|
+
]);
|
|
254
|
+
const MessageContent = union([string(), array(ContentBlock)]);
|
|
255
|
+
const Message = object({
|
|
256
|
+
role: _enum(["user", "assistant"]),
|
|
257
|
+
content: MessageContent
|
|
258
|
+
});
|
|
259
|
+
const SystemBlock = object({
|
|
260
|
+
type: literal("text"),
|
|
261
|
+
text: string(),
|
|
262
|
+
cache_control: CacheControl.optional()
|
|
263
|
+
});
|
|
264
|
+
const InputSchema = object({
|
|
265
|
+
type: string(),
|
|
266
|
+
properties: record(string(), record(string(), JsonValueSchema)).optional(),
|
|
267
|
+
required: array(string()).optional(),
|
|
268
|
+
additionalProperties: boolean().optional(),
|
|
269
|
+
$schema: string().optional()
|
|
270
|
+
});
|
|
271
|
+
const ToolDefinition = object({
|
|
272
|
+
name: string(),
|
|
273
|
+
description: string().optional(),
|
|
274
|
+
input_schema: InputSchema.optional(),
|
|
275
|
+
cache_control: CacheControl.optional()
|
|
276
|
+
});
|
|
277
|
+
const ThinkingConfig = discriminatedUnion("type", [
|
|
278
|
+
object({ type: literal("enabled"), budget_tokens: number() }),
|
|
279
|
+
object({ type: literal("disabled") }),
|
|
280
|
+
object({ type: literal("adaptive") })
|
|
281
|
+
]);
|
|
282
|
+
const AnthropicRequestSchema = object({
|
|
283
|
+
model: string(),
|
|
284
|
+
messages: array(Message),
|
|
285
|
+
system: array(SystemBlock).optional(),
|
|
286
|
+
tools: array(ToolDefinition).optional(),
|
|
287
|
+
max_tokens: number().optional(),
|
|
288
|
+
temperature: number().optional(),
|
|
289
|
+
stream: boolean().optional(),
|
|
290
|
+
thinking: ThinkingConfig.optional(),
|
|
291
|
+
metadata: object({
|
|
292
|
+
user_id: string().optional()
|
|
293
|
+
}).optional()
|
|
294
|
+
});
|
|
295
|
+
const ResponseContentBlock = discriminatedUnion("type", [
|
|
296
|
+
TextContentBlock,
|
|
297
|
+
ThinkingContentBlock,
|
|
298
|
+
ToolUseContentBlock
|
|
299
|
+
]);
|
|
300
|
+
const ResponseUsageSchema = object({
|
|
301
|
+
input_tokens: number(),
|
|
302
|
+
output_tokens: number(),
|
|
303
|
+
cache_creation_input_tokens: number().optional(),
|
|
304
|
+
cache_read_input_tokens: number().optional()
|
|
305
|
+
});
|
|
306
|
+
const AnthropicResponseSchema$1 = object({
|
|
307
|
+
id: string(),
|
|
308
|
+
type: literal("message"),
|
|
309
|
+
model: string(),
|
|
310
|
+
role: literal("assistant"),
|
|
311
|
+
content: array(ResponseContentBlock),
|
|
312
|
+
stop_reason: string().nullable(),
|
|
313
|
+
stop_sequence: string().nullable(),
|
|
314
|
+
usage: ResponseUsageSchema
|
|
315
|
+
});
|
|
316
|
+
const SseMessageStartEvent = object({
|
|
317
|
+
type: literal("message_start"),
|
|
318
|
+
message: object({
|
|
319
|
+
id: string(),
|
|
320
|
+
type: literal("message"),
|
|
321
|
+
model: string(),
|
|
322
|
+
role: literal("assistant"),
|
|
323
|
+
content: array(ResponseContentBlock),
|
|
324
|
+
stop_reason: _null(),
|
|
325
|
+
stop_sequence: _null(),
|
|
326
|
+
usage: object({
|
|
327
|
+
input_tokens: number(),
|
|
328
|
+
cache_creation_input_tokens: number().optional(),
|
|
329
|
+
cache_read_input_tokens: number().optional()
|
|
330
|
+
}).passthrough()
|
|
331
|
+
})
|
|
332
|
+
});
|
|
333
|
+
const SseContentBlockStartEvent = object({
|
|
334
|
+
type: literal("content_block_start"),
|
|
335
|
+
index: number(),
|
|
336
|
+
content_block: ResponseContentBlock
|
|
337
|
+
});
|
|
338
|
+
const SseDeltaBlock = discriminatedUnion("type", [
|
|
339
|
+
object({ type: literal("text_delta"), text: string() }),
|
|
340
|
+
object({ type: literal("input_json_delta"), partial_json: string() }),
|
|
341
|
+
object({ type: literal("thinking_delta"), thinking: string() }),
|
|
342
|
+
object({ type: literal("signature_delta"), signature: string() })
|
|
343
|
+
]);
|
|
344
|
+
const SseContentBlockDeltaEvent = object({
|
|
345
|
+
type: literal("content_block_delta"),
|
|
346
|
+
index: number(),
|
|
347
|
+
delta: SseDeltaBlock
|
|
348
|
+
});
|
|
349
|
+
const SseContentBlockStopEvent = object({
|
|
350
|
+
type: literal("content_block_stop"),
|
|
351
|
+
index: number()
|
|
352
|
+
});
|
|
353
|
+
const SseMessageDeltaEvent = object({
|
|
354
|
+
type: literal("message_delta"),
|
|
355
|
+
delta: object({
|
|
356
|
+
stop_reason: string().nullable(),
|
|
357
|
+
stop_sequence: string().nullable().optional()
|
|
358
|
+
}),
|
|
359
|
+
usage: object({
|
|
360
|
+
output_tokens: number(),
|
|
361
|
+
input_tokens: number().optional(),
|
|
362
|
+
cache_creation_input_tokens: number().optional(),
|
|
363
|
+
cache_read_input_tokens: number().optional()
|
|
364
|
+
}).passthrough()
|
|
365
|
+
});
|
|
366
|
+
const SseMessageStopEvent = object({
|
|
367
|
+
type: literal("message_stop")
|
|
368
|
+
});
|
|
369
|
+
const SsePingEvent = object({
|
|
370
|
+
type: literal("ping")
|
|
371
|
+
});
|
|
372
|
+
const SseEventSchema = discriminatedUnion("type", [
|
|
373
|
+
SseMessageStartEvent,
|
|
374
|
+
SseContentBlockStartEvent,
|
|
375
|
+
SseContentBlockDeltaEvent,
|
|
376
|
+
SseContentBlockStopEvent,
|
|
377
|
+
SseMessageDeltaEvent,
|
|
378
|
+
SseMessageStopEvent,
|
|
379
|
+
SsePingEvent
|
|
380
|
+
]);
|
|
381
|
+
const InspectorRequestSchema = AnthropicRequestSchema;
|
|
382
|
+
const InspectorResponseSchema = AnthropicResponseSchema$1;
|
|
383
|
+
const OpenAIMessageContent = union([
|
|
384
|
+
string(),
|
|
385
|
+
array(
|
|
386
|
+
discriminatedUnion("type", [
|
|
387
|
+
object({ type: literal("text"), text: string() }),
|
|
388
|
+
object({
|
|
389
|
+
type: literal("image_url"),
|
|
390
|
+
image_url: object({ url: string(), detail: string().optional() })
|
|
391
|
+
})
|
|
392
|
+
])
|
|
393
|
+
)
|
|
394
|
+
]);
|
|
395
|
+
const OpenAIMessage = object({
|
|
396
|
+
role: _enum(["system", "user", "assistant", "tool"]),
|
|
397
|
+
content: OpenAIMessageContent,
|
|
398
|
+
name: string().optional(),
|
|
399
|
+
reasoning_content: string().optional()
|
|
400
|
+
});
|
|
401
|
+
const OpenAIFunctionCall = object({
|
|
402
|
+
name: string(),
|
|
403
|
+
arguments: string()
|
|
404
|
+
});
|
|
405
|
+
OpenAIMessage.extend({
|
|
406
|
+
content: union([string(), array(object({ type: literal("text"), text: string() }))]).optional(),
|
|
407
|
+
function_call: OpenAIFunctionCall.optional()
|
|
408
|
+
});
|
|
409
|
+
const OpenAIToolDefinition = object({
|
|
410
|
+
type: literal("function"),
|
|
411
|
+
function: object({
|
|
412
|
+
name: string(),
|
|
413
|
+
description: string().optional(),
|
|
414
|
+
parameters: record(string(), JsonValueSchema)
|
|
415
|
+
})
|
|
416
|
+
});
|
|
417
|
+
const OpenAIRequestSchema = object({
|
|
418
|
+
model: string(),
|
|
419
|
+
messages: array(OpenAIMessage),
|
|
420
|
+
temperature: number().optional(),
|
|
421
|
+
max_tokens: number().optional(),
|
|
422
|
+
stream: boolean().optional(),
|
|
423
|
+
tools: array(OpenAIToolDefinition).optional(),
|
|
424
|
+
tool_choice: union([
|
|
425
|
+
object({ type: literal("auto") }),
|
|
426
|
+
object({ type: literal("none") }),
|
|
427
|
+
object({ type: literal("function"), function: object({ name: string() }) })
|
|
428
|
+
]).optional(),
|
|
429
|
+
user: string().optional()
|
|
430
|
+
});
|
|
431
|
+
const OpenAIChoiceDelta = object({
|
|
432
|
+
role: _enum(["assistant"]).optional(),
|
|
433
|
+
content: string().nullable().optional(),
|
|
434
|
+
reasoning_content: string().nullable().optional(),
|
|
435
|
+
function_call: object({ name: string().optional(), arguments: string().optional() }).optional(),
|
|
436
|
+
tool_calls: array(
|
|
437
|
+
object({
|
|
438
|
+
index: number(),
|
|
439
|
+
id: string().optional(),
|
|
440
|
+
type: literal("function").optional(),
|
|
441
|
+
function: object({
|
|
442
|
+
name: string().optional(),
|
|
443
|
+
arguments: string().optional()
|
|
444
|
+
})
|
|
445
|
+
})
|
|
446
|
+
).optional()
|
|
447
|
+
});
|
|
448
|
+
const OpenAIChoice = object({
|
|
449
|
+
index: number(),
|
|
450
|
+
message: object({
|
|
451
|
+
role: _enum(["assistant"]),
|
|
452
|
+
content: string().nullable(),
|
|
453
|
+
reasoning_content: string().optional(),
|
|
454
|
+
function_call: object({ name: string(), arguments: string() }).optional()
|
|
455
|
+
}).optional(),
|
|
456
|
+
delta: OpenAIChoiceDelta.optional(),
|
|
457
|
+
finish_reason: string().nullable()
|
|
458
|
+
});
|
|
459
|
+
const OpenAIResponseSchema$1 = object({
|
|
460
|
+
id: string(),
|
|
461
|
+
object: literal("chat.completion"),
|
|
462
|
+
created: number(),
|
|
463
|
+
model: string(),
|
|
464
|
+
choices: array(OpenAIChoice),
|
|
465
|
+
usage: object({
|
|
466
|
+
prompt_tokens: number(),
|
|
467
|
+
completion_tokens: number(),
|
|
468
|
+
total_tokens: number()
|
|
469
|
+
})
|
|
470
|
+
});
|
|
471
|
+
const OpenAISSERawChunkSchema = object({
|
|
472
|
+
id: string(),
|
|
473
|
+
object: literal("chat.completion.chunk"),
|
|
474
|
+
created: number(),
|
|
475
|
+
model: string(),
|
|
476
|
+
choices: array(
|
|
477
|
+
object({
|
|
478
|
+
index: number(),
|
|
479
|
+
delta: OpenAIChoiceDelta,
|
|
480
|
+
finish_reason: string().nullable().optional()
|
|
481
|
+
})
|
|
482
|
+
),
|
|
483
|
+
usage: object({
|
|
484
|
+
prompt_tokens: number(),
|
|
485
|
+
completion_tokens: number(),
|
|
486
|
+
total_tokens: number()
|
|
487
|
+
}).nullable().optional()
|
|
488
|
+
});
|
|
489
|
+
function parseOpenAIResponse(rawBody) {
|
|
490
|
+
try {
|
|
491
|
+
const json = JSON.parse(rawBody);
|
|
492
|
+
const result = OpenAIResponseSchema$1.safeParse(json);
|
|
493
|
+
if (result.success) return result.data;
|
|
494
|
+
return null;
|
|
495
|
+
} catch {
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
const StreamingChunkSchema$1 = object({
|
|
500
|
+
index: number(),
|
|
501
|
+
timestamp: number(),
|
|
502
|
+
type: string(),
|
|
503
|
+
data: JsonValueSchema
|
|
504
|
+
});
|
|
505
|
+
const StreamingChunksArraySchema = object({
|
|
506
|
+
chunks: array(StreamingChunkSchema$1),
|
|
507
|
+
truncated: boolean().optional().default(false)
|
|
508
|
+
});
|
|
509
|
+
const CapturedLogSchema = object({
|
|
510
|
+
id: number(),
|
|
511
|
+
timestamp: string(),
|
|
512
|
+
method: string(),
|
|
513
|
+
path: string(),
|
|
514
|
+
model: string().nullable(),
|
|
515
|
+
sessionId: string().nullable(),
|
|
516
|
+
rawRequestBody: string().nullable(),
|
|
517
|
+
responseStatus: number().nullable(),
|
|
518
|
+
responseText: string().nullable(),
|
|
519
|
+
inputTokens: number().nullable(),
|
|
520
|
+
outputTokens: number().nullable(),
|
|
521
|
+
cacheCreationInputTokens: number().nullable(),
|
|
522
|
+
cacheReadInputTokens: number().nullable(),
|
|
523
|
+
elapsedMs: number().nullable(),
|
|
524
|
+
streaming: boolean(),
|
|
525
|
+
userAgent: string().nullable(),
|
|
526
|
+
origin: string().nullable(),
|
|
527
|
+
rawHeaders: record(string(), string()).optional(),
|
|
528
|
+
/** Headers sent to the upstream LLM */
|
|
529
|
+
headers: record(string(), string()).optional(),
|
|
530
|
+
apiFormat: _enum(["anthropic", "openai", "unknown"]).default("unknown"),
|
|
531
|
+
isTest: boolean().optional().default(false),
|
|
532
|
+
providerName: string().nullable().optional(),
|
|
533
|
+
clientPort: number().nullable().optional(),
|
|
534
|
+
clientPid: number().nullable().optional(),
|
|
535
|
+
clientCwd: string().nullable().optional(),
|
|
536
|
+
clientProjectFolder: string().nullable().optional(),
|
|
537
|
+
streamingChunks: StreamingChunksArraySchema.optional(),
|
|
538
|
+
streamingChunksPath: string().nullable().optional()
|
|
539
|
+
});
|
|
540
|
+
const RequestModelSchema = object({
|
|
541
|
+
model: string()
|
|
542
|
+
});
|
|
543
|
+
function extractModelFromBody(body) {
|
|
544
|
+
try {
|
|
545
|
+
const json = JSON.parse(body);
|
|
546
|
+
const parsed = RequestModelSchema.safeParse(json);
|
|
547
|
+
if (parsed.success) {
|
|
548
|
+
return parsed.data.model;
|
|
549
|
+
}
|
|
550
|
+
return null;
|
|
551
|
+
} catch {
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
function parseRequest(rawBody) {
|
|
556
|
+
if (rawBody === null) return null;
|
|
557
|
+
try {
|
|
558
|
+
const json = JSON.parse(rawBody);
|
|
559
|
+
const result = InspectorRequestSchema.safeParse(json);
|
|
560
|
+
if (result.success) return result.data;
|
|
561
|
+
return null;
|
|
562
|
+
} catch {
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
const StreamingChunkSchema = object({
|
|
567
|
+
index: number(),
|
|
568
|
+
timestamp: number(),
|
|
569
|
+
type: string(),
|
|
570
|
+
data: JsonValueSchema
|
|
571
|
+
});
|
|
572
|
+
const StreamingChunksDataSchema = object({
|
|
573
|
+
chunks: array(StreamingChunkSchema),
|
|
574
|
+
truncated: boolean().optional()
|
|
575
|
+
});
|
|
576
|
+
const CHUNKS_DIR_ENV = process.env["CHUNKS_DIR"];
|
|
577
|
+
function getChunksDir() {
|
|
578
|
+
const isWindows = process.platform === "win32";
|
|
579
|
+
const base = isWindows ? process.env["APPDATA"] ?? join(process.env["USERPROFILE"] ?? "C:\\", ".llm-inspector") : process.env["HOME"] ?? "/tmp";
|
|
580
|
+
if (CHUNKS_DIR_ENV !== void 0) {
|
|
581
|
+
return isAbsolute(CHUNKS_DIR_ENV) ? CHUNKS_DIR_ENV : join(base, CHUNKS_DIR_ENV);
|
|
582
|
+
}
|
|
583
|
+
return join(base, ".llm-inspector", "chunks");
|
|
584
|
+
}
|
|
585
|
+
function getChunkFilePath(logId) {
|
|
586
|
+
return join(getChunksDir(), `${logId}.json`);
|
|
587
|
+
}
|
|
588
|
+
function getTempFilePath(logId) {
|
|
589
|
+
return join(getChunksDir(), `.${logId}.tmp`);
|
|
590
|
+
}
|
|
591
|
+
function writeChunks(logId, chunks, truncated) {
|
|
592
|
+
const dir = getChunksDir();
|
|
593
|
+
const targetPath = getChunkFilePath(logId);
|
|
594
|
+
const tempPath = getTempFilePath(logId);
|
|
595
|
+
try {
|
|
596
|
+
mkdirSync(dir, { recursive: true });
|
|
597
|
+
} catch (err) {
|
|
598
|
+
console.error("[chunkStorage] Failed to create chunks directory:", err);
|
|
599
|
+
}
|
|
600
|
+
const data = { chunks, truncated };
|
|
601
|
+
try {
|
|
602
|
+
writeFileSync(tempPath, JSON.stringify(data), "utf-8");
|
|
603
|
+
} catch (err) {
|
|
604
|
+
console.error("[chunkStorage] Failed to write chunks temp file:", err);
|
|
605
|
+
return targetPath;
|
|
606
|
+
}
|
|
607
|
+
try {
|
|
608
|
+
renameSync(tempPath, targetPath);
|
|
609
|
+
} catch (err) {
|
|
610
|
+
console.warn("[chunkStorage] Rename failed, falling back to copy+delete:", err);
|
|
611
|
+
try {
|
|
612
|
+
copyFileSync(tempPath, targetPath);
|
|
613
|
+
unlinkSync(tempPath);
|
|
614
|
+
} catch (copyErr) {
|
|
615
|
+
console.error("[chunkStorage] Failed to copy chunks file:", copyErr);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return targetPath;
|
|
619
|
+
}
|
|
620
|
+
function readChunks(path2) {
|
|
621
|
+
if (!existsSync(path2)) {
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
try {
|
|
625
|
+
const content = readFileSync(path2, "utf-8");
|
|
626
|
+
const parsed = JSON.parse(content);
|
|
627
|
+
const result = StreamingChunksDataSchema.safeParse(parsed);
|
|
628
|
+
if (!result.success) return null;
|
|
629
|
+
return result.data;
|
|
630
|
+
} catch {
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
const LooseRequestSchema = object({
|
|
635
|
+
model: string().optional(),
|
|
636
|
+
metadata: object({ user_id: string().optional() }).passthrough().optional()
|
|
637
|
+
});
|
|
638
|
+
const MAX_MEMORY_CACHE = 100;
|
|
639
|
+
const memoryCache = /* @__PURE__ */ new Map();
|
|
640
|
+
let headId = null;
|
|
641
|
+
let tailId = null;
|
|
642
|
+
const prevMap = /* @__PURE__ */ new Map();
|
|
643
|
+
const nextMap = /* @__PURE__ */ new Map();
|
|
644
|
+
function evictOldestIfNeeded() {
|
|
645
|
+
while (memoryCache.size >= MAX_MEMORY_CACHE && headId !== null) {
|
|
646
|
+
const oldest = headId;
|
|
647
|
+
const next = nextMap.get(oldest) ?? null;
|
|
648
|
+
memoryCache.delete(oldest);
|
|
649
|
+
prevMap.delete(oldest);
|
|
650
|
+
nextMap.delete(oldest);
|
|
651
|
+
if (next !== null) {
|
|
652
|
+
prevMap.set(next, null);
|
|
653
|
+
headId = next;
|
|
654
|
+
} else {
|
|
655
|
+
headId = null;
|
|
656
|
+
tailId = null;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function addToCache(log) {
|
|
661
|
+
evictOldestIfNeeded();
|
|
662
|
+
if (tailId !== null) {
|
|
663
|
+
nextMap.set(tailId, log.id);
|
|
664
|
+
prevMap.set(log.id, tailId);
|
|
665
|
+
} else {
|
|
666
|
+
headId = log.id;
|
|
667
|
+
prevMap.set(log.id, null);
|
|
668
|
+
}
|
|
669
|
+
nextMap.set(log.id, null);
|
|
670
|
+
tailId = log.id;
|
|
671
|
+
memoryCache.set(log.id, log);
|
|
672
|
+
}
|
|
673
|
+
async function addTestLogEntry(entry) {
|
|
674
|
+
const id = await getNextLogId();
|
|
675
|
+
const index = await loadIndex();
|
|
676
|
+
if (id > index.maxId) {
|
|
677
|
+
index.maxId = id;
|
|
678
|
+
await saveIndex(index);
|
|
679
|
+
}
|
|
680
|
+
let streamingChunksPath = null;
|
|
681
|
+
if (entry.streamingChunks !== void 0 && entry.streamingChunks.chunks.length > 0) {
|
|
682
|
+
streamingChunksPath = writeChunks(
|
|
683
|
+
id,
|
|
684
|
+
entry.streamingChunks.chunks,
|
|
685
|
+
entry.streamingChunks.truncated
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
const log = {
|
|
689
|
+
id,
|
|
690
|
+
...entry,
|
|
691
|
+
streamingChunksPath
|
|
692
|
+
};
|
|
693
|
+
addToCache(log);
|
|
694
|
+
emitLogUpdate(log);
|
|
695
|
+
return log;
|
|
696
|
+
}
|
|
697
|
+
async function createLog(method, path2, requestBody, headers, clientInfo, rawHeaders, upstreamHeaders, apiFormat = "unknown") {
|
|
698
|
+
let model = null;
|
|
699
|
+
let sessionId = null;
|
|
700
|
+
if (requestBody !== null) {
|
|
701
|
+
try {
|
|
702
|
+
const json = JSON.parse(requestBody);
|
|
703
|
+
const loose = LooseRequestSchema.safeParse(json);
|
|
704
|
+
if (loose.success) {
|
|
705
|
+
model = loose.data.model ?? null;
|
|
706
|
+
sessionId = loose.data.metadata?.user_id ?? headers.get("x-session-affinity") ?? null;
|
|
707
|
+
}
|
|
708
|
+
} catch {
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
const userAgent = headers.get("user-agent");
|
|
712
|
+
const origin = headers.get("origin");
|
|
713
|
+
const id = await getNextLogId();
|
|
714
|
+
const log = {
|
|
715
|
+
id,
|
|
716
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
717
|
+
method,
|
|
718
|
+
path: path2,
|
|
719
|
+
model,
|
|
720
|
+
sessionId,
|
|
721
|
+
rawRequestBody: requestBody,
|
|
722
|
+
responseStatus: null,
|
|
723
|
+
responseText: null,
|
|
724
|
+
inputTokens: null,
|
|
725
|
+
outputTokens: null,
|
|
726
|
+
cacheCreationInputTokens: null,
|
|
727
|
+
cacheReadInputTokens: null,
|
|
728
|
+
elapsedMs: null,
|
|
729
|
+
streaming: false,
|
|
730
|
+
userAgent,
|
|
731
|
+
origin,
|
|
732
|
+
rawHeaders,
|
|
733
|
+
headers: upstreamHeaders,
|
|
734
|
+
apiFormat,
|
|
735
|
+
isTest: false,
|
|
736
|
+
providerName: null,
|
|
737
|
+
clientPort: clientInfo?.port ?? null,
|
|
738
|
+
clientPid: clientInfo?.pid ?? null,
|
|
739
|
+
clientCwd: clientInfo?.cwd ?? null,
|
|
740
|
+
clientProjectFolder: clientInfo?.projectFolder ?? null,
|
|
741
|
+
streamingChunksPath: null
|
|
742
|
+
};
|
|
743
|
+
const logFile = getCurrentLogFile();
|
|
744
|
+
appendLogEntry(log);
|
|
745
|
+
await addToIndex(id, logFile, -1, -1);
|
|
746
|
+
addToCache(log);
|
|
747
|
+
emitLogUpdate(log);
|
|
748
|
+
return log;
|
|
749
|
+
}
|
|
750
|
+
async function getLogById(id) {
|
|
751
|
+
const cached = memoryCache.get(id);
|
|
752
|
+
if (cached !== void 0) {
|
|
753
|
+
return cached;
|
|
754
|
+
}
|
|
755
|
+
const entry = await findInIndex(id);
|
|
756
|
+
if (entry === null) {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
try {
|
|
760
|
+
const filePath = join(resolveLogDir(), entry.file);
|
|
761
|
+
if (!existsSync(filePath)) {
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
const content = readFileSync(filePath, "utf-8");
|
|
765
|
+
const lines = content.split("\n");
|
|
766
|
+
let lastMatch = null;
|
|
767
|
+
for (const line of lines) {
|
|
768
|
+
if (line.trim() === "") continue;
|
|
769
|
+
try {
|
|
770
|
+
const parsed = JSON.parse(line);
|
|
771
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
772
|
+
const desc = Object.getOwnPropertyDescriptor(parsed, "id");
|
|
773
|
+
if (desc !== void 0 && typeof desc.value === "number" && desc.value === id) {
|
|
774
|
+
const result = CapturedLogSchema.safeParse(parsed);
|
|
775
|
+
if (result.success) {
|
|
776
|
+
lastMatch = result.data;
|
|
777
|
+
if (result.data.responseStatus !== null) {
|
|
778
|
+
return result.data;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
} catch {
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
if (lastMatch !== null) return lastMatch;
|
|
787
|
+
} catch (err) {
|
|
788
|
+
console.error("[store] Failed to read log from disk:", err);
|
|
789
|
+
}
|
|
790
|
+
return null;
|
|
791
|
+
}
|
|
792
|
+
function getFilteredLogs(sessionId, model) {
|
|
793
|
+
const cachedLogs = Array.from(memoryCache.values()).sort((a, b) => a.id - b.id);
|
|
794
|
+
return cachedLogs.filter((l) => {
|
|
795
|
+
if (sessionId !== void 0 && l.sessionId !== sessionId) return false;
|
|
796
|
+
if (model !== void 0 && l.model !== model) return false;
|
|
797
|
+
return true;
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
function getSessions() {
|
|
801
|
+
const set = /* @__PURE__ */ new Set();
|
|
802
|
+
for (const l of memoryCache.values()) {
|
|
803
|
+
if (l.sessionId !== null && l.sessionId !== "") set.add(l.sessionId);
|
|
804
|
+
}
|
|
805
|
+
return [...set];
|
|
806
|
+
}
|
|
807
|
+
function getModels() {
|
|
808
|
+
const set = /* @__PURE__ */ new Set();
|
|
809
|
+
for (const l of memoryCache.values()) {
|
|
810
|
+
if (l.model !== null && l.model !== "") set.add(l.model);
|
|
811
|
+
}
|
|
812
|
+
return [...set];
|
|
813
|
+
}
|
|
814
|
+
function clearAllLogs() {
|
|
815
|
+
const count = memoryCache.size;
|
|
816
|
+
memoryCache.clear();
|
|
817
|
+
headId = null;
|
|
818
|
+
tailId = null;
|
|
819
|
+
prevMap.clear();
|
|
820
|
+
nextMap.clear();
|
|
821
|
+
return { cleared: count };
|
|
822
|
+
}
|
|
823
|
+
const sseHandlers = /* @__PURE__ */ new Set();
|
|
824
|
+
function onLogUpdate(handler) {
|
|
825
|
+
sseHandlers.add(handler);
|
|
826
|
+
return () => {
|
|
827
|
+
sseHandlers.delete(handler);
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
function emitLogUpdate(log) {
|
|
831
|
+
for (const handler of sseHandlers) {
|
|
832
|
+
try {
|
|
833
|
+
handler(log);
|
|
834
|
+
} catch {
|
|
835
|
+
sseHandlers.delete(handler);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
const DEFAULT_UPSTREAM$1 = "https://api.anthropic.com";
|
|
840
|
+
const DEFAULT_OPENAI_UPSTREAM$1 = "https://api.openai.com/v1";
|
|
841
|
+
const PROXY_IDENTITY = process.env["PROXY_IDENTITY"] ?? "inspector9527@Tony";
|
|
842
|
+
const PATH_V1_CHAT_COMPLETIONS = "/v1/chat/completions";
|
|
843
|
+
const PATH_CHAT_COMPLETIONS = "/chat/completions";
|
|
844
|
+
const PATH_V1_MESSAGES = "/v1/messages";
|
|
845
|
+
const HEADER_CONTENT_TYPE = "content-type";
|
|
846
|
+
const HEADER_USER_AGENT = "user-agent";
|
|
847
|
+
const HEADER_X_PROXY_IDENTITY = "x-proxy-identity";
|
|
848
|
+
const HEADER_AUTHORIZATION = "authorization";
|
|
849
|
+
const HEADER_X_API_KEY = "x-api-key";
|
|
850
|
+
const HEADER_CONTENT_ENCODING = "content-encoding";
|
|
851
|
+
const HEADER_CONTENT_LENGTH = "content-length";
|
|
852
|
+
const HEADER_HOST = "host";
|
|
853
|
+
const AUTH_HEADER_X_API_KEY = "x-api-key";
|
|
854
|
+
const CONTENT_TYPE_EVENT_STREAM = "text/event-stream";
|
|
855
|
+
const STATUS_FORBIDDEN = 403;
|
|
856
|
+
const STATUS_BAD_GATEWAY = 502;
|
|
857
|
+
const PRESERVE_HEADERS = /* @__PURE__ */ new Set([HEADER_CONTENT_TYPE]);
|
|
858
|
+
class FormatRegistryImpl {
|
|
859
|
+
handlers = /* @__PURE__ */ new Map();
|
|
860
|
+
pathMap = /* @__PURE__ */ new Map();
|
|
861
|
+
register(handler) {
|
|
862
|
+
this.handlers.set(handler.format, handler);
|
|
863
|
+
}
|
|
864
|
+
registerPath(path2, format) {
|
|
865
|
+
this.pathMap.set(path2, format);
|
|
866
|
+
}
|
|
867
|
+
/** Get handler by format identifier */
|
|
868
|
+
get(format) {
|
|
869
|
+
return this.handlers.get(format);
|
|
870
|
+
}
|
|
871
|
+
/** Get handler matching a request path */
|
|
872
|
+
getByPath(path2) {
|
|
873
|
+
const messagesPath = path2.split("?")[0] ?? "";
|
|
874
|
+
if (messagesPath === PATH_V1_MESSAGES) return this.handlers.get("anthropic");
|
|
875
|
+
if (messagesPath === PATH_V1_CHAT_COMPLETIONS || messagesPath === PATH_CHAT_COMPLETIONS) {
|
|
876
|
+
return this.handlers.get("openai");
|
|
877
|
+
}
|
|
878
|
+
return void 0;
|
|
879
|
+
}
|
|
880
|
+
/** Detect format from request body content */
|
|
881
|
+
detectFormat(rawBody) {
|
|
882
|
+
if (rawBody === null) return "unknown";
|
|
883
|
+
for (const handler of this.handlers.values()) {
|
|
884
|
+
if (handler.detectFormat(rawBody)) return handler.format;
|
|
885
|
+
}
|
|
886
|
+
return "unknown";
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
const formatRegistry = new FormatRegistryImpl();
|
|
890
|
+
function formatForPath(path2) {
|
|
891
|
+
return formatRegistry.getByPath(path2) ?? null;
|
|
892
|
+
}
|
|
893
|
+
formatRegistry.registerPath(PATH_V1_MESSAGES, "anthropic");
|
|
894
|
+
formatRegistry.registerPath(PATH_V1_CHAT_COMPLETIONS, "openai");
|
|
895
|
+
formatRegistry.registerPath(PATH_CHAT_COMPLETIONS, "openai");
|
|
896
|
+
function parseInputJson(json) {
|
|
897
|
+
if (json === "") return {};
|
|
898
|
+
try {
|
|
899
|
+
const parsed = JSON.parse(json);
|
|
900
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
901
|
+
return { ...parsed };
|
|
902
|
+
}
|
|
903
|
+
return {};
|
|
904
|
+
} catch {
|
|
905
|
+
return {};
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
function finalizeBlock(block) {
|
|
909
|
+
switch (block.type) {
|
|
910
|
+
case "text":
|
|
911
|
+
return { type: "text", text: block.text };
|
|
912
|
+
case "thinking": {
|
|
913
|
+
const out = { type: "thinking", thinking: block.thinking };
|
|
914
|
+
if (block.signature !== "") out.signature = block.signature;
|
|
915
|
+
return out;
|
|
916
|
+
}
|
|
917
|
+
case "tool_use":
|
|
918
|
+
return {
|
|
919
|
+
type: "tool_use",
|
|
920
|
+
id: block.id,
|
|
921
|
+
name: block.name,
|
|
922
|
+
input: parseInputJson(block.inputJson)
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
function extractAnthropicStream(raw, log, fallbackModel, collectChunks) {
|
|
927
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
928
|
+
let id = "";
|
|
929
|
+
let model = "";
|
|
930
|
+
let stopReason = null;
|
|
931
|
+
let stopSequence = null;
|
|
932
|
+
let inputTokens = 0;
|
|
933
|
+
let outputTokens = 0;
|
|
934
|
+
const MAX_CHUNKS = 1e3;
|
|
935
|
+
let chunkIndex = 0;
|
|
936
|
+
let streamStartMs = 0;
|
|
937
|
+
const chunks = [];
|
|
938
|
+
for (const line of raw.split("\n")) {
|
|
939
|
+
if (!line.startsWith("data: ")) continue;
|
|
940
|
+
try {
|
|
941
|
+
const json = JSON.parse(line.slice(6));
|
|
942
|
+
const parsed = SseEventSchema.safeParse(json);
|
|
943
|
+
if (!parsed.success) continue;
|
|
944
|
+
const data = parsed.data;
|
|
945
|
+
if (chunkIndex === 0) streamStartMs = Date.now();
|
|
946
|
+
if (collectChunks === true && chunks.length < MAX_CHUNKS) {
|
|
947
|
+
chunks.push({
|
|
948
|
+
index: chunkIndex,
|
|
949
|
+
timestamp: Date.now() - streamStartMs,
|
|
950
|
+
type: data.type,
|
|
951
|
+
data: JsonValueSchema.parse(data)
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
chunkIndex++;
|
|
955
|
+
switch (data.type) {
|
|
956
|
+
case "message_start":
|
|
957
|
+
id = data.message.id;
|
|
958
|
+
model = data.message.model;
|
|
959
|
+
inputTokens = data.message.usage.input_tokens;
|
|
960
|
+
log.inputTokens = inputTokens;
|
|
961
|
+
log.cacheCreationInputTokens = data.message.usage.cache_creation_input_tokens ?? null;
|
|
962
|
+
log.cacheReadInputTokens = data.message.usage.cache_read_input_tokens ?? null;
|
|
963
|
+
if (log.model === null) log.model = model;
|
|
964
|
+
break;
|
|
965
|
+
case "content_block_start": {
|
|
966
|
+
const cb = data.content_block;
|
|
967
|
+
if (cb.type === "text") {
|
|
968
|
+
blocks.set(data.index, { type: "text", text: cb.text ?? "" });
|
|
969
|
+
} else if (cb.type === "thinking") {
|
|
970
|
+
blocks.set(data.index, {
|
|
971
|
+
type: "thinking",
|
|
972
|
+
thinking: cb.thinking ?? "",
|
|
973
|
+
signature: cb.signature ?? ""
|
|
974
|
+
});
|
|
975
|
+
} else {
|
|
976
|
+
blocks.set(data.index, {
|
|
977
|
+
type: "tool_use",
|
|
978
|
+
id: cb.id,
|
|
979
|
+
name: cb.name,
|
|
980
|
+
inputJson: ""
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
break;
|
|
984
|
+
}
|
|
985
|
+
case "content_block_delta": {
|
|
986
|
+
const block = blocks.get(data.index);
|
|
987
|
+
if (block === void 0) break;
|
|
988
|
+
const delta = data.delta;
|
|
989
|
+
if (delta.type === "text_delta" && block.type === "text") {
|
|
990
|
+
block.text += delta.text;
|
|
991
|
+
} else if (delta.type === "thinking_delta" && block.type === "thinking") {
|
|
992
|
+
block.thinking += delta.thinking;
|
|
993
|
+
} else if (delta.type === "signature_delta" && block.type === "thinking") {
|
|
994
|
+
block.signature += delta.signature;
|
|
995
|
+
} else if (delta.type === "input_json_delta" && block.type === "tool_use") {
|
|
996
|
+
block.inputJson += delta.partial_json;
|
|
997
|
+
}
|
|
998
|
+
break;
|
|
999
|
+
}
|
|
1000
|
+
case "message_delta":
|
|
1001
|
+
stopReason = data.delta.stop_reason;
|
|
1002
|
+
stopSequence = data.delta.stop_sequence ?? null;
|
|
1003
|
+
outputTokens = data.usage.output_tokens;
|
|
1004
|
+
log.outputTokens = outputTokens;
|
|
1005
|
+
break;
|
|
1006
|
+
case "content_block_stop":
|
|
1007
|
+
case "message_stop":
|
|
1008
|
+
case "ping":
|
|
1009
|
+
break;
|
|
1010
|
+
}
|
|
1011
|
+
} catch {
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (collectChunks === true) {
|
|
1015
|
+
log.streamingChunks = {
|
|
1016
|
+
chunks,
|
|
1017
|
+
truncated: chunkIndex > MAX_CHUNKS
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
const orderedContent = [...blocks.entries()].sort(([a], [b]) => a - b).map(([, block]) => finalizeBlock(block));
|
|
1021
|
+
return JSON.stringify({
|
|
1022
|
+
id,
|
|
1023
|
+
type: "message",
|
|
1024
|
+
model: model !== "" ? model : fallbackModel ?? "",
|
|
1025
|
+
role: "assistant",
|
|
1026
|
+
content: orderedContent,
|
|
1027
|
+
stop_reason: stopReason,
|
|
1028
|
+
stop_sequence: stopSequence,
|
|
1029
|
+
usage: { input_tokens: inputTokens, output_tokens: outputTokens }
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
const AnthropicFormatHandler = {
|
|
1033
|
+
format: "anthropic",
|
|
1034
|
+
parseRequest(rawBody, headers) {
|
|
1035
|
+
try {
|
|
1036
|
+
const json = JSON.parse(rawBody);
|
|
1037
|
+
const result = AnthropicRequestSchema.safeParse(json);
|
|
1038
|
+
if (result.success) {
|
|
1039
|
+
return {
|
|
1040
|
+
model: result.data.model,
|
|
1041
|
+
sessionId: result.data.metadata?.user_id ?? headers?.get("x-session-affinity") ?? null
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
return null;
|
|
1045
|
+
} catch {
|
|
1046
|
+
return null;
|
|
1047
|
+
}
|
|
1048
|
+
},
|
|
1049
|
+
extractTokens(responseBody) {
|
|
1050
|
+
try {
|
|
1051
|
+
const json = JSON.parse(responseBody);
|
|
1052
|
+
const result = AnthropicResponseSchema$1.safeParse(json);
|
|
1053
|
+
if (result.success) {
|
|
1054
|
+
return {
|
|
1055
|
+
inputTokens: result.data.usage.input_tokens,
|
|
1056
|
+
outputTokens: result.data.usage.output_tokens,
|
|
1057
|
+
cacheCreationInputTokens: result.data.usage.cache_creation_input_tokens ?? null,
|
|
1058
|
+
cacheReadInputTokens: result.data.usage.cache_read_input_tokens ?? null
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
} catch {
|
|
1062
|
+
}
|
|
1063
|
+
return {
|
|
1064
|
+
inputTokens: null,
|
|
1065
|
+
outputTokens: null,
|
|
1066
|
+
cacheCreationInputTokens: null,
|
|
1067
|
+
cacheReadInputTokens: null
|
|
1068
|
+
};
|
|
1069
|
+
},
|
|
1070
|
+
extractStream(raw, log, fallbackModel, collectChunks) {
|
|
1071
|
+
return extractAnthropicStream(raw, log, fallbackModel, collectChunks);
|
|
1072
|
+
},
|
|
1073
|
+
detectFormat(rawBody) {
|
|
1074
|
+
if (rawBody === null) return false;
|
|
1075
|
+
try {
|
|
1076
|
+
const json = JSON.parse(rawBody);
|
|
1077
|
+
if (typeof json === "object" && json !== null && !Array.isArray(json)) {
|
|
1078
|
+
const keys = Object.keys(json);
|
|
1079
|
+
if (keys.includes("model") && keys.includes("messages")) {
|
|
1080
|
+
if (keys.includes("system") || keys.includes("tools")) {
|
|
1081
|
+
return true;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
return false;
|
|
1086
|
+
} catch {
|
|
1087
|
+
return false;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
formatRegistry.register(AnthropicFormatHandler);
|
|
1092
|
+
class ProviderRegistryImpl {
|
|
1093
|
+
providers = [];
|
|
1094
|
+
/**
|
|
1095
|
+
* Register a provider with the registry.
|
|
1096
|
+
* @param provider - Provider implementation to register
|
|
1097
|
+
*/
|
|
1098
|
+
register(provider) {
|
|
1099
|
+
this.providers.push(provider);
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Find the first provider that matches the given model.
|
|
1103
|
+
* @param model - Model name to match
|
|
1104
|
+
* @returns Matching provider or null if none found
|
|
1105
|
+
*/
|
|
1106
|
+
findProvider(model) {
|
|
1107
|
+
let fallback = null;
|
|
1108
|
+
for (const provider of this.providers) {
|
|
1109
|
+
if (provider.matches(model)) {
|
|
1110
|
+
if (provider.name !== "anthropic") {
|
|
1111
|
+
return provider;
|
|
1112
|
+
}
|
|
1113
|
+
fallback = provider;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return fallback;
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Get all registered providers.
|
|
1120
|
+
* @returns Array of all registered providers
|
|
1121
|
+
*/
|
|
1122
|
+
getAll() {
|
|
1123
|
+
return [...this.providers];
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
const registry = new ProviderRegistryImpl();
|
|
1127
|
+
const DEFAULT_UPSTREAM = "https://api.anthropic.com";
|
|
1128
|
+
const DEFAULT_ROUTES = {
|
|
1129
|
+
MiniMax: "https://api.minimaxi.com/anthropic"
|
|
1130
|
+
};
|
|
1131
|
+
const anthropicProvider = {
|
|
1132
|
+
name: "anthropic",
|
|
1133
|
+
matches(model) {
|
|
1134
|
+
const modelLower = model.toLowerCase();
|
|
1135
|
+
if (modelLower.startsWith("openai-")) {
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
if (modelLower.includes("claude") || modelLower.includes("anthropic")) {
|
|
1139
|
+
return true;
|
|
1140
|
+
}
|
|
1141
|
+
for (const prefix of Object.keys(DEFAULT_ROUTES)) {
|
|
1142
|
+
if (modelLower.startsWith(prefix.toLowerCase())) {
|
|
1143
|
+
return true;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return true;
|
|
1147
|
+
},
|
|
1148
|
+
getUpstreamBase(_isChatCompletions, providerConfig) {
|
|
1149
|
+
if (providerConfig?.baseUrl !== void 0) {
|
|
1150
|
+
return providerConfig.baseUrl;
|
|
1151
|
+
}
|
|
1152
|
+
return DEFAULT_UPSTREAM;
|
|
1153
|
+
},
|
|
1154
|
+
extractStream(raw, log, fallbackModel, collectChunks) {
|
|
1155
|
+
return extractAnthropicStream(raw, log, fallbackModel, collectChunks);
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
registry.register(anthropicProvider);
|
|
1159
|
+
function extractOpenAIStream(raw, log, fallbackModel, collectChunks = true) {
|
|
1160
|
+
let id = "";
|
|
1161
|
+
let model = "";
|
|
1162
|
+
let completionContent = "";
|
|
1163
|
+
let reasoningContent = "";
|
|
1164
|
+
let finishReason = null;
|
|
1165
|
+
let promptTokens = 0;
|
|
1166
|
+
let completionTokens = 0;
|
|
1167
|
+
let usageCaptured = false;
|
|
1168
|
+
let started = false;
|
|
1169
|
+
const toolCalls = [];
|
|
1170
|
+
const MAX_CHUNKS = 1e3;
|
|
1171
|
+
let chunkIndex = 0;
|
|
1172
|
+
let streamStartMs = 0;
|
|
1173
|
+
const chunks = [];
|
|
1174
|
+
for (const line of raw.split("\n")) {
|
|
1175
|
+
if (!line.startsWith("data: ")) continue;
|
|
1176
|
+
const dataStr = line.slice(6).trim();
|
|
1177
|
+
if (dataStr === "[DONE]") break;
|
|
1178
|
+
try {
|
|
1179
|
+
const parsed = JSON.parse(dataStr);
|
|
1180
|
+
const chunkResult = OpenAISSERawChunkSchema.safeParse(parsed);
|
|
1181
|
+
if (!chunkResult.success) continue;
|
|
1182
|
+
const chunk = chunkResult.data;
|
|
1183
|
+
if (chunkIndex === 0) streamStartMs = Date.now();
|
|
1184
|
+
if (collectChunks === true && chunks.length < MAX_CHUNKS) {
|
|
1185
|
+
const jsonResult = JsonValueSchema.safeParse(chunk);
|
|
1186
|
+
if (jsonResult.success) {
|
|
1187
|
+
chunks.push({
|
|
1188
|
+
index: chunkIndex,
|
|
1189
|
+
timestamp: Date.now() - streamStartMs,
|
|
1190
|
+
type: "chat.completion.chunk",
|
|
1191
|
+
data: jsonResult.data
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
chunkIndex++;
|
|
1196
|
+
if (!started) {
|
|
1197
|
+
id = chunk.id;
|
|
1198
|
+
model = chunk.model;
|
|
1199
|
+
started = true;
|
|
1200
|
+
}
|
|
1201
|
+
if (!usageCaptured && chunk.usage !== void 0 && chunk.usage !== null) {
|
|
1202
|
+
promptTokens = chunk.usage.prompt_tokens;
|
|
1203
|
+
completionTokens = chunk.usage.completion_tokens;
|
|
1204
|
+
log.inputTokens = promptTokens;
|
|
1205
|
+
usageCaptured = true;
|
|
1206
|
+
}
|
|
1207
|
+
for (const choice of chunk.choices) {
|
|
1208
|
+
const delta = choice.delta;
|
|
1209
|
+
if (delta.content !== void 0 && delta.content !== null) {
|
|
1210
|
+
completionContent += delta.content;
|
|
1211
|
+
}
|
|
1212
|
+
if (delta.reasoning_content !== void 0 && delta.reasoning_content !== null) {
|
|
1213
|
+
reasoningContent += delta.reasoning_content;
|
|
1214
|
+
}
|
|
1215
|
+
if (choice.finish_reason !== void 0 && choice.finish_reason !== null) {
|
|
1216
|
+
finishReason = choice.finish_reason;
|
|
1217
|
+
}
|
|
1218
|
+
if (delta.tool_calls !== void 0 && delta.tool_calls !== null) {
|
|
1219
|
+
for (const tc of delta.tool_calls) {
|
|
1220
|
+
let existing = toolCalls.find((t) => t.index === tc.index);
|
|
1221
|
+
if (!existing) {
|
|
1222
|
+
existing = {
|
|
1223
|
+
index: tc.index,
|
|
1224
|
+
type: "function",
|
|
1225
|
+
function: { name: "", arguments: "" }
|
|
1226
|
+
};
|
|
1227
|
+
toolCalls.push(existing);
|
|
1228
|
+
}
|
|
1229
|
+
if (tc.id !== void 0) existing.id = tc.id;
|
|
1230
|
+
if (tc.function?.name !== void 0) existing.function.name += tc.function.name;
|
|
1231
|
+
if (tc.function?.arguments !== void 0)
|
|
1232
|
+
existing.function.arguments += tc.function.arguments;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
} catch {
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
if (collectChunks === true) {
|
|
1240
|
+
log.streamingChunks = {
|
|
1241
|
+
chunks,
|
|
1242
|
+
truncated: chunkIndex > MAX_CHUNKS
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
if (!usageCaptured) {
|
|
1246
|
+
completionTokens = Math.ceil(completionContent.length / 4);
|
|
1247
|
+
}
|
|
1248
|
+
log.outputTokens = completionTokens;
|
|
1249
|
+
const message = {
|
|
1250
|
+
role: "assistant",
|
|
1251
|
+
content: completionContent
|
|
1252
|
+
};
|
|
1253
|
+
if (reasoningContent) message.reasoning_content = reasoningContent;
|
|
1254
|
+
if (toolCalls.length > 0) message.tool_calls = toolCalls;
|
|
1255
|
+
return JSON.stringify({
|
|
1256
|
+
id,
|
|
1257
|
+
object: "chat.completion",
|
|
1258
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1259
|
+
model: model !== "" ? model : fallbackModel ?? "",
|
|
1260
|
+
choices: [
|
|
1261
|
+
{
|
|
1262
|
+
index: 0,
|
|
1263
|
+
message,
|
|
1264
|
+
finish_reason: finishReason
|
|
1265
|
+
}
|
|
1266
|
+
],
|
|
1267
|
+
usage: {
|
|
1268
|
+
prompt_tokens: promptTokens,
|
|
1269
|
+
completion_tokens: completionTokens,
|
|
1270
|
+
total_tokens: promptTokens + completionTokens
|
|
1271
|
+
}
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
const OpenAIFormatHandler = {
|
|
1275
|
+
format: "openai",
|
|
1276
|
+
parseRequest(rawBody, headers) {
|
|
1277
|
+
try {
|
|
1278
|
+
const json = JSON.parse(rawBody);
|
|
1279
|
+
const result = OpenAIRequestSchema.safeParse(json);
|
|
1280
|
+
if (result.success) {
|
|
1281
|
+
return {
|
|
1282
|
+
model: result.data.model,
|
|
1283
|
+
sessionId: headers?.get("x-session-affinity") ?? null
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
return null;
|
|
1287
|
+
} catch {
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
},
|
|
1291
|
+
extractTokens(responseBody) {
|
|
1292
|
+
const parsed = parseOpenAIResponse(responseBody);
|
|
1293
|
+
if (parsed) {
|
|
1294
|
+
return {
|
|
1295
|
+
inputTokens: parsed.usage.prompt_tokens,
|
|
1296
|
+
outputTokens: parsed.usage.completion_tokens,
|
|
1297
|
+
cacheCreationInputTokens: null,
|
|
1298
|
+
cacheReadInputTokens: null
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
return {
|
|
1302
|
+
inputTokens: null,
|
|
1303
|
+
outputTokens: null,
|
|
1304
|
+
cacheCreationInputTokens: null,
|
|
1305
|
+
cacheReadInputTokens: null
|
|
1306
|
+
};
|
|
1307
|
+
},
|
|
1308
|
+
extractStream(raw, log, fallbackModel, collectChunks) {
|
|
1309
|
+
return extractOpenAIStream(raw, log, fallbackModel, collectChunks);
|
|
1310
|
+
},
|
|
1311
|
+
detectFormat(rawBody) {
|
|
1312
|
+
if (rawBody === null) return false;
|
|
1313
|
+
try {
|
|
1314
|
+
const json = JSON.parse(rawBody);
|
|
1315
|
+
if (typeof json === "object" && json !== null && !Array.isArray(json)) {
|
|
1316
|
+
const keys = Object.keys(json);
|
|
1317
|
+
if (keys.includes("model") && keys.includes("messages")) {
|
|
1318
|
+
if (!keys.includes("system") && !keys.includes("tools")) {
|
|
1319
|
+
return true;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
return false;
|
|
1324
|
+
} catch {
|
|
1325
|
+
return false;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
};
|
|
1329
|
+
formatRegistry.register(OpenAIFormatHandler);
|
|
1330
|
+
const DEFAULT_OPENAI_UPSTREAM = "https://api.openai.com/v1";
|
|
1331
|
+
const openaiProvider = {
|
|
1332
|
+
name: "openai",
|
|
1333
|
+
matches(model) {
|
|
1334
|
+
const modelLower = model.toLowerCase();
|
|
1335
|
+
if (modelLower.startsWith("openai-")) {
|
|
1336
|
+
return true;
|
|
1337
|
+
}
|
|
1338
|
+
if (modelLower.includes("gpt-") || modelLower.includes("o1") || modelLower.includes("o2") || modelLower.includes("o3")) {
|
|
1339
|
+
return true;
|
|
1340
|
+
}
|
|
1341
|
+
return false;
|
|
1342
|
+
},
|
|
1343
|
+
getUpstreamBase(_isChatCompletions, providerConfig) {
|
|
1344
|
+
if (providerConfig?.baseUrl !== void 0) {
|
|
1345
|
+
return providerConfig.baseUrl;
|
|
1346
|
+
}
|
|
1347
|
+
return DEFAULT_OPENAI_UPSTREAM;
|
|
1348
|
+
},
|
|
1349
|
+
extractStream(raw, log, fallbackModel, collectChunks) {
|
|
1350
|
+
return extractOpenAIStream(raw, log, fallbackModel, collectChunks);
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
registry.register(openaiProvider);
|
|
1354
|
+
const DEFAULT_ALIBABA_UPSTREAM = "https://dashscope.aliyuncs.com/compatible-mode/v1";
|
|
1355
|
+
const alibabaProvider = {
|
|
1356
|
+
name: "alibaba",
|
|
1357
|
+
matches(model) {
|
|
1358
|
+
const m = model.toLowerCase().replace(/\s+/g, "-");
|
|
1359
|
+
if (m.startsWith("glm-5")) {
|
|
1360
|
+
return true;
|
|
1361
|
+
}
|
|
1362
|
+
if (m.startsWith("qwen")) {
|
|
1363
|
+
return true;
|
|
1364
|
+
}
|
|
1365
|
+
return false;
|
|
1366
|
+
},
|
|
1367
|
+
getUpstreamBase(_isChatCompletions, _providerConfig) {
|
|
1368
|
+
return DEFAULT_ALIBABA_UPSTREAM;
|
|
1369
|
+
},
|
|
1370
|
+
extractStream(raw, log, fallbackModel) {
|
|
1371
|
+
return extractOpenAIStream(raw, log, fallbackModel);
|
|
1372
|
+
}
|
|
1373
|
+
};
|
|
1374
|
+
registry.register(alibabaProvider);
|
|
1375
|
+
const ProviderConfigSchema = object({
|
|
1376
|
+
id: string(),
|
|
1377
|
+
name: string(),
|
|
1378
|
+
apiKey: string(),
|
|
1379
|
+
model: string().optional(),
|
|
1380
|
+
/** API format: "anthropic" or "openai" */
|
|
1381
|
+
format: _enum(["anthropic", "openai"]).optional(),
|
|
1382
|
+
/** Base URL for the provider (deprecated - use anthropicBaseUrl/openaiBaseUrl instead) */
|
|
1383
|
+
baseUrl: string().optional(),
|
|
1384
|
+
/** Anthropic API base URL */
|
|
1385
|
+
anthropicBaseUrl: string().optional(),
|
|
1386
|
+
/** OpenAI API base URL */
|
|
1387
|
+
openaiBaseUrl: string().optional(),
|
|
1388
|
+
/** Auth header to use: "bearer" (default) or "x-api-key" */
|
|
1389
|
+
authHeader: _enum(["bearer", "x-api-key"]).optional().default("bearer"),
|
|
1390
|
+
createdAt: string(),
|
|
1391
|
+
updatedAt: string()
|
|
1392
|
+
});
|
|
1393
|
+
object({
|
|
1394
|
+
providers: array(ProviderConfigSchema)
|
|
1395
|
+
});
|
|
1396
|
+
const store = new Conf({
|
|
1397
|
+
projectName: "llm-inspector",
|
|
1398
|
+
defaults: {
|
|
1399
|
+
providers: []
|
|
1400
|
+
}
|
|
1401
|
+
});
|
|
1402
|
+
function migrateProviders() {
|
|
1403
|
+
const providers = store.get("providers", []);
|
|
1404
|
+
let migrated = false;
|
|
1405
|
+
const updated = providers.map((p) => {
|
|
1406
|
+
function getOldUrl(obj, key) {
|
|
1407
|
+
if (obj !== null && typeof obj === "object") {
|
|
1408
|
+
const desc = Object.getOwnPropertyDescriptor(obj, key);
|
|
1409
|
+
if (desc !== void 0 && typeof desc.value === "string") {
|
|
1410
|
+
return desc.value;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
return "";
|
|
1414
|
+
}
|
|
1415
|
+
const oldAnthropicBaseUrl = getOldUrl(p, "anthropicBaseUrl");
|
|
1416
|
+
const oldOpenaiBaseUrl = getOldUrl(p, "openaiBaseUrl");
|
|
1417
|
+
if (p.format !== void 0 && oldAnthropicBaseUrl === "" && oldOpenaiBaseUrl === "") {
|
|
1418
|
+
return p;
|
|
1419
|
+
}
|
|
1420
|
+
const newAnthropicUrl = oldAnthropicBaseUrl !== "" ? oldAnthropicBaseUrl : p.anthropicBaseUrl ?? "";
|
|
1421
|
+
const newOpenaiUrl = oldOpenaiBaseUrl !== "" ? oldOpenaiBaseUrl : p.openaiBaseUrl ?? "";
|
|
1422
|
+
let format;
|
|
1423
|
+
let baseUrl;
|
|
1424
|
+
if (newAnthropicUrl !== "" && newOpenaiUrl !== "") {
|
|
1425
|
+
format = p.format ?? "anthropic";
|
|
1426
|
+
baseUrl = p.baseUrl !== void 0 && p.baseUrl !== "" ? p.baseUrl : newAnthropicUrl;
|
|
1427
|
+
} else if (newOpenaiUrl !== "") {
|
|
1428
|
+
format = "openai";
|
|
1429
|
+
baseUrl = newOpenaiUrl;
|
|
1430
|
+
} else if (newAnthropicUrl !== "") {
|
|
1431
|
+
format = "anthropic";
|
|
1432
|
+
baseUrl = newAnthropicUrl;
|
|
1433
|
+
}
|
|
1434
|
+
migrated = true;
|
|
1435
|
+
return {
|
|
1436
|
+
...p,
|
|
1437
|
+
format,
|
|
1438
|
+
baseUrl,
|
|
1439
|
+
anthropicBaseUrl: newAnthropicUrl,
|
|
1440
|
+
openaiBaseUrl: newOpenaiUrl
|
|
1441
|
+
};
|
|
1442
|
+
});
|
|
1443
|
+
if (migrated) {
|
|
1444
|
+
store.set("providers", updated);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
migrateProviders();
|
|
1448
|
+
function getProviders() {
|
|
1449
|
+
return store.get("providers", []);
|
|
1450
|
+
}
|
|
1451
|
+
function getProvider(id) {
|
|
1452
|
+
const providers = getProviders();
|
|
1453
|
+
return providers.find((p) => p.id === id);
|
|
1454
|
+
}
|
|
1455
|
+
function normalizeApiKey(apiKey) {
|
|
1456
|
+
return apiKey.replace(/^Bearer\s+/i, "").trim();
|
|
1457
|
+
}
|
|
1458
|
+
function addProvider(name, apiKey, format, baseUrl, model, authHeader) {
|
|
1459
|
+
const providers = getProviders();
|
|
1460
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1461
|
+
const newProvider = {
|
|
1462
|
+
id: randomUUID(),
|
|
1463
|
+
name,
|
|
1464
|
+
apiKey: normalizeApiKey(apiKey),
|
|
1465
|
+
format,
|
|
1466
|
+
baseUrl,
|
|
1467
|
+
model,
|
|
1468
|
+
authHeader: authHeader ?? "bearer",
|
|
1469
|
+
createdAt: now,
|
|
1470
|
+
updatedAt: now
|
|
1471
|
+
};
|
|
1472
|
+
providers.push(newProvider);
|
|
1473
|
+
store.set("providers", providers);
|
|
1474
|
+
return newProvider;
|
|
1475
|
+
}
|
|
1476
|
+
function updateProvider(id, updates) {
|
|
1477
|
+
const providers = getProviders();
|
|
1478
|
+
const existing = providers.find((p) => p.id === id);
|
|
1479
|
+
if (!existing) return null;
|
|
1480
|
+
const updated = {
|
|
1481
|
+
id: existing.id,
|
|
1482
|
+
name: updates.name ?? existing.name,
|
|
1483
|
+
apiKey: updates.apiKey !== void 0 ? normalizeApiKey(updates.apiKey) : existing.apiKey,
|
|
1484
|
+
model: updates.model !== void 0 ? updates.model : existing.model,
|
|
1485
|
+
format: updates.format ?? existing.format,
|
|
1486
|
+
baseUrl: updates.baseUrl !== void 0 ? updates.baseUrl : existing.baseUrl,
|
|
1487
|
+
authHeader: updates.authHeader ?? existing.authHeader,
|
|
1488
|
+
createdAt: existing.createdAt,
|
|
1489
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1490
|
+
};
|
|
1491
|
+
const index = providers.findIndex((p) => p.id === id);
|
|
1492
|
+
providers[index] = updated;
|
|
1493
|
+
store.set("providers", providers);
|
|
1494
|
+
return updated;
|
|
1495
|
+
}
|
|
1496
|
+
function deleteProvider(id) {
|
|
1497
|
+
const providers = getProviders();
|
|
1498
|
+
const filtered = providers.filter((p) => p.id !== id);
|
|
1499
|
+
if (filtered.length === providers.length) return false;
|
|
1500
|
+
store.set("providers", filtered);
|
|
1501
|
+
return true;
|
|
1502
|
+
}
|
|
1503
|
+
function getModelUsageName(model, providerName) {
|
|
1504
|
+
if (providerName !== void 0 && providerName !== "" && providerName.toLowerCase().includes("minimax")) {
|
|
1505
|
+
return model.replace(/ /g, "-");
|
|
1506
|
+
}
|
|
1507
|
+
return model;
|
|
1508
|
+
}
|
|
1509
|
+
function normalizeModelName(name) {
|
|
1510
|
+
return name.toLowerCase().replace(/\s+/g, "-");
|
|
1511
|
+
}
|
|
1512
|
+
function findProviderByModel(model) {
|
|
1513
|
+
const providers = getProviders();
|
|
1514
|
+
const modelLower = model.toLowerCase();
|
|
1515
|
+
const modelNormalized = normalizeModelName(model);
|
|
1516
|
+
for (const provider of providers) {
|
|
1517
|
+
const providerPrefix = (provider.name + "-").toLowerCase();
|
|
1518
|
+
if (modelLower.startsWith(providerPrefix)) {
|
|
1519
|
+
return provider;
|
|
1520
|
+
}
|
|
1521
|
+
if (provider.model !== void 0 && provider.model !== "" && modelNormalized === normalizeModelName(provider.model)) {
|
|
1522
|
+
return provider;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
return null;
|
|
1526
|
+
}
|
|
1527
|
+
const execAsync = promisify(exec);
|
|
1528
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1529
|
+
const CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1530
|
+
function getFromCache(port) {
|
|
1531
|
+
const entry = cache.get(port);
|
|
1532
|
+
if (entry && Date.now() < entry.expiresAt) {
|
|
1533
|
+
return { port: entry.port, pid: entry.pid, cwd: entry.cwd, projectFolder: entry.projectFolder };
|
|
1534
|
+
}
|
|
1535
|
+
if (entry) cache.delete(port);
|
|
1536
|
+
return null;
|
|
1537
|
+
}
|
|
1538
|
+
function setCache(port, info) {
|
|
1539
|
+
cache.set(port, { ...info, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
1540
|
+
}
|
|
1541
|
+
function extractRemotePort(request) {
|
|
1542
|
+
const socket = request.socket;
|
|
1543
|
+
const remotePort = socket?.remotePort;
|
|
1544
|
+
if (remotePort !== void 0 && remotePort !== null) return remotePort;
|
|
1545
|
+
return null;
|
|
1546
|
+
}
|
|
1547
|
+
async function lookupPidByPort(port) {
|
|
1548
|
+
try {
|
|
1549
|
+
const { stdout } = await execAsync(`netstat -aon | findstr :${port} | findstr ESTABLISHED`, {
|
|
1550
|
+
windowsHide: true
|
|
1551
|
+
});
|
|
1552
|
+
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
1553
|
+
for (const line of lines) {
|
|
1554
|
+
const parts = line.trim().split(/\s+/);
|
|
1555
|
+
const lastPart = parts[parts.length - 1];
|
|
1556
|
+
if (lastPart !== void 0) {
|
|
1557
|
+
const pid = parseInt(lastPart, 10);
|
|
1558
|
+
if (!isNaN(pid) && pid > 0) return pid;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
return null;
|
|
1562
|
+
} catch {
|
|
1563
|
+
return null;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
async function lookupProcessInfo(pid) {
|
|
1567
|
+
try {
|
|
1568
|
+
const { stdout } = await execAsync(
|
|
1569
|
+
`wmic process where processid=${pid} get commandline /value`,
|
|
1570
|
+
{ windowsHide: true }
|
|
1571
|
+
);
|
|
1572
|
+
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
1573
|
+
for (const line of lines) {
|
|
1574
|
+
if (line.includes("=")) {
|
|
1575
|
+
const commandLine = (line.split("=")[1] ?? "").trim();
|
|
1576
|
+
if (commandLine) {
|
|
1577
|
+
const exeMatch = commandLine.match(/^"([^"]+)"/) || commandLine.match(/^([^\s]+)/);
|
|
1578
|
+
if (exeMatch && exeMatch[1] !== void 0) {
|
|
1579
|
+
const exePath = exeMatch[1];
|
|
1580
|
+
const exeDir = exePath.substring(0, exePath.lastIndexOf("\\"));
|
|
1581
|
+
return {
|
|
1582
|
+
cwd: exeDir,
|
|
1583
|
+
projectFolder: exeDir.split("\\").pop() ?? null
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
return { cwd: null, projectFolder: null };
|
|
1590
|
+
} catch {
|
|
1591
|
+
return { cwd: null, projectFolder: null };
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
async function getClientInfo(request) {
|
|
1595
|
+
const port = extractRemotePort(request);
|
|
1596
|
+
if (port === null) {
|
|
1597
|
+
return { port: null, pid: null, cwd: null, projectFolder: null };
|
|
1598
|
+
}
|
|
1599
|
+
const cached = getFromCache(port);
|
|
1600
|
+
if (cached) return cached;
|
|
1601
|
+
const pid = await lookupPidByPort(port);
|
|
1602
|
+
if (pid === null) {
|
|
1603
|
+
const info2 = { port, pid: null, cwd: null, projectFolder: null };
|
|
1604
|
+
setCache(port, info2);
|
|
1605
|
+
return info2;
|
|
1606
|
+
}
|
|
1607
|
+
const { cwd, projectFolder } = await lookupProcessInfo(pid);
|
|
1608
|
+
const info = { port, pid, cwd, projectFolder };
|
|
1609
|
+
setCache(port, info);
|
|
1610
|
+
return info;
|
|
1611
|
+
}
|
|
1612
|
+
function buildProxyHeaders(originalHeaders) {
|
|
1613
|
+
const rawHeaders = {};
|
|
1614
|
+
const headers = new Headers();
|
|
1615
|
+
originalHeaders.forEach((value, key) => {
|
|
1616
|
+
rawHeaders[key.toLowerCase()] = value;
|
|
1617
|
+
});
|
|
1618
|
+
headers.set(HEADER_USER_AGENT, PROXY_IDENTITY);
|
|
1619
|
+
headers.set(HEADER_X_PROXY_IDENTITY, PROXY_IDENTITY);
|
|
1620
|
+
for (const name of PRESERVE_HEADERS) {
|
|
1621
|
+
const value = originalHeaders.get(name);
|
|
1622
|
+
if (value !== null) {
|
|
1623
|
+
headers.set(name, value);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return { headers, rawHeaders };
|
|
1627
|
+
}
|
|
1628
|
+
function getHostFromUrl$1(urlStr) {
|
|
1629
|
+
try {
|
|
1630
|
+
const url = new URL(urlStr);
|
|
1631
|
+
return url.host;
|
|
1632
|
+
} catch {
|
|
1633
|
+
return "api.anthropic.com";
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
function buildFileLogEntry(log, upstreamUrl) {
|
|
1637
|
+
return {
|
|
1638
|
+
timestamp: log.timestamp,
|
|
1639
|
+
id: log.id,
|
|
1640
|
+
method: log.method,
|
|
1641
|
+
path: log.path,
|
|
1642
|
+
model: log.model,
|
|
1643
|
+
sessionId: log.sessionId,
|
|
1644
|
+
rawRequestBody: log.rawRequestBody,
|
|
1645
|
+
responseStatus: log.responseStatus,
|
|
1646
|
+
responseText: log.responseText,
|
|
1647
|
+
inputTokens: log.inputTokens,
|
|
1648
|
+
outputTokens: log.outputTokens,
|
|
1649
|
+
elapsedMs: log.elapsedMs,
|
|
1650
|
+
streaming: log.streaming,
|
|
1651
|
+
userAgent: log.userAgent,
|
|
1652
|
+
origin: log.origin,
|
|
1653
|
+
upstreamUrl,
|
|
1654
|
+
clientPort: log.clientPort,
|
|
1655
|
+
clientPid: log.clientPid,
|
|
1656
|
+
clientCwd: log.clientCwd,
|
|
1657
|
+
clientProjectFolder: log.clientProjectFolder,
|
|
1658
|
+
streamingChunks: log.streamingChunks,
|
|
1659
|
+
streamingChunksPath: log.streamingChunksPath
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
function parseRequestPath(req, url) {
|
|
1663
|
+
const apiPath = url.pathname.replace(/^\/proxy/, "") + url.search;
|
|
1664
|
+
const messagesPath = apiPath.split("?")[0] ?? "";
|
|
1665
|
+
const isChatCompletionsV1 = messagesPath === PATH_V1_CHAT_COMPLETIONS;
|
|
1666
|
+
const isChatCompletions = messagesPath === PATH_CHAT_COMPLETIONS || isChatCompletionsV1;
|
|
1667
|
+
const isMessages = req.method === "POST" && (messagesPath === PATH_V1_MESSAGES || messagesPath === PATH_V1_CHAT_COMPLETIONS || messagesPath === PATH_CHAT_COMPLETIONS);
|
|
1668
|
+
const normalizedPath = isChatCompletions && !apiPath.startsWith("/v1/") ? "/v1" + apiPath : apiPath;
|
|
1669
|
+
return {
|
|
1670
|
+
apiPath,
|
|
1671
|
+
messagesPath,
|
|
1672
|
+
isChatCompletionsV1,
|
|
1673
|
+
isChatCompletions,
|
|
1674
|
+
isMessages,
|
|
1675
|
+
normalizedPath
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
function buildUpstreamUrl$1(upstreamBase, normalizedPath) {
|
|
1679
|
+
return upstreamBase + normalizedPath;
|
|
1680
|
+
}
|
|
1681
|
+
function selectUpstreamBase$1(isChatCompletions, matchedProviderConfig) {
|
|
1682
|
+
let upstreamBase;
|
|
1683
|
+
if (matchedProviderConfig) {
|
|
1684
|
+
if (isChatCompletions && matchedProviderConfig.openaiBaseUrl !== void 0 && matchedProviderConfig.openaiBaseUrl !== "") {
|
|
1685
|
+
upstreamBase = matchedProviderConfig.openaiBaseUrl;
|
|
1686
|
+
} else if (!isChatCompletions && matchedProviderConfig.anthropicBaseUrl !== void 0 && matchedProviderConfig.anthropicBaseUrl !== "") {
|
|
1687
|
+
upstreamBase = matchedProviderConfig.anthropicBaseUrl;
|
|
1688
|
+
} else if (matchedProviderConfig.baseUrl !== void 0 && matchedProviderConfig.baseUrl !== "") {
|
|
1689
|
+
upstreamBase = matchedProviderConfig.baseUrl;
|
|
1690
|
+
} else {
|
|
1691
|
+
upstreamBase = matchedProviderConfig.format === "openai" ? DEFAULT_OPENAI_UPSTREAM$1 : DEFAULT_UPSTREAM$1;
|
|
1692
|
+
}
|
|
1693
|
+
} else {
|
|
1694
|
+
upstreamBase = isChatCompletions ? DEFAULT_OPENAI_UPSTREAM$1 : DEFAULT_UPSTREAM$1;
|
|
1695
|
+
}
|
|
1696
|
+
return upstreamBase;
|
|
1697
|
+
}
|
|
1698
|
+
function injectAuthHeaders$1(upstreamHeaders, matchedProviderConfig) {
|
|
1699
|
+
if (!matchedProviderConfig) return;
|
|
1700
|
+
const apiKey = matchedProviderConfig.apiKey.replace(/^Bearer\s+/i, "").trim();
|
|
1701
|
+
if (matchedProviderConfig.authHeader === AUTH_HEADER_X_API_KEY) {
|
|
1702
|
+
upstreamHeaders.set(HEADER_X_API_KEY, apiKey);
|
|
1703
|
+
upstreamHeaders.delete(HEADER_AUTHORIZATION);
|
|
1704
|
+
} else {
|
|
1705
|
+
upstreamHeaders.set(HEADER_AUTHORIZATION, `Bearer ${apiKey}`);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
function handleNonStreamingResponse(upstreamRes, responseBody, startTime, formatHandler, upstreamUrl, log) {
|
|
1709
|
+
const elapsedMs = Date.now() - startTime;
|
|
1710
|
+
const tokens = formatHandler.extractTokens(responseBody);
|
|
1711
|
+
log.elapsedMs = elapsedMs;
|
|
1712
|
+
log.responseStatus = upstreamRes.status;
|
|
1713
|
+
log.responseText = responseBody;
|
|
1714
|
+
log.inputTokens = tokens.inputTokens;
|
|
1715
|
+
log.outputTokens = tokens.outputTokens;
|
|
1716
|
+
log.cacheCreationInputTokens = tokens.cacheCreationInputTokens;
|
|
1717
|
+
log.cacheReadInputTokens = tokens.cacheReadInputTokens;
|
|
1718
|
+
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: null });
|
|
1719
|
+
const responseHeaders = new Headers(upstreamRes.headers);
|
|
1720
|
+
responseHeaders.delete(HEADER_CONTENT_ENCODING);
|
|
1721
|
+
responseHeaders.delete(HEADER_CONTENT_LENGTH);
|
|
1722
|
+
return new Response(responseBody, {
|
|
1723
|
+
status: upstreamRes.status,
|
|
1724
|
+
headers: responseHeaders
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
function handleStreamingResponse(upstreamRes, req, startTime, formatHandler, upstreamUrl, log) {
|
|
1728
|
+
log.streaming = true;
|
|
1729
|
+
log.responseStatus = upstreamRes.status;
|
|
1730
|
+
const chunks = [];
|
|
1731
|
+
const decoder = new TextDecoder();
|
|
1732
|
+
const transform = new TransformStream({
|
|
1733
|
+
transform(chunk, controller) {
|
|
1734
|
+
controller.enqueue(chunk);
|
|
1735
|
+
chunks.push(decoder.decode(chunk, { stream: true }));
|
|
1736
|
+
},
|
|
1737
|
+
flush() {
|
|
1738
|
+
const full = chunks.join("");
|
|
1739
|
+
log.elapsedMs = Date.now() - startTime;
|
|
1740
|
+
log.responseText = formatHandler.extractStream(full, log, log.model ?? void 0, true);
|
|
1741
|
+
if (log.streamingChunks && log.streamingChunks.chunks.length > 0) {
|
|
1742
|
+
const chunkPath = writeChunks(
|
|
1743
|
+
log.id,
|
|
1744
|
+
log.streamingChunks.chunks,
|
|
1745
|
+
log.streamingChunks.truncated
|
|
1746
|
+
);
|
|
1747
|
+
log.streamingChunksPath = chunkPath;
|
|
1748
|
+
}
|
|
1749
|
+
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: null });
|
|
1750
|
+
emitLogUpdate(log);
|
|
1751
|
+
}
|
|
1752
|
+
});
|
|
1753
|
+
if (upstreamRes.body === null) {
|
|
1754
|
+
return new Response("No response body", { status: STATUS_BAD_GATEWAY });
|
|
1755
|
+
}
|
|
1756
|
+
const loggedStream = upstreamRes.body.pipeThrough(transform);
|
|
1757
|
+
req.signal?.addEventListener("abort", () => {
|
|
1758
|
+
if (log.responseText === null && chunks.length > 0) {
|
|
1759
|
+
const full = chunks.join("");
|
|
1760
|
+
log.elapsedMs = Date.now() - startTime;
|
|
1761
|
+
log.responseText = formatHandler.extractStream(full, log, log.model ?? void 0, true);
|
|
1762
|
+
if (log.streamingChunks && log.streamingChunks.chunks.length > 0) {
|
|
1763
|
+
const chunkPath = writeChunks(
|
|
1764
|
+
log.id,
|
|
1765
|
+
log.streamingChunks.chunks,
|
|
1766
|
+
log.streamingChunks.truncated
|
|
1767
|
+
);
|
|
1768
|
+
log.streamingChunksPath = chunkPath;
|
|
1769
|
+
}
|
|
1770
|
+
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: "Client aborted" });
|
|
1771
|
+
emitLogUpdate(log);
|
|
1772
|
+
}
|
|
1773
|
+
});
|
|
1774
|
+
const responseHeaders = new Headers(upstreamRes.headers);
|
|
1775
|
+
responseHeaders.delete(HEADER_CONTENT_ENCODING);
|
|
1776
|
+
responseHeaders.delete(HEADER_CONTENT_LENGTH);
|
|
1777
|
+
return new Response(loggedStream, {
|
|
1778
|
+
status: upstreamRes.status,
|
|
1779
|
+
headers: responseHeaders
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
async function handleProxy(req) {
|
|
1783
|
+
const url = new URL(req.url);
|
|
1784
|
+
const parsed = parseRequestPath(req, url);
|
|
1785
|
+
let requestBody = null;
|
|
1786
|
+
if (req.body && req.method !== "GET" && req.method !== "HEAD") {
|
|
1787
|
+
requestBody = await req.text();
|
|
1788
|
+
}
|
|
1789
|
+
const model = requestBody !== null ? extractModelFromBody(requestBody) : null;
|
|
1790
|
+
const matchedProviderConfig = findProviderByModelFromConfig(requestBody);
|
|
1791
|
+
const upstreamBase = selectUpstreamBase$1(parsed.isChatCompletions, matchedProviderConfig);
|
|
1792
|
+
const upstreamUrl = buildUpstreamUrl$1(upstreamBase, parsed.normalizedPath);
|
|
1793
|
+
const upstreamHost = getHostFromUrl$1(upstreamBase);
|
|
1794
|
+
const startTime = Date.now();
|
|
1795
|
+
const { headers: upstreamHeaders, rawHeaders } = buildProxyHeaders(req.headers);
|
|
1796
|
+
upstreamHeaders.set(HEADER_HOST, upstreamHost);
|
|
1797
|
+
injectAuthHeaders$1(upstreamHeaders, matchedProviderConfig);
|
|
1798
|
+
const provider = model !== null ? registry.findProvider(model) : null;
|
|
1799
|
+
if (model === null || provider === null) {
|
|
1800
|
+
return new Response("Forbidden: unsupported provider", { status: STATUS_FORBIDDEN });
|
|
1801
|
+
}
|
|
1802
|
+
let formatHandler;
|
|
1803
|
+
if (matchedProviderConfig?.format) {
|
|
1804
|
+
formatHandler = formatRegistry.get(matchedProviderConfig.format) ?? null;
|
|
1805
|
+
} else {
|
|
1806
|
+
formatHandler = formatForPath(parsed.apiPath);
|
|
1807
|
+
}
|
|
1808
|
+
if (formatHandler === null) {
|
|
1809
|
+
return new Response("Forbidden: unsupported format", { status: STATUS_FORBIDDEN });
|
|
1810
|
+
}
|
|
1811
|
+
const clientInfo = await getClientInfo(req);
|
|
1812
|
+
const upstreamHeadersObj = {};
|
|
1813
|
+
upstreamHeaders.forEach((value, key) => {
|
|
1814
|
+
upstreamHeadersObj[key.toLowerCase()] = value;
|
|
1815
|
+
});
|
|
1816
|
+
const log = await createLog(
|
|
1817
|
+
req.method,
|
|
1818
|
+
parsed.apiPath,
|
|
1819
|
+
requestBody,
|
|
1820
|
+
req.headers,
|
|
1821
|
+
clientInfo,
|
|
1822
|
+
rawHeaders,
|
|
1823
|
+
upstreamHeadersObj,
|
|
1824
|
+
formatHandler.format
|
|
1825
|
+
);
|
|
1826
|
+
let upstreamRes;
|
|
1827
|
+
try {
|
|
1828
|
+
upstreamRes = await fetch(upstreamUrl, {
|
|
1829
|
+
method: req.method,
|
|
1830
|
+
headers: upstreamHeaders,
|
|
1831
|
+
body: requestBody
|
|
1832
|
+
});
|
|
1833
|
+
} catch (err) {
|
|
1834
|
+
log.elapsedMs = Date.now() - startTime;
|
|
1835
|
+
log.responseStatus = STATUS_BAD_GATEWAY;
|
|
1836
|
+
log.responseText = String(err);
|
|
1837
|
+
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: String(err) });
|
|
1838
|
+
return new Response(`Proxy error: ${err}`, { status: STATUS_BAD_GATEWAY });
|
|
1839
|
+
}
|
|
1840
|
+
const isStream = upstreamRes.headers.get(HEADER_CONTENT_TYPE)?.includes(CONTENT_TYPE_EVENT_STREAM) ?? false;
|
|
1841
|
+
if (!isStream) {
|
|
1842
|
+
const responseBody = await upstreamRes.text();
|
|
1843
|
+
return handleNonStreamingResponse(
|
|
1844
|
+
upstreamRes,
|
|
1845
|
+
responseBody,
|
|
1846
|
+
startTime,
|
|
1847
|
+
formatHandler,
|
|
1848
|
+
upstreamUrl,
|
|
1849
|
+
log
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1852
|
+
return handleStreamingResponse(upstreamRes, req, startTime, formatHandler, upstreamUrl, log);
|
|
1853
|
+
}
|
|
1854
|
+
function findProviderByModelFromConfig(requestBody) {
|
|
1855
|
+
if (requestBody === null) return null;
|
|
1856
|
+
const model = extractModelFromBody(requestBody);
|
|
1857
|
+
if (model === null) return null;
|
|
1858
|
+
return findProviderByModel(model);
|
|
1859
|
+
}
|
|
1860
|
+
const Route$c = createFileRoute("/proxy/$")({
|
|
1861
|
+
server: {
|
|
1862
|
+
handlers: {
|
|
1863
|
+
GET: ({ request }) => handleProxy(request),
|
|
1864
|
+
POST: ({ request }) => handleProxy(request),
|
|
1865
|
+
PUT: ({ request }) => handleProxy(request),
|
|
1866
|
+
DELETE: ({ request }) => handleProxy(request),
|
|
1867
|
+
PATCH: ({ request }) => handleProxy(request),
|
|
1868
|
+
OPTIONS: ({ request }) => handleProxy(request)
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
});
|
|
1872
|
+
const Route$b = createFileRoute("/api/sessions")({
|
|
1873
|
+
server: {
|
|
1874
|
+
handlers: {
|
|
1875
|
+
GET: () => Response.json(getSessions())
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
});
|
|
1879
|
+
const ProviderInputSchema = object({
|
|
1880
|
+
name: string().min(1, "Name is required"),
|
|
1881
|
+
apiKey: string().min(1, "API key is required"),
|
|
1882
|
+
format: _enum(["anthropic", "openai"]),
|
|
1883
|
+
baseUrl: string().min(1, "Base URL is required"),
|
|
1884
|
+
model: string().min(1, "Model is required"),
|
|
1885
|
+
authHeader: _enum(["bearer", "x-api-key"]).optional().default("bearer")
|
|
1886
|
+
});
|
|
1887
|
+
const Route$a = createFileRoute("/api/providers")({
|
|
1888
|
+
server: {
|
|
1889
|
+
handlers: {
|
|
1890
|
+
GET: () => {
|
|
1891
|
+
return Response.json(getProviders());
|
|
1892
|
+
},
|
|
1893
|
+
POST: async ({ request }) => {
|
|
1894
|
+
const parsed = ProviderInputSchema.safeParse(await request.json());
|
|
1895
|
+
if (!parsed.success) {
|
|
1896
|
+
return Response.json({ error: parsed.error.message }, { status: 400 });
|
|
1897
|
+
}
|
|
1898
|
+
const newProvider = addProvider(
|
|
1899
|
+
parsed.data.name,
|
|
1900
|
+
parsed.data.apiKey,
|
|
1901
|
+
parsed.data.format,
|
|
1902
|
+
parsed.data.baseUrl,
|
|
1903
|
+
parsed.data.model,
|
|
1904
|
+
parsed.data.authHeader
|
|
1905
|
+
);
|
|
1906
|
+
return Response.json(newProvider, { status: 201 });
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
});
|
|
1911
|
+
const Route$9 = createFileRoute("/api/models")({
|
|
1912
|
+
server: {
|
|
1913
|
+
handlers: {
|
|
1914
|
+
GET: () => Response.json(getModels())
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
});
|
|
1918
|
+
const Route$8 = createFileRoute("/api/logs")({
|
|
1919
|
+
server: {
|
|
1920
|
+
handlers: {
|
|
1921
|
+
GET: ({ request }) => {
|
|
1922
|
+
const url = new URL(request.url);
|
|
1923
|
+
const sessionId = url.searchParams.get("sessionId") ?? void 0;
|
|
1924
|
+
const model = url.searchParams.get("model") ?? void 0;
|
|
1925
|
+
const offset = Number(url.searchParams.get("offset") ?? 0);
|
|
1926
|
+
const limit = Number(url.searchParams.get("limit") ?? 50);
|
|
1927
|
+
const allLogs = getFilteredLogs(sessionId, model);
|
|
1928
|
+
const paginatedLogs = allLogs.slice(offset, offset + limit);
|
|
1929
|
+
return Response.json({
|
|
1930
|
+
logs: paginatedLogs,
|
|
1931
|
+
total: allLogs.length,
|
|
1932
|
+
offset,
|
|
1933
|
+
limit
|
|
1934
|
+
});
|
|
1935
|
+
},
|
|
1936
|
+
DELETE: () => {
|
|
1937
|
+
const result = clearAllLogs();
|
|
1938
|
+
return Response.json({ success: true, cleared: result.cleared });
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
});
|
|
1943
|
+
const Route$7 = createFileRoute("/api/health")({
|
|
1944
|
+
server: {
|
|
1945
|
+
handlers: {
|
|
1946
|
+
GET: () => {
|
|
1947
|
+
return Response.json({ status: "ok" });
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
});
|
|
1952
|
+
const ProviderUpdateSchema = object({
|
|
1953
|
+
name: string().min(1, "Name is required").optional(),
|
|
1954
|
+
apiKey: string().min(1, "API key is required").optional(),
|
|
1955
|
+
format: _enum(["anthropic", "openai"]).optional(),
|
|
1956
|
+
baseUrl: string().min(1, "Base URL is required").optional(),
|
|
1957
|
+
model: string().min(1, "Model is required").optional(),
|
|
1958
|
+
authHeader: _enum(["bearer", "x-api-key"]).optional()
|
|
1959
|
+
});
|
|
1960
|
+
const Route$6 = createFileRoute("/api/providers/$providerId")({
|
|
1961
|
+
server: {
|
|
1962
|
+
handlers: {
|
|
1963
|
+
GET: ({ params }) => {
|
|
1964
|
+
const providers = getProviders();
|
|
1965
|
+
const provider = providers.find((p) => p.id === params.providerId);
|
|
1966
|
+
if (!provider) {
|
|
1967
|
+
return Response.json({ error: "Provider not found" }, { status: 404 });
|
|
1968
|
+
}
|
|
1969
|
+
return Response.json(provider);
|
|
1970
|
+
},
|
|
1971
|
+
PUT: async ({ params, request }) => {
|
|
1972
|
+
const parsed = ProviderUpdateSchema.safeParse(await request.json());
|
|
1973
|
+
if (!parsed.success) {
|
|
1974
|
+
return Response.json({ error: parsed.error.message }, { status: 400 });
|
|
1975
|
+
}
|
|
1976
|
+
const updated = updateProvider(params.providerId, parsed.data);
|
|
1977
|
+
if (!updated) {
|
|
1978
|
+
return Response.json({ error: "Provider not found" }, { status: 404 });
|
|
1979
|
+
}
|
|
1980
|
+
return Response.json(updated);
|
|
1981
|
+
},
|
|
1982
|
+
DELETE: ({ params }) => {
|
|
1983
|
+
const deleted = deleteProvider(params.providerId);
|
|
1984
|
+
if (!deleted) {
|
|
1985
|
+
return Response.json({ error: "Provider not found" }, { status: 404 });
|
|
1986
|
+
}
|
|
1987
|
+
return Response.json({ success: true });
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
});
|
|
1992
|
+
const Route$5 = createFileRoute("/api/logs/stream")({
|
|
1993
|
+
server: {
|
|
1994
|
+
handlers: {
|
|
1995
|
+
GET: ({ request }) => {
|
|
1996
|
+
const url = new URL(request.url);
|
|
1997
|
+
const sessionId = url.searchParams.get("sessionId") ?? void 0;
|
|
1998
|
+
const model = url.searchParams.get("model") ?? void 0;
|
|
1999
|
+
let controllerRef = null;
|
|
2000
|
+
const heartbeat = setInterval(() => {
|
|
2001
|
+
if (controllerRef) {
|
|
2002
|
+
try {
|
|
2003
|
+
controllerRef.enqueue(new TextEncoder().encode(": heartbeat\n\n"));
|
|
2004
|
+
} catch {
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
}, 3e4);
|
|
2008
|
+
const unsubscribe = onLogUpdate((log) => {
|
|
2009
|
+
if (!controllerRef) return;
|
|
2010
|
+
if (sessionId !== void 0 && log.sessionId !== sessionId) return;
|
|
2011
|
+
if (model !== void 0 && log.model !== model) return;
|
|
2012
|
+
try {
|
|
2013
|
+
const data = `data: ${JSON.stringify({ type: "update", log })}
|
|
2014
|
+
|
|
2015
|
+
`;
|
|
2016
|
+
controllerRef.enqueue(new TextEncoder().encode(data));
|
|
2017
|
+
} catch {
|
|
2018
|
+
}
|
|
2019
|
+
});
|
|
2020
|
+
const stream = new ReadableStream({
|
|
2021
|
+
start(controller) {
|
|
2022
|
+
controllerRef = controller;
|
|
2023
|
+
const logs = getFilteredLogs(sessionId, model);
|
|
2024
|
+
const initData = `data: ${JSON.stringify({ type: "init", logs })}
|
|
2025
|
+
|
|
2026
|
+
`;
|
|
2027
|
+
controller.enqueue(new TextEncoder().encode(initData));
|
|
2028
|
+
},
|
|
2029
|
+
cancel() {
|
|
2030
|
+
clearInterval(heartbeat);
|
|
2031
|
+
unsubscribe();
|
|
2032
|
+
controllerRef = null;
|
|
2033
|
+
}
|
|
2034
|
+
});
|
|
2035
|
+
return new Response(stream, {
|
|
2036
|
+
headers: {
|
|
2037
|
+
"Content-Type": "text/event-stream",
|
|
2038
|
+
"Cache-Control": "no-cache",
|
|
2039
|
+
Connection: "keep-alive"
|
|
2040
|
+
}
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
});
|
|
2046
|
+
const Route$4 = createFileRoute("/api/logs/$id")({
|
|
2047
|
+
server: {
|
|
2048
|
+
handlers: {
|
|
2049
|
+
GET: async ({ params }) => {
|
|
2050
|
+
const id = Number(params.id);
|
|
2051
|
+
if (isNaN(id)) {
|
|
2052
|
+
return Response.json({ error: "Invalid ID" }, { status: 400 });
|
|
2053
|
+
}
|
|
2054
|
+
const log = await getLogById(id);
|
|
2055
|
+
if (log === null) {
|
|
2056
|
+
return Response.json({ error: "Log not found" }, { status: 404 });
|
|
2057
|
+
}
|
|
2058
|
+
return Response.json(log);
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
});
|
|
2063
|
+
const Route$3 = createFileRoute("/api/config/paths")({
|
|
2064
|
+
server: {
|
|
2065
|
+
handlers: {
|
|
2066
|
+
GET: () => {
|
|
2067
|
+
return Response.json({
|
|
2068
|
+
providerConfig: store.path
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
});
|
|
2074
|
+
const AnthropicResponseSchema = object({
|
|
2075
|
+
id: string().optional(),
|
|
2076
|
+
type: string().optional(),
|
|
2077
|
+
model: string().optional(),
|
|
2078
|
+
usage: object({
|
|
2079
|
+
input_tokens: number().optional(),
|
|
2080
|
+
output_tokens: number().optional()
|
|
2081
|
+
}).optional(),
|
|
2082
|
+
content: array(
|
|
2083
|
+
discriminatedUnion("type", [
|
|
2084
|
+
object({ type: literal("text"), text: string() }),
|
|
2085
|
+
object({ type: literal("thinking"), thinking: string() })
|
|
2086
|
+
])
|
|
2087
|
+
)
|
|
2088
|
+
});
|
|
2089
|
+
const OpenAIResponseSchema = object({
|
|
2090
|
+
id: string().optional(),
|
|
2091
|
+
model: string().optional(),
|
|
2092
|
+
usage: object({
|
|
2093
|
+
prompt_tokens: number().optional(),
|
|
2094
|
+
completion_tokens: number().optional()
|
|
2095
|
+
}).optional(),
|
|
2096
|
+
choices: array(
|
|
2097
|
+
object({
|
|
2098
|
+
message: object({
|
|
2099
|
+
role: string(),
|
|
2100
|
+
content: string().nullable()
|
|
2101
|
+
}),
|
|
2102
|
+
finish_reason: string().nullable()
|
|
2103
|
+
})
|
|
2104
|
+
)
|
|
2105
|
+
});
|
|
2106
|
+
async function testEndpoint(baseUrl, apiKey, path2, model, isOpenAI) {
|
|
2107
|
+
const startTime = Date.now();
|
|
2108
|
+
const body = JSON.stringify({
|
|
2109
|
+
model,
|
|
2110
|
+
messages: [{ role: "user", content: "say hello and briefly introduce yourself" }],
|
|
2111
|
+
max_tokens: 1024
|
|
2112
|
+
});
|
|
2113
|
+
try {
|
|
2114
|
+
const url = `${baseUrl}${path2}`;
|
|
2115
|
+
const response = await fetch(url, {
|
|
2116
|
+
method: "POST",
|
|
2117
|
+
headers: {
|
|
2118
|
+
"Content-Type": "application/json",
|
|
2119
|
+
Authorization: `Bearer ${apiKey}`
|
|
2120
|
+
},
|
|
2121
|
+
body
|
|
2122
|
+
});
|
|
2123
|
+
const latencyMs = Date.now() - startTime;
|
|
2124
|
+
const responseText = await response.text();
|
|
2125
|
+
if (!response.ok) {
|
|
2126
|
+
return {
|
|
2127
|
+
success: false,
|
|
2128
|
+
error: `HTTP ${response.status}: ${responseText.slice(0, 200)}`,
|
|
2129
|
+
latencyMs
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
try {
|
|
2133
|
+
if (isOpenAI) {
|
|
2134
|
+
const json = OpenAIResponseSchema.parse(JSON.parse(responseText));
|
|
2135
|
+
const responseModel = json.model ?? model;
|
|
2136
|
+
const inputTokens = json.usage?.prompt_tokens;
|
|
2137
|
+
const outputTokens = json.usage?.completion_tokens;
|
|
2138
|
+
const content = json.choices?.[0]?.message?.content ?? null;
|
|
2139
|
+
return {
|
|
2140
|
+
success: true,
|
|
2141
|
+
model: responseModel,
|
|
2142
|
+
inputTokens,
|
|
2143
|
+
outputTokens,
|
|
2144
|
+
latencyMs,
|
|
2145
|
+
content: content !== null && content !== "" ? [{ type: "text", text: content }] : void 0,
|
|
2146
|
+
rawResponse: responseText
|
|
2147
|
+
};
|
|
2148
|
+
} else {
|
|
2149
|
+
const json = AnthropicResponseSchema.parse(JSON.parse(responseText));
|
|
2150
|
+
const responseModel = json.model ?? model;
|
|
2151
|
+
const inputTokens = json.usage?.input_tokens;
|
|
2152
|
+
const outputTokens = json.usage?.output_tokens;
|
|
2153
|
+
const content = [];
|
|
2154
|
+
for (const block of json.content ?? []) {
|
|
2155
|
+
if (block.type === "text" && block.text) {
|
|
2156
|
+
content.push({ type: "text", text: block.text });
|
|
2157
|
+
} else if (block.type === "thinking" && block.thinking) {
|
|
2158
|
+
content.push({ type: "thinking", thinking: block.thinking });
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
return {
|
|
2162
|
+
success: true,
|
|
2163
|
+
model: responseModel,
|
|
2164
|
+
inputTokens,
|
|
2165
|
+
outputTokens,
|
|
2166
|
+
latencyMs,
|
|
2167
|
+
content: content.length > 0 ? content : void 0,
|
|
2168
|
+
rawResponse: responseText
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2171
|
+
} catch (parseErr) {
|
|
2172
|
+
return {
|
|
2173
|
+
success: true,
|
|
2174
|
+
model,
|
|
2175
|
+
latencyMs,
|
|
2176
|
+
content: [{ type: "text", text: responseText.slice(0, 2e3) }],
|
|
2177
|
+
rawResponse: responseText
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2180
|
+
} catch (err) {
|
|
2181
|
+
return {
|
|
2182
|
+
success: false,
|
|
2183
|
+
error: String(err),
|
|
2184
|
+
latencyMs: Date.now() - startTime
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
async function testStreamingEndpoint(baseUrl, apiKey, path2, model, isOpenAI) {
|
|
2189
|
+
const startTime = Date.now();
|
|
2190
|
+
const body = JSON.stringify({
|
|
2191
|
+
model,
|
|
2192
|
+
messages: [{ role: "user", content: "say hello" }],
|
|
2193
|
+
max_tokens: 256,
|
|
2194
|
+
stream: true
|
|
2195
|
+
});
|
|
2196
|
+
try {
|
|
2197
|
+
const url = `${baseUrl}${path2}`;
|
|
2198
|
+
const response = await fetch(url, {
|
|
2199
|
+
method: "POST",
|
|
2200
|
+
headers: {
|
|
2201
|
+
"Content-Type": "application/json",
|
|
2202
|
+
Authorization: `Bearer ${apiKey}`
|
|
2203
|
+
},
|
|
2204
|
+
body
|
|
2205
|
+
});
|
|
2206
|
+
const latencyMs = Date.now() - startTime;
|
|
2207
|
+
if (!response.ok) {
|
|
2208
|
+
const responseText = await response.text();
|
|
2209
|
+
return {
|
|
2210
|
+
success: false,
|
|
2211
|
+
error: `HTTP ${response.status}: ${responseText.slice(0, 200)}`,
|
|
2212
|
+
latencyMs,
|
|
2213
|
+
streaming: true
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
const chunks = [];
|
|
2217
|
+
const reader = response.body?.getReader();
|
|
2218
|
+
if (!reader) {
|
|
2219
|
+
return {
|
|
2220
|
+
success: false,
|
|
2221
|
+
error: "No response body",
|
|
2222
|
+
latencyMs,
|
|
2223
|
+
streaming: true
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
const decoder = new TextDecoder();
|
|
2227
|
+
try {
|
|
2228
|
+
while (true) {
|
|
2229
|
+
const { done, value } = await reader.read();
|
|
2230
|
+
if (done) break;
|
|
2231
|
+
chunks.push(decoder.decode(value, { stream: true }));
|
|
2232
|
+
}
|
|
2233
|
+
} catch (readErr) {
|
|
2234
|
+
return {
|
|
2235
|
+
success: false,
|
|
2236
|
+
error: `Stream read error: ${readErr}`,
|
|
2237
|
+
latencyMs,
|
|
2238
|
+
streaming: true
|
|
2239
|
+
};
|
|
2240
|
+
}
|
|
2241
|
+
const fullResponse = chunks.join("");
|
|
2242
|
+
const mockLog = {
|
|
2243
|
+
id: 0,
|
|
2244
|
+
timestamp: "",
|
|
2245
|
+
method: "POST",
|
|
2246
|
+
path: "",
|
|
2247
|
+
model: null,
|
|
2248
|
+
sessionId: null,
|
|
2249
|
+
rawRequestBody: null,
|
|
2250
|
+
responseStatus: null,
|
|
2251
|
+
responseText: null,
|
|
2252
|
+
inputTokens: null,
|
|
2253
|
+
outputTokens: null,
|
|
2254
|
+
cacheCreationInputTokens: null,
|
|
2255
|
+
cacheReadInputTokens: null,
|
|
2256
|
+
elapsedMs: 0,
|
|
2257
|
+
streaming: true,
|
|
2258
|
+
userAgent: null,
|
|
2259
|
+
origin: null,
|
|
2260
|
+
apiFormat: "anthropic",
|
|
2261
|
+
isTest: true
|
|
2262
|
+
};
|
|
2263
|
+
let reconstructedJson;
|
|
2264
|
+
if (isOpenAI) {
|
|
2265
|
+
reconstructedJson = extractOpenAIStream(fullResponse, mockLog, model, true);
|
|
2266
|
+
} else {
|
|
2267
|
+
reconstructedJson = extractAnthropicStream(fullResponse, mockLog, model, true);
|
|
2268
|
+
}
|
|
2269
|
+
const streamingChunks = mockLog.streamingChunks ?? void 0;
|
|
2270
|
+
try {
|
|
2271
|
+
let content = [];
|
|
2272
|
+
let inputTokens;
|
|
2273
|
+
let outputTokens;
|
|
2274
|
+
let responseModel;
|
|
2275
|
+
if (isOpenAI) {
|
|
2276
|
+
const parsed = OpenAIResponseSchema.parse(JSON.parse(reconstructedJson));
|
|
2277
|
+
responseModel = typeof parsed.model === "string" && parsed.model !== "" ? parsed.model : void 0;
|
|
2278
|
+
inputTokens = parsed.usage?.prompt_tokens;
|
|
2279
|
+
outputTokens = parsed.usage?.completion_tokens;
|
|
2280
|
+
const textContent = parsed.choices?.[0]?.message?.content;
|
|
2281
|
+
if (textContent !== null && textContent !== "") {
|
|
2282
|
+
content = [{ type: "text", text: textContent }];
|
|
2283
|
+
}
|
|
2284
|
+
} else {
|
|
2285
|
+
const parsed = AnthropicResponseSchema.parse(JSON.parse(reconstructedJson));
|
|
2286
|
+
responseModel = typeof parsed.model === "string" && parsed.model !== "" ? parsed.model : void 0;
|
|
2287
|
+
inputTokens = parsed.usage?.input_tokens;
|
|
2288
|
+
outputTokens = parsed.usage?.output_tokens;
|
|
2289
|
+
for (const block of parsed.content ?? []) {
|
|
2290
|
+
if (block.type === "text" && block.text) {
|
|
2291
|
+
content.push({ type: "text", text: block.text });
|
|
2292
|
+
} else if (block.type === "thinking" && block.thinking) {
|
|
2293
|
+
content.push({ type: "thinking", thinking: block.thinking });
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
return {
|
|
2298
|
+
success: true,
|
|
2299
|
+
model: responseModel ?? model,
|
|
2300
|
+
inputTokens,
|
|
2301
|
+
outputTokens,
|
|
2302
|
+
latencyMs,
|
|
2303
|
+
content: content.length > 0 ? content : void 0,
|
|
2304
|
+
rawResponse: reconstructedJson,
|
|
2305
|
+
streaming: true,
|
|
2306
|
+
streamingChunks
|
|
2307
|
+
};
|
|
2308
|
+
} catch {
|
|
2309
|
+
return {
|
|
2310
|
+
success: true,
|
|
2311
|
+
model,
|
|
2312
|
+
latencyMs,
|
|
2313
|
+
content: [{ type: "text", text: reconstructedJson.slice(0, 2e3) }],
|
|
2314
|
+
rawResponse: reconstructedJson,
|
|
2315
|
+
streaming: true,
|
|
2316
|
+
streamingChunks
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2319
|
+
} catch (err) {
|
|
2320
|
+
return {
|
|
2321
|
+
success: false,
|
|
2322
|
+
error: String(err),
|
|
2323
|
+
latencyMs: Date.now() - startTime,
|
|
2324
|
+
streaming: true
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
function createTestLogEntry(providerName, path2, body, upstreamUrl, result, isTest) {
|
|
2329
|
+
return {
|
|
2330
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2331
|
+
id: `test-${Date.now()}`,
|
|
2332
|
+
method: "POST",
|
|
2333
|
+
path: path2,
|
|
2334
|
+
model: isTest ? result.model : void 0,
|
|
2335
|
+
sessionId: null,
|
|
2336
|
+
rawRequestBody: body,
|
|
2337
|
+
responseStatus: result.success ? 200 : 500,
|
|
2338
|
+
responseText: result.rawResponse ?? JSON.stringify(result),
|
|
2339
|
+
inputTokens: result.inputTokens ?? null,
|
|
2340
|
+
outputTokens: result.outputTokens ?? null,
|
|
2341
|
+
elapsedMs: result.latencyMs ?? 0,
|
|
2342
|
+
streaming: result.streaming ?? false,
|
|
2343
|
+
userAgent: "provider-test",
|
|
2344
|
+
origin: null,
|
|
2345
|
+
upstreamUrl,
|
|
2346
|
+
error: result.success ? null : result.error,
|
|
2347
|
+
isTest: true,
|
|
2348
|
+
providerName
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
const Route$2 = createFileRoute("/api/providers/$providerId/test")({
|
|
2352
|
+
server: {
|
|
2353
|
+
handlers: {
|
|
2354
|
+
POST: async ({ params }) => {
|
|
2355
|
+
const provider = getProvider(params.providerId);
|
|
2356
|
+
if (!provider) {
|
|
2357
|
+
return Response.json({ error: "Provider not found" }, { status: 404 });
|
|
2358
|
+
}
|
|
2359
|
+
if (provider.model === void 0 || provider.model.trim().length === 0) {
|
|
2360
|
+
return Response.json(
|
|
2361
|
+
{ error: "Please configure a model name in provider settings" },
|
|
2362
|
+
{ status: 400 }
|
|
2363
|
+
);
|
|
2364
|
+
}
|
|
2365
|
+
const model = provider.model.trim();
|
|
2366
|
+
const usageModel = getModelUsageName(model, provider.name);
|
|
2367
|
+
const results = {
|
|
2368
|
+
anthropic: { nonStreaming: { notConfigured: true }, streaming: { notConfigured: true } },
|
|
2369
|
+
openai: { nonStreaming: { notConfigured: true }, streaming: { notConfigured: true } }
|
|
2370
|
+
};
|
|
2371
|
+
if (provider.anthropicBaseUrl !== void 0 && provider.anthropicBaseUrl.length > 0) {
|
|
2372
|
+
const nonStreamingResult = await testEndpoint(
|
|
2373
|
+
provider.anthropicBaseUrl,
|
|
2374
|
+
provider.apiKey,
|
|
2375
|
+
"/v1/messages",
|
|
2376
|
+
usageModel,
|
|
2377
|
+
false
|
|
2378
|
+
);
|
|
2379
|
+
results.anthropic.nonStreaming = nonStreamingResult;
|
|
2380
|
+
const requestBody = JSON.stringify({
|
|
2381
|
+
model: usageModel,
|
|
2382
|
+
messages: [{ role: "user", content: "say hello and briefly introduce yourself" }],
|
|
2383
|
+
max_tokens: 1024
|
|
2384
|
+
});
|
|
2385
|
+
const upstreamUrl = `${provider.anthropicBaseUrl}/v1/messages`;
|
|
2386
|
+
await addTestLogEntry({
|
|
2387
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2388
|
+
method: "POST",
|
|
2389
|
+
path: "/v1/messages",
|
|
2390
|
+
model: nonStreamingResult.model ?? model,
|
|
2391
|
+
sessionId: null,
|
|
2392
|
+
rawRequestBody: requestBody,
|
|
2393
|
+
responseStatus: nonStreamingResult.success ? 200 : 500,
|
|
2394
|
+
responseText: nonStreamingResult.rawResponse ?? JSON.stringify(nonStreamingResult),
|
|
2395
|
+
inputTokens: nonStreamingResult.inputTokens ?? null,
|
|
2396
|
+
outputTokens: nonStreamingResult.outputTokens ?? null,
|
|
2397
|
+
cacheCreationInputTokens: null,
|
|
2398
|
+
cacheReadInputTokens: null,
|
|
2399
|
+
elapsedMs: nonStreamingResult.latencyMs ?? 0,
|
|
2400
|
+
streaming: false,
|
|
2401
|
+
userAgent: "provider-test",
|
|
2402
|
+
origin: null,
|
|
2403
|
+
apiFormat: "anthropic",
|
|
2404
|
+
isTest: true,
|
|
2405
|
+
providerName: provider.name
|
|
2406
|
+
});
|
|
2407
|
+
appendLogEntry(
|
|
2408
|
+
createTestLogEntry(
|
|
2409
|
+
provider.name,
|
|
2410
|
+
"/v1/messages",
|
|
2411
|
+
requestBody,
|
|
2412
|
+
upstreamUrl,
|
|
2413
|
+
nonStreamingResult,
|
|
2414
|
+
true
|
|
2415
|
+
)
|
|
2416
|
+
);
|
|
2417
|
+
const streamingResult = await testStreamingEndpoint(
|
|
2418
|
+
provider.anthropicBaseUrl,
|
|
2419
|
+
provider.apiKey,
|
|
2420
|
+
"/v1/messages",
|
|
2421
|
+
usageModel,
|
|
2422
|
+
false
|
|
2423
|
+
);
|
|
2424
|
+
results.anthropic.streaming = streamingResult;
|
|
2425
|
+
const streamingRequestBody = JSON.stringify({
|
|
2426
|
+
model: usageModel,
|
|
2427
|
+
messages: [{ role: "user", content: "say hello" }],
|
|
2428
|
+
max_tokens: 256,
|
|
2429
|
+
stream: true
|
|
2430
|
+
});
|
|
2431
|
+
await addTestLogEntry({
|
|
2432
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2433
|
+
method: "POST",
|
|
2434
|
+
path: "/v1/messages",
|
|
2435
|
+
model: streamingResult.model ?? model,
|
|
2436
|
+
sessionId: null,
|
|
2437
|
+
rawRequestBody: streamingRequestBody,
|
|
2438
|
+
responseStatus: streamingResult.success ? 200 : 500,
|
|
2439
|
+
responseText: streamingResult.rawResponse ?? JSON.stringify(streamingResult),
|
|
2440
|
+
inputTokens: streamingResult.inputTokens ?? null,
|
|
2441
|
+
outputTokens: streamingResult.outputTokens ?? null,
|
|
2442
|
+
cacheCreationInputTokens: null,
|
|
2443
|
+
cacheReadInputTokens: null,
|
|
2444
|
+
elapsedMs: streamingResult.latencyMs ?? 0,
|
|
2445
|
+
streaming: true,
|
|
2446
|
+
streamingChunks: streamingResult.streamingChunks,
|
|
2447
|
+
userAgent: "provider-test",
|
|
2448
|
+
origin: null,
|
|
2449
|
+
apiFormat: "anthropic",
|
|
2450
|
+
isTest: true,
|
|
2451
|
+
providerName: provider.name
|
|
2452
|
+
});
|
|
2453
|
+
appendLogEntry(
|
|
2454
|
+
createTestLogEntry(
|
|
2455
|
+
provider.name,
|
|
2456
|
+
"/v1/messages",
|
|
2457
|
+
streamingRequestBody,
|
|
2458
|
+
upstreamUrl,
|
|
2459
|
+
streamingResult,
|
|
2460
|
+
true
|
|
2461
|
+
)
|
|
2462
|
+
);
|
|
2463
|
+
}
|
|
2464
|
+
if (provider.openaiBaseUrl !== void 0 && provider.openaiBaseUrl.length > 0) {
|
|
2465
|
+
const nonStreamingResult = await testEndpoint(
|
|
2466
|
+
provider.openaiBaseUrl,
|
|
2467
|
+
provider.apiKey,
|
|
2468
|
+
"/v1/chat/completions",
|
|
2469
|
+
usageModel,
|
|
2470
|
+
true
|
|
2471
|
+
);
|
|
2472
|
+
results.openai.nonStreaming = nonStreamingResult;
|
|
2473
|
+
const requestBody = JSON.stringify({
|
|
2474
|
+
model: usageModel,
|
|
2475
|
+
messages: [{ role: "user", content: "say hello and briefly introduce yourself" }],
|
|
2476
|
+
max_tokens: 1024
|
|
2477
|
+
});
|
|
2478
|
+
const upstreamUrl = `${provider.openaiBaseUrl}/v1/chat/completions`;
|
|
2479
|
+
await addTestLogEntry({
|
|
2480
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2481
|
+
method: "POST",
|
|
2482
|
+
path: "/v1/chat/completions",
|
|
2483
|
+
model: nonStreamingResult.model ?? model,
|
|
2484
|
+
sessionId: null,
|
|
2485
|
+
rawRequestBody: requestBody,
|
|
2486
|
+
responseStatus: nonStreamingResult.success ? 200 : 500,
|
|
2487
|
+
responseText: nonStreamingResult.rawResponse ?? JSON.stringify(nonStreamingResult),
|
|
2488
|
+
inputTokens: nonStreamingResult.inputTokens ?? null,
|
|
2489
|
+
outputTokens: nonStreamingResult.outputTokens ?? null,
|
|
2490
|
+
cacheCreationInputTokens: null,
|
|
2491
|
+
cacheReadInputTokens: null,
|
|
2492
|
+
elapsedMs: nonStreamingResult.latencyMs ?? 0,
|
|
2493
|
+
streaming: false,
|
|
2494
|
+
userAgent: "provider-test",
|
|
2495
|
+
origin: null,
|
|
2496
|
+
apiFormat: "openai",
|
|
2497
|
+
isTest: true,
|
|
2498
|
+
providerName: provider.name
|
|
2499
|
+
});
|
|
2500
|
+
appendLogEntry(
|
|
2501
|
+
createTestLogEntry(
|
|
2502
|
+
provider.name,
|
|
2503
|
+
"/v1/chat/completions",
|
|
2504
|
+
requestBody,
|
|
2505
|
+
upstreamUrl,
|
|
2506
|
+
nonStreamingResult,
|
|
2507
|
+
true
|
|
2508
|
+
)
|
|
2509
|
+
);
|
|
2510
|
+
const streamingResult = await testStreamingEndpoint(
|
|
2511
|
+
provider.openaiBaseUrl,
|
|
2512
|
+
provider.apiKey,
|
|
2513
|
+
"/v1/chat/completions",
|
|
2514
|
+
usageModel,
|
|
2515
|
+
true
|
|
2516
|
+
);
|
|
2517
|
+
results.openai.streaming = streamingResult;
|
|
2518
|
+
const streamingRequestBody = JSON.stringify({
|
|
2519
|
+
model: usageModel,
|
|
2520
|
+
messages: [{ role: "user", content: "say hello" }],
|
|
2521
|
+
max_tokens: 256,
|
|
2522
|
+
stream: true
|
|
2523
|
+
});
|
|
2524
|
+
await addTestLogEntry({
|
|
2525
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2526
|
+
method: "POST",
|
|
2527
|
+
path: "/v1/chat/completions",
|
|
2528
|
+
model: streamingResult.model ?? model,
|
|
2529
|
+
sessionId: null,
|
|
2530
|
+
rawRequestBody: streamingRequestBody,
|
|
2531
|
+
responseStatus: streamingResult.success ? 200 : 500,
|
|
2532
|
+
responseText: streamingResult.rawResponse ?? JSON.stringify(streamingResult),
|
|
2533
|
+
inputTokens: streamingResult.inputTokens ?? null,
|
|
2534
|
+
outputTokens: streamingResult.outputTokens ?? null,
|
|
2535
|
+
cacheCreationInputTokens: null,
|
|
2536
|
+
cacheReadInputTokens: null,
|
|
2537
|
+
elapsedMs: streamingResult.latencyMs ?? 0,
|
|
2538
|
+
streaming: true,
|
|
2539
|
+
streamingChunks: streamingResult.streamingChunks,
|
|
2540
|
+
userAgent: "provider-test",
|
|
2541
|
+
origin: null,
|
|
2542
|
+
apiFormat: "openai",
|
|
2543
|
+
isTest: true,
|
|
2544
|
+
providerName: provider.name
|
|
2545
|
+
});
|
|
2546
|
+
appendLogEntry(
|
|
2547
|
+
createTestLogEntry(
|
|
2548
|
+
provider.name,
|
|
2549
|
+
"/v1/chat/completions",
|
|
2550
|
+
streamingRequestBody,
|
|
2551
|
+
upstreamUrl,
|
|
2552
|
+
streamingResult,
|
|
2553
|
+
true
|
|
2554
|
+
)
|
|
2555
|
+
);
|
|
2556
|
+
}
|
|
2557
|
+
return Response.json(results);
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
});
|
|
2562
|
+
const ReplayRequestSchema = object({
|
|
2563
|
+
modifiedBody: string()
|
|
2564
|
+
});
|
|
2565
|
+
function getHostFromUrl(urlStr) {
|
|
2566
|
+
try {
|
|
2567
|
+
const url = new URL(urlStr);
|
|
2568
|
+
return url.host;
|
|
2569
|
+
} catch {
|
|
2570
|
+
return "api.anthropic.com";
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
function buildUpstreamUrl(upstreamBase, apiPath) {
|
|
2574
|
+
return upstreamBase + apiPath;
|
|
2575
|
+
}
|
|
2576
|
+
function selectUpstreamBase(isChatCompletions, matchedProviderConfig) {
|
|
2577
|
+
let upstreamBase;
|
|
2578
|
+
if (matchedProviderConfig) {
|
|
2579
|
+
if (isChatCompletions && matchedProviderConfig.openaiBaseUrl !== void 0 && matchedProviderConfig.openaiBaseUrl !== "") {
|
|
2580
|
+
upstreamBase = matchedProviderConfig.openaiBaseUrl;
|
|
2581
|
+
} else if (!isChatCompletions && matchedProviderConfig.anthropicBaseUrl !== void 0 && matchedProviderConfig.anthropicBaseUrl !== "") {
|
|
2582
|
+
upstreamBase = matchedProviderConfig.anthropicBaseUrl;
|
|
2583
|
+
} else if (matchedProviderConfig.baseUrl !== void 0 && matchedProviderConfig.baseUrl !== "") {
|
|
2584
|
+
upstreamBase = matchedProviderConfig.baseUrl;
|
|
2585
|
+
} else {
|
|
2586
|
+
upstreamBase = matchedProviderConfig.format === "openai" ? DEFAULT_OPENAI_UPSTREAM$1 : DEFAULT_UPSTREAM$1;
|
|
2587
|
+
}
|
|
2588
|
+
} else {
|
|
2589
|
+
upstreamBase = isChatCompletions ? DEFAULT_OPENAI_UPSTREAM$1 : DEFAULT_UPSTREAM$1;
|
|
2590
|
+
}
|
|
2591
|
+
return upstreamBase;
|
|
2592
|
+
}
|
|
2593
|
+
function injectAuthHeaders(upstreamHeaders, matchedProviderConfig) {
|
|
2594
|
+
if (!matchedProviderConfig) return;
|
|
2595
|
+
const apiKey = matchedProviderConfig.apiKey.replace(/^Bearer\s+/i, "").trim();
|
|
2596
|
+
if (matchedProviderConfig.authHeader === AUTH_HEADER_X_API_KEY) {
|
|
2597
|
+
upstreamHeaders.set(HEADER_X_API_KEY, apiKey);
|
|
2598
|
+
upstreamHeaders.delete(HEADER_AUTHORIZATION);
|
|
2599
|
+
} else {
|
|
2600
|
+
upstreamHeaders.set(HEADER_AUTHORIZATION, `Bearer ${apiKey}`);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
const Route$1 = createFileRoute("/api/logs/$id/replay")({
|
|
2604
|
+
server: {
|
|
2605
|
+
handlers: {
|
|
2606
|
+
POST: async ({ params, request }) => {
|
|
2607
|
+
const id = Number(params.id);
|
|
2608
|
+
if (isNaN(id)) {
|
|
2609
|
+
return Response.json({ error: "Invalid log ID" }, { status: 400 });
|
|
2610
|
+
}
|
|
2611
|
+
let replayRequest;
|
|
2612
|
+
try {
|
|
2613
|
+
const raw = await request.json();
|
|
2614
|
+
const parsed = ReplayRequestSchema.safeParse(raw);
|
|
2615
|
+
if (!parsed.success) {
|
|
2616
|
+
return Response.json({ error: "Invalid request body" }, { status: 400 });
|
|
2617
|
+
}
|
|
2618
|
+
replayRequest = parsed.data;
|
|
2619
|
+
} catch {
|
|
2620
|
+
return Response.json({ error: "Invalid JSON body" }, { status: 400 });
|
|
2621
|
+
}
|
|
2622
|
+
const { modifiedBody } = replayRequest;
|
|
2623
|
+
const log = await getLogById(id);
|
|
2624
|
+
if (log === null) {
|
|
2625
|
+
return Response.json({ error: "Log not found" }, { status: 404 });
|
|
2626
|
+
}
|
|
2627
|
+
const model = extractModelFromBody(modifiedBody);
|
|
2628
|
+
if (model === null) {
|
|
2629
|
+
return Response.json(
|
|
2630
|
+
{ error: "Could not extract model from request body" },
|
|
2631
|
+
{ status: 400 }
|
|
2632
|
+
);
|
|
2633
|
+
}
|
|
2634
|
+
const matchedProviderConfig = findProviderByModel(model);
|
|
2635
|
+
const provider = registry.findProvider(model);
|
|
2636
|
+
if (provider === null) {
|
|
2637
|
+
return Response.json(
|
|
2638
|
+
{ error: "No provider found for model" },
|
|
2639
|
+
{ status: STATUS_FORBIDDEN }
|
|
2640
|
+
);
|
|
2641
|
+
}
|
|
2642
|
+
const apiPath = log.path;
|
|
2643
|
+
const isChatCompletions = apiPath === PATH_V1_CHAT_COMPLETIONS || apiPath === PATH_CHAT_COMPLETIONS;
|
|
2644
|
+
const normalizedPath = isChatCompletions && !apiPath.startsWith("/v1/") ? "/v1" + apiPath : apiPath;
|
|
2645
|
+
const formatHandler = formatForPath(apiPath);
|
|
2646
|
+
if (formatHandler === null) {
|
|
2647
|
+
return Response.json({ error: "Unsupported path" }, { status: STATUS_FORBIDDEN });
|
|
2648
|
+
}
|
|
2649
|
+
const upstreamBase = selectUpstreamBase(isChatCompletions, matchedProviderConfig);
|
|
2650
|
+
const upstreamUrl = buildUpstreamUrl(upstreamBase, normalizedPath);
|
|
2651
|
+
const upstreamHost = getHostFromUrl(upstreamBase);
|
|
2652
|
+
const headers = new Headers();
|
|
2653
|
+
headers.set(HEADER_USER_AGENT, PROXY_IDENTITY);
|
|
2654
|
+
headers.set(HEADER_X_PROXY_IDENTITY, PROXY_IDENTITY);
|
|
2655
|
+
headers.set(HEADER_CONTENT_TYPE, "application/json");
|
|
2656
|
+
for (const name of PRESERVE_HEADERS) {
|
|
2657
|
+
const value = log.rawHeaders?.[name.toLowerCase()];
|
|
2658
|
+
if (value !== void 0) {
|
|
2659
|
+
headers.set(name, value);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
headers.set(HEADER_HOST, upstreamHost);
|
|
2663
|
+
injectAuthHeaders(headers, matchedProviderConfig);
|
|
2664
|
+
const startTime = Date.now();
|
|
2665
|
+
let upstreamRes;
|
|
2666
|
+
try {
|
|
2667
|
+
upstreamRes = await fetch(upstreamUrl, {
|
|
2668
|
+
method: "POST",
|
|
2669
|
+
headers,
|
|
2670
|
+
body: modifiedBody
|
|
2671
|
+
});
|
|
2672
|
+
} catch (err) {
|
|
2673
|
+
return Response.json({
|
|
2674
|
+
success: false,
|
|
2675
|
+
error: String(err)
|
|
2676
|
+
});
|
|
2677
|
+
}
|
|
2678
|
+
const elapsedMs = Date.now() - startTime;
|
|
2679
|
+
const isStream = upstreamRes.headers.get(HEADER_CONTENT_TYPE)?.includes("text/event-stream") ?? false;
|
|
2680
|
+
if (isStream) {
|
|
2681
|
+
const chunks = [];
|
|
2682
|
+
const decoder = new TextDecoder();
|
|
2683
|
+
const reader = upstreamRes.body?.getReader();
|
|
2684
|
+
if (reader) {
|
|
2685
|
+
try {
|
|
2686
|
+
while (true) {
|
|
2687
|
+
const { done, value } = await reader.read();
|
|
2688
|
+
if (done) break;
|
|
2689
|
+
chunks.push(decoder.decode(value, { stream: true }));
|
|
2690
|
+
}
|
|
2691
|
+
} catch {
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
const fullResponse = chunks.join("");
|
|
2695
|
+
const mockLog = { ...log };
|
|
2696
|
+
const responseText = formatHandler.extractStream(fullResponse, mockLog, model, false);
|
|
2697
|
+
const tokens = formatHandler.extractTokens(responseText);
|
|
2698
|
+
return Response.json({
|
|
2699
|
+
success: true,
|
|
2700
|
+
responseStatus: upstreamRes.status,
|
|
2701
|
+
responseText,
|
|
2702
|
+
inputTokens: tokens.inputTokens ?? void 0,
|
|
2703
|
+
outputTokens: tokens.outputTokens ?? void 0,
|
|
2704
|
+
elapsedMs,
|
|
2705
|
+
streaming: true
|
|
2706
|
+
});
|
|
2707
|
+
} else {
|
|
2708
|
+
const responseText = await upstreamRes.text();
|
|
2709
|
+
const tokens = formatHandler.extractTokens(responseText);
|
|
2710
|
+
return Response.json({
|
|
2711
|
+
success: true,
|
|
2712
|
+
responseStatus: upstreamRes.status,
|
|
2713
|
+
responseText,
|
|
2714
|
+
inputTokens: tokens.inputTokens ?? void 0,
|
|
2715
|
+
outputTokens: tokens.outputTokens ?? void 0,
|
|
2716
|
+
elapsedMs,
|
|
2717
|
+
streaming: false
|
|
2718
|
+
});
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
});
|
|
2724
|
+
const Route = createFileRoute("/api/logs/$id/chunks")({
|
|
2725
|
+
server: {
|
|
2726
|
+
handlers: {
|
|
2727
|
+
GET: async ({ params }) => {
|
|
2728
|
+
const id = Number(params.id);
|
|
2729
|
+
if (isNaN(id)) {
|
|
2730
|
+
return Response.json({ error: "Invalid log ID" }, { status: 400 });
|
|
2731
|
+
}
|
|
2732
|
+
const log = await getLogById(id);
|
|
2733
|
+
if (log === null) {
|
|
2734
|
+
return Response.json({ error: "Log not found" }, { status: 404 });
|
|
2735
|
+
}
|
|
2736
|
+
if (log.streamingChunksPath !== null && log.streamingChunksPath !== void 0) {
|
|
2737
|
+
const chunksData = readChunks(log.streamingChunksPath);
|
|
2738
|
+
if (chunksData !== null) {
|
|
2739
|
+
return Response.json(chunksData);
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
if (log.streamingChunks !== void 0 && log.streamingChunks.chunks.length > 0) {
|
|
2743
|
+
return Response.json(log.streamingChunks);
|
|
2744
|
+
}
|
|
2745
|
+
return Response.json({ error: "Chunks not found" }, { status: 404 });
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
});
|
|
2750
|
+
const IndexRoute = Route$d.update({
|
|
2751
|
+
id: "/",
|
|
2752
|
+
path: "/",
|
|
2753
|
+
getParentRoute: () => Route$e
|
|
2754
|
+
});
|
|
2755
|
+
const ProxySplatRoute = Route$c.update({
|
|
2756
|
+
id: "/proxy/$",
|
|
2757
|
+
path: "/proxy/$",
|
|
2758
|
+
getParentRoute: () => Route$e
|
|
2759
|
+
});
|
|
2760
|
+
const ApiSessionsRoute = Route$b.update({
|
|
2761
|
+
id: "/api/sessions",
|
|
2762
|
+
path: "/api/sessions",
|
|
2763
|
+
getParentRoute: () => Route$e
|
|
2764
|
+
});
|
|
2765
|
+
const ApiProvidersRoute = Route$a.update({
|
|
2766
|
+
id: "/api/providers",
|
|
2767
|
+
path: "/api/providers",
|
|
2768
|
+
getParentRoute: () => Route$e
|
|
2769
|
+
});
|
|
2770
|
+
const ApiModelsRoute = Route$9.update({
|
|
2771
|
+
id: "/api/models",
|
|
2772
|
+
path: "/api/models",
|
|
2773
|
+
getParentRoute: () => Route$e
|
|
2774
|
+
});
|
|
2775
|
+
const ApiLogsRoute = Route$8.update({
|
|
2776
|
+
id: "/api/logs",
|
|
2777
|
+
path: "/api/logs",
|
|
2778
|
+
getParentRoute: () => Route$e
|
|
2779
|
+
});
|
|
2780
|
+
const ApiHealthRoute = Route$7.update({
|
|
2781
|
+
id: "/api/health",
|
|
2782
|
+
path: "/api/health",
|
|
2783
|
+
getParentRoute: () => Route$e
|
|
2784
|
+
});
|
|
2785
|
+
const ApiProvidersProviderIdRoute = Route$6.update({
|
|
2786
|
+
id: "/$providerId",
|
|
2787
|
+
path: "/$providerId",
|
|
2788
|
+
getParentRoute: () => ApiProvidersRoute
|
|
2789
|
+
});
|
|
2790
|
+
const ApiLogsStreamRoute = Route$5.update({
|
|
2791
|
+
id: "/stream",
|
|
2792
|
+
path: "/stream",
|
|
2793
|
+
getParentRoute: () => ApiLogsRoute
|
|
2794
|
+
});
|
|
2795
|
+
const ApiLogsIdRoute = Route$4.update({
|
|
2796
|
+
id: "/$id",
|
|
2797
|
+
path: "/$id",
|
|
2798
|
+
getParentRoute: () => ApiLogsRoute
|
|
2799
|
+
});
|
|
2800
|
+
const ApiConfigPathsRoute = Route$3.update({
|
|
2801
|
+
id: "/api/config/paths",
|
|
2802
|
+
path: "/api/config/paths",
|
|
2803
|
+
getParentRoute: () => Route$e
|
|
2804
|
+
});
|
|
2805
|
+
const ApiProvidersProviderIdTestRoute = Route$2.update({
|
|
2806
|
+
id: "/test",
|
|
2807
|
+
path: "/test",
|
|
2808
|
+
getParentRoute: () => ApiProvidersProviderIdRoute
|
|
2809
|
+
});
|
|
2810
|
+
const ApiLogsIdReplayRoute = Route$1.update({
|
|
2811
|
+
id: "/replay",
|
|
2812
|
+
path: "/replay",
|
|
2813
|
+
getParentRoute: () => ApiLogsIdRoute
|
|
2814
|
+
});
|
|
2815
|
+
const ApiLogsIdChunksRoute = Route.update({
|
|
2816
|
+
id: "/chunks",
|
|
2817
|
+
path: "/chunks",
|
|
2818
|
+
getParentRoute: () => ApiLogsIdRoute
|
|
2819
|
+
});
|
|
2820
|
+
const ApiLogsIdRouteChildren = {
|
|
2821
|
+
ApiLogsIdChunksRoute,
|
|
2822
|
+
ApiLogsIdReplayRoute
|
|
2823
|
+
};
|
|
2824
|
+
const ApiLogsIdRouteWithChildren = ApiLogsIdRoute._addFileChildren(
|
|
2825
|
+
ApiLogsIdRouteChildren
|
|
2826
|
+
);
|
|
2827
|
+
const ApiLogsRouteChildren = {
|
|
2828
|
+
ApiLogsIdRoute: ApiLogsIdRouteWithChildren,
|
|
2829
|
+
ApiLogsStreamRoute
|
|
2830
|
+
};
|
|
2831
|
+
const ApiLogsRouteWithChildren = ApiLogsRoute._addFileChildren(ApiLogsRouteChildren);
|
|
2832
|
+
const ApiProvidersProviderIdRouteChildren = {
|
|
2833
|
+
ApiProvidersProviderIdTestRoute
|
|
2834
|
+
};
|
|
2835
|
+
const ApiProvidersProviderIdRouteWithChildren = ApiProvidersProviderIdRoute._addFileChildren(
|
|
2836
|
+
ApiProvidersProviderIdRouteChildren
|
|
2837
|
+
);
|
|
2838
|
+
const ApiProvidersRouteChildren = {
|
|
2839
|
+
ApiProvidersProviderIdRoute: ApiProvidersProviderIdRouteWithChildren
|
|
2840
|
+
};
|
|
2841
|
+
const ApiProvidersRouteWithChildren = ApiProvidersRoute._addFileChildren(
|
|
2842
|
+
ApiProvidersRouteChildren
|
|
2843
|
+
);
|
|
2844
|
+
const rootRouteChildren = {
|
|
2845
|
+
IndexRoute,
|
|
2846
|
+
ApiHealthRoute,
|
|
2847
|
+
ApiLogsRoute: ApiLogsRouteWithChildren,
|
|
2848
|
+
ApiModelsRoute,
|
|
2849
|
+
ApiProvidersRoute: ApiProvidersRouteWithChildren,
|
|
2850
|
+
ApiSessionsRoute,
|
|
2851
|
+
ProxySplatRoute,
|
|
2852
|
+
ApiConfigPathsRoute
|
|
2853
|
+
};
|
|
2854
|
+
const routeTree = Route$e._addFileChildren(rootRouteChildren)._addFileTypes();
|
|
2855
|
+
function getRouter() {
|
|
2856
|
+
const router2 = createRouter({
|
|
2857
|
+
routeTree,
|
|
2858
|
+
scrollRestoration: false
|
|
2859
|
+
});
|
|
2860
|
+
return router2;
|
|
2861
|
+
}
|
|
2862
|
+
const router = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
2863
|
+
__proto__: null,
|
|
2864
|
+
getRouter
|
|
2865
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
2866
|
+
export {
|
|
2867
|
+
CapturedLogSchema as C,
|
|
2868
|
+
InspectorResponseSchema as I,
|
|
2869
|
+
parseRequest as a,
|
|
2870
|
+
parseOpenAIResponse as p,
|
|
2871
|
+
router as r
|
|
2872
|
+
};
|