@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.
- package/LICENSE +21 -0
- package/bin/agenthippo.js +2 -0
- package/out/agentpack/github.js +547 -0
- package/out/agentpack/index.js +66 -0
- package/out/agentpack/install.js +297 -0
- package/out/agentpack/manager.js +441 -0
- package/out/agentpack/manifest.js +135 -0
- package/out/agentpack/paths.js +475 -0
- package/out/agentpack/publish.js +248 -0
- package/out/agentpack/stores.js +554 -0
- package/out/agentpack/types.js +5 -0
- package/out/analytics/dashboard/artifactManager.js +439 -0
- package/out/analytics/dashboard/fleetDisplayUtils.js +159 -0
- package/out/analytics/dashboard/fleetOrchestrator.js +142 -0
- package/out/analytics/dashboard/fleetRegistry.js +79 -0
- package/out/analytics/dashboard/fleetRowDefaults.js +87 -0
- package/out/analytics/dashboard/fleetScriptAdapter.js +146 -0
- package/out/analytics/dashboard/fleetStatusUtils.js +30 -0
- package/out/analytics/dashboard/fleetTitleUtils.js +68 -0
- package/out/analytics/dashboard/fleetTypes.js +29 -0
- package/out/analytics/dashboard/index.js +17 -0
- package/out/analytics/dashboard/indexingService.js +422 -0
- package/out/analytics/dashboard/localFleetAdapter.js +222 -0
- package/out/analytics/dashboard/performanceMonitor.js +76 -0
- package/out/analytics/dashboard/processManager.js +209 -0
- package/out/analytics/dashboard/provider.js +3885 -0
- package/out/analytics/dashboard/queryRunner.js +994 -0
- package/out/analytics/dashboard/refreshPolicy.js +16 -0
- package/out/analytics/dashboard/scheduledFleetFingerprint.js +22 -0
- package/out/analytics/dashboard/scheduledFleetStorage.js +241 -0
- package/out/analytics/dashboard/scheduledFleetUtils.js +22 -0
- package/out/analytics/dashboard/sessionEventFleetStats.js +118 -0
- package/out/analytics/dashboard/sessionJsonlFleet.js +204 -0
- package/out/analytics/dashboard/teamViewDiscovery.js +106 -0
- package/out/analytics/dashboard/viewsConfigMerge.js +221 -0
- package/out/analytics/dashboard/webview/analytics-panel.html +155 -0
- package/out/analytics/dashboard/webview/codicon.ttf +0 -0
- package/out/analytics/dashboard/webview/index.html +78 -0
- package/out/analytics/dashboard/webview/panel.js +2295 -0
- package/out/analytics/dashboard/webview/runtime-bridge.js +82 -0
- package/out/analytics/dashboard/webview/styles.css +1009 -0
- package/out/analytics/lifecycle.js +43 -0
- package/out/analytics/service.js +1279 -0
- package/out/analytics/sessionEvents.js +231 -0
- package/out/auth/bootstrapApply.js +72 -0
- package/out/auth/brokerClient.js +126 -0
- package/out/auth/callbackUri.js +59 -0
- package/out/auth/callbackUriParse.js +59 -0
- package/out/auth/githubStoreGate.js +70 -0
- package/out/auth/loopbackAuthServer.js +165 -0
- package/out/auth/pkce.js +9 -0
- package/out/auth/provider.js +239 -0
- package/out/auth/sessionStore.js +193 -0
- package/out/auth/signInFlow.js +182 -0
- package/out/auth/types.js +12 -0
- package/out/auth-broker/config.js +62 -0
- package/out/auth-broker/jwt.js +105 -0
- package/out/auth-broker/pkce.js +57 -0
- package/out/auth-broker/server.js +539 -0
- package/out/auth-broker/store.js +273 -0
- package/out/base-prompts/gateway/openclaw-starter.json5 +144 -0
- package/out/base-prompts/gateway/slack-socket-mode-minimal.manifest.json +44 -0
- package/out/base-prompts/mcp-servers/.mcp.json +13 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/README.md +142 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/mcp.json +13 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/package-lock.json +1192 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/package.json +23 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/run-with-env.sh +12 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/sdk-test.js +102 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/simple-test.js +137 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-client.js +118 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-server.js +209 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/src/client.ts +431 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/src/index.ts +270 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/tsconfig.json +18 -0
- package/out/base-prompts/rules/AGENTS.md +204 -0
- package/out/base-prompts/skills/agent-browser/SKILL.md +102 -0
- package/out/base-prompts/skills/agent-builder/SKILL.md +73 -0
- package/out/base-prompts/skills/agent-builder/references/agent-pack.md +63 -0
- package/out/base-prompts/skills/agent-builder/references/crewai.md +66 -0
- package/out/base-prompts/skills/agent-builder/references/langchain.md +60 -0
- package/out/base-prompts/skills/agent-builder/references/raw-sdk.md +73 -0
- package/out/base-prompts/skills/agent-engine-creator/SKILL.md +586 -0
- package/out/base-prompts/skills/agent-engine-installer/SKILL.md +106 -0
- package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.mjs +259 -0
- package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.ps1 +189 -0
- package/out/base-prompts/skills/agenthippo-cli/SKILL.md +69 -0
- package/out/base-prompts/skills/agenthippo-cli/scripts/install.sh +63 -0
- package/out/base-prompts/skills/agentpack-creator/SKILL.md +375 -0
- package/out/base-prompts/skills/agentpack-creator/references/agent-templates.md +261 -0
- package/out/base-prompts/skills/agentpack-creator/references/agents-md-guide.md +195 -0
- package/out/base-prompts/skills/agentpack-creator/scripts/init_pack.py +391 -0
- package/out/base-prompts/skills/agentpack-creator/scripts/init_pack_test.py +294 -0
- package/out/base-prompts/skills/agentpack-testbench/SKILL.md +135 -0
- package/out/base-prompts/skills/algorithmic-art/LICENSE.txt +202 -0
- package/out/base-prompts/skills/algorithmic-art/SKILL.md +405 -0
- package/out/base-prompts/skills/algorithmic-art/templates/generator_template.js +223 -0
- package/out/base-prompts/skills/algorithmic-art/templates/viewer.html +599 -0
- package/out/base-prompts/skills/debug-agent/SKILL.md +83 -0
- package/out/base-prompts/skills/desktop-automation/SKILL.md +166 -0
- package/out/base-prompts/skills/desktop-automation/references/agenthippo-ui-recipes.md +441 -0
- package/out/base-prompts/skills/desktop-automation/scripts/windows-desktop-uia.psm1 +280 -0
- package/out/base-prompts/skills/docx/LICENSE.txt +30 -0
- package/out/base-prompts/skills/docx/SKILL.md +197 -0
- package/out/base-prompts/skills/docx/docx-js.md +350 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/mce/mc.xsd +75 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/pack.py +159 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/unpack.py +29 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validate.py +69 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validation/__init__.py +15 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validation/base.py +951 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validation/docx.py +274 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validation/pptx.py +315 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validation/redlining.py +279 -0
- package/out/base-prompts/skills/docx/ooxml.md +610 -0
- package/out/base-prompts/skills/docx/scripts/__init__.py +1 -0
- package/out/base-prompts/skills/docx/scripts/document.py +1276 -0
- package/out/base-prompts/skills/docx/scripts/templates/comments.xml +3 -0
- package/out/base-prompts/skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/out/base-prompts/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/out/base-prompts/skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/out/base-prompts/skills/docx/scripts/templates/people.xml +3 -0
- package/out/base-prompts/skills/docx/scripts/utilities.py +374 -0
- package/out/base-prompts/skills/gateway-outbound/SKILL.md +136 -0
- package/out/base-prompts/skills/mcp-builder/LICENSE.txt +202 -0
- package/out/base-prompts/skills/mcp-builder/SKILL.md +328 -0
- package/out/base-prompts/skills/mcp-builder/reference/evaluation.md +602 -0
- package/out/base-prompts/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/out/base-prompts/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/out/base-prompts/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/out/base-prompts/skills/mcp-builder/scripts/connections.py +151 -0
- package/out/base-prompts/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/out/base-prompts/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/out/base-prompts/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/out/base-prompts/skills/openclaw/SKILL.md +406 -0
- package/out/base-prompts/skills/openclaw/scripts/run-smoke.sh +61 -0
- package/out/base-prompts/skills/pdf/LICENSE.txt +30 -0
- package/out/base-prompts/skills/pdf/SKILL.md +294 -0
- package/out/base-prompts/skills/pdf/forms.md +205 -0
- package/out/base-prompts/skills/pdf/reference.md +612 -0
- package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes.py +70 -0
- package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes_test.py +226 -0
- package/out/base-prompts/skills/pdf/scripts/check_fillable_fields.py +12 -0
- package/out/base-prompts/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
- package/out/base-prompts/skills/pdf/scripts/create_validation_image.py +41 -0
- package/out/base-prompts/skills/pdf/scripts/extract_form_field_info.py +152 -0
- package/out/base-prompts/skills/pdf/scripts/fill_fillable_fields.py +114 -0
- package/out/base-prompts/skills/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
- package/out/base-prompts/skills/pptx/LICENSE.txt +30 -0
- package/out/base-prompts/skills/pptx/SKILL.md +484 -0
- package/out/base-prompts/skills/pptx/html2pptx.md +625 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/mce/mc.xsd +75 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/pack.py +159 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/unpack.py +29 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validate.py +69 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validation/__init__.py +15 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validation/base.py +951 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validation/docx.py +274 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validation/pptx.py +315 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validation/redlining.py +279 -0
- package/out/base-prompts/skills/pptx/ooxml.md +427 -0
- package/out/base-prompts/skills/pptx/scripts/html2pptx.js +984 -0
- package/out/base-prompts/skills/pptx/scripts/inventory.py +1020 -0
- package/out/base-prompts/skills/pptx/scripts/rearrange.py +231 -0
- package/out/base-prompts/skills/pptx/scripts/replace.py +385 -0
- package/out/base-prompts/skills/pptx/scripts/thumbnail.py +450 -0
- package/out/base-prompts/skills/prompt-lab/SKILL.md +149 -0
- package/out/base-prompts/skills/prompt-lab/scripts/query-analytics.sh +2551 -0
- package/out/base-prompts/skills/skill-creator/LICENSE.txt +202 -0
- package/out/base-prompts/skills/skill-creator/SKILL.md +371 -0
- package/out/base-prompts/skills/skill-creator/references/output-patterns.md +82 -0
- package/out/base-prompts/skills/skill-creator/references/workflows.md +28 -0
- package/out/base-prompts/skills/skill-creator/scripts/init_skill.py +303 -0
- package/out/base-prompts/skills/skill-creator/scripts/package_skill.py +110 -0
- package/out/base-prompts/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/out/base-prompts/skills/skill-installer/SKILL.md +251 -0
- package/out/base-prompts/skills/skill-installer/references/store-api.md +219 -0
- package/out/base-prompts/skills/spotlight-dashboard/SKILL.md +83 -0
- package/out/base-prompts/skills/spotlight-insights/SKILL.md +101 -0
- package/out/base-prompts/skills/spotlight-optimize/SKILL.md +93 -0
- package/out/base-prompts/skills/spotlight-optimize/scripts/query-analytics.sh +2551 -0
- package/out/base-prompts/skills/spotlight-query/SKILL.md +314 -0
- package/out/base-prompts/skills/spotlight-query/references/otel-schema.md +163 -0
- package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.sh +2551 -0
- package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.test.sh +744 -0
- package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/sessions/sess-test-1.jsonl +14 -0
- package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/.duckdb-index.json +7 -0
- package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/sample.jsonl +5 -0
- package/out/base-prompts/skills/spotlight-report/SKILL.md +86 -0
- package/out/base-prompts/skills/spotlight-visualize/SKILL.md +92 -0
- package/out/base-prompts/skills/spotlight-visualize/scripts/query-analytics.sh +2551 -0
- package/out/base-prompts/skills/xlsx/LICENSE.txt +30 -0
- package/out/base-prompts/skills/xlsx/SKILL.md +289 -0
- package/out/base-prompts/skills/xlsx/recalc.py +178 -0
- package/out/branding.js +23 -0
- package/out/cli/commands/anywhere.js +66 -0
- package/out/cli/commands/ask.js +243 -0
- package/out/cli/commands/auth-broker.js +24 -0
- package/out/cli/commands/auth.js +219 -0
- package/out/cli/commands/chat.js +354 -0
- package/out/cli/commands/deploy.js +223 -0
- package/out/cli/commands/gateway.js +160 -0
- package/out/cli/commands/pack.js +220 -0
- package/out/cli/commands/probe-custom-engine.js +82 -0
- package/out/cli/commands/serve.js +583 -0
- package/out/cli/commands/store.js +919 -0
- package/out/cli/credential-context.js +113 -0
- package/out/cli/deploy-init.js +2087 -0
- package/out/cli/deps.js +238 -0
- package/out/cli/index.js +390 -0
- package/out/cli/pack-loader.js +376 -0
- package/out/cli/run-custom-engine-runner.js +318 -0
- package/out/cli/runtime.js +409 -0
- package/out/cli/serve-hardening.js +325 -0
- package/out/cli/state-sync.js +135 -0
- package/out/cli/utils.js +220 -0
- package/out/cli/vscode-shim.js +86 -0
- package/out/engines/base.js +159 -0
- package/out/engines/claude.js +1215 -0
- package/out/engines/codex.js +759 -0
- package/out/engines/custom.js +632 -0
- package/out/engines/customEngineIpc.js +95 -0
- package/out/engines/customEngineRunnerEmbed.js +107 -0
- package/out/engines/customRegistry.js +274 -0
- package/out/engines/embeddedCustomEngineRunner.generated.js +9 -0
- package/out/engines/engineApiKey.js +138 -0
- package/out/engines/factory.js +45 -0
- package/out/engines/install.js +372 -0
- package/out/engines/persistentEngineWorker.js +320 -0
- package/out/engines/publish.js +341 -0
- package/out/engines/types.js +17 -0
- package/out/examples/run-headless-repl.js +291 -0
- package/out/examples/run-headless.js +187 -0
- package/out/extension.js +4472 -0
- package/out/gateway/bridge.js +805 -0
- package/out/gateway/cli-agent-id.js +36 -0
- package/out/gateway/client.js +303 -0
- package/out/gateway/client.test.js +395 -0
- package/out/gateway/gateway-scope-errors.js +14 -0
- package/out/gateway/gatewayBackup.js +76 -0
- package/out/gateway/gatewayRouteIndex.js +371 -0
- package/out/gateway/gatewaySessionStore.js +243 -0
- package/out/gateway/index.js +31 -0
- package/out/gateway/lifecycle.js +3056 -0
- package/out/gateway/openclaw-channel-routing.js +317 -0
- package/out/gateway/outboundHandles.js +373 -0
- package/out/gateway/outboundPaths.js +72 -0
- package/out/gateway/outboundSend.js +254 -0
- package/out/gateway/protocol.js +48 -0
- package/out/gateway/resetMappings.js +52 -0
- package/out/gateway/service.js +1244 -0
- package/out/gateway/sessionRouting.js +77 -0
- package/out/headless/index.js +505 -0
- package/out/headless/sync.js +473 -0
- package/out/mcp/publish.js +143 -0
- package/out/mcp.js +597 -0
- package/out/permissions.js +108 -0
- package/out/permissions.mapping.yaml +68 -0
- package/out/registry/index.js +332 -0
- package/out/remote/handleRemoteChat.js +122 -0
- package/out/remote/remotePackStatusBar.js +103 -0
- package/out/remote/serveClient.js +216 -0
- package/out/remoteSkills.js +191 -0
- package/out/runtime/index.js +32 -0
- package/out/runtime/interfaces/config-provider.js +32 -0
- package/out/runtime/interfaces/edit-tracker.js +22 -0
- package/out/runtime/interfaces/index.js +29 -0
- package/out/runtime/interfaces/logger.js +40 -0
- package/out/runtime/interfaces/permission-handler.js +66 -0
- package/out/runtime/interfaces/session-store.js +342 -0
- package/out/runtime/vscode/index.js +20 -0
- package/out/runtime/vscode/vscode-config-provider.js +77 -0
- package/out/runtime/vscode/vscode-edit-tracker.js +120 -0
- package/out/runtime/vscode/vscode-logger.js +30 -0
- package/out/runtime/vscode/vscode-permission-handler.js +72 -0
- package/out/runtime/vscode/vscode-session-store.js +22 -0
- package/out/scripts/agentide-litellm.ps1 +501 -0
- package/out/scripts/agentide-litellm.sh +700 -0
- package/out/scripts/agentide_litellm_hooks_v1.py +604 -0
- package/out/scripts/analytics/agent-fleet-widget.js +954 -0
- package/out/scripts/analytics/agentide-litellm-otel.config.yaml +160 -0
- package/out/scripts/analytics/default-seed-scripts.json +12 -0
- package/out/scripts/analytics/duckdbLocator.js +29 -0
- package/out/scripts/analytics/fleet-local-adapter.js +246 -0
- package/out/scripts/analytics/fleet-workflow.sh +263 -0
- package/out/scripts/analytics/generate-default-charts.py +685 -0
- package/out/scripts/analytics/hotspot-analysis-widget.js +909 -0
- package/out/scripts/analytics/index-traces.py +1394 -0
- package/out/scripts/analytics/install-analytics.sh +286 -0
- package/out/scripts/analytics/live-fleet-4-collector.cjs +315 -0
- package/out/scripts/analytics/live-fleet-4-daemon.mjs +134 -0
- package/out/scripts/analytics/live-fleet-4-widget.cjs +433 -0
- package/out/scripts/analytics/otel-collector.yaml +55 -0
- package/out/scripts/analytics/query-analytics.ps1 +396 -0
- package/out/scripts/analytics/query-analytics.sh +2551 -0
- package/out/scripts/analytics/session-comparison-widget.js +1468 -0
- package/out/scripts/analytics/session-timeline-widget.js +2137 -0
- package/out/scripts/analytics/setup-analytics.ps1 +432 -0
- package/out/scripts/analytics/setup-analytics.sh +854 -0
- package/out/scripts/analytics/start-analytics.ps1 +169 -0
- package/out/scripts/analytics/start-analytics.sh +323 -0
- package/out/scripts/analytics/token-cost-flamegraph-widget.js +954 -0
- package/out/scripts/analytics/token-cost-trend-widget.js +1062 -0
- package/out/scripts/analytics/workflows-collector.cjs +315 -0
- package/out/scripts/analytics/workflows-daemon.mjs +186 -0
- package/out/scripts/analytics/workflows-widget.cjs +433 -0
- package/out/scripts/analytics/wow-model-widget.js +191 -0
- package/out/scripts/gateway/Dockerfile.serve +45 -0
- package/out/scripts/gateway/docker-serve.sh +248 -0
- package/out/scripts/gateway/setup-gateway-server.ps1 +953 -0
- package/out/scripts/gateway/setup-gateway-server.sh +935 -0
- package/out/scripts/gateway/setup-gateway.ps1 +1813 -0
- package/out/scripts/gateway/setup-gateway.sh +1541 -0
- package/out/scripts/setup-cli.ps1 +86 -0
- package/out/scripts/setup-cli.sh +117 -0
- package/out/scripts/tools-manifest.json +83 -0
- package/out/scripts/vendor-tools.mjs +234 -0
- package/out/session.js +272 -0
- package/out/shared/agentEngines.js +170 -0
- package/out/shared/index.js +109 -0
- package/out/shared/models.js +235 -0
- package/out/shared/providers.js +202 -0
- package/out/skills/publish.js +178 -0
- package/out/spotlight/install.js +339 -0
- package/out/spotlight/publish.js +544 -0
- package/out/spotlight/yaml.js +31 -0
- package/out/store/github/setup.js +235 -0
- package/out/store/github/templates/build-index.ts +339 -0
- package/out/store/github/templates/build-index.yml +97 -0
- package/out/store/github/templates/package.json +11 -0
- package/out/store/index.js +755 -0
- package/out/store/oramaIndex.js +884 -0
- package/out/store/shared.js +241 -0
- package/out/sync.js +1827 -0
- package/out/terminalLogger.js +358 -0
- package/out/test/agentEnvFile.test.js +97 -0
- package/out/test/agentYaml.test.js +373 -0
- package/out/test/agentpack.test.js +639 -0
- package/out/test/analytics-lifecycle.test.js +174 -0
- package/out/test/analytics.test.js +524 -0
- package/out/test/auth-bootstrapApply.test.js +102 -0
- package/out/test/auth-broker.test.js +434 -0
- package/out/test/auth-callback.test.js +62 -0
- package/out/test/branding.test.js +229 -0
- package/out/test/cli-agent-id.test.js +55 -0
- package/out/test/credential-context.test.js +105 -0
- package/out/test/customEngineInstall.test.js +100 -0
- package/out/test/customRegistry.test.js +122 -0
- package/out/test/deploy-init.test.js +532 -0
- package/out/test/deps-auto-install.test.js +81 -0
- package/out/test/engine-integration.test.js +297 -0
- package/out/test/enginePaths.test.js +195 -0
- package/out/test/enginePublish.test.js +82 -0
- package/out/test/fleetDisplayUtils.test.js +96 -0
- package/out/test/gateway-client.test.js +381 -0
- package/out/test/gateway-lifecycle.test.js +608 -0
- package/out/test/gateway-outbound.test.js +531 -0
- package/out/test/gateway-reset-mappings.test.js +221 -0
- package/out/test/gateway-route-index.test.js +235 -0
- package/out/test/gateway-session-routing.test.js +47 -0
- package/out/test/gateway-session-store.test.js +216 -0
- package/out/test/github.test.js +232 -0
- package/out/test/headless-sync.test.js +279 -0
- package/out/test/helpers/manifest-fixtures.js +27 -0
- package/out/test/mcp-obo.test.js +82 -0
- package/out/test/mcp.test.js +307 -0
- package/out/test/openclaw-channel-routing.test.js +191 -0
- package/out/test/pathSafety.test.js +63 -0
- package/out/test/permissions.test.js +175 -0
- package/out/test/persistent-engine-worker.test.js +220 -0
- package/out/test/refreshPolicy.test.js +60 -0
- package/out/test/registry.test.js +351 -0
- package/out/test/runtime.test.js +349 -0
- package/out/test/scheduledFleetStorage.test.js +72 -0
- package/out/test/serve-hardening.test.js +192 -0
- package/out/test/serveClient.test.js +173 -0
- package/out/test/sessionJsonlFleet.test.js +78 -0
- package/out/test/shared.test.js +173 -0
- package/out/test/skills-mcp.test.js +299 -0
- package/out/test/spotlightPublish.test.js +299 -0
- package/out/test/spotlightYaml.test.js +58 -0
- package/out/test/state-sync-peer.test.js +74 -0
- package/out/test/store.test.js +331 -0
- package/out/test/stores.test.js +504 -0
- package/out/test/sync-switch.test.js +251 -0
- package/out/test/teamViewDiscovery.test.js +90 -0
- package/out/test/toolPath.test.js +120 -0
- package/out/test/utils.test.js +308 -0
- package/out/test/viewsConfigMerge.test.js +180 -0
- package/out/test/windows-command-line.test.js +75 -0
- package/out/utils/agentEnvFile.js +131 -0
- package/out/utils/binary.js +175 -0
- package/out/utils/index.js +37 -0
- package/out/utils/network.js +74 -0
- package/out/utils/pathSafety.js +60 -0
- package/out/utils/paths.js +116 -0
- package/out/utils/pid.js +180 -0
- package/out/utils/pythonRunner.js +258 -0
- package/out/utils/resolveNodeBinary.js +168 -0
- package/out/utils/toolPath.js +83 -0
- package/out/utils/windowsCommandLine.js +29 -0
- package/out/utils.js +915 -0
- package/out/workspaceFolders.js +59 -0
- 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())
|