@nightowlsdev/telemetry-core 0.1.2 → 2.0.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.cjs +40 -2
- package/dist/index.d.cts +57 -2
- package/dist/index.d.ts +57 -2
- package/dist/index.js +40 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -21,11 +21,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
KIND_MAP: () => KIND_MAP,
|
|
24
|
+
aisdkTelemetry: () => aisdkTelemetry,
|
|
24
25
|
createSpanReplayer: () => createSpanReplayer,
|
|
25
26
|
deriveRootSpanId: () => deriveRootSpanId,
|
|
26
27
|
deriveTraceId: () => deriveTraceId,
|
|
27
28
|
replayerExporter: () => replayerExporter,
|
|
28
|
-
toOtelAttributes: () => toOtelAttributes
|
|
29
|
+
toOtelAttributes: () => toOtelAttributes,
|
|
30
|
+
withEmbeddingSpan: () => withEmbeddingSpan
|
|
29
31
|
});
|
|
30
32
|
module.exports = __toCommonJS(index_exports);
|
|
31
33
|
|
|
@@ -135,12 +137,48 @@ function replayerExporter(opts) {
|
|
|
135
137
|
const replayer = createSpanReplayer(opts);
|
|
136
138
|
return { export: (spans) => replayer.replay(spans) };
|
|
137
139
|
}
|
|
140
|
+
|
|
141
|
+
// src/aisdk.ts
|
|
142
|
+
var import_api3 = require("@opentelemetry/api");
|
|
143
|
+
var import_incubating2 = require("@opentelemetry/semantic-conventions/incubating");
|
|
144
|
+
function aisdkTelemetry(opts) {
|
|
145
|
+
return {
|
|
146
|
+
isEnabled: true,
|
|
147
|
+
functionId: opts.functionId,
|
|
148
|
+
...opts.metadata ? { metadata: opts.metadata } : {},
|
|
149
|
+
recordInputs: opts.recordInputs ?? false,
|
|
150
|
+
recordOutputs: opts.recordOutputs ?? false
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
async function withEmbeddingSpan(opts, fn) {
|
|
154
|
+
const tracer = opts.tracer ?? import_api3.trace.getTracer("nightowls");
|
|
155
|
+
return tracer.startActiveSpan(opts.functionId, async (span) => {
|
|
156
|
+
const attrs = { "nightowls.span.kind": "embedding", [import_incubating2.ATTR_GEN_AI_REQUEST_MODEL]: opts.model, "gen_ai.operation.name": "embeddings" };
|
|
157
|
+
if (typeof opts.count === "number") attrs["gen_ai.request.embedding.count"] = opts.count;
|
|
158
|
+
for (const [k, v] of Object.entries(opts.metadata ?? {})) attrs[k] = v;
|
|
159
|
+
span.setAttributes(attrs);
|
|
160
|
+
try {
|
|
161
|
+
const out = await fn();
|
|
162
|
+
const tokens = opts.tokens ?? out?.usage?.tokens;
|
|
163
|
+
if (typeof tokens === "number") span.setAttribute(import_incubating2.ATTR_GEN_AI_USAGE_INPUT_TOKENS, tokens);
|
|
164
|
+
span.setStatus({ code: import_api3.SpanStatusCode.OK });
|
|
165
|
+
return out;
|
|
166
|
+
} catch (err) {
|
|
167
|
+
span.setStatus({ code: import_api3.SpanStatusCode.ERROR, message: err instanceof Error ? err.message : String(err) });
|
|
168
|
+
throw err;
|
|
169
|
+
} finally {
|
|
170
|
+
span.end();
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
138
174
|
// Annotate the CommonJS export names for ESM import in node:
|
|
139
175
|
0 && (module.exports = {
|
|
140
176
|
KIND_MAP,
|
|
177
|
+
aisdkTelemetry,
|
|
141
178
|
createSpanReplayer,
|
|
142
179
|
deriveRootSpanId,
|
|
143
180
|
deriveTraceId,
|
|
144
181
|
replayerExporter,
|
|
145
|
-
toOtelAttributes
|
|
182
|
+
toOtelAttributes,
|
|
183
|
+
withEmbeddingSpan
|
|
146
184
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SpanExporter, SpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
2
2
|
import { SwarmSpan, TelemetryExporter } from '@nightowlsdev/core';
|
|
3
|
-
import { SpanKind, Attributes } from '@opentelemetry/api';
|
|
3
|
+
import { SpanKind, Attributes, Tracer } from '@opentelemetry/api';
|
|
4
4
|
|
|
5
5
|
interface SpanReplayerOpts {
|
|
6
6
|
/** Provide a SpanExporter (telemetry-otel: OTLP) OR a full SpanProcessor (telemetry-langfuse). */
|
|
@@ -27,4 +27,59 @@ declare const KIND_MAP: Record<SwarmSpan["kind"], SpanKind>;
|
|
|
27
27
|
/** Map a SwarmSpan's attributes to OTel attributes, lifting generation usage/model/cost to gen_ai.* semconv. */
|
|
28
28
|
declare function toOtelAttributes(span: SwarmSpan): Attributes;
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
/** The subset of the AI SDK's `experimental_telemetry` settings we produce (typed locally so this package keeps
|
|
31
|
+
* ZERO dependency on `ai`). Assignable to `generateText`/`streamText`'s `experimental_telemetry` field. */
|
|
32
|
+
interface AiSdkTelemetrySettings {
|
|
33
|
+
isEnabled: boolean;
|
|
34
|
+
functionId?: string;
|
|
35
|
+
metadata?: Record<string, string | number | boolean>;
|
|
36
|
+
recordInputs?: boolean;
|
|
37
|
+
recordOutputs?: boolean;
|
|
38
|
+
}
|
|
39
|
+
interface AiSdkTelemetryOpts {
|
|
40
|
+
/** A stable name for this call (the Langfuse generation name / OTel span functionId), e.g. "entity_extraction". */
|
|
41
|
+
functionId: string;
|
|
42
|
+
/** Arbitrary key/values attached to the span (trace correlation: userId, tenantId, jobId, …). */
|
|
43
|
+
metadata?: Record<string, string | number | boolean>;
|
|
44
|
+
/** Record prompt/response bodies on the span. Default false (privacy-safe — only metadata + usage). */
|
|
45
|
+
recordInputs?: boolean;
|
|
46
|
+
recordOutputs?: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* FR-008 — produce `experimental_telemetry` settings for ANY `generateText`/`streamText`, engine-independent, so a
|
|
50
|
+
* non-swarm call lands in Langfuse/OTel with `gen_ai.*` + tokens + cost via the host's existing exporter.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* await generateText({ model, prompt, experimental_telemetry: aisdkTelemetry({ functionId: "summarize" }) });
|
|
54
|
+
*/
|
|
55
|
+
declare function aisdkTelemetry(opts: AiSdkTelemetryOpts): AiSdkTelemetrySettings;
|
|
56
|
+
interface EmbeddingSpanOpts {
|
|
57
|
+
/** A stable name for this embedding call, e.g. "embedding" (the OTel span name). */
|
|
58
|
+
functionId: string;
|
|
59
|
+
/** The embedding model id (lands on `gen_ai.request.model`). */
|
|
60
|
+
model: string;
|
|
61
|
+
/** Number of inputs embedded (lands on `gen_ai.usage.input_tokens` is NOT it — this is the value count). */
|
|
62
|
+
count?: number;
|
|
63
|
+
/** Token usage, if the caller can supply it (the AI SDK's `embedMany` returns `.usage.tokens`). */
|
|
64
|
+
tokens?: number;
|
|
65
|
+
/** Extra correlation attributes (userId, tenantId, …). */
|
|
66
|
+
metadata?: Record<string, string | number | boolean>;
|
|
67
|
+
/** Override the tracer (defaults to the global `nightowls` tracer the host's provider registered). */
|
|
68
|
+
tracer?: Tracer;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* FR-008/FR-012 — wrap an EMBEDDING call (`embed`/`embedMany`) in an OTel `gen_ai.*` span, since the AI SDK's
|
|
72
|
+
* `experimental_telemetry` callbacks do NOT fire for embeddings. Records model + value count (+ token usage if the
|
|
73
|
+
* result exposes it). Forwards through the host's existing SpanProcessor. Re-throws after recording on error.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* const res = await withEmbeddingSpan({ functionId: "embedding", model: "text-embedding-3-small", count: values.length },
|
|
77
|
+
* () => embedMany({ model, values }));
|
|
78
|
+
*/
|
|
79
|
+
declare function withEmbeddingSpan<T extends {
|
|
80
|
+
usage?: {
|
|
81
|
+
tokens?: number;
|
|
82
|
+
};
|
|
83
|
+
}>(opts: EmbeddingSpanOpts, fn: () => Promise<T>): Promise<T>;
|
|
84
|
+
|
|
85
|
+
export { type AiSdkTelemetryOpts, type AiSdkTelemetrySettings, type EmbeddingSpanOpts, KIND_MAP, type SpanReplayer, type SpanReplayerOpts, aisdkTelemetry, createSpanReplayer, deriveRootSpanId, deriveTraceId, replayerExporter, toOtelAttributes, withEmbeddingSpan };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SpanExporter, SpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
2
2
|
import { SwarmSpan, TelemetryExporter } from '@nightowlsdev/core';
|
|
3
|
-
import { SpanKind, Attributes } from '@opentelemetry/api';
|
|
3
|
+
import { SpanKind, Attributes, Tracer } from '@opentelemetry/api';
|
|
4
4
|
|
|
5
5
|
interface SpanReplayerOpts {
|
|
6
6
|
/** Provide a SpanExporter (telemetry-otel: OTLP) OR a full SpanProcessor (telemetry-langfuse). */
|
|
@@ -27,4 +27,59 @@ declare const KIND_MAP: Record<SwarmSpan["kind"], SpanKind>;
|
|
|
27
27
|
/** Map a SwarmSpan's attributes to OTel attributes, lifting generation usage/model/cost to gen_ai.* semconv. */
|
|
28
28
|
declare function toOtelAttributes(span: SwarmSpan): Attributes;
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
/** The subset of the AI SDK's `experimental_telemetry` settings we produce (typed locally so this package keeps
|
|
31
|
+
* ZERO dependency on `ai`). Assignable to `generateText`/`streamText`'s `experimental_telemetry` field. */
|
|
32
|
+
interface AiSdkTelemetrySettings {
|
|
33
|
+
isEnabled: boolean;
|
|
34
|
+
functionId?: string;
|
|
35
|
+
metadata?: Record<string, string | number | boolean>;
|
|
36
|
+
recordInputs?: boolean;
|
|
37
|
+
recordOutputs?: boolean;
|
|
38
|
+
}
|
|
39
|
+
interface AiSdkTelemetryOpts {
|
|
40
|
+
/** A stable name for this call (the Langfuse generation name / OTel span functionId), e.g. "entity_extraction". */
|
|
41
|
+
functionId: string;
|
|
42
|
+
/** Arbitrary key/values attached to the span (trace correlation: userId, tenantId, jobId, …). */
|
|
43
|
+
metadata?: Record<string, string | number | boolean>;
|
|
44
|
+
/** Record prompt/response bodies on the span. Default false (privacy-safe — only metadata + usage). */
|
|
45
|
+
recordInputs?: boolean;
|
|
46
|
+
recordOutputs?: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* FR-008 — produce `experimental_telemetry` settings for ANY `generateText`/`streamText`, engine-independent, so a
|
|
50
|
+
* non-swarm call lands in Langfuse/OTel with `gen_ai.*` + tokens + cost via the host's existing exporter.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* await generateText({ model, prompt, experimental_telemetry: aisdkTelemetry({ functionId: "summarize" }) });
|
|
54
|
+
*/
|
|
55
|
+
declare function aisdkTelemetry(opts: AiSdkTelemetryOpts): AiSdkTelemetrySettings;
|
|
56
|
+
interface EmbeddingSpanOpts {
|
|
57
|
+
/** A stable name for this embedding call, e.g. "embedding" (the OTel span name). */
|
|
58
|
+
functionId: string;
|
|
59
|
+
/** The embedding model id (lands on `gen_ai.request.model`). */
|
|
60
|
+
model: string;
|
|
61
|
+
/** Number of inputs embedded (lands on `gen_ai.usage.input_tokens` is NOT it — this is the value count). */
|
|
62
|
+
count?: number;
|
|
63
|
+
/** Token usage, if the caller can supply it (the AI SDK's `embedMany` returns `.usage.tokens`). */
|
|
64
|
+
tokens?: number;
|
|
65
|
+
/** Extra correlation attributes (userId, tenantId, …). */
|
|
66
|
+
metadata?: Record<string, string | number | boolean>;
|
|
67
|
+
/** Override the tracer (defaults to the global `nightowls` tracer the host's provider registered). */
|
|
68
|
+
tracer?: Tracer;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* FR-008/FR-012 — wrap an EMBEDDING call (`embed`/`embedMany`) in an OTel `gen_ai.*` span, since the AI SDK's
|
|
72
|
+
* `experimental_telemetry` callbacks do NOT fire for embeddings. Records model + value count (+ token usage if the
|
|
73
|
+
* result exposes it). Forwards through the host's existing SpanProcessor. Re-throws after recording on error.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* const res = await withEmbeddingSpan({ functionId: "embedding", model: "text-embedding-3-small", count: values.length },
|
|
77
|
+
* () => embedMany({ model, values }));
|
|
78
|
+
*/
|
|
79
|
+
declare function withEmbeddingSpan<T extends {
|
|
80
|
+
usage?: {
|
|
81
|
+
tokens?: number;
|
|
82
|
+
};
|
|
83
|
+
}>(opts: EmbeddingSpanOpts, fn: () => Promise<T>): Promise<T>;
|
|
84
|
+
|
|
85
|
+
export { type AiSdkTelemetryOpts, type AiSdkTelemetrySettings, type EmbeddingSpanOpts, KIND_MAP, type SpanReplayer, type SpanReplayerOpts, aisdkTelemetry, createSpanReplayer, deriveRootSpanId, deriveTraceId, replayerExporter, toOtelAttributes, withEmbeddingSpan };
|
package/dist/index.js
CHANGED
|
@@ -111,11 +111,50 @@ function replayerExporter(opts) {
|
|
|
111
111
|
const replayer = createSpanReplayer(opts);
|
|
112
112
|
return { export: (spans) => replayer.replay(spans) };
|
|
113
113
|
}
|
|
114
|
+
|
|
115
|
+
// src/aisdk.ts
|
|
116
|
+
import { trace as trace2, SpanStatusCode } from "@opentelemetry/api";
|
|
117
|
+
import {
|
|
118
|
+
ATTR_GEN_AI_REQUEST_MODEL as ATTR_GEN_AI_REQUEST_MODEL2,
|
|
119
|
+
ATTR_GEN_AI_USAGE_INPUT_TOKENS as ATTR_GEN_AI_USAGE_INPUT_TOKENS2
|
|
120
|
+
} from "@opentelemetry/semantic-conventions/incubating";
|
|
121
|
+
function aisdkTelemetry(opts) {
|
|
122
|
+
return {
|
|
123
|
+
isEnabled: true,
|
|
124
|
+
functionId: opts.functionId,
|
|
125
|
+
...opts.metadata ? { metadata: opts.metadata } : {},
|
|
126
|
+
recordInputs: opts.recordInputs ?? false,
|
|
127
|
+
recordOutputs: opts.recordOutputs ?? false
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
async function withEmbeddingSpan(opts, fn) {
|
|
131
|
+
const tracer = opts.tracer ?? trace2.getTracer("nightowls");
|
|
132
|
+
return tracer.startActiveSpan(opts.functionId, async (span) => {
|
|
133
|
+
const attrs = { "nightowls.span.kind": "embedding", [ATTR_GEN_AI_REQUEST_MODEL2]: opts.model, "gen_ai.operation.name": "embeddings" };
|
|
134
|
+
if (typeof opts.count === "number") attrs["gen_ai.request.embedding.count"] = opts.count;
|
|
135
|
+
for (const [k, v] of Object.entries(opts.metadata ?? {})) attrs[k] = v;
|
|
136
|
+
span.setAttributes(attrs);
|
|
137
|
+
try {
|
|
138
|
+
const out = await fn();
|
|
139
|
+
const tokens = opts.tokens ?? out?.usage?.tokens;
|
|
140
|
+
if (typeof tokens === "number") span.setAttribute(ATTR_GEN_AI_USAGE_INPUT_TOKENS2, tokens);
|
|
141
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
142
|
+
return out;
|
|
143
|
+
} catch (err) {
|
|
144
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: err instanceof Error ? err.message : String(err) });
|
|
145
|
+
throw err;
|
|
146
|
+
} finally {
|
|
147
|
+
span.end();
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
114
151
|
export {
|
|
115
152
|
KIND_MAP,
|
|
153
|
+
aisdkTelemetry,
|
|
116
154
|
createSpanReplayer,
|
|
117
155
|
deriveRootSpanId,
|
|
118
156
|
deriveTraceId,
|
|
119
157
|
replayerExporter,
|
|
120
|
-
toOtelAttributes
|
|
158
|
+
toOtelAttributes,
|
|
159
|
+
withEmbeddingSpan
|
|
121
160
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nightowlsdev/telemetry-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -34,16 +34,16 @@
|
|
|
34
34
|
"@opentelemetry/semantic-conventions": "^1.36.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@nightowlsdev/core": "0.
|
|
37
|
+
"@nightowlsdev/core": "0.5.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^24.12.4",
|
|
41
41
|
"tsup": "8.5.1",
|
|
42
42
|
"typescript": "6.0.3",
|
|
43
43
|
"vitest": "^3.2.0",
|
|
44
|
-
"@nightowlsdev/core": "0.
|
|
45
|
-
"@nightowlsdev/
|
|
46
|
-
"@nightowlsdev/
|
|
44
|
+
"@nightowlsdev/core": "0.5.0",
|
|
45
|
+
"@nightowlsdev/eslint-config": "0.0.0",
|
|
46
|
+
"@nightowlsdev/tsconfig": "0.0.0"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "tsup",
|