@glassflow-ai/rius 0.1.0 → 0.2.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.js CHANGED
@@ -8,6 +8,15 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
+ // node_modules/tsup/assets/esm_shims.js
12
+ import path from "path";
13
+ import { fileURLToPath } from "url";
14
+ var init_esm_shims = __esm({
15
+ "node_modules/tsup/assets/esm_shims.js"() {
16
+ "use strict";
17
+ }
18
+ });
19
+
11
20
  // src/config.ts
12
21
  function bool(name, raw, fallback) {
13
22
  if (raw === void 0) return fallback;
@@ -25,24 +34,64 @@ function rate(raw, fallback) {
25
34
  if (Number.isNaN(parsed)) return fallback;
26
35
  return Math.min(1, Math.max(0, parsed));
27
36
  }
37
+ function seconds(raw, fallback) {
38
+ if (raw === void 0) return fallback;
39
+ const parsed = Number.parseFloat(raw);
40
+ if (Number.isNaN(parsed)) return fallback;
41
+ return parsed;
42
+ }
43
+ function clamp(name, value, min, max) {
44
+ if (value >= min && value <= max) return value;
45
+ const clamped = Math.min(Math.max(value, min), max);
46
+ console.warn(`[rius] ${name}=${value} is outside [${min}, ${max}]; clamped to ${clamped}.`);
47
+ return clamped;
48
+ }
28
49
  function resolveConfig(options = {}, env = process.env) {
29
50
  const endpoint = (options.endpoint ?? env.RIUS_ENDPOINT ?? DEFAULT_ENDPOINT).replace(/\/+$/, "");
51
+ const serviceName = options.serviceName ?? env.RIUS_SERVICE_NAME ?? DEFAULT_SERVICE_NAME;
52
+ const heartbeatIntervalSeconds = clamp(
53
+ "heartbeatInterval",
54
+ options.heartbeatInterval ?? seconds(env.RIUS_HEARTBEAT_INTERVAL, DEFAULT_HEARTBEAT_INTERVAL),
55
+ HEARTBEAT_INTERVAL_MIN,
56
+ HEARTBEAT_INTERVAL_MAX
57
+ );
58
+ const partialSpansDelaySeconds = clamp(
59
+ "partialSpansDelay",
60
+ options.partialSpansDelay ?? seconds(env.RIUS_PARTIAL_SPANS_DELAY, DEFAULT_PARTIAL_SPANS_DELAY),
61
+ PARTIAL_SPANS_DELAY_MIN,
62
+ PARTIAL_SPANS_DELAY_MAX
63
+ );
30
64
  return {
31
65
  endpoint,
32
66
  apiKey: options.apiKey ?? env.RIUS_API_KEY,
33
- serviceName: options.serviceName ?? env.RIUS_SERVICE_NAME ?? DEFAULT_SERVICE_NAME,
67
+ serviceName,
34
68
  disabled: options.disabled ?? bool("RIUS_DISABLED", env.RIUS_DISABLED, false),
35
69
  sampleRate: options.sampleRate ?? rate(env.RIUS_SAMPLE_RATE, 1),
36
70
  captureContent: options.captureContent ?? bool("RIUS_CAPTURE_CONTENT", env.RIUS_CAPTURE_CONTENT, true),
37
- mask: options.mask
71
+ mask: options.mask,
72
+ heartbeat: options.heartbeat ?? bool("RIUS_HEARTBEAT", env.RIUS_HEARTBEAT, true),
73
+ heartbeatIntervalMs: heartbeatIntervalSeconds * 1e3,
74
+ heartbeatEndpoint: `${endpoint}/v1/heartbeat`,
75
+ // Empty falls through to serviceName rather than shipping a blank identity
76
+ // on every heartbeat payload, so `??` would be wrong here.
77
+ agentName: options.agentName || env.RIUS_AGENT_NAME || serviceName,
78
+ partialSpans: options.partialSpans ?? bool("RIUS_PARTIAL_SPANS", env.RIUS_PARTIAL_SPANS, false),
79
+ partialSpansDelayMs: partialSpansDelaySeconds * 1e3
38
80
  };
39
81
  }
40
- var DEFAULT_ENDPOINT, DEFAULT_SERVICE_NAME, AFFIRMATIVE, NEGATIVE;
82
+ var DEFAULT_ENDPOINT, DEFAULT_SERVICE_NAME, HEARTBEAT_INTERVAL_MIN, HEARTBEAT_INTERVAL_MAX, DEFAULT_HEARTBEAT_INTERVAL, PARTIAL_SPANS_DELAY_MIN, PARTIAL_SPANS_DELAY_MAX, DEFAULT_PARTIAL_SPANS_DELAY, AFFIRMATIVE, NEGATIVE;
41
83
  var init_config = __esm({
42
84
  "src/config.ts"() {
43
85
  "use strict";
86
+ init_esm_shims();
44
87
  DEFAULT_ENDPOINT = "https://ingest.eu.console.rius-glassflow.com";
45
88
  DEFAULT_SERVICE_NAME = "unknown_service";
89
+ HEARTBEAT_INTERVAL_MIN = 5;
90
+ HEARTBEAT_INTERVAL_MAX = 300;
91
+ DEFAULT_HEARTBEAT_INTERVAL = 15;
92
+ PARTIAL_SPANS_DELAY_MIN = 0;
93
+ PARTIAL_SPANS_DELAY_MAX = 60;
94
+ DEFAULT_PARTIAL_SPANS_DELAY = 0;
46
95
  AFFIRMATIVE = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
47
96
  NEGATIVE = /* @__PURE__ */ new Set(["0", "false", "no", "off"]);
48
97
  }
@@ -53,6 +102,7 @@ var DelegatingSpanProcessor;
53
102
  var init_delegatingProcessor = __esm({
54
103
  "src/delegatingProcessor.ts"() {
55
104
  "use strict";
105
+ init_esm_shims();
56
106
  DelegatingSpanProcessor = class {
57
107
  delegates = [];
58
108
  add(processor) {
@@ -90,6 +140,7 @@ var ExportResultCode;
90
140
  var init_ExportResult = __esm({
91
141
  "node_modules/@opentelemetry/core/build/esm/ExportResult.js"() {
92
142
  "use strict";
143
+ init_esm_shims();
93
144
  (function(ExportResultCode2) {
94
145
  ExportResultCode2[ExportResultCode2["SUCCESS"] = 0] = "SUCCESS";
95
146
  ExportResultCode2[ExportResultCode2["FAILED"] = 1] = "FAILED";
@@ -101,6 +152,7 @@ var init_ExportResult = __esm({
101
152
  var init_esm = __esm({
102
153
  "node_modules/@opentelemetry/core/build/esm/index.js"() {
103
154
  "use strict";
155
+ init_esm_shims();
104
156
  init_ExportResult();
105
157
  }
106
158
  });
@@ -110,6 +162,7 @@ var ExportOutcomeExporter;
110
162
  var init_exportHealth = __esm({
111
163
  "src/exportHealth.ts"() {
112
164
  "use strict";
165
+ init_esm_shims();
113
166
  init_esm();
114
167
  ExportOutcomeExporter = class {
115
168
  constructor(inner, onFirstFailure = (m) => console.warn(m)) {
@@ -152,6 +205,134 @@ var init_exportHealth = __esm({
152
205
  }
153
206
  });
154
207
 
208
+ // src/heartbeat.ts
209
+ import { randomUUID } from "crypto";
210
+ import { createRequire } from "module";
211
+ function httpTransport(url, headers) {
212
+ return async (payload, timeoutMs) => {
213
+ const response = await fetch(url, {
214
+ method: "POST",
215
+ headers: { "content-type": "application/json", ...headers },
216
+ body: JSON.stringify(payload),
217
+ signal: AbortSignal.timeout(timeoutMs)
218
+ });
219
+ await response.body?.cancel().catch(() => {
220
+ });
221
+ if (!response.ok) {
222
+ throw new Error(`heartbeat POST ${url} returned ${response.status}`);
223
+ }
224
+ };
225
+ }
226
+ var PAYLOAD_VERSION, OPEN_TRACES_CAP, DEFAULT_PING_TIMEOUT_MS, DEFAULT_FINAL_PING_TIMEOUT_MS, SDK_VERSION, OpenRootSpanTracker, HeartbeatSender;
227
+ var init_heartbeat = __esm({
228
+ "src/heartbeat.ts"() {
229
+ "use strict";
230
+ init_esm_shims();
231
+ PAYLOAD_VERSION = 1;
232
+ OPEN_TRACES_CAP = 32;
233
+ DEFAULT_PING_TIMEOUT_MS = 3e3;
234
+ DEFAULT_FINAL_PING_TIMEOUT_MS = 1e3;
235
+ SDK_VERSION = (() => {
236
+ try {
237
+ const pkg = createRequire(import.meta.url)("../package.json");
238
+ return pkg.version ?? "0.0.0";
239
+ } catch {
240
+ return "0.0.0";
241
+ }
242
+ })();
243
+ OpenRootSpanTracker = class {
244
+ // trace id (32-hex) -> count of open root spans in that trace. A trace id
245
+ // normally has one root, but the count guards against duplicates.
246
+ open = /* @__PURE__ */ new Map();
247
+ onStart(span, _parentContext) {
248
+ if (span.parentSpanContext !== void 0) return;
249
+ const traceId = span.spanContext().traceId;
250
+ this.open.set(traceId, (this.open.get(traceId) ?? 0) + 1);
251
+ }
252
+ onEnd(span) {
253
+ if (span.parentSpanContext !== void 0) return;
254
+ const traceId = span.spanContext().traceId;
255
+ const count = (this.open.get(traceId) ?? 0) - 1;
256
+ if (count <= 0) {
257
+ this.open.delete(traceId);
258
+ } else {
259
+ this.open.set(traceId, count);
260
+ }
261
+ }
262
+ /** Trace ids of currently-open root spans, in the order they opened. */
263
+ openTraceIds() {
264
+ return [...this.open.keys()];
265
+ }
266
+ async forceFlush() {
267
+ }
268
+ async shutdown() {
269
+ }
270
+ };
271
+ HeartbeatSender = class {
272
+ /** Identity of one process lifetime, fresh per sender. */
273
+ instanceId = randomUUID();
274
+ agentName;
275
+ tracker;
276
+ intervalMs;
277
+ pingTimeoutMs;
278
+ finalPingTimeoutMs;
279
+ send;
280
+ timer;
281
+ stopped = false;
282
+ deliveryWarned = false;
283
+ constructor(opts) {
284
+ this.agentName = opts.agentName;
285
+ this.tracker = opts.tracker;
286
+ this.intervalMs = opts.intervalMs;
287
+ this.pingTimeoutMs = opts.pingTimeoutMs ?? DEFAULT_PING_TIMEOUT_MS;
288
+ this.finalPingTimeoutMs = opts.finalPingTimeoutMs ?? DEFAULT_FINAL_PING_TIMEOUT_MS;
289
+ this.send = opts.transport ?? httpTransport(opts.url, opts.headers);
290
+ }
291
+ /** Starts pinging: an immediate first ping, then every `intervalMs`. */
292
+ start() {
293
+ void this.ping(this.pingTimeoutMs, false);
294
+ const timer = setInterval(() => void this.ping(this.pingTimeoutMs, false), this.intervalMs);
295
+ if (typeof timer.unref === "function") timer.unref();
296
+ this.timer = timer;
297
+ }
298
+ /** Stops the interval and sends the final `stopped: true` ping. Idempotent. */
299
+ async stop() {
300
+ if (this.stopped) return;
301
+ this.stopped = true;
302
+ if (this.timer !== void 0) clearInterval(this.timer);
303
+ await this.ping(this.finalPingTimeoutMs, true);
304
+ }
305
+ buildPayload(stopped) {
306
+ const openIds = this.tracker.openTraceIds();
307
+ const payload = {
308
+ v: PAYLOAD_VERSION,
309
+ instance_id: this.instanceId,
310
+ agent_name: this.agentName,
311
+ // RFC3339 UTC with millisecond precision and a Z suffix, already.
312
+ sent_at: (/* @__PURE__ */ new Date()).toISOString(),
313
+ sdk_language: "typescript",
314
+ sdk_version: SDK_VERSION,
315
+ open_traces: openIds.slice(0, OPEN_TRACES_CAP),
316
+ open_trace_count: openIds.length
317
+ };
318
+ if (stopped) payload.stopped = true;
319
+ return payload;
320
+ }
321
+ async ping(timeoutMs, stopped) {
322
+ try {
323
+ await this.send(this.buildPayload(stopped), timeoutMs);
324
+ } catch (error) {
325
+ if (!this.deliveryWarned) {
326
+ this.deliveryWarned = true;
327
+ const message = error instanceof Error ? error.message : String(error);
328
+ console.warn(`[rius] heartbeat delivery failed (${message}); further failures are silent.`);
329
+ }
330
+ }
331
+ }
332
+ };
333
+ }
334
+ });
335
+
155
336
  // src/semconv.ts
156
337
  function kindAttributes(kind) {
157
338
  const attributes = { [OPENINFERENCE_SPAN_KIND]: kind };
@@ -159,10 +340,11 @@ function kindAttributes(kind) {
159
340
  if (operation !== void 0) attributes[GEN_AI_OPERATION_NAME] = operation;
160
341
  return attributes;
161
342
  }
162
- var TRACER_NAME, OPENINFERENCE_SPAN_KIND, INPUT_VALUE, OUTPUT_VALUE, GEN_AI_OPERATION_NAME, GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_MODEL, GEN_AI_RESPONSE_MODEL, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_INPUT_MESSAGES, GEN_AI_OUTPUT_MESSAGES, GEN_AI_RESPONSE_FINISH_REASONS, GEN_AI_TOOL_NAME, GEN_AI_REQUEST_PREFIX, MCP_RESULT_TYPE, GEN_AI_FIRST_TOKEN_EVENT, SpanKind, OPERATION_BY_KIND, CONTENT_ATTRIBUTES, CONTENT_ATTRIBUTE_PREFIXES, CONTENT_ATTRIBUTE_SUFFIXES;
343
+ var TRACER_NAME, OPENINFERENCE_SPAN_KIND, INPUT_VALUE, OUTPUT_VALUE, GEN_AI_OPERATION_NAME, GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_MODEL, GEN_AI_RESPONSE_MODEL, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_INPUT_MESSAGES, GEN_AI_OUTPUT_MESSAGES, GEN_AI_RESPONSE_FINISH_REASONS, GEN_AI_TOOL_NAME, GEN_AI_REQUEST_PREFIX, MCP_RESULT_TYPE, GEN_AI_FIRST_TOKEN_EVENT, SpanKind, OPERATION_BY_KIND, CONTENT_ATTRIBUTES, CONTENT_ATTRIBUTE_PREFIXES, CONTENT_ATTRIBUTE_SUFFIXES, GLASSFLOW_SPAN_PENDING, PENDING_IDENTITY_ATTRIBUTES, PENDING_IDENTITY_PREFIXES;
163
344
  var init_semconv = __esm({
164
345
  "src/semconv.ts"() {
165
346
  "use strict";
347
+ init_esm_shims();
166
348
  TRACER_NAME = "glassflow";
167
349
  OPENINFERENCE_SPAN_KIND = "openinference.span.kind";
168
350
  INPUT_VALUE = "input.value";
@@ -227,6 +409,14 @@ var init_semconv = __esm({
227
409
  ".document.content",
228
410
  ".embedding.text"
229
411
  ];
412
+ GLASSFLOW_SPAN_PENDING = "glassflow.span.pending";
413
+ PENDING_IDENTITY_ATTRIBUTES = /* @__PURE__ */ new Set([
414
+ OPENINFERENCE_SPAN_KIND,
415
+ GEN_AI_OPERATION_NAME,
416
+ GEN_AI_PROVIDER_NAME,
417
+ GEN_AI_TOOL_NAME
418
+ ]);
419
+ PENDING_IDENTITY_PREFIXES = [GEN_AI_REQUEST_PREFIX];
230
420
  }
231
421
  });
232
422
 
@@ -255,6 +445,7 @@ function toAttributeValue(value) {
255
445
  var init_serde = __esm({
256
446
  "src/serde.ts"() {
257
447
  "use strict";
448
+ init_esm_shims();
258
449
  }
259
450
  });
260
451
 
@@ -292,6 +483,7 @@ var Observation;
292
483
  var init_spans = __esm({
293
484
  "src/spans.ts"() {
294
485
  "use strict";
486
+ init_esm_shims();
295
487
  init_client();
296
488
  init_semconv();
297
489
  init_serde();
@@ -384,6 +576,7 @@ var INPUT_REQUIRED;
384
576
  var init_instrumentationMcp = __esm({
385
577
  "src/instrumentationMcp.ts"() {
386
578
  "use strict";
579
+ init_esm_shims();
387
580
  init_semconv();
388
581
  init_spans();
389
582
  INPUT_REQUIRED = "input_required";
@@ -391,7 +584,7 @@ var init_instrumentationMcp = __esm({
391
584
  });
392
585
 
393
586
  // src/instrumentation.ts
394
- import { createRequire } from "module";
587
+ import { createRequire as createRequire2 } from "module";
395
588
  import { join, sep } from "path";
396
589
  import { registerInstrumentations } from "@opentelemetry/instrumentation";
397
590
  function packageOf(specifier) {
@@ -437,7 +630,7 @@ function warnBroken(subject, error) {
437
630
  function cachedCjsExports(pkg, toPatchable) {
438
631
  let cache;
439
632
  try {
440
- cache = createRequire(join(process.cwd(), "noop.js")).cache;
633
+ cache = createRequire2(join(process.cwd(), "noop.js")).cache;
441
634
  } catch {
442
635
  return void 0;
443
636
  }
@@ -506,6 +699,7 @@ var dynamicImport, REGISTRY;
506
699
  var init_instrumentation = __esm({
507
700
  "src/instrumentation.ts"() {
508
701
  "use strict";
702
+ init_esm_shims();
509
703
  dynamicImport = new Function("s", "return import(s)");
510
704
  REGISTRY = [
511
705
  {
@@ -600,6 +794,7 @@ var EXCEPTION_EVENT_NAME, EXCEPTION_CONTENT_KEYS, MaskingSpanExporter;
600
794
  var init_masking = __esm({
601
795
  "src/masking.ts"() {
602
796
  "use strict";
797
+ init_esm_shims();
603
798
  init_semconv();
604
799
  init_serde();
605
800
  EXCEPTION_EVENT_NAME = "exception";
@@ -661,6 +856,93 @@ var init_masking = __esm({
661
856
  }
662
857
  });
663
858
 
859
+ // src/pending.ts
860
+ import { SpanStatusCode as SpanStatusCode3 } from "@opentelemetry/api";
861
+ function identityAttributes(attributes) {
862
+ if (!attributes) return {};
863
+ const result = {};
864
+ for (const [key, value] of Object.entries(attributes)) {
865
+ if (PENDING_IDENTITY_ATTRIBUTES.has(key) || PENDING_IDENTITY_PREFIXES.some((p) => key.startsWith(p))) {
866
+ result[key] = value;
867
+ }
868
+ }
869
+ return result;
870
+ }
871
+ function buildSnapshot(span) {
872
+ const attributes = identityAttributes(span.attributes);
873
+ attributes[GLASSFLOW_SPAN_PENDING] = true;
874
+ return {
875
+ name: span.name,
876
+ kind: span.kind,
877
+ spanContext: () => span.spanContext(),
878
+ parentSpanContext: span.parentSpanContext,
879
+ startTime: span.startTime,
880
+ endTime: span.startTime,
881
+ duration: [0, 0],
882
+ status: { code: SpanStatusCode3.UNSET },
883
+ attributes,
884
+ links: [],
885
+ events: [],
886
+ resource: span.resource,
887
+ instrumentationScope: span.instrumentationScope,
888
+ droppedAttributesCount: 0,
889
+ droppedEventsCount: 0,
890
+ droppedLinksCount: 0,
891
+ ended: true
892
+ };
893
+ }
894
+ function spanKey(context) {
895
+ return `${context.traceId}:${context.spanId}`;
896
+ }
897
+ var PendingSpanProcessor;
898
+ var init_pending = __esm({
899
+ "src/pending.ts"() {
900
+ "use strict";
901
+ init_esm_shims();
902
+ init_semconv();
903
+ PendingSpanProcessor = class {
904
+ delegate;
905
+ delayMs;
906
+ scheduled = /* @__PURE__ */ new Map();
907
+ constructor(delegate, opts) {
908
+ this.delegate = delegate;
909
+ this.delayMs = opts?.delayMs ?? 0;
910
+ }
911
+ onStart(span, _parentContext) {
912
+ if (!span.isRecording()) return;
913
+ const snapshot = buildSnapshot(span);
914
+ if (this.delayMs <= 0) {
915
+ this.delegate.onEnd(snapshot);
916
+ return;
917
+ }
918
+ const key = spanKey(span.spanContext());
919
+ const timer = setTimeout(() => {
920
+ this.scheduled.delete(key);
921
+ try {
922
+ this.delegate.onEnd(snapshot);
923
+ } catch {
924
+ }
925
+ }, this.delayMs);
926
+ if (typeof timer.unref === "function") timer.unref();
927
+ this.scheduled.set(key, timer);
928
+ }
929
+ onEnd(span) {
930
+ const key = spanKey(span.spanContext());
931
+ const timer = this.scheduled.get(key);
932
+ if (timer === void 0) return;
933
+ clearTimeout(timer);
934
+ this.scheduled.delete(key);
935
+ }
936
+ async shutdown() {
937
+ for (const timer of this.scheduled.values()) clearTimeout(timer);
938
+ this.scheduled.clear();
939
+ }
940
+ async forceFlush() {
941
+ }
942
+ };
943
+ }
944
+ });
945
+
664
946
  // src/client.ts
665
947
  import { trace } from "@opentelemetry/api";
666
948
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
@@ -681,18 +963,23 @@ function init(options = {}) {
681
963
  }
682
964
  const config = resolveConfig(options);
683
965
  const processors = new DelegatingSpanProcessor();
966
+ const authHeaders = config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {};
684
967
  let health;
685
968
  if (!config.disabled) {
686
969
  const base = options.spanExporter ?? new OTLPTraceExporter({
687
970
  url: `${config.endpoint}/v1/traces`,
688
- headers: config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : void 0
971
+ headers: config.apiKey ? authHeaders : void 0
689
972
  });
690
973
  health = new ExportOutcomeExporter(base);
691
974
  const exporter = !config.captureContent || config.mask !== void 0 ? new MaskingSpanExporter(health, {
692
975
  captureContent: config.captureContent,
693
976
  mask: config.mask
694
977
  }) : health;
695
- processors.add(new BatchSpanProcessor(exporter));
978
+ const batch = new BatchSpanProcessor(exporter);
979
+ if (config.partialSpans) {
980
+ processors.add(new PendingSpanProcessor(batch, { delayMs: config.partialSpansDelayMs }));
981
+ }
982
+ processors.add(batch);
696
983
  }
697
984
  const provider = new NodeTracerProvider({
698
985
  resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: config.serviceName }),
@@ -706,23 +993,46 @@ function init(options = {}) {
706
993
  });
707
994
  const ready = config.disabled ? Promise.resolve([]) : enableInstrumentations(processors, provider).catch(() => []);
708
995
  provider.register();
709
- globalClient = createClient({ provider, processors, health, ready });
996
+ let heartbeat;
997
+ if (config.heartbeat && !config.disabled) {
998
+ const tracker = new OpenRootSpanTracker();
999
+ processors.add(tracker);
1000
+ const sender = new HeartbeatSender({
1001
+ url: config.heartbeatEndpoint,
1002
+ headers: authHeaders,
1003
+ intervalMs: config.heartbeatIntervalMs,
1004
+ agentName: config.agentName,
1005
+ tracker,
1006
+ transport: options.heartbeatTransport
1007
+ });
1008
+ sender.start();
1009
+ const beforeExitHandler = () => {
1010
+ void sender.stop();
1011
+ };
1012
+ process.once("beforeExit", beforeExitHandler);
1013
+ heartbeat = { sender, beforeExitHandler };
1014
+ }
1015
+ globalClient = createClient({ provider, processors, health, ready, heartbeat });
710
1016
  return globalClient;
711
1017
  }
712
1018
  function getTracer() {
713
1019
  return trace.getTracer(TRACER_NAME);
714
1020
  }
715
- var sinks, createClient, RiusClient, globalClient;
1021
+ var sinks, heartbeats, createClient, RiusClient, globalClient;
716
1022
  var init_client = __esm({
717
1023
  "src/client.ts"() {
718
1024
  "use strict";
1025
+ init_esm_shims();
719
1026
  init_config();
720
1027
  init_delegatingProcessor();
721
1028
  init_exportHealth();
1029
+ init_heartbeat();
722
1030
  init_instrumentation();
723
1031
  init_masking();
1032
+ init_pending();
724
1033
  init_semconv();
725
1034
  sinks = /* @__PURE__ */ new WeakMap();
1035
+ heartbeats = /* @__PURE__ */ new WeakMap();
726
1036
  RiusClient = class _RiusClient {
727
1037
  provider;
728
1038
  health;
@@ -733,6 +1043,7 @@ var init_client = __esm({
733
1043
  this.health = parts.health;
734
1044
  this.ready = parts.ready;
735
1045
  sinks.set(this, parts.processors);
1046
+ if (parts.heartbeat) heartbeats.set(this, parts.heartbeat);
736
1047
  }
737
1048
  static {
738
1049
  createClient = (parts) => new _RiusClient(parts);
@@ -745,8 +1056,19 @@ var init_client = __esm({
745
1056
  /**
746
1057
  * Drains and tears down the provider, then releases the global registration
747
1058
  * so a later init() can reconfigure the SDK.
1059
+ *
1060
+ * The heartbeat's final `stopped: true` ping is sent before the provider
1061
+ * shuts down, so the backend hears "stopped" while the trace pipeline can
1062
+ * still export it. Idempotent: `sender.stop()` no-ops on a second call, and
1063
+ * the `beforeExit` listener is removed here so repeated init/shutdown
1064
+ * cycles never leak listeners.
748
1065
  */
749
1066
  async shutdown() {
1067
+ const heartbeat = heartbeats.get(this);
1068
+ if (heartbeat) {
1069
+ process.removeListener("beforeExit", heartbeat.beforeExitHandler);
1070
+ await heartbeat.sender.stop();
1071
+ }
750
1072
  try {
751
1073
  await this.provider.shutdown();
752
1074
  } finally {
@@ -761,9 +1083,11 @@ var init_client = __esm({
761
1083
  });
762
1084
 
763
1085
  // src/index.ts
1086
+ init_esm_shims();
764
1087
  init_client();
765
1088
 
766
1089
  // src/generation.ts
1090
+ init_esm_shims();
767
1091
  init_client();
768
1092
  init_semconv();
769
1093
  init_serde();
@@ -851,6 +1175,7 @@ function startAsCurrentGeneration(name, optionsOrFn, maybeFn) {
851
1175
  }
852
1176
 
853
1177
  // src/observe.ts
1178
+ init_esm_shims();
854
1179
  init_spans();
855
1180
  function observe(fn, options = {}) {
856
1181
  const name = options.name ?? (fn.name || "anonymous");
@@ -872,7 +1197,7 @@ function observe(fn, options = {}) {
872
1197
  // src/index.ts
873
1198
  init_semconv();
874
1199
  init_spans();
875
- var VERSION = "0.1.0";
1200
+ var VERSION = "0.2.2";
876
1201
  export {
877
1202
  Generation,
878
1203
  Observation,
package/package.json CHANGED
@@ -1,8 +1,26 @@
1
1
  {
2
2
  "name": "@glassflow-ai/rius",
3
- "version": "0.1.0",
3
+ "version": "0.2.2",
4
4
  "description": "OpenTelemetry-native tracing for AI agents and LLM applications",
5
+ "keywords": [
6
+ "opentelemetry",
7
+ "otel",
8
+ "observability",
9
+ "tracing",
10
+ "llm",
11
+ "ai-agents",
12
+ "genai",
13
+ "instrumentation",
14
+ "rius",
15
+ "glassflow"
16
+ ],
5
17
  "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/glassflow/rius-sdk-typescript.git"
21
+ },
22
+ "homepage": "https://docs.glassflow.ai/rius/sdk",
23
+ "bugs": "https://github.com/glassflow/rius-sdk-typescript/issues",
6
24
  "type": "module",
7
25
  "engines": {
8
26
  "node": ">=18"
@@ -22,7 +40,9 @@
22
40
  }
23
41
  }
24
42
  },
25
- "files": ["dist"],
43
+ "files": [
44
+ "dist"
45
+ ],
26
46
  "scripts": {
27
47
  "build": "tsup",
28
48
  "test": "vitest run",