@obsunified/collector 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/dist/auth.d.ts +2 -0
  4. package/dist/auth.js +9 -0
  5. package/dist/auth.js.map +1 -0
  6. package/dist/chunk-UGWDOGGH.js +319 -0
  7. package/dist/chunk-UGWDOGGH.js.map +1 -0
  8. package/dist/chunk-XKQTVIRE.js +671 -0
  9. package/dist/chunk-XKQTVIRE.js.map +1 -0
  10. package/dist/index.d.ts +1197 -0
  11. package/dist/index.js +17707 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/ingest-auth-HFVO1zIp.d.ts +165 -0
  14. package/dist/openai-FJYOQ7WO.js +384 -0
  15. package/dist/openai-FJYOQ7WO.js.map +1 -0
  16. package/package.json +73 -0
  17. package/src/ai-parity/ai-parity.acceptance.test.ts +449 -0
  18. package/src/analyses/derive.ts +642 -0
  19. package/src/analyses/index.ts +50 -0
  20. package/src/analyses/investigations.ts +277 -0
  21. package/src/analyses/tier0.ts +568 -0
  22. package/src/auth/dashboard-auth.ts +269 -0
  23. package/src/auth/ingest-auth.ts +147 -0
  24. package/src/durable-objects/tail-hub.test.ts +32 -0
  25. package/src/durable-objects/tail-hub.ts +158 -0
  26. package/src/framework/collector.ts +467 -0
  27. package/src/framework/env.ts +90 -0
  28. package/src/framework/logger.ts +81 -0
  29. package/src/index.ts +211 -0
  30. package/src/lib/action-aggregates.ts +1031 -0
  31. package/src/lib/action-identity.ts +106 -0
  32. package/src/lib/actions-store.ts +397 -0
  33. package/src/lib/ai-pricing.ts +80 -0
  34. package/src/lib/ai-store/calls.ts +148 -0
  35. package/src/lib/ai-store/evaluations.test.ts +47 -0
  36. package/src/lib/ai-store/evaluations.ts +125 -0
  37. package/src/lib/ai-store/helpers.ts +37 -0
  38. package/src/lib/ai-store/retention.ts +17 -0
  39. package/src/lib/ai-store/sessions.ts +277 -0
  40. package/src/lib/ai-store/spans.ts +109 -0
  41. package/src/lib/ai-store/types.ts +112 -0
  42. package/src/lib/ai-store.ts +76 -0
  43. package/src/lib/alerts-store.ts +458 -0
  44. package/src/lib/analyses-runner.ts +439 -0
  45. package/src/lib/analyses-store.test.ts +243 -0
  46. package/src/lib/analyses-store.ts +454 -0
  47. package/src/lib/ask.test.ts +302 -0
  48. package/src/lib/ask.ts +390 -0
  49. package/src/lib/blob-store-s3.ts +213 -0
  50. package/src/lib/blob-store.ts +366 -0
  51. package/src/lib/eval-cases-store.test.ts +171 -0
  52. package/src/lib/eval-cases-store.ts +1033 -0
  53. package/src/lib/evidence-references.test.ts +10 -0
  54. package/src/lib/evidence-references.ts +684 -0
  55. package/src/lib/hash.ts +26 -0
  56. package/src/lib/identity-index/action-lookups.ts +805 -0
  57. package/src/lib/identity-index/constants.ts +1 -0
  58. package/src/lib/identity-index/key-lookups.ts +199 -0
  59. package/src/lib/identity-index/mappers.ts +349 -0
  60. package/src/lib/identity-index/types.ts +196 -0
  61. package/src/lib/identity-index/user-lookup.ts +137 -0
  62. package/src/lib/identity-index.test.ts +624 -0
  63. package/src/lib/identity-index.ts +85 -0
  64. package/src/lib/json.ts +39 -0
  65. package/src/lib/llm.ts +263 -0
  66. package/src/lib/logs-store.ts +188 -0
  67. package/src/lib/metrics-store.test.ts +189 -0
  68. package/src/lib/metrics-store.ts +312 -0
  69. package/src/lib/narrate-gate.test.ts +260 -0
  70. package/src/lib/narrate-gate.ts +253 -0
  71. package/src/lib/openai.test.ts +300 -0
  72. package/src/lib/openai.ts +512 -0
  73. package/src/lib/otlp.ts +212 -0
  74. package/src/lib/parse-pprof.test.ts +64 -0
  75. package/src/lib/parse-pprof.ts +184 -0
  76. package/src/lib/payload-capture.ts +22 -0
  77. package/src/lib/projects-store.ts +207 -0
  78. package/src/lib/propagation-metric.test.ts +196 -0
  79. package/src/lib/propagation-metric.ts +175 -0
  80. package/src/lib/sql-db-dialect.test.ts +22 -0
  81. package/src/lib/sql-db-postgres.ts +229 -0
  82. package/src/lib/sql-db.ts +166 -0
  83. package/src/lib/stage6.test.ts +216 -0
  84. package/src/lib/store/export.ts +65 -0
  85. package/src/lib/store/helpers.ts +367 -0
  86. package/src/lib/store/ingest.ts +68 -0
  87. package/src/lib/store/issues.ts +158 -0
  88. package/src/lib/store/overview.ts +127 -0
  89. package/src/lib/store/retention.ts +10 -0
  90. package/src/lib/store/service-map.ts +384 -0
  91. package/src/lib/store/trace-detail.bench.ts +76 -0
  92. package/src/lib/store/trace-detail.test.ts +410 -0
  93. package/src/lib/store/trace-detail.ts +427 -0
  94. package/src/lib/store/trace-queries.test.ts +29 -0
  95. package/src/lib/store/trace-queries.ts +89 -0
  96. package/src/lib/store-overview.test.ts +77 -0
  97. package/src/lib/store.ts +96 -0
  98. package/src/lib/tail-publisher.ts +64 -0
  99. package/src/lib/test-utils/mem-sql-db.test.ts +93 -0
  100. package/src/lib/test-utils/mem-sql-db.ts +86 -0
  101. package/src/lib/ua-parser.ts +61 -0
  102. package/src/lib/usage-store.ts +567 -0
  103. package/src/migrations/001_telemetry_spans.sql +28 -0
  104. package/src/migrations/002_usage_events.sql +24 -0
  105. package/src/migrations/003_usage_analytics_columns.sql +13 -0
  106. package/src/migrations/004_logs.sql +19 -0
  107. package/src/migrations/005_ai_calls.sql +25 -0
  108. package/src/migrations/006_user_profiles.sql +11 -0
  109. package/src/migrations/007_session_replay_metadata.sql +10 -0
  110. package/src/migrations/008_session_replay_storage_bytes.sql +1 -0
  111. package/src/migrations/009_projects_and_keys.sql +22 -0
  112. package/src/migrations/010_signal_project_id.sql +15 -0
  113. package/src/migrations/011_default_project_seed.sql +3 -0
  114. package/src/migrations/012_alert_rules.sql +17 -0
  115. package/src/migrations/013_alert_evaluations.sql +13 -0
  116. package/src/migrations/014_alert_state.sql +9 -0
  117. package/src/migrations/015_metrics.sql +58 -0
  118. package/src/migrations/016_metrics_exp_histogram_summary.sql +5 -0
  119. package/src/migrations/017_span_dropped_counts.sql +11 -0
  120. package/src/migrations/018_log_flags.sql +8 -0
  121. package/src/migrations/019_ai_span_payloads.sql +30 -0
  122. package/src/migrations/020_ai_span_evaluations.sql +33 -0
  123. package/src/migrations/021_ai_span_session.sql +13 -0
  124. package/src/migrations/022_span_log_session_id.sql +13 -0
  125. package/src/migrations/023_service_map_indexes.sql +32 -0
  126. package/src/migrations/024_analysis_results.sql +49 -0
  127. package/src/migrations/025_stage6_autopinning_alerts.sql +38 -0
  128. package/src/migrations/026_obs_dashboard_project_seed.sql +18 -0
  129. package/src/migrations/027_identity_propagation.sql +50 -0
  130. package/src/migrations/028_profile_blobs.sql +57 -0
  131. package/src/migrations/029_telemetry_sdk_name.sql +20 -0
  132. package/src/migrations/030_analysis_claim.sql +18 -0
  133. package/src/migrations/031_agent_action_graph.sql +154 -0
  134. package/src/migrations/032_payload_capture_flags.sql +8 -0
  135. package/src/migrations/033_eval_cases.sql +52 -0
  136. package/src/migrations/034_eval_case_results.sql +23 -0
  137. package/src/migrations/035_metric_exemplars.sql +33 -0
  138. package/src/migrations/036_trace_instrumentation_gaps.sql +19 -0
  139. package/src/migrations/037_eval_runs.sql +37 -0
  140. package/src/migrations/038_tool_call_evidence.sql +11 -0
  141. package/src/migrations/039_drop_trace_instrumentation_gaps.sql +11 -0
  142. package/src/migrations/040_evidence_retrieval_refs.sql +50 -0
  143. package/src/migrations-postgres/001_telemetry_spans.sql +33 -0
  144. package/src/migrations-postgres/002_usage_events.sql +24 -0
  145. package/src/migrations-postgres/003_usage_analytics_columns.sql +13 -0
  146. package/src/migrations-postgres/004_logs.sql +19 -0
  147. package/src/migrations-postgres/005_ai_calls.sql +25 -0
  148. package/src/migrations-postgres/006_user_profiles.sql +11 -0
  149. package/src/migrations-postgres/007_session_replay_metadata.sql +10 -0
  150. package/src/migrations-postgres/008_session_replay_storage_bytes.sql +2 -0
  151. package/src/migrations-postgres/009_projects_and_keys.sql +25 -0
  152. package/src/migrations-postgres/010_signal_project_id.sql +18 -0
  153. package/src/migrations-postgres/011_default_project_seed.sql +5 -0
  154. package/src/migrations-postgres/012_alert_rules.sql +17 -0
  155. package/src/migrations-postgres/013_alert_evaluations.sql +13 -0
  156. package/src/migrations-postgres/014_alert_state.sql +9 -0
  157. package/src/migrations-postgres/015_metrics.sql +47 -0
  158. package/src/migrations-postgres/016_metrics_exp_histogram_summary.sql +1 -0
  159. package/src/migrations-postgres/017_span_dropped_counts.sql +4 -0
  160. package/src/migrations-postgres/018_log_flags.sql +2 -0
  161. package/src/migrations-postgres/019_ai_span_payloads.sql +18 -0
  162. package/src/migrations-postgres/020_ai_span_evaluations.sql +21 -0
  163. package/src/migrations-postgres/021_ai_span_session.sql +7 -0
  164. package/src/migrations-postgres/022_span_log_session_id.sql +7 -0
  165. package/src/migrations-postgres/023_service_map_indexes.sql +9 -0
  166. package/src/migrations-postgres/024_analysis_results.sql +41 -0
  167. package/src/migrations-postgres/025_stage6_autopinning_alerts.sql +17 -0
  168. package/src/migrations-postgres/026_obs_dashboard_project_seed.sql +14 -0
  169. package/src/migrations-postgres/027_identity_propagation.sql +27 -0
  170. package/src/migrations-postgres/028_profile_blobs.sql +48 -0
  171. package/src/migrations-postgres/029_telemetry_sdk_name.sql +5 -0
  172. package/src/migrations-postgres/030_analysis_claim.sql +5 -0
  173. package/src/migrations-postgres/031_agent_action_graph.sql +190 -0
  174. package/src/migrations-postgres/032_payload_capture_flags.sql +8 -0
  175. package/src/migrations-postgres/033_eval_cases.sql +55 -0
  176. package/src/migrations-postgres/034_eval_case_results.sql +26 -0
  177. package/src/migrations-postgres/035_metric_exemplars.sql +31 -0
  178. package/src/migrations-postgres/036_trace_instrumentation_gaps.sql +19 -0
  179. package/src/migrations-postgres/037_eval_runs.sql +40 -0
  180. package/src/migrations-postgres/038_tool_call_evidence.sql +11 -0
  181. package/src/migrations-postgres/039_drop_trace_instrumentation_gaps.sql +11 -0
  182. package/src/migrations-postgres/040_evidence_retrieval_refs.sql +47 -0
  183. package/src/migrations-postgres/README.md +40 -0
  184. package/src/otlp/acceptance.test.ts +373 -0
  185. package/src/otlp/decode/body.ts +127 -0
  186. package/src/otlp/decode/logs.ts +96 -0
  187. package/src/otlp/decode/metrics.ts +402 -0
  188. package/src/otlp/decode/traces.ts +92 -0
  189. package/src/otlp/decode/values.ts +150 -0
  190. package/src/otlp/decode.test.ts +800 -0
  191. package/src/otlp/decode.ts +11 -0
  192. package/src/otlp/gen/opentelemetry/proto/collector/logs/v1/logs_service_pb.ts +132 -0
  193. package/src/otlp/gen/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.ts +158 -0
  194. package/src/otlp/gen/opentelemetry/proto/collector/trace/v1/trace_service_pb.ts +158 -0
  195. package/src/otlp/gen/opentelemetry/proto/common/v1/common_pb.ts +289 -0
  196. package/src/otlp/gen/opentelemetry/proto/logs/v1/logs_pb.ts +397 -0
  197. package/src/otlp/gen/opentelemetry/proto/metrics/v1/metrics_pb.ts +1329 -0
  198. package/src/otlp/gen/opentelemetry/proto/resource/v1/resource_pb.ts +86 -0
  199. package/src/otlp/gen/opentelemetry/proto/trace/v1/trace_pb.ts +710 -0
  200. package/src/otlp/response.test.ts +158 -0
  201. package/src/otlp/response.ts +129 -0
  202. package/src/plugins/_context.ts +15 -0
  203. package/src/plugins/action-graph-processor/enrichers.ts +45 -0
  204. package/src/plugins/action-graph-processor/redaction.ts +84 -0
  205. package/src/plugins/action-graph-processor.test.ts +1023 -0
  206. package/src/plugins/action-graph-processor.ts +1172 -0
  207. package/src/plugins/action-routes.test.ts +716 -0
  208. package/src/plugins/action-routes.ts +343 -0
  209. package/src/plugins/ai-receiver.ts +254 -0
  210. package/src/plugins/ai-span-payloads-processor.test.ts +90 -0
  211. package/src/plugins/ai-span-payloads-processor.ts +180 -0
  212. package/src/plugins/alerts-evaluator.test.ts +91 -0
  213. package/src/plugins/alerts-evaluator.ts +328 -0
  214. package/src/plugins/alerts-routes.ts +101 -0
  215. package/src/plugins/analyses-routes.ts +138 -0
  216. package/src/plugins/ask-routes.ts +182 -0
  217. package/src/plugins/bot-filter.ts +19 -0
  218. package/src/plugins/connected-routes/manifest.ts +531 -0
  219. package/src/plugins/connected-routes.test.ts +1791 -0
  220. package/src/plugins/connected-routes.ts +1103 -0
  221. package/src/plugins/dashboard-routes.ts +41 -0
  222. package/src/plugins/default-span-enrichment.ts +92 -0
  223. package/src/plugins/eval-cases-routes.test.ts +391 -0
  224. package/src/plugins/eval-cases-routes.ts +529 -0
  225. package/src/plugins/evidence-retrieval-routes.benchmark.test.ts +245 -0
  226. package/src/plugins/evidence-retrieval-routes.test.ts +932 -0
  227. package/src/plugins/evidence-retrieval-routes.ts +2179 -0
  228. package/src/plugins/gen-ai-normalizer.test.ts +680 -0
  229. package/src/plugins/gen-ai-normalizer.ts +302 -0
  230. package/src/plugins/identity-receiver.ts +96 -0
  231. package/src/plugins/issue-enrichment.ts +132 -0
  232. package/src/plugins/issue-insights.ts +81 -0
  233. package/src/plugins/logs-receiver.ts +160 -0
  234. package/src/plugins/metrics-receiver.ts +91 -0
  235. package/src/plugins/onboarding-routes.ts +54 -0
  236. package/src/plugins/otlp-receiver.ts +124 -0
  237. package/src/plugins/platform-routes.ts +165 -0
  238. package/src/plugins/profile-routes.ts +468 -0
  239. package/src/plugins/projects-routes.ts +92 -0
  240. package/src/plugins/query-routes.ts +220 -0
  241. package/src/plugins/redaction-processor.ts +132 -0
  242. package/src/plugins/replay-query-routes.test.ts +38 -0
  243. package/src/plugins/replay-query-routes.ts +199 -0
  244. package/src/plugins/replay-receiver.ts +81 -0
  245. package/src/plugins/tail-routes.test.ts +47 -0
  246. package/src/plugins/tail-routes.ts +42 -0
  247. package/src/plugins/timeline-routes.ts +244 -0
  248. package/src/plugins/ua-enrichment.ts +27 -0
  249. package/src/plugins/usage-normalization.ts +46 -0
  250. package/src/plugins/usage-privacy.ts +55 -0
  251. package/src/plugins/usage-query-routes.ts +102 -0
  252. package/src/plugins/usage-receiver.ts +129 -0
  253. package/src/plugins/users-query-routes.ts +78 -0
  254. package/src/plugins/utm-enrichment.ts +36 -0
@@ -0,0 +1,805 @@
1
+ import { ActionConfidence } from "@obsunified/types/constants";
2
+ import type { SqlDb } from "../sql-db";
3
+ import { FETCH_LIMIT } from "./constants";
4
+ import { manifestByInteraction } from "./key-lookups";
5
+ import {
6
+ mapAction,
7
+ mapAgentRun,
8
+ mapAi,
9
+ mapArtifact,
10
+ mapEvalResult,
11
+ mapLog,
12
+ mapMetricExemplar,
13
+ mapRetrievalEvent,
14
+ mapSpan,
15
+ mapToolCall,
16
+ } from "./mappers";
17
+ import type { ActionRef, EntityManifestExtended, ReplayRow } from "./types";
18
+
19
+ const emptyExtendedManifest = (): EntityManifestExtended => ({
20
+ spans: [],
21
+ logs: [],
22
+ usageEvents: [],
23
+ aiCalls: [],
24
+ metricExemplars: [],
25
+ replay: null,
26
+ actions: [],
27
+ agentRuns: [],
28
+ toolCalls: [],
29
+ retrievalEvents: [],
30
+ evalResults: [],
31
+ artifacts: [],
32
+ });
33
+
34
+ const earliest = (values: Array<string | null | undefined>): string =>
35
+ values.filter((v): v is string => Boolean(v)).sort()[0] ??
36
+ new Date(0).toISOString();
37
+
38
+ const latest = (values: Array<string | null | undefined>): string | null => {
39
+ const sorted = values.filter((v): v is string => Boolean(v)).sort();
40
+ return sorted[sorted.length - 1] ?? null;
41
+ };
42
+
43
+ const durationMs = (
44
+ startedAt: string,
45
+ endedAt: string | null,
46
+ ): number | null => {
47
+ if (!endedAt) return null;
48
+ const duration = Date.parse(endedAt) - Date.parse(startedAt);
49
+ return Number.isFinite(duration) && duration >= 0 ? duration : null;
50
+ };
51
+
52
+ const projectLegacyInteraction = async (
53
+ db: SqlDb,
54
+ projectId: string,
55
+ interactionId: string,
56
+ ): Promise<EntityManifestExtended> => {
57
+ const manifest = await manifestByInteraction(db, projectId, interactionId);
58
+ const hasSignals =
59
+ manifest.spans.length > 0 ||
60
+ manifest.logs.length > 0 ||
61
+ manifest.usageEvents.length > 0 ||
62
+ manifest.aiCalls.length > 0;
63
+
64
+ if (!hasSignals) return emptyExtendedManifest();
65
+
66
+ const startedAt = earliest([
67
+ ...manifest.spans.map((s) => s.startTime),
68
+ ...manifest.logs.map((l) => l.occurredAt),
69
+ ...manifest.usageEvents.map((u) => u.occurredAt),
70
+ ...manifest.aiCalls.map((a) => a.occurredAt),
71
+ ]);
72
+ const endedAt = latest([
73
+ ...manifest.logs.map((l) => l.occurredAt),
74
+ ...manifest.usageEvents.map((u) => u.occurredAt),
75
+ ...manifest.aiCalls.map((a) => a.occurredAt),
76
+ ]);
77
+ const firstSpan = manifest.spans[0];
78
+ const firstUsage = manifest.usageEvents[0];
79
+ const action: ActionRef = {
80
+ id: interactionId,
81
+ projectId,
82
+ rootActionId: interactionId,
83
+ causedByActionId: null,
84
+ actorType: "human",
85
+ actorId: null,
86
+ actionKind: "browser.interaction",
87
+ name: firstUsage?.eventName ?? firstSpan?.spanName ?? "Legacy interaction",
88
+ status:
89
+ manifest.logs.some((l) => l.severity.toLowerCase() === "error") ||
90
+ manifest.spans.some((s) => s.statusCode >= 2)
91
+ ? "error"
92
+ : "ok",
93
+ startedAt,
94
+ endedAt,
95
+ durationMs: durationMs(startedAt, endedAt),
96
+ traceId: firstSpan?.traceId ?? manifest.aiCalls[0]?.traceId ?? null,
97
+ spanId: firstSpan?.spanId ?? null,
98
+ sessionId: firstUsage?.sessionId ?? null,
99
+ interactionId,
100
+ userId: null,
101
+ agentRunId: null,
102
+ stepId: null,
103
+ toolCallId: null,
104
+ promptVersion: null,
105
+ modelName: manifest.aiCalls[0]?.modelName ?? null,
106
+ provider: manifest.aiCalls[0]?.provider ?? null,
107
+ totalCostUsd:
108
+ manifest.aiCalls.length > 0
109
+ ? manifest.aiCalls.reduce(
110
+ (sum, call) => sum + (call.totalCostUsd ?? 0),
111
+ 0,
112
+ )
113
+ : null,
114
+ causalConfidence: ActionConfidence.Fallback,
115
+ attrsJson: JSON.stringify({ projectedFrom: "interaction_id" }),
116
+ };
117
+
118
+ return {
119
+ ...manifest,
120
+ actions: [action],
121
+ agentRuns: [],
122
+ toolCalls: [],
123
+ retrievalEvents: [],
124
+ evalResults: [],
125
+ artifacts: [],
126
+ };
127
+ };
128
+
129
+ export async function manifestByAction(
130
+ db: SqlDb,
131
+ projectId: string,
132
+ actionId: string,
133
+ ): Promise<EntityManifestExtended> {
134
+ const action = await db
135
+ .prepare(`SELECT * FROM actions WHERE project_id = ? AND id = ? LIMIT 1`)
136
+ .bind(projectId, actionId)
137
+ .first<{
138
+ id: string;
139
+ project_id: string;
140
+ root_action_id: string;
141
+ caused_by_action_id: string | null;
142
+ actor_type: string;
143
+ actor_id: string | null;
144
+ action_kind: string;
145
+ name: string | null;
146
+ status: string;
147
+ started_at: string;
148
+ ended_at: string | null;
149
+ duration_ms: number | null;
150
+ trace_id: string | null;
151
+ span_id: string | null;
152
+ session_id: string | null;
153
+ interaction_id: string | null;
154
+ user_id: string | null;
155
+ agent_run_id: string | null;
156
+ step_id: string | null;
157
+ tool_call_id: string | null;
158
+ prompt_version: string | null;
159
+ model_name: string | null;
160
+ provider: string | null;
161
+ total_cost_usd: number | null;
162
+ attrs_json: string | null;
163
+ }>();
164
+
165
+ if (!action) {
166
+ return projectLegacyInteraction(db, projectId, actionId);
167
+ }
168
+
169
+ // Retrieve all actions that share the same root_action_id
170
+ const actionsRes = await db
171
+ .prepare(
172
+ `SELECT * FROM actions
173
+ WHERE project_id = ? AND root_action_id = ?
174
+ ORDER BY started_at ASC LIMIT ?`,
175
+ )
176
+ .bind(projectId, action.root_action_id, FETCH_LIMIT)
177
+ .all<Parameters<typeof mapAction>[0]>();
178
+
179
+ const matchedActions = actionsRes.results;
180
+ const actionIds = matchedActions.map((a) => a.id);
181
+
182
+ // Execute child queries in parallel
183
+ const placeholders = actionIds.map(() => "?").join(", ");
184
+ const [agentRuns, toolCalls, retrievalEvents, evalResults, artifacts] =
185
+ await Promise.all([
186
+ db
187
+ .prepare(
188
+ `SELECT * FROM agent_runs
189
+ WHERE project_id = ? AND id = ?
190
+ LIMIT 1`,
191
+ )
192
+ .bind(projectId, action.root_action_id)
193
+ .all<Parameters<typeof mapAgentRun>[0]>(),
194
+ actionIds.length > 0
195
+ ? db
196
+ .prepare(
197
+ `SELECT * FROM tool_calls
198
+ WHERE project_id = ? AND action_id IN (${placeholders})
199
+ LIMIT ?`,
200
+ )
201
+ .bind(projectId, ...actionIds, FETCH_LIMIT)
202
+ .all<Parameters<typeof mapToolCall>[0]>()
203
+ : { results: [] },
204
+ actionIds.length > 0
205
+ ? db
206
+ .prepare(
207
+ `SELECT * FROM retrieval_events
208
+ WHERE project_id = ? AND action_id IN (${placeholders})
209
+ LIMIT ?`,
210
+ )
211
+ .bind(projectId, ...actionIds, FETCH_LIMIT)
212
+ .all<Parameters<typeof mapRetrievalEvent>[0]>()
213
+ : { results: [] },
214
+ actionIds.length > 0
215
+ ? db
216
+ .prepare(
217
+ `SELECT * FROM eval_results
218
+ WHERE project_id = ? AND action_id IN (${placeholders})
219
+ LIMIT ?`,
220
+ )
221
+ .bind(projectId, ...actionIds, FETCH_LIMIT)
222
+ .all<Parameters<typeof mapEvalResult>[0]>()
223
+ : { results: [] },
224
+ actionIds.length > 0
225
+ ? db
226
+ .prepare(
227
+ `SELECT * FROM artifacts
228
+ WHERE project_id = ? AND action_id IN (${placeholders})
229
+ LIMIT ?`,
230
+ )
231
+ .bind(projectId, ...actionIds, FETCH_LIMIT)
232
+ .all<Parameters<typeof mapArtifact>[0]>()
233
+ : { results: [] },
234
+ ]);
235
+
236
+ // Extract unique trace IDs and session IDs
237
+ const traceIds = Array.from(
238
+ new Set(
239
+ matchedActions
240
+ .map((a) => a.trace_id)
241
+ .filter((t): t is string => Boolean(t)),
242
+ ),
243
+ );
244
+ const sessionIds = Array.from(
245
+ new Set(
246
+ matchedActions
247
+ .map((a) => a.session_id)
248
+ .filter((s): s is string => Boolean(s)),
249
+ ),
250
+ );
251
+
252
+ let spansRes = { results: [] as Parameters<typeof mapSpan>[0][] };
253
+ let logsRes = { results: [] as Parameters<typeof mapLog>[0][] };
254
+ let aiCallsRes = { results: [] as Parameters<typeof mapAi>[0][] };
255
+ let metricExemplarsRes = {
256
+ results: [] as Parameters<typeof mapMetricExemplar>[0][],
257
+ };
258
+ let replayRes: ReplayRow | null = null;
259
+
260
+ if (traceIds.length > 0) {
261
+ const tracePlaceholders = traceIds.map(() => "?").join(", ");
262
+ const [s, l, a, e] = await Promise.all([
263
+ db
264
+ .prepare(
265
+ `SELECT trace_id, span_id, parent_span_id, service_name, span_name,
266
+ status_code, status_message, start_time, duration_ms, interaction_id
267
+ FROM telemetry_spans
268
+ WHERE project_id = ? AND trace_id IN (${tracePlaceholders})
269
+ ORDER BY start_time ASC LIMIT ?`,
270
+ )
271
+ .bind(projectId, ...traceIds, FETCH_LIMIT)
272
+ .all<Parameters<typeof mapSpan>[0]>(),
273
+ db
274
+ .prepare(
275
+ `SELECT log_id, trace_id, span_id, service_name, logger_name,
276
+ severity, message, occurred_at, interaction_id, session_id
277
+ FROM logs
278
+ WHERE project_id = ? AND trace_id IN (${tracePlaceholders})
279
+ ORDER BY occurred_at ASC LIMIT ?`,
280
+ )
281
+ .bind(projectId, ...traceIds, FETCH_LIMIT)
282
+ .all<Parameters<typeof mapLog>[0]>(),
283
+ db
284
+ .prepare(
285
+ `SELECT call_id, trace_id, model_name, provider, total_cost_usd,
286
+ occurred_at, interaction_id, session_id
287
+ FROM ai_calls
288
+ WHERE project_id = ? AND trace_id IN (${tracePlaceholders})
289
+ ORDER BY occurred_at ASC LIMIT ?`,
290
+ )
291
+ .bind(projectId, ...traceIds, FETCH_LIMIT)
292
+ .all<Parameters<typeof mapAi>[0]>(),
293
+ db
294
+ .prepare(
295
+ `SELECT id, point_id, series_id, metric_name, service_name,
296
+ trace_id, span_id, ts_ns, value, received_at
297
+ FROM metric_exemplars
298
+ WHERE project_id = ? AND trace_id IN (${tracePlaceholders})
299
+ ORDER BY ts_ns DESC LIMIT ?`,
300
+ )
301
+ .bind(projectId, ...traceIds, FETCH_LIMIT)
302
+ .all<Parameters<typeof mapMetricExemplar>[0]>(),
303
+ ]);
304
+ spansRes = s;
305
+ logsRes = l;
306
+ aiCallsRes = a;
307
+ metricExemplarsRes = e;
308
+ }
309
+
310
+ if (sessionIds.length > 0) {
311
+ const sessionPlaceholders = sessionIds.map(() => "?").join(", ");
312
+ replayRes = await db
313
+ .prepare(
314
+ `SELECT session_id, first_chunk_at, last_chunk_at, chunk_count, events_count
315
+ FROM session_replay_metadata
316
+ WHERE session_id IN (${sessionPlaceholders})
317
+ ORDER BY last_chunk_at DESC LIMIT 1`,
318
+ )
319
+ .bind(...sessionIds)
320
+ .first<{
321
+ session_id: string;
322
+ first_chunk_at: string;
323
+ last_chunk_at: string;
324
+ chunk_count: number;
325
+ events_count: number;
326
+ }>();
327
+ }
328
+
329
+ const sumAiCost = aiCallsRes.results.reduce(
330
+ (sum, a) => sum + (a.total_cost_usd ?? 0),
331
+ 0,
332
+ );
333
+ const startTimes = matchedActions.map((a) => a.started_at).filter(Boolean);
334
+ const endTimes = matchedActions.map((a) => a.ended_at).filter(Boolean);
335
+ const earliestStart = startTimes.sort()[0] ?? null;
336
+ const latestEnd = endTimes.sort()[endTimes.length - 1] ?? null;
337
+ const totalDurationMs =
338
+ earliestStart && latestEnd
339
+ ? Date.parse(latestEnd) - Date.parse(earliestStart)
340
+ : null;
341
+
342
+ const mappedRuns = agentRuns.results.map((r) => {
343
+ const mapped = mapAgentRun(r);
344
+ return {
345
+ ...mapped,
346
+ totalCostUsd: sumAiCost || mapped.totalCostUsd || 0.0,
347
+ totalDurationMs: totalDurationMs || mapped.totalDurationMs,
348
+ };
349
+ });
350
+
351
+ return {
352
+ spans: spansRes.results.map(mapSpan),
353
+ logs: logsRes.results.map(mapLog),
354
+ usageEvents: [],
355
+ aiCalls: aiCallsRes.results.map(mapAi),
356
+ metricExemplars: metricExemplarsRes.results.map(mapMetricExemplar),
357
+ replay: replayRes
358
+ ? {
359
+ sessionId: replayRes.session_id,
360
+ firstChunkAt: replayRes.first_chunk_at,
361
+ lastChunkAt: replayRes.last_chunk_at,
362
+ chunkCount: replayRes.chunk_count,
363
+ eventsCount: replayRes.events_count,
364
+ }
365
+ : null,
366
+ actions: matchedActions.map(mapAction),
367
+ agentRuns: mappedRuns,
368
+ toolCalls: toolCalls.results.map(mapToolCall),
369
+ retrievalEvents: retrievalEvents.results.map(mapRetrievalEvent),
370
+ evalResults: evalResults.results.map(mapEvalResult),
371
+ artifacts: artifacts.results.map(mapArtifact),
372
+ };
373
+ }
374
+
375
+ export async function manifestByAgentRun(
376
+ db: SqlDb,
377
+ projectId: string,
378
+ agentRunId: string,
379
+ ): Promise<EntityManifestExtended> {
380
+ const run = await db
381
+ .prepare(`SELECT * FROM agent_runs WHERE project_id = ? AND id = ? LIMIT 1`)
382
+ .bind(projectId, agentRunId)
383
+ .first<{
384
+ id: string;
385
+ project_id: string;
386
+ agent_id: string;
387
+ agent_name: string;
388
+ agent_version: string;
389
+ goal: string | null;
390
+ outcome: string | null;
391
+ autonomy_level: string;
392
+ status: string;
393
+ error_message: string | null;
394
+ total_cost_usd: number | null;
395
+ total_duration_ms: number | null;
396
+ metadata_json: string | null;
397
+ }>();
398
+
399
+ if (!run) {
400
+ return emptyExtendedManifest();
401
+ }
402
+
403
+ const actionsRes = await db
404
+ .prepare(
405
+ `SELECT * FROM actions
406
+ WHERE project_id = ? AND (root_action_id = ? OR agent_run_id = ?)
407
+ ORDER BY started_at ASC LIMIT ?`,
408
+ )
409
+ .bind(projectId, agentRunId, agentRunId, FETCH_LIMIT)
410
+ .all<Parameters<typeof mapAction>[0]>();
411
+
412
+ const matchedActions = actionsRes.results;
413
+ const actionIds = matchedActions.map((a) => a.id);
414
+
415
+ // Execute child queries in parallel
416
+ const placeholders = actionIds.map(() => "?").join(", ");
417
+ const [agentRuns, toolCalls, retrievalEvents, evalResults, artifacts] =
418
+ await Promise.all([
419
+ db
420
+ .prepare(
421
+ `SELECT * FROM agent_runs
422
+ WHERE project_id = ? AND id = ?
423
+ LIMIT 1`,
424
+ )
425
+ .bind(projectId, agentRunId)
426
+ .all<Parameters<typeof mapAgentRun>[0]>(),
427
+ actionIds.length > 0
428
+ ? db
429
+ .prepare(
430
+ `SELECT * FROM tool_calls
431
+ WHERE project_id = ? AND action_id IN (${placeholders})
432
+ LIMIT ?`,
433
+ )
434
+ .bind(projectId, ...actionIds, FETCH_LIMIT)
435
+ .all<Parameters<typeof mapToolCall>[0]>()
436
+ : { results: [] },
437
+ actionIds.length > 0
438
+ ? db
439
+ .prepare(
440
+ `SELECT * FROM retrieval_events
441
+ WHERE project_id = ? AND action_id IN (${placeholders})
442
+ LIMIT ?`,
443
+ )
444
+ .bind(projectId, ...actionIds, FETCH_LIMIT)
445
+ .all<Parameters<typeof mapRetrievalEvent>[0]>()
446
+ : { results: [] },
447
+ actionIds.length > 0
448
+ ? db
449
+ .prepare(
450
+ `SELECT * FROM eval_results
451
+ WHERE project_id = ? AND action_id IN (${placeholders})
452
+ LIMIT ?`,
453
+ )
454
+ .bind(projectId, ...actionIds, FETCH_LIMIT)
455
+ .all<Parameters<typeof mapEvalResult>[0]>()
456
+ : { results: [] },
457
+ actionIds.length > 0
458
+ ? db
459
+ .prepare(
460
+ `SELECT * FROM artifacts
461
+ WHERE project_id = ? AND action_id IN (${placeholders})
462
+ LIMIT ?`,
463
+ )
464
+ .bind(projectId, ...actionIds, FETCH_LIMIT)
465
+ .all<Parameters<typeof mapArtifact>[0]>()
466
+ : { results: [] },
467
+ ]);
468
+
469
+ // Extract unique trace IDs and session IDs
470
+ const traceIds = Array.from(
471
+ new Set(
472
+ matchedActions
473
+ .map((a) => a.trace_id)
474
+ .filter((t): t is string => Boolean(t)),
475
+ ),
476
+ );
477
+ const sessionIds = Array.from(
478
+ new Set(
479
+ matchedActions
480
+ .map((a) => a.session_id)
481
+ .filter((s): s is string => Boolean(s)),
482
+ ),
483
+ );
484
+
485
+ let spansRes = { results: [] as Parameters<typeof mapSpan>[0][] };
486
+ let logsRes = { results: [] as Parameters<typeof mapLog>[0][] };
487
+ let aiCallsRes = { results: [] as Parameters<typeof mapAi>[0][] };
488
+ let metricExemplarsRes = {
489
+ results: [] as Parameters<typeof mapMetricExemplar>[0][],
490
+ };
491
+ let replayRes: ReplayRow | null = null;
492
+
493
+ if (traceIds.length > 0) {
494
+ const tracePlaceholders = traceIds.map(() => "?").join(", ");
495
+ const [s, l, a, e] = await Promise.all([
496
+ db
497
+ .prepare(
498
+ `SELECT trace_id, span_id, parent_span_id, service_name, span_name,
499
+ status_code, status_message, start_time, duration_ms, interaction_id
500
+ FROM telemetry_spans
501
+ WHERE project_id = ? AND trace_id IN (${tracePlaceholders})
502
+ ORDER BY start_time ASC LIMIT ?`,
503
+ )
504
+ .bind(projectId, ...traceIds, FETCH_LIMIT)
505
+ .all<Parameters<typeof mapSpan>[0]>(),
506
+ db
507
+ .prepare(
508
+ `SELECT log_id, trace_id, span_id, service_name, logger_name,
509
+ severity, message, occurred_at, interaction_id, session_id
510
+ FROM logs
511
+ WHERE project_id = ? AND trace_id IN (${tracePlaceholders})
512
+ ORDER BY occurred_at ASC LIMIT ?`,
513
+ )
514
+ .bind(projectId, ...traceIds, FETCH_LIMIT)
515
+ .all<Parameters<typeof mapLog>[0]>(),
516
+ db
517
+ .prepare(
518
+ `SELECT call_id, trace_id, model_name, provider, total_cost_usd,
519
+ occurred_at, interaction_id, session_id
520
+ FROM ai_calls
521
+ WHERE project_id = ? AND trace_id IN (${tracePlaceholders})
522
+ ORDER BY occurred_at ASC LIMIT ?`,
523
+ )
524
+ .bind(projectId, ...traceIds, FETCH_LIMIT)
525
+ .all<Parameters<typeof mapAi>[0]>(),
526
+ db
527
+ .prepare(
528
+ `SELECT id, point_id, series_id, metric_name, service_name,
529
+ trace_id, span_id, ts_ns, value, received_at
530
+ FROM metric_exemplars
531
+ WHERE project_id = ? AND trace_id IN (${tracePlaceholders})
532
+ ORDER BY ts_ns DESC LIMIT ?`,
533
+ )
534
+ .bind(projectId, ...traceIds, FETCH_LIMIT)
535
+ .all<Parameters<typeof mapMetricExemplar>[0]>(),
536
+ ]);
537
+ spansRes = s;
538
+ logsRes = l;
539
+ aiCallsRes = a;
540
+ metricExemplarsRes = e;
541
+ }
542
+
543
+ if (sessionIds.length > 0) {
544
+ const sessionPlaceholders = sessionIds.map(() => "?").join(", ");
545
+ replayRes = await db
546
+ .prepare(
547
+ `SELECT session_id, first_chunk_at, last_chunk_at, chunk_count, events_count
548
+ FROM session_replay_metadata
549
+ WHERE session_id IN (${sessionPlaceholders})
550
+ ORDER BY last_chunk_at DESC LIMIT 1`,
551
+ )
552
+ .bind(...sessionIds)
553
+ .first<{
554
+ session_id: string;
555
+ first_chunk_at: string;
556
+ last_chunk_at: string;
557
+ chunk_count: number;
558
+ events_count: number;
559
+ }>();
560
+ }
561
+
562
+ const sumAiCost = aiCallsRes.results.reduce(
563
+ (sum, a) => sum + (a.total_cost_usd ?? 0),
564
+ 0,
565
+ );
566
+ const startTimes = matchedActions.map((a) => a.started_at).filter(Boolean);
567
+ const endTimes = matchedActions.map((a) => a.ended_at).filter(Boolean);
568
+ const earliestStart = startTimes.sort()[0] ?? null;
569
+ const latestEnd = endTimes.sort()[endTimes.length - 1] ?? null;
570
+ const totalDurationMs =
571
+ earliestStart && latestEnd
572
+ ? Date.parse(latestEnd) - Date.parse(earliestStart)
573
+ : null;
574
+
575
+ const mappedRuns = agentRuns.results.map((r) => {
576
+ const mapped = mapAgentRun(r);
577
+ return {
578
+ ...mapped,
579
+ totalCostUsd: sumAiCost || mapped.totalCostUsd || 0.0,
580
+ totalDurationMs: totalDurationMs || mapped.totalDurationMs,
581
+ };
582
+ });
583
+
584
+ return {
585
+ spans: spansRes.results.map(mapSpan),
586
+ logs: logsRes.results.map(mapLog),
587
+ usageEvents: [],
588
+ aiCalls: aiCallsRes.results.map(mapAi),
589
+ metricExemplars: metricExemplarsRes.results.map(mapMetricExemplar),
590
+ replay: replayRes
591
+ ? {
592
+ sessionId: replayRes.session_id,
593
+ firstChunkAt: replayRes.first_chunk_at,
594
+ lastChunkAt: replayRes.last_chunk_at,
595
+ chunkCount: replayRes.chunk_count,
596
+ eventsCount: replayRes.events_count,
597
+ }
598
+ : null,
599
+ actions: matchedActions.map(mapAction),
600
+ agentRuns: mappedRuns,
601
+ toolCalls: toolCalls.results.map(mapToolCall),
602
+ retrievalEvents: retrievalEvents.results.map(mapRetrievalEvent),
603
+ evalResults: evalResults.results.map(mapEvalResult),
604
+ artifacts: artifacts.results.map(mapArtifact),
605
+ };
606
+ }
607
+
608
+ export async function manifestByActor(
609
+ db: SqlDb,
610
+ projectId: string,
611
+ actorType: string,
612
+ actorId: string,
613
+ ): Promise<EntityManifestExtended> {
614
+ const actionsRes = await db
615
+ .prepare(
616
+ `SELECT * FROM actions
617
+ WHERE project_id = ? AND actor_type = ? AND actor_id = ?
618
+ ORDER BY started_at DESC LIMIT ?`,
619
+ )
620
+ .bind(projectId, actorType, actorId, FETCH_LIMIT)
621
+ .all<Parameters<typeof mapAction>[0]>();
622
+
623
+ const matchedActions = actionsRes.results;
624
+ if (matchedActions.length === 0) {
625
+ return emptyExtendedManifest();
626
+ }
627
+
628
+ const actionIds = matchedActions.map((a) => a.id);
629
+ const rootActionIds = Array.from(
630
+ new Set(matchedActions.map((a) => a.root_action_id)),
631
+ );
632
+
633
+ // Execute child queries in parallel
634
+ const placeholders = actionIds.map(() => "?").join(", ");
635
+ const rootPlaceholders = rootActionIds.map(() => "?").join(", ");
636
+ const [agentRuns, toolCalls, retrievalEvents, evalResults, artifacts] =
637
+ await Promise.all([
638
+ rootActionIds.length > 0
639
+ ? db
640
+ .prepare(
641
+ `SELECT * FROM agent_runs
642
+ WHERE project_id = ? AND id IN (${rootPlaceholders})
643
+ LIMIT ?`,
644
+ )
645
+ .bind(projectId, ...rootActionIds, FETCH_LIMIT)
646
+ .all<Parameters<typeof mapAgentRun>[0]>()
647
+ : { results: [] },
648
+ actionIds.length > 0
649
+ ? db
650
+ .prepare(
651
+ `SELECT * FROM tool_calls
652
+ WHERE project_id = ? AND action_id IN (${placeholders})
653
+ LIMIT ?`,
654
+ )
655
+ .bind(projectId, ...actionIds, FETCH_LIMIT)
656
+ .all<Parameters<typeof mapToolCall>[0]>()
657
+ : { results: [] },
658
+ actionIds.length > 0
659
+ ? db
660
+ .prepare(
661
+ `SELECT * FROM retrieval_events
662
+ WHERE project_id = ? AND action_id IN (${placeholders})
663
+ LIMIT ?`,
664
+ )
665
+ .bind(projectId, ...actionIds, FETCH_LIMIT)
666
+ .all<Parameters<typeof mapRetrievalEvent>[0]>()
667
+ : { results: [] },
668
+ actionIds.length > 0
669
+ ? db
670
+ .prepare(
671
+ `SELECT * FROM eval_results
672
+ WHERE project_id = ? AND action_id IN (${placeholders})
673
+ LIMIT ?`,
674
+ )
675
+ .bind(projectId, ...actionIds, FETCH_LIMIT)
676
+ .all<Parameters<typeof mapEvalResult>[0]>()
677
+ : { results: [] },
678
+ actionIds.length > 0
679
+ ? db
680
+ .prepare(
681
+ `SELECT * FROM artifacts
682
+ WHERE project_id = ? AND action_id IN (${placeholders})
683
+ LIMIT ?`,
684
+ )
685
+ .bind(projectId, ...actionIds, FETCH_LIMIT)
686
+ .all<Parameters<typeof mapArtifact>[0]>()
687
+ : { results: [] },
688
+ ]);
689
+
690
+ // Extract unique trace IDs and session IDs
691
+ const traceIds = Array.from(
692
+ new Set(
693
+ matchedActions
694
+ .map((a) => a.trace_id)
695
+ .filter((t): t is string => Boolean(t)),
696
+ ),
697
+ );
698
+ const sessionIds = Array.from(
699
+ new Set(
700
+ matchedActions
701
+ .map((a) => a.session_id)
702
+ .filter((s): s is string => Boolean(s)),
703
+ ),
704
+ );
705
+
706
+ let spansRes = { results: [] as Parameters<typeof mapSpan>[0][] };
707
+ let logsRes = { results: [] as Parameters<typeof mapLog>[0][] };
708
+ let aiCallsRes = { results: [] as Parameters<typeof mapAi>[0][] };
709
+ let metricExemplarsRes = {
710
+ results: [] as Parameters<typeof mapMetricExemplar>[0][],
711
+ };
712
+ let replayRes: ReplayRow | null = null;
713
+
714
+ if (traceIds.length > 0) {
715
+ const tracePlaceholders = traceIds.map(() => "?").join(", ");
716
+ const [s, l, a, e] = await Promise.all([
717
+ db
718
+ .prepare(
719
+ `SELECT trace_id, span_id, parent_span_id, service_name, span_name,
720
+ status_code, status_message, start_time, duration_ms, interaction_id
721
+ FROM telemetry_spans
722
+ WHERE project_id = ? AND trace_id IN (${tracePlaceholders})
723
+ ORDER BY start_time ASC LIMIT ?`,
724
+ )
725
+ .bind(projectId, ...traceIds, FETCH_LIMIT)
726
+ .all<Parameters<typeof mapSpan>[0]>(),
727
+ db
728
+ .prepare(
729
+ `SELECT log_id, trace_id, span_id, service_name, logger_name,
730
+ severity, message, occurred_at, interaction_id, session_id
731
+ FROM logs
732
+ WHERE project_id = ? AND trace_id IN (${tracePlaceholders})
733
+ ORDER BY occurred_at ASC LIMIT ?`,
734
+ )
735
+ .bind(projectId, ...traceIds, FETCH_LIMIT)
736
+ .all<Parameters<typeof mapLog>[0]>(),
737
+ db
738
+ .prepare(
739
+ `SELECT call_id, trace_id, model_name, provider, total_cost_usd,
740
+ occurred_at, interaction_id, session_id
741
+ FROM ai_calls
742
+ WHERE project_id = ? AND trace_id IN (${tracePlaceholders})
743
+ ORDER BY occurred_at ASC LIMIT ?`,
744
+ )
745
+ .bind(projectId, ...traceIds, FETCH_LIMIT)
746
+ .all<Parameters<typeof mapAi>[0]>(),
747
+ db
748
+ .prepare(
749
+ `SELECT id, point_id, series_id, metric_name, service_name,
750
+ trace_id, span_id, ts_ns, value, received_at
751
+ FROM metric_exemplars
752
+ WHERE project_id = ? AND trace_id IN (${tracePlaceholders})
753
+ ORDER BY ts_ns DESC LIMIT ?`,
754
+ )
755
+ .bind(projectId, ...traceIds, FETCH_LIMIT)
756
+ .all<Parameters<typeof mapMetricExemplar>[0]>(),
757
+ ]);
758
+ spansRes = s;
759
+ logsRes = l;
760
+ aiCallsRes = a;
761
+ metricExemplarsRes = e;
762
+ }
763
+
764
+ if (sessionIds.length > 0) {
765
+ const sessionPlaceholders = sessionIds.map(() => "?").join(", ");
766
+ replayRes = await db
767
+ .prepare(
768
+ `SELECT session_id, first_chunk_at, last_chunk_at, chunk_count, events_count
769
+ FROM session_replay_metadata
770
+ WHERE session_id IN (${sessionPlaceholders})
771
+ ORDER BY last_chunk_at DESC LIMIT 1`,
772
+ )
773
+ .bind(...sessionIds)
774
+ .first<{
775
+ session_id: string;
776
+ first_chunk_at: string;
777
+ last_chunk_at: string;
778
+ chunk_count: number;
779
+ events_count: number;
780
+ }>();
781
+ }
782
+
783
+ return {
784
+ spans: spansRes.results.map(mapSpan),
785
+ logs: logsRes.results.map(mapLog),
786
+ usageEvents: [],
787
+ aiCalls: aiCallsRes.results.map(mapAi),
788
+ metricExemplars: metricExemplarsRes.results.map(mapMetricExemplar),
789
+ replay: replayRes
790
+ ? {
791
+ sessionId: replayRes.session_id,
792
+ firstChunkAt: replayRes.first_chunk_at,
793
+ lastChunkAt: replayRes.last_chunk_at,
794
+ chunkCount: replayRes.chunk_count,
795
+ eventsCount: replayRes.events_count,
796
+ }
797
+ : null,
798
+ actions: matchedActions.map(mapAction),
799
+ agentRuns: agentRuns.results.map(mapAgentRun),
800
+ toolCalls: toolCalls.results.map(mapToolCall),
801
+ retrievalEvents: retrievalEvents.results.map(mapRetrievalEvent),
802
+ evalResults: evalResults.results.map(mapEvalResult),
803
+ artifacts: artifacts.results.map(mapArtifact),
804
+ };
805
+ }