@agenthippo-ai/agenthippo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (480) hide show
  1. package/LICENSE +21 -0
  2. package/bin/agenthippo.js +2 -0
  3. package/out/agentpack/github.js +547 -0
  4. package/out/agentpack/index.js +66 -0
  5. package/out/agentpack/install.js +297 -0
  6. package/out/agentpack/manager.js +441 -0
  7. package/out/agentpack/manifest.js +135 -0
  8. package/out/agentpack/paths.js +475 -0
  9. package/out/agentpack/publish.js +248 -0
  10. package/out/agentpack/stores.js +554 -0
  11. package/out/agentpack/types.js +5 -0
  12. package/out/analytics/dashboard/artifactManager.js +439 -0
  13. package/out/analytics/dashboard/fleetDisplayUtils.js +159 -0
  14. package/out/analytics/dashboard/fleetOrchestrator.js +142 -0
  15. package/out/analytics/dashboard/fleetRegistry.js +79 -0
  16. package/out/analytics/dashboard/fleetRowDefaults.js +87 -0
  17. package/out/analytics/dashboard/fleetScriptAdapter.js +146 -0
  18. package/out/analytics/dashboard/fleetStatusUtils.js +30 -0
  19. package/out/analytics/dashboard/fleetTitleUtils.js +68 -0
  20. package/out/analytics/dashboard/fleetTypes.js +29 -0
  21. package/out/analytics/dashboard/index.js +17 -0
  22. package/out/analytics/dashboard/indexingService.js +422 -0
  23. package/out/analytics/dashboard/localFleetAdapter.js +222 -0
  24. package/out/analytics/dashboard/performanceMonitor.js +76 -0
  25. package/out/analytics/dashboard/processManager.js +209 -0
  26. package/out/analytics/dashboard/provider.js +3885 -0
  27. package/out/analytics/dashboard/queryRunner.js +994 -0
  28. package/out/analytics/dashboard/refreshPolicy.js +16 -0
  29. package/out/analytics/dashboard/scheduledFleetFingerprint.js +22 -0
  30. package/out/analytics/dashboard/scheduledFleetStorage.js +241 -0
  31. package/out/analytics/dashboard/scheduledFleetUtils.js +22 -0
  32. package/out/analytics/dashboard/sessionEventFleetStats.js +118 -0
  33. package/out/analytics/dashboard/sessionJsonlFleet.js +204 -0
  34. package/out/analytics/dashboard/teamViewDiscovery.js +106 -0
  35. package/out/analytics/dashboard/viewsConfigMerge.js +221 -0
  36. package/out/analytics/dashboard/webview/analytics-panel.html +155 -0
  37. package/out/analytics/dashboard/webview/codicon.ttf +0 -0
  38. package/out/analytics/dashboard/webview/index.html +78 -0
  39. package/out/analytics/dashboard/webview/panel.js +2295 -0
  40. package/out/analytics/dashboard/webview/runtime-bridge.js +82 -0
  41. package/out/analytics/dashboard/webview/styles.css +1009 -0
  42. package/out/analytics/lifecycle.js +43 -0
  43. package/out/analytics/service.js +1279 -0
  44. package/out/analytics/sessionEvents.js +231 -0
  45. package/out/auth/bootstrapApply.js +72 -0
  46. package/out/auth/brokerClient.js +126 -0
  47. package/out/auth/callbackUri.js +59 -0
  48. package/out/auth/callbackUriParse.js +59 -0
  49. package/out/auth/githubStoreGate.js +70 -0
  50. package/out/auth/loopbackAuthServer.js +165 -0
  51. package/out/auth/pkce.js +9 -0
  52. package/out/auth/provider.js +239 -0
  53. package/out/auth/sessionStore.js +193 -0
  54. package/out/auth/signInFlow.js +182 -0
  55. package/out/auth/types.js +12 -0
  56. package/out/auth-broker/config.js +62 -0
  57. package/out/auth-broker/jwt.js +105 -0
  58. package/out/auth-broker/pkce.js +57 -0
  59. package/out/auth-broker/server.js +539 -0
  60. package/out/auth-broker/store.js +273 -0
  61. package/out/base-prompts/gateway/openclaw-starter.json5 +144 -0
  62. package/out/base-prompts/gateway/slack-socket-mode-minimal.manifest.json +44 -0
  63. package/out/base-prompts/mcp-servers/.mcp.json +13 -0
  64. package/out/base-prompts/mcp-servers/langfuse-analytics/README.md +142 -0
  65. package/out/base-prompts/mcp-servers/langfuse-analytics/mcp.json +13 -0
  66. package/out/base-prompts/mcp-servers/langfuse-analytics/package-lock.json +1192 -0
  67. package/out/base-prompts/mcp-servers/langfuse-analytics/package.json +23 -0
  68. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/run-with-env.sh +12 -0
  69. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/sdk-test.js +102 -0
  70. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/simple-test.js +137 -0
  71. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-client.js +118 -0
  72. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-server.js +209 -0
  73. package/out/base-prompts/mcp-servers/langfuse-analytics/src/client.ts +431 -0
  74. package/out/base-prompts/mcp-servers/langfuse-analytics/src/index.ts +270 -0
  75. package/out/base-prompts/mcp-servers/langfuse-analytics/tsconfig.json +18 -0
  76. package/out/base-prompts/rules/AGENTS.md +204 -0
  77. package/out/base-prompts/skills/agent-browser/SKILL.md +102 -0
  78. package/out/base-prompts/skills/agent-builder/SKILL.md +73 -0
  79. package/out/base-prompts/skills/agent-builder/references/agent-pack.md +63 -0
  80. package/out/base-prompts/skills/agent-builder/references/crewai.md +66 -0
  81. package/out/base-prompts/skills/agent-builder/references/langchain.md +60 -0
  82. package/out/base-prompts/skills/agent-builder/references/raw-sdk.md +73 -0
  83. package/out/base-prompts/skills/agent-engine-creator/SKILL.md +586 -0
  84. package/out/base-prompts/skills/agent-engine-installer/SKILL.md +106 -0
  85. package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.mjs +259 -0
  86. package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.ps1 +189 -0
  87. package/out/base-prompts/skills/agenthippo-cli/SKILL.md +69 -0
  88. package/out/base-prompts/skills/agenthippo-cli/scripts/install.sh +63 -0
  89. package/out/base-prompts/skills/agentpack-creator/SKILL.md +375 -0
  90. package/out/base-prompts/skills/agentpack-creator/references/agent-templates.md +261 -0
  91. package/out/base-prompts/skills/agentpack-creator/references/agents-md-guide.md +195 -0
  92. package/out/base-prompts/skills/agentpack-creator/scripts/init_pack.py +391 -0
  93. package/out/base-prompts/skills/agentpack-creator/scripts/init_pack_test.py +294 -0
  94. package/out/base-prompts/skills/agentpack-testbench/SKILL.md +135 -0
  95. package/out/base-prompts/skills/algorithmic-art/LICENSE.txt +202 -0
  96. package/out/base-prompts/skills/algorithmic-art/SKILL.md +405 -0
  97. package/out/base-prompts/skills/algorithmic-art/templates/generator_template.js +223 -0
  98. package/out/base-prompts/skills/algorithmic-art/templates/viewer.html +599 -0
  99. package/out/base-prompts/skills/debug-agent/SKILL.md +83 -0
  100. package/out/base-prompts/skills/desktop-automation/SKILL.md +166 -0
  101. package/out/base-prompts/skills/desktop-automation/references/agenthippo-ui-recipes.md +441 -0
  102. package/out/base-prompts/skills/desktop-automation/scripts/windows-desktop-uia.psm1 +280 -0
  103. package/out/base-prompts/skills/docx/LICENSE.txt +30 -0
  104. package/out/base-prompts/skills/docx/SKILL.md +197 -0
  105. package/out/base-prompts/skills/docx/docx-js.md +350 -0
  106. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  107. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  108. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  109. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  110. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  111. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  112. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  113. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  114. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  115. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  116. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  117. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  118. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  119. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  120. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  121. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  122. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  123. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  124. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  125. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  126. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  127. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  128. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  129. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  130. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  131. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  132. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  133. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  134. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  135. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  136. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  137. package/out/base-prompts/skills/docx/ooxml/schemas/mce/mc.xsd +75 -0
  138. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  139. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  140. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  141. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  142. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  143. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  144. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  145. package/out/base-prompts/skills/docx/ooxml/scripts/pack.py +159 -0
  146. package/out/base-prompts/skills/docx/ooxml/scripts/unpack.py +29 -0
  147. package/out/base-prompts/skills/docx/ooxml/scripts/validate.py +69 -0
  148. package/out/base-prompts/skills/docx/ooxml/scripts/validation/__init__.py +15 -0
  149. package/out/base-prompts/skills/docx/ooxml/scripts/validation/base.py +951 -0
  150. package/out/base-prompts/skills/docx/ooxml/scripts/validation/docx.py +274 -0
  151. package/out/base-prompts/skills/docx/ooxml/scripts/validation/pptx.py +315 -0
  152. package/out/base-prompts/skills/docx/ooxml/scripts/validation/redlining.py +279 -0
  153. package/out/base-prompts/skills/docx/ooxml.md +610 -0
  154. package/out/base-prompts/skills/docx/scripts/__init__.py +1 -0
  155. package/out/base-prompts/skills/docx/scripts/document.py +1276 -0
  156. package/out/base-prompts/skills/docx/scripts/templates/comments.xml +3 -0
  157. package/out/base-prompts/skills/docx/scripts/templates/commentsExtended.xml +3 -0
  158. package/out/base-prompts/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
  159. package/out/base-prompts/skills/docx/scripts/templates/commentsIds.xml +3 -0
  160. package/out/base-prompts/skills/docx/scripts/templates/people.xml +3 -0
  161. package/out/base-prompts/skills/docx/scripts/utilities.py +374 -0
  162. package/out/base-prompts/skills/gateway-outbound/SKILL.md +136 -0
  163. package/out/base-prompts/skills/mcp-builder/LICENSE.txt +202 -0
  164. package/out/base-prompts/skills/mcp-builder/SKILL.md +328 -0
  165. package/out/base-prompts/skills/mcp-builder/reference/evaluation.md +602 -0
  166. package/out/base-prompts/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
  167. package/out/base-prompts/skills/mcp-builder/reference/node_mcp_server.md +970 -0
  168. package/out/base-prompts/skills/mcp-builder/reference/python_mcp_server.md +719 -0
  169. package/out/base-prompts/skills/mcp-builder/scripts/connections.py +151 -0
  170. package/out/base-prompts/skills/mcp-builder/scripts/evaluation.py +373 -0
  171. package/out/base-prompts/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  172. package/out/base-prompts/skills/mcp-builder/scripts/requirements.txt +2 -0
  173. package/out/base-prompts/skills/openclaw/SKILL.md +406 -0
  174. package/out/base-prompts/skills/openclaw/scripts/run-smoke.sh +61 -0
  175. package/out/base-prompts/skills/pdf/LICENSE.txt +30 -0
  176. package/out/base-prompts/skills/pdf/SKILL.md +294 -0
  177. package/out/base-prompts/skills/pdf/forms.md +205 -0
  178. package/out/base-prompts/skills/pdf/reference.md +612 -0
  179. package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes.py +70 -0
  180. package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes_test.py +226 -0
  181. package/out/base-prompts/skills/pdf/scripts/check_fillable_fields.py +12 -0
  182. package/out/base-prompts/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
  183. package/out/base-prompts/skills/pdf/scripts/create_validation_image.py +41 -0
  184. package/out/base-prompts/skills/pdf/scripts/extract_form_field_info.py +152 -0
  185. package/out/base-prompts/skills/pdf/scripts/fill_fillable_fields.py +114 -0
  186. package/out/base-prompts/skills/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
  187. package/out/base-prompts/skills/pptx/LICENSE.txt +30 -0
  188. package/out/base-prompts/skills/pptx/SKILL.md +484 -0
  189. package/out/base-prompts/skills/pptx/html2pptx.md +625 -0
  190. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  191. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  192. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  193. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  194. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  195. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  196. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  197. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  198. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  199. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  200. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  201. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  202. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  203. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  204. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  205. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  206. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  207. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  208. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  209. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  210. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  211. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  212. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  213. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  214. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  215. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  216. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  217. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  218. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  219. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  220. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  221. package/out/base-prompts/skills/pptx/ooxml/schemas/mce/mc.xsd +75 -0
  222. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  223. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  224. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  225. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  226. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  227. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  228. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  229. package/out/base-prompts/skills/pptx/ooxml/scripts/pack.py +159 -0
  230. package/out/base-prompts/skills/pptx/ooxml/scripts/unpack.py +29 -0
  231. package/out/base-prompts/skills/pptx/ooxml/scripts/validate.py +69 -0
  232. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/__init__.py +15 -0
  233. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/base.py +951 -0
  234. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/docx.py +274 -0
  235. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/pptx.py +315 -0
  236. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/redlining.py +279 -0
  237. package/out/base-prompts/skills/pptx/ooxml.md +427 -0
  238. package/out/base-prompts/skills/pptx/scripts/html2pptx.js +984 -0
  239. package/out/base-prompts/skills/pptx/scripts/inventory.py +1020 -0
  240. package/out/base-prompts/skills/pptx/scripts/rearrange.py +231 -0
  241. package/out/base-prompts/skills/pptx/scripts/replace.py +385 -0
  242. package/out/base-prompts/skills/pptx/scripts/thumbnail.py +450 -0
  243. package/out/base-prompts/skills/prompt-lab/SKILL.md +149 -0
  244. package/out/base-prompts/skills/prompt-lab/scripts/query-analytics.sh +2551 -0
  245. package/out/base-prompts/skills/skill-creator/LICENSE.txt +202 -0
  246. package/out/base-prompts/skills/skill-creator/SKILL.md +371 -0
  247. package/out/base-prompts/skills/skill-creator/references/output-patterns.md +82 -0
  248. package/out/base-prompts/skills/skill-creator/references/workflows.md +28 -0
  249. package/out/base-prompts/skills/skill-creator/scripts/init_skill.py +303 -0
  250. package/out/base-prompts/skills/skill-creator/scripts/package_skill.py +110 -0
  251. package/out/base-prompts/skills/skill-creator/scripts/quick_validate.py +95 -0
  252. package/out/base-prompts/skills/skill-installer/SKILL.md +251 -0
  253. package/out/base-prompts/skills/skill-installer/references/store-api.md +219 -0
  254. package/out/base-prompts/skills/spotlight-dashboard/SKILL.md +83 -0
  255. package/out/base-prompts/skills/spotlight-insights/SKILL.md +101 -0
  256. package/out/base-prompts/skills/spotlight-optimize/SKILL.md +93 -0
  257. package/out/base-prompts/skills/spotlight-optimize/scripts/query-analytics.sh +2551 -0
  258. package/out/base-prompts/skills/spotlight-query/SKILL.md +314 -0
  259. package/out/base-prompts/skills/spotlight-query/references/otel-schema.md +163 -0
  260. package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.sh +2551 -0
  261. package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.test.sh +744 -0
  262. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/sessions/sess-test-1.jsonl +14 -0
  263. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/.duckdb-index.json +7 -0
  264. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/sample.jsonl +5 -0
  265. package/out/base-prompts/skills/spotlight-report/SKILL.md +86 -0
  266. package/out/base-prompts/skills/spotlight-visualize/SKILL.md +92 -0
  267. package/out/base-prompts/skills/spotlight-visualize/scripts/query-analytics.sh +2551 -0
  268. package/out/base-prompts/skills/xlsx/LICENSE.txt +30 -0
  269. package/out/base-prompts/skills/xlsx/SKILL.md +289 -0
  270. package/out/base-prompts/skills/xlsx/recalc.py +178 -0
  271. package/out/branding.js +23 -0
  272. package/out/cli/commands/anywhere.js +66 -0
  273. package/out/cli/commands/ask.js +243 -0
  274. package/out/cli/commands/auth-broker.js +24 -0
  275. package/out/cli/commands/auth.js +219 -0
  276. package/out/cli/commands/chat.js +354 -0
  277. package/out/cli/commands/deploy.js +223 -0
  278. package/out/cli/commands/gateway.js +160 -0
  279. package/out/cli/commands/pack.js +220 -0
  280. package/out/cli/commands/probe-custom-engine.js +82 -0
  281. package/out/cli/commands/serve.js +583 -0
  282. package/out/cli/commands/store.js +919 -0
  283. package/out/cli/credential-context.js +113 -0
  284. package/out/cli/deploy-init.js +2087 -0
  285. package/out/cli/deps.js +238 -0
  286. package/out/cli/index.js +390 -0
  287. package/out/cli/pack-loader.js +376 -0
  288. package/out/cli/run-custom-engine-runner.js +318 -0
  289. package/out/cli/runtime.js +409 -0
  290. package/out/cli/serve-hardening.js +325 -0
  291. package/out/cli/state-sync.js +135 -0
  292. package/out/cli/utils.js +220 -0
  293. package/out/cli/vscode-shim.js +86 -0
  294. package/out/engines/base.js +159 -0
  295. package/out/engines/claude.js +1215 -0
  296. package/out/engines/codex.js +759 -0
  297. package/out/engines/custom.js +632 -0
  298. package/out/engines/customEngineIpc.js +95 -0
  299. package/out/engines/customEngineRunnerEmbed.js +107 -0
  300. package/out/engines/customRegistry.js +274 -0
  301. package/out/engines/embeddedCustomEngineRunner.generated.js +9 -0
  302. package/out/engines/engineApiKey.js +138 -0
  303. package/out/engines/factory.js +45 -0
  304. package/out/engines/install.js +372 -0
  305. package/out/engines/persistentEngineWorker.js +320 -0
  306. package/out/engines/publish.js +341 -0
  307. package/out/engines/types.js +17 -0
  308. package/out/examples/run-headless-repl.js +291 -0
  309. package/out/examples/run-headless.js +187 -0
  310. package/out/extension.js +4472 -0
  311. package/out/gateway/bridge.js +805 -0
  312. package/out/gateway/cli-agent-id.js +36 -0
  313. package/out/gateway/client.js +303 -0
  314. package/out/gateway/client.test.js +395 -0
  315. package/out/gateway/gateway-scope-errors.js +14 -0
  316. package/out/gateway/gatewayBackup.js +76 -0
  317. package/out/gateway/gatewayRouteIndex.js +371 -0
  318. package/out/gateway/gatewaySessionStore.js +243 -0
  319. package/out/gateway/index.js +31 -0
  320. package/out/gateway/lifecycle.js +3056 -0
  321. package/out/gateway/openclaw-channel-routing.js +317 -0
  322. package/out/gateway/outboundHandles.js +373 -0
  323. package/out/gateway/outboundPaths.js +72 -0
  324. package/out/gateway/outboundSend.js +254 -0
  325. package/out/gateway/protocol.js +48 -0
  326. package/out/gateway/resetMappings.js +52 -0
  327. package/out/gateway/service.js +1244 -0
  328. package/out/gateway/sessionRouting.js +77 -0
  329. package/out/headless/index.js +505 -0
  330. package/out/headless/sync.js +473 -0
  331. package/out/mcp/publish.js +143 -0
  332. package/out/mcp.js +597 -0
  333. package/out/permissions.js +108 -0
  334. package/out/permissions.mapping.yaml +68 -0
  335. package/out/registry/index.js +332 -0
  336. package/out/remote/handleRemoteChat.js +122 -0
  337. package/out/remote/remotePackStatusBar.js +103 -0
  338. package/out/remote/serveClient.js +216 -0
  339. package/out/remoteSkills.js +191 -0
  340. package/out/runtime/index.js +32 -0
  341. package/out/runtime/interfaces/config-provider.js +32 -0
  342. package/out/runtime/interfaces/edit-tracker.js +22 -0
  343. package/out/runtime/interfaces/index.js +29 -0
  344. package/out/runtime/interfaces/logger.js +40 -0
  345. package/out/runtime/interfaces/permission-handler.js +66 -0
  346. package/out/runtime/interfaces/session-store.js +342 -0
  347. package/out/runtime/vscode/index.js +20 -0
  348. package/out/runtime/vscode/vscode-config-provider.js +77 -0
  349. package/out/runtime/vscode/vscode-edit-tracker.js +120 -0
  350. package/out/runtime/vscode/vscode-logger.js +30 -0
  351. package/out/runtime/vscode/vscode-permission-handler.js +72 -0
  352. package/out/runtime/vscode/vscode-session-store.js +22 -0
  353. package/out/scripts/agentide-litellm.ps1 +501 -0
  354. package/out/scripts/agentide-litellm.sh +700 -0
  355. package/out/scripts/agentide_litellm_hooks_v1.py +604 -0
  356. package/out/scripts/analytics/agent-fleet-widget.js +954 -0
  357. package/out/scripts/analytics/agentide-litellm-otel.config.yaml +160 -0
  358. package/out/scripts/analytics/default-seed-scripts.json +12 -0
  359. package/out/scripts/analytics/duckdbLocator.js +29 -0
  360. package/out/scripts/analytics/fleet-local-adapter.js +246 -0
  361. package/out/scripts/analytics/fleet-workflow.sh +263 -0
  362. package/out/scripts/analytics/generate-default-charts.py +685 -0
  363. package/out/scripts/analytics/hotspot-analysis-widget.js +909 -0
  364. package/out/scripts/analytics/index-traces.py +1394 -0
  365. package/out/scripts/analytics/install-analytics.sh +286 -0
  366. package/out/scripts/analytics/live-fleet-4-collector.cjs +315 -0
  367. package/out/scripts/analytics/live-fleet-4-daemon.mjs +134 -0
  368. package/out/scripts/analytics/live-fleet-4-widget.cjs +433 -0
  369. package/out/scripts/analytics/otel-collector.yaml +55 -0
  370. package/out/scripts/analytics/query-analytics.ps1 +396 -0
  371. package/out/scripts/analytics/query-analytics.sh +2551 -0
  372. package/out/scripts/analytics/session-comparison-widget.js +1468 -0
  373. package/out/scripts/analytics/session-timeline-widget.js +2137 -0
  374. package/out/scripts/analytics/setup-analytics.ps1 +432 -0
  375. package/out/scripts/analytics/setup-analytics.sh +854 -0
  376. package/out/scripts/analytics/start-analytics.ps1 +169 -0
  377. package/out/scripts/analytics/start-analytics.sh +323 -0
  378. package/out/scripts/analytics/token-cost-flamegraph-widget.js +954 -0
  379. package/out/scripts/analytics/token-cost-trend-widget.js +1062 -0
  380. package/out/scripts/analytics/workflows-collector.cjs +315 -0
  381. package/out/scripts/analytics/workflows-daemon.mjs +186 -0
  382. package/out/scripts/analytics/workflows-widget.cjs +433 -0
  383. package/out/scripts/analytics/wow-model-widget.js +191 -0
  384. package/out/scripts/gateway/Dockerfile.serve +45 -0
  385. package/out/scripts/gateway/docker-serve.sh +248 -0
  386. package/out/scripts/gateway/setup-gateway-server.ps1 +953 -0
  387. package/out/scripts/gateway/setup-gateway-server.sh +935 -0
  388. package/out/scripts/gateway/setup-gateway.ps1 +1813 -0
  389. package/out/scripts/gateway/setup-gateway.sh +1541 -0
  390. package/out/scripts/setup-cli.ps1 +86 -0
  391. package/out/scripts/setup-cli.sh +117 -0
  392. package/out/scripts/tools-manifest.json +83 -0
  393. package/out/scripts/vendor-tools.mjs +234 -0
  394. package/out/session.js +272 -0
  395. package/out/shared/agentEngines.js +170 -0
  396. package/out/shared/index.js +109 -0
  397. package/out/shared/models.js +235 -0
  398. package/out/shared/providers.js +202 -0
  399. package/out/skills/publish.js +178 -0
  400. package/out/spotlight/install.js +339 -0
  401. package/out/spotlight/publish.js +544 -0
  402. package/out/spotlight/yaml.js +31 -0
  403. package/out/store/github/setup.js +235 -0
  404. package/out/store/github/templates/build-index.ts +339 -0
  405. package/out/store/github/templates/build-index.yml +97 -0
  406. package/out/store/github/templates/package.json +11 -0
  407. package/out/store/index.js +755 -0
  408. package/out/store/oramaIndex.js +884 -0
  409. package/out/store/shared.js +241 -0
  410. package/out/sync.js +1827 -0
  411. package/out/terminalLogger.js +358 -0
  412. package/out/test/agentEnvFile.test.js +97 -0
  413. package/out/test/agentYaml.test.js +373 -0
  414. package/out/test/agentpack.test.js +639 -0
  415. package/out/test/analytics-lifecycle.test.js +174 -0
  416. package/out/test/analytics.test.js +524 -0
  417. package/out/test/auth-bootstrapApply.test.js +102 -0
  418. package/out/test/auth-broker.test.js +434 -0
  419. package/out/test/auth-callback.test.js +62 -0
  420. package/out/test/branding.test.js +229 -0
  421. package/out/test/cli-agent-id.test.js +55 -0
  422. package/out/test/credential-context.test.js +105 -0
  423. package/out/test/customEngineInstall.test.js +100 -0
  424. package/out/test/customRegistry.test.js +122 -0
  425. package/out/test/deploy-init.test.js +532 -0
  426. package/out/test/deps-auto-install.test.js +81 -0
  427. package/out/test/engine-integration.test.js +297 -0
  428. package/out/test/enginePaths.test.js +195 -0
  429. package/out/test/enginePublish.test.js +82 -0
  430. package/out/test/fleetDisplayUtils.test.js +96 -0
  431. package/out/test/gateway-client.test.js +381 -0
  432. package/out/test/gateway-lifecycle.test.js +608 -0
  433. package/out/test/gateway-outbound.test.js +531 -0
  434. package/out/test/gateway-reset-mappings.test.js +221 -0
  435. package/out/test/gateway-route-index.test.js +235 -0
  436. package/out/test/gateway-session-routing.test.js +47 -0
  437. package/out/test/gateway-session-store.test.js +216 -0
  438. package/out/test/github.test.js +232 -0
  439. package/out/test/headless-sync.test.js +279 -0
  440. package/out/test/helpers/manifest-fixtures.js +27 -0
  441. package/out/test/mcp-obo.test.js +82 -0
  442. package/out/test/mcp.test.js +307 -0
  443. package/out/test/openclaw-channel-routing.test.js +191 -0
  444. package/out/test/pathSafety.test.js +63 -0
  445. package/out/test/permissions.test.js +175 -0
  446. package/out/test/persistent-engine-worker.test.js +220 -0
  447. package/out/test/refreshPolicy.test.js +60 -0
  448. package/out/test/registry.test.js +351 -0
  449. package/out/test/runtime.test.js +349 -0
  450. package/out/test/scheduledFleetStorage.test.js +72 -0
  451. package/out/test/serve-hardening.test.js +192 -0
  452. package/out/test/serveClient.test.js +173 -0
  453. package/out/test/sessionJsonlFleet.test.js +78 -0
  454. package/out/test/shared.test.js +173 -0
  455. package/out/test/skills-mcp.test.js +299 -0
  456. package/out/test/spotlightPublish.test.js +299 -0
  457. package/out/test/spotlightYaml.test.js +58 -0
  458. package/out/test/state-sync-peer.test.js +74 -0
  459. package/out/test/store.test.js +331 -0
  460. package/out/test/stores.test.js +504 -0
  461. package/out/test/sync-switch.test.js +251 -0
  462. package/out/test/teamViewDiscovery.test.js +90 -0
  463. package/out/test/toolPath.test.js +120 -0
  464. package/out/test/utils.test.js +308 -0
  465. package/out/test/viewsConfigMerge.test.js +180 -0
  466. package/out/test/windows-command-line.test.js +75 -0
  467. package/out/utils/agentEnvFile.js +131 -0
  468. package/out/utils/binary.js +175 -0
  469. package/out/utils/index.js +37 -0
  470. package/out/utils/network.js +74 -0
  471. package/out/utils/pathSafety.js +60 -0
  472. package/out/utils/paths.js +116 -0
  473. package/out/utils/pid.js +180 -0
  474. package/out/utils/pythonRunner.js +258 -0
  475. package/out/utils/resolveNodeBinary.js +168 -0
  476. package/out/utils/toolPath.js +83 -0
  477. package/out/utils/windowsCommandLine.js +29 -0
  478. package/out/utils.js +915 -0
  479. package/out/workspaceFolders.js +59 -0
  480. package/package.json +41 -0
@@ -0,0 +1,1394 @@
1
+ #!/usr/bin/env python3
2
+ # Copyright (c) AgentHippo.ai. All rights reserved.
3
+ """
4
+ Incremental JSONL → DuckDB indexing worker.
5
+
6
+ Converts OpenTelemetry JSONL trace files to indexed DuckDB database for fast analytics queries.
7
+
8
+ Usage:
9
+ python3 index-traces.py <traces_dir>
10
+ python3 index-traces.py <traces_dir> <output_db_path>
11
+
12
+ Environment variables:
13
+ AGENTIDE_TRACES_DIR - Path to traces directory (can also be passed as arg)
14
+ AGENTIDE_INDEX_DB_PATH - Path to output .duckdb file
15
+
16
+ Exit codes:
17
+ 0 = success (indexing completed)
18
+ 1 = error (indexing failed)
19
+ 2 = already indexed (no changes needed)
20
+ 3 = indexing already in progress
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import ast
26
+ import hashlib
27
+ import json
28
+ import logging
29
+ import os
30
+ import sys
31
+ from datetime import datetime
32
+ from pathlib import Path
33
+ from typing import Any
34
+
35
+ # Setup logging
36
+ logging.basicConfig(
37
+ level=logging.DEBUG,
38
+ format='%(levelname)s: %(message)s',
39
+ stream=sys.stderr,
40
+ )
41
+ log = logging.getLogger(__name__)
42
+
43
+
44
+ def get_traces_dir(arg_traces_dir: str | None = None) -> Path:
45
+ """Get traces directory from arg, env var, or default."""
46
+ if arg_traces_dir:
47
+ return Path(arg_traces_dir)
48
+
49
+ env_traces_dir = os.environ.get('AGENTIDE_TRACES_DIR', '').strip()
50
+ if env_traces_dir:
51
+ return Path(env_traces_dir)
52
+
53
+ # Default: ~/.agent-hippo/analytics/traces
54
+ agent_home = Path.home() / '.agent-hippo'
55
+ return agent_home / 'analytics' / 'traces'
56
+
57
+
58
+ def get_db_path(traces_dir: Path, arg_db_path: str | None = None) -> Path:
59
+ """Get database path from arg, env var, or default (traces_dir/analytics.duckdb)."""
60
+ if arg_db_path:
61
+ return Path(arg_db_path)
62
+
63
+ env_db_path = os.environ.get('AGENTIDE_INDEX_DB_PATH', '').strip()
64
+ if env_db_path:
65
+ return Path(env_db_path)
66
+
67
+ return traces_dir / 'analytics.duckdb'
68
+
69
+
70
+ def get_state_file(traces_dir: Path) -> Path:
71
+ """Get indexing state file path."""
72
+ return traces_dir / '.duckdb-index.json'
73
+
74
+
75
+ def get_lock_file(traces_dir: Path) -> Path:
76
+ """Get process lock file path."""
77
+ return traces_dir / '.indexing.lock'
78
+
79
+
80
+ def try_acquire_index_lock(lock_file: Path):
81
+ """
82
+ Try to acquire a cross-process exclusive lock.
83
+ Returns an open file handle when acquired, otherwise None.
84
+ """
85
+ lock_file.parent.mkdir(parents=True, exist_ok=True)
86
+ handle = lock_file.open('a+b')
87
+
88
+ # Ensure the file has at least one byte for Windows byte-range locking.
89
+ handle.seek(0, os.SEEK_END)
90
+ if handle.tell() == 0:
91
+ handle.write(b'0')
92
+ handle.flush()
93
+
94
+ try:
95
+ if os.name == 'nt':
96
+ import msvcrt
97
+ handle.seek(0)
98
+ msvcrt.locking(handle.fileno(), msvcrt.LK_NBLCK, 1)
99
+ else:
100
+ import fcntl
101
+ fcntl.flock(handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
102
+ except OSError:
103
+ handle.close()
104
+ return None
105
+
106
+ # Record owner PID for diagnostics.
107
+ handle.seek(0)
108
+ handle.truncate(0)
109
+ handle.write(f"{os.getpid()}\n".encode('utf-8'))
110
+ handle.flush()
111
+
112
+ return handle
113
+
114
+
115
+ def release_index_lock(handle) -> None:
116
+ """Release the process lock (best effort)."""
117
+ if handle is None:
118
+ return
119
+
120
+ try:
121
+ if os.name == 'nt':
122
+ import msvcrt
123
+ handle.seek(0)
124
+ msvcrt.locking(handle.fileno(), msvcrt.LK_UNLCK, 1)
125
+ else:
126
+ import fcntl
127
+ fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
128
+ except OSError:
129
+ pass
130
+ finally:
131
+ handle.close()
132
+
133
+
134
+ def load_index_state(state_file: Path) -> dict[str, Any]:
135
+ """Load indexing state from disk."""
136
+ try:
137
+ if state_file.exists():
138
+ content = state_file.read_text('utf-8')
139
+ return json.loads(content)
140
+ except Exception as e:
141
+ log.debug(f"Failed to load state file: {e}")
142
+
143
+ return {
144
+ 'version': 2,
145
+ 'indexed': False,
146
+ 'lastMtime': 0,
147
+ 'duckdbPath': '',
148
+ }
149
+
150
+
151
+ def save_index_state(state_file: Path, state: dict[str, Any]) -> None:
152
+ """Save indexing state to disk."""
153
+ try:
154
+ state_file.write_text(json.dumps(state, indent=2), 'utf-8')
155
+ except Exception as e:
156
+ log.warning(f"Failed to save state file: {e}")
157
+
158
+
159
+ def get_latest_jsonl_mtime(traces_dir: Path) -> int:
160
+ """Get maximum mtime of all JSONL files in traces_dir."""
161
+ try:
162
+ max_mtime = 0
163
+ for jsonl_file in traces_dir.glob('*.jsonl'):
164
+ try:
165
+ mtime_ms = int(jsonl_file.stat().st_mtime * 1000)
166
+ max_mtime = max(max_mtime, mtime_ms)
167
+ except OSError:
168
+ pass
169
+ return max_mtime
170
+ except Exception as e:
171
+ log.error(f"Failed to get JSONL mtimes: {e}")
172
+ return 0
173
+
174
+
175
+ def ensure_schema(conn) -> None:
176
+ """Create normalized LLM-agent analytics tables, marts, and indexes."""
177
+ conn.execute("""
178
+ CREATE TABLE IF NOT EXISTS model_calls (
179
+ model_call_id VARCHAR PRIMARY KEY,
180
+ trace_id VARCHAR,
181
+ span_id VARCHAR,
182
+ span_name VARCHAR,
183
+ session_id VARCHAR,
184
+ turn_id VARCHAR,
185
+ request_id VARCHAR,
186
+ user_id VARCHAR,
187
+ project_id VARCHAR,
188
+ agent_id VARCHAR,
189
+ agent_version VARCHAR,
190
+ source VARCHAR,
191
+ engine VARCHAR,
192
+ model VARCHAR,
193
+ start_ms BIGINT,
194
+ end_ms BIGINT,
195
+ duration_ms DOUBLE,
196
+ prompt_tokens BIGINT,
197
+ completion_tokens BIGINT,
198
+ total_tokens BIGINT,
199
+ total_cost_usd DOUBLE,
200
+ status VARCHAR,
201
+ error_type VARCHAR,
202
+ error_message VARCHAR,
203
+ input_messages_raw VARCHAR,
204
+ output_messages_raw VARCHAR,
205
+ created_at TIMESTAMP,
206
+ updated_at TIMESTAMP
207
+ )
208
+ """)
209
+
210
+ conn.execute("""
211
+ CREATE TABLE IF NOT EXISTS sessions (
212
+ session_id VARCHAR PRIMARY KEY,
213
+ user_id VARCHAR,
214
+ project_id VARCHAR,
215
+ agent_id VARCHAR,
216
+ agent_version VARCHAR,
217
+ source VARCHAR,
218
+ engine VARCHAR,
219
+ started_at TIMESTAMP,
220
+ ended_at TIMESTAMP,
221
+ status VARCHAR,
222
+ total_turns BIGINT,
223
+ total_requests BIGINT,
224
+ total_cost_usd DOUBLE,
225
+ total_tokens BIGINT,
226
+ created_at TIMESTAMP,
227
+ updated_at TIMESTAMP
228
+ )
229
+ """)
230
+
231
+ conn.execute("""
232
+ CREATE TABLE IF NOT EXISTS turns (
233
+ turn_id VARCHAR PRIMARY KEY,
234
+ session_id VARCHAR,
235
+ request_id VARCHAR,
236
+ turn_index BIGINT,
237
+ started_at TIMESTAMP,
238
+ ended_at TIMESTAMP,
239
+ status VARCHAR,
240
+ model VARCHAR,
241
+ provider VARCHAR,
242
+ prompt_tokens BIGINT,
243
+ completion_tokens BIGINT,
244
+ total_tokens BIGINT,
245
+ cost_usd DOUBLE,
246
+ trace_id VARCHAR,
247
+ created_at TIMESTAMP,
248
+ updated_at TIMESTAMP
249
+ )
250
+ """)
251
+
252
+ conn.execute("""
253
+ CREATE TABLE IF NOT EXISTS tool_calls (
254
+ tool_call_id VARCHAR PRIMARY KEY,
255
+ session_id VARCHAR,
256
+ turn_id VARCHAR,
257
+ request_id VARCHAR,
258
+ model_call_id VARCHAR,
259
+ trace_id VARCHAR,
260
+ span_id VARCHAR,
261
+ tool_name VARCHAR,
262
+ arguments_json JSON,
263
+ arguments_text VARCHAR,
264
+ call_ts TIMESTAMP,
265
+ result_json JSON,
266
+ result_text VARCHAR,
267
+ status VARCHAR,
268
+ duration_ms DOUBLE,
269
+ error_type VARCHAR,
270
+ error_message VARCHAR,
271
+ created_at TIMESTAMP,
272
+ updated_at TIMESTAMP
273
+ )
274
+ """)
275
+
276
+ conn.execute("""
277
+ CREATE TABLE IF NOT EXISTS artifacts (
278
+ artifact_id VARCHAR PRIMARY KEY,
279
+ session_id VARCHAR,
280
+ turn_id VARCHAR,
281
+ trace_id VARCHAR,
282
+ kind VARCHAR,
283
+ mime_type VARCHAR,
284
+ storage_uri VARCHAR,
285
+ sha256 VARCHAR,
286
+ byte_size BIGINT,
287
+ created_at TIMESTAMP
288
+ )
289
+ """)
290
+
291
+ # Gold compatibility/fast dashboard table
292
+ conn.execute("""
293
+ CREATE TABLE IF NOT EXISTS spans (
294
+ trace_id VARCHAR,
295
+ span_id VARCHAR,
296
+ span_name VARCHAR,
297
+ model VARCHAR,
298
+ cost DOUBLE,
299
+ input_tokens BIGINT,
300
+ output_tokens BIGINT,
301
+ session_id VARCHAR,
302
+ user_id VARCHAR,
303
+ project_id VARCHAR,
304
+ agent_id VARCHAR,
305
+ engine VARCHAR,
306
+ duration_ms DOUBLE,
307
+ start_ms BIGINT,
308
+ created_at TIMESTAMP,
309
+ PRIMARY KEY(trace_id, span_id)
310
+ )
311
+ """)
312
+ try:
313
+ conn.execute("ALTER TABLE spans ADD COLUMN IF NOT EXISTS engine VARCHAR")
314
+ except Exception as e:
315
+ log.debug(f"Spans engine column (may already exist): {e}")
316
+
317
+ conn.execute("""
318
+ CREATE TABLE IF NOT EXISTS processed_files (
319
+ file_path VARCHAR PRIMARY KEY,
320
+ mtime_ms BIGINT NOT NULL,
321
+ size_bytes BIGINT NOT NULL,
322
+ processed_at TIMESTAMP NOT NULL
323
+ )
324
+ """)
325
+
326
+ conn.execute("""
327
+ CREATE OR REPLACE VIEW agent_kpis_daily AS
328
+ SELECT
329
+ strftime(to_timestamp(start_ms / 1000.0), '%Y-%m-%d') AS day,
330
+ coalesce(agent_id, '(no agent)') AS agent_id,
331
+ coalesce(agent_version, '(unknown)') AS agent_version,
332
+ coalesce(model, '(unknown)') AS model,
333
+ count(*) AS requests,
334
+ count(distinct session_id) AS sessions,
335
+ count(distinct turn_id) AS turns,
336
+ round(sum(coalesce(total_cost_usd, 0.0)), 6) AS total_cost_usd,
337
+ sum(coalesce(total_tokens, 0)) AS total_tokens,
338
+ round(avg(duration_ms), 2) AS avg_latency_ms,
339
+ round(percentile_cont(0.50) within group (order by duration_ms), 2) AS p50_latency_ms,
340
+ round(percentile_cont(0.95) within group (order by duration_ms), 2) AS p95_latency_ms,
341
+ round(100.0 * sum(case when status = 'error' then 1 else 0 end) / nullif(count(*), 0), 2) AS error_rate_pct
342
+ FROM model_calls
343
+ GROUP BY 1, 2, 3, 4
344
+ """)
345
+
346
+ # Common indexes for dashboard and skill queries.
347
+ for stmt in [
348
+ "CREATE INDEX IF NOT EXISTS idx_model_calls_session_id ON model_calls(session_id)",
349
+ "CREATE INDEX IF NOT EXISTS idx_model_calls_start_ms ON model_calls(start_ms)",
350
+ "CREATE INDEX IF NOT EXISTS idx_model_calls_agent_id ON model_calls(agent_id)",
351
+ "CREATE INDEX IF NOT EXISTS idx_model_calls_project_id ON model_calls(project_id)",
352
+ "CREATE INDEX IF NOT EXISTS idx_tool_calls_session ON tool_calls(session_id)",
353
+ "CREATE INDEX IF NOT EXISTS idx_spans_session ON spans(session_id)",
354
+ "CREATE INDEX IF NOT EXISTS idx_spans_start_ms ON spans(start_ms)",
355
+ "CREATE INDEX IF NOT EXISTS idx_spans_model ON spans(model)",
356
+ "CREATE INDEX IF NOT EXISTS idx_spans_agent_id ON spans(agent_id)",
357
+ "CREATE INDEX IF NOT EXISTS idx_spans_project_id ON spans(project_id)",
358
+ ]:
359
+ try:
360
+ conn.execute(stmt)
361
+ except Exception as e:
362
+ log.debug(f"Index creation hint (may already exist): {e}")
363
+
364
+
365
+ def get_file_stats(jsonl_file: Path) -> tuple[int, int]:
366
+ stats = jsonl_file.stat()
367
+ return int(stats.st_mtime * 1000), int(stats.st_size)
368
+
369
+
370
+ def get_processed_file_map(conn) -> dict[str, tuple[int, int]]:
371
+ rows = conn.execute(
372
+ "SELECT file_path, mtime_ms, size_bytes FROM processed_files"
373
+ ).fetchall()
374
+ return {row[0]: (int(row[1]), int(row[2])) for row in rows}
375
+
376
+
377
+ def get_changed_files(conn, traces_dir: Path) -> list[Path]:
378
+ processed = get_processed_file_map(conn)
379
+ changed: list[Path] = []
380
+
381
+ for jsonl_file in sorted(traces_dir.glob('*.jsonl')):
382
+ try:
383
+ mtime_ms, size_bytes = get_file_stats(jsonl_file)
384
+ except OSError:
385
+ continue
386
+
387
+ key = str(jsonl_file)
388
+ previous = processed.get(key)
389
+ if previous is None or previous[0] != mtime_ms or previous[1] != size_bytes:
390
+ changed.append(jsonl_file)
391
+
392
+ return changed
393
+
394
+
395
+ def _safe_json_parse(raw: Any) -> Any:
396
+ if raw is None:
397
+ return None
398
+ if isinstance(raw, (dict, list)):
399
+ return raw
400
+
401
+ text = str(raw).strip()
402
+ if text == '':
403
+ return None
404
+
405
+ # try strict JSON first
406
+ try:
407
+ parsed = json.loads(text)
408
+ except Exception:
409
+ parsed = None
410
+
411
+ # fallback to python-literal-like payloads (single-quoted dict/list)
412
+ if parsed is None:
413
+ try:
414
+ parsed = ast.literal_eval(text)
415
+ except Exception:
416
+ parsed = None
417
+
418
+ if isinstance(parsed, str) and parsed != text:
419
+ return _safe_json_parse(parsed)
420
+
421
+ return parsed
422
+
423
+
424
+ def _to_json_text(value: Any) -> str | None:
425
+ if value is None:
426
+ return None
427
+ try:
428
+ return json.dumps(value, ensure_ascii=False)
429
+ except Exception:
430
+ return None
431
+
432
+
433
+ def _extract_text_content(content: Any) -> str:
434
+ if content is None:
435
+ return ''
436
+ if isinstance(content, str):
437
+ return content
438
+ if isinstance(content, list):
439
+ parts: list[str] = []
440
+ for item in content:
441
+ if isinstance(item, str):
442
+ parts.append(item)
443
+ continue
444
+ if isinstance(item, dict):
445
+ item_type = str(item.get('type', '')).lower()
446
+ if item_type in {'thinking', 'signature'}:
447
+ continue
448
+ text = item.get('text')
449
+ if isinstance(text, str) and text:
450
+ parts.append(text)
451
+ elif item.get('content') is not None:
452
+ parts.append(_extract_text_content(item.get('content')))
453
+ return '\n'.join([p for p in parts if p])
454
+ if isinstance(content, dict):
455
+ if isinstance(content.get('text'), str):
456
+ return content.get('text', '')
457
+ if content.get('content') is not None:
458
+ return _extract_text_content(content.get('content'))
459
+ if content.get('input') is not None:
460
+ return _extract_text_content(content.get('input'))
461
+ return str(content)
462
+
463
+
464
+ def _normalize_messages(payload: Any, default_role: str) -> list[dict[str, Any]]:
465
+ parsed = _safe_json_parse(payload)
466
+
467
+ if parsed is None:
468
+ return []
469
+
470
+ if isinstance(parsed, dict):
471
+ if isinstance(parsed.get('messages'), list):
472
+ parsed = parsed['messages']
473
+ elif isinstance(parsed.get('choices'), list):
474
+ out: list[dict[str, Any]] = []
475
+ for choice in parsed.get('choices', []):
476
+ if isinstance(choice, dict):
477
+ msg = choice.get('message')
478
+ if isinstance(msg, dict):
479
+ out.append(msg)
480
+ return out
481
+ else:
482
+ parsed = [parsed]
483
+
484
+ if not isinstance(parsed, list):
485
+ return [{"role": default_role, "content": str(parsed)}]
486
+
487
+ out: list[dict[str, Any]] = []
488
+ for entry in parsed:
489
+ if isinstance(entry, dict):
490
+ out.append(entry)
491
+ else:
492
+ out.append({"role": default_role, "content": str(entry)})
493
+ return out
494
+
495
+
496
+ def _tool_call_id(session_id: str | None, trace_id: str, call_id: str | None, idx: int) -> str:
497
+ if call_id:
498
+ return call_id
499
+ seed = f"{session_id or ''}|{trace_id}|tool|{idx}"
500
+ return hashlib.sha1(seed.encode('utf-8')).hexdigest()
501
+
502
+
503
+ def extract_messages_and_tools(conn) -> None:
504
+ """
505
+ Parse tool-call payloads from current temp batch (tmp_model_calls)
506
+ and upsert into tool_calls.
507
+ """
508
+ rows = conn.execute(
509
+ """
510
+ SELECT
511
+ model_call_id,
512
+ trace_id,
513
+ span_id,
514
+ session_id,
515
+ turn_id,
516
+ request_id,
517
+ start_ms,
518
+ end_ms,
519
+ status,
520
+ error_type,
521
+ error_message,
522
+ input_messages_raw,
523
+ output_messages_raw,
524
+ span_tool_name,
525
+ span_tool_call_id,
526
+ span_tool_arguments_raw,
527
+ span_tool_result_raw
528
+ FROM tmp_model_calls
529
+ """
530
+ ).fetchall()
531
+
532
+ tool_results_by_id: dict[str, dict[str, Any]] = {}
533
+
534
+ for row in rows:
535
+ (
536
+ model_call_id,
537
+ trace_id,
538
+ span_id,
539
+ session_id,
540
+ turn_id,
541
+ request_id,
542
+ start_ms,
543
+ end_ms,
544
+ call_status,
545
+ call_error_type,
546
+ call_error_message,
547
+ input_raw,
548
+ output_raw,
549
+ span_tool_name,
550
+ span_tool_call_id,
551
+ span_tool_arguments_raw,
552
+ span_tool_result_raw,
553
+ ) = row
554
+
555
+ tool_call_inserted = False
556
+ tool_base_ts = int(end_ms) if end_ms is not None else int(start_ms) if start_ms is not None else 0
557
+
558
+ if span_tool_name:
559
+ fallback_call_id = span_tool_call_id
560
+ if fallback_call_id is not None:
561
+ fallback_call_id = str(fallback_call_id)
562
+ fallback_tool_call_id = _tool_call_id(session_id, str(trace_id), fallback_call_id, 0)
563
+ fallback_status = 'error' if str(call_status or '').lower() == 'error' else 'called'
564
+ fallback_result_text = _extract_text_content(span_tool_result_raw)
565
+ fallback_result_json = _to_json_text(_safe_json_parse(span_tool_result_raw) if span_tool_result_raw else None)
566
+ if fallback_result_json is None and span_tool_result_raw:
567
+ fallback_result_json = _to_json_text(span_tool_result_raw)
568
+
569
+ conn.execute(
570
+ """
571
+ INSERT INTO tool_calls (
572
+ tool_call_id, session_id, turn_id, request_id, model_call_id,
573
+ trace_id, span_id, tool_name, arguments_json, arguments_text,
574
+ call_ts, result_json, result_text, status, duration_ms,
575
+ error_type, error_message, created_at, updated_at
576
+ )
577
+ VALUES (
578
+ ?, ?, ?, ?, ?, ?, ?, ?, ?::JSON, ?,
579
+ to_timestamp(? / 1000.0), ?::JSON, ?, ?, NULL,
580
+ ?, ?, now(), now()
581
+ )
582
+ ON CONFLICT(tool_call_id) DO UPDATE SET
583
+ result_json = COALESCE(excluded.result_json, tool_calls.result_json),
584
+ result_text = COALESCE(excluded.result_text, tool_calls.result_text),
585
+ status = CASE
586
+ WHEN excluded.status = 'error' THEN 'error'
587
+ WHEN excluded.status = 'ok' THEN 'ok'
588
+ ELSE tool_calls.status
589
+ END,
590
+ error_type = COALESCE(excluded.error_type, tool_calls.error_type),
591
+ error_message = COALESCE(excluded.error_message, tool_calls.error_message),
592
+ updated_at = now()
593
+ """,
594
+ [
595
+ fallback_tool_call_id,
596
+ session_id,
597
+ turn_id,
598
+ request_id,
599
+ model_call_id,
600
+ trace_id,
601
+ span_id,
602
+ str(span_tool_name),
603
+ _to_json_text(_safe_json_parse(span_tool_arguments_raw) if span_tool_arguments_raw else None)
604
+ or _to_json_text(span_tool_arguments_raw),
605
+ _extract_text_content(span_tool_arguments_raw),
606
+ tool_base_ts,
607
+ fallback_result_json,
608
+ fallback_result_text,
609
+ fallback_status,
610
+ call_error_type,
611
+ call_error_message,
612
+ ],
613
+ )
614
+ tool_call_inserted = True
615
+
616
+ if str(call_status or '').lower() == 'error' and not tool_call_inserted and call_error_message:
617
+ synthetic_tool_call_id = _tool_call_id(session_id, str(trace_id), None, 0)
618
+ conn.execute(
619
+ """
620
+ INSERT INTO tool_calls (
621
+ tool_call_id, session_id, turn_id, request_id, model_call_id,
622
+ trace_id, span_id, tool_name, arguments_json, arguments_text,
623
+ call_ts, result_json, result_text, status, duration_ms,
624
+ error_type, error_message, created_at, updated_at
625
+ )
626
+ VALUES (
627
+ ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL,
628
+ to_timestamp(? / 1000.0), NULL, ?, 'error', NULL,
629
+ ?, ?, now(), now()
630
+ )
631
+ ON CONFLICT(tool_call_id) DO UPDATE SET
632
+ status = 'error',
633
+ result_text = COALESCE(excluded.result_text, tool_calls.result_text),
634
+ error_type = COALESCE(excluded.error_type, tool_calls.error_type),
635
+ error_message = COALESCE(excluded.error_message, tool_calls.error_message),
636
+ updated_at = now()
637
+ """,
638
+ [
639
+ synthetic_tool_call_id,
640
+ session_id,
641
+ turn_id,
642
+ request_id,
643
+ model_call_id,
644
+ trace_id,
645
+ span_id,
646
+ '(tool-error)',
647
+ tool_base_ts,
648
+ call_error_message,
649
+ call_error_type,
650
+ call_error_message,
651
+ ],
652
+ )
653
+
654
+ if span_tool_call_id:
655
+ tool_results_by_id[str(span_tool_call_id)] = {
656
+ 'result_json': _to_json_text(_safe_json_parse(span_tool_result_raw) if span_tool_result_raw else None),
657
+ 'result_text': _extract_text_content(span_tool_result_raw),
658
+ 'status': 'error' if str(call_status or '').lower() == 'error' else 'ok',
659
+ 'updated_at_ms': tool_base_ts,
660
+ }
661
+ if tool_results_by_id[str(span_tool_call_id)]['result_json'] is None and span_tool_result_raw:
662
+ tool_results_by_id[str(span_tool_call_id)]['result_json'] = _to_json_text(span_tool_result_raw)
663
+
664
+ input_messages = _normalize_messages(input_raw, 'user')
665
+ output_messages = _normalize_messages(output_raw, 'assistant')
666
+
667
+ # tool_result blocks may appear in input messages and complete tool calls emitted in output
668
+ for i, msg in enumerate(input_messages):
669
+ content = msg.get('content', msg.get('parts', msg.get('text', '')))
670
+ if isinstance(content, list):
671
+ for j, block in enumerate(content):
672
+ if not isinstance(block, dict):
673
+ continue
674
+ block_type = str(block.get('type', '')).lower()
675
+ if block_type != 'tool_result':
676
+ continue
677
+ call_id = block.get('tool_use_id') or block.get('call_id') or block.get('id')
678
+ if not call_id:
679
+ continue
680
+ tool_results_by_id[str(call_id)] = {
681
+ 'result_json': _to_json_text(block.get('content', block.get('result'))),
682
+ 'result_text': _extract_text_content(block.get('content', block.get('result'))),
683
+ 'status': 'ok',
684
+ 'updated_at_ms': int(start_ms) + j if start_ms is not None else 0,
685
+ }
686
+
687
+ # Output-side tool calls
688
+ for i, msg in enumerate(output_messages):
689
+ content = msg.get('content', msg.get('parts', msg.get('text', '')))
690
+ output_ts = int(end_ms) if end_ms is not None else int(start_ms) if start_ms is not None else 0
691
+
692
+ # Normalize tool blocks from multiple provider formats.
693
+ tool_blocks: list[dict[str, Any]] = []
694
+ if isinstance(content, list):
695
+ tool_blocks.extend([b for b in content if isinstance(b, dict)])
696
+
697
+ direct_tool_calls = msg.get('tool_calls')
698
+ if isinstance(direct_tool_calls, list):
699
+ for block in direct_tool_calls:
700
+ if isinstance(block, dict):
701
+ normalized = dict(block)
702
+ normalized.setdefault('type', 'tool_call')
703
+ tool_blocks.append(normalized)
704
+
705
+ function_call = msg.get('function_call')
706
+ if isinstance(function_call, dict):
707
+ tool_blocks.append({'type': 'function_call', 'function': function_call})
708
+
709
+ if not tool_blocks:
710
+ continue
711
+
712
+ for j, block in enumerate(tool_blocks):
713
+ if not isinstance(block, dict):
714
+ continue
715
+
716
+ block_type = str(block.get('type', '')).lower()
717
+ if block_type not in {'tool_use', 'tool_call', 'function_call', 'server_tool_use', 'mcp_tool_use'}:
718
+ continue
719
+
720
+ if block_type in {'server_tool_use', 'mcp_tool_use'} and not block.get('name'):
721
+ # Normalize Anthropic streaming variants that still represent tool invocation.
722
+ block['name'] = block.get('tool_name') or block.get('toolName')
723
+ block.setdefault('input', block.get('arguments'))
724
+
725
+ if block_type == 'tool_call' and not block.get('function') and isinstance(block.get('name'), str):
726
+ block['function'] = {
727
+ 'name': block.get('name'),
728
+ 'arguments': block.get('arguments') or block.get('input'),
729
+ }
730
+
731
+ if block_type == 'function_call' and not block.get('function') and (block.get('name') or block.get('arguments')):
732
+ block['function'] = {
733
+ 'name': block.get('name'),
734
+ 'arguments': block.get('arguments'),
735
+ }
736
+
737
+ if block_type == 'tool_use' and not block.get('name'):
738
+ block['name'] = block.get('tool_name') or block.get('toolName')
739
+
740
+ if block_type == 'tool_use' and block.get('input') is None and block.get('arguments') is not None:
741
+ block['input'] = block.get('arguments')
742
+
743
+ if block_type in {'tool_call', 'function_call'} and block.get('function') is None and block.get('name'):
744
+ block['function'] = {
745
+ 'name': block.get('name'),
746
+ 'arguments': block.get('arguments') or block.get('input'),
747
+ }
748
+
749
+ if block_type == 'function_call' and block.get('function') is None:
750
+ continue
751
+
752
+ if block_type == 'tool_call' and block.get('function') is None and block.get('name') is None:
753
+ continue
754
+
755
+ if block_type in {'server_tool_use', 'mcp_tool_use'} and not block.get('name'):
756
+ continue
757
+
758
+ if block_type == 'tool_use' and not block.get('name'):
759
+ continue
760
+
761
+ if block_type == 'tool_call' and block.get('function') is None and not block.get('name'):
762
+ continue
763
+
764
+ if block_type == 'function_call' and block.get('function') is None:
765
+ continue
766
+
767
+ # Harmonize final type for downstream extraction.
768
+ if block_type in {'server_tool_use', 'mcp_tool_use'}:
769
+ block_type = 'tool_use'
770
+
771
+ block['type'] = block_type
772
+
773
+
774
+ call_id = block.get('id') or block.get('tool_call_id') or block.get('call_id')
775
+ tool_name = (
776
+ block.get('name')
777
+ or (block.get('function', {}) or {}).get('name')
778
+ or 'unknown_tool'
779
+ )
780
+
781
+ args = (
782
+ block.get('input')
783
+ if block.get('input') is not None
784
+ else (block.get('function', {}) or {}).get('arguments')
785
+ )
786
+ args_json = _to_json_text(args)
787
+ args_text = _extract_text_content(args)
788
+
789
+ tool_call_id = _tool_call_id(session_id, str(trace_id), str(call_id) if call_id else None, j)
790
+ tool_result = tool_results_by_id.get(str(call_id)) if call_id else None
791
+
792
+ result_json = tool_result['result_json'] if tool_result else None
793
+ result_text = tool_result['result_text'] if tool_result else None
794
+ status = tool_result['status'] if tool_result else 'called'
795
+
796
+ conn.execute(
797
+ """
798
+ INSERT INTO tool_calls (
799
+ tool_call_id, session_id, turn_id, request_id, model_call_id,
800
+ trace_id, span_id, tool_name, arguments_json, arguments_text,
801
+ call_ts, result_json, result_text, status, duration_ms,
802
+ error_type, error_message, created_at, updated_at
803
+ )
804
+ VALUES (
805
+ ?, ?, ?, ?, ?, ?, ?, ?, ?::JSON, ?,
806
+ to_timestamp(? / 1000.0), ?::JSON, ?, ?, NULL,
807
+ NULL, NULL, now(), now()
808
+ )
809
+ ON CONFLICT(tool_call_id) DO UPDATE SET
810
+ result_json = COALESCE(excluded.result_json, tool_calls.result_json),
811
+ result_text = COALESCE(excluded.result_text, tool_calls.result_text),
812
+ status = CASE
813
+ WHEN excluded.status = 'ok' THEN 'ok'
814
+ ELSE tool_calls.status
815
+ END,
816
+ updated_at = now()
817
+ """,
818
+ [
819
+ tool_call_id,
820
+ session_id,
821
+ turn_id,
822
+ request_id,
823
+ model_call_id,
824
+ trace_id,
825
+ span_id,
826
+ str(tool_name),
827
+ args_json,
828
+ args_text,
829
+ output_ts + j,
830
+ result_json,
831
+ result_text,
832
+ status,
833
+ ],
834
+ )
835
+
836
+
837
+ def ingest_single_jsonl_file(conn, jsonl_file: Path) -> None:
838
+ """Ingest one JSONL file into Silver and Gold analytics tables."""
839
+ escaped = str(jsonl_file).replace("'", "''")
840
+
841
+ conn.execute("DROP TABLE IF EXISTS tmp_model_calls")
842
+ conn.execute(f"""
843
+ CREATE TEMP TABLE tmp_model_calls AS
844
+ WITH raw_spans AS (
845
+ SELECT unnest(resourceSpans) as rs
846
+ FROM read_json('{escaped}', format='newline_delimited', ignore_errors=true)
847
+ ),
848
+ spans_unnest AS (SELECT unnest(rs.scopeSpans) as ss FROM raw_spans),
849
+ flat_spans AS (SELECT unnest(ss.spans) as span FROM spans_unnest),
850
+ parsed AS (
851
+ SELECT
852
+ span.traceId as trace_id,
853
+ span.spanId as span_id,
854
+ span.name as span_name,
855
+ span.startTimeUnixNano::BIGINT / 1000000 as start_ms,
856
+ span.endTimeUnixNano::BIGINT / 1000000 as end_ms,
857
+ (span.endTimeUnixNano::BIGINT - span.startTimeUnixNano::BIGINT) / 1000000.0 as duration_ms,
858
+ json_extract(to_json(span), '$.attributes') as attrs_json
859
+ FROM flat_spans
860
+ ),
861
+ attrs AS (
862
+ SELECT
863
+ trace_id,
864
+ span_id,
865
+ span_name,
866
+ duration_ms,
867
+ start_ms,
868
+ end_ms,
869
+ json_extract_string(value, '$.key') as attr_key,
870
+ json_extract_string(value, '$.value.stringValue') as attr_string,
871
+ json_extract_string(value, '$.value.intValue') as attr_int,
872
+ json_extract(value, '$.value.doubleValue') as attr_double
873
+ FROM parsed, json_each(coalesce(attrs_json, '[]'::JSON))
874
+ ),
875
+ extracted AS (
876
+ SELECT
877
+ trace_id,
878
+ span_id,
879
+ span_name,
880
+ start_ms,
881
+ end_ms,
882
+ duration_ms,
883
+ max(CASE WHEN attr_key IN ('gen_ai.request.model','gen_ai.response.model','model') THEN attr_string END) as model,
884
+ max(CASE WHEN attr_key = 'gen_ai.cost.total_cost' THEN CAST(attr_double AS DOUBLE) END) as total_cost_usd,
885
+ max(CASE WHEN attr_key = 'gen_ai.usage.input_tokens' THEN CAST(attr_int AS BIGINT) END) as prompt_tokens,
886
+ max(CASE WHEN attr_key = 'gen_ai.usage.output_tokens' THEN CAST(attr_int AS BIGINT) END) as completion_tokens,
887
+ COALESCE(
888
+ NULLIF(regexp_extract(max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END), 'x-litellm-metadata-session-id'': ''([^'']+)''', 1), ''),
889
+ NULLIF(regexp_extract(max(CASE WHEN attr_key = 'metadata.requester_metadata' THEN attr_string END), 'session_id'': ''([^'']+)''', 1), '')
890
+ ) as session_id,
891
+ COALESCE(
892
+ NULLIF(regexp_extract(max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END), 'x-litellm-metadata-user-id'': ''([^'']+)''', 1), ''),
893
+ NULLIF(regexp_extract(max(CASE WHEN attr_key = 'metadata.requester_metadata' THEN attr_string END), 'user_id'': ''([^'']+)''', 1), '')
894
+ ) as user_id,
895
+ COALESCE(
896
+ NULLIF(regexp_extract(max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END), 'x-litellm-metadata-project-id'': ''([^'']+)''', 1), ''),
897
+ NULLIF(regexp_extract(max(CASE WHEN attr_key = 'metadata.requester_metadata' THEN attr_string END), 'project_id'': ''([^'']+)''', 1), '')
898
+ ) as project_id,
899
+ COALESCE(
900
+ NULLIF(regexp_extract(max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END), 'x-litellm-metadata-agent-id'': ''([^'']+)''', 1), ''),
901
+ NULLIF(regexp_extract(max(CASE WHEN attr_key = 'metadata.requester_metadata' THEN attr_string END), 'agent_id'': ''([^'']+)''', 1), '')
902
+ ) as agent_id,
903
+ COALESCE(
904
+ NULLIF(regexp_extract(max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END), 'x-litellm-metadata-agent-version'': ''([^'']+)''', 1), ''),
905
+ NULLIF(regexp_extract(max(CASE WHEN attr_key = 'metadata.requester_metadata' THEN attr_string END), 'agent_version'': ''([^'']+)''', 1), '')
906
+ ) as agent_version,
907
+ COALESCE(
908
+ NULLIF(regexp_extract(max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END), 'x-litellm-metadata-source'': ''([^'']+)''', 1), ''),
909
+ NULLIF(regexp_extract(max(CASE WHEN attr_key = 'metadata.requester_metadata' THEN attr_string END), 'source'': ''([^'']+)''', 1), '')
910
+ ) as source,
911
+ COALESCE(
912
+ NULLIF(regexp_extract(max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END), 'x-litellm-metadata-engine'': ''([^'']+)''', 1), ''),
913
+ NULLIF(regexp_extract(max(CASE WHEN attr_key = 'metadata.requester_metadata' THEN attr_string END), 'engine'': ''([^'']+)''', 1), '')
914
+ ) as engine,
915
+ COALESCE(
916
+ NULLIF(regexp_extract(max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END), 'x-litellm-metadata-request-id'': ''([^'']+)''', 1), ''),
917
+ NULLIF(regexp_extract(max(CASE WHEN attr_key = 'metadata.requester_metadata' THEN attr_string END), 'request_id'': ''([^'']+)''', 1), '')
918
+ ) as request_id,
919
+ max(CASE WHEN attr_key IN ('otel.status_code') THEN attr_string END) as otel_status,
920
+ max(CASE WHEN attr_key IN ('exception.type') THEN attr_string END) as error_type,
921
+ max(CASE WHEN attr_key IN ('exception.message') THEN attr_string END) as error_message,
922
+ max(CASE WHEN attr_key IN (
923
+ 'gen_ai.tool.name',
924
+ 'tool.name',
925
+ 'toolName'
926
+ ) THEN attr_string END) as span_tool_name,
927
+ max(CASE WHEN attr_key IN (
928
+ 'gen_ai.tool.call.id',
929
+ 'tool.call.id',
930
+ 'tool_call_id',
931
+ 'tool.use.id',
932
+ 'tool_use_id'
933
+ ) THEN attr_string END) as span_tool_call_id,
934
+ max(CASE WHEN attr_key IN (
935
+ 'gen_ai.tool.arguments',
936
+ 'tool.arguments',
937
+ 'tool.input',
938
+ 'tool.args'
939
+ ) THEN attr_string END) as span_tool_arguments_raw,
940
+ max(CASE WHEN attr_key IN (
941
+ 'gen_ai.tool.result',
942
+ 'tool.result',
943
+ 'tool.output'
944
+ ) THEN attr_string END) as span_tool_result_raw,
945
+ max(CASE WHEN attr_key = 'gen_ai.input.messages' THEN attr_string END) as input_messages_raw,
946
+ max(CASE WHEN attr_key = 'gen_ai.output.messages' THEN attr_string END) as output_messages_raw
947
+ FROM attrs
948
+ GROUP BY trace_id, span_id, span_name, duration_ms, start_ms, end_ms
949
+ ),
950
+ representative AS (
951
+ SELECT DISTINCT ON (trace_id)
952
+ trace_id,
953
+ span_id,
954
+ span_name,
955
+ session_id,
956
+ request_id,
957
+ user_id,
958
+ project_id,
959
+ agent_id,
960
+ agent_version,
961
+ source,
962
+ engine,
963
+ model,
964
+ start_ms,
965
+ end_ms,
966
+ duration_ms,
967
+ COALESCE(prompt_tokens, 0) as prompt_tokens,
968
+ COALESCE(completion_tokens, 0) as completion_tokens,
969
+ COALESCE(total_cost_usd, 0.0) as total_cost_usd,
970
+ error_type,
971
+ error_message,
972
+ span_tool_name,
973
+ span_tool_call_id,
974
+ span_tool_arguments_raw,
975
+ span_tool_result_raw,
976
+ input_messages_raw,
977
+ output_messages_raw
978
+ FROM extracted
979
+ ORDER BY
980
+ trace_id,
981
+ CASE WHEN output_messages_raw IS NOT NULL AND output_messages_raw != '' THEN 0 ELSE 1 END,
982
+ CASE WHEN input_messages_raw IS NOT NULL AND input_messages_raw != '' THEN 0 ELSE 1 END,
983
+ COALESCE(total_cost_usd, 0) DESC,
984
+ CASE
985
+ WHEN span_name = 'raw_gen_ai_request' THEN 0
986
+ WHEN span_name = 'litellm_request' THEN 1
987
+ ELSE 2
988
+ END,
989
+ start_ms DESC
990
+ ),
991
+ merged AS (
992
+ SELECT
993
+ e.trace_id,
994
+ r.span_id,
995
+ r.span_name,
996
+ COALESCE(MAX(CASE WHEN e.session_id IS NOT NULL AND e.session_id != '' THEN e.session_id END), r.session_id) AS session_id,
997
+ COALESCE(MAX(CASE WHEN e.request_id IS NOT NULL AND e.request_id != '' THEN e.request_id END), r.request_id) AS request_id,
998
+ COALESCE(MAX(CASE WHEN e.user_id IS NOT NULL AND e.user_id != '' THEN e.user_id END), r.user_id) AS user_id,
999
+ COALESCE(MAX(CASE WHEN e.project_id IS NOT NULL AND e.project_id != '' THEN e.project_id END), r.project_id) AS project_id,
1000
+ COALESCE(MAX(CASE WHEN e.agent_id IS NOT NULL AND e.agent_id != '' THEN e.agent_id END), r.agent_id) AS agent_id,
1001
+ COALESCE(MAX(CASE WHEN e.agent_version IS NOT NULL AND e.agent_version != '' THEN e.agent_version END), r.agent_version) AS agent_version,
1002
+ COALESCE(MAX(CASE WHEN e.source IS NOT NULL AND e.source != '' THEN e.source END), r.source) AS source,
1003
+ COALESCE(MAX(CASE WHEN e.engine IS NOT NULL AND e.engine != '' THEN e.engine END), r.engine) AS engine,
1004
+ COALESCE(MAX(CASE WHEN e.model IS NOT NULL AND e.model != '' THEN e.model END), r.model) AS model,
1005
+ MIN(e.start_ms) AS start_ms,
1006
+ MAX(e.end_ms) AS end_ms,
1007
+ MAX(e.duration_ms) AS duration_ms,
1008
+ COALESCE(MAX(e.prompt_tokens), r.prompt_tokens, 0) AS prompt_tokens,
1009
+ COALESCE(MAX(e.completion_tokens), r.completion_tokens, 0) AS completion_tokens,
1010
+ COALESCE(MAX(e.total_cost_usd), r.total_cost_usd, 0.0) AS total_cost_usd,
1011
+ CASE
1012
+ WHEN SUM(CASE WHEN lower(coalesce(e.otel_status, '')) = 'error' OR e.error_type IS NOT NULL OR e.error_message IS NOT NULL THEN 1 ELSE 0 END) > 0 THEN 'error'
1013
+ ELSE 'ok'
1014
+ END AS status,
1015
+ COALESCE(MAX(CASE WHEN e.error_type IS NOT NULL AND e.error_type != '' THEN e.error_type END), r.error_type) AS error_type,
1016
+ COALESCE(MAX(CASE WHEN e.error_message IS NOT NULL AND e.error_message != '' THEN e.error_message END), r.error_message) AS error_message,
1017
+ COALESCE(MAX(CASE WHEN e.span_tool_name IS NOT NULL AND e.span_tool_name != '' THEN e.span_tool_name END), r.span_tool_name) AS span_tool_name,
1018
+ COALESCE(MAX(CASE WHEN e.span_tool_call_id IS NOT NULL AND e.span_tool_call_id != '' THEN e.span_tool_call_id END), r.span_tool_call_id) AS span_tool_call_id,
1019
+ COALESCE(MAX(CASE WHEN e.span_tool_arguments_raw IS NOT NULL AND e.span_tool_arguments_raw != '' THEN e.span_tool_arguments_raw END), r.span_tool_arguments_raw) AS span_tool_arguments_raw,
1020
+ COALESCE(MAX(CASE WHEN e.span_tool_result_raw IS NOT NULL AND e.span_tool_result_raw != '' THEN e.span_tool_result_raw END), r.span_tool_result_raw) AS span_tool_result_raw,
1021
+ COALESCE(MAX(CASE WHEN e.input_messages_raw IS NOT NULL AND e.input_messages_raw != '' THEN e.input_messages_raw END), r.input_messages_raw) AS input_messages_raw,
1022
+ COALESCE(MAX(CASE WHEN e.output_messages_raw IS NOT NULL AND e.output_messages_raw != '' THEN e.output_messages_raw END), r.output_messages_raw) AS output_messages_raw
1023
+ FROM extracted e
1024
+ JOIN representative r ON r.trace_id = e.trace_id
1025
+ GROUP BY e.trace_id, r.span_id, r.span_name, r.session_id, r.request_id, r.user_id, r.project_id, r.agent_id, r.agent_version, r.source, r.engine, r.model, r.prompt_tokens, r.completion_tokens, r.total_cost_usd, r.error_type, r.error_message, r.span_tool_name, r.span_tool_call_id, r.span_tool_arguments_raw, r.span_tool_result_raw, r.input_messages_raw, r.output_messages_raw
1026
+ )
1027
+ SELECT
1028
+ trace_id as model_call_id,
1029
+ trace_id,
1030
+ span_id,
1031
+ span_name,
1032
+ session_id,
1033
+ CASE
1034
+ WHEN session_id IS NOT NULL AND request_id IS NOT NULL THEN session_id || ':' || request_id
1035
+ ELSE trace_id
1036
+ END as turn_id,
1037
+ request_id,
1038
+ user_id,
1039
+ project_id,
1040
+ agent_id,
1041
+ agent_version,
1042
+ source,
1043
+ engine,
1044
+ model,
1045
+ start_ms,
1046
+ end_ms,
1047
+ duration_ms,
1048
+ prompt_tokens,
1049
+ completion_tokens,
1050
+ COALESCE(prompt_tokens, 0) + COALESCE(completion_tokens, 0) as total_tokens,
1051
+ total_cost_usd,
1052
+ status,
1053
+ error_type,
1054
+ error_message,
1055
+ span_tool_name,
1056
+ span_tool_call_id,
1057
+ span_tool_arguments_raw,
1058
+ span_tool_result_raw,
1059
+ input_messages_raw,
1060
+ output_messages_raw
1061
+ FROM merged
1062
+ """)
1063
+
1064
+ # Silver: model calls
1065
+ conn.execute("""
1066
+ INSERT INTO model_calls (
1067
+ model_call_id, trace_id, span_id, span_name, session_id, turn_id, request_id,
1068
+ user_id, project_id, agent_id, agent_version, source, engine, model,
1069
+ start_ms, end_ms, duration_ms, prompt_tokens, completion_tokens, total_tokens,
1070
+ total_cost_usd, status, error_type, error_message,
1071
+ input_messages_raw, output_messages_raw, created_at, updated_at
1072
+ )
1073
+ SELECT
1074
+ model_call_id, trace_id, span_id, span_name, session_id, turn_id, request_id,
1075
+ user_id, project_id, agent_id, agent_version, source, engine, model,
1076
+ start_ms, end_ms, duration_ms, prompt_tokens, completion_tokens, total_tokens,
1077
+ total_cost_usd, status, error_type, error_message,
1078
+ input_messages_raw, output_messages_raw, now(), now()
1079
+ FROM tmp_model_calls
1080
+ ON CONFLICT(model_call_id) DO UPDATE SET
1081
+ span_id = COALESCE(excluded.span_id, model_calls.span_id),
1082
+ span_name = COALESCE(excluded.span_name, model_calls.span_name),
1083
+ session_id = COALESCE(excluded.session_id, model_calls.session_id),
1084
+ turn_id = COALESCE(excluded.turn_id, model_calls.turn_id),
1085
+ request_id = COALESCE(excluded.request_id, model_calls.request_id),
1086
+ user_id = COALESCE(excluded.user_id, model_calls.user_id),
1087
+ project_id = COALESCE(excluded.project_id, model_calls.project_id),
1088
+ agent_id = COALESCE(excluded.agent_id, model_calls.agent_id),
1089
+ agent_version = COALESCE(excluded.agent_version, model_calls.agent_version),
1090
+ source = COALESCE(excluded.source, model_calls.source),
1091
+ engine = COALESCE(excluded.engine, model_calls.engine),
1092
+ model = COALESCE(excluded.model, model_calls.model),
1093
+ start_ms = COALESCE(excluded.start_ms, model_calls.start_ms),
1094
+ end_ms = COALESCE(excluded.end_ms, model_calls.end_ms),
1095
+ duration_ms = COALESCE(excluded.duration_ms, model_calls.duration_ms),
1096
+ prompt_tokens = COALESCE(excluded.prompt_tokens, model_calls.prompt_tokens),
1097
+ completion_tokens = COALESCE(excluded.completion_tokens, model_calls.completion_tokens),
1098
+ total_tokens = COALESCE(excluded.total_tokens, model_calls.total_tokens),
1099
+ total_cost_usd = COALESCE(excluded.total_cost_usd, model_calls.total_cost_usd),
1100
+ status = COALESCE(excluded.status, model_calls.status),
1101
+ error_type = COALESCE(excluded.error_type, model_calls.error_type),
1102
+ error_message = COALESCE(excluded.error_message, model_calls.error_message),
1103
+ input_messages_raw = COALESCE(excluded.input_messages_raw, model_calls.input_messages_raw),
1104
+ output_messages_raw = COALESCE(excluded.output_messages_raw, model_calls.output_messages_raw),
1105
+ updated_at = now()
1106
+ """)
1107
+
1108
+ # Gold summary table used by existing dashboards
1109
+ conn.execute("""
1110
+ INSERT INTO spans (
1111
+ trace_id, span_id, span_name, model, cost, input_tokens, output_tokens,
1112
+ session_id, user_id, project_id, agent_id, engine, duration_ms, start_ms, created_at
1113
+ )
1114
+ SELECT
1115
+ trace_id,
1116
+ span_id,
1117
+ span_name,
1118
+ model,
1119
+ total_cost_usd,
1120
+ prompt_tokens,
1121
+ completion_tokens,
1122
+ session_id,
1123
+ user_id,
1124
+ project_id,
1125
+ agent_id,
1126
+ engine,
1127
+ duration_ms,
1128
+ start_ms,
1129
+ now()
1130
+ FROM tmp_model_calls
1131
+ ON CONFLICT(trace_id, span_id) DO UPDATE SET
1132
+ span_name = COALESCE(excluded.span_name, spans.span_name),
1133
+ model = COALESCE(excluded.model, spans.model),
1134
+ cost = COALESCE(excluded.cost, spans.cost),
1135
+ input_tokens = COALESCE(excluded.input_tokens, spans.input_tokens),
1136
+ output_tokens = COALESCE(excluded.output_tokens, spans.output_tokens),
1137
+ session_id = COALESCE(excluded.session_id, spans.session_id),
1138
+ user_id = COALESCE(excluded.user_id, spans.user_id),
1139
+ project_id = COALESCE(excluded.project_id, spans.project_id),
1140
+ agent_id = COALESCE(excluded.agent_id, spans.agent_id),
1141
+ engine = COALESCE(excluded.engine, spans.engine),
1142
+ duration_ms = COALESCE(excluded.duration_ms, spans.duration_ms),
1143
+ start_ms = COALESCE(excluded.start_ms, spans.start_ms)
1144
+ """)
1145
+
1146
+ # Turns from model calls
1147
+ conn.execute("""
1148
+ INSERT INTO turns (
1149
+ turn_id, session_id, request_id, turn_index, started_at, ended_at, status,
1150
+ model, provider, prompt_tokens, completion_tokens, total_tokens, cost_usd,
1151
+ trace_id, created_at, updated_at
1152
+ )
1153
+ SELECT
1154
+ turn_id,
1155
+ session_id,
1156
+ request_id,
1157
+ NULL,
1158
+ to_timestamp(start_ms / 1000.0),
1159
+ to_timestamp(end_ms / 1000.0),
1160
+ status,
1161
+ model,
1162
+ engine,
1163
+ prompt_tokens,
1164
+ completion_tokens,
1165
+ total_tokens,
1166
+ total_cost_usd,
1167
+ trace_id,
1168
+ now(),
1169
+ now()
1170
+ FROM tmp_model_calls
1171
+ ON CONFLICT(turn_id) DO UPDATE SET
1172
+ session_id = excluded.session_id,
1173
+ request_id = excluded.request_id,
1174
+ started_at = LEAST(turns.started_at, excluded.started_at),
1175
+ ended_at = GREATEST(turns.ended_at, excluded.ended_at),
1176
+ status = CASE WHEN excluded.status = 'error' THEN 'error' ELSE turns.status END,
1177
+ model = excluded.model,
1178
+ provider = excluded.provider,
1179
+ prompt_tokens = COALESCE(excluded.prompt_tokens, turns.prompt_tokens),
1180
+ completion_tokens = COALESCE(excluded.completion_tokens, turns.completion_tokens),
1181
+ total_tokens = COALESCE(excluded.total_tokens, turns.total_tokens),
1182
+ cost_usd = COALESCE(excluded.cost_usd, turns.cost_usd),
1183
+ trace_id = excluded.trace_id,
1184
+ updated_at = now()
1185
+ """)
1186
+
1187
+ # Parse structured tool calls from raw message payloads.
1188
+ extract_messages_and_tools(conn)
1189
+
1190
+ # Rebuild session rollups for sessions touched in this file.
1191
+ conn.execute("DROP TABLE IF EXISTS tmp_touched_sessions")
1192
+ conn.execute("""
1193
+ CREATE TEMP TABLE tmp_touched_sessions AS
1194
+ SELECT DISTINCT session_id
1195
+ FROM tmp_model_calls
1196
+ WHERE session_id IS NOT NULL AND session_id != ''
1197
+ """)
1198
+
1199
+ conn.execute("""
1200
+ INSERT INTO sessions (
1201
+ session_id, user_id, project_id, agent_id, agent_version, source, engine,
1202
+ started_at, ended_at, status, total_turns, total_requests,
1203
+ total_cost_usd, total_tokens, created_at, updated_at
1204
+ )
1205
+ SELECT
1206
+ mc.session_id,
1207
+ any_value(mc.user_id) as user_id,
1208
+ any_value(mc.project_id) as project_id,
1209
+ any_value(mc.agent_id) as agent_id,
1210
+ any_value(mc.agent_version) as agent_version,
1211
+ any_value(mc.source) as source,
1212
+ any_value(mc.engine) as engine,
1213
+ to_timestamp(min(mc.start_ms) / 1000.0) as started_at,
1214
+ to_timestamp(max(mc.end_ms) / 1000.0) as ended_at,
1215
+ CASE WHEN sum(CASE WHEN mc.status = 'error' THEN 1 ELSE 0 END) > 0 THEN 'error' ELSE 'ok' END as status,
1216
+ count(distinct mc.turn_id) as total_turns,
1217
+ count(*) as total_requests,
1218
+ sum(coalesce(mc.total_cost_usd, 0.0)) as total_cost_usd,
1219
+ sum(coalesce(mc.total_tokens, 0)) as total_tokens,
1220
+ now(),
1221
+ now()
1222
+ FROM model_calls mc
1223
+ JOIN tmp_touched_sessions t ON t.session_id = mc.session_id
1224
+ GROUP BY mc.session_id
1225
+ ON CONFLICT(session_id) DO UPDATE SET
1226
+ user_id = excluded.user_id,
1227
+ project_id = excluded.project_id,
1228
+ agent_id = excluded.agent_id,
1229
+ agent_version = excluded.agent_version,
1230
+ source = excluded.source,
1231
+ engine = excluded.engine,
1232
+ started_at = excluded.started_at,
1233
+ ended_at = excluded.ended_at,
1234
+ status = excluded.status,
1235
+ total_turns = excluded.total_turns,
1236
+ total_requests = excluded.total_requests,
1237
+ total_cost_usd = excluded.total_cost_usd,
1238
+ total_tokens = excluded.total_tokens,
1239
+ updated_at = now()
1240
+ """)
1241
+
1242
+ # Recompute per-session ordering for turns.
1243
+ conn.execute("""
1244
+ UPDATE turns AS t
1245
+ SET turn_index = ranked.turn_index
1246
+ FROM (
1247
+ SELECT
1248
+ turn_id,
1249
+ row_number() OVER (PARTITION BY session_id ORDER BY started_at, turn_id) - 1 AS turn_index
1250
+ FROM turns
1251
+ WHERE session_id IN (SELECT session_id FROM tmp_touched_sessions)
1252
+ ) ranked
1253
+ WHERE t.turn_id = ranked.turn_id
1254
+ """)
1255
+
1256
+
1257
+ def upsert_processed_file(conn, jsonl_file: Path) -> None:
1258
+ mtime_ms, size_bytes = get_file_stats(jsonl_file)
1259
+ conn.execute(
1260
+ """
1261
+ INSERT INTO processed_files (file_path, mtime_ms, size_bytes, processed_at)
1262
+ VALUES (?, ?, ?, now())
1263
+ ON CONFLICT(file_path) DO UPDATE SET
1264
+ mtime_ms = excluded.mtime_ms,
1265
+ size_bytes = excluded.size_bytes,
1266
+ processed_at = excluded.processed_at
1267
+ """,
1268
+ [str(jsonl_file), mtime_ms, size_bytes],
1269
+ )
1270
+
1271
+
1272
+ def main() -> int:
1273
+ """Main entry point."""
1274
+ traces_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else None
1275
+ db_path = Path(sys.argv[2]) if len(sys.argv) > 2 else None
1276
+
1277
+ traces_dir = get_traces_dir(str(traces_dir) if traces_dir else None)
1278
+ db_path = get_db_path(traces_dir, str(db_path) if db_path else None)
1279
+
1280
+ if not traces_dir.exists():
1281
+ log.error(f"Traces directory not found: {traces_dir}")
1282
+ print(json.dumps({
1283
+ 'status': 'error',
1284
+ 'indexed': False,
1285
+ 'message': f'Traces directory not found: {traces_dir}',
1286
+ }))
1287
+ return 1
1288
+
1289
+ lock_handle = try_acquire_index_lock(get_lock_file(traces_dir))
1290
+ if lock_handle is None:
1291
+ log.info('Indexing already in progress')
1292
+ print(json.dumps({
1293
+ 'status': 'busy',
1294
+ 'indexed': False,
1295
+ 'message': 'Indexing already in progress',
1296
+ }))
1297
+ return 3
1298
+
1299
+ try:
1300
+ jsonl_files = list(traces_dir.glob('*.jsonl'))
1301
+ if not jsonl_files:
1302
+ log.info(f"No JSONL files found in {traces_dir}")
1303
+ print(json.dumps({
1304
+ 'status': 'ok',
1305
+ 'indexed': False,
1306
+ 'message': 'No trace files to index',
1307
+ }))
1308
+ return 2
1309
+
1310
+ log.debug(f"Found {len(jsonl_files)} JSONL files")
1311
+
1312
+ state_file = get_state_file(traces_dir)
1313
+ state = load_index_state(state_file)
1314
+
1315
+ db_path.parent.mkdir(parents=True, exist_ok=True)
1316
+
1317
+ try:
1318
+ import duckdb
1319
+ except ImportError:
1320
+ log.error("duckdb module not found. Install with: pip install duckdb")
1321
+ print(json.dumps({
1322
+ 'status': 'error',
1323
+ 'indexed': False,
1324
+ 'message': 'duckdb module not found',
1325
+ }), file=sys.stderr)
1326
+ return 1
1327
+
1328
+ try:
1329
+ log.debug(f"Connecting to database: {db_path}")
1330
+ conn = duckdb.connect(str(db_path))
1331
+ ensure_schema(conn)
1332
+
1333
+ changed_files = get_changed_files(conn, traces_dir)
1334
+ if not changed_files:
1335
+ current_mtime = get_latest_jsonl_mtime(traces_dir)
1336
+ state['version'] = 2
1337
+ state['indexed'] = True
1338
+ state['lastMtime'] = current_mtime
1339
+ state['duckdbPath'] = str(db_path)
1340
+ state['indexedAt'] = datetime.utcnow().isoformat() + 'Z'
1341
+ save_index_state(state_file, state)
1342
+
1343
+ log.info("Index is up-to-date; no changed files")
1344
+ print(json.dumps({
1345
+ 'status': 'ok',
1346
+ 'indexed': True,
1347
+ 'message': 'Already indexed',
1348
+ }))
1349
+ conn.close()
1350
+ return 2
1351
+
1352
+ log.info(f"Incremental indexing {len(changed_files)} changed file(s)")
1353
+
1354
+ for jsonl_file in changed_files:
1355
+ log.debug(f"Indexing file: {jsonl_file}")
1356
+ ingest_single_jsonl_file(conn, jsonl_file)
1357
+ upsert_processed_file(conn, jsonl_file)
1358
+
1359
+ current_mtime = get_latest_jsonl_mtime(traces_dir)
1360
+ state['version'] = 2
1361
+ state['indexed'] = True
1362
+ state['lastMtime'] = current_mtime
1363
+ state['duckdbPath'] = str(db_path)
1364
+ state['indexedAt'] = datetime.utcnow().isoformat() + 'Z'
1365
+ save_index_state(state_file, state)
1366
+
1367
+ count_result = conn.execute("SELECT COUNT(*) FROM model_calls").fetchall()
1368
+ total_records = count_result[0][0] if count_result else 0
1369
+ log.debug(f"Ingestion complete. Total model_calls: {total_records}")
1370
+
1371
+ conn.close()
1372
+
1373
+ log.info(f"Indexing complete: {db_path}")
1374
+ print(json.dumps({
1375
+ 'status': 'ok',
1376
+ 'indexed': True,
1377
+ 'message': f'Indexed {len(changed_files)} changed file(s) at {state["indexedAt"]}',
1378
+ }))
1379
+ return 0
1380
+
1381
+ except Exception as e:
1382
+ log.error(f"Indexing failed: {e}")
1383
+ print(json.dumps({
1384
+ 'status': 'error',
1385
+ 'indexed': False,
1386
+ 'message': str(e),
1387
+ }), file=sys.stderr)
1388
+ return 1
1389
+ finally:
1390
+ release_index_lock(lock_handle)
1391
+
1392
+
1393
+ if __name__ == '__main__':
1394
+ sys.exit(main())