@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,671 @@
1
+ // src/lib/evidence-references.ts
2
+ var analysisRoute = (analysisId) => `#/investigate/${encodeURIComponent(analysisId)}`;
3
+ var analysisEvidenceReference = (evidence) => {
4
+ const route = analysisRoute(evidence.analysisId);
5
+ const status = evidence.result?.status ?? "unknown";
6
+ return {
7
+ evidenceId: `ask:analysis:${evidence.analysisId}`,
8
+ entityKind: "analysis",
9
+ entityId: evidence.analysisId,
10
+ route,
11
+ source: "ask.run_analysis",
12
+ confidence: evidence.result ? 1 : 0.55,
13
+ reason: evidence.result ? `Ask consulted analysis "${evidence.definition.title}" with latest status ${status}.` : `Ask consulted analysis "${evidence.definition.title}", but no latest result was available.`,
14
+ citations: [
15
+ {
16
+ label: evidence.definition.title,
17
+ entityKind: "analysis",
18
+ entityId: evidence.analysisId,
19
+ route
20
+ }
21
+ ],
22
+ suggestedNextPivots: [
23
+ {
24
+ label: "Open investigation",
25
+ entityKind: "analysis",
26
+ entityId: evidence.analysisId,
27
+ route,
28
+ reason: "Review the analysis payload, narrative, and connected telemetry rail."
29
+ }
30
+ ]
31
+ };
32
+ };
33
+ var askEvidenceReferences = (evidence) => [...evidence].map((item) => analysisEvidenceReference(item));
34
+ var MAX_ANALYSIS_EVIDENCE_REFERENCES = 50;
35
+ var isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
36
+ var asString = (value) => {
37
+ if (typeof value === "string" && value.trim()) return value.trim();
38
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
39
+ return null;
40
+ };
41
+ var normalizeHeader = (header) => header.trim().toLowerCase().replace(/[\s-]+/g, "_");
42
+ var valueFor = (row, headerIndex, names) => {
43
+ for (const name of names) {
44
+ const index = headerIndex.get(name);
45
+ if (index === void 0) continue;
46
+ const value = asString(row[index]);
47
+ if (value) return value;
48
+ }
49
+ return null;
50
+ };
51
+ var traceRoute = (traceId) => `#/traces/${encodeURIComponent(traceId)}`;
52
+ var spanRoute = (traceId, spanId) => `${traceRoute(traceId)}#span=${encodeURIComponent(spanId)}`;
53
+ var evidenceRouteFor = (entityKind, entityId, traceId) => {
54
+ switch (entityKind) {
55
+ case "analysis":
56
+ return analysisRoute(entityId);
57
+ case "action":
58
+ return `#/actions/${encodeURIComponent(entityId)}`;
59
+ case "alert":
60
+ return `#/alerts?alert=${encodeURIComponent(entityId)}`;
61
+ case "agent_run":
62
+ return `#/agent-runs/${encodeURIComponent(entityId)}`;
63
+ case "ai_call":
64
+ return `#/ai/calls/${encodeURIComponent(entityId)}`;
65
+ case "eval":
66
+ return `#/evals/${encodeURIComponent(entityId)}`;
67
+ case "log":
68
+ return `#/logs?id=${encodeURIComponent(entityId)}`;
69
+ case "profile":
70
+ return traceId ? `#/profiles/${encodeURIComponent(entityId)}?trace_id=${encodeURIComponent(traceId)}` : `#/profiles/${encodeURIComponent(entityId)}`;
71
+ case "replay":
72
+ return `#/replay?session=${encodeURIComponent(entityId)}`;
73
+ case "service":
74
+ return `#/traces?service=${encodeURIComponent(entityId)}`;
75
+ case "span": {
76
+ const [tracePart, spanPart] = entityId.split(":");
77
+ return spanPart ? spanRoute(tracePart, spanPart) : `#/spans/${entityId}`;
78
+ }
79
+ case "tool_call":
80
+ return `#/tool-calls/${encodeURIComponent(entityId)}`;
81
+ case "trace":
82
+ return traceRoute(entityId);
83
+ case "docs":
84
+ return entityId.startsWith("#/") ? entityId : `#/docs/${entityId}`;
85
+ }
86
+ const exhaustive = entityKind;
87
+ return exhaustive;
88
+ };
89
+ var parseEvidenceTables = (payload) => {
90
+ const evidence = payload.evidence;
91
+ if (!isRecord(evidence)) return [];
92
+ const tables = [];
93
+ for (const [sectionName, section] of Object.entries(evidence)) {
94
+ if (!isRecord(section)) continue;
95
+ const headers = section.headers;
96
+ const rows = section.rows;
97
+ if (!Array.isArray(headers) || !headers.every(
98
+ (header) => typeof header === "string"
99
+ ) || !Array.isArray(rows)) {
100
+ continue;
101
+ }
102
+ const normalizedRows = rows.filter(
103
+ (row) => Array.isArray(row)
104
+ );
105
+ tables.push({
106
+ sectionName,
107
+ table: {
108
+ title: asString(section.title),
109
+ headers,
110
+ rows: normalizedRows
111
+ }
112
+ });
113
+ }
114
+ return tables;
115
+ };
116
+ var instrumentationGapEvidenceReferences = (result) => {
117
+ const gaps = result.payload.instrumentationGaps;
118
+ if (!isRecord(gaps)) return [];
119
+ const traceId = asString(gaps.traceId);
120
+ const blindspots = gaps.blindspots;
121
+ if (!traceId || !Array.isArray(blindspots)) return [];
122
+ return blindspots.filter(isRecord).map((gap, index) => {
123
+ const spanId = asString(gap.parentSpanId);
124
+ if (!spanId) return null;
125
+ const serviceName = asString(gap.parentServiceName);
126
+ const spanName = asString(gap.parentSpanName);
127
+ const durationMs = typeof gap.durationMs === "number" && Number.isFinite(gap.durationMs) ? gap.durationMs : null;
128
+ const ratio = typeof gap.ratioOfParent === "number" && Number.isFinite(gap.ratioOfParent) ? gap.ratioOfParent : null;
129
+ const recommendation = asString(gap.recommendation);
130
+ const thresholdVersion = asString(gap.thresholdVersion) ?? (isRecord(gaps.thresholds) ? asString(gaps.thresholds.version) : null);
131
+ const entityId = `${traceId}:${spanId}`;
132
+ const route = spanRoute(traceId, spanId);
133
+ const label = spanName ? `${spanName} (${spanId})` : `span ${spanId}`;
134
+ const durationText = durationMs === null ? "unknown duration" : `${durationMs}ms`;
135
+ const ratioText = ratio === null ? "" : `, ${(ratio * 100).toFixed(1)}% of parent`;
136
+ const thresholdText = thresholdVersion ? ` Thresholds: ${thresholdVersion}.` : "";
137
+ const docsRoute = "#/docs/howto/ebpf";
138
+ return {
139
+ evidenceId: `analysis:${result.analysisId}:instrumentation-gap:${index}:span:${entityId}`,
140
+ entityKind: "span",
141
+ entityId,
142
+ route,
143
+ source: "analysis.payload.instrumentationGaps",
144
+ confidence: 0.9,
145
+ reason: `Analysis "${result.analysisId}" found uninstrumented self-time on ${label}: ${durationText}${ratioText}.${thresholdText}${recommendation ? ` ${recommendation}` : ""}`,
146
+ citations: [
147
+ {
148
+ label: `trace ${traceId}`,
149
+ entityKind: "trace",
150
+ entityId: traceId,
151
+ route: traceRoute(traceId)
152
+ }
153
+ ],
154
+ suggestedNextPivots: [
155
+ {
156
+ label: "Open uninstrumented span",
157
+ entityKind: "span",
158
+ entityId,
159
+ route,
160
+ reason: "Inspect the parent span whose self-time suggests missing child instrumentation."
161
+ },
162
+ {
163
+ label: "Open trace gap data",
164
+ entityKind: "trace",
165
+ entityId: traceId,
166
+ route: `${traceRoute(traceId)}/gaps`,
167
+ reason: "Review all structured instrumentation blindspots for this trace."
168
+ },
169
+ {
170
+ label: "Open profiler setup docs",
171
+ entityKind: "docs",
172
+ entityId: "howto/ebpf",
173
+ route: docsRoute,
174
+ reason: serviceName != null ? `Use profiler or eBPF setup guidance to cover missing spans in ${serviceName}.` : "Use profiler or eBPF setup guidance to cover missing spans."
175
+ }
176
+ ]
177
+ };
178
+ }).filter((ref) => ref !== null).slice(0, MAX_ANALYSIS_EVIDENCE_REFERENCES);
179
+ };
180
+ var entityFromRow = (row, headers) => {
181
+ const headerIndex = new Map(headers.map((header, index) => [header, index]));
182
+ const traceId = valueFor(row, headerIndex, ["trace_id", "traceid", "trace"]);
183
+ const spanId = valueFor(row, headerIndex, ["span_id", "spanid"]);
184
+ const logId = valueFor(row, headerIndex, ["log_id", "logid"]);
185
+ const profileId = valueFor(row, headerIndex, ["profile_id", "profileid"]);
186
+ const actionId = valueFor(row, headerIndex, ["action_id", "actionid"]);
187
+ const agentRunId = valueFor(row, headerIndex, [
188
+ "agent_run_id",
189
+ "agentrunid",
190
+ "agent_run"
191
+ ]);
192
+ const toolCallId = valueFor(row, headerIndex, [
193
+ "tool_call_id",
194
+ "toolcallid",
195
+ "tool_call"
196
+ ]);
197
+ const evalId = valueFor(row, headerIndex, ["eval_id", "evalid"]);
198
+ const service = valueFor(row, headerIndex, ["service", "service_name"]);
199
+ const traceCitation = traceId ? [
200
+ {
201
+ label: `trace ${traceId}`,
202
+ entityKind: "trace",
203
+ entityId: traceId,
204
+ route: traceRoute(traceId)
205
+ }
206
+ ] : [];
207
+ if (traceId && spanId) {
208
+ const entityId = `${traceId}:${spanId}`;
209
+ return {
210
+ entityKind: "span",
211
+ entityId,
212
+ route: spanRoute(traceId, spanId),
213
+ label: `span ${spanId}`,
214
+ confidence: 0.95,
215
+ citations: traceCitation,
216
+ suggestedNextPivots: [
217
+ {
218
+ label: "Open span",
219
+ entityKind: "span",
220
+ entityId,
221
+ route: spanRoute(traceId, spanId),
222
+ reason: "Inspect the cited span in its trace context."
223
+ },
224
+ {
225
+ label: "Open trace",
226
+ entityKind: "trace",
227
+ entityId: traceId,
228
+ route: traceRoute(traceId),
229
+ reason: "Review the surrounding trace context."
230
+ }
231
+ ]
232
+ };
233
+ }
234
+ const candidates = [
235
+ {
236
+ entityKind: "trace",
237
+ entityId: traceId,
238
+ labelPrefix: "trace",
239
+ confidence: 0.95
240
+ },
241
+ {
242
+ entityKind: "action",
243
+ entityId: actionId,
244
+ labelPrefix: "action",
245
+ confidence: 0.9
246
+ },
247
+ {
248
+ entityKind: "tool_call",
249
+ entityId: toolCallId,
250
+ labelPrefix: "tool call",
251
+ confidence: 0.9
252
+ },
253
+ {
254
+ entityKind: "agent_run",
255
+ entityId: agentRunId,
256
+ labelPrefix: "agent run",
257
+ confidence: 0.9
258
+ },
259
+ {
260
+ entityKind: "profile",
261
+ entityId: profileId,
262
+ labelPrefix: "profile",
263
+ confidence: 0.9
264
+ },
265
+ {
266
+ entityKind: "log",
267
+ entityId: logId,
268
+ labelPrefix: "log",
269
+ confidence: 0.85
270
+ },
271
+ {
272
+ entityKind: "eval",
273
+ entityId: evalId,
274
+ labelPrefix: "eval",
275
+ confidence: 0.85
276
+ },
277
+ {
278
+ entityKind: "service",
279
+ entityId: service,
280
+ labelPrefix: "service",
281
+ confidence: 0.7
282
+ }
283
+ ];
284
+ const candidate = candidates.find((item) => item.entityId);
285
+ if (!candidate?.entityId) return null;
286
+ const route = evidenceRouteFor(
287
+ candidate.entityKind,
288
+ candidate.entityId,
289
+ traceId
290
+ );
291
+ return {
292
+ entityKind: candidate.entityKind,
293
+ entityId: candidate.entityId,
294
+ route,
295
+ label: `${candidate.labelPrefix} ${candidate.entityId}`,
296
+ confidence: candidate.confidence,
297
+ citations: traceCitation.filter(
298
+ (citation) => citation.entityKind !== candidate.entityKind || citation.entityId !== candidate.entityId
299
+ ),
300
+ suggestedNextPivots: [
301
+ {
302
+ label: `Open ${candidate.labelPrefix}`,
303
+ entityKind: candidate.entityKind,
304
+ entityId: candidate.entityId,
305
+ route,
306
+ reason: "Inspect the cited evidence row."
307
+ }
308
+ ]
309
+ };
310
+ };
311
+ var alertEvidenceReferences = (rule, evaluation) => {
312
+ const route = evidenceRouteFor("alert", rule.id);
313
+ const references = [
314
+ {
315
+ evidenceId: evaluation ? `alert:${rule.id}:evaluation:${evaluation.id}` : `alert:${rule.id}:rule`,
316
+ entityKind: "alert",
317
+ entityId: rule.id,
318
+ route,
319
+ source: evaluation ? "alert_evaluations" : "alerts.rule_evaluation_preview",
320
+ confidence: evaluation ? 1 : 0.8,
321
+ reason: evaluation ? `Alert "${rule.name}" evaluated to ${evaluation.value} and state ${evaluation.state}.` : `Alert "${rule.name}" was evaluated against its configured threshold.`,
322
+ citations: [
323
+ {
324
+ label: rule.name,
325
+ entityKind: "alert",
326
+ entityId: rule.id,
327
+ route
328
+ }
329
+ ],
330
+ suggestedNextPivots: [
331
+ {
332
+ label: "Open alert",
333
+ entityKind: "alert",
334
+ entityId: rule.id,
335
+ route,
336
+ reason: "Inspect the alert rule, threshold, and recent evaluations."
337
+ }
338
+ ]
339
+ }
340
+ ];
341
+ if (rule.analysisId) {
342
+ const analysisRouteValue = analysisRoute(rule.analysisId);
343
+ references.push({
344
+ evidenceId: evaluation ? `alert:${rule.id}:evaluation:${evaluation.id}:analysis:${rule.analysisId}` : `alert:${rule.id}:analysis:${rule.analysisId}`,
345
+ entityKind: "analysis",
346
+ entityId: rule.analysisId,
347
+ route: analysisRouteValue,
348
+ source: "alert_rules.analysis_id",
349
+ confidence: 0.9,
350
+ reason: `Alert "${rule.name}" is bound to analysis "${rule.analysisId}".`,
351
+ citations: [
352
+ {
353
+ label: rule.name,
354
+ entityKind: "alert",
355
+ entityId: rule.id,
356
+ route
357
+ }
358
+ ],
359
+ suggestedNextPivots: [
360
+ {
361
+ label: "Open investigation",
362
+ entityKind: "analysis",
363
+ entityId: rule.analysisId,
364
+ route: analysisRouteValue,
365
+ reason: "Review the analysis result that drives this alert."
366
+ }
367
+ ]
368
+ });
369
+ }
370
+ return references;
371
+ };
372
+ var aiEvaluationEvidenceReferences = (evaluation) => {
373
+ const spanEntityId = `${evaluation.traceId}:${evaluation.spanId}`;
374
+ const spanRouteValue = evidenceRouteFor(
375
+ "span",
376
+ spanEntityId,
377
+ evaluation.traceId
378
+ );
379
+ return [
380
+ {
381
+ evidenceId: `ai-evaluation:${evaluation.evaluationId}`,
382
+ entityKind: "eval",
383
+ entityId: evaluation.evaluationId,
384
+ route: evidenceRouteFor("eval", evaluation.evaluationId),
385
+ source: `ai_span_evaluations.${evaluation.source}`,
386
+ confidence: 0.95,
387
+ reason: `AI evaluation "${evaluation.name}" graded span "${evaluation.spanId}".`,
388
+ citations: [
389
+ {
390
+ label: `span ${evaluation.spanId}`,
391
+ entityKind: "span",
392
+ entityId: spanEntityId,
393
+ route: spanRouteValue
394
+ },
395
+ {
396
+ label: `trace ${evaluation.traceId}`,
397
+ entityKind: "trace",
398
+ entityId: evaluation.traceId,
399
+ route: evidenceRouteFor("trace", evaluation.traceId)
400
+ }
401
+ ],
402
+ suggestedNextPivots: [
403
+ {
404
+ label: "Open span",
405
+ entityKind: "span",
406
+ entityId: spanEntityId,
407
+ route: spanRouteValue,
408
+ reason: "Inspect the evaluated AI span and its surrounding trace."
409
+ }
410
+ ]
411
+ }
412
+ ];
413
+ };
414
+ var sourceLinkEvidenceReferences = (source, links) => {
415
+ const citations = [];
416
+ if (source.sourceRoute) {
417
+ citations.push({
418
+ label: source.sourceName ?? source.sourceId,
419
+ entityKind: "eval",
420
+ entityId: source.sourceId,
421
+ route: source.sourceRoute
422
+ });
423
+ }
424
+ const candidates = [
425
+ {
426
+ kind: "action",
427
+ id: links.sourceActionId,
428
+ label: "source action",
429
+ confidence: 0.92
430
+ },
431
+ {
432
+ kind: "tool_call",
433
+ id: links.sourceToolCallId,
434
+ label: "source tool call",
435
+ confidence: 0.9
436
+ },
437
+ {
438
+ kind: "agent_run",
439
+ id: links.sourceAgentRunId,
440
+ label: "source agent run",
441
+ confidence: 0.9
442
+ },
443
+ {
444
+ kind: "span",
445
+ id: links.sourceTraceId && links.sourceSpanId ? `${links.sourceTraceId}:${links.sourceSpanId}` : null,
446
+ label: "source span",
447
+ confidence: 0.9
448
+ },
449
+ {
450
+ kind: "trace",
451
+ id: links.sourceTraceId,
452
+ label: "source trace",
453
+ confidence: 0.88
454
+ }
455
+ ];
456
+ return candidates.filter(
457
+ (candidate) => Boolean(candidate.id)
458
+ ).map((candidate) => {
459
+ const route = evidenceRouteFor(
460
+ candidate.kind,
461
+ candidate.id,
462
+ links.sourceTraceId
463
+ );
464
+ return {
465
+ evidenceId: `${source.sourceKind}:${source.sourceId}:${candidate.kind}:${candidate.id}`,
466
+ entityKind: candidate.kind,
467
+ entityId: candidate.id,
468
+ route,
469
+ source: `${source.sourceKind}.source_links`,
470
+ confidence: candidate.confidence,
471
+ reason: `${source.sourceLabel} links to ${candidate.label} "${candidate.id}".`,
472
+ citations,
473
+ suggestedNextPivots: [
474
+ {
475
+ label: `Open ${candidate.label}`,
476
+ entityKind: candidate.kind,
477
+ entityId: candidate.id,
478
+ route,
479
+ reason: "Inspect the production evidence behind this evaluation item."
480
+ }
481
+ ]
482
+ };
483
+ });
484
+ };
485
+ var analysisResultEvidenceReferences = (result) => {
486
+ const references = [
487
+ ...instrumentationGapEvidenceReferences(result)
488
+ ];
489
+ for (const { sectionName, table } of parseEvidenceTables(result.payload)) {
490
+ const headers = table.headers.map(normalizeHeader);
491
+ for (const [rowIndex, row] of table.rows.entries()) {
492
+ if (references.length >= MAX_ANALYSIS_EVIDENCE_REFERENCES) {
493
+ return references;
494
+ }
495
+ const entity = entityFromRow(row, headers);
496
+ if (!entity) continue;
497
+ const title = table.title ?? sectionName;
498
+ references.push({
499
+ evidenceId: `analysis:${result.analysisId}:${sectionName}:${rowIndex}:${entity.entityKind}:${entity.entityId}`,
500
+ entityKind: entity.entityKind,
501
+ entityId: entity.entityId,
502
+ route: entity.route,
503
+ source: `analysis.payload.evidence.${sectionName}`,
504
+ confidence: entity.confidence,
505
+ reason: `Analysis "${result.analysisId}" cited ${entity.label} in "${title}" row ${rowIndex + 1}.`,
506
+ citations: entity.citations,
507
+ suggestedNextPivots: entity.suggestedNextPivots
508
+ });
509
+ }
510
+ }
511
+ return references;
512
+ };
513
+
514
+ // src/lib/llm.ts
515
+ var DEFAULT_API_URL = "https://api.anthropic.com/v1/messages";
516
+ var SYSTEM_PROMPT = `You write one-sentence operational narratives for a self-hosted observability dashboard.
517
+
518
+ Hard rules \u2014 violations are unusable:
519
+ 1. Never start with "I", "Here's", "We", "It looks", or any first-person/chatbot framing. Write declarative statements about the system.
520
+ 2. If you reference a trace, span, log, or service, include the supplied identifier inline (e.g. "trace abc12345").
521
+ 3. Include a time anchor: "starting Nm ago", "for the last Nm", "since HH:MM" \u2014 pull from the inputs.
522
+ 4. \u2264 2 sentences total, \u2264 220 characters. Numbers belong in the sentence, not in a separate label.
523
+ 5. If the inputs are insufficient to write a useful sentence, output exactly the string NO_NARRATIVE (no quotes).
524
+
525
+ Tone: like a senior on-call writing a Slack message. Specific, blunt, no hedging, no advice ("you should..."), no questions.`;
526
+ var renderTemplate = (template, def, r) => {
527
+ const scope = def.scope ?? {};
528
+ const traceIds = Array.isArray(r.payload?.trace_ids) ? r.payload.trace_ids.filter((x) => typeof x === "string").slice(0, 5).join(", ") : "";
529
+ const subs = {
530
+ title: def.title,
531
+ status: r.status,
532
+ primary: r.primaryValue === null ? "n/a" : String(r.primaryValue),
533
+ baseline: r.baselineValue === null ? "n/a" : String(r.baselineValue),
534
+ delta_pct: r.deltaPct === null ? "n/a" : `${r.deltaPct.toFixed(1)}%`,
535
+ service: typeof scope.service === "string" ? scope.service : "",
536
+ trace_ids: traceIds
537
+ };
538
+ return template.replace(/\{\{(\w+)\}\}/g, (_, k) => subs[k] ?? "");
539
+ };
540
+ var buildUserPrompt = ({
541
+ definition,
542
+ current,
543
+ previous
544
+ }) => {
545
+ const lines = [];
546
+ const tmplRendered = renderTemplate(
547
+ definition.narrate?.prompt ?? "",
548
+ definition,
549
+ current
550
+ ).trim();
551
+ if (tmplRendered) lines.push(tmplRendered);
552
+ lines.push("");
553
+ lines.push("Current state:");
554
+ lines.push(`- panel: ${definition.title}`);
555
+ lines.push(`- status: ${current.status}`);
556
+ if (current.primaryValue !== null)
557
+ lines.push(`- primary: ${current.primaryValue}`);
558
+ if (current.baselineValue !== null)
559
+ lines.push(`- baseline (1h): ${current.baselineValue}`);
560
+ if (current.deltaPct !== null)
561
+ lines.push(`- delta vs baseline: ${current.deltaPct.toFixed(1)}%`);
562
+ const since = previous ? Math.max(
563
+ 1,
564
+ Math.round(
565
+ (new Date(current.generatedAt).getTime() - new Date(previous.generatedAt).getTime()) / 6e4
566
+ )
567
+ ) : null;
568
+ if (since !== null) {
569
+ lines.push(
570
+ `- previous run was ${since} minute${since === 1 ? "" : "s"} ago, status=${previous?.status ?? "n/a"}`
571
+ );
572
+ }
573
+ const payloadKeys = Object.keys(current.payload ?? {}).filter(
574
+ (k) => k !== "sparkline"
575
+ );
576
+ if (payloadKeys.length > 0) {
577
+ lines.push("");
578
+ lines.push("Payload details:");
579
+ for (const k of payloadKeys) {
580
+ const v = current.payload[k];
581
+ const rendered = typeof v === "string" || typeof v === "number" || typeof v === "boolean" ? String(v) : JSON.stringify(v);
582
+ if (rendered.length <= 200) lines.push(`- ${k}: ${rendered}`);
583
+ }
584
+ }
585
+ lines.push("");
586
+ lines.push(
587
+ "Output: a single sentence (max two), no preamble, no quotes, no Markdown."
588
+ );
589
+ return lines.join("\n");
590
+ };
591
+ var LlmCallError = class extends Error {
592
+ status;
593
+ constructor(message, status = null) {
594
+ super(message);
595
+ this.name = "LlmCallError";
596
+ this.status = status;
597
+ }
598
+ };
599
+ async function generateNarrative(req, config) {
600
+ if (config.provider === "openai") {
601
+ const { generateNarrativeOpenAI } = await import("./openai-FJYOQ7WO.js");
602
+ return generateNarrativeOpenAI(req, config);
603
+ }
604
+ return generateNarrativeAnthropic(req, config);
605
+ }
606
+ async function generateNarrativeAnthropic(req, config) {
607
+ const url = config.apiUrl ?? DEFAULT_API_URL;
608
+ const userPrompt = buildUserPrompt(req);
609
+ const tracer = config.tracer;
610
+ const callLLM = async (span) => {
611
+ const response = await fetch(url, {
612
+ method: "POST",
613
+ headers: {
614
+ "x-api-key": config.apiKey,
615
+ "anthropic-version": "2023-06-01",
616
+ "content-type": "application/json"
617
+ },
618
+ body: JSON.stringify({
619
+ model: config.model,
620
+ max_tokens: 200,
621
+ system: SYSTEM_PROMPT,
622
+ messages: [{ role: "user", content: userPrompt }]
623
+ }),
624
+ signal: config.signal
625
+ });
626
+ if (span) {
627
+ span.setAttribute("http.response.status_code", response.status);
628
+ }
629
+ if (!response.ok) {
630
+ const text2 = await response.text().catch(() => "");
631
+ throw new LlmCallError(
632
+ `anthropic ${response.status}: ${text2.slice(0, 200)}`,
633
+ response.status
634
+ );
635
+ }
636
+ const body = await response.json();
637
+ if (body.stop_reason === "max_tokens") {
638
+ throw new LlmCallError("anthropic response truncated by max_tokens");
639
+ }
640
+ if (span && body.usage) {
641
+ if (typeof body.usage.input_tokens === "number")
642
+ span.setAttribute("gen_ai.usage.input_tokens", body.usage.input_tokens);
643
+ if (typeof body.usage.output_tokens === "number")
644
+ span.setAttribute(
645
+ "gen_ai.usage.output_tokens",
646
+ body.usage.output_tokens
647
+ );
648
+ }
649
+ const text = (body.content ?? []).filter((block) => block?.type === "text").map((block) => block?.text ?? "").join("").trim();
650
+ if (!text || text === "NO_NARRATIVE") return null;
651
+ return text.replace(/^["']|["']$/g, "").trim();
652
+ };
653
+ if (!tracer) return callLLM(null);
654
+ return tracer("llm.anthropic.messages", async (span) => callLLM(span), {
655
+ "openinference.span.kind": "LLM",
656
+ "gen_ai.system": "anthropic",
657
+ "gen_ai.request.model": config.model,
658
+ "gen_ai.request.max_tokens": 200
659
+ });
660
+ }
661
+
662
+ export {
663
+ askEvidenceReferences,
664
+ alertEvidenceReferences,
665
+ aiEvaluationEvidenceReferences,
666
+ sourceLinkEvidenceReferences,
667
+ analysisResultEvidenceReferences,
668
+ LlmCallError,
669
+ generateNarrative
670
+ };
671
+ //# sourceMappingURL=chunk-XKQTVIRE.js.map