@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/dist/index.mjs ADDED
@@ -0,0 +1,558 @@
1
+ import { INVALID_SPAN_CONTEXT, ROOT_CONTEXT, SpanKind, SpanStatusCode, context, defaultTextMapSetter, propagation, trace } from "@opentelemetry/api";
2
+ import { W3CTraceContextPropagator, parseTraceParent } from "@opentelemetry/core";
3
+ import { resourceFromAttributes } from "@opentelemetry/resources";
4
+ import { BatchLogRecordProcessor, LoggerProvider, SimpleLogRecordProcessor } from "@opentelemetry/sdk-logs";
5
+ import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
6
+ import { AlsContextManager, BATCHED_METRIC_INTERVAL_MS, DEFAULT_BASE_URL, DEFAULT_BATCH, DORMANT_INTERVAL_MS, PROPAGATED_KEY, SCOPE_NAME, SCOPE_VERSION, StampingSpanProcessor, activeContext, als, createLogExporter, createMetricExporter, createMetricsPipeline, createTraceExporter, diag, jsonAttr, omitUndefined, otlpHeaders, propagateAttributes, propagatedFromContext, reportError, resolveEnv, setLogLevel, withContext } from "@telemetry-dev/otel";
7
+ import { SeverityNumber } from "@opentelemetry/api-logs";
8
+ function resolveConfig(options = {}) {
9
+ const env = resolveEnv();
10
+ const baseUrl = (options.baseUrl ?? env.TELEMETRY_DEV_BASE_URL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
11
+ return {
12
+ apiKey: options.apiKey ?? env.TELEMETRY_DEV_API_KEY,
13
+ baseUrl,
14
+ environment: options.environment ?? env.TELEMETRY_DEV_ENVIRONMENT ?? "production",
15
+ serviceName: options.serviceName ?? env.OTEL_SERVICE_NAME ?? "unknown_service",
16
+ enabled: options.enabled ?? true,
17
+ registerGlobal: options.registerGlobal ?? false,
18
+ exportMode: options.exportMode ?? "batched",
19
+ captureInput: options.captureInput ?? true,
20
+ captureOutput: options.captureOutput ?? true,
21
+ mask: options.mask,
22
+ maxAttributeLength: options.maxAttributeLength ?? 65536,
23
+ batch: {
24
+ ...DEFAULT_BATCH,
25
+ ...options.batch
26
+ },
27
+ spanFilter: options.spanFilter,
28
+ resourceAttributes: options.resourceAttributes,
29
+ logLevel: options.logLevel ?? "warn",
30
+ fetchImpl: options.fetch ?? globalThis.fetch,
31
+ waitUntil: options.waitUntil,
32
+ onError: options.onError
33
+ };
34
+ }
35
+ //#endregion
36
+ //#region src/client.ts
37
+ const NOOP_CLIENT = {
38
+ enabled: false,
39
+ flush: () => Promise.resolve(),
40
+ shutdown: () => Promise.resolve()
41
+ };
42
+ let activeClient;
43
+ function currentClient() {
44
+ return activeClient ?? NOOP_CLIENT;
45
+ }
46
+ function flush() {
47
+ return currentClient().flush();
48
+ }
49
+ function shutdown() {
50
+ return currentClient().shutdown();
51
+ }
52
+ function init(options, overrides) {
53
+ try {
54
+ return initInner(options, overrides);
55
+ } catch (error) {
56
+ reportError(options?.onError, error);
57
+ activeClient = NOOP_CLIENT;
58
+ return NOOP_CLIENT;
59
+ }
60
+ }
61
+ function initInner(options, overrides) {
62
+ const config = resolveConfig(options);
63
+ setLogLevel(config.logLevel);
64
+ if (activeClient && activeClient !== NOOP_CLIENT) {
65
+ diag.warn("init() called again; replacing the previous client");
66
+ activeClient.shutdown().catch((error) => reportError(config.onError, error));
67
+ }
68
+ if (!(config.enabled && Boolean(config.apiKey ?? overrides?.spanExporter))) {
69
+ if (config.enabled && !config.apiKey) diag.debug("no api key (apiKey option or TELEMETRY_DEV_API_KEY); telemetry disabled");
70
+ activeClient = NOOP_CLIENT;
71
+ return NOOP_CLIENT;
72
+ }
73
+ const resource = resourceFromAttributes({
74
+ "service.name": config.serviceName,
75
+ "deployment.environment.name": config.environment,
76
+ ...config.resourceAttributes
77
+ });
78
+ const transport = {
79
+ fetchImpl: config.fetchImpl,
80
+ onError: config.onError
81
+ };
82
+ const headers = config.apiKey ? otlpHeaders(config.apiKey) : void 0;
83
+ const spanExporter = overrides?.spanExporter ?? createTraceExporter({
84
+ url: `${config.baseUrl}/v1/traces`,
85
+ headers
86
+ }, transport);
87
+ const metricExporter = overrides?.metricExporter ?? (headers ? createMetricExporter({
88
+ url: `${config.baseUrl}/v1/metrics`,
89
+ headers
90
+ }, transport) : void 0);
91
+ const metrics = metricExporter ? createMetricsPipeline({
92
+ resource,
93
+ exporter: metricExporter,
94
+ exportIntervalMillis: config.exportMode === "batched" ? BATCHED_METRIC_INTERVAL_MS : DORMANT_INTERVAL_MS
95
+ }) : void 0;
96
+ const spanFilter = config.spanFilter ?? (config.registerGlobal ? (span) => span.instrumentationScope.name === SCOPE_NAME : void 0);
97
+ const provider = new BasicTracerProvider({
98
+ resource,
99
+ spanProcessors: [new StampingSpanProcessor({
100
+ exporter: spanExporter,
101
+ exportMode: config.exportMode,
102
+ batch: config.batch,
103
+ spanFilter,
104
+ recordMetrics: metrics ? (span) => metrics.record(span) : void 0,
105
+ onError: config.onError
106
+ })],
107
+ spanLimits: { attributeValueLengthLimit: config.maxAttributeLength }
108
+ });
109
+ const tracer = provider.getTracer(SCOPE_NAME, SCOPE_VERSION);
110
+ let registeredTrace = false;
111
+ let registeredContext = false;
112
+ let registeredPropagation = false;
113
+ if (config.registerGlobal) {
114
+ registeredTrace = trace.setGlobalTracerProvider(provider);
115
+ if (als) registeredContext = context.setGlobalContextManager(new AlsContextManager(als));
116
+ registeredPropagation = propagation.setGlobalPropagator(new W3CTraceContextPropagator());
117
+ if (!registeredTrace) diag.warn("a global TracerProvider is already registered; attach the TelemetrySpanProcessor from '@telemetry-dev/otel' to your own provider instead");
118
+ }
119
+ const logExporter = overrides?.logRecordExporter ?? (headers ? createLogExporter({
120
+ url: `${config.baseUrl}/v1/logs`,
121
+ headers
122
+ }, transport) : void 0);
123
+ let logs;
124
+ const ensureLogger = () => {
125
+ if (!logExporter) return void 0;
126
+ if (!logs) {
127
+ const logProcessor = config.exportMode === "immediate" ? new SimpleLogRecordProcessor(logExporter) : new BatchLogRecordProcessor(logExporter);
128
+ const loggerProvider = new LoggerProvider({
129
+ resource,
130
+ logRecordLimits: { attributeValueLengthLimit: config.maxAttributeLength },
131
+ processors: [logProcessor]
132
+ });
133
+ logs = {
134
+ logger: loggerProvider.getLogger(SCOPE_NAME, SCOPE_VERSION),
135
+ forceFlush: () => loggerProvider.forceFlush(),
136
+ shutdown: () => loggerProvider.shutdown()
137
+ };
138
+ }
139
+ return logs.logger;
140
+ };
141
+ let torn = false;
142
+ const collect = (op) => {
143
+ const parts = [provider[op]()];
144
+ if (logs) parts.push(logs[op]());
145
+ if (metrics) parts.push(metrics[op]());
146
+ return Promise.all(parts).then(() => void 0).catch((error) => reportError(config.onError, error));
147
+ };
148
+ const handoff = (p) => {
149
+ if (config.waitUntil) {
150
+ config.waitUntil(p);
151
+ return Promise.resolve();
152
+ }
153
+ return p;
154
+ };
155
+ const handle = {
156
+ get enabled() {
157
+ return !torn;
158
+ },
159
+ flush: () => torn ? Promise.resolve() : handoff(collect("forceFlush")),
160
+ shutdown: () => {
161
+ if (torn) return Promise.resolve();
162
+ torn = true;
163
+ if (registeredTrace) trace.disable();
164
+ if (registeredContext) context.disable();
165
+ if (registeredPropagation) propagation.disable();
166
+ handle.core = void 0;
167
+ return handoff(collect("shutdown"));
168
+ }
169
+ };
170
+ handle.core = {
171
+ config,
172
+ tracer,
173
+ ensureLogger
174
+ };
175
+ activeClient = handle;
176
+ return handle;
177
+ }
178
+ //#endregion
179
+ //#region src/capture.ts
180
+ const TRUNCATION_MARKER = "...[truncated]";
181
+ function truncate(value, maxLength) {
182
+ if (value.length <= maxLength) return value;
183
+ return value.slice(0, Math.max(maxLength - 14, 0)) + TRUNCATION_MARKER;
184
+ }
185
+ /** The single content funnel: mask → JSON stringify → truncate. */
186
+ function prepareCaptureValue(key, value, cfg) {
187
+ let masked = value;
188
+ if (cfg.mask) try {
189
+ masked = cfg.mask(value, { key });
190
+ } catch (error) {
191
+ reportError(cfg.onError, error);
192
+ return;
193
+ }
194
+ const serialized = jsonAttr(masked);
195
+ if (serialized === void 0) return void 0;
196
+ return truncate(serialized, cfg.maxAttributeLength);
197
+ }
198
+ //#endregion
199
+ //#region src/logs.ts
200
+ const SEVERITIES = {
201
+ debug: {
202
+ number: SeverityNumber.DEBUG,
203
+ text: "DEBUG"
204
+ },
205
+ info: {
206
+ number: SeverityNumber.INFO,
207
+ text: "INFO"
208
+ },
209
+ warn: {
210
+ number: SeverityNumber.WARN,
211
+ text: "WARN"
212
+ },
213
+ error: {
214
+ number: SeverityNumber.ERROR,
215
+ text: "ERROR"
216
+ }
217
+ };
218
+ /**
219
+ * Emit a log record to /v1/logs, correlated to the active span (when any) and stamped with the
220
+ * propagated correlation attributes. Works inside and outside spans.
221
+ */
222
+ function log(message, options) {
223
+ const core = currentClient().core;
224
+ if (!core) return;
225
+ try {
226
+ const logger = core.ensureLogger();
227
+ if (!logger) return;
228
+ const cfg = core.config;
229
+ const captureCfg = {
230
+ mask: cfg.mask,
231
+ maxAttributeLength: cfg.maxAttributeLength,
232
+ onError: cfg.onError
233
+ };
234
+ const severity = SEVERITIES[options?.level ?? "info"];
235
+ const ctx = activeContext();
236
+ const attributes = {};
237
+ for (const [key, value] of Object.entries(options?.attributes ?? {})) {
238
+ if (value === void 0) continue;
239
+ if (typeof value === "number" || typeof value === "boolean") {
240
+ attributes[key] = value;
241
+ continue;
242
+ }
243
+ const prepared = prepareCaptureValue(key, value, captureCfg);
244
+ if (prepared !== void 0) attributes[key] = prepared;
245
+ }
246
+ const propagated = propagatedFromContext(ctx);
247
+ if (propagated) {
248
+ for (const [key, value] of Object.entries(propagated)) if (value !== void 0) attributes[key] = typeof value === "string" ? truncate(value, cfg.maxAttributeLength) : value;
249
+ }
250
+ logger.emit({
251
+ body: prepareCaptureValue("log.message", message, captureCfg) ?? "",
252
+ severityNumber: severity.number,
253
+ severityText: severity.text,
254
+ eventName: options?.eventName,
255
+ timestamp: options?.timestamp,
256
+ context: ctx,
257
+ attributes
258
+ });
259
+ } catch (error) {
260
+ reportError(core.config.onError, error);
261
+ }
262
+ }
263
+ //#endregion
264
+ //#region src/attrs.ts
265
+ const SPAN_TYPE_OPERATIONS = {
266
+ span: "function",
267
+ generation: "chat",
268
+ tool: "execute_tool",
269
+ agent: "invoke_agent",
270
+ embedding: "embeddings"
271
+ };
272
+ function inputKeyFor(type) {
273
+ return type === "tool" ? "gen_ai.tool.call.arguments" : "gen_ai.input.messages";
274
+ }
275
+ function outputKeyFor(type) {
276
+ return type === "tool" ? "gen_ai.tool.call.result" : "gen_ai.output.messages";
277
+ }
278
+ const RESERVED_METADATA_KEYS = new Set([
279
+ "userId",
280
+ "sessionId",
281
+ "user_id",
282
+ "session_id"
283
+ ]);
284
+ function fieldsToAttributes(fields, type, cfg) {
285
+ const attrs = omitUndefined({
286
+ "gen_ai.request.model": fields.model,
287
+ "gen_ai.provider.name": fields.provider,
288
+ "gen_ai.response.model": fields.responseModel,
289
+ "gen_ai.response.id": fields.responseId,
290
+ "gen_ai.usage.input_tokens": fields.usage?.inputTokens,
291
+ "gen_ai.usage.output_tokens": fields.usage?.outputTokens,
292
+ "gen_ai.usage.total_tokens": fields.usage?.totalTokens,
293
+ "gen_ai.usage.cache_read.input_tokens": fields.usage?.cacheReadInputTokens,
294
+ "gen_ai.usage.cache_creation.input_tokens": fields.usage?.cacheCreationInputTokens,
295
+ "gen_ai.usage.reasoning.output_tokens": fields.usage?.reasoningOutputTokens,
296
+ "gen_ai.usage.cost": fields.costUsd,
297
+ "gen_ai.response.finish_reasons": fields.finishReason ? [fields.finishReason] : void 0,
298
+ "gen_ai.output.type": fields.outputType,
299
+ "gen_ai.request.temperature": fields.temperature,
300
+ "gen_ai.request.top_p": fields.topP,
301
+ "gen_ai.request.top_k": fields.topK,
302
+ "gen_ai.request.max_tokens": fields.maxTokens,
303
+ "gen_ai.request.stop_sequences": fields.stopSequences,
304
+ "gen_ai.request.seed": fields.seed,
305
+ "gen_ai.request.frequency_penalty": fields.frequencyPenalty,
306
+ "gen_ai.request.presence_penalty": fields.presencePenalty,
307
+ "gen_ai.response.time_to_first_chunk": fields.timeToFirstChunkMs !== void 0 ? fields.timeToFirstChunkMs / 1e3 : void 0,
308
+ "gen_ai.tool.name": fields.toolName,
309
+ "gen_ai.tool.call.id": fields.toolCallId,
310
+ "gen_ai.tool.description": fields.toolDescription,
311
+ "gen_ai.agent.name": fields.agentName,
312
+ "gen_ai.agent.id": fields.agentId
313
+ });
314
+ if (fields.systemInstructions !== void 0 && cfg.captureInput) {
315
+ const value = prepareCaptureValue("gen_ai.system_instructions", fields.systemInstructions, cfg);
316
+ if (value !== void 0) attrs["gen_ai.system_instructions"] = value;
317
+ }
318
+ if (fields.input !== void 0 && cfg.captureInput) {
319
+ const key = inputKeyFor(type);
320
+ const value = prepareCaptureValue(key, fields.input, cfg);
321
+ if (value !== void 0) attrs[key] = value;
322
+ }
323
+ if (fields.output !== void 0 && cfg.captureOutput) {
324
+ const key = outputKeyFor(type);
325
+ const value = prepareCaptureValue(key, fields.output, cfg);
326
+ if (value !== void 0) attrs[key] = value;
327
+ }
328
+ if (fields.metadata) for (const [key, value] of Object.entries(fields.metadata)) {
329
+ if (RESERVED_METADATA_KEYS.has(key)) {
330
+ diag.debug(`metadata key "${key}" is reserved; pass it via propagateAttributes instead`);
331
+ continue;
332
+ }
333
+ const attr = typeof value === "string" ? value : jsonAttr(value);
334
+ if (attr !== void 0) attrs[`td.metadata.${key}`] = truncate(attr, cfg.maxAttributeLength);
335
+ }
336
+ if (fields.attributes) Object.assign(attrs, omitUndefined(fields.attributes));
337
+ return attrs;
338
+ }
339
+ //#endregion
340
+ //#region src/noop.ts
341
+ const noopSpan = trace.wrapSpanContext(INVALID_SPAN_CONTEXT);
342
+ /** Shared inert handle returned by span primitives before init() or when disabled. */
343
+ const NOOP_SPAN_HANDLE = {
344
+ span: noopSpan,
345
+ context: trace.setSpan(ROOT_CONTEXT, noopSpan),
346
+ traceId: INVALID_SPAN_CONTEXT.traceId,
347
+ spanId: INVALID_SPAN_CONTEXT.spanId,
348
+ traceparent: null,
349
+ isRecording: false,
350
+ update: () => NOOP_SPAN_HANDLE,
351
+ end: () => {}
352
+ };
353
+ //#endregion
354
+ //#region src/span.ts
355
+ const SPAN_META = /* @__PURE__ */ new WeakMap();
356
+ const SEVERITY_ERROR = 17;
357
+ function isThenable(value) {
358
+ return value != null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function";
359
+ }
360
+ function isContext(value) {
361
+ return "getValue" in value && typeof value.getValue === "function";
362
+ }
363
+ function isSpanHandle(value) {
364
+ return "span" in value && "context" in value && "update" in value;
365
+ }
366
+ function withAmbientPropagated(ctx) {
367
+ if (propagatedFromContext(ctx)) return ctx;
368
+ const ambient = propagatedFromContext(activeContext());
369
+ return ambient ? ctx.setValue(PROPAGATED_KEY, ambient) : ctx;
370
+ }
371
+ function resolveParent(parent) {
372
+ const base = activeContext();
373
+ if (parent === void 0) return base;
374
+ if (typeof parent === "string") {
375
+ const parsed = parseTraceParent(parent);
376
+ if (!parsed) return base;
377
+ return trace.setSpanContext(base, {
378
+ ...parsed,
379
+ isRemote: true
380
+ });
381
+ }
382
+ if (isSpanHandle(parent)) return withAmbientPropagated(parent.context);
383
+ if (isContext(parent)) return withAmbientPropagated(parent);
384
+ return trace.setSpanContext(base, parent);
385
+ }
386
+ function applyError(span, error) {
387
+ const err = error instanceof Error ? error : void 0;
388
+ const errorType = err?.name ?? "Error";
389
+ const message = err?.message ?? String(error);
390
+ span.setStatus({ code: SpanStatusCode.ERROR });
391
+ span.setAttribute("error.type", errorType);
392
+ span.addEvent("exception", omitUndefined({
393
+ "exception.type": errorType,
394
+ "exception.message": message,
395
+ "exception.stacktrace": err?.stack,
396
+ "log.severity_number": SEVERITY_ERROR
397
+ }));
398
+ }
399
+ function applyFields(span, meta, fields) {
400
+ if (fields.name) span.updateName(fields.name);
401
+ const attrs = fieldsToAttributes(fields, meta.type, meta.capture);
402
+ if (Object.keys(attrs).length > 0) span.setAttributes(attrs);
403
+ if (fields.error !== void 0) applyError(span, fields.error);
404
+ }
405
+ function metaFor(core, options) {
406
+ const cfg = core.config;
407
+ return {
408
+ type: options?.type ?? "span",
409
+ capture: {
410
+ mask: cfg.mask,
411
+ maxAttributeLength: cfg.maxAttributeLength,
412
+ onError: cfg.onError,
413
+ captureInput: options?.captureInput ?? cfg.captureInput,
414
+ captureOutput: options?.captureOutput ?? cfg.captureOutput
415
+ },
416
+ onError: cfg.onError
417
+ };
418
+ }
419
+ function createSpanHandle(core, name, options = {}) {
420
+ const meta = metaFor(core, options);
421
+ const parentCtx = resolveParent(options.parent);
422
+ const span = core.tracer.startSpan(name, {
423
+ kind: SpanKind.INTERNAL,
424
+ startTime: options.startTime,
425
+ attributes: { "gen_ai.operation.name": SPAN_TYPE_OPERATIONS[meta.type] }
426
+ }, parentCtx);
427
+ SPAN_META.set(span, meta);
428
+ applyFields(span, meta, options);
429
+ const spanContext = span.spanContext();
430
+ const flags = (spanContext.traceFlags & 255).toString(16).padStart(2, "0");
431
+ const handle = {
432
+ span,
433
+ context: trace.setSpan(parentCtx, span),
434
+ traceId: spanContext.traceId,
435
+ spanId: spanContext.spanId,
436
+ traceparent: `00-${spanContext.traceId}-${spanContext.spanId}-${flags}`,
437
+ get isRecording() {
438
+ return span.isRecording();
439
+ },
440
+ update(fields) {
441
+ try {
442
+ applyFields(span, meta, fields);
443
+ } catch (error) {
444
+ reportError(meta.onError, error);
445
+ }
446
+ return handle;
447
+ },
448
+ end(fields) {
449
+ try {
450
+ if (fields) applyFields(span, meta, fields);
451
+ } catch (error) {
452
+ reportError(meta.onError, error);
453
+ }
454
+ span.end(fields?.endTime);
455
+ }
456
+ };
457
+ return handle;
458
+ }
459
+ /** Start a span WITHOUT activating context; the caller must call .end(). */
460
+ function startSpan(name, options) {
461
+ const core = currentClient().core;
462
+ if (!core) return NOOP_SPAN_HANDLE;
463
+ try {
464
+ return createSpanHandle(core, name, options);
465
+ } catch (error) {
466
+ reportError(core.config.onError, error);
467
+ return NOOP_SPAN_HANDLE;
468
+ }
469
+ }
470
+ function runWithHandle(handle, fn) {
471
+ try {
472
+ const result = withContext(handle.context, () => fn(handle));
473
+ if (isThenable(result)) return result.then((value) => {
474
+ handle.end();
475
+ return value;
476
+ }, (error) => {
477
+ handle.end({ error });
478
+ throw error;
479
+ });
480
+ handle.end();
481
+ return result;
482
+ } catch (error) {
483
+ handle.end({ error });
484
+ throw error;
485
+ }
486
+ }
487
+ function startActiveSpan(name, optionsOrFn, maybeFn) {
488
+ const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
489
+ const handle = startSpan(name, typeof optionsOrFn === "function" ? void 0 : optionsOrFn);
490
+ if (!handle.isRecording) return fn(handle);
491
+ return runWithHandle(handle, fn);
492
+ }
493
+ /** Update the innermost active span; no-op when none is recording. */
494
+ function updateActiveSpan(fields) {
495
+ const core = currentClient().core;
496
+ if (!core) return;
497
+ try {
498
+ const span = trace.getSpan(activeContext());
499
+ if (!span?.isRecording()) return;
500
+ applyFields(span, SPAN_META.get(span) ?? metaFor(core), fields);
501
+ } catch (error) {
502
+ reportError(core.config.onError, error);
503
+ }
504
+ }
505
+ const PROPAGATOR = new W3CTraceContextPropagator();
506
+ /** Serialize the active span context as a W3C traceparent; null when there is none. */
507
+ function getTraceparent() {
508
+ const carrier = {};
509
+ PROPAGATOR.inject(activeContext(), carrier, defaultTextMapSetter);
510
+ return carrier.traceparent ?? null;
511
+ }
512
+ //#endregion
513
+ //#region src/observe.ts
514
+ /**
515
+ * Wrap a function (sync or async) so every call becomes a span: args → input, return value →
516
+ * output, thrown/rejected errors captured and rethrown. Activates context so nested SDK calls
517
+ * parent to it. Resolves the client at call time, so wrapping at module load before init() works.
518
+ */
519
+ function observe(fn, options) {
520
+ const wrapped = function(...args) {
521
+ const core = currentClient().core;
522
+ if (!core) return fn.apply(this, args);
523
+ let handle;
524
+ try {
525
+ const name = options?.name ?? (fn.name || "anonymous");
526
+ const input = options?.input !== void 0 ? options.input : args.length === 0 ? void 0 : args.length === 1 ? args[0] : args;
527
+ handle = createSpanHandle(core, name, {
528
+ ...options,
529
+ input
530
+ });
531
+ } catch (error) {
532
+ reportError(core.config.onError, error);
533
+ return fn.apply(this, args);
534
+ }
535
+ try {
536
+ const result = withContext(handle.context, () => fn.apply(this, args));
537
+ if (isThenable(result)) return result.then((value) => {
538
+ handle.end({ output: value });
539
+ return value;
540
+ }, (error) => {
541
+ handle.end({ error });
542
+ throw error;
543
+ });
544
+ handle.end({ output: result });
545
+ return result;
546
+ } catch (error) {
547
+ handle.end({ error });
548
+ throw error;
549
+ }
550
+ };
551
+ Object.defineProperty(wrapped, "name", {
552
+ value: fn.name || "anonymous",
553
+ configurable: true
554
+ });
555
+ return wrapped;
556
+ }
557
+ //#endregion
558
+ export { flush, getTraceparent, init, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan };
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@telemetry-dev/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Pure TypeScript SDK for telemetry.dev: LLM traces, logs, and GenAI metrics on OpenTelemetry, for Node and edge runtimes.",
5
+ "keywords": [
6
+ "genai",
7
+ "llm",
8
+ "observability",
9
+ "opentelemetry",
10
+ "otel",
11
+ "sdk",
12
+ "telemetry",
13
+ "tracing"
14
+ ],
15
+ "homepage": "https://telemetry.dev",
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/telemetry-dev/telemetry.dev.git",
20
+ "directory": "packages/sdk"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "src"
25
+ ],
26
+ "type": "module",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.mts",
30
+ "import": "./dist/index.mjs",
31
+ "default": "./dist/index.mjs"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "dependencies": {
39
+ "@opentelemetry/api-logs": "^0.218.0",
40
+ "@opentelemetry/core": "^2.7.1",
41
+ "@opentelemetry/resources": "^2.7.1",
42
+ "@opentelemetry/sdk-logs": "^0.218.0",
43
+ "@opentelemetry/sdk-metrics": "^2.7.1",
44
+ "@opentelemetry/sdk-trace-base": "^2.7.1",
45
+ "@telemetry-dev/otel": "^0.1.0"
46
+ },
47
+ "devDependencies": {
48
+ "@opentelemetry/api": "^1.9.1",
49
+ "@types/node": "^25.5.0",
50
+ "@typescript/native-preview": "7.0.0-dev.20260328.1",
51
+ "typescript": "^6.0.2",
52
+ "vite-plus": "0.1.20",
53
+ "vitest": "npm:@voidzero-dev/vite-plus-test@0.1.20"
54
+ },
55
+ "peerDependencies": {
56
+ "@opentelemetry/api": ">=1.9.0 <2"
57
+ },
58
+ "engines": {
59
+ "node": ">=20.19.0"
60
+ },
61
+ "scripts": {
62
+ "build": "pnpm exec vp pack",
63
+ "dev": "pnpm exec vp pack --watch",
64
+ "test": "vp test",
65
+ "check": "vp check"
66
+ }
67
+ }