@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,268 @@
1
+ import { readFile, writeFile, stat, readdir, mkdir } from "node:fs/promises";
2
+ import { createReadStream, existsSync } from "node:fs";
3
+ import { join, dirname } from "node:path";
4
+ import { createInterface } from "node:readline";
5
+ import { Buffer } from "node:buffer";
6
+ import { resolveLogDir, logger } from "./logger";
7
+
8
+ type LogIndexEntry = {
9
+ id: number;
10
+ file: string;
11
+ byteOffset: number;
12
+ byteLength: number;
13
+ };
14
+
15
+ type LogIndex = {
16
+ version: number;
17
+ entries: Record<number, LogIndexEntry>;
18
+ maxId: number;
19
+ };
20
+
21
+ const INDEX_VERSION = 1;
22
+ const INDEX_FILE = "logs.idx";
23
+
24
+ export { resolveLogDir as getLogDir };
25
+
26
+ function getIndexPath(): string {
27
+ return join(resolveLogDir(), INDEX_FILE);
28
+ }
29
+
30
+ let cachedIndex: LogIndex | null = null;
31
+
32
+ function createEmptyIndex(): LogIndex {
33
+ return {
34
+ version: INDEX_VERSION,
35
+ entries: {},
36
+ maxId: 0,
37
+ };
38
+ }
39
+
40
+ function isLogIndex(obj: unknown): obj is LogIndex {
41
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj)) return false;
42
+ const versionDesc = Object.getOwnPropertyDescriptor(obj, "version");
43
+ const entriesDesc = Object.getOwnPropertyDescriptor(obj, "entries");
44
+ const maxIdDesc = Object.getOwnPropertyDescriptor(obj, "maxId");
45
+ return (
46
+ versionDesc !== undefined &&
47
+ typeof versionDesc.value === "number" &&
48
+ entriesDesc !== undefined &&
49
+ typeof entriesDesc.value === "object" &&
50
+ entriesDesc.value !== null &&
51
+ maxIdDesc !== undefined &&
52
+ typeof maxIdDesc.value === "number"
53
+ );
54
+ }
55
+
56
+ export async function loadIndex(): Promise<LogIndex> {
57
+ if (cachedIndex !== null) return cachedIndex;
58
+
59
+ const indexPath = getIndexPath();
60
+ if (!existsSync(indexPath)) {
61
+ cachedIndex = createEmptyIndex();
62
+ return cachedIndex;
63
+ }
64
+
65
+ try {
66
+ const content = await readFile(indexPath, "utf-8");
67
+ const parsed: unknown = JSON.parse(content);
68
+ if (isLogIndex(parsed)) {
69
+ cachedIndex = parsed;
70
+ } else {
71
+ cachedIndex = createEmptyIndex();
72
+ }
73
+ return cachedIndex;
74
+ } catch (err) {
75
+ logger.error("[logIndex] Failed to load index:", String(err));
76
+ cachedIndex = createEmptyIndex();
77
+ return cachedIndex;
78
+ }
79
+ }
80
+
81
+ export async function saveIndex(index: LogIndex): Promise<void> {
82
+ const indexPath = getIndexPath();
83
+ const dir = dirname(indexPath);
84
+
85
+ try {
86
+ await mkdir(dir, { recursive: true });
87
+ } catch {
88
+ // Ignore
89
+ }
90
+
91
+ try {
92
+ await writeFile(indexPath, JSON.stringify(index), "utf-8");
93
+ } catch (err) {
94
+ logger.error("[logIndex] Failed to save index:", String(err));
95
+ }
96
+ }
97
+
98
+ export async function addToIndex(
99
+ id: number,
100
+ file: string,
101
+ byteOffset: number,
102
+ byteLength: number,
103
+ ): Promise<void> {
104
+ const index = await loadIndex();
105
+ index.entries[id] = { id, file, byteOffset, byteLength };
106
+ if (id > index.maxId) {
107
+ index.maxId = id;
108
+ }
109
+ // Defer disk writes to reduce I/O - flush after a batch of updates
110
+ scheduleIndexFlush();
111
+ }
112
+
113
+ // Batch writes: collect pending flushes and write once
114
+ let indexFlushScheduled = false;
115
+ let indexFlushTimeout: ReturnType<typeof setTimeout> | null = null;
116
+
117
+ async function flushIndexAsync(): Promise<void> {
118
+ indexFlushScheduled = false;
119
+ indexFlushTimeout = null;
120
+ const index = await loadIndex();
121
+ await saveIndex(index);
122
+ }
123
+
124
+ function scheduleIndexFlush(): void {
125
+ if (indexFlushScheduled) return;
126
+ indexFlushScheduled = true;
127
+ indexFlushTimeout = setTimeout(() => {
128
+ void flushIndexAsync();
129
+ }, 1000); // Flush after 1 second of inactivity
130
+ }
131
+
132
+ export async function flushIndex(): Promise<void> {
133
+ if (indexFlushTimeout !== null) {
134
+ clearTimeout(indexFlushTimeout);
135
+ indexFlushTimeout = null;
136
+ }
137
+ indexFlushScheduled = false;
138
+ const index = await loadIndex();
139
+ await saveIndex(index);
140
+ }
141
+
142
+ export async function findInIndex(id: number): Promise<LogIndexEntry | null> {
143
+ const index = await loadIndex();
144
+ return index.entries[id] ?? null;
145
+ }
146
+
147
+ type FileIndexResult = {
148
+ entries: Record<number, LogIndexEntry>;
149
+ maxId: number;
150
+ };
151
+
152
+ async function indexFile(filePath: string, file: string): Promise<FileIndexResult> {
153
+ const entries: Record<number, LogIndexEntry> = {};
154
+ let maxId = 0;
155
+ let byteOffset = 0;
156
+
157
+ const fileStream = createInterface({
158
+ input: createReadStream(filePath),
159
+ crlfDelay: Infinity,
160
+ });
161
+
162
+ for await (const line of fileStream) {
163
+ const lineBytes = Buffer.byteLength(line, "utf-8") + 1;
164
+ if (line.trim() !== "") {
165
+ try {
166
+ const entry: unknown = JSON.parse(line);
167
+ if (typeof entry === "object" && entry !== null && !Array.isArray(entry)) {
168
+ const idDesc = Object.getOwnPropertyDescriptor(entry, "id");
169
+ if (idDesc !== undefined && typeof idDesc.value === "number") {
170
+ const entryId = idDesc.value;
171
+ maxId = Math.max(maxId, entryId);
172
+ entries[entryId] = {
173
+ id: entryId,
174
+ file,
175
+ byteOffset,
176
+ byteLength: lineBytes,
177
+ };
178
+ }
179
+ }
180
+ } catch {
181
+ // Skip malformed lines
182
+ }
183
+ }
184
+ byteOffset += lineBytes;
185
+ }
186
+
187
+ return { entries, maxId };
188
+ }
189
+
190
+ export async function rebuildIndex(): Promise<LogIndex> {
191
+ const logDir = resolveLogDir();
192
+ const newIndex = createEmptyIndex();
193
+
194
+ if (!existsSync(logDir)) {
195
+ cachedIndex = newIndex;
196
+ await saveIndex(newIndex);
197
+ return newIndex;
198
+ }
199
+
200
+ const files = (await readdir(logDir)).filter((f) => f.endsWith(".jsonl")).sort();
201
+ const CONCURRENCY = 4;
202
+
203
+ // Process files in bounded-concurrency batches
204
+ for (let i = 0; i < files.length; i += CONCURRENCY) {
205
+ const batch = files.slice(i, i + CONCURRENCY);
206
+ const results = await Promise.all(batch.map((file) => indexFile(join(logDir, file), file)));
207
+
208
+ for (const result of results) {
209
+ Object.assign(newIndex.entries, result.entries);
210
+ newIndex.maxId = Math.max(newIndex.maxId, result.maxId);
211
+ }
212
+ }
213
+
214
+ cachedIndex = newIndex;
215
+ await saveIndex(newIndex);
216
+ return newIndex;
217
+ }
218
+
219
+ // Async mutex for atomic ID generation to prevent race conditions
220
+ // Uses a promise queue instead of busy-waiting
221
+ let idGenerationPromise: Promise<void> | null = null;
222
+ let releaseLock: (() => void) | null = null;
223
+
224
+ async function acquireLock(): Promise<void> {
225
+ if (releaseLock === null) {
226
+ idGenerationPromise = new Promise<void>((resolve) => {
227
+ releaseLock = resolve;
228
+ });
229
+ } else {
230
+ // Wait for the previous lock to be released
231
+ await idGenerationPromise;
232
+ // After waiting, we need to create a new promise for the next waiter
233
+ idGenerationPromise = new Promise<void>((resolve) => {
234
+ releaseLock = resolve;
235
+ });
236
+ }
237
+ }
238
+
239
+ function releaseLockFn(): void {
240
+ if (releaseLock) {
241
+ const resolve = releaseLock;
242
+ releaseLock = null;
243
+ idGenerationPromise = null;
244
+ resolve();
245
+ }
246
+ }
247
+
248
+ export async function getNextLogId(): Promise<number> {
249
+ await acquireLock();
250
+ try {
251
+ const index = await loadIndex();
252
+ const nextId = index.maxId + 1;
253
+ index.maxId = nextId;
254
+ // Synchronously update the index in memory (disk write is deferred via batching)
255
+ cachedIndex = index;
256
+ return nextId;
257
+ } finally {
258
+ releaseLockFn();
259
+ }
260
+ }
261
+
262
+ export function getCurrentLogFile(): string {
263
+ const now = new Date();
264
+ const yyyy = now.getUTCFullYear();
265
+ const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
266
+ const dd = String(now.getUTCDate()).padStart(2, "0");
267
+ return `${yyyy}-${mm}-${dd}.jsonl`;
268
+ }
@@ -0,0 +1,179 @@
1
+ import { readdir, stat, unlink, appendFile, mkdir } from "node:fs/promises";
2
+ import { writeFileSync, mkdirSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { getDataDir } from "./dataDir";
5
+
6
+ const LOG_DIR_ENV = process.env["LOG_DIR"];
7
+ const RETENTION_DAYS = Number(process.env["LOG_RETENTION_DAYS"] ?? "7");
8
+ const LOG_FILE_ENV = process.env["AGENT_INSPECTOR_LOG_FILE"];
9
+
10
+ let resolvedLogDir: string | null = null;
11
+
12
+ export function resolveLogDir(): string {
13
+ if (resolvedLogDir !== null) return resolvedLogDir;
14
+
15
+ if (LOG_DIR_ENV !== undefined) {
16
+ resolvedLogDir = path.isAbsolute(LOG_DIR_ENV)
17
+ ? LOG_DIR_ENV
18
+ : path.join(getDataDir(), LOG_DIR_ENV);
19
+ } else {
20
+ resolvedLogDir = path.join(getDataDir(), "logs");
21
+ }
22
+ return resolvedLogDir;
23
+ }
24
+
25
+ export function getLogFilePath(): string {
26
+ const date = new Date();
27
+ const yyyy = date.getUTCFullYear();
28
+ const mm = String(date.getUTCMonth() + 1).padStart(2, "0");
29
+ const dd = String(date.getUTCDate()).padStart(2, "0");
30
+ return path.join(resolveLogDir(), `${yyyy}-${mm}-${dd}.jsonl`);
31
+ }
32
+
33
+ function getInspectorLogPath(): string {
34
+ if (LOG_FILE_ENV !== undefined) {
35
+ return LOG_FILE_ENV;
36
+ }
37
+ return path.join(getDataDir(), "logs", "inspector.log");
38
+ }
39
+
40
+ export async function initLogger(): Promise<void> {
41
+ const dir = resolveLogDir();
42
+ const retentionMs = RETENTION_DAYS * 24 * 60 * 60 * 1000;
43
+ const cutoff = Date.now() - retentionMs;
44
+
45
+ try {
46
+ const entries = await readdir(dir);
47
+ for (const entry of entries) {
48
+ if (!entry.endsWith(".jsonl")) continue;
49
+ const fullPath = path.join(dir, entry);
50
+ try {
51
+ const s = await stat(fullPath);
52
+ if (s.mtimeMs < cutoff) {
53
+ await unlink(fullPath);
54
+ }
55
+ } catch {
56
+ // Skip files that can't be stat - not critical
57
+ }
58
+ }
59
+ } catch (err) {
60
+ // Log directory initialization errors but don't fail startup
61
+ // eslint-disable-next-line no-console
62
+ console.error("[logger] Failed to initialize log directory:", err);
63
+ }
64
+ }
65
+
66
+ // File-based logger for application logs (not log entries)
67
+ let loggerInitialized = false;
68
+
69
+ async function writeAppLog(message: string): Promise<void> {
70
+ // Auto-initialize on first use if not already initialized
71
+ if (!loggerInitialized) {
72
+ loggerInitialized = true;
73
+ void initLogger();
74
+ }
75
+
76
+ const logPath = getInspectorLogPath();
77
+ try {
78
+ const logDirPath = path.dirname(logPath);
79
+ await mkdir(logDirPath, { recursive: true });
80
+ const timestamp = new Date().toISOString();
81
+ await appendFile(logPath, `[${timestamp}] ${message}\n`, "utf-8");
82
+ } catch (err) {
83
+ // Log to stderr since file logging failed
84
+ // eslint-disable-next-line no-console
85
+ console.error(`[logger] Failed to write to ${logPath}:`, err);
86
+ }
87
+ }
88
+
89
+ // Application logger - writes to inspector.log
90
+ export const logger = {
91
+ debug(message: string, ...args: unknown[]): void {
92
+ const msg = args.length > 0 ? `${message} ${args.map(String).join(" ")}` : message;
93
+ void writeAppLog(`[DEBUG] ${msg}`);
94
+ },
95
+ info(message: string, ...args: unknown[]): void {
96
+ const msg = args.length > 0 ? `${message} ${args.map(String).join(" ")}` : message;
97
+ void writeAppLog(`[INFO] ${msg}`);
98
+ },
99
+ warn(message: string, ...args: unknown[]): void {
100
+ const msg = args.length > 0 ? `${message} ${args.map(String).join(" ")}` : message;
101
+ void writeAppLog(`[WARN] ${msg}`);
102
+ },
103
+ error(message: string, ...args: unknown[]): void {
104
+ const msg = args.length > 0 ? `${message} ${args.map(String).join(" ")}` : message;
105
+ void writeAppLog(`[ERROR] ${msg}`);
106
+ },
107
+ };
108
+
109
+ // Write buffer for batching async writes
110
+ const MAX_BUFFER_SIZE = 1000;
111
+ let writeBuffer: string[] = [];
112
+ let writeQueue: Promise<void> = Promise.resolve();
113
+
114
+ function drainBuffer(): string {
115
+ const toWrite = writeBuffer.join("");
116
+ writeBuffer = [];
117
+ return toWrite;
118
+ }
119
+
120
+ async function flushWriteBuffer(): Promise<void> {
121
+ let toWrite: string;
122
+ {
123
+ toWrite = drainBuffer();
124
+ }
125
+ if (toWrite.length === 0) return;
126
+
127
+ try {
128
+ const filePath = getLogFilePath();
129
+ await mkdir(path.dirname(filePath), { recursive: true });
130
+ await appendFile(filePath, toWrite, "utf-8");
131
+ } catch (err) {
132
+ // On failure, re-queue the data so it is not lost
133
+ writeBuffer.unshift(toWrite);
134
+ // eslint-disable-next-line no-console
135
+ console.error("[logger] Failed to flush write buffer, re-queued:", err);
136
+ }
137
+ }
138
+
139
+ export function appendLogEntry(entry: Record<string, unknown>): void {
140
+ const line = JSON.stringify(entry) + "\n";
141
+ writeBuffer.push(line);
142
+ if (writeBuffer.length >= MAX_BUFFER_SIZE) {
143
+ writeQueue = writeQueue.then(() => flushWriteBuffer());
144
+ } else if (writeBuffer.length === 1) {
145
+ writeQueue = writeQueue.then(() => flushWriteBuffer());
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Flush all buffered log entries to disk. Call before process exit
151
+ * to avoid losing unflushed data.
152
+ */
153
+ export async function flushLogBuffer(): Promise<void> {
154
+ await writeQueue;
155
+ if (writeBuffer.length > 0) {
156
+ await flushWriteBuffer();
157
+ }
158
+ }
159
+
160
+ // Ensure buffered logs are written before the process exits
161
+ process.on("exit", () => {
162
+ if (writeBuffer.length > 0) {
163
+ const toWrite = drainBuffer();
164
+ try {
165
+ // Synchronous write required in 'exit' handler (async I/O not allowed)
166
+ const filePath = getLogFilePath();
167
+ mkdirSync(path.dirname(filePath), { recursive: true });
168
+ writeFileSync(filePath, toWrite, "utf-8");
169
+ } catch {
170
+ // Best-effort: nothing we can do in an exit handler
171
+ }
172
+ }
173
+ });
174
+
175
+ for (const signal of ["SIGINT", "SIGTERM"] as const) {
176
+ process.on(signal, () => {
177
+ void flushLogBuffer().then(() => process.exit(0));
178
+ });
179
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * OpenAI Chat Completions protocol guard: orphan `tool` messages.
3
+ *
4
+ * The OpenAI Chat Completions spec requires every message with
5
+ * `role: "tool"` to reference a `tool_call_id` that appears in a preceding
6
+ * `assistant` message's `tool_calls` array. When this invariant is broken
7
+ * (typically by a buggy client that records a tool result without the
8
+ * matching assistant tool_call), the upstream rejects the request with a
9
+ * 400 like:
10
+ *
11
+ * {"type":"error","error":{"type":"bad_request_error",
12
+ * "message":"invalid params, tool result's tool id(call_xxx) not found"}}
13
+ *
14
+ * This module finds such orphan tool messages and returns a rewritten body
15
+ * with them removed so the proxy can forward a valid request. The original
16
+ * body is preserved by the caller for the captured log, so the inspector UI
17
+ * still shows the broken request the client actually sent.
18
+ */
19
+
20
+ const ROLE_ASSISTANT = "assistant";
21
+ const ROLE_TOOL = "tool";
22
+
23
+ export type OrphanToolStripResult = {
24
+ /** The new request body. Equal to input (by reference) when no change was made. */
25
+ body: string;
26
+ /** Number of orphan tool messages removed. */
27
+ removed: number;
28
+ /**
29
+ * The `tool_call_id` values of the removed messages, in document order.
30
+ * `null` is used when a tool message had no `tool_call_id` at all.
31
+ */
32
+ orphanIds: Array<string | null>;
33
+ };
34
+
35
+ import { safeGetOwnProperty, isPlainRecord } from "../lib/objectUtils";
36
+
37
+ function isObjectWithMessages(value: unknown): value is { messages: unknown[] } {
38
+ if (!isPlainRecord(value)) return false;
39
+ if (!Object.prototype.hasOwnProperty.call(value, "messages")) return false;
40
+ const messages = safeGetOwnProperty(value, "messages");
41
+ return Array.isArray(messages);
42
+ }
43
+
44
+ /**
45
+ * Collects the set of tool_call ids from an assistant message's `tool_calls`
46
+ * array. Tolerant of malformed entries: anything without a string `id` is
47
+ * skipped silently (those ids cannot be referenced by a tool message anyway).
48
+ */
49
+ function collectAssistantToolCallIds(message: unknown, into: Set<string>): void {
50
+ if (!isPlainRecord(message)) return;
51
+ const toolCalls = safeGetOwnProperty(message, "tool_calls");
52
+ if (!Array.isArray(toolCalls)) return;
53
+ for (const tc of toolCalls) {
54
+ if (!isPlainRecord(tc)) continue;
55
+ const id = safeGetOwnProperty(tc, "id");
56
+ if (typeof id === "string" && id.length > 0) {
57
+ into.add(id);
58
+ }
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Returns the indices of `tool`-role messages whose `tool_call_id` does not
64
+ * appear in any *preceding* assistant message's `tool_calls`. A tool message
65
+ * with no `tool_call_id` at all is also considered orphan.
66
+ *
67
+ * Pure, exported for unit tests. Used by `stripOpenAIOrphanToolMessages`.
68
+ */
69
+ export function findOrphanToolMessageIndices(messages: readonly unknown[]): {
70
+ indices: number[];
71
+ orphanIds: Array<string | null>;
72
+ } {
73
+ const knownToolCallIds = new Set<string>();
74
+ const indices: number[] = [];
75
+ const orphanIds: Array<string | null> = [];
76
+
77
+ for (let i = 0; i < messages.length; i++) {
78
+ const msg = messages[i];
79
+ if (!isPlainRecord(msg)) continue;
80
+
81
+ const role = safeGetOwnProperty(msg, "role");
82
+
83
+ if (role === ROLE_ASSISTANT) {
84
+ collectAssistantToolCallIds(msg, knownToolCallIds);
85
+ continue;
86
+ }
87
+
88
+ if (role === ROLE_TOOL) {
89
+ const id = safeGetOwnProperty(msg, "tool_call_id");
90
+ if (typeof id !== "string" || id.length === 0) {
91
+ indices.push(i);
92
+ orphanIds.push(null);
93
+ continue;
94
+ }
95
+ if (!knownToolCallIds.has(id)) {
96
+ indices.push(i);
97
+ orphanIds.push(id);
98
+ }
99
+ }
100
+ }
101
+
102
+ return { indices, orphanIds };
103
+ }
104
+
105
+ /**
106
+ * Inspects an OpenAI-format request body and, if its `messages` array
107
+ * contains one or more orphan `tool` messages (see `findOrphanToolMessageIndices`),
108
+ * returns a new body with those messages removed. The original string is
109
+ * returned unchanged (by reference equality) when no edit is needed so the
110
+ * caller can skip a re-serialization.
111
+ *
112
+ * Tolerant of bodies that are not valid JSON, not OpenAI-format, or that
113
+ * have no `messages` array — all such inputs are returned unchanged.
114
+ */
115
+ export function stripOpenAIOrphanToolMessages(rawBody: string): OrphanToolStripResult {
116
+ let parsed: unknown;
117
+ try {
118
+ parsed = JSON.parse(rawBody);
119
+ } catch {
120
+ return { body: rawBody, removed: 0, orphanIds: [] };
121
+ }
122
+
123
+ if (!isObjectWithMessages(parsed)) {
124
+ return { body: rawBody, removed: 0, orphanIds: [] };
125
+ }
126
+
127
+ const messages = parsed.messages;
128
+ const { indices, orphanIds } = findOrphanToolMessageIndices(messages);
129
+
130
+ if (indices.length === 0) {
131
+ return { body: rawBody, removed: 0, orphanIds: [] };
132
+ }
133
+
134
+ const dropSet = new Set(indices);
135
+ const kept: unknown[] = [];
136
+ for (let i = 0; i < messages.length; i++) {
137
+ if (!dropSet.has(i)) kept.push(messages[i]);
138
+ }
139
+ parsed.messages = kept;
140
+
141
+ return { body: JSON.stringify(parsed), removed: indices.length, orphanIds };
142
+ }