@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.
Files changed (286) hide show
  1. package/.output/nitro.json +17 -0
  2. package/.output/public/assets/alibaba-TTwafVwX.svg +1 -0
  3. package/.output/public/assets/index-B3RwBPLW.css +1 -0
  4. package/.output/public/assets/index-s4lwsWvq.js +97 -0
  5. package/.output/public/assets/main-Cp8AM0Pa.js +17 -0
  6. package/.output/public/assets/minimax-BPMzvuL-.jpeg +0 -0
  7. package/.output/public/assets/qwen-CONDcHqt.png +0 -0
  8. package/.output/public/assets/zhipuai-BPNAnxo-.svg +219 -0
  9. package/.output/server/_chunks/ssr-renderer.mjs +17 -0
  10. package/.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs +1 -0
  11. package/.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs +210 -0
  12. package/.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs +1 -0
  13. package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +1 -0
  14. package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +1 -0
  15. package/.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs +11 -0
  16. package/.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs +69 -0
  17. package/.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs +1 -0
  18. package/.output/server/_libs/@radix-ui/react-use-escape-keydown+[...].mjs +17 -0
  19. package/.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs +1 -0
  20. package/.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs +6 -0
  21. package/.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs +34 -0
  22. package/.output/server/_libs/ajv-formats.mjs +330 -0
  23. package/.output/server/_libs/ajv.mjs +11444 -0
  24. package/.output/server/_libs/aria-hidden.mjs +122 -0
  25. package/.output/server/_libs/atomically.mjs +152 -0
  26. package/.output/server/_libs/bail.mjs +8 -0
  27. package/.output/server/_libs/character-entities.mjs +2130 -0
  28. package/.output/server/_libs/class-variance-authority.mjs +44 -0
  29. package/.output/server/_libs/clsx.mjs +16 -0
  30. package/.output/server/_libs/comma-separated-tokens.mjs +10 -0
  31. package/.output/server/_libs/conf.mjs +635 -0
  32. package/.output/server/_libs/cookie-es.mjs +58 -0
  33. package/.output/server/_libs/core-util-is.mjs +75 -0
  34. package/.output/server/_libs/croner.mjs +1 -0
  35. package/.output/server/_libs/crossws.mjs +1 -0
  36. package/.output/server/_libs/debounce-fn.mjs +69 -0
  37. package/.output/server/_libs/decode-named-character-reference+[...].mjs +8 -0
  38. package/.output/server/_libs/detect-node-es.mjs +1 -0
  39. package/.output/server/_libs/devlop.mjs +8 -0
  40. package/.output/server/_libs/dot-prop.mjs +265 -0
  41. package/.output/server/_libs/env-paths.mjs +57 -0
  42. package/.output/server/_libs/estree-util-is-identifier-name.mjs +11 -0
  43. package/.output/server/_libs/extend.mjs +97 -0
  44. package/.output/server/_libs/fast-deep-equal.mjs +38 -0
  45. package/.output/server/_libs/fast-uri.mjs +812 -0
  46. package/.output/server/_libs/floating-ui__core.mjs +725 -0
  47. package/.output/server/_libs/floating-ui__dom.mjs +622 -0
  48. package/.output/server/_libs/floating-ui__react-dom.mjs +292 -0
  49. package/.output/server/_libs/floating-ui__utils.mjs +320 -0
  50. package/.output/server/_libs/get-nonce.mjs +9 -0
  51. package/.output/server/_libs/h3-v2.mjs +276 -0
  52. package/.output/server/_libs/h3.mjs +400 -0
  53. package/.output/server/_libs/hast-util-to-jsx-runtime.mjs +388 -0
  54. package/.output/server/_libs/hast-util-whitespace.mjs +10 -0
  55. package/.output/server/_libs/hookable.mjs +1 -0
  56. package/.output/server/_libs/html-url-attributes.mjs +26 -0
  57. package/.output/server/_libs/immediate.mjs +74 -0
  58. package/.output/server/_libs/inherits.mjs +50 -0
  59. package/.output/server/_libs/inline-style-parser.mjs +142 -0
  60. package/.output/server/_libs/is-plain-obj.mjs +10 -0
  61. package/.output/server/_libs/isarray.mjs +14 -0
  62. package/.output/server/_libs/isbot.mjs +20 -0
  63. package/.output/server/_libs/json-schema-traverse.mjs +180 -0
  64. package/.output/server/_libs/jszip.mjs +3049 -0
  65. package/.output/server/_libs/lie.mjs +273 -0
  66. package/.output/server/_libs/lucide-react.mjs +368 -0
  67. package/.output/server/_libs/mdast-util-from-markdown.mjs +717 -0
  68. package/.output/server/_libs/mdast-util-to-hast.mjs +710 -0
  69. package/.output/server/_libs/mdast-util-to-string.mjs +38 -0
  70. package/.output/server/_libs/micromark-core-commonmark.mjs +2259 -0
  71. package/.output/server/_libs/micromark-factory-destination.mjs +94 -0
  72. package/.output/server/_libs/micromark-factory-label.mjs +63 -0
  73. package/.output/server/_libs/micromark-factory-space.mjs +24 -0
  74. package/.output/server/_libs/micromark-factory-title.mjs +65 -0
  75. package/.output/server/_libs/micromark-factory-whitespace.mjs +22 -0
  76. package/.output/server/_libs/micromark-util-character.mjs +44 -0
  77. package/.output/server/_libs/micromark-util-chunked.mjs +36 -0
  78. package/.output/server/_libs/micromark-util-classify-character+[...].mjs +12 -0
  79. package/.output/server/_libs/micromark-util-combine-extensions+[...].mjs +41 -0
  80. package/.output/server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +19 -0
  81. package/.output/server/_libs/micromark-util-decode-string.mjs +21 -0
  82. package/.output/server/_libs/micromark-util-encode.mjs +1 -0
  83. package/.output/server/_libs/micromark-util-html-tag-name.mjs +69 -0
  84. package/.output/server/_libs/micromark-util-normalize-identifier+[...].mjs +6 -0
  85. package/.output/server/_libs/micromark-util-resolve-all.mjs +15 -0
  86. package/.output/server/_libs/micromark-util-sanitize-uri.mjs +41 -0
  87. package/.output/server/_libs/micromark-util-subtokenize.mjs +346 -0
  88. package/.output/server/_libs/micromark.mjs +906 -0
  89. package/.output/server/_libs/mimic-function.mjs +47 -0
  90. package/.output/server/_libs/ohash.mjs +1 -0
  91. package/.output/server/_libs/pako.mjs +4223 -0
  92. package/.output/server/_libs/process-nextick-args.mjs +48 -0
  93. package/.output/server/_libs/property-information.mjs +1209 -0
  94. package/.output/server/_libs/radix-ui.mjs +1 -0
  95. package/.output/server/_libs/radix-ui__number.mjs +6 -0
  96. package/.output/server/_libs/radix-ui__primitive.mjs +11 -0
  97. package/.output/server/_libs/radix-ui__react-accordion.mjs +1 -0
  98. package/.output/server/_libs/radix-ui__react-alert-dialog.mjs +1 -0
  99. package/.output/server/_libs/radix-ui__react-arrow.mjs +23 -0
  100. package/.output/server/_libs/radix-ui__react-aspect-ratio.mjs +1 -0
  101. package/.output/server/_libs/radix-ui__react-avatar.mjs +1 -0
  102. package/.output/server/_libs/radix-ui__react-checkbox.mjs +1 -0
  103. package/.output/server/_libs/radix-ui__react-collapsible.mjs +144 -0
  104. package/.output/server/_libs/radix-ui__react-collection.mjs +69 -0
  105. package/.output/server/_libs/radix-ui__react-compose-refs.mjs +39 -0
  106. package/.output/server/_libs/radix-ui__react-context-menu.mjs +1 -0
  107. package/.output/server/_libs/radix-ui__react-context.mjs +78 -0
  108. package/.output/server/_libs/radix-ui__react-dialog.mjs +325 -0
  109. package/.output/server/_libs/radix-ui__react-direction.mjs +9 -0
  110. package/.output/server/_libs/radix-ui__react-dropdown-menu.mjs +1 -0
  111. package/.output/server/_libs/radix-ui__react-focus-guards.mjs +29 -0
  112. package/.output/server/_libs/radix-ui__react-focus-scope.mjs +206 -0
  113. package/.output/server/_libs/radix-ui__react-form.mjs +1 -0
  114. package/.output/server/_libs/radix-ui__react-hover-card.mjs +1 -0
  115. package/.output/server/_libs/radix-ui__react-id.mjs +14 -0
  116. package/.output/server/_libs/radix-ui__react-label.mjs +1 -0
  117. package/.output/server/_libs/radix-ui__react-menu.mjs +1 -0
  118. package/.output/server/_libs/radix-ui__react-menubar.mjs +1 -0
  119. package/.output/server/_libs/radix-ui__react-popover.mjs +1 -0
  120. package/.output/server/_libs/radix-ui__react-popper.mjs +286 -0
  121. package/.output/server/_libs/radix-ui__react-portal.mjs +16 -0
  122. package/.output/server/_libs/radix-ui__react-presence.mjs +128 -0
  123. package/.output/server/_libs/radix-ui__react-primitive.mjs +42 -0
  124. package/.output/server/_libs/radix-ui__react-progress.mjs +1 -0
  125. package/.output/server/_libs/radix-ui__react-radio-group.mjs +1 -0
  126. package/.output/server/_libs/radix-ui__react-roving-focus.mjs +224 -0
  127. package/.output/server/_libs/radix-ui__react-scroll-area.mjs +721 -0
  128. package/.output/server/_libs/radix-ui__react-select.mjs +1163 -0
  129. package/.output/server/_libs/radix-ui__react-separator.mjs +28 -0
  130. package/.output/server/_libs/radix-ui__react-slider.mjs +1 -0
  131. package/.output/server/_libs/radix-ui__react-slot.mjs +99 -0
  132. package/.output/server/_libs/radix-ui__react-switch.mjs +1 -0
  133. package/.output/server/_libs/radix-ui__react-tabs.mjs +189 -0
  134. package/.output/server/_libs/radix-ui__react-toast.mjs +1 -0
  135. package/.output/server/_libs/radix-ui__react-toggle-group.mjs +1 -0
  136. package/.output/server/_libs/radix-ui__react-toggle.mjs +1 -0
  137. package/.output/server/_libs/radix-ui__react-toolbar.mjs +1 -0
  138. package/.output/server/_libs/radix-ui__react-tooltip.mjs +495 -0
  139. package/.output/server/_libs/radix-ui__react-use-previous.mjs +14 -0
  140. package/.output/server/_libs/radix-ui__react-use-size.mjs +39 -0
  141. package/.output/server/_libs/react-dom.mjs +9935 -0
  142. package/.output/server/_libs/react-markdown.mjs +147 -0
  143. package/.output/server/_libs/react-remove-scroll-bar.mjs +82 -0
  144. package/.output/server/_libs/react-remove-scroll.mjs +328 -0
  145. package/.output/server/_libs/react-style-singleton.mjs +69 -0
  146. package/.output/server/_libs/react.mjs +515 -0
  147. package/.output/server/_libs/readable-stream.mjs +1518 -0
  148. package/.output/server/_libs/remark-parse.mjs +19 -0
  149. package/.output/server/_libs/remark-rehype.mjs +21 -0
  150. package/.output/server/_libs/rou3.mjs +8 -0
  151. package/.output/server/_libs/safe-buffer.mjs +64 -0
  152. package/.output/server/_libs/semver.mjs +1984 -0
  153. package/.output/server/_libs/seroval-plugins.mjs +58 -0
  154. package/.output/server/_libs/seroval.mjs +1765 -0
  155. package/.output/server/_libs/setimmediate.mjs +1 -0
  156. package/.output/server/_libs/space-separated-tokens.mjs +6 -0
  157. package/.output/server/_libs/srvx.mjs +334 -0
  158. package/.output/server/_libs/stubborn-fs.mjs +91 -0
  159. package/.output/server/_libs/stubborn-utils.mjs +66 -0
  160. package/.output/server/_libs/style-to-js.mjs +72 -0
  161. package/.output/server/_libs/style-to-object.mjs +38 -0
  162. package/.output/server/_libs/tailwind-merge.mjs +3010 -0
  163. package/.output/server/_libs/tanstack__history.mjs +217 -0
  164. package/.output/server/_libs/tanstack__react-router.mjs +1480 -0
  165. package/.output/server/_libs/tanstack__react-store.mjs +1 -0
  166. package/.output/server/_libs/tanstack__react-virtual.mjs +44 -0
  167. package/.output/server/_libs/tanstack__router-core.mjs +4827 -0
  168. package/.output/server/_libs/tanstack__store.mjs +1 -0
  169. package/.output/server/_libs/tanstack__virtual-core.mjs +1225 -0
  170. package/.output/server/_libs/tiny-invariant.mjs +12 -0
  171. package/.output/server/_libs/tiny-warning.mjs +5 -0
  172. package/.output/server/_libs/trim-lines.mjs +41 -0
  173. package/.output/server/_libs/trough.mjs +85 -0
  174. package/.output/server/_libs/tslib.mjs +576 -0
  175. package/.output/server/_libs/ufo.mjs +54 -0
  176. package/.output/server/_libs/uint8array-extras.mjs +69 -0
  177. package/.output/server/_libs/unctx.mjs +1 -0
  178. package/.output/server/_libs/ungap__structured-clone.mjs +212 -0
  179. package/.output/server/_libs/unified.mjs +661 -0
  180. package/.output/server/_libs/unist-util-is.mjs +100 -0
  181. package/.output/server/_libs/unist-util-position.mjs +27 -0
  182. package/.output/server/_libs/unist-util-stringify-position.mjs +27 -0
  183. package/.output/server/_libs/unist-util-visit-parents.mjs +82 -0
  184. package/.output/server/_libs/unist-util-visit.mjs +24 -0
  185. package/.output/server/_libs/unstorage.mjs +1 -0
  186. package/.output/server/_libs/use-callback-ref.mjs +66 -0
  187. package/.output/server/_libs/use-sidecar.mjs +106 -0
  188. package/.output/server/_libs/use-sync-external-store.mjs +1 -0
  189. package/.output/server/_libs/util-deprecate.mjs +12 -0
  190. package/.output/server/_libs/vfile-message.mjs +138 -0
  191. package/.output/server/_libs/vfile.mjs +467 -0
  192. package/.output/server/_libs/when-exit.mjs +53 -0
  193. package/.output/server/_libs/zod.mjs +4460 -0
  194. package/.output/server/_ssr/index-ByCLZu7J.mjs +3061 -0
  195. package/.output/server/_ssr/index.mjs +1176 -0
  196. package/.output/server/_ssr/router-Bq_mxeNz.mjs +2872 -0
  197. package/.output/server/_ssr/start-HYkvq4Ni.mjs +4 -0
  198. package/.output/server/_tanstack-start-manifest_v-C4E0e9my.mjs +4 -0
  199. package/.output/server/index.mjs +393 -0
  200. package/README.md +196 -0
  201. package/package.json +91 -0
  202. package/src/assets/logos/alibaba.svg +1 -0
  203. package/src/assets/logos/anthropic.svg +1 -0
  204. package/src/assets/logos/deepseek.svg +1 -0
  205. package/src/assets/logos/minimax.jpeg +0 -0
  206. package/src/assets/logos/openai.svg +1 -0
  207. package/src/assets/logos/qwen.png +0 -0
  208. package/src/assets/logos/zhipuai.svg +219 -0
  209. package/src/cli.ts +68 -0
  210. package/src/components/ProxyViewer.tsx +325 -0
  211. package/src/components/ProxyViewerContainer.tsx +211 -0
  212. package/src/components/providers/ProviderCard.tsx +186 -0
  213. package/src/components/providers/ProviderForm.tsx +259 -0
  214. package/src/components/providers/ProviderLogo.tsx +111 -0
  215. package/src/components/providers/ProvidersPanel.tsx +259 -0
  216. package/src/components/providers/SettingsDialog.tsx +39 -0
  217. package/src/components/proxy-viewer/ConversationGroup.tsx +68 -0
  218. package/src/components/proxy-viewer/ConversationHeader.tsx +141 -0
  219. package/src/components/proxy-viewer/LogEntry.tsx +225 -0
  220. package/src/components/proxy-viewer/LogEntryHeader.tsx +250 -0
  221. package/src/components/proxy-viewer/ReplayDialog.tsx +208 -0
  222. package/src/components/proxy-viewer/ResponseView.tsx +161 -0
  223. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +171 -0
  224. package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +139 -0
  225. package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +64 -0
  226. package/src/components/proxy-viewer/formats/index.tsx +24 -0
  227. package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +80 -0
  228. package/src/components/proxy-viewer/index.ts +8 -0
  229. package/src/components/ui/badge.tsx +47 -0
  230. package/src/components/ui/button.tsx +47 -0
  231. package/src/components/ui/collapsible.tsx +21 -0
  232. package/src/components/ui/dialog.tsx +129 -0
  233. package/src/components/ui/json-viewer.tsx +464 -0
  234. package/src/components/ui/scroll-area.tsx +54 -0
  235. package/src/components/ui/select.tsx +178 -0
  236. package/src/components/ui/separator.tsx +28 -0
  237. package/src/components/ui/tabs.tsx +88 -0
  238. package/src/components/ui/tooltip.tsx +51 -0
  239. package/src/index.css +11 -0
  240. package/src/lib/export-logs.ts +51 -0
  241. package/src/lib/utils.ts +22 -0
  242. package/src/proxy/chunkStorage.ts +118 -0
  243. package/src/proxy/constants.ts +36 -0
  244. package/src/proxy/formats/anthropic/anthropicProvider.ts +75 -0
  245. package/src/proxy/formats/anthropic/handler.ts +74 -0
  246. package/src/proxy/formats/anthropic/index.ts +14 -0
  247. package/src/proxy/formats/anthropic/register.ts +4 -0
  248. package/src/proxy/formats/anthropic/schemas.ts +217 -0
  249. package/src/proxy/formats/anthropic/stream.ts +167 -0
  250. package/src/proxy/formats/handler.ts +46 -0
  251. package/src/proxy/formats/index.ts +12 -0
  252. package/src/proxy/formats/jsonSchema.ts +24 -0
  253. package/src/proxy/formats/openai/alibabaProvider.ts +38 -0
  254. package/src/proxy/formats/openai/handler.ts +70 -0
  255. package/src/proxy/formats/openai/index.ts +25 -0
  256. package/src/proxy/formats/openai/provider.ts +50 -0
  257. package/src/proxy/formats/openai/register.ts +4 -0
  258. package/src/proxy/formats/openai/schemas.ts +150 -0
  259. package/src/proxy/formats/openai/stream.ts +153 -0
  260. package/src/proxy/formats/protocol.ts +50 -0
  261. package/src/proxy/formats/providerRegistry.ts +51 -0
  262. package/src/proxy/formats/providers/index.ts +3 -0
  263. package/src/proxy/formats/registry.ts +61 -0
  264. package/src/proxy/handler.ts +389 -0
  265. package/src/proxy/logIndex.ts +187 -0
  266. package/src/proxy/logger.ts +99 -0
  267. package/src/proxy/providers.ts +234 -0
  268. package/src/proxy/schemas.ts +160 -0
  269. package/src/proxy/socketTracker.ts +158 -0
  270. package/src/proxy/store.ts +386 -0
  271. package/src/router.tsx +16 -0
  272. package/src/routes/__root.tsx +38 -0
  273. package/src/routes/api/config.paths.ts +14 -0
  274. package/src/routes/api/health.ts +11 -0
  275. package/src/routes/api/logs.$id.chunks.ts +36 -0
  276. package/src/routes/api/logs.$id.replay.ts +262 -0
  277. package/src/routes/api/logs.$id.ts +22 -0
  278. package/src/routes/api/logs.stream.ts +64 -0
  279. package/src/routes/api/logs.ts +30 -0
  280. package/src/routes/api/models.ts +10 -0
  281. package/src/routes/api/providers.$providerId.ts +45 -0
  282. package/src/routes/api/providers.ts +37 -0
  283. package/src/routes/api/sessions.ts +10 -0
  284. package/src/routes/index.tsx +6 -0
  285. package/src/routes/proxy/$.ts +15 -0
  286. package/styles/globals.css +121 -0
@@ -0,0 +1,234 @@
1
+ import { z } from "zod";
2
+ import Conf from "conf";
3
+ import { randomUUID } from "crypto";
4
+
5
+ export const ProviderConfigSchema = z.object({
6
+ id: z.string(),
7
+ name: z.string(),
8
+ apiKey: z.string(),
9
+ model: z.string().optional(),
10
+ /** API format: "anthropic" or "openai" */
11
+ format: z.enum(["anthropic", "openai"]).optional(),
12
+ /** Base URL for the provider (deprecated - use anthropicBaseUrl/openaiBaseUrl instead) */
13
+ baseUrl: z.string().optional(),
14
+ /** Anthropic API base URL */
15
+ anthropicBaseUrl: z.string().optional(),
16
+ /** OpenAI API base URL */
17
+ openaiBaseUrl: z.string().optional(),
18
+ /** Auth header to use: "bearer" (default) or "x-api-key" */
19
+ authHeader: z.enum(["bearer", "x-api-key"]).optional().default("bearer"),
20
+ createdAt: z.string(),
21
+ updatedAt: z.string(),
22
+ });
23
+
24
+ export type ProviderConfig = z.infer<typeof ProviderConfigSchema>;
25
+
26
+ const ProvidersStoreSchema = z.object({
27
+ providers: z.array(ProviderConfigSchema),
28
+ });
29
+
30
+ type ProvidersStore = z.infer<typeof ProvidersStoreSchema>;
31
+
32
+ // Using conf for storage - works in any Node.js environment without Electron
33
+ // Note: conf stores data in plain JSON. For production, consider additional encryption.
34
+ export const store = new Conf<ProvidersStore>({
35
+ projectName: "llm-inspector",
36
+ defaults: {
37
+ providers: [],
38
+ },
39
+ });
40
+
41
+ /**
42
+ * Migrates existing provider configs to preserve both Anthropic and OpenAI URLs.
43
+ * Old configs had anthropicBaseUrl/openaiBaseUrl, we now keep both.
44
+ */
45
+ function migrateProviders(): void {
46
+ const providers = store.get("providers", []);
47
+ let migrated = false;
48
+
49
+ const updated = providers.map((p) => {
50
+ // Helper to safely get old URL properties using Object.getOwnPropertyDescriptor
51
+ function getOldUrl(obj: unknown, key: string): string {
52
+ if (obj !== null && typeof obj === "object") {
53
+ const desc = Object.getOwnPropertyDescriptor(obj, key);
54
+ if (desc !== undefined && typeof desc.value === "string") {
55
+ return desc.value;
56
+ }
57
+ }
58
+ return "";
59
+ }
60
+
61
+ const oldAnthropicBaseUrl = getOldUrl(p, "anthropicBaseUrl");
62
+ const oldOpenaiBaseUrl = getOldUrl(p, "openaiBaseUrl");
63
+
64
+ // If already migrated (has format field and both URLs), skip
65
+ if (p.format !== undefined && oldAnthropicBaseUrl === "" && oldOpenaiBaseUrl === "") {
66
+ return p;
67
+ }
68
+
69
+ // Preserve both URLs
70
+ const newAnthropicUrl =
71
+ oldAnthropicBaseUrl !== "" ? oldAnthropicBaseUrl : (p.anthropicBaseUrl ?? "");
72
+ const newOpenaiUrl = oldOpenaiBaseUrl !== "" ? oldOpenaiBaseUrl : (p.openaiBaseUrl ?? "");
73
+
74
+ // Determine primary format based on which URL is set
75
+ let format: "anthropic" | "openai" | undefined;
76
+ let baseUrl: string | undefined;
77
+
78
+ if (newAnthropicUrl !== "" && newOpenaiUrl !== "") {
79
+ // Both URLs set - prefer anthropic as default, use baseUrl for backward compat
80
+ format = p.format ?? "anthropic";
81
+ baseUrl = p.baseUrl !== undefined && p.baseUrl !== "" ? p.baseUrl : newAnthropicUrl;
82
+ } else if (newOpenaiUrl !== "") {
83
+ format = "openai";
84
+ baseUrl = newOpenaiUrl;
85
+ } else if (newAnthropicUrl !== "") {
86
+ format = "anthropic";
87
+ baseUrl = newAnthropicUrl;
88
+ }
89
+
90
+ migrated = true;
91
+
92
+ return {
93
+ ...p,
94
+ format,
95
+ baseUrl,
96
+ anthropicBaseUrl: newAnthropicUrl,
97
+ openaiBaseUrl: newOpenaiUrl,
98
+ };
99
+ });
100
+
101
+ if (migrated) {
102
+ store.set("providers", updated);
103
+ }
104
+ }
105
+
106
+ // Run migration on module load
107
+ migrateProviders();
108
+
109
+ export function getProviders(): ProviderConfig[] {
110
+ return store.get("providers", []);
111
+ }
112
+
113
+ export function getProvider(id: string): ProviderConfig | undefined {
114
+ const providers = getProviders();
115
+ return providers.find((p) => p.id === id);
116
+ }
117
+
118
+ /**
119
+ * Normalizes an API key by stripping "Bearer " prefix if present.
120
+ */
121
+ function normalizeApiKey(apiKey: string): string {
122
+ return apiKey.replace(/^Bearer\s+/i, "").trim();
123
+ }
124
+
125
+ export function addProvider(
126
+ name: string,
127
+ apiKey: string,
128
+ format: "anthropic" | "openai",
129
+ baseUrl?: string,
130
+ model?: string,
131
+ authHeader?: "bearer" | "x-api-key",
132
+ ): ProviderConfig {
133
+ const providers = getProviders();
134
+ const now = new Date().toISOString();
135
+ const newProvider: ProviderConfig = {
136
+ id: randomUUID(),
137
+ name,
138
+ apiKey: normalizeApiKey(apiKey),
139
+ format,
140
+ baseUrl,
141
+ model,
142
+ authHeader: authHeader ?? "bearer",
143
+ createdAt: now,
144
+ updatedAt: now,
145
+ };
146
+ providers.push(newProvider);
147
+ store.set("providers", providers);
148
+ return newProvider;
149
+ }
150
+
151
+ export function updateProvider(
152
+ id: string,
153
+ updates: Partial<Omit<ProviderConfig, "id" | "createdAt">>,
154
+ ): ProviderConfig | null {
155
+ const providers = getProviders();
156
+ const existing = providers.find((p) => p.id === id);
157
+ if (!existing) return null;
158
+
159
+ const updated: ProviderConfig = {
160
+ id: existing.id,
161
+ name: updates.name ?? existing.name,
162
+ apiKey: updates.apiKey !== undefined ? normalizeApiKey(updates.apiKey) : existing.apiKey,
163
+ model: updates.model !== undefined ? updates.model : existing.model,
164
+ format: updates.format ?? existing.format,
165
+ baseUrl: updates.baseUrl !== undefined ? updates.baseUrl : existing.baseUrl,
166
+ authHeader: updates.authHeader ?? existing.authHeader,
167
+ createdAt: existing.createdAt,
168
+ updatedAt: new Date().toISOString(),
169
+ };
170
+ const index = providers.findIndex((p) => p.id === id);
171
+ providers[index] = updated;
172
+ store.set("providers", providers);
173
+ return updated;
174
+ }
175
+
176
+ export function deleteProvider(id: string): boolean {
177
+ const providers = getProviders();
178
+ const filtered = providers.filter((p) => p.id !== id);
179
+ if (filtered.length === providers.length) return false;
180
+
181
+ store.set("providers", filtered);
182
+ return true;
183
+ }
184
+
185
+ /**
186
+ * Converts display model name to API usage name based on provider-specific rules.
187
+ * Default behavior: returns model as-is.
188
+ * MiniMax: replaces spaces with hyphens.
189
+ */
190
+ export function getModelUsageName(model: string, providerName?: string): string {
191
+ if (
192
+ providerName !== undefined &&
193
+ providerName !== "" &&
194
+ providerName.toLowerCase().includes("minimax")
195
+ ) {
196
+ return model.replace(/ /g, "-");
197
+ }
198
+ return model;
199
+ }
200
+
201
+ /**
202
+ * Normalize a model name for comparison: lowercase, replace whitespace with hyphens.
203
+ */
204
+ function normalizeModelName(name: string): string {
205
+ return name.toLowerCase().replace(/\s+/g, "-");
206
+ }
207
+
208
+ /**
209
+ * Finds a provider by model name using two strategies:
210
+ * 1. Case-insensitive prefix match against "{provider.name}-" (e.g., "deepseek-" matches "deepseek-*")
211
+ * 2. Case-insensitive match against provider.model field (with whitespace/hyphen normalization)
212
+ */
213
+ export function findProviderByModel(model: string): ProviderConfig | null {
214
+ const providers = getProviders();
215
+ const modelLower = model.toLowerCase();
216
+ const modelNormalized = normalizeModelName(model);
217
+
218
+ for (const provider of providers) {
219
+ // Strategy 1: provider name prefix match
220
+ const providerPrefix = (provider.name + "-").toLowerCase();
221
+ if (modelLower.startsWith(providerPrefix)) {
222
+ return provider;
223
+ }
224
+ // Strategy 2: match against provider.model field with normalization
225
+ if (
226
+ provider.model !== undefined &&
227
+ provider.model !== "" &&
228
+ modelNormalized === normalizeModelName(provider.model)
229
+ ) {
230
+ return provider;
231
+ }
232
+ }
233
+ return null;
234
+ }
@@ -0,0 +1,160 @@
1
+ import { z } from "zod";
2
+
3
+ // Import JsonValueSchema from shared location to avoid circular dependency
4
+ import { JsonValueSchema, type JsonValue } from "./formats/jsonSchema";
5
+
6
+ export { JsonValueSchema };
7
+ export type { JsonValue };
8
+
9
+ export type RequestFormat = "anthropic" | "openai" | "unknown";
10
+
11
+ function safeGetProperty(obj: unknown, key: string): unknown {
12
+ if (obj === null || typeof obj !== "object" || Array.isArray(obj)) return undefined;
13
+ const desc = Object.getOwnPropertyDescriptor(obj, key);
14
+ return desc?.value;
15
+ }
16
+
17
+ /**
18
+ * A single SSE event from a streaming response.
19
+ */
20
+ export const StreamingChunkSchema = z.object({
21
+ index: z.number(),
22
+ timestamp: z.number(),
23
+ type: z.string(),
24
+ data: JsonValueSchema,
25
+ });
26
+
27
+ export type StreamingChunk = z.infer<typeof StreamingChunkSchema>;
28
+
29
+ const StreamingChunksArraySchema = z.object({
30
+ chunks: z.array(StreamingChunkSchema),
31
+ truncated: z.boolean().optional().default(false),
32
+ });
33
+
34
+ export const CapturedLogSchema = z.object({
35
+ id: z.number(),
36
+ timestamp: z.string(),
37
+ method: z.string(),
38
+ path: z.string(),
39
+ model: z.string().nullable(),
40
+ sessionId: z.string().nullable(),
41
+ rawRequestBody: z.string().nullable(),
42
+ responseStatus: z.number().nullable(),
43
+ responseText: z.string().nullable(),
44
+ inputTokens: z.number().nullable(),
45
+ outputTokens: z.number().nullable(),
46
+ cacheCreationInputTokens: z.number().nullable(),
47
+ cacheReadInputTokens: z.number().nullable(),
48
+ elapsedMs: z.number().nullable(),
49
+ streaming: z.boolean(),
50
+ userAgent: z.string().nullable(),
51
+ origin: z.string().nullable(),
52
+ rawHeaders: z.record(z.string(), z.string()).optional(),
53
+ /** Headers sent to the upstream LLM */
54
+ headers: z.record(z.string(), z.string()).optional(),
55
+ apiFormat: z.enum(["anthropic", "openai", "unknown"]).default("unknown"),
56
+ isTest: z.boolean().optional().default(false),
57
+ providerName: z.string().nullable().optional(),
58
+ clientPort: z.number().nullable().optional(),
59
+ clientPid: z.number().nullable().optional(),
60
+ clientCwd: z.string().nullable().optional(),
61
+ clientProjectFolder: z.string().nullable().optional(),
62
+ streamingChunks: StreamingChunksArraySchema.optional(),
63
+ streamingChunksPath: z.string().nullable().optional(),
64
+ });
65
+
66
+ export type CapturedLog = z.infer<typeof CapturedLogSchema>;
67
+
68
+ export type TokenUsage = {
69
+ inputTokens: number | null;
70
+ outputTokens: number | null;
71
+ cacheCreationInputTokens: number | null;
72
+ cacheReadInputTokens: number | null;
73
+ };
74
+
75
+ // ============================================================
76
+ // Import types and schemas from formats
77
+ // ============================================================
78
+
79
+ // Import types and schemas from formats for internal use
80
+ import { InspectorRequestSchema, type InspectorRequest } from "./formats/anthropic/schemas";
81
+
82
+ // Re-export types and schemas from formats for backward compatibility
83
+ export {
84
+ InspectorRequestSchema,
85
+ InspectorResponseSchema,
86
+ SseEventSchema,
87
+ } from "./formats/anthropic/schemas";
88
+ export type {
89
+ InspectorRequest,
90
+ InspectorResponse,
91
+ ResponseContentBlockType,
92
+ } from "./formats/anthropic/schemas";
93
+
94
+ export {
95
+ OpenAIRequestSchema,
96
+ OpenAIResponseSchema,
97
+ OpenAISSERawChunkSchema,
98
+ type OpenAIResponse,
99
+ parseOpenAIResponse,
100
+ } from "./formats/openai/schemas";
101
+
102
+ // ============================================================
103
+ // Utility functions
104
+ // ============================================================
105
+
106
+ function detectFormat(rawBody: string | null): RequestFormat {
107
+ if (rawBody === null) return "unknown";
108
+ try {
109
+ const json: unknown = JSON.parse(rawBody);
110
+ if (typeof json === "object" && json !== null && !Array.isArray(json)) {
111
+ const keys = Object.keys(json);
112
+ if (keys.includes("model") && keys.includes("messages")) {
113
+ if (keys.includes("system") || keys.includes("tools")) {
114
+ return "anthropic";
115
+ }
116
+ const msgVal = safeGetProperty(json, "messages");
117
+ if (Array.isArray(msgVal)) {
118
+ return "openai";
119
+ }
120
+ }
121
+ }
122
+ return "unknown";
123
+ } catch {
124
+ return "unknown";
125
+ }
126
+ }
127
+
128
+ // Schema for model name in request body (used for routing)
129
+ const RequestModelSchema = z.object({
130
+ model: z.string(),
131
+ });
132
+
133
+ export function extractModelFromBody(body: string): string | null {
134
+ try {
135
+ const json: unknown = JSON.parse(body);
136
+ const parsed = RequestModelSchema.safeParse(json);
137
+ if (parsed.success) {
138
+ return parsed.data.model;
139
+ }
140
+ return null;
141
+ } catch {
142
+ return null;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Parse a raw request body into an InspectorRequest.
148
+ * @deprecated Use FormatHandler.parseRequest() instead
149
+ */
150
+ export function parseRequest(rawBody: string | null): InspectorRequest | null {
151
+ if (rawBody === null) return null;
152
+ try {
153
+ const json: unknown = JSON.parse(rawBody);
154
+ const result = InspectorRequestSchema.safeParse(json);
155
+ if (result.success) return result.data;
156
+ return null;
157
+ } catch {
158
+ return null;
159
+ }
160
+ }
@@ -0,0 +1,158 @@
1
+ import { exec } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+
4
+ const execAsync = promisify(exec);
5
+
6
+ type ClientInfo = {
7
+ port: number | null;
8
+ pid: number | null;
9
+ cwd: string | null;
10
+ projectFolder: string | null;
11
+ };
12
+
13
+ type CacheEntry = ClientInfo & {
14
+ expiresAt: number;
15
+ };
16
+
17
+ // Cache entries: port -> { pid, cwd, projectFolder, expiresAt }
18
+ const cache = new Map<number, CacheEntry>();
19
+
20
+ // Cache TTL in milliseconds (5 minutes)
21
+ const CACHE_TTL_MS = 5 * 60 * 1000;
22
+
23
+ function getFromCache(port: number): ClientInfo | null {
24
+ const entry = cache.get(port);
25
+ if (entry && Date.now() < entry.expiresAt) {
26
+ return { port: entry.port, pid: entry.pid, cwd: entry.cwd, projectFolder: entry.projectFolder };
27
+ }
28
+ // Expired or not found
29
+ if (entry) cache.delete(port);
30
+ return null;
31
+ }
32
+
33
+ function setCache(port: number, info: ClientInfo): void {
34
+ cache.set(port, { ...info, expiresAt: Date.now() + CACHE_TTL_MS });
35
+ }
36
+
37
+ /**
38
+ * Get the remote port from a Request's underlying socket.
39
+ * Works with Bun, Node.js, and Nitro's event.node.req.
40
+ */
41
+ function extractRemotePort(request: Request): number | null {
42
+ // Try Bun's socket property by accessing the underlying socket
43
+ // Bun's Request has a socket property with remotePort that standard Request doesn't have
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
45
+ const socket = (request as any).socket;
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
47
+ const remotePort = (socket as any)?.remotePort;
48
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
49
+ if (remotePort !== undefined && remotePort !== null) return remotePort;
50
+
51
+ // Try to get from headers (some proxies add x-forwarded-port)
52
+ // This is a fallback - not all requests will have this
53
+
54
+ return null;
55
+ }
56
+
57
+ /**
58
+ * Look up PID from a local port using netstat.
59
+ * Returns PID as number, or null if not found.
60
+ */
61
+ async function lookupPidByPort(port: number): Promise<number | null> {
62
+ try {
63
+ // netstat -aon finds all connections with port, -n avoids DNS resolution
64
+ // Filter by ESTABLISHED connections on the specific port
65
+ const { stdout } = await execAsync(`netstat -aon | findstr :${port} | findstr ESTABLISHED`, {
66
+ windowsHide: true,
67
+ });
68
+
69
+ const lines = stdout.trim().split("\n").filter(Boolean);
70
+ for (const line of lines) {
71
+ // Format: TCP 127.0.0.1:12345 127.0.0.1:54321 ESTABLISHED 12345
72
+ // The last space-separated token is the PID
73
+ const parts = line.trim().split(/\s+/);
74
+ const lastPart = parts[parts.length - 1];
75
+ if (lastPart !== undefined) {
76
+ const pid = parseInt(lastPart, 10);
77
+ if (!isNaN(pid) && pid > 0) return pid;
78
+ }
79
+ }
80
+ return null;
81
+ } catch {
82
+ return null;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Get the working directory and command line for a process by PID.
88
+ * Returns { cwd, projectFolder } or { cwd: null, projectFolder: null }.
89
+ */
90
+ async function lookupProcessInfo(
91
+ pid: number,
92
+ ): Promise<{ cwd: string | null; projectFolder: string | null }> {
93
+ try {
94
+ // Use wmic to get the full command line
95
+ const { stdout } = await execAsync(
96
+ `wmic process where processid=${pid} get commandline /value`,
97
+ { windowsHide: true },
98
+ );
99
+
100
+ const lines = stdout.trim().split("\n").filter(Boolean);
101
+ for (const line of lines) {
102
+ if (line.includes("=")) {
103
+ const commandLine = (line.split("=")[1] ?? "").trim();
104
+ if (commandLine) {
105
+ // Extract CWD from command line
106
+ // The command typically looks like: "C:\path\to\ai-tool\resources\app\bin\cli\win32-x64\tool.exe" ...
107
+ // or "C:\Users\user\AppData\Local\Programs\Claude\Claude.exe" --some-flag
108
+ // We need to find the working directory - it's usually the directory containing the exe
109
+ // but we also need to check if a --parent-dir flag was passed
110
+
111
+ // Parse the command line to find the executable path
112
+ const exeMatch = commandLine.match(/^"([^"]+)"/) || commandLine.match(/^([^\s]+)/);
113
+ if (exeMatch && exeMatch[1] !== undefined) {
114
+ const exePath = exeMatch[1];
115
+ const exeDir = exePath.substring(0, exePath.lastIndexOf("\\"));
116
+ return {
117
+ cwd: exeDir,
118
+ projectFolder: exeDir.split("\\").pop() ?? null,
119
+ };
120
+ }
121
+ }
122
+ }
123
+ }
124
+ return { cwd: null, projectFolder: null };
125
+ } catch {
126
+ return { cwd: null, projectFolder: null };
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Get client info (PID, CWD, project folder) from a request's source port.
132
+ * Uses caching to avoid repeated netstat/wmic calls.
133
+ */
134
+ export async function getClientInfo(request: Request): Promise<ClientInfo> {
135
+ const port = extractRemotePort(request);
136
+ if (port === null) {
137
+ return { port: null, pid: null, cwd: null, projectFolder: null };
138
+ }
139
+
140
+ // Check cache first
141
+ const cached = getFromCache(port);
142
+ if (cached) return cached;
143
+
144
+ // Lookup PID from port
145
+ const pid = await lookupPidByPort(port);
146
+ if (pid === null) {
147
+ const info = { port, pid: null, cwd: null, projectFolder: null };
148
+ setCache(port, info);
149
+ return info;
150
+ }
151
+
152
+ // Lookup process info from PID
153
+ const { cwd, projectFolder } = await lookupProcessInfo(pid);
154
+
155
+ const info = { port, pid, cwd, projectFolder };
156
+ setCache(port, info);
157
+ return info;
158
+ }