@tonyclaw/agent-inspector 4.0.1 → 4.0.2

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 (373) hide show
  1. package/.output/backend/nitro.json +1 -1
  2. package/.output/cli.js +192 -109
  3. package/.output/server/_ssr/index.mjs +1 -1
  4. package/.output/server/_ssr/{router-CTHqmroT.mjs → router-BBY_zWRK.mjs} +91 -3
  5. package/.output/server/index.mjs +1 -1
  6. package/.output/ui/assets/{CompareDrawer-L3n4J-Or.js → CompareDrawer-rtNElN-B.js} +1 -1
  7. package/.output/ui/assets/{InspectorPet-D5o_3OZS.js → InspectorPet-QaAiuF3d.js} +1 -1
  8. package/.output/ui/assets/{ProxyViewerContainer-Ocagx75S.js → ProxyViewerContainer-6twNZHwl.js} +4 -4
  9. package/.output/ui/assets/{ReplayDialog-Bk7x1h-y.js → ReplayDialog-B7KlziO2.js} +1 -1
  10. package/.output/ui/assets/{RequestAnatomy-U4lzL7Sc.js → RequestAnatomy-2XVFBrW4.js} +1 -1
  11. package/.output/ui/assets/{ResponseView-LOWfvBLK.js → ResponseView-BULgzLVB.js} +1 -1
  12. package/.output/ui/assets/{StreamingChunkSequence-DPfsrrXQ.js → StreamingChunkSequence-DSkvkbns.js} +1 -1
  13. package/.output/ui/assets/{_sessionId-OdX2Jvt1.js → _sessionId-D2dmJi3G.js} +1 -1
  14. package/.output/ui/assets/{_sessionId-0MlWS-CO.js → _sessionId-DcS0s1Mr.js} +1 -1
  15. package/.output/ui/assets/{index-Du4qNOOt.js → index-B0TN1KKO.js} +2 -2
  16. package/.output/ui/assets/{index-gvFj_9K6.js → index-Bva1zHEs.js} +1 -1
  17. package/.output/ui/assets/{index-Ea-c8hPg.js → index-BzBExOxu.js} +1 -1
  18. package/.output/ui/assets/{index-zVVS53Hd.js → index-k6sRQz55.js} +1 -1
  19. package/.output/ui/assets/{json-viewer-C1WAMzg3.js → json-viewer-CWM4tY-j.js} +1 -1
  20. package/.output/ui/assets/{jszip.min-B8AKu2zm.js → jszip.min-BkWvRFlo.js} +1 -1
  21. package/.output/ui/index.html +1 -1
  22. package/package.json +5 -10
  23. package/src/assets/IDE/claudecode-color.svg +0 -1
  24. package/src/assets/IDE/codex-color.svg +0 -1
  25. package/src/assets/IDE/opencode.svg +0 -1
  26. package/src/assets/IDE/xiaomimimo.svg +0 -1
  27. package/src/assets/favicon.svg +0 -21
  28. package/src/assets/logos/alibaba.svg +0 -1
  29. package/src/assets/logos/anthropic.svg +0 -1
  30. package/src/assets/logos/claude-code.svg +0 -4
  31. package/src/assets/logos/deepseek.svg +0 -1
  32. package/src/assets/logos/minimax.jpeg +0 -0
  33. package/src/assets/logos/openai.svg +0 -1
  34. package/src/assets/logos/opencode.svg +0 -4
  35. package/src/assets/logos/qwen.webp +0 -0
  36. package/src/assets/logos/zhipuai.svg +0 -219
  37. package/src/backend/router.tsx +0 -36
  38. package/src/backend/routes/$.ts +0 -26
  39. package/src/backend/routes/__root.ts +0 -4
  40. package/src/backend/routes/api/-instances.ts +0 -7
  41. package/src/backend/routes/api/alerts.summary.ts +0 -28
  42. package/src/backend/routes/api/alerts.ts +0 -70
  43. package/src/backend/routes/api/config.paths.ts +0 -14
  44. package/src/backend/routes/api/config.ts +0 -73
  45. package/src/backend/routes/api/ecosystem.packages.$packageId.help.ts +0 -11
  46. package/src/backend/routes/api/ecosystem.packages.$packageId.install.ts +0 -11
  47. package/src/backend/routes/api/ecosystem.packages.$packageId.runner-presets.ts +0 -11
  48. package/src/backend/routes/api/ecosystem.packages.$packageId.upgrade.ts +0 -11
  49. package/src/backend/routes/api/ecosystem.packages.ts +0 -21
  50. package/src/backend/routes/api/ecosystem.recipes.$recipeId.run.ts +0 -11
  51. package/src/backend/routes/api/ecosystem.tasks.$taskId.ts +0 -16
  52. package/src/backend/routes/api/ecosystem.tasks.ts +0 -12
  53. package/src/backend/routes/api/groups.$groupId.evidence.ts +0 -55
  54. package/src/backend/routes/api/groups.$groupId.sessions.ts +0 -50
  55. package/src/backend/routes/api/groups.$groupId.ts +0 -93
  56. package/src/backend/routes/api/groups.ts +0 -79
  57. package/src/backend/routes/api/health.ts +0 -22
  58. package/src/backend/routes/api/instances.$name.connection.ts +0 -12
  59. package/src/backend/routes/api/instances.$name.restart.ts +0 -23
  60. package/src/backend/routes/api/instances.$name.start.ts +0 -18
  61. package/src/backend/routes/api/instances.$name.stop.ts +0 -23
  62. package/src/backend/routes/api/instances.$name.ts +0 -12
  63. package/src/backend/routes/api/instances.ts +0 -16
  64. package/src/backend/routes/api/knowledge.candidates.$candidateId.promote.ts +0 -32
  65. package/src/backend/routes/api/knowledge.candidates.$candidateId.ts +0 -50
  66. package/src/backend/routes/api/knowledge.candidates.ts +0 -10
  67. package/src/backend/routes/api/knowledge.project-context.ts +0 -29
  68. package/src/backend/routes/api/knowledge.search.ts +0 -62
  69. package/src/backend/routes/api/knowledge.sessions.$sessionId.candidates.ts +0 -26
  70. package/src/backend/routes/api/logs.$id.body.ts +0 -58
  71. package/src/backend/routes/api/logs.$id.chunks.ts +0 -37
  72. package/src/backend/routes/api/logs.$id.replay.ts +0 -297
  73. package/src/backend/routes/api/logs.$id.ts +0 -23
  74. package/src/backend/routes/api/logs.import.ts +0 -39
  75. package/src/backend/routes/api/logs.stream.ts +0 -174
  76. package/src/backend/routes/api/logs.ts +0 -214
  77. package/src/backend/routes/api/mcp.ts +0 -25
  78. package/src/backend/routes/api/models.ts +0 -10
  79. package/src/backend/routes/api/pi-agent.ts +0 -29
  80. package/src/backend/routes/api/providers.$providerId.model-metadata.ts +0 -173
  81. package/src/backend/routes/api/providers.$providerId.test.log.ts +0 -342
  82. package/src/backend/routes/api/providers.$providerId.ts +0 -111
  83. package/src/backend/routes/api/providers.export.ts +0 -44
  84. package/src/backend/routes/api/providers.import.ts +0 -81
  85. package/src/backend/routes/api/providers.scan.ts +0 -28
  86. package/src/backend/routes/api/providers.ts +0 -102
  87. package/src/backend/routes/api/runs.$runId.evidence.ts +0 -55
  88. package/src/backend/routes/api/runs.$runId.ts +0 -50
  89. package/src/backend/routes/api/runs.ts +0 -62
  90. package/src/backend/routes/api/runtime.ts +0 -46
  91. package/src/backend/routes/api/sessions.ts +0 -51
  92. package/src/backend/routes/api/storage.ts +0 -56
  93. package/src/backend/routes/livez.ts +0 -13
  94. package/src/backend/routes/metrics.ts +0 -14
  95. package/src/backend/routes/proxy/$.ts +0 -15
  96. package/src/backend/routes/readyz.ts +0 -18
  97. package/src/backend/start.ts +0 -10
  98. package/src/cli/alias.ts +0 -431
  99. package/src/cli/detect-tools.ts +0 -123
  100. package/src/cli/doctor.ts +0 -836
  101. package/src/cli/instance.ts +0 -1071
  102. package/src/cli/instanceApi.ts +0 -213
  103. package/src/cli/instanceArgs.ts +0 -301
  104. package/src/cli/instanceControl.ts +0 -191
  105. package/src/cli/instanceModel.ts +0 -276
  106. package/src/cli/instanceRegistry.ts +0 -731
  107. package/src/cli/networkHints.ts +0 -170
  108. package/src/cli/onboard.ts +0 -1668
  109. package/src/cli/onboardTarget.ts +0 -228
  110. package/src/cli/rustBackendPackage.ts +0 -458
  111. package/src/cli/startupOutput.ts +0 -16
  112. package/src/cli/templates/codex-skill-onboard.ts +0 -320
  113. package/src/cli/templates/command-onboard.ts +0 -25
  114. package/src/cli/templates/skill-onboard.ts +0 -625
  115. package/src/cli.ts +0 -1433
  116. package/src/components/OnboardingBanner.tsx +0 -115
  117. package/src/components/ProxyViewer.tsx +0 -2259
  118. package/src/components/ProxyViewerContainer.tsx +0 -1076
  119. package/src/components/alerts/AlertsDialog.tsx +0 -615
  120. package/src/components/clients/ClientLogo.tsx +0 -175
  121. package/src/components/ecosystem/AgentLabDialog.tsx +0 -947
  122. package/src/components/errors/SafeErrorBoundary.tsx +0 -201
  123. package/src/components/groups/GroupsDialog.tsx +0 -1304
  124. package/src/components/inspector-pet/InspectorPet.tsx +0 -398
  125. package/src/components/inspector-pet/InspectorPetCrab3D.tsx +0 -455
  126. package/src/components/inspector-pet/inspectorPetLogic.ts +0 -272
  127. package/src/components/pi-agent/PiAgentPanel.tsx +0 -446
  128. package/src/components/pi-agent/piAgentChatLogic.ts +0 -273
  129. package/src/components/providers/ImportWizardDialog.tsx +0 -281
  130. package/src/components/providers/ProviderCard.tsx +0 -347
  131. package/src/components/providers/ProviderForm.tsx +0 -1183
  132. package/src/components/providers/ProviderLogo.tsx +0 -122
  133. package/src/components/providers/ProviderTestResultsView.tsx +0 -734
  134. package/src/components/providers/ProvidersPanel.tsx +0 -714
  135. package/src/components/providers/SettingsDialog.tsx +0 -1042
  136. package/src/components/providers/providerFormCapabilities.ts +0 -36
  137. package/src/components/proxy-viewer/AgentTraceSummary.tsx +0 -902
  138. package/src/components/proxy-viewer/AnswerMarkdown.tsx +0 -51
  139. package/src/components/proxy-viewer/ApplicationBar.tsx +0 -123
  140. package/src/components/proxy-viewer/CompareDrawer.tsx +0 -984
  141. package/src/components/proxy-viewer/ConversationGroup.tsx +0 -208
  142. package/src/components/proxy-viewer/ConversationGroupList.tsx +0 -652
  143. package/src/components/proxy-viewer/ConversationHeader.tsx +0 -429
  144. package/src/components/proxy-viewer/LiveConnectionStatus.tsx +0 -186
  145. package/src/components/proxy-viewer/LogEntry.tsx +0 -1419
  146. package/src/components/proxy-viewer/LogEntryHeader.tsx +0 -596
  147. package/src/components/proxy-viewer/ProviderLogoStack.tsx +0 -57
  148. package/src/components/proxy-viewer/RawExportWarning.tsx +0 -77
  149. package/src/components/proxy-viewer/ReplayDialog.tsx +0 -636
  150. package/src/components/proxy-viewer/RequestToolsPanel.tsx +0 -299
  151. package/src/components/proxy-viewer/ResponseView.tsx +0 -202
  152. package/src/components/proxy-viewer/SessionLoadProgressBar.tsx +0 -61
  153. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +0 -265
  154. package/src/components/proxy-viewer/ThreadConnector.tsx +0 -165
  155. package/src/components/proxy-viewer/ToolTraceEvents.tsx +0 -64
  156. package/src/components/proxy-viewer/TurnGroup.tsx +0 -383
  157. package/src/components/proxy-viewer/TurnGroupList.tsx +0 -156
  158. package/src/components/proxy-viewer/accessibility.ts +0 -8
  159. package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +0 -887
  160. package/src/components/proxy-viewer/anatomy/SegmentBar.tsx +0 -222
  161. package/src/components/proxy-viewer/anatomy/contextIntelligence.ts +0 -737
  162. package/src/components/proxy-viewer/anatomy/sessionContextSummary.ts +0 -268
  163. package/src/components/proxy-viewer/anatomy/tokenEstimate.ts +0 -53
  164. package/src/components/proxy-viewer/anatomy/types.ts +0 -41
  165. package/src/components/proxy-viewer/anatomy/useAnatomyJump.ts +0 -114
  166. package/src/components/proxy-viewer/bodyHydration.ts +0 -123
  167. package/src/components/proxy-viewer/cacheTrend.ts +0 -50
  168. package/src/components/proxy-viewer/diff/DiffView.tsx +0 -321
  169. package/src/components/proxy-viewer/diff/computeDiff.ts +0 -178
  170. package/src/components/proxy-viewer/diff/index.ts +0 -3
  171. package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +0 -194
  172. package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +0 -81
  173. package/src/components/proxy-viewer/formats/anthropic/thinkingExtract.ts +0 -21
  174. package/src/components/proxy-viewer/formats/index.tsx +0 -47
  175. package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +0 -442
  176. package/src/components/proxy-viewer/index.ts +0 -9
  177. package/src/components/proxy-viewer/lazy.ts +0 -41
  178. package/src/components/proxy-viewer/liveConnectionState.ts +0 -158
  179. package/src/components/proxy-viewer/log-formats/anthropic.ts +0 -191
  180. package/src/components/proxy-viewer/log-formats/index.ts +0 -23
  181. package/src/components/proxy-viewer/log-formats/openai.ts +0 -256
  182. package/src/components/proxy-viewer/log-formats/types.ts +0 -40
  183. package/src/components/proxy-viewer/log-formats/unknown.ts +0 -18
  184. package/src/components/proxy-viewer/logEntryVisibility.ts +0 -73
  185. package/src/components/proxy-viewer/logFocus.ts +0 -54
  186. package/src/components/proxy-viewer/proxyViewerContainerLogic.ts +0 -124
  187. package/src/components/proxy-viewer/proxyViewerLogic.ts +0 -488
  188. package/src/components/proxy-viewer/replayComparison.ts +0 -131
  189. package/src/components/proxy-viewer/requestDiff.ts +0 -277
  190. package/src/components/proxy-viewer/requestTools.ts +0 -220
  191. package/src/components/proxy-viewer/useCopyFeedback.ts +0 -38
  192. package/src/components/proxy-viewer/useKeyboardNavigation.ts +0 -232
  193. package/src/components/proxy-viewer/viewerState.ts +0 -176
  194. package/src/components/ui/badge.tsx +0 -47
  195. package/src/components/ui/button.tsx +0 -47
  196. package/src/components/ui/collapsible.tsx +0 -21
  197. package/src/components/ui/confirm-dialog.tsx +0 -51
  198. package/src/components/ui/crab-logo.tsx +0 -45
  199. package/src/components/ui/crab-variants.tsx +0 -467
  200. package/src/components/ui/dialog.tsx +0 -130
  201. package/src/components/ui/icon-trigger.ts +0 -2
  202. package/src/components/ui/json-viewer-bulk.ts +0 -132
  203. package/src/components/ui/json-viewer.tsx +0 -508
  204. package/src/components/ui/scroll-area.tsx +0 -54
  205. package/src/components/ui/select.tsx +0 -178
  206. package/src/components/ui/separator.tsx +0 -28
  207. package/src/components/ui/tabs.tsx +0 -88
  208. package/src/components/ui/tooltip.tsx +0 -51
  209. package/src/components/ui/transient-toast.tsx +0 -105
  210. package/src/contracts/anthropic.ts +0 -230
  211. package/src/contracts/index.ts +0 -59
  212. package/src/contracts/json.ts +0 -37
  213. package/src/contracts/log.ts +0 -139
  214. package/src/contracts/openai.ts +0 -267
  215. package/src/index.css +0 -11
  216. package/src/knowledge/candidateStore.ts +0 -94
  217. package/src/knowledge/distiller.ts +0 -98
  218. package/src/knowledge/openclawClient.ts +0 -161
  219. package/src/knowledge/openclawGatewayClient.ts +0 -249
  220. package/src/knowledge/openclawMarkdown.ts +0 -146
  221. package/src/knowledge/redactor.ts +0 -80
  222. package/src/knowledge/types.ts +0 -83
  223. package/src/lib/alertContract.ts +0 -76
  224. package/src/lib/apiClient.ts +0 -166
  225. package/src/lib/apiFormat.ts +0 -29
  226. package/src/lib/backendImplementation.ts +0 -16
  227. package/src/lib/basePath.ts +0 -107
  228. package/src/lib/browserRuntimeContract.ts +0 -124
  229. package/src/lib/browserRuntimeResolver.ts +0 -333
  230. package/src/lib/chunkLoadRecovery.ts +0 -83
  231. package/src/lib/claudeCodeStrip.ts +0 -98
  232. package/src/lib/clipboard.ts +0 -67
  233. package/src/lib/codingAgentEndpoints.ts +0 -54
  234. package/src/lib/ecosystemContract.ts +0 -130
  235. package/src/lib/export-logs.ts +0 -287
  236. package/src/lib/groupContract.ts +0 -166
  237. package/src/lib/instanceContract.ts +0 -239
  238. package/src/lib/jsonFile.ts +0 -46
  239. package/src/lib/logImportContract.ts +0 -12
  240. package/src/lib/managedInstance.ts +0 -112
  241. package/src/lib/mask.ts +0 -4
  242. package/src/lib/objectUtils.ts +0 -22
  243. package/src/lib/piAgentContract.ts +0 -102
  244. package/src/lib/providerContract.ts +0 -125
  245. package/src/lib/providerImportContract.ts +0 -27
  246. package/src/lib/providerModelMetadata.ts +0 -458
  247. package/src/lib/providerTestContract.ts +0 -170
  248. package/src/lib/providerTestPrompt.ts +0 -100
  249. package/src/lib/publicBasePathContext.ts +0 -137
  250. package/src/lib/resourceLimits.ts +0 -345
  251. package/src/lib/runContract.ts +0 -164
  252. package/src/lib/runtimeConfig.ts +0 -43
  253. package/src/lib/safeDiagnostic.ts +0 -38
  254. package/src/lib/safeFetch.ts +0 -508
  255. package/src/lib/serverPort.ts +0 -47
  256. package/src/lib/sessionInfoContract.ts +0 -78
  257. package/src/lib/sessionUrl.ts +0 -46
  258. package/src/lib/ssrfGuard.ts +0 -133
  259. package/src/lib/stopReason.ts +0 -130
  260. package/src/lib/themeMode.ts +0 -41
  261. package/src/lib/timeDisplay.ts +0 -22
  262. package/src/lib/toolTrace.ts +0 -290
  263. package/src/lib/uiScale.ts +0 -117
  264. package/src/lib/unknown.ts +0 -27
  265. package/src/lib/upstreamUrl.ts +0 -150
  266. package/src/lib/useAlerts.ts +0 -82
  267. package/src/lib/useGroupEvidence.ts +0 -37
  268. package/src/lib/useGroups.ts +0 -32
  269. package/src/lib/useOnboarding.ts +0 -93
  270. package/src/lib/useProviders.ts +0 -30
  271. package/src/lib/useStripConfig.ts +0 -154
  272. package/src/lib/useThemeMode.ts +0 -40
  273. package/src/lib/useUiScale.ts +0 -52
  274. package/src/lib/utils.ts +0 -78
  275. package/src/mcp/currentContext.ts +0 -65
  276. package/src/mcp/instanceHandlers.ts +0 -151
  277. package/src/mcp/loopback.ts +0 -88
  278. package/src/mcp/mode.ts +0 -75
  279. package/src/mcp/previewExtractor.ts +0 -233
  280. package/src/mcp/server.ts +0 -2274
  281. package/src/mcp/toolHandlers.ts +0 -1533
  282. package/src/proxy/alerts.ts +0 -664
  283. package/src/proxy/chunkStorage.ts +0 -123
  284. package/src/proxy/config.ts +0 -218
  285. package/src/proxy/constants.ts +0 -66
  286. package/src/proxy/dataDir.ts +0 -147
  287. package/src/proxy/ecosystemExecutionConfirmation.ts +0 -114
  288. package/src/proxy/ecosystemExecutionRoute.ts +0 -116
  289. package/src/proxy/ecosystemRegistry.ts +0 -296
  290. package/src/proxy/ecosystemTasks.ts +0 -415
  291. package/src/proxy/evidenceAnalysis.ts +0 -526
  292. package/src/proxy/evidenceExporter.ts +0 -235
  293. package/src/proxy/formats/anthropic/anthropicProvider.ts +0 -75
  294. package/src/proxy/formats/anthropic/handler.ts +0 -75
  295. package/src/proxy/formats/anthropic/index.ts +0 -13
  296. package/src/proxy/formats/anthropic/register.ts +0 -4
  297. package/src/proxy/formats/anthropic/schemas.ts +0 -6
  298. package/src/proxy/formats/anthropic/stream.ts +0 -205
  299. package/src/proxy/formats/handler.ts +0 -46
  300. package/src/proxy/formats/index.ts +0 -13
  301. package/src/proxy/formats/jsonSchema.ts +0 -2
  302. package/src/proxy/formats/openai/alibabaProvider.ts +0 -34
  303. package/src/proxy/formats/openai/codexMetadata.ts +0 -61
  304. package/src/proxy/formats/openai/handler.ts +0 -133
  305. package/src/proxy/formats/openai/index.ts +0 -33
  306. package/src/proxy/formats/openai/provider.ts +0 -50
  307. package/src/proxy/formats/openai/register.ts +0 -4
  308. package/src/proxy/formats/openai/schemas.ts +0 -25
  309. package/src/proxy/formats/openai/stream.ts +0 -600
  310. package/src/proxy/formats/openai/zhipuProvider.ts +0 -31
  311. package/src/proxy/formats/providerAdapter.ts +0 -50
  312. package/src/proxy/formats/providerRegistry.ts +0 -51
  313. package/src/proxy/formats/providers/index.ts +0 -3
  314. package/src/proxy/formats/registry.ts +0 -65
  315. package/src/proxy/groupEvidenceExporter.ts +0 -371
  316. package/src/proxy/groupStore.ts +0 -336
  317. package/src/proxy/handler.ts +0 -729
  318. package/src/proxy/identityProxy.ts +0 -1842
  319. package/src/proxy/jsonlRecovery.ts +0 -554
  320. package/src/proxy/logBodyChunks.ts +0 -91
  321. package/src/proxy/logFinalizer.ts +0 -777
  322. package/src/proxy/logFinalizer.worker.ts +0 -24
  323. package/src/proxy/logImportUpload.ts +0 -128
  324. package/src/proxy/logImporter.ts +0 -813
  325. package/src/proxy/logIndex.ts +0 -663
  326. package/src/proxy/logSearch.ts +0 -122
  327. package/src/proxy/logger.ts +0 -537
  328. package/src/proxy/openaiOrphanToolStrip.ts +0 -142
  329. package/src/proxy/platformCommands.ts +0 -258
  330. package/src/proxy/privateDataPath.ts +0 -419
  331. package/src/proxy/providerImporters.ts +0 -578
  332. package/src/proxy/providerScanStore.ts +0 -87
  333. package/src/proxy/providerSecretStore.ts +0 -216
  334. package/src/proxy/providers.ts +0 -998
  335. package/src/proxy/rawStreamCapture.ts +0 -342
  336. package/src/proxy/runFailures.ts +0 -94
  337. package/src/proxy/runStore.ts +0 -214
  338. package/src/proxy/runtimeAdmission.ts +0 -55
  339. package/src/proxy/runtimeHealth.ts +0 -221
  340. package/src/proxy/runtimeMetrics.ts +0 -149
  341. package/src/proxy/runtimeShutdown.ts +0 -157
  342. package/src/proxy/schemas.ts +0 -112
  343. package/src/proxy/sessionArchive.ts +0 -479
  344. package/src/proxy/sessionInfo.ts +0 -266
  345. package/src/proxy/sessionProcess.ts +0 -194
  346. package/src/proxy/sessionRuntime.ts +0 -121
  347. package/src/proxy/sessionSupervisor.ts +0 -305
  348. package/src/proxy/sessionWorkerEntry.ts +0 -26
  349. package/src/proxy/shutdownCoordinator.ts +0 -90
  350. package/src/proxy/socketTracker.ts +0 -639
  351. package/src/proxy/sqliteLogIndex.ts +0 -733
  352. package/src/proxy/storageLifecycle.ts +0 -432
  353. package/src/proxy/store.ts +0 -1773
  354. package/src/proxy/streamTiming.ts +0 -31
  355. package/src/proxy/toolSchemaWarnings.ts +0 -529
  356. package/src/proxy/uiAssetServer.ts +0 -106
  357. package/src/proxy/upstream.ts +0 -127
  358. package/src/services/alerts.ts +0 -12
  359. package/src/services/groups.ts +0 -119
  360. package/src/services/piAgent.ts +0 -421
  361. package/src/services/piAgentRetrieval.ts +0 -1180
  362. package/src/services/result.ts +0 -17
  363. package/src/services/runs.ts +0 -76
  364. package/src/ui/bootstrap.ts +0 -167
  365. package/src/ui/index.html +0 -38
  366. package/src/ui/logsStreamClient.ts +0 -20
  367. package/src/ui/main.tsx +0 -13
  368. package/src/ui/public/runtime-config.js +0 -2
  369. package/src/ui/router.tsx +0 -13
  370. package/src/ui/routes/__root.tsx +0 -69
  371. package/src/ui/routes/index.tsx +0 -35
  372. package/src/ui/routes/session/$sessionId.tsx +0 -50
  373. package/src/ui/runtimeDiscovery.ts +0 -50
@@ -1,1533 +0,0 @@
1
- /**
2
- * Tool handler implementations for the MCP server. These are split out from
3
- * `server.ts` so the wire-level MCP registration stays thin.
4
- *
5
- * Log/provider/knowledge handlers still use an injected `callApi` while those
6
- * domains are being migrated. Run/group/evidence handlers call the shared
7
- * service layer directly so MCP and REST API are peers instead of MCP looping
8
- * back through `/api/*`.
9
- *
10
- * Each impl returns a `CallToolResult`-shaped object: either
11
- * `{ content: [{ type: "text", text: string }] }` on success or
12
- * `{ content: [...], isError: true }` on failure. The wrapping
13
- * `safeCall` in `server.ts` only catches *thrown* errors (loopback
14
- * timeouts, etc); semantic errors (404 from /api) are returned as
15
- * `isError: true` results from inside the impl.
16
- */
17
-
18
- import { Buffer } from "node:buffer";
19
- import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
20
- import { dirname, join } from "node:path";
21
- import JSZip from "jszip";
22
- import { z } from "zod";
23
- import { getPublicPort } from "../lib/publicBasePathContext";
24
- import { LoopbackTimeoutError, type CallApiOptions } from "./loopback";
25
- import { extractLastUserMessagePreview, extractResponsePreview } from "./previewExtractor";
26
- import {
27
- getCurrentContext,
28
- setCurrentContext,
29
- type InspectorCurrentContextPatch,
30
- } from "./currentContext";
31
- import {
32
- type AddInspectorGroupSessionInput,
33
- type CreateInspectorGroupInput,
34
- type DeleteInspectorGroupOptions,
35
- type UpdateInspectorGroupInput,
36
- } from "../lib/groupContract";
37
- import { type CreateInspectorRunInput, type UpdateInspectorRunInput } from "../lib/runContract";
38
- import { SessionInfoSchema } from "../lib/sessionInfoContract";
39
- import {
40
- ProviderConfigSchema,
41
- toRedactedProviderDto,
42
- toRedactedProviderDtos,
43
- } from "../lib/providerContract";
44
- import { isRecord, textByteLength } from "../lib/unknown";
45
- import { CapturedLogSchema, type CapturedLog } from "../contracts";
46
- import {
47
- createResourceLimitError,
48
- MAX_PROVIDER_MODELS,
49
- MAX_SEARCH_PROJECT_CHARS,
50
- MAX_SEARCH_QUERY_CHARS,
51
- type ResourceLimitErrorInput,
52
- } from "../lib/resourceLimits";
53
- import {
54
- addInspectorGroupSession,
55
- createInspectorGroup,
56
- deleteInspectorGroup,
57
- exportInspectorGroupEvidence,
58
- getInspectorGroup,
59
- listInspectorGroups,
60
- readInspectorGroupEvidence,
61
- updateInspectorGroup,
62
- } from "../services/groups";
63
- import {
64
- createInspectorRun,
65
- exportInspectorRunEvidence,
66
- getInspectorRun,
67
- listInspectorRecentFailures,
68
- readInspectorRunEvidence,
69
- updateInspectorRun,
70
- } from "../services/runs";
71
-
72
- /** Minimal callApi contract: same shape as the real `callApi` in loopback.ts. */
73
- export type CallApiFn = (path: string, options?: CallApiOptions) => Promise<Response>;
74
-
75
- export type ToolResult = {
76
- content: { type: "text"; text: string }[];
77
- isError?: true;
78
- };
79
-
80
- // Loose shape returned by `GET /api/logs`: `logs: CapturedLog[]` and a few
81
- // pagination fields. The CapturedLog items are fully validated downstream.
82
- const LogsListResponseSchema = z.object({
83
- logs: z.array(CapturedLogSchema).optional(),
84
- total: z.number().optional(),
85
- offset: z.number().optional(),
86
- limit: z.number().optional(),
87
- });
88
-
89
- const SearchLogsResponseSchema = z.object({
90
- query: z.string(),
91
- logs: z.array(CapturedLogSchema),
92
- total: z.number(),
93
- offset: z.number(),
94
- limit: z.number(),
95
- scanned: z.number().optional(),
96
- });
97
-
98
- const PAGINATION_DEFAULTS = { offset: 0, limit: 3 };
99
- export const LIMIT_HARD_CAP = 5;
100
- const LOG_DETAIL_TEXT_LIMIT = 200_000;
101
- const PROCESS_LOG_LIMIT_DEFAULT = 50;
102
- const PROCESS_LOG_LIMIT_MAX = 200;
103
-
104
- export function clampLimit(input: number | undefined): number {
105
- if (input === undefined) return PAGINATION_DEFAULTS.limit;
106
- if (!Number.isFinite(input) || input < 1) return 1;
107
- return Math.min(Math.floor(input), LIMIT_HARD_CAP);
108
- }
109
-
110
- function clampProcessLogLimit(input: number | undefined): number {
111
- if (input === undefined) return PROCESS_LOG_LIMIT_DEFAULT;
112
- if (!Number.isFinite(input) || input < 1) return 1;
113
- return Math.min(Math.floor(input), PROCESS_LOG_LIMIT_MAX);
114
- }
115
-
116
- function textJson(data: unknown): ToolResult {
117
- return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
118
- }
119
-
120
- function toolError(message: string): ToolResult {
121
- return { content: [{ type: "text", text: message }], isError: true };
122
- }
123
-
124
- function resourceLimitToolError(input: ResourceLimitErrorInput): ToolResult {
125
- return toolError(JSON.stringify(createResourceLimitError(input), null, 2));
126
- }
127
-
128
- function redactedProviderResult(value: unknown): ToolResult {
129
- const parsed = ProviderConfigSchema.safeParse(value);
130
- if (!parsed.success) return toolError("Provider API returned an invalid provider response");
131
- return textJson(toRedactedProviderDto(parsed.data));
132
- }
133
-
134
- function redactedProvidersResult(value: unknown): ToolResult {
135
- const parsed = ProviderConfigSchema.array().safeParse(value);
136
- if (!parsed.success) return toolError("Provider API returned an invalid providers response");
137
- return textJson(toRedactedProviderDtos(parsed.data));
138
- }
139
-
140
- function inspectorBaseUrl(): string {
141
- return `http://127.0.0.1:${String(getPublicPort())}`;
142
- }
143
-
144
- function hasBodyText(value: string | null, byteLength: number | null | undefined): boolean {
145
- if (value !== null && value.length > 0) return true;
146
- return byteLength !== undefined && byteLength !== null && byteLength > 0;
147
- }
148
-
149
- function truncateToolText(value: string | null): {
150
- value: string | null;
151
- byteLength: number | null;
152
- truncated: boolean;
153
- } {
154
- if (value === null) return { value: null, byteLength: null, truncated: false };
155
- const byteLength = textByteLength(value);
156
- if (value.length <= LOG_DETAIL_TEXT_LIMIT) {
157
- return { value, byteLength, truncated: false };
158
- }
159
- const suffix = `\n[truncated by agent-inspector MCP: original ${byteLength ?? 0} bytes]`;
160
- return {
161
- value: `${value.slice(0, LOG_DETAIL_TEXT_LIMIT)}${suffix}`,
162
- byteLength,
163
- truncated: true,
164
- };
165
- }
166
-
167
- function buildBoundedLogDetail(log: CapturedLog): Record<string, unknown> {
168
- const { streamingChunks: _chunks, rawRequestBody, responseText, ...rest } = log;
169
- const boundedRequest = truncateToolText(rawRequestBody);
170
- const boundedResponse = truncateToolText(responseText);
171
- return {
172
- ...rest,
173
- rawRequestBody: boundedRequest.value,
174
- responseText: boundedResponse.value,
175
- rawRequestBodyBytes: boundedRequest.byteLength,
176
- responseTextBytes: boundedResponse.byteLength,
177
- rawRequestBodyTruncated: boundedRequest.truncated,
178
- responseTextTruncated: boundedResponse.truncated,
179
- };
180
- }
181
-
182
- function logHasError(log: CapturedLog): boolean {
183
- return (
184
- (log.error !== null && log.error !== undefined && log.error.length > 0) ||
185
- (log.responseStatus !== null && log.responseStatus !== undefined && log.responseStatus >= 400)
186
- );
187
- }
188
-
189
- export function buildLogSummary(log: CapturedLog) {
190
- const hasError = logHasError(log);
191
- const rawRequestBodyBytes = log.rawRequestBodyBytes ?? textByteLength(log.rawRequestBody);
192
- const responseTextBytes = log.responseTextBytes ?? textByteLength(log.responseText);
193
- return {
194
- id: log.id,
195
- timestamp: log.timestamp,
196
- provider: log.providerName ?? null,
197
- model: log.model,
198
- apiFormat: log.apiFormat,
199
- method: log.method,
200
- path: log.path,
201
- status: log.responseStatus,
202
- isStreaming: log.streaming,
203
- hasError,
204
- latencyMs: log.elapsedMs,
205
- firstChunkMs: log.firstChunkMs ?? null,
206
- totalStreamMs: log.totalStreamMs ?? null,
207
- tokensPerSecond: log.tokensPerSecond ?? null,
208
- tokens: {
209
- input: log.inputTokens,
210
- output: log.outputTokens,
211
- cacheCreate: log.cacheCreationInputTokens,
212
- cacheRead: log.cacheReadInputTokens,
213
- },
214
- sessionId: log.sessionId,
215
- userAgent: log.userAgent ?? null,
216
- clientPid: log.clientPid ?? null,
217
- clientCwd: log.clientCwd ?? null,
218
- clientProjectFolder: log.clientProjectFolder ?? null,
219
- lastUserMessagePreview: extractLastUserMessagePreview(log),
220
- responsePreview: extractResponsePreview(log),
221
- hasChunks:
222
- (log.streamingChunks !== undefined && log.streamingChunks !== null) ||
223
- (log.streamingChunksPath !== null && log.streamingChunksPath !== undefined),
224
- hasRawRequestBody: hasBodyText(log.rawRequestBody, rawRequestBodyBytes),
225
- rawRequestBodyBytes,
226
- responseTextBytes,
227
- };
228
- }
229
-
230
- function sortedUniqueStrings(values: Array<string | null | undefined>): string[] {
231
- return [
232
- ...new Set(
233
- values.filter(
234
- (value): value is string => value !== null && value !== undefined && value !== "",
235
- ),
236
- ),
237
- ].sort((left, right) => left.localeCompare(right));
238
- }
239
-
240
- function sortedUniqueNumbers(values: Array<number | null | undefined>): number[] {
241
- return [
242
- ...new Set(
243
- values.filter(
244
- (value): value is number => value !== null && value !== undefined && Number.isFinite(value),
245
- ),
246
- ),
247
- ].sort((left, right) => left - right);
248
- }
249
-
250
- function inferAgentFromUserAgent(userAgent: string | null | undefined): string | null {
251
- if (userAgent === null || userAgent === undefined) return null;
252
- const lower = userAgent.toLowerCase();
253
- if (lower.includes("codex")) return "Codex";
254
- if (lower.includes("opencode")) return "OpenCode";
255
- if (lower.includes("claude")) return "Claude Code";
256
- if (lower.includes("mimocode")) return "MiMoCode";
257
- if (lower.includes("ai-sdk")) return "AI SDK";
258
- return null;
259
- }
260
-
261
- function processSummary(
262
- clientPid: number,
263
- logs: readonly CapturedLog[],
264
- total: number,
265
- limit: number,
266
- ): Record<string, unknown> {
267
- const sortedLogs = [...logs].sort((left, right) => left.id - right.id);
268
- const first = sortedLogs[0];
269
- const last = sortedLogs[sortedLogs.length - 1];
270
- const userAgents = sortedUniqueStrings(sortedLogs.map((log) => log.userAgent));
271
- const agents = sortedUniqueStrings(userAgents.map(inferAgentFromUserAgent));
272
- const sessions = sortedUniqueStrings(sortedLogs.map((log) => log.sessionId));
273
- return {
274
- clientPid,
275
- total,
276
- limit,
277
- logCount: sortedLogs.length,
278
- sessionCount: sessions.length,
279
- sessions,
280
- agents,
281
- userAgents,
282
- clientPorts: sortedUniqueNumbers(sortedLogs.map((log) => log.clientPort)),
283
- clientCwds: sortedUniqueStrings(sortedLogs.map((log) => log.clientCwd)),
284
- clientProjectFolders: sortedUniqueStrings(sortedLogs.map((log) => log.clientProjectFolder)),
285
- models: sortedUniqueStrings(sortedLogs.map((log) => log.model)),
286
- providers: sortedUniqueStrings(sortedLogs.map((log) => log.providerName)),
287
- errorCount: sortedLogs.filter(logHasError).length,
288
- startedAt: first?.timestamp ?? null,
289
- updatedAt: last?.timestamp ?? null,
290
- latestLogs: sortedLogs.slice(-Math.min(10, sortedLogs.length)).reverse().map(buildLogSummary),
291
- };
292
- }
293
-
294
- async function readCompactLogsPage(
295
- callApi: CallApiFn,
296
- params: URLSearchParams,
297
- ): Promise<{ logs: CapturedLog[]; total: number } | ToolResult> {
298
- const path = `/api/logs?${params.toString()}`;
299
- const parsed = await fetchApiJson(callApi, path, LogsListResponseSchema);
300
- if (!parsed.ok) return parsed.error;
301
- const logs = parsed.data.logs ?? [];
302
- return { logs, total: parsed.data.total ?? logs.length };
303
- }
304
-
305
- /**
306
- * Fetches a JSON endpoint and validates it against a Zod schema in one call.
307
- * On HTTP error or schema mismatch returns a `ToolResult` error so the caller
308
- * can propagate it directly. On success returns the typed parsed value.
309
- */
310
- type FetchApiResult<T> = { ok: true; data: T } | { ok: false; error: ToolResult };
311
-
312
- async function fetchApiJson<T>(
313
- callApi: CallApiFn,
314
- path: string,
315
- schema: z.ZodType<T>,
316
- init?: RequestInit,
317
- ): Promise<FetchApiResult<T>> {
318
- const res = await callApi(path, init);
319
- if (!res.ok) {
320
- return { ok: false, error: toolError(`${methodFor(init)} ${path} returned ${res.status}`) };
321
- }
322
- const rawBody: unknown = await res.json();
323
- const parsed = schema.safeParse(rawBody);
324
- if (!parsed.success) {
325
- return {
326
- ok: false,
327
- error: toolError(`${methodFor(init)} ${path} returned an unparseable shape`),
328
- };
329
- }
330
- return { ok: true, data: parsed.data };
331
- }
332
-
333
- function methodFor(init: RequestInit | undefined): string {
334
- return (init?.method ?? "GET").toUpperCase();
335
- }
336
-
337
- function isToolResult(value: unknown): value is ToolResult {
338
- return (
339
- typeof value === "object" &&
340
- value !== null &&
341
- Object.getOwnPropertyDescriptor(value, "content") !== undefined
342
- );
343
- }
344
-
345
- // ===========================================================================
346
- // Per-tool impls
347
- // ===========================================================================
348
-
349
- export type ListLogsArgs = {
350
- offset?: number;
351
- limit?: number;
352
- sessionId?: string;
353
- model?: string;
354
- clientPid?: number;
355
- };
356
-
357
- export async function listLogsImpl(callApi: CallApiFn, args: ListLogsArgs): Promise<ToolResult> {
358
- if (
359
- args.limit !== undefined &&
360
- (!Number.isSafeInteger(args.limit) || args.limit < 1 || args.limit > LIMIT_HARD_CAP)
361
- ) {
362
- return resourceLimitToolError({
363
- error: `MCP log pages may contain at most ${String(LIMIT_HARD_CAP)} entries.`,
364
- code: "invalid-page-request",
365
- status: 400,
366
- resource: "mcp-log-page-size",
367
- limit: LIMIT_HARD_CAP,
368
- retryable: false,
369
- });
370
- }
371
- const offset = args.offset ?? PAGINATION_DEFAULTS.offset;
372
- const limit = clampLimit(args.limit);
373
- const params = new URLSearchParams({
374
- offset: String(offset),
375
- limit: String(limit),
376
- compact: "1",
377
- });
378
- if (args.sessionId !== undefined && args.sessionId.length > 0) {
379
- params.set("sessionId", args.sessionId);
380
- }
381
- if (args.model !== undefined && args.model.length > 0) {
382
- params.set("model", args.model);
383
- }
384
- if (args.clientPid !== undefined) {
385
- params.set("clientPid", String(args.clientPid));
386
- }
387
- const res = await callApi(`/api/logs?${params.toString()}`);
388
- if (!res.ok) return toolError(`GET /api/logs returned ${res.status}`);
389
- const rawBody: unknown = await res.json();
390
- const parsed = LogsListResponseSchema.safeParse(rawBody);
391
- if (!parsed.success) return toolError("GET /api/logs returned an unparseable shape");
392
- const logs = parsed.data.logs ?? [];
393
- return textJson(logs.map(buildLogSummary));
394
- }
395
-
396
- export type SearchLogsArgs = {
397
- query?: string;
398
- offset?: number;
399
- limit?: number;
400
- scanLimit?: number;
401
- sessionId?: string;
402
- model?: string;
403
- clientPid?: number;
404
- };
405
-
406
- export async function searchLogsImpl(
407
- callApi: CallApiFn,
408
- args: SearchLogsArgs,
409
- ): Promise<ToolResult> {
410
- if ((args.query ?? "").length > MAX_SEARCH_QUERY_CHARS) {
411
- return resourceLimitToolError({
412
- error: `MCP search queries may contain at most ${String(MAX_SEARCH_QUERY_CHARS)} characters.`,
413
- code: "search-limit-exceeded",
414
- status: 400,
415
- resource: "mcp-log-search-query",
416
- limit: MAX_SEARCH_QUERY_CHARS,
417
- retryable: false,
418
- });
419
- }
420
- if (
421
- args.limit !== undefined &&
422
- (!Number.isSafeInteger(args.limit) || args.limit < 1 || args.limit > LIMIT_HARD_CAP)
423
- ) {
424
- return resourceLimitToolError({
425
- error: `MCP search pages may contain at most ${String(LIMIT_HARD_CAP)} entries.`,
426
- code: "invalid-page-request",
427
- status: 400,
428
- resource: "mcp-log-search-page-size",
429
- limit: LIMIT_HARD_CAP,
430
- retryable: false,
431
- });
432
- }
433
- if (
434
- args.scanLimit !== undefined &&
435
- (!Number.isSafeInteger(args.scanLimit) || args.scanLimit < 1 || args.scanLimit > 1_000)
436
- ) {
437
- return resourceLimitToolError({
438
- error: "MCP log searches may scan at most 1000 entries.",
439
- code: "search-limit-exceeded",
440
- status: 400,
441
- resource: "mcp-log-search-scan-size",
442
- limit: 1_000,
443
- retryable: false,
444
- });
445
- }
446
- const offset = args.offset ?? PAGINATION_DEFAULTS.offset;
447
- const limit = clampLimit(args.limit);
448
- const params = new URLSearchParams({
449
- q: args.query ?? "",
450
- offset: String(offset),
451
- limit: String(limit),
452
- });
453
- if (args.scanLimit !== undefined) {
454
- params.set("scanLimit", String(args.scanLimit));
455
- }
456
- if (args.sessionId !== undefined && args.sessionId.length > 0) {
457
- params.set("sessionId", args.sessionId);
458
- }
459
- if (args.model !== undefined && args.model.length > 0) {
460
- params.set("model", args.model);
461
- }
462
- if (args.clientPid !== undefined) {
463
- params.set("clientPid", String(args.clientPid));
464
- }
465
-
466
- const res = await callApi(`/api/logs?${params.toString()}`);
467
- if (!res.ok) return toolError(`GET /api/logs search returned ${res.status}`);
468
- const rawBody: unknown = await res.json();
469
- const parsed = SearchLogsResponseSchema.safeParse(rawBody);
470
- if (!parsed.success) return toolError("GET /api/logs search returned an unparseable shape");
471
- return textJson({
472
- ...parsed.data,
473
- logs: parsed.data.logs.map(buildLogSummary),
474
- });
475
- }
476
-
477
- export async function getLogImpl(callApi: CallApiFn, id: number): Promise<ToolResult> {
478
- const parsed = await fetchApiJson(callApi, `/api/logs/${id}`, CapturedLogSchema);
479
- if (!parsed.ok) return parsed.error;
480
- return textJson(buildBoundedLogDetail(parsed.data));
481
- }
482
-
483
- export async function getLogChunksImpl(callApi: CallApiFn, id: number): Promise<ToolResult> {
484
- const res = await callApi(`/api/logs/${id}/chunks`);
485
- if (!res.ok) return toolError(`GET /api/logs/${id}/chunks returned ${res.status}`);
486
- return textJson(await res.json());
487
- }
488
-
489
- export async function listSessionsImpl(callApi: CallApiFn): Promise<ToolResult> {
490
- const res = await callApi("/api/sessions");
491
- if (!res.ok) return toolError(`GET /api/sessions returned ${res.status}`);
492
- return textJson(await res.json());
493
- }
494
-
495
- export type GetSessionArgs = {
496
- sessionId: string;
497
- includeHistory?: boolean;
498
- latestLogLimit?: number;
499
- };
500
-
501
- export async function getSessionImpl(
502
- callApi: CallApiFn,
503
- args: GetSessionArgs,
504
- ): Promise<ToolResult> {
505
- const params = new URLSearchParams({ sessionId: args.sessionId });
506
- if (args.latestLogLimit !== undefined) {
507
- params.set("limit", String(args.latestLogLimit));
508
- }
509
- if (args.includeHistory === true) {
510
- params.set("includeHistory", "1");
511
- }
512
- const path = `/api/sessions?${params.toString()}`;
513
- const parsed = await fetchApiJson(callApi, path, SessionInfoSchema);
514
- if (!parsed.ok) return parsed.error;
515
- return textJson(parsed.data);
516
- }
517
-
518
- export function createRunImpl(args: CreateInspectorRunInput): ToolResult {
519
- return textJson(createInspectorRun(args));
520
- }
521
-
522
- export type GetRunArgs = {
523
- runId: string;
524
- };
525
-
526
- export function getRunImpl(args: GetRunArgs): ToolResult {
527
- const run = getInspectorRun(args.runId);
528
- if (!run.ok) return toolError(run.message);
529
- return textJson(run.value);
530
- }
531
-
532
- export type UpdateRunArgs = {
533
- runId: string;
534
- } & UpdateInspectorRunInput;
535
-
536
- export function updateRunImpl(args: UpdateRunArgs): ToolResult {
537
- const { runId, ...patch } = args;
538
- const run = updateInspectorRun(runId, patch);
539
- if (!run.ok) return toolError(run.message);
540
- return textJson(run.value);
541
- }
542
-
543
- export type GetRecentFailuresArgs = {
544
- limit?: number;
545
- };
546
-
547
- export function getRecentFailuresImpl(args: GetRecentFailuresArgs): ToolResult {
548
- const limit = args.limit ?? 5;
549
- return textJson(listInspectorRecentFailures(limit));
550
- }
551
-
552
- export type ExportEvidenceArgs = {
553
- runId: string;
554
- includeHistory?: boolean;
555
- latestLogLimit?: number;
556
- };
557
-
558
- export async function exportEvidenceImpl(args: ExportEvidenceArgs): Promise<ToolResult> {
559
- const exported = await exportInspectorRunEvidence(
560
- args.runId,
561
- {
562
- includeHistory: args.includeHistory,
563
- latestLogLimit: args.latestLogLimit,
564
- },
565
- inspectorBaseUrl(),
566
- );
567
- if (!exported.ok) return toolError(exported.message);
568
- return textJson(exported.value);
569
- }
570
-
571
- export function listGroupsImpl(): ToolResult {
572
- return textJson(listInspectorGroups());
573
- }
574
-
575
- export function createGroupImpl(args: CreateInspectorGroupInput): ToolResult {
576
- return textJson(createInspectorGroup(args));
577
- }
578
-
579
- export type GetGroupArgs = {
580
- groupId: string;
581
- };
582
-
583
- export function getGroupImpl(args: GetGroupArgs): ToolResult {
584
- const group = getInspectorGroup(args.groupId);
585
- if (!group.ok) return toolError(group.message);
586
- return textJson(group.value);
587
- }
588
-
589
- export type UpdateGroupArgs = {
590
- groupId: string;
591
- } & UpdateInspectorGroupInput;
592
-
593
- export function updateGroupImpl(args: UpdateGroupArgs): ToolResult {
594
- const { groupId, ...patch } = args;
595
- const group = updateInspectorGroup(groupId, patch);
596
- if (!group.ok) return toolError(group.message);
597
- return textJson(group.value);
598
- }
599
-
600
- export type DeleteGroupArgs = {
601
- groupId: string;
602
- } & DeleteInspectorGroupOptions;
603
-
604
- export function deleteGroupImpl(args: DeleteGroupArgs): ToolResult {
605
- const deleted = deleteInspectorGroup(args.groupId, { deleteEvidence: args.deleteEvidence });
606
- if (!deleted.ok) return toolError(deleted.message);
607
- return textJson(deleted.value);
608
- }
609
-
610
- export type AddGroupSessionArgs = {
611
- groupId: string;
612
- } & AddInspectorGroupSessionInput;
613
-
614
- export function addGroupSessionImpl(args: AddGroupSessionArgs): ToolResult {
615
- const { groupId, ...session } = args;
616
- const group = addInspectorGroupSession(groupId, session);
617
- if (!group.ok) return toolError(group.message);
618
- return textJson(group.value);
619
- }
620
-
621
- export type ExportGroupEvidenceArgs = {
622
- groupId: string;
623
- includeHistory?: boolean;
624
- latestLogLimit?: number;
625
- };
626
-
627
- export async function exportGroupEvidenceImpl(args: ExportGroupEvidenceArgs): Promise<ToolResult> {
628
- const exported = await exportInspectorGroupEvidence(
629
- args.groupId,
630
- {
631
- includeHistory: args.includeHistory,
632
- latestLogLimit: args.latestLogLimit,
633
- },
634
- inspectorBaseUrl(),
635
- );
636
- if (!exported.ok) return toolError(exported.message);
637
- return textJson(exported.value);
638
- }
639
-
640
- const SESSION_TIMELINE_DEFAULT_LIMIT = 20;
641
- const SESSION_TIMELINE_LIMIT_MAX = 50;
642
-
643
- export type SessionTimelineArgs = {
644
- sessionId: string;
645
- limit?: number;
646
- offset?: number;
647
- };
648
-
649
- function clampTimelineLimit(input: number | undefined): number {
650
- if (input === undefined) return SESSION_TIMELINE_DEFAULT_LIMIT;
651
- if (!Number.isFinite(input) || input < 1) return 1;
652
- return Math.min(Math.floor(input), SESSION_TIMELINE_LIMIT_MAX);
653
- }
654
-
655
- export function buildSessionTimeline(
656
- sessionId: string,
657
- logs: CapturedLog[],
658
- total: number,
659
- limit: number,
660
- ): Record<string, unknown> {
661
- const sortedLogs = [...logs].sort((left, right) => left.id - right.id);
662
- const events = sortedLogs.map((log, index) => ({
663
- sequence: index + 1,
664
- kind: "request",
665
- logId: log.id,
666
- ...buildLogSummary(log),
667
- }));
668
- const first = sortedLogs[0];
669
- const last = sortedLogs[sortedLogs.length - 1];
670
- const errorCount = events.filter((event) => event.hasError).length;
671
- const models = [...new Set(sortedLogs.map((log) => log.model).filter((model) => model !== null))];
672
- const providers = [
673
- ...new Set(
674
- sortedLogs.map((log) => log.providerName ?? null).filter((provider) => provider !== null),
675
- ),
676
- ];
677
-
678
- return {
679
- sessionId,
680
- logCoverage: "bounded",
681
- total,
682
- limit,
683
- eventCount: events.length,
684
- requestCount: events.length,
685
- errorCount,
686
- startedAt: first?.timestamp ?? null,
687
- updatedAt: last?.timestamp ?? null,
688
- models,
689
- providers,
690
- events,
691
- };
692
- }
693
-
694
- export async function getSessionTimelineImpl(
695
- callApi: CallApiFn,
696
- args: SessionTimelineArgs,
697
- ): Promise<ToolResult> {
698
- const limit = clampTimelineLimit(args.limit);
699
- const offset = args.offset ?? 0;
700
- const params = new URLSearchParams({
701
- offset: String(offset),
702
- limit: String(limit),
703
- compact: "1",
704
- sessionId: args.sessionId,
705
- });
706
- const path = `/api/logs?${params.toString()}`;
707
- const res = await callApi(path);
708
- if (!res.ok) return toolError(`GET ${path} returned ${res.status}`);
709
- const rawBody: unknown = await res.json();
710
- const parsed = LogsListResponseSchema.safeParse(rawBody);
711
- if (!parsed.success) return toolError("GET /api/logs returned an unparseable timeline shape");
712
- const logs = parsed.data.logs ?? [];
713
- return textJson(
714
- buildSessionTimeline(args.sessionId, logs, parsed.data.total ?? logs.length, limit),
715
- );
716
- }
717
-
718
- export type ListProcessesArgs = {
719
- limit?: number;
720
- scanLimit?: number;
721
- };
722
-
723
- export async function listProcessesImpl(
724
- callApi: CallApiFn,
725
- args: ListProcessesArgs = {},
726
- ): Promise<ToolResult> {
727
- const scanLimit = clampProcessLogLimit(args.scanLimit ?? args.limit);
728
- const params = new URLSearchParams({
729
- q: "",
730
- offset: "0",
731
- limit: String(Math.min(scanLimit, 50)),
732
- scanLimit: String(scanLimit),
733
- });
734
- const res = await callApi(`/api/logs?${params.toString()}`);
735
- if (!res.ok) return toolError(`GET /api/logs process scan returned ${res.status}`);
736
- const rawBody: unknown = await res.json();
737
- const parsed = SearchLogsResponseSchema.safeParse(rawBody);
738
- if (!parsed.success) return toolError("GET /api/logs returned an unparseable process scan shape");
739
-
740
- const grouped = new Map<number, CapturedLog[]>();
741
- for (const log of parsed.data.logs) {
742
- if (log.clientPid === null || log.clientPid === undefined) continue;
743
- const existing = grouped.get(log.clientPid) ?? [];
744
- existing.push(log);
745
- grouped.set(log.clientPid, existing);
746
- }
747
-
748
- const processes = [...grouped.entries()]
749
- .map(([clientPid, logs]) => processSummary(clientPid, logs, logs.length, logs.length))
750
- .sort((left, right) => {
751
- const leftUpdated = typeof left["updatedAt"] === "string" ? left["updatedAt"] : "";
752
- const rightUpdated = typeof right["updatedAt"] === "string" ? right["updatedAt"] : "";
753
- return rightUpdated.localeCompare(leftUpdated);
754
- });
755
-
756
- return textJson({
757
- scanned: parsed.data.scanned ?? parsed.data.logs.length,
758
- processCount: processes.length,
759
- processes,
760
- });
761
- }
762
-
763
- export type ProcessArgs = {
764
- clientPid: number;
765
- limit?: number;
766
- offset?: number;
767
- };
768
-
769
- export async function getProcessImpl(callApi: CallApiFn, args: ProcessArgs): Promise<ToolResult> {
770
- const limit = clampProcessLogLimit(args.limit);
771
- const offset = args.offset ?? 0;
772
- const params = new URLSearchParams({
773
- clientPid: String(args.clientPid),
774
- offset: String(offset),
775
- limit: String(limit),
776
- compact: "1",
777
- });
778
- const result = await readCompactLogsPage(callApi, params);
779
- if (isToolResult(result)) return result;
780
- return textJson(processSummary(args.clientPid, result.logs, result.total, limit));
781
- }
782
-
783
- export async function getProcessTimelineImpl(
784
- callApi: CallApiFn,
785
- args: ProcessArgs,
786
- ): Promise<ToolResult> {
787
- const limit = clampTimelineLimit(args.limit);
788
- const offset = args.offset ?? 0;
789
- const params = new URLSearchParams({
790
- clientPid: String(args.clientPid),
791
- offset: String(offset),
792
- limit: String(limit),
793
- compact: "1",
794
- });
795
- const result = await readCompactLogsPage(callApi, params);
796
- if (isToolResult(result)) return result;
797
- const sortedLogs = [...result.logs].sort((left, right) => left.id - right.id);
798
- return textJson({
799
- clientPid: args.clientPid,
800
- logCoverage: "bounded",
801
- total: result.total,
802
- limit,
803
- eventCount: sortedLogs.length,
804
- requestCount: sortedLogs.length,
805
- errorCount: sortedLogs.filter(logHasError).length,
806
- startedAt: sortedLogs[0]?.timestamp ?? null,
807
- updatedAt: sortedLogs.at(-1)?.timestamp ?? null,
808
- sessions: sortedUniqueStrings(sortedLogs.map((log) => log.sessionId)),
809
- models: sortedUniqueStrings(sortedLogs.map((log) => log.model)),
810
- providers: sortedUniqueStrings(sortedLogs.map((log) => log.providerName)),
811
- events: sortedLogs.map((log, index) => ({
812
- sequence: index + 1,
813
- kind: "request",
814
- logId: log.id,
815
- ...buildLogSummary(log),
816
- })),
817
- });
818
- }
819
-
820
- export async function getLogHeadersImpl(callApi: CallApiFn, id: number): Promise<ToolResult> {
821
- const parsed = await fetchApiJson(callApi, `/api/logs/${id}`, CapturedLogSchema);
822
- if (!parsed.ok) return parsed.error;
823
- const log = parsed.data;
824
- return textJson({
825
- id: log.id,
826
- sessionId: log.sessionId,
827
- userAgent: log.userAgent,
828
- origin: log.origin,
829
- clientPid: log.clientPid ?? null,
830
- clientPort: log.clientPort ?? null,
831
- clientCwd: log.clientCwd ?? null,
832
- clientProjectFolder: log.clientProjectFolder ?? null,
833
- apiFormat: log.apiFormat,
834
- method: log.method,
835
- path: log.path,
836
- model: log.model,
837
- rawHeaders: log.rawHeaders ?? null,
838
- headers: log.headers ?? null,
839
- hasRawHeaders: log.rawHeaders !== undefined && Object.keys(log.rawHeaders).length > 0,
840
- hasHeaders: log.headers !== undefined && Object.keys(log.headers).length > 0,
841
- bodyContentMode: log.bodyContentMode ?? null,
842
- });
843
- }
844
-
845
- export async function getRuntimeConfigImpl(callApi: CallApiFn): Promise<ToolResult> {
846
- const configRes = await callApi("/api/config");
847
- if (!configRes.ok) return toolError(`GET /api/config returned ${configRes.status}`);
848
- const statsRes = await callApi("/api/logs?stats=1");
849
- if (!statsRes.ok) return toolError(`GET /api/logs?stats=1 returned ${statsRes.status}`);
850
- const config: unknown = await configRes.json();
851
- const storage: unknown = await statsRes.json();
852
- return textJson({
853
- config,
854
- storage,
855
- });
856
- }
857
-
858
- export type DiagnoseCaptureArgs = {
859
- id?: number;
860
- };
861
-
862
- export async function diagnoseCaptureImpl(
863
- callApi: CallApiFn,
864
- args: DiagnoseCaptureArgs = {},
865
- ): Promise<ToolResult> {
866
- const runtime = await getRuntimeConfigImpl(callApi);
867
- if (runtime.isError === true) return runtime;
868
- const runtimeText = runtime.content[0]?.text ?? "{}";
869
- let runtimePayload: unknown = {};
870
- try {
871
- runtimePayload = JSON.parse(runtimeText);
872
- } catch {
873
- // Non-JSON runtime config text — treat as empty config and continue diagnostics
874
- }
875
- const runtimeRecord = isRecord(runtimePayload) ? runtimePayload : {};
876
- const configRecord = isRecord(runtimeRecord["config"]) ? runtimeRecord["config"] : {};
877
- const captureMode = stringField(configRecord, "captureMode");
878
-
879
- let log: CapturedLog | null = null;
880
- if (args.id !== undefined) {
881
- const logRes = await callApi(`/api/logs/${args.id}`);
882
- if (!logRes.ok) return toolError(`GET /api/logs/${args.id} returned ${logRes.status}`);
883
- const rawLog: unknown = await logRes.json();
884
- const parsed = CapturedLogSchema.safeParse(rawLog);
885
- if (!parsed.success) return toolError(`GET /api/logs/${args.id} returned an unparseable log`);
886
- log = parsed.data;
887
- }
888
-
889
- const findings: string[] = [];
890
- const recommendations: string[] = [];
891
- if (captureMode !== "full") {
892
- findings.push(
893
- "Runtime captureMode is not full, so request/response bodies and headers may be compacted.",
894
- );
895
- recommendations.push(
896
- "Restart or launch Agent Inspector with full capture mode when header/body evidence is required.",
897
- );
898
- }
899
- if (log !== null) {
900
- if (log.rawHeaders === undefined || Object.keys(log.rawHeaders).length === 0) {
901
- findings.push(`Log ${String(log.id)} has no rawHeaders stored.`);
902
- }
903
- if (log.headers === undefined || Object.keys(log.headers).length === 0) {
904
- findings.push(`Log ${String(log.id)} has no upstream headers stored.`);
905
- }
906
- if (log.rawRequestBody === null) {
907
- findings.push(`Log ${String(log.id)} does not include rawRequestBody in this response.`);
908
- }
909
- if (log.bodyContentMode === "compact") {
910
- findings.push(`Log ${String(log.id)} is in compact body mode.`);
911
- recommendations.push(
912
- "Use inspector_get_log_headers for header-only checks and enable full capture for future body inspection.",
913
- );
914
- }
915
- }
916
-
917
- return textJson({
918
- captureMode,
919
- logId: log?.id ?? null,
920
- hasLog: log !== null,
921
- findings,
922
- recommendations,
923
- runtime: runtimeRecord,
924
- });
925
- }
926
-
927
- export type AddGroupProcessArgs = {
928
- groupId: string;
929
- clientPid: number;
930
- labelPrefix?: string;
931
- };
932
-
933
- export async function addGroupProcessImpl(
934
- callApi: CallApiFn,
935
- args: AddGroupProcessArgs,
936
- ): Promise<ToolResult> {
937
- const params = new URLSearchParams({
938
- clientPid: String(args.clientPid),
939
- offset: "0",
940
- limit: String(PROCESS_LOG_LIMIT_MAX),
941
- compact: "1",
942
- });
943
- const result = await readCompactLogsPage(callApi, params);
944
- if (isToolResult(result)) return result;
945
-
946
- const bySession = new Map<string, CapturedLog[]>();
947
- for (const log of result.logs) {
948
- if (log.sessionId === null || log.sessionId === "") continue;
949
- const existing = bySession.get(log.sessionId) ?? [];
950
- existing.push(log);
951
- bySession.set(log.sessionId, existing);
952
- }
953
- if (bySession.size === 0) {
954
- return toolError(`No sessions found for clientPid ${String(args.clientPid)}`);
955
- }
956
-
957
- let groupResult: ReturnType<typeof addInspectorGroupSession> | null = null;
958
- const attached: Array<Record<string, unknown>> = [];
959
- for (const [sessionId, logs] of bySession.entries()) {
960
- const first = logs[0];
961
- const last = logs.at(-1);
962
- const userAgent = last?.userAgent ?? first?.userAgent ?? null;
963
- const agent = inferAgentFromUserAgent(userAgent);
964
- groupResult = addInspectorGroupSession(args.groupId, {
965
- sessionId,
966
- label: `${args.labelPrefix ?? `PID ${String(args.clientPid)}`} - ${sessionId}`,
967
- agent,
968
- provider: last?.providerName ?? first?.providerName ?? null,
969
- model: last?.model ?? first?.model ?? null,
970
- metadata: {
971
- clientPid: args.clientPid,
972
- processLogCount: logs.length,
973
- userAgent: userAgent ?? "",
974
- },
975
- });
976
- if (!groupResult.ok) return toolError(groupResult.message);
977
- attached.push({ sessionId, logCount: logs.length, agent, userAgent });
978
- }
979
-
980
- if (groupResult === null || !groupResult.ok) return toolError("Group process attach failed");
981
- return textJson({
982
- group: groupResult.value,
983
- clientPid: args.clientPid,
984
- attached,
985
- });
986
- }
987
-
988
- type EvidenceFileKind = "json" | "markdown" | "html" | "zip";
989
-
990
- type EvidenceFileSummary = {
991
- kind: EvidenceFileKind;
992
- path: string;
993
- exists: boolean;
994
- bytes: number | null;
995
- included?: boolean;
996
- };
997
-
998
- type EvidencePaths = {
999
- jsonPath: string;
1000
- markdownPath: string;
1001
- htmlPath: string;
1002
- exportedAt: string;
1003
- };
1004
-
1005
- function fileBytes(path: string): number | null {
1006
- if (!existsSync(path)) return null;
1007
- try {
1008
- return statSync(path).size;
1009
- } catch {
1010
- return null;
1011
- }
1012
- }
1013
-
1014
- function evidenceFileSummary(kind: EvidenceFileKind, path: string): EvidenceFileSummary {
1015
- return {
1016
- kind,
1017
- path,
1018
- exists: existsSync(path),
1019
- bytes: fileBytes(path),
1020
- };
1021
- }
1022
-
1023
- function evidenceFiles(evidence: EvidencePaths): EvidenceFileSummary[] {
1024
- return [
1025
- evidenceFileSummary("json", evidence.jsonPath),
1026
- evidenceFileSummary("markdown", evidence.markdownPath),
1027
- evidenceFileSummary("html", evidence.htmlPath),
1028
- ];
1029
- }
1030
-
1031
- function readEvidenceFile(path: string): Buffer | null {
1032
- if (!existsSync(path)) return null;
1033
- try {
1034
- return readFileSync(path);
1035
- } catch {
1036
- return null;
1037
- }
1038
- }
1039
-
1040
- async function exportEvidenceZip(
1041
- ownerKind: "run" | "group",
1042
- ownerId: string,
1043
- evidence: EvidencePaths,
1044
- ): Promise<Record<string, unknown>> {
1045
- const zip = new JSZip();
1046
- const sourceFiles = evidenceFiles(evidence);
1047
- const includedFiles: EvidenceFileSummary[] = [];
1048
-
1049
- for (const file of sourceFiles) {
1050
- const content = readEvidenceFile(file.path);
1051
- const included = content !== null;
1052
- includedFiles.push({ ...file, included });
1053
- if (included) {
1054
- zip.file(`${file.kind}.${file.kind === "markdown" ? "md" : file.kind}`, content);
1055
- }
1056
- }
1057
-
1058
- zip.file(
1059
- "manifest.json",
1060
- `${JSON.stringify(
1061
- {
1062
- ownerKind,
1063
- ownerId,
1064
- exportedAt: evidence.exportedAt,
1065
- bundledAt: new Date().toISOString(),
1066
- files: includedFiles,
1067
- },
1068
- null,
1069
- 2,
1070
- )}\n`,
1071
- );
1072
-
1073
- const zipPath = join(
1074
- dirname(evidence.jsonPath),
1075
- ownerKind === "run" ? "evidence.zip" : "group-evidence.zip",
1076
- );
1077
- const buffer = await zip.generateAsync({ type: "nodebuffer" });
1078
- writeFileSync(zipPath, buffer);
1079
- const zipSummary = evidenceFileSummary("zip", zipPath);
1080
- return {
1081
- ownerKind,
1082
- ownerId,
1083
- bundlePath: zipPath,
1084
- bytes: zipSummary.bytes,
1085
- files: [...includedFiles, zipSummary],
1086
- };
1087
- }
1088
-
1089
- export function getEvidenceFilesImpl(args: { runId: string }): ToolResult {
1090
- const evidence = readInspectorRunEvidence(args.runId);
1091
- if (!evidence.ok) return toolError(evidence.message);
1092
- return textJson({
1093
- runId: args.runId,
1094
- exportedAt: evidence.value.evidence.exportedAt,
1095
- files: evidenceFiles(evidence.value.evidence),
1096
- });
1097
- }
1098
-
1099
- export async function exportEvidenceBundleImpl(args: { runId: string }): Promise<ToolResult> {
1100
- const evidence = readInspectorRunEvidence(args.runId);
1101
- if (!evidence.ok) return toolError(evidence.message);
1102
- return textJson(await exportEvidenceZip("run", args.runId, evidence.value.evidence));
1103
- }
1104
-
1105
- export function getGroupEvidenceFilesImpl(args: { groupId: string }): ToolResult {
1106
- const evidence = readInspectorGroupEvidence(args.groupId);
1107
- if (!evidence.ok) return toolError(evidence.message);
1108
- return textJson({
1109
- groupId: args.groupId,
1110
- exportedAt: evidence.value.evidence.exportedAt,
1111
- files: evidenceFiles(evidence.value.evidence),
1112
- });
1113
- }
1114
-
1115
- export async function exportGroupEvidenceBundleImpl(args: {
1116
- groupId: string;
1117
- }): Promise<ToolResult> {
1118
- const evidence = readInspectorGroupEvidence(args.groupId);
1119
- if (!evidence.ok) return toolError(evidence.message);
1120
- return textJson(await exportEvidenceZip("group", args.groupId, evidence.value.evidence));
1121
- }
1122
-
1123
- export function getCurrentContextImpl(): ToolResult {
1124
- return textJson(getCurrentContext());
1125
- }
1126
-
1127
- export function setCurrentContextImpl(args: InspectorCurrentContextPatch): ToolResult {
1128
- return textJson(setCurrentContext(args));
1129
- }
1130
-
1131
- export async function listModelsImpl(callApi: CallApiFn): Promise<ToolResult> {
1132
- const res = await callApi("/api/models");
1133
- if (!res.ok) return toolError(`GET /api/models returned ${res.status}`);
1134
- return textJson(await res.json());
1135
- }
1136
-
1137
- export async function listProvidersImpl(callApi: CallApiFn): Promise<ToolResult> {
1138
- const res = await callApi("/api/providers");
1139
- if (!res.ok) return toolError(`GET /api/providers returned ${res.status}`);
1140
- return redactedProvidersResult(await res.json());
1141
- }
1142
-
1143
- export async function getProviderImpl(callApi: CallApiFn, id: string): Promise<ToolResult> {
1144
- const res = await callApi(`/api/providers/${encodeURIComponent(id)}`);
1145
- if (!res.ok) return toolError(`GET /api/providers/${id} returned ${res.status}`);
1146
- return redactedProviderResult(await res.json());
1147
- }
1148
-
1149
- export type ReplayLogArgs = { id: number };
1150
-
1151
- export async function replayLogImpl(callApi: CallApiFn, args: ReplayLogArgs): Promise<ToolResult> {
1152
- const logRes = await callApi(`/api/logs/${args.id}`);
1153
- if (!logRes.ok) return toolError(`GET /api/logs/${args.id} returned ${logRes.status}`);
1154
- const logRaw: unknown = await logRes.json();
1155
- const logParsed = CapturedLogSchema.safeParse(logRaw);
1156
- if (!logParsed.success) {
1157
- return toolError(`GET /api/logs/${args.id} returned an unparseable CapturedLog`);
1158
- }
1159
- const log = logParsed.data;
1160
- if (log.rawRequestBody === null) return toolError("Log has no rawRequestBody to replay");
1161
- const replayRes = await callApi(`/api/logs/${args.id}/replay`, {
1162
- method: "POST",
1163
- headers: { "content-type": "application/json" },
1164
- body: JSON.stringify({ modifiedBody: log.rawRequestBody }),
1165
- });
1166
- if (!replayRes.ok) {
1167
- return toolError(`POST /api/logs/${args.id}/replay returned ${replayRes.status}`);
1168
- }
1169
- return textJson(await replayRes.json());
1170
- }
1171
-
1172
- export type AddProviderInput = {
1173
- name: string;
1174
- apiKey: string;
1175
- format?: "anthropic" | "openai";
1176
- model?: string;
1177
- models?: string[];
1178
- anthropicBaseUrl?: string;
1179
- openaiBaseUrl?: string;
1180
- openaiResponsesBaseUrl?: string;
1181
- authHeader?: "bearer" | "x-api-key";
1182
- apiDocsUrl?: string;
1183
- };
1184
-
1185
- function normalizedProviderModels(input: { model?: string; models?: readonly string[] }): string[] {
1186
- const modelList = input.models ?? [];
1187
- const models = modelList.map((model) => model.trim()).filter((model) => model !== "");
1188
- if (models.length > 0) return models;
1189
- const model = input.model?.trim();
1190
- return model !== undefined && model !== "" ? [model] : [];
1191
- }
1192
-
1193
- export async function addProviderImpl(
1194
- callApi: CallApiFn,
1195
- provider: AddProviderInput,
1196
- ): Promise<ToolResult> {
1197
- const models = normalizedProviderModels(provider);
1198
- if (models.length > MAX_PROVIDER_MODELS) {
1199
- return resourceLimitToolError({
1200
- error: `Provider model lists may contain at most ${String(MAX_PROVIDER_MODELS)} entries.`,
1201
- code: "provider-model-limit-exceeded",
1202
- status: 413,
1203
- resource: "provider-models",
1204
- limit: MAX_PROVIDER_MODELS,
1205
- retryable: false,
1206
- });
1207
- }
1208
- const res = await callApi("/api/providers", {
1209
- method: "POST",
1210
- headers: { "content-type": "application/json" },
1211
- body: JSON.stringify({ ...provider, models }),
1212
- });
1213
- if (!res.ok) return toolError(`POST /api/providers returned ${res.status}`);
1214
- return redactedProviderResult(await res.json());
1215
- }
1216
-
1217
- export type UpdateProviderInput = {
1218
- id: string;
1219
- name?: string;
1220
- apiKey?: string;
1221
- format?: "anthropic" | "openai";
1222
- model?: string;
1223
- models?: string[];
1224
- baseUrl?: string;
1225
- authHeader?: "bearer" | "x-api-key";
1226
- anthropicBaseUrl?: string;
1227
- openaiBaseUrl?: string;
1228
- openaiResponsesBaseUrl?: string;
1229
- apiDocsUrl?: string;
1230
- };
1231
-
1232
- export async function updateProviderImpl(
1233
- callApi: CallApiFn,
1234
- input: UpdateProviderInput,
1235
- ): Promise<ToolResult> {
1236
- const { id, ...patch } = input;
1237
- const models =
1238
- patch.models !== undefined || patch.model !== undefined
1239
- ? normalizedProviderModels(patch)
1240
- : undefined;
1241
- if (models !== undefined && models.length > MAX_PROVIDER_MODELS) {
1242
- return resourceLimitToolError({
1243
- error: `Provider model lists may contain at most ${String(MAX_PROVIDER_MODELS)} entries.`,
1244
- code: "provider-model-limit-exceeded",
1245
- status: 413,
1246
- resource: "provider-models",
1247
- limit: MAX_PROVIDER_MODELS,
1248
- retryable: false,
1249
- });
1250
- }
1251
- const body = models === undefined ? patch : { ...patch, models };
1252
- const res = await callApi(`/api/providers/${encodeURIComponent(id)}`, {
1253
- method: "PUT",
1254
- headers: { "content-type": "application/json" },
1255
- body: JSON.stringify(body),
1256
- });
1257
- if (!res.ok) return toolError(`PUT /api/providers/${id} returned ${res.status}`);
1258
- return redactedProviderResult(await res.json());
1259
- }
1260
-
1261
- export async function testProviderImpl(callApi: CallApiFn, id: string): Promise<ToolResult> {
1262
- const res = await callApi(`/api/providers/${encodeURIComponent(id)}/test`, {
1263
- method: "POST",
1264
- });
1265
- if (!res.ok) return toolError(`POST /api/providers/${id}/test returned ${res.status}`);
1266
- return textJson(await res.json());
1267
- }
1268
-
1269
- type ProviderDiagnosticIssue = {
1270
- path: string;
1271
- status: number | null;
1272
- error: string | null;
1273
- durationMs: number | null;
1274
- endpoint: string | null;
1275
- };
1276
-
1277
- function stringField(record: Record<string, unknown>, key: string): string | null {
1278
- const value = record[key];
1279
- return typeof value === "string" && value.length > 0 ? value : null;
1280
- }
1281
-
1282
- function numberField(record: Record<string, unknown>, key: string): number | null {
1283
- const value = record[key];
1284
- return typeof value === "number" && Number.isFinite(value) ? value : null;
1285
- }
1286
-
1287
- function firstStringField(record: Record<string, unknown>, keys: string[]): string | null {
1288
- for (const key of keys) {
1289
- const value = stringField(record, key);
1290
- if (value !== null) return value;
1291
- }
1292
- return null;
1293
- }
1294
-
1295
- function firstNumberField(record: Record<string, unknown>, keys: string[]): number | null {
1296
- for (const key of keys) {
1297
- const value = numberField(record, key);
1298
- if (value !== null) return value;
1299
- }
1300
- return null;
1301
- }
1302
-
1303
- function collectProviderDiagnosticIssues(
1304
- value: unknown,
1305
- path: string,
1306
- issues: ProviderDiagnosticIssue[],
1307
- ): void {
1308
- if (Array.isArray(value)) {
1309
- value.forEach((item, index) =>
1310
- collectProviderDiagnosticIssues(item, `${path}[${String(index)}]`, issues),
1311
- );
1312
- return;
1313
- }
1314
- if (!isRecord(value)) return;
1315
-
1316
- const success = value["success"];
1317
- const status = firstNumberField(value, ["status", "responseStatus", "statusCode"]);
1318
- const error = firstStringField(value, ["error", "message", "errorMessage"]);
1319
- if (success === false || error !== null || (status !== null && status >= 400)) {
1320
- issues.push({
1321
- path,
1322
- status,
1323
- error,
1324
- durationMs: firstNumberField(value, ["durationMs", "elapsedMs", "latencyMs"]),
1325
- endpoint: firstStringField(value, ["endpoint", "url", "requestUrl", "baseUrl"]),
1326
- });
1327
- }
1328
-
1329
- for (const [key, child] of Object.entries(value)) {
1330
- if (typeof child === "object" && child !== null) {
1331
- collectProviderDiagnosticIssues(child, `${path}.${key}`, issues);
1332
- }
1333
- }
1334
- }
1335
-
1336
- function stringifyUnknown(value: unknown): string {
1337
- try {
1338
- const text = JSON.stringify(value);
1339
- return text === undefined ? "" : text.toLowerCase();
1340
- } catch {
1341
- return "";
1342
- }
1343
- }
1344
-
1345
- function providerDiagnosticHints(result: unknown): string[] {
1346
- const text = stringifyUnknown(result);
1347
- const hints: string[] = [];
1348
- if (text.includes("timeout") || text.includes("aborted")) {
1349
- hints.push(
1350
- "Provider test timed out or was aborted. Increase Settings > Proxy > Provider test timeout when the provider or model is slow.",
1351
- );
1352
- }
1353
- if (text.includes("localhost") || text.includes("127.0.0.1")) {
1354
- hints.push(
1355
- "The upstream URL contains localhost. If the agent or provider runs in another container, replace localhost with a reachable host/IP.",
1356
- );
1357
- }
1358
- if (text.includes("econnrefused") || text.includes("enotfound") || text.includes("network")) {
1359
- hints.push(
1360
- "This looks like a network or DNS failure. Verify the Inspector container/host can reach the provider base URL.",
1361
- );
1362
- }
1363
- if (
1364
- text.includes("401") ||
1365
- text.includes("403") ||
1366
- text.includes("unauthorized") ||
1367
- text.includes("forbidden")
1368
- ) {
1369
- hints.push(
1370
- "This looks like an authentication failure. Check the provider API key and auth header mode.",
1371
- );
1372
- }
1373
- if (text.includes("404") || text.includes("not found")) {
1374
- hints.push(
1375
- "This looks like a base URL or path mismatch. Check whether the provider endpoint already includes /v1 or needs a vendor-specific path.",
1376
- );
1377
- }
1378
- if (text.includes("nonstream") || text.includes("non-stream")) {
1379
- hints.push(
1380
- "The failure mentions non-streaming. Compare the Anthropic, OpenAI Chat, and OpenAI Responses non-streaming path and payload with the streaming request.",
1381
- );
1382
- }
1383
- return [...new Set(hints)];
1384
- }
1385
-
1386
- export async function diagnoseProviderTestImpl(
1387
- callApi: CallApiFn,
1388
- id: string,
1389
- ): Promise<ToolResult> {
1390
- const res = await callApi(`/api/providers/${encodeURIComponent(id)}/test`, {
1391
- method: "POST",
1392
- });
1393
- if (!res.ok) return toolError(`POST /api/providers/${id}/test returned ${res.status}`);
1394
- const result: unknown = await res.json();
1395
- const issues: ProviderDiagnosticIssue[] = [];
1396
- collectProviderDiagnosticIssues(result, "result", issues);
1397
- return textJson({
1398
- providerId: id,
1399
- issueCount: issues.length,
1400
- issues,
1401
- hints: providerDiagnosticHints(result),
1402
- result,
1403
- });
1404
- }
1405
-
1406
- export type CreateSessionKnowledgeArgs = {
1407
- sessionId: string;
1408
- };
1409
-
1410
- export async function createSessionKnowledgeImpl(
1411
- callApi: CallApiFn,
1412
- args: CreateSessionKnowledgeArgs,
1413
- ): Promise<ToolResult> {
1414
- const res = await callApi(
1415
- `/api/knowledge/sessions/${encodeURIComponent(args.sessionId)}/candidates`,
1416
- {
1417
- method: "POST",
1418
- },
1419
- );
1420
- if (!res.ok) {
1421
- return toolError(
1422
- `POST /api/knowledge/sessions/${args.sessionId}/candidates returned ${res.status}`,
1423
- );
1424
- }
1425
- return textJson(await res.json());
1426
- }
1427
-
1428
- export async function listKnowledgeCandidatesImpl(callApi: CallApiFn): Promise<ToolResult> {
1429
- const res = await callApi("/api/knowledge/candidates");
1430
- if (!res.ok) return toolError(`GET /api/knowledge/candidates returned ${res.status}`);
1431
- return textJson(await res.json());
1432
- }
1433
-
1434
- export type PromoteKnowledgeCandidateArgs = {
1435
- candidateId: string;
1436
- };
1437
-
1438
- export async function promoteKnowledgeCandidateImpl(
1439
- callApi: CallApiFn,
1440
- args: PromoteKnowledgeCandidateArgs,
1441
- ): Promise<ToolResult> {
1442
- const res = await callApi(
1443
- `/api/knowledge/candidates/${encodeURIComponent(args.candidateId)}/promote`,
1444
- {
1445
- method: "POST",
1446
- },
1447
- );
1448
- if (!res.ok) {
1449
- return toolError(
1450
- `POST /api/knowledge/candidates/${args.candidateId}/promote returned ${res.status}`,
1451
- );
1452
- }
1453
- return textJson(await res.json());
1454
- }
1455
-
1456
- export type SearchKnowledgeArgs = {
1457
- query: string;
1458
- project?: string;
1459
- };
1460
-
1461
- export async function searchKnowledgeImpl(
1462
- callApi: CallApiFn,
1463
- args: SearchKnowledgeArgs,
1464
- ): Promise<ToolResult> {
1465
- if (args.query.length > MAX_SEARCH_QUERY_CHARS) {
1466
- return resourceLimitToolError({
1467
- error: `MCP search queries may contain at most ${String(MAX_SEARCH_QUERY_CHARS)} characters.`,
1468
- code: "search-limit-exceeded",
1469
- status: 400,
1470
- resource: "mcp-knowledge-search-query",
1471
- limit: MAX_SEARCH_QUERY_CHARS,
1472
- retryable: false,
1473
- });
1474
- }
1475
- if (args.project !== undefined && args.project.length > MAX_SEARCH_PROJECT_CHARS) {
1476
- return resourceLimitToolError({
1477
- error: `MCP search project filters may contain at most ${String(MAX_SEARCH_PROJECT_CHARS)} characters.`,
1478
- code: "search-limit-exceeded",
1479
- status: 400,
1480
- resource: "mcp-knowledge-search-project",
1481
- limit: MAX_SEARCH_PROJECT_CHARS,
1482
- retryable: false,
1483
- });
1484
- }
1485
- const params = new URLSearchParams({ q: args.query });
1486
- if (args.project !== undefined && args.project.length > 0) {
1487
- params.set("project", args.project);
1488
- }
1489
- const res = await callApi(`/api/knowledge/search?${params.toString()}`);
1490
- if (!res.ok) return toolError(`GET /api/knowledge/search returned ${res.status}`);
1491
- return textJson(await res.json());
1492
- }
1493
-
1494
- export type GetProjectContextArgs = {
1495
- project: string;
1496
- };
1497
-
1498
- export async function getProjectContextImpl(
1499
- callApi: CallApiFn,
1500
- args: GetProjectContextArgs,
1501
- ): Promise<ToolResult> {
1502
- if (args.project.length > MAX_SEARCH_PROJECT_CHARS) {
1503
- return resourceLimitToolError({
1504
- error: `MCP search project filters may contain at most ${String(MAX_SEARCH_PROJECT_CHARS)} characters.`,
1505
- code: "search-limit-exceeded",
1506
- status: 400,
1507
- resource: "mcp-knowledge-search-project",
1508
- limit: MAX_SEARCH_PROJECT_CHARS,
1509
- retryable: false,
1510
- });
1511
- }
1512
- const params = new URLSearchParams({ project: args.project });
1513
- const res = await callApi(`/api/knowledge/project-context?${params.toString()}`);
1514
- if (!res.ok) return toolError(`GET /api/knowledge/project-context returned ${res.status}`);
1515
- return textJson(await res.json());
1516
- }
1517
-
1518
- /**
1519
- * Wrap an impl so any thrown error becomes a well-formed `isError: true`
1520
- * CallToolResult (instead of a JSON-RPC protocol error, which would abort
1521
- * the whole session). Loopback timeouts get a clear message.
1522
- */
1523
- export async function safeCall(fn: () => ToolResult | Promise<ToolResult>): Promise<ToolResult> {
1524
- try {
1525
- return await fn();
1526
- } catch (err) {
1527
- if (err instanceof LoopbackTimeoutError) {
1528
- return toolError(`Internal loopback call timed out after ${err.timeoutMs}ms: ${err.path}`);
1529
- }
1530
- const message = err instanceof Error ? err.message : String(err);
1531
- return toolError(`Tool execution failed: ${message}`);
1532
- }
1533
- }