@telemetry-dev/otel 0.1.0 → 0.1.2

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 CHANGED
@@ -1,8 +1,8 @@
1
- import { ROOT_CONTEXT, context, createContextKey } from "@opentelemetry/api";
1
+ import { ROOT_CONTEXT, TraceFlags, context, createContextKey, diag as diag$1, isSpanContextValid, trace } from "@opentelemetry/api";
2
2
  import { AggregationTemporality, MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
3
- import { ExportResultCode } from "@opentelemetry/core";
3
+ import { ExportResultCode, getNumberFromEnv, getStringFromEnv } from "@opentelemetry/core";
4
4
  import { resourceFromAttributes } from "@opentelemetry/resources";
5
- import { BatchSpanProcessor, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
5
+ import { AlwaysOffSampler, AlwaysOnSampler, BatchSpanProcessor, ParentBasedSampler, SimpleSpanProcessor, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";
6
6
  import { ProtobufLogsSerializer, ProtobufMetricsSerializer, ProtobufTraceSerializer } from "@opentelemetry/otlp-transformer";
7
7
  //#region src/attrs.ts
8
8
  const SCOPE_NAME = "@telemetry-dev/sdk";
@@ -34,7 +34,7 @@ const DEFAULT_BATCH = {
34
34
  exportTimeoutMillis: 3e4
35
35
  };
36
36
  function resolveEnv() {
37
- if (typeof process !== "undefined" && process.env) return process.env;
37
+ if (globalThis.process !== void 0 && process.env) return process.env;
38
38
  return {};
39
39
  }
40
40
  //#endregion
@@ -61,11 +61,11 @@ const diag = {
61
61
  error: (...args) => emit("error", args)
62
62
  };
63
63
  /** Fail-open guard: SDK internals report through onError + diagnostics, never into user code. */
64
- function reportError(onError, error) {
64
+ function reportError(onError, cause) {
65
65
  try {
66
- onError?.(error);
66
+ onError?.(cause instanceof Error ? cause : new Error(String(cause)));
67
67
  } catch {}
68
- diag.error(error);
68
+ diag.error(cause);
69
69
  }
70
70
  //#endregion
71
71
  //#region src/context.ts
@@ -204,7 +204,7 @@ const TOKEN_OPERATIONS = new Set([
204
204
  const DORMANT_INTERVAL_MS = 2 ** 31 - 1;
205
205
  const BATCHED_METRIC_INTERVAL_MS = 6e4;
206
206
  function stringAttr(value) {
207
- return typeof value === "string" ? value : void 0;
207
+ return value?.constructor === String ? `${value}` : void 0;
208
208
  }
209
209
  function createMetricsPipeline({ resource, exporter, exportIntervalMillis }) {
210
210
  const reader = new PeriodicExportingMetricReader({
@@ -226,7 +226,7 @@ function createMetricsPipeline({ resource, exporter, exportIntervalMillis }) {
226
226
  });
227
227
  const record = (span) => {
228
228
  const operation = span.attributes["gen_ai.operation.name"];
229
- if (typeof operation !== "string" || !DURATION_OPERATIONS.has(operation)) return;
229
+ if (operation?.constructor !== String || !DURATION_OPERATIONS.has(`${operation}`)) return;
230
230
  const attrs = omitUndefined({
231
231
  "gen_ai.operation.name": operation,
232
232
  "gen_ai.provider.name": stringAttr(span.attributes["gen_ai.provider.name"]),
@@ -241,12 +241,12 @@ function createMetricsPipeline({ resource, exporter, exportIntervalMillis }) {
241
241
  } : attrs);
242
242
  if (!TOKEN_OPERATIONS.has(operation)) return;
243
243
  const inputTokens = span.attributes["gen_ai.usage.input_tokens"];
244
- if (typeof inputTokens === "number") tokenHistogram.record(inputTokens, {
244
+ if (inputTokens?.constructor === Number) tokenHistogram.record(inputTokens, {
245
245
  ...attrs,
246
246
  "gen_ai.token.type": "input"
247
247
  });
248
248
  const outputTokens = span.attributes["gen_ai.usage.output_tokens"];
249
- if (typeof outputTokens === "number") tokenHistogram.record(outputTokens, {
249
+ if (outputTokens?.constructor === Number) tokenHistogram.record(outputTokens, {
250
250
  ...attrs,
251
251
  "gen_ai.token.type": "output"
252
252
  });
@@ -301,6 +301,8 @@ var StampingSpanProcessor = class {
301
301
  //#endregion
302
302
  //#region src/transport.ts
303
303
  const RETRY_DELAYS_MS = [100, 500];
304
+ const MAX_RETRY_AFTER_MS = 2e3;
305
+ const MAX_TIMER_DELAY_MS = 2147483647;
304
306
  const RETRYABLE_STATUSES = new Set([
305
307
  429,
306
308
  502,
@@ -308,21 +310,105 @@ const RETRYABLE_STATUSES = new Set([
308
310
  504
309
311
  ]);
310
312
  const isRetryableStatus = (status) => RETRYABLE_STATUSES.has(status);
311
- const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
313
+ const HTTP_MONTHS = [
314
+ "Jan",
315
+ "Feb",
316
+ "Mar",
317
+ "Apr",
318
+ "May",
319
+ "Jun",
320
+ "Jul",
321
+ "Aug",
322
+ "Sep",
323
+ "Oct",
324
+ "Nov",
325
+ "Dec"
326
+ ];
327
+ const HTTP_WEEKDAYS = [
328
+ "Sun",
329
+ "Mon",
330
+ "Tue",
331
+ "Wed",
332
+ "Thu",
333
+ "Fri",
334
+ "Sat"
335
+ ];
336
+ const HTTP_WEEKDAYS_LONG = [
337
+ "Sunday",
338
+ "Monday",
339
+ "Tuesday",
340
+ "Wednesday",
341
+ "Thursday",
342
+ "Friday",
343
+ "Saturday"
344
+ ];
345
+ const httpDateTimestamp = (weekday, dayText, monthText, yearText, hourText, minuteText, secondText, weekdays) => {
346
+ const day = Number(dayText);
347
+ const month = HTTP_MONTHS.indexOf(monthText);
348
+ const year = Number(yearText);
349
+ const hour = Number(hourText);
350
+ const minute = Number(minuteText);
351
+ const second = Number(secondText);
352
+ if (hour > 23 || minute > 59 || second > 59) return void 0;
353
+ const date = /* @__PURE__ */ new Date(0);
354
+ date.setUTCFullYear(year, month, day);
355
+ date.setUTCHours(hour, minute, second, 0);
356
+ if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month || date.getUTCDate() !== day || weekdays[date.getUTCDay()] !== weekday) return;
357
+ return date.getTime();
358
+ };
359
+ const parseHttpDate = (value) => {
360
+ const imf = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\d{2}) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d{4}) (\d{2}):(\d{2}):(\d{2}) GMT$/.exec(value);
361
+ if (imf) return httpDateTimestamp(...imf.slice(1), HTTP_WEEKDAYS);
362
+ const rfc850 = /^(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (\d{2})-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d{2}) (\d{2}):(\d{2}):(\d{2}) GMT$/.exec(value);
363
+ if (rfc850) {
364
+ const fields = rfc850.slice(1);
365
+ const currentYear = (/* @__PURE__ */ new Date()).getUTCFullYear();
366
+ let year = Math.floor(currentYear / 100) * 100 + Number(fields[3]);
367
+ if (year > currentYear + 50) year -= 100;
368
+ fields[3] = String(year);
369
+ return httpDateTimestamp(...fields, HTTP_WEEKDAYS_LONG);
370
+ }
371
+ const asctime = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ( [1-9]|[12]\d|3[01]) (\d{2}):(\d{2}):(\d{2}) (\d{4})$/.exec(value);
372
+ if (!asctime) return void 0;
373
+ const [, weekday, month, day, hour, minute, second, year] = asctime;
374
+ return httpDateTimestamp(weekday, day.trim(), month, year, hour, minute, second, HTTP_WEEKDAYS);
375
+ };
376
+ const retryAfterMs = (res) => {
377
+ const header = res.headers.get("retry-after")?.trim();
378
+ if (header === void 0) return void 0;
379
+ if (/^\d+$/.test(header)) return Number(header) * 1e3;
380
+ const at = parseHttpDate(header);
381
+ return at === void 0 ? void 0 : Math.max(0, at - Date.now());
382
+ };
383
+ const delay = (ms, signal) => new Promise((resolve, reject) => {
384
+ const onAbort = () => {
385
+ clearTimeout(timer);
386
+ reject(signal?.reason ?? /* @__PURE__ */ new Error("telemetry.dev export aborted"));
387
+ };
388
+ const timer = setTimeout(() => {
389
+ signal?.removeEventListener("abort", onAbort);
390
+ resolve();
391
+ }, ms);
392
+ if (signal?.aborted) onAbort();
393
+ else signal?.addEventListener("abort", onAbort, { once: true });
394
+ });
312
395
  const cancelBody = async (res) => {
313
396
  try {
314
397
  await res.body?.cancel();
315
398
  } catch {}
316
399
  };
317
- const postOtlp = async ({ fetchImpl, url, headers, body }) => {
400
+ const postOtlp = async ({ fetchImpl, url, headers, body, signal }) => {
318
401
  for (let attempt = 0;; attempt += 1) {
402
+ let retryAfter;
319
403
  try {
320
404
  const res = await fetchImpl(url, {
321
405
  method: "POST",
322
406
  headers,
323
- body
407
+ body,
408
+ signal
324
409
  });
325
- if (res.ok || !isRetryableStatus(res.status) || attempt === RETRY_DELAYS_MS.length) {
410
+ retryAfter = retryAfterMs(res);
411
+ if (res.ok || !isRetryableStatus(res.status) || attempt === RETRY_DELAYS_MS.length || retryAfter !== void 0 && retryAfter > MAX_RETRY_AFTER_MS) {
326
412
  await cancelBody(res);
327
413
  return res;
328
414
  }
@@ -330,12 +416,12 @@ const postOtlp = async ({ fetchImpl, url, headers, body }) => {
330
416
  } catch (error) {
331
417
  if (attempt === RETRY_DELAYS_MS.length) throw error;
332
418
  }
333
- await delay(RETRY_DELAYS_MS[attempt]);
419
+ await delay(retryAfter ?? RETRY_DELAYS_MS[attempt], signal);
334
420
  }
335
421
  };
336
422
  const GZIP_THRESHOLD_BYTES = 1024;
337
423
  async function maybeGzip(body) {
338
- if (body.byteLength <= GZIP_THRESHOLD_BYTES || typeof CompressionStream === "undefined") return { body };
424
+ if (body.byteLength <= GZIP_THRESHOLD_BYTES || globalThis.CompressionStream === void 0) return { body };
339
425
  try {
340
426
  const stream = new Blob([body]).stream().pipeThrough(new CompressionStream("gzip"));
341
427
  return {
@@ -346,119 +432,140 @@ async function maybeGzip(body) {
346
432
  return { body };
347
433
  }
348
434
  }
435
+ async function postSerialized(body, target, transport, label, post = postOtlp, signal) {
436
+ const { body: finalBody, contentEncoding } = await maybeGzip(body);
437
+ const headers = contentEncoding ? {
438
+ ...target.headers,
439
+ "content-encoding": contentEncoding
440
+ } : target.headers;
441
+ const res = await post({
442
+ fetchImpl: transport.fetchImpl,
443
+ url: target.url,
444
+ headers,
445
+ body: finalBody,
446
+ signal
447
+ });
448
+ if (!res.ok) {
449
+ const retryAfter = retryAfterMs(res);
450
+ const retryAfterHeader = res.headers.get("retry-after")?.trim();
451
+ const retryDetail = retryAfter !== void 0 && retryAfter > MAX_RETRY_AFTER_MS && retryAfterHeader !== void 0 ? `; retry-after ${retryAfterHeader} exceeds ${MAX_RETRY_AFTER_MS / 1e3}s retry cap` : "";
452
+ throw new Error(`telemetry.dev ${label} ingest failed: ${res.status}${retryDetail}`);
453
+ }
454
+ }
455
+ const createExportLifecycle = (transport) => {
456
+ const inFlight = /* @__PURE__ */ new Set();
457
+ let shutDown = false;
458
+ const configuredTimeoutMillis = transport.exportTimeoutMillis ?? DEFAULT_BATCH.exportTimeoutMillis;
459
+ const timeoutMillis = Number.isFinite(configuredTimeoutMillis) && configuredTimeoutMillis >= 0 && configuredTimeoutMillis <= MAX_TIMER_DELAY_MS ? configuredTimeoutMillis : DEFAULT_BATCH.exportTimeoutMillis;
460
+ const callback = (resultCallback, result) => {
461
+ try {
462
+ resultCallback(result);
463
+ } catch (error) {
464
+ reportError(transport.onError, error);
465
+ }
466
+ };
467
+ const exportBatch = (send, resultCallback) => {
468
+ if (shutDown) {
469
+ const error = /* @__PURE__ */ new Error("telemetry.dev exporter is shut down");
470
+ callback(resultCallback, {
471
+ code: ExportResultCode.FAILED,
472
+ error
473
+ });
474
+ return;
475
+ }
476
+ const controller = new AbortController();
477
+ let timer;
478
+ const timeout = new Promise((_, reject) => {
479
+ timer = setTimeout(() => {
480
+ controller.abort();
481
+ reject(/* @__PURE__ */ new Error(`telemetry.dev export timed out after ${timeoutMillis}ms`));
482
+ }, timeoutMillis);
483
+ });
484
+ let tracked;
485
+ tracked = Promise.race([Promise.resolve().then(() => send(controller.signal)), timeout]).then(() => callback(resultCallback, { code: ExportResultCode.SUCCESS }), (cause) => {
486
+ const error = cause instanceof Error ? cause : new Error(String(cause));
487
+ reportError(transport.onError, error);
488
+ callback(resultCallback, {
489
+ code: ExportResultCode.FAILED,
490
+ error
491
+ });
492
+ }).finally(() => {
493
+ clearTimeout(timer);
494
+ inFlight.delete(tracked);
495
+ });
496
+ inFlight.add(tracked);
497
+ };
498
+ const forceFlush = () => Promise.all(inFlight).then(() => void 0);
499
+ const shutdown = () => {
500
+ shutDown = true;
501
+ return forceFlush();
502
+ };
503
+ return {
504
+ exportBatch,
505
+ forceFlush,
506
+ shutdown
507
+ };
508
+ };
349
509
  const MAX_BODY_BYTES = 35e5;
350
510
  function createOtlpBatchSender(serializer, target, transport, label) {
351
- const send = async (items) => {
511
+ const send = async (items, signal) => {
352
512
  const body = serializer.serializeRequest(items);
353
513
  if (!body || body.byteLength === 0) return;
354
514
  if (body.byteLength > MAX_BODY_BYTES) {
355
515
  if (items.length > 1) {
356
516
  const mid = Math.ceil(items.length / 2);
357
- await send(items.slice(0, mid));
358
- await send(items.slice(mid));
517
+ await send(items.slice(0, mid), signal);
518
+ await send(items.slice(mid), signal);
359
519
  return;
360
520
  }
361
521
  reportError(transport.onError, /* @__PURE__ */ new Error(`telemetry.dev: ${label} record exceeds max export size, dropped`));
362
522
  return;
363
523
  }
364
- const { body: finalBody, contentEncoding } = await maybeGzip(body);
365
- const headers = contentEncoding ? {
366
- ...target.headers,
367
- "content-encoding": contentEncoding
368
- } : target.headers;
369
- const res = await postOtlp({
370
- fetchImpl: transport.fetchImpl,
371
- url: target.url,
372
- headers,
373
- body: finalBody
374
- });
375
- if (!res.ok) throw new Error(`telemetry.dev ${label} ingest failed: ${res.status}`);
524
+ await postSerialized(body, target, transport, label, postOtlp, signal);
376
525
  };
377
526
  return send;
378
527
  }
379
528
  function createTraceExporter(target, transport) {
380
529
  const send = createOtlpBatchSender(ProtobufTraceSerializer, target, transport, "trace");
530
+ const lifecycle = createExportLifecycle(transport);
381
531
  return {
382
532
  export(spans, resultCallback) {
383
- send(spans).then(() => resultCallback({ code: ExportResultCode.SUCCESS }), (error) => {
384
- reportError(transport.onError, error);
385
- resultCallback({
386
- code: ExportResultCode.FAILED,
387
- error: error instanceof Error ? error : void 0
388
- });
389
- });
533
+ lifecycle.exportBatch((signal) => send(spans, signal), resultCallback);
390
534
  },
391
- forceFlush: () => Promise.resolve(),
392
- shutdown: () => Promise.resolve()
535
+ forceFlush: lifecycle.forceFlush,
536
+ shutdown: lifecycle.shutdown
393
537
  };
394
538
  }
395
539
  function createLogExporter(target, transport) {
396
540
  const send = createOtlpBatchSender(ProtobufLogsSerializer, target, transport, "log");
541
+ const lifecycle = createExportLifecycle(transport);
397
542
  return {
398
543
  export(logs, resultCallback) {
399
- send(logs).then(() => resultCallback({ code: ExportResultCode.SUCCESS }), (error) => {
400
- reportError(transport.onError, error);
401
- resultCallback({
402
- code: ExportResultCode.FAILED,
403
- error: error instanceof Error ? error : void 0
404
- });
405
- });
544
+ lifecycle.exportBatch((signal) => send(logs, signal), resultCallback);
406
545
  },
407
- forceFlush: () => Promise.resolve(),
408
- shutdown: () => Promise.resolve()
546
+ forceFlush: lifecycle.forceFlush,
547
+ shutdown: lifecycle.shutdown
409
548
  };
410
549
  }
411
550
  function createMetricExporter(target, transport) {
412
- const { fetchImpl, onError } = transport;
551
+ const lifecycle = createExportLifecycle(transport);
413
552
  return {
414
553
  export(resourceMetrics, resultCallback) {
415
- if (!resourceMetrics.scopeMetrics.some((scope) => scope.metrics.some((metric) => metric.dataPoints.length > 0))) {
416
- resultCallback({ code: 0 });
417
- return;
418
- }
419
- const body = ProtobufMetricsSerializer.serializeRequest(resourceMetrics);
420
- if (!body || body.byteLength === 0) {
421
- resultCallback({ code: 0 });
422
- return;
423
- }
424
- maybeGzip(body).then(({ body: finalBody, contentEncoding }) => {
425
- const headers = contentEncoding ? {
426
- ...target.headers,
427
- "content-encoding": contentEncoding
428
- } : target.headers;
429
- return fetchImpl(target.url, {
430
- method: "POST",
431
- headers,
432
- body: finalBody
433
- });
434
- }).then((res) => {
435
- if (!res.ok) {
436
- const error = /* @__PURE__ */ new Error(`telemetry.dev metric ingest failed: ${res.status}`);
437
- reportError(onError, error);
438
- resultCallback({
439
- code: 1,
440
- error
441
- });
442
- return;
443
- }
444
- resultCallback({ code: 0 });
445
- }).catch((error) => {
446
- reportError(onError, error);
447
- resultCallback({
448
- code: 1,
449
- error: error instanceof Error ? error : void 0
450
- });
451
- });
554
+ lifecycle.exportBatch(async (signal) => {
555
+ const body = resourceMetrics.scopeMetrics.some((scope) => scope.metrics.some((metric) => metric.dataPoints.length > 0)) ? ProtobufMetricsSerializer.serializeRequest(resourceMetrics) : void 0;
556
+ if (body !== void 0 && body.byteLength > 0) await postSerialized(body, target, transport, "metric", postOtlp, signal);
557
+ }, resultCallback);
452
558
  },
453
559
  selectAggregationTemporality: () => AggregationTemporality.DELTA,
454
- forceFlush: () => Promise.resolve(),
455
- shutdown: () => Promise.resolve()
560
+ forceFlush: lifecycle.forceFlush,
561
+ shutdown: lifecycle.shutdown
456
562
  };
457
563
  }
458
- function otlpHeaders(apiKey) {
564
+ function otlpHeaders(apiKey, sdkName = "@telemetry-dev/otel") {
459
565
  return {
460
566
  "content-type": "application/x-protobuf",
461
- authorization: `Bearer ${apiKey}`
567
+ authorization: `Bearer ${apiKey}`,
568
+ "x-telemetry-dev-sdk": sdkName
462
569
  };
463
570
  }
464
571
  //#endregion
@@ -477,7 +584,8 @@ var TelemetrySpanProcessor = class {
477
584
  const baseUrl = (options.baseUrl ?? env.TELEMETRY_DEV_BASE_URL ?? "https://ingest.telemetry.dev").replace(/\/+$/, "");
478
585
  const transport = {
479
586
  fetchImpl: options.fetch ?? globalThis.fetch,
480
- onError: options.onError
587
+ onError: options.onError,
588
+ exportTimeoutMillis: options.batch?.exportTimeoutMillis
481
589
  };
482
590
  const exporter = options.spanExporter ?? (apiKey ? createTraceExporter({
483
591
  url: `${baseUrl}/v1/traces`,
@@ -546,8 +654,272 @@ function createTelemetrySpanExporter(options = {}) {
546
654
  headers: otlpHeaders(apiKey)
547
655
  }, {
548
656
  fetchImpl: options.fetch ?? globalThis.fetch,
549
- onError: options.onError
657
+ onError: options.onError,
658
+ exportTimeoutMillis: options.exportTimeoutMillis
550
659
  });
551
660
  }
552
661
  //#endregion
553
- export { AlsContextManager, BATCHED_METRIC_INTERVAL_MS, DEFAULT_BASE_URL, DEFAULT_BATCH, DORMANT_INTERVAL_MS, DURATION_BUCKETS, PROPAGATED_KEY, SCOPE_NAME, SCOPE_VERSION, StampingSpanProcessor, TOKEN_BUCKETS, TelemetrySpanProcessor, activeContext, als, buildPropagatedAttributes, createLogExporter, createMetricExporter, createMetricsPipeline, createTelemetrySpanExporter, createTraceExporter, diag, jsonAttr, maybeGzip, omitUndefined, otlpHeaders, postOtlp, propagateAttributes, propagatedFromContext, reportError, resolveEnv, setLogLevel, withContext };
662
+ //#region src/session.ts
663
+ const SESSION_PARENTS = /* @__PURE__ */ new WeakMap();
664
+ /**
665
+ * Install on the provider used with withSessionParent or sessionRootTracerProvider.
666
+ * Pass the provider's configured sampler explicitly; OTel cannot read it back from a provider.
667
+ * Without an argument, uses OTEL_TRACES_SAMPLER / OTEL_TRACES_SAMPLER_ARG defaults.
668
+ */
669
+ function sessionSampler(inner = samplerFromEnv()) {
670
+ return {
671
+ shouldSample(ctx, traceId, name, kind, attributes, links) {
672
+ const parent = trace.getSpan(ctx);
673
+ const original = parent && SESSION_PARENTS.get(parent);
674
+ if (original) {
675
+ const span = trace.getSpan(original);
676
+ ctx = span ? trace.setSpan(ctx, span) : trace.deleteSpan(ctx);
677
+ }
678
+ return inner.shouldSample(ctx, traceId, name, kind, attributes, links);
679
+ },
680
+ toString: () => `SessionSampler{${inner.toString()}}`
681
+ };
682
+ }
683
+ function samplerFromEnv() {
684
+ const name = getStringFromEnv("OTEL_TRACES_SAMPLER") ?? "parentbased_always_on";
685
+ let root;
686
+ switch (name) {
687
+ case "always_off":
688
+ case "parentbased_always_off":
689
+ root = new AlwaysOffSampler();
690
+ break;
691
+ case "traceidratio":
692
+ case "parentbased_traceidratio": {
693
+ const ratio = getNumberFromEnv("OTEL_TRACES_SAMPLER_ARG");
694
+ const valid = ratio !== void 0 && Number.isFinite(ratio) && ratio >= 0 && ratio <= 1;
695
+ if (!valid) diag$1.error("Invalid OTEL_TRACES_SAMPLER_ARG; using 1.");
696
+ root = new TraceIdRatioBasedSampler(valid ? ratio : 1);
697
+ break;
698
+ }
699
+ case "always_on":
700
+ case "parentbased_always_on":
701
+ root = new AlwaysOnSampler();
702
+ break;
703
+ default:
704
+ diag$1.error(`Invalid OTEL_TRACES_SAMPLER "${name}"; using parentbased_always_on.`);
705
+ return new ParentBasedSampler({ root: new AlwaysOnSampler() });
706
+ }
707
+ return name.startsWith("parentbased_") ? new ParentBasedSampler({ root }) : root;
708
+ }
709
+ /**
710
+ * Deterministic remote parent for a session: every root span of the session joins one trace
711
+ * (`traceId = SHA-256(apiKey ‖ 0x00 ‖ sessionId)[0:16]`, parent `spanId = digest[16:24]`).
712
+ * The API key is part of the input so two projects with the same session id never share a trace
713
+ * id. No span is ever emitted for the parent. Its flags are not a sampling decision:
714
+ * use withSessionParent and install sessionSampler on the provider. Python mirrors these IDs.
715
+ */
716
+ function sessionSpanContext(apiKey, sessionId) {
717
+ const digest = sha256(new TextEncoder().encode(`${apiKey ?? ""}\0${sessionId}`));
718
+ return {
719
+ traceId: hex(digest.subarray(0, 16)),
720
+ spanId: hex(digest.subarray(16, 24)),
721
+ traceFlags: TraceFlags.NONE,
722
+ isRemote: true
723
+ };
724
+ }
725
+ /**
726
+ * Wraps a provider so that spans `sessionRootOf` recognizes are parented under the session
727
+ * parent even when a parent is active. Frameworks that run each turn inside their own
728
+ * engine span (eve's "workflow" scope) otherwise give every turn its own trace.
729
+ * The inner provider MUST use sessionSampler(configuredSampler) to retain sampling policy.
730
+ */
731
+ function sessionRootTracerProvider(inner, apiKey, sessionRootOf, onError) {
732
+ const reparent = (name, options, ctx) => {
733
+ if (!apiKey) return ctx;
734
+ try {
735
+ const sessionId = sessionRootOf(name, options?.attributes ?? {});
736
+ return sessionId ? setSessionParent(options?.root ? trace.deleteSpan(ctx) : ctx, sessionId, apiKey) : ctx;
737
+ } catch (error) {
738
+ reportError(onError, error);
739
+ return ctx;
740
+ }
741
+ };
742
+ return { getTracer(name, version, options) {
743
+ const tracer = inner.getTracer(name, version, options);
744
+ return {
745
+ startSpan: (spanName, spanOptions, ctx = context.active()) => {
746
+ const parent = reparent(spanName, spanOptions, ctx);
747
+ return tracer.startSpan(spanName, parent !== ctx && spanOptions?.root ? {
748
+ ...spanOptions,
749
+ root: false
750
+ } : spanOptions, parent);
751
+ },
752
+ startActiveSpan(spanName, ...args) {
753
+ const fn = args.at(-1);
754
+ const spanOptions = args.length > 1 ? args[0] : void 0;
755
+ const ctx = args.length > 2 ? args[1] : context.active();
756
+ const parent = reparent(spanName, spanOptions, ctx);
757
+ return tracer.startActiveSpan(spanName, parent !== ctx && spanOptions?.root ? {
758
+ ...spanOptions,
759
+ root: false
760
+ } : spanOptions ?? {}, parent, fn);
761
+ }
762
+ };
763
+ } };
764
+ }
765
+ /**
766
+ * Parent a would-be root under the session; nested spans keep their real parent.
767
+ * Requires sessionSampler(configuredSampler) on the provider that starts the span.
768
+ */
769
+ function withSessionParent(ctx, sessionId, apiKey) {
770
+ if (!apiKey || !sessionId) return ctx;
771
+ const current = trace.getSpanContext(ctx);
772
+ if (current && isSpanContextValid(current)) return ctx;
773
+ return setSessionParent(ctx, sessionId, apiKey);
774
+ }
775
+ function setSessionParent(ctx, sessionId, apiKey) {
776
+ const parent = trace.wrapSpanContext({
777
+ ...sessionSpanContext(apiKey, sessionId),
778
+ traceState: trace.getSpanContext(ctx)?.traceState
779
+ });
780
+ SESSION_PARENTS.set(parent, ctx);
781
+ return trace.setSpan(ctx, parent);
782
+ }
783
+ function sessionIdOf(ctx, attributes) {
784
+ const explicit = attributes?.["gen_ai.conversation.id"];
785
+ if (typeof explicit === "string") return explicit;
786
+ const propagated = propagatedFromContext(ctx)?.["gen_ai.conversation.id"];
787
+ return typeof propagated === "string" ? propagated : void 0;
788
+ }
789
+ function hex(bytes) {
790
+ let s = "";
791
+ for (const b of bytes) s += b.toString(16).padStart(2, "0");
792
+ return s;
793
+ }
794
+ const K = new Uint32Array([
795
+ 1116352408,
796
+ 1899447441,
797
+ 3049323471,
798
+ 3921009573,
799
+ 961987163,
800
+ 1508970993,
801
+ 2453635748,
802
+ 2870763221,
803
+ 3624381080,
804
+ 310598401,
805
+ 607225278,
806
+ 1426881987,
807
+ 1925078388,
808
+ 2162078206,
809
+ 2614888103,
810
+ 3248222580,
811
+ 3835390401,
812
+ 4022224774,
813
+ 264347078,
814
+ 604807628,
815
+ 770255983,
816
+ 1249150122,
817
+ 1555081692,
818
+ 1996064986,
819
+ 2554220882,
820
+ 2821834349,
821
+ 2952996808,
822
+ 3210313671,
823
+ 3336571891,
824
+ 3584528711,
825
+ 113926993,
826
+ 338241895,
827
+ 666307205,
828
+ 773529912,
829
+ 1294757372,
830
+ 1396182291,
831
+ 1695183700,
832
+ 1986661051,
833
+ 2177026350,
834
+ 2456956037,
835
+ 2730485921,
836
+ 2820302411,
837
+ 3259730800,
838
+ 3345764771,
839
+ 3516065817,
840
+ 3600352804,
841
+ 4094571909,
842
+ 275423344,
843
+ 430227734,
844
+ 506948616,
845
+ 659060556,
846
+ 883997877,
847
+ 958139571,
848
+ 1322822218,
849
+ 1537002063,
850
+ 1747873779,
851
+ 1955562222,
852
+ 2024104815,
853
+ 2227730452,
854
+ 2361852424,
855
+ 2428436474,
856
+ 2756734187,
857
+ 3204031479,
858
+ 3329325298
859
+ ]);
860
+ const rotr = (x, n) => x >>> n | x << 32 - n;
861
+ /** FIPS 180-4 SHA-256 in plain JS: the package must stay synchronous and runtime-neutral. */
862
+ function sha256(bytes) {
863
+ const h = new Uint32Array([
864
+ 1779033703,
865
+ 3144134277,
866
+ 1013904242,
867
+ 2773480762,
868
+ 1359893119,
869
+ 2600822924,
870
+ 528734635,
871
+ 1541459225
872
+ ]);
873
+ const padded = new Uint8Array(Math.ceil((bytes.length + 9) / 64) * 64);
874
+ padded.set(bytes);
875
+ padded[bytes.length] = 128;
876
+ const view = new DataView(padded.buffer);
877
+ const bitLen = bytes.length * 8;
878
+ view.setUint32(padded.length - 8, Math.floor(bitLen / 4294967296));
879
+ view.setUint32(padded.length - 4, bitLen >>> 0);
880
+ const w = new Uint32Array(64);
881
+ for (let off = 0; off < padded.length; off += 64) {
882
+ for (let i = 0; i < 16; i++) w[i] = view.getUint32(off + i * 4);
883
+ for (let i = 16; i < 64; i++) {
884
+ const x = w[i - 15];
885
+ const y = w[i - 2];
886
+ const s0 = rotr(x, 7) ^ rotr(x, 18) ^ x >>> 3;
887
+ const s1 = rotr(y, 17) ^ rotr(y, 19) ^ y >>> 10;
888
+ w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
889
+ }
890
+ let a = h[0];
891
+ let b = h[1];
892
+ let c = h[2];
893
+ let d = h[3];
894
+ let e = h[4];
895
+ let f = h[5];
896
+ let g = h[6];
897
+ let hh = h[7];
898
+ for (let i = 0; i < 64; i++) {
899
+ const t1 = hh + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + (e & f ^ ~e & g) + K[i] + w[i] >>> 0;
900
+ const t2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
901
+ hh = g;
902
+ g = f;
903
+ f = e;
904
+ e = d + t1 >>> 0;
905
+ d = c;
906
+ c = b;
907
+ b = a;
908
+ a = t1 + t2 >>> 0;
909
+ }
910
+ h[0] = h[0] + a >>> 0;
911
+ h[1] = h[1] + b >>> 0;
912
+ h[2] = h[2] + c >>> 0;
913
+ h[3] = h[3] + d >>> 0;
914
+ h[4] = h[4] + e >>> 0;
915
+ h[5] = h[5] + f >>> 0;
916
+ h[6] = h[6] + g >>> 0;
917
+ h[7] = h[7] + hh >>> 0;
918
+ }
919
+ const out = new Uint8Array(32);
920
+ const outView = new DataView(out.buffer);
921
+ for (let i = 0; i < 8; i++) outView.setUint32(i * 4, h[i]);
922
+ return out;
923
+ }
924
+ //#endregion
925
+ export { AlsContextManager, BATCHED_METRIC_INTERVAL_MS, DEFAULT_BASE_URL, DEFAULT_BATCH, DORMANT_INTERVAL_MS, DURATION_BUCKETS, PROPAGATED_KEY, SCOPE_NAME, SCOPE_VERSION, StampingSpanProcessor, TOKEN_BUCKETS, TelemetrySpanProcessor, activeContext, als, buildPropagatedAttributes, createLogExporter, createMetricExporter, createMetricsPipeline, createTelemetrySpanExporter, createTraceExporter, diag, jsonAttr, maybeGzip, omitUndefined, otlpHeaders, postOtlp, propagateAttributes, propagatedFromContext, reportError, resolveEnv, sessionIdOf, sessionRootTracerProvider, sessionSampler, sessionSpanContext, setLogLevel, sha256, withContext, withSessionParent };