@glassflow-ai/rius 0.5.0 → 0.6.1

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.cjs CHANGED
@@ -53,7 +53,11 @@ function seconds(raw, fallback) {
53
53
  if (Number.isNaN(parsed)) return fallback;
54
54
  return parsed;
55
55
  }
56
- function clamp(name, value, min, max) {
56
+ function clamp(name, value, min, max, fallback) {
57
+ if (!Number.isFinite(value)) {
58
+ console.warn(`[rius] ${name}=${value} is not a finite number; using ${fallback}.`);
59
+ return fallback;
60
+ }
57
61
  if (value >= min && value <= max) return value;
58
62
  const clamped = Math.min(Math.max(value, min), max);
59
63
  console.warn(`[rius] ${name}=${value} is outside [${min}, ${max}]; clamped to ${clamped}.`);
@@ -66,13 +70,15 @@ function resolveConfig(options = {}, env = process.env) {
66
70
  "heartbeatInterval",
67
71
  options.heartbeatInterval ?? seconds(env.RIUS_HEARTBEAT_INTERVAL, DEFAULT_HEARTBEAT_INTERVAL),
68
72
  HEARTBEAT_INTERVAL_MIN,
69
- HEARTBEAT_INTERVAL_MAX
73
+ HEARTBEAT_INTERVAL_MAX,
74
+ DEFAULT_HEARTBEAT_INTERVAL
70
75
  );
71
76
  const partialSpansDelaySeconds = clamp(
72
77
  "partialSpansDelay",
73
78
  options.partialSpansDelay ?? seconds(env.RIUS_PARTIAL_SPANS_DELAY, DEFAULT_PARTIAL_SPANS_DELAY),
74
79
  PARTIAL_SPANS_DELAY_MIN,
75
- PARTIAL_SPANS_DELAY_MAX
80
+ PARTIAL_SPANS_DELAY_MAX,
81
+ DEFAULT_PARTIAL_SPANS_DELAY
76
82
  );
77
83
  return {
78
84
  endpoint,
@@ -221,6 +227,24 @@ var init_exportHealth = __esm({
221
227
  }
222
228
  });
223
229
 
230
+ // src/version.ts
231
+ var import_node_module, SDK_VERSION;
232
+ var init_version = __esm({
233
+ "src/version.ts"() {
234
+ "use strict";
235
+ init_cjs_shims();
236
+ import_node_module = require("module");
237
+ SDK_VERSION = (() => {
238
+ try {
239
+ const pkg = (0, import_node_module.createRequire)(importMetaUrl)("../package.json");
240
+ return pkg.version ?? "0.0.0";
241
+ } catch {
242
+ return "0.0.0";
243
+ }
244
+ })();
245
+ }
246
+ });
247
+
224
248
  // src/heartbeat.ts
225
249
  function httpTransport(url, headers) {
226
250
  return async (payload, timeoutMs) => {
@@ -237,24 +261,16 @@ function httpTransport(url, headers) {
237
261
  }
238
262
  };
239
263
  }
240
- var import_node_module, PAYLOAD_VERSION, OPEN_TRACES_CAP, DEFAULT_PING_TIMEOUT_MS, DEFAULT_FINAL_PING_TIMEOUT_MS, SDK_VERSION, OpenRootSpanTracker, HeartbeatSender;
264
+ var PAYLOAD_VERSION, OPEN_TRACES_CAP, DEFAULT_PING_TIMEOUT_MS, DEFAULT_FINAL_PING_TIMEOUT_MS, OpenRootSpanTracker, HeartbeatSender;
241
265
  var init_heartbeat = __esm({
242
266
  "src/heartbeat.ts"() {
243
267
  "use strict";
244
268
  init_cjs_shims();
245
- import_node_module = require("module");
269
+ init_version();
246
270
  PAYLOAD_VERSION = 1;
247
271
  OPEN_TRACES_CAP = 32;
248
272
  DEFAULT_PING_TIMEOUT_MS = 3e3;
249
273
  DEFAULT_FINAL_PING_TIMEOUT_MS = 1e3;
250
- SDK_VERSION = (() => {
251
- try {
252
- const pkg = (0, import_node_module.createRequire)(importMetaUrl)("../package.json");
253
- return pkg.version ?? "0.0.0";
254
- } catch {
255
- return "0.0.0";
256
- }
257
- })();
258
274
  OpenRootSpanTracker = class {
259
275
  // trace id (32-hex) -> count of open root spans in that trace. A trace id
260
276
  // normally has one root, but the count guards against duplicates.
@@ -306,6 +322,7 @@ var init_heartbeat = __esm({
306
322
  }
307
323
  /** Starts pinging: an immediate first ping, then every `intervalMs`. */
308
324
  start() {
325
+ if (this.timer !== void 0) return;
309
326
  void this.ping(this.pingTimeoutMs, false);
310
327
  const timer = setInterval(() => void this.ping(this.pingTimeoutMs, false), this.intervalMs);
311
328
  if (typeof timer.unref === "function") timer.unref();
@@ -356,7 +373,7 @@ function kindAttributes(kind) {
356
373
  if (operation !== void 0) attributes[GEN_AI_OPERATION_NAME] = operation;
357
374
  return attributes;
358
375
  }
359
- var TRACER_NAME, SERVICE_INSTANCE_ID, OPENINFERENCE_SPAN_KIND, INPUT_VALUE, OUTPUT_VALUE, SESSION_ID, WORKSPACE_ROUTE, GEN_AI_OPERATION_NAME, GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_MODEL, GEN_AI_REQUEST_REASONING_LEVEL, GEN_AI_RESPONSE_MODEL, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS, GEN_AI_USAGE_REASONING_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;
376
+ var TRACER_NAME, SERVICE_INSTANCE_ID, OPENINFERENCE_SPAN_KIND, INPUT_VALUE, OUTPUT_VALUE, SESSION_ID, USER_ID, WORKSPACE_ROUTE, GEN_AI_OPERATION_NAME, GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_MODEL, GEN_AI_REQUEST_REASONING_LEVEL, GEN_AI_RESPONSE_MODEL, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS, GEN_AI_USAGE_REASONING_OUTPUT_TOKENS, GEN_AI_INPUT_MESSAGES, GEN_AI_OUTPUT_MESSAGES, GEN_AI_RESPONSE_FINISH_REASONS, GEN_AI_TOOL_NAME, GEN_AI_TOOL_DEFINITIONS, GEN_AI_REQUEST_PREFIX, MCP_RESULT_TYPE, GEN_AI_FIRST_TOKEN_EVENT, SpanKind, OPERATION_BY_KIND, CONTENT_ATTRIBUTES, LLM_INVOCATION_PARAMETERS, INVOCATION_PARAMETERS_CONTENT_MEMBERS, CONTENT_ATTRIBUTE_PREFIXES, CONTENT_ATTRIBUTE_SUFFIXES, GLASSFLOW_SPAN_PENDING, PENDING_IDENTITY_ATTRIBUTES, PENDING_IDENTITY_PREFIXES;
360
377
  var init_semconv = __esm({
361
378
  "src/semconv.ts"() {
362
379
  "use strict";
@@ -367,6 +384,7 @@ var init_semconv = __esm({
367
384
  INPUT_VALUE = "input.value";
368
385
  OUTPUT_VALUE = "output.value";
369
386
  SESSION_ID = "session.id";
387
+ USER_ID = "user.id";
370
388
  WORKSPACE_ROUTE = "rius.workspace";
371
389
  GEN_AI_OPERATION_NAME = "gen_ai.operation.name";
372
390
  GEN_AI_PROVIDER_NAME = "gen_ai.provider.name";
@@ -382,6 +400,7 @@ var init_semconv = __esm({
382
400
  GEN_AI_OUTPUT_MESSAGES = "gen_ai.output.messages";
383
401
  GEN_AI_RESPONSE_FINISH_REASONS = "gen_ai.response.finish_reasons";
384
402
  GEN_AI_TOOL_NAME = "gen_ai.tool.name";
403
+ GEN_AI_TOOL_DEFINITIONS = "gen_ai.tool.definitions";
385
404
  GEN_AI_REQUEST_PREFIX = "gen_ai.request.";
386
405
  MCP_RESULT_TYPE = "mcp.result_type";
387
406
  GEN_AI_FIRST_TOKEN_EVENT = "gen_ai.first_token";
@@ -410,7 +429,18 @@ var init_semconv = __esm({
410
429
  // or internal URLs in parameter defaults. gen_ai.tool.name stays: it is
411
430
  // identity, not content.
412
431
  "gen_ai.tool.description",
413
- "gen_ai.tool.definitions",
432
+ GEN_AI_TOOL_DEFINITIONS,
433
+ // GenAI semconv keys emitted by OTel-native instrumentations (@ai-sdk/otel,
434
+ // which init() registers for ai v7, among them; pinned 2026-09-14): the
435
+ // system prompt and each tool call's input and output are content in the
436
+ // same sense messages are. gen_ai.tool.call.id stays: identity.
437
+ "gen_ai.system_instructions",
438
+ "gen_ai.tool.call.arguments",
439
+ "gen_ai.tool.call.result",
440
+ // OpenInference TOOL-kind spans carry the definition under these two bare
441
+ // keys, sensitive for the reason gen_ai.tool.description is.
442
+ "tool.description",
443
+ "tool.parameters",
414
444
  "gen_ai.prompt",
415
445
  "gen_ai.completion",
416
446
  "llm.input_messages",
@@ -424,15 +454,44 @@ var init_semconv = __esm({
424
454
  "mlflow.spanInputs",
425
455
  "mlflow.spanOutputs",
426
456
  "traceloop.entity.input",
427
- "traceloop.entity.output"
457
+ "traceloop.entity.output",
458
+ // Every bundled OpenInference instrumentation emits tool definitions as
459
+ // llm.tools.{i}.tool.json_schema (pinned empirically 2026-09-14); covered
460
+ // by prefix below, bare key listed per the bare-key rule.
461
+ "llm.tools",
462
+ // The Vercel AI SDK's own telemetry keys survive the OpenInference
463
+ // transform untouched, and they carry the full prompt (messages AND tool
464
+ // definitions), response content, and tool-call I/O. Names, ids and models
465
+ // (ai.toolCall.name, ai.response.model, ...) are identity and stay.
466
+ "ai.prompt",
467
+ "ai.response.text",
468
+ "ai.response.object",
469
+ "ai.toolCall.args",
470
+ "ai.toolCall.result",
471
+ // Same family, keys enumerated from the ai v7 telemetry surface
472
+ // (2026-09-14): model reasoning, tool calls in the response, response
473
+ // files, embedding inputs, rerank documents, and the generateObject schema
474
+ // (a tool definition by another name).
475
+ "ai.response.reasoning",
476
+ "ai.response.toolCalls",
477
+ "ai.response.files",
478
+ "ai.value",
479
+ "ai.values",
480
+ "ai.documents",
481
+ "ai.schema",
482
+ "ai.schema.description"
428
483
  ]);
484
+ LLM_INVOCATION_PARAMETERS = "llm.invocation_parameters";
485
+ INVOCATION_PARAMETERS_CONTENT_MEMBERS = ["tools", "functions"];
429
486
  CONTENT_ATTRIBUTE_PREFIXES = [
430
487
  "llm.input_messages.",
431
488
  "llm.output_messages.",
432
489
  "gen_ai.prompt.",
433
490
  "gen_ai.completion.",
434
491
  "llm.prompts.",
435
- "llm.prompt_template."
492
+ "llm.prompt_template.",
493
+ "llm.tools.",
494
+ "ai.prompt."
436
495
  ];
437
496
  CONTENT_ATTRIBUTE_SUFFIXES = [
438
497
  ".document.content",
@@ -447,6 +506,9 @@ var init_semconv = __esm({
447
506
  // Identity, not content: a pending span must be groupable into its
448
507
  // session while still running, that is the live view's whole point.
449
508
  SESSION_ID,
509
+ // Same reason: a crashed run's partial spans must still be attributable
510
+ // to the user they served.
511
+ USER_ID,
450
512
  // Routing, not content: a crashed run's snapshot must land in the same
451
513
  // workspace its final span would have. Stripped at export either way.
452
514
  WORKSPACE_ROUTE
@@ -456,31 +518,77 @@ var init_semconv = __esm({
456
518
  });
457
519
 
458
520
  // src/serde.ts
459
- function toAttributeValue(value) {
460
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
461
- return value;
521
+ function truncate(text) {
522
+ return text.length > MAX_ATTR_CHARS ? text.slice(0, MAX_ATTR_CHARS) + TRUNCATION_MARKER : text;
523
+ }
524
+ function renderable(raw, value) {
525
+ if (typeof raw === "bigint") return String(raw);
526
+ if (raw instanceof Map) return Object.fromEntries(raw);
527
+ if (raw instanceof Set) return [...raw];
528
+ if (ArrayBuffer.isView(raw)) {
529
+ return `<${raw.constructor.name} ${raw.byteLength} bytes>`;
462
530
  }
531
+ if (raw instanceof ArrayBuffer) return `<ArrayBuffer ${raw.byteLength} bytes>`;
532
+ return value;
533
+ }
534
+ function toAttributeValue(value) {
535
+ if (typeof value === "string") return truncate(value);
536
+ if (typeof value === "number" || typeof value === "boolean") return value;
463
537
  if (value === null || value === void 0) return "";
538
+ if (typeof value === "bigint") return String(value);
464
539
  const ancestors = [];
465
540
  try {
466
- return JSON.stringify(value, function(_key, v) {
467
- if (typeof v === "object" && v !== null) {
541
+ const text = JSON.stringify({ "": value }[""], function(key, v) {
542
+ const raw = this?.[key];
543
+ const rendered = renderable(raw, v);
544
+ if (typeof rendered === "object" && rendered !== null) {
468
545
  while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
469
546
  ancestors.pop();
470
547
  }
471
- if (ancestors.includes(v)) return "[Circular]";
472
- ancestors.push(v);
548
+ if (ancestors.includes(rendered)) return "[Circular]";
549
+ ancestors.push(rendered);
473
550
  }
474
- return v;
551
+ return rendered;
475
552
  }) ?? String(value);
553
+ return truncate(text);
476
554
  } catch {
477
555
  return "[unserializable]";
478
556
  }
479
557
  }
558
+ var MAX_ATTR_CHARS, TRUNCATION_MARKER;
480
559
  var init_serde = __esm({
481
560
  "src/serde.ts"() {
482
561
  "use strict";
483
562
  init_cjs_shims();
563
+ MAX_ATTR_CHARS = 32 * 1024;
564
+ TRUNCATION_MARKER = "\u2026(truncated)";
565
+ }
566
+ });
567
+
568
+ // src/user.ts
569
+ function withUser(userId, fn) {
570
+ return import_api.context.with(import_api.context.active().setValue(USER_KEY, userId), () => fn(userId));
571
+ }
572
+ var import_api, USER_KEY, UserSpanProcessor;
573
+ var init_user = __esm({
574
+ "src/user.ts"() {
575
+ "use strict";
576
+ init_cjs_shims();
577
+ import_api = require("@opentelemetry/api");
578
+ init_semconv();
579
+ USER_KEY = (0, import_api.createContextKey)("rius-user-id");
580
+ UserSpanProcessor = class {
581
+ onStart(span, parentContext) {
582
+ const value = parentContext.getValue(USER_KEY);
583
+ if (typeof value === "string") span.setAttribute(USER_ID, value);
584
+ }
585
+ onEnd(_span) {
586
+ }
587
+ async forceFlush() {
588
+ }
589
+ async shutdown() {
590
+ }
591
+ };
484
592
  }
485
593
  });
486
594
 
@@ -489,39 +597,50 @@ function configure(observation, options) {
489
597
  if (options.input !== void 0) observation.setInput(options.input);
490
598
  return observation;
491
599
  }
600
+ function creationAttributes(options) {
601
+ const attributes = { ...kindAttributes(options.kind ?? "CHAIN" /* CHAIN */), ...options.attributes };
602
+ if (options.userId !== void 0) attributes[USER_ID] = options.userId;
603
+ return attributes;
604
+ }
492
605
  function startSpan(name, options = {}) {
493
- const span = getTracer().startSpan(name, {
494
- attributes: kindAttributes(options.kind ?? "CHAIN" /* CHAIN */)
495
- });
606
+ const span = getTracer().startSpan(name, { attributes: creationAttributes(options) });
496
607
  return configure(new Observation(span), options);
497
608
  }
498
609
  function startAsCurrentSpan(name, optionsOrFn, maybeFn) {
499
610
  const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
500
- return getTracer().startActiveSpan(
611
+ return runActive(
501
612
  name,
502
- { attributes: kindAttributes(options.kind ?? "CHAIN" /* CHAIN */) },
503
- async (span) => {
504
- const observation = configure(new Observation(span), options);
505
- try {
506
- return await fn(observation);
507
- } catch (error) {
508
- observation.recordException(error);
509
- throw error;
510
- } finally {
511
- observation.end();
512
- }
513
- }
613
+ creationAttributes(options),
614
+ options.userId,
615
+ (span) => configure(new Observation(span), options),
616
+ fn
514
617
  );
515
618
  }
516
- var import_api, Observation;
619
+ function runActive(name, attributes, userId, makeHandle, fn) {
620
+ const run = () => getTracer().startActiveSpan(name, { attributes }, async (span) => {
621
+ const handle = makeHandle(span);
622
+ try {
623
+ return await fn(handle);
624
+ } catch (error) {
625
+ handle.recordException(error);
626
+ throw error;
627
+ } finally {
628
+ handle.end();
629
+ }
630
+ });
631
+ return userId !== void 0 ? withUser(userId, run) : run();
632
+ }
633
+ var import_api2, Observation;
517
634
  var init_spans = __esm({
518
635
  "src/spans.ts"() {
519
636
  "use strict";
520
637
  init_cjs_shims();
521
- import_api = require("@opentelemetry/api");
638
+ import_api2 = require("@opentelemetry/api");
522
639
  init_client();
523
640
  init_semconv();
524
641
  init_serde();
642
+ init_user();
643
+ Symbol.dispose ??= /* @__PURE__ */ Symbol.for("Symbol.dispose");
525
644
  Observation = class {
526
645
  constructor(span) {
527
646
  this.span = span;
@@ -536,7 +655,26 @@ var init_spans = __esm({
536
655
  this.span.setAttribute(OUTPUT_VALUE, toAttributeValue(value));
537
656
  return this;
538
657
  }
658
+ /**
659
+ * Set an arbitrary attribute. Primitives and homogeneous primitive arrays
660
+ * are passed through as the OTel values they are; objects are JSON-encoded
661
+ * and bounded; `undefined` and `null` set nothing, since "no value" is not
662
+ * an empty string.
663
+ */
539
664
  setAttribute(key, value) {
665
+ if (value === void 0 || value === null) return this;
666
+ if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
667
+ this.span.setAttribute(key, value);
668
+ return this;
669
+ }
670
+ if (Array.isArray(value) && value.every((v) => typeof v === "number")) {
671
+ this.span.setAttribute(key, value);
672
+ return this;
673
+ }
674
+ if (Array.isArray(value) && value.every((v) => typeof v === "boolean")) {
675
+ this.span.setAttribute(key, value);
676
+ return this;
677
+ }
540
678
  this.span.setAttribute(key, toAttributeValue(value));
541
679
  return this;
542
680
  }
@@ -551,7 +689,7 @@ var init_spans = __esm({
551
689
  recordException(error) {
552
690
  const wrapped = error instanceof Error ? error : new Error(String(error));
553
691
  this.span.recordException(wrapped);
554
- this.span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: wrapped.message });
692
+ this.span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message: wrapped.message });
555
693
  return this;
556
694
  }
557
695
  end() {
@@ -572,6 +710,21 @@ var instrumentationMcp_exports = {};
572
710
  __export(instrumentationMcp_exports, {
573
711
  instrumentMcpClient: () => instrumentMcpClient
574
712
  });
713
+ function serializeResult(result) {
714
+ if (typeof result !== "object" || result === null) return toAttributeValue(result);
715
+ const record = result;
716
+ const structured = record.structured_content ?? record.structuredContent;
717
+ if (structured !== void 0 && structured !== null) return toAttributeValue(structured);
718
+ const content = record.content;
719
+ if (Array.isArray(content)) {
720
+ const texts = content.map(
721
+ (block) => typeof block === "object" && block !== null ? block.text : void 0
722
+ ).filter((text) => typeof text === "string");
723
+ if (texts.length === 1) return truncate(texts[0]);
724
+ if (texts.length > 1) return toAttributeValue(texts);
725
+ }
726
+ return toAttributeValue(result);
727
+ }
575
728
  function resultIsError(result) {
576
729
  if (typeof result !== "object" || result === null) return false;
577
730
  const record = result;
@@ -583,10 +736,10 @@ function recordResult(observation, result) {
583
736
  observation.setAttribute(MCP_RESULT_TYPE, INPUT_REQUIRED);
584
737
  return;
585
738
  }
586
- observation.setOutput(result);
739
+ observation.setAttribute(OUTPUT_VALUE, serializeResult(result));
587
740
  if (resultIsError(result)) {
588
741
  observation.span.setStatus({
589
- code: import_api2.SpanStatusCode.ERROR,
742
+ code: import_api3.SpanStatusCode.ERROR,
590
743
  message: "tool returned an error result"
591
744
  });
592
745
  }
@@ -603,9 +756,14 @@ function instrumentMcpClient(ClientClass) {
603
756
  const instrumented = function instrumentedCallTool(params, ...rest) {
604
757
  return startAsCurrentSpan(
605
758
  `execute_tool ${params.name}`,
606
- { kind: "TOOL" /* TOOL */, input: params.arguments },
759
+ // Tool name at CREATION: pending snapshots are built at start, so an
760
+ // attribute set inside the callback never reaches them.
761
+ {
762
+ kind: "TOOL" /* TOOL */,
763
+ input: params.arguments,
764
+ attributes: { [GEN_AI_TOOL_NAME]: params.name }
765
+ },
607
766
  async (observation) => {
608
- observation.setAttribute(GEN_AI_TOOL_NAME, params.name);
609
767
  const result = await original.call(this, params, ...rest);
610
768
  recordResult(observation, result);
611
769
  return result;
@@ -618,13 +776,14 @@ function instrumentMcpClient(ClientClass) {
618
776
  ClientClass.prototype.callTool = original;
619
777
  };
620
778
  }
621
- var import_api2, INPUT_REQUIRED;
779
+ var import_api3, INPUT_REQUIRED;
622
780
  var init_instrumentationMcp = __esm({
623
781
  "src/instrumentationMcp.ts"() {
624
782
  "use strict";
625
783
  init_cjs_shims();
626
- import_api2 = require("@opentelemetry/api");
784
+ import_api3 = require("@opentelemetry/api");
627
785
  init_semconv();
786
+ init_serde();
628
787
  init_spans();
629
788
  INPUT_REQUIRED = "input_required";
630
789
  }
@@ -716,21 +875,55 @@ function mcpClientPatchable(exports2) {
716
875
  const cls = exports2.Client;
717
876
  return typeof cls?.prototype?.callTool === "function" ? { Client: cls } : void 0;
718
877
  }
719
- async function enableInstrumentations(sink, tracerProvider, names) {
878
+ async function registerVercelTelemetry(tracerProvider, teardown) {
879
+ const ai = await optional("ai");
880
+ const register = ai?.registerTelemetry;
881
+ if (register === void 0) return false;
882
+ const otel = await optional("@ai-sdk/otel");
883
+ const OpenTelemetryIntegration = otel?.OpenTelemetry;
884
+ if (OpenTelemetryIntegration === void 0) {
885
+ console.warn(
886
+ "[rius] Vercel AI SDK v7+ found, but its spans require the @ai-sdk/otel package, which is not installed \u2014 `ai` calls will not be traced. Install it (npm i @ai-sdk/otel) and rius registers it automatically."
887
+ );
888
+ return false;
889
+ }
890
+ const bag = globalThis;
891
+ if (vercelTelemetryIntegration !== void 0 && bag.AI_SDK_TELEMETRY_INTEGRATIONS) {
892
+ bag.AI_SDK_TELEMETRY_INTEGRATIONS = bag.AI_SDK_TELEMETRY_INTEGRATIONS.filter(
893
+ (integration2) => integration2 !== vercelTelemetryIntegration
894
+ );
895
+ }
896
+ const integration = new OpenTelemetryIntegration({
897
+ tracer: tracerProvider.getTracer(TRACER_NAME, SDK_VERSION)
898
+ });
899
+ vercelTelemetryIntegration = integration;
900
+ register(integration);
901
+ teardown?.push(() => {
902
+ if (bag.AI_SDK_TELEMETRY_INTEGRATIONS) {
903
+ bag.AI_SDK_TELEMETRY_INTEGRATIONS = bag.AI_SDK_TELEMETRY_INTEGRATIONS.filter(
904
+ (candidate) => candidate !== integration
905
+ );
906
+ }
907
+ if (vercelTelemetryIntegration === integration) vercelTelemetryIntegration = void 0;
908
+ });
909
+ return true;
910
+ }
911
+ async function enableInstrumentations(sink, tracerProvider, names, teardown) {
720
912
  const wanted = names ? REGISTRY.filter((e) => names.includes(e.name)) : REGISTRY;
721
913
  const enabled = [];
722
914
  for (const entry of wanted) {
723
915
  try {
724
- const loaded = await entry.load();
916
+ const loaded = await entry.load(tracerProvider, teardown);
725
917
  if (loaded === void 0) continue;
726
918
  if (entry.kind === "processor") {
727
919
  if (entry.insert === "first") sink.addFirst(loaded);
728
920
  else sink.add(loaded);
729
921
  } else if (entry.kind === "instrumentation") {
730
- (0, import_instrumentation.registerInstrumentations)({
922
+ const disable = (0, import_instrumentation.registerInstrumentations)({
731
923
  instrumentations: [loaded],
732
924
  tracerProvider
733
925
  });
926
+ teardown?.push(disable);
734
927
  }
735
928
  enabled.push(entry.name);
736
929
  } catch (error) {
@@ -739,7 +932,7 @@ async function enableInstrumentations(sink, tracerProvider, names) {
739
932
  }
740
933
  return enabled;
741
934
  }
742
- var import_node_module2, import_node_path, import_instrumentation, dynamicImport, REGISTRY;
935
+ var import_node_module2, import_node_path, import_instrumentation, dynamicImport, vercelTelemetryIntegration, REGISTRY;
743
936
  var init_instrumentation = __esm({
744
937
  "src/instrumentation.ts"() {
745
938
  "use strict";
@@ -747,6 +940,8 @@ var init_instrumentation = __esm({
747
940
  import_node_module2 = require("module");
748
941
  import_node_path = require("path");
749
942
  import_instrumentation = require("@opentelemetry/instrumentation");
943
+ init_semconv();
944
+ init_version();
750
945
  dynamicImport = new Function("s", "return import(s)");
751
946
  REGISTRY = [
752
947
  {
@@ -757,15 +952,16 @@ var init_instrumentation = __esm({
757
952
  // transform has to run before the exporting processor sees the span or the
758
953
  // attributes it added would never be sanitised.
759
954
  insert: "first",
760
- async load() {
955
+ async load(tracerProvider, teardown) {
956
+ const registered = tracerProvider === void 0 ? false : await registerVercelTelemetry(tracerProvider, teardown);
761
957
  const utils = await optional("@arizeai/openinference-vercel/utils");
762
958
  const add = utils?.addOpenInferenceAttributesToSpan;
763
- if (add === void 0) return void 0;
959
+ if (add === void 0 && !registered) return void 0;
764
960
  return {
765
961
  onStart() {
766
962
  },
767
963
  onEnd(span) {
768
- add(span);
964
+ add?.(span);
769
965
  },
770
966
  async forceFlush() {
771
967
  },
@@ -815,14 +1011,18 @@ var init_instrumentation = __esm({
815
1011
  {
816
1012
  name: "mcp",
817
1013
  kind: "self-applying",
818
- async load() {
1014
+ async load(_tracerProvider, teardown) {
819
1015
  const mod = await optional("@modelcontextprotocol/sdk/client/index.js");
820
1016
  const ClientClass = mod?.Client;
821
1017
  if (ClientClass === void 0) return void 0;
822
1018
  const { instrumentMcpClient: instrumentMcpClient2 } = await Promise.resolve().then(() => (init_instrumentationMcp(), instrumentationMcp_exports));
823
- instrumentMcpClient2(ClientClass);
1019
+ const uninstallEsm = instrumentMcpClient2(ClientClass);
1020
+ teardown?.push(uninstallEsm);
824
1021
  const cjs = cachedCjsExports("@modelcontextprotocol/sdk", mcpClientPatchable);
825
- if (cjs !== void 0) instrumentMcpClient2(cjs.Client);
1022
+ if (cjs !== void 0) {
1023
+ const uninstallCjs = instrumentMcpClient2(cjs.Client);
1024
+ teardown?.push(uninstallCjs);
1025
+ }
826
1026
  return true;
827
1027
  }
828
1028
  }
@@ -834,18 +1034,39 @@ var init_instrumentation = __esm({
834
1034
  function isContentKey(key) {
835
1035
  if (CONTENT_ATTRIBUTES.has(key)) return true;
836
1036
  if (CONTENT_ATTRIBUTE_PREFIXES.some((p) => key.startsWith(p))) return true;
837
- return CONTENT_ATTRIBUTE_SUFFIXES.some((s) => key.endsWith(s));
1037
+ if (CONTENT_ATTRIBUTE_SUFFIXES.some((s) => key.endsWith(s))) return true;
1038
+ return key.startsWith(METADATA_PREFIX) && isContentKey(key.slice(METADATA_PREFIX.length));
1039
+ }
1040
+ function redactInvocationParameters(value) {
1041
+ if (typeof value !== "string") return void 0;
1042
+ let parameters;
1043
+ try {
1044
+ parameters = JSON.parse(value);
1045
+ } catch {
1046
+ return void 0;
1047
+ }
1048
+ if (parameters === null || typeof parameters !== "object" || Array.isArray(parameters)) {
1049
+ return void 0;
1050
+ }
1051
+ const bag = parameters;
1052
+ if (!INVOCATION_PARAMETERS_CONTENT_MEMBERS.some((member) => member in bag)) {
1053
+ return value;
1054
+ }
1055
+ for (const member of INVOCATION_PARAMETERS_CONTENT_MEMBERS) delete bag[member];
1056
+ return JSON.stringify(bag);
838
1057
  }
839
- var import_api3, EXCEPTION_EVENT_NAME, EXCEPTION_CONTENT_KEYS, MaskingSpanExporter;
1058
+ var import_api4, METADATA_PREFIX, EXCEPTION_EVENT_NAME, EXCEPTION_CONTENT_KEYS, STATUS_DESCRIPTION_KEY, MaskingSpanExporter;
840
1059
  var init_masking = __esm({
841
1060
  "src/masking.ts"() {
842
1061
  "use strict";
843
1062
  init_cjs_shims();
844
- import_api3 = require("@opentelemetry/api");
1063
+ import_api4 = require("@opentelemetry/api");
845
1064
  init_semconv();
846
1065
  init_serde();
1066
+ METADATA_PREFIX = "metadata.";
847
1067
  EXCEPTION_EVENT_NAME = "exception";
848
1068
  EXCEPTION_CONTENT_KEYS = ["exception.message", "exception.stacktrace"];
1069
+ STATUS_DESCRIPTION_KEY = "status.description";
849
1070
  MaskingSpanExporter = class {
850
1071
  constructor(inner, opts) {
851
1072
  this.inner = inner;
@@ -871,14 +1092,42 @@ var init_masking = __esm({
871
1092
  for (const link of span.links ?? []) {
872
1093
  this.sanitizeAttributes(link.attributes);
873
1094
  }
874
- if (!this.opts.captureContent && span.status?.code === import_api3.SpanStatusCode.ERROR) {
875
- span.status.message = void 0;
876
- }
1095
+ this.sanitizeStatus(span);
877
1096
  return span;
878
1097
  }
1098
+ /**
1099
+ * Drops or masks the status description, in place. `status` is mutable and
1100
+ * exposed by reference on the SDK span, same as `attributes`, so mutating it
1101
+ * here reaches the span about to be handed to the inner exporter. The ERROR
1102
+ * code itself is left alone so the failure stays visible and classifiable,
1103
+ * the same policy as `exception.type`.
1104
+ */
1105
+ sanitizeStatus(span) {
1106
+ const status = span.status;
1107
+ if (status?.code !== import_api4.SpanStatusCode.ERROR || !status.message) return;
1108
+ if (!this.opts.captureContent) {
1109
+ status.message = void 0;
1110
+ return;
1111
+ }
1112
+ if (this.opts.mask === void 0) return;
1113
+ try {
1114
+ const masked = toAttributeValue(
1115
+ this.opts.mask(status.message, { key: STATUS_DESCRIPTION_KEY })
1116
+ );
1117
+ status.message = typeof masked === "string" ? masked : String(masked);
1118
+ } catch {
1119
+ status.message = "[mask error]";
1120
+ }
1121
+ }
879
1122
  /** Strips or masks the content keys of one attribute bag, in place. */
880
1123
  sanitizeAttributes(attributes) {
881
1124
  if (attributes === void 0) return;
1125
+ const sanitizing = !this.opts.captureContent || this.opts.mask !== void 0;
1126
+ if (sanitizing && LLM_INVOCATION_PARAMETERS in attributes) {
1127
+ const redacted = redactInvocationParameters(attributes[LLM_INVOCATION_PARAMETERS]);
1128
+ if (redacted === void 0) delete attributes[LLM_INVOCATION_PARAMETERS];
1129
+ else attributes[LLM_INVOCATION_PARAMETERS] = redacted;
1130
+ }
882
1131
  for (const key of Object.keys(attributes)) {
883
1132
  if (!isContentKey(key)) continue;
884
1133
  if (!this.opts.captureContent) {
@@ -925,7 +1174,7 @@ function buildSnapshot(span) {
925
1174
  startTime: span.startTime,
926
1175
  endTime: span.startTime,
927
1176
  duration: [0, 0],
928
- status: { code: import_api4.SpanStatusCode.UNSET },
1177
+ status: { code: import_api5.SpanStatusCode.UNSET },
929
1178
  attributes,
930
1179
  links: [],
931
1180
  events: [],
@@ -937,15 +1186,15 @@ function buildSnapshot(span) {
937
1186
  ended: true
938
1187
  };
939
1188
  }
940
- function spanKey(context) {
941
- return `${context.traceId}:${context.spanId}`;
1189
+ function spanKey(context2) {
1190
+ return `${context2.traceId}:${context2.spanId}`;
942
1191
  }
943
- var import_api4, PendingSpanProcessor;
1192
+ var import_api5, PendingSpanProcessor;
944
1193
  var init_pending = __esm({
945
1194
  "src/pending.ts"() {
946
1195
  "use strict";
947
1196
  init_cjs_shims();
948
- import_api4 = require("@opentelemetry/api");
1197
+ import_api5 = require("@opentelemetry/api");
949
1198
  init_semconv();
950
1199
  PendingSpanProcessor = class {
951
1200
  delegate;
@@ -993,17 +1242,17 @@ var init_pending = __esm({
993
1242
  // src/session.ts
994
1243
  function withSession(sessionIdOrFn, maybeFn) {
995
1244
  const [sessionId, fn] = typeof sessionIdOrFn === "function" ? [(0, import_node_crypto.randomUUID)(), sessionIdOrFn] : [sessionIdOrFn, maybeFn];
996
- return import_api5.context.with(import_api5.context.active().setValue(SESSION_KEY, sessionId), () => fn(sessionId));
1245
+ return import_api6.context.with(import_api6.context.active().setValue(SESSION_KEY, sessionId), () => fn(sessionId));
997
1246
  }
998
- var import_node_crypto, import_api5, SESSION_KEY, SessionSpanProcessor;
1247
+ var import_node_crypto, import_api6, SESSION_KEY, SessionSpanProcessor;
999
1248
  var init_session = __esm({
1000
1249
  "src/session.ts"() {
1001
1250
  "use strict";
1002
1251
  init_cjs_shims();
1003
1252
  import_node_crypto = require("crypto");
1004
- import_api5 = require("@opentelemetry/api");
1253
+ import_api6 = require("@opentelemetry/api");
1005
1254
  init_semconv();
1006
- SESSION_KEY = (0, import_api5.createContextKey)("rius-session-id");
1255
+ SESSION_KEY = (0, import_api6.createContextKey)("rius-session-id");
1007
1256
  SessionSpanProcessor = class {
1008
1257
  constructor(defaultSessionId) {
1009
1258
  this.defaultSessionId = defaultSessionId;
@@ -1011,7 +1260,9 @@ var init_session = __esm({
1011
1260
  defaultSessionId;
1012
1261
  onStart(span, parentContext) {
1013
1262
  const value = parentContext.getValue(SESSION_KEY);
1014
- const sessionId = typeof value === "string" ? value : this.defaultSessionId;
1263
+ const parent = import_api6.trace.getSpan(parentContext);
1264
+ const inherited = parent?.attributes?.[SESSION_ID];
1265
+ const sessionId = typeof value === "string" ? value : typeof inherited === "string" ? inherited : this.defaultSessionId;
1015
1266
  if (sessionId !== void 0) span.setAttribute(SESSION_ID, sessionId);
1016
1267
  }
1017
1268
  onEnd(_span) {
@@ -1027,7 +1278,7 @@ var init_session = __esm({
1027
1278
  // src/workspace.ts
1028
1279
  function withWorkspace(alias, fn) {
1029
1280
  if (!alias) throw new Error("workspace alias must be a non-empty string");
1030
- return import_api6.context.with(import_api6.context.active().setValue(WORKSPACE_KEY, alias), () => fn(alias));
1281
+ return import_api7.context.with(import_api7.context.active().setValue(WORKSPACE_KEY, alias), () => fn(alias));
1031
1282
  }
1032
1283
  function setGlobalRouting(routing) {
1033
1284
  globalRouting = routing;
@@ -1040,25 +1291,32 @@ function registerWorkspace(alias, apiKey) {
1040
1291
  }
1041
1292
  globalRouting.register(alias, apiKey);
1042
1293
  }
1043
- var import_api6, WORKSPACE_KEY, WorkspaceSpanProcessor, RoutingSpanExporter, globalRouting;
1294
+ var import_api7, WORKSPACE_KEY, WorkspaceSpanProcessor, RoutingSpanExporter, globalRouting;
1044
1295
  var init_workspace = __esm({
1045
1296
  "src/workspace.ts"() {
1046
1297
  "use strict";
1047
1298
  init_cjs_shims();
1048
- import_api6 = require("@opentelemetry/api");
1299
+ import_api7 = require("@opentelemetry/api");
1049
1300
  init_esm();
1050
1301
  init_semconv();
1051
- WORKSPACE_KEY = (0, import_api6.createContextKey)("rius-workspace-alias");
1302
+ WORKSPACE_KEY = (0, import_api7.createContextKey)("rius-workspace-alias");
1052
1303
  WorkspaceSpanProcessor = class {
1304
+ /** Straddles already warned about, as "parentAlias->alias"; one warning each, not one per span. */
1305
+ warnedStraddles = /* @__PURE__ */ new Set();
1053
1306
  onStart(span, parentContext) {
1054
- const alias = parentContext.getValue(WORKSPACE_KEY);
1055
- if (typeof alias !== "string") return;
1056
- const parent = import_api6.trace.getSpan(parentContext);
1307
+ const parent = import_api7.trace.getSpan(parentContext);
1057
1308
  const parentAlias = parent?.attributes?.[WORKSPACE_ROUTE];
1309
+ const scoped = parentContext.getValue(WORKSPACE_KEY);
1310
+ const alias = typeof scoped === "string" ? scoped : parentAlias;
1311
+ if (typeof alias !== "string") return;
1058
1312
  if (parentAlias !== void 0 && parentAlias !== alias) {
1059
- console.warn(
1060
- `[rius] span "${span.name}" starts under workspace "${alias}" but its parent is stamped "${String(parentAlias)}"; a trace cannot straddle two workspaces (the backend derives the workspace from the API key). Switch workspaces at request boundaries, before the root span starts.`
1061
- );
1313
+ const straddle = `${String(parentAlias)}->${alias}`;
1314
+ if (!this.warnedStraddles.has(straddle)) {
1315
+ this.warnedStraddles.add(straddle);
1316
+ console.warn(
1317
+ `[rius] span "${span.name}" starts under workspace "${alias}" but its parent is stamped "${String(parentAlias)}"; a trace cannot straddle two workspaces (the backend derives the workspace from the API key). Switch workspaces at request boundaries, before the root span starts. Further spans of this straddle are not reported.`
1318
+ );
1319
+ }
1062
1320
  }
1063
1321
  span.setAttribute(WORKSPACE_ROUTE, alias);
1064
1322
  }
@@ -1179,6 +1437,7 @@ function init(options = {}) {
1179
1437
  }) : health;
1180
1438
  const batch = new import_sdk_trace_base.BatchSpanProcessor(exporter);
1181
1439
  processors.add(new SessionSpanProcessor(config.sessionId));
1440
+ processors.add(new UserSpanProcessor());
1182
1441
  if (routing !== void 0) {
1183
1442
  processors.add(new WorkspaceSpanProcessor());
1184
1443
  }
@@ -1189,9 +1448,13 @@ function init(options = {}) {
1189
1448
  }
1190
1449
  const instanceId = (0, import_node_crypto2.randomUUID)();
1191
1450
  const provider = new import_sdk_trace_node.NodeTracerProvider({
1451
+ // telemetry.sdk.* is reserved for the OTel SDK itself; we identify as a
1452
+ // distribution via telemetry.distro.*, the same two keys Python stamps.
1192
1453
  resource: (0, import_resources.resourceFromAttributes)({
1193
1454
  [import_semantic_conventions.ATTR_SERVICE_NAME]: config.serviceName,
1194
- [SERVICE_INSTANCE_ID]: instanceId
1455
+ [SERVICE_INSTANCE_ID]: instanceId,
1456
+ "telemetry.distro.name": "glassflow-rius",
1457
+ "telemetry.distro.version": SDK_VERSION
1195
1458
  }),
1196
1459
  // Always ParentBased, with no AlwaysOn shortcut at rate 1. They are not
1197
1460
  // equivalent: ParentBased honours a remote UNSAMPLED parent and drops,
@@ -1201,10 +1464,12 @@ function init(options = {}) {
1201
1464
  sampler: new import_sdk_trace_base.ParentBasedSampler({ root: new import_sdk_trace_base.TraceIdRatioBasedSampler(config.sampleRate) }),
1202
1465
  spanProcessors: [processors]
1203
1466
  });
1204
- const ready = config.disabled ? Promise.resolve([]) : enableInstrumentations(processors, provider).catch(() => []);
1467
+ const teardown = [];
1468
+ const ready = config.disabled ? Promise.resolve([]) : enableInstrumentations(processors, provider, void 0, teardown);
1205
1469
  provider.register();
1206
1470
  let heartbeat;
1207
- if (config.heartbeat && !config.disabled) {
1471
+ const heartbeatCanAuthenticate = config.apiKey !== void 0 || options.heartbeatTransport !== void 0;
1472
+ if (config.heartbeat && !config.disabled && heartbeatCanAuthenticate) {
1208
1473
  const tracker = new OpenRootSpanTracker();
1209
1474
  processors.add(tracker);
1210
1475
  const sender = new HeartbeatSender({
@@ -1223,20 +1488,20 @@ function init(options = {}) {
1223
1488
  process.once("beforeExit", beforeExitHandler);
1224
1489
  heartbeat = { sender, beforeExitHandler };
1225
1490
  }
1226
- globalClient = createClient({ provider, processors, health, ready, heartbeat });
1491
+ globalClient = createClient({ provider, processors, health, ready, teardown, heartbeat });
1227
1492
  setGlobalRouting(routing);
1228
1493
  return globalClient;
1229
1494
  }
1230
1495
  function getTracer() {
1231
- return import_api7.trace.getTracer(TRACER_NAME);
1496
+ return import_api8.trace.getTracer(TRACER_NAME, SDK_VERSION);
1232
1497
  }
1233
- var import_node_crypto2, import_api7, import_exporter_trace_otlp_proto, import_resources, import_sdk_trace_base, import_sdk_trace_node, import_semantic_conventions, sinks, heartbeats, createClient, RiusClient, globalClient;
1498
+ var import_node_crypto2, import_api8, import_exporter_trace_otlp_proto, import_resources, import_sdk_trace_base, import_sdk_trace_node, import_semantic_conventions, sinks, heartbeats, createClient, RiusClient, globalClient;
1234
1499
  var init_client = __esm({
1235
1500
  "src/client.ts"() {
1236
1501
  "use strict";
1237
1502
  init_cjs_shims();
1238
1503
  import_node_crypto2 = require("crypto");
1239
- import_api7 = require("@opentelemetry/api");
1504
+ import_api8 = require("@opentelemetry/api");
1240
1505
  import_exporter_trace_otlp_proto = require("@opentelemetry/exporter-trace-otlp-proto");
1241
1506
  import_resources = require("@opentelemetry/resources");
1242
1507
  import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
@@ -1251,18 +1516,22 @@ var init_client = __esm({
1251
1516
  init_pending();
1252
1517
  init_semconv();
1253
1518
  init_session();
1519
+ init_user();
1520
+ init_version();
1254
1521
  init_workspace();
1255
1522
  sinks = /* @__PURE__ */ new WeakMap();
1256
1523
  heartbeats = /* @__PURE__ */ new WeakMap();
1257
1524
  RiusClient = class _RiusClient {
1258
1525
  provider;
1259
1526
  health;
1527
+ teardown;
1260
1528
  /** Resolves with the names of the auto-instrumentations that attached. */
1261
1529
  ready;
1262
1530
  constructor(parts) {
1263
1531
  this.provider = parts.provider;
1264
1532
  this.health = parts.health;
1265
1533
  this.ready = parts.ready;
1534
+ this.teardown = parts.teardown;
1266
1535
  sinks.set(this, parts.processors);
1267
1536
  if (parts.heartbeat) heartbeats.set(this, parts.heartbeat);
1268
1537
  }
@@ -1290,13 +1559,22 @@ var init_client = __esm({
1290
1559
  process.removeListener("beforeExit", heartbeat.beforeExitHandler);
1291
1560
  await heartbeat.sender.stop();
1292
1561
  }
1562
+ await this.ready;
1563
+ for (const disable of this.teardown.splice(0)) {
1564
+ try {
1565
+ disable();
1566
+ } catch {
1567
+ }
1568
+ }
1293
1569
  try {
1294
1570
  await this.provider.shutdown();
1295
1571
  } finally {
1296
1572
  if (globalClient === this) {
1297
1573
  globalClient = void 0;
1298
1574
  setGlobalRouting(void 0);
1299
- import_api7.trace.disable();
1575
+ import_api8.trace.disable();
1576
+ import_api8.context.disable();
1577
+ import_api8.propagation.disable();
1300
1578
  }
1301
1579
  }
1302
1580
  }
@@ -1321,6 +1599,7 @@ __export(index_exports, {
1321
1599
  startGeneration: () => startGeneration,
1322
1600
  startSpan: () => startSpan,
1323
1601
  withSession: () => withSession,
1602
+ withUser: () => withUser,
1324
1603
  withWorkspace: () => withWorkspace
1325
1604
  });
1326
1605
  module.exports = __toCommonJS(index_exports);
@@ -1330,6 +1609,67 @@ init_client();
1330
1609
  // src/generation.ts
1331
1610
  init_cjs_shims();
1332
1611
  init_client();
1612
+
1613
+ // src/messages.ts
1614
+ init_cjs_shims();
1615
+ init_serde();
1616
+ function isRecord(value) {
1617
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1618
+ }
1619
+ function asText(value) {
1620
+ if (typeof value === "string") return value;
1621
+ const text = toAttributeValue(value);
1622
+ return typeof text === "string" ? text : String(text);
1623
+ }
1624
+ function normalizePart(item) {
1625
+ if (isRecord(item)) {
1626
+ if (item.type === "text" && "text" in item) return { type: "text", content: item.text };
1627
+ if ("type" in item) return item;
1628
+ }
1629
+ return { type: "text", content: asText(item) };
1630
+ }
1631
+ function normalizeMessage(message, defaultRole) {
1632
+ if (typeof message === "string") {
1633
+ return { role: defaultRole, parts: [{ type: "text", content: message }] };
1634
+ }
1635
+ if (!isRecord(message)) {
1636
+ return { role: defaultRole, parts: [{ type: "text", content: asText(message) }] };
1637
+ }
1638
+ if ("parts" in message) {
1639
+ return { ...message, role: message.role ?? defaultRole };
1640
+ }
1641
+ const role = message.role ?? defaultRole;
1642
+ if (role === "tool" && "tool_call_id" in message) {
1643
+ return {
1644
+ role: "tool",
1645
+ parts: [{ type: "tool_call_response", id: message.tool_call_id, response: message.content }]
1646
+ };
1647
+ }
1648
+ const parts = [];
1649
+ const content = message.content;
1650
+ if (typeof content === "string") {
1651
+ parts.push({ type: "text", content });
1652
+ } else if (Array.isArray(content)) {
1653
+ for (const item of content) parts.push(normalizePart(item));
1654
+ } else if (content !== null && content !== void 0) {
1655
+ parts.push({ type: "text", content: asText(content) });
1656
+ }
1657
+ const toolCalls = message.tool_calls;
1658
+ if (Array.isArray(toolCalls)) {
1659
+ for (const call of toolCalls) {
1660
+ if (!isRecord(call)) continue;
1661
+ const fn = isRecord(call.function) ? call.function : {};
1662
+ parts.push({ type: "tool_call", id: call.id, name: fn.name, arguments: fn.arguments });
1663
+ }
1664
+ }
1665
+ return { role, parts };
1666
+ }
1667
+ function serializeMessages(messages, defaultRole) {
1668
+ const list = Array.isArray(messages) ? messages : [messages];
1669
+ return toAttributeValue(list.map((message) => normalizeMessage(message, defaultRole)));
1670
+ }
1671
+
1672
+ // src/generation.ts
1333
1673
  init_semconv();
1334
1674
  init_serde();
1335
1675
  init_spans();
@@ -1344,12 +1684,31 @@ var Generation = class extends Observation {
1344
1684
  }
1345
1685
  provider;
1346
1686
  firstTokenRecorded = false;
1687
+ /**
1688
+ * Record the request messages (`gen_ai.input.messages`), normalised to the
1689
+ * GenAI `{role, parts}` shape like the Python SDK does: bare strings, OpenAI
1690
+ * dicts (including `tool_calls` and tool responses) and multimodal content
1691
+ * lists are all accepted. Bare strings default to the `user` role.
1692
+ */
1347
1693
  setInput(value) {
1348
- this.span.setAttribute(GEN_AI_INPUT_MESSAGES, toAttributeValue(value));
1694
+ this.span.setAttribute(GEN_AI_INPUT_MESSAGES, serializeMessages(value, "user"));
1349
1695
  return this;
1350
1696
  }
1697
+ /** Record the response messages (`gen_ai.output.messages`); bare strings default to `assistant`. */
1351
1698
  setOutput(value) {
1352
- this.span.setAttribute(GEN_AI_OUTPUT_MESSAGES, toAttributeValue(value));
1699
+ this.span.setAttribute(GEN_AI_OUTPUT_MESSAGES, serializeMessages(value, "assistant"));
1700
+ return this;
1701
+ }
1702
+ /**
1703
+ * The request's tool/function definitions (`gen_ai.tool.definitions`).
1704
+ * Serialized verbatim, in whatever shape the provider request used (OpenAI
1705
+ * nests each tool under `function`, Anthropic uses top-level
1706
+ * `name`/`input_schema`) — no normalization, so what is recorded is exactly
1707
+ * what the model was shown. Definitions are content, not identity: they are
1708
+ * masked/stripped under `captureContent: false` like messages are.
1709
+ */
1710
+ setToolDefinitions(tools) {
1711
+ this.span.setAttribute(GEN_AI_TOOL_DEFINITIONS, toAttributeValue(tools));
1353
1712
  return this;
1354
1713
  }
1355
1714
  setModel(model) {
@@ -1415,8 +1774,10 @@ var Generation = class extends Observation {
1415
1774
  };
1416
1775
  function attributesFor(options) {
1417
1776
  const attributes = { ...kindAttributes("LLM" /* LLM */) };
1777
+ if (options.operation !== void 0) attributes[GEN_AI_OPERATION_NAME] = options.operation;
1418
1778
  if (options.model !== void 0) attributes[GEN_AI_REQUEST_MODEL] = options.model;
1419
1779
  if (options.provider !== void 0) attributes[GEN_AI_PROVIDER_NAME] = options.provider;
1780
+ if (options.userId !== void 0) attributes[USER_ID] = options.userId;
1420
1781
  return attributes;
1421
1782
  }
1422
1783
  function configure2(generation, options) {
@@ -1426,6 +1787,7 @@ function configure2(generation, options) {
1426
1787
  if (options.reasoningLevel !== void 0) {
1427
1788
  generation.setAttribute(GEN_AI_REQUEST_REASONING_LEVEL, options.reasoningLevel);
1428
1789
  }
1790
+ if (options.tools !== void 0) generation.setToolDefinitions(options.tools);
1429
1791
  if (options.input !== void 0) generation.setInput(options.input);
1430
1792
  return generation;
1431
1793
  }
@@ -1435,17 +1797,13 @@ function startGeneration(name, options = {}) {
1435
1797
  }
1436
1798
  function startAsCurrentGeneration(name, optionsOrFn, maybeFn) {
1437
1799
  const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
1438
- return getTracer().startActiveSpan(name, { attributes: attributesFor(options) }, async (span) => {
1439
- const generation = configure2(new Generation(span, options.provider), options);
1440
- try {
1441
- return await fn(generation);
1442
- } catch (error) {
1443
- generation.recordException(error);
1444
- throw error;
1445
- } finally {
1446
- generation.end();
1447
- }
1448
- });
1800
+ return runActive(
1801
+ name,
1802
+ attributesFor(options),
1803
+ options.userId,
1804
+ (span) => configure2(new Generation(span, options.provider), options),
1805
+ fn
1806
+ );
1449
1807
  }
1450
1808
 
1451
1809
  // src/observe.ts
@@ -1457,7 +1815,10 @@ function observe(fn, options = {}) {
1457
1815
  const captureOutput = options.captureOutput ?? true;
1458
1816
  const wrapped = (...args) => startAsCurrentSpan(
1459
1817
  name,
1460
- { kind: options.kind, input: captureInput ? args : void 0 },
1818
+ // {args, kwargs} is the Python SDK's shape; JavaScript has no keyword
1819
+ // arguments, so kwargs is always empty, but the key stays so a saved
1820
+ // search or a console view reads both SDKs' input.value the same way.
1821
+ { kind: options.kind, input: captureInput ? { args, kwargs: {} } : void 0 },
1461
1822
  async (observation) => {
1462
1823
  const result = await fn(...args);
1463
1824
  if (captureOutput && result !== void 0) observation.setOutput(result);
@@ -1471,9 +1832,10 @@ function observe(fn, options = {}) {
1471
1832
  // src/index.ts
1472
1833
  init_semconv();
1473
1834
  init_session();
1835
+ init_user();
1474
1836
  init_spans();
1475
1837
  init_workspace();
1476
- var VERSION = "0.5.0";
1838
+ var VERSION = "0.6.1";
1477
1839
  // Annotate the CommonJS export names for ESM import in node:
1478
1840
  0 && (module.exports = {
1479
1841
  Generation,
@@ -1490,5 +1852,6 @@ var VERSION = "0.5.0";
1490
1852
  startGeneration,
1491
1853
  startSpan,
1492
1854
  withSession,
1855
+ withUser,
1493
1856
  withWorkspace
1494
1857
  });