@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,341 @@
1
+ /**
2
+ * Pure (dependency-injected) tool handler implementations for the MCP
3
+ * server. These are split out from `server.ts` so unit tests can pass a
4
+ * mock `callApi` without spinning up a real HTTP loopback. The
5
+ * `registerTool` calls in `server.ts` simply wrap each impl in `safeCall`
6
+ * and bind the real `callApi` from `./loopback`.
7
+ *
8
+ * Each impl returns a `CallToolResult`-shaped object: either
9
+ * `{ content: [{ type: "text", text: string }] }` on success or
10
+ * `{ content: [...], isError: true }` on failure. The wrapping
11
+ * `safeCall` in `server.ts` only catches *thrown* errors (loopback
12
+ * timeouts, etc); semantic errors (404 from /api) are returned as
13
+ * `isError: true` results from inside the impl.
14
+ */
15
+
16
+ import { z } from "zod";
17
+ import { LoopbackTimeoutError, type CallApiOptions } from "./loopback";
18
+ import { extractLastUserMessagePreview, extractResponsePreview } from "./previewExtractor";
19
+ import { CapturedLogSchema, type CapturedLog } from "../proxy/schemas";
20
+
21
+ /** Minimal callApi contract — same shape as the real `callApi` in loopback.ts. */
22
+ export type CallApiFn = (path: string, options?: CallApiOptions) => Promise<Response>;
23
+
24
+ export type ToolResult = {
25
+ content: { type: "text"; text: string }[];
26
+ isError?: true;
27
+ };
28
+
29
+ // Loose shape returned by `GET /api/logs` — `logs: CapturedLog[]` and a few
30
+ // pagination fields. The CapturedLog items are fully validated downstream.
31
+ const LogsListResponseSchema = z.object({
32
+ logs: z.array(CapturedLogSchema).optional(),
33
+ total: z.number().optional(),
34
+ offset: z.number().optional(),
35
+ limit: z.number().optional(),
36
+ });
37
+
38
+ const PAGINATION_DEFAULTS = { offset: 0, limit: 3 };
39
+ export const LIMIT_HARD_CAP = 5;
40
+
41
+ export function clampLimit(input: number | undefined): number {
42
+ if (input === undefined) return PAGINATION_DEFAULTS.limit;
43
+ if (!Number.isFinite(input) || input < 1) return 1;
44
+ return Math.min(Math.floor(input), LIMIT_HARD_CAP);
45
+ }
46
+
47
+ function textJson(data: unknown): ToolResult {
48
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
49
+ }
50
+
51
+ function toolError(message: string): ToolResult {
52
+ return { content: [{ type: "text", text: message }], isError: true };
53
+ }
54
+
55
+ export function buildLogSummary(log: CapturedLog) {
56
+ const hasError =
57
+ (log.error !== null && log.error !== undefined && log.error.length > 0) ||
58
+ (log.responseStatus !== null && log.responseStatus !== undefined && log.responseStatus >= 400);
59
+ return {
60
+ id: log.id,
61
+ timestamp: log.timestamp,
62
+ provider: log.providerName ?? null,
63
+ model: log.model,
64
+ apiFormat: log.apiFormat,
65
+ method: log.method,
66
+ path: log.path,
67
+ status: log.responseStatus,
68
+ isStreaming: log.streaming,
69
+ hasError,
70
+ latencyMs: log.elapsedMs,
71
+ tokens: {
72
+ input: log.inputTokens,
73
+ output: log.outputTokens,
74
+ cacheCreate: log.cacheCreationInputTokens,
75
+ cacheRead: log.cacheReadInputTokens,
76
+ },
77
+ sessionId: log.sessionId,
78
+ clientPid: log.clientPid ?? null,
79
+ lastUserMessagePreview: extractLastUserMessagePreview(log),
80
+ responsePreview: extractResponsePreview(log),
81
+ hasChunks:
82
+ (log.streamingChunks !== undefined && log.streamingChunks !== null) ||
83
+ (log.streamingChunksPath !== null && log.streamingChunksPath !== undefined),
84
+ hasRawRequestBody: log.rawRequestBody !== null && log.rawRequestBody !== undefined,
85
+ };
86
+ }
87
+
88
+ // ===========================================================================
89
+ // Per-tool impls
90
+ // ===========================================================================
91
+
92
+ export type ListLogsArgs = {
93
+ offset?: number;
94
+ limit?: number;
95
+ sessionId?: string;
96
+ model?: string;
97
+ };
98
+
99
+ export async function listLogsImpl(callApi: CallApiFn, args: ListLogsArgs): Promise<ToolResult> {
100
+ const offset = args.offset ?? PAGINATION_DEFAULTS.offset;
101
+ const limit = clampLimit(args.limit);
102
+ const params = new URLSearchParams({ offset: String(offset), limit: String(limit) });
103
+ if (args.sessionId !== undefined && args.sessionId.length > 0) {
104
+ params.set("sessionId", args.sessionId);
105
+ }
106
+ if (args.model !== undefined && args.model.length > 0) {
107
+ params.set("model", args.model);
108
+ }
109
+ const res = await callApi(`/api/logs?${params.toString()}`);
110
+ if (!res.ok) return toolError(`GET /api/logs returned ${res.status}`);
111
+ const rawBody: unknown = await res.json();
112
+ const parsed = LogsListResponseSchema.safeParse(rawBody);
113
+ if (!parsed.success) return toolError("GET /api/logs returned an unparseable shape");
114
+ const logs = parsed.data.logs ?? [];
115
+ return textJson(logs.map(buildLogSummary));
116
+ }
117
+
118
+ export async function getLogImpl(callApi: CallApiFn, id: number): Promise<ToolResult> {
119
+ const res = await callApi(`/api/logs/${id}`);
120
+ if (!res.ok) return toolError(`GET /api/logs/${id} returned ${res.status}`);
121
+ const rawBody: unknown = await res.json();
122
+ const parsed = CapturedLogSchema.safeParse(rawBody);
123
+ if (!parsed.success) {
124
+ return toolError(`GET /api/logs/${id} returned an unparseable CapturedLog`);
125
+ }
126
+ // Strip streamingChunks per design D6 — the agent asks for chunks
127
+ // explicitly via inspector_get_log_chunks.
128
+ const { streamingChunks: _chunks, ...rest } = parsed.data;
129
+ return textJson(rest);
130
+ }
131
+
132
+ export async function getLogChunksImpl(callApi: CallApiFn, id: number): Promise<ToolResult> {
133
+ const res = await callApi(`/api/logs/${id}/chunks`);
134
+ if (!res.ok) return toolError(`GET /api/logs/${id}/chunks returned ${res.status}`);
135
+ return textJson(await res.json());
136
+ }
137
+
138
+ export async function listSessionsImpl(callApi: CallApiFn): Promise<ToolResult> {
139
+ const res = await callApi("/api/sessions");
140
+ if (!res.ok) return toolError(`GET /api/sessions returned ${res.status}`);
141
+ return textJson(await res.json());
142
+ }
143
+
144
+ export async function listModelsImpl(callApi: CallApiFn): Promise<ToolResult> {
145
+ const res = await callApi("/api/models");
146
+ if (!res.ok) return toolError(`GET /api/models returned ${res.status}`);
147
+ return textJson(await res.json());
148
+ }
149
+
150
+ export async function listProvidersImpl(callApi: CallApiFn): Promise<ToolResult> {
151
+ const res = await callApi("/api/providers");
152
+ if (!res.ok) return toolError(`GET /api/providers returned ${res.status}`);
153
+ return textJson(await res.json());
154
+ }
155
+
156
+ export async function getProviderImpl(callApi: CallApiFn, id: string): Promise<ToolResult> {
157
+ const res = await callApi(`/api/providers/${encodeURIComponent(id)}`);
158
+ if (!res.ok) return toolError(`GET /api/providers/${id} returned ${res.status}`);
159
+ return textJson(await res.json());
160
+ }
161
+
162
+ export type ReplayLogArgs = { id: number };
163
+
164
+ export async function replayLogImpl(callApi: CallApiFn, args: ReplayLogArgs): Promise<ToolResult> {
165
+ const logRes = await callApi(`/api/logs/${args.id}`);
166
+ if (!logRes.ok) return toolError(`GET /api/logs/${args.id} returned ${logRes.status}`);
167
+ const logRaw: unknown = await logRes.json();
168
+ const logParsed = CapturedLogSchema.safeParse(logRaw);
169
+ if (!logParsed.success) {
170
+ return toolError(`GET /api/logs/${args.id} returned an unparseable CapturedLog`);
171
+ }
172
+ const log = logParsed.data;
173
+ if (log.rawRequestBody === null) return toolError("Log has no rawRequestBody to replay");
174
+ const replayRes = await callApi(`/api/logs/${args.id}/replay`, {
175
+ method: "POST",
176
+ headers: { "content-type": "application/json" },
177
+ body: JSON.stringify({ modifiedBody: log.rawRequestBody }),
178
+ });
179
+ if (!replayRes.ok) {
180
+ return toolError(`POST /api/logs/${args.id}/replay returned ${replayRes.status}`);
181
+ }
182
+ return textJson(await replayRes.json());
183
+ }
184
+
185
+ export type AddProviderInput = {
186
+ name: string;
187
+ apiKey: string;
188
+ format: "anthropic" | "openai";
189
+ model: string;
190
+ anthropicBaseUrl?: string;
191
+ openaiBaseUrl?: string;
192
+ authHeader?: "bearer" | "x-api-key";
193
+ apiDocsUrl?: string;
194
+ };
195
+
196
+ export async function addProviderImpl(
197
+ callApi: CallApiFn,
198
+ provider: AddProviderInput,
199
+ ): Promise<ToolResult> {
200
+ const res = await callApi("/api/providers", {
201
+ method: "POST",
202
+ headers: { "content-type": "application/json" },
203
+ body: JSON.stringify(provider),
204
+ });
205
+ if (!res.ok) return toolError(`POST /api/providers returned ${res.status}`);
206
+ return textJson(await res.json());
207
+ }
208
+
209
+ export type UpdateProviderInput = {
210
+ id: string;
211
+ name?: string;
212
+ apiKey?: string;
213
+ format?: "anthropic" | "openai";
214
+ model?: string;
215
+ baseUrl?: string;
216
+ authHeader?: "bearer" | "x-api-key";
217
+ anthropicBaseUrl?: string;
218
+ openaiBaseUrl?: string;
219
+ apiDocsUrl?: string;
220
+ };
221
+
222
+ export async function updateProviderImpl(
223
+ callApi: CallApiFn,
224
+ input: UpdateProviderInput,
225
+ ): Promise<ToolResult> {
226
+ const { id, ...patch } = input;
227
+ const res = await callApi(`/api/providers/${encodeURIComponent(id)}`, {
228
+ method: "PUT",
229
+ headers: { "content-type": "application/json" },
230
+ body: JSON.stringify(patch),
231
+ });
232
+ if (!res.ok) return toolError(`PUT /api/providers/${id} returned ${res.status}`);
233
+ return textJson(await res.json());
234
+ }
235
+
236
+ export async function testProviderImpl(callApi: CallApiFn, id: string): Promise<ToolResult> {
237
+ const res = await callApi(`/api/providers/${encodeURIComponent(id)}/test`, {
238
+ method: "POST",
239
+ });
240
+ if (!res.ok) return toolError(`POST /api/providers/${id}/test returned ${res.status}`);
241
+ return textJson(await res.json());
242
+ }
243
+
244
+ export type CreateSessionKnowledgeArgs = {
245
+ sessionId: string;
246
+ };
247
+
248
+ export async function createSessionKnowledgeImpl(
249
+ callApi: CallApiFn,
250
+ args: CreateSessionKnowledgeArgs,
251
+ ): Promise<ToolResult> {
252
+ const res = await callApi(
253
+ `/api/knowledge/sessions/${encodeURIComponent(args.sessionId)}/candidates`,
254
+ {
255
+ method: "POST",
256
+ },
257
+ );
258
+ if (!res.ok) {
259
+ return toolError(
260
+ `POST /api/knowledge/sessions/${args.sessionId}/candidates returned ${res.status}`,
261
+ );
262
+ }
263
+ return textJson(await res.json());
264
+ }
265
+
266
+ export async function listKnowledgeCandidatesImpl(callApi: CallApiFn): Promise<ToolResult> {
267
+ const res = await callApi("/api/knowledge/candidates");
268
+ if (!res.ok) return toolError(`GET /api/knowledge/candidates returned ${res.status}`);
269
+ return textJson(await res.json());
270
+ }
271
+
272
+ export type PromoteKnowledgeCandidateArgs = {
273
+ candidateId: string;
274
+ };
275
+
276
+ export async function promoteKnowledgeCandidateImpl(
277
+ callApi: CallApiFn,
278
+ args: PromoteKnowledgeCandidateArgs,
279
+ ): Promise<ToolResult> {
280
+ const res = await callApi(
281
+ `/api/knowledge/candidates/${encodeURIComponent(args.candidateId)}/promote`,
282
+ {
283
+ method: "POST",
284
+ },
285
+ );
286
+ if (!res.ok) {
287
+ return toolError(
288
+ `POST /api/knowledge/candidates/${args.candidateId}/promote returned ${res.status}`,
289
+ );
290
+ }
291
+ return textJson(await res.json());
292
+ }
293
+
294
+ export type SearchKnowledgeArgs = {
295
+ query: string;
296
+ project?: string;
297
+ };
298
+
299
+ export async function searchKnowledgeImpl(
300
+ callApi: CallApiFn,
301
+ args: SearchKnowledgeArgs,
302
+ ): Promise<ToolResult> {
303
+ const params = new URLSearchParams({ q: args.query });
304
+ if (args.project !== undefined && args.project.length > 0) {
305
+ params.set("project", args.project);
306
+ }
307
+ const res = await callApi(`/api/knowledge/search?${params.toString()}`);
308
+ if (!res.ok) return toolError(`GET /api/knowledge/search returned ${res.status}`);
309
+ return textJson(await res.json());
310
+ }
311
+
312
+ export type GetProjectContextArgs = {
313
+ project: string;
314
+ };
315
+
316
+ export async function getProjectContextImpl(
317
+ callApi: CallApiFn,
318
+ args: GetProjectContextArgs,
319
+ ): Promise<ToolResult> {
320
+ const params = new URLSearchParams({ project: args.project });
321
+ const res = await callApi(`/api/knowledge/project-context?${params.toString()}`);
322
+ if (!res.ok) return toolError(`GET /api/knowledge/project-context returned ${res.status}`);
323
+ return textJson(await res.json());
324
+ }
325
+
326
+ /**
327
+ * Wrap an impl so any thrown error becomes a well-formed `isError: true`
328
+ * CallToolResult (instead of a JSON-RPC protocol error, which would abort
329
+ * the whole session). Loopback timeouts get a clear message.
330
+ */
331
+ export async function safeCall(fn: () => Promise<ToolResult>): Promise<ToolResult> {
332
+ try {
333
+ return await fn();
334
+ } catch (err) {
335
+ if (err instanceof LoopbackTimeoutError) {
336
+ return toolError(`Internal loopback call timed out after ${err.timeoutMs}ms: ${err.path}`);
337
+ }
338
+ const message = err instanceof Error ? err.message : String(err);
339
+ return toolError(`Tool execution failed: ${message}`);
340
+ }
341
+ }
@@ -0,0 +1,112 @@
1
+ import {
2
+ writeFileSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ unlinkSync,
6
+ existsSync,
7
+ renameSync,
8
+ copyFileSync,
9
+ } from "node:fs";
10
+ import { join, isAbsolute } from "node:path";
11
+ import { logger } from "./logger";
12
+ import { z } from "zod";
13
+ import { JsonValueSchema, type StreamingChunk } from "./schemas";
14
+ import { getDataDir } from "./dataDir";
15
+
16
+ export type StreamingChunksData = {
17
+ chunks: StreamingChunk[];
18
+ truncated?: boolean;
19
+ };
20
+
21
+ const StreamingChunkSchema: z.ZodType<StreamingChunk> = z.object({
22
+ index: z.number(),
23
+ timestamp: z.number(),
24
+ type: z.string(),
25
+ data: JsonValueSchema,
26
+ });
27
+
28
+ const StreamingChunksDataSchema = z.object({
29
+ chunks: z.array(StreamingChunkSchema),
30
+ truncated: z.boolean().optional(),
31
+ });
32
+
33
+ const CHUNKS_DIR_ENV = process.env["CHUNKS_DIR"];
34
+
35
+ export function getChunksDir(): string {
36
+ if (CHUNKS_DIR_ENV !== undefined) {
37
+ return isAbsolute(CHUNKS_DIR_ENV) ? CHUNKS_DIR_ENV : join(getDataDir(), CHUNKS_DIR_ENV);
38
+ }
39
+ return join(getDataDir(), "chunks");
40
+ }
41
+
42
+ function getChunkFilePath(logId: number): string {
43
+ return join(getChunksDir(), `${logId}.json`);
44
+ }
45
+
46
+ function getTempFilePath(logId: number): string {
47
+ return join(getChunksDir(), `.${logId}.tmp`);
48
+ }
49
+
50
+ /**
51
+ * Write chunks to disk atomically: write to temp file first, then rename.
52
+ * Returns the absolute path to the written chunk file.
53
+ */
54
+ export function writeChunks(logId: number, chunks: StreamingChunk[], truncated?: boolean): string {
55
+ const dir = getChunksDir();
56
+ const targetPath = getChunkFilePath(logId);
57
+ const tempPath = getTempFilePath(logId);
58
+
59
+ // Ensure directory exists
60
+ try {
61
+ mkdirSync(dir, { recursive: true });
62
+ } catch (err) {
63
+ logger.error("[chunkStorage] Failed to create chunks directory:", String(err));
64
+ }
65
+
66
+ const data: StreamingChunksData = { chunks, truncated };
67
+
68
+ // Write to temp file first
69
+ try {
70
+ writeFileSync(tempPath, JSON.stringify(data), "utf-8");
71
+ } catch (err) {
72
+ logger.error("[chunkStorage] Failed to write chunks temp file:", String(err));
73
+ return targetPath;
74
+ }
75
+
76
+ // Rename to target (atomic on most filesystems)
77
+ try {
78
+ renameSync(tempPath, targetPath);
79
+ } catch (err) {
80
+ // eslint-disable-next-line no-console
81
+ console.warn("[chunkStorage] Rename failed, falling back to copy+delete:", err);
82
+ // If rename fails, try copy + delete
83
+ try {
84
+ copyFileSync(tempPath, targetPath);
85
+ unlinkSync(tempPath);
86
+ } catch (copyErr) {
87
+ logger.error("[chunkStorage] Failed to copy chunks file:", String(copyErr));
88
+ }
89
+ }
90
+
91
+ return targetPath;
92
+ }
93
+
94
+ /**
95
+ * Read chunks from a chunk file path.
96
+ * Returns null if the file does not exist or cannot be read.
97
+ */
98
+ export function readChunks(path: string): StreamingChunksData | null {
99
+ if (!existsSync(path)) {
100
+ return null;
101
+ }
102
+ try {
103
+ const content = readFileSync(path, "utf-8");
104
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
105
+ const parsed = JSON.parse(content);
106
+ const result = StreamingChunksDataSchema.safeParse(parsed);
107
+ if (!result.success) return null;
108
+ return result.data;
109
+ } catch {
110
+ return null;
111
+ }
112
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Claude Code (and similar Anthropic SDK-based clients) prepend a synthetic
3
+ * text block to the `system` array that looks like an HTTP header:
4
+ *
5
+ * x-anthropic-billing-header: cc_version=2.1.92.61c; cc_entrypoint=cli; cch=00000;
6
+ *
7
+ * The `cch` field is a per-request cache-hint value. When this block is
8
+ * forwarded verbatim to a third-party model, the system prompt prefix
9
+ * changes on every request, which kills prefix-cache hit rates and wastes
10
+ * input tokens.
11
+ *
12
+ * This module detects the marker and returns a rewritten body with the
13
+ * block removed. It is opt-in (see constants.ts) so the proxy remains a
14
+ * transparent pass-through by default.
15
+ */
16
+
17
+ const BILLING_HEADER_PREFIX = "x-anthropic-billing-header:";
18
+
19
+ type StripResult = {
20
+ /** The new request body. Equal to input when no change was made. */
21
+ body: string;
22
+ /** Number of billing-header text blocks removed. */
23
+ removed: number;
24
+ };
25
+
26
+ /**
27
+ * Returns true if the given text looks like the synthetic billing-header
28
+ * block that Claude Code injects. Whitespace at the start is tolerated and
29
+ * the match is case-insensitive on the header name.
30
+ */
31
+ export function isClaudeCodeBillingHeaderBlock(text: string): boolean {
32
+ const trimmed = text.trimStart();
33
+ return trimmed.toLowerCase().startsWith(BILLING_HEADER_PREFIX);
34
+ }
35
+
36
+ import { safeGetOwnProperty, isPlainRecord } from "../lib/objectUtils";
37
+
38
+ function isObjectWithSystem(value: unknown): value is { system?: unknown[] } {
39
+ if (!isPlainRecord(value)) return false;
40
+ return Object.prototype.hasOwnProperty.call(value, "system");
41
+ }
42
+
43
+ function isBillingHeaderTextBlock(block: unknown): boolean {
44
+ if (!isPlainRecord(block)) return false;
45
+ const typeVal = safeGetOwnProperty(block, "type");
46
+ const textVal = safeGetOwnProperty(block, "text");
47
+ if (typeof typeVal !== "string" || typeVal !== "text") return false;
48
+ if (typeof textVal !== "string") return false;
49
+ return isClaudeCodeBillingHeaderBlock(textVal);
50
+ }
51
+
52
+ /**
53
+ * Inspects the request body and, if it is an Anthropic-format request whose
54
+ * `system` array contains one or more Claude Code billing-header text blocks,
55
+ * returns a new body with those blocks removed. The original string is
56
+ * returned unchanged (by reference equality on the result) when no edit is
57
+ * needed so callers can avoid re-serializing.
58
+ */
59
+ export function stripClaudeCodeBillingHeader(rawBody: string): StripResult {
60
+ let parsed: unknown;
61
+ try {
62
+ parsed = JSON.parse(rawBody);
63
+ } catch {
64
+ return { body: rawBody, removed: 0 };
65
+ }
66
+
67
+ if (!isObjectWithSystem(parsed)) {
68
+ return { body: rawBody, removed: 0 };
69
+ }
70
+
71
+ const system = parsed.system;
72
+
73
+ if (!Array.isArray(system) || system.length === 0) {
74
+ return { body: rawBody, removed: 0 };
75
+ }
76
+
77
+ const kept: unknown[] = [];
78
+ let removed = 0;
79
+
80
+ for (const block of system) {
81
+ if (isBillingHeaderTextBlock(block)) {
82
+ removed += 1;
83
+ continue;
84
+ }
85
+ kept.push(block);
86
+ }
87
+
88
+ if (removed === 0) {
89
+ return { body: rawBody, removed: 0 };
90
+ }
91
+
92
+ if (kept.length === 0) {
93
+ delete parsed.system;
94
+ } else {
95
+ parsed.system = kept;
96
+ }
97
+
98
+ return { body: JSON.stringify(parsed), removed };
99
+ }