@noetaris/harness-otel 0.1.0 → 0.2.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/dist/index.d.ts +11 -17
- package/dist/index.js +60 -18
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -9,38 +9,32 @@ interface OtelObserverOptions {
|
|
|
9
9
|
meterProvider?: MeterProvider;
|
|
10
10
|
/**
|
|
11
11
|
* Explicit parent context for the root span. When provided, the root
|
|
12
|
-
*
|
|
12
|
+
* "invoke_agent {agentId}" span is created as a child of the span in this context.
|
|
13
13
|
* When absent, `context.active()` is used (ambient context from OTel middleware).
|
|
14
14
|
*/
|
|
15
15
|
parentContext?: Context;
|
|
16
16
|
/**
|
|
17
|
-
* Extra attributes merged onto the root
|
|
18
|
-
* These are merged with the built-in attributes
|
|
19
|
-
* if a key conflicts, the built-in attribute takes precedence.
|
|
17
|
+
* Extra attributes merged onto the root span at start time.
|
|
18
|
+
* These are merged with the built-in attributes; built-in attributes take precedence.
|
|
20
19
|
*/
|
|
21
20
|
attributes?: Attributes;
|
|
22
21
|
}
|
|
23
22
|
/**
|
|
24
|
-
* Create an {@link Observer} that records traces and metrics via OpenTelemetry
|
|
23
|
+
* Create an {@link Observer} that records traces and metrics via OpenTelemetry
|
|
24
|
+
* following the GenAI semantic conventions.
|
|
25
25
|
*
|
|
26
26
|
* **Spans produced:**
|
|
27
|
-
* - `
|
|
28
|
-
* - `
|
|
27
|
+
* - `invoke_agent {agentId}` — root span, one per `agent.run()` invocation.
|
|
28
|
+
* - `harness.step {stepName}` — child span, one per step execution.
|
|
29
|
+
* - `chat {modelId}` — INTERNAL child span, one per `"llm.response"` event.
|
|
30
|
+
* - `execute_tool {toolName}` — INTERNAL child span, one per `"tool.call"` event.
|
|
29
31
|
*
|
|
30
32
|
* **Metrics produced** (requires `options.meterProvider`):
|
|
31
|
-
* - `
|
|
32
|
-
* - `
|
|
33
|
+
* - `gen_ai.client.token.usage` (histogram, `{token}`) — input/output tokens per inference call.
|
|
34
|
+
* - `gen_ai.client.operation.duration` (histogram, `s`) — agent invocation duration.
|
|
33
35
|
*
|
|
34
36
|
* @param tracer - An OTel `Tracer` instance from your SDK.
|
|
35
37
|
* @param options - Optional meter provider, parent context, and extra span attributes.
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```ts
|
|
39
|
-
* const observer = createOtelObserver(trace.getTracer('my-agent'), {
|
|
40
|
-
* meterProvider: metrics.getMeterProvider(),
|
|
41
|
-
* })
|
|
42
|
-
* agent.run({}, { llm, observer })
|
|
43
|
-
* ```
|
|
44
38
|
*/
|
|
45
39
|
declare function createOtelObserver(tracer: Tracer, options?: OtelObserverOptions): Observer;
|
|
46
40
|
|
package/dist/index.js
CHANGED
|
@@ -1,59 +1,101 @@
|
|
|
1
1
|
// src/observer/otel-observer.ts
|
|
2
|
-
import { SpanStatusCode, context, trace } from "@opentelemetry/api";
|
|
2
|
+
import { SpanStatusCode, SpanKind, context, trace } from "@opentelemetry/api";
|
|
3
3
|
function createOtelObserver(tracer, options) {
|
|
4
4
|
let rootSpan;
|
|
5
5
|
let stepSpan;
|
|
6
|
-
|
|
7
|
-
let
|
|
6
|
+
const toolSpans = /* @__PURE__ */ new Map();
|
|
7
|
+
let tokenHistogram;
|
|
8
|
+
let durationHistogram;
|
|
8
9
|
if (options?.meterProvider) {
|
|
9
10
|
const meter = options.meterProvider.getMeter("@noetaris/harness-otel", "0.1.0");
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
tokenHistogram = meter.createHistogram("gen_ai.client.token.usage", { unit: "{token}" });
|
|
12
|
+
durationHistogram = meter.createHistogram("gen_ai.client.operation.duration", { unit: "s" });
|
|
12
13
|
}
|
|
13
14
|
return {
|
|
14
15
|
onRunStart(ctx) {
|
|
15
16
|
const builtIn = {
|
|
16
|
-
"agent.id": ctx.agentId,
|
|
17
|
-
"
|
|
17
|
+
"gen_ai.agent.id": ctx.agentId,
|
|
18
|
+
"gen_ai.conversation.id": ctx.sessionId,
|
|
19
|
+
"gen_ai.operation.name": "invoke_agent"
|
|
18
20
|
};
|
|
19
21
|
const merged = { ...options?.attributes, ...builtIn };
|
|
20
22
|
const parentCtx = options?.parentContext ?? context.active();
|
|
21
|
-
rootSpan = tracer.startSpan(
|
|
23
|
+
rootSpan = tracer.startSpan(`invoke_agent ${ctx.agentId}`, { attributes: merged }, parentCtx);
|
|
22
24
|
},
|
|
23
|
-
onRunEnd(_ctx,
|
|
25
|
+
onRunEnd(_ctx, event) {
|
|
24
26
|
if (!rootSpan) return;
|
|
25
27
|
rootSpan.end();
|
|
26
28
|
rootSpan = void 0;
|
|
29
|
+
durationHistogram?.record(event.durationMs / 1e3, { "gen_ai.operation.name": "invoke_agent" });
|
|
27
30
|
},
|
|
28
31
|
onStepStart(ctx) {
|
|
29
32
|
if (!rootSpan) return;
|
|
30
33
|
const childCtx = trace.setSpan(context.active(), rootSpan);
|
|
31
|
-
stepSpan = tracer.startSpan(
|
|
34
|
+
stepSpan = tracer.startSpan(`harness.step ${ctx.stepName}`, { attributes: { "gen_ai.step.name": ctx.stepName } }, childCtx);
|
|
32
35
|
},
|
|
33
|
-
onStepEnd(
|
|
36
|
+
onStepEnd(_ctx, _event) {
|
|
34
37
|
if (!stepSpan) return;
|
|
35
38
|
const span = stepSpan;
|
|
36
39
|
stepSpan = void 0;
|
|
37
|
-
histogram?.record(event.durationMs, { "step.name": ctx.stepName });
|
|
38
40
|
span.end();
|
|
39
41
|
},
|
|
40
|
-
onStepError(
|
|
42
|
+
onStepError(_ctx, event) {
|
|
41
43
|
if (!stepSpan) return;
|
|
42
44
|
const span = stepSpan;
|
|
43
45
|
stepSpan = void 0;
|
|
44
46
|
span.setStatus({ code: SpanStatusCode.ERROR, message: String(event.error) });
|
|
45
|
-
histogram?.record(event.durationMs, { "step.name": ctx.stepName });
|
|
46
47
|
span.end();
|
|
47
48
|
},
|
|
48
49
|
onEvent(_ctx, type, payload) {
|
|
49
50
|
if (type === "llm.response") {
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
const shaped = payload;
|
|
52
|
+
const parentSpan = stepSpan ?? rootSpan;
|
|
53
|
+
if (parentSpan && typeof shaped?.modelId === "string" && typeof shaped?.providerName === "string") {
|
|
54
|
+
const childCtx = trace.setSpan(context.active(), parentSpan);
|
|
55
|
+
const inferenceSpan = tracer.startSpan(`chat ${shaped.modelId}`, { kind: SpanKind.INTERNAL }, childCtx);
|
|
56
|
+
inferenceSpan.setAttribute("gen_ai.request.model", shaped.modelId);
|
|
57
|
+
inferenceSpan.setAttribute("gen_ai.provider.name", shaped.providerName);
|
|
52
58
|
if (typeof shaped?.tokens?.input === "number" && typeof shaped?.tokens?.output === "number") {
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
inferenceSpan.setAttribute("gen_ai.usage.input_tokens", shaped.tokens.input);
|
|
60
|
+
inferenceSpan.setAttribute("gen_ai.usage.output_tokens", shaped.tokens.output);
|
|
55
61
|
}
|
|
62
|
+
if (typeof shaped?.stopReason === "string") {
|
|
63
|
+
inferenceSpan.setAttribute("gen_ai.response.finish_reasons", [shaped.stopReason]);
|
|
64
|
+
}
|
|
65
|
+
inferenceSpan.end();
|
|
66
|
+
}
|
|
67
|
+
if (tokenHistogram && typeof shaped?.tokens?.input === "number" && typeof shaped?.tokens?.output === "number") {
|
|
68
|
+
tokenHistogram.record(shaped.tokens.input, { "gen_ai.token.type": "input" });
|
|
69
|
+
tokenHistogram.record(shaped.tokens.output, { "gen_ai.token.type": "output" });
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (type === "tool.call") {
|
|
74
|
+
const shaped = payload;
|
|
75
|
+
if (typeof shaped?.toolName !== "string" || typeof shaped?.toolCallId !== "string") return;
|
|
76
|
+
const parentSpan = stepSpan ?? rootSpan;
|
|
77
|
+
if (!parentSpan) return;
|
|
78
|
+
const childCtx = trace.setSpan(context.active(), parentSpan);
|
|
79
|
+
const toolSpan = tracer.startSpan("execute_tool " + shaped.toolName, {
|
|
80
|
+
kind: SpanKind.INTERNAL,
|
|
81
|
+
attributes: {
|
|
82
|
+
"gen_ai.tool.name": shaped.toolName,
|
|
83
|
+
"gen_ai.operation.name": "execute_tool"
|
|
84
|
+
}
|
|
85
|
+
}, childCtx);
|
|
86
|
+
toolSpans.set(shaped.toolCallId, toolSpan);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (type === "tool.result") {
|
|
90
|
+
const shaped = payload;
|
|
91
|
+
if (typeof shaped?.toolCallId !== "string") return;
|
|
92
|
+
const toolSpan = toolSpans.get(shaped.toolCallId);
|
|
93
|
+
if (!toolSpan) return;
|
|
94
|
+
toolSpans.delete(shaped.toolCallId);
|
|
95
|
+
if (shaped.error !== void 0) {
|
|
96
|
+
toolSpan.setStatus({ code: SpanStatusCode.ERROR, message: String(shaped.error) });
|
|
56
97
|
}
|
|
98
|
+
toolSpan.end();
|
|
57
99
|
return;
|
|
58
100
|
}
|
|
59
101
|
const activeSpan = stepSpan ?? rootSpan;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/observer/otel-observer.ts"],"sourcesContent":["import type { Tracer, MeterProvider, Context, Attributes, Span, Counter, Histogram } from '@opentelemetry/api'\nimport { SpanStatusCode, context, trace } from '@opentelemetry/api'\nimport type { Observer, RunContext, StepContext } from '@noetaris/harness'\n\n/**\n * Options for {@link createOtelObserver}.\n */\nexport interface OtelObserverOptions {\n /** OTel MeterProvider to use for metrics. If absent, metrics are skipped. */\n meterProvider?: MeterProvider\n /**\n * Explicit parent context for the root span. When provided, the root\n * `agent.run` span is created as a child of the span in this context.\n * When absent, `context.active()` is used (ambient context from OTel middleware).\n */\n parentContext?: Context\n /**\n * Extra attributes merged onto the root `agent.run` span at start time.\n * These are merged with the built-in attributes (`agent.id`, `session.id`);\n * if a key conflicts, the built-in attribute takes precedence.\n */\n attributes?: Attributes\n}\n\n// Local shape guard — avoids importing @noetaris/harness-types\ntype LLMUsageShape = { tokens?: { input?: unknown; output?: unknown } | null }\n\n/**\n * Create an {@link Observer} that records traces and metrics via OpenTelemetry.\n *\n * **Spans produced:**\n * - `agent.run` — root span, one per `agent.run()` invocation.\n * - `agent.step` — child span, one per step execution.\n *\n * **Metrics produced** (requires `options.meterProvider`):\n * - `agent.llm.tokens` (counter) — input/output tokens, tagged by `token.type`.\n * - `agent.step.duration` (histogram, ms) — step duration, tagged by `step.name`.\n *\n * @param tracer - An OTel `Tracer` instance from your SDK.\n * @param options - Optional meter provider, parent context, and extra span attributes.\n *\n * @example\n * ```ts\n * const observer = createOtelObserver(trace.getTracer('my-agent'), {\n * meterProvider: metrics.getMeterProvider(),\n * })\n * agent.run({}, { llm, observer })\n * ```\n */\nexport function createOtelObserver(tracer: Tracer, options?: OtelObserverOptions): Observer {\n let rootSpan: Span | undefined\n let stepSpan: Span | undefined\n\n let counter: Counter | undefined\n let histogram: Histogram | undefined\n\n if (options?.meterProvider) {\n const meter = options.meterProvider.getMeter('@noetaris/harness-otel', '0.1.0')\n counter = meter.createCounter('agent.llm.tokens')\n histogram = meter.createHistogram('agent.step.duration', { unit: 'ms' })\n }\n\n return {\n onRunStart(ctx: RunContext): void {\n const builtIn: Attributes = {\n 'agent.id': ctx.agentId,\n 'session.id': ctx.sessionId,\n }\n const merged: Attributes = { ...options?.attributes, ...builtIn }\n const parentCtx = options?.parentContext ?? context.active()\n rootSpan = tracer.startSpan('agent.run', { attributes: merged }, parentCtx)\n },\n\n onRunEnd(_ctx: RunContext, _event: { signal: string; durationMs: number }): void {\n if (!rootSpan) return\n rootSpan.end()\n rootSpan = undefined\n },\n\n onStepStart(ctx: StepContext): void {\n if (!rootSpan) return\n const childCtx = trace.setSpan(context.active(), rootSpan)\n stepSpan = tracer.startSpan('agent.step', { attributes: { 'step.name': ctx.stepName } }, childCtx)\n },\n\n onStepEnd(ctx: StepContext, event: { durationMs: number }): void {\n if (!stepSpan) return\n const span = stepSpan\n stepSpan = undefined\n histogram?.record(event.durationMs, { 'step.name': ctx.stepName })\n span.end()\n },\n\n onStepError(ctx: StepContext, event: { error: unknown; durationMs: number }): void {\n if (!stepSpan) return\n const span = stepSpan\n stepSpan = undefined\n span.setStatus({ code: SpanStatusCode.ERROR, message: String(event.error) })\n histogram?.record(event.durationMs, { 'step.name': ctx.stepName })\n span.end()\n },\n\n onEvent(_ctx: StepContext, type: string, payload: unknown): void {\n if (type === 'llm.response') {\n if (counter) {\n const shaped = payload as LLMUsageShape // as: payload is unknown; guard below validates the shape before use\n if (typeof shaped?.tokens?.input === 'number' && typeof shaped?.tokens?.output === 'number') {\n counter.add(shaped.tokens.input, { 'token.type': 'input' })\n counter.add(shaped.tokens.output, { 'token.type': 'output' })\n }\n }\n return\n }\n\n const activeSpan = stepSpan ?? rootSpan\n activeSpan?.addEvent(type)\n },\n }\n}\n"],"mappings":";AACA,SAAS,gBAAgB,SAAS,aAAa;AAgDxC,SAAS,mBAAmB,QAAgB,SAAyC;AAC1F,MAAI;AACJ,MAAI;AAEJ,MAAI;AACJ,MAAI;AAEJ,MAAI,SAAS,eAAe;AAC1B,UAAM,QAAQ,QAAQ,cAAc,SAAS,0BAA0B,OAAO;AAC9E,cAAU,MAAM,cAAc,kBAAkB;AAChD,gBAAY,MAAM,gBAAgB,uBAAuB,EAAE,MAAM,KAAK,CAAC;AAAA,EACzE;AAEA,SAAO;AAAA,IACL,WAAW,KAAuB;AAChC,YAAM,UAAsB;AAAA,QAC1B,YAAY,IAAI;AAAA,QAChB,cAAc,IAAI;AAAA,MACpB;AACA,YAAM,SAAqB,EAAE,GAAG,SAAS,YAAY,GAAG,QAAQ;AAChE,YAAM,YAAY,SAAS,iBAAiB,QAAQ,OAAO;AAC3D,iBAAW,OAAO,UAAU,aAAa,EAAE,YAAY,OAAO,GAAG,SAAS;AAAA,IAC5E;AAAA,IAEA,SAAS,MAAkB,QAAsD;AAC/E,UAAI,CAAC,SAAU;AACf,eAAS,IAAI;AACb,iBAAW;AAAA,IACb;AAAA,IAEA,YAAY,KAAwB;AAClC,UAAI,CAAC,SAAU;AACf,YAAM,WAAW,MAAM,QAAQ,QAAQ,OAAO,GAAG,QAAQ;AACzD,iBAAW,OAAO,UAAU,cAAc,EAAE,YAAY,EAAE,aAAa,IAAI,SAAS,EAAE,GAAG,QAAQ;AAAA,IACnG;AAAA,IAEA,UAAU,KAAkB,OAAqC;AAC/D,UAAI,CAAC,SAAU;AACf,YAAM,OAAO;AACb,iBAAW;AACX,iBAAW,OAAO,MAAM,YAAY,EAAE,aAAa,IAAI,SAAS,CAAC;AACjE,WAAK,IAAI;AAAA,IACX;AAAA,IAEA,YAAY,KAAkB,OAAqD;AACjF,UAAI,CAAC,SAAU;AACf,YAAM,OAAO;AACb,iBAAW;AACX,WAAK,UAAU,EAAE,MAAM,eAAe,OAAO,SAAS,OAAO,MAAM,KAAK,EAAE,CAAC;AAC3E,iBAAW,OAAO,MAAM,YAAY,EAAE,aAAa,IAAI,SAAS,CAAC;AACjE,WAAK,IAAI;AAAA,IACX;AAAA,IAEA,QAAQ,MAAmB,MAAc,SAAwB;AAC/D,UAAI,SAAS,gBAAgB;AAC3B,YAAI,SAAS;AACX,gBAAM,SAAS;AACf,cAAI,OAAO,QAAQ,QAAQ,UAAU,YAAY,OAAO,QAAQ,QAAQ,WAAW,UAAU;AAC3F,oBAAQ,IAAI,OAAO,OAAO,OAAO,EAAE,cAAc,QAAQ,CAAC;AAC1D,oBAAQ,IAAI,OAAO,OAAO,QAAQ,EAAE,cAAc,SAAS,CAAC;AAAA,UAC9D;AAAA,QACF;AACA;AAAA,MACF;AAEA,YAAM,aAAa,YAAY;AAC/B,kBAAY,SAAS,IAAI;AAAA,IAC3B;AAAA,EACF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/observer/otel-observer.ts"],"sourcesContent":["import type { Tracer, MeterProvider, Context, Attributes, Span, Histogram } from '@opentelemetry/api'\nimport { SpanStatusCode, SpanKind, context, trace } from '@opentelemetry/api'\nimport type { Observer, RunContext, StepContext } from '@noetaris/harness'\n\n/**\n * Options for {@link createOtelObserver}.\n */\nexport interface OtelObserverOptions {\n /** OTel MeterProvider to use for metrics. If absent, metrics are skipped. */\n meterProvider?: MeterProvider\n /**\n * Explicit parent context for the root span. When provided, the root\n * \"invoke_agent {agentId}\" span is created as a child of the span in this context.\n * When absent, `context.active()` is used (ambient context from OTel middleware).\n */\n parentContext?: Context\n /**\n * Extra attributes merged onto the root span at start time.\n * These are merged with the built-in attributes; built-in attributes take precedence.\n */\n attributes?: Attributes\n}\n\n// Local shape guards — avoid importing @noetaris/harness-types\ntype LLMUsageShape = {\n modelId?: unknown\n providerName?: unknown\n stopReason?: unknown\n tokens?: { input?: unknown; output?: unknown } | null\n}\ntype ToolCallShape = { toolName?: unknown; toolCallId?: unknown }\ntype ToolResultShape = { toolName?: unknown; toolCallId?: unknown; durationMs?: unknown; error?: unknown }\n\n/**\n * Create an {@link Observer} that records traces and metrics via OpenTelemetry\n * following the GenAI semantic conventions.\n *\n * **Spans produced:**\n * - `invoke_agent {agentId}` — root span, one per `agent.run()` invocation.\n * - `harness.step {stepName}` — child span, one per step execution.\n * - `chat {modelId}` — INTERNAL child span, one per `\"llm.response\"` event.\n * - `execute_tool {toolName}` — INTERNAL child span, one per `\"tool.call\"` event.\n *\n * **Metrics produced** (requires `options.meterProvider`):\n * - `gen_ai.client.token.usage` (histogram, `{token}`) — input/output tokens per inference call.\n * - `gen_ai.client.operation.duration` (histogram, `s`) — agent invocation duration.\n *\n * @param tracer - An OTel `Tracer` instance from your SDK.\n * @param options - Optional meter provider, parent context, and extra span attributes.\n */\nexport function createOtelObserver(tracer: Tracer, options?: OtelObserverOptions): Observer {\n let rootSpan: Span | undefined\n let stepSpan: Span | undefined\n const toolSpans = new Map<string, Span>()\n\n let tokenHistogram: Histogram | undefined\n let durationHistogram: Histogram | undefined\n\n if (options?.meterProvider) {\n const meter = options.meterProvider.getMeter('@noetaris/harness-otel', '0.1.0')\n tokenHistogram = meter.createHistogram('gen_ai.client.token.usage', { unit: '{token}' })\n durationHistogram = meter.createHistogram('gen_ai.client.operation.duration', { unit: 's' })\n }\n\n return {\n onRunStart(ctx: RunContext): void {\n const builtIn: Attributes = {\n 'gen_ai.agent.id': ctx.agentId,\n 'gen_ai.conversation.id': ctx.sessionId,\n 'gen_ai.operation.name': 'invoke_agent',\n }\n const merged: Attributes = { ...options?.attributes, ...builtIn }\n const parentCtx = options?.parentContext ?? context.active()\n rootSpan = tracer.startSpan(`invoke_agent ${ctx.agentId}`, { attributes: merged }, parentCtx)\n },\n\n onRunEnd(_ctx: RunContext, event: { signal: string; durationMs: number }): void {\n if (!rootSpan) return\n rootSpan.end()\n rootSpan = undefined\n durationHistogram?.record(event.durationMs / 1000, { 'gen_ai.operation.name': 'invoke_agent' })\n },\n\n onStepStart(ctx: StepContext): void {\n if (!rootSpan) return\n const childCtx = trace.setSpan(context.active(), rootSpan)\n stepSpan = tracer.startSpan(`harness.step ${ctx.stepName}`, { attributes: { 'gen_ai.step.name': ctx.stepName } }, childCtx)\n },\n\n onStepEnd(_ctx: StepContext, _event: { durationMs: number }): void {\n if (!stepSpan) return\n const span = stepSpan\n stepSpan = undefined\n span.end()\n },\n\n onStepError(_ctx: StepContext, event: { error: unknown; durationMs: number }): void {\n if (!stepSpan) return\n const span = stepSpan\n stepSpan = undefined\n span.setStatus({ code: SpanStatusCode.ERROR, message: String(event.error) })\n span.end()\n },\n\n onEvent(_ctx: StepContext, type: string, payload: unknown): void {\n if (type === 'llm.response') {\n const shaped = payload as LLMUsageShape // as: payload is unknown; guards below validate the shape before use\n\n // inference span — requires string modelId and providerName\n const parentSpan = stepSpan ?? rootSpan\n if (parentSpan && typeof shaped?.modelId === 'string' && typeof shaped?.providerName === 'string') {\n const childCtx = trace.setSpan(context.active(), parentSpan)\n const inferenceSpan = tracer.startSpan(`chat ${shaped.modelId}`, { kind: SpanKind.INTERNAL }, childCtx)\n inferenceSpan.setAttribute('gen_ai.request.model', shaped.modelId)\n inferenceSpan.setAttribute('gen_ai.provider.name', shaped.providerName)\n if (typeof shaped?.tokens?.input === 'number' && typeof shaped?.tokens?.output === 'number') {\n inferenceSpan.setAttribute('gen_ai.usage.input_tokens', shaped.tokens.input)\n inferenceSpan.setAttribute('gen_ai.usage.output_tokens', shaped.tokens.output)\n }\n if (typeof shaped?.stopReason === 'string') {\n inferenceSpan.setAttribute('gen_ai.response.finish_reasons', [shaped.stopReason])\n }\n inferenceSpan.end()\n }\n\n // token histogram — independent of span creation\n if (tokenHistogram && typeof shaped?.tokens?.input === 'number' && typeof shaped?.tokens?.output === 'number') {\n tokenHistogram.record(shaped.tokens.input, { 'gen_ai.token.type': 'input' })\n tokenHistogram.record(shaped.tokens.output, { 'gen_ai.token.type': 'output' })\n }\n return\n }\n\n if (type === 'tool.call') {\n const shaped = payload as ToolCallShape // as: payload is unknown; guard below validates the shape before use\n if (typeof shaped?.toolName !== 'string' || typeof shaped?.toolCallId !== 'string') return\n\n const parentSpan = stepSpan ?? rootSpan\n if (!parentSpan) return\n\n const childCtx = trace.setSpan(context.active(), parentSpan)\n const toolSpan = tracer.startSpan('execute_tool ' + shaped.toolName, {\n kind: SpanKind.INTERNAL,\n attributes: {\n 'gen_ai.tool.name': shaped.toolName,\n 'gen_ai.operation.name': 'execute_tool',\n },\n }, childCtx)\n toolSpans.set(shaped.toolCallId, toolSpan)\n return\n }\n\n if (type === 'tool.result') {\n const shaped = payload as ToolResultShape // as: payload is unknown; guard below validates the shape before use\n if (typeof shaped?.toolCallId !== 'string') return\n\n const toolSpan = toolSpans.get(shaped.toolCallId)\n if (!toolSpan) return\n\n toolSpans.delete(shaped.toolCallId)\n\n if (shaped.error !== undefined) {\n toolSpan.setStatus({ code: SpanStatusCode.ERROR, message: String(shaped.error) })\n }\n toolSpan.end()\n return\n }\n\n const activeSpan = stepSpan ?? rootSpan\n activeSpan?.addEvent(type)\n },\n }\n}\n"],"mappings":";AACA,SAAS,gBAAgB,UAAU,SAAS,aAAa;AAiDlD,SAAS,mBAAmB,QAAgB,SAAyC;AAC1F,MAAI;AACJ,MAAI;AACJ,QAAM,YAAY,oBAAI,IAAkB;AAExC,MAAI;AACJ,MAAI;AAEJ,MAAI,SAAS,eAAe;AAC1B,UAAM,QAAQ,QAAQ,cAAc,SAAS,0BAA0B,OAAO;AAC9E,qBAAiB,MAAM,gBAAgB,6BAA6B,EAAE,MAAM,UAAU,CAAC;AACvF,wBAAoB,MAAM,gBAAgB,oCAAoC,EAAE,MAAM,IAAI,CAAC;AAAA,EAC7F;AAEA,SAAO;AAAA,IACL,WAAW,KAAuB;AAChC,YAAM,UAAsB;AAAA,QAC1B,mBAAmB,IAAI;AAAA,QACvB,0BAA0B,IAAI;AAAA,QAC9B,yBAAyB;AAAA,MAC3B;AACA,YAAM,SAAqB,EAAE,GAAG,SAAS,YAAY,GAAG,QAAQ;AAChE,YAAM,YAAY,SAAS,iBAAiB,QAAQ,OAAO;AAC3D,iBAAW,OAAO,UAAU,gBAAgB,IAAI,OAAO,IAAI,EAAE,YAAY,OAAO,GAAG,SAAS;AAAA,IAC9F;AAAA,IAEA,SAAS,MAAkB,OAAqD;AAC9E,UAAI,CAAC,SAAU;AACf,eAAS,IAAI;AACb,iBAAW;AACX,yBAAmB,OAAO,MAAM,aAAa,KAAM,EAAE,yBAAyB,eAAe,CAAC;AAAA,IAChG;AAAA,IAEA,YAAY,KAAwB;AAClC,UAAI,CAAC,SAAU;AACf,YAAM,WAAW,MAAM,QAAQ,QAAQ,OAAO,GAAG,QAAQ;AACzD,iBAAW,OAAO,UAAU,gBAAgB,IAAI,QAAQ,IAAI,EAAE,YAAY,EAAE,oBAAoB,IAAI,SAAS,EAAE,GAAG,QAAQ;AAAA,IAC5H;AAAA,IAEA,UAAU,MAAmB,QAAsC;AACjE,UAAI,CAAC,SAAU;AACf,YAAM,OAAO;AACb,iBAAW;AACX,WAAK,IAAI;AAAA,IACX;AAAA,IAEA,YAAY,MAAmB,OAAqD;AAClF,UAAI,CAAC,SAAU;AACf,YAAM,OAAO;AACb,iBAAW;AACX,WAAK,UAAU,EAAE,MAAM,eAAe,OAAO,SAAS,OAAO,MAAM,KAAK,EAAE,CAAC;AAC3E,WAAK,IAAI;AAAA,IACX;AAAA,IAEA,QAAQ,MAAmB,MAAc,SAAwB;AAC/D,UAAI,SAAS,gBAAgB;AAC3B,cAAM,SAAS;AAGf,cAAM,aAAa,YAAY;AAC/B,YAAI,cAAc,OAAO,QAAQ,YAAY,YAAY,OAAO,QAAQ,iBAAiB,UAAU;AACjG,gBAAM,WAAW,MAAM,QAAQ,QAAQ,OAAO,GAAG,UAAU;AAC3D,gBAAM,gBAAgB,OAAO,UAAU,QAAQ,OAAO,OAAO,IAAI,EAAE,MAAM,SAAS,SAAS,GAAG,QAAQ;AACtG,wBAAc,aAAa,wBAAwB,OAAO,OAAO;AACjE,wBAAc,aAAa,wBAAwB,OAAO,YAAY;AACtE,cAAI,OAAO,QAAQ,QAAQ,UAAU,YAAY,OAAO,QAAQ,QAAQ,WAAW,UAAU;AAC3F,0BAAc,aAAa,6BAA6B,OAAO,OAAO,KAAK;AAC3E,0BAAc,aAAa,8BAA8B,OAAO,OAAO,MAAM;AAAA,UAC/E;AACA,cAAI,OAAO,QAAQ,eAAe,UAAU;AAC1C,0BAAc,aAAa,kCAAkC,CAAC,OAAO,UAAU,CAAC;AAAA,UAClF;AACA,wBAAc,IAAI;AAAA,QACpB;AAGA,YAAI,kBAAkB,OAAO,QAAQ,QAAQ,UAAU,YAAY,OAAO,QAAQ,QAAQ,WAAW,UAAU;AAC7G,yBAAe,OAAO,OAAO,OAAO,OAAO,EAAE,qBAAqB,QAAQ,CAAC;AAC3E,yBAAe,OAAO,OAAO,OAAO,QAAQ,EAAE,qBAAqB,SAAS,CAAC;AAAA,QAC/E;AACA;AAAA,MACF;AAEA,UAAI,SAAS,aAAa;AACxB,cAAM,SAAS;AACf,YAAI,OAAO,QAAQ,aAAa,YAAY,OAAO,QAAQ,eAAe,SAAU;AAEpF,cAAM,aAAa,YAAY;AAC/B,YAAI,CAAC,WAAY;AAEjB,cAAM,WAAW,MAAM,QAAQ,QAAQ,OAAO,GAAG,UAAU;AAC3D,cAAM,WAAW,OAAO,UAAU,kBAAkB,OAAO,UAAU;AAAA,UACnE,MAAM,SAAS;AAAA,UACf,YAAY;AAAA,YACV,oBAAoB,OAAO;AAAA,YAC3B,yBAAyB;AAAA,UAC3B;AAAA,QACF,GAAG,QAAQ;AACX,kBAAU,IAAI,OAAO,YAAY,QAAQ;AACzC;AAAA,MACF;AAEA,UAAI,SAAS,eAAe;AAC1B,cAAM,SAAS;AACf,YAAI,OAAO,QAAQ,eAAe,SAAU;AAE5C,cAAM,WAAW,UAAU,IAAI,OAAO,UAAU;AAChD,YAAI,CAAC,SAAU;AAEf,kBAAU,OAAO,OAAO,UAAU;AAElC,YAAI,OAAO,UAAU,QAAW;AAC9B,mBAAS,UAAU,EAAE,MAAM,eAAe,OAAO,SAAS,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,QAClF;AACA,iBAAS,IAAI;AACb;AAAA,MACF;AAEA,YAAM,aAAa,YAAY;AAC/B,kBAAY,SAAS,IAAI;AAAA,IAC3B;AAAA,EACF;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noetaris/harness-otel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "OpenTelemetry observer bridge for @noetaris/harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@opentelemetry/api": ">=1.0.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@noetaris/harness": "^0.
|
|
46
|
+
"@noetaris/harness": "^0.3.0",
|
|
47
47
|
"@opentelemetry/api": "^1.9.1",
|
|
48
48
|
"@types/node": "^25.8.0",
|
|
49
49
|
"tsup": "^8.5.1",
|