@backendkit-labs/agent-otel 0.0.0-stage → 0.1.0

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/README.md CHANGED
@@ -1,3 +1,92 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # @backendkit-labs/agent-otel
2
+
3
+ OpenTelemetry export for the traces of [`@backendkit-labs/agent-core`](../core). Pick the exporter that fits
4
+ how you run telemetry:
5
+
6
+ | You have | Use |
7
+ |---|---|
8
+ | A collector or backend that speaks OTLP/HTTP (Jaeger, Tempo, Langfuse, Datadog, the OpenTelemetry Collector…) and **no** OpenTelemetry SDK | `otlpHttpSpanExporter` |
9
+ | An OpenTelemetry SDK already registered in the process (auto-instrumentation, your own processors/exporters) | `otelApiSpanExporter` |
10
+
11
+ ```ts
12
+ import { createBaseEngine } from '@backendkit-labs/agent-core';
13
+ import { otlpHttpSpanExporter } from '@backendkit-labs/agent-otel';
14
+
15
+ const exporter = otlpHttpSpanExporter({ endpoint: 'http://localhost:4318', serviceName: 'support-agent' });
16
+
17
+ const engine = createBaseEngine({
18
+ // ...
19
+ observability: { tracing: { exporter } },
20
+ });
21
+
22
+ // on shutdown
23
+ await exporter.shutdown();
24
+ ```
25
+
26
+ ```ts
27
+ import { otelApiSpanExporter } from '@backendkit-labs/agent-otel'; // needs @opentelemetry/api
28
+ observability: { tracing: { exporter: otelApiSpanExporter() } }
29
+ ```
30
+
31
+ ## What you get
32
+
33
+ One trace per `engine.run()`:
34
+
35
+ ```
36
+ agent.run
37
+ └─ invoke_agent Main
38
+ ├─ chat deepseek-chat (tokens, model, provider, cost, cache tokens)
39
+ ├─ execute_tool read_file (success, duration — parallel tools overlap in time)
40
+ └─ chat deepseek-chat
41
+ ```
42
+
43
+ Attributes follow the OpenTelemetry **GenAI semantic conventions**: `gen_ai.operation.name`
44
+ (`invoke_agent` / `chat` / `execute_tool`), `gen_ai.request.model`, `gen_ai.provider.name`,
45
+ `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, `gen_ai.usage.cache_read.input_tokens`,
46
+ `gen_ai.usage.cache_creation.input_tokens`, `gen_ai.agent.id/name`, `gen_ai.tool.name`. Anything specific to
47
+ agent-core (cost in USD, the original trace id, guardrail actions, why an extra LLM call was made) lives under
48
+ `bk.*`, e.g. `bk.cost_usd`, `bk.llm.purpose` (`context_summary`, `structured_output`), `bk.guardrail.<name>.<action>`.
49
+
50
+ **Conversation content is never exported.** Spans carry names, durations, token counts and ids only: no
51
+ prompts, answers, tool arguments or tool results.
52
+
53
+ Requires `@backendkit-labs/agent-core` with `llm_call` / `tool_call` spans (0.28+). With 0.27 you only get
54
+ `agent.run` and `invoke_agent`.
55
+
56
+ ## `otlpHttpSpanExporter(options)`
57
+
58
+ Sends OTLP/JSON to `<endpoint>/v1/traces`. No SDK, no dependencies.
59
+
60
+ | Option | Default | |
61
+ |---|---|---|
62
+ | `endpoint` | `OTEL_EXPORTER_OTLP_ENDPOINT` or `http://localhost:4318` | `/v1/traces` is appended unless already there |
63
+ | `headers` | – | e.g. `{ Authorization: 'Basic …' }` |
64
+ | `serviceName` | `agent` | `service.name` resource attribute |
65
+ | `resourceAttributes` | – | `deployment.environment`, `service.version`… |
66
+ | `batchSize` | 64 | spans buffered before sending |
67
+ | `flushIntervalMs` | 2000 | a partial batch is sent after this long (timer is `unref`'d) |
68
+ | `timeoutMs` | 10000 | per request |
69
+ | `onError` | – | a failed send is reported here and **that batch is dropped**; it never reaches the agent |
70
+ | `fetch` | global `fetch` | for tests or custom transports |
71
+
72
+ Returns a function with `flush()` and `shutdown()`. Sends are serialized, never overlapping.
73
+ Ids are derived deterministically from agent-core's ids, so parent/child links are preserved.
74
+
75
+ ## `otelApiSpanExporter(options)`
76
+
77
+ Re-creates the spans through `@opentelemetry/api` (`options.tracer`, default
78
+ `trace.getTracer('@backendkit-labs/agent-core')`), so they reach whatever your SDK exports to.
79
+
80
+ The OpenTelemetry API cannot choose a span's id, and a child must be created from its parent's real span.
81
+ agent-core exports spans as they end (children first), so this adapter holds a trace's spans until the root
82
+ `agent.run` ends and then creates the tree parents-first with the original start/end times. Consequences:
83
+
84
+ - Spans of a run appear in your backend when the run finishes, not while it runs.
85
+ - A run that never ends (crash) leaves its spans buffered: call `exporter.flush()` on shutdown (spans whose
86
+ parent never arrived become roots). `maxBufferedSpans` (default 5000) bounds memory.
87
+ - `@opentelemetry/api` is an optional peer dependency; without it the exporter throws a clear error at creation.
88
+
89
+ ## Verified
90
+
91
+ Unit tests plus a run of a real engine through both exporters. The API adapter was also checked against a real
92
+ `@opentelemetry/sdk-trace-base` provider: one trace, correct hierarchy, parallel tool spans overlapping in time.
@@ -0,0 +1,7 @@
1
+ export { otlpHttpSpanExporter, toOtlpSpan } from './otlpHttpExporter';
2
+ export type { OtlpHttpExporterOptions, OtlpHttpSpanExporter } from './otlpHttpExporter';
3
+ export { otelApiSpanExporter } from './otelApiExporter';
4
+ export type { OtelApiExporterOptions, OtelApiSpanExporter } from './otelApiExporter';
5
+ export { mapSpan, toTraceId, toSpanId } from './mapping';
6
+ export type { MappedSpan, OtelAttributes } from './mapping';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACtE,YAAY,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACxF,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,YAAY,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACzD,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toSpanId = exports.toTraceId = exports.mapSpan = exports.otelApiSpanExporter = exports.toOtlpSpan = exports.otlpHttpSpanExporter = void 0;
4
+ var otlpHttpExporter_1 = require("./otlpHttpExporter");
5
+ Object.defineProperty(exports, "otlpHttpSpanExporter", { enumerable: true, get: function () { return otlpHttpExporter_1.otlpHttpSpanExporter; } });
6
+ Object.defineProperty(exports, "toOtlpSpan", { enumerable: true, get: function () { return otlpHttpExporter_1.toOtlpSpan; } });
7
+ var otelApiExporter_1 = require("./otelApiExporter");
8
+ Object.defineProperty(exports, "otelApiSpanExporter", { enumerable: true, get: function () { return otelApiExporter_1.otelApiSpanExporter; } });
9
+ var mapping_1 = require("./mapping");
10
+ Object.defineProperty(exports, "mapSpan", { enumerable: true, get: function () { return mapping_1.mapSpan; } });
11
+ Object.defineProperty(exports, "toTraceId", { enumerable: true, get: function () { return mapping_1.toTraceId; } });
12
+ Object.defineProperty(exports, "toSpanId", { enumerable: true, get: function () { return mapping_1.toSpanId; } });
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,uDAAsE;AAA7D,wHAAA,oBAAoB,OAAA;AAAE,8GAAA,UAAU,OAAA;AAEzC,qDAAwD;AAA/C,sHAAA,mBAAmB,OAAA;AAE5B,qCAAyD;AAAhD,kGAAA,OAAO,OAAA;AAAE,oGAAA,SAAS,OAAA;AAAE,mGAAA,QAAQ,OAAA"}
@@ -0,0 +1,20 @@
1
+ import type { Span } from '@backendkit-labs/agent-core';
2
+ export type OtelAttributes = Record<string, string | number | boolean>;
3
+ export interface MappedSpan {
4
+ name: string;
5
+ kind: 'internal' | 'client';
6
+ attributes: OtelAttributes;
7
+ /** Set when the span ended in error. */
8
+ errorMessage?: string;
9
+ isError: boolean;
10
+ }
11
+ /** Deterministic OTel-shaped ids (32 / 16 hex chars) from whatever id format the tracer used. */
12
+ export declare function toTraceId(id: string): string;
13
+ export declare function toSpanId(id: string): string;
14
+ /**
15
+ * Translates an agent-core span into OpenTelemetry names and attributes following the GenAI semantic
16
+ * conventions (`invoke_agent`, `chat`, `execute_tool`). Prompts, answers and tool arguments are never
17
+ * part of a span, so nothing here can export conversation content.
18
+ */
19
+ export declare function mapSpan(span: Span): MappedSpan;
20
+ //# sourceMappingURL=mapping.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../src/mapping.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AAExD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAEvE,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,GAAG,QAAQ,CAAC;IAC5B,UAAU,EAAE,cAAc,CAAC;IAC3B,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;CACpB;AAED,iGAAiG;AACjG,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE3C;AAiBD;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,CAgE9C"}
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toTraceId = toTraceId;
4
+ exports.toSpanId = toSpanId;
5
+ exports.mapSpan = mapSpan;
6
+ const crypto_1 = require("crypto");
7
+ /** Deterministic OTel-shaped ids (32 / 16 hex chars) from whatever id format the tracer used. */
8
+ function toTraceId(id) {
9
+ return (0, crypto_1.createHash)('sha256').update(`trace:${id}`).digest('hex').slice(0, 32);
10
+ }
11
+ function toSpanId(id) {
12
+ return (0, crypto_1.createHash)('sha256').update(`span:${id}`).digest('hex').slice(0, 16);
13
+ }
14
+ /** `cost_usd` and similar keys keep their meaning under a vendor prefix. */
15
+ const VENDOR = 'bk.';
16
+ function str(v) {
17
+ return typeof v === 'string' && v.length > 0 ? v : undefined;
18
+ }
19
+ function rest(tags, consumed) {
20
+ const out = {};
21
+ for (const [k, v] of Object.entries(tags)) {
22
+ if (!consumed.includes(k))
23
+ out[`${VENDOR}${k}`] = v;
24
+ }
25
+ return out;
26
+ }
27
+ /**
28
+ * Translates an agent-core span into OpenTelemetry names and attributes following the GenAI semantic
29
+ * conventions (`invoke_agent`, `chat`, `execute_tool`). Prompts, answers and tool arguments are never
30
+ * part of a span, so nothing here can export conversation content.
31
+ */
32
+ function mapSpan(span) {
33
+ const t = span.tags;
34
+ const isError = span.status === 'error';
35
+ switch (span.name) {
36
+ case 'run': {
37
+ const message = str(t.error);
38
+ return {
39
+ name: 'agent.run', kind: 'internal', isError, ...(message ? { errorMessage: message } : {}),
40
+ attributes: { 'bk.trace_id': span.traceId, ...rest(t, ['error']) },
41
+ };
42
+ }
43
+ case 'agent_block': {
44
+ const id = str(t.agent_id);
45
+ const name = str(t.agent_name);
46
+ return {
47
+ name: `invoke_agent ${name ?? id ?? 'agent'}`, kind: 'internal', isError,
48
+ attributes: {
49
+ 'gen_ai.operation.name': 'invoke_agent',
50
+ ...(id ? { 'gen_ai.agent.id': id } : {}),
51
+ ...(name ? { 'gen_ai.agent.name': name } : {}),
52
+ ...rest(t, ['agent_id', 'agent_name']),
53
+ },
54
+ };
55
+ }
56
+ case 'llm_call': {
57
+ const model = str(t.model);
58
+ const provider = str(t.provider);
59
+ const agent = str(t.agent_id);
60
+ const attributes = { 'gen_ai.operation.name': 'chat' };
61
+ if (model)
62
+ attributes['gen_ai.request.model'] = model;
63
+ if (provider) {
64
+ attributes['gen_ai.provider.name'] = provider;
65
+ attributes['gen_ai.system'] = provider;
66
+ }
67
+ if (agent)
68
+ attributes['gen_ai.agent.id'] = agent;
69
+ if (typeof t.input_tokens === 'number')
70
+ attributes['gen_ai.usage.input_tokens'] = t.input_tokens;
71
+ if (typeof t.output_tokens === 'number')
72
+ attributes['gen_ai.usage.output_tokens'] = t.output_tokens;
73
+ if (typeof t.cache_read_tokens === 'number')
74
+ attributes['gen_ai.usage.cache_read.input_tokens'] = t.cache_read_tokens;
75
+ if (typeof t.cache_write_tokens === 'number')
76
+ attributes['gen_ai.usage.cache_creation.input_tokens'] = t.cache_write_tokens;
77
+ if (typeof t.cost_usd === 'number')
78
+ attributes['bk.cost_usd'] = t.cost_usd;
79
+ if (str(t.purpose))
80
+ attributes['bk.llm.purpose'] = String(t.purpose);
81
+ Object.assign(attributes, rest(t, [
82
+ 'agent_id', 'provider', 'model', 'input_tokens', 'output_tokens',
83
+ 'cache_read_tokens', 'cache_write_tokens', 'cost_usd', 'purpose',
84
+ ]));
85
+ return {
86
+ name: `chat ${model ?? 'unknown'}`, kind: 'client', isError, attributes,
87
+ };
88
+ }
89
+ case 'tool_call': {
90
+ const tool = str(t['tool.name']);
91
+ const agent = str(t.agent_id);
92
+ return {
93
+ name: `execute_tool ${tool ?? 'unknown'}`, kind: 'internal', isError,
94
+ attributes: {
95
+ 'gen_ai.operation.name': 'execute_tool',
96
+ 'gen_ai.tool.type': 'function',
97
+ ...(tool ? { 'gen_ai.tool.name': tool } : {}),
98
+ ...(agent ? { 'gen_ai.agent.id': agent } : {}),
99
+ ...rest(t, ['tool.name', 'agent_id']),
100
+ },
101
+ };
102
+ }
103
+ default:
104
+ return { name: span.name, kind: 'internal', isError, attributes: rest(t, []) };
105
+ }
106
+ }
107
+ //# sourceMappingURL=mapping.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mapping.js","sourceRoot":"","sources":["../src/mapping.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { SpanExporter } from '@backendkit-labs/agent-core';
2
+ import type { Tracer as OtelTracer } from '@opentelemetry/api';
3
+ export interface OtelApiExporterOptions {
4
+ /** Tracer to create spans with. Default: `trace.getTracer('@backendkit-labs/agent-core')`. */
5
+ tracer?: OtelTracer;
6
+ /**
7
+ * Spans held while waiting for their root to end. Past this, everything pending is flushed with
8
+ * orphans promoted to roots. Default 5000.
9
+ */
10
+ maxBufferedSpans?: number;
11
+ }
12
+ export type OtelApiSpanExporter = SpanExporter & {
13
+ /** Emits spans still waiting for a root (e.g. a run that never finished). */
14
+ flush(): void;
15
+ };
16
+ /**
17
+ * Re-creates agent-core spans through the OpenTelemetry API, so they flow to whatever SDK, processor and
18
+ * exporter the host application already configured.
19
+ *
20
+ * The OpenTelemetry API cannot choose a span's id, so a child must be created from its parent's real
21
+ * OTel span. agent-core exports spans when they end (children first), therefore this adapter holds a
22
+ * trace's spans until its root `run` span ends and then creates the tree parents-first, keeping the
23
+ * original start and end times. Use `otlpHttpSpanExporter` when you do not run the SDK.
24
+ */
25
+ export declare function otelApiSpanExporter(options?: OtelApiExporterOptions): OtelApiSpanExporter;
26
+ //# sourceMappingURL=otelApiExporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otelApiExporter.d.ts","sourceRoot":"","sources":["../src/otelApiExporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,YAAY,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,KAAK,EAA6B,MAAM,IAAI,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAG1F,MAAM,WAAW,sBAAsB;IACnC,8FAA8F;IAC9F,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG;IAC7C,6EAA6E;IAC7E,KAAK,IAAI,IAAI,CAAC;CACjB,CAAC;AAgBF;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,sBAA2B,GAAG,mBAAmB,CA4D7F"}
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.otelApiSpanExporter = otelApiSpanExporter;
4
+ const mapping_1 = require("./mapping");
5
+ function loadApi() {
6
+ try {
7
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
8
+ return require('@opentelemetry/api');
9
+ }
10
+ catch {
11
+ throw new Error('otelApiSpanExporter needs "@opentelemetry/api" (npm i @opentelemetry/api) and an OpenTelemetry SDK ' +
12
+ 'registered in the process. Use otlpHttpSpanExporter() to export without the SDK.');
13
+ }
14
+ }
15
+ /**
16
+ * Re-creates agent-core spans through the OpenTelemetry API, so they flow to whatever SDK, processor and
17
+ * exporter the host application already configured.
18
+ *
19
+ * The OpenTelemetry API cannot choose a span's id, so a child must be created from its parent's real
20
+ * OTel span. agent-core exports spans when they end (children first), therefore this adapter holds a
21
+ * trace's spans until its root `run` span ends and then creates the tree parents-first, keeping the
22
+ * original start and end times. Use `otlpHttpSpanExporter` when you do not run the SDK.
23
+ */
24
+ function otelApiSpanExporter(options = {}) {
25
+ const api = loadApi();
26
+ const tracer = options.tracer ?? api.trace.getTracer('@backendkit-labs/agent-core');
27
+ const maxBuffered = options.maxBufferedSpans ?? 5000;
28
+ const pending = new Map(); // by core traceId
29
+ let buffered = 0;
30
+ const create = (span, parent) => {
31
+ const mapped = (0, mapping_1.mapSpan)(span);
32
+ const ctx = parent ? api.trace.setSpan(api.ROOT_CONTEXT, parent) : api.ROOT_CONTEXT;
33
+ const otel = tracer.startSpan(mapped.name, {
34
+ kind: mapped.kind === 'client' ? api.SpanKind.CLIENT : api.SpanKind.INTERNAL,
35
+ startTime: span.startedAt,
36
+ attributes: mapped.attributes,
37
+ }, ctx);
38
+ if (mapped.isError) {
39
+ otel.setStatus({ code: api.SpanStatusCode.ERROR, ...(mapped.errorMessage ? { message: mapped.errorMessage } : {}) });
40
+ }
41
+ otel.end(span.endedAt);
42
+ return otel;
43
+ };
44
+ // Creates `roots` and all their descendants, parents before children, and drops them from the buffer.
45
+ const emit = (traceId, rootIds) => {
46
+ const list = pending.get(traceId) ?? [];
47
+ const children = new Map();
48
+ for (const s of list) {
49
+ if (s.parentSpanId)
50
+ children.set(s.parentSpanId, [...(children.get(s.parentSpanId) ?? []), s]);
51
+ }
52
+ const used = new Set();
53
+ const walk = (span, parent) => {
54
+ used.add(span);
55
+ const otel = create(span, parent);
56
+ for (const child of (children.get(span.spanId) ?? []).sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime())) {
57
+ walk(child, otel);
58
+ }
59
+ };
60
+ for (const root of list.filter(s => rootIds.has(s.spanId)))
61
+ walk(root, undefined);
62
+ const left = list.filter(s => !used.has(s));
63
+ buffered -= used.size;
64
+ if (left.length > 0)
65
+ pending.set(traceId, left);
66
+ else
67
+ pending.delete(traceId);
68
+ };
69
+ const flush = () => {
70
+ for (const [traceId, list] of [...pending]) {
71
+ const ids = new Set(list.map(s => s.spanId));
72
+ // Whatever has no pending parent starts a tree: real roots and spans whose parent never arrived.
73
+ emit(traceId, new Set(list.filter(s => !s.parentSpanId || !ids.has(s.parentSpanId)).map(s => s.spanId)));
74
+ }
75
+ };
76
+ const exporter = ((span) => {
77
+ pending.set(span.traceId, [...(pending.get(span.traceId) ?? []), span]);
78
+ buffered++;
79
+ if (!span.parentSpanId)
80
+ emit(span.traceId, new Set([span.spanId]));
81
+ else if (buffered > maxBuffered)
82
+ flush();
83
+ });
84
+ exporter.flush = flush;
85
+ return exporter;
86
+ }
87
+ //# sourceMappingURL=otelApiExporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otelApiExporter.js","sourceRoot":"","sources":["../src/otelApiExporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import type { Span, SpanExporter } from '@backendkit-labs/agent-core';
2
+ import type { OtelAttributes } from './mapping';
3
+ export interface OtlpHttpExporterOptions {
4
+ /**
5
+ * Base URL of the collector or backend. Default: `OTEL_EXPORTER_OTLP_ENDPOINT` or
6
+ * `http://localhost:4318`. `/v1/traces` is appended unless the URL already ends with it.
7
+ */
8
+ endpoint?: string;
9
+ /** Extra HTTP headers, e.g. `{ Authorization: 'Basic …' }` for Langfuse or a hosted backend. */
10
+ headers?: Record<string, string>;
11
+ /** Value of the `service.name` resource attribute. Default `agent`. */
12
+ serviceName?: string;
13
+ /** Additional resource attributes (deployment.environment, service.version, …). */
14
+ resourceAttributes?: OtelAttributes;
15
+ /** Spans buffered before a send is triggered. Default 64. */
16
+ batchSize?: number;
17
+ /** A partial batch is sent after this long. Default 2000 ms. */
18
+ flushIntervalMs?: number;
19
+ /** Request timeout. Default 10000 ms. */
20
+ timeoutMs?: number;
21
+ /** Called when a send fails (the spans of that batch are dropped). Never thrown. */
22
+ onError?: (error: Error) => void;
23
+ /** Override for tests or custom transports. Default: global `fetch`. */
24
+ fetch?: typeof fetch;
25
+ }
26
+ export type OtlpHttpSpanExporter = SpanExporter & {
27
+ /** Sends everything buffered and resolves when the collector answered. */
28
+ flush(): Promise<void>;
29
+ /** Flushes and stops the timer. Call it on process shutdown. */
30
+ shutdown(): Promise<void>;
31
+ };
32
+ /** OTLP/JSON form of one span. */
33
+ export declare function toOtlpSpan(span: Span): Record<string, unknown>;
34
+ /**
35
+ * Sends agent-core spans to any OTLP/HTTP collector or backend (Jaeger, Tempo, Langfuse, Datadog, the
36
+ * OpenTelemetry Collector…) without the OpenTelemetry SDK. Spans are batched; a failing send never reaches
37
+ * the agent: it is reported through `onError` and that batch is dropped.
38
+ *
39
+ * @example
40
+ * observability: { tracing: { exporter: otlpHttpSpanExporter({ endpoint: 'http://localhost:4318', serviceName: 'support-agent' }) } }
41
+ */
42
+ export declare function otlpHttpSpanExporter(options?: OtlpHttpExporterOptions): OtlpHttpSpanExporter;
43
+ //# sourceMappingURL=otlpHttpExporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otlpHttpExporter.d.ts","sourceRoot":"","sources":["../src/otlpHttpExporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,MAAM,WAAW,uBAAuB;IACpC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gGAAgG;IAChG,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,cAAc,CAAC;IACpC,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,wEAAwE;IACxE,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAED,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG;IAC9C,0EAA0E;IAC1E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,gEAAgE;IAChE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B,CAAC;AAmBF,kCAAkC;AAClC,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAa9D;AAOD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,uBAA4B,GAAG,oBAAoB,CAwDhG"}
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toOtlpSpan = toOtlpSpan;
4
+ exports.otlpHttpSpanExporter = otlpHttpSpanExporter;
5
+ const mapping_1 = require("./mapping");
6
+ const KIND = { internal: 1, client: 3 };
7
+ const SCOPE = { name: '@backendkit-labs/agent-otel', version: '0.1.0' };
8
+ function attrValue(v) {
9
+ if (typeof v === 'string')
10
+ return { stringValue: v };
11
+ if (typeof v === 'boolean')
12
+ return { boolValue: v };
13
+ return Number.isInteger(v) ? { intValue: String(v) } : { doubleValue: v };
14
+ }
15
+ function attributes(attrs) {
16
+ return Object.entries(attrs).map(([key, value]) => ({ key, value: attrValue(value) }));
17
+ }
18
+ function nanos(d) {
19
+ return (BigInt(d.getTime()) * 1000000n).toString();
20
+ }
21
+ /** OTLP/JSON form of one span. */
22
+ function toOtlpSpan(span) {
23
+ const m = (0, mapping_1.mapSpan)(span);
24
+ return {
25
+ traceId: (0, mapping_1.toTraceId)(span.traceId),
26
+ spanId: (0, mapping_1.toSpanId)(span.spanId),
27
+ ...(span.parentSpanId ? { parentSpanId: (0, mapping_1.toSpanId)(span.parentSpanId) } : {}),
28
+ name: m.name,
29
+ kind: KIND[m.kind],
30
+ startTimeUnixNano: nanos(span.startedAt),
31
+ endTimeUnixNano: nanos(span.endedAt),
32
+ attributes: attributes(m.attributes),
33
+ status: m.isError ? { code: 2, ...(m.errorMessage ? { message: m.errorMessage } : {}) } : { code: 1 },
34
+ };
35
+ }
36
+ function resolveUrl(endpoint) {
37
+ const base = endpoint.replace(/\/+$/, '');
38
+ return base.endsWith('/v1/traces') ? base : `${base}/v1/traces`;
39
+ }
40
+ /**
41
+ * Sends agent-core spans to any OTLP/HTTP collector or backend (Jaeger, Tempo, Langfuse, Datadog, the
42
+ * OpenTelemetry Collector…) without the OpenTelemetry SDK. Spans are batched; a failing send never reaches
43
+ * the agent: it is reported through `onError` and that batch is dropped.
44
+ *
45
+ * @example
46
+ * observability: { tracing: { exporter: otlpHttpSpanExporter({ endpoint: 'http://localhost:4318', serviceName: 'support-agent' }) } }
47
+ */
48
+ function otlpHttpSpanExporter(options = {}) {
49
+ const url = resolveUrl(options.endpoint ?? process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? 'http://localhost:4318');
50
+ const batchSize = Math.max(1, options.batchSize ?? 64);
51
+ const intervalMs = options.flushIntervalMs ?? 2000;
52
+ const timeoutMs = options.timeoutMs ?? 10_000;
53
+ const doFetch = options.fetch ?? globalThis.fetch;
54
+ const resourceAttrs = { 'service.name': options.serviceName ?? 'agent', ...(options.resourceAttributes ?? {}) };
55
+ let queue = [];
56
+ let timer;
57
+ // Sends run one after another so a slow collector never gets overlapping requests.
58
+ let chain = Promise.resolve();
59
+ const send = async (spans) => {
60
+ try {
61
+ const res = await doFetch(url, {
62
+ method: 'POST',
63
+ headers: { 'content-type': 'application/json', ...(options.headers ?? {}) },
64
+ body: JSON.stringify({
65
+ resourceSpans: [{
66
+ resource: { attributes: attributes(resourceAttrs) },
67
+ scopeSpans: [{ scope: SCOPE, spans }],
68
+ }],
69
+ }),
70
+ signal: AbortSignal.timeout(timeoutMs),
71
+ });
72
+ if (!res.ok)
73
+ throw new Error(`OTLP export failed: HTTP ${res.status}`);
74
+ }
75
+ catch (err) {
76
+ options.onError?.(err instanceof Error ? err : new Error(String(err)));
77
+ }
78
+ };
79
+ const flush = () => {
80
+ if (queue.length > 0) {
81
+ const batch = queue;
82
+ queue = [];
83
+ chain = chain.then(() => send(batch));
84
+ }
85
+ return chain;
86
+ };
87
+ const exporter = ((span) => {
88
+ queue.push(toOtlpSpan(span));
89
+ if (queue.length >= batchSize) {
90
+ void flush();
91
+ return;
92
+ }
93
+ if (!timer && intervalMs > 0) {
94
+ timer = setInterval(() => { void flush(); }, intervalMs);
95
+ timer.unref?.();
96
+ }
97
+ });
98
+ exporter.flush = flush;
99
+ exporter.shutdown = async () => {
100
+ if (timer) {
101
+ clearInterval(timer);
102
+ timer = undefined;
103
+ }
104
+ await flush();
105
+ };
106
+ return exporter;
107
+ }
108
+ //# sourceMappingURL=otlpHttpExporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otlpHttpExporter.js","sourceRoot":"","sources":["../src/otlpHttpExporter.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,53 @@
1
- {
2
- "name": "@backendkit-labs/agent-otel",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {
2
+ "name": "@backendkit-labs/agent-otel",
3
+ "version": "0.1.0",
4
+ "description": "OpenTelemetry export for @backendkit-labs/agent-core traces: OTLP/HTTP exporter (no SDK needed) and an adapter for the OpenTelemetry API, with GenAI semantic conventions",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist/",
15
+ "README.md"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "dev": "tsc --watch",
20
+ "typecheck": "tsc --noEmit",
21
+ "prepublishOnly": "npm run build",
22
+ "test": "jest"
23
+ },
24
+ "peerDependencies": {
25
+ "@backendkit-labs/agent-core": ">=0.27.0 <0.29.0",
26
+ "@opentelemetry/api": ">=1.4.0 <2"
27
+ },
28
+ "peerDependenciesMeta": {
29
+ "@opentelemetry/api": {
30
+ "optional": true
31
+ }
32
+ },
33
+ "devDependencies": {
34
+ "@opentelemetry/api": "^1.9.0",
35
+ "@types/jest": "^29.0.0",
36
+ "@types/node": "^20.0.0",
37
+ "jest": "^29.0.0",
38
+ "ts-jest": "^29.0.0",
39
+ "typescript": "^5.4.0"
40
+ },
41
+ "engines": {
42
+ "node": ">=20.0.0"
43
+ },
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/BackendKit-labs/backendkit-agent-framework.git",
47
+ "directory": "packages/otel"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "license": "MIT"
53
+ }