@cat-factory/observability-otel 0.2.10

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Igor Savin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # @cat-factory/observability-otel
2
+
3
+ Opt-in [OpenTelemetry](https://opentelemetry.io) (OTLP) trace + metrics publisher for the
4
+ Agent Architecture Board.
5
+
6
+ It implements the runtime-neutral `LlmTraceSink` port from `@cat-factory/kernel`, so when
7
+ wired into a facade every LLM call — container-agent calls (through the LLM proxy) **and**
8
+ inline calls (requirements review, document planner, fragment selector, inline agent) —
9
+ is exported to any **OTLP/HTTP** backend (Grafana Tempo/Mimir, Honeycomb, Datadog OTLP,
10
+ Jaeger, an OpenTelemetry Collector, …) as:
11
+
12
+ - **a trace span per generation**, plus a span per container tool call, all grouped under a
13
+ shared per-run trace id (they are sibling spans sharing the trace, not parent/child —
14
+ generations and tool calls arrive as independent, stateless emissions); and
15
+ - **metrics** — a `gen_ai.client.token.usage` counter (input/output) and a
16
+ `gen_ai.client.operation.duration` histogram — following the OpenTelemetry GenAI
17
+ semantic conventions.
18
+
19
+ ## Two transports, one behaviour
20
+
21
+ The Cloudflare Worker runtime (workerd) cannot run the official `@opentelemetry/*` SDK
22
+ (it relies on Node-only APIs), so this package ships **two exporters** behind the same
23
+ port:
24
+
25
+ | Entry | Export | Transport | Used by |
26
+ | -------------------------------------- | -------------------- | ------------------------------------------- | ----------------- |
27
+ | `@cat-factory/observability-otel` | `createOtelSink` | hand-rolled **OTLP/HTTP JSON over `fetch`** | Cloudflare Worker |
28
+ | `@cat-factory/observability-otel/node` | `createNodeOtelSink` | the official **`@opentelemetry/*` SDK** | Node / local |
29
+
30
+ Both map events through the **same** `src/mapping.ts` layer, so they emit identical span
31
+ names, attributes, trace-id grouping and metric names/units. `src/conformity.test.ts`
32
+ feeds the same events through both and asserts the emitted telemetry matches — the guard
33
+ that the transports never drift.
34
+
35
+ The Worker entry (`.`) never imports `@opentelemetry/*`, so the SDK is kept out of the
36
+ workerd bundle; it depends only on the `fetch`/`crypto` globals.
37
+
38
+ ## Behaviour
39
+
40
+ - Never throws into the caller: every method swallows its own errors (logging at most a
41
+ warning). Observability must never break agent work.
42
+ - Honours the same `LLM_RECORD_PROMPTS` privacy switch as the local metric store: when
43
+ prompt recording is off, spans carry usage/timing/attributes but no prompt or response
44
+ bodies.
45
+ - Composes **alongside** the Langfuse sink (`@cat-factory/observability-langfuse`) via the
46
+ kernel `composeTraceSinks` fan-out — a deployment can export to both at once.
47
+
48
+ ## Usage
49
+
50
+ ```ts
51
+ // Cloudflare Worker (workerd-safe fetch exporter)
52
+ import { createOtelSink } from '@cat-factory/observability-otel'
53
+
54
+ const sink = createOtelSink({
55
+ endpoint: env.OTEL_EXPORTER_OTLP_ENDPOINT, // e.g. http://collector:4318
56
+ headers: { 'x-api-key': env.OTEL_KEY }, // optional
57
+ serviceName: 'cat-factory', // optional; defaults to 'cat-factory'
58
+ })
59
+ ```
60
+
61
+ ```ts
62
+ // Node / local (official @opentelemetry/* SDK exporter)
63
+ import { createNodeOtelSink } from '@cat-factory/observability-otel/node'
64
+
65
+ const sink = createNodeOtelSink({ endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT! })
66
+ ```
67
+
68
+ Wired into a facade via its container's `buildTraceSink(config)`; absent config (no
69
+ `OTEL_ENABLED=true` + endpoint) ⇒ the sink is never built and there is no external
70
+ emission or behaviour change.
71
+
72
+ ## Platform-operator metrics (deployment health)
73
+
74
+ The per-call sink above answers "what did THIS run do". The **`PlatformMetricsOtelExporter`**
75
+ (`createPlatformMetricsOtelExporter`, the `.` entry) answers "how is the WHOLE deployment
76
+ doing": a periodic sweep (Worker `scheduled` cron ⇄ Node interval, runtime-symmetric) computes
77
+ the platform-observability projection per account and this exporter pushes it to the same
78
+ OTLP endpoint as OpenTelemetry **gauge** metrics — so an operator watches deployment health in
79
+ their own metrics backend, the dual of the `post-release-health` gate that watches the
80
+ _user's_ release.
81
+
82
+ Metrics (`cat_factory.platform.*`, all gauges — the OTel backend trends the series over time):
83
+
84
+ | Metric | Unit | Split dimension |
85
+ | --------------------------------------- | ------- | --------------------------- |
86
+ | `cat_factory.platform.runs` | `{run}` | `cat_factory.run_status` |
87
+ | `cat_factory.platform.run_success_rate` | `1` | — |
88
+ | `cat_factory.platform.run_failures` | `{run}` | `cat_factory.failure_kind` |
89
+ | `cat_factory.platform.live_runs` | `{run}` | `cat_factory.run_state` |
90
+ | `cat_factory.platform.run_duration` | `s` | `cat_factory.duration_stat` |
91
+
92
+ Every point carries `cat_factory.account_id` (the bounded tenant scope — safe on a metric,
93
+ unlike the unbounded workspace id excluded from the per-call metrics); the windowed gauges
94
+ also carry `cat_factory.window`. Null aggregates (a success rate / percentiles with no
95
+ terminal runs) are omitted rather than emitted as a misleading zero.
96
+
97
+ Unlike the per-call LLM path, the platform exporter is the **fetch transport on both
98
+ runtimes** (there is no SDK counterpart): the push is a stateless, low-frequency snapshot
99
+ POST with no need for the SDK's async instruments / periodic reader, so one workerd-safe
100
+ exporter serves both facades and is tested once.
101
+
102
+ **Opt-in on top of the base exporter** (it adds recurring DB rollup load): off unless
103
+ `OTEL_ENABLED=true` + an endpoint AND `OTEL_PLATFORM_METRICS=true`. `OTEL_PLATFORM_METRICS_WINDOW`
104
+ (`1h`/`24h`/`7d`, default `1h`) sets the trailing window; on Node
105
+ `OTEL_PLATFORM_METRICS_INTERVAL_MS` (default 60s) sets the sweep cadence (the Worker is
106
+ cron-driven). The runtime-neutral `sweepPlatformMetrics` driver + `distinctAccountIds`
107
+ account enumeration live in `@cat-factory/orchestration`.
@@ -0,0 +1,59 @@
1
+ import type { LlmGenerationEvent, LlmToolSpan, LlmToolSpanContext, LlmTraceSink } from '@cat-factory/kernel';
2
+ /** Minimal structured logger (pino-compatible); optional. */
3
+ export interface OtelLogger {
4
+ warn(obj: Record<string, unknown>, msg?: string): void;
5
+ }
6
+ export interface OtelSinkConfig {
7
+ /** OTLP/HTTP base URL, e.g. `http://collector:4318` (the `/v1/*` paths are appended). */
8
+ endpoint: string;
9
+ /** Extra headers merged onto every request (auth tokens, tenant ids, …). */
10
+ headers?: Record<string, string>;
11
+ /** OTLP resource `service.name`; defaults to `cat-factory`. */
12
+ serviceName?: string;
13
+ /** Optional logger for swallowed errors. */
14
+ logger?: OtelLogger;
15
+ /** Injectable fetch (tests); defaults to the global `fetch`. */
16
+ fetchImpl?: typeof fetch;
17
+ }
18
+ export declare class OtelTraceSink implements LlmTraceSink {
19
+ private readonly tracesEndpoint;
20
+ private readonly metricsEndpoint;
21
+ private readonly headers;
22
+ private readonly serviceName;
23
+ private readonly logger?;
24
+ private readonly fetchImpl;
25
+ constructor(config: OtelSinkConfig);
26
+ private resourceAttributes;
27
+ recordGeneration(event: LlmGenerationEvent): Promise<void>;
28
+ recordToolSpans(context: LlmToolSpanContext, spans: LlmToolSpan[]): Promise<void>;
29
+ private sendSpans;
30
+ private sendMetrics;
31
+ private send;
32
+ }
33
+ /** Build a fetch-based {@link OtelTraceSink}. The workerd-safe opt-in OTLP exporter. */
34
+ export declare function createOtelSink(config: OtelSinkConfig): OtelTraceSink;
35
+ export { PlatformMetricsOtelExporter, type PlatformMetricsOtelExporterConfig, createPlatformMetricsOtelExporter, } from './platform.js';
36
+ /**
37
+ * Parse the OTLP `OTEL_EXPORTER_OTLP_HEADERS` convention — comma-separated `key=value`
38
+ * pairs (e.g. `x-api-key=abc,x-tenant=42`) — into a header map, or undefined when
39
+ * unset/empty. Shared by every facade so the two transports read headers identically.
40
+ */
41
+ export declare function parseOtlpHeaders(raw: string | undefined): Record<string, string> | undefined;
42
+ /** Default platform-metrics sweep interval (Node timer); the Worker is cron-driven. */
43
+ export declare const PLATFORM_METRICS_DEFAULT_INTERVAL_MS = 60000;
44
+ /** The trailing windows the platform-metrics sweep may aggregate over. */
45
+ export declare const PLATFORM_METRICS_WINDOWS: readonly ['1h', '24h', '7d'];
46
+ export type PlatformMetricsWindow = (typeof PLATFORM_METRICS_WINDOWS)[number];
47
+ /**
48
+ * Parse `OTEL_PLATFORM_METRICS_INTERVAL_MS` into a positive integer ms, falling back to
49
+ * {@link PLATFORM_METRICS_DEFAULT_INTERVAL_MS} for unset / non-numeric / non-positive values.
50
+ * Shared by every facade so the sweep cadence is parsed identically.
51
+ */
52
+ export declare function parsePlatformMetricsIntervalMs(raw: string | undefined): number;
53
+ /**
54
+ * Parse `OTEL_PLATFORM_METRICS_WINDOW` into a valid trailing window, defaulting to `1h`
55
+ * (the shortest, most operationally useful — the OTel backend builds longer trends from the
56
+ * gauge series). Shared so both facades default + validate identically.
57
+ */
58
+ export declare function parsePlatformMetricsWindow(raw: string | undefined): PlatformMetricsWindow;
59
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,WAAW,EACX,kBAAkB,EAClB,YAAY,EACb,MAAM,qBAAqB,CAAA;AAyC5B,6DAA6D;AAC7D,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACvD;AAED,MAAM,WAAW,cAAc;IAC7B,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAA;IAChB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,gEAAgE;IAChE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACzB;AAwBD,qBAAa,aAAc,YAAW,YAAY;IAChD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IACvC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAY;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IAExC,YAAY,MAAM,EAAE,cAAc,EAQjC;IAED,OAAO,CAAC,kBAAkB;IAIpB,gBAAgB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAK/D;IAEK,eAAe,CAAC,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAOtF;YAEa,SAAS;YAiBT,WAAW;YAuDX,IAAI;CASnB;AAED,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,aAAa,CAEpE;AAKD,OAAO,EACL,2BAA2B,EAC3B,KAAK,iCAAiC,EACtC,iCAAiC,GAClC,MAAM,eAAe,CAAA;AAEtB;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAY5F;AAID,uFAAuF;AACvF,eAAO,MAAM,oCAAoC,QAAS,CAAA;AAE1D,0EAA0E;AAC1E,eAAO,MAAM,wBAAwB,YAAI,IAAI,EAAE,KAAK,EAAE,IAAI,CAAU,CAAA;AACpE,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE7E;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAG9E;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,qBAAqB,CAGzF"}
package/dist/index.js ADDED
@@ -0,0 +1,208 @@
1
+ import { ATTR, DEFAULT_SERVICE_NAME, DURATION_UNIT, METRIC, SCOPE_NAME, TOKEN_UNIT, mapGeneration, mapGenerationMetrics, mapToolSpan, randomSpanId, toUnixNano, } from './mapping.js';
2
+ import { keyValues, postOtlp } from './otlp.js';
3
+ // A fetch-based OpenTelemetry exporter that speaks OTLP/HTTP with the **JSON** encoding
4
+ // (`POST {endpoint}/v1/traces` and `/v1/metrics`) using only the global `fetch`/`crypto`.
5
+ // It deliberately does NOT depend on `@opentelemetry/*` — the SDK relies on Node-only APIs
6
+ // unavailable on the Cloudflare Worker runtime (workerd), exactly as the Langfuse sink
7
+ // avoids them. This keeps the exporter byte-for-byte identical on the Worker and, if ever
8
+ // desired, Node; the Node facade instead uses the official-SDK exporter in `./node`, kept
9
+ // conformant with this one by the shared `./mapping` layer + `conformity.test.ts`.
10
+ //
11
+ // Contract (identical to the Langfuse sink): observability must never break the product,
12
+ // so every method swallows its own errors (logging at most a warning) and each POST is
13
+ // bounded by a timeout — a dropped batch is the documented best-effort worst case. One
14
+ // POST per call keeps the exporter stateless (no cross-request buffer to flush), which is
15
+ // the only shape that survives the Worker's per-request isolate lifecycle. Metrics are
16
+ // emitted with DELTA temporality so a single call is a valid, self-contained data point.
17
+ /** OTLP `AggregationTemporality.DELTA` (proto enum value). */
18
+ const TEMPORALITY_DELTA = 1;
19
+ /** OTLP span kind CLIENT / INTERNAL (proto enum values). */
20
+ const SPAN_KIND_CLIENT = 3;
21
+ const SPAN_KIND_INTERNAL = 1;
22
+ /** OTLP status codes: UNSET / ERROR. */
23
+ const STATUS_UNSET = 0;
24
+ const STATUS_ERROR = 2;
25
+ // ---- OTLP/JSON encoding helpers -------------------------------------------
26
+ function encodeSpan(span, kind) {
27
+ return {
28
+ traceId: span.traceId,
29
+ spanId: randomSpanId(),
30
+ name: span.name,
31
+ kind,
32
+ startTimeUnixNano: toUnixNano(span.startTimeMs),
33
+ endTimeUnixNano: toUnixNano(span.endTimeMs),
34
+ attributes: keyValues(span.attributes),
35
+ events: span.events.map((e) => ({
36
+ name: e.name,
37
+ timeUnixNano: toUnixNano(e.timeMs),
38
+ attributes: keyValues(e.attributes),
39
+ })),
40
+ status: span.ok
41
+ ? { code: STATUS_UNSET }
42
+ : { code: STATUS_ERROR, ...(span.statusMessage ? { message: span.statusMessage } : {}) },
43
+ };
44
+ }
45
+ export class OtelTraceSink {
46
+ tracesEndpoint;
47
+ metricsEndpoint;
48
+ headers;
49
+ serviceName;
50
+ logger;
51
+ fetchImpl;
52
+ constructor(config) {
53
+ const base = config.endpoint.replace(/\/+$/, '');
54
+ this.tracesEndpoint = `${base}/v1/traces`;
55
+ this.metricsEndpoint = `${base}/v1/metrics`;
56
+ this.headers = { 'content-type': 'application/json', ...config.headers };
57
+ this.serviceName = config.serviceName || DEFAULT_SERVICE_NAME;
58
+ this.logger = config.logger;
59
+ this.fetchImpl = config.fetchImpl ?? fetch;
60
+ }
61
+ resourceAttributes() {
62
+ return [{ key: ATTR.serviceName, value: { stringValue: this.serviceName } }];
63
+ }
64
+ async recordGeneration(event) {
65
+ const span = mapGeneration(event);
66
+ const metrics = mapGenerationMetrics(event);
67
+ // Traces and metrics go to distinct OTLP endpoints (two POSTs), each best-effort.
68
+ await Promise.all([this.sendSpans([span], SPAN_KIND_CLIENT), this.sendMetrics(metrics)]);
69
+ }
70
+ async recordToolSpans(context, spans) {
71
+ // Tool spans are only meaningful as children of a run's trace.
72
+ if (!context.executionId || spans.length === 0)
73
+ return;
74
+ await this.sendSpans(spans.map((span) => mapToolSpan(context, span)), SPAN_KIND_INTERNAL);
75
+ }
76
+ async sendSpans(spans, kind) {
77
+ const payload = {
78
+ resourceSpans: [
79
+ {
80
+ resource: { attributes: this.resourceAttributes() },
81
+ scopeSpans: [
82
+ {
83
+ scope: { name: SCOPE_NAME },
84
+ spans: spans.map((span) => encodeSpan(span, kind)),
85
+ },
86
+ ],
87
+ },
88
+ ],
89
+ };
90
+ await this.send(this.tracesEndpoint, payload);
91
+ }
92
+ async sendMetrics(metrics) {
93
+ const startNano = toUnixNano(metrics.startTimeMs);
94
+ const timeNano = toUnixNano(metrics.endTimeMs);
95
+ const payload = {
96
+ resourceMetrics: [
97
+ {
98
+ resource: { attributes: this.resourceAttributes() },
99
+ scopeMetrics: [
100
+ {
101
+ scope: { name: SCOPE_NAME },
102
+ metrics: [
103
+ {
104
+ name: METRIC.tokenUsage,
105
+ unit: TOKEN_UNIT,
106
+ sum: {
107
+ aggregationTemporality: TEMPORALITY_DELTA,
108
+ isMonotonic: true,
109
+ dataPoints: metrics.tokenUsage.map((point) => ({
110
+ attributes: keyValues(point.attributes),
111
+ startTimeUnixNano: startNano,
112
+ timeUnixNano: timeNano,
113
+ asInt: String(point.value),
114
+ })),
115
+ },
116
+ },
117
+ {
118
+ name: METRIC.duration,
119
+ unit: DURATION_UNIT,
120
+ histogram: {
121
+ aggregationTemporality: TEMPORALITY_DELTA,
122
+ dataPoints: [
123
+ {
124
+ attributes: keyValues(metrics.durationAttributes),
125
+ startTimeUnixNano: startNano,
126
+ timeUnixNano: timeNano,
127
+ count: '1',
128
+ sum: metrics.durationSeconds,
129
+ // A single observation: one implicit bucket, no explicit bounds.
130
+ bucketCounts: ['1'],
131
+ explicitBounds: [],
132
+ min: metrics.durationSeconds,
133
+ max: metrics.durationSeconds,
134
+ },
135
+ ],
136
+ },
137
+ },
138
+ ],
139
+ },
140
+ ],
141
+ },
142
+ ],
143
+ };
144
+ await this.send(this.metricsEndpoint, payload);
145
+ }
146
+ async send(endpoint, payload) {
147
+ await postOtlp({
148
+ fetchImpl: this.fetchImpl,
149
+ endpoint,
150
+ headers: this.headers,
151
+ payload,
152
+ logger: this.logger,
153
+ });
154
+ }
155
+ }
156
+ /** Build a fetch-based {@link OtelTraceSink}. The workerd-safe opt-in OTLP exporter. */
157
+ export function createOtelSink(config) {
158
+ return new OtelTraceSink(config);
159
+ }
160
+ // The deployment-level (platform-operator) metrics exporter — the dual of the per-run LLM
161
+ // sink above. Fetch-based on both runtimes (see `./platform`), so it lives in this
162
+ // workerd-safe entry rather than the SDK `./node` one.
163
+ export { PlatformMetricsOtelExporter, createPlatformMetricsOtelExporter, } from './platform.js';
164
+ /**
165
+ * Parse the OTLP `OTEL_EXPORTER_OTLP_HEADERS` convention — comma-separated `key=value`
166
+ * pairs (e.g. `x-api-key=abc,x-tenant=42`) — into a header map, or undefined when
167
+ * unset/empty. Shared by every facade so the two transports read headers identically.
168
+ */
169
+ export function parseOtlpHeaders(raw) {
170
+ const trimmed = raw?.trim();
171
+ if (!trimmed)
172
+ return undefined;
173
+ const headers = {};
174
+ for (const pair of trimmed.split(',')) {
175
+ const eq = pair.indexOf('=');
176
+ if (eq <= 0)
177
+ continue;
178
+ const key = pair.slice(0, eq).trim();
179
+ const value = pair.slice(eq + 1).trim();
180
+ if (key)
181
+ headers[key] = value;
182
+ }
183
+ return Object.keys(headers).length ? headers : undefined;
184
+ }
185
+ // ---- platform-metrics sweep config parsing (shared by both facades) --------
186
+ /** Default platform-metrics sweep interval (Node timer); the Worker is cron-driven. */
187
+ export const PLATFORM_METRICS_DEFAULT_INTERVAL_MS = 60_000;
188
+ /** The trailing windows the platform-metrics sweep may aggregate over. */
189
+ export const PLATFORM_METRICS_WINDOWS = ['1h', '24h', '7d'];
190
+ /**
191
+ * Parse `OTEL_PLATFORM_METRICS_INTERVAL_MS` into a positive integer ms, falling back to
192
+ * {@link PLATFORM_METRICS_DEFAULT_INTERVAL_MS} for unset / non-numeric / non-positive values.
193
+ * Shared by every facade so the sweep cadence is parsed identically.
194
+ */
195
+ export function parsePlatformMetricsIntervalMs(raw) {
196
+ const n = Number(raw?.trim());
197
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : PLATFORM_METRICS_DEFAULT_INTERVAL_MS;
198
+ }
199
+ /**
200
+ * Parse `OTEL_PLATFORM_METRICS_WINDOW` into a valid trailing window, defaulting to `1h`
201
+ * (the shortest, most operationally useful — the OTel backend builds longer trends from the
202
+ * gauge series). Shared so both facades default + validate identically.
203
+ */
204
+ export function parsePlatformMetricsWindow(raw) {
205
+ const w = raw?.trim();
206
+ return w === '24h' || w === '7d' ? w : '1h';
207
+ }
208
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,133 @@
1
+ import type { LlmGenerationEvent, LlmToolSpan, LlmToolSpanContext } from '@cat-factory/kernel';
2
+ import type { PlatformObservability } from '@cat-factory/contracts';
3
+ /** Default OTLP resource `service.name`; overridable via `OTEL_SERVICE_NAME`. */
4
+ export declare const DEFAULT_SERVICE_NAME = "cat-factory";
5
+ /** The instrumentation scope name stamped on every emitted span/metric. */
6
+ export declare const SCOPE_NAME = "@cat-factory/observability-otel";
7
+ /**
8
+ * Attribute keys, following the OpenTelemetry GenAI semantic conventions where they exist
9
+ * (`gen_ai.*`) plus a small `cat_factory.*` namespace for our own dimensions. Centralised
10
+ * so the two transports can never disagree on a key.
11
+ */
12
+ export declare const ATTR: {
13
+ readonly system: 'gen_ai.system';
14
+ readonly requestModel: 'gen_ai.request.model';
15
+ readonly inputTokens: 'gen_ai.usage.input_tokens';
16
+ readonly outputTokens: 'gen_ai.usage.output_tokens';
17
+ readonly finishReasons: 'gen_ai.response.finish_reasons';
18
+ readonly tokenType: 'gen_ai.token.type';
19
+ readonly workspaceId: 'cat_factory.workspace_id';
20
+ readonly agentKind: 'cat_factory.agent_kind';
21
+ readonly serviceName: 'service.name';
22
+ };
23
+ /** Metric names + units (OTel GenAI client metrics). */
24
+ export declare const METRIC: {
25
+ readonly tokenUsage: 'gen_ai.client.token.usage';
26
+ readonly duration: 'gen_ai.client.operation.duration';
27
+ };
28
+ export declare const TOKEN_UNIT = "{token}";
29
+ export declare const DURATION_UNIT = "s";
30
+ /** A neutral attribute value both transports understand (string / number / string list). */
31
+ export type AttributeValue = string | number | string[];
32
+ export type AttributeMap = Record<string, AttributeValue>;
33
+ interface MappedEvent {
34
+ name: string;
35
+ /** Epoch ms. */
36
+ timeMs: number;
37
+ attributes: AttributeMap;
38
+ }
39
+ /** A transport-neutral span, ready to encode as OTLP JSON or feed the SDK tracer. */
40
+ export interface MappedSpan {
41
+ /** 32-hex trace id (a run's calls share one; standalone calls get a random one). */
42
+ traceId: string;
43
+ name: string;
44
+ /** Epoch ms. */
45
+ startTimeMs: number;
46
+ /** Epoch ms. */
47
+ endTimeMs: number;
48
+ /** false ⇒ the span carries ERROR status + {@link statusMessage}. */
49
+ ok: boolean;
50
+ statusMessage?: string;
51
+ attributes: AttributeMap;
52
+ events: MappedEvent[];
53
+ }
54
+ /** One token-usage counter data point (one per {@link ATTR.tokenType}). */
55
+ interface MappedTokenUsage {
56
+ value: number;
57
+ attributes: AttributeMap;
58
+ }
59
+ /** The metrics derived from one generation. */
60
+ export interface MappedMetrics {
61
+ tokenUsage: MappedTokenUsage[];
62
+ /** Request duration in seconds (histogram value). */
63
+ durationSeconds: number;
64
+ durationAttributes: AttributeMap;
65
+ /** Epoch ms bounds for the (delta) data points. */
66
+ startTimeMs: number;
67
+ endTimeMs: number;
68
+ }
69
+ export declare function randomTraceId(): string;
70
+ export declare function randomSpanId(): string;
71
+ /** Epoch ms → OTLP unix-nano string (string arithmetic avoids float precision loss). */
72
+ export declare function toUnixNano(ms: number): string;
73
+ /** Map one completed LLM call to a neutral span. */
74
+ export declare function mapGeneration(event: LlmGenerationEvent): MappedSpan;
75
+ /** Map one completed LLM call to its token-usage + duration metrics. */
76
+ export declare function mapGenerationMetrics(event: LlmGenerationEvent): MappedMetrics;
77
+ /** Metric names for the deployment-level platform observability gauges. */
78
+ export declare const PLATFORM_METRIC: {
79
+ /** Windowed run count, split by run status (done/failed/running/…). */
80
+ readonly runs: 'cat_factory.platform.runs';
81
+ /** Windowed `done / (done + failed)` success ratio (0..1). */
82
+ readonly runSuccessRate: 'cat_factory.platform.run_success_rate';
83
+ /** Windowed failed-run count, split by failure kind. */
84
+ readonly runFailures: 'cat_factory.platform.run_failures';
85
+ /** Current live/parked run count (a snapshot, not windowed), split by lifecycle state. */
86
+ readonly liveRuns: 'cat_factory.platform.live_runs';
87
+ /** Windowed wall-clock run duration (seconds), split by statistic (avg/min/max/pNN). */
88
+ readonly runDuration: 'cat_factory.platform.run_duration';
89
+ };
90
+ /**
91
+ * Attribute keys for the platform metrics. `account_id` is the tenant scope (bounded — the
92
+ * billing entity, far fewer than workspaces, so safe on a metric time series, unlike the
93
+ * workspace id excluded from the per-call metrics); `window` labels the trailing aggregation
94
+ * window. The remaining keys are the bounded split dimensions of each gauge.
95
+ */
96
+ export declare const PLATFORM_ATTR: {
97
+ readonly accountId: 'cat_factory.account_id';
98
+ readonly window: 'cat_factory.window';
99
+ readonly runStatus: 'cat_factory.run_status';
100
+ readonly runState: 'cat_factory.run_state';
101
+ readonly failureKind: 'cat_factory.failure_kind';
102
+ readonly durationStat: 'cat_factory.duration_stat';
103
+ };
104
+ /** Metric units: a dimensionless run count, a dimensionless ratio, and seconds. */
105
+ export declare const RUN_UNIT = "{run}";
106
+ export declare const RATIO_UNIT = "1";
107
+ /** One gauge data point: its dimensions, value, and whether to encode as int or double. */
108
+ export interface MappedGaugePoint {
109
+ attributes: AttributeMap;
110
+ value: number;
111
+ /** true ⇒ encode as an integer (counts); false ⇒ a double (ratios/durations). */
112
+ isInt: boolean;
113
+ }
114
+ /** A gauge metric ready to encode as OTLP or feed the SDK meter. */
115
+ export interface MappedGauge {
116
+ name: string;
117
+ unit: string;
118
+ points: MappedGaugePoint[];
119
+ }
120
+ /**
121
+ * Map a {@link PlatformObservability} projection to the OpenTelemetry gauge metrics. All are
122
+ * point-in-time gauges (the OTel backend builds trends from the series over time), stamped
123
+ * with the projection's `generatedAt`. Every point carries the `account_id`; the windowed
124
+ * gauges additionally carry the `window` label. Null/absent aggregates (e.g. a success rate
125
+ * or percentiles with no terminal runs) are omitted rather than emitted as a misleading zero.
126
+ */
127
+ export declare function mapPlatformMetrics(snapshot: PlatformObservability, dims: {
128
+ accountId: string;
129
+ }): MappedGauge[];
130
+ /** Map one container tool call to a neutral span under its run's trace. */
131
+ export declare function mapToolSpan(context: LlmToolSpanContext, span: LlmToolSpan): MappedSpan;
132
+ export {};
133
+ //# sourceMappingURL=mapping.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../src/mapping.ts"],"names":[],"mappings":"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"}