@kb-labs/rest-api-app 1.3.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/.kb/agents/sessions/session-1771531145393-ws3uh6q/turns.json +42 -0
- package/.kb/agents/sessions/session-1771548930486-ui6rtvm/turns.json +33 -0
- package/.kb/agents/sessions/session-1771549691485-nibqroq/turns.json +133 -0
- package/.kb/agents/sessions/session-1771550943182-dkwqdjc/turns.json +130 -0
- package/.kb/agents/sessions/session-1771551564182-cr4bwzn/turns.json +349 -0
- package/.kb/agents/sessions/session-1771552401370-g4ztwd1/turns.json +206 -0
- package/.kb/commit/current/plan.json +248 -0
- package/.kb/commit/current/status.json +472 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/health.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/health.js +215 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/health.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/list.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/list.js +271 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/list.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/logs.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/logs.js +230 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/logs.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/metrics.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/metrics.js +228 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/metrics.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/run.d.ts +11 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/run.js +281 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/run.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/status.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/status.js +266 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/status.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/workflow-run.d.ts +11 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/workflow-run.js +284 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/workflow-run.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/flags-WLdpSZl4.d.ts +77 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/index.d.ts +2 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/index.js +441 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/index.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/manifest.d.ts +196 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/manifest.js +441 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/manifest.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/cron-list-handler.d.ts +19 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/cron-list-handler.js +48 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/cron-list-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/index.d.ts +6 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/index.js +170 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/index.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-cancel-handler.d.ts +12 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-cancel-handler.js +52 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-cancel-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-detail-handler.d.ts +22 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-detail-handler.js +52 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-detail-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-logs-handler.d.ts +13 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-logs-handler.js +52 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-logs-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-steps-handler.d.ts +9 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-steps-handler.js +43 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-steps-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/jobs-list-handler.d.ts +26 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/jobs-list-handler.js +63 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/jobs-list-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/pending-approvals-handler.d.ts +9 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/pending-approvals-handler.js +46 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/pending-approvals-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/resolve-approval-handler.d.ts +16 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/resolve-approval-handler.js +58 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/resolve-approval-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/run-detail-handler.d.ts +9 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/run-detail-handler.js +46 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/run-detail-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/runs-list-handler.d.ts +11 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/runs-list-handler.js +50 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/runs-list-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/stats-handler.d.ts +7 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/stats-handler.js +39 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/stats-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-detail-handler.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-detail-handler.js +46 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-detail-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-cancel-handler.d.ts +13 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-cancel-handler.js +47 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-cancel-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-handler.d.ts +14 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-handler.js +53 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-runs-handler.d.ts +13 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-runs-handler.js +54 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-runs-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflows-list-handler.d.ts +12 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflows-list-handler.js +51 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflows-list-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/logs-channel.d.ts +17 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/logs-channel.js +133 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/logs-channel.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/progress-channel.d.ts +11 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/progress-channel.js +46 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/progress-channel.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/health.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/health.js +215 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/health.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/list.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/list.js +271 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/list.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/logs.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/logs.js +230 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/logs.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/metrics.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/metrics.js +228 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/metrics.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/run.d.ts +11 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/run.js +281 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/run.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/status.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/status.js +266 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/status.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/workflow-run.d.ts +11 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/workflow-run.js +284 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/workflow-run.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/flags-WLdpSZl4.d.ts +77 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/index.d.ts +2 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/index.js +441 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/index.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/manifest.d.ts +196 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/manifest.js +441 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/manifest.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/cron-list-handler.d.ts +19 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/cron-list-handler.js +48 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/cron-list-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/index.d.ts +6 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/index.js +170 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/index.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-cancel-handler.d.ts +12 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-cancel-handler.js +52 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-cancel-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-detail-handler.d.ts +22 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-detail-handler.js +52 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-detail-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-logs-handler.d.ts +13 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-logs-handler.js +52 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-logs-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-steps-handler.d.ts +9 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-steps-handler.js +43 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-steps-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/jobs-list-handler.d.ts +26 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/jobs-list-handler.js +63 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/jobs-list-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/pending-approvals-handler.d.ts +9 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/pending-approvals-handler.js +46 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/pending-approvals-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/resolve-approval-handler.d.ts +16 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/resolve-approval-handler.js +58 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/resolve-approval-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/run-detail-handler.d.ts +9 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/run-detail-handler.js +46 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/run-detail-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/runs-list-handler.d.ts +11 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/runs-list-handler.js +50 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/runs-list-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/stats-handler.d.ts +7 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/stats-handler.js +39 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/stats-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-detail-handler.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-detail-handler.js +46 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-detail-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-cancel-handler.d.ts +13 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-cancel-handler.js +47 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-cancel-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-handler.d.ts +14 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-handler.js +53 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-runs-handler.d.ts +13 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-runs-handler.js +54 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-runs-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflows-list-handler.d.ts +12 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflows-list-handler.js +51 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflows-list-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/logs-channel.d.ts +17 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/logs-channel.js +133 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/logs-channel.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/progress-channel.d.ts +11 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/progress-channel.js +46 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/progress-channel.js.map +1 -0
- package/README.md +54 -0
- package/eslint.config.js +23 -0
- package/package.json +72 -0
- package/src/.kb/agents/sessions/session-1773470583796-ximqnfk/kpi-baseline.json +17 -0
- package/src/.kb/memory/sessions/session-1773470583796-ximqnfk/fact-sheet.json +1 -0
- package/src/bootstrap.ts +233 -0
- package/src/diagnostics/header-debug.ts +37 -0
- package/src/events/hub.ts +60 -0
- package/src/events/index.ts +1 -0
- package/src/index.ts +13 -0
- package/src/manifest.ts +21 -0
- package/src/middleware/__tests__/cache.spec.ts +184 -0
- package/src/middleware/__tests__/envelope.spec.ts +268 -0
- package/src/middleware/__tests__/metrics.redis.spec.ts +64 -0
- package/src/middleware/__tests__/rate-limit.spec.ts +283 -0
- package/src/middleware/__tests__/request-id.spec.ts +160 -0
- package/src/middleware/cache.ts +67 -0
- package/src/middleware/envelope.ts +136 -0
- package/src/middleware/error-guard.ts +69 -0
- package/src/middleware/index.ts +37 -0
- package/src/middleware/metrics.ts +810 -0
- package/src/middleware/mock-mode.ts +44 -0
- package/src/middleware/prom-metrics.ts +284 -0
- package/src/middleware/rate-limit-routes.ts +73 -0
- package/src/middleware/rate-limit.ts +72 -0
- package/src/middleware/request-id.ts +41 -0
- package/src/middleware/request-timeout.ts +69 -0
- package/src/middleware/security-headers.ts +24 -0
- package/src/middleware/startup-guard.ts +107 -0
- package/src/observability/service-contract.ts +192 -0
- package/src/plugins/index.ts +118 -0
- package/src/routes/__tests__/adapters-date-helpers.spec.ts +332 -0
- package/src/routes/__tests__/adapters.spec.ts +680 -0
- package/src/routes/__tests__/cache.spec.ts +264 -0
- package/src/routes/__tests__/events.spec.ts +434 -0
- package/src/routes/__tests__/health.spec.ts +414 -0
- package/src/routes/__tests__/logs.spec.ts +494 -0
- package/src/routes/__tests__/observability.spec.ts +392 -0
- package/src/routes/__tests__/plugins.spec.ts +931 -0
- package/src/routes/__tests__/workflow-management.spec.ts +698 -0
- package/src/routes/__tests__/workflows.spec.ts +204 -0
- package/src/routes/adapter-call.ts +220 -0
- package/src/routes/adapters.ts +1206 -0
- package/src/routes/analytics.ts +328 -0
- package/src/routes/cache.ts +104 -0
- package/src/routes/debug-routes.ts +95 -0
- package/src/routes/events.ts +115 -0
- package/src/routes/health.ts +348 -0
- package/src/routes/hello.ts +47 -0
- package/src/routes/index.ts +395 -0
- package/src/routes/jobs.ts +253 -0
- package/src/routes/logs.ts +718 -0
- package/src/routes/metrics.ts +120 -0
- package/src/routes/observability.ts +1388 -0
- package/src/routes/openapi.ts +101 -0
- package/src/routes/platform.ts +143 -0
- package/src/routes/plugins.ts +998 -0
- package/src/routes/readiness.ts +83 -0
- package/src/routes/workflow-management.ts +469 -0
- package/src/routes/workflows.ts +256 -0
- package/src/server.ts +203 -0
- package/src/services/historical-metrics.ts +496 -0
- package/src/services/incident-analyzer.ts +289 -0
- package/src/services/incident-detector.ts +691 -0
- package/src/services/incident-schema.sql +76 -0
- package/src/services/incident-storage.ts +593 -0
- package/src/services/system-metrics-collector.ts +244 -0
- package/src/types/fastify.d.ts +58 -0
- package/src/utils/__tests__/sse-auth.spec.ts +113 -0
- package/src/utils/path-helpers.ts +15 -0
- package/src/utils/repo.ts +42 -0
- package/src/utils/schema.ts +20 -0
- package/src/utils/sse-auth.ts +81 -0
- package/test-sandbox.sh +105 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +18 -0
|
@@ -0,0 +1,1206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/rest-api-app/routes/adapters
|
|
3
|
+
* Platform adapter analytics endpoints
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { FastifyInstance } from 'fastify';
|
|
7
|
+
import type { RestApiConfig } from '@kb-labs/rest-api-core';
|
|
8
|
+
import { normalizeBasePath, resolvePaths } from '../utils/path-helpers';
|
|
9
|
+
import { platform } from '@kb-labs/core-runtime';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* LLM usage statistics response
|
|
13
|
+
*/
|
|
14
|
+
interface LLMUsageStats {
|
|
15
|
+
totalRequests: number;
|
|
16
|
+
totalTokens: number;
|
|
17
|
+
totalCost: number;
|
|
18
|
+
costPer1KTokens: number; // Enhanced: Cost efficiency metric
|
|
19
|
+
// Cache savings
|
|
20
|
+
totalCacheReadTokens: number;
|
|
21
|
+
totalBillableTokens: number;
|
|
22
|
+
totalCacheSavingsUsd: number;
|
|
23
|
+
byModel: Record<
|
|
24
|
+
string,
|
|
25
|
+
{
|
|
26
|
+
requests: number;
|
|
27
|
+
promptTokens: number;
|
|
28
|
+
completionTokens: number;
|
|
29
|
+
totalTokens: number;
|
|
30
|
+
cost: number;
|
|
31
|
+
costPer1KTokens: number; // Enhanced: Cost per 1K tokens for this model
|
|
32
|
+
tokensPerRequest: number; // Enhanced: Average tokens per request
|
|
33
|
+
errorRate: number; // Enhanced: Error rate percentage (0-100)
|
|
34
|
+
avgDurationMs: number;
|
|
35
|
+
// Enhanced: Latency percentiles
|
|
36
|
+
p50DurationMs: number;
|
|
37
|
+
p95DurationMs: number;
|
|
38
|
+
p99DurationMs: number;
|
|
39
|
+
// Cache savings per model
|
|
40
|
+
cacheReadTokens: number;
|
|
41
|
+
billableTokens: number;
|
|
42
|
+
cacheSavingsUsd: number;
|
|
43
|
+
}
|
|
44
|
+
>;
|
|
45
|
+
errors: number;
|
|
46
|
+
// Enhanced: Error breakdown by type
|
|
47
|
+
errorBreakdown: {
|
|
48
|
+
timeout: number;
|
|
49
|
+
rateLimit: number;
|
|
50
|
+
invalidRequest: number;
|
|
51
|
+
serverError: number;
|
|
52
|
+
other: number;
|
|
53
|
+
};
|
|
54
|
+
timeRange: {
|
|
55
|
+
from: string;
|
|
56
|
+
to: string;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Helper to get event data, supporting both kb.v1 (payload) and legacy (properties) formats
|
|
62
|
+
*/
|
|
63
|
+
function getEventData(event: any): Record<string, unknown> {
|
|
64
|
+
// kb.v1 format uses 'payload'
|
|
65
|
+
if (event.schema === 'kb.v1' && event.payload) {
|
|
66
|
+
return event.payload as Record<string, unknown>;
|
|
67
|
+
}
|
|
68
|
+
// Legacy format uses 'properties'
|
|
69
|
+
return (event.properties as Record<string, unknown>) || {};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Extract date range from query parameters
|
|
74
|
+
*/
|
|
75
|
+
function extractDateRange(query: any): { from?: string; to?: string } {
|
|
76
|
+
// Handle null/undefined query
|
|
77
|
+
if (!query) {
|
|
78
|
+
return { from: undefined, to: undefined };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const from = query.from as string | undefined;
|
|
82
|
+
const to = query.to as string | undefined;
|
|
83
|
+
|
|
84
|
+
// Validate ISO 8601 format
|
|
85
|
+
if (from && !isValidISODate(from)) {
|
|
86
|
+
throw new Error('Invalid "from" date format. Expected ISO 8601 datetime (e.g., 2026-01-01T00:00:00Z)');
|
|
87
|
+
}
|
|
88
|
+
if (to && !isValidISODate(to)) {
|
|
89
|
+
throw new Error('Invalid "to" date format. Expected ISO 8601 datetime (e.g., 2026-01-31T23:59:59Z)');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { from, to };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Extract model filter from query parameters
|
|
97
|
+
* Supports both single model (?models=gpt-4) and multiple (?models=gpt-4,claude-sonnet)
|
|
98
|
+
*/
|
|
99
|
+
function extractModelFilter(query: any): string[] | undefined {
|
|
100
|
+
if (!query || !query.models) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const modelsParam = query.models as string;
|
|
105
|
+
|
|
106
|
+
// Split by comma and trim whitespace
|
|
107
|
+
const models = modelsParam
|
|
108
|
+
.split(',')
|
|
109
|
+
.map((m) => m.trim())
|
|
110
|
+
.filter((m) => m.length > 0);
|
|
111
|
+
|
|
112
|
+
return models.length > 0 ? models : undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Extract StatsQuery fields (groupBy, breakdownBy, metrics) from query parameters.
|
|
117
|
+
* These are optional — adapters silently ignore fields they don't support.
|
|
118
|
+
*/
|
|
119
|
+
function extractStatsOptions(query: any): {
|
|
120
|
+
groupBy?: 'hour' | 'day' | 'week' | 'month';
|
|
121
|
+
breakdownBy?: string;
|
|
122
|
+
metrics?: string[];
|
|
123
|
+
} {
|
|
124
|
+
if (!query) {return {};}
|
|
125
|
+
|
|
126
|
+
const groupBy = query.groupBy as string | undefined;
|
|
127
|
+
const validGroupBy = ['hour', 'day', 'week', 'month'];
|
|
128
|
+
const resolvedGroupBy =
|
|
129
|
+
groupBy && validGroupBy.includes(groupBy)
|
|
130
|
+
? (groupBy as 'hour' | 'day' | 'week' | 'month')
|
|
131
|
+
: undefined;
|
|
132
|
+
|
|
133
|
+
const breakdownBy = query.breakdownBy as string | undefined;
|
|
134
|
+
|
|
135
|
+
const metricsParam = query.metrics as string | undefined;
|
|
136
|
+
const metrics = metricsParam
|
|
137
|
+
? metricsParam
|
|
138
|
+
.split(',')
|
|
139
|
+
.map((m) => m.trim())
|
|
140
|
+
.filter((m) => m.length > 0)
|
|
141
|
+
: undefined;
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
groupBy: resolvedGroupBy,
|
|
145
|
+
breakdownBy: breakdownBy || undefined,
|
|
146
|
+
metrics: metrics && metrics.length > 0 ? metrics : undefined,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Fetch all events for a given query in batches to avoid memory issues.
|
|
152
|
+
*
|
|
153
|
+
* Backend handles pagination internally:
|
|
154
|
+
* - PAGE_SIZE: 10,000 events per request
|
|
155
|
+
* - MAX_EVENTS: 100,000 total events to process
|
|
156
|
+
*
|
|
157
|
+
* Frontend just sends date range, backend fetches all events in that range.
|
|
158
|
+
*
|
|
159
|
+
* @param analytics - Analytics adapter
|
|
160
|
+
* @param query - Event query (type, from, to)
|
|
161
|
+
* @param fastify - Fastify instance for logging
|
|
162
|
+
* @returns All events (up to MAX_EVENTS)
|
|
163
|
+
*/
|
|
164
|
+
async function fetchAllEventsBatched(
|
|
165
|
+
analytics: any,
|
|
166
|
+
query: { type: string | string[]; from?: string; to?: string },
|
|
167
|
+
fastify: FastifyInstance
|
|
168
|
+
): Promise<any[]> {
|
|
169
|
+
const PAGE_SIZE = 10000;
|
|
170
|
+
const MAX_EVENTS = 100000;
|
|
171
|
+
|
|
172
|
+
const allEvents: any[] = [];
|
|
173
|
+
let offset = 0;
|
|
174
|
+
let hasMore = true;
|
|
175
|
+
|
|
176
|
+
while (hasMore && allEvents.length < MAX_EVENTS) {
|
|
177
|
+
const response = await analytics.getEvents({
|
|
178
|
+
...query,
|
|
179
|
+
limit: PAGE_SIZE,
|
|
180
|
+
offset,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
allEvents.push(...response.events);
|
|
184
|
+
hasMore = response.hasMore;
|
|
185
|
+
offset += PAGE_SIZE;
|
|
186
|
+
|
|
187
|
+
// Stop if we've fetched all events or page not full
|
|
188
|
+
if (!hasMore || response.events.length < PAGE_SIZE) {
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Warn if we hit the safety limit
|
|
194
|
+
if (allEvents.length >= MAX_EVENTS) {
|
|
195
|
+
fastify.log.warn({ query }, `Hit max events limit (${MAX_EVENTS}) for query. Some events excluded from stats.`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return allEvents;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Basic ISO 8601 validation
|
|
203
|
+
*/
|
|
204
|
+
function isValidISODate(dateString: string): boolean {
|
|
205
|
+
// Check if it's a string
|
|
206
|
+
if (typeof dateString !== 'string' || dateString.length === 0) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const date = new Date(dateString);
|
|
211
|
+
return !isNaN(date.getTime());
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Register platform adapter analytics routes
|
|
216
|
+
*/
|
|
217
|
+
export async function registerAdaptersRoutes(
|
|
218
|
+
fastify: FastifyInstance,
|
|
219
|
+
config: RestApiConfig
|
|
220
|
+
): Promise<void> {
|
|
221
|
+
const basePath = normalizeBasePath(config.basePath);
|
|
222
|
+
|
|
223
|
+
// GET /api/v1/adapters/llm/usage
|
|
224
|
+
// Returns aggregated LLM usage statistics
|
|
225
|
+
const llmUsagePaths = resolvePaths(basePath, '/adapters/llm/usage');
|
|
226
|
+
for (const path of llmUsagePaths) {
|
|
227
|
+
fastify.get(path, async (request, reply) => {
|
|
228
|
+
const analytics = platform.analytics;
|
|
229
|
+
|
|
230
|
+
// Check if analytics supports reading
|
|
231
|
+
if (!analytics.getEvents) {
|
|
232
|
+
return reply.code(501).send({
|
|
233
|
+
ok: false,
|
|
234
|
+
error: {
|
|
235
|
+
code: 'ANALYTICS_NOT_IMPLEMENTED',
|
|
236
|
+
message: 'Analytics adapter does not support reading events',
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
// Extract and validate date range
|
|
243
|
+
const dateRange = extractDateRange(request.query);
|
|
244
|
+
|
|
245
|
+
// Fetch ALL LLM completion events in batches (backend handles pagination)
|
|
246
|
+
const completedEvents = await fetchAllEventsBatched(
|
|
247
|
+
analytics,
|
|
248
|
+
{
|
|
249
|
+
type: ['llm.completion.completed', 'llm.chatWithTools.completed'],
|
|
250
|
+
from: dateRange.from,
|
|
251
|
+
to: dateRange.to,
|
|
252
|
+
},
|
|
253
|
+
fastify
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// Fetch ALL error events in batches
|
|
257
|
+
const errorEvents = await fetchAllEventsBatched(
|
|
258
|
+
analytics,
|
|
259
|
+
{
|
|
260
|
+
type: ['llm.completion.error', 'llm.chatWithTools.error'],
|
|
261
|
+
from: dateRange.from,
|
|
262
|
+
to: dateRange.to,
|
|
263
|
+
},
|
|
264
|
+
fastify
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
// Aggregate statistics
|
|
268
|
+
const stats: LLMUsageStats = {
|
|
269
|
+
totalRequests: 0,
|
|
270
|
+
totalTokens: 0,
|
|
271
|
+
totalCost: 0,
|
|
272
|
+
costPer1KTokens: 0,
|
|
273
|
+
totalCacheReadTokens: 0,
|
|
274
|
+
totalBillableTokens: 0,
|
|
275
|
+
totalCacheSavingsUsd: 0,
|
|
276
|
+
byModel: {},
|
|
277
|
+
errors: errorEvents.length,
|
|
278
|
+
errorBreakdown: {
|
|
279
|
+
timeout: 0,
|
|
280
|
+
rateLimit: 0,
|
|
281
|
+
invalidRequest: 0,
|
|
282
|
+
serverError: 0,
|
|
283
|
+
other: 0,
|
|
284
|
+
},
|
|
285
|
+
timeRange: {
|
|
286
|
+
from: '',
|
|
287
|
+
to: '',
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// Collect durations per model for percentile calculation
|
|
292
|
+
const durationsByModel: Record<string, number[]> = {};
|
|
293
|
+
|
|
294
|
+
// Track errors per model
|
|
295
|
+
const errorsByModel: Record<string, number> = {};
|
|
296
|
+
|
|
297
|
+
// Process completed events
|
|
298
|
+
for (const event of completedEvents) {
|
|
299
|
+
const props = getEventData(event);
|
|
300
|
+
|
|
301
|
+
const model = (props.model || 'unknown') as string;
|
|
302
|
+
const promptTokens = Number(props.promptTokens || 0);
|
|
303
|
+
const completionTokens = Number(props.completionTokens || 0);
|
|
304
|
+
const totalTokens = Number(props.totalTokens || promptTokens + completionTokens);
|
|
305
|
+
const estimatedCost = Number(props.estimatedCost || 0);
|
|
306
|
+
const durationMs = Number(props.durationMs || 0);
|
|
307
|
+
const cacheReadTokens = Number(props.cacheReadTokens || 0);
|
|
308
|
+
const billableTokens = Number(props.billableTotalTokens || totalTokens);
|
|
309
|
+
const cacheSavingsUsd = Number(props.estimatedCacheSavingsUsd || 0);
|
|
310
|
+
|
|
311
|
+
stats.totalRequests++;
|
|
312
|
+
stats.totalTokens += totalTokens;
|
|
313
|
+
stats.totalCost += estimatedCost;
|
|
314
|
+
stats.totalCacheReadTokens += cacheReadTokens;
|
|
315
|
+
stats.totalBillableTokens += billableTokens;
|
|
316
|
+
stats.totalCacheSavingsUsd += cacheSavingsUsd;
|
|
317
|
+
|
|
318
|
+
if (!stats.byModel[model]) {
|
|
319
|
+
stats.byModel[model] = {
|
|
320
|
+
requests: 0,
|
|
321
|
+
promptTokens: 0,
|
|
322
|
+
completionTokens: 0,
|
|
323
|
+
totalTokens: 0,
|
|
324
|
+
cost: 0,
|
|
325
|
+
costPer1KTokens: 0,
|
|
326
|
+
tokensPerRequest: 0,
|
|
327
|
+
errorRate: 0,
|
|
328
|
+
avgDurationMs: 0,
|
|
329
|
+
p50DurationMs: 0,
|
|
330
|
+
p95DurationMs: 0,
|
|
331
|
+
p99DurationMs: 0,
|
|
332
|
+
cacheReadTokens: 0,
|
|
333
|
+
billableTokens: 0,
|
|
334
|
+
cacheSavingsUsd: 0,
|
|
335
|
+
};
|
|
336
|
+
durationsByModel[model] = [];
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const modelStats = stats.byModel[model]!;
|
|
340
|
+
modelStats.requests++;
|
|
341
|
+
modelStats.promptTokens += promptTokens;
|
|
342
|
+
modelStats.completionTokens += completionTokens;
|
|
343
|
+
modelStats.totalTokens += totalTokens;
|
|
344
|
+
modelStats.cost += estimatedCost;
|
|
345
|
+
modelStats.cacheReadTokens += cacheReadTokens;
|
|
346
|
+
modelStats.billableTokens += billableTokens;
|
|
347
|
+
modelStats.cacheSavingsUsd += cacheSavingsUsd;
|
|
348
|
+
modelStats.avgDurationMs =
|
|
349
|
+
(modelStats.avgDurationMs * (modelStats.requests - 1) + durationMs) / modelStats.requests;
|
|
350
|
+
|
|
351
|
+
// Collect duration for percentile calculation
|
|
352
|
+
durationsByModel[model]!.push(durationMs);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Process error events for breakdown
|
|
356
|
+
for (const event of errorEvents) {
|
|
357
|
+
const props = getEventData(event);
|
|
358
|
+
const errorType = String(props.errorType || props.errorCode || '').toLowerCase();
|
|
359
|
+
const errorMessage = String(props.error || props.message || '').toLowerCase();
|
|
360
|
+
const model = (props.model || 'unknown') as string;
|
|
361
|
+
|
|
362
|
+
// Track errors per model
|
|
363
|
+
errorsByModel[model] = (errorsByModel[model] || 0) + 1;
|
|
364
|
+
|
|
365
|
+
// Categorize error by type
|
|
366
|
+
if (errorType.includes('timeout') || errorMessage.includes('timeout') || errorMessage.includes('timed out')) {
|
|
367
|
+
stats.errorBreakdown.timeout++;
|
|
368
|
+
} else if (errorType.includes('rate') || errorType.includes('quota') || errorMessage.includes('rate limit') || errorMessage.includes('quota exceeded')) {
|
|
369
|
+
stats.errorBreakdown.rateLimit++;
|
|
370
|
+
} else if (errorType.includes('invalid') || errorType.includes('validation') || errorMessage.includes('invalid request') || errorMessage.includes('bad request')) {
|
|
371
|
+
stats.errorBreakdown.invalidRequest++;
|
|
372
|
+
} else if (errorType.includes('server') || errorType.includes('5xx') || errorMessage.includes('internal error') || errorMessage.includes('server error')) {
|
|
373
|
+
stats.errorBreakdown.serverError++;
|
|
374
|
+
} else {
|
|
375
|
+
stats.errorBreakdown.other++;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Calculate percentiles and cost per 1K tokens for each model
|
|
380
|
+
for (const [model, durations] of Object.entries(durationsByModel)) {
|
|
381
|
+
const modelStats = stats.byModel[model]!;
|
|
382
|
+
|
|
383
|
+
// Calculate percentiles
|
|
384
|
+
if (durations.length > 0) {
|
|
385
|
+
const sorted = durations.slice().sort((a, b) => a - b);
|
|
386
|
+
const p50Index = Math.floor(sorted.length * 0.5);
|
|
387
|
+
const p95Index = Math.floor(sorted.length * 0.95);
|
|
388
|
+
const p99Index = Math.floor(sorted.length * 0.99);
|
|
389
|
+
|
|
390
|
+
modelStats.p50DurationMs = sorted[p50Index] ?? 0;
|
|
391
|
+
modelStats.p95DurationMs = sorted[p95Index] ?? 0;
|
|
392
|
+
modelStats.p99DurationMs = sorted[p99Index] ?? 0;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Calculate cost per 1K tokens
|
|
396
|
+
if (modelStats.totalTokens > 0) {
|
|
397
|
+
modelStats.costPer1KTokens = (modelStats.cost / modelStats.totalTokens) * 1000;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Calculate tokens per request
|
|
401
|
+
if (modelStats.requests > 0) {
|
|
402
|
+
modelStats.tokensPerRequest = modelStats.totalTokens / modelStats.requests;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Calculate error rate percentage
|
|
406
|
+
const modelErrors = errorsByModel[model] || 0;
|
|
407
|
+
const totalModelRequests = modelStats.requests + modelErrors;
|
|
408
|
+
if (totalModelRequests > 0) {
|
|
409
|
+
modelStats.errorRate = (modelErrors / totalModelRequests) * 100;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Calculate overall cost per 1K tokens
|
|
414
|
+
if (stats.totalTokens > 0) {
|
|
415
|
+
stats.costPer1KTokens = (stats.totalCost / stats.totalTokens) * 1000;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Calculate time range
|
|
419
|
+
const allTimestamps = completedEvents.map((e) => e.ts);
|
|
420
|
+
if (allTimestamps.length > 0) {
|
|
421
|
+
stats.timeRange.from = allTimestamps[allTimestamps.length - 1]!; // oldest
|
|
422
|
+
stats.timeRange.to = allTimestamps[0]!; // newest
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
ok: true,
|
|
427
|
+
data: stats,
|
|
428
|
+
meta: {
|
|
429
|
+
source: 'analytics-adapter',
|
|
430
|
+
totalEvents: completedEvents.length,
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
} catch (error) {
|
|
434
|
+
// Handle validation errors
|
|
435
|
+
if (error instanceof Error && error.message.includes('Invalid')) {
|
|
436
|
+
return reply.code(400).send({
|
|
437
|
+
ok: false,
|
|
438
|
+
error: {
|
|
439
|
+
code: 'INVALID_DATE_RANGE',
|
|
440
|
+
message: error.message,
|
|
441
|
+
},
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
platform.logger.error('Failed to fetch LLM usage stats', error instanceof Error ? error : new Error(String(error)));
|
|
446
|
+
|
|
447
|
+
return reply.code(500).send({
|
|
448
|
+
ok: false,
|
|
449
|
+
error: {
|
|
450
|
+
code: 'LLM_USAGE_STATS_ERROR',
|
|
451
|
+
message: error instanceof Error ? error.message : 'Failed to fetch LLM usage statistics',
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// GET /api/v1/adapters/llm/daily-stats
|
|
459
|
+
// Returns time-bucketed aggregated LLM statistics for time-series visualization
|
|
460
|
+
// Query params: from, to, groupBy (hour|day|week|month), breakdownBy (dot-path), metrics (csv), models (csv, legacy filter)
|
|
461
|
+
const llmDailyStatsPaths = resolvePaths(basePath, '/adapters/llm/daily-stats');
|
|
462
|
+
for (const path of llmDailyStatsPaths) {
|
|
463
|
+
fastify.get(path, async (request, reply) => {
|
|
464
|
+
const analytics = platform.analytics;
|
|
465
|
+
|
|
466
|
+
if (!analytics.getDailyStats) {
|
|
467
|
+
return reply.code(501).send({
|
|
468
|
+
ok: false,
|
|
469
|
+
error: {
|
|
470
|
+
code: 'DAILY_STATS_NOT_IMPLEMENTED',
|
|
471
|
+
message: 'Analytics adapter does not support daily statistics',
|
|
472
|
+
},
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
try {
|
|
477
|
+
const dateRange = extractDateRange(request.query);
|
|
478
|
+
const statsOptions = extractStatsOptions(request.query);
|
|
479
|
+
const modelFilter = extractModelFilter(request.query);
|
|
480
|
+
|
|
481
|
+
const dailyStats = await analytics.getDailyStats({
|
|
482
|
+
type: ['llm.completion.completed', 'llm.chatWithTools.completed'],
|
|
483
|
+
from: dateRange.from,
|
|
484
|
+
to: dateRange.to,
|
|
485
|
+
limit: 50000,
|
|
486
|
+
// Legacy ?models= filter maps to source filter via getEvents inside the adapter.
|
|
487
|
+
// When breakdownBy is provided explicitly it takes precedence.
|
|
488
|
+
// Models filter is handled post-aggregation below for backward compat.
|
|
489
|
+
...statsOptions,
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// Legacy model filter: post-filter rows by breakdown value when models param is used
|
|
493
|
+
// without an explicit breakdownBy (backward compatibility with existing Studio pages)
|
|
494
|
+
const result =
|
|
495
|
+
modelFilter && modelFilter.length > 0 && !statsOptions.breakdownBy
|
|
496
|
+
? dailyStats.filter((s) => !s.breakdown || modelFilter.includes(s.breakdown))
|
|
497
|
+
: dailyStats;
|
|
498
|
+
|
|
499
|
+
return reply.send({ ok: true, data: result });
|
|
500
|
+
} catch (error) {
|
|
501
|
+
if (error instanceof Error && error.message.includes('Invalid')) {
|
|
502
|
+
return reply.code(400).send({
|
|
503
|
+
ok: false,
|
|
504
|
+
error: { code: 'INVALID_DATE_RANGE', message: error.message },
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
return reply.code(500).send({
|
|
508
|
+
ok: false,
|
|
509
|
+
error: {
|
|
510
|
+
code: 'DAILY_STATS_ERROR',
|
|
511
|
+
message: error instanceof Error ? error.message : 'Failed to fetch daily statistics',
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// GET /api/v1/adapters/embeddings/daily-stats
|
|
519
|
+
// Returns time-bucketed aggregated Embeddings statistics for time-series visualization
|
|
520
|
+
// Query params: from, to, groupBy (hour|day|week|month), breakdownBy (dot-path), metrics (csv)
|
|
521
|
+
const embeddingsDailyStatsPaths = resolvePaths(basePath, '/adapters/embeddings/daily-stats');
|
|
522
|
+
for (const path of embeddingsDailyStatsPaths) {
|
|
523
|
+
fastify.get(path, async (request, reply) => {
|
|
524
|
+
const analytics = platform.analytics;
|
|
525
|
+
|
|
526
|
+
if (!analytics.getDailyStats) {
|
|
527
|
+
return reply.code(501).send({
|
|
528
|
+
ok: false,
|
|
529
|
+
error: {
|
|
530
|
+
code: 'DAILY_STATS_NOT_IMPLEMENTED',
|
|
531
|
+
message: 'Analytics adapter does not support daily statistics',
|
|
532
|
+
},
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
try {
|
|
537
|
+
const dateRange = extractDateRange(request.query);
|
|
538
|
+
const statsOptions = extractStatsOptions(request.query);
|
|
539
|
+
|
|
540
|
+
const dailyStats = await analytics.getDailyStats({
|
|
541
|
+
type: ['embeddings.embed.completed', 'embeddings.embedBatch.completed'],
|
|
542
|
+
from: dateRange.from,
|
|
543
|
+
to: dateRange.to,
|
|
544
|
+
limit: 50000,
|
|
545
|
+
...statsOptions,
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
return reply.send({ ok: true, data: dailyStats });
|
|
549
|
+
} catch (error) {
|
|
550
|
+
if (error instanceof Error && error.message.includes('Invalid')) {
|
|
551
|
+
return reply.code(400).send({
|
|
552
|
+
ok: false,
|
|
553
|
+
error: { code: 'INVALID_DATE_RANGE', message: error.message },
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
return reply.code(500).send({
|
|
557
|
+
ok: false,
|
|
558
|
+
error: {
|
|
559
|
+
code: 'DAILY_STATS_ERROR',
|
|
560
|
+
message: error instanceof Error ? error.message : 'Failed to fetch daily statistics',
|
|
561
|
+
},
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// GET /api/v1/adapters/embeddings/usage
|
|
568
|
+
// Returns aggregated Embeddings usage statistics
|
|
569
|
+
const embeddingsUsagePaths = resolvePaths(basePath, '/adapters/embeddings/usage');
|
|
570
|
+
for (const path of embeddingsUsagePaths) {
|
|
571
|
+
fastify.get(path, async (request, reply) => {
|
|
572
|
+
const analytics = platform.analytics;
|
|
573
|
+
|
|
574
|
+
if (!analytics.getEvents) {
|
|
575
|
+
return reply.code(501).send({
|
|
576
|
+
ok: false,
|
|
577
|
+
error: {
|
|
578
|
+
code: 'ANALYTICS_NOT_IMPLEMENTED',
|
|
579
|
+
message: 'Analytics adapter does not support reading events',
|
|
580
|
+
},
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
// Extract and validate date range
|
|
586
|
+
const dateRange = extractDateRange(request.query);
|
|
587
|
+
|
|
588
|
+
const completedEvents = await fetchAllEventsBatched(
|
|
589
|
+
analytics,
|
|
590
|
+
{
|
|
591
|
+
type: ['embeddings.embed.completed', 'embeddings.embedBatch.completed'],
|
|
592
|
+
from: dateRange.from,
|
|
593
|
+
to: dateRange.to,
|
|
594
|
+
},
|
|
595
|
+
fastify
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
const errorEvents = await fetchAllEventsBatched(
|
|
599
|
+
analytics,
|
|
600
|
+
{
|
|
601
|
+
type: ['embeddings.embed.error', 'embeddings.embedBatch.error'],
|
|
602
|
+
from: dateRange.from,
|
|
603
|
+
to: dateRange.to,
|
|
604
|
+
},
|
|
605
|
+
fastify
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
const stats = {
|
|
609
|
+
totalRequests: completedEvents.length,
|
|
610
|
+
totalTextLength: 0,
|
|
611
|
+
totalCost: 0,
|
|
612
|
+
costPer1KChars: 0, // Enhanced: Cost efficiency metric
|
|
613
|
+
errors: errorEvents.length,
|
|
614
|
+
// Enhanced: Error breakdown by type
|
|
615
|
+
errorBreakdown: {
|
|
616
|
+
timeout: 0,
|
|
617
|
+
rateLimit: 0,
|
|
618
|
+
invalidRequest: 0,
|
|
619
|
+
serverError: 0,
|
|
620
|
+
other: 0,
|
|
621
|
+
},
|
|
622
|
+
avgDurationMs: 0,
|
|
623
|
+
// Enhanced: Latency percentiles
|
|
624
|
+
p50DurationMs: 0,
|
|
625
|
+
p95DurationMs: 0,
|
|
626
|
+
p99DurationMs: 0,
|
|
627
|
+
batchRequests: 0,
|
|
628
|
+
singleRequests: 0,
|
|
629
|
+
avgBatchSize: 0,
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
let totalDuration = 0;
|
|
633
|
+
let totalBatchSize = 0;
|
|
634
|
+
const durations: number[] = [];
|
|
635
|
+
|
|
636
|
+
for (const event of completedEvents) {
|
|
637
|
+
const props = getEventData(event);
|
|
638
|
+
|
|
639
|
+
const textLength = Number(props.textLength || props.totalTextLength || 0);
|
|
640
|
+
const cost = Number(props.estimatedCost || 0);
|
|
641
|
+
const duration = Number(props.durationMs || 0);
|
|
642
|
+
const batchSize = Number(props.batchSize || 1);
|
|
643
|
+
|
|
644
|
+
stats.totalTextLength += textLength;
|
|
645
|
+
stats.totalCost += cost;
|
|
646
|
+
totalDuration += duration;
|
|
647
|
+
durations.push(duration);
|
|
648
|
+
|
|
649
|
+
if (batchSize > 1) {
|
|
650
|
+
stats.batchRequests++;
|
|
651
|
+
totalBatchSize += batchSize;
|
|
652
|
+
} else {
|
|
653
|
+
stats.singleRequests++;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
stats.avgDurationMs = stats.totalRequests > 0 ? totalDuration / stats.totalRequests : 0;
|
|
658
|
+
stats.avgBatchSize = stats.batchRequests > 0 ? totalBatchSize / stats.batchRequests : 0;
|
|
659
|
+
|
|
660
|
+
// Calculate percentiles
|
|
661
|
+
if (durations.length > 0) {
|
|
662
|
+
const sorted = durations.slice().sort((a, b) => a - b);
|
|
663
|
+
const p50Index = Math.floor(sorted.length * 0.5);
|
|
664
|
+
const p95Index = Math.floor(sorted.length * 0.95);
|
|
665
|
+
const p99Index = Math.floor(sorted.length * 0.99);
|
|
666
|
+
|
|
667
|
+
stats.p50DurationMs = sorted[p50Index] ?? 0;
|
|
668
|
+
stats.p95DurationMs = sorted[p95Index] ?? 0;
|
|
669
|
+
stats.p99DurationMs = sorted[p99Index] ?? 0;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Calculate cost per 1K characters
|
|
673
|
+
if (stats.totalTextLength > 0) {
|
|
674
|
+
stats.costPer1KChars = (stats.totalCost / stats.totalTextLength) * 1000;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Process error events for breakdown
|
|
678
|
+
for (const event of errorEvents) {
|
|
679
|
+
const props = getEventData(event);
|
|
680
|
+
const errorType = String(props.errorType || props.errorCode || '').toLowerCase();
|
|
681
|
+
const errorMessage = String(props.error || props.message || '').toLowerCase();
|
|
682
|
+
|
|
683
|
+
// Categorize error by type
|
|
684
|
+
if (errorType.includes('timeout') || errorMessage.includes('timeout') || errorMessage.includes('timed out')) {
|
|
685
|
+
stats.errorBreakdown.timeout++;
|
|
686
|
+
} else if (errorType.includes('rate') || errorType.includes('quota') || errorMessage.includes('rate limit') || errorMessage.includes('quota exceeded')) {
|
|
687
|
+
stats.errorBreakdown.rateLimit++;
|
|
688
|
+
} else if (errorType.includes('invalid') || errorType.includes('validation') || errorMessage.includes('invalid request') || errorMessage.includes('bad request')) {
|
|
689
|
+
stats.errorBreakdown.invalidRequest++;
|
|
690
|
+
} else if (errorType.includes('server') || errorType.includes('5xx') || errorMessage.includes('internal error') || errorMessage.includes('server error')) {
|
|
691
|
+
stats.errorBreakdown.serverError++;
|
|
692
|
+
} else {
|
|
693
|
+
stats.errorBreakdown.other++;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return { ok: true, data: stats, meta: { source: 'analytics-adapter' } };
|
|
698
|
+
} catch (error) {
|
|
699
|
+
// Handle validation errors
|
|
700
|
+
if (error instanceof Error && error.message.includes('Invalid')) {
|
|
701
|
+
return reply.code(400).send({
|
|
702
|
+
ok: false,
|
|
703
|
+
error: {
|
|
704
|
+
code: 'INVALID_DATE_RANGE',
|
|
705
|
+
message: error.message,
|
|
706
|
+
},
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
platform.logger.error('Failed to fetch Embeddings usage stats', error instanceof Error ? error : new Error(String(error)));
|
|
711
|
+
return reply.code(500).send({
|
|
712
|
+
ok: false,
|
|
713
|
+
error: {
|
|
714
|
+
code: 'EMBEDDINGS_USAGE_STATS_ERROR',
|
|
715
|
+
message: error instanceof Error ? error.message : 'Failed to fetch embeddings statistics',
|
|
716
|
+
},
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// GET /api/v1/adapters/vectorstore/daily-stats
|
|
723
|
+
// Returns time-bucketed aggregated VectorStore statistics for time-series visualization
|
|
724
|
+
// Query params: from, to, groupBy (hour|day|week|month), breakdownBy (dot-path), metrics (csv)
|
|
725
|
+
const vectorstoreDailyStatsPaths = resolvePaths(basePath, '/adapters/vectorstore/daily-stats');
|
|
726
|
+
for (const path of vectorstoreDailyStatsPaths) {
|
|
727
|
+
fastify.get(path, async (request, reply) => {
|
|
728
|
+
const analytics = platform.analytics;
|
|
729
|
+
|
|
730
|
+
if (!analytics.getDailyStats) {
|
|
731
|
+
return reply.code(501).send({
|
|
732
|
+
ok: false,
|
|
733
|
+
error: {
|
|
734
|
+
code: 'DAILY_STATS_NOT_IMPLEMENTED',
|
|
735
|
+
message: 'Analytics adapter does not support daily statistics',
|
|
736
|
+
},
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
try {
|
|
741
|
+
const dateRange = extractDateRange(request.query);
|
|
742
|
+
const statsOptions = extractStatsOptions(request.query);
|
|
743
|
+
|
|
744
|
+
const dailyStats = await analytics.getDailyStats({
|
|
745
|
+
type: ['vectorstore.search.completed', 'vectorstore.upsert.completed', 'vectorstore.delete.completed'],
|
|
746
|
+
from: dateRange.from,
|
|
747
|
+
to: dateRange.to,
|
|
748
|
+
limit: 50000,
|
|
749
|
+
...statsOptions,
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
return reply.send({ ok: true, data: dailyStats });
|
|
753
|
+
} catch (error) {
|
|
754
|
+
if (error instanceof Error && error.message.includes('Invalid')) {
|
|
755
|
+
return reply.code(400).send({
|
|
756
|
+
ok: false,
|
|
757
|
+
error: { code: 'INVALID_DATE_RANGE', message: error.message },
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
return reply.code(500).send({
|
|
761
|
+
ok: false,
|
|
762
|
+
error: {
|
|
763
|
+
code: 'DAILY_STATS_ERROR',
|
|
764
|
+
message: error instanceof Error ? error.message : 'Failed to fetch daily statistics',
|
|
765
|
+
},
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// GET /api/v1/adapters/vectorstore/usage
|
|
772
|
+
// Returns aggregated VectorStore usage statistics
|
|
773
|
+
const vectorstoreUsagePaths = resolvePaths(basePath, '/adapters/vectorstore/usage');
|
|
774
|
+
for (const path of vectorstoreUsagePaths) {
|
|
775
|
+
fastify.get(path, async (request, reply) => {
|
|
776
|
+
const analytics = platform.analytics;
|
|
777
|
+
|
|
778
|
+
if (!analytics.getEvents) {
|
|
779
|
+
return reply.code(501).send({
|
|
780
|
+
ok: false,
|
|
781
|
+
error: {
|
|
782
|
+
code: 'ANALYTICS_NOT_IMPLEMENTED',
|
|
783
|
+
message: 'Analytics adapter does not support reading events',
|
|
784
|
+
},
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
try {
|
|
789
|
+
// Extract and validate date range
|
|
790
|
+
const dateRange = extractDateRange(request.query);
|
|
791
|
+
|
|
792
|
+
const searchEvents = await fetchAllEventsBatched(
|
|
793
|
+
analytics,
|
|
794
|
+
{
|
|
795
|
+
type: 'vectorstore.search.completed',
|
|
796
|
+
from: dateRange.from,
|
|
797
|
+
to: dateRange.to,
|
|
798
|
+
},
|
|
799
|
+
fastify
|
|
800
|
+
);
|
|
801
|
+
|
|
802
|
+
const upsertEvents = await fetchAllEventsBatched(
|
|
803
|
+
analytics,
|
|
804
|
+
{
|
|
805
|
+
type: 'vectorstore.upsert.completed',
|
|
806
|
+
from: dateRange.from,
|
|
807
|
+
to: dateRange.to,
|
|
808
|
+
},
|
|
809
|
+
fastify
|
|
810
|
+
);
|
|
811
|
+
|
|
812
|
+
const deleteEvents = await fetchAllEventsBatched(
|
|
813
|
+
analytics,
|
|
814
|
+
{
|
|
815
|
+
type: 'vectorstore.delete.completed',
|
|
816
|
+
from: dateRange.from,
|
|
817
|
+
to: dateRange.to,
|
|
818
|
+
},
|
|
819
|
+
fastify
|
|
820
|
+
);
|
|
821
|
+
|
|
822
|
+
const stats = {
|
|
823
|
+
searchQueries: searchEvents.length,
|
|
824
|
+
upsertOperations: upsertEvents.length,
|
|
825
|
+
deleteOperations: deleteEvents.length,
|
|
826
|
+
avgSearchDuration: 0,
|
|
827
|
+
// Enhanced: Search latency percentiles
|
|
828
|
+
p50SearchDuration: 0,
|
|
829
|
+
p95SearchDuration: 0,
|
|
830
|
+
p99SearchDuration: 0,
|
|
831
|
+
avgSearchScore: 0,
|
|
832
|
+
avgResultsCount: 0,
|
|
833
|
+
totalVectorsUpserted: 0,
|
|
834
|
+
totalVectorsDeleted: 0,
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
let totalSearchDuration = 0;
|
|
838
|
+
let totalSearchScore = 0;
|
|
839
|
+
let totalResultsCount = 0;
|
|
840
|
+
const searchDurations: number[] = [];
|
|
841
|
+
|
|
842
|
+
for (const event of searchEvents) {
|
|
843
|
+
const props = getEventData(event);
|
|
844
|
+
|
|
845
|
+
const duration = Number(props.durationMs || 0);
|
|
846
|
+
const avgScore = Number(props.avgScore || 0);
|
|
847
|
+
const resultsCount = Number(props.resultsCount || 0);
|
|
848
|
+
|
|
849
|
+
totalSearchDuration += duration;
|
|
850
|
+
totalSearchScore += avgScore;
|
|
851
|
+
totalResultsCount += resultsCount;
|
|
852
|
+
searchDurations.push(duration);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
stats.avgSearchDuration = stats.searchQueries > 0 ? totalSearchDuration / stats.searchQueries : 0;
|
|
856
|
+
stats.avgSearchScore = stats.searchQueries > 0 ? totalSearchScore / stats.searchQueries : 0;
|
|
857
|
+
stats.avgResultsCount = stats.searchQueries > 0 ? totalResultsCount / stats.searchQueries : 0;
|
|
858
|
+
|
|
859
|
+
// Calculate search latency percentiles
|
|
860
|
+
if (searchDurations.length > 0) {
|
|
861
|
+
const sorted = searchDurations.slice().sort((a, b) => a - b);
|
|
862
|
+
const p50Index = Math.floor(sorted.length * 0.5);
|
|
863
|
+
const p95Index = Math.floor(sorted.length * 0.95);
|
|
864
|
+
const p99Index = Math.floor(sorted.length * 0.99);
|
|
865
|
+
|
|
866
|
+
stats.p50SearchDuration = sorted[p50Index] ?? 0;
|
|
867
|
+
stats.p95SearchDuration = sorted[p95Index] ?? 0;
|
|
868
|
+
stats.p99SearchDuration = sorted[p99Index] ?? 0;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
for (const event of upsertEvents) {
|
|
872
|
+
const props = getEventData(event);
|
|
873
|
+
stats.totalVectorsUpserted += Number(props.vectorCount || 0);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
for (const event of deleteEvents) {
|
|
877
|
+
const props = getEventData(event);
|
|
878
|
+
stats.totalVectorsDeleted += Number(props.idsCount || 0);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
return { ok: true, data: stats, meta: { source: 'analytics-adapter' } };
|
|
882
|
+
} catch (error) {
|
|
883
|
+
// Handle validation errors
|
|
884
|
+
if (error instanceof Error && error.message.includes('Invalid')) {
|
|
885
|
+
return reply.code(400).send({
|
|
886
|
+
ok: false,
|
|
887
|
+
error: {
|
|
888
|
+
code: 'INVALID_DATE_RANGE',
|
|
889
|
+
message: error.message,
|
|
890
|
+
},
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
platform.logger.error('Failed to fetch VectorStore usage stats', error instanceof Error ? error : new Error(String(error)));
|
|
895
|
+
return reply.code(500).send({
|
|
896
|
+
ok: false,
|
|
897
|
+
error: {
|
|
898
|
+
code: 'VECTORSTORE_USAGE_STATS_ERROR',
|
|
899
|
+
message: error instanceof Error ? error.message : 'Failed to fetch vectorstore statistics',
|
|
900
|
+
},
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// GET /api/v1/adapters/cache/daily-stats
|
|
907
|
+
// Returns time-bucketed aggregated Cache statistics for time-series visualization
|
|
908
|
+
// Query params: from, to, groupBy (hour|day|week|month), breakdownBy (dot-path), metrics (csv)
|
|
909
|
+
const cacheDailyStatsPaths = resolvePaths(basePath, '/adapters/cache/daily-stats');
|
|
910
|
+
for (const path of cacheDailyStatsPaths) {
|
|
911
|
+
fastify.get(path, async (request, reply) => {
|
|
912
|
+
const analytics = platform.analytics;
|
|
913
|
+
|
|
914
|
+
if (!analytics.getDailyStats) {
|
|
915
|
+
return reply.code(501).send({
|
|
916
|
+
ok: false,
|
|
917
|
+
error: {
|
|
918
|
+
code: 'DAILY_STATS_NOT_IMPLEMENTED',
|
|
919
|
+
message: 'Analytics adapter does not support daily statistics',
|
|
920
|
+
},
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
try {
|
|
925
|
+
const dateRange = extractDateRange(request.query);
|
|
926
|
+
const statsOptions = extractStatsOptions(request.query);
|
|
927
|
+
|
|
928
|
+
const dailyStats = await analytics.getDailyStats({
|
|
929
|
+
type: ['cache.get.hit', 'cache.get.miss', 'cache.set.completed'],
|
|
930
|
+
from: dateRange.from,
|
|
931
|
+
to: dateRange.to,
|
|
932
|
+
limit: 50000,
|
|
933
|
+
...statsOptions,
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
return reply.send({ ok: true, data: dailyStats });
|
|
937
|
+
} catch (error) {
|
|
938
|
+
if (error instanceof Error && error.message.includes('Invalid')) {
|
|
939
|
+
return reply.code(400).send({
|
|
940
|
+
ok: false,
|
|
941
|
+
error: { code: 'INVALID_DATE_RANGE', message: error.message },
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
return reply.code(500).send({
|
|
945
|
+
ok: false,
|
|
946
|
+
error: {
|
|
947
|
+
code: 'DAILY_STATS_ERROR',
|
|
948
|
+
message: error instanceof Error ? error.message : 'Failed to fetch daily statistics',
|
|
949
|
+
},
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// GET /api/v1/adapters/cache/usage
|
|
956
|
+
// Returns aggregated Cache usage statistics
|
|
957
|
+
const cacheUsagePaths = resolvePaths(basePath, '/adapters/cache/usage');
|
|
958
|
+
for (const path of cacheUsagePaths) {
|
|
959
|
+
fastify.get(path, async (request, reply) => {
|
|
960
|
+
const analytics = platform.analytics;
|
|
961
|
+
|
|
962
|
+
if (!analytics.getEvents) {
|
|
963
|
+
return reply.code(501).send({
|
|
964
|
+
ok: false,
|
|
965
|
+
error: {
|
|
966
|
+
code: 'ANALYTICS_NOT_IMPLEMENTED',
|
|
967
|
+
message: 'Analytics adapter does not support reading events',
|
|
968
|
+
},
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
try {
|
|
973
|
+
// Extract and validate date range
|
|
974
|
+
const dateRange = extractDateRange(request.query);
|
|
975
|
+
|
|
976
|
+
const hitEvents = await fetchAllEventsBatched(
|
|
977
|
+
analytics,
|
|
978
|
+
{
|
|
979
|
+
type: 'cache.get.hit',
|
|
980
|
+
from: dateRange.from,
|
|
981
|
+
to: dateRange.to,
|
|
982
|
+
},
|
|
983
|
+
fastify
|
|
984
|
+
);
|
|
985
|
+
const missEvents = await fetchAllEventsBatched(
|
|
986
|
+
analytics,
|
|
987
|
+
{
|
|
988
|
+
type: 'cache.get.miss',
|
|
989
|
+
from: dateRange.from,
|
|
990
|
+
to: dateRange.to,
|
|
991
|
+
},
|
|
992
|
+
fastify
|
|
993
|
+
);
|
|
994
|
+
const setEvents = await fetchAllEventsBatched(
|
|
995
|
+
analytics,
|
|
996
|
+
{
|
|
997
|
+
type: 'cache.set.completed',
|
|
998
|
+
from: dateRange.from,
|
|
999
|
+
to: dateRange.to,
|
|
1000
|
+
},
|
|
1001
|
+
fastify
|
|
1002
|
+
);
|
|
1003
|
+
|
|
1004
|
+
const totalGets = hitEvents.length + missEvents.length;
|
|
1005
|
+
const hitRate = totalGets > 0 ? (hitEvents.length / totalGets) * 100 : 0;
|
|
1006
|
+
|
|
1007
|
+
const stats = {
|
|
1008
|
+
totalGets,
|
|
1009
|
+
hits: hitEvents.length,
|
|
1010
|
+
misses: missEvents.length,
|
|
1011
|
+
hitRate,
|
|
1012
|
+
sets: setEvents.length,
|
|
1013
|
+
avgGetDuration: 0,
|
|
1014
|
+
avgSetDuration: 0,
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
let totalGetDuration = 0;
|
|
1018
|
+
for (const event of [...hitEvents, ...missEvents]) {
|
|
1019
|
+
const props = getEventData(event);
|
|
1020
|
+
totalGetDuration += Number(props.durationMs || 0);
|
|
1021
|
+
}
|
|
1022
|
+
stats.avgGetDuration = totalGets > 0 ? totalGetDuration / totalGets : 0;
|
|
1023
|
+
|
|
1024
|
+
let totalSetDuration = 0;
|
|
1025
|
+
for (const event of setEvents) {
|
|
1026
|
+
const props = getEventData(event);
|
|
1027
|
+
totalSetDuration += Number(props.durationMs || 0);
|
|
1028
|
+
}
|
|
1029
|
+
stats.avgSetDuration = stats.sets > 0 ? totalSetDuration / stats.sets : 0;
|
|
1030
|
+
|
|
1031
|
+
return { ok: true, data: stats, meta: { source: 'analytics-adapter' } };
|
|
1032
|
+
} catch (error) {
|
|
1033
|
+
// Handle validation errors
|
|
1034
|
+
if (error instanceof Error && error.message.includes('Invalid')) {
|
|
1035
|
+
return reply.code(400).send({
|
|
1036
|
+
ok: false,
|
|
1037
|
+
error: {
|
|
1038
|
+
code: 'INVALID_DATE_RANGE',
|
|
1039
|
+
message: error.message,
|
|
1040
|
+
},
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
platform.logger.error('Failed to fetch Cache usage stats', error instanceof Error ? error : new Error(String(error)));
|
|
1045
|
+
return reply.code(500).send({
|
|
1046
|
+
ok: false,
|
|
1047
|
+
error: {
|
|
1048
|
+
code: 'CACHE_USAGE_STATS_ERROR',
|
|
1049
|
+
message: error instanceof Error ? error.message : 'Failed to fetch cache statistics',
|
|
1050
|
+
},
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// GET /api/v1/adapters/storage/daily-stats
|
|
1057
|
+
// Returns time-bucketed aggregated Storage statistics for time-series visualization
|
|
1058
|
+
// Query params: from, to, groupBy (hour|day|week|month), breakdownBy (dot-path), metrics (csv)
|
|
1059
|
+
const storageDailyStatsPaths = resolvePaths(basePath, '/adapters/storage/daily-stats');
|
|
1060
|
+
for (const path of storageDailyStatsPaths) {
|
|
1061
|
+
fastify.get(path, async (request, reply) => {
|
|
1062
|
+
const analytics = platform.analytics;
|
|
1063
|
+
|
|
1064
|
+
if (!analytics.getDailyStats) {
|
|
1065
|
+
return reply.code(501).send({
|
|
1066
|
+
ok: false,
|
|
1067
|
+
error: {
|
|
1068
|
+
code: 'DAILY_STATS_NOT_IMPLEMENTED',
|
|
1069
|
+
message: 'Analytics adapter does not support daily statistics',
|
|
1070
|
+
},
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
try {
|
|
1075
|
+
const dateRange = extractDateRange(request.query);
|
|
1076
|
+
const statsOptions = extractStatsOptions(request.query);
|
|
1077
|
+
|
|
1078
|
+
const dailyStats = await analytics.getDailyStats({
|
|
1079
|
+
type: ['storage.read.completed', 'storage.write.completed', 'storage.delete.completed'],
|
|
1080
|
+
from: dateRange.from,
|
|
1081
|
+
to: dateRange.to,
|
|
1082
|
+
limit: 50000,
|
|
1083
|
+
...statsOptions,
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
return reply.send({ ok: true, data: dailyStats });
|
|
1087
|
+
} catch (error) {
|
|
1088
|
+
if (error instanceof Error && error.message.includes('Invalid')) {
|
|
1089
|
+
return reply.code(400).send({
|
|
1090
|
+
ok: false,
|
|
1091
|
+
error: { code: 'INVALID_DATE_RANGE', message: error.message },
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
return reply.code(500).send({
|
|
1095
|
+
ok: false,
|
|
1096
|
+
error: {
|
|
1097
|
+
code: 'DAILY_STATS_ERROR',
|
|
1098
|
+
message: error instanceof Error ? error.message : 'Failed to fetch daily statistics',
|
|
1099
|
+
},
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// GET /api/v1/adapters/storage/usage
|
|
1106
|
+
// Returns aggregated Storage usage statistics
|
|
1107
|
+
const storageUsagePaths = resolvePaths(basePath, '/adapters/storage/usage');
|
|
1108
|
+
for (const path of storageUsagePaths) {
|
|
1109
|
+
fastify.get(path, async (request, reply) => {
|
|
1110
|
+
const analytics = platform.analytics;
|
|
1111
|
+
|
|
1112
|
+
if (!analytics.getEvents) {
|
|
1113
|
+
return reply.code(501).send({
|
|
1114
|
+
ok: false,
|
|
1115
|
+
error: {
|
|
1116
|
+
code: 'ANALYTICS_NOT_IMPLEMENTED',
|
|
1117
|
+
message: 'Analytics adapter does not support reading events',
|
|
1118
|
+
},
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
try {
|
|
1123
|
+
// Extract and validate date range
|
|
1124
|
+
const dateRange = extractDateRange(request.query);
|
|
1125
|
+
|
|
1126
|
+
const readEvents = await fetchAllEventsBatched(
|
|
1127
|
+
analytics,
|
|
1128
|
+
{
|
|
1129
|
+
type: 'storage.read.completed',
|
|
1130
|
+
from: dateRange.from,
|
|
1131
|
+
to: dateRange.to,
|
|
1132
|
+
},
|
|
1133
|
+
fastify
|
|
1134
|
+
);
|
|
1135
|
+
const writeEvents = await fetchAllEventsBatched(
|
|
1136
|
+
analytics,
|
|
1137
|
+
{
|
|
1138
|
+
type: 'storage.write.completed',
|
|
1139
|
+
from: dateRange.from,
|
|
1140
|
+
to: dateRange.to,
|
|
1141
|
+
},
|
|
1142
|
+
fastify
|
|
1143
|
+
);
|
|
1144
|
+
const deleteEvents = await fetchAllEventsBatched(
|
|
1145
|
+
analytics,
|
|
1146
|
+
{
|
|
1147
|
+
type: 'storage.delete.completed',
|
|
1148
|
+
from: dateRange.from,
|
|
1149
|
+
to: dateRange.to,
|
|
1150
|
+
},
|
|
1151
|
+
fastify
|
|
1152
|
+
);
|
|
1153
|
+
|
|
1154
|
+
const stats = {
|
|
1155
|
+
readOperations: readEvents.length,
|
|
1156
|
+
writeOperations: writeEvents.length,
|
|
1157
|
+
deleteOperations: deleteEvents.length,
|
|
1158
|
+
totalBytesRead: 0,
|
|
1159
|
+
totalBytesWritten: 0,
|
|
1160
|
+
avgReadDuration: 0,
|
|
1161
|
+
avgWriteDuration: 0,
|
|
1162
|
+
};
|
|
1163
|
+
|
|
1164
|
+
let totalReadDuration = 0;
|
|
1165
|
+
for (const event of readEvents) {
|
|
1166
|
+
const props = getEventData(event);
|
|
1167
|
+
stats.totalBytesRead += Number(props.bytesRead || 0);
|
|
1168
|
+
totalReadDuration += Number(props.durationMs || 0);
|
|
1169
|
+
}
|
|
1170
|
+
stats.avgReadDuration = stats.readOperations > 0 ? totalReadDuration / stats.readOperations : 0;
|
|
1171
|
+
|
|
1172
|
+
let totalWriteDuration = 0;
|
|
1173
|
+
for (const event of writeEvents) {
|
|
1174
|
+
const props = getEventData(event);
|
|
1175
|
+
stats.totalBytesWritten += Number(props.bytesWritten || 0);
|
|
1176
|
+
totalWriteDuration += Number(props.durationMs || 0);
|
|
1177
|
+
}
|
|
1178
|
+
stats.avgWriteDuration = stats.writeOperations > 0 ? totalWriteDuration / stats.writeOperations : 0;
|
|
1179
|
+
|
|
1180
|
+
return { ok: true, data: stats, meta: { source: 'analytics-adapter' } };
|
|
1181
|
+
} catch (error) {
|
|
1182
|
+
// Handle validation errors
|
|
1183
|
+
if (error instanceof Error && error.message.includes('Invalid')) {
|
|
1184
|
+
return reply.code(400).send({
|
|
1185
|
+
ok: false,
|
|
1186
|
+
error: {
|
|
1187
|
+
code: 'INVALID_DATE_RANGE',
|
|
1188
|
+
message: error.message,
|
|
1189
|
+
},
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
platform.logger.error('Failed to fetch Storage usage stats', error instanceof Error ? error : new Error(String(error)));
|
|
1194
|
+
return reply.code(500).send({
|
|
1195
|
+
ok: false,
|
|
1196
|
+
error: {
|
|
1197
|
+
code: 'STORAGE_USAGE_STATS_ERROR',
|
|
1198
|
+
message: error instanceof Error ? error.message : 'Failed to fetch storage statistics',
|
|
1199
|
+
},
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
fastify.log.info('Platform adapter routes registered');
|
|
1206
|
+
}
|