@tonyclaw/agent-inspector 3.0.45 → 3.0.46

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 (338) hide show
  1. package/.output/{nitro.json → backend/nitro.json} +3 -3
  2. package/.output/backend-public/assets/index-D4LX-sw-.js +11 -0
  3. package/.output/cli.js +2326 -516
  4. package/.output/server/_libs/extend.mjs +4 -8
  5. package/.output/server/_libs/jszip.mjs +9 -35
  6. package/.output/server/_libs/react-dom.mjs +2 -3
  7. package/.output/server/_libs/react.mjs +1 -27
  8. package/.output/server/_libs/tanstack__react-router.mjs +1 -329
  9. package/.output/server/_libs/tanstack__router-core.mjs +42 -100
  10. package/.output/server/_libs/use-sync-external-store.mjs +1 -64
  11. package/.output/server/_libs/ws.mjs +1 -1
  12. package/.output/server/_ssr/index.mjs +4 -4
  13. package/.output/server/_ssr/{publicBasePathContext-5264L3rX.mjs → publicBasePathContext-IH2ilOfu.mjs} +52 -24
  14. package/.output/server/_ssr/{router-BJ_ltLim.mjs → router-D18yUq36.mjs} +19770 -19447
  15. package/.output/server/_ssr/{start-C6MjPjxp.mjs → start-COQu_AgD.mjs} +1 -1
  16. package/.output/server/_tanstack-start-manifest_v-DnbdNeun.mjs +4 -0
  17. package/.output/server/index.mjs +5 -145
  18. package/.output/{public/assets/CompareDrawer-B3_TLTJh.js → ui/assets/CompareDrawer-DAUuIJ6G.js} +1 -1
  19. package/.output/{public/assets/InspectorPet-Cpn6hv4P.js → ui/assets/InspectorPet-BRBVjOWI.js} +1 -1
  20. package/.output/ui/assets/ProxyViewerContainer-D7Sq0ctc.js +59 -0
  21. package/.output/{public/assets/ReplayDialog-B3EKVdJk.js → ui/assets/ReplayDialog-nDLsjOhs.js} +1 -1
  22. package/.output/{public/assets/RequestAnatomy-COOQjyUC.js → ui/assets/RequestAnatomy-zd4BDgL0.js} +1 -1
  23. package/.output/ui/assets/ResponseView-AdIXGcSn.js +2 -0
  24. package/.output/ui/assets/StreamingChunkSequence-CesTz8He.js +1 -0
  25. package/.output/{public/assets/_sessionId-zbgYl7ZJ.js → ui/assets/_sessionId-BAfzrhSU.js} +1 -1
  26. package/.output/{public/assets/_sessionId-BJlM3nmt.js → ui/assets/_sessionId-QnBomgPD.js} +1 -1
  27. package/.output/ui/assets/favicon-DYEkHSNl.svg +21 -0
  28. package/.output/{public/assets/index-BIK4bPTv.js → ui/assets/index-BcsdxBAd.js} +1 -1
  29. package/.output/ui/assets/index-BvXp42al.css +1 -0
  30. package/.output/{public/assets/index-D2Qw6Ri2.js → ui/assets/index-D0rtCN9V.js} +1 -1
  31. package/.output/ui/assets/index-DHpwr08Z.js +73 -0
  32. package/.output/{public/assets/index-xF_NY8Yb.js → ui/assets/index-dNuk2dsU.js} +5 -5
  33. package/.output/ui/assets/json-viewer-BvFHglMb.js +1 -0
  34. package/.output/{public/assets/jszip.min-B_rfAUCe.js → ui/assets/jszip.min-B5Z7gNZB.js} +1 -1
  35. package/.output/ui/index.html +229 -0
  36. package/.output/ui/runtime-config.js +2 -0
  37. package/.output/workers/logFinalizer.worker.js +979 -414
  38. package/.output/workers/sessionWorkerEntry.js +979 -414
  39. package/README.md +175 -43
  40. package/docs/instance-control.openapi.yaml +59 -4
  41. package/package.json +25 -5
  42. package/scripts/generate-theme-scales.mjs +3 -3
  43. package/src/{router.tsx → backend/router.tsx} +3 -15
  44. package/src/backend/routes/$.ts +26 -0
  45. package/src/backend/routes/__root.ts +4 -0
  46. package/src/{routes → backend/routes}/api/-instances.ts +3 -3
  47. package/src/{routes → backend/routes}/api/alerts.summary.ts +3 -3
  48. package/src/{routes → backend/routes}/api/alerts.ts +4 -4
  49. package/src/{routes → backend/routes}/api/config.paths.ts +1 -1
  50. package/src/{routes → backend/routes}/api/config.ts +11 -2
  51. package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.help.ts +1 -1
  52. package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.install.ts +1 -1
  53. package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.runner-presets.ts +1 -1
  54. package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.upgrade.ts +1 -1
  55. package/src/{routes → backend/routes}/api/ecosystem.packages.ts +5 -1
  56. package/src/{routes → backend/routes}/api/ecosystem.recipes.$recipeId.run.ts +1 -1
  57. package/src/{routes → backend/routes}/api/ecosystem.tasks.$taskId.ts +1 -1
  58. package/src/{routes → backend/routes}/api/ecosystem.tasks.ts +1 -1
  59. package/src/{routes → backend/routes}/api/groups.$groupId.evidence.ts +2 -2
  60. package/src/{routes → backend/routes}/api/groups.$groupId.sessions.ts +2 -2
  61. package/src/{routes → backend/routes}/api/groups.$groupId.ts +2 -2
  62. package/src/{routes → backend/routes}/api/groups.ts +2 -2
  63. package/src/{routes → backend/routes}/api/health.ts +3 -3
  64. package/src/{routes → backend/routes}/api/instances.$name.connection.ts +1 -1
  65. package/src/{routes → backend/routes}/api/instances.$name.restart.ts +2 -2
  66. package/src/{routes → backend/routes}/api/instances.$name.start.ts +2 -2
  67. package/src/{routes → backend/routes}/api/instances.$name.stop.ts +2 -2
  68. package/src/{routes → backend/routes}/api/instances.$name.ts +1 -1
  69. package/src/{routes → backend/routes}/api/instances.ts +1 -1
  70. package/src/{routes → backend/routes}/api/knowledge.candidates.$candidateId.promote.ts +2 -2
  71. package/src/{routes → backend/routes}/api/knowledge.candidates.$candidateId.ts +2 -2
  72. package/src/{routes → backend/routes}/api/knowledge.candidates.ts +1 -1
  73. package/src/{routes → backend/routes}/api/knowledge.project-context.ts +2 -2
  74. package/src/{routes → backend/routes}/api/knowledge.search.ts +2 -2
  75. package/src/{routes → backend/routes}/api/knowledge.sessions.$sessionId.candidates.ts +3 -3
  76. package/src/{routes → backend/routes}/api/logs.$id.body.ts +6 -6
  77. package/src/{routes → backend/routes}/api/logs.$id.chunks.ts +5 -4
  78. package/src/{routes → backend/routes}/api/logs.$id.replay.ts +12 -11
  79. package/src/{routes → backend/routes}/api/logs.$id.ts +4 -3
  80. package/src/{routes → backend/routes}/api/logs.import.ts +3 -3
  81. package/src/{routes → backend/routes}/api/logs.stream.ts +11 -7
  82. package/src/{routes → backend/routes}/api/logs.ts +17 -9
  83. package/src/{routes → backend/routes}/api/mcp.ts +1 -1
  84. package/src/{routes → backend/routes}/api/models.ts +1 -1
  85. package/src/{routes → backend/routes}/api/pi-agent.ts +2 -2
  86. package/src/{routes → backend/routes}/api/providers.$providerId.model-metadata.ts +5 -5
  87. package/src/{routes → backend/routes}/api/providers.$providerId.test.log.ts +7 -7
  88. package/src/{routes → backend/routes}/api/providers.$providerId.ts +4 -4
  89. package/src/{routes → backend/routes}/api/providers.export.ts +1 -1
  90. package/src/{routes → backend/routes}/api/providers.import.ts +3 -3
  91. package/src/{routes → backend/routes}/api/providers.scan.ts +2 -2
  92. package/src/{routes → backend/routes}/api/providers.ts +9 -4
  93. package/src/{routes → backend/routes}/api/runs.$runId.evidence.ts +2 -2
  94. package/src/{routes → backend/routes}/api/runs.$runId.ts +2 -2
  95. package/src/{routes → backend/routes}/api/runs.ts +4 -4
  96. package/src/backend/routes/api/runtime.ts +46 -0
  97. package/src/{routes → backend/routes}/api/sessions.ts +3 -3
  98. package/src/{routes → backend/routes}/proxy/$.ts +1 -1
  99. package/src/{routes → backend/routes}/readyz.ts +1 -1
  100. package/src/{start.ts → backend/start.ts} +1 -1
  101. package/src/cli/alias.ts +2 -11
  102. package/src/cli/doctor.ts +32 -9
  103. package/src/cli/instance.ts +26 -13
  104. package/src/cli/instanceArgs.ts +30 -11
  105. package/src/cli/instanceControl.ts +0 -2
  106. package/src/cli/instanceModel.ts +9 -17
  107. package/src/cli/instanceRegistry.ts +0 -9
  108. package/src/cli/networkHints.ts +27 -14
  109. package/src/cli/onboard.ts +69 -2
  110. package/src/cli/onboardTarget.ts +89 -40
  111. package/src/cli/templates/codex-skill-onboard.ts +44 -14
  112. package/src/cli/templates/skill-onboard.ts +64 -28
  113. package/src/cli.ts +184 -61
  114. package/src/components/ProxyViewer.tsx +11 -9
  115. package/src/components/ProxyViewerContainer.tsx +12 -11
  116. package/src/components/pi-agent/PiAgentPanel.tsx +2 -2
  117. package/src/components/providers/ProviderCard.tsx +1 -1
  118. package/src/components/providers/ProviderTestResultsView.tsx +1 -1
  119. package/src/components/providers/SettingsDialog.tsx +159 -144
  120. package/src/components/proxy-viewer/ApplicationBar.tsx +2 -1
  121. package/src/components/proxy-viewer/LogEntry.tsx +1 -1
  122. package/src/components/proxy-viewer/log-formats/index.ts +1 -1
  123. package/src/contracts/index.ts +5 -0
  124. package/src/contracts/log.ts +33 -3
  125. package/src/knowledge/types.ts +3 -2
  126. package/src/lib/alertContract.ts +2 -2
  127. package/src/lib/apiClient.ts +30 -7
  128. package/src/lib/apiFormat.ts +29 -0
  129. package/src/lib/basePath.ts +29 -11
  130. package/src/lib/browserRuntimeContract.ts +124 -0
  131. package/src/lib/browserRuntimeResolver.ts +244 -0
  132. package/src/{proxy → lib}/claudeCodeStrip.ts +6 -7
  133. package/src/lib/codingAgentEndpoints.ts +38 -0
  134. package/src/lib/instanceContract.ts +27 -11
  135. package/src/lib/piAgentContract.ts +3 -2
  136. package/src/lib/publicBasePathContext.ts +40 -10
  137. package/src/lib/resourceLimits.ts +2 -0
  138. package/src/lib/runContract.ts +3 -3
  139. package/src/lib/sessionInfoContract.ts +3 -2
  140. package/src/mcp/instanceHandlers.ts +1 -0
  141. package/src/mcp/server.ts +19 -24
  142. package/src/proxy/config.ts +25 -30
  143. package/src/proxy/dataDir.ts +43 -7
  144. package/src/proxy/ecosystemTasks.ts +161 -70
  145. package/src/proxy/formats/registry.ts +6 -21
  146. package/src/proxy/handler.ts +129 -29
  147. package/src/proxy/identityProxy.ts +419 -39
  148. package/src/proxy/jsonlRecovery.ts +554 -0
  149. package/src/proxy/logFinalizer.ts +25 -10
  150. package/src/proxy/logImporter.ts +2 -1
  151. package/src/proxy/logIndex.ts +191 -95
  152. package/src/proxy/logger.ts +77 -14
  153. package/src/proxy/platformCommands.ts +41 -0
  154. package/src/proxy/privateDataPath.ts +310 -88
  155. package/src/proxy/rawStreamCapture.ts +194 -13
  156. package/src/proxy/runtimeAdmission.ts +5 -2
  157. package/src/proxy/runtimeHealth.ts +14 -1
  158. package/src/proxy/runtimeShutdown.ts +11 -1
  159. package/src/proxy/schemas.ts +7 -1
  160. package/src/proxy/sessionArchive.ts +78 -14
  161. package/src/proxy/sqliteLogIndex.ts +65 -2
  162. package/src/proxy/store.ts +141 -34
  163. package/src/proxy/uiAssetServer.ts +106 -0
  164. package/src/proxy/upstream.ts +29 -2
  165. package/src/ui/bootstrap.ts +167 -0
  166. package/src/ui/index.html +38 -0
  167. package/src/ui/logsStreamClient.ts +20 -0
  168. package/src/ui/main.tsx +13 -0
  169. package/src/ui/public/runtime-config.js +2 -0
  170. package/src/ui/router.tsx +13 -0
  171. package/src/ui/routes/__root.tsx +69 -0
  172. package/src/{routes → ui/routes}/index.tsx +2 -3
  173. package/src/{routes → ui/routes}/session/$sessionId.tsx +5 -11
  174. package/src/ui/runtimeDiscovery.ts +50 -0
  175. package/styles/globals.css +1 -0
  176. package/.output/public/assets/ProxyViewerContainer-BEAhuGOJ.js +0 -126
  177. package/.output/public/assets/ResponseView-DBCjkwye.js +0 -2
  178. package/.output/public/assets/StreamingChunkSequence-CuXhDgMo.js +0 -1
  179. package/.output/public/assets/index-BZo_8koU.js +0 -70
  180. package/.output/public/assets/index-ZWschkCh.css +0 -1
  181. package/.output/public/assets/json-viewer-B-Ao6pwW.js +0 -1
  182. package/.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs +0 -1
  183. package/.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs +0 -344
  184. package/.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs +0 -2
  185. package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +0 -2
  186. package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +0 -2
  187. package/.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs +0 -11
  188. package/.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs +0 -69
  189. package/.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs +0 -1
  190. package/.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs +0 -28
  191. package/.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs +0 -6
  192. package/.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs +0 -34
  193. package/.output/server/_libs/aria-hidden.mjs +0 -122
  194. package/.output/server/_libs/bail.mjs +0 -8
  195. package/.output/server/_libs/character-entities.mjs +0 -2130
  196. package/.output/server/_libs/class-variance-authority.mjs +0 -44
  197. package/.output/server/_libs/clsx.mjs +0 -16
  198. package/.output/server/_libs/comma-separated-tokens.mjs +0 -10
  199. package/.output/server/_libs/decode-named-character-reference+[...].mjs +0 -8
  200. package/.output/server/_libs/dequal.mjs +0 -27
  201. package/.output/server/_libs/detect-node-es.mjs +0 -1
  202. package/.output/server/_libs/devlop.mjs +0 -8
  203. package/.output/server/_libs/diff.mjs +0 -320
  204. package/.output/server/_libs/estree-util-is-identifier-name.mjs +0 -11
  205. package/.output/server/_libs/floating-ui__core.mjs +0 -725
  206. package/.output/server/_libs/floating-ui__dom.mjs +0 -622
  207. package/.output/server/_libs/floating-ui__react-dom.mjs +0 -292
  208. package/.output/server/_libs/floating-ui__utils.mjs +0 -320
  209. package/.output/server/_libs/get-nonce.mjs +0 -9
  210. package/.output/server/_libs/hast-util-to-jsx-runtime.mjs +0 -388
  211. package/.output/server/_libs/hast-util-whitespace.mjs +0 -10
  212. package/.output/server/_libs/html-url-attributes.mjs +0 -26
  213. package/.output/server/_libs/inline-style-parser.mjs +0 -142
  214. package/.output/server/_libs/is-plain-obj.mjs +0 -10
  215. package/.output/server/_libs/lucide-react.mjs +0 -1027
  216. package/.output/server/_libs/mdast-util-from-markdown.mjs +0 -717
  217. package/.output/server/_libs/mdast-util-to-hast.mjs +0 -710
  218. package/.output/server/_libs/mdast-util-to-string.mjs +0 -38
  219. package/.output/server/_libs/micromark-core-commonmark.mjs +0 -2259
  220. package/.output/server/_libs/micromark-factory-destination.mjs +0 -94
  221. package/.output/server/_libs/micromark-factory-label.mjs +0 -63
  222. package/.output/server/_libs/micromark-factory-space.mjs +0 -24
  223. package/.output/server/_libs/micromark-factory-title.mjs +0 -65
  224. package/.output/server/_libs/micromark-factory-whitespace.mjs +0 -22
  225. package/.output/server/_libs/micromark-util-character.mjs +0 -44
  226. package/.output/server/_libs/micromark-util-chunked.mjs +0 -36
  227. package/.output/server/_libs/micromark-util-classify-character+[...].mjs +0 -12
  228. package/.output/server/_libs/micromark-util-combine-extensions+[...].mjs +0 -41
  229. package/.output/server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +0 -19
  230. package/.output/server/_libs/micromark-util-decode-string.mjs +0 -21
  231. package/.output/server/_libs/micromark-util-encode.mjs +0 -1
  232. package/.output/server/_libs/micromark-util-html-tag-name.mjs +0 -69
  233. package/.output/server/_libs/micromark-util-normalize-identifier+[...].mjs +0 -6
  234. package/.output/server/_libs/micromark-util-resolve-all.mjs +0 -15
  235. package/.output/server/_libs/micromark-util-sanitize-uri.mjs +0 -41
  236. package/.output/server/_libs/micromark-util-subtokenize.mjs +0 -346
  237. package/.output/server/_libs/micromark.mjs +0 -906
  238. package/.output/server/_libs/property-information.mjs +0 -1209
  239. package/.output/server/_libs/radix-ui.mjs +0 -1
  240. package/.output/server/_libs/radix-ui__number.mjs +0 -6
  241. package/.output/server/_libs/radix-ui__primitive.mjs +0 -11
  242. package/.output/server/_libs/radix-ui__react-accordion.mjs +0 -1
  243. package/.output/server/_libs/radix-ui__react-alert-dialog.mjs +0 -1
  244. package/.output/server/_libs/radix-ui__react-arrow.mjs +0 -23
  245. package/.output/server/_libs/radix-ui__react-aspect-ratio.mjs +0 -1
  246. package/.output/server/_libs/radix-ui__react-avatar.mjs +0 -1
  247. package/.output/server/_libs/radix-ui__react-checkbox.mjs +0 -1
  248. package/.output/server/_libs/radix-ui__react-collapsible.mjs +0 -144
  249. package/.output/server/_libs/radix-ui__react-collection.mjs +0 -69
  250. package/.output/server/_libs/radix-ui__react-compose-refs.mjs +0 -38
  251. package/.output/server/_libs/radix-ui__react-context-menu.mjs +0 -1
  252. package/.output/server/_libs/radix-ui__react-context.mjs +0 -64
  253. package/.output/server/_libs/radix-ui__react-dialog.mjs +0 -280
  254. package/.output/server/_libs/radix-ui__react-direction.mjs +0 -9
  255. package/.output/server/_libs/radix-ui__react-dropdown-menu.mjs +0 -1
  256. package/.output/server/_libs/radix-ui__react-focus-guards.mjs +0 -39
  257. package/.output/server/_libs/radix-ui__react-focus-scope.mjs +0 -210
  258. package/.output/server/_libs/radix-ui__react-form.mjs +0 -1
  259. package/.output/server/_libs/radix-ui__react-hover-card.mjs +0 -1
  260. package/.output/server/_libs/radix-ui__react-id.mjs +0 -14
  261. package/.output/server/_libs/radix-ui__react-label.mjs +0 -1
  262. package/.output/server/_libs/radix-ui__react-menu.mjs +0 -1
  263. package/.output/server/_libs/radix-ui__react-menubar.mjs +0 -1
  264. package/.output/server/_libs/radix-ui__react-popover.mjs +0 -1
  265. package/.output/server/_libs/radix-ui__react-popper.mjs +0 -340
  266. package/.output/server/_libs/radix-ui__react-portal.mjs +0 -16
  267. package/.output/server/_libs/radix-ui__react-presence.mjs +0 -173
  268. package/.output/server/_libs/radix-ui__react-primitive.mjs +0 -42
  269. package/.output/server/_libs/radix-ui__react-progress.mjs +0 -1
  270. package/.output/server/_libs/radix-ui__react-radio-group.mjs +0 -1
  271. package/.output/server/_libs/radix-ui__react-roving-focus.mjs +0 -235
  272. package/.output/server/_libs/radix-ui__react-scroll-area.mjs +0 -731
  273. package/.output/server/_libs/radix-ui__react-select.mjs +0 -1207
  274. package/.output/server/_libs/radix-ui__react-separator.mjs +0 -28
  275. package/.output/server/_libs/radix-ui__react-slider.mjs +0 -1
  276. package/.output/server/_libs/radix-ui__react-slot.mjs +0 -132
  277. package/.output/server/_libs/radix-ui__react-switch.mjs +0 -1
  278. package/.output/server/_libs/radix-ui__react-tabs.mjs +0 -194
  279. package/.output/server/_libs/radix-ui__react-toast.mjs +0 -2
  280. package/.output/server/_libs/radix-ui__react-toggle-group.mjs +0 -1
  281. package/.output/server/_libs/radix-ui__react-toggle.mjs +0 -1
  282. package/.output/server/_libs/radix-ui__react-toolbar.mjs +0 -1
  283. package/.output/server/_libs/radix-ui__react-tooltip.mjs +0 -498
  284. package/.output/server/_libs/radix-ui__react-use-previous.mjs +0 -14
  285. package/.output/server/_libs/radix-ui__react-use-size.mjs +0 -39
  286. package/.output/server/_libs/react-markdown.mjs +0 -235
  287. package/.output/server/_libs/react-remove-scroll-bar.mjs +0 -82
  288. package/.output/server/_libs/react-remove-scroll.mjs +0 -328
  289. package/.output/server/_libs/react-style-singleton.mjs +0 -69
  290. package/.output/server/_libs/remark-parse.mjs +0 -19
  291. package/.output/server/_libs/remark-rehype.mjs +0 -21
  292. package/.output/server/_libs/space-separated-tokens.mjs +0 -6
  293. package/.output/server/_libs/style-to-js.mjs +0 -72
  294. package/.output/server/_libs/style-to-object.mjs +0 -38
  295. package/.output/server/_libs/swr.mjs +0 -947
  296. package/.output/server/_libs/tailwind-merge.mjs +0 -3255
  297. package/.output/server/_libs/tanstack__react-virtual.mjs +0 -130
  298. package/.output/server/_libs/tanstack__virtual-core.mjs +0 -1319
  299. package/.output/server/_libs/three.mjs +0 -26016
  300. package/.output/server/_libs/trim-lines.mjs +0 -41
  301. package/.output/server/_libs/trough.mjs +0 -85
  302. package/.output/server/_libs/tslib.mjs +0 -1
  303. package/.output/server/_libs/ungap__structured-clone.mjs +0 -212
  304. package/.output/server/_libs/unified.mjs +0 -661
  305. package/.output/server/_libs/unist-util-is.mjs +0 -100
  306. package/.output/server/_libs/unist-util-position.mjs +0 -27
  307. package/.output/server/_libs/unist-util-stringify-position.mjs +0 -27
  308. package/.output/server/_libs/unist-util-visit-parents.mjs +0 -82
  309. package/.output/server/_libs/unist-util-visit.mjs +0 -24
  310. package/.output/server/_libs/use-callback-ref.mjs +0 -66
  311. package/.output/server/_libs/use-sidecar.mjs +0 -106
  312. package/.output/server/_libs/vfile-message.mjs +0 -138
  313. package/.output/server/_libs/vfile.mjs +0 -467
  314. package/.output/server/_sessionId-CJ99gZFV.mjs +0 -84
  315. package/.output/server/_sessionId-DEjScBpK.mjs +0 -146
  316. package/.output/server/_ssr/CompareDrawer-DtOIMeQB.mjs +0 -1060
  317. package/.output/server/_ssr/InspectorPet-CXc4oCBQ.mjs +0 -938
  318. package/.output/server/_ssr/ProxyViewerContainer-DVPlwfkt.mjs +0 -17632
  319. package/.output/server/_ssr/ReplayDialog-BQEA95qC.mjs +0 -725
  320. package/.output/server/_ssr/RequestAnatomy-BMyd8B7Q.mjs +0 -837
  321. package/.output/server/_ssr/ResponseView-qrTaEIM8.mjs +0 -757
  322. package/.output/server/_ssr/StreamingChunkSequence-BFTL8mIy.mjs +0 -274
  323. package/.output/server/_ssr/index-Bcj1Hcy4.mjs +0 -135
  324. package/.output/server/_ssr/index-DW-M7XUG.mjs +0 -84
  325. package/.output/server/_ssr/json-viewer-DiI0Yjsn.mjs +0 -483
  326. package/.output/server/_tanstack-start-manifest_v-C6pXXX9R.mjs +0 -4
  327. package/.output/server/node_modules/tslib/modules/index.js +0 -70
  328. package/.output/server/node_modules/tslib/modules/package.json +0 -3
  329. package/.output/server/node_modules/tslib/package.json +0 -47
  330. package/.output/server/node_modules/tslib/tslib.js +0 -484
  331. package/.output/server/package.json +0 -9
  332. package/src/routes/__root.tsx +0 -170
  333. /package/.output/{public → ui}/assets/agent-inspector-58K1_MsC.ico +0 -0
  334. /package/.output/{public → ui}/assets/alibaba-TTwafVwX.svg +0 -0
  335. /package/.output/{public → ui}/assets/minimax-BPMzvuL-.jpeg +0 -0
  336. /package/.output/{public → ui}/assets/qwen-mMn3f5ul.webp +0 -0
  337. /package/.output/{public → ui}/assets/zhipuai-BPNAnxo-.svg +0 -0
  338. /package/src/{routes → backend/routes}/livez.ts +0 -0
@@ -1,5 +1,5 @@
1
1
  import { Buffer } from "node:buffer";
2
- import { spawn } from "node:child_process";
2
+ import { spawn, type ChildProcess } from "node:child_process";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import type {
5
5
  EcosystemExecutionPlan,
@@ -8,15 +8,31 @@ import type {
8
8
  EcosystemTaskStatus,
9
9
  } from "../lib/ecosystemContract";
10
10
  import { findEcosystemPackage, findEcosystemRecipe } from "./ecosystemRegistry";
11
- import { npmCommand, npmSpawnCommand } from "./platformCommands";
11
+ import {
12
+ npmCommand,
13
+ npmSpawnCommand,
14
+ signalProcessTree,
15
+ type CommandSpec,
16
+ } from "./platformCommands";
12
17
 
13
18
  const MAX_TASKS = 50;
14
19
  const OUTPUT_LIMIT = 40_000;
15
20
  const INSTALL_TIMEOUT_MS = 120_000;
16
21
  const HELP_TIMEOUT_MS = 30_000;
17
22
  const RECIPE_TIMEOUT_MS = 90_000;
23
+ const SHUTDOWN_GRACE_MS = 2_000;
18
24
 
19
25
  const tasks = new Map<string, EcosystemTask>();
26
+ type ActiveTask = {
27
+ child: ChildProcess;
28
+ closed: Promise<void>;
29
+ resolveClosed: () => void;
30
+ timeout: ReturnType<typeof setTimeout>;
31
+ };
32
+
33
+ const activeTasks = new Map<string, ActiveTask>();
34
+ let acceptingTasks = true;
35
+ let shutdownForceTimer: ReturnType<typeof setTimeout> | null = null;
20
36
 
21
37
  function nowIso(): string {
22
38
  return new Date().toISOString();
@@ -152,6 +168,77 @@ function completeTask(
152
168
  task.finishedAt = nowIso();
153
169
  }
154
170
 
171
+ function finishActiveTask(taskId: string): void {
172
+ const active = activeTasks.get(taskId);
173
+ if (active === undefined) return;
174
+ clearTimeout(active.timeout);
175
+ activeTasks.delete(taskId);
176
+ active.resolveClosed();
177
+ if (activeTasks.size === 0 && shutdownForceTimer !== null) {
178
+ clearTimeout(shutdownForceTimer);
179
+ shutdownForceTimer = null;
180
+ }
181
+ }
182
+
183
+ function failTaskForShutdown(task: EcosystemTask): void {
184
+ if (task.status !== "running" && task.status !== "queued") return;
185
+ const error = "Runtime shutdown terminated the ecosystem command";
186
+ appendTaskOutput(task, `\n${error}.\n`);
187
+ completeTask(task, "failed", null, error);
188
+ }
189
+
190
+ function forceActiveTaskTrees(): void {
191
+ for (const active of activeTasks.values()) {
192
+ const pid = active.child.pid;
193
+ if (pid !== undefined) signalProcessTree(pid, "SIGKILL");
194
+ }
195
+ }
196
+
197
+ function launchTask(task: EcosystemTask, spawnCommand: CommandSpec, timeoutMs: number): void {
198
+ const child = spawn(spawnCommand.command, spawnCommand.args, {
199
+ detached: process.platform !== "win32",
200
+ windowsHide: true,
201
+ shell: false,
202
+ stdio: ["ignore", "pipe", "pipe"],
203
+ });
204
+ let resolveClosed = (): void => {};
205
+ const closed = new Promise<void>((resolve) => {
206
+ resolveClosed = resolve;
207
+ });
208
+ const timeout = setTimeout(() => {
209
+ const pid = child.pid;
210
+ if (pid !== undefined) signalProcessTree(pid, "SIGKILL");
211
+ appendTaskOutput(task, `\nTimed out after ${String(timeoutMs / 1000)}s.\n`);
212
+ }, timeoutMs);
213
+ activeTasks.set(task.id, { child, closed, resolveClosed, timeout });
214
+
215
+ task.status = "running";
216
+ task.startedAt = nowIso();
217
+ appendTaskOutput(task, `$ ${task.command}\n`);
218
+
219
+ child.stdout?.on("data", (chunk: Buffer) => {
220
+ appendTaskOutput(task, chunk.toString("utf8"));
221
+ });
222
+ child.stderr?.on("data", (chunk: Buffer) => {
223
+ appendTaskOutput(task, chunk.toString("utf8"));
224
+ });
225
+ child.on("error", (error) => {
226
+ appendTaskOutput(task, `\n${error.message}\n`);
227
+ if (task.status !== "failed") completeTask(task, "failed", null, error.message);
228
+ finishActiveTask(task.id);
229
+ });
230
+ child.on("close", (code) => {
231
+ if (task.status !== "failed") {
232
+ if (code === 0) {
233
+ completeTask(task, "succeeded", code, null);
234
+ } else {
235
+ completeTask(task, "failed", code, `Command exited with code ${String(code ?? "unknown")}`);
236
+ }
237
+ }
238
+ finishActiveTask(task.id);
239
+ });
240
+ }
241
+
155
242
  export function getEcosystemTask(taskId: string): EcosystemTask | null {
156
243
  return tasks.get(taskId) ?? null;
157
244
  }
@@ -164,6 +251,7 @@ export function startEcosystemTask(
164
251
  packageId: string,
165
252
  action: EcosystemTaskAction,
166
253
  ): EcosystemTask | null {
254
+ if (!acceptingTasks) return null;
167
255
  if (action === "recipe") return null;
168
256
  const definition =
169
257
  action === "runner-presets"
@@ -190,46 +278,13 @@ export function startEcosystemTask(
190
278
  };
191
279
  rememberTask(task);
192
280
 
193
- const spawnCommand = npmSpawnCommand(args);
194
- const child = spawn(spawnCommand.command, spawnCommand.args, {
195
- windowsHide: true,
196
- shell: false,
197
- stdio: ["ignore", "pipe", "pipe"],
198
- });
199
- const timeout = setTimeout(() => {
200
- child.kill();
201
- appendTaskOutput(task, `\nTimed out after ${String(packageActionTimeout(action) / 1000)}s.\n`);
202
- }, packageActionTimeout(action));
203
-
204
- task.status = "running";
205
- task.startedAt = nowIso();
206
- appendTaskOutput(task, `$ ${task.command}\n`);
207
-
208
- child.stdout.on("data", (chunk: Buffer) => {
209
- appendTaskOutput(task, chunk.toString("utf8"));
210
- });
211
- child.stderr.on("data", (chunk: Buffer) => {
212
- appendTaskOutput(task, chunk.toString("utf8"));
213
- });
214
- child.on("error", (error) => {
215
- clearTimeout(timeout);
216
- appendTaskOutput(task, `\n${error.message}\n`);
217
- completeTask(task, "failed", null, error.message);
218
- });
219
- child.on("close", (code) => {
220
- clearTimeout(timeout);
221
- if (task.status === "failed") return;
222
- if (code === 0) {
223
- completeTask(task, "succeeded", code, null);
224
- return;
225
- }
226
- completeTask(task, "failed", code, `Command exited with code ${String(code ?? "unknown")}`);
227
- });
281
+ launchTask(task, npmSpawnCommand(args), packageActionTimeout(action));
228
282
 
229
283
  return task;
230
284
  }
231
285
 
232
286
  export function startEcosystemRecipeTask(recipeId: string): EcosystemTask | null {
287
+ if (!acceptingTasks) return null;
233
288
  const recipe = findEcosystemRecipe(recipeId);
234
289
  if (recipe === null) return null;
235
290
  if (!recipe.runnable) return null;
@@ -255,41 +310,77 @@ export function startEcosystemRecipeTask(recipeId: string): EcosystemTask | null
255
310
  };
256
311
  rememberTask(task);
257
312
 
258
- const spawnCommand = npmSpawnCommand(args);
259
- const child = spawn(spawnCommand.command, spawnCommand.args, {
260
- windowsHide: true,
261
- shell: false,
262
- stdio: ["ignore", "pipe", "pipe"],
263
- });
264
- const timeout = setTimeout(() => {
265
- child.kill();
266
- appendTaskOutput(task, `\nTimed out after ${String(RECIPE_TIMEOUT_MS / 1000)}s.\n`);
267
- }, RECIPE_TIMEOUT_MS);
313
+ launchTask(task, npmSpawnCommand(args), RECIPE_TIMEOUT_MS);
268
314
 
269
- task.status = "running";
270
- task.startedAt = nowIso();
271
- appendTaskOutput(task, `$ ${task.command}\n`);
315
+ return task;
316
+ }
272
317
 
273
- child.stdout.on("data", (chunk: Buffer) => {
274
- appendTaskOutput(task, chunk.toString("utf8"));
275
- });
276
- child.stderr.on("data", (chunk: Buffer) => {
277
- appendTaskOutput(task, chunk.toString("utf8"));
278
- });
279
- child.on("error", (error) => {
280
- clearTimeout(timeout);
281
- appendTaskOutput(task, `\n${error.message}\n`);
282
- completeTask(task, "failed", null, error.message);
283
- });
284
- child.on("close", (code) => {
285
- clearTimeout(timeout);
286
- if (task.status === "failed") return;
287
- if (code === 0) {
288
- completeTask(task, "succeeded", code, null);
289
- return;
290
- }
291
- completeTask(task, "failed", code, `Command exited with code ${String(code ?? "unknown")}`);
292
- });
318
+ /** Stop admitting commands and begin terminating every tracked child process tree. */
319
+ export function beginEcosystemTaskShutdown(): void {
320
+ acceptingTasks = false;
321
+ for (const [taskId, active] of activeTasks) {
322
+ const task = tasks.get(taskId);
323
+ if (task !== undefined) failTaskForShutdown(task);
324
+ clearTimeout(active.timeout);
325
+ const pid = active.child.pid;
326
+ if (pid !== undefined) signalProcessTree(pid, "SIGTERM");
327
+ }
328
+ if (activeTasks.size > 0 && shutdownForceTimer === null) {
329
+ shutdownForceTimer = setTimeout(() => {
330
+ shutdownForceTimer = null;
331
+ forceActiveTaskTrees();
332
+ }, SHUTDOWN_GRACE_MS);
333
+ shutdownForceTimer.unref();
334
+ }
335
+ }
336
+
337
+ /** Wait until children signalled by beginEcosystemTaskShutdown have closed. */
338
+ export async function waitForEcosystemTasksToClose(): Promise<void> {
339
+ await Promise.all([...activeTasks.values()].map(async (active) => await active.closed));
340
+ }
341
+
342
+ /** Immediate deadline fallback used by the runtime shutdown coordinator. */
343
+ export function forceCloseEcosystemTasks(): void {
344
+ acceptingTasks = false;
345
+ if (shutdownForceTimer !== null) {
346
+ clearTimeout(shutdownForceTimer);
347
+ shutdownForceTimer = null;
348
+ }
349
+ for (const taskId of activeTasks.keys()) {
350
+ const task = tasks.get(taskId);
351
+ if (task !== undefined) failTaskForShutdown(task);
352
+ }
353
+ forceActiveTaskTrees();
354
+ }
293
355
 
356
+ export function _startEcosystemTaskForTests(
357
+ spawnCommand: CommandSpec,
358
+ timeoutMs = RECIPE_TIMEOUT_MS,
359
+ ): EcosystemTask | null {
360
+ if (!acceptingTasks) return null;
361
+ const task: EcosystemTask = {
362
+ id: randomUUID(),
363
+ packageId: "test-package",
364
+ npmName: "test-package",
365
+ recipeId: null,
366
+ action: "help",
367
+ status: "queued",
368
+ command: `${spawnCommand.command} ${spawnCommand.args.join(" ")}`,
369
+ output: "",
370
+ createdAt: nowIso(),
371
+ startedAt: null,
372
+ finishedAt: null,
373
+ exitCode: null,
374
+ error: null,
375
+ };
376
+ rememberTask(task);
377
+ launchTask(task, spawnCommand, timeoutMs);
294
378
  return task;
295
379
  }
380
+
381
+ export function _resetEcosystemTasksForTests(): void {
382
+ forceCloseEcosystemTasks();
383
+ tasks.clear();
384
+ activeTasks.clear();
385
+ acceptingTasks = true;
386
+ }
@@ -1,12 +1,8 @@
1
1
  import type { FormatHandler } from "./handler";
2
2
  import type { ApiFormat } from "../schemas";
3
- import {
4
- PATH_V1_CHAT_COMPLETIONS,
5
- PATH_CHAT_COMPLETIONS,
6
- PATH_V1_RESPONSES,
7
- PATH_RESPONSES,
8
- PATH_V1_MESSAGES,
9
- } from "../constants";
3
+ import { API_FORMAT_PATH_ENTRIES, apiFormatForPath } from "../../lib/apiFormat";
4
+
5
+ export { apiFormatForPath };
10
6
 
11
7
  /**
12
8
  * API format registry maps request paths and API formats to their FormatHandler.
@@ -63,18 +59,7 @@ export function formatForPath(path: string): FormatHandler | null {
63
59
  return formatRegistry.getByPath(path) ?? null;
64
60
  }
65
61
 
66
- /**
67
- * Resolve the API format from the request endpoint. The endpoint is
68
- * authoritative because Anthropic and OpenAI request bodies overlap for
69
- * simple message-only calls.
70
- */
71
- export function apiFormatForPath(path: string): ApiFormat {
72
- return formatRegistry.getApiFormatByPath(path) ?? "unknown";
73
- }
74
-
75
62
  // Register known paths
76
- formatRegistry.registerPath(PATH_V1_MESSAGES, "anthropic");
77
- formatRegistry.registerPath(PATH_V1_CHAT_COMPLETIONS, "openai");
78
- formatRegistry.registerPath(PATH_CHAT_COMPLETIONS, "openai");
79
- formatRegistry.registerPath(PATH_V1_RESPONSES, "openai");
80
- formatRegistry.registerPath(PATH_RESPONSES, "openai");
63
+ for (const [path, format] of API_FORMAT_PATH_ENTRIES) {
64
+ formatRegistry.registerPath(path, format);
65
+ }
@@ -1,7 +1,7 @@
1
- import { createLog, finalizeLogUpdate, getNextLogId, type CapturedLog } from "./store";
1
+ import { Buffer } from "node:buffer";
2
+ import { createLog, getNextLogId, persistFinalizedLogUpdate, type CapturedLog } from "./store";
2
3
  import { appendLogEntry, logger } from "./logger";
3
4
  import { extractRequestMetadata } from "./schemas";
4
- import { registry } from "./formats";
5
5
  import { findProviderByModel } from "./providers";
6
6
  import { getClientInfo } from "./socketTracker";
7
7
  import { formatForPath } from "./formats";
@@ -25,18 +25,26 @@ import { getConfig } from "./config";
25
25
  import { safeFetch } from "../lib/safeFetch";
26
26
 
27
27
  const ALLOW_LOOPBACK = process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1";
28
- import { stripClaudeCodeBillingHeader } from "./claudeCodeStrip";
28
+ const EMPTY_UPSTREAM_BODY_ERROR = "Upstream response did not include a body";
29
+ const UPSTREAM_STREAM_ERROR = "Upstream stream terminated unexpectedly";
30
+ import { stripClaudeCodeBillingHeader } from "../lib/claudeCodeStrip";
29
31
  import { stripOpenAIOrphanToolMessages } from "./openaiOrphanToolStrip";
30
32
  import { maskApiKey } from "../lib/mask";
31
33
  import { buildFileLogEntry, type FinalizeLogJob } from "./logFinalizer";
32
34
  import { enqueueFinalizeLogJob } from "./sessionRuntime";
33
35
  import { RawStreamCapture } from "./rawStreamCapture";
34
- import { acquireRuntimeRequest, type RuntimeRequestLease } from "./runtimeAdmission";
36
+ import {
37
+ acquireRuntimeRequest,
38
+ getRuntimeAdmissionState,
39
+ type RuntimeRequestLease,
40
+ } from "./runtimeAdmission";
41
+ import { resourceLimitResponse } from "../lib/resourceLimits";
35
42
  import {
36
43
  buildUpstreamUrl,
37
44
  describeApiRoute,
38
45
  getProxyApiPath,
39
46
  injectProviderAuth,
47
+ providerSupportsApiRoute,
40
48
  selectUpstreamBase,
41
49
  setUpstreamHost,
42
50
  } from "./upstream";
@@ -181,31 +189,81 @@ function handleNonStreamingResponse(
181
189
  });
182
190
  }
183
191
 
184
- function handleStreamingResponse(
192
+ function applyRawStreamCaptureStatus(log: CapturedLog, capture: RawStreamCapture): void {
193
+ const status = capture.status;
194
+ log.captureIncomplete = status.incomplete;
195
+ log.droppedChunks = status.droppedChunks;
196
+ log.droppedBytes = status.droppedBytes;
197
+ log.captureIncompleteReason = status.reason;
198
+ }
199
+
200
+ function applyUpstreamStreamError(log: CapturedLog): void {
201
+ log.captureIncomplete = true;
202
+ log.captureIncompleteReason = "upstream-stream-error";
203
+ log.error = UPSTREAM_STREAM_ERROR;
204
+ }
205
+
206
+ async function handleStreamingResponse(
185
207
  upstreamRes: Response,
186
208
  req: Request,
187
209
  startTime: number,
188
210
  upstreamUrl: string,
189
211
  log: CapturedLog,
190
212
  collectStreamingChunks: boolean,
191
- ): Response {
213
+ ): Promise<Response> {
192
214
  log.streaming = true;
193
215
  log.responseStatus = upstreamRes.status;
194
216
 
217
+ if (upstreamRes.body === null) {
218
+ log.elapsedMs = Date.now() - startTime;
219
+ log.responseStatus = STATUS_BAD_GATEWAY;
220
+ log.responseText = EMPTY_UPSTREAM_BODY_ERROR;
221
+ log.captureIncomplete = true;
222
+ log.droppedChunks = 0;
223
+ log.droppedBytes = 0;
224
+ log.captureIncompleteReason = "upstream-stream-error";
225
+ log.error = EMPTY_UPSTREAM_BODY_ERROR;
226
+ appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: EMPTY_UPSTREAM_BODY_ERROR });
227
+ await persistFinalizedLogUpdate(log);
228
+ return new Response("No response body", { status: STATUS_BAD_GATEWAY });
229
+ }
230
+
195
231
  const capture = new RawStreamCapture();
196
232
  const decoder = new TextDecoder();
197
233
  let finalizationScheduled = false;
198
234
  let firstChunkMs: number | null = null;
235
+ let pendingDecoderBytes = 0;
236
+ let downstreamCancelled = false;
237
+
238
+ const submitDecodedText = (text: string, flush: boolean = false): void => {
239
+ const observedBytes = flush
240
+ ? pendingDecoderBytes
241
+ : Math.min(pendingDecoderBytes, Buffer.byteLength(text, "utf-8"));
242
+ pendingDecoderBytes -= observedBytes;
243
+ if (text === "" && observedBytes === 0) return;
244
+ capture.tryAppend(text, observedBytes);
245
+ applyRawStreamCaptureStatus(log, capture);
246
+ };
199
247
 
200
- const scheduleStreamingFinalization = (type: "streaming" | "stream-abort"): void => {
248
+ const scheduleStreamingFinalization = (
249
+ type: "streaming" | "stream-abort",
250
+ upstreamStreamFailed: boolean = false,
251
+ ): void => {
201
252
  if (finalizationScheduled) return;
202
253
  finalizationScheduled = true;
203
254
 
255
+ if (pendingDecoderBytes > 0) {
256
+ capture.recordDroppedObservation("pipeline-closed", pendingDecoderBytes);
257
+ pendingDecoderBytes = 0;
258
+ }
259
+
204
260
  const totalStreamMs = Date.now() - startTime;
205
261
  const elapsedMs = totalStreamMs;
206
262
  void capture
207
263
  .finalize()
208
264
  .then((rawStream) => {
265
+ applyRawStreamCaptureStatus(log, capture);
266
+ if (upstreamStreamFailed) applyUpstreamStreamError(log);
209
267
  switch (type) {
210
268
  case "streaming":
211
269
  scheduleLogFinalization({
@@ -218,6 +276,7 @@ function handleStreamingResponse(
218
276
  collectStreamingChunks,
219
277
  firstChunkMs,
220
278
  totalStreamMs,
279
+ terminalError: upstreamStreamFailed ? UPSTREAM_STREAM_ERROR : null,
221
280
  });
222
281
  return;
223
282
  case "stream-abort":
@@ -237,6 +296,10 @@ function handleStreamingResponse(
237
296
  })
238
297
  .catch((error: unknown) => {
239
298
  const message = error instanceof Error ? error.message : String(error);
299
+ applyRawStreamCaptureStatus(log, capture);
300
+ if (upstreamStreamFailed) applyUpstreamStreamError(log);
301
+ log.captureIncomplete = true;
302
+ log.captureIncompleteReason ??= "finalize-failed";
240
303
  logger.error(
241
304
  `[handler] Raw stream finalization failed for log #${String(log.id)}:`,
242
305
  message,
@@ -256,28 +319,48 @@ function handleStreamingResponse(
256
319
  };
257
320
 
258
321
  const transform = new TransformStream<Uint8Array, Uint8Array>({
259
- async transform(chunk, controller) {
322
+ transform(chunk, controller) {
260
323
  if (firstChunkMs === null) firstChunkMs = Date.now() - startTime;
261
324
  controller.enqueue(chunk);
262
- await capture.append(decoder.decode(chunk, { stream: true }));
325
+ pendingDecoderBytes += chunk.byteLength;
326
+ submitDecodedText(decoder.decode(chunk, { stream: true }));
263
327
  },
264
- async flush() {
265
- await capture.append(decoder.decode());
328
+ flush() {
329
+ submitDecodedText(decoder.decode(), true);
266
330
  scheduleStreamingFinalization("streaming");
267
331
  },
268
332
  });
269
333
 
270
- if (upstreamRes.body === null) {
271
- return new Response("No response body", { status: STATUS_BAD_GATEWAY });
272
- }
273
-
274
334
  // pipeTo returns a promise that rejects when either side errors. Without
275
335
  // this catch, an upstream connection drop skips flush() and leaves the log
276
336
  // permanently un-finalized with a leaked temp file.
277
337
  void upstreamRes.body.pipeTo(transform.writable).catch(() => {
278
- scheduleStreamingFinalization("streaming");
338
+ if (downstreamCancelled || req.signal?.aborted === true) {
339
+ scheduleStreamingFinalization("stream-abort");
340
+ return;
341
+ }
342
+ scheduleStreamingFinalization("streaming", true);
343
+ });
344
+ const transformedReader = transform.readable.getReader();
345
+ const loggedStream = new ReadableStream<Uint8Array>({
346
+ async pull(controller) {
347
+ try {
348
+ const next = await transformedReader.read();
349
+ if (next.done) {
350
+ controller.close();
351
+ return;
352
+ }
353
+ controller.enqueue(next.value);
354
+ } catch (error) {
355
+ controller.error(error);
356
+ }
357
+ },
358
+ async cancel(reason) {
359
+ downstreamCancelled = true;
360
+ scheduleStreamingFinalization("stream-abort");
361
+ await transformedReader.cancel(reason);
362
+ },
279
363
  });
280
- const loggedStream = transform.readable;
281
364
 
282
365
  if (req.signal === undefined) {
283
366
  logger.warn("[handler] req.signal is undefined; abort detection disabled for this request");
@@ -361,6 +444,19 @@ async function handleAdmittedProxy(req: Request): Promise<Response> {
361
444
  // Find provider config using already-extracted model (not calling extractModelFromBody again)
362
445
  const matchedProviderConfig = model !== null ? findProviderByModel(model) : null;
363
446
  const route = describeApiRoute(parsed.apiPath);
447
+
448
+ // Model matching is necessary but not sufficient: the Provider must explicitly expose the
449
+ // protocol selected by the request path. This guard stays before log creation and fetch so a
450
+ // protocol mismatch has the same zero-side-effect contract as an unconfigured model.
451
+ if (
452
+ model === null ||
453
+ matchedProviderConfig === null ||
454
+ !providerSupportsApiRoute(route, matchedProviderConfig)
455
+ ) {
456
+ logger.warn(`[handler] Unsupported provider: model=${model}`);
457
+ return new Response("Forbidden: unsupported provider", { status: STATUS_FORBIDDEN });
458
+ }
459
+
364
460
  const upstreamBase = selectUpstreamBase(route, matchedProviderConfig);
365
461
  const upstreamUrl = buildUpstreamUrl(upstreamBase, parsed.normalizedPath);
366
462
 
@@ -374,15 +470,7 @@ async function handleAdmittedProxy(req: Request): Promise<Response> {
374
470
  setUpstreamHost(upstreamHeaders, upstreamBase);
375
471
  injectProviderAuth(upstreamHeaders, matchedProviderConfig);
376
472
 
377
- const provider = model !== null ? registry.findProvider(model) : null;
378
-
379
- // Only proxy requests matching a registered provider
380
- if (model === null || provider === null) {
381
- logger.warn(`[handler] Unsupported provider: model=${model}`);
382
- return new Response("Forbidden: unsupported provider", { status: STATUS_FORBIDDEN });
383
- }
384
-
385
- // Pick the format handler from the request path. provider.format is only a
473
+ // Pick the format handler from the request path. ProviderConfig.format is only a
386
474
  // hint used as a fallback when no anthropicBaseUrl/openaiBaseUrl is set, and
387
475
  // does NOT describe what the upstream actually returns: a provider configured
388
476
  // with format="openai" may still expose an Anthropic-compatible endpoint
@@ -441,7 +529,7 @@ async function handleAdmittedProxy(req: Request): Promise<Response> {
441
529
  log.responseStatus = status;
442
530
  log.responseText = fetchResult.reason;
443
531
  appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: fetchResult.reason });
444
- finalizeLogUpdate(log);
532
+ await persistFinalizedLogUpdate(log);
445
533
  return new Response(
446
534
  fetchResult.kind === "network-policy"
447
535
  ? "Forbidden: upstream URL is not permitted"
@@ -458,14 +546,14 @@ async function handleAdmittedProxy(req: Request): Promise<Response> {
458
546
  log.responseStatus = 499; // Client Closed Request (non-standard but descriptive)
459
547
  log.responseText = "Client aborted";
460
548
  appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: "Client aborted" });
461
- finalizeLogUpdate(log);
549
+ await persistFinalizedLogUpdate(log);
462
550
  return new Response("Client aborted", { status: 499 });
463
551
  }
464
552
  logger.error(`[handler] Proxy error: ${req.method} ${parsed.apiPath}`, String(err));
465
553
  log.responseStatus = STATUS_BAD_GATEWAY;
466
554
  log.responseText = String(err);
467
555
  appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: String(err) });
468
- finalizeLogUpdate(log);
556
+ await persistFinalizedLogUpdate(log);
469
557
  return new Response(`Proxy error: ${err}`, { status: STATUS_BAD_GATEWAY });
470
558
  }
471
559
 
@@ -527,6 +615,18 @@ function responseWithRuntimeLease(response: Response, lease: RuntimeRequestLease
527
615
  export async function handleProxy(req: Request): Promise<Response> {
528
616
  const lease = acquireRuntimeRequest();
529
617
  if (lease === null) {
618
+ const admission = getRuntimeAdmissionState();
619
+ if (admission.acceptingRequests) {
620
+ return resourceLimitResponse({
621
+ error: `Proxy request capacity reached. At most ${String(admission.capacity)} active requests are allowed; retry after a response completes.`,
622
+ code: "proxy-capacity-exceeded",
623
+ status: 503,
624
+ resource: "proxy-requests",
625
+ limit: admission.capacity,
626
+ retryable: true,
627
+ retryAfterSeconds: 1,
628
+ });
629
+ }
530
630
  return Response.json(
531
631
  { error: "Agent Inspector is shutting down" },
532
632
  { status: 503, headers: { "cache-control": "no-store", "retry-after": "5" } },