@telemetry-dev/sdk 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 telemetry.dev
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,123 @@
1
+ # @telemetry-dev/sdk
2
+
3
+ Pure TypeScript SDK for [telemetry.dev](https://telemetry.dev): add LLM traces, logs, and GenAI
4
+ metrics to any app. Built on OpenTelemetry — spans use the `gen_ai.*` semantic conventions and
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.
8
+
9
+ ```sh
10
+ npm install @telemetry-dev/sdk
11
+ ```
12
+
13
+ ## Quickstart
14
+
15
+ ```ts
16
+ import { init, observe, startSpan, propagateAttributes, log, flush } from "@telemetry-dev/sdk";
17
+
18
+ init({ apiKey: "td_live_..." }); // or TELEMETRY_DEV_API_KEY
19
+
20
+ const answer = observe(
21
+ async function answer(question: string) {
22
+ const generation = startSpan("chat-completion", {
23
+ type: "generation",
24
+ model: "gpt-4o",
25
+ provider: "openai",
26
+ input: [{ role: "user", content: question }],
27
+ });
28
+ const res = await llm.chat(question);
29
+ generation.end({
30
+ output: res.message,
31
+ usage: { inputTokens: res.usage.input, outputTokens: res.usage.output },
32
+ });
33
+ log("generation finished", { attributes: { cached: false } });
34
+ return res.message;
35
+ },
36
+ { type: "agent" },
37
+ );
38
+
39
+ await propagateAttributes({ userId: "user_123", sessionId: "conv_42" }, () =>
40
+ answer("What is OTLP?"),
41
+ );
42
+ await flush();
43
+ ```
44
+
45
+ Without an API key every call is a silent no-op — the SDK never throws into your code.
46
+
47
+ ## Configuration
48
+
49
+ | Option | Env var | Default |
50
+ | ------------- | --------------------------- | ------------------------------ |
51
+ | `apiKey` | `TELEMETRY_DEV_API_KEY` | — (absent ⇒ no-op) |
52
+ | `baseUrl` | `TELEMETRY_DEV_BASE_URL` | `https://ingest.telemetry.dev` |
53
+ | `environment` | `TELEMETRY_DEV_ENVIRONMENT` | `production` |
54
+ | `serviceName` | `OTEL_SERVICE_NAME` | `unknown_service` |
55
+
56
+ Other `init()` options: `enabled` (kill switch), `registerGlobal`, `exportMode: "batched" |
57
+ "immediate"`, `captureInput` / `captureOutput`, `mask(value, { key })` redaction hook,
58
+ `maxAttributeLength` (default 65536, truncated values end with `...[truncated]`), `batch`
59
+ (`maxExportBatchSize` 64, `scheduledDelayMillis` 1000, `maxQueueSize` 2048,
60
+ `exportTimeoutMillis` 30000), `spanFilter`, `resourceAttributes`, `logLevel` (SDK diagnostics,
61
+ default `"warn"`), `fetch`, `waitUntil`, `onError`.
62
+
63
+ ## API
64
+
65
+ - `init(options?)` — create the client (isolated OTel TracerProvider by default).
66
+ - `observe(fn, options?)` — wrap a sync/async function: args → input, return → output, errors
67
+ captured and rethrown; activates context for nesting.
68
+ - `startSpan(name, options?)` — manual handle; does **not** activate context. `handle.update()`,
69
+ `handle.end()`, `handle.traceparent`, `handle.span` (raw OTel span).
70
+ - `startActiveSpan(name, options?, fn)` — callback-scoped; activates context, auto-ends.
71
+ - `updateActiveSpan(fields)` — update the innermost active span.
72
+ - `propagateAttributes({ userId, sessionId, metadata }, fn)` — stamps `user.id`,
73
+ `gen_ai.conversation.id`, and `td.metadata.*` on every span and log record in scope.
74
+ - `log(message, { level, eventName, attributes }?)` — log record correlated to the active trace.
75
+ - `getTraceparent()` — W3C traceparent of the active span (pass to other services; accept it via
76
+ `startSpan(name, { parent })`).
77
+ - `flush()` / `shutdown()` — export pending data; required before serverless freeze/exit.
78
+
79
+ Span types: `span` (default) | `generation` | `tool` | `agent` | `embedding` — mapped to
80
+ `gen_ai.operation.name` `function` / `chat` / `execute_tool` / `invoke_agent` / `embeddings`.
81
+ Generation fields (`model`, `provider`, `usage`, `costUsd`, sampling params, …) map to the OTel
82
+ GenAI semantic conventions; cost is computed server-side from usage unless `costUsd` is set.
83
+
84
+ Duration and token-usage histograms (`gen_ai.client.operation.duration`,
85
+ `gen_ai.client.token.usage`) are recorded automatically for generation/agent/embedding/tool spans.
86
+
87
+ ## Serverless
88
+
89
+ Use `exportMode: "immediate"` and either `await flush()` before returning or pass the platform
90
+ extender: `init({ waitUntil: (p) => ctx.waitUntil(p) })`. In batched mode spans wait up to
91
+ `scheduledDelayMillis` for a timer that frozen isolates may never fire.
92
+
93
+ ## Bring your own OpenTelemetry
94
+
95
+ If your app already runs an OTel setup (NodeSDK, `@vercel/otel`), don't use `init()` — attach the
96
+ span processor to your provider; it exports every span it sees (narrow with `spanFilter`). The BYO
97
+ surface ships as its own package and the SDK is not required for it:
98
+
99
+ ```sh
100
+ npm i @telemetry-dev/otel
101
+ ```
102
+
103
+ ```ts
104
+ import { NodeSDK } from "@opentelemetry/sdk-node";
105
+ import { TelemetrySpanProcessor } from "@telemetry-dev/otel";
106
+
107
+ const sdk = new NodeSDK({ spanProcessors: [new TelemetrySpanProcessor()] });
108
+ sdk.start();
109
+ ```
110
+
111
+ Alternatively `init({ registerGlobal: true })` makes the SDK's provider the app's global one; in
112
+ that mode only this SDK's spans are exported by default.
113
+
114
+ Any vanilla OTel app can also skip the SDK entirely: point a stock OTLP/HTTP protobuf exporter at
115
+ `https://ingest.telemetry.dev/v1/traces` with an `Authorization: Bearer td_live_...` header.
116
+
117
+ ## Notes
118
+
119
+ - AsyncLocalStorage powers context propagation. On runtimes without it (Workers without
120
+ `nodejs_compat`) automatic nesting is unavailable entirely unless the host app registered its
121
+ own global OTel context manager — pass `parent` explicitly there.
122
+ - `@opentelemetry/api` is a peer dependency (installed automatically by npm/pnpm) so the SDK
123
+ shares the host app's OTel API singleton.
@@ -0,0 +1,184 @@
1
+ import { AttributeValue, Context, Span, SpanContext } from "@opentelemetry/api";
2
+ import { LogRecordExporter } from "@opentelemetry/sdk-logs";
3
+ import { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base";
4
+ import { BatchOptions, BatchOptions as BatchOptions$1, ExportMode, ExportMode as ExportMode$1, LogLevel, PropagatedAttributes, SdkLogLevel, SdkLogLevel as SdkLogLevel$1, propagateAttributes } from "@telemetry-dev/otel";
5
+ import { PushMetricExporter } from "@opentelemetry/sdk-metrics";
6
+
7
+ //#region src/config.d.ts
8
+ /** Redaction hook applied to captured content (input/output/log message) before JSON stringification. */
9
+ type MaskFn = (value: unknown, ctx: {
10
+ key: string;
11
+ }) => unknown;
12
+ interface TelemetryOptions {
13
+ /** telemetry.dev ingest key (`td_live_…`). Falls back to `TELEMETRY_DEV_API_KEY`. When absent the SDK is a no-op. */
14
+ apiKey?: string;
15
+ /** Ingest base URL. Falls back to `TELEMETRY_DEV_BASE_URL`, then `https://ingest.telemetry.dev`. */
16
+ baseUrl?: string;
17
+ /** Deployment environment label. Falls back to `TELEMETRY_DEV_ENVIRONMENT`, then `production`. */
18
+ environment?: string;
19
+ /** Service name attached to every trace. Falls back to `OTEL_SERVICE_NAME`, then `unknown_service`. */
20
+ serviceName?: string;
21
+ /** Kill switch: `false` makes the SDK a complete no-op (tests). Default true. */
22
+ enabled?: boolean;
23
+ /** Register this SDK's TracerProvider/context manager/propagator globally. Default false (isolated). */
24
+ registerGlobal?: boolean;
25
+ /** "batched" (default) buffers spans; "immediate" exports per span (serverless). */
26
+ exportMode?: ExportMode$1;
27
+ /** Capture span inputs (args, prompts) by default. Default true. */
28
+ captureInput?: boolean;
29
+ /** Capture span outputs (results, completions) by default. Default true. */
30
+ captureOutput?: boolean;
31
+ /** Redaction hook run on captured content before stringify/truncate. */
32
+ mask?: MaskFn;
33
+ /** Truncation cap (chars) for content attributes; also set as the provider's attributeValueLengthLimit. Default 65536. */
34
+ maxAttributeLength?: number;
35
+ batch?: BatchOptions$1;
36
+ /** Export filter. Default when registerGlobal: only spans from this SDK's instrumentation scope. */
37
+ spanFilter?: (span: ReadableSpan) => boolean;
38
+ /** Extra resource attributes merged onto service.name / deployment.environment.name. */
39
+ resourceAttributes?: Record<string, AttributeValue>;
40
+ /** SDK self-diagnostics console level. Default "warn"; "silent" disables. */
41
+ logLevel?: SdkLogLevel$1;
42
+ /** Injected fetch implementation. Defaults to `globalThis.fetch`. */
43
+ fetch?: typeof fetch;
44
+ /** Serverless extender (e.g. Cloudflare `ctx.waitUntil`). When provided, `flush()` does not await exports. */
45
+ waitUntil?: (p: Promise<unknown>) => void;
46
+ /** Receives any error raised while emitting telemetry; the SDK never throws into user code. */
47
+ onError?: (error: unknown) => void;
48
+ }
49
+ //#endregion
50
+ //#region src/attrs.d.ts
51
+ type SpanType = "span" | "generation" | "tool" | "agent" | "embedding";
52
+ interface TokenUsage {
53
+ inputTokens?: number;
54
+ outputTokens?: number;
55
+ totalTokens?: number;
56
+ cacheReadInputTokens?: number;
57
+ cacheCreationInputTokens?: number;
58
+ reasoningOutputTokens?: number;
59
+ }
60
+ interface GenerationFields {
61
+ model?: string;
62
+ provider?: string;
63
+ systemInstructions?: unknown;
64
+ responseModel?: string;
65
+ responseId?: string;
66
+ usage?: TokenUsage;
67
+ /** Optional client-side cost override; the server computes cost from usage by default. */
68
+ costUsd?: number;
69
+ finishReason?: string;
70
+ outputType?: string;
71
+ temperature?: number;
72
+ topP?: number;
73
+ topK?: number;
74
+ maxTokens?: number;
75
+ stopSequences?: string[];
76
+ seed?: number;
77
+ frequencyPenalty?: number;
78
+ presencePenalty?: number;
79
+ timeToFirstChunkMs?: number;
80
+ }
81
+ interface ToolFields {
82
+ toolName?: string;
83
+ toolCallId?: string;
84
+ toolDescription?: string;
85
+ }
86
+ interface AgentFields {
87
+ agentName?: string;
88
+ agentId?: string;
89
+ }
90
+ interface SpanFields extends GenerationFields, ToolFields, AgentFields {
91
+ /** Rename the span (update only). */
92
+ name?: string;
93
+ input?: unknown;
94
+ output?: unknown;
95
+ /** Marks the span failed: status ERROR + error.type + an "exception" event. */
96
+ error?: unknown;
97
+ /** td.metadata.<key> on this span only; use propagateAttributes for scope-wide metadata. */
98
+ metadata?: Record<string, unknown>;
99
+ /** Raw attribute escape hatch, merged last. */
100
+ attributes?: Record<string, AttributeValue>;
101
+ }
102
+ //#endregion
103
+ //#region src/client.d.ts
104
+ /** Test seam: inject in-memory exporters instead of the OTLP fetch transport. */
105
+ interface ClientOverrides {
106
+ spanExporter?: SpanExporter;
107
+ logRecordExporter?: LogRecordExporter;
108
+ metricExporter?: PushMetricExporter;
109
+ }
110
+ interface TelemetryClient {
111
+ readonly enabled: boolean;
112
+ /** Never rejects; hands the export off to waitUntil when configured. */
113
+ flush(): Promise<void>;
114
+ /** Flush + teardown + unregister globals; the client becomes a no-op afterwards. */
115
+ shutdown(): Promise<void>;
116
+ }
117
+ declare function flush(): Promise<void>;
118
+ declare function shutdown(): Promise<void>;
119
+ declare function init(options?: TelemetryOptions, overrides?: ClientOverrides): TelemetryClient;
120
+ //#endregion
121
+ //#region src/logs.d.ts
122
+ interface LogOptions {
123
+ /** Default "info". */
124
+ level?: LogLevel;
125
+ attributes?: Record<string, unknown>;
126
+ eventName?: string;
127
+ timestamp?: Date | number;
128
+ }
129
+ /**
130
+ * Emit a log record to /v1/logs, correlated to the active span (when any) and stamped with the
131
+ * propagated correlation attributes. Works inside and outside spans.
132
+ */
133
+ declare function log(message: string, options?: LogOptions): void;
134
+ //#endregion
135
+ //#region src/span.d.ts
136
+ type ParentRef = string | SpanHandle | SpanContext | Context;
137
+ interface StartSpanOptions extends SpanFields {
138
+ /** Default "span" → operation "function". */
139
+ type?: SpanType;
140
+ /** Parent override; defaults to the active context. */
141
+ parent?: ParentRef;
142
+ startTime?: Date | number;
143
+ /** Per-call override of the global captureInput. */
144
+ captureInput?: boolean;
145
+ /** Per-call override of the global captureOutput. */
146
+ captureOutput?: boolean;
147
+ }
148
+ interface SpanHandle {
149
+ /** Raw OTel span (interop escape hatch). */
150
+ readonly span: Span;
151
+ /** Context with this span active, for manual parenting. */
152
+ readonly context: Context;
153
+ readonly traceId: string;
154
+ readonly spanId: string;
155
+ /** W3C traceparent for this span; null for the no-op handle. */
156
+ readonly traceparent: string | null;
157
+ readonly isRecording: boolean;
158
+ update(fields: SpanFields): SpanHandle;
159
+ end(fields?: SpanFields & {
160
+ endTime?: Date | number;
161
+ }): void;
162
+ }
163
+ /** Start a span WITHOUT activating context; the caller must call .end(). */
164
+ declare function startSpan(name: string, options?: StartSpanOptions): SpanHandle;
165
+ declare function startActiveSpan<T>(name: string, fn: (span: SpanHandle) => T): T;
166
+ declare function startActiveSpan<T>(name: string, options: StartSpanOptions, fn: (span: SpanHandle) => T): T;
167
+ /** Update the innermost active span; no-op when none is recording. */
168
+ declare function updateActiveSpan(fields: SpanFields): void;
169
+ /** Serialize the active span context as a W3C traceparent; null when there is none. */
170
+ declare function getTraceparent(): string | null;
171
+ //#endregion
172
+ //#region src/observe.d.ts
173
+ interface ObserveOptions extends StartSpanOptions {
174
+ /** Span name; defaults to fn.name or "anonymous". */
175
+ name?: string;
176
+ }
177
+ /**
178
+ * Wrap a function (sync or async) so every call becomes a span: args → input, return value →
179
+ * output, thrown/rejected errors captured and rethrown. Activates context so nested SDK calls
180
+ * parent to it. Resolves the client at call time, so wrapping at module load before init() works.
181
+ */
182
+ declare function observe<TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => TReturn, options?: ObserveOptions): (...args: TArgs) => TReturn;
183
+ //#endregion
184
+ export { type AgentFields, type BatchOptions, type ClientOverrides, type ExportMode, type GenerationFields, type LogLevel, type LogOptions, type MaskFn, type ObserveOptions, type ParentRef, type PropagatedAttributes, type SdkLogLevel, type SpanFields, type SpanHandle, type SpanType, type StartSpanOptions, type TelemetryClient, type TelemetryOptions, type TokenUsage, type ToolFields, flush, getTraceparent, init, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan };