@telemetry-dev/sdk 0.1.7 → 0.1.9

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
@@ -3,8 +3,11 @@
3
3
  Pure TypeScript SDK for [telemetry.dev](https://telemetry.dev): add LLM traces, logs, and GenAI
4
4
  metrics to any app. Built on OpenTelemetry — spans use the `gen_ai.*` semantic conventions and
5
5
  ship as OTLP protobuf to `https://ingest.telemetry.dev`. Works on Node >= 20.19 and edge runtimes
6
- (Vercel Edge, Cloudflare Workers with `nodejs_compat`). ESM-only; CJS consumers can `require()` it
7
- on Node >= 20.19.
6
+ (Vercel Edge, and Cloudflare Workers with Node.js compatibility, which is on by default for
7
+ compatibility dates of 2026-08-04 or later; earlier dates need the `nodejs_compat` flag). ESM-only;
8
+ CJS consumers can `require()` it on Node >= 20.19.
9
+
10
+ Docs: https://telemetry.dev/docs/sdk/typescript
8
11
 
9
12
  ```sh
10
13
  npm install @telemetry-dev/sdk
@@ -12,10 +15,20 @@ npm install @telemetry-dev/sdk
12
15
 
13
16
  ## Quickstart
14
17
 
18
+ Set `TELEMETRY_DEV_API_KEY` to your project API key. `init()` reads it from the environment.
19
+
15
20
  ```ts
16
21
  import { init, observe, startSpan, propagateAttributes, log, flush } from "@telemetry-dev/sdk";
17
22
 
18
- init({ apiKey: "td_live_..." }); // or TELEMETRY_DEV_API_KEY
23
+ init();
24
+
25
+ // Stand-in for your model client, so the example runs on its own. Replace it with a real call.
26
+ async function callModel(question: string) {
27
+ return {
28
+ message: { role: "assistant", content: `You asked: ${question}` },
29
+ usage: { input: 12, output: 9 },
30
+ };
31
+ }
19
32
 
20
33
  const answer = observe(
21
34
  async function answer(question: string) {
@@ -25,13 +38,13 @@ const answer = observe(
25
38
  provider: "openai",
26
39
  input: [{ role: "user", content: question }],
27
40
  });
28
- const res = await llm.chat(question);
41
+ const res = await callModel(question);
29
42
  generation.end({
30
- output: res.message,
43
+ output: [res.message],
31
44
  usage: { inputTokens: res.usage.input, outputTokens: res.usage.output },
32
45
  });
33
46
  log("generation finished", { attributes: { cached: false } });
34
- return res.message;
47
+ return res.message.content;
35
48
  },
36
49
  { type: "agent" },
37
50
  );
@@ -70,6 +83,12 @@ still record non-content metadata such as response IDs and models, finish reason
70
83
  and errors. Capture flags do not gate stop sequences, tool descriptions and definitions (`gen_ai.tool.description`, `gen_ai.tool.definitions`), caller-supplied metadata or raw attributes,
71
84
  or exception messages and stack traces; redact those separately when needed.
72
85
 
86
+ `mask` receives input and output content, system instructions, log messages, log attributes
87
+ other than numbers and booleans, and content that integrations pass to
88
+ `handle.serializeContent`. `ctx.key` names the attribute being written. It does not receive
89
+ span or propagated `metadata`, raw `attributes`, or exception messages and stack traces; redact
90
+ those separately.
91
+
73
92
  Root spans remain independent by default. `init({ sessionMode: "process" })` groups otherwise
74
93
  uncorrelated spans and logs using a fresh opaque UUID for each enabled SDK initialization.
75
94
  Explicit and propagated session IDs take precedence, and valid parent trace context is preserved.
@@ -79,7 +98,7 @@ new ID. It is not persisted across processes.
79
98
  `sampler` accepts an OpenTelemetry `Sampler` and overrides `OTEL_TRACES_SAMPLER` / `OTEL_TRACES_SAMPLER_ARG`.
80
99
  The default is parent-based always-on. Session roots keep the configured root policy.
81
100
  Nested spans and explicitly parented spans keep the real parent's policy.
82
- The SDK installs the [session sampling wrapper](../otel/README.md#correlation-attributes) on its provider. This includes the global provider that Eve uses.
101
+ The SDK installs the [session sampling wrapper](https://github.com/telemetry-dev/telemetry/blob/main/packages/otel/README.md#correlation-attributes) on its provider. This includes the global provider that Eve uses.
83
102
 
84
103
  ## API
85
104
 
@@ -89,6 +108,13 @@ The SDK installs the [session sampling wrapper](../otel/README.md#correlation-at
89
108
  - `startSpan(name, options?)` — manual handle; does **not** activate context. `handle.update()`,
90
109
  `handle.end()`, `handle.recordOutputChunk(timestampMs?)`, `handle.traceparent`, `handle.span`
91
110
  (raw OTel span).
111
+ - `handle.serializeContent(value, key)` — prepare content that an integration writes outside
112
+ `input` and `output`, such as an error message or a raw attribute, the way `input` and `output`
113
+ are prepared: the configured `mask` with `key`, JSON stringify (strings pass through), then
114
+ truncation to `maxAttributeLength`. Returns `undefined` when the mask throws (reported through
115
+ `onError`) or drops the value, or the value cannot be serialized; write nothing then. It does not
116
+ check `captureInput` / `captureOutput`. Integrations that support older cores should treat a
117
+ missing method as a configured mask.
92
118
  - `startActiveSpan(name, options?, fn)` — callback-scoped; activates context, auto-ends.
93
119
  - `updateActiveSpan(fields)` — update the innermost active span.
94
120
  - `propagateAttributes({ userId, sessionId, metadata }, fn)` — stamps `user.id`,
@@ -102,6 +128,8 @@ The SDK installs the [session sampling wrapper](../otel/README.md#correlation-at
102
128
  or inject W3C trace context. Baggage is omitted unless `includeBaggage` is true.
103
129
  - `captureEnabled("input" | "output")` — report whether the active initialized client permits that
104
130
  capture direction. Returns `false` when no client is initialized.
131
+ - `captureMasked()` — report whether the active initialized client has a `mask` configured.
132
+ Returns `false` when no client is initialized.
105
133
  - `boundedCapture(value, options?)` — copy a JSON-compatible value within bounded work and retained
106
134
  size. Defaults to 48 KiB, depth 32, and 1,000 traversal items. Returns `{ value, truncated }`;
107
135
  `value` can be partial or `undefined` when a limit, cycle, getter, or skip rule prevents capture.
@@ -126,9 +154,9 @@ histogram state and does not retain individual intervals. Pull-based timing incl
126
154
  between reads. An explicit timestamp uses the same monotonic millisecond clock for every chunk.
127
155
 
128
156
  Provider integrations feature-detect stream APIs. With an older core SDK that lacks the stream
129
- capture policy, streams are still traced with usage, model, finish reason, and timing, but output
130
- retention assumes the default 65536 limit and treats a mask as configured, so incomplete output is
131
- withheld. Instrumentation failures are not reported to `onError` with such a core. If only
157
+ capture policy, streams are still traced with usage, model, finish reason, and timing. Output is
158
+ retained within the same fixed bounds as with a current core, but a mask is assumed to be
159
+ configured, so incomplete output is withheld. Instrumentation failures are not reported to `onError` with such a core. If only
132
160
  `recordOutputChunk` is unavailable, tracing and reconstruction continue but chunk-interval metrics
133
161
  are unavailable. Upgrade the core SDK with provider integrations to enable the full stream
134
162
  telemetry.
@@ -163,10 +191,21 @@ that mode only this SDK's spans are exported by default.
163
191
  Any vanilla OTel app can also skip the SDK entirely: point a stock OTLP/HTTP protobuf exporter at
164
192
  `https://ingest.telemetry.dev/v1/traces` with an `Authorization: Bearer td_live_...` header.
165
193
 
194
+ ### Native Claude SDK spans
195
+
196
+ `@anthropic-ai/sdk` 0.134.0 and later emits OpenTelemetry spans natively through the global tracer provider.
197
+ To send these spans to telemetry.dev, refer to [Claude SDK](https://telemetry.dev/docs/integrations/claude-sdk).
198
+ If you use `init({ registerGlobal: true })`, also set `spanFilter: () => true`. The default filter of that mode exports only the spans of this SDK.
199
+ Do not use the native spans and `@telemetry-dev/anthropic` together. If you use the two, each request has two spans.
200
+
166
201
  ## Notes
167
202
 
168
- - AsyncLocalStorage powers context propagation. On runtimes without it (Workers without
169
- `nodejs_compat`) automatic nesting is unavailable entirely unless the host app registered its
203
+ - AsyncLocalStorage powers context propagation. On runtimes without it (Cloudflare Workers without
204
+ Node.js compatibility) automatic nesting is unavailable entirely unless the host app registered its
170
205
  own global OTel context manager — pass `parent` explicitly there.
171
206
  - `@opentelemetry/api` is a peer dependency (installed automatically by npm/pnpm) so the SDK
172
207
  shares the host app's OTel API singleton.
208
+
209
+ ## License
210
+
211
+ MIT. See [LICENSE](https://github.com/telemetry-dev/telemetry/blob/main/packages/sdk/LICENSE).
package/dist/index.d.mts CHANGED
@@ -5,7 +5,11 @@ import { BatchOptions, BatchOptions as BatchOptions$1, ExportMode, ExportMode as
5
5
  import { PushMetricExporter } from "@opentelemetry/sdk-metrics";
6
6
 
7
7
  //#region src/config.d.ts
8
- /** Redaction hook applied to captured content (input/output/log message) before JSON stringification. */
8
+ /**
9
+ * Redaction hook applied to captured content (input/output, log messages and attributes, and
10
+ * content passed to `SpanHandle.serializeContent`) before JSON stringification. Not applied to
11
+ * metadata or raw attributes.
12
+ */
9
13
  type MaskFn<T = unknown> = (value: T, ctx: {
10
14
  key: string;
11
15
  }) => T;
@@ -154,6 +158,8 @@ interface TelemetryClient {
154
158
  shutdown(): Promise<void>;
155
159
  }
156
160
  declare function captureEnabled(kind: "input" | "output"): boolean;
161
+ /** Whether the active client runs captured values through a mask; false before `init`. */
162
+ declare function captureMasked(): boolean;
157
163
  declare function flush(): Promise<void>;
158
164
  declare function shutdown(): Promise<void>;
159
165
  declare function init(options?: TelemetryOptions, overrides?: ClientOverrides): TelemetryClient;
@@ -209,6 +215,15 @@ interface SpanHandle {
209
215
  /** Records an output chunk arrival. Pull-based streams include consumer delay between pulls. */
210
216
  recordOutputChunk(timestampMs?: number): void;
211
217
  reportError?(cause: unknown): void;
218
+ /**
219
+ * Prepares content that the span writes outside `input` and `output` the way those are
220
+ * prepared: the configured mask with `key` (the attribute the result is written to), JSON
221
+ * stringify (strings pass through), then truncation to `maxAttributeLength`. Returns undefined
222
+ * when the mask throws (reported to `onError`) or drops the value, or the value cannot be
223
+ * serialized. Capture flags are not checked. Absent on cores that predate it; treat a missing
224
+ * method as masked.
225
+ */
226
+ serializeContent?(value: unknown, key: string): string | undefined;
212
227
  end(fields?: SpanFields & {
213
228
  endTime?: Date | number;
214
229
  }): void;
@@ -242,4 +257,4 @@ interface ObserveOptions extends StartSpanOptions {
242
257
  */
243
258
  declare function observe<TThis, TArgs extends unknown[], TReturn>(fn: (this: TThis, ...args: TArgs) => TReturn, options?: ObserveOptions): (...args: TArgs) => TReturn;
244
259
  //#endregion
245
- export { type AgentFields, type BatchOptions, type BoundedCaptureDetails, type BoundedCaptureOptions, type BoundedCaptureResult, type ClientOverrides, type ExportMode, type GenerationFields, type LogLevel, type LogOptions, type MaskFn, type ObserveOptions, type ParentRef, type PropagatedAttributes, SCOPE_NAME, type SdkLogLevel, type SpanCapturePolicy, type SpanFields, type SpanHandle, type SpanType, type StartSpanOptions, type TelemetryClient, type TelemetryOptions, TelemetrySpanProcessor, type TelemetrySpanProcessorOptions, type TokenUsage, type ToolFields, activeContext, boundedCapture, boundedCaptureDetails, captureEnabled, extractW3cContext, flush, getTraceparent, init, injectW3cContext, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan, withContext };
260
+ export { type AgentFields, type BatchOptions, type BoundedCaptureDetails, type BoundedCaptureOptions, type BoundedCaptureResult, type ClientOverrides, type ExportMode, type GenerationFields, type LogLevel, type LogOptions, type MaskFn, type ObserveOptions, type ParentRef, type PropagatedAttributes, SCOPE_NAME, type SdkLogLevel, type SpanCapturePolicy, type SpanFields, type SpanHandle, type SpanType, type StartSpanOptions, type TelemetryClient, type TelemetryOptions, TelemetrySpanProcessor, type TelemetrySpanProcessorOptions, type TokenUsage, type ToolFields, activeContext, boundedCapture, boundedCaptureDetails, captureEnabled, captureMasked, extractW3cContext, flush, getTraceparent, init, injectW3cContext, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan, withContext };
package/dist/index.mjs CHANGED
@@ -63,6 +63,10 @@ function captureEnabled(kind) {
63
63
  const config = currentClient().core?.config;
64
64
  return kind === "input" ? config?.captureInput ?? false : config?.captureOutput ?? false;
65
65
  }
66
+ /** Whether the active client runs captured values through a mask; false before `init`. */
67
+ function captureMasked() {
68
+ return currentClient().core?.config.mask !== void 0;
69
+ }
66
70
  function flush() {
67
71
  return currentClient().flush();
68
72
  }
@@ -353,7 +357,7 @@ function prepareCaptureValue(key, value, cfg) {
353
357
  if (cfg.mask) try {
354
358
  masked = cfg.mask(value, { key });
355
359
  } catch (error) {
356
- reportError(cfg.onError, error instanceof Error ? error : new Error(String(error)));
360
+ reportError(cfg.onError, error);
357
361
  return;
358
362
  }
359
363
  const serialized = jsonAttr(masked);
@@ -536,6 +540,7 @@ const NOOP_SPAN_HANDLE = {
536
540
  update: () => NOOP_SPAN_HANDLE,
537
541
  recordOutputChunk: () => {},
538
542
  reportError: () => {},
543
+ serializeContent: () => void 0,
539
544
  end: () => {}
540
545
  };
541
546
  //#endregion
@@ -678,6 +683,9 @@ function createSpanHandle(core, name, options = {}) {
678
683
  reportError(cause) {
679
684
  reportError(meta.onError, cause instanceof Error ? cause : new Error(String(cause)));
680
685
  },
686
+ serializeContent(value, key) {
687
+ return prepareCaptureValue(key, value, meta.capture);
688
+ },
681
689
  end(fields) {
682
690
  try {
683
691
  if (fields) applyFields(span, meta, fields);
@@ -805,4 +813,4 @@ function observe(fn, options) {
805
813
  return wrapped;
806
814
  }
807
815
  //#endregion
808
- export { SCOPE_NAME, TelemetrySpanProcessor, activeContext, boundedCapture, boundedCaptureDetails, captureEnabled, extractW3cContext, flush, getTraceparent, init, injectW3cContext, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan, withContext };
816
+ export { SCOPE_NAME, TelemetrySpanProcessor, activeContext, boundedCapture, boundedCaptureDetails, captureEnabled, captureMasked, extractW3cContext, flush, getTraceparent, init, injectW3cContext, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan, withContext };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telemetry-dev/sdk",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Pure TypeScript SDK for telemetry.dev: LLM traces, logs, and GenAI metrics on OpenTelemetry, for Node and edge runtimes.",
5
5
  "keywords": [
6
6
  "genai",
@@ -42,7 +42,7 @@
42
42
  "@opentelemetry/sdk-logs": "^0.218.0",
43
43
  "@opentelemetry/sdk-metrics": "^2.7.1",
44
44
  "@opentelemetry/sdk-trace-base": "^2.7.1",
45
- "@telemetry-dev/otel": "^0.1.5"
45
+ "@telemetry-dev/otel": "^0.1.6"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@opentelemetry/api": "^1.9.1",
package/src/capture.ts CHANGED
@@ -271,7 +271,7 @@ export function prepareCaptureValue(
271
271
  try {
272
272
  masked = cfg.mask(value, { key });
273
273
  } catch (error) {
274
- reportError(cfg.onError, error instanceof Error ? error : new Error(String(error)));
274
+ reportError(cfg.onError, error);
275
275
 
276
276
  return undefined;
277
277
  }
package/src/client.ts CHANGED
@@ -94,6 +94,11 @@ export function captureEnabled(kind: "input" | "output"): boolean {
94
94
  return kind === "input" ? (config?.captureInput ?? false) : (config?.captureOutput ?? false);
95
95
  }
96
96
 
97
+ /** Whether the active client runs captured values through a mask; false before `init`. */
98
+ export function captureMasked(): boolean {
99
+ return currentClient().core?.config.mask !== undefined;
100
+ }
101
+
97
102
  export function flush(): Promise<void> {
98
103
  return currentClient().flush();
99
104
  }
package/src/config.ts CHANGED
@@ -12,7 +12,11 @@ import {
12
12
 
13
13
  export type { BatchOptions, ExportMode, LogLevel, SdkLogLevel } from "@telemetry-dev/otel";
14
14
 
15
- /** Redaction hook applied to captured content (input/output/log message) before JSON stringification. */
15
+ /**
16
+ * Redaction hook applied to captured content (input/output, log messages and attributes, and
17
+ * content passed to `SpanHandle.serializeContent`) before JSON stringification. Not applied to
18
+ * metadata or raw attributes.
19
+ */
16
20
  export type MaskFn<T = unknown> = (value: T, ctx: { key: string }) => T;
17
21
 
18
22
  export interface TelemetryOptions {
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ export type {
9
9
 
10
10
  export {
11
11
  captureEnabled,
12
+ captureMasked,
12
13
  type ClientOverrides,
13
14
  flush,
14
15
  init,
package/src/noop.ts CHANGED
@@ -16,5 +16,6 @@ export const NOOP_SPAN_HANDLE: SpanHandle = {
16
16
  update: () => NOOP_SPAN_HANDLE,
17
17
  recordOutputChunk: () => {},
18
18
  reportError: () => {},
19
+ serializeContent: () => undefined,
19
20
  end: () => {},
20
21
  };
package/src/span.ts CHANGED
@@ -38,6 +38,7 @@ import {
38
38
  type SpanFields,
39
39
  type SpanType,
40
40
  } from "./attrs.ts";
41
+ import { prepareCaptureValue } from "./capture.ts";
41
42
  import { type ClientCore, currentClient } from "./client.ts";
42
43
  import { NOOP_SPAN_HANDLE } from "./noop.ts";
43
44
 
@@ -80,6 +81,15 @@ export interface SpanHandle {
80
81
  /** Records an output chunk arrival. Pull-based streams include consumer delay between pulls. */
81
82
  recordOutputChunk(timestampMs?: number): void;
82
83
  reportError?(cause: unknown): void;
84
+ /**
85
+ * Prepares content that the span writes outside `input` and `output` the way those are
86
+ * prepared: the configured mask with `key` (the attribute the result is written to), JSON
87
+ * stringify (strings pass through), then truncation to `maxAttributeLength`. Returns undefined
88
+ * when the mask throws (reported to `onError`) or drops the value, or the value cannot be
89
+ * serialized. Capture flags are not checked. Absent on cores that predate it; treat a missing
90
+ * method as masked.
91
+ */
92
+ serializeContent?(value: unknown, key: string): string | undefined;
83
93
  end(fields?: SpanFields & { endTime?: Date | number }): void;
84
94
  }
85
95
 
@@ -303,6 +313,9 @@ export function createSpanHandle(
303
313
  reportError(cause) {
304
314
  reportError(meta.onError, cause instanceof Error ? cause : new Error(String(cause)));
305
315
  },
316
+ serializeContent(value, key) {
317
+ return prepareCaptureValue(key, value, meta.capture);
318
+ },
306
319
  end(fields) {
307
320
  try {
308
321
  if (fields) applyFields(span, meta, fields);