@tonyclaw/agent-inspector 2.0.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 (400) hide show
  1. package/.output/cli.js +1611 -0
  2. package/.output/nitro.json +17 -0
  3. package/.output/public/assets/CompareDrawer-CU5ZrWcL.js +1 -0
  4. package/.output/public/assets/ProxyViewerContainer-pEBqVp1d.js +101 -0
  5. package/.output/public/assets/ReplayDialog-F58yNg5j.js +1 -0
  6. package/.output/public/assets/RequestAnatomy-C9lT0qE_.js +1 -0
  7. package/.output/public/assets/ResponseView-DHJq6bnz.js +1 -0
  8. package/.output/public/assets/StreamingChunkSequence-BTgfpFUT.js +1 -0
  9. package/.output/public/assets/_sessionId-DsNRbnNm.js +1 -0
  10. package/.output/public/assets/alibaba-TTwafVwX.svg +1 -0
  11. package/.output/public/assets/index-CpWG2hFn.css +1 -0
  12. package/.output/public/assets/index-DmBV8Gve.js +1 -0
  13. package/.output/public/assets/json-viewer-CZVYLR8j.js +14 -0
  14. package/.output/public/assets/main-DHs7FBK3.js +18 -0
  15. package/.output/public/assets/minimax-BPMzvuL-.jpeg +0 -0
  16. package/.output/public/assets/qwen-CONDcHqt.png +0 -0
  17. package/.output/public/assets/zhipuai-BPNAnxo-.svg +219 -0
  18. package/.output/server/_chunks/ssr-renderer.mjs +22 -0
  19. package/.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs +1 -0
  20. package/.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs +210 -0
  21. package/.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs +2 -0
  22. package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +2 -0
  23. package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +2 -0
  24. package/.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs +11 -0
  25. package/.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs +69 -0
  26. package/.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs +1 -0
  27. package/.output/server/_libs/@radix-ui/react-use-escape-keydown+[...].mjs +17 -0
  28. package/.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs +1 -0
  29. package/.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs +6 -0
  30. package/.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs +34 -0
  31. package/.output/server/_libs/ajv-formats.mjs +330 -0
  32. package/.output/server/_libs/ajv.mjs +11444 -0
  33. package/.output/server/_libs/aria-hidden.mjs +122 -0
  34. package/.output/server/_libs/atomically.mjs +152 -0
  35. package/.output/server/_libs/bail.mjs +8 -0
  36. package/.output/server/_libs/cfworker__json-schema.mjs +1 -0
  37. package/.output/server/_libs/character-entities.mjs +2130 -0
  38. package/.output/server/_libs/class-variance-authority.mjs +44 -0
  39. package/.output/server/_libs/clsx.mjs +16 -0
  40. package/.output/server/_libs/comma-separated-tokens.mjs +10 -0
  41. package/.output/server/_libs/conf.mjs +635 -0
  42. package/.output/server/_libs/cookie-es.mjs +58 -0
  43. package/.output/server/_libs/core-util-is.mjs +75 -0
  44. package/.output/server/_libs/croner.mjs +1 -0
  45. package/.output/server/_libs/crossws.mjs +1 -0
  46. package/.output/server/_libs/debounce-fn.mjs +69 -0
  47. package/.output/server/_libs/decode-named-character-reference+[...].mjs +8 -0
  48. package/.output/server/_libs/dequal.mjs +27 -0
  49. package/.output/server/_libs/detect-node-es.mjs +1 -0
  50. package/.output/server/_libs/devlop.mjs +8 -0
  51. package/.output/server/_libs/diff.mjs +320 -0
  52. package/.output/server/_libs/dot-prop.mjs +265 -0
  53. package/.output/server/_libs/env-paths.mjs +57 -0
  54. package/.output/server/_libs/estree-util-is-identifier-name.mjs +11 -0
  55. package/.output/server/_libs/extend.mjs +97 -0
  56. package/.output/server/_libs/fast-deep-equal.mjs +38 -0
  57. package/.output/server/_libs/fast-uri.mjs +812 -0
  58. package/.output/server/_libs/floating-ui__core.mjs +725 -0
  59. package/.output/server/_libs/floating-ui__dom.mjs +622 -0
  60. package/.output/server/_libs/floating-ui__react-dom.mjs +292 -0
  61. package/.output/server/_libs/floating-ui__utils.mjs +320 -0
  62. package/.output/server/_libs/get-nonce.mjs +9 -0
  63. package/.output/server/_libs/h3-v2.mjs +276 -0
  64. package/.output/server/_libs/h3.mjs +408 -0
  65. package/.output/server/_libs/hast-util-to-jsx-runtime.mjs +388 -0
  66. package/.output/server/_libs/hast-util-whitespace.mjs +10 -0
  67. package/.output/server/_libs/hookable.mjs +1 -0
  68. package/.output/server/_libs/html-url-attributes.mjs +26 -0
  69. package/.output/server/_libs/immediate.mjs +74 -0
  70. package/.output/server/_libs/inherits.mjs +50 -0
  71. package/.output/server/_libs/inline-style-parser.mjs +142 -0
  72. package/.output/server/_libs/is-plain-obj.mjs +10 -0
  73. package/.output/server/_libs/isarray.mjs +14 -0
  74. package/.output/server/_libs/isbot.mjs +20 -0
  75. package/.output/server/_libs/json-schema-traverse.mjs +180 -0
  76. package/.output/server/_libs/jszip.mjs +3051 -0
  77. package/.output/server/_libs/lie.mjs +273 -0
  78. package/.output/server/_libs/lucide-react.mjs +492 -0
  79. package/.output/server/_libs/mdast-util-from-markdown.mjs +717 -0
  80. package/.output/server/_libs/mdast-util-to-hast.mjs +710 -0
  81. package/.output/server/_libs/mdast-util-to-string.mjs +38 -0
  82. package/.output/server/_libs/micromark-core-commonmark.mjs +2259 -0
  83. package/.output/server/_libs/micromark-factory-destination.mjs +94 -0
  84. package/.output/server/_libs/micromark-factory-label.mjs +63 -0
  85. package/.output/server/_libs/micromark-factory-space.mjs +24 -0
  86. package/.output/server/_libs/micromark-factory-title.mjs +65 -0
  87. package/.output/server/_libs/micromark-factory-whitespace.mjs +22 -0
  88. package/.output/server/_libs/micromark-util-character.mjs +44 -0
  89. package/.output/server/_libs/micromark-util-chunked.mjs +36 -0
  90. package/.output/server/_libs/micromark-util-classify-character+[...].mjs +12 -0
  91. package/.output/server/_libs/micromark-util-combine-extensions+[...].mjs +41 -0
  92. package/.output/server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +19 -0
  93. package/.output/server/_libs/micromark-util-decode-string.mjs +21 -0
  94. package/.output/server/_libs/micromark-util-encode.mjs +1 -0
  95. package/.output/server/_libs/micromark-util-html-tag-name.mjs +69 -0
  96. package/.output/server/_libs/micromark-util-normalize-identifier+[...].mjs +6 -0
  97. package/.output/server/_libs/micromark-util-resolve-all.mjs +15 -0
  98. package/.output/server/_libs/micromark-util-sanitize-uri.mjs +41 -0
  99. package/.output/server/_libs/micromark-util-subtokenize.mjs +346 -0
  100. package/.output/server/_libs/micromark.mjs +906 -0
  101. package/.output/server/_libs/mimic-function.mjs +47 -0
  102. package/.output/server/_libs/modelcontextprotocol__server.mjs +9738 -0
  103. package/.output/server/_libs/ocache.mjs +1 -0
  104. package/.output/server/_libs/ohash.mjs +1 -0
  105. package/.output/server/_libs/pako.mjs +4223 -0
  106. package/.output/server/_libs/process-nextick-args.mjs +48 -0
  107. package/.output/server/_libs/property-information.mjs +1209 -0
  108. package/.output/server/_libs/radix-ui.mjs +1 -0
  109. package/.output/server/_libs/radix-ui__number.mjs +6 -0
  110. package/.output/server/_libs/radix-ui__primitive.mjs +11 -0
  111. package/.output/server/_libs/radix-ui__react-accordion.mjs +1 -0
  112. package/.output/server/_libs/radix-ui__react-alert-dialog.mjs +1 -0
  113. package/.output/server/_libs/radix-ui__react-arrow.mjs +23 -0
  114. package/.output/server/_libs/radix-ui__react-aspect-ratio.mjs +1 -0
  115. package/.output/server/_libs/radix-ui__react-avatar.mjs +1 -0
  116. package/.output/server/_libs/radix-ui__react-checkbox.mjs +1 -0
  117. package/.output/server/_libs/radix-ui__react-collapsible.mjs +144 -0
  118. package/.output/server/_libs/radix-ui__react-collection.mjs +69 -0
  119. package/.output/server/_libs/radix-ui__react-compose-refs.mjs +39 -0
  120. package/.output/server/_libs/radix-ui__react-context-menu.mjs +1 -0
  121. package/.output/server/_libs/radix-ui__react-context.mjs +78 -0
  122. package/.output/server/_libs/radix-ui__react-dialog.mjs +325 -0
  123. package/.output/server/_libs/radix-ui__react-direction.mjs +9 -0
  124. package/.output/server/_libs/radix-ui__react-dropdown-menu.mjs +1 -0
  125. package/.output/server/_libs/radix-ui__react-focus-guards.mjs +29 -0
  126. package/.output/server/_libs/radix-ui__react-focus-scope.mjs +206 -0
  127. package/.output/server/_libs/radix-ui__react-form.mjs +1 -0
  128. package/.output/server/_libs/radix-ui__react-hover-card.mjs +1 -0
  129. package/.output/server/_libs/radix-ui__react-id.mjs +14 -0
  130. package/.output/server/_libs/radix-ui__react-label.mjs +1 -0
  131. package/.output/server/_libs/radix-ui__react-menu.mjs +1 -0
  132. package/.output/server/_libs/radix-ui__react-menubar.mjs +1 -0
  133. package/.output/server/_libs/radix-ui__react-popover.mjs +1 -0
  134. package/.output/server/_libs/radix-ui__react-popper.mjs +286 -0
  135. package/.output/server/_libs/radix-ui__react-portal.mjs +16 -0
  136. package/.output/server/_libs/radix-ui__react-presence.mjs +128 -0
  137. package/.output/server/_libs/radix-ui__react-primitive.mjs +42 -0
  138. package/.output/server/_libs/radix-ui__react-progress.mjs +1 -0
  139. package/.output/server/_libs/radix-ui__react-radio-group.mjs +1 -0
  140. package/.output/server/_libs/radix-ui__react-roving-focus.mjs +224 -0
  141. package/.output/server/_libs/radix-ui__react-scroll-area.mjs +721 -0
  142. package/.output/server/_libs/radix-ui__react-select.mjs +1163 -0
  143. package/.output/server/_libs/radix-ui__react-separator.mjs +28 -0
  144. package/.output/server/_libs/radix-ui__react-slider.mjs +1 -0
  145. package/.output/server/_libs/radix-ui__react-slot.mjs +99 -0
  146. package/.output/server/_libs/radix-ui__react-switch.mjs +1 -0
  147. package/.output/server/_libs/radix-ui__react-tabs.mjs +189 -0
  148. package/.output/server/_libs/radix-ui__react-toast.mjs +2 -0
  149. package/.output/server/_libs/radix-ui__react-toggle-group.mjs +1 -0
  150. package/.output/server/_libs/radix-ui__react-toggle.mjs +1 -0
  151. package/.output/server/_libs/radix-ui__react-toolbar.mjs +1 -0
  152. package/.output/server/_libs/radix-ui__react-tooltip.mjs +495 -0
  153. package/.output/server/_libs/radix-ui__react-use-previous.mjs +14 -0
  154. package/.output/server/_libs/radix-ui__react-use-size.mjs +39 -0
  155. package/.output/server/_libs/react-dom.mjs +10781 -0
  156. package/.output/server/_libs/react-markdown.mjs +147 -0
  157. package/.output/server/_libs/react-remove-scroll-bar.mjs +82 -0
  158. package/.output/server/_libs/react-remove-scroll.mjs +328 -0
  159. package/.output/server/_libs/react-style-singleton.mjs +69 -0
  160. package/.output/server/_libs/react.mjs +515 -0
  161. package/.output/server/_libs/readable-stream.mjs +1518 -0
  162. package/.output/server/_libs/remark-parse.mjs +19 -0
  163. package/.output/server/_libs/remark-rehype.mjs +21 -0
  164. package/.output/server/_libs/rou3.mjs +14 -0
  165. package/.output/server/_libs/safe-buffer.mjs +64 -0
  166. package/.output/server/_libs/semver.mjs +1938 -0
  167. package/.output/server/_libs/seroval-plugins.mjs +58 -0
  168. package/.output/server/_libs/seroval.mjs +1765 -0
  169. package/.output/server/_libs/setimmediate.mjs +152 -0
  170. package/.output/server/_libs/space-separated-tokens.mjs +6 -0
  171. package/.output/server/_libs/srvx.mjs +1029 -0
  172. package/.output/server/_libs/stubborn-fs.mjs +91 -0
  173. package/.output/server/_libs/stubborn-utils.mjs +66 -0
  174. package/.output/server/_libs/style-to-js.mjs +72 -0
  175. package/.output/server/_libs/style-to-object.mjs +38 -0
  176. package/.output/server/_libs/swr.mjs +939 -0
  177. package/.output/server/_libs/tailwind-merge.mjs +3010 -0
  178. package/.output/server/_libs/tanstack__history.mjs +217 -0
  179. package/.output/server/_libs/tanstack__react-router.mjs +1480 -0
  180. package/.output/server/_libs/tanstack__react-store.mjs +1 -0
  181. package/.output/server/_libs/tanstack__react-virtual.mjs +44 -0
  182. package/.output/server/_libs/tanstack__router-core.mjs +4827 -0
  183. package/.output/server/_libs/tanstack__store.mjs +1 -0
  184. package/.output/server/_libs/tanstack__virtual-core.mjs +1225 -0
  185. package/.output/server/_libs/tiny-invariant.mjs +12 -0
  186. package/.output/server/_libs/tiny-warning.mjs +5 -0
  187. package/.output/server/_libs/trim-lines.mjs +41 -0
  188. package/.output/server/_libs/trough.mjs +85 -0
  189. package/.output/server/_libs/tslib.mjs +1 -0
  190. package/.output/server/_libs/ufo.mjs +54 -0
  191. package/.output/server/_libs/uint8array-extras.mjs +69 -0
  192. package/.output/server/_libs/ungap__structured-clone.mjs +212 -0
  193. package/.output/server/_libs/unified.mjs +661 -0
  194. package/.output/server/_libs/unist-util-is.mjs +100 -0
  195. package/.output/server/_libs/unist-util-position.mjs +27 -0
  196. package/.output/server/_libs/unist-util-stringify-position.mjs +27 -0
  197. package/.output/server/_libs/unist-util-visit-parents.mjs +82 -0
  198. package/.output/server/_libs/unist-util-visit.mjs +24 -0
  199. package/.output/server/_libs/unstorage.mjs +1 -0
  200. package/.output/server/_libs/use-callback-ref.mjs +66 -0
  201. package/.output/server/_libs/use-sidecar.mjs +106 -0
  202. package/.output/server/_libs/use-sync-external-store.mjs +64 -0
  203. package/.output/server/_libs/util-deprecate.mjs +12 -0
  204. package/.output/server/_libs/vfile-message.mjs +138 -0
  205. package/.output/server/_libs/vfile.mjs +467 -0
  206. package/.output/server/_libs/when-exit.mjs +53 -0
  207. package/.output/server/_libs/zod.mjs +4524 -0
  208. package/.output/server/_sessionId-wMLPvC5g.mjs +123 -0
  209. package/.output/server/_ssr/CompareDrawer-BU4V0uVf.mjs +1041 -0
  210. package/.output/server/_ssr/ProxyViewerContainer-BnRwFEnn.mjs +5972 -0
  211. package/.output/server/_ssr/ReplayDialog-C7dn9pd_.mjs +322 -0
  212. package/.output/server/_ssr/RequestAnatomy-C1rWpe9-.mjs +353 -0
  213. package/.output/server/_ssr/ResponseView-hGpPaYsf.mjs +602 -0
  214. package/.output/server/_ssr/StreamingChunkSequence-BRWI1r_G.mjs +302 -0
  215. package/.output/server/_ssr/index-BKURLVPz.mjs +118 -0
  216. package/.output/server/_ssr/index.mjs +1184 -0
  217. package/.output/server/_ssr/json-viewer-BBd2DtQP.mjs +515 -0
  218. package/.output/server/_ssr/router-BcZ0D6AB.mjs +6317 -0
  219. package/.output/server/_ssr/start-HYkvq4Ni.mjs +4 -0
  220. package/.output/server/_tanstack-start-manifest_v-1y8ZVxRI.mjs +4 -0
  221. package/.output/server/index.mjs +436 -0
  222. package/.output/server/node_modules/tslib/modules/index.js +70 -0
  223. package/.output/server/node_modules/tslib/modules/package.json +3 -0
  224. package/.output/server/node_modules/tslib/package.json +47 -0
  225. package/.output/server/node_modules/tslib/tslib.js +484 -0
  226. package/.output/server/package.json +9 -0
  227. package/LICENSE +21 -0
  228. package/README.md +52 -0
  229. package/package.json +110 -0
  230. package/src/assets/favicon.svg +31 -0
  231. package/src/assets/logos/alibaba.svg +1 -0
  232. package/src/assets/logos/anthropic.svg +1 -0
  233. package/src/assets/logos/claude-code.svg +4 -0
  234. package/src/assets/logos/deepseek.svg +1 -0
  235. package/src/assets/logos/mcp.png +0 -0
  236. package/src/assets/logos/minimax.jpeg +0 -0
  237. package/src/assets/logos/openai.svg +1 -0
  238. package/src/assets/logos/opencode.svg +4 -0
  239. package/src/assets/logos/qwen.png +0 -0
  240. package/src/assets/logos/zhipuai.svg +219 -0
  241. package/src/cli/detect-tools.ts +147 -0
  242. package/src/cli/doctor.ts +521 -0
  243. package/src/cli/onboard.ts +224 -0
  244. package/src/cli/templates/command-onboard.ts +17 -0
  245. package/src/cli/templates/skill-onboard.ts +547 -0
  246. package/src/cli.ts +345 -0
  247. package/src/components/OnboardingBanner.tsx +67 -0
  248. package/src/components/ProxyViewer.tsx +545 -0
  249. package/src/components/ProxyViewerContainer.tsx +363 -0
  250. package/src/components/providers/ImportWizardDialog.tsx +349 -0
  251. package/src/components/providers/ProviderCard.tsx +474 -0
  252. package/src/components/providers/ProviderForm.tsx +494 -0
  253. package/src/components/providers/ProviderLogo.tsx +117 -0
  254. package/src/components/providers/ProvidersPanel.tsx +619 -0
  255. package/src/components/providers/SettingsDialog.tsx +202 -0
  256. package/src/components/proxy-viewer/CompareDrawer.tsx +893 -0
  257. package/src/components/proxy-viewer/ConversationGroup.tsx +107 -0
  258. package/src/components/proxy-viewer/ConversationHeader.tsx +300 -0
  259. package/src/components/proxy-viewer/LogEntry.tsx +543 -0
  260. package/src/components/proxy-viewer/LogEntryHeader.tsx +501 -0
  261. package/src/components/proxy-viewer/ReplayDialog.tsx +218 -0
  262. package/src/components/proxy-viewer/ResponseView.tsx +171 -0
  263. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +188 -0
  264. package/src/components/proxy-viewer/ThreadConnector.tsx +136 -0
  265. package/src/components/proxy-viewer/TurnGroup.tsx +337 -0
  266. package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +98 -0
  267. package/src/components/proxy-viewer/anatomy/SegmentBar.tsx +196 -0
  268. package/src/components/proxy-viewer/anatomy/tokenEstimate.ts +53 -0
  269. package/src/components/proxy-viewer/anatomy/types.ts +39 -0
  270. package/src/components/proxy-viewer/anatomy/useAnatomyJump.ts +114 -0
  271. package/src/components/proxy-viewer/cacheTrend.ts +50 -0
  272. package/src/components/proxy-viewer/diff/DiffView.tsx +321 -0
  273. package/src/components/proxy-viewer/diff/computeDiff.ts +178 -0
  274. package/src/components/proxy-viewer/diff/index.ts +3 -0
  275. package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +157 -0
  276. package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +66 -0
  277. package/src/components/proxy-viewer/formats/anthropic/thinkingExtract.ts +21 -0
  278. package/src/components/proxy-viewer/formats/index.tsx +33 -0
  279. package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +170 -0
  280. package/src/components/proxy-viewer/index.ts +9 -0
  281. package/src/components/proxy-viewer/lazy.ts +37 -0
  282. package/src/components/proxy-viewer/log-formats/anthropic.ts +194 -0
  283. package/src/components/proxy-viewer/log-formats/index.ts +23 -0
  284. package/src/components/proxy-viewer/log-formats/openai.ts +167 -0
  285. package/src/components/proxy-viewer/log-formats/types.ts +40 -0
  286. package/src/components/proxy-viewer/log-formats/unknown.ts +18 -0
  287. package/src/components/proxy-viewer/logEntryVisibility.ts +39 -0
  288. package/src/components/proxy-viewer/requestDiff.ts +277 -0
  289. package/src/components/proxy-viewer/useCopyFeedback.ts +36 -0
  290. package/src/components/proxy-viewer/useKeyboardNavigation.ts +190 -0
  291. package/src/components/proxy-viewer/viewerState.ts +66 -0
  292. package/src/components/ui/badge.tsx +47 -0
  293. package/src/components/ui/button.tsx +47 -0
  294. package/src/components/ui/collapsible.tsx +21 -0
  295. package/src/components/ui/confirm-dialog.tsx +51 -0
  296. package/src/components/ui/crab-logo.tsx +95 -0
  297. package/src/components/ui/crab-variants.tsx +467 -0
  298. package/src/components/ui/dialog.tsx +129 -0
  299. package/src/components/ui/json-expansion-button.tsx +56 -0
  300. package/src/components/ui/json-viewer-bulk.ts +97 -0
  301. package/src/components/ui/json-viewer.tsx +494 -0
  302. package/src/components/ui/mcp-logo.tsx +20 -0
  303. package/src/components/ui/scroll-area.tsx +54 -0
  304. package/src/components/ui/select.tsx +178 -0
  305. package/src/components/ui/separator.tsx +28 -0
  306. package/src/components/ui/tabs.tsx +88 -0
  307. package/src/components/ui/tooltip.tsx +51 -0
  308. package/src/index.css +11 -0
  309. package/src/knowledge/candidateStore.ts +63 -0
  310. package/src/knowledge/distiller.ts +98 -0
  311. package/src/knowledge/openclawClient.ts +118 -0
  312. package/src/knowledge/redactor.ts +80 -0
  313. package/src/knowledge/types.ts +84 -0
  314. package/src/lib/apiClient.ts +49 -0
  315. package/src/lib/export-logs.ts +51 -0
  316. package/src/lib/mask.ts +4 -0
  317. package/src/lib/objectUtils.ts +22 -0
  318. package/src/lib/providerContract.ts +26 -0
  319. package/src/lib/providerTestContract.ts +107 -0
  320. package/src/lib/runtimeConfig.ts +25 -0
  321. package/src/lib/serverPort.ts +41 -0
  322. package/src/lib/sessionUrl.ts +44 -0
  323. package/src/lib/stopReason.ts +58 -0
  324. package/src/lib/useOnboarding.ts +80 -0
  325. package/src/lib/useProviders.ts +30 -0
  326. package/src/lib/useStripConfig.ts +108 -0
  327. package/src/lib/utils.ts +21 -0
  328. package/src/mcp/loopback.ts +76 -0
  329. package/src/mcp/previewExtractor.ts +166 -0
  330. package/src/mcp/server.ts +396 -0
  331. package/src/mcp/toolHandlers.ts +341 -0
  332. package/src/proxy/chunkStorage.ts +112 -0
  333. package/src/proxy/claudeCodeStrip.ts +99 -0
  334. package/src/proxy/config.ts +172 -0
  335. package/src/proxy/constants.ts +47 -0
  336. package/src/proxy/dataDir.ts +86 -0
  337. package/src/proxy/formats/anthropic/anthropicProvider.ts +75 -0
  338. package/src/proxy/formats/anthropic/handler.ts +71 -0
  339. package/src/proxy/formats/anthropic/index.ts +14 -0
  340. package/src/proxy/formats/anthropic/register.ts +4 -0
  341. package/src/proxy/formats/anthropic/schemas.ts +237 -0
  342. package/src/proxy/formats/anthropic/stream.ts +205 -0
  343. package/src/proxy/formats/handler.ts +46 -0
  344. package/src/proxy/formats/index.ts +12 -0
  345. package/src/proxy/formats/jsonSchema.ts +36 -0
  346. package/src/proxy/formats/openai/alibabaProvider.ts +38 -0
  347. package/src/proxy/formats/openai/handler.ts +96 -0
  348. package/src/proxy/formats/openai/index.ts +25 -0
  349. package/src/proxy/formats/openai/provider.ts +50 -0
  350. package/src/proxy/formats/openai/register.ts +4 -0
  351. package/src/proxy/formats/openai/schemas.ts +187 -0
  352. package/src/proxy/formats/openai/stream.ts +206 -0
  353. package/src/proxy/formats/protocol.ts +50 -0
  354. package/src/proxy/formats/providerRegistry.ts +51 -0
  355. package/src/proxy/formats/providers/index.ts +3 -0
  356. package/src/proxy/formats/registry.ts +66 -0
  357. package/src/proxy/handler.ts +334 -0
  358. package/src/proxy/logFinalizer.ts +305 -0
  359. package/src/proxy/logFinalizer.worker.ts +24 -0
  360. package/src/proxy/logIndex.ts +268 -0
  361. package/src/proxy/logger.ts +179 -0
  362. package/src/proxy/openaiOrphanToolStrip.ts +142 -0
  363. package/src/proxy/providerImporters.ts +491 -0
  364. package/src/proxy/providers.ts +613 -0
  365. package/src/proxy/schemas.ts +209 -0
  366. package/src/proxy/sessionProcess.ts +140 -0
  367. package/src/proxy/sessionRuntime.ts +85 -0
  368. package/src/proxy/sessionSupervisor.ts +283 -0
  369. package/src/proxy/sessionWorkerEntry.ts +26 -0
  370. package/src/proxy/socketTracker.ts +255 -0
  371. package/src/proxy/store.ts +412 -0
  372. package/src/proxy/upstream.ts +90 -0
  373. package/src/router.tsx +16 -0
  374. package/src/routes/__root.tsx +45 -0
  375. package/src/routes/api/config.paths.ts +14 -0
  376. package/src/routes/api/config.ts +53 -0
  377. package/src/routes/api/health.ts +15 -0
  378. package/src/routes/api/knowledge.candidates.$candidateId.promote.ts +32 -0
  379. package/src/routes/api/knowledge.candidates.ts +10 -0
  380. package/src/routes/api/knowledge.project-context.ts +18 -0
  381. package/src/routes/api/knowledge.search.ts +31 -0
  382. package/src/routes/api/knowledge.sessions.$sessionId.candidates.ts +16 -0
  383. package/src/routes/api/logs.$id.chunks.ts +36 -0
  384. package/src/routes/api/logs.$id.replay.ts +191 -0
  385. package/src/routes/api/logs.$id.ts +22 -0
  386. package/src/routes/api/logs.stream.ts +74 -0
  387. package/src/routes/api/logs.ts +59 -0
  388. package/src/routes/api/mcp.ts +25 -0
  389. package/src/routes/api/models.ts +10 -0
  390. package/src/routes/api/providers.$providerId.test.log.ts +293 -0
  391. package/src/routes/api/providers.$providerId.ts +50 -0
  392. package/src/routes/api/providers.export.ts +26 -0
  393. package/src/routes/api/providers.import.ts +47 -0
  394. package/src/routes/api/providers.scan.ts +23 -0
  395. package/src/routes/api/providers.ts +45 -0
  396. package/src/routes/api/sessions.ts +17 -0
  397. package/src/routes/index.tsx +6 -0
  398. package/src/routes/proxy/$.ts +15 -0
  399. package/src/routes/session/$sessionId.tsx +23 -0
  400. package/styles/globals.css +188 -0
@@ -0,0 +1,613 @@
1
+ import { z } from "zod";
2
+ import Conf from "conf";
3
+ import { randomUUID } from "crypto";
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { logger } from "./logger";
7
+ import { getDataDir, hasExplicitDataDir } from "./dataDir";
8
+ import { ProviderConfigSchema, type ProviderConfig } from "../lib/providerContract";
9
+
10
+ export { ProviderConfigSchema };
11
+ export type { ProviderConfig };
12
+
13
+ const ProvidersStoreSchema = z.object({
14
+ providers: z.array(ProviderConfigSchema),
15
+ });
16
+
17
+ type ProvidersStore = z.infer<typeof ProvidersStoreSchema>;
18
+
19
+ const LegacyProviderConfigSchema = z.object({
20
+ id: z.string().optional(),
21
+ name: z.string().optional(),
22
+ type: z.enum(["anthropic", "openai"]).optional(),
23
+ format: z.enum(["anthropic", "openai"]).optional(),
24
+ apiKey: z.string().optional(),
25
+ model: z.string().optional(),
26
+ models: z.array(z.string()).optional(),
27
+ baseUrl: z.string().optional(),
28
+ anthropicBaseUrl: z.string().optional(),
29
+ openaiBaseUrl: z.string().optional(),
30
+ authHeader: z.enum(["bearer", "x-api-key"]).optional(),
31
+ apiDocsUrl: z.string().optional(),
32
+ source: z.enum(["company", "personal"]).optional(),
33
+ createdAt: z.string().optional(),
34
+ updatedAt: z.string().optional(),
35
+ });
36
+
37
+ export type LegacyProviderConfig = z.infer<typeof LegacyProviderConfigSchema>;
38
+
39
+ const LegacyProviderStoreSchema = z.object({
40
+ providers: z.array(LegacyProviderConfigSchema),
41
+ });
42
+
43
+ function legacyModels(provider: LegacyProviderConfig): string[] {
44
+ const modelList = provider.models?.map((model) => model.trim()).filter((model) => model !== "");
45
+ if (modelList !== undefined && modelList.length > 0) return modelList;
46
+ const model = provider.model?.trim();
47
+ if (model !== undefined && model !== "") return [model];
48
+ return [""];
49
+ }
50
+
51
+ export function migrateLegacyProvider(
52
+ provider: LegacyProviderConfig,
53
+ index: number,
54
+ now: string,
55
+ ): ProviderConfig | null {
56
+ const apiKey = normalizeApiKey(provider.apiKey ?? "");
57
+ if (apiKey === "") return null;
58
+
59
+ const format = provider.format ?? provider.type;
60
+ const fallbackName =
61
+ format === "openai" ? "OpenAI" : format === "anthropic" ? "Anthropic" : "Legacy Provider";
62
+ const trimmedName = provider.name?.trim();
63
+ const name =
64
+ trimmedName !== undefined && trimmedName !== "" ? trimmedName : `${fallbackName} ${index + 1}`;
65
+ const baseUrl = provider.baseUrl;
66
+ const anthropicBaseUrl =
67
+ provider.anthropicBaseUrl ?? (format === "anthropic" && baseUrl !== undefined ? baseUrl : "");
68
+ const openaiBaseUrl =
69
+ provider.openaiBaseUrl ?? (format === "openai" && baseUrl !== undefined ? baseUrl : "");
70
+
71
+ const candidate: ProviderConfig = {
72
+ id: provider.id ?? randomUUID(),
73
+ name,
74
+ apiKey,
75
+ model: provider.model,
76
+ models: legacyModels(provider),
77
+ format,
78
+ baseUrl,
79
+ anthropicBaseUrl,
80
+ openaiBaseUrl,
81
+ authHeader: provider.authHeader ?? "bearer",
82
+ apiDocsUrl: provider.apiDocsUrl,
83
+ source: provider.source,
84
+ createdAt: provider.createdAt ?? now,
85
+ updatedAt: provider.updatedAt ?? now,
86
+ };
87
+
88
+ const parsed = ProviderConfigSchema.safeParse(candidate);
89
+ return parsed.success ? parsed.data : null;
90
+ }
91
+
92
+ function migrateFromDataDirConfig(targetStore: Conf<ProvidersStore>, dataDirPath: string): void {
93
+ if (process.env["AGENT_INSPECTOR_CONFIG_PATH"] !== undefined) return;
94
+ if (targetStore.get("providers", []).length > 0) return;
95
+
96
+ const legacyConfigPath = join(dataDirPath, "config.json");
97
+ if (!existsSync(legacyConfigPath)) return;
98
+
99
+ try {
100
+ const raw = readFileSync(legacyConfigPath, "utf-8");
101
+ const parsedJson: unknown = JSON.parse(raw);
102
+ const parsed = LegacyProviderStoreSchema.safeParse(parsedJson);
103
+ if (!parsed.success) return;
104
+
105
+ const now = new Date().toISOString();
106
+ const migrated: ProviderConfig[] = [];
107
+ for (let index = 0; index < parsed.data.providers.length; index++) {
108
+ const provider = parsed.data.providers[index];
109
+ if (provider === undefined) continue;
110
+ const current = migrateLegacyProvider(provider, index, now);
111
+ if (current !== null) migrated.push(current);
112
+ }
113
+ if (migrated.length === 0) return;
114
+
115
+ targetStore.set("providers", migrated);
116
+ logger.info(
117
+ `[providers] Migrated ${migrated.length} provider(s) from ${legacyConfigPath} to ${targetStore.path}`,
118
+ );
119
+ } catch (err) {
120
+ logger.warn(`[providers] Data-dir config migration failed: ${String(err)}`);
121
+ }
122
+ }
123
+
124
+ /**
125
+ * One-time migration from the legacy conf v15 default location
126
+ * (`%APPDATA%\agent-inspector-nodejs\Config\config.json` on Windows,
127
+ * `$XDG_CONFIG_HOME/agent-inspector-nodejs/Config/config.json` on Linux).
128
+ * Older versions of agent-inspector let the `conf` library pick the path,
129
+ * which produced a project-suffixed directory under the OS user-config
130
+ * root. Users who upgrade now have their providers copied to the new
131
+ * `<dataDir>/providers.json` location; the legacy file is left in
132
+ * place for safety and can be deleted manually.
133
+ *
134
+ * The check is "new store is still empty" rather than "new file doesn't
135
+ * exist" because `Conf` materialises the defaults file on construction
136
+ * — so the file is always present immediately after the `new Conf()`
137
+ * call above, even on a brand-new install.
138
+ */
139
+ function migrateFromLegacyConfLocation(targetStore: Conf<ProvidersStore>): void {
140
+ if (process.env["AGENT_INSPECTOR_CONFIG_PATH"] !== undefined) return;
141
+ if (hasExplicitDataDir()) return;
142
+ if (targetStore.get("providers", []).length > 0) return;
143
+ let legacyStore: Conf<ProvidersStore> | undefined;
144
+ try {
145
+ legacyStore = new Conf<ProvidersStore>({
146
+ projectName: "agent-inspector",
147
+ defaults: { providers: [] },
148
+ });
149
+ const legacyProviders = legacyStore.get("providers", []);
150
+ if (legacyProviders.length === 0) return;
151
+ targetStore.set("providers", legacyProviders);
152
+ logger.info(
153
+ `[providers] Migrated ${legacyProviders.length} provider(s) from ${legacyStore.path} to ${targetStore.path}`,
154
+ );
155
+ } catch (err) {
156
+ logger.warn(`[providers] Legacy migration failed: ${String(err)}`);
157
+ } finally {
158
+ legacyStore?._closeWatcher();
159
+ }
160
+ }
161
+
162
+ // Using conf for storage - works in any Node.js environment without Electron
163
+ // Note: conf stores data in plain JSON. For production, consider additional encryption.
164
+ const configPath = process.env["AGENT_INSPECTOR_CONFIG_PATH"];
165
+
166
+ // The data directory is shared with the runtime config (`config.ts`).
167
+ // We pass `cwd` so conf writes the file as `<cwd>/<configName>.<ext>`,
168
+ // i.e. `<dataDir>/providers.json`. The runtime config lives at
169
+ // `<dataDir>/config.json`; the different filenames keep the two stores
170
+ // from overwriting each other. `mkdirSync(..., { recursive: true })` is
171
+ // a no-op when the directory already exists.
172
+ const dataDir = getDataDir();
173
+ if (configPath === undefined) {
174
+ mkdirSync(dataDir, { recursive: true });
175
+ }
176
+
177
+ export const store = new Conf<ProvidersStore>({
178
+ cwd: dataDir,
179
+ configName: "providers",
180
+ defaults: {
181
+ providers: [],
182
+ },
183
+ ...(configPath !== undefined ? { path: configPath } : {}),
184
+ });
185
+
186
+ // Older builds stored providers in `<dataDir>/config.json`. Migrate that
187
+ // shape before checking the older Conf-managed default location.
188
+ migrateFromDataDirConfig(store, dataDir);
189
+
190
+ // Run the legacy migration once on module load. No-op when the new file
191
+ // already has data, when the user has set AGENT_INSPECTOR_CONFIG_PATH or
192
+ // an explicit data dir, or when the legacy file is empty.
193
+ migrateFromLegacyConfLocation(store);
194
+
195
+ /**
196
+ * Expands the old `{ format, baseUrl }` representation into the matching
197
+ * format-specific URL. Once either format-specific field exists, the record is
198
+ * already on the current schema and must be preserved exactly: an empty or
199
+ * missing counterpart means that protocol was intentionally not configured.
200
+ */
201
+ export function migrateProvider(p: ProviderConfig): ProviderConfig {
202
+ if (p.anthropicBaseUrl !== undefined || p.openaiBaseUrl !== undefined) {
203
+ return p;
204
+ }
205
+ if (p.format === undefined || p.baseUrl === undefined || p.baseUrl === "") {
206
+ return p;
207
+ }
208
+
209
+ return p.format === "openai"
210
+ ? { ...p, anthropicBaseUrl: "", openaiBaseUrl: p.baseUrl }
211
+ : { ...p, anthropicBaseUrl: p.baseUrl, openaiBaseUrl: "" };
212
+ }
213
+
214
+ /**
215
+ * Migrates existing provider configs to preserve both Anthropic and OpenAI URLs.
216
+ * Old configs had anthropicBaseUrl/openaiBaseUrl, we now keep both.
217
+ */
218
+ function migrateProviders(): void {
219
+ const providers = store.get("providers", []);
220
+ const updated = providers.map(migrateProvider);
221
+ const migrated = updated.some((p, i) => JSON.stringify(p) !== JSON.stringify(providers[i]));
222
+
223
+ if (migrated) {
224
+ store.set("providers", updated);
225
+ }
226
+ }
227
+
228
+ // Run migration on module load
229
+ migrateProviders();
230
+
231
+ /**
232
+ * Migrate: merge providers with identical (apiKey, anthropicBaseUrl, openaiBaseUrl)
233
+ * into multi-model providers. Existing single-model providers get their `model`
234
+ * value promoted to `models: [model]`.
235
+ */
236
+ function migrateMultiModel(): void {
237
+ const providers = store.get("providers", []);
238
+ if (providers.length === 0) return;
239
+ let changed = false;
240
+
241
+ // Step 1: Promote single model to models array for any provider missing models
242
+ const promoted = providers.map((p) => {
243
+ if (p.models !== undefined && p.models.length > 0) return p;
244
+ const models = p.model !== undefined && p.model !== "" ? [p.model] : [];
245
+ changed = true;
246
+ return { ...p, models };
247
+ });
248
+
249
+ // Step 2: Merge providers with same (apiKey, anthropicBaseUrl, openaiBaseUrl)
250
+ const groups = new Map<string, ProviderConfig[]>();
251
+ for (const p of promoted) {
252
+ const key = `${p.apiKey}::${p.anthropicBaseUrl ?? ""}::${p.openaiBaseUrl ?? ""}`;
253
+ const group = groups.get(key);
254
+ if (group !== undefined) {
255
+ group.push(p);
256
+ } else {
257
+ groups.set(key, [p]);
258
+ }
259
+ }
260
+
261
+ const merged: ProviderConfig[] = [];
262
+ for (const group of groups.values()) {
263
+ const first = group[0];
264
+ if (group.length === 1 && first !== undefined) {
265
+ merged.push(first);
266
+ continue;
267
+ }
268
+ changed = true;
269
+ // Merge: keep the earliest provider's metadata, combine all models (deduplicated)
270
+ const sorted = group.toSorted(
271
+ (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
272
+ );
273
+ const earliest = sorted[0];
274
+ if (earliest === undefined) continue;
275
+ const allModels = new Set<string>();
276
+ for (const p of sorted) {
277
+ for (const m of p.models ?? []) {
278
+ if (m !== "") allModels.add(m);
279
+ }
280
+ }
281
+ // Take first non-empty source
282
+ const mergedSource = sorted.find((p) => p.source !== undefined)?.source;
283
+ merged.push({
284
+ ...earliest,
285
+ models: [...allModels],
286
+ source: mergedSource,
287
+ updatedAt: new Date().toISOString(),
288
+ });
289
+ }
290
+
291
+ if (changed && merged.length > 0) {
292
+ // Safety backup before writing
293
+ try {
294
+ writeFileSync(`${store.path}.bak`, readFileSync(store.path, "utf-8"));
295
+ } catch {
296
+ // best-effort, ignore
297
+ }
298
+ store.set("providers", merged);
299
+ }
300
+ }
301
+
302
+ migrateMultiModel();
303
+
304
+ // Override with JSON env var if provided (for testing)
305
+ const providersJson = process.env["AGENT_INSPECTOR_PROVIDERS_JSON"];
306
+ if (providersJson !== undefined) {
307
+ try {
308
+ const parsed = ProviderConfigSchema.array().safeParse(JSON.parse(providersJson));
309
+ if (parsed.success) {
310
+ // Apply migration to JSON providers (e.g., convert baseUrl to openaiBaseUrl/anthropicBaseUrl)
311
+ const migrated = parsed.data.map(migrateProvider);
312
+ store.set("providers", migrated);
313
+ }
314
+ } catch {
315
+ // Ignore invalid JSON
316
+ }
317
+ }
318
+
319
+ export function getProviders(): ProviderConfig[] {
320
+ return store.get("providers", []);
321
+ }
322
+
323
+ export function getProvider(id: string): ProviderConfig | undefined {
324
+ const providers = getProviders();
325
+ return providers.find((p) => p.id === id);
326
+ }
327
+
328
+ /**
329
+ * Normalizes an API key by stripping "Bearer " prefix if present.
330
+ */
331
+ export function normalizeApiKey(apiKey: string): string {
332
+ return apiKey.replace(/^Bearer\s+/i, "").trim();
333
+ }
334
+
335
+ export function addProvider(
336
+ name: string,
337
+ apiKey: string,
338
+ format?: "anthropic" | "openai",
339
+ baseUrl?: string,
340
+ models?: string[],
341
+ authHeader?: "bearer" | "x-api-key",
342
+ apiDocsUrl?: string,
343
+ anthropicBaseUrl?: string,
344
+ openaiBaseUrl?: string,
345
+ source?: "company" | "personal",
346
+ ): ProviderConfig {
347
+ const providers = getProviders();
348
+ const normalizedKey = normalizeApiKey(apiKey);
349
+ const resolvedAnthropicUrl =
350
+ anthropicBaseUrl ?? (format === "anthropic" && baseUrl !== undefined ? baseUrl : "");
351
+ const resolvedOpenaiUrl =
352
+ openaiBaseUrl ?? (format === "openai" && baseUrl !== undefined ? baseUrl : "");
353
+ const now = new Date().toISOString();
354
+
355
+ // If a provider with the same (apiKey, anthropicBaseUrl, openaiBaseUrl) already exists,
356
+ // merge the new models into it instead of creating a duplicate.
357
+ const existing = providers.find(
358
+ (p) =>
359
+ p.apiKey === normalizedKey &&
360
+ (p.anthropicBaseUrl ?? "") === (resolvedAnthropicUrl ?? "") &&
361
+ (p.openaiBaseUrl ?? "") === (resolvedOpenaiUrl ?? ""),
362
+ );
363
+ if (existing) {
364
+ const newModels = (models ?? []).filter((m) => m !== "");
365
+ if (newModels.length > 0) {
366
+ const mergedModels = new Set(existing.models ?? []);
367
+ for (const m of newModels) mergedModels.add(m);
368
+ existing.models = [...mergedModels];
369
+ existing.updatedAt = now;
370
+ store.set("providers", providers);
371
+ }
372
+ return existing;
373
+ }
374
+
375
+ const newProvider: ProviderConfig = {
376
+ id: randomUUID(),
377
+ name,
378
+ apiKey: normalizedKey,
379
+ format:
380
+ format ??
381
+ (anthropicBaseUrl !== undefined
382
+ ? "anthropic"
383
+ : openaiBaseUrl !== undefined
384
+ ? "openai"
385
+ : undefined),
386
+ baseUrl,
387
+ models: models ?? [],
388
+ authHeader: authHeader ?? "bearer",
389
+ apiDocsUrl,
390
+ createdAt: now,
391
+ updatedAt: now,
392
+ anthropicBaseUrl: resolvedAnthropicUrl,
393
+ openaiBaseUrl: resolvedOpenaiUrl,
394
+ source,
395
+ };
396
+ providers.push(newProvider);
397
+ store.set("providers", providers);
398
+ return newProvider;
399
+ }
400
+
401
+ export function updateProvider(
402
+ id: string,
403
+ updates: Partial<Omit<ProviderConfig, "id" | "createdAt">>,
404
+ ): ProviderConfig | null {
405
+ const providers = getProviders();
406
+ const existing = providers.find((p) => p.id === id);
407
+ if (!existing) return null;
408
+
409
+ const updated: ProviderConfig = {
410
+ id: existing.id,
411
+ name: updates.name ?? existing.name,
412
+ apiKey: updates.apiKey !== undefined ? normalizeApiKey(updates.apiKey) : existing.apiKey,
413
+ models: updates.models !== undefined ? updates.models : existing.models,
414
+ format: updates.format ?? existing.format,
415
+ baseUrl: updates.baseUrl !== undefined ? updates.baseUrl : existing.baseUrl,
416
+ authHeader: updates.authHeader ?? existing.authHeader,
417
+ apiDocsUrl: updates.apiDocsUrl ?? existing.apiDocsUrl,
418
+ createdAt: existing.createdAt,
419
+ updatedAt: new Date().toISOString(),
420
+ // Handle format-specific URLs
421
+ anthropicBaseUrl:
422
+ updates.anthropicBaseUrl !== undefined ? updates.anthropicBaseUrl : existing.anthropicBaseUrl,
423
+ openaiBaseUrl:
424
+ updates.openaiBaseUrl !== undefined ? updates.openaiBaseUrl : existing.openaiBaseUrl,
425
+ source: updates.source !== undefined ? updates.source : existing.source,
426
+ };
427
+ const index = providers.findIndex((p) => p.id === id);
428
+ providers[index] = updated;
429
+ store.set("providers", providers);
430
+ return updated;
431
+ }
432
+
433
+ export function deleteProvider(id: string): boolean {
434
+ const providers = getProviders();
435
+ const filtered = providers.filter((p) => p.id !== id);
436
+ if (filtered.length === providers.length) return false;
437
+
438
+ store.set("providers", filtered);
439
+ return true;
440
+ }
441
+
442
+ /**
443
+ * Export all providers as a JSON string (without API keys for security).
444
+ */
445
+ export function exportProviders(): string {
446
+ const providers = getProviders();
447
+ // Mask API keys for security
448
+ const safeProviders = providers.map(({ apiKey, ...rest }) => ({
449
+ ...rest,
450
+ apiKey: maskApiKey(apiKey),
451
+ }));
452
+ return JSON.stringify(safeProviders, null, 2);
453
+ }
454
+
455
+ /**
456
+ * Export all providers as a JSON string including API keys (user must opt-in).
457
+ */
458
+ export function exportProvidersWithKeys(): string {
459
+ return JSON.stringify(getProviders(), null, 2);
460
+ }
461
+
462
+ /**
463
+ * Import providers from a JSON string.
464
+ * Returns { imported: number, errors: string[] }
465
+ */
466
+ export function importProviders(json: string): { imported: number; errors: string[] } {
467
+ const errors: string[] = [];
468
+ let imported = 0;
469
+
470
+ try {
471
+ const parsed: unknown = JSON.parse(json);
472
+
473
+ // Use Zod to validate the structure
474
+ const ImportSchema = z.union([
475
+ z.array(ProviderConfigSchema),
476
+ z.object({
477
+ providers: z.array(ProviderConfigSchema),
478
+ }),
479
+ ]);
480
+
481
+ const parseResult = ImportSchema.safeParse(parsed);
482
+ if (!parseResult.success) {
483
+ return {
484
+ imported: 0,
485
+ errors: [`Invalid format: ${parseResult.error.message}`],
486
+ };
487
+ }
488
+
489
+ const providerArray = Array.isArray(parseResult.data)
490
+ ? parseResult.data
491
+ : parseResult.data.providers;
492
+
493
+ // Snapshot existing providers once
494
+ const existingProviders = getProviders();
495
+
496
+ // Build a Set of existing (name, anthropicBaseUrl) keys for O(1) duplicate lookup
497
+ const existingKeys = new Set<string>();
498
+ for (const p of existingProviders) {
499
+ existingKeys.add(`${p.name}::${p.anthropicBaseUrl ?? ""}`);
500
+ }
501
+
502
+ const newProviders: ProviderConfig[] = [];
503
+
504
+ for (let i = 0; i < providerArray.length; i++) {
505
+ const item = providerArray[i];
506
+ const result = ProviderConfigSchema.safeParse(item);
507
+
508
+ if (!result.success) {
509
+ errors.push(`Provider ${i + 1}: ${result.error.message}`);
510
+ continue;
511
+ }
512
+
513
+ const provider = result.data;
514
+ const key = `${provider.name}::${provider.anthropicBaseUrl ?? ""}`;
515
+
516
+ if (existingKeys.has(key)) {
517
+ errors.push(`Provider "${provider.name}" already exists, skipping`);
518
+ continue;
519
+ }
520
+
521
+ // Generate new ID and add
522
+ const newProvider: ProviderConfig = {
523
+ ...provider,
524
+ id: randomUUID(),
525
+ createdAt: new Date().toISOString(),
526
+ updatedAt: new Date().toISOString(),
527
+ };
528
+
529
+ newProviders.push(newProvider);
530
+ existingKeys.add(key);
531
+ imported++;
532
+ }
533
+
534
+ // Single write: append all new providers at once
535
+ if (newProviders.length > 0) {
536
+ store.set("providers", [...existingProviders, ...newProviders]);
537
+ }
538
+ } catch (err) {
539
+ return {
540
+ imported: 0,
541
+ errors: [`Failed to parse JSON: ${err instanceof Error ? err.message : String(err)}`],
542
+ };
543
+ }
544
+
545
+ return { imported, errors };
546
+ }
547
+
548
+ function maskApiKey(apiKey: string): string {
549
+ if (apiKey.length <= 8) return "••••••••";
550
+ return apiKey.slice(0, 4) + "••••••••" + apiKey.slice(-4);
551
+ }
552
+
553
+ /**
554
+ * Converts display model name to API usage name based on provider-specific rules.
555
+ * Default behavior: returns model as-is.
556
+ * MiniMax: replaces spaces with hyphens.
557
+ */
558
+ export function getModelUsageName(model: string, providerName?: string): string {
559
+ if (
560
+ providerName !== undefined &&
561
+ providerName !== "" &&
562
+ providerName.toLowerCase().includes("minimax")
563
+ ) {
564
+ return model.replace(/ /g, "-");
565
+ }
566
+ return model;
567
+ }
568
+
569
+ /**
570
+ * Normalize a model name for comparison: lowercase, replace whitespace with hyphens.
571
+ */
572
+ function normalizeModelName(name: string): string {
573
+ return name.toLowerCase().replace(/\s+/g, "-");
574
+ }
575
+
576
+ /**
577
+ * Finds a provider by model name using three strategies:
578
+ * 1. Case-insensitive prefix match against "{provider.name}-" (e.g., "deepseek-" matches "deepseek-*")
579
+ * 2. Case-insensitive match against provider.model field (with whitespace/hyphen normalization)
580
+ * 3. Fallback: concatenate provider name with provider's model and check if the request model matches
581
+ * this concatenation (handles "MiniMax" + "M3" → "MiniMax-M3" pattern when case differs)
582
+ */
583
+ export function findProviderByModel(model: string): ProviderConfig | null {
584
+ const providers = getProviders();
585
+ const modelLower = model.toLowerCase();
586
+ const modelNormalized = normalizeModelName(model);
587
+
588
+ for (const provider of providers) {
589
+ // Strategy 1: provider name prefix match
590
+ const providerPrefix = (provider.name + "-").toLowerCase();
591
+ if (modelLower.startsWith(providerPrefix)) {
592
+ return provider;
593
+ }
594
+ // Strategy 2: match against provider.models array with normalization
595
+ const modelList = provider.models ?? (provider.model !== undefined ? [provider.model] : []);
596
+ for (const m of modelList) {
597
+ if (m !== "" && modelNormalized === normalizeModelName(m)) {
598
+ return provider;
599
+ }
600
+ }
601
+ // Strategy 3: fallback - concatenate provider name with model part and compare
602
+ for (const m of modelList) {
603
+ if (m !== "") {
604
+ const modelPart = modelNormalized.replace(normalizeModelName(provider.name), "");
605
+ const concatenated = normalizeModelName(provider.name) + modelPart;
606
+ if (modelNormalized === concatenated) {
607
+ return provider;
608
+ }
609
+ }
610
+ }
611
+ }
612
+ return null;
613
+ }