@tonyclaw/agent-inspector 4.0.1 → 4.0.3

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 +193 -110
  3. package/.output/server/_ssr/index.mjs +1 -1
  4. package/.output/server/_ssr/{router-CTHqmroT.mjs → router-ChILzhmz.mjs} +92 -4
  5. package/.output/server/index.mjs +1 -1
  6. package/.output/ui/assets/{CompareDrawer-L3n4J-Or.js → CompareDrawer-Co3cUF_I.js} +1 -1
  7. package/.output/ui/assets/{InspectorPet-D5o_3OZS.js → InspectorPet-jFcrL37X.js} +1 -1
  8. package/.output/ui/assets/{ProxyViewerContainer-Ocagx75S.js → ProxyViewerContainer-BPzt_VpM.js} +4 -4
  9. package/.output/ui/assets/{ReplayDialog-Bk7x1h-y.js → ReplayDialog-C6O_zuwI.js} +1 -1
  10. package/.output/ui/assets/{RequestAnatomy-U4lzL7Sc.js → RequestAnatomy-Ca6NvB7L.js} +1 -1
  11. package/.output/ui/assets/{ResponseView-LOWfvBLK.js → ResponseView-BzqKcveJ.js} +1 -1
  12. package/.output/ui/assets/{StreamingChunkSequence-DPfsrrXQ.js → StreamingChunkSequence-9pCZIsS_.js} +1 -1
  13. package/.output/ui/assets/{_sessionId-0MlWS-CO.js → _sessionId-BMP3KVGR.js} +1 -1
  14. package/.output/ui/assets/{_sessionId-OdX2Jvt1.js → _sessionId-y4eRoT1H.js} +1 -1
  15. package/.output/ui/assets/{index-Ea-c8hPg.js → index-CH5w-haG.js} +1 -1
  16. package/.output/ui/assets/{index-zVVS53Hd.js → index-CmocEhyU.js} +1 -1
  17. package/.output/ui/assets/{index-Du4qNOOt.js → index-DT8sMJ_n.js} +2 -2
  18. package/.output/ui/assets/{index-gvFj_9K6.js → index-bfsbIRZa.js} +1 -1
  19. package/.output/ui/assets/{json-viewer-C1WAMzg3.js → json-viewer-BfFOSDG5.js} +1 -1
  20. package/.output/ui/assets/{jszip.min-B8AKu2zm.js → jszip.min-BkbVkOsp.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,984 +0,0 @@
1
- import { Suspense, useEffect, useMemo, useRef, useState } from "react";
2
- import type { JSX, KeyboardEvent as ReactKeyboardEvent } from "react";
3
- import {
4
- Check,
5
- ChevronRight,
6
- Columns2,
7
- Copy,
8
- Equal,
9
- Minus,
10
- Pencil,
11
- Plus,
12
- Rows3,
13
- X,
14
- } from "lucide-react";
15
- import { cn, formatTokens } from "../../lib/utils";
16
- import { copyTextToClipboard } from "../../lib/clipboard";
17
- import type { CapturedLog } from "../../contracts";
18
- import {
19
- type DiffOp,
20
- type JsonNode,
21
- diffTrees,
22
- normalizeRequest,
23
- previewNode,
24
- } from "./requestDiff";
25
- import { getConversationId } from "./ConversationHeader";
26
- import { Badge } from "../ui/badge";
27
- import { getLogFormatAdapter, resolveLogFormat } from "./log-formats";
28
- import { LazyJsonViewerFromString } from "./lazy";
29
-
30
- export type CompareDrawerProps = {
31
- /** Log selected first (shown on the left). */
32
- left: CapturedLog;
33
- /** Log selected second (shown on the right). */
34
- right: CapturedLog;
35
- /** Human-facing ordinal inside the left log's session. */
36
- leftDisplayNumber: number;
37
- /** Human-facing ordinal inside the right log's session. */
38
- rightDisplayNumber: number;
39
- onClose: () => void;
40
- };
41
-
42
- type EqualOp = Extract<DiffOp, { kind: "equal" }>;
43
- type AddedOp = Extract<DiffOp, { kind: "added" }>;
44
- type RemovedOp = Extract<DiffOp, { kind: "removed" }>;
45
- type ChangedOp = Extract<DiffOp, { kind: "changed" }>;
46
-
47
- type DiffMode = "unified" | "split";
48
-
49
- export const COMPARE_RESPONSIVE_HEADER_CLASS_NAME =
50
- "flex flex-col gap-3 border-b border-border px-3 py-3 sm:flex-row sm:items-start sm:gap-4 sm:px-4";
51
- export const COMPARE_RESPONSIVE_SPLIT_CLASS_NAME =
52
- "grid min-w-0 grid-cols-1 gap-x-2 gap-y-0.5 px-3 py-2 text-xs md:grid-cols-[200px_minmax(0,1fr)_minmax(0,1fr)]";
53
-
54
- /** Walk the JsonNode tree and pretty-print it back to a JSON string for the
55
- * expanded-equal-subtree view. The node is a plain object structure so
56
- * `JSON.stringify` produces correct output. */
57
- function nodeToJsonString(node: JsonNode, indent = 2): string {
58
- return JSON.stringify(nodeToJsonValue(node), null, indent);
59
- }
60
-
61
- function nodeToJsonValue(node: JsonNode): unknown {
62
- switch (node.kind) {
63
- case "primitive":
64
- return node.value;
65
- case "array":
66
- return node.value.map(nodeToJsonValue);
67
- case "object": {
68
- const out: Record<string, unknown> = {};
69
- for (const [k, v] of Object.entries(node.value)) {
70
- out[k] = nodeToJsonValue(v);
71
- }
72
- return out;
73
- }
74
- }
75
- }
76
-
77
- function LazyJsonStringView({
78
- text,
79
- defaultExpandDepth,
80
- }: {
81
- text: string;
82
- defaultExpandDepth: number;
83
- }): JSX.Element {
84
- return (
85
- <Suspense fallback={<div className="text-xs text-muted-foreground">Loading JSON...</div>}>
86
- <LazyJsonViewerFromString text={text} defaultExpandDepth={defaultExpandDepth} />
87
- </Suspense>
88
- );
89
- }
90
-
91
- /** The parent path of a JSON path string. E.g. `messages[3].content` →
92
- * `messages[3]`, `messages[3]` → `messages`, `messages` → `""`. */
93
- function parentPath(path: string): string {
94
- if (path === "") return "";
95
- for (let i = path.length - 1; i >= 0; i--) {
96
- const ch = path[i];
97
- if (ch === "." || ch === "[") {
98
- return path.substring(0, i);
99
- }
100
- }
101
- return "";
102
- }
103
-
104
- /** Group contiguous deep-equal ops (object/array, not primitive) that share
105
- * a common parent into a single collapsed row, so an unchanged block of N
106
- * sibling subtrees renders as one row instead of N. Primitive equals
107
- * always render as their own row (they're 1 line each). */
108
- type GroupedOp = { kind: "single"; op: DiffOp } | { kind: "equal-run"; ops: EqualOp[] };
109
-
110
- function isDeepEqual(op: DiffOp): op is EqualOp {
111
- return op.kind === "equal" && (op.value.kind === "object" || op.value.kind === "array");
112
- }
113
-
114
- function groupContiguousEquals(ops: DiffOp[]): GroupedOp[] {
115
- const out: GroupedOp[] = [];
116
- let i = 0;
117
- while (i < ops.length) {
118
- const op = ops[i];
119
- if (op !== undefined && isDeepEqual(op)) {
120
- const startParent = parentPath(op.path);
121
- let j = i + 1;
122
- while (j < ops.length) {
123
- const next = ops[j];
124
- if (next === undefined) break;
125
- if (!isDeepEqual(next)) break;
126
- if (parentPath(next.path) !== startParent) break;
127
- j++;
128
- }
129
- if (j - i > 1) {
130
- const equalOps: EqualOp[] = [];
131
- for (let k = i; k < j; k++) {
132
- const eop = ops[k];
133
- if (eop !== undefined && eop.kind === "equal") {
134
- equalOps.push(eop);
135
- }
136
- }
137
- out.push({ kind: "equal-run", ops: equalOps });
138
- i = j;
139
- continue;
140
- }
141
- }
142
- if (op !== undefined) {
143
- out.push({ kind: "single", op });
144
- }
145
- i++;
146
- }
147
- return out;
148
- }
149
-
150
- /** Visual configuration for each diff kind. Centralized so the unified row,
151
- * summary chips, and split columns all use the same colors / icons. */
152
- type DiffKind = "added" | "removed" | "changed" | "equal";
153
-
154
- const KIND_VISUAL: Record<
155
- DiffKind,
156
- {
157
- icon: typeof Plus;
158
- /** Accent color for the icon + label. */
159
- accent: string;
160
- /** Background tint applied to the row. */
161
- bg: string;
162
- /** Thick left-border color. */
163
- border: string;
164
- /** Short text label (uppercase, used in the row badge). */
165
- label: string;
166
- }
167
- > = {
168
- added: {
169
- icon: Plus,
170
- accent: "text-status-success",
171
- bg: "bg-status-success/5 hover:bg-status-success/10",
172
- border: "border-l-status-success",
173
- label: "ADDED",
174
- },
175
- removed: {
176
- icon: Minus,
177
- accent: "text-status-danger",
178
- bg: "bg-status-danger/5 hover:bg-status-danger/10",
179
- border: "border-l-status-danger",
180
- label: "REMOVED",
181
- },
182
- changed: {
183
- icon: Pencil,
184
- accent: "text-status-watch",
185
- bg: "bg-status-watch/5 hover:bg-status-watch/10",
186
- border: "border-l-status-watch",
187
- label: "CHANGED",
188
- },
189
- equal: {
190
- icon: Equal,
191
- accent: "text-muted-foreground/70",
192
- bg: "bg-muted/20 hover:bg-muted/30",
193
- border: "border-l-muted-foreground/20",
194
- label: "EQUAL",
195
- },
196
- };
197
-
198
- function EqualRunRow({
199
- ops,
200
- expanded,
201
- onToggle,
202
- }: {
203
- ops: EqualOp[];
204
- expanded: boolean;
205
- onToggle: () => void;
206
- }): JSX.Element {
207
- const first = ops[0];
208
- const last = ops[ops.length - 1];
209
- if (first === undefined || last === undefined) {
210
- return <div className="text-muted-foreground/40 text-xs">—</div>;
211
- }
212
- const firstPath = first.path;
213
- const lastPath = last.path;
214
- const label = ops.length === 1 ? firstPath : `${firstPath} … ${lastPath}`;
215
- const summary =
216
- first.value.kind === "array"
217
- ? `${ops.length} equal arrays`
218
- : first.value.kind === "object"
219
- ? `${ops.length} equal objects`
220
- : "equal";
221
-
222
- const v = KIND_VISUAL.equal;
223
-
224
- return (
225
- <div className={cn("border-l-4 rounded-sm", v.border, v.bg)}>
226
- <button
227
- type="button"
228
- onClick={onToggle}
229
- className="w-full text-left flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground cursor-pointer"
230
- >
231
- <ChevronRight
232
- className={cn("size-3 transition-transform shrink-0", expanded && "rotate-90")}
233
- />
234
- <v.icon className={cn("size-3 shrink-0", v.accent)} />
235
- <span className="font-mono truncate flex-1" title={`${firstPath} … ${lastPath}`}>
236
- {label}
237
- </span>
238
- <span className={cn("text-[10px] uppercase tracking-wider shrink-0", v.accent)}>
239
- {v.label}
240
- </span>
241
- <span className="text-muted-foreground/60 shrink-0">({summary})</span>
242
- </button>
243
- {expanded && (
244
- <div className="ml-5 mt-1 mb-2 space-y-2 pr-2">
245
- {ops.map((op) => (
246
- <div key={op.path} className="border border-border/50 rounded p-2 bg-muted/20">
247
- <div className="font-mono text-xs text-muted-foreground mb-1">{op.path}</div>
248
- <LazyJsonStringView text={nodeToJsonString(op.value)} defaultExpandDepth={0} />
249
- </div>
250
- ))}
251
- </div>
252
- )}
253
- </div>
254
- );
255
- }
256
-
257
- /** A single diff row in the unified view. Renders the icon, path, kind label,
258
- * value preview, and a copy-path button. `data-diff-idx` lets the parent
259
- * scroll the row into view when a summary chip is clicked.
260
- *
261
- * When `expanded` is true (controlled by the parent), renders the full
262
- * added / removed / changed subtree below the row. Primitive changes and
263
- * primitive equal values are not expandable (the value is already fully
264
- * shown on the row). */
265
- function UnifiedOpRow({
266
- op,
267
- idx,
268
- copiedPath,
269
- onCopyPath,
270
- expanded,
271
- onToggle,
272
- }: {
273
- op: AddedOp | RemovedOp | ChangedOp | EqualOp;
274
- idx: number;
275
- copiedPath: string | null;
276
- onCopyPath: (path: string) => void;
277
- expanded: boolean;
278
- onToggle: () => void;
279
- }): JSX.Element {
280
- const v = KIND_VISUAL[op.kind];
281
- const Icon = v.icon;
282
-
283
- // True iff this row has a useful subtree to drill into. Primitives (and
284
- // the root "equal" pseudo-op) have no extra detail beyond the preview.
285
- const isExpandable =
286
- op.kind === "added" || op.kind === "removed"
287
- ? op.value.kind === "object" || op.value.kind === "array"
288
- : op.kind === "changed"
289
- ? op.left.kind === "object" ||
290
- op.left.kind === "array" ||
291
- op.right.kind === "object" ||
292
- op.right.kind === "array"
293
- : false; // equal (single primitive) — preview is the whole value
294
-
295
- // For changed rows, show the left and right previews stacked. For added /
296
- // removed / equal, one preview is enough.
297
- const preview =
298
- op.kind === "changed"
299
- ? [
300
- {
301
- text: previewNode(op.left, 400),
302
- tone: "text-status-danger line-through",
303
- },
304
- { text: previewNode(op.right, 400), tone: "text-status-success" },
305
- ]
306
- : op.kind === "removed"
307
- ? [
308
- {
309
- text: previewNode(op.value, 400),
310
- tone: "text-status-danger line-through",
311
- },
312
- ]
313
- : op.kind === "added"
314
- ? [{ text: previewNode(op.value, 400), tone: "text-status-success" }]
315
- : [{ text: previewNode(op.value, 400), tone: "text-muted-foreground" }];
316
-
317
- const justCopied = copiedPath === op.path && op.path !== "";
318
-
319
- return (
320
- <div
321
- data-diff-idx={idx}
322
- data-diff-kind={op.kind}
323
- className={cn("border-l-4 rounded-sm px-3 py-2 my-0.5 transition-colors", v.border, v.bg)}
324
- >
325
- <button
326
- type="button"
327
- onClick={onToggle}
328
- disabled={!isExpandable}
329
- className={cn(
330
- "w-full flex items-center gap-2 text-xs text-left rounded-sm",
331
- isExpandable ? "cursor-pointer" : "cursor-default",
332
- )}
333
- aria-expanded={isExpandable ? expanded : undefined}
334
- aria-label={
335
- isExpandable
336
- ? expanded
337
- ? `Collapse ${op.path || "root"}`
338
- : `Expand ${op.path || "root"}`
339
- : undefined
340
- }
341
- >
342
- {isExpandable ? (
343
- <ChevronRight
344
- className={cn(
345
- "size-3 shrink-0 transition-transform",
346
- v.accent,
347
- expanded && "rotate-90",
348
- )}
349
- />
350
- ) : (
351
- <span className="size-3 shrink-0" aria-hidden="true" />
352
- )}
353
- <Icon className={cn("size-3.5 shrink-0", v.accent)} strokeWidth={2.5} />
354
- <span className="font-mono truncate flex-1 min-w-0" title={op.path || "(root)"}>
355
- {op.path === "" ? "(root)" : op.path}
356
- </span>
357
- <span
358
- className={cn(
359
- "text-[9px] font-bold uppercase tracking-wider shrink-0 px-1.5 py-0.5 rounded",
360
- v.accent,
361
- op.kind === "equal" ? "bg-muted/40" : "bg-background/60",
362
- )}
363
- >
364
- {v.label}
365
- </span>
366
- {op.path !== "" && (
367
- <span
368
- role="button"
369
- tabIndex={0}
370
- onClick={(e) => {
371
- e.stopPropagation();
372
- onCopyPath(op.path);
373
- }}
374
- onKeyDown={(e) => {
375
- if (e.key === "Enter" || e.key === " ") {
376
- e.stopPropagation();
377
- e.preventDefault();
378
- onCopyPath(op.path);
379
- }
380
- }}
381
- className={cn(
382
- "shrink-0 p-1 rounded transition-colors cursor-pointer inline-flex items-center justify-center",
383
- justCopied
384
- ? "text-status-success"
385
- : "text-muted-foreground/50 hover:text-foreground hover:bg-muted",
386
- )}
387
- aria-label={justCopied ? "Copied" : "Copy"}
388
- title={justCopied ? "Copied!" : "Copy"}
389
- >
390
- {justCopied ? <Check className="size-3" /> : <Copy className="size-3" />}
391
- </span>
392
- )}
393
- </button>
394
- {preview.map((p, i) => (
395
- // biome-ignore lint/suspicious/noArrayIndexKey: preview list is rebuilt on every render and is positional
396
- <div key={i} className={cn("font-mono text-xs mt-1 break-all pl-5", p.tone)}>
397
- {p.text}
398
- </div>
399
- ))}
400
- <div
401
- className="overflow-hidden transition-all duration-200"
402
- style={{ maxHeight: expanded && isExpandable ? "2000px" : "0" }}
403
- aria-hidden={!expanded}
404
- >
405
- {expanded && isExpandable && op.kind !== "equal" ? <ExpandedSubtree op={op} /> : null}
406
- </div>
407
- </div>
408
- );
409
- }
410
-
411
- /** Render the full subtree for an expanded diff row.
412
- * - added / removed: a single JsonViewerFromString for the value.
413
- * - changed (object/array on either side): two viewers side by side.
414
- * - changed (both primitives): a small "values are shown above" note. */
415
- function ExpandedSubtree({ op }: { op: AddedOp | RemovedOp | ChangedOp }): JSX.Element {
416
- if (op.kind === "added" || op.kind === "removed") {
417
- return (
418
- <div className="pl-5 mt-2 border border-border/50 rounded p-2 bg-muted/20">
419
- <LazyJsonStringView text={nodeToJsonString(op.value)} defaultExpandDepth={0} />
420
- </div>
421
- );
422
- }
423
- // changed
424
- const leftIsStructured = op.left.kind === "object" || op.left.kind === "array";
425
- const rightIsStructured = op.right.kind === "object" || op.right.kind === "array";
426
- if (!leftIsStructured && !rightIsStructured) {
427
- return (
428
- <div className="pl-5 mt-2 text-xs text-muted-foreground/70 italic">
429
- Primitive values are shown inline above.
430
- </div>
431
- );
432
- }
433
- return (
434
- <div className="pl-5 mt-2 grid grid-cols-1 md:grid-cols-2 gap-2">
435
- <div className="rounded border border-status-danger/30 bg-status-danger/5 p-2">
436
- <div className="mb-1 text-[10px] uppercase tracking-wider text-status-danger">Old</div>
437
- <LazyJsonStringView text={nodeToJsonString(op.left)} defaultExpandDepth={0} />
438
- </div>
439
- <div className="rounded border border-status-success/30 bg-status-success/5 p-2">
440
- <div className="mb-1 text-[10px] uppercase tracking-wider text-status-success">New</div>
441
- <LazyJsonStringView text={nodeToJsonString(op.right)} defaultExpandDepth={0} />
442
- </div>
443
- </div>
444
- );
445
- }
446
-
447
- /** Summary chips at the top of the body. Each chip is a button that scrolls
448
- * the body to the first op of that kind. Disabled when the count is 0. */
449
- function SummaryChips({
450
- counts,
451
- onJumpTo,
452
- }: {
453
- counts: { added: number; removed: number; changed: number };
454
- onJumpTo: (kind: "added" | "removed" | "changed") => void;
455
- }): JSX.Element {
456
- const total = counts.added + counts.removed + counts.changed;
457
- return (
458
- <div className="px-4 py-2 border-b border-border bg-muted/20 flex items-center gap-2 text-xs flex-wrap">
459
- <span className="text-muted-foreground font-medium">
460
- {total} {total === 1 ? "change" : "changes"}
461
- </span>
462
- <button
463
- type="button"
464
- onClick={() => onJumpTo("removed")}
465
- disabled={counts.removed === 0}
466
- className={cn(
467
- "inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
468
- counts.removed > 0
469
- ? "border-status-danger/40 bg-status-danger/10 text-status-danger hover:bg-status-danger/20"
470
- : "border-border text-muted-foreground/40 cursor-not-allowed",
471
- )}
472
- title={counts.removed > 0 ? "Jump to first removed" : "No removals"}
473
- >
474
- <Minus className="size-3" />
475
- {counts.removed} removed
476
- </button>
477
- <button
478
- type="button"
479
- onClick={() => onJumpTo("added")}
480
- disabled={counts.added === 0}
481
- className={cn(
482
- "inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
483
- counts.added > 0
484
- ? "border-status-success/40 bg-status-success/10 text-status-success hover:bg-status-success/20"
485
- : "border-border text-muted-foreground/40 cursor-not-allowed",
486
- )}
487
- title={counts.added > 0 ? "Jump to first added" : "No additions"}
488
- >
489
- <Plus className="size-3" />
490
- {counts.added} added
491
- </button>
492
- <button
493
- type="button"
494
- onClick={() => onJumpTo("changed")}
495
- disabled={counts.changed === 0}
496
- className={cn(
497
- "inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
498
- counts.changed > 0
499
- ? "border-status-watch/40 bg-status-watch/10 text-status-watch hover:bg-status-watch/20"
500
- : "border-border text-muted-foreground/40 cursor-not-allowed",
501
- )}
502
- title={counts.changed > 0 ? "Jump to first changed" : "No changes"}
503
- >
504
- <Pencil className="size-3" />
505
- {counts.changed} changed
506
- </button>
507
- </div>
508
- );
509
- }
510
-
511
- /** Mode toggle in the header: unified (single column) or split (path | left | right). */
512
- function ModeToggle({
513
- mode,
514
- onChange,
515
- }: {
516
- mode: DiffMode;
517
- onChange: (mode: DiffMode) => void;
518
- }): JSX.Element {
519
- return (
520
- <div className="inline-flex rounded-md border border-border overflow-hidden">
521
- <button
522
- type="button"
523
- onClick={() => onChange("unified")}
524
- aria-pressed={mode === "unified"}
525
- className={cn(
526
- "flex items-center gap-1 px-2 py-1 text-xs transition-colors cursor-pointer",
527
- mode === "unified"
528
- ? "bg-muted text-foreground"
529
- : "hover:bg-muted/50 text-muted-foreground",
530
- )}
531
- title="Unified view (single column, emphasized diffs)"
532
- >
533
- <Rows3 className="size-3" />
534
- Unified
535
- </button>
536
- <button
537
- type="button"
538
- onClick={() => onChange("split")}
539
- aria-pressed={mode === "split"}
540
- className={cn(
541
- "flex items-center gap-1 px-2 py-1 text-xs transition-colors border-l border-border cursor-pointer",
542
- mode === "split" ? "bg-muted text-foreground" : "hover:bg-muted/50 text-muted-foreground",
543
- )}
544
- title="Split view (path | left | right)"
545
- >
546
- <Columns2 className="size-3" />
547
- Split
548
- </button>
549
- </div>
550
- );
551
- }
552
-
553
- function SideSummary({
554
- log,
555
- side,
556
- displayNumber,
557
- }: {
558
- log: CapturedLog;
559
- side: "left" | "right";
560
- displayNumber: number;
561
- }): JSX.Element {
562
- const conversationId = getConversationId(log);
563
- return (
564
- <div className="flex-1 min-w-0 space-y-1 text-xs">
565
- <div className="flex items-center gap-2">
566
- <Badge
567
- variant="outline"
568
- className={cn(
569
- "text-[10px] px-1.5 py-0 h-5 font-mono shrink-0",
570
- side === "left"
571
- ? "border-status-danger/40 text-status-danger"
572
- : "border-status-success/40 text-status-success",
573
- )}
574
- >
575
- {side === "left" ? "← Left" : "Right →"}
576
- </Badge>
577
- <span className="font-mono text-status-info" title={`Log ID ${String(log.id)}`}>
578
- #{displayNumber}
579
- </span>
580
- {log.model !== null && (
581
- <span className="min-w-0 truncate font-mono leading-snug text-muted-foreground">
582
- {log.model}
583
- </span>
584
- )}
585
- </div>
586
- <div className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1 font-mono text-muted-foreground">
587
- {log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && (
588
- <span className="text-status-success">
589
- KV Cache +{formatTokens(log.cacheCreationInputTokens)}
590
- </span>
591
- )}
592
- {log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && (
593
- <span className="text-status-accent">
594
- KV Cache ~{formatTokens(log.cacheReadInputTokens)}
595
- </span>
596
- )}
597
- <span className="truncate" title={log.timestamp}>
598
- {log.timestamp}
599
- </span>
600
- </div>
601
- <div className="text-muted-foreground/70 font-mono truncate" title={conversationId}>
602
- session: {conversationId}
603
- </div>
604
- </div>
605
- );
606
- }
607
-
608
- export function CompareDrawer({
609
- left,
610
- right,
611
- leftDisplayNumber,
612
- rightDisplayNumber,
613
- onClose,
614
- }: CompareDrawerProps): JSX.Element {
615
- const panelRef = useRef<HTMLDivElement>(null);
616
- const closeButtonRef = useRef<HTMLButtonElement>(null);
617
- const previousActiveElementRef = useRef<HTMLElement | null>(null);
618
- // Memoize the diff so re-renders (e.g. parent re-renders) don't recompute.
619
- const ops = useMemo<DiffOp[]>(() => {
620
- const leftRequest = getLogFormatAdapter(resolveLogFormat(left)).analyzeRequest(
621
- left.rawRequestBody,
622
- );
623
- const rightRequest = getLogFormatAdapter(resolveLogFormat(right)).analyzeRequest(
624
- right.rawRequestBody,
625
- );
626
- const l = normalizeRequest(leftRequest.comparisonValue);
627
- const r = normalizeRequest(rightRequest.comparisonValue);
628
- return diffTrees(l, r);
629
- }, [
630
- left.apiFormat,
631
- left.path,
632
- left.rawRequestBody,
633
- right.apiFormat,
634
- right.path,
635
- right.rawRequestBody,
636
- ]);
637
-
638
- const grouped = useMemo(() => groupContiguousEquals(ops), [ops]);
639
-
640
- // Count diff ops by kind (ignoring equal-run groups, which are always
641
- // collapsed and not actionable). Drives the summary chips.
642
- const counts = useMemo(() => {
643
- let added = 0;
644
- let removed = 0;
645
- let changed = 0;
646
- for (const g of grouped) {
647
- if (g.kind !== "single") continue;
648
- switch (g.op.kind) {
649
- case "added":
650
- added++;
651
- break;
652
- case "removed":
653
- removed++;
654
- break;
655
- case "changed":
656
- changed++;
657
- break;
658
- case "equal":
659
- break;
660
- }
661
- }
662
- return { added, removed, changed };
663
- }, [grouped]);
664
-
665
- // Track which collapsed equal runs are expanded.
666
- const [expandedRuns, setExpandedRuns] = useState<Set<number>>(new Set());
667
- const toggleRun = (idx: number) => {
668
- setExpandedRuns((prev) => {
669
- const next = new Set(prev);
670
- if (next.has(idx)) next.delete(idx);
671
- else next.add(idx);
672
- return next;
673
- });
674
- };
675
-
676
- // Track which individual diff rows (added / removed / changed) are
677
- // expanded. Independent of `expandedRuns` (which is for equal-run groups)
678
- // so the two expand affordances don't interfere.
679
- const [expandedRows, setExpandedRows] = useState<Set<number>>(new Set());
680
- const toggleRow = (idx: number) => {
681
- setExpandedRows((prev) => {
682
- const next = new Set(prev);
683
- if (next.has(idx)) next.delete(idx);
684
- else next.add(idx);
685
- return next;
686
- });
687
- };
688
-
689
- // Reset per-row expand state when the comparison pair changes.
690
- useEffect(() => {
691
- setExpandedRows(new Set());
692
- }, [left.id, right.id]);
693
-
694
- const [mode, setMode] = useState<DiffMode>("unified");
695
-
696
- // Body scroll container — used to scroll a target row into view when a
697
- // summary chip is clicked.
698
- const bodyRef = useRef<HTMLDivElement>(null);
699
-
700
- // Most-recently-copied path. Used to flash the icon from Copy → Check.
701
- const [copiedPath, setCopiedPath] = useState<string | null>(null);
702
- const copyResetTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
703
- const onCopyPath = (path: string) => {
704
- void copyTextToClipboard(path).then((success) => {
705
- if (!success) return;
706
- setCopiedPath(path);
707
- if (copyResetTimer.current !== null) clearTimeout(copyResetTimer.current);
708
- copyResetTimer.current = setTimeout(() => setCopiedPath(null), 1500);
709
- });
710
- };
711
- useEffect(() => {
712
- return () => {
713
- if (copyResetTimer.current !== null) clearTimeout(copyResetTimer.current);
714
- };
715
- }, []);
716
-
717
- // Find the grouped-index of the first op matching `kind`, and scroll to it.
718
- const jumpToKind = (kind: "added" | "removed" | "changed") => {
719
- const idx = grouped.findIndex((g) => g.kind === "single" && g.op.kind === kind);
720
- if (idx === -1) return;
721
- const root = bodyRef.current;
722
- if (root === null) return;
723
- const el = root.querySelector(`[data-diff-idx="${idx}"]`);
724
- if (el !== null) {
725
- el.scrollIntoView({ behavior: "smooth", block: "center" });
726
- }
727
- };
728
-
729
- // Esc keybinding, initial focus, focus restoration, and body scroll lock while open.
730
- useEffect(() => {
731
- previousActiveElementRef.current =
732
- document.activeElement instanceof HTMLElement ? document.activeElement : null;
733
- closeButtonRef.current?.focus();
734
-
735
- const onKey = (e: KeyboardEvent) => {
736
- if (e.key === "Escape") onClose();
737
- };
738
- document.addEventListener("keydown", onKey);
739
- const prevOverflow = document.body.style.overflow;
740
- document.body.style.overflow = "hidden";
741
- return () => {
742
- document.removeEventListener("keydown", onKey);
743
- document.body.style.overflow = prevOverflow;
744
- previousActiveElementRef.current?.focus();
745
- };
746
- }, [onClose]);
747
-
748
- const handlePanelKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>): void => {
749
- event.stopPropagation();
750
- if (event.key !== "Tab") return;
751
-
752
- const panel = panelRef.current;
753
- if (panel === null) return;
754
- const focusable = Array.from(
755
- panel.querySelectorAll<HTMLElement>(
756
- 'button:not([disabled]), [href], input:not([disabled]), [tabindex]:not([tabindex="-1"])',
757
- ),
758
- );
759
- const first = focusable[0];
760
- const last = focusable[focusable.length - 1];
761
- if (first === undefined || last === undefined) return;
762
-
763
- if (event.shiftKey && document.activeElement === first) {
764
- event.preventDefault();
765
- last.focus();
766
- return;
767
- }
768
- if (!event.shiftKey && document.activeElement === last) {
769
- event.preventDefault();
770
- first.focus();
771
- }
772
- };
773
-
774
- const sameSession = getConversationId(left) === getConversationId(right);
775
- const allEqual = ops.length === 1 && ops[0]?.kind === "equal";
776
-
777
- return (
778
- <div
779
- className="fixed inset-0 z-50 flex justify-end"
780
- role="dialog"
781
- aria-modal="true"
782
- aria-label="Compare two log requests"
783
- >
784
- {/* Backdrop */}
785
- <button
786
- type="button"
787
- onClick={onClose}
788
- aria-label="Close compare drawer"
789
- className="absolute inset-0 bg-black/40 cursor-default"
790
- tabIndex={-1}
791
- />
792
-
793
- {/* Drawer panel */}
794
- <div
795
- ref={panelRef}
796
- className={cn(
797
- "relative overflow-x-hidden bg-background border-l border-border shadow-xl",
798
- "w-full md:w-[70vw] max-w-[1100px] flex flex-col h-full",
799
- )}
800
- onClick={(e) => e.stopPropagation()}
801
- onKeyDown={handlePanelKeyDown}
802
- >
803
- {/* Header */}
804
- <div className={COMPARE_RESPONSIVE_HEADER_CLASS_NAME}>
805
- <div className="grid min-w-0 flex-1 grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-4">
806
- <SideSummary log={left} side="left" displayNumber={leftDisplayNumber} />
807
- <SideSummary log={right} side="right" displayNumber={rightDisplayNumber} />
808
- </div>
809
- <div className="flex w-full shrink-0 items-center justify-between gap-2 sm:w-auto sm:justify-start">
810
- <ModeToggle mode={mode} onChange={setMode} />
811
- <button
812
- ref={closeButtonRef}
813
- type="button"
814
- onClick={onClose}
815
- aria-label="Close"
816
- className="p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted cursor-pointer"
817
- >
818
- <X className="size-4" />
819
- </button>
820
- </div>
821
- </div>
822
-
823
- {!sameSession && (
824
- <div className="border-b border-border bg-warning/10 px-4 py-1.5 text-xs text-foreground">
825
- Heads up: the two selected logs are from different sessions.
826
- </div>
827
- )}
828
-
829
- {allEqual ? (
830
- <div className="flex-1 min-h-0 overflow-y-auto flex items-center justify-center text-muted-foreground text-sm">
831
- The two Request payloads are identical.
832
- </div>
833
- ) : (
834
- <>
835
- <SummaryChips counts={counts} onJumpTo={jumpToKind} />
836
- <div ref={bodyRef} className="flex-1 min-h-0 overflow-y-auto">
837
- {mode === "unified" ? (
838
- <div className="px-3 py-2 space-y-0.5">
839
- {grouped.map((g, i) => {
840
- if (g.kind === "equal-run") {
841
- return (
842
- <EqualRunRow
843
- key={`r${i}`}
844
- ops={g.ops}
845
- expanded={expandedRuns.has(i)}
846
- onToggle={() => toggleRun(i)}
847
- />
848
- );
849
- }
850
- const op = g.op;
851
- return (
852
- <UnifiedOpRow
853
- key={`o${i}`}
854
- op={op}
855
- idx={i}
856
- copiedPath={copiedPath}
857
- onCopyPath={onCopyPath}
858
- expanded={expandedRows.has(i)}
859
- onToggle={() => toggleRow(i)}
860
- />
861
- );
862
- })}
863
- </div>
864
- ) : (
865
- <SplitBody
866
- grouped={grouped}
867
- left={left}
868
- right={right}
869
- leftDisplayNumber={leftDisplayNumber}
870
- rightDisplayNumber={rightDisplayNumber}
871
- />
872
- )}
873
- </div>
874
- </>
875
- )}
876
- </div>
877
- </div>
878
- );
879
- }
880
-
881
- /** Three-column on wide screens and a bounded stacked comparison on narrow screens. */
882
- function SplitBody({
883
- grouped,
884
- left,
885
- right,
886
- leftDisplayNumber,
887
- rightDisplayNumber,
888
- }: {
889
- grouped: GroupedOp[];
890
- left: CapturedLog;
891
- right: CapturedLog;
892
- leftDisplayNumber: number;
893
- rightDisplayNumber: number;
894
- }): JSX.Element {
895
- return (
896
- <div data-compare-layout="responsive-split" className={COMPARE_RESPONSIVE_SPLIT_CLASS_NAME}>
897
- {/* Column headers */}
898
- <div className="col-span-1 mb-2 grid grid-cols-1 gap-x-2 gap-y-1 border-b border-border pb-2 text-[10px] uppercase tracking-wider text-muted-foreground md:col-span-3 md:grid-cols-[200px_minmax(0,1fr)_minmax(0,1fr)]">
899
- <span>Path</span>
900
- <span title={`Log ID ${String(left.id)}`}>Left (Log #{leftDisplayNumber})</span>
901
- <span title={`Log ID ${String(right.id)}`}>Right (Log #{rightDisplayNumber})</span>
902
- </div>
903
-
904
- {grouped.map((g, i) => {
905
- if (g.kind === "equal-run") {
906
- return (
907
- <div
908
- // biome-ignore lint/suspicious/noArrayIndexKey: positional in the grouped list
909
- key={i}
910
- className="col-span-1 px-2 py-1 text-xs text-muted-foreground/60 md:col-span-3"
911
- >
912
- {g.ops.length} equal siblings collapsed — switch to Unified to expand
913
- </div>
914
- );
915
- }
916
- const op = g.op;
917
- if (op.kind === "equal") {
918
- return (
919
- <div
920
- // biome-ignore lint/suspicious/noArrayIndexKey: positional in the grouped list
921
- key={i}
922
- className="col-span-1 grid min-w-0 grid-cols-1 gap-x-2 gap-y-1 px-2 py-0.5 text-muted-foreground md:col-span-3 md:grid-cols-[200px_minmax(0,1fr)_minmax(0,1fr)]"
923
- >
924
- <span className="font-mono text-xs truncate" title={op.path}>
925
- {op.path}
926
- </span>
927
- <span className="font-mono text-xs break-all opacity-60">
928
- {previewNode(op.value, 200)}
929
- </span>
930
- <span className="font-mono text-xs break-all opacity-60">
931
- {previewNode(op.value, 200)}
932
- </span>
933
- </div>
934
- );
935
- }
936
- if (op.kind === "added") {
937
- return (
938
- <div
939
- // biome-ignore lint/suspicious/noArrayIndexKey: positional in the grouped list
940
- key={i}
941
- className="col-span-1 rounded border-l-2 border-l-status-success bg-status-success/5 px-2 py-1 text-xs md:col-span-3"
942
- >
943
- <div className="font-mono text-xs text-muted-foreground mb-0.5">{op.path}</div>
944
- <div className="font-mono break-all text-status-success">
945
- + {previewNode(op.value, 400)}
946
- </div>
947
- </div>
948
- );
949
- }
950
- if (op.kind === "removed") {
951
- return (
952
- <div
953
- // biome-ignore lint/suspicious/noArrayIndexKey: positional in the grouped list
954
- key={i}
955
- className="col-span-1 rounded border-l-2 border-l-status-danger bg-status-danger/5 px-2 py-1 text-xs md:col-span-3"
956
- >
957
- <div className="font-mono text-xs text-muted-foreground mb-0.5">{op.path}</div>
958
- <div className="font-mono break-all text-status-danger line-through">
959
- − {previewNode(op.value, 400)}
960
- </div>
961
- </div>
962
- );
963
- }
964
- return (
965
- <div
966
- // biome-ignore lint/suspicious/noArrayIndexKey: positional in the grouped list
967
- key={i}
968
- className="col-span-1 rounded border-l-2 border-l-status-watch bg-status-watch/5 px-2 py-1 text-xs md:col-span-3"
969
- >
970
- <div className="font-mono text-xs text-muted-foreground mb-1">{op.path}</div>
971
- <div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
972
- <div className="font-mono break-all text-status-danger line-through">
973
- {previewNode(op.left, 400)}
974
- </div>
975
- <div className="font-mono break-all text-status-success">
976
- {previewNode(op.right, 400)}
977
- </div>
978
- </div>
979
- </div>
980
- );
981
- })}
982
- </div>
983
- );
984
- }