@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,50 @@
1
+ import type { CapturedLog } from "../../schemas";
2
+ import type { ProviderProtocol } from "../protocol";
3
+ import { registry } from "../providers";
4
+ import { extractOpenAIStream } from "./stream";
5
+
6
+ const DEFAULT_OPENAI_UPSTREAM = "https://api.openai.com/v1";
7
+
8
+ /**
9
+ * OpenAI Provider Implementation
10
+ */
11
+ export const openaiProvider: ProviderProtocol = {
12
+ name: "openai",
13
+
14
+ matches(model: string): boolean {
15
+ const modelLower = model.toLowerCase();
16
+ // Match OpenAI models
17
+ if (modelLower.startsWith("openai-")) {
18
+ return true;
19
+ }
20
+ // Also match common OpenAI model names
21
+ if (
22
+ modelLower.includes("gpt-") ||
23
+ modelLower.includes("o1") ||
24
+ modelLower.includes("o2") ||
25
+ modelLower.includes("o3")
26
+ ) {
27
+ return true;
28
+ }
29
+ return false;
30
+ },
31
+
32
+ getUpstreamBase(_isChatCompletions: boolean, providerConfig?: { baseUrl?: string }): string {
33
+ if (providerConfig?.baseUrl !== undefined) {
34
+ return providerConfig.baseUrl;
35
+ }
36
+ return DEFAULT_OPENAI_UPSTREAM;
37
+ },
38
+
39
+ extractStream(
40
+ raw: string,
41
+ log: CapturedLog,
42
+ fallbackModel?: string,
43
+ collectChunks?: boolean,
44
+ ): string {
45
+ return extractOpenAIStream(raw, log, fallbackModel, collectChunks);
46
+ },
47
+ };
48
+
49
+ // Register the provider
50
+ registry.register(openaiProvider);
@@ -0,0 +1,4 @@
1
+ import { formatRegistry } from "../registry";
2
+ import { OpenAIFormatHandler } from "./handler";
3
+
4
+ formatRegistry.register(OpenAIFormatHandler);
@@ -0,0 +1,187 @@
1
+ import { z } from "zod";
2
+ import { JsonValueSchema } from "../jsonSchema";
3
+
4
+ // OpenAI Chat Completions Schemas
5
+
6
+ export const OpenAIMessageContent = z.union([
7
+ z.string(),
8
+ z.array(
9
+ z.discriminatedUnion("type", [
10
+ z.object({ type: z.literal("text"), text: z.string() }),
11
+ z.object({
12
+ type: z.literal("image_url"),
13
+ image_url: z.object({ url: z.string(), detail: z.string().optional() }),
14
+ }),
15
+ ]),
16
+ ),
17
+ ]);
18
+
19
+ export const OpenAIMessage = z.object({
20
+ role: z.enum(["system", "user", "assistant", "tool"]),
21
+ content: OpenAIMessageContent,
22
+ name: z.string().optional(),
23
+ reasoning_content: z.string().optional(),
24
+ thinking: z.string().optional(), // Some providers use 'thinking' field
25
+ think: z.string().optional(), // MiniMax uses 'think' field
26
+ });
27
+
28
+ export const OpenAIFunctionCall = z.object({
29
+ name: z.string(),
30
+ arguments: z.string(),
31
+ });
32
+
33
+ /** A single OpenAI tool_call (the modern multi-tool form, returned by most
34
+ * providers in `choices[].message.tool_calls`). `function.arguments` is
35
+ * a JSON-encoded string per the OpenAI spec — we keep it as a string at
36
+ * parse time and let the renderer decide whether to JSON-parse it for
37
+ * display. */
38
+ export const OpenAIToolCallSchema = z.object({
39
+ index: z.number().optional(),
40
+ id: z.string().optional(),
41
+ type: z.literal("function").optional(),
42
+ function: z.object({
43
+ name: z.string().optional(),
44
+ arguments: z.string().optional(),
45
+ }),
46
+ });
47
+
48
+ export type OpenAIToolCall = z.infer<typeof OpenAIToolCallSchema>;
49
+
50
+ const OpenAIMessageWithFunctionCall = OpenAIMessage.extend({
51
+ content: z
52
+ .union([z.string(), z.array(z.object({ type: z.literal("text"), text: z.string() }))])
53
+ .optional(),
54
+ function_call: OpenAIFunctionCall.optional(),
55
+ tool_calls: z.array(OpenAIToolCallSchema).optional(),
56
+ });
57
+
58
+ export const OpenAIToolDefinition = z.object({
59
+ type: z.literal("function"),
60
+ function: z.object({
61
+ name: z.string(),
62
+ description: z.string().optional(),
63
+ parameters: z.record(z.string(), JsonValueSchema),
64
+ }),
65
+ });
66
+
67
+ export const OpenAIRequestSchema = z.object({
68
+ model: z.string(),
69
+ messages: z.array(OpenAIMessage),
70
+ temperature: z.number().optional(),
71
+ max_tokens: z.number().optional(),
72
+ stream: z.boolean().optional(),
73
+ tools: z.array(OpenAIToolDefinition).optional(),
74
+ tool_choice: z
75
+ .union([
76
+ z.enum(["auto", "none", "required"]),
77
+ z.object({ type: z.literal("auto") }),
78
+ z.object({ type: z.literal("none") }),
79
+ z.object({ type: z.literal("function"), function: z.object({ name: z.string() }) }),
80
+ ])
81
+ .optional(),
82
+ user: z.string().optional(),
83
+ });
84
+
85
+ export const OpenAIChoiceDelta = z.object({
86
+ role: z.enum(["assistant"]).optional(),
87
+ content: z.string().nullable().optional(),
88
+ reasoning_content: z.string().nullable().optional(),
89
+ thinking: z.string().nullable().optional(), // Some providers use 'thinking' field
90
+ think: z.string().nullable().optional(), // MiniMax uses 'think' field
91
+ function_call: z
92
+ .object({ name: z.string().optional(), arguments: z.string().optional() })
93
+ .nullable()
94
+ .optional(),
95
+ tool_calls: z
96
+ .array(
97
+ z.object({
98
+ index: z.number(),
99
+ id: z.string().optional(),
100
+ type: z.literal("function").optional(),
101
+ function: z.object({
102
+ name: z.string().optional(),
103
+ arguments: z.string().optional(),
104
+ }),
105
+ }),
106
+ )
107
+ .nullable()
108
+ .optional(),
109
+ });
110
+
111
+ export const OpenAIChoice = z.object({
112
+ index: z.number(),
113
+ message: z
114
+ .object({
115
+ role: z.enum(["assistant"]),
116
+ content: z.string().nullable(),
117
+ reasoning_content: z.string().optional(),
118
+ thinking: z.string().optional(), // Some providers use 'thinking' field in message
119
+ think: z.string().optional(), // MiniMax uses 'think' field in message
120
+ function_call: z.object({ name: z.string(), arguments: z.string() }).nullable().optional(),
121
+ tool_calls: z.array(OpenAIToolCallSchema).optional(),
122
+ })
123
+ .optional(),
124
+ delta: OpenAIChoiceDelta.optional(),
125
+ finish_reason: z.string().nullable(),
126
+ });
127
+
128
+ export const OpenAIResponseSchema = z
129
+ .object({
130
+ id: z.string(),
131
+ object: z.literal("chat.completion"),
132
+ created: z.number(),
133
+ model: z.string(),
134
+ choices: z.array(OpenAIChoice),
135
+ usage: z
136
+ .object({
137
+ prompt_tokens: z.number().nullable().optional(),
138
+ completion_tokens: z.number().nullable().optional(),
139
+ total_tokens: z.number().nullable().optional(),
140
+ })
141
+ .passthrough(), // Allow extra fields like prompt_tokens_details, completion_tokens_details
142
+ service_tier: z.string().nullable().optional(),
143
+ system_fingerprint: z.string().nullable().optional(),
144
+ })
145
+ .passthrough(); // Allow extra fields like refusal, annotations, audio
146
+
147
+ // Schema for actual OpenAI SSE chunk format (not wrapped in event/data)
148
+ export const OpenAISSERawChunkSchema = z
149
+ .object({
150
+ id: z.string(),
151
+ object: z.literal("chat.completion.chunk"),
152
+ created: z.number(),
153
+ model: z.string(),
154
+ choices: z.array(
155
+ z.object({
156
+ index: z.number(),
157
+ delta: OpenAIChoiceDelta,
158
+ finish_reason: z.string().nullable().optional(),
159
+ }),
160
+ ),
161
+ usage: z
162
+ .object({
163
+ prompt_tokens: z.number().nullable().optional(),
164
+ completion_tokens: z.number().nullable().optional(),
165
+ total_tokens: z.number().nullable().optional(),
166
+ })
167
+ .passthrough()
168
+ .nullable()
169
+ .optional(),
170
+ })
171
+ .passthrough(); // Allow extra fields in chunks
172
+
173
+ export type OpenAIResponse = z.infer<typeof OpenAIResponseSchema>;
174
+
175
+ /**
176
+ * Parse an OpenAI response body.
177
+ */
178
+ export function parseOpenAIResponse(rawBody: string): OpenAIResponse | null {
179
+ try {
180
+ const json: unknown = JSON.parse(rawBody);
181
+ const result = OpenAIResponseSchema.safeParse(json);
182
+ if (result.success) return result.data;
183
+ return null;
184
+ } catch {
185
+ return null;
186
+ }
187
+ }
@@ -0,0 +1,206 @@
1
+ import type { CapturedLog, JsonValue } from "../../schemas";
2
+ import { trustAsJsonValue } from "../jsonSchema";
3
+ import { OpenAISSERawChunkSchema } from "./schemas";
4
+
5
+ /**
6
+ * Extract OpenAI streaming response and return normalized JSON.
7
+ * Optionally collects SSE chunks into log.streamingChunks for debugging/display.
8
+ */
9
+ export function extractOpenAIStream(
10
+ raw: string,
11
+ log: CapturedLog,
12
+ fallbackModel?: string,
13
+ collectChunks: boolean = true,
14
+ ): string {
15
+ let id = "";
16
+ let model = "";
17
+ let completionContent = "";
18
+ let reasoningContent = "";
19
+ let finishReason: string | null = null;
20
+ let promptTokens = 0;
21
+ let completionTokens = 0;
22
+ let usageCaptured = false;
23
+ let started = false;
24
+ type ToolCallEntry = {
25
+ index: number;
26
+ id?: string;
27
+ type: "function";
28
+ function: { name: string; arguments: string };
29
+ };
30
+ // Map keyed by `index` so delta accumulation is O(1) per delta instead of
31
+ // O(N) (the previous Array#find). Map preserves insertion order, so the
32
+ // final `[...toolCalls.values()]` keeps tool calls in the order they were
33
+ // first seen.
34
+ const toolCalls: Map<number, ToolCallEntry> = new Map();
35
+
36
+ // Chunk collection state
37
+ const MAX_CHUNKS = 1000;
38
+ let chunkIndex = 0;
39
+ let streamStartMs = 0;
40
+ const chunks: Array<{ index: number; timestamp: number; type: string; data: JsonValue }> = [];
41
+
42
+ for (const line of raw.split("\n")) {
43
+ const trimmedLine = line.trim();
44
+ if (!trimmedLine.startsWith("data: ")) continue;
45
+ const dataStr = trimmedLine.slice(6);
46
+ if (dataStr === "[DONE]") break;
47
+
48
+ try {
49
+ const parsed: unknown = JSON.parse(dataStr);
50
+ // Check for error object in SSE stream before trying to parse as chunk
51
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
52
+ const errorDesc = Object.getOwnPropertyDescriptor(parsed, "error");
53
+ if (
54
+ errorDesc !== undefined &&
55
+ typeof errorDesc.value === "object" &&
56
+ errorDesc.value !== null
57
+ ) {
58
+ const msgDesc = Object.getOwnPropertyDescriptor(errorDesc.value, "message");
59
+ if (msgDesc !== undefined && typeof msgDesc.value === "string") {
60
+ log.error = msgDesc.value;
61
+ }
62
+ continue;
63
+ }
64
+ }
65
+ const chunkResult = OpenAISSERawChunkSchema.safeParse(parsed);
66
+ if (!chunkResult.success) continue;
67
+ const chunk = chunkResult.data;
68
+
69
+ // Track stream start for timestamps
70
+ if (chunkIndex === 0) streamStartMs = Date.now();
71
+
72
+ // Collect chunks if enabled
73
+ if (collectChunks === true && chunks.length < MAX_CHUNKS) {
74
+ // `chunk` has already been validated by OpenAISSERawChunkSchema above;
75
+ // skipping the recursive JsonValue walk avoids a second full-tree zod
76
+ // parse on every SSE event. See trustAsJsonValue for the rationale.
77
+ chunks.push({
78
+ index: chunkIndex,
79
+ timestamp: Date.now() - streamStartMs,
80
+ type: "chat.completion.chunk",
81
+ data: trustAsJsonValue(chunk),
82
+ });
83
+ }
84
+ chunkIndex++;
85
+
86
+ if (!started) {
87
+ id = chunk.id;
88
+ model = chunk.model;
89
+ started = true;
90
+ }
91
+
92
+ if (!usageCaptured && chunk.usage !== undefined && chunk.usage !== null) {
93
+ promptTokens = chunk.usage.prompt_tokens ?? 0;
94
+ completionTokens = chunk.usage.completion_tokens ?? 0;
95
+ log.inputTokens = promptTokens;
96
+ // Extract cached_tokens from raw parsed object (passthrough field in usage)
97
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
98
+ const usageDesc = Object.getOwnPropertyDescriptor(parsed, "usage");
99
+ if (
100
+ usageDesc !== undefined &&
101
+ typeof usageDesc.value === "object" &&
102
+ usageDesc.value !== null
103
+ ) {
104
+ const detailsDesc = Object.getOwnPropertyDescriptor(
105
+ usageDesc.value,
106
+ "prompt_tokens_details",
107
+ );
108
+ if (
109
+ detailsDesc !== undefined &&
110
+ typeof detailsDesc.value === "object" &&
111
+ detailsDesc.value !== null
112
+ ) {
113
+ const cacheDesc = Object.getOwnPropertyDescriptor(detailsDesc.value, "cached_tokens");
114
+ if (cacheDesc !== undefined && typeof cacheDesc.value === "number") {
115
+ log.cacheReadInputTokens = cacheDesc.value;
116
+ }
117
+ }
118
+ }
119
+ }
120
+ usageCaptured = true;
121
+ }
122
+
123
+ for (const choice of chunk.choices) {
124
+ const delta = choice.delta;
125
+ if (delta.content !== undefined && delta.content !== null) {
126
+ completionContent += delta.content;
127
+ }
128
+ if (delta.reasoning_content !== undefined && delta.reasoning_content !== null) {
129
+ reasoningContent += delta.reasoning_content;
130
+ }
131
+ if (delta.thinking !== undefined && delta.thinking !== null) {
132
+ reasoningContent += delta.thinking;
133
+ }
134
+ // MiniMax uses 'think' field for thinking content - check via bracket notation
135
+ const thinkValue = delta.think;
136
+ if (thinkValue !== undefined && thinkValue !== null) {
137
+ reasoningContent += thinkValue;
138
+ }
139
+ if (choice.finish_reason !== undefined && choice.finish_reason !== null) {
140
+ finishReason = choice.finish_reason;
141
+ }
142
+ // Accumulate tool_calls
143
+ if (delta.tool_calls !== undefined && delta.tool_calls !== null) {
144
+ for (const tc of delta.tool_calls) {
145
+ // Find or create tool call entry by index
146
+ let existing = toolCalls.get(tc.index);
147
+ if (existing === undefined) {
148
+ existing = {
149
+ index: tc.index,
150
+ type: "function",
151
+ function: { name: "", arguments: "" },
152
+ };
153
+ toolCalls.set(tc.index, existing);
154
+ }
155
+ if (tc.id !== undefined) existing.id = tc.id;
156
+ if (tc.function?.name !== undefined) existing.function.name += tc.function.name;
157
+ if (tc.function?.arguments !== undefined)
158
+ existing.function.arguments += tc.function.arguments;
159
+ }
160
+ }
161
+ }
162
+ } catch {
163
+ // non-JSON SSE line, skip
164
+ }
165
+ }
166
+
167
+ // Store collected chunks on the log
168
+ if (collectChunks === true) {
169
+ log.streamingChunks = {
170
+ chunks,
171
+ truncated: chunkIndex > MAX_CHUNKS,
172
+ };
173
+ }
174
+
175
+ if (!usageCaptured) {
176
+ completionTokens = Math.ceil(completionContent.length / 4);
177
+ }
178
+
179
+ log.outputTokens = completionTokens;
180
+
181
+ const message: Record<string, unknown> = {
182
+ role: "assistant",
183
+ content: completionContent,
184
+ };
185
+ if (reasoningContent) message.reasoning_content = reasoningContent;
186
+ if (toolCalls.size > 0) message.tool_calls = [...toolCalls.values()];
187
+
188
+ return JSON.stringify({
189
+ id,
190
+ object: "chat.completion",
191
+ created: Math.floor(Date.now() / 1000),
192
+ model: model !== "" ? model : (fallbackModel ?? ""),
193
+ choices: [
194
+ {
195
+ index: 0,
196
+ message,
197
+ finish_reason: finishReason,
198
+ },
199
+ ],
200
+ usage: {
201
+ prompt_tokens: promptTokens,
202
+ completion_tokens: completionTokens,
203
+ total_tokens: promptTokens + completionTokens,
204
+ },
205
+ });
206
+ }
@@ -0,0 +1,50 @@
1
+ import type { CapturedLog, RequestFormat } from "../schemas";
2
+
3
+ /**
4
+ * Provider Protocol Interface
5
+ *
6
+ * Defines the contract that all providers must implement to support
7
+ * different LLM APIs (Anthropic, OpenAI, etc.).
8
+ */
9
+ export type ProviderProtocol = {
10
+ /** Provider identifier (e.g., "anthropic", "openai") */
11
+ name: string;
12
+
13
+ /**
14
+ * Check if this provider handles the given model.
15
+ * @param model - Model name from request
16
+ * @returns true if this provider should handle the model
17
+ */
18
+ matches(model: string): boolean;
19
+
20
+ /**
21
+ * Get the upstream base URL for this provider.
22
+ * @param isChatCompletions - Whether the request uses OpenAI chat completions format
23
+ * @param providerConfig - Optional provider configuration for custom base URL
24
+ * @returns Upstream base URL
25
+ */
26
+ getUpstreamBase(isChatCompletions: boolean, providerConfig?: { baseUrl?: string }): string;
27
+
28
+ /**
29
+ * Extract structured response from streaming data.
30
+ * @param raw - Raw SSE stream data
31
+ * @param log - Captured log entry to update
32
+ * @param fallbackModel - Fallback model name if not in stream
33
+ * @param collectChunks - Whether to collect SSE chunks into log.streamingChunks
34
+ * @returns JSON string of structured response
35
+ */
36
+ extractStream(
37
+ raw: string,
38
+ log: CapturedLog,
39
+ fallbackModel?: string,
40
+ collectChunks?: boolean,
41
+ ): string;
42
+
43
+ /**
44
+ * Optional request transformation hook.
45
+ * @param body - Original request body
46
+ * @param apiFormat - Request format (anthropic/openai/unknown)
47
+ * @returns Transformed body or null to use original
48
+ */
49
+ transformRequest?(body: string, apiFormat: RequestFormat): string | null;
50
+ };
@@ -0,0 +1,51 @@
1
+ import type { ProviderProtocol } from "./protocol";
2
+
3
+ /**
4
+ * Provider Registry
5
+ *
6
+ * Singleton registry for provider implementations.
7
+ * Providers register themselves at module load time.
8
+ */
9
+ class ProviderRegistryImpl {
10
+ private providers: ProviderProtocol[] = [];
11
+
12
+ /**
13
+ * Register a provider with the registry.
14
+ * @param provider - Provider implementation to register
15
+ */
16
+ register(provider: ProviderProtocol): void {
17
+ this.providers.push(provider);
18
+ }
19
+
20
+ /**
21
+ * Find the first provider that matches the given model.
22
+ * @param model - Model name to match
23
+ * @returns Matching provider or null if none found
24
+ */
25
+ findProvider(model: string): ProviderProtocol | null {
26
+ let fallback: ProviderProtocol | null = null;
27
+
28
+ for (const provider of this.providers) {
29
+ if (provider.matches(model)) {
30
+ // Non-anthropic providers win immediately (explicit match)
31
+ if (provider.name !== "anthropic") {
32
+ return provider;
33
+ }
34
+ // Anthropic is the fallback — only use if nothing else matches
35
+ fallback = provider;
36
+ }
37
+ }
38
+ return fallback;
39
+ }
40
+
41
+ /**
42
+ * Get all registered providers.
43
+ * @returns Array of all registered providers
44
+ */
45
+ getAll(): ProviderProtocol[] {
46
+ return [...this.providers];
47
+ }
48
+ }
49
+
50
+ /** Global registry instance */
51
+ export const registry = new ProviderRegistryImpl();
@@ -0,0 +1,3 @@
1
+ // Provider types and registry
2
+ export type { ProviderProtocol } from "../protocol";
3
+ export { registry } from "../providerRegistry";
@@ -0,0 +1,66 @@
1
+ import type { FormatHandler } from "./handler";
2
+ import type { RequestFormat } from "../schemas";
3
+ import { PATH_V1_CHAT_COMPLETIONS, PATH_CHAT_COMPLETIONS, PATH_V1_MESSAGES } from "../constants";
4
+
5
+ /**
6
+ * Format registry — maps request paths and formats to their FormatHandler.
7
+ *
8
+ * Registered at module load time by each format's init module.
9
+ */
10
+ class FormatRegistryImpl {
11
+ private handlers = new Map<RequestFormat, FormatHandler>();
12
+ private pathMap = new Map<string, RequestFormat>();
13
+
14
+ register(handler: FormatHandler): void {
15
+ this.handlers.set(handler.format, handler);
16
+ }
17
+
18
+ registerPath(path: string, format: RequestFormat): void {
19
+ this.pathMap.set(path, format);
20
+ }
21
+
22
+ /** Get handler by format identifier */
23
+ get(format: RequestFormat): FormatHandler | undefined {
24
+ return this.handlers.get(format);
25
+ }
26
+
27
+ /** Get handler matching a request path */
28
+ getByPath(path: string): FormatHandler | undefined {
29
+ const messagesPath = path.split("?")[0] ?? "";
30
+ const format = this.pathMap.get(messagesPath);
31
+ return format === undefined ? undefined : this.handlers.get(format);
32
+ }
33
+
34
+ /** Detect format from request body content */
35
+ detectFormat(rawBody: string | null): RequestFormat {
36
+ if (rawBody === null) return "unknown";
37
+ for (const handler of this.handlers.values()) {
38
+ if (handler.detectFormat(rawBody)) return handler.format;
39
+ }
40
+ return "unknown";
41
+ }
42
+ }
43
+
44
+ export const formatRegistry = new FormatRegistryImpl();
45
+
46
+ /**
47
+ * Get the FormatHandler for a request path.
48
+ * Returns null if no format matches the path.
49
+ */
50
+ export function formatForPath(path: string): FormatHandler | null {
51
+ return formatRegistry.getByPath(path) ?? null;
52
+ }
53
+
54
+ /**
55
+ * Resolve the wire format from the request endpoint. The endpoint is
56
+ * authoritative because Anthropic and OpenAI request bodies overlap for
57
+ * simple message-only calls.
58
+ */
59
+ export function requestFormatForPath(path: string): RequestFormat {
60
+ return formatForPath(path)?.format ?? "unknown";
61
+ }
62
+
63
+ // Register known paths
64
+ formatRegistry.registerPath(PATH_V1_MESSAGES, "anthropic");
65
+ formatRegistry.registerPath(PATH_V1_CHAT_COMPLETIONS, "openai");
66
+ formatRegistry.registerPath(PATH_CHAT_COMPLETIONS, "openai");