@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,334 @@
1
+ import { createLog, finalizeLogUpdate, getNextLogId, type CapturedLog } from "./store";
2
+ import { appendLogEntry, logger } from "./logger";
3
+ import { extractRequestMetadata } from "./schemas";
4
+ import { registry } from "./formats";
5
+ import { findProviderByModel } from "./providers";
6
+ import { getClientInfo } from "./socketTracker";
7
+ import { formatForPath } from "./formats";
8
+ import {
9
+ PROXY_IDENTITY,
10
+ PRESERVE_HEADERS,
11
+ PATH_V1_MESSAGES,
12
+ HEADER_CONTENT_TYPE,
13
+ HEADER_USER_AGENT,
14
+ HEADER_X_PROXY_IDENTITY,
15
+ HEADER_CONTENT_ENCODING,
16
+ HEADER_CONTENT_LENGTH,
17
+ CONTENT_TYPE_EVENT_STREAM,
18
+ STATUS_FORBIDDEN,
19
+ STATUS_BAD_GATEWAY,
20
+ } from "./constants";
21
+ import { getConfig } from "./config";
22
+ import { stripClaudeCodeBillingHeader } from "./claudeCodeStrip";
23
+ import { stripOpenAIOrphanToolMessages } from "./openaiOrphanToolStrip";
24
+ import { buildFileLogEntry, type FinalizeLogJob } from "./logFinalizer";
25
+ import { enqueueFinalizeLogJob } from "./sessionRuntime";
26
+ import {
27
+ buildUpstreamUrl,
28
+ describeApiRoute,
29
+ getProxyApiPath,
30
+ injectProviderAuth,
31
+ selectUpstreamBase,
32
+ setUpstreamHost,
33
+ } from "./upstream";
34
+
35
+ /**
36
+ * Strips all custom/non-standard headers from the request and replaces with
37
+ * unified proxy identity. Only preserves standard HTTP headers needed for API calls.
38
+ */
39
+ function buildProxyHeaders(originalHeaders: Headers): {
40
+ headers: Headers;
41
+ rawHeaders: Record<string, string>;
42
+ } {
43
+ const rawHeaders: Record<string, string> = {};
44
+ const headers = new Headers();
45
+
46
+ originalHeaders.forEach((value, key) => {
47
+ rawHeaders[key.toLowerCase()] = value;
48
+ });
49
+
50
+ headers.set(HEADER_USER_AGENT, PROXY_IDENTITY);
51
+ headers.set(HEADER_X_PROXY_IDENTITY, PROXY_IDENTITY);
52
+
53
+ for (const name of PRESERVE_HEADERS) {
54
+ const value = originalHeaders.get(name);
55
+ if (value !== null) {
56
+ headers.set(name, value);
57
+ }
58
+ }
59
+
60
+ return { headers, rawHeaders };
61
+ }
62
+
63
+ type ParsedRequestPath = {
64
+ apiPath: string;
65
+ isMessages: boolean;
66
+ isChatCompletions: boolean;
67
+ normalizedPath: string;
68
+ };
69
+
70
+ function parseRequestPath(req: Request, url: URL): ParsedRequestPath {
71
+ const route = describeApiRoute(getProxyApiPath(url));
72
+ const isPost = req.method === "POST";
73
+ const isChatCompletions = isPost && route.isChatCompletions;
74
+ const isMessages = isPost && (route.endpointPath === PATH_V1_MESSAGES || route.isChatCompletions);
75
+
76
+ return {
77
+ apiPath: route.apiPath,
78
+ isMessages,
79
+ isChatCompletions,
80
+ normalizedPath: route.normalizedPath,
81
+ };
82
+ }
83
+
84
+ function errorMessage(err: unknown): string {
85
+ return err instanceof Error ? err.message : String(err);
86
+ }
87
+
88
+ function scheduleLogFinalization(job: FinalizeLogJob): void {
89
+ void enqueueFinalizeLogJob(job).catch((err) => {
90
+ logger.error(
91
+ `[handler] Session finalization task failed for log #${job.log.id}:`,
92
+ errorMessage(err),
93
+ );
94
+ });
95
+ }
96
+
97
+ function handleNonStreamingResponse(
98
+ upstreamRes: Response,
99
+ responseBody: string,
100
+ startTime: number,
101
+ upstreamUrl: string,
102
+ log: CapturedLog,
103
+ ): Response {
104
+ const elapsedMs = Date.now() - startTime;
105
+
106
+ scheduleLogFinalization({
107
+ type: "non-streaming",
108
+ log,
109
+ upstreamUrl,
110
+ elapsedMs,
111
+ responseStatus: upstreamRes.status,
112
+ responseBody,
113
+ });
114
+
115
+ const responseHeaders = new Headers(upstreamRes.headers);
116
+ responseHeaders.delete(HEADER_CONTENT_ENCODING);
117
+ responseHeaders.delete(HEADER_CONTENT_LENGTH);
118
+
119
+ return new Response(responseBody, {
120
+ status: upstreamRes.status,
121
+ headers: responseHeaders,
122
+ });
123
+ }
124
+
125
+ function handleStreamingResponse(
126
+ upstreamRes: Response,
127
+ req: Request,
128
+ startTime: number,
129
+ upstreamUrl: string,
130
+ log: CapturedLog,
131
+ ): Response {
132
+ log.streaming = true;
133
+ log.responseStatus = upstreamRes.status;
134
+
135
+ const chunks: string[] = [];
136
+ const decoder = new TextDecoder();
137
+
138
+ const transform = new TransformStream<Uint8Array, Uint8Array>({
139
+ transform(chunk, controller) {
140
+ controller.enqueue(chunk);
141
+ chunks.push(decoder.decode(chunk, { stream: true }));
142
+ },
143
+ flush() {
144
+ const full = chunks.join("");
145
+ const elapsedMs = Date.now() - startTime;
146
+
147
+ scheduleLogFinalization({
148
+ type: "streaming",
149
+ log,
150
+ upstreamUrl,
151
+ elapsedMs,
152
+ responseStatus: upstreamRes.status,
153
+ rawStream: full,
154
+ });
155
+ },
156
+ });
157
+
158
+ if (upstreamRes.body === null) {
159
+ return new Response("No response body", { status: STATUS_BAD_GATEWAY });
160
+ }
161
+
162
+ const loggedStream = upstreamRes.body.pipeThrough(transform);
163
+
164
+ req.signal?.addEventListener("abort", () => {
165
+ if (log.responseText === null) {
166
+ logger.info(`[handler] Streaming client aborted: ${log.method} ${log.path}`);
167
+ const elapsedMs = Date.now() - startTime;
168
+ const full = chunks.join("");
169
+ const hasChunks = chunks.length > 0;
170
+
171
+ scheduleLogFinalization({
172
+ type: "stream-abort",
173
+ log,
174
+ upstreamUrl,
175
+ elapsedMs,
176
+ rawStream: full,
177
+ hasChunks,
178
+ });
179
+ }
180
+ });
181
+
182
+ const responseHeaders = new Headers(upstreamRes.headers);
183
+ responseHeaders.delete(HEADER_CONTENT_ENCODING);
184
+ responseHeaders.delete(HEADER_CONTENT_LENGTH);
185
+
186
+ return new Response(loggedStream, {
187
+ status: upstreamRes.status,
188
+ headers: responseHeaders,
189
+ });
190
+ }
191
+
192
+ export async function handleProxy(req: Request): Promise<Response> {
193
+ const url = new URL(req.url);
194
+ const parsed = parseRequestPath(req, url);
195
+
196
+ // Read body only after cheap path checks
197
+ let requestBody: string | null = null;
198
+ if (req.body && req.method !== "GET" && req.method !== "HEAD") {
199
+ requestBody = await req.text();
200
+ }
201
+
202
+ // Opt-in: strip Claude Code's synthetic billing-header text block from the
203
+ // system prompt. Only runs for Anthropic-format requests (those whose path
204
+ // resolves to /v1/messages). The flag is read once per request from the
205
+ // runtime config, so a PATCH to /api/config takes effect on the next
206
+ // request without a restart.
207
+ //
208
+ // Important: the original `requestBody` must be preserved so the log stores
209
+ // the unmodified request (the "Raw Request" tab needs the true bytes the
210
+ // client sent). The rewritten body is only used for the upstream fetch.
211
+ let bodyToForward = requestBody;
212
+ if (getConfig().stripClaudeCodeBillingHeader && requestBody !== null && parsed.isMessages) {
213
+ const stripped = stripClaudeCodeBillingHeader(requestBody);
214
+ if (stripped.removed > 0) {
215
+ logger.info(
216
+ `[handler] Stripped ${stripped.removed} Claude Code billing-header block(s) from system prompt`,
217
+ );
218
+ bodyToForward = stripped.body;
219
+ }
220
+ }
221
+
222
+ // Always-on protocol guard: OpenAI Chat Completions requires every
223
+ // `role: "tool"` message to reference a `tool_call_id` present in a
224
+ // preceding assistant message's `tool_calls`. Buggy clients sometimes
225
+ // record a tool result without the matching assistant tool_call, which
226
+ // makes the upstream reject the request with a 400 ("tool result's tool
227
+ // id(call_xxx) not found"). Drop the orphan messages so the proxy can
228
+ // forward a valid request; the original body is still kept in the log.
229
+ if (bodyToForward !== null && parsed.isChatCompletions) {
230
+ const stripped = stripOpenAIOrphanToolMessages(bodyToForward);
231
+ if (stripped.removed > 0) {
232
+ logger.warn(
233
+ `[handler] Dropped ${stripped.removed} orphan OpenAI tool message(s) with tool_call_id(s) ${JSON.stringify(stripped.orphanIds)} — the client sent a tool result with no matching assistant.tool_calls`,
234
+ );
235
+ bodyToForward = stripped.body;
236
+ }
237
+ }
238
+
239
+ // Parse the request body exactly once. The handler needs the model for
240
+ // provider routing and the session id for the log; `createLog` would
241
+ // otherwise re-parse the same body to extract the same fields.
242
+ const { model, sessionId } = extractRequestMetadata(requestBody, req.headers);
243
+
244
+ // Find provider config using already-extracted model (not calling extractModelFromBody again)
245
+ const matchedProviderConfig = model !== null ? findProviderByModel(model) : null;
246
+ const route = describeApiRoute(parsed.apiPath);
247
+ const upstreamBase = selectUpstreamBase(route, matchedProviderConfig);
248
+ const upstreamUrl = buildUpstreamUrl(upstreamBase, parsed.normalizedPath);
249
+ const startTime = Date.now();
250
+
251
+ const { headers: upstreamHeaders, rawHeaders } = buildProxyHeaders(req.headers);
252
+ setUpstreamHost(upstreamHeaders, upstreamBase);
253
+ injectProviderAuth(upstreamHeaders, matchedProviderConfig);
254
+
255
+ const provider = model !== null ? registry.findProvider(model) : null;
256
+
257
+ // Only proxy requests matching a registered provider
258
+ if (model === null || provider === null) {
259
+ logger.warn(`[handler] Unsupported provider: model=${model}`);
260
+ return new Response("Forbidden: unsupported provider", { status: STATUS_FORBIDDEN });
261
+ }
262
+
263
+ // Pick the format handler from the request path. provider.format is only a
264
+ // hint used as a fallback when no anthropicBaseUrl/openaiBaseUrl is set, and
265
+ // does NOT describe what the upstream actually returns: a provider configured
266
+ // with format="openai" may still expose an Anthropic-compatible endpoint
267
+ // (e.g. MiniMax's /anthropic path), and the response shape always follows
268
+ // the URL path the client hit, not the provider.format label.
269
+ const formatHandler = formatForPath(parsed.apiPath);
270
+ if (formatHandler === null) {
271
+ return new Response("Forbidden: unsupported format", { status: STATUS_FORBIDDEN });
272
+ }
273
+
274
+ // Acquire client info and the next log id in parallel. On Windows
275
+ // `getClientInfo` shells out to PowerShell and is the single slowest
276
+ // thing on the hot path; serializing it before `createLog` paid the
277
+ // full PowerShell round-trip on top of everything else before
278
+ // `fetch` is even issued.
279
+ const [clientInfo, preAcquiredId] = await Promise.all([getClientInfo(req), getNextLogId()]);
280
+ const upstreamHeadersObj: Record<string, string> = {};
281
+ upstreamHeaders.forEach((value, key) => {
282
+ upstreamHeadersObj[key.toLowerCase()] = value;
283
+ });
284
+ const log = await createLog(
285
+ req.method,
286
+ parsed.apiPath,
287
+ requestBody,
288
+ req.headers,
289
+ clientInfo,
290
+ rawHeaders,
291
+ upstreamHeadersObj,
292
+ formatHandler.format,
293
+ model,
294
+ sessionId,
295
+ preAcquiredId,
296
+ );
297
+
298
+ let upstreamRes: Response;
299
+ try {
300
+ upstreamRes = await fetch(upstreamUrl, {
301
+ method: req.method,
302
+ headers: upstreamHeaders,
303
+ body: bodyToForward,
304
+ signal: req.signal,
305
+ });
306
+ } catch (err) {
307
+ log.elapsedMs = Date.now() - startTime;
308
+ // Check if it was a client abort (not a proxy error)
309
+ if (err instanceof Error && err.name === "AbortError") {
310
+ logger.info(`[handler] Client aborted: ${req.method} ${parsed.apiPath}`);
311
+ log.responseStatus = 499; // Client Closed Request (non-standard but descriptive)
312
+ log.responseText = "Client aborted";
313
+ appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: "Client aborted" });
314
+ finalizeLogUpdate(log);
315
+ return new Response("Client aborted", { status: 499 });
316
+ }
317
+ logger.error(`[handler] Proxy error: ${req.method} ${parsed.apiPath}`, String(err));
318
+ log.responseStatus = STATUS_BAD_GATEWAY;
319
+ log.responseText = String(err);
320
+ appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: String(err) });
321
+ finalizeLogUpdate(log);
322
+ return new Response(`Proxy error: ${err}`, { status: STATUS_BAD_GATEWAY });
323
+ }
324
+
325
+ const isStream =
326
+ upstreamRes.headers.get(HEADER_CONTENT_TYPE)?.includes(CONTENT_TYPE_EVENT_STREAM) ?? false;
327
+
328
+ if (!isStream) {
329
+ const responseBody = await upstreamRes.text();
330
+ return handleNonStreamingResponse(upstreamRes, responseBody, startTime, upstreamUrl, log);
331
+ }
332
+
333
+ return handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log);
334
+ }
@@ -0,0 +1,305 @@
1
+ import { Worker } from "node:worker_threads";
2
+ import { writeChunks } from "./chunkStorage";
3
+ import { formatForPath } from "./formats";
4
+ import { appendLogEntry, logger } from "./logger";
5
+ import type { CapturedLog } from "./schemas";
6
+ import { getSessionProcess, isSessionProcessAvailable } from "./sessionProcess";
7
+ import { finalizeLogUpdate } from "./store";
8
+
9
+ type BaseFinalizeLogJob = {
10
+ log: CapturedLog;
11
+ upstreamUrl: string;
12
+ elapsedMs: number;
13
+ };
14
+
15
+ export type FinalizeNonStreamingLogJob = BaseFinalizeLogJob & {
16
+ type: "non-streaming";
17
+ responseStatus: number;
18
+ responseBody: string;
19
+ };
20
+
21
+ export type FinalizeStreamingLogJob = BaseFinalizeLogJob & {
22
+ type: "streaming";
23
+ responseStatus: number;
24
+ rawStream: string;
25
+ };
26
+
27
+ export type FinalizeStreamAbortLogJob = BaseFinalizeLogJob & {
28
+ type: "stream-abort";
29
+ rawStream: string;
30
+ hasChunks: boolean;
31
+ };
32
+
33
+ export type FinalizeLogJob =
34
+ | FinalizeNonStreamingLogJob
35
+ | FinalizeStreamingLogJob
36
+ | FinalizeStreamAbortLogJob;
37
+
38
+ export type FinalizeLogResult = {
39
+ log: CapturedLog;
40
+ upstreamUrl: string;
41
+ error: string | null;
42
+ };
43
+
44
+ export function buildFileLogEntry(log: CapturedLog, upstreamUrl: string): Record<string, unknown> {
45
+ return {
46
+ timestamp: log.timestamp,
47
+ id: log.id,
48
+ method: log.method,
49
+ path: log.path,
50
+ model: log.model,
51
+ sessionId: log.sessionId,
52
+ rawRequestBody: log.rawRequestBody,
53
+ responseStatus: log.responseStatus,
54
+ responseText: log.responseText,
55
+ inputTokens: log.inputTokens,
56
+ outputTokens: log.outputTokens,
57
+ cacheCreationInputTokens: log.cacheCreationInputTokens,
58
+ cacheReadInputTokens: log.cacheReadInputTokens,
59
+ elapsedMs: log.elapsedMs,
60
+ streaming: log.streaming,
61
+ userAgent: log.userAgent,
62
+ origin: log.origin,
63
+ upstreamUrl,
64
+ clientPort: log.clientPort,
65
+ clientPid: log.clientPid,
66
+ clientCwd: log.clientCwd,
67
+ clientProjectFolder: log.clientProjectFolder,
68
+ streamingChunks: log.streamingChunks,
69
+ streamingChunksPath: log.streamingChunksPath,
70
+ error: log.error,
71
+ };
72
+ }
73
+
74
+ function cloneLog(log: CapturedLog): CapturedLog {
75
+ return { ...log };
76
+ }
77
+
78
+ function errorMessage(err: unknown): string {
79
+ return err instanceof Error ? err.message : String(err);
80
+ }
81
+
82
+ function persistStreamingChunks(log: CapturedLog): void {
83
+ if (log.streamingChunks !== undefined && log.streamingChunks.chunks.length > 0) {
84
+ const chunkPath = writeChunks(
85
+ log.id,
86
+ log.streamingChunks.chunks,
87
+ log.streamingChunks.truncated,
88
+ );
89
+ log.streamingChunksPath = chunkPath;
90
+ }
91
+ }
92
+
93
+ function finalizeWithError(
94
+ job: FinalizeLogJob,
95
+ log: CapturedLog,
96
+ fallbackStatus: number,
97
+ fallbackResponseText: string,
98
+ err: unknown,
99
+ ): FinalizeLogResult {
100
+ const message = errorMessage(err);
101
+ logger.error(`[logFinalizer] Failed to finalize log #${log.id}:`, message);
102
+ log.responseStatus = log.responseStatus ?? fallbackStatus;
103
+ log.responseText = log.responseText ?? fallbackResponseText;
104
+ log.elapsedMs = job.elapsedMs;
105
+ log.error = message;
106
+ return { log, upstreamUrl: job.upstreamUrl, error: message };
107
+ }
108
+
109
+ function finalizeNonStreaming(
110
+ job: FinalizeNonStreamingLogJob,
111
+ log: CapturedLog,
112
+ ): FinalizeLogResult {
113
+ const formatHandler = formatForPath(log.path);
114
+ if (formatHandler === null) {
115
+ return finalizeWithError(job, log, job.responseStatus, job.responseBody, "Unsupported format");
116
+ }
117
+
118
+ try {
119
+ const tokens = formatHandler.extractTokens(job.responseBody);
120
+
121
+ log.elapsedMs = job.elapsedMs;
122
+ log.responseStatus = job.responseStatus;
123
+ log.responseText = job.responseBody;
124
+ log.inputTokens = tokens.inputTokens;
125
+ log.outputTokens = tokens.outputTokens;
126
+ log.cacheCreationInputTokens = tokens.cacheCreationInputTokens;
127
+ log.cacheReadInputTokens = tokens.cacheReadInputTokens;
128
+
129
+ return { log, upstreamUrl: job.upstreamUrl, error: null };
130
+ } catch (err) {
131
+ return finalizeWithError(job, log, job.responseStatus, job.responseBody, err);
132
+ }
133
+ }
134
+
135
+ function finalizeStreaming(job: FinalizeStreamingLogJob, log: CapturedLog): FinalizeLogResult {
136
+ const formatHandler = formatForPath(log.path);
137
+ if (formatHandler === null) {
138
+ return finalizeWithError(job, log, job.responseStatus, job.rawStream, "Unsupported format");
139
+ }
140
+
141
+ try {
142
+ log.elapsedMs = job.elapsedMs;
143
+ log.responseStatus = job.responseStatus;
144
+ log.responseText = formatHandler.extractStream(
145
+ job.rawStream,
146
+ log,
147
+ log.model ?? undefined,
148
+ true,
149
+ );
150
+ persistStreamingChunks(log);
151
+
152
+ return { log, upstreamUrl: job.upstreamUrl, error: null };
153
+ } catch (err) {
154
+ return finalizeWithError(job, log, job.responseStatus, job.rawStream, err);
155
+ }
156
+ }
157
+
158
+ function finalizeStreamAbort(job: FinalizeStreamAbortLogJob, log: CapturedLog): FinalizeLogResult {
159
+ const formatHandler = formatForPath(log.path);
160
+ if (formatHandler === null && job.hasChunks) {
161
+ return finalizeWithError(job, log, 499, "Client aborted", "Unsupported format");
162
+ }
163
+
164
+ try {
165
+ log.elapsedMs = job.elapsedMs;
166
+ if (job.hasChunks && formatHandler !== null) {
167
+ log.responseText = formatHandler.extractStream(
168
+ job.rawStream,
169
+ log,
170
+ log.model ?? undefined,
171
+ true,
172
+ );
173
+ persistStreamingChunks(log);
174
+ } else {
175
+ log.responseText = "Client aborted";
176
+ }
177
+
178
+ return { log, upstreamUrl: job.upstreamUrl, error: "Client aborted" };
179
+ } catch (err) {
180
+ return finalizeWithError(job, log, 499, "Client aborted", err);
181
+ }
182
+ }
183
+
184
+ export function buildFinalizeLogResult(job: FinalizeLogJob): FinalizeLogResult {
185
+ const log = cloneLog(job.log);
186
+
187
+ switch (job.type) {
188
+ case "non-streaming":
189
+ return finalizeNonStreaming(job, log);
190
+ case "streaming":
191
+ return finalizeStreaming(job, log);
192
+ case "stream-abort":
193
+ return finalizeStreamAbort(job, log);
194
+ }
195
+ }
196
+
197
+ export function commitFinalizeLogResult(result: FinalizeLogResult): void {
198
+ appendLogEntry({ ...buildFileLogEntry(result.log, result.upstreamUrl), error: result.error });
199
+ finalizeLogUpdate(result.log);
200
+ }
201
+
202
+ // ── Worker pool ────────────────────────────────────────────────
203
+ // buildFinalizeLogResult is the CPU-heavy step (token extraction,
204
+ // SSE parsing, chunk persistence). We offload it to a fixed pool
205
+ // of Worker Threads so the main thread stays responsive even under
206
+ // concurrent sessions. commitFinalizeLogResult stays in-process
207
+ // because it touches the in-memory cache, SSE emitter, and
208
+ // append‑only log — all of which belong to the main thread.
209
+
210
+ // ── Routing ─────────────────────────────────────────────────────
211
+ // FINALIZER_RUNTIME selects the execution backend:
212
+ // "process" → per-session child process (max isolation)
213
+ // "worker" → shared Worker Thread pool
214
+ // "inline" → synchronous in-process (debug / fallback)
215
+ // The default is "process" only when its worker entry exists. Production
216
+ // bundles that do not emit the standalone worker entry fall back to "inline"
217
+ // instead of repeatedly spawning a failing child process.
218
+ // For backward compatibility, FINALIZER_USE_WORKER=0 forces "inline".
219
+
220
+ const RUNTIME: "process" | "worker" | "inline" = (() => {
221
+ if (process.env["FINALIZER_USE_WORKER"] === "0") return "inline";
222
+ const mode = process.env["FINALIZER_RUNTIME"];
223
+ if (mode === "process") return "process";
224
+ if (mode === "worker") return "worker";
225
+ if (mode === "inline") return "inline";
226
+ return isSessionProcessAvailable() ? "process" : "inline";
227
+ })();
228
+
229
+ function executeBuildInSessionProcess(job: FinalizeLogJob): Promise<FinalizeLogResult> {
230
+ const sessionId = job.log.sessionId ?? "__unassigned__";
231
+ return getSessionProcess(sessionId)
232
+ .enqueue(job)
233
+ .catch((err: unknown) => {
234
+ const message = err instanceof Error ? err.message : String(err);
235
+ logger.error(
236
+ `[logFinalizer] Session process failed for log #${job.log.id}, ` +
237
+ `falling back to in-process: ${message}`,
238
+ );
239
+ return buildFinalizeLogResult(job);
240
+ });
241
+ }
242
+
243
+ function resolveBuildPromise(job: FinalizeLogJob): Promise<FinalizeLogResult> {
244
+ switch (RUNTIME) {
245
+ case "process":
246
+ return executeBuildInSessionProcess(job);
247
+ case "worker":
248
+ return executeBuildInWorker(job);
249
+ case "inline":
250
+ return Promise.resolve(buildFinalizeLogResult(job));
251
+ }
252
+ }
253
+
254
+ const WORKER_COUNT = Math.max(1, Number(process.env["FINALIZER_WORKER_COUNT"]) || 4);
255
+ let _workers: Worker[] | null = null;
256
+ const _pending = new Map<string, (result: FinalizeLogResult) => void>();
257
+ let _nextWorker = 0;
258
+ let _jobSeq = 0;
259
+
260
+ function getWorkers(): Worker[] {
261
+ if (_workers !== null) return _workers;
262
+ _workers = [];
263
+ for (let i = 0; i < WORKER_COUNT; i++) {
264
+ const w = new Worker(new URL("./logFinalizer.worker.ts", import.meta.url));
265
+ w.on("message", (msg: { id: string; result: FinalizeLogResult }) => {
266
+ const resolve = _pending.get(msg.id);
267
+ if (resolve !== undefined) {
268
+ _pending.delete(msg.id);
269
+ resolve(msg.result);
270
+ }
271
+ });
272
+ w.on("error", (err) => {
273
+ logger.error(
274
+ "[logFinalizer] Worker error:",
275
+ err instanceof Error ? err.message : String(err),
276
+ );
277
+ });
278
+ _workers.push(w);
279
+ }
280
+ return _workers;
281
+ }
282
+
283
+ export function executeBuildInWorker(job: FinalizeLogJob): Promise<FinalizeLogResult> {
284
+ return new Promise((resolve) => {
285
+ const list = getWorkers();
286
+ // getWorkers() always returns at least 1 worker; the array index is safe
287
+ const idx = _nextWorker % list.length;
288
+ _nextWorker++;
289
+ const w = list[idx];
290
+ if (w === undefined) return; // unreachable, satisfies type-checker
291
+ const id = String(++_jobSeq);
292
+ _pending.set(id, resolve);
293
+ w.postMessage({ id, job });
294
+ });
295
+ }
296
+
297
+ export function executeFinalizeLogJob(job: FinalizeLogJob): Promise<void> {
298
+ return resolveBuildPromise(job).then((result) => {
299
+ commitFinalizeLogResult(result);
300
+ if (result.error !== null && result.error !== "Client aborted") {
301
+ return Promise.reject(new Error(result.error));
302
+ }
303
+ return undefined;
304
+ });
305
+ }
@@ -0,0 +1,24 @@
1
+ import { isMainThread, parentPort } from "node:worker_threads";
2
+ import {
3
+ buildFinalizeLogResult,
4
+ type FinalizeLogJob,
5
+ type FinalizeLogResult,
6
+ } from "./logFinalizer";
7
+
8
+ if (!isMainThread && parentPort !== null) {
9
+ const port = parentPort;
10
+ port.on("message", (msg: { id: string; job: FinalizeLogJob }) => {
11
+ let result: FinalizeLogResult;
12
+ try {
13
+ result = buildFinalizeLogResult(msg.job);
14
+ } catch (err) {
15
+ const message = err instanceof Error ? err.message : String(err);
16
+ result = {
17
+ log: msg.job.log,
18
+ upstreamUrl: msg.job.upstreamUrl,
19
+ error: message,
20
+ };
21
+ }
22
+ port.postMessage({ id: msg.id, result });
23
+ });
24
+ }