@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,165 @@
1
+ import { Hono, Context, MiddlewareHandler } from 'hono';
2
+
3
+ /**
4
+ * Pluggable logger interface for the collector framework.
5
+ *
6
+ * Structurally compatible with `@obsunified/telemetry-sdk`'s `Logger`, so the worker
7
+ * entrypoint can do:
8
+ *
9
+ * import { createLogger } from "@obsunified/telemetry-sdk";
10
+ * createDefaultCollectorApp({ logger: createLogger("obs-collector") });
11
+ *
12
+ * The framework package intentionally does not import telemetry-sdk to keep
13
+ * the dependency graph one-way (SDK depends on collector contracts, not the
14
+ * other way around). Anything that conforms to this interface works.
15
+ *
16
+ * When no logger is supplied, the framework falls back to a console-backed
17
+ * logger so out-of-the-box deployments keep their existing log output.
18
+ */
19
+ interface Logger {
20
+ debug(message: string, attributes?: Record<string, unknown>): void;
21
+ info(message: string, attributes?: Record<string, unknown>): void;
22
+ warn(message: string, attributes?: Record<string, unknown>): void;
23
+ error(message: string, attributes?: Record<string, unknown>): void;
24
+ }
25
+ declare const consoleLogger: Logger;
26
+ /**
27
+ * Minimal handle exposed to a `ChildSpanRunner` callback so the wrapped fn
28
+ * can stamp attributes / events / status onto the span as it learns about
29
+ * them (e.g., LLM token counts that are only known after the response).
30
+ *
31
+ * Structurally compatible with `ChildSpan` from `@obsunified/telemetry-sdk` so the
32
+ * worker entrypoint can pass the SDK's child span through directly without
33
+ * a bespoke adapter.
34
+ */
35
+ interface ChildSpanHandle {
36
+ setAttribute(key: string, value: unknown): void;
37
+ addEvent?(name: string, attributes?: Record<string, unknown>): void;
38
+ setStatus?(code: number, message?: string): void;
39
+ }
40
+ /**
41
+ * Wraps an async fn in a child span attached to the active parent span.
42
+ * Structurally compatible with `withChildSpan` from `@obsunified/telemetry-sdk`.
43
+ * Default is pass-through — wire from the worker entrypoint to enable.
44
+ *
45
+ * The wrapped fn receives a `ChildSpanHandle` so it can stamp attributes
46
+ * after the fact (e.g., `gen_ai.usage.input_tokens` once the LLM response
47
+ * arrives). When unwired, the handle's setters are no-ops.
48
+ */
49
+ type ChildSpanRunner = <T>(name: string, fn: (span: ChildSpanHandle) => Promise<T>, attributes?: Record<string, unknown>) => Promise<T>;
50
+
51
+ /**
52
+ * Collector worker bindings + env vars.
53
+ *
54
+ * Lives here, not in `@obsunified/types`, because it references Cloudflare
55
+ * Workers ambient globals (`D1Database`, `R2Bucket`,
56
+ * `DurableObjectNamespace`) that other packages — notably the web
57
+ * dashboard — should not transitively depend on.
58
+ */
59
+ interface CollectorEnv {
60
+ DB: D1Database;
61
+ REPLAYS_BUCKET?: R2Bucket;
62
+ /**
63
+ * RFC 0007 — pprof profile blob storage. Keyed by `${projectId}/${ts}/${profileId}.pprof.gz`.
64
+ * Same dispatch pattern as REPLAYS_BUCKET — when unbound, the receiver
65
+ * falls back to filesystem under `PROFILE_BLOB_DIR` (Node) or returns
66
+ * 503 (Workers).
67
+ */
68
+ PROFILES_BUCKET?: R2Bucket;
69
+ /** Durable Object namespace for live-tail SSE pub/sub. Optional — when
70
+ * unbound the /internal/telemetry/tail endpoint returns 503. */
71
+ TAIL_HUB?: DurableObjectNamespace;
72
+ /** Write-only API key for SDK ingest endpoints (/v1/*) */
73
+ INGEST_KEY?: string;
74
+ /** Password for dashboard login */
75
+ DASHBOARD_PASSWORD?: string;
76
+ /** Comma-separated allowed origins for CORS (e.g. "https://my-app.com,https://staging.my-app.com") */
77
+ ALLOWED_ORIGINS?: string;
78
+ /** Set to "true" to allow unauthenticated ingest (local dev only) */
79
+ ALLOW_UNAUTHENTICATED?: string;
80
+ TELEMETRY_REDACT_FIELDS?: string;
81
+ /**
82
+ * Local/test escape hatch for payload capture. Production should prefer the
83
+ * per-project `payload_capture_enabled` flag, which defaults to false.
84
+ */
85
+ OBS_PAYLOAD_CAPTURE_DEFAULT?: string;
86
+ RETENTION_HOURS?: string;
87
+ /**
88
+ * Stage 3 narrative pipeline (RFC 0002).
89
+ * Either ANTHROPIC_API_KEY or OPENAI_API_KEY enables the narrate pass +
90
+ * Ask box. When neither is set, panels remain data-only.
91
+ *
92
+ * If both are set, OpenAI wins — configuring both is presumed deliberate.
93
+ *
94
+ * - ANTHROPIC_API_KEY: enables Anthropic /messages.
95
+ * - OPENAI_API_KEY: enables OpenAI /chat/completions. Use OPENAI_BASE_URL
96
+ * to point at compatible endpoints (openrouter, vLLM, azure, ollama).
97
+ * - NARRATIVE_MODEL: model id. Defaults are provider-specific
98
+ * (claude-haiku-4-5 for anthropic, gpt-4o-mini for openai).
99
+ * - NARRATIVE_BUDGET_PER_HOUR: max narrative writes per project per hour.
100
+ * Acts as a safety rail against a flapping panel running up cost.
101
+ */
102
+ ANTHROPIC_API_KEY?: string;
103
+ OPENAI_API_KEY?: string;
104
+ OPENAI_BASE_URL?: string;
105
+ NARRATIVE_MODEL?: string;
106
+ NARRATIVE_BUDGET_PER_HOUR?: string;
107
+ /**
108
+ * Self-instrumentation — collector dogfoods itself into the `obs-dashboard`
109
+ * project via @obsunified/telemetry-sdk. When unset, self-instrumentation is a
110
+ * no-op (collector still runs normally). See apps/collector/SELF_INSTRUMENTATION.md.
111
+ */
112
+ OBS_DASHBOARD_INGEST_KEY?: string;
113
+ /** Self-URL for cron-time telemetry exports (no inbound request to derive from). */
114
+ OBS_COLLECTOR_SELF_URL?: string;
115
+ /**
116
+ * When "true", the worker's self-instrumentation middleware awaits its
117
+ * span/log export instead of using `ctx.waitUntil`. Miniflare drops
118
+ * waitUntil after the response, losing post-async telemetry. Set in
119
+ * `.dev.vars` only; production CF Workers should leave this unset.
120
+ */
121
+ OBS_SELF_AWAIT_EXPORTS?: string;
122
+ /** @deprecated Use INGEST_KEY instead */
123
+ TELEMETRY_INGEST_TOKEN?: string;
124
+ /** @deprecated Use INGEST_KEY instead */
125
+ USAGE_INGEST_TOKEN?: string;
126
+ /** @deprecated Use DASHBOARD_PASSWORD instead */
127
+ TELEMETRY_QUERY_TOKEN?: string;
128
+ }
129
+ interface CollectorRouteContext {
130
+ hono: Context<{
131
+ Bindings: CollectorEnv;
132
+ }>;
133
+ env: CollectorEnv;
134
+ now: Date;
135
+ logger: Logger;
136
+ }
137
+ type CollectorApp = Hono<{
138
+ Bindings: CollectorEnv;
139
+ }>;
140
+
141
+ /** Test helper — clears the in-memory cache and bootstrap flag. */
142
+ declare function resetIngestAuthCache(): void;
143
+ /**
144
+ * Creates middleware that validates ingest API keys against the ingest_keys table.
145
+ *
146
+ * Accepts the key from either:
147
+ * - Authorization: Bearer <key>
148
+ * - X-API-Key: <key>
149
+ *
150
+ * On success, sets `projectId` on the Hono context via `c.set('projectId', ...)`.
151
+ *
152
+ * On the first request per isolate, if `env.INGEST_KEY` is set, the middleware
153
+ * registers it as a `bootstrap` key on the default project — so existing
154
+ * single-tenant deployments continue to work without manual migration.
155
+ */
156
+ declare function createIngestAuth(config?: {
157
+ allowUnauthenticated?: boolean;
158
+ }): MiddlewareHandler<{
159
+ Bindings: CollectorEnv;
160
+ Variables: {
161
+ projectId: string;
162
+ };
163
+ }>;
164
+
165
+ export { type ChildSpanRunner as C, type Logger as L, type CollectorApp as a, type CollectorEnv as b, type CollectorRouteContext as c, consoleLogger as d, createIngestAuth as e, resetIngestAuthCache as r };
@@ -0,0 +1,384 @@
1
+ import {
2
+ LlmCallError,
3
+ askEvidenceReferences
4
+ } from "./chunk-XKQTVIRE.js";
5
+
6
+ // src/lib/openai.ts
7
+ import { EVIDENCE_REFERENCE_CONTRACT } from "@obsunified/types";
8
+ var DEFAULT_API_URL = "https://api.openai.com/v1";
9
+ var NARRATIVE_SYSTEM_PROMPT = `You write one-sentence operational narratives for a self-hosted observability dashboard.
10
+
11
+ Hard rules \u2014 violations are unusable:
12
+ 1. Never start with "I", "Here's", "We", "It looks", or any first-person/chatbot framing. Write declarative statements about the system.
13
+ 2. If you reference a trace, span, log, or service, include the supplied identifier inline (e.g. "trace abc12345").
14
+ 3. Include a time anchor: "starting Nm ago", "for the last Nm", "since HH:MM" \u2014 pull from the inputs.
15
+ 4. \u2264 2 sentences total, \u2264 220 characters. Numbers belong in the sentence, not in a separate label.
16
+ 5. If the inputs are insufficient to write a useful sentence, output exactly the string NO_NARRATIVE (no quotes).
17
+
18
+ Tone: like a senior on-call writing a Slack message. Specific, blunt, no hedging, no advice ("you should..."), no questions.`;
19
+ var ASK_SYSTEM_PROMPT = `You answer operational questions about a self-hosted observability dashboard's telemetry. Use the supplied tools \u2014 do not invent numbers.
20
+
21
+ Workflow:
22
+ 1. Use list_analyses to see what's available. Filter by group when it helps.
23
+ 2. Pick the analyses most likely to answer the question. Call run_analysis on each (typically 1\u20133, max 5).
24
+ 3. Read the results. Synthesize a 1\u20132 sentence answer.
25
+
26
+ Hard rules \u2014 violations are unusable:
27
+ 1. Never start with "I", "Here's", "We", or any chatbot framing. Write declarative statements about the system.
28
+ 2. Cite the analyses you consulted by id, e.g. "(latency_p95_overall)".
29
+ 3. Include a time anchor when describing a number ("in the last 5 minutes", "vs the prior hour", "for the past 24h").
30
+ 4. \u2264 2 sentences, \u2264 280 characters total. Numbers belong in the sentence.
31
+ 5. If the available analyses don't cover the question, say exactly that and name the closest analysis you found.
32
+
33
+ Output: write only the final answer text. No preamble, no JSON.`;
34
+ var OPENAI_TOOLS = [
35
+ {
36
+ type: "function",
37
+ function: {
38
+ name: "list_analyses",
39
+ description: "List registered analyses for the current project. Optionally filter by group (Health, Services, Dependencies, Async, AI, Frontend, Custom). Returns id, title, group, source, view, and scope (when set).",
40
+ parameters: {
41
+ type: "object",
42
+ properties: {
43
+ group: { type: "string" },
44
+ view: { type: "string" }
45
+ }
46
+ }
47
+ }
48
+ },
49
+ {
50
+ type: "function",
51
+ function: {
52
+ name: "run_analysis",
53
+ description: "Read the latest result for one analysis by id. Returns status, primary value, baseline, delta_pct, and the analysis-specific payload.",
54
+ parameters: {
55
+ type: "object",
56
+ properties: {
57
+ id: { type: "string" }
58
+ },
59
+ required: ["id"]
60
+ }
61
+ }
62
+ }
63
+ ];
64
+ var baseUrl = (cfg) => (cfg.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
65
+ var post = async (cfg, path, body, meta = {}) => {
66
+ const url = `${baseUrl(cfg)}${path}`;
67
+ const exec = async (span) => {
68
+ const response = await fetch(url, {
69
+ method: "POST",
70
+ headers: {
71
+ Authorization: `Bearer ${cfg.apiKey}`,
72
+ "content-type": "application/json"
73
+ },
74
+ body: JSON.stringify(body),
75
+ signal: cfg.signal
76
+ });
77
+ if (span) span.setAttribute("http.response.status_code", response.status);
78
+ if (!response.ok) {
79
+ const text = await response.text().catch(() => "");
80
+ throw new LlmCallError(
81
+ `openai ${response.status}: ${text.slice(0, 200)}`,
82
+ response.status
83
+ );
84
+ }
85
+ const json = await response.json();
86
+ if (span && json.usage) {
87
+ if (typeof json.usage.prompt_tokens === "number")
88
+ span.setAttribute(
89
+ "gen_ai.usage.input_tokens",
90
+ json.usage.prompt_tokens
91
+ );
92
+ if (typeof json.usage.completion_tokens === "number")
93
+ span.setAttribute(
94
+ "gen_ai.usage.output_tokens",
95
+ json.usage.completion_tokens
96
+ );
97
+ if (typeof json.usage.total_tokens === "number")
98
+ span.setAttribute("gen_ai.usage.total_tokens", json.usage.total_tokens);
99
+ }
100
+ return json;
101
+ };
102
+ if (!cfg.tracer) return exec(null);
103
+ const attrs = {
104
+ "openinference.span.kind": "LLM",
105
+ "gen_ai.system": "openai",
106
+ "gen_ai.request.model": cfg.model,
107
+ "http.url": url
108
+ };
109
+ if (typeof meta.maxTokens === "number")
110
+ attrs["gen_ai.request.max_tokens"] = meta.maxTokens;
111
+ if (typeof meta.turnIndex === "number") attrs["llm.turn"] = meta.turnIndex;
112
+ return cfg.tracer("llm.openai.chat", async (span) => exec(span), attrs);
113
+ };
114
+ var renderTemplate = (template, def, r) => {
115
+ const scope = def.scope ?? {};
116
+ const traceIds = Array.isArray(r.payload?.trace_ids) ? r.payload.trace_ids.filter((x) => typeof x === "string").slice(0, 5).join(", ") : "";
117
+ const subs = {
118
+ title: def.title,
119
+ status: r.status,
120
+ primary: r.primaryValue === null ? "n/a" : String(r.primaryValue),
121
+ baseline: r.baselineValue === null ? "n/a" : String(r.baselineValue),
122
+ delta_pct: r.deltaPct === null ? "n/a" : `${r.deltaPct.toFixed(1)}%`,
123
+ service: typeof scope.service === "string" ? scope.service : "",
124
+ trace_ids: traceIds
125
+ };
126
+ return template.replace(/\{\{(\w+)\}\}/g, (_, k) => subs[k] ?? "");
127
+ };
128
+ var buildNarrativeUserPrompt = ({
129
+ definition,
130
+ current,
131
+ previous
132
+ }) => {
133
+ const lines = [];
134
+ const tmplRendered = renderTemplate(
135
+ definition.narrate?.prompt ?? "",
136
+ definition,
137
+ current
138
+ ).trim();
139
+ if (tmplRendered) lines.push(tmplRendered);
140
+ lines.push("", "Current state:");
141
+ lines.push(`- panel: ${definition.title}`);
142
+ lines.push(`- status: ${current.status}`);
143
+ if (current.primaryValue !== null)
144
+ lines.push(`- primary: ${current.primaryValue}`);
145
+ if (current.baselineValue !== null)
146
+ lines.push(`- baseline (1h): ${current.baselineValue}`);
147
+ if (current.deltaPct !== null)
148
+ lines.push(`- delta vs baseline: ${current.deltaPct.toFixed(1)}%`);
149
+ const since = previous ? Math.max(
150
+ 1,
151
+ Math.round(
152
+ (new Date(current.generatedAt).getTime() - new Date(previous.generatedAt).getTime()) / 6e4
153
+ )
154
+ ) : null;
155
+ if (since !== null) {
156
+ lines.push(
157
+ `- previous run was ${since} minute${since === 1 ? "" : "s"} ago, status=${previous?.status ?? "n/a"}`
158
+ );
159
+ }
160
+ const payloadKeys = Object.keys(current.payload ?? {}).filter(
161
+ (k) => k !== "sparkline"
162
+ );
163
+ if (payloadKeys.length > 0) {
164
+ lines.push("", "Payload details:");
165
+ for (const k of payloadKeys) {
166
+ const v = current.payload[k];
167
+ const rendered = typeof v === "string" || typeof v === "number" || typeof v === "boolean" ? String(v) : JSON.stringify(v);
168
+ if (rendered.length <= 200) lines.push(`- ${k}: ${rendered}`);
169
+ }
170
+ }
171
+ lines.push(
172
+ "",
173
+ "Output: a single sentence (max two), no preamble, no quotes, no Markdown."
174
+ );
175
+ return lines.join("\n");
176
+ };
177
+ async function generateNarrativeOpenAI(req, config) {
178
+ const body = await post(
179
+ config,
180
+ "/chat/completions",
181
+ {
182
+ model: config.model,
183
+ messages: [
184
+ { role: "system", content: NARRATIVE_SYSTEM_PROMPT },
185
+ { role: "user", content: buildNarrativeUserPrompt(req) }
186
+ ],
187
+ max_tokens: 200
188
+ },
189
+ { maxTokens: 200 }
190
+ );
191
+ const text = (body.choices?.[0]?.message?.content ?? "").trim();
192
+ if (body.choices?.[0]?.finish_reason === "length") {
193
+ throw new LlmCallError("openai response truncated by max_tokens");
194
+ }
195
+ if (!text || text === "NO_NARRATIVE") return null;
196
+ return text.replace(/^["']|["']$/g, "").trim();
197
+ }
198
+ var MAX_ITERATIONS = 5;
199
+ var summarizeDefinition = (def) => ({
200
+ id: def.id,
201
+ title: def.title,
202
+ group: def.group,
203
+ source: def.source,
204
+ view: def.view,
205
+ scope: def.scope ?? null
206
+ });
207
+ var summarizeResult = (def, result) => {
208
+ if (!result) {
209
+ return {
210
+ definition: summarizeDefinition(def),
211
+ result: null,
212
+ note: "No result yet \u2014 analysis hasn't run."
213
+ };
214
+ }
215
+ return {
216
+ definition: summarizeDefinition(def),
217
+ result: {
218
+ generatedAt: result.generatedAt,
219
+ status: result.status,
220
+ primaryValue: result.primaryValue,
221
+ baselineValue: result.baselineValue,
222
+ deltaPct: result.deltaPct,
223
+ payload: result.payload,
224
+ narrative: result.narrative
225
+ }
226
+ };
227
+ };
228
+ async function runAskOpenAI(question, deps) {
229
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
230
+ const queries = [];
231
+ const evidence = /* @__PURE__ */ new Map();
232
+ const messages = [
233
+ { role: "system", content: ASK_SYSTEM_PROMPT },
234
+ { role: "user", content: question }
235
+ ];
236
+ for (let i = 0; i < MAX_ITERATIONS; i += 1) {
237
+ const body = await post(
238
+ deps.llm,
239
+ "/chat/completions",
240
+ {
241
+ model: deps.llm.model,
242
+ messages,
243
+ tools: OPENAI_TOOLS,
244
+ max_tokens: 1024
245
+ },
246
+ { maxTokens: 1024, turnIndex: i }
247
+ );
248
+ const choice = body.choices?.[0];
249
+ const message = choice?.message;
250
+ const toolCalls = message?.tool_calls ?? [];
251
+ const finishReason = choice?.finish_reason;
252
+ const text = (message?.content ?? "").trim();
253
+ if ((!toolCalls || toolCalls.length === 0) && (finishReason === "stop" || finishReason === void 0)) {
254
+ return {
255
+ answer: text.length > 0 ? text : null,
256
+ evidence: [...evidence.values()],
257
+ evidenceReferences: askEvidenceReferences(evidence.values()),
258
+ evidenceContract: EVIDENCE_REFERENCE_CONTRACT,
259
+ queries,
260
+ error: text.length > 0 ? null : "model returned no text",
261
+ timestamp: startedAt
262
+ };
263
+ }
264
+ messages.push({
265
+ role: "assistant",
266
+ content: message?.content ?? null,
267
+ tool_calls: toolCalls
268
+ });
269
+ for (const call of toolCalls) {
270
+ const t0 = Date.now();
271
+ let resultPayload;
272
+ let parsedArgs = {};
273
+ try {
274
+ parsedArgs = call.function.arguments ? JSON.parse(call.function.arguments) : {};
275
+ } catch {
276
+ parsedArgs = {};
277
+ }
278
+ if ((!toolCalls || toolCalls.length === 0) && finishReason === "length") {
279
+ return {
280
+ answer: null,
281
+ evidence: [...evidence.values()],
282
+ evidenceReferences: askEvidenceReferences(evidence.values()),
283
+ evidenceContract: EVIDENCE_REFERENCE_CONTRACT,
284
+ queries,
285
+ error: "model response was truncated by max_tokens",
286
+ timestamp: startedAt
287
+ };
288
+ }
289
+ const toolName = call.function.name;
290
+ const tracer = deps.llm.tracer;
291
+ const dispatchTool = async (span) => {
292
+ if (toolName === "list_analyses") {
293
+ const all = await deps.listAnalyses({
294
+ group: typeof parsedArgs.group === "string" ? parsedArgs.group : void 0,
295
+ view: typeof parsedArgs.view === "string" ? parsedArgs.view : void 0
296
+ });
297
+ const out = all.slice(0, 80).map(summarizeDefinition);
298
+ if (span) span.setAttribute("tool.result_count", out.length);
299
+ queries.push({
300
+ tool: "list_analyses",
301
+ args: parsedArgs,
302
+ durationMs: Date.now() - t0
303
+ });
304
+ return out;
305
+ }
306
+ if (toolName === "run_analysis") {
307
+ const id = typeof parsedArgs.id === "string" ? parsedArgs.id : "";
308
+ if (!id) {
309
+ if (span) span.setAttribute("tool.outcome", "missing_id");
310
+ queries.push({
311
+ tool: "run_analysis",
312
+ args: parsedArgs,
313
+ durationMs: Date.now() - t0
314
+ });
315
+ return { error: "id is required" };
316
+ }
317
+ const found = await deps.getLatestResult(id);
318
+ queries.push({
319
+ tool: "run_analysis",
320
+ args: parsedArgs,
321
+ durationMs: Date.now() - t0
322
+ });
323
+ if (!found) {
324
+ if (span) span.setAttribute("tool.outcome", "not_found");
325
+ return { error: `analysis ${id} not found` };
326
+ }
327
+ if (span) {
328
+ span.setAttribute("tool.outcome", "ok");
329
+ span.setAttribute("analysis.id", id);
330
+ if (found.result) {
331
+ span.setAttribute("analysis.status", found.result.status);
332
+ }
333
+ }
334
+ evidence.set(id, {
335
+ analysisId: id,
336
+ definition: found.definition,
337
+ result: found.result
338
+ });
339
+ return summarizeResult(found.definition, found.result);
340
+ }
341
+ if (span) span.setAttribute("tool.outcome", "unknown_tool");
342
+ return { error: `unknown tool: ${toolName}` };
343
+ };
344
+ try {
345
+ if (tracer) {
346
+ resultPayload = await tracer(
347
+ `tool.${toolName}`,
348
+ async (span) => dispatchTool(span),
349
+ {
350
+ "openinference.span.kind": "TOOL",
351
+ "tool.name": toolName,
352
+ "tool.args": JSON.stringify(parsedArgs).slice(0, 512)
353
+ }
354
+ );
355
+ } else {
356
+ resultPayload = await dispatchTool(null);
357
+ }
358
+ } catch (err) {
359
+ resultPayload = {
360
+ error: err instanceof Error ? err.message : String(err)
361
+ };
362
+ }
363
+ messages.push({
364
+ role: "tool",
365
+ tool_call_id: call.id,
366
+ content: JSON.stringify(resultPayload)
367
+ });
368
+ }
369
+ }
370
+ return {
371
+ answer: null,
372
+ evidence: [...evidence.values()],
373
+ evidenceReferences: askEvidenceReferences(evidence.values()),
374
+ evidenceContract: EVIDENCE_REFERENCE_CONTRACT,
375
+ queries,
376
+ error: `iteration cap (${MAX_ITERATIONS}) reached without final answer`,
377
+ timestamp: startedAt
378
+ };
379
+ }
380
+ export {
381
+ generateNarrativeOpenAI,
382
+ runAskOpenAI
383
+ };
384
+ //# sourceMappingURL=openai-FJYOQ7WO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/openai.ts"],"sourcesContent":["/**\n * RFC 0002 Stage 3 + 5 — OpenAI provider adapter.\n *\n * Mirrors `lib/llm.ts` (Anthropic) and the tool-use loop in `lib/ask.ts`,\n * but talks to OpenAI's /chat/completions (function-calling) shape:\n *\n * - Auth: `Authorization: Bearer <key>`\n * - Messages: { role: \"system\" | \"user\" | \"assistant\" | \"tool\", content }\n * - Tools: top-level `tools: [{ type: \"function\", function: { name, description, parameters } }]`\n * - Model emits `message.tool_calls: [{ id, type: \"function\", function: { name, arguments: \"<json string>\" } }]`\n * - Tool results: `{ role: \"tool\", tool_call_id, content }`\n * - Stop: `finish_reason === \"stop\"` for end_turn, `\"tool_calls\"` for tool use\n *\n * Same system prompts as Anthropic — the rendering rules don't change.\n * Same NarrativeRequest / AskRunDeps inputs so the dispatcher in\n * `lib/llm.ts` and `lib/ask.ts` is one-line.\n */\n\nimport type {\n\tAnalysisDefinition,\n\tAnalysisResult,\n\tAskEvidence,\n\tAskQuery,\n\tAskResponse,\n} from \"@obsunified/types\";\nimport { EVIDENCE_REFERENCE_CONTRACT } from \"@obsunified/types\";\nimport type { AskRunDeps } from \"./ask\";\nimport { askEvidenceReferences } from \"./evidence-references\";\nimport type { LlmConfig, NarrativeRequest } from \"./llm\";\nimport { LlmCallError } from \"./llm\";\n\nconst DEFAULT_API_URL = \"https://api.openai.com/v1\";\n\nconst NARRATIVE_SYSTEM_PROMPT = `You write one-sentence operational narratives for a self-hosted observability dashboard.\n\nHard rules — violations are unusable:\n1. Never start with \"I\", \"Here's\", \"We\", \"It looks\", or any first-person/chatbot framing. Write declarative statements about the system.\n2. If you reference a trace, span, log, or service, include the supplied identifier inline (e.g. \"trace abc12345\").\n3. Include a time anchor: \"starting Nm ago\", \"for the last Nm\", \"since HH:MM\" — pull from the inputs.\n4. ≤ 2 sentences total, ≤ 220 characters. Numbers belong in the sentence, not in a separate label.\n5. If the inputs are insufficient to write a useful sentence, output exactly the string NO_NARRATIVE (no quotes).\n\nTone: like a senior on-call writing a Slack message. Specific, blunt, no hedging, no advice (\"you should...\"), no questions.`;\n\nconst ASK_SYSTEM_PROMPT = `You answer operational questions about a self-hosted observability dashboard's telemetry. Use the supplied tools — do not invent numbers.\n\nWorkflow:\n1. Use list_analyses to see what's available. Filter by group when it helps.\n2. Pick the analyses most likely to answer the question. Call run_analysis on each (typically 1–3, max 5).\n3. Read the results. Synthesize a 1–2 sentence answer.\n\nHard rules — violations are unusable:\n1. Never start with \"I\", \"Here's\", \"We\", or any chatbot framing. Write declarative statements about the system.\n2. Cite the analyses you consulted by id, e.g. \"(latency_p95_overall)\".\n3. Include a time anchor when describing a number (\"in the last 5 minutes\", \"vs the prior hour\", \"for the past 24h\").\n4. ≤ 2 sentences, ≤ 280 characters total. Numbers belong in the sentence.\n5. If the available analyses don't cover the question, say exactly that and name the closest analysis you found.\n\nOutput: write only the final answer text. No preamble, no JSON.`;\n\nconst OPENAI_TOOLS = [\n\t{\n\t\ttype: \"function\" as const,\n\t\tfunction: {\n\t\t\tname: \"list_analyses\",\n\t\t\tdescription:\n\t\t\t\t\"List registered analyses for the current project. Optionally filter by group (Health, Services, Dependencies, Async, AI, Frontend, Custom). Returns id, title, group, source, view, and scope (when set).\",\n\t\t\tparameters: {\n\t\t\t\ttype: \"object\",\n\t\t\t\tproperties: {\n\t\t\t\t\tgroup: { type: \"string\" },\n\t\t\t\t\tview: { type: \"string\" },\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\ttype: \"function\" as const,\n\t\tfunction: {\n\t\t\tname: \"run_analysis\",\n\t\t\tdescription:\n\t\t\t\t\"Read the latest result for one analysis by id. Returns status, primary value, baseline, delta_pct, and the analysis-specific payload.\",\n\t\t\tparameters: {\n\t\t\t\ttype: \"object\",\n\t\t\t\tproperties: {\n\t\t\t\t\tid: { type: \"string\" },\n\t\t\t\t},\n\t\t\t\trequired: [\"id\"],\n\t\t\t},\n\t\t},\n\t},\n];\n\ninterface OpenAiToolCall {\n\tid: string;\n\ttype: \"function\";\n\tfunction: { name: string; arguments: string };\n}\n\ninterface OpenAiMessage {\n\trole: \"system\" | \"user\" | \"assistant\" | \"tool\";\n\tcontent?: string | null;\n\ttool_calls?: OpenAiToolCall[];\n\ttool_call_id?: string;\n}\n\ninterface OpenAiChoice {\n\tindex?: number;\n\tmessage?: OpenAiMessage;\n\tfinish_reason?: string;\n}\n\ninterface OpenAiResponse {\n\tchoices?: OpenAiChoice[];\n\tusage?: {\n\t\tprompt_tokens?: number;\n\t\tcompletion_tokens?: number;\n\t\ttotal_tokens?: number;\n\t};\n}\n\nconst baseUrl = (cfg: LlmConfig) =>\n\t(cfg.apiUrl ?? DEFAULT_API_URL).replace(/\\/$/, \"\");\n\ninterface PostMeta {\n\tmaxTokens?: number;\n\tturnIndex?: number;\n}\n\nconst post = async <T extends { usage?: OpenAiResponse[\"usage\"] }>(\n\tcfg: LlmConfig,\n\tpath: string,\n\tbody: unknown,\n\tmeta: PostMeta = {},\n): Promise<T> => {\n\tconst url = `${baseUrl(cfg)}${path}`;\n\tconst exec = async (\n\t\tspan: { setAttribute(k: string, v: unknown): void } | null,\n\t): Promise<T> => {\n\t\tconst response = await fetch(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${cfg.apiKey}`,\n\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify(body),\n\t\t\tsignal: cfg.signal,\n\t\t});\n\t\tif (span) span.setAttribute(\"http.response.status_code\", response.status);\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tthrow new LlmCallError(\n\t\t\t\t`openai ${response.status}: ${text.slice(0, 200)}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\t\tconst json = (await response.json()) as T;\n\t\tif (span && json.usage) {\n\t\t\tif (typeof json.usage.prompt_tokens === \"number\")\n\t\t\t\tspan.setAttribute(\n\t\t\t\t\t\"gen_ai.usage.input_tokens\",\n\t\t\t\t\tjson.usage.prompt_tokens,\n\t\t\t\t);\n\t\t\tif (typeof json.usage.completion_tokens === \"number\")\n\t\t\t\tspan.setAttribute(\n\t\t\t\t\t\"gen_ai.usage.output_tokens\",\n\t\t\t\t\tjson.usage.completion_tokens,\n\t\t\t\t);\n\t\t\tif (typeof json.usage.total_tokens === \"number\")\n\t\t\t\tspan.setAttribute(\"gen_ai.usage.total_tokens\", json.usage.total_tokens);\n\t\t}\n\t\treturn json;\n\t};\n\n\tif (!cfg.tracer) return exec(null);\n\tconst attrs: Record<string, unknown> = {\n\t\t\"openinference.span.kind\": \"LLM\",\n\t\t\"gen_ai.system\": \"openai\",\n\t\t\"gen_ai.request.model\": cfg.model,\n\t\t\"http.url\": url,\n\t};\n\tif (typeof meta.maxTokens === \"number\")\n\t\tattrs[\"gen_ai.request.max_tokens\"] = meta.maxTokens;\n\tif (typeof meta.turnIndex === \"number\") attrs[\"llm.turn\"] = meta.turnIndex;\n\treturn cfg.tracer(\"llm.openai.chat\", async (span) => exec(span), attrs);\n};\n\n// ── narrative (single-shot) ────────────────────────────────────────────────\n\nconst renderTemplate = (\n\ttemplate: string,\n\tdef: AnalysisDefinition,\n\tr: AnalysisResult,\n): string => {\n\tconst scope = (def.scope ?? {}) as Record<string, unknown>;\n\tconst traceIds = Array.isArray(r.payload?.trace_ids)\n\t\t? (r.payload.trace_ids as unknown[])\n\t\t\t\t.filter((x): x is string => typeof x === \"string\")\n\t\t\t\t.slice(0, 5)\n\t\t\t\t.join(\", \")\n\t\t: \"\";\n\tconst subs: Record<string, string> = {\n\t\ttitle: def.title,\n\t\tstatus: r.status,\n\t\tprimary: r.primaryValue === null ? \"n/a\" : String(r.primaryValue),\n\t\tbaseline: r.baselineValue === null ? \"n/a\" : String(r.baselineValue),\n\t\tdelta_pct: r.deltaPct === null ? \"n/a\" : `${r.deltaPct.toFixed(1)}%`,\n\t\tservice: typeof scope.service === \"string\" ? scope.service : \"\",\n\t\ttrace_ids: traceIds,\n\t};\n\treturn template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, k) => subs[k] ?? \"\");\n};\n\nconst buildNarrativeUserPrompt = ({\n\tdefinition,\n\tcurrent,\n\tprevious,\n}: NarrativeRequest): string => {\n\tconst lines: string[] = [];\n\tconst tmplRendered = renderTemplate(\n\t\tdefinition.narrate?.prompt ?? \"\",\n\t\tdefinition,\n\t\tcurrent,\n\t).trim();\n\tif (tmplRendered) lines.push(tmplRendered);\n\n\tlines.push(\"\", \"Current state:\");\n\tlines.push(`- panel: ${definition.title}`);\n\tlines.push(`- status: ${current.status}`);\n\tif (current.primaryValue !== null)\n\t\tlines.push(`- primary: ${current.primaryValue}`);\n\tif (current.baselineValue !== null)\n\t\tlines.push(`- baseline (1h): ${current.baselineValue}`);\n\tif (current.deltaPct !== null)\n\t\tlines.push(`- delta vs baseline: ${current.deltaPct.toFixed(1)}%`);\n\n\tconst since = previous\n\t\t? Math.max(\n\t\t\t\t1,\n\t\t\t\tMath.round(\n\t\t\t\t\t(new Date(current.generatedAt).getTime() -\n\t\t\t\t\t\tnew Date(previous.generatedAt).getTime()) /\n\t\t\t\t\t\t60000,\n\t\t\t\t),\n\t\t\t)\n\t\t: null;\n\tif (since !== null) {\n\t\tlines.push(\n\t\t\t`- previous run was ${since} minute${since === 1 ? \"\" : \"s\"} ago, status=${previous?.status ?? \"n/a\"}`,\n\t\t);\n\t}\n\n\tconst payloadKeys = Object.keys(current.payload ?? {}).filter(\n\t\t(k) => k !== \"sparkline\",\n\t);\n\tif (payloadKeys.length > 0) {\n\t\tlines.push(\"\", \"Payload details:\");\n\t\tfor (const k of payloadKeys) {\n\t\t\tconst v = current.payload[k];\n\t\t\tconst rendered =\n\t\t\t\ttypeof v === \"string\" || typeof v === \"number\" || typeof v === \"boolean\"\n\t\t\t\t\t? String(v)\n\t\t\t\t\t: JSON.stringify(v);\n\t\t\tif (rendered.length <= 200) lines.push(`- ${k}: ${rendered}`);\n\t\t}\n\t}\n\tlines.push(\n\t\t\"\",\n\t\t\"Output: a single sentence (max two), no preamble, no quotes, no Markdown.\",\n\t);\n\treturn lines.join(\"\\n\");\n};\n\nexport async function generateNarrativeOpenAI(\n\treq: NarrativeRequest,\n\tconfig: LlmConfig,\n): Promise<string | null> {\n\tconst body = await post<OpenAiResponse>(\n\t\tconfig,\n\t\t\"/chat/completions\",\n\t\t{\n\t\t\tmodel: config.model,\n\t\t\tmessages: [\n\t\t\t\t{ role: \"system\", content: NARRATIVE_SYSTEM_PROMPT },\n\t\t\t\t{ role: \"user\", content: buildNarrativeUserPrompt(req) },\n\t\t\t],\n\t\t\tmax_tokens: 200,\n\t\t},\n\t\t{ maxTokens: 200 },\n\t);\n\tconst text = (body.choices?.[0]?.message?.content ?? \"\").trim();\n\tif (body.choices?.[0]?.finish_reason === \"length\") {\n\t\tthrow new LlmCallError(\"openai response truncated by max_tokens\");\n\t}\n\tif (!text || text === \"NO_NARRATIVE\") return null;\n\treturn text.replace(/^[\"']|[\"']$/g, \"\").trim();\n}\n\n// ── ask (tool-use loop) ────────────────────────────────────────────────────\n\nconst MAX_ITERATIONS = 5;\n\nconst summarizeDefinition = (def: AnalysisDefinition) => ({\n\tid: def.id,\n\ttitle: def.title,\n\tgroup: def.group,\n\tsource: def.source,\n\tview: def.view,\n\tscope: def.scope ?? null,\n});\n\nconst summarizeResult = (\n\tdef: AnalysisDefinition,\n\tresult: AnalysisResult | null,\n) => {\n\tif (!result) {\n\t\treturn {\n\t\t\tdefinition: summarizeDefinition(def),\n\t\t\tresult: null,\n\t\t\tnote: \"No result yet — analysis hasn't run.\",\n\t\t};\n\t}\n\treturn {\n\t\tdefinition: summarizeDefinition(def),\n\t\tresult: {\n\t\t\tgeneratedAt: result.generatedAt,\n\t\t\tstatus: result.status,\n\t\t\tprimaryValue: result.primaryValue,\n\t\t\tbaselineValue: result.baselineValue,\n\t\t\tdeltaPct: result.deltaPct,\n\t\t\tpayload: result.payload,\n\t\t\tnarrative: result.narrative,\n\t\t},\n\t};\n};\n\nexport async function runAskOpenAI(\n\tquestion: string,\n\tdeps: AskRunDeps,\n): Promise<AskResponse> {\n\tconst startedAt = new Date().toISOString();\n\tconst queries: AskQuery[] = [];\n\tconst evidence = new Map<string, AskEvidence>();\n\n\tconst messages: OpenAiMessage[] = [\n\t\t{ role: \"system\", content: ASK_SYSTEM_PROMPT },\n\t\t{ role: \"user\", content: question },\n\t];\n\n\tfor (let i = 0; i < MAX_ITERATIONS; i += 1) {\n\t\tconst body = await post<OpenAiResponse>(\n\t\t\tdeps.llm,\n\t\t\t\"/chat/completions\",\n\t\t\t{\n\t\t\t\tmodel: deps.llm.model,\n\t\t\t\tmessages,\n\t\t\t\ttools: OPENAI_TOOLS,\n\t\t\t\tmax_tokens: 1024,\n\t\t\t},\n\t\t\t{ maxTokens: 1024, turnIndex: i },\n\t\t);\n\t\tconst choice = body.choices?.[0];\n\t\tconst message = choice?.message;\n\t\tconst toolCalls = message?.tool_calls ?? [];\n\t\tconst finishReason = choice?.finish_reason;\n\t\tconst text = (message?.content ?? \"\").trim();\n\n\t\t// Final answer.\n\t\tif (\n\t\t\t(!toolCalls || toolCalls.length === 0) &&\n\t\t\t(finishReason === \"stop\" || finishReason === undefined)\n\t\t) {\n\t\t\treturn {\n\t\t\t\tanswer: text.length > 0 ? text : null,\n\t\t\t\tevidence: [...evidence.values()],\n\t\t\t\tevidenceReferences: askEvidenceReferences(evidence.values()),\n\t\t\t\tevidenceContract: EVIDENCE_REFERENCE_CONTRACT,\n\t\t\t\tqueries,\n\t\t\t\terror: text.length > 0 ? null : \"model returned no text\",\n\t\t\t\ttimestamp: startedAt,\n\t\t\t};\n\t\t}\n\n\t\t// Append assistant message verbatim — must include tool_calls for the\n\t\t// next turn's tool result messages to be valid.\n\t\tmessages.push({\n\t\t\trole: \"assistant\",\n\t\t\tcontent: message?.content ?? null,\n\t\t\ttool_calls: toolCalls,\n\t\t});\n\n\t\t// Execute each requested tool.\n\t\tfor (const call of toolCalls) {\n\t\t\tconst t0 = Date.now();\n\t\t\tlet resultPayload: unknown;\n\t\t\tlet parsedArgs: Record<string, unknown> = {};\n\t\t\ttry {\n\t\t\t\tparsedArgs = call.function.arguments\n\t\t\t\t\t? (JSON.parse(call.function.arguments) as Record<string, unknown>)\n\t\t\t\t\t: {};\n\t\t\t} catch {\n\t\t\t\tparsedArgs = {};\n\t\t\t}\n\t\t\tif ((!toolCalls || toolCalls.length === 0) && finishReason === \"length\") {\n\t\t\t\treturn {\n\t\t\t\t\tanswer: null,\n\t\t\t\t\tevidence: [...evidence.values()],\n\t\t\t\t\tevidenceReferences: askEvidenceReferences(evidence.values()),\n\t\t\t\t\tevidenceContract: EVIDENCE_REFERENCE_CONTRACT,\n\t\t\t\t\tqueries,\n\t\t\t\t\terror: \"model response was truncated by max_tokens\",\n\t\t\t\t\ttimestamp: startedAt,\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst toolName = call.function.name;\n\t\t\tconst tracer = deps.llm.tracer;\n\t\t\tconst dispatchTool = async (\n\t\t\t\tspan: { setAttribute(k: string, v: unknown): void } | null,\n\t\t\t): Promise<unknown> => {\n\t\t\t\tif (toolName === \"list_analyses\") {\n\t\t\t\t\tconst all = await deps.listAnalyses({\n\t\t\t\t\t\tgroup:\n\t\t\t\t\t\t\ttypeof parsedArgs.group === \"string\"\n\t\t\t\t\t\t\t\t? parsedArgs.group\n\t\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\tview:\n\t\t\t\t\t\t\ttypeof parsedArgs.view === \"string\" ? parsedArgs.view : undefined,\n\t\t\t\t\t});\n\t\t\t\t\tconst out = all.slice(0, 80).map(summarizeDefinition);\n\t\t\t\t\tif (span) span.setAttribute(\"tool.result_count\", out.length);\n\t\t\t\t\tqueries.push({\n\t\t\t\t\t\ttool: \"list_analyses\",\n\t\t\t\t\t\targs: parsedArgs,\n\t\t\t\t\t\tdurationMs: Date.now() - t0,\n\t\t\t\t\t});\n\t\t\t\t\treturn out;\n\t\t\t\t}\n\t\t\t\tif (toolName === \"run_analysis\") {\n\t\t\t\t\tconst id = typeof parsedArgs.id === \"string\" ? parsedArgs.id : \"\";\n\t\t\t\t\tif (!id) {\n\t\t\t\t\t\tif (span) span.setAttribute(\"tool.outcome\", \"missing_id\");\n\t\t\t\t\t\tqueries.push({\n\t\t\t\t\t\t\ttool: \"run_analysis\",\n\t\t\t\t\t\t\targs: parsedArgs,\n\t\t\t\t\t\t\tdurationMs: Date.now() - t0,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn { error: \"id is required\" };\n\t\t\t\t\t}\n\t\t\t\t\tconst found = await deps.getLatestResult(id);\n\t\t\t\t\tqueries.push({\n\t\t\t\t\t\ttool: \"run_analysis\",\n\t\t\t\t\t\targs: parsedArgs,\n\t\t\t\t\t\tdurationMs: Date.now() - t0,\n\t\t\t\t\t});\n\t\t\t\t\tif (!found) {\n\t\t\t\t\t\tif (span) span.setAttribute(\"tool.outcome\", \"not_found\");\n\t\t\t\t\t\treturn { error: `analysis ${id} not found` };\n\t\t\t\t\t}\n\t\t\t\t\tif (span) {\n\t\t\t\t\t\tspan.setAttribute(\"tool.outcome\", \"ok\");\n\t\t\t\t\t\tspan.setAttribute(\"analysis.id\", id);\n\t\t\t\t\t\tif (found.result) {\n\t\t\t\t\t\t\tspan.setAttribute(\"analysis.status\", found.result.status);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tevidence.set(id, {\n\t\t\t\t\t\tanalysisId: id,\n\t\t\t\t\t\tdefinition: found.definition,\n\t\t\t\t\t\tresult: found.result,\n\t\t\t\t\t});\n\t\t\t\t\treturn summarizeResult(found.definition, found.result);\n\t\t\t\t}\n\t\t\t\tif (span) span.setAttribute(\"tool.outcome\", \"unknown_tool\");\n\t\t\t\treturn { error: `unknown tool: ${toolName}` };\n\t\t\t};\n\t\t\ttry {\n\t\t\t\tif (tracer) {\n\t\t\t\t\tresultPayload = await tracer(\n\t\t\t\t\t\t`tool.${toolName}`,\n\t\t\t\t\t\tasync (span) => dispatchTool(span),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"openinference.span.kind\": \"TOOL\",\n\t\t\t\t\t\t\t\"tool.name\": toolName,\n\t\t\t\t\t\t\t\"tool.args\": JSON.stringify(parsedArgs).slice(0, 512),\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tresultPayload = await dispatchTool(null);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tresultPayload = {\n\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t};\n\t\t\t}\n\t\t\tmessages.push({\n\t\t\t\trole: \"tool\",\n\t\t\t\ttool_call_id: call.id,\n\t\t\t\tcontent: JSON.stringify(resultPayload),\n\t\t\t});\n\t\t}\n\t}\n\n\treturn {\n\t\tanswer: null,\n\t\tevidence: [...evidence.values()],\n\t\tevidenceReferences: askEvidenceReferences(evidence.values()),\n\t\tevidenceContract: EVIDENCE_REFERENCE_CONTRACT,\n\t\tqueries,\n\t\terror: `iteration cap (${MAX_ITERATIONS}) reached without final answer`,\n\t\ttimestamp: startedAt,\n\t};\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@obsunified/collector",
3
+ "version": "2.0.1",
4
+ "description": "Plugin-based observability collector runtime with pluggable SQL/blob storage.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/obs-unified/obs-unified.git",
9
+ "directory": "packages/obs-collector"
10
+ },
11
+ "keywords": [
12
+ "observability",
13
+ "opentelemetry",
14
+ "otlp",
15
+ "collector",
16
+ "cloudflare-workers",
17
+ "obs-unified"
18
+ ],
19
+ "type": "module",
20
+ "main": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js"
26
+ },
27
+ "./auth": {
28
+ "types": "./dist/auth.d.ts",
29
+ "import": "./dist/auth.js"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "src",
35
+ "migrations",
36
+ "README.md"
37
+ ],
38
+ "sideEffects": false,
39
+ "dependencies": {
40
+ "@bufbuild/protobuf": "^2.11.0",
41
+ "@obsunified/pprof-decoder": "2.0.1",
42
+ "@obsunified/types": "2.0.1"
43
+ },
44
+ "peerDependencies": {
45
+ "hono": "^4.7.0",
46
+ "pg": "^8.13.0"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "pg": {
50
+ "optional": true
51
+ }
52
+ },
53
+ "devDependencies": {
54
+ "@bufbuild/buf": "^1.68.1",
55
+ "@bufbuild/protoc-gen-es": "^2.11.0",
56
+ "@types/pg": "^8.11.10",
57
+ "hono": "^4.12.11",
58
+ "pg": "^8.13.1",
59
+ "tsup": "^8.3.5",
60
+ "typescript": "^6.0.2"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public",
64
+ "registry": "https://registry.npmjs.org/"
65
+ },
66
+ "scripts": {
67
+ "build": "tsup",
68
+ "type-check": "tsc --noEmit",
69
+ "test": "vitest run --passWithNoTests",
70
+ "bench": "vitest bench --run",
71
+ "codegen": "PATH=\"$PWD/node_modules/.bin:$PATH\" buf generate"
72
+ }
73
+ }