@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.
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/auth.d.ts +2 -0
- package/dist/auth.js +9 -0
- package/dist/auth.js.map +1 -0
- package/dist/chunk-UGWDOGGH.js +319 -0
- package/dist/chunk-UGWDOGGH.js.map +1 -0
- package/dist/chunk-XKQTVIRE.js +671 -0
- package/dist/chunk-XKQTVIRE.js.map +1 -0
- package/dist/index.d.ts +1197 -0
- package/dist/index.js +17707 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest-auth-HFVO1zIp.d.ts +165 -0
- package/dist/openai-FJYOQ7WO.js +384 -0
- package/dist/openai-FJYOQ7WO.js.map +1 -0
- package/package.json +73 -0
- package/src/ai-parity/ai-parity.acceptance.test.ts +449 -0
- package/src/analyses/derive.ts +642 -0
- package/src/analyses/index.ts +50 -0
- package/src/analyses/investigations.ts +277 -0
- package/src/analyses/tier0.ts +568 -0
- package/src/auth/dashboard-auth.ts +269 -0
- package/src/auth/ingest-auth.ts +147 -0
- package/src/durable-objects/tail-hub.test.ts +32 -0
- package/src/durable-objects/tail-hub.ts +158 -0
- package/src/framework/collector.ts +467 -0
- package/src/framework/env.ts +90 -0
- package/src/framework/logger.ts +81 -0
- package/src/index.ts +211 -0
- package/src/lib/action-aggregates.ts +1031 -0
- package/src/lib/action-identity.ts +106 -0
- package/src/lib/actions-store.ts +397 -0
- package/src/lib/ai-pricing.ts +80 -0
- package/src/lib/ai-store/calls.ts +148 -0
- package/src/lib/ai-store/evaluations.test.ts +47 -0
- package/src/lib/ai-store/evaluations.ts +125 -0
- package/src/lib/ai-store/helpers.ts +37 -0
- package/src/lib/ai-store/retention.ts +17 -0
- package/src/lib/ai-store/sessions.ts +277 -0
- package/src/lib/ai-store/spans.ts +109 -0
- package/src/lib/ai-store/types.ts +112 -0
- package/src/lib/ai-store.ts +76 -0
- package/src/lib/alerts-store.ts +458 -0
- package/src/lib/analyses-runner.ts +439 -0
- package/src/lib/analyses-store.test.ts +243 -0
- package/src/lib/analyses-store.ts +454 -0
- package/src/lib/ask.test.ts +302 -0
- package/src/lib/ask.ts +390 -0
- package/src/lib/blob-store-s3.ts +213 -0
- package/src/lib/blob-store.ts +366 -0
- package/src/lib/eval-cases-store.test.ts +171 -0
- package/src/lib/eval-cases-store.ts +1033 -0
- package/src/lib/evidence-references.test.ts +10 -0
- package/src/lib/evidence-references.ts +684 -0
- package/src/lib/hash.ts +26 -0
- package/src/lib/identity-index/action-lookups.ts +805 -0
- package/src/lib/identity-index/constants.ts +1 -0
- package/src/lib/identity-index/key-lookups.ts +199 -0
- package/src/lib/identity-index/mappers.ts +349 -0
- package/src/lib/identity-index/types.ts +196 -0
- package/src/lib/identity-index/user-lookup.ts +137 -0
- package/src/lib/identity-index.test.ts +624 -0
- package/src/lib/identity-index.ts +85 -0
- package/src/lib/json.ts +39 -0
- package/src/lib/llm.ts +263 -0
- package/src/lib/logs-store.ts +188 -0
- package/src/lib/metrics-store.test.ts +189 -0
- package/src/lib/metrics-store.ts +312 -0
- package/src/lib/narrate-gate.test.ts +260 -0
- package/src/lib/narrate-gate.ts +253 -0
- package/src/lib/openai.test.ts +300 -0
- package/src/lib/openai.ts +512 -0
- package/src/lib/otlp.ts +212 -0
- package/src/lib/parse-pprof.test.ts +64 -0
- package/src/lib/parse-pprof.ts +184 -0
- package/src/lib/payload-capture.ts +22 -0
- package/src/lib/projects-store.ts +207 -0
- package/src/lib/propagation-metric.test.ts +196 -0
- package/src/lib/propagation-metric.ts +175 -0
- package/src/lib/sql-db-dialect.test.ts +22 -0
- package/src/lib/sql-db-postgres.ts +229 -0
- package/src/lib/sql-db.ts +166 -0
- package/src/lib/stage6.test.ts +216 -0
- package/src/lib/store/export.ts +65 -0
- package/src/lib/store/helpers.ts +367 -0
- package/src/lib/store/ingest.ts +68 -0
- package/src/lib/store/issues.ts +158 -0
- package/src/lib/store/overview.ts +127 -0
- package/src/lib/store/retention.ts +10 -0
- package/src/lib/store/service-map.ts +384 -0
- package/src/lib/store/trace-detail.bench.ts +76 -0
- package/src/lib/store/trace-detail.test.ts +410 -0
- package/src/lib/store/trace-detail.ts +427 -0
- package/src/lib/store/trace-queries.test.ts +29 -0
- package/src/lib/store/trace-queries.ts +89 -0
- package/src/lib/store-overview.test.ts +77 -0
- package/src/lib/store.ts +96 -0
- package/src/lib/tail-publisher.ts +64 -0
- package/src/lib/test-utils/mem-sql-db.test.ts +93 -0
- package/src/lib/test-utils/mem-sql-db.ts +86 -0
- package/src/lib/ua-parser.ts +61 -0
- package/src/lib/usage-store.ts +567 -0
- package/src/migrations/001_telemetry_spans.sql +28 -0
- package/src/migrations/002_usage_events.sql +24 -0
- package/src/migrations/003_usage_analytics_columns.sql +13 -0
- package/src/migrations/004_logs.sql +19 -0
- package/src/migrations/005_ai_calls.sql +25 -0
- package/src/migrations/006_user_profiles.sql +11 -0
- package/src/migrations/007_session_replay_metadata.sql +10 -0
- package/src/migrations/008_session_replay_storage_bytes.sql +1 -0
- package/src/migrations/009_projects_and_keys.sql +22 -0
- package/src/migrations/010_signal_project_id.sql +15 -0
- package/src/migrations/011_default_project_seed.sql +3 -0
- package/src/migrations/012_alert_rules.sql +17 -0
- package/src/migrations/013_alert_evaluations.sql +13 -0
- package/src/migrations/014_alert_state.sql +9 -0
- package/src/migrations/015_metrics.sql +58 -0
- package/src/migrations/016_metrics_exp_histogram_summary.sql +5 -0
- package/src/migrations/017_span_dropped_counts.sql +11 -0
- package/src/migrations/018_log_flags.sql +8 -0
- package/src/migrations/019_ai_span_payloads.sql +30 -0
- package/src/migrations/020_ai_span_evaluations.sql +33 -0
- package/src/migrations/021_ai_span_session.sql +13 -0
- package/src/migrations/022_span_log_session_id.sql +13 -0
- package/src/migrations/023_service_map_indexes.sql +32 -0
- package/src/migrations/024_analysis_results.sql +49 -0
- package/src/migrations/025_stage6_autopinning_alerts.sql +38 -0
- package/src/migrations/026_obs_dashboard_project_seed.sql +18 -0
- package/src/migrations/027_identity_propagation.sql +50 -0
- package/src/migrations/028_profile_blobs.sql +57 -0
- package/src/migrations/029_telemetry_sdk_name.sql +20 -0
- package/src/migrations/030_analysis_claim.sql +18 -0
- package/src/migrations/031_agent_action_graph.sql +154 -0
- package/src/migrations/032_payload_capture_flags.sql +8 -0
- package/src/migrations/033_eval_cases.sql +52 -0
- package/src/migrations/034_eval_case_results.sql +23 -0
- package/src/migrations/035_metric_exemplars.sql +33 -0
- package/src/migrations/036_trace_instrumentation_gaps.sql +19 -0
- package/src/migrations/037_eval_runs.sql +37 -0
- package/src/migrations/038_tool_call_evidence.sql +11 -0
- package/src/migrations/039_drop_trace_instrumentation_gaps.sql +11 -0
- package/src/migrations/040_evidence_retrieval_refs.sql +50 -0
- package/src/migrations-postgres/001_telemetry_spans.sql +33 -0
- package/src/migrations-postgres/002_usage_events.sql +24 -0
- package/src/migrations-postgres/003_usage_analytics_columns.sql +13 -0
- package/src/migrations-postgres/004_logs.sql +19 -0
- package/src/migrations-postgres/005_ai_calls.sql +25 -0
- package/src/migrations-postgres/006_user_profiles.sql +11 -0
- package/src/migrations-postgres/007_session_replay_metadata.sql +10 -0
- package/src/migrations-postgres/008_session_replay_storage_bytes.sql +2 -0
- package/src/migrations-postgres/009_projects_and_keys.sql +25 -0
- package/src/migrations-postgres/010_signal_project_id.sql +18 -0
- package/src/migrations-postgres/011_default_project_seed.sql +5 -0
- package/src/migrations-postgres/012_alert_rules.sql +17 -0
- package/src/migrations-postgres/013_alert_evaluations.sql +13 -0
- package/src/migrations-postgres/014_alert_state.sql +9 -0
- package/src/migrations-postgres/015_metrics.sql +47 -0
- package/src/migrations-postgres/016_metrics_exp_histogram_summary.sql +1 -0
- package/src/migrations-postgres/017_span_dropped_counts.sql +4 -0
- package/src/migrations-postgres/018_log_flags.sql +2 -0
- package/src/migrations-postgres/019_ai_span_payloads.sql +18 -0
- package/src/migrations-postgres/020_ai_span_evaluations.sql +21 -0
- package/src/migrations-postgres/021_ai_span_session.sql +7 -0
- package/src/migrations-postgres/022_span_log_session_id.sql +7 -0
- package/src/migrations-postgres/023_service_map_indexes.sql +9 -0
- package/src/migrations-postgres/024_analysis_results.sql +41 -0
- package/src/migrations-postgres/025_stage6_autopinning_alerts.sql +17 -0
- package/src/migrations-postgres/026_obs_dashboard_project_seed.sql +14 -0
- package/src/migrations-postgres/027_identity_propagation.sql +27 -0
- package/src/migrations-postgres/028_profile_blobs.sql +48 -0
- package/src/migrations-postgres/029_telemetry_sdk_name.sql +5 -0
- package/src/migrations-postgres/030_analysis_claim.sql +5 -0
- package/src/migrations-postgres/031_agent_action_graph.sql +190 -0
- package/src/migrations-postgres/032_payload_capture_flags.sql +8 -0
- package/src/migrations-postgres/033_eval_cases.sql +55 -0
- package/src/migrations-postgres/034_eval_case_results.sql +26 -0
- package/src/migrations-postgres/035_metric_exemplars.sql +31 -0
- package/src/migrations-postgres/036_trace_instrumentation_gaps.sql +19 -0
- package/src/migrations-postgres/037_eval_runs.sql +40 -0
- package/src/migrations-postgres/038_tool_call_evidence.sql +11 -0
- package/src/migrations-postgres/039_drop_trace_instrumentation_gaps.sql +11 -0
- package/src/migrations-postgres/040_evidence_retrieval_refs.sql +47 -0
- package/src/migrations-postgres/README.md +40 -0
- package/src/otlp/acceptance.test.ts +373 -0
- package/src/otlp/decode/body.ts +127 -0
- package/src/otlp/decode/logs.ts +96 -0
- package/src/otlp/decode/metrics.ts +402 -0
- package/src/otlp/decode/traces.ts +92 -0
- package/src/otlp/decode/values.ts +150 -0
- package/src/otlp/decode.test.ts +800 -0
- package/src/otlp/decode.ts +11 -0
- package/src/otlp/gen/opentelemetry/proto/collector/logs/v1/logs_service_pb.ts +132 -0
- package/src/otlp/gen/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.ts +158 -0
- package/src/otlp/gen/opentelemetry/proto/collector/trace/v1/trace_service_pb.ts +158 -0
- package/src/otlp/gen/opentelemetry/proto/common/v1/common_pb.ts +289 -0
- package/src/otlp/gen/opentelemetry/proto/logs/v1/logs_pb.ts +397 -0
- package/src/otlp/gen/opentelemetry/proto/metrics/v1/metrics_pb.ts +1329 -0
- package/src/otlp/gen/opentelemetry/proto/resource/v1/resource_pb.ts +86 -0
- package/src/otlp/gen/opentelemetry/proto/trace/v1/trace_pb.ts +710 -0
- package/src/otlp/response.test.ts +158 -0
- package/src/otlp/response.ts +129 -0
- package/src/plugins/_context.ts +15 -0
- package/src/plugins/action-graph-processor/enrichers.ts +45 -0
- package/src/plugins/action-graph-processor/redaction.ts +84 -0
- package/src/plugins/action-graph-processor.test.ts +1023 -0
- package/src/plugins/action-graph-processor.ts +1172 -0
- package/src/plugins/action-routes.test.ts +716 -0
- package/src/plugins/action-routes.ts +343 -0
- package/src/plugins/ai-receiver.ts +254 -0
- package/src/plugins/ai-span-payloads-processor.test.ts +90 -0
- package/src/plugins/ai-span-payloads-processor.ts +180 -0
- package/src/plugins/alerts-evaluator.test.ts +91 -0
- package/src/plugins/alerts-evaluator.ts +328 -0
- package/src/plugins/alerts-routes.ts +101 -0
- package/src/plugins/analyses-routes.ts +138 -0
- package/src/plugins/ask-routes.ts +182 -0
- package/src/plugins/bot-filter.ts +19 -0
- package/src/plugins/connected-routes/manifest.ts +531 -0
- package/src/plugins/connected-routes.test.ts +1791 -0
- package/src/plugins/connected-routes.ts +1103 -0
- package/src/plugins/dashboard-routes.ts +41 -0
- package/src/plugins/default-span-enrichment.ts +92 -0
- package/src/plugins/eval-cases-routes.test.ts +391 -0
- package/src/plugins/eval-cases-routes.ts +529 -0
- package/src/plugins/evidence-retrieval-routes.benchmark.test.ts +245 -0
- package/src/plugins/evidence-retrieval-routes.test.ts +932 -0
- package/src/plugins/evidence-retrieval-routes.ts +2179 -0
- package/src/plugins/gen-ai-normalizer.test.ts +680 -0
- package/src/plugins/gen-ai-normalizer.ts +302 -0
- package/src/plugins/identity-receiver.ts +96 -0
- package/src/plugins/issue-enrichment.ts +132 -0
- package/src/plugins/issue-insights.ts +81 -0
- package/src/plugins/logs-receiver.ts +160 -0
- package/src/plugins/metrics-receiver.ts +91 -0
- package/src/plugins/onboarding-routes.ts +54 -0
- package/src/plugins/otlp-receiver.ts +124 -0
- package/src/plugins/platform-routes.ts +165 -0
- package/src/plugins/profile-routes.ts +468 -0
- package/src/plugins/projects-routes.ts +92 -0
- package/src/plugins/query-routes.ts +220 -0
- package/src/plugins/redaction-processor.ts +132 -0
- package/src/plugins/replay-query-routes.test.ts +38 -0
- package/src/plugins/replay-query-routes.ts +199 -0
- package/src/plugins/replay-receiver.ts +81 -0
- package/src/plugins/tail-routes.test.ts +47 -0
- package/src/plugins/tail-routes.ts +42 -0
- package/src/plugins/timeline-routes.ts +244 -0
- package/src/plugins/ua-enrichment.ts +27 -0
- package/src/plugins/usage-normalization.ts +46 -0
- package/src/plugins/usage-privacy.ts +55 -0
- package/src/plugins/usage-query-routes.ts +102 -0
- package/src/plugins/usage-receiver.ts +129 -0
- package/src/plugins/users-query-routes.ts +78 -0
- 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
|
+
}
|