@agenthippo-ai/agenthippo 1.110.11 → 1.110.19

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 (534) hide show
  1. package/out/agentpack/types.js +2 -5
  2. package/out/analytics/dashboard/artifactManager.js +2 -439
  3. package/out/analytics/dashboard/fleetDisplayUtils.js +2 -162
  4. package/out/analytics/dashboard/fleetOrchestrator.js +3 -142
  5. package/out/analytics/dashboard/fleetRegistry.js +2 -79
  6. package/out/analytics/dashboard/fleetRowDefaults.js +2 -87
  7. package/out/analytics/dashboard/fleetScriptAdapter.js +2 -146
  8. package/out/analytics/dashboard/fleetTypes.js +2 -29
  9. package/out/analytics/dashboard/index.js +2 -17
  10. package/out/analytics/dashboard/indexingService.js +2 -422
  11. package/out/analytics/dashboard/localFleetAdapter.js +2 -302
  12. package/out/analytics/dashboard/performanceMonitor.js +5 -76
  13. package/out/analytics/dashboard/processManager.js +2 -209
  14. package/out/analytics/dashboard/provider.js +38 -3817
  15. package/out/analytics/dashboard/queryRunner.js +62 -750
  16. package/out/analytics/dashboard/scheduledFleetStorage.js +2 -241
  17. package/out/analytics/dashboard/sessionJsonlFleet.js +3 -204
  18. package/out/analytics/dashboard/teamViewDiscovery.js +2 -106
  19. package/out/analytics/dashboard/webview/panel.js +34 -2318
  20. package/out/analytics/dashboard/webview/runtime-bridge.js +2 -82
  21. package/out/analytics/lifecycle.js +2 -43
  22. package/out/analytics/service.js +8 -1283
  23. package/out/auth/authBaseUrl.js +2 -59
  24. package/out/auth/bootstrapApply.js +2 -71
  25. package/out/auth/brokerClient.js +2 -160
  26. package/out/auth/callbackUri.js +2 -59
  27. package/out/auth/githubStoreGate.js +2 -70
  28. package/out/auth/loopbackAuthServer.js +4 -159
  29. package/out/auth/onboardingPrompt.js +2 -112
  30. package/out/auth/pkce.js +2 -9
  31. package/out/auth/provider.js +2 -226
  32. package/out/auth/sessionStore.js +2 -156
  33. package/out/auth/signInFlow.js +2 -183
  34. package/out/cli/index.js +617 -564
  35. package/out/cli/run-custom-engine-runner.js +3 -318
  36. package/out/cli/vscode-shim.js +2 -86
  37. package/out/engines/factory.js +2 -45
  38. package/out/engines/types.js +2 -17
  39. package/out/examples/run-headless-repl.js +6 -286
  40. package/out/examples/run-headless.js +2 -187
  41. package/out/extension.js +32 -4473
  42. package/out/gateway/gatewayRouteIndex.js +6 -371
  43. package/out/gateway/gatewaySessionStore.js +3 -243
  44. package/out/gateway/index.js +2 -31
  45. package/out/gateway/resetMappings.js +2 -52
  46. package/out/gateway/service.js +5 -1249
  47. package/out/mcp/publish.js +2 -143
  48. package/out/remote/handleRemoteChat.js +2 -169
  49. package/out/remote/remotePackStatusBar.js +3 -103
  50. package/out/remote/remoteTrust.js +6 -277
  51. package/out/remote/serveClient.js +4 -277
  52. package/out/remoteSkills.js +3 -191
  53. package/out/runtime/index.js +2 -32
  54. package/out/runtime/vscode/index.js +2 -20
  55. package/out/runtime/vscode/vscode-config-provider.js +2 -77
  56. package/out/runtime/vscode/vscode-edit-tracker.js +2 -122
  57. package/out/runtime/vscode/vscode-logger.js +2 -30
  58. package/out/runtime/vscode/vscode-permission-handler.js +2 -72
  59. package/out/scripts/agentide-litellm.sh +0 -1
  60. package/out/scripts/agentide_litellm_hooks_v1.py +127 -459
  61. package/out/scripts/analytics/agent-fleet-widget.js +18 -257
  62. package/out/scripts/analytics/agentide-litellm-otel.config.yaml +0 -4
  63. package/out/scripts/analytics/duckdbLocator.js +2 -28
  64. package/out/scripts/analytics/generate-default-charts.py +43 -323
  65. package/out/scripts/analytics/hotspot-analysis-widget.js +4 -430
  66. package/out/scripts/analytics/index-traces.py +78 -1036
  67. package/out/scripts/analytics/session-comparison-widget.js +26 -472
  68. package/out/scripts/analytics/session-timeline-widget.js +25 -846
  69. package/out/scripts/analytics/token-cost-flamegraph-widget.js +4 -461
  70. package/out/scripts/analytics/token-cost-trend-widget.js +4 -534
  71. package/out/scripts/analytics/workflows-collector.cjs +4 -314
  72. package/out/scripts/analytics/workflows-daemon.mjs +9 -185
  73. package/out/scripts/analytics/workflows-widget.cjs +15 -159
  74. package/out/scripts/analytics/wow-model-widget.js +10 -120
  75. package/out/scripts/vendor-tools.mjs +2 -233
  76. package/out/skills/publish.js +3 -178
  77. package/out/store/github/setup.js +7 -212
  78. package/out/store/oramaIndex.js +2 -884
  79. package/out/sync.js +26 -1829
  80. package/out/terminalLogger.js +5 -358
  81. package/out/utils/pathSafety.js +2 -60
  82. package/out/utils/pythonRunner.js +4 -258
  83. package/out/utils/toolPath.js +2 -83
  84. package/out/workspaceFolders.js +2 -59
  85. package/package.json +1 -1
  86. package/out/agentpack/github.js +0 -547
  87. package/out/agentpack/index.js +0 -69
  88. package/out/agentpack/install.js +0 -297
  89. package/out/agentpack/manager.js +0 -441
  90. package/out/agentpack/manifest.js +0 -135
  91. package/out/agentpack/paths.js +0 -521
  92. package/out/agentpack/publish.js +0 -248
  93. package/out/agentpack/stores.js +0 -554
  94. package/out/analytics/dashboard/fleetStatusUtils.js +0 -30
  95. package/out/analytics/dashboard/fleetTitleUtils.js +0 -68
  96. package/out/analytics/dashboard/refreshPolicy.js +0 -16
  97. package/out/analytics/dashboard/scheduledFleetFingerprint.js +0 -22
  98. package/out/analytics/dashboard/scheduledFleetUtils.js +0 -22
  99. package/out/analytics/dashboard/sessionEventFleetStats.js +0 -118
  100. package/out/analytics/dashboard/viewsConfigMerge.js +0 -221
  101. package/out/analytics/dashboard/webview/codicon.ttf +0 -0
  102. package/out/analytics/identity.js +0 -0
  103. package/out/analytics/sessionEvents.js +0 -324
  104. package/out/auth/callbackUriParse.js +0 -59
  105. package/out/auth/types.js +0 -12
  106. package/out/auth-broker/config.js +0 -112
  107. package/out/auth-broker/jwt.js +0 -105
  108. package/out/auth-broker/pkce.js +0 -59
  109. package/out/auth-broker/server.js +0 -681
  110. package/out/auth-broker/store.js +0 -275
  111. package/out/authority/warrant.js +0 -206
  112. package/out/base-prompts/mcp-servers/.mcp.json +0 -13
  113. package/out/base-prompts/mcp-servers/langfuse-analytics/README.md +0 -142
  114. package/out/base-prompts/mcp-servers/langfuse-analytics/mcp.json +0 -13
  115. package/out/base-prompts/mcp-servers/langfuse-analytics/package-lock.json +0 -1192
  116. package/out/base-prompts/mcp-servers/langfuse-analytics/package.json +0 -23
  117. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/run-with-env.sh +0 -12
  118. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/sdk-test.js +0 -102
  119. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/simple-test.js +0 -137
  120. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-client.js +0 -118
  121. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-server.js +0 -209
  122. package/out/base-prompts/mcp-servers/langfuse-analytics/src/client.ts +0 -431
  123. package/out/base-prompts/mcp-servers/langfuse-analytics/src/index.ts +0 -270
  124. package/out/base-prompts/mcp-servers/langfuse-analytics/tsconfig.json +0 -18
  125. package/out/base-prompts/rules/AGENTS.md +0 -204
  126. package/out/base-prompts/skills/agent-browser/SKILL.md +0 -102
  127. package/out/base-prompts/skills/agent-builder/SKILL.md +0 -73
  128. package/out/base-prompts/skills/agent-builder/references/agent-pack.md +0 -63
  129. package/out/base-prompts/skills/agent-builder/references/crewai.md +0 -66
  130. package/out/base-prompts/skills/agent-builder/references/langchain.md +0 -60
  131. package/out/base-prompts/skills/agent-builder/references/raw-sdk.md +0 -73
  132. package/out/base-prompts/skills/agent-engine-creator/SKILL.md +0 -586
  133. package/out/base-prompts/skills/agent-engine-installer/SKILL.md +0 -106
  134. package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.mjs +0 -259
  135. package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.ps1 +0 -189
  136. package/out/base-prompts/skills/agenthippo-cli/SKILL.md +0 -69
  137. package/out/base-prompts/skills/agenthippo-cli/scripts/install.sh +0 -63
  138. package/out/base-prompts/skills/agentpack-creator/SKILL.md +0 -375
  139. package/out/base-prompts/skills/agentpack-creator/references/agent-templates.md +0 -261
  140. package/out/base-prompts/skills/agentpack-creator/references/agents-md-guide.md +0 -195
  141. package/out/base-prompts/skills/agentpack-creator/scripts/init_pack.py +0 -391
  142. package/out/base-prompts/skills/agentpack-creator/scripts/init_pack_test.py +0 -294
  143. package/out/base-prompts/skills/agentpack-testbench/SKILL.md +0 -135
  144. package/out/base-prompts/skills/algorithmic-art/LICENSE.txt +0 -202
  145. package/out/base-prompts/skills/algorithmic-art/SKILL.md +0 -405
  146. package/out/base-prompts/skills/algorithmic-art/templates/generator_template.js +0 -223
  147. package/out/base-prompts/skills/algorithmic-art/templates/viewer.html +0 -599
  148. package/out/base-prompts/skills/debug-agent/SKILL.md +0 -83
  149. package/out/base-prompts/skills/desktop-automation/SKILL.md +0 -166
  150. package/out/base-prompts/skills/desktop-automation/references/agenthippo-ui-recipes.md +0 -441
  151. package/out/base-prompts/skills/desktop-automation/scripts/windows-desktop-uia.psm1 +0 -280
  152. package/out/base-prompts/skills/docx/LICENSE.txt +0 -30
  153. package/out/base-prompts/skills/docx/SKILL.md +0 -197
  154. package/out/base-prompts/skills/docx/docx-js.md +0 -350
  155. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
  156. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
  157. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
  158. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
  159. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
  160. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
  161. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
  162. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
  163. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
  164. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
  165. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
  166. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
  167. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
  168. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
  169. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
  170. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
  171. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
  172. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
  173. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
  174. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
  175. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
  176. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
  177. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
  178. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
  179. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
  180. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
  181. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
  182. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
  183. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
  184. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
  185. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
  186. package/out/base-prompts/skills/docx/ooxml/schemas/mce/mc.xsd +0 -75
  187. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +0 -560
  188. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +0 -67
  189. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +0 -14
  190. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +0 -20
  191. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +0 -13
  192. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
  193. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +0 -8
  194. package/out/base-prompts/skills/docx/ooxml/scripts/pack.py +0 -159
  195. package/out/base-prompts/skills/docx/ooxml/scripts/unpack.py +0 -29
  196. package/out/base-prompts/skills/docx/ooxml/scripts/validate.py +0 -69
  197. package/out/base-prompts/skills/docx/ooxml/scripts/validation/__init__.py +0 -15
  198. package/out/base-prompts/skills/docx/ooxml/scripts/validation/base.py +0 -951
  199. package/out/base-prompts/skills/docx/ooxml/scripts/validation/docx.py +0 -274
  200. package/out/base-prompts/skills/docx/ooxml/scripts/validation/pptx.py +0 -315
  201. package/out/base-prompts/skills/docx/ooxml/scripts/validation/redlining.py +0 -279
  202. package/out/base-prompts/skills/docx/ooxml.md +0 -610
  203. package/out/base-prompts/skills/docx/scripts/__init__.py +0 -1
  204. package/out/base-prompts/skills/docx/scripts/document.py +0 -1276
  205. package/out/base-prompts/skills/docx/scripts/templates/comments.xml +0 -3
  206. package/out/base-prompts/skills/docx/scripts/templates/commentsExtended.xml +0 -3
  207. package/out/base-prompts/skills/docx/scripts/templates/commentsExtensible.xml +0 -3
  208. package/out/base-prompts/skills/docx/scripts/templates/commentsIds.xml +0 -3
  209. package/out/base-prompts/skills/docx/scripts/templates/people.xml +0 -3
  210. package/out/base-prompts/skills/docx/scripts/utilities.py +0 -374
  211. package/out/base-prompts/skills/gateway-outbound/SKILL.md +0 -136
  212. package/out/base-prompts/skills/mcp-builder/LICENSE.txt +0 -202
  213. package/out/base-prompts/skills/mcp-builder/SKILL.md +0 -328
  214. package/out/base-prompts/skills/mcp-builder/reference/evaluation.md +0 -602
  215. package/out/base-prompts/skills/mcp-builder/reference/mcp_best_practices.md +0 -249
  216. package/out/base-prompts/skills/mcp-builder/reference/node_mcp_server.md +0 -970
  217. package/out/base-prompts/skills/mcp-builder/reference/python_mcp_server.md +0 -719
  218. package/out/base-prompts/skills/mcp-builder/scripts/connections.py +0 -151
  219. package/out/base-prompts/skills/mcp-builder/scripts/evaluation.py +0 -373
  220. package/out/base-prompts/skills/mcp-builder/scripts/example_evaluation.xml +0 -22
  221. package/out/base-prompts/skills/mcp-builder/scripts/requirements.txt +0 -2
  222. package/out/base-prompts/skills/openclaw/SKILL.md +0 -406
  223. package/out/base-prompts/skills/openclaw/scripts/run-smoke.sh +0 -61
  224. package/out/base-prompts/skills/pdf/LICENSE.txt +0 -30
  225. package/out/base-prompts/skills/pdf/SKILL.md +0 -294
  226. package/out/base-prompts/skills/pdf/forms.md +0 -205
  227. package/out/base-prompts/skills/pdf/reference.md +0 -612
  228. package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes.py +0 -70
  229. package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes_test.py +0 -226
  230. package/out/base-prompts/skills/pdf/scripts/check_fillable_fields.py +0 -12
  231. package/out/base-prompts/skills/pdf/scripts/convert_pdf_to_images.py +0 -35
  232. package/out/base-prompts/skills/pdf/scripts/create_validation_image.py +0 -41
  233. package/out/base-prompts/skills/pdf/scripts/extract_form_field_info.py +0 -152
  234. package/out/base-prompts/skills/pdf/scripts/fill_fillable_fields.py +0 -114
  235. package/out/base-prompts/skills/pdf/scripts/fill_pdf_form_with_annotations.py +0 -108
  236. package/out/base-prompts/skills/pptx/LICENSE.txt +0 -30
  237. package/out/base-prompts/skills/pptx/SKILL.md +0 -484
  238. package/out/base-prompts/skills/pptx/html2pptx.md +0 -625
  239. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
  240. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
  241. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
  242. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
  243. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
  244. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
  245. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
  246. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
  247. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
  248. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
  249. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
  250. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
  251. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
  252. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
  253. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
  254. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
  255. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
  256. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
  257. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
  258. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
  259. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
  260. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
  261. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
  262. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
  263. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
  264. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
  265. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
  266. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
  267. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
  268. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
  269. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
  270. package/out/base-prompts/skills/pptx/ooxml/schemas/mce/mc.xsd +0 -75
  271. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +0 -560
  272. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +0 -67
  273. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +0 -14
  274. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +0 -20
  275. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +0 -13
  276. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
  277. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +0 -8
  278. package/out/base-prompts/skills/pptx/ooxml/scripts/pack.py +0 -159
  279. package/out/base-prompts/skills/pptx/ooxml/scripts/unpack.py +0 -29
  280. package/out/base-prompts/skills/pptx/ooxml/scripts/validate.py +0 -69
  281. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/__init__.py +0 -15
  282. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/base.py +0 -951
  283. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/docx.py +0 -274
  284. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/pptx.py +0 -315
  285. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/redlining.py +0 -279
  286. package/out/base-prompts/skills/pptx/ooxml.md +0 -427
  287. package/out/base-prompts/skills/pptx/scripts/html2pptx.js +0 -984
  288. package/out/base-prompts/skills/pptx/scripts/inventory.py +0 -1020
  289. package/out/base-prompts/skills/pptx/scripts/rearrange.py +0 -231
  290. package/out/base-prompts/skills/pptx/scripts/replace.py +0 -385
  291. package/out/base-prompts/skills/pptx/scripts/thumbnail.py +0 -450
  292. package/out/base-prompts/skills/prompt-lab/SKILL.md +0 -149
  293. package/out/base-prompts/skills/prompt-lab/scripts/query-analytics.sh +0 -2551
  294. package/out/base-prompts/skills/skill-creator/LICENSE.txt +0 -202
  295. package/out/base-prompts/skills/skill-creator/SKILL.md +0 -371
  296. package/out/base-prompts/skills/skill-creator/references/output-patterns.md +0 -82
  297. package/out/base-prompts/skills/skill-creator/references/workflows.md +0 -28
  298. package/out/base-prompts/skills/skill-creator/scripts/init_skill.py +0 -303
  299. package/out/base-prompts/skills/skill-creator/scripts/package_skill.py +0 -110
  300. package/out/base-prompts/skills/skill-creator/scripts/quick_validate.py +0 -95
  301. package/out/base-prompts/skills/skill-installer/SKILL.md +0 -251
  302. package/out/base-prompts/skills/skill-installer/references/store-api.md +0 -219
  303. package/out/base-prompts/skills/spotlight-dashboard/SKILL.md +0 -83
  304. package/out/base-prompts/skills/spotlight-insights/SKILL.md +0 -101
  305. package/out/base-prompts/skills/spotlight-optimize/SKILL.md +0 -93
  306. package/out/base-prompts/skills/spotlight-optimize/scripts/query-analytics.sh +0 -2551
  307. package/out/base-prompts/skills/spotlight-query/SKILL.md +0 -314
  308. package/out/base-prompts/skills/spotlight-query/references/otel-schema.md +0 -163
  309. package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.sh +0 -2551
  310. package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.test.sh +0 -744
  311. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/sessions/sess-test-1.jsonl +0 -14
  312. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/.duckdb-index.json +0 -7
  313. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/sample.jsonl +0 -5
  314. package/out/base-prompts/skills/spotlight-report/SKILL.md +0 -86
  315. package/out/base-prompts/skills/spotlight-visualize/SKILL.md +0 -92
  316. package/out/base-prompts/skills/spotlight-visualize/scripts/query-analytics.sh +0 -2551
  317. package/out/base-prompts/skills/xlsx/LICENSE.txt +0 -30
  318. package/out/base-prompts/skills/xlsx/SKILL.md +0 -289
  319. package/out/base-prompts/skills/xlsx/recalc.py +0 -178
  320. package/out/branding.js +0 -49
  321. package/out/cli/commands/anywhere.js +0 -66
  322. package/out/cli/commands/ask.js +0 -246
  323. package/out/cli/commands/auth-broker.js +0 -24
  324. package/out/cli/commands/auth.js +0 -219
  325. package/out/cli/commands/chat.js +0 -357
  326. package/out/cli/commands/conformance.js +0 -249
  327. package/out/cli/commands/data-plane.js +0 -151
  328. package/out/cli/commands/deploy.js +0 -263
  329. package/out/cli/commands/gateway.js +0 -218
  330. package/out/cli/commands/idp.js +0 -85
  331. package/out/cli/commands/pack.js +0 -399
  332. package/out/cli/commands/probe-custom-engine.js +0 -82
  333. package/out/cli/commands/sandbox.js +0 -351
  334. package/out/cli/commands/serve.js +0 -1950
  335. package/out/cli/commands/store.js +0 -927
  336. package/out/cli/credential-context.js +0 -108
  337. package/out/cli/data-plane/broker.js +0 -78
  338. package/out/cli/data-plane/connector-host.js +0 -121
  339. package/out/cli/data-plane/egress.js +0 -189
  340. package/out/cli/data-plane/gateway.js +0 -218
  341. package/out/cli/data-plane/index.js +0 -153
  342. package/out/cli/data-plane/registry.js +0 -89
  343. package/out/cli/data-plane/remote.js +0 -106
  344. package/out/cli/data-plane/sidecar.js +0 -225
  345. package/out/cli/deploy/blueprints/registry.js +0 -84
  346. package/out/cli/deploy/layers/identity.js +0 -175
  347. package/out/cli/deploy-init.js +0 -3023
  348. package/out/cli/deps.js +0 -342
  349. package/out/cli/idp-init.js +0 -338
  350. package/out/cli/pack-install-ref.js +0 -15
  351. package/out/cli/pack-loader.js +0 -418
  352. package/out/cli/pack-runtime.js +0 -61
  353. package/out/cli/runtime.js +0 -450
  354. package/out/cli/serve-hardening.js +0 -776
  355. package/out/cli/state-sync.js +0 -424
  356. package/out/cli/utils.js +0 -231
  357. package/out/engines/base.js +0 -188
  358. package/out/engines/claude.js +0 -1254
  359. package/out/engines/codex.js +0 -862
  360. package/out/engines/custom.js +0 -680
  361. package/out/engines/customEngineIpc.js +0 -95
  362. package/out/engines/customEngineRunnerEmbed.js +0 -107
  363. package/out/engines/customRegistry.js +0 -274
  364. package/out/engines/embeddedCustomEngineRunner.generated.js +0 -9
  365. package/out/engines/engineApiKey.js +0 -138
  366. package/out/engines/install.js +0 -372
  367. package/out/engines/persistentEngineWorker.js +0 -320
  368. package/out/engines/publish.js +0 -341
  369. package/out/evidence/sessionShip.js +0 -643
  370. package/out/gateway/bridge.js +0 -843
  371. package/out/gateway/cli-agent-id.js +0 -36
  372. package/out/gateway/client.js +0 -336
  373. package/out/gateway/client.test.js +0 -395
  374. package/out/gateway/gateway-scope-errors.js +0 -14
  375. package/out/gateway/gatewayBackup.js +0 -76
  376. package/out/gateway/installSource.js +0 -68
  377. package/out/gateway/lifecycle.js +0 -3190
  378. package/out/gateway/openclaw-channel-routing.js +0 -317
  379. package/out/gateway/outboundHandles.js +0 -373
  380. package/out/gateway/outboundPaths.js +0 -75
  381. package/out/gateway/outboundSend.js +0 -254
  382. package/out/gateway/protocol.js +0 -48
  383. package/out/gateway/secretsPreflight.js +0 -90
  384. package/out/gateway/sessionRouting.js +0 -77
  385. package/out/headless/index.js +0 -546
  386. package/out/headless/sync.js +0 -473
  387. package/out/live-e2e/mcp-demo/server.mjs +0 -32
  388. package/out/live-probe/connector.mjs +0 -8
  389. package/out/live-probe/env-report.mjs +0 -8
  390. package/out/mcp.js +0 -654
  391. package/out/pack/caps.js +0 -271
  392. package/out/pack/governor.js +0 -326
  393. package/out/pack/lease-schema.js +0 -273
  394. package/out/pack/lease.js +0 -682
  395. package/out/pack/reconcile.js +0 -217
  396. package/out/pack/signature.js +0 -839
  397. package/out/permissions.js +0 -108
  398. package/out/permissions.mapping.yaml +0 -68
  399. package/out/registry/index.js +0 -332
  400. package/out/runner/host.js +0 -141
  401. package/out/runner/protocol.js +0 -128
  402. package/out/runner/remoteEngine.js +0 -166
  403. package/out/runtime/interfaces/config-provider.js +0 -32
  404. package/out/runtime/interfaces/edit-tracker.js +0 -24
  405. package/out/runtime/interfaces/index.js +0 -29
  406. package/out/runtime/interfaces/logger.js +0 -40
  407. package/out/runtime/interfaces/permission-handler.js +0 -66
  408. package/out/runtime/interfaces/session-store.js +0 -342
  409. package/out/runtime/vscode/vscode-session-store.js +0 -22
  410. package/out/sandbox/containerDriver.js +0 -572
  411. package/out/sandbox/processDriver.js +0 -318
  412. package/out/sandbox/sandboxRunner.js +0 -303
  413. package/out/sandbox/support.js +0 -301
  414. package/out/sandbox/types.js +0 -55
  415. package/out/scripts/analytics/fleet-local-adapter.js +0 -246
  416. package/out/scripts/analytics/live-fleet-4-collector.cjs +0 -315
  417. package/out/scripts/analytics/live-fleet-4-daemon.mjs +0 -134
  418. package/out/scripts/analytics/live-fleet-4-widget.cjs +0 -433
  419. package/out/scripts/gateway/Dockerfile.serve +0 -45
  420. package/out/scripts/gateway/docker-serve.sh +0 -248
  421. package/out/scripts/gateway/setup-gateway-server.ps1 +0 -953
  422. package/out/scripts/gateway/setup-gateway-server.sh +0 -935
  423. package/out/session.js +0 -272
  424. package/out/shared/agentEngines.js +0 -170
  425. package/out/shared/index.js +0 -109
  426. package/out/shared/models.js +0 -136
  427. package/out/shared/providers.js +0 -205
  428. package/out/spotlight/install.js +0 -345
  429. package/out/spotlight/publish.js +0 -544
  430. package/out/spotlight/yaml.js +0 -31
  431. package/out/store/index.js +0 -786
  432. package/out/store/shared.js +0 -241
  433. package/out/test/agentEnvFile.test.js +0 -97
  434. package/out/test/agentYaml.test.js +0 -373
  435. package/out/test/agentpack.test.js +0 -639
  436. package/out/test/analytics-lifecycle.test.js +0 -174
  437. package/out/test/analytics.test.js +0 -756
  438. package/out/test/auth-baseurl.test.js +0 -132
  439. package/out/test/auth-bootstrapApply.test.js +0 -123
  440. package/out/test/auth-broker-hardening.test.js +0 -227
  441. package/out/test/auth-broker.test.js +0 -541
  442. package/out/test/auth-callback.test.js +0 -62
  443. package/out/test/blueprint-registry.test.js +0 -105
  444. package/out/test/branding.test.js +0 -242
  445. package/out/test/caps-governor.test.js +0 -460
  446. package/out/test/cli-agent-id.test.js +0 -55
  447. package/out/test/codex-new-file-survival.test.js +0 -119
  448. package/out/test/configuration-scopes.test.js +0 -131
  449. package/out/test/credential-context.test.js +0 -160
  450. package/out/test/custom-env-layering.test.js +0 -151
  451. package/out/test/customEngineInstall.test.js +0 -100
  452. package/out/test/customRegistry.test.js +0 -122
  453. package/out/test/data-plane-engine-wiring.test.js +0 -282
  454. package/out/test/data-plane.test.js +0 -624
  455. package/out/test/deploy-init.test.js +0 -860
  456. package/out/test/deps-auto-install.test.js +0 -81
  457. package/out/test/egress-broker.test.js +0 -172
  458. package/out/test/engine-integration.test.js +0 -297
  459. package/out/test/enginePaths.test.js +0 -195
  460. package/out/test/enginePublish.test.js +0 -82
  461. package/out/test/fleetDisplayUtils.test.js +0 -100
  462. package/out/test/gateway-client.test.js +0 -409
  463. package/out/test/gateway-install-source.test.js +0 -111
  464. package/out/test/gateway-lifecycle.test.js +0 -608
  465. package/out/test/gateway-outbound.test.js +0 -538
  466. package/out/test/gateway-reset-mappings.test.js +0 -221
  467. package/out/test/gateway-route-index.test.js +0 -235
  468. package/out/test/gateway-secrets-preflight.test.js +0 -127
  469. package/out/test/gateway-session-routing.test.js +0 -65
  470. package/out/test/gateway-session-store.test.js +0 -216
  471. package/out/test/github.test.js +0 -232
  472. package/out/test/governor-hardening.test.js +0 -222
  473. package/out/test/headless-sync.test.js +0 -301
  474. package/out/test/helpers/manifest-fixtures.js +0 -27
  475. package/out/test/idp-init.test.js +0 -151
  476. package/out/test/lease-signature.test.js +0 -357
  477. package/out/test/lease-trust-root.test.js +0 -273
  478. package/out/test/lease-validation.test.js +0 -224
  479. package/out/test/lease.test.js +0 -114
  480. package/out/test/localFleetAdapter.test.js +0 -91
  481. package/out/test/mcp-obo.test.js +0 -107
  482. package/out/test/mcp.test.js +0 -389
  483. package/out/test/model-routing.test.js +0 -133
  484. package/out/test/openclaw-channel-routing.test.js +0 -191
  485. package/out/test/pack-admission.test.js +0 -216
  486. package/out/test/pack-digest-parity.test.js +0 -140
  487. package/out/test/pack-reconcile.test.js +0 -183
  488. package/out/test/pack-signature-artifact.test.js +0 -103
  489. package/out/test/pack-signature.test.js +0 -176
  490. package/out/test/pack-trust-root.test.js +0 -202
  491. package/out/test/pathSafety.test.js +0 -63
  492. package/out/test/permissions.test.js +0 -175
  493. package/out/test/persistent-engine-worker.test.js +0 -220
  494. package/out/test/refreshPolicy.test.js +0 -60
  495. package/out/test/registry.test.js +0 -351
  496. package/out/test/remote-trust.test.js +0 -242
  497. package/out/test/runner-protocol.test.js +0 -249
  498. package/out/test/runtime.test.js +0 -349
  499. package/out/test/sandbox-driver.test.js +0 -553
  500. package/out/test/sandbox-hardening.test.js +0 -406
  501. package/out/test/sandbox-runner.test.js +0 -343
  502. package/out/test/sandbox-scope.test.js +0 -449
  503. package/out/test/scheduledFleetStorage.test.js +0 -72
  504. package/out/test/serve-cron-acceptance.test.js +0 -370
  505. package/out/test/serve-gateway-turn.test.js +0 -389
  506. package/out/test/serve-hardening.test.js +0 -591
  507. package/out/test/serveClient.test.js +0 -209
  508. package/out/test/sessionJsonlFleet.test.js +0 -78
  509. package/out/test/sessionShip.test.js +0 -618
  510. package/out/test/shared.test.js +0 -173
  511. package/out/test/skills-mcp.test.js +0 -299
  512. package/out/test/spotlightPublish.test.js +0 -327
  513. package/out/test/spotlightYaml.test.js +0 -58
  514. package/out/test/state-sync-peer.test.js +0 -156
  515. package/out/test/store-install-safe.test.js +0 -69
  516. package/out/test/store.test.js +0 -331
  517. package/out/test/stores.test.js +0 -504
  518. package/out/test/sync-switch.test.js +0 -251
  519. package/out/test/teamViewDiscovery.test.js +0 -90
  520. package/out/test/tls-bypass.test.js +0 -90
  521. package/out/test/toolPath.test.js +0 -120
  522. package/out/test/utils.test.js +0 -308
  523. package/out/test/viewsConfigMerge.test.js +0 -180
  524. package/out/test/warrant.test.js +0 -138
  525. package/out/test/windows-command-line.test.js +0 -75
  526. package/out/utils/agentEnvFile.js +0 -131
  527. package/out/utils/binary.js +0 -175
  528. package/out/utils/index.js +0 -37
  529. package/out/utils/network.js +0 -74
  530. package/out/utils/paths.js +0 -116
  531. package/out/utils/pid.js +0 -180
  532. package/out/utils/resolveNodeBinary.js +0 -168
  533. package/out/utils/windowsCommandLine.js +0 -29
  534. package/out/utils.js +0 -968
@@ -1,2551 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # Copyright (c) AgentHippo.ai. All rights reserved.
4
- #
5
- # Query AgentHippo Analytics with DuckDB
6
- #
7
- # Run with --help for full usage. Key queries:
8
- #
9
- # Cost: --cost, --cost-by-session, --cost-by-model, --cost-by-pack
10
- # Debug: --session <id>, --request <id>, --latest-session
11
- # --session-messages <id> (parse messages directly from traces)
12
- # Regress: --compare-versions <agent>, --top-sessions <agent>
13
- # Health: --problems, --errors, --largest-requests, --tool-loops <id>
14
- # Custom: --sql "SELECT ..." (can use ${BASE_QUERY} and ${EXTRACT_ATTRS} CTEs)
15
- #
16
-
17
- set -euo pipefail
18
-
19
- # AGENT_HOME_DIR is set by the extension with the branded directory path
20
- # Falls back to ~/.agent-hippo for manual script runs
21
- AGENT_HOME_DIR="${AGENT_HOME_DIR:-$HOME/.agent-hippo}"
22
- ANALYTICS_DIR="${AGENT_HOME_DIR}/analytics"
23
- BIN_DIR="${AGENT_HOME_DIR}/bin"
24
- # Prefer explicit traces dir from dashboard env; fall back to default analytics path.
25
- TRACES_DIR="${AGENTIDE_TRACES_DIR:-${ANALYTICS_DIR}/traces}"
26
- INDEX_DB_PATH="${AGENTIDE_INDEX_DB_PATH:-${TRACES_DIR}/analytics.duckdb}"
27
- TMP_DIR="${AGENTIDE_ANALYTICS_TMP_DIR:-${ANALYTICS_DIR}/tmp}"
28
- MESSAGE_PREVIEW_CHARS="${AGENTIDE_SPOTLIGHT_MESSAGE_PREVIEW_CHARS:-800}"
29
- MESSAGE_PART_PREVIEW_CHARS="${AGENTIDE_SPOTLIGHT_MESSAGE_PART_PREVIEW_CHARS:-400}"
30
- MESSAGE_OUTPUT_BUDGET_CHARS="${AGENTIDE_SPOTLIGHT_OUTPUT_BUDGET_CHARS:-12000}"
31
- TRACE_EVENT_PREVIEW_LIMIT="${AGENTIDE_SPOTLIGHT_TRACE_EVENT_PREVIEW_LIMIT:-12}"
32
- SESSION_DEBUG_REQUEST_LIMIT="${AGENTIDE_SPOTLIGHT_SESSION_DEBUG_REQUEST_LIMIT:-6}"
33
- SESSION_DEBUG_TOOL_LIMIT="${AGENTIDE_SPOTLIGHT_SESSION_DEBUG_TOOL_LIMIT:-8}"
34
-
35
- # Find duckdb binary
36
- # Use indexed DB when available for fast session/message queries.
37
- find_duckdb() {
38
- if command -v duckdb &> /dev/null; then
39
- echo "duckdb"
40
- elif [[ -x "${BIN_DIR}/duckdb" ]]; then
41
- echo "${BIN_DIR}/duckdb"
42
- else
43
- echo "ERROR: DuckDB not found. Run install-analytics.sh first." >&2
44
- exit 1
45
- fi
46
- }
47
-
48
- DUCKDB=$(find_duckdb)
49
-
50
- # Check if traces exist
51
- check_traces() {
52
- if ! ls "${TRACES_DIR}"/*.jsonl &> /dev/null; then
53
- echo "No trace files found in ${TRACES_DIR}/"
54
- echo "Make sure OTEL Collector is running and LiteLLM has the 'otel' callback enabled."
55
- exit 0
56
- fi
57
- }
58
-
59
- has_indexed_db() {
60
- if [[ ! -f "${INDEX_DB_PATH}" ]]; then
61
- return 1
62
- fi
63
-
64
- local table_count
65
- table_count="$(${DUCKDB} -readonly "${INDEX_DB_PATH}" -csv -c "
66
- SELECT COUNT(DISTINCT table_name)
67
- FROM information_schema.tables
68
- WHERE table_name IN ('model_calls', 'sessions', 'turns');
69
- " 2>/dev/null | tail -n 1)"
70
-
71
- [[ "${table_count}" == "3" ]]
72
- }
73
-
74
- has_indexed_table() {
75
- local table_name="$1"
76
- if [[ ! -f "${INDEX_DB_PATH}" ]]; then
77
- return 1
78
- fi
79
- local found
80
- found="$(${DUCKDB} -readonly "${INDEX_DB_PATH}" -csv -c "
81
- SELECT COUNT(*)
82
- FROM information_schema.tables
83
- WHERE table_name = '$(escape_sql_literal "${table_name}")';
84
- " 2>/dev/null | tail -n 1)"
85
- [[ "${found}" == "1" ]]
86
- }
87
-
88
- run_duckdb_sql() {
89
- local sql="$1"
90
- if [[ -f "${INDEX_DB_PATH}" ]]; then
91
- ${DUCKDB} -readonly "${INDEX_DB_PATH}" -c "${sql}"
92
- else
93
- ${DUCKDB} -c "${sql}"
94
- fi
95
- }
96
-
97
- run_duckdb_json_sql() {
98
- local sql="$1"
99
- if [[ -f "${INDEX_DB_PATH}" ]]; then
100
- ${DUCKDB} -readonly "${INDEX_DB_PATH}" -json -c "${sql}"
101
- else
102
- ${DUCKDB} -json -c "${sql}"
103
- fi
104
- }
105
-
106
- escape_sql_literal() {
107
- local value="${1:-}"
108
- printf "%s" "${value//\'/'\'}"
109
- }
110
-
111
- sanitize_artifact_label() {
112
- local value="${1:-artifact}"
113
- printf "%s" "${value}" | tr -cs 'A-Za-z0-9._-' '_'
114
- }
115
-
116
- ensure_tmp_dir() {
117
- mkdir -p "${TMP_DIR}"
118
- }
119
-
120
- make_artifact_path() {
121
- local prefix="${1:-spotlight-query}"
122
- local suffix="${2:-txt}"
123
- local path
124
- ensure_tmp_dir
125
- path="$(mktemp "${TMP_DIR}/$(sanitize_artifact_label "${prefix}")-XXXXXX")"
126
- if [[ -n "${suffix}" ]]; then
127
- mv "${path}" "${path}.${suffix}"
128
- path="${path}.${suffix}"
129
- fi
130
- printf "%s\n" "${path}"
131
- }
132
-
133
- format_messages_rows() {
134
- python3 -c '
135
- import os
136
- import json
137
- import sys
138
- import textwrap
139
-
140
- role_colors = {
141
- "system": "\033[33m",
142
- "user": "\033[36m",
143
- "assistant": "\033[32m",
144
- "tool": "\033[35m",
145
- }
146
- reset = "\033[0m"
147
- budget = max(1000, int(os.environ.get("MESSAGE_OUTPUT_BUDGET_CHARS", "12000")))
148
- artifact_path = os.environ.get("MESSAGE_ARTIFACT_PATH", "")
149
-
150
- def format_content(content, indent=2):
151
- if content is None:
152
- content = ""
153
- content = str(content)
154
- prefix = " " * indent
155
- return textwrap.fill(content, width=100, initial_indent=prefix, subsequent_indent=prefix)
156
-
157
- payload = sys.stdin.read().strip()
158
- if not payload:
159
- print("(no messages found)")
160
- sys.exit(0)
161
-
162
- rows = json.loads(payload)
163
- if not rows:
164
- print("(no messages found)")
165
- sys.exit(0)
166
-
167
- rendered_blocks = []
168
- artifact_lines = []
169
- truncated_rows = 0
170
- for row in rows:
171
- seq = row.get("seq_in_session")
172
- role = str(row.get("role") or "unknown")
173
- ts = row.get("ts") or ""
174
- artifact_content = str(row.get("artifact_content_text") or row.get("content_text") or "")
175
- content = str(row.get("content_text") or "")
176
- content_chars = len(artifact_content)
177
- preview_part_count = max(1, int(row.get("preview_part_count") or 1))
178
- truncated_part_count = max(0, int(row.get("truncated_part_count") or 0))
179
- is_truncated = bool(row.get("is_truncated"))
180
- color = role_colors.get(role, "")
181
- if seq is None:
182
- header = f"\n{color}{role.upper()}{reset} {ts}"
183
- else:
184
- header = f"\n{color}[{seq}] {role.upper()}{reset} {ts}"
185
- suffix = ""
186
- artifact_suffix = ""
187
- if is_truncated:
188
- truncated_rows += 1
189
- if preview_part_count > 1:
190
- suffix = (
191
- f"\n [per-part tail preview: {preview_part_count} parts, "
192
- f"{truncated_part_count} truncated, {content_chars} chars total]"
193
- )
194
- artifact_suffix += (
195
- f"\n [preview used per-part tail excerpts across {preview_part_count} parts; "
196
- f"full extracted content retained]"
197
- )
198
- else:
199
- visible_chars = len(content) - 3 if content.startswith("...") else len(content)
200
- suffix = f"\n [tail preview truncated: showing last {min(content_chars, visible_chars)} of {content_chars} chars]"
201
- artifact_suffix += f"\n [tail preview truncated from {content_chars} chars]"
202
- block = f"{header}\n{format_content(content)}{suffix}"
203
- rendered_blocks.append(block)
204
- artifact_lines.append(f"{header}\n{format_content(artifact_content)}{artifact_suffix}")
205
-
206
- total_chars = 0
207
- printed = 0
208
- for block in rendered_blocks:
209
- projected = total_chars + len(block)
210
- if printed > 0 and projected > budget:
211
- break
212
- print(block)
213
- total_chars = projected
214
- printed += 1
215
-
216
- omitted = len(rendered_blocks) - printed
217
- artifact_needed = bool(artifact_path) and (
218
- omitted > 0 or truncated_rows > 0 or len(rendered_blocks) > 8
219
- )
220
- if artifact_needed:
221
- with open(artifact_path, "w", encoding="utf-8") as handle:
222
- handle.write("Spotlight Query message artifact\n")
223
- handle.write(f"rows={len(rendered_blocks)} truncated_rows={truncated_rows}\n")
224
- handle.write("\n".join(artifact_lines))
225
- handle.write("\n")
226
-
227
- if omitted > 0:
228
- print(f"\nOutput clipped after {printed} rows to stay within {budget} chars.")
229
- if artifact_path:
230
- print(f"Artifact: {artifact_path}")
231
- elif artifact_needed:
232
- print(f"\nArtifact: {artifact_path}")
233
- '
234
- }
235
-
236
- query_session_last_messages() {
237
- local session_id="$1"
238
- local limit="${2:-8}"
239
- local escaped_session_id
240
- local artifact_path
241
- escaped_session_id="$(escape_sql_literal "${session_id}")"
242
-
243
- if ! [[ "${limit}" =~ ^[0-9]+$ ]]; then
244
- echo "Limit must be numeric"
245
- exit 1
246
- fi
247
-
248
- if has_indexed_db; then
249
- artifact_path="$(make_artifact_path "session-last-$(sanitize_artifact_label "${session_id}")" "txt")"
250
- echo "📊 Last ${limit} User/Assistant Messages (safe preview): ${session_id}"
251
- echo "================================================================"
252
- run_duckdb_json_sql "
253
- WITH latest_call AS (
254
- SELECT
255
- trace_id,
256
- turn_id,
257
- request_id,
258
- start_ms,
259
- input_messages_raw,
260
- output_messages_raw
261
- FROM model_calls
262
- WHERE session_id = '${escaped_session_id}'
263
- ORDER BY start_ms DESC
264
- LIMIT 1
265
- ),
266
- input_messages AS (
267
- SELECT
268
- CAST(message.key AS BIGINT) + 1 AS seq_in_session,
269
- LOWER(COALESCE(
270
- json_extract_string(message.value, '$.role'),
271
- json_extract_string(message.value, '$.message.role'),
272
- 'unknown'
273
- )) AS role,
274
- message.value AS message_json,
275
- to_timestamp((SELECT start_ms FROM latest_call) / 1000.0) AS ts,
276
- (SELECT turn_id FROM latest_call) AS turn_id,
277
- (SELECT trace_id FROM latest_call) AS trace_id,
278
- COALESCE(
279
- NULLIF(json_extract_string(message.value, '$.text'), ''),
280
- NULLIF(json_extract_string(message.value, '$.message.content'), ''),
281
- NULLIF(json_extract_string(message.value, '$.content'), ''),
282
- ''
283
- ) AS fallback_text
284
- FROM latest_call,
285
- json_each(COALESCE(input_messages_raw, '[]'::JSON)) message
286
- ),
287
- input_parts AS (
288
- SELECT
289
- raw_parts.seq_in_session,
290
- raw_parts.part_index,
291
- raw_parts.part_text,
292
- CASE
293
- WHEN length(raw_parts.part_text) > ${MESSAGE_PART_PREVIEW_CHARS}
294
- THEN '...' || right(raw_parts.part_text, ${MESSAGE_PART_PREVIEW_CHARS})
295
- ELSE raw_parts.part_text
296
- END AS part_preview_text,
297
- length(raw_parts.part_text) > ${MESSAGE_PART_PREVIEW_CHARS} AS part_is_truncated
298
- FROM (
299
- SELECT
300
- input_messages.seq_in_session,
301
- CAST(part.key AS BIGINT) AS part_index,
302
- LOWER(COALESCE(json_extract_string(part.value, '$.type'), '')) AS part_type,
303
- part_text
304
- FROM input_messages
305
- LEFT JOIN json_each(
306
- CASE
307
- WHEN json_type(json_extract(input_messages.message_json, '$.parts')) = 'ARRAY'
308
- THEN json_extract(input_messages.message_json, '$.parts')
309
- WHEN json_type(json_extract(input_messages.message_json, '$.message.parts')) = 'ARRAY'
310
- THEN json_extract(input_messages.message_json, '$.message.parts')
311
- WHEN json_type(json_extract(input_messages.message_json, '$.content')) = 'ARRAY'
312
- THEN json_extract(input_messages.message_json, '$.content')
313
- WHEN json_type(json_extract(input_messages.message_json, '$.message.content')) = 'ARRAY'
314
- THEN json_extract(input_messages.message_json, '$.message.content')
315
- ELSE '[]'::JSON
316
- END
317
- ) part ON TRUE
318
- CROSS JOIN (
319
- SELECT COALESCE(
320
- NULLIF(json_extract_string(part.value, '$.text'), ''),
321
- NULLIF(json_extract_string(part.value, '$.content'), ''),
322
- ''
323
- ) AS part_text
324
- ) extracted
325
- ) raw_parts
326
- WHERE raw_parts.part_type NOT IN ('thinking', 'signature', 'tool_result')
327
- AND raw_parts.part_text <> ''
328
- ),
329
- input_expanded AS (
330
- SELECT
331
- input_messages.seq_in_session,
332
- input_messages.role,
333
- COALESCE(
334
- NULLIF(string_agg(input_parts.part_text, '\n' ORDER BY input_parts.part_index), ''),
335
- input_messages.fallback_text
336
- ) AS content_text,
337
- COALESCE(
338
- NULLIF(string_agg(input_parts.part_preview_text, '\n...\n' ORDER BY input_parts.part_index), ''),
339
- CASE
340
- WHEN length(input_messages.fallback_text) > ${MESSAGE_PART_PREVIEW_CHARS}
341
- THEN '...' || right(input_messages.fallback_text, ${MESSAGE_PART_PREVIEW_CHARS})
342
- ELSE input_messages.fallback_text
343
- END
344
- ) AS preview_text,
345
- CASE
346
- WHEN COUNT(input_parts.part_text) > 0 THEN COUNT(input_parts.part_text)
347
- WHEN input_messages.fallback_text <> '' THEN 1
348
- ELSE 0
349
- END AS preview_part_count,
350
- CASE
351
- WHEN COUNT(input_parts.part_text) > 0 THEN SUM(CASE WHEN input_parts.part_is_truncated THEN 1 ELSE 0 END)
352
- WHEN length(input_messages.fallback_text) > ${MESSAGE_PART_PREVIEW_CHARS} THEN 1
353
- ELSE 0
354
- END AS truncated_part_count,
355
- input_messages.ts,
356
- input_messages.turn_id,
357
- input_messages.trace_id
358
- FROM input_messages
359
- LEFT JOIN input_parts
360
- ON input_parts.seq_in_session = input_messages.seq_in_session
361
- GROUP BY
362
- input_messages.seq_in_session,
363
- input_messages.role,
364
- input_messages.fallback_text,
365
- input_messages.ts,
366
- input_messages.turn_id,
367
- input_messages.trace_id
368
- ),
369
- output_messages AS (
370
- SELECT
371
- (SELECT COALESCE(MAX(seq_in_session), 0) FROM input_expanded) + CAST(message.key AS BIGINT) + 1 AS seq_in_session,
372
- LOWER(COALESCE(
373
- json_extract_string(message.value, '$.role'),
374
- json_extract_string(message.value, '$.message.role'),
375
- 'assistant'
376
- )) AS role,
377
- message.value AS message_json,
378
- to_timestamp((SELECT start_ms FROM latest_call) / 1000.0) AS ts,
379
- (SELECT turn_id FROM latest_call) AS turn_id,
380
- (SELECT trace_id FROM latest_call) AS trace_id,
381
- COALESCE(
382
- NULLIF(json_extract_string(message.value, '$.text'), ''),
383
- NULLIF(json_extract_string(message.value, '$.message.content'), ''),
384
- NULLIF(json_extract_string(message.value, '$.content'), ''),
385
- ''
386
- ) AS fallback_text
387
- FROM latest_call,
388
- json_each(COALESCE(output_messages_raw, '[]'::JSON)) message
389
- ),
390
- output_parts AS (
391
- SELECT
392
- raw_parts.seq_in_session,
393
- raw_parts.part_index,
394
- raw_parts.part_text,
395
- CASE
396
- WHEN length(raw_parts.part_text) > ${MESSAGE_PART_PREVIEW_CHARS}
397
- THEN '...' || right(raw_parts.part_text, ${MESSAGE_PART_PREVIEW_CHARS})
398
- ELSE raw_parts.part_text
399
- END AS part_preview_text,
400
- length(raw_parts.part_text) > ${MESSAGE_PART_PREVIEW_CHARS} AS part_is_truncated
401
- FROM (
402
- SELECT
403
- output_messages.seq_in_session,
404
- CAST(part.key AS BIGINT) AS part_index,
405
- LOWER(COALESCE(json_extract_string(part.value, '$.type'), '')) AS part_type,
406
- part_text
407
- FROM output_messages
408
- LEFT JOIN json_each(
409
- CASE
410
- WHEN json_type(json_extract(output_messages.message_json, '$.parts')) = 'ARRAY'
411
- THEN json_extract(output_messages.message_json, '$.parts')
412
- WHEN json_type(json_extract(output_messages.message_json, '$.message.parts')) = 'ARRAY'
413
- THEN json_extract(output_messages.message_json, '$.message.parts')
414
- WHEN json_type(json_extract(output_messages.message_json, '$.content')) = 'ARRAY'
415
- THEN json_extract(output_messages.message_json, '$.content')
416
- WHEN json_type(json_extract(output_messages.message_json, '$.message.content')) = 'ARRAY'
417
- THEN json_extract(output_messages.message_json, '$.message.content')
418
- ELSE '[]'::JSON
419
- END
420
- ) part ON TRUE
421
- CROSS JOIN (
422
- SELECT COALESCE(
423
- NULLIF(json_extract_string(part.value, '$.text'), ''),
424
- NULLIF(json_extract_string(part.value, '$.content'), ''),
425
- ''
426
- ) AS part_text
427
- ) extracted
428
- ) raw_parts
429
- WHERE raw_parts.part_type NOT IN ('thinking', 'signature', 'tool_result')
430
- AND raw_parts.part_text <> ''
431
- ),
432
- output_expanded AS (
433
- SELECT
434
- output_messages.seq_in_session,
435
- output_messages.role,
436
- COALESCE(
437
- NULLIF(string_agg(output_parts.part_text, '\n' ORDER BY output_parts.part_index), ''),
438
- output_messages.fallback_text
439
- ) AS content_text,
440
- COALESCE(
441
- NULLIF(string_agg(output_parts.part_preview_text, '\n...\n' ORDER BY output_parts.part_index), ''),
442
- CASE
443
- WHEN length(output_messages.fallback_text) > ${MESSAGE_PART_PREVIEW_CHARS}
444
- THEN '...' || right(output_messages.fallback_text, ${MESSAGE_PART_PREVIEW_CHARS})
445
- ELSE output_messages.fallback_text
446
- END
447
- ) AS preview_text,
448
- CASE
449
- WHEN COUNT(output_parts.part_text) > 0 THEN COUNT(output_parts.part_text)
450
- WHEN output_messages.fallback_text <> '' THEN 1
451
- ELSE 0
452
- END AS preview_part_count,
453
- CASE
454
- WHEN COUNT(output_parts.part_text) > 0 THEN SUM(CASE WHEN output_parts.part_is_truncated THEN 1 ELSE 0 END)
455
- WHEN length(output_messages.fallback_text) > ${MESSAGE_PART_PREVIEW_CHARS} THEN 1
456
- ELSE 0
457
- END AS truncated_part_count,
458
- output_messages.ts,
459
- output_messages.turn_id,
460
- output_messages.trace_id
461
- FROM output_messages
462
- LEFT JOIN output_parts
463
- ON output_parts.seq_in_session = output_messages.seq_in_session
464
- GROUP BY
465
- output_messages.seq_in_session,
466
- output_messages.role,
467
- output_messages.fallback_text,
468
- output_messages.ts,
469
- output_messages.turn_id,
470
- output_messages.trace_id
471
- ),
472
- expanded AS (
473
- SELECT * FROM input_expanded
474
- UNION ALL
475
- SELECT * FROM output_expanded
476
- ),
477
- filtered AS (
478
- SELECT *
479
- FROM expanded
480
- WHERE role IN ('user', 'assistant')
481
- AND content_text <> ''
482
- ),
483
- last_rows AS (
484
- SELECT *
485
- FROM filtered
486
- ORDER BY seq_in_session DESC
487
- LIMIT ${limit}
488
- )
489
- SELECT
490
- seq_in_session,
491
- role,
492
- preview_text AS content_text,
493
- content_text AS artifact_content_text,
494
- length(content_text) AS content_chars,
495
- truncated_part_count > 0 AS is_truncated,
496
- preview_part_count,
497
- truncated_part_count,
498
- ts,
499
- turn_id,
500
- trace_id
501
- FROM last_rows
502
- ORDER BY seq_in_session ASC;
503
- " | MESSAGE_PREVIEW_CHARS="${MESSAGE_PREVIEW_CHARS}" MESSAGE_OUTPUT_BUDGET_CHARS="${MESSAGE_OUTPUT_BUDGET_CHARS}" MESSAGE_ARTIFACT_PATH="${artifact_path}" format_messages_rows
504
- return
505
- fi
506
-
507
- echo "Indexed analytics DB not found; falling back to trace parsing."
508
- query_session_messages "${session_id}" "${limit}"
509
- }
510
-
511
- query_session_tools() {
512
- local session_id="$1"
513
- local escaped_session_id
514
- escaped_session_id="$(escape_sql_literal "${session_id}")"
515
-
516
- if has_indexed_db; then
517
- echo "🔧 Session Tools: ${session_id}"
518
- echo "=============================="
519
- run_duckdb_sql "
520
- SELECT
521
- call_ts,
522
- tool_name,
523
- status,
524
- ROUND(COALESCE(duration_ms, 0), 2) AS duration_ms,
525
- LEFT(COALESCE(arguments_text, ''), 200) AS arguments_preview,
526
- LEFT(COALESCE(result_text, ''), 200) AS result_preview,
527
- COALESCE(error_message, '') AS error_message
528
- FROM tool_calls
529
- WHERE session_id = '${escaped_session_id}'
530
- ORDER BY call_ts;
531
- "
532
- return
533
- fi
534
-
535
- echo "Indexed analytics DB not found; tool-level session history is unavailable without index DB."
536
- exit 1
537
- }
538
-
539
- query_session_summary_fast() {
540
- local session_id="$1"
541
- local escaped_session_id
542
- escaped_session_id="$(escape_sql_literal "${session_id}")"
543
-
544
- if has_indexed_db; then
545
- echo "📊 Session Summary: ${session_id}"
546
- echo "==============================="
547
- run_duckdb_sql "
548
- SELECT
549
- session_id,
550
- COALESCE(agent_id, '') AS agent_id,
551
- COALESCE(agent_version, '') AS agent_version,
552
- COALESCE(engine, '') AS engine,
553
- COALESCE(status, '') AS status,
554
- started_at,
555
- ended_at,
556
- total_turns,
557
- total_requests,
558
- ROUND(COALESCE(total_cost_usd, 0), 6) AS total_cost_usd,
559
- COALESCE(total_tokens, 0) AS total_tokens
560
- FROM sessions
561
- WHERE session_id = '${escaped_session_id}'
562
- LIMIT 1;
563
- "
564
- return
565
- fi
566
-
567
- query_session "${session_id}"
568
- }
569
-
570
- query_latest_session_fast() {
571
- if has_indexed_db; then
572
- echo "📊 Latest Session"
573
- echo "================"
574
- run_duckdb_sql "
575
- SELECT
576
- session_id,
577
- COALESCE(agent_id, '') AS agent_id,
578
- COALESCE(agent_version, '') AS agent_version,
579
- COALESCE(engine, '') AS engine,
580
- COALESCE(status, '') AS status,
581
- started_at,
582
- ended_at,
583
- total_turns,
584
- total_requests,
585
- ROUND(COALESCE(total_cost_usd, 0), 6) AS total_cost_usd,
586
- COALESCE(total_tokens, 0) AS total_tokens
587
- FROM sessions
588
- ORDER BY ended_at DESC NULLS LAST, started_at DESC NULLS LAST
589
- LIMIT 1;
590
- "
591
- return
592
- fi
593
-
594
- query_latest_session
595
- }
596
-
597
- query_request_fast() {
598
- local request_id="$1"
599
- local escaped_request_id
600
- escaped_request_id="$(escape_sql_literal "${request_id}")"
601
-
602
- if has_indexed_db; then
603
- echo "📊 Request: ${request_id}"
604
- echo "========================="
605
- run_duckdb_sql "
606
- SELECT
607
- model,
608
- ROUND(COALESCE(total_cost_usd, 0), 6) AS cost,
609
- prompt_tokens AS input_tokens,
610
- completion_tokens AS output_tokens,
611
- ROUND(COALESCE(duration_ms, 0), 2) AS latency_ms,
612
- COALESCE(turn_id, '') AS turn_id,
613
- to_timestamp(start_ms / 1000.0) AS timestamp
614
- FROM model_calls
615
- WHERE request_id = '${escaped_request_id}'
616
- ORDER BY start_ms;
617
- "
618
- return
619
- fi
620
-
621
- query_request "${request_id}"
622
- }
623
-
624
- query_session_fast() {
625
- local session_id="$1"
626
- local escaped_session_id
627
- escaped_session_id="$(escape_sql_literal "${session_id}")"
628
-
629
- if has_indexed_db; then
630
- echo "📊 Session: ${session_id}"
631
- echo "========================="
632
- run_duckdb_sql "
633
- SELECT
634
- model,
635
- ROUND(COALESCE(total_cost_usd, 0), 6) as cost,
636
- prompt_tokens as input_tokens,
637
- completion_tokens as output_tokens,
638
- ROUND(COALESCE(duration_ms, 0), 2) as latency_ms,
639
- COALESCE(request_id, '') as request_id,
640
- to_timestamp(start_ms / 1000.0) as timestamp
641
- FROM model_calls
642
- WHERE session_id = '${escaped_session_id}'
643
- ORDER BY start_ms;
644
- "
645
- return
646
- fi
647
-
648
- query_session "${session_id}"
649
- }
650
-
651
- query_cost_by_session_fast() {
652
- if has_indexed_db; then
653
- echo "📊 Cost by Session"
654
- echo "=================="
655
- run_duckdb_sql "
656
- SELECT
657
- session_id,
658
- ROUND(COALESCE(total_cost_usd, 0), 6) as total_cost,
659
- COALESCE(total_tokens, 0) as total_tokens,
660
- total_requests as requests,
661
- total_turns as turns,
662
- started_at,
663
- ended_at
664
- FROM sessions
665
- ORDER BY total_cost DESC
666
- LIMIT 20;
667
- "
668
- return
669
- fi
670
-
671
- query_cost_by_session
672
- }
673
-
674
- query_cost_by_model_fast() {
675
- if has_indexed_db; then
676
- echo "📊 Cost by Model"
677
- echo "================"
678
- run_duckdb_sql "
679
- SELECT
680
- COALESCE(model, '(unknown)') as model,
681
- ROUND(SUM(COALESCE(total_cost_usd, 0)), 6) as total_cost,
682
- SUM(COALESCE(prompt_tokens, 0)) as input_tokens,
683
- SUM(COALESCE(completion_tokens, 0)) as output_tokens,
684
- COUNT(*) as requests,
685
- ROUND(AVG(COALESCE(duration_ms, 0)), 2) as avg_latency_ms
686
- FROM model_calls
687
- GROUP BY model
688
- ORDER BY total_cost DESC;
689
- "
690
- return
691
- fi
692
-
693
- query_cost_by_model
694
- }
695
-
696
- query_cost_by_pack_fast() {
697
- if has_indexed_db; then
698
- echo "📊 Cost by Agent"
699
- echo "================"
700
- run_duckdb_sql "
701
- SELECT
702
- COALESCE(agent_id, '(no agent)') AS agent,
703
- COALESCE(agent_version, '?') AS version,
704
- ROUND(SUM(COALESCE(total_cost_usd, 0)), 6) AS total_cost,
705
- SUM(COALESCE(prompt_tokens, 0)) AS input_tokens,
706
- SUM(COALESCE(completion_tokens, 0)) AS output_tokens,
707
- COUNT(*) AS requests
708
- FROM model_calls
709
- GROUP BY agent_id, agent_version
710
- ORDER BY total_cost DESC;
711
- "
712
- return
713
- fi
714
-
715
- query_cost_by_pack
716
- }
717
-
718
- query_cost_by_engine_fast() {
719
- if has_indexed_db; then
720
- echo "📊 Cost by Engine"
721
- echo "================="
722
- run_duckdb_sql "
723
- SELECT
724
- COALESCE(engine, '(unknown)') AS engine,
725
- COALESCE(model, '(unknown)') AS model,
726
- ROUND(SUM(COALESCE(total_cost_usd, 0)), 6) AS total_cost,
727
- COUNT(*) AS requests,
728
- SUM(COALESCE(prompt_tokens, 0)) AS input_tokens,
729
- SUM(COALESCE(completion_tokens, 0)) AS output_tokens
730
- FROM model_calls
731
- GROUP BY engine, model
732
- ORDER BY total_cost DESC;
733
- "
734
- return
735
- fi
736
-
737
- query_cost_by_engine
738
- }
739
-
740
- query_cost_by_source_fast() {
741
- if has_indexed_db; then
742
- echo "📊 Cost by Source"
743
- echo "================="
744
- run_duckdb_sql "
745
- SELECT
746
- COALESCE(source, '(unknown)') AS source,
747
- COALESCE(model, '(unknown)') AS model,
748
- ROUND(SUM(COALESCE(total_cost_usd, 0)), 6) AS total_cost,
749
- COUNT(*) AS requests,
750
- SUM(COALESCE(prompt_tokens, 0)) AS input_tokens,
751
- SUM(COALESCE(completion_tokens, 0)) AS output_tokens
752
- FROM model_calls
753
- GROUP BY source, model
754
- ORDER BY total_cost DESC;
755
- "
756
- return
757
- fi
758
-
759
- query_cost_by_source
760
- }
761
-
762
- query_total_cost_fast() {
763
- if has_indexed_db; then
764
- echo "📊 Total Cost Summary"
765
- echo "===================="
766
- run_duckdb_sql "
767
- SELECT
768
- ROUND(COALESCE(SUM(total_cost_usd), 0), 6) as total_cost,
769
- COALESCE(SUM(prompt_tokens), 0) as total_input_tokens,
770
- COALESCE(SUM(completion_tokens), 0) as total_output_tokens,
771
- COUNT(*) as total_requests,
772
- ROUND(AVG(COALESCE(duration_ms, 0)), 2) as avg_latency_ms
773
- FROM model_calls;
774
- "
775
- return
776
- fi
777
-
778
- query_total_cost
779
- }
780
-
781
- query_cost_per_request_fast() {
782
- local session_id="${1:-}"
783
- if has_indexed_db; then
784
- local where_clause="WHERE request_id IS NOT NULL"
785
- echo "📊 Cost per Request"
786
- echo "==================="
787
- if [[ -n "${session_id}" ]]; then
788
- where_clause="WHERE session_id = '$(escape_sql_literal "${session_id}")' AND request_id IS NOT NULL"
789
- echo " Session: ${session_id}"
790
- fi
791
- run_duckdb_sql "
792
- SELECT
793
- request_id,
794
- COUNT(*) AS llm_calls,
795
- ROUND(SUM(COALESCE(total_cost_usd, 0)), 6) AS request_cost,
796
- SUM(COALESCE(prompt_tokens, 0)) AS input_tokens,
797
- SUM(COALESCE(completion_tokens, 0)) AS output_tokens,
798
- ROUND(SUM(COALESCE(duration_ms, 0)), 0) AS total_latency_ms,
799
- MIN(to_timestamp(start_ms / 1000.0)) AS started_at
800
- FROM model_calls
801
- ${where_clause}
802
- GROUP BY request_id
803
- ORDER BY MIN(start_ms);
804
- "
805
- return
806
- fi
807
-
808
- query_cost_per_request "${session_id}"
809
- }
810
-
811
- query_session_timeline_fast() {
812
- local session_id="$1"
813
- local escaped_session_id
814
- escaped_session_id="$(escape_sql_literal "${session_id}")"
815
-
816
- if has_indexed_db; then
817
- echo "📊 Session Timeline: ${session_id}"
818
- run_duckdb_sql "
819
- SELECT
820
- to_timestamp(start_ms / 1000.0) AS event_ts,
821
- 'llm_call' AS event_type,
822
- '' AS role,
823
- COALESCE(model, '') AS model,
824
- '' AS tool_name,
825
- LEFT(COALESCE(request_id, ''), 220) AS detail,
826
- ROUND(COALESCE(total_cost_usd, 0), 6) AS cost,
827
- COALESCE(total_tokens, 0) AS total_tokens,
828
- COALESCE(status, '') AS status
829
- FROM model_calls
830
- WHERE session_id = '${escaped_session_id}'
831
-
832
- UNION ALL
833
-
834
- SELECT
835
- call_ts AS event_ts,
836
- 'tool_call' AS event_type,
837
- '' AS role,
838
- '' AS model,
839
- COALESCE(tool_name, '') AS tool_name,
840
- LEFT(COALESCE(arguments_text, ''), 220) AS detail,
841
- 0.0 AS cost,
842
- 0 AS total_tokens,
843
- COALESCE(status, '') AS status
844
- FROM tool_calls
845
- WHERE session_id = '${escaped_session_id}'
846
-
847
- ORDER BY event_ts;
848
- "
849
- return
850
- fi
851
-
852
- query_session_timeline "${session_id}"
853
- }
854
-
855
- query_compare_versions_fast() {
856
- local agent_name="$1"
857
- local escaped_agent_name
858
- escaped_agent_name="$(escape_sql_literal "${agent_name}")"
859
-
860
- if has_indexed_db; then
861
- echo "📊 Compare Versions: ${agent_name}"
862
- echo "===================================="
863
- run_duckdb_sql "
864
- SELECT
865
- agent_version AS version,
866
- COUNT(DISTINCT session_id) AS sessions,
867
- ROUND(AVG(session_cost), 4) AS avg_cost_per_session,
868
- ROUND(AVG(session_tokens), 0) AS avg_tokens_per_session,
869
- ROUND(AVG(session_latency), 0) AS avg_latency_ms,
870
- ROUND(AVG(requests_per_session), 1) AS avg_llm_calls
871
- FROM (
872
- SELECT
873
- agent_version,
874
- session_id,
875
- SUM(COALESCE(total_cost_usd, 0)) AS session_cost,
876
- SUM(COALESCE(prompt_tokens, 0) + COALESCE(completion_tokens, 0)) AS session_tokens,
877
- SUM(COALESCE(duration_ms, 0)) AS session_latency,
878
- COUNT(*) AS requests_per_session
879
- FROM model_calls
880
- WHERE agent_id = '${escaped_agent_name}'
881
- GROUP BY agent_version, session_id
882
- )
883
- GROUP BY agent_version
884
- ORDER BY agent_version DESC;
885
- "
886
- return
887
- fi
888
-
889
- query_compare_versions "${agent_name}"
890
- }
891
-
892
- query_top_sessions_fast() {
893
- local agent_name="$1"
894
- local since_ms="${2:-86400000}"
895
- local escaped_agent_name
896
- escaped_agent_name="$(escape_sql_literal "${agent_name}")"
897
-
898
- if has_indexed_db; then
899
- echo "📊 Top Sessions: ${agent_name}"
900
- echo "==============================="
901
- run_duckdb_sql "
902
- WITH recent AS (
903
- SELECT *
904
- FROM model_calls
905
- WHERE agent_id = '${escaped_agent_name}'
906
- AND start_ms > (EXTRACT(EPOCH FROM NOW()) * 1000 - ${since_ms})
907
- ),
908
- target AS (
909
- SELECT * FROM recent
910
- UNION ALL
911
- SELECT *
912
- FROM model_calls
913
- WHERE agent_id = '${escaped_agent_name}'
914
- AND NOT EXISTS (SELECT 1 FROM recent)
915
- )
916
- SELECT
917
- session_id,
918
- ROUND(SUM(COALESCE(total_cost_usd, 0)), 6) AS total_cost,
919
- SUM(COALESCE(prompt_tokens, 0)) AS input_tokens,
920
- SUM(COALESCE(completion_tokens, 0)) AS output_tokens,
921
- COUNT(*) AS llm_calls,
922
- COUNT(DISTINCT request_id) AS requests,
923
- ROUND(AVG(COALESCE(duration_ms, 0)), 0) AS avg_latency_ms,
924
- MIN(to_timestamp(start_ms / 1000.0)) AS started_at
925
- FROM target
926
- GROUP BY session_id
927
- ORDER BY total_cost DESC
928
- LIMIT 10;
929
- "
930
- return
931
- fi
932
-
933
- query_top_sessions "${agent_name}" "${since_ms}"
934
- }
935
-
936
- query_agent_kpis_daily() {
937
- if has_indexed_db; then
938
- echo "📊 Agent KPIs Daily"
939
- run_duckdb_sql "
940
- SELECT *
941
- FROM agent_kpis_daily
942
- ORDER BY day DESC, total_cost_usd DESC
943
- LIMIT 50;
944
- "
945
- return
946
- fi
947
-
948
- echo "Indexed analytics DB not found; daily KPI view unavailable without index DB."
949
- exit 1
950
- }
951
-
952
- query_training_examples() {
953
- echo "training_examples view has been retired in this refactor."
954
- echo "Use --session-last for message snapshots and --session-timeline for model/tool event chronology."
955
- exit 1
956
- }
957
-
958
- query_recent_messages() {
959
- local limit="${1:-20}"
960
- if ! [[ "${limit}" =~ ^[0-9]+$ ]]; then
961
- echo "Limit must be numeric"
962
- exit 1
963
- fi
964
-
965
- if has_indexed_db; then
966
- echo "📊 Recent Sessions (message snapshot source)"
967
- echo "==========================================="
968
- run_duckdb_sql "
969
- SELECT
970
- session_id,
971
- to_timestamp(MAX(start_ms) / 1000.0) AS latest_call_ts,
972
- COUNT(*) AS model_calls,
973
- ROUND(SUM(COALESCE(total_cost_usd, 0)), 6) AS total_cost,
974
- SUM(COALESCE(total_tokens, 0)) AS total_tokens
975
- FROM model_calls
976
- GROUP BY session_id
977
- ORDER BY latest_call_ts DESC
978
- LIMIT ${limit};
979
- "
980
- return
981
- fi
982
-
983
- echo "Indexed analytics DB not found; recent session query unavailable without index DB."
984
- exit 1
985
- }
986
-
987
- query_last_session_messages() {
988
- local limit="${1:-8}"
989
- if ! [[ "${limit}" =~ ^[0-9]+$ ]]; then
990
- echo "Limit must be numeric"
991
- exit 1
992
- fi
993
-
994
- if has_indexed_db; then
995
- local latest_session
996
- latest_session="$(${DUCKDB} -readonly "${INDEX_DB_PATH}" -csv -c "
997
- SELECT session_id
998
- FROM sessions
999
- ORDER BY ended_at DESC NULLS LAST, started_at DESC NULLS LAST
1000
- LIMIT 1;
1001
- " | tail -n 1)"
1002
-
1003
- if [[ -z "${latest_session}" ]]; then
1004
- echo "No sessions found in indexed DB."
1005
- exit 0
1006
- fi
1007
-
1008
- query_session_last_messages "${latest_session}" "${limit}"
1009
- return
1010
- fi
1011
-
1012
- local latest_session
1013
- latest_session="$(${DUCKDB} -csv -c "
1014
- ${BASE_QUERY},
1015
- extracted AS (${EXTRACT_ATTRS})
1016
- SELECT session_id
1017
- FROM extracted
1018
- WHERE session_id IS NOT NULL
1019
- ORDER BY start_ms DESC
1020
- LIMIT 1;
1021
- " | tail -n 1)"
1022
- if [[ -z "${latest_session}" ]]; then
1023
- echo "No sessions found in traces."
1024
- exit 0
1025
- fi
1026
- query_session_messages "${latest_session}" "${limit}"
1027
- }
1028
-
1029
- query_session_debug() {
1030
- local session_id="$1"
1031
- local escaped_session_id
1032
- escaped_session_id="$(escape_sql_literal "${session_id}")"
1033
-
1034
- echo "📊 Session Debug: ${session_id}"
1035
- echo "=============================="
1036
-
1037
- if has_indexed_db; then
1038
- echo ""
1039
- echo "Summary"
1040
- echo "-------"
1041
- run_duckdb_sql "
1042
- SELECT
1043
- session_id,
1044
- COALESCE(agent_id, '') AS agent_id,
1045
- COALESCE(agent_version, '') AS agent_version,
1046
- COALESCE(engine, '') AS engine,
1047
- COALESCE(status, '') AS status,
1048
- started_at,
1049
- ended_at,
1050
- total_turns,
1051
- total_requests,
1052
- ROUND(COALESCE(total_cost_usd, 0), 6) AS total_cost_usd,
1053
- COALESCE(total_tokens, 0) AS total_tokens
1054
- FROM sessions
1055
- WHERE session_id = '${escaped_session_id}'
1056
- LIMIT 1;
1057
- "
1058
-
1059
- echo ""
1060
- echo "Recent Requests"
1061
- echo "---------------"
1062
- run_duckdb_sql "
1063
- SELECT
1064
- COALESCE(request_id, '') AS request_id,
1065
- COUNT(*) AS llm_calls,
1066
- ROUND(SUM(COALESCE(total_cost_usd, 0)), 6) AS total_cost_usd,
1067
- SUM(COALESCE(prompt_tokens, 0) + COALESCE(completion_tokens, 0)) AS total_tokens,
1068
- SUM(CASE WHEN lower(COALESCE(status, '')) IN ('error', 'failed', 'failure') THEN 1 ELSE 0 END) AS error_calls,
1069
- MAX(to_timestamp(start_ms / 1000.0)) AS latest_call_ts
1070
- FROM model_calls
1071
- WHERE session_id = '${escaped_session_id}'
1072
- GROUP BY request_id
1073
- ORDER BY latest_call_ts DESC
1074
- LIMIT ${SESSION_DEBUG_REQUEST_LIMIT};
1075
- "
1076
-
1077
- if has_indexed_table "tool_calls"; then
1078
- echo ""
1079
- echo "Tool Signals"
1080
- echo "------------"
1081
- run_duckdb_sql "
1082
- SELECT
1083
- COALESCE(request_id, '') AS request_id,
1084
- COALESCE(tool_name, '') AS tool_name,
1085
- COUNT(*) AS calls,
1086
- SUM(CASE WHEN lower(COALESCE(status, '')) IN ('error', 'failed', 'failure') THEN 1 ELSE 0 END) AS error_calls,
1087
- ROUND(AVG(COALESCE(duration_ms, 0)), 2) AS avg_duration_ms,
1088
- MAX(call_ts) AS latest_call_ts
1089
- FROM tool_calls
1090
- WHERE session_id = '${escaped_session_id}'
1091
- GROUP BY request_id, tool_name
1092
- ORDER BY error_calls DESC, calls DESC, latest_call_ts DESC
1093
- LIMIT ${SESSION_DEBUG_TOOL_LIMIT};
1094
- "
1095
- fi
1096
-
1097
- echo ""
1098
- query_session_last_messages "${session_id}" 4
1099
- echo ""
1100
- echo "Next"
1101
- echo "----"
1102
- echo "Use --request <request-id> for one request, --session-tools ${session_id} for tool details, or --session-timeline ${session_id} for chronology."
1103
- return
1104
- fi
1105
-
1106
- echo ""
1107
- echo "Indexed analytics DB not found; using trace/session-file fallback."
1108
- echo ""
1109
- query_session "${session_id}"
1110
- echo ""
1111
- if [[ -f "${SESSIONS_DIR}/${session_id}.jsonl" ]]; then
1112
- query_tool_loops "${session_id}"
1113
- echo ""
1114
- fi
1115
- query_session_messages "${session_id}" 4
1116
- echo ""
1117
- echo "Next"
1118
- echo "----"
1119
- echo "Use --session ${session_id} for all LLM calls or --session-timeline ${session_id} for chronology."
1120
- }
1121
-
1122
- # Base query to parse OTEL JSONL format
1123
- # Note: OTEL Collector file exporter writes one JSON object per line
1124
- BASE_QUERY="
1125
- WITH raw_spans AS (
1126
- SELECT
1127
- unnest(resourceSpans) as rs
1128
- FROM read_json('${TRACES_DIR}/*.jsonl',
1129
- format='newline_delimited',
1130
- ignore_errors=true)
1131
- ),
1132
- spans AS (
1133
- SELECT
1134
- unnest(rs.scopeSpans) as ss
1135
- FROM raw_spans
1136
- ),
1137
- flat_spans AS (
1138
- SELECT
1139
- unnest(ss.spans) as span
1140
- FROM spans
1141
- ),
1142
- parsed AS (
1143
- SELECT
1144
- span.traceId as trace_id,
1145
- span.spanId as span_id,
1146
- span.name as span_name,
1147
- span.startTimeUnixNano::BIGINT / 1000000 as start_ms,
1148
- span.endTimeUnixNano::BIGINT / 1000000 as end_ms,
1149
- (span.endTimeUnixNano::BIGINT - span.startTimeUnixNano::BIGINT) / 1000000 as duration_ms,
1150
- json_extract(to_json(span), '$.attributes') as attrs_json
1151
- FROM flat_spans
1152
- ),
1153
- span_filter AS (
1154
- SELECT
1155
- trace_id,
1156
- span_name,
1157
- duration_ms,
1158
- start_ms,
1159
- attrs_json
1160
- FROM parsed
1161
- WHERE span_name = 'Received Proxy Server Request'
1162
- OR EXISTS (
1163
- SELECT 1
1164
- FROM json_each(coalesce(attrs_json, '[]'::JSON)) as attr
1165
- WHERE json_extract_string(attr.value, '$.key') = 'gen_ai.cost.total_cost'
1166
- )
1167
- ),
1168
- attrs AS (
1169
- SELECT
1170
- trace_id,
1171
- span_name,
1172
- duration_ms,
1173
- start_ms,
1174
- json_extract_string(value, '$.key') as attr_key,
1175
- json_extract_string(value, '$.value.stringValue') as attr_string,
1176
- json_extract_string(value, '$.value.intValue') as attr_int,
1177
- json_extract(value, '$.value.doubleValue') as attr_double
1178
- FROM span_filter,
1179
- json_each(coalesce(attrs_json, '[]'::JSON))
1180
- )
1181
- "
1182
-
1183
- # Extract LLM attributes from OTEL span attributes
1184
- # OTEL attributes are stored as array of {key, value} objects
1185
- # Canonical metadata source: metadata.requester_custom_headers (x-litellm-metadata-*)
1186
- # Enriched with: session_id, user_id, project_id, agent_id, agent_version, source, engine, request_id
1187
- EXTRACT_ATTRS="
1188
- WITH span_rows AS (
1189
- SELECT
1190
- trace_id,
1191
- span_name,
1192
- duration_ms,
1193
- start_ms,
1194
- NULLIF(regexp_extract(
1195
- max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END),
1196
- 'x-litellm-metadata-session-id'': ''([^'']+)''', 1
1197
- ), '') as session_id,
1198
- NULLIF(regexp_extract(
1199
- max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END),
1200
- 'x-litellm-metadata-user-id'': ''([^'']+)''', 1
1201
- ), '') as user_id,
1202
- NULLIF(regexp_extract(
1203
- max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END),
1204
- 'x-litellm-metadata-project-id'': ''([^'']+)''', 1
1205
- ), '') as project_id,
1206
- max(CASE WHEN attr_key IN ('gen_ai.request.model', 'gen_ai.response.model', 'model') THEN attr_string END) as model,
1207
- max(CASE WHEN attr_key = 'gen_ai.cost.total_cost' THEN CAST(attr_double AS DOUBLE) END) as cost,
1208
- max(CASE WHEN attr_key = 'gen_ai.usage.input_tokens' THEN CAST(attr_int AS BIGINT) END) as input_tokens,
1209
- max(CASE WHEN attr_key = 'gen_ai.usage.output_tokens' THEN CAST(attr_int AS BIGINT) END) as output_tokens,
1210
- NULLIF(regexp_extract(
1211
- max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END),
1212
- 'x-litellm-metadata-agent-id'': ''([^'']+)''', 1
1213
- ), '') as agent_id,
1214
- NULLIF(regexp_extract(
1215
- max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END),
1216
- 'x-litellm-metadata-agent-version'': ''([^'']+)''', 1
1217
- ), '') as agent_version,
1218
- NULLIF(regexp_extract(
1219
- max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END),
1220
- 'x-litellm-metadata-source'': ''([^'']+)''', 1
1221
- ), '') as source,
1222
- NULLIF(regexp_extract(
1223
- max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END),
1224
- 'x-litellm-metadata-engine'': ''([^'']+)''', 1
1225
- ), '') as engine,
1226
- NULLIF(regexp_extract(
1227
- max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END),
1228
- 'x-litellm-metadata-request-id'': ''([^'']+)''', 1
1229
- ), '') as request_id
1230
- FROM attrs
1231
- GROUP BY trace_id, span_name, duration_ms, start_ms
1232
- ),
1233
- deduped AS (
1234
- SELECT DISTINCT ON (trace_id)
1235
- trace_id,
1236
- span_name,
1237
- duration_ms,
1238
- start_ms,
1239
- session_id,
1240
- user_id,
1241
- project_id,
1242
- model,
1243
- cost,
1244
- input_tokens,
1245
- output_tokens,
1246
- agent_id,
1247
- agent_version,
1248
- source,
1249
- engine,
1250
- request_id
1251
- FROM span_rows
1252
- ORDER BY
1253
- trace_id,
1254
- COALESCE(cost, 0) DESC,
1255
- CASE
1256
- WHEN span_name = 'raw_gen_ai_request' THEN 0
1257
- WHEN span_name = 'litellm_request' THEN 1
1258
- ELSE 2
1259
- END,
1260
- start_ms DESC
1261
- )
1262
- SELECT * FROM deduped
1263
- "
1264
-
1265
- # ============================================================================
1266
- # Query functions
1267
- # ============================================================================
1268
-
1269
- query_total_cost() {
1270
- check_traces
1271
- echo "📊 Total Cost Summary"
1272
- echo "===================="
1273
- ${DUCKDB} -c "
1274
- ${BASE_QUERY},
1275
- extracted AS (${EXTRACT_ATTRS})
1276
- SELECT
1277
- COALESCE(SUM(cost), 0) as total_cost,
1278
- COALESCE(SUM(input_tokens), 0) as total_input_tokens,
1279
- COALESCE(SUM(output_tokens), 0) as total_output_tokens,
1280
- COUNT(*) as total_requests,
1281
- ROUND(AVG(duration_ms), 0) as avg_latency_ms
1282
- FROM extracted;
1283
- "
1284
- }
1285
-
1286
- query_cost_by_session() {
1287
- check_traces
1288
- echo "📊 Cost by Session"
1289
- echo "=================="
1290
- ${DUCKDB} -c "
1291
- ${BASE_QUERY},
1292
- extracted AS (${EXTRACT_ATTRS})
1293
- SELECT
1294
- COALESCE(session_id, '(no session)') as session_id,
1295
- ROUND(COALESCE(SUM(cost), 0), 6) as total_cost,
1296
- COALESCE(SUM(input_tokens), 0) as input_tokens,
1297
- COALESCE(SUM(output_tokens), 0) as output_tokens,
1298
- COUNT(*) as requests,
1299
- ROUND(AVG(duration_ms), 0) as avg_latency_ms
1300
- FROM extracted
1301
- GROUP BY session_id
1302
- ORDER BY total_cost DESC
1303
- LIMIT 20;
1304
- "
1305
- }
1306
-
1307
- query_cost_by_model() {
1308
- check_traces
1309
- echo "📊 Cost by Model"
1310
- echo "================"
1311
- ${DUCKDB} -c "
1312
- ${BASE_QUERY},
1313
- extracted AS (${EXTRACT_ATTRS})
1314
- SELECT
1315
- COALESCE(model, '(unknown)') as model,
1316
- ROUND(COALESCE(SUM(cost), 0), 6) as total_cost,
1317
- COALESCE(SUM(input_tokens), 0) as input_tokens,
1318
- COALESCE(SUM(output_tokens), 0) as output_tokens,
1319
- COUNT(*) as requests,
1320
- ROUND(AVG(duration_ms), 0) as avg_latency_ms
1321
- FROM extracted
1322
- GROUP BY model
1323
- ORDER BY total_cost DESC;
1324
- "
1325
- }
1326
-
1327
- query_cost_by_pack() {
1328
- check_traces
1329
- echo "📊 Cost by Agent"
1330
- echo "================"
1331
- ${DUCKDB} -c "
1332
- ${BASE_QUERY},
1333
- extracted AS (${EXTRACT_ATTRS})
1334
- SELECT
1335
- COALESCE(agent_id, '(no agent)') as agent,
1336
- COALESCE(agent_version, '?') as version,
1337
- ROUND(COALESCE(SUM(cost), 0), 6) as total_cost,
1338
- COALESCE(SUM(input_tokens), 0) as input_tokens,
1339
- COALESCE(SUM(output_tokens), 0) as output_tokens,
1340
- COUNT(*) as requests
1341
- FROM extracted
1342
- GROUP BY agent_id, agent_version
1343
- ORDER BY total_cost DESC;
1344
- "
1345
- }
1346
-
1347
- query_cost_by_engine() {
1348
- check_traces
1349
- echo "📊 Cost by Engine"
1350
- echo "================="
1351
- ${DUCKDB} -c "
1352
- ${BASE_QUERY},
1353
- extracted AS (${EXTRACT_ATTRS})
1354
- SELECT
1355
- COALESCE(engine, '(unknown)') as engine,
1356
- model,
1357
- ROUND(SUM(cost), 6) as total_cost,
1358
- COUNT(*) as requests,
1359
- ROUND(AVG(duration_ms), 0) as avg_latency_ms
1360
- FROM extracted
1361
- GROUP BY engine, model
1362
- ORDER BY total_cost DESC;
1363
- "
1364
- }
1365
-
1366
- query_cost_by_source() {
1367
- check_traces
1368
- echo "📊 Cost by Source Framework"
1369
- echo "=========================="
1370
- ${DUCKDB} -c "
1371
- ${BASE_QUERY},
1372
- extracted AS (${EXTRACT_ATTRS})
1373
- SELECT
1374
- COALESCE(source, '(unknown)') as framework,
1375
- ROUND(SUM(cost), 6) as total_cost,
1376
- COUNT(DISTINCT session_id) as sessions,
1377
- COUNT(*) as requests,
1378
- ROUND(AVG(cost), 6) as avg_cost_per_request
1379
- FROM extracted
1380
- GROUP BY source
1381
- ORDER BY total_cost DESC;
1382
- "
1383
- }
1384
-
1385
- query_cost_per_request() {
1386
- local session_id="${1:-}"
1387
- check_traces
1388
- echo "📊 Cost per Request"
1389
- echo "==================="
1390
- local where_clause=""
1391
- if [[ -n "${session_id}" ]]; then
1392
- where_clause="WHERE session_id = '${session_id}' AND request_id IS NOT NULL"
1393
- echo " Session: ${session_id}"
1394
- else
1395
- where_clause="WHERE request_id IS NOT NULL"
1396
- fi
1397
- ${DUCKDB} -c "
1398
- ${BASE_QUERY},
1399
- extracted AS (${EXTRACT_ATTRS})
1400
- SELECT
1401
- request_id,
1402
- COUNT(*) as llm_calls,
1403
- ROUND(COALESCE(SUM(cost), 0), 6) as request_cost,
1404
- COALESCE(SUM(input_tokens), 0) as input_tokens,
1405
- COALESCE(SUM(output_tokens), 0) as output_tokens,
1406
- ROUND(COALESCE(SUM(duration_ms), 0), 0) as total_latency_ms,
1407
- MIN(to_timestamp(start_ms / 1000)) as started_at
1408
- FROM extracted
1409
- ${where_clause}
1410
- GROUP BY request_id
1411
- ORDER BY MIN(start_ms);
1412
- "
1413
- }
1414
-
1415
- query_session() {
1416
- local session_id="$1"
1417
- check_traces
1418
- echo "📊 Session: ${session_id}"
1419
- echo "========================="
1420
- ${DUCKDB} -c "
1421
- ${BASE_QUERY},
1422
- extracted AS (${EXTRACT_ATTRS})
1423
- SELECT
1424
- model,
1425
- ROUND(cost, 6) as cost,
1426
- input_tokens,
1427
- output_tokens,
1428
- duration_ms as latency_ms,
1429
- COALESCE(request_id, '') as request_id,
1430
- to_timestamp(start_ms / 1000) as timestamp
1431
- FROM extracted
1432
- WHERE session_id = '${session_id}'
1433
- ORDER BY start_ms;
1434
- "
1435
- }
1436
-
1437
- query_request() {
1438
- local request_id="$1"
1439
- check_traces
1440
- echo "📊 Request: ${request_id}"
1441
- echo "========================="
1442
- ${DUCKDB} -c "
1443
- ${BASE_QUERY},
1444
- extracted AS (${EXTRACT_ATTRS})
1445
- SELECT
1446
- model,
1447
- ROUND(cost, 6) as cost,
1448
- input_tokens,
1449
- output_tokens,
1450
- duration_ms as latency_ms,
1451
- to_timestamp(start_ms / 1000) as timestamp
1452
- FROM extracted
1453
- WHERE request_id = '${request_id}'
1454
- ORDER BY start_ms;
1455
- "
1456
- }
1457
-
1458
- query_session_messages() {
1459
- local session_id="$1"
1460
- local limit="${2:-${TRACE_EVENT_PREVIEW_LIMIT}}"
1461
- local artifact_path
1462
- check_traces
1463
- if ! [[ "${limit}" =~ ^[0-9]+$ ]]; then
1464
- echo "Limit must be numeric"
1465
- exit 1
1466
- fi
1467
- artifact_path="$(make_artifact_path "session-trace-$(sanitize_artifact_label "${session_id}")" "txt")"
1468
- echo "📊 Session Messages (trace parse, safe preview): ${session_id}"
1469
- echo "================================================"
1470
- python3 - "${TRACES_DIR}" "${session_id}" "${limit}" "${MESSAGE_PREVIEW_CHARS}" "${MESSAGE_OUTPUT_BUDGET_CHARS}" "${artifact_path}" <<'PY'
1471
- import hashlib
1472
- import json
1473
- import os
1474
- import pathlib
1475
- import re
1476
- import sys
1477
- import textwrap
1478
- from datetime import datetime, timezone
1479
-
1480
- traces_dir = pathlib.Path(sys.argv[1])
1481
- target_session = sys.argv[2]
1482
- limit = max(1, int(sys.argv[3]))
1483
- preview_chars = max(80, int(sys.argv[4]))
1484
- output_budget = max(1000, int(sys.argv[5]))
1485
- artifact_path = sys.argv[6]
1486
-
1487
- session_patterns = [
1488
- re.compile(r"x-litellm-metadata-session-id'?:\s*'([^']+)'"),
1489
- re.compile(r'"x-litellm-metadata-session-id"\s*:\s*"([^"]+)"'),
1490
- ]
1491
- request_patterns = [
1492
- re.compile(r"x-litellm-metadata-request-id'?:\s*'([^']+)'"),
1493
- re.compile(r'"x-litellm-metadata-request-id"\s*:\s*"([^"]+)"'),
1494
- ]
1495
-
1496
- input_keys = {
1497
- "gen_ai.input.messages",
1498
- "llm.None.messages",
1499
- "llm.openai.messages",
1500
- "llm.anthropic.messages",
1501
- }
1502
- output_keys = {
1503
- "gen_ai.output.messages",
1504
- "llm.openai.choices",
1505
- "llm.anthropic.completion",
1506
- }
1507
-
1508
-
1509
- def attr_string_value(attr):
1510
- value = (attr or {}).get("value", {})
1511
- if "stringValue" in value:
1512
- return value.get("stringValue")
1513
- if "intValue" in value:
1514
- return str(value.get("intValue"))
1515
- if "doubleValue" in value:
1516
- return str(value.get("doubleValue"))
1517
- if "boolValue" in value:
1518
- return "true" if value.get("boolValue") else "false"
1519
- return ""
1520
-
1521
-
1522
- def extract_header_value(raw, patterns):
1523
- if not isinstance(raw, str):
1524
- return ""
1525
- for pattern in patterns:
1526
- match = pattern.search(raw)
1527
- if match:
1528
- return match.group(1)
1529
- return ""
1530
-
1531
-
1532
- def extract_text_parts(msg):
1533
- if isinstance(msg, str):
1534
- return [msg]
1535
- if not isinstance(msg, dict):
1536
- return [str(msg)]
1537
-
1538
- if "message" in msg:
1539
- return extract_text_parts(msg["message"])
1540
-
1541
- if "choices" in msg and isinstance(msg["choices"], list):
1542
- collected = []
1543
- for choice in msg["choices"]:
1544
- collected.extend(extract_text_parts(choice))
1545
- return collected
1546
-
1547
- for key in ("parts", "content"):
1548
- value = msg.get(key)
1549
- if isinstance(value, str):
1550
- return [value]
1551
- if isinstance(value, list):
1552
- collected = []
1553
- for item in value:
1554
- if isinstance(item, dict):
1555
- item_type = item.get("type", "")
1556
- if item_type in ("thinking", "signature", "tool_result"):
1557
- continue
1558
- text = item.get("text", item.get("content", ""))
1559
- if text:
1560
- collected.append(str(text))
1561
- continue
1562
- collected.extend(extract_text_parts(item))
1563
- elif isinstance(item, str):
1564
- collected.append(item)
1565
- if collected:
1566
- return collected
1567
-
1568
- text = msg.get("text")
1569
- if isinstance(text, str) and text:
1570
- return [text]
1571
-
1572
- content = msg.get("content")
1573
- if isinstance(content, str) and content:
1574
- return [content]
1575
-
1576
- return [str(msg)]
1577
-
1578
-
1579
- def build_part_tail_preview(parts):
1580
- source_parts = [str(part) for part in parts if str(part)]
1581
- if not source_parts:
1582
- return "", "", 0, 0
1583
- part_preview_chars = max(80, min(400, preview_chars))
1584
- preview_parts = []
1585
- truncated_part_count = 0
1586
- for part in source_parts:
1587
- if len(part) > part_preview_chars:
1588
- preview_parts.append("..." + part[-part_preview_chars:])
1589
- truncated_part_count += 1
1590
- else:
1591
- preview_parts.append(part)
1592
- return "\n".join(source_parts), "\n...\n".join(preview_parts), len(source_parts), truncated_part_count
1593
-
1594
-
1595
- def format_text(content, indent=4, width=100):
1596
- if content is None:
1597
- content = ""
1598
- content = str(content)
1599
- prefix = " " * indent
1600
- return textwrap.fill(content, width=width, initial_indent=prefix, subsequent_indent=prefix)
1601
-
1602
-
1603
- def iter_payload_messages(payload, kind):
1604
- try:
1605
- parsed = json.loads(payload)
1606
- except Exception:
1607
- parsed = None
1608
-
1609
- if isinstance(parsed, list):
1610
- for item in parsed:
1611
- yield item
1612
- return
1613
- if isinstance(parsed, dict) and isinstance(parsed.get("choices"), list):
1614
- for item in parsed["choices"]:
1615
- yield item
1616
- return
1617
- if parsed is not None:
1618
- yield parsed
1619
- return
1620
- yield payload
1621
-
1622
-
1623
- messages = []
1624
- seen_payloads = set()
1625
- message_seq = 0
1626
- for file_path in sorted(traces_dir.glob("*.jsonl")):
1627
- try:
1628
- with file_path.open("r", encoding="utf-8", errors="ignore") as handle:
1629
- for line in handle:
1630
- line = line.strip()
1631
- if not line:
1632
- continue
1633
- try:
1634
- root = json.loads(line)
1635
- except Exception:
1636
- continue
1637
-
1638
- for resource_span in root.get("resourceSpans", []) or []:
1639
- for scope_span in resource_span.get("scopeSpans", []) or []:
1640
- for span in scope_span.get("spans", []) or []:
1641
- attrs = span.get("attributes", []) or []
1642
- headers = ""
1643
- model = ""
1644
- input_payloads = []
1645
- output_payloads = []
1646
-
1647
- for attr in attrs:
1648
- key = attr.get("key")
1649
- value = attr_string_value(attr)
1650
- if key == "metadata.requester_custom_headers" and isinstance(value, str):
1651
- headers = value
1652
- elif key in ("gen_ai.request.model", "gen_ai.response.model", "model") and value:
1653
- model = str(value)
1654
- elif key in input_keys and value:
1655
- input_payloads.append(str(value))
1656
- elif key in output_keys and value:
1657
- output_payloads.append(str(value))
1658
-
1659
- session_id = extract_header_value(headers, session_patterns)
1660
- if session_id != target_session:
1661
- continue
1662
-
1663
- request_id = extract_header_value(headers, request_patterns)
1664
- try:
1665
- start_ns = int(span.get("startTimeUnixNano") or 0)
1666
- except Exception:
1667
- start_ns = 0
1668
-
1669
- trace_id = span.get("traceId", "")
1670
- timestamp = datetime.fromtimestamp(start_ns / 1_000_000_000, tz=timezone.utc).isoformat() if start_ns else "(unknown)"
1671
- for kind, payloads in (("input", input_payloads), ("output", output_payloads)):
1672
- for payload in payloads:
1673
- digest = hashlib.sha1(payload.encode("utf-8", errors="ignore")).hexdigest()
1674
- request_key = request_id or trace_id
1675
- dedupe_key = (kind, request_key, digest)
1676
- if dedupe_key in seen_payloads:
1677
- continue
1678
- seen_payloads.add(dedupe_key)
1679
- for raw_message in iter_payload_messages(payload, kind):
1680
- role = "assistant" if kind == "output" else "user"
1681
- if isinstance(raw_message, dict):
1682
- role = str(
1683
- raw_message.get("role")
1684
- or raw_message.get("message", {}).get("role")
1685
- or role
1686
- ).lower()
1687
- content_parts = extract_text_parts(raw_message)
1688
- content, preview, preview_part_count, truncated_part_count = build_part_tail_preview(content_parts)
1689
- if role not in ("user", "assistant") or not content:
1690
- continue
1691
- message_seq += 1
1692
- messages.append(
1693
- {
1694
- "role": role,
1695
- "ts": timestamp,
1696
- "request_id": request_id,
1697
- "model": model or "(unknown)",
1698
- "start_ns": start_ns,
1699
- "seq": message_seq,
1700
- "content_text": content,
1701
- "content_preview": preview,
1702
- "content_chars": len(content),
1703
- "is_truncated": truncated_part_count > 0,
1704
- "preview_part_count": preview_part_count,
1705
- "truncated_part_count": truncated_part_count,
1706
- }
1707
- )
1708
- except Exception:
1709
- continue
1710
-
1711
- if not messages:
1712
- print("(no trace messages found for this session)")
1713
- raise SystemExit(0)
1714
-
1715
- messages.sort(key=lambda row: (row["start_ns"], row["seq"]))
1716
- selected = messages[-limit:]
1717
-
1718
- def render_row(row, index, full=False):
1719
- header = (
1720
- f"\n[{index}] {row['ts']} | {row['role'].upper()} | model={row['model']}"
1721
- + (f" | request_id={row['request_id']}" if row["request_id"] else "")
1722
- )
1723
- body = format_text(row["content_text"] if full else row["content_preview"])
1724
- suffix = ""
1725
- if row["is_truncated"] and not full:
1726
- if row.get("preview_part_count", 1) > 1:
1727
- suffix = (
1728
- f"\n [per-part tail preview: {row['preview_part_count']} parts, "
1729
- f"{row.get('truncated_part_count', 0)} truncated, {row['content_chars']} chars total]"
1730
- )
1731
- else:
1732
- visible_chars = len(row["content_preview"]) - 3 if row["content_preview"].startswith("...") else len(row["content_preview"])
1733
- suffix = f"\n [tail preview truncated: showing last {visible_chars} of {row['content_chars']} chars]"
1734
- return f"{header}\n{body}{suffix}"
1735
-
1736
- rendered_selected = [render_row(row, idx) for idx, row in enumerate(selected, start=1)]
1737
- artifact_needed = (
1738
- len(messages) > len(selected)
1739
- or any(row["is_truncated"] for row in messages)
1740
- )
1741
- if artifact_needed:
1742
- with open(artifact_path, "w", encoding="utf-8") as handle:
1743
- handle.write("Spotlight Query trace message artifact\n")
1744
- handle.write(f"messages={len(messages)} selected={len(selected)}\n")
1745
- for idx, row in enumerate(messages, start=1):
1746
- handle.write(render_row(row, idx, full=True))
1747
- handle.write("\n")
1748
-
1749
- printed = 0
1750
- consumed = 0
1751
- for block in rendered_selected:
1752
- projected = consumed + len(block)
1753
- if printed > 0 and projected > output_budget:
1754
- break
1755
- print(block)
1756
- consumed = projected
1757
- printed += 1
1758
-
1759
- omitted_from_selection = len(rendered_selected) - printed
1760
- if len(messages) > len(selected):
1761
- print(f"\nShowing last {len(selected)} of {len(messages)} extracted user/assistant messages.")
1762
- if omitted_from_selection > 0:
1763
- print(f"Output clipped after {printed} rows to stay within {output_budget} chars.")
1764
- if artifact_needed:
1765
- print(f"Artifact: {artifact_path}")
1766
- PY
1767
- }
1768
-
1769
- query_compare_versions() {
1770
- local agent_name="$1"
1771
- check_traces
1772
- echo "📊 Compare Versions: ${agent_name}"
1773
- echo "===================================="
1774
- ${DUCKDB} -c "
1775
- ${BASE_QUERY},
1776
- extracted AS (${EXTRACT_ATTRS})
1777
- SELECT
1778
- agent_version as version,
1779
- COUNT(DISTINCT session_id) as sessions,
1780
- ROUND(AVG(session_cost), 4) as avg_cost_per_session,
1781
- ROUND(AVG(session_tokens), 0) as avg_tokens_per_session,
1782
- ROUND(AVG(session_latency), 0) as avg_latency_ms,
1783
- ROUND(AVG(requests_per_session), 1) as avg_llm_calls
1784
- FROM (
1785
- SELECT
1786
- agent_version,
1787
- session_id,
1788
- SUM(cost) as session_cost,
1789
- SUM(COALESCE(input_tokens, 0) + COALESCE(output_tokens, 0)) as session_tokens,
1790
- SUM(duration_ms) as session_latency,
1791
- COUNT(*) as requests_per_session
1792
- FROM extracted
1793
- WHERE agent_id = '${agent_name}'
1794
- GROUP BY agent_version, session_id
1795
- )
1796
- GROUP BY agent_version
1797
- ORDER BY agent_version DESC;
1798
- "
1799
- }
1800
-
1801
- query_top_sessions() {
1802
- local agent_name="$1"
1803
- local since_ms="${2:-86400000}" # default: 24h in ms
1804
- check_traces
1805
- echo "📊 Top Sessions: ${agent_name}"
1806
- echo "==============================="
1807
- ${DUCKDB} -c "
1808
- ${BASE_QUERY},
1809
- extracted AS (${EXTRACT_ATTRS})
1810
- SELECT
1811
- session_id,
1812
- ROUND(SUM(cost), 6) as total_cost,
1813
- COALESCE(SUM(input_tokens), 0) as input_tokens,
1814
- COALESCE(SUM(output_tokens), 0) as output_tokens,
1815
- COUNT(*) as llm_calls,
1816
- COUNT(DISTINCT request_id) as requests,
1817
- ROUND(AVG(duration_ms), 0) as avg_latency_ms,
1818
- MIN(to_timestamp(start_ms / 1000)) as started_at
1819
- FROM extracted
1820
- WHERE agent_id = '${agent_name}'
1821
- AND start_ms > (EXTRACT(EPOCH FROM NOW()) * 1000 - ${since_ms})
1822
- GROUP BY session_id
1823
- ORDER BY total_cost DESC
1824
- LIMIT 10;
1825
- "
1826
- }
1827
-
1828
- query_latency_percentiles() {
1829
- check_traces
1830
- echo "📊 Latency Percentiles (by model and agent)"
1831
- echo "============================================"
1832
- ${DUCKDB} -c "
1833
- ${BASE_QUERY},
1834
- extracted AS (${EXTRACT_ATTRS})
1835
- SELECT
1836
- COALESCE(agent_id, '(default)') as agent,
1837
- model,
1838
- COUNT(*) as requests,
1839
- ROUND(PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY duration_ms), 0) as p50_ms,
1840
- ROUND(PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY duration_ms), 0) as p95_ms,
1841
- ROUND(AVG(duration_ms), 0) as avg_ms,
1842
- MAX(duration_ms) as max_ms
1843
- FROM extracted
1844
- GROUP BY agent_id, model
1845
- ORDER BY p95_ms DESC;
1846
- "
1847
- }
1848
-
1849
- query_errors() {
1850
- check_traces
1851
- echo "📊 Error Rate by Model"
1852
- echo "======================"
1853
- echo "(Note: error detection depends on LiteLLM OTEL span attributes)"
1854
- ${DUCKDB} -c "
1855
- ${BASE_QUERY},
1856
- extracted AS (${EXTRACT_ATTRS}),
1857
- error_traces AS (
1858
- SELECT DISTINCT trace_id
1859
- FROM attrs
1860
- WHERE attr_key IN ('exception.type', 'exception.message')
1861
- OR (attr_key = 'otel.status_code' AND attr_string = 'ERROR')
1862
- OR (attr_key = 'http.status_code' AND CAST(attr_int AS INT) >= 400)
1863
- )
1864
- SELECT
1865
- COALESCE(e.model, '(unknown)') as model,
1866
- COUNT(*) as total_requests,
1867
- COUNT(er.trace_id) as errors,
1868
- ROUND(COUNT(er.trace_id)::FLOAT / COUNT(*) * 100, 1) as error_rate_pct
1869
- FROM extracted e
1870
- LEFT JOIN error_traces er ON e.trace_id = er.trace_id
1871
- GROUP BY e.model
1872
- ORDER BY error_rate_pct DESC;
1873
- "
1874
- }
1875
-
1876
- query_largest_requests() {
1877
- local limit="${1:-20}"
1878
- check_traces
1879
- echo "📊 Largest Requests by Input Tokens"
1880
- echo "===================================="
1881
- ${DUCKDB} -c "
1882
- ${BASE_QUERY},
1883
- extracted AS (${EXTRACT_ATTRS})
1884
- SELECT
1885
- COALESCE(session_id, '') as session_id,
1886
- COALESCE(request_id, '') as request_id,
1887
- COALESCE(agent_id, '') as agent,
1888
- model,
1889
- input_tokens,
1890
- output_tokens,
1891
- ROUND(cost, 6) as cost,
1892
- duration_ms as latency_ms,
1893
- to_timestamp(start_ms / 1000) as timestamp
1894
- FROM extracted
1895
- WHERE input_tokens IS NOT NULL
1896
- ORDER BY input_tokens DESC
1897
- LIMIT ${limit};
1898
- "
1899
- }
1900
-
1901
- query_problems() {
1902
- check_traces
1903
- echo "📊 Problem Sessions (statistical outliers)"
1904
- echo "==========================================="
1905
- ${DUCKDB} -c "
1906
- ${BASE_QUERY},
1907
- extracted AS (${EXTRACT_ATTRS}),
1908
- session_summary AS (
1909
- SELECT
1910
- session_id,
1911
- COALESCE(agent_id, '(default)') as agent_id,
1912
- ROUND(SUM(cost), 6) as total_cost,
1913
- SUM(COALESCE(input_tokens, 0)) as total_input_tokens,
1914
- COUNT(*) as llm_calls,
1915
- COUNT(DISTINCT request_id) as requests,
1916
- MAX(duration_ms) as max_latency_ms,
1917
- MIN(to_timestamp(start_ms / 1000)) as started_at
1918
- FROM extracted
1919
- GROUP BY session_id, agent_id
1920
- ),
1921
- overall_stats AS (
1922
- SELECT
1923
- AVG(total_cost) as avg_cost,
1924
- STDDEV(total_cost) as stddev_cost,
1925
- AVG(total_input_tokens) as avg_tokens,
1926
- STDDEV(total_input_tokens) as stddev_tokens
1927
- FROM session_summary
1928
- WHERE total_cost > 0
1929
- )
1930
- SELECT
1931
- s.session_id,
1932
- s.agent_id as agent,
1933
- s.total_cost,
1934
- s.total_input_tokens as input_tokens,
1935
- s.llm_calls,
1936
- s.requests,
1937
- s.max_latency_ms,
1938
- CASE
1939
- WHEN o.stddev_cost > 0 AND s.total_cost > o.avg_cost + 2 * o.stddev_cost THEN 'COST OUTLIER'
1940
- WHEN o.stddev_tokens > 0 AND s.total_input_tokens > o.avg_tokens + 2 * o.stddev_tokens THEN 'TOKEN OUTLIER'
1941
- WHEN s.requests > 0 AND s.llm_calls > s.requests * 5 THEN 'EXCESSIVE LLM CALLS'
1942
- WHEN s.max_latency_ms > 30000 THEN 'HIGH LATENCY'
1943
- ELSE '?'
1944
- END as problem_type,
1945
- s.started_at
1946
- FROM session_summary s, overall_stats o
1947
- WHERE (o.stddev_cost > 0 AND s.total_cost > o.avg_cost + 2 * o.stddev_cost)
1948
- OR (o.stddev_tokens > 0 AND s.total_input_tokens > o.avg_tokens + 2 * o.stddev_tokens)
1949
- OR (s.requests > 0 AND s.llm_calls > s.requests * 5)
1950
- OR s.max_latency_ms > 30000
1951
- ORDER BY s.total_cost DESC
1952
- LIMIT 20;
1953
- "
1954
- }
1955
-
1956
- # ============================================================================
1957
- # Tier 2: Session Event Queries (require Layer 2 session events)
1958
- # Session events live in ${ANALYTICS_DIR}/sessions/<session_id>.jsonl
1959
- # ============================================================================
1960
- SESSIONS_DIR="${ANALYTICS_DIR}/sessions"
1961
-
1962
- check_sessions() {
1963
- if ! ls "${SESSIONS_DIR}"/*.jsonl &> /dev/null; then
1964
- echo "No session event files found in ${SESSIONS_DIR}/"
1965
- echo "Session events are captured when running agents with observability enabled."
1966
- exit 0
1967
- fi
1968
- }
1969
-
1970
- query_tool_loops() {
1971
- local session_id="$1"
1972
- check_sessions
1973
- echo "📊 Tool Loop Detection: ${session_id}"
1974
- echo "======================================"
1975
- ${DUCKDB} -c "
1976
- WITH tool_events AS (
1977
- SELECT
1978
- session_id,
1979
- request_id,
1980
- json_extract_string(to_json(payload), '$.toolName') as tool_name,
1981
- json_extract_string(to_json(payload), '$.callId') as call_id,
1982
- timestamp
1983
- FROM read_json(
1984
- '${SESSIONS_DIR}/${session_id}.jsonl',
1985
- format='newline_delimited',
1986
- ignore_errors=true
1987
- )
1988
- WHERE event_type = 'tool_start'
1989
- ),
1990
- tool_counts AS (
1991
- SELECT
1992
- request_id,
1993
- tool_name,
1994
- COUNT(*) as call_count,
1995
- MIN(timestamp) as first_call,
1996
- MAX(timestamp) as last_call
1997
- FROM tool_events
1998
- GROUP BY request_id, tool_name
1999
- )
2000
- SELECT
2001
- COALESCE(request_id, '(unknown)') as request_id,
2002
- tool_name,
2003
- call_count,
2004
- CASE WHEN call_count > 5 THEN 'POSSIBLE LOOP' ELSE 'ok' END as status,
2005
- ROUND((last_call - first_call) / 1000.0, 1) as span_seconds
2006
- FROM tool_counts
2007
- ORDER BY call_count DESC;
2008
- "
2009
- }
2010
-
2011
- query_tool_stats() {
2012
- local session_filter="${1:-}"
2013
- check_sessions
2014
- echo "📊 Tool Usage Statistics"
2015
- echo "========================"
2016
- local glob_pattern
2017
- if [[ -n "${session_filter}" ]]; then
2018
- glob_pattern="${SESSIONS_DIR}/${session_filter}.jsonl"
2019
- echo " Session: ${session_filter}"
2020
- else
2021
- glob_pattern="${SESSIONS_DIR}/*.jsonl"
2022
- fi
2023
- ${DUCKDB} -c "
2024
- WITH starts AS (
2025
- SELECT
2026
- session_id,
2027
- request_id,
2028
- json_extract_string(to_json(payload), '$.toolName') as tool_name,
2029
- json_extract_string(to_json(payload), '$.callId') as call_id,
2030
- timestamp as start_ts
2031
- FROM read_json('${glob_pattern}',
2032
- format='newline_delimited', ignore_errors=true)
2033
- WHERE event_type = 'tool_start'
2034
- AND json_extract_string(to_json(payload), '$.callId') IS NOT NULL
2035
- AND json_extract_string(to_json(payload), '$.callId') != ''
2036
- ),
2037
- ends AS (
2038
- SELECT
2039
- session_id,
2040
- request_id,
2041
- json_extract_string(to_json(payload), '$.toolName') as tool_name,
2042
- json_extract_string(to_json(payload), '$.callId') as call_id,
2043
- timestamp as end_ts,
2044
- CASE WHEN json_extract_string(to_json(payload), '$.isError') = 'true' THEN 1 ELSE 0 END as is_error
2045
- FROM read_json('${glob_pattern}',
2046
- format='newline_delimited', ignore_errors=true)
2047
- WHERE event_type = 'tool_end'
2048
- AND json_extract_string(to_json(payload), '$.callId') IS NOT NULL
2049
- AND json_extract_string(to_json(payload), '$.callId') != ''
2050
- ),
2051
- tool_pairs AS (
2052
- SELECT
2053
- s.tool_name,
2054
- (e.end_ts - s.start_ts) as duration_ms,
2055
- e.is_error
2056
- FROM starts s
2057
- JOIN ends e ON s.call_id = e.call_id AND s.session_id = e.session_id AND s.request_id = e.request_id
2058
- )
2059
- SELECT
2060
- tool_name,
2061
- COUNT(*) as total_calls,
2062
- ROUND(AVG(duration_ms), 0) as avg_duration_ms,
2063
- ROUND(SUM(is_error)::FLOAT / COUNT(*) * 100, 1) as error_rate_pct,
2064
- MAX(duration_ms) as max_duration_ms
2065
- FROM tool_pairs
2066
- GROUP BY tool_name
2067
- ORDER BY total_calls DESC;
2068
- "
2069
- }
2070
-
2071
- query_session_timeline() {
2072
- local session_id="$1"
2073
- check_traces
2074
- echo "📊 Session Timeline: ${session_id}"
2075
- echo "==================================="
2076
- echo "(unified LLM calls + tool events)"
2077
- # Check if session events exist for this session
2078
- local has_events=false
2079
- if [[ -f "${SESSIONS_DIR}/${session_id}.jsonl" ]]; then
2080
- has_events=true
2081
- fi
2082
-
2083
- if [[ "${has_events}" == "true" ]]; then
2084
- ${DUCKDB} -c "
2085
- ${BASE_QUERY},
2086
- extracted AS (${EXTRACT_ATTRS}),
2087
- llm_events AS (
2088
- SELECT
2089
- start_ms as timestamp,
2090
- 'llm_call' as event_type,
2091
- model as detail,
2092
- ROUND(cost, 6) as cost,
2093
- input_tokens,
2094
- output_tokens,
2095
- duration_ms
2096
- FROM extracted
2097
- WHERE session_id = '${session_id}'
2098
- ),
2099
- agent_events AS (
2100
- SELECT
2101
- timestamp,
2102
- event_type,
2103
- COALESCE(
2104
- json_extract_string(to_json(payload), '$.toolName'),
2105
- event_type
2106
- ) as detail,
2107
- NULL::DOUBLE as cost,
2108
- NULL::BIGINT as input_tokens,
2109
- NULL::BIGINT as output_tokens,
2110
- NULL::BIGINT as duration_ms
2111
- FROM read_json(
2112
- '${SESSIONS_DIR}/${session_id}.jsonl',
2113
- format='newline_delimited',
2114
- ignore_errors=true
2115
- )
2116
- )
2117
- SELECT * FROM (
2118
- SELECT * FROM llm_events
2119
- UNION ALL
2120
- SELECT * FROM agent_events
2121
- )
2122
- ORDER BY timestamp;
2123
- "
2124
- else
2125
- # No session events — just show LLM calls
2126
- ${DUCKDB} -c "
2127
- ${BASE_QUERY},
2128
- extracted AS (${EXTRACT_ATTRS})
2129
- SELECT
2130
- start_ms as timestamp,
2131
- 'llm_call' as event_type,
2132
- model as detail,
2133
- ROUND(cost, 6) as cost,
2134
- input_tokens,
2135
- output_tokens,
2136
- duration_ms
2137
- FROM extracted
2138
- WHERE session_id = '${session_id}'
2139
- ORDER BY start_ms;
2140
- "
2141
- fi
2142
- }
2143
-
2144
- query_latest_session() {
2145
- check_traces
2146
- echo "📊 Latest Session"
2147
- echo "================"
2148
- ${DUCKDB} -c "
2149
- ${BASE_QUERY},
2150
- extracted AS (${EXTRACT_ATTRS})
2151
- SELECT
2152
- COALESCE(session_id, '(no session)') as session_id,
2153
- COALESCE(model, '(unknown)') as model,
2154
- ROUND(COALESCE(cost, 0), 6) as cost,
2155
- COALESCE(input_tokens, 0) as input_tokens,
2156
- COALESCE(output_tokens, 0) as output_tokens,
2157
- duration_ms as latency_ms,
2158
- COALESCE(agent_id, '') as agent_id,
2159
- COALESCE(request_id, '') as request_id,
2160
- to_timestamp(start_ms / 1000) as timestamp
2161
- FROM extracted
2162
- ORDER BY start_ms DESC
2163
- LIMIT 1;
2164
- "
2165
- }
2166
-
2167
- query_custom() {
2168
- local sql="$1"
2169
- check_traces
2170
-
2171
- # Catch common shell-expansion pitfall:
2172
- # --sql "${BASE_QUERY}, extracted AS (${EXTRACT_ATTRS}) ..."
2173
- # If BASE_QUERY/EXTRACT_ATTRS are not defined in the caller shell, this becomes invalid SQL.
2174
- if [[ "${sql}" == *"extracted AS ()"* ]] || [[ "${sql}" == *"attrs AS ()"* ]]; then
2175
- echo "Invalid SQL: CTE placeholders were expanded to empty by your shell."
2176
- echo "Hint: wrap --sql in single quotes, e.g.:"
2177
- echo " --sql '\${BASE_QUERY}, extracted AS (\${EXTRACT_ATTRS}) SELECT COUNT(*) FROM extracted'"
2178
- exit 1
2179
- fi
2180
-
2181
- # Support callers that pass placeholders literally (e.g. Python subprocess):
2182
- # ${BASE_QUERY}, extracted AS (${EXTRACT_ATTRS}) ...
2183
- local expanded_sql="${sql//\$\{BASE_QUERY\}/${BASE_QUERY}}"
2184
- expanded_sql="${expanded_sql//\$\{EXTRACT_ATTRS\}/${EXTRACT_ATTRS}}"
2185
- expanded_sql="${expanded_sql//\$BASE_QUERY/${BASE_QUERY}}"
2186
- expanded_sql="${expanded_sql//\$EXTRACT_ATTRS/${EXTRACT_ATTRS}}"
2187
- run_duckdb_sql "${expanded_sql}"
2188
- }
2189
-
2190
- interactive_shell() {
2191
- check_traces
2192
- echo "🦆 DuckDB Interactive Shell"
2193
- echo "==========================="
2194
- echo ""
2195
- if has_indexed_db; then
2196
- echo "Indexed DB: ${INDEX_DB_PATH}"
2197
- echo ""
2198
- echo "Example queries:"
2199
- echo " SELECT * FROM sessions ORDER BY ended_at DESC LIMIT 5;"
2200
- echo " SELECT session_id, request_id, start_ms FROM model_calls ORDER BY start_ms DESC LIMIT 20;"
2201
- echo ""
2202
- ${DUCKDB} -readonly "${INDEX_DB_PATH}"
2203
- else
2204
- echo "Trace files: ${TRACES_DIR}/*.jsonl"
2205
- echo ""
2206
- echo "Example queries:"
2207
- echo " SELECT * FROM read_json('${TRACES_DIR}/*.jsonl') LIMIT 5;"
2208
- echo ""
2209
- ${DUCKDB}
2210
- fi
2211
- }
2212
-
2213
- dump_schema() {
2214
- if ! has_indexed_db; then
2215
- echo '{"error": "Indexed analytics DB not found. Schema discovery requires analytics.duckdb."}' | python3 -m json.tool
2216
- exit 1
2217
- fi
2218
-
2219
- INDEX_DB_PATH="${INDEX_DB_PATH}" DUCKDB_BIN="${DUCKDB}" python3 << 'PYTHON_EOF'
2220
- import json
2221
- import subprocess
2222
- import sys
2223
- import os
2224
-
2225
- db_path = os.environ.get('INDEX_DB_PATH', '')
2226
- duckdb_bin = os.environ.get('DUCKDB_BIN', 'duckdb')
2227
- if not db_path:
2228
- print('{"error": "INDEX_DB_PATH not set"}', file=sys.stderr)
2229
- sys.exit(1)
2230
-
2231
- def run_query(sql):
2232
- """Run DuckDB query and return JSON results."""
2233
- result = subprocess.run(
2234
- [duckdb_bin, '-readonly', db_path, '-json', '-c', sql],
2235
- capture_output=True,
2236
- text=True
2237
- )
2238
- if result.returncode != 0:
2239
- print(f"Error: {result.stderr}", file=sys.stderr)
2240
- return []
2241
- try:
2242
- return json.loads(result.stdout)
2243
- except json.JSONDecodeError:
2244
- return []
2245
-
2246
- # Fetch all tables
2247
- tables = run_query("""
2248
- SELECT table_name
2249
- FROM information_schema.tables
2250
- WHERE table_schema = 'main'
2251
- ORDER BY table_name
2252
- """)
2253
-
2254
- # Fetch all columns with types and indices
2255
- columns = run_query("""
2256
- SELECT table_name, column_name, data_type
2257
- FROM information_schema.columns
2258
- WHERE table_schema = 'main'
2259
- ORDER BY table_name, ordinal_position
2260
- """)
2261
-
2262
- # Fetch primary keys
2263
- pks = run_query("""
2264
- SELECT table_name, column_name
2265
- FROM information_schema.key_column_usage
2266
- WHERE table_schema = 'main' AND constraint_type = 'PRIMARY KEY'
2267
- ORDER BY table_name, ordinal_position
2268
- """)
2269
-
2270
- # Fetch foreign keys
2271
- fks = run_query("""
2272
- SELECT
2273
- table_name,
2274
- column_name,
2275
- constraint_name
2276
- FROM information_schema.key_column_usage
2277
- WHERE table_schema = 'main' AND constraint_type = 'FOREIGN KEY'
2278
- ORDER BY table_name
2279
- """)
2280
-
2281
- # Fetch index info
2282
- indices = run_query("""
2283
- SELECT table_name, index_name, expressions
2284
- FROM duckdb_indexes()
2285
- ORDER BY table_name
2286
- """)
2287
-
2288
- # Build output structure
2289
- pk_set = set((row['table_name'], row['column_name']) for row in pks)
2290
- col_map = {}
2291
- for row in columns:
2292
- tbl = row['table_name']
2293
- if tbl not in col_map:
2294
- col_map[tbl] = []
2295
- col_map[tbl].append({
2296
- 'name': row['column_name'],
2297
- 'type': row['data_type'],
2298
- 'is_primary_key': (tbl, row['column_name']) in pk_set
2299
- })
2300
-
2301
- idx_map = {}
2302
- for row in indices:
2303
- tbl = row['table_name']
2304
- if tbl not in idx_map:
2305
- idx_map[tbl] = []
2306
- idx_map[tbl].append({
2307
- 'name': row['index_name'],
2308
- 'expressions': row['expressions']
2309
- })
2310
-
2311
- schema = {
2312
- 'tables': [
2313
- {
2314
- 'name': t['table_name'],
2315
- 'columns': col_map.get(t['table_name'], []),
2316
- 'indices': idx_map.get(t['table_name'], [])
2317
- }
2318
- for t in tables
2319
- ],
2320
- 'relationships': [
2321
- {
2322
- 'table': fk['table_name'],
2323
- 'column': fk['column_name'],
2324
- 'constraint': fk['constraint_name']
2325
- }
2326
- for fk in fks
2327
- ]
2328
- }
2329
-
2330
- print(json.dumps(schema, indent=2))
2331
- PYTHON_EOF
2332
- }
2333
-
2334
- # Parse --since duration to milliseconds (e.g., 24h, 7d, 1h)
2335
- parse_since() {
2336
- local since="$1"
2337
- local num="${since%[hdm]}"
2338
- local unit="${since: -1}"
2339
- case "${unit}" in
2340
- h) echo $(( num * 3600000 )) ;;
2341
- d) echo $(( num * 86400000 )) ;;
2342
- m) echo $(( num * 60000 )) ;;
2343
- *) echo "86400000" ;; # default 24h
2344
- esac
2345
- }
2346
-
2347
- # Main
2348
- case "${1:-}" in
2349
- --cost)
2350
- query_total_cost_fast
2351
- ;;
2352
- --cost-by-session|--sessions)
2353
- query_cost_by_session_fast
2354
- ;;
2355
- --cost-by-model|--models)
2356
- query_cost_by_model_fast
2357
- ;;
2358
- --cost-by-pack|--agents)
2359
- query_cost_by_pack_fast
2360
- ;;
2361
- --cost-by-engine)
2362
- query_cost_by_engine_fast
2363
- ;;
2364
- --cost-by-source)
2365
- query_cost_by_source_fast
2366
- ;;
2367
- --cost-per-request)
2368
- query_cost_per_request_fast "${2:-}"
2369
- ;;
2370
- --session)
2371
- if [[ -z "${2:-}" ]]; then
2372
- echo "Usage: $0 --session <session-id>"
2373
- exit 1
2374
- fi
2375
- query_session_fast "$2"
2376
- ;;
2377
- --session-summary)
2378
- if [[ -z "${2:-}" ]]; then
2379
- echo "Usage: $0 --session-summary <session-id>"
2380
- exit 1
2381
- fi
2382
- query_session_summary_fast "$2"
2383
- ;;
2384
- --session-debug)
2385
- if [[ -z "${2:-}" ]]; then
2386
- echo "Usage: $0 --session-debug <session-id>"
2387
- exit 1
2388
- fi
2389
- query_session_debug "$2"
2390
- ;;
2391
- --session-last)
2392
- if [[ -z "${2:-}" ]]; then
2393
- echo "Usage: $0 --session-last <session-id> [limit]"
2394
- exit 1
2395
- fi
2396
- query_session_last_messages "$2" "${3:-8}"
2397
- ;;
2398
- --session-messages|--messages)
2399
- if [[ -z "${2:-}" ]]; then
2400
- echo "Usage: $0 --session-messages <session-id>"
2401
- exit 1
2402
- fi
2403
- query_session_last_messages "$2" "${3:-8}"
2404
- ;;
2405
- --session-tools)
2406
- if [[ -z "${2:-}" ]]; then
2407
- echo "Usage: $0 --session-tools <session-id>"
2408
- exit 1
2409
- fi
2410
- query_session_tools "$2"
2411
- ;;
2412
- --recent-messages)
2413
- query_recent_messages "${2:-20}"
2414
- ;;
2415
- --last-session-messages)
2416
- query_last_session_messages "${2:-8}"
2417
- ;;
2418
- --request)
2419
- if [[ -z "${2:-}" ]]; then
2420
- echo "Usage: $0 --request <request-id>"
2421
- exit 1
2422
- fi
2423
- query_request_fast "$2"
2424
- ;;
2425
- --latest-session)
2426
- query_latest_session_fast
2427
- ;;
2428
- --kpis-daily)
2429
- query_agent_kpis_daily
2430
- ;;
2431
- --training-examples)
2432
- query_training_examples "${2:-20}"
2433
- ;;
2434
- --session-timeline)
2435
- if [[ -z "${2:-}" ]]; then
2436
- echo "Usage: $0 --session-timeline <session-id>"
2437
- exit 1
2438
- fi
2439
- query_session_timeline_fast "$2"
2440
- ;;
2441
- --compare-versions)
2442
- if [[ -z "${2:-}" ]]; then
2443
- echo "Usage: $0 --compare-versions <agent-name>"
2444
- exit 1
2445
- fi
2446
- query_compare_versions_fast "$2"
2447
- ;;
2448
- --top-sessions)
2449
- if [[ -z "${2:-}" ]]; then
2450
- echo "Usage: $0 --top-sessions <agent-name> [--since <duration>]"
2451
- exit 1
2452
- fi
2453
- local_since="86400000"
2454
- if [[ "${3:-}" == "--since" && -n "${4:-}" ]]; then
2455
- local_since=$(parse_since "$4")
2456
- fi
2457
- query_top_sessions_fast "$2" "${local_since}"
2458
- ;;
2459
- --latency-percentiles|--latency)
2460
- query_latency_percentiles
2461
- ;;
2462
- --errors)
2463
- query_errors
2464
- ;;
2465
- --largest-requests)
2466
- query_largest_requests "${2:-20}"
2467
- ;;
2468
- --problems)
2469
- query_problems
2470
- ;;
2471
- --tool-loops)
2472
- if [[ -z "${2:-}" ]]; then
2473
- echo "Usage: $0 --tool-loops <session-id>"
2474
- exit 1
2475
- fi
2476
- query_tool_loops "$2"
2477
- ;;
2478
- --tool-stats)
2479
- query_tool_stats "${2:-}"
2480
- ;;
2481
- --sql)
2482
- if [[ -z "${2:-}" ]]; then
2483
- echo "Usage: $0 --sql \"SELECT ...\""
2484
- exit 1
2485
- fi
2486
- query_custom "$2"
2487
- ;;
2488
- --schema)
2489
- dump_schema
2490
- ;;
2491
- --help|-h)
2492
- echo "Usage: $0 [flag] [args]"
2493
- echo ""
2494
- echo "COST ANALYSIS — \"Where is money going?\""
2495
- echo " --cost Total cost, tokens, request count"
2496
- echo " --cost-by-session Cost per session — find expensive conversations"
2497
- echo " --cost-by-model Cost per model — check tier usage"
2498
- echo " --cost-by-pack Cost per agent — compare agent efficiency"
2499
- echo " --cost-by-engine Cost per engine (claude/codex/etc)"
2500
- echo " --cost-by-source Cost per framework (vscode/cli/api)"
2501
- echo " --cost-per-request [id] Cost per user message — find bloated requests"
2502
- echo ""
2503
- echo "DEBUGGING — \"What happened in this session/request?\""
2504
- echo " --session <id> All LLM calls in a session"
2505
- echo " --session-summary <id> Session-level totals and metadata"
2506
- echo " --session-debug <id> Compact agent-safe bundle: summary, requests, tools, latest messages"
2507
- echo " --session-last <id> [N] Last N conversational user/assistant previews from latest call snapshot"
2508
- echo " --session-messages <id> Alias of --session-last"
2509
- echo " --session-tools <id> Tool calls and outcomes in session"
2510
- echo " --request <id> All LLM calls for one user message"
2511
- echo " --latest-session Most recent session summary"
2512
- echo " --last-session-messages [N] Last N messages for latest session"
2513
- echo " --recent-messages [N] Most recent sessions by latest call"
2514
- echo " --session-timeline <id> Interleaved model/tool timeline"
2515
- echo " --kpis-daily Daily KPI rollup from indexed DB"
2516
- echo " --training-examples [N] Retired (prints guidance)"
2517
- echo ""
2518
- echo "REGRESSION — \"Did performance change?\""
2519
- echo " --compare-versions <agent> Avg cost/tokens/latency by agent version"
2520
- echo " --top-sessions <agent> Expensive sessions for an agent (last 24h)"
2521
- echo ""
2522
- echo "HEALTH — \"What's broken?\""
2523
- echo " --problems Statistical outliers (cost/token/latency spikes)"
2524
- echo " --errors Error rate by model"
2525
- echo " --latency-percentiles p50/p95 latency by model and agent"
2526
- echo " --largest-requests [N] Biggest input token payloads (prompt bloat)"
2527
- echo " --tool-loops <id> Detect repeated tool calls (possible loops)"
2528
- echo " --tool-stats [id] Tool usage frequency and error rates"
2529
- echo ""
2530
- echo "DISCOVERY — \"Agent self-service schema learning\""
2531
- echo " --schema All tables, columns, types, and relationships (JSON for agents)"
2532
- echo " (no args) Interactive DuckDB shell"
2533
- echo ""
2534
- echo "CUSTOM — \"I need something specific\""
2535
- echo " --sql \"SELECT ...\" Run custom DuckDB SQL"
2536
- echo ""
2537
- echo "The --sql flag can use built-in CTEs for complex queries:"
2538
- echo " \${BASE_QUERY} — flattens OTEL JSONL into spans"
2539
- echo " \${EXTRACT_ATTRS} — extracts common attributes (cost, model, session, etc)"
2540
- echo ""
2541
- echo "Example: scripts/query-analytics.sh --sql \"\${BASE_QUERY}, attrs AS (\${EXTRACT_ATTRS}) SELECT model, SUM(cost) FROM attrs GROUP BY model\""
2542
- echo ""
2543
- echo "Options:"
2544
- echo " --since <dur> Time filter for --top-sessions (24h, 7d, 30m)"
2545
- echo ""
2546
- echo "Message previews are bounded by default and may spill a fuller preview artifact to ${TMP_DIR}."
2547
- ;;
2548
- *)
2549
- interactive_shell
2550
- ;;
2551
- esac