@glassflow-ai/rius 0.7.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -30,7 +30,74 @@ var init_cjs_shims = __esm({
30
30
  }
31
31
  });
32
32
 
33
+ // node_modules/@opentelemetry/core/build/esm/platform/node/environment.js
34
+ function getNumberFromEnv(key) {
35
+ const raw = process.env[key];
36
+ if (raw == null || raw.trim() === "") {
37
+ return void 0;
38
+ }
39
+ const value = Number(raw);
40
+ if (isNaN(value)) {
41
+ import_api.diag.warn(`Unknown value ${(0, import_util.inspect)(raw)} for ${key}, expected a number, using defaults`);
42
+ return void 0;
43
+ }
44
+ return value;
45
+ }
46
+ var import_api, import_util;
47
+ var init_environment = __esm({
48
+ "node_modules/@opentelemetry/core/build/esm/platform/node/environment.js"() {
49
+ "use strict";
50
+ init_cjs_shims();
51
+ import_api = require("@opentelemetry/api");
52
+ import_util = require("util");
53
+ }
54
+ });
55
+
56
+ // node_modules/@opentelemetry/core/build/esm/platform/node/index.js
57
+ var init_node = __esm({
58
+ "node_modules/@opentelemetry/core/build/esm/platform/node/index.js"() {
59
+ "use strict";
60
+ init_cjs_shims();
61
+ init_environment();
62
+ }
63
+ });
64
+
65
+ // node_modules/@opentelemetry/core/build/esm/platform/index.js
66
+ var init_platform = __esm({
67
+ "node_modules/@opentelemetry/core/build/esm/platform/index.js"() {
68
+ "use strict";
69
+ init_cjs_shims();
70
+ init_node();
71
+ }
72
+ });
73
+
74
+ // node_modules/@opentelemetry/core/build/esm/ExportResult.js
75
+ var ExportResultCode;
76
+ var init_ExportResult = __esm({
77
+ "node_modules/@opentelemetry/core/build/esm/ExportResult.js"() {
78
+ "use strict";
79
+ init_cjs_shims();
80
+ (function(ExportResultCode2) {
81
+ ExportResultCode2[ExportResultCode2["SUCCESS"] = 0] = "SUCCESS";
82
+ ExportResultCode2[ExportResultCode2["FAILED"] = 1] = "FAILED";
83
+ })(ExportResultCode || (ExportResultCode = {}));
84
+ }
85
+ });
86
+
87
+ // node_modules/@opentelemetry/core/build/esm/index.js
88
+ var init_esm = __esm({
89
+ "node_modules/@opentelemetry/core/build/esm/index.js"() {
90
+ "use strict";
91
+ init_cjs_shims();
92
+ init_ExportResult();
93
+ init_platform();
94
+ }
95
+ });
96
+
33
97
  // src/config.ts
98
+ function namedAgent(agentName) {
99
+ return agentName === void 0 || agentName === DEFAULT_SERVICE_NAME ? void 0 : agentName;
100
+ }
34
101
  function bool(name, raw, fallback) {
35
102
  if (raw === void 0) return fallback;
36
103
  const value = raw.trim().toLowerCase();
@@ -63,6 +130,21 @@ function clamp(name, value, min, max, fallback) {
63
130
  console.warn(`[rius] ${name}=${value} is outside [${min}, ${max}]; clamped to ${clamped}.`);
64
131
  return clamped;
65
132
  }
133
+ function serviceVersionFromOtelEnv(raw) {
134
+ if (raw === void 0) return void 0;
135
+ for (const pair of raw.split(",")) {
136
+ const separator = pair.indexOf("=");
137
+ if (separator === -1) continue;
138
+ if (pair.slice(0, separator).trim() !== "service.version") continue;
139
+ const value = pair.slice(separator + 1).trim();
140
+ try {
141
+ return decodeURIComponent(value) || void 0;
142
+ } catch {
143
+ return value || void 0;
144
+ }
145
+ }
146
+ return void 0;
147
+ }
66
148
  function resolveConfig(options = {}, env = process.env) {
67
149
  const endpoint = (options.endpoint ?? env.RIUS_ENDPOINT ?? DEFAULT_ENDPOINT).replace(/\/+$/, "");
68
150
  const serviceName = options.serviceName ?? env.RIUS_SERVICE_NAME ?? DEFAULT_SERVICE_NAME;
@@ -84,6 +166,11 @@ function resolveConfig(options = {}, env = process.env) {
84
166
  endpoint,
85
167
  apiKey: options.apiKey ?? env.RIUS_API_KEY,
86
168
  serviceName,
169
+ // Empty is unset, as it is for the agent name and the session id: a blank
170
+ // version would stamp `service.version=""` on every span, which reads as
171
+ // a real (and wrong) answer rather than as no answer. Falls through to
172
+ // the OTel environment last, and to nothing after that.
173
+ serviceVersion: options.serviceVersion || env.RIUS_SERVICE_VERSION || serviceVersionFromOtelEnv(env.OTEL_RESOURCE_ATTRIBUTES) || void 0,
87
174
  disabled: options.disabled ?? bool("RIUS_DISABLED", env.RIUS_DISABLED, false),
88
175
  sampleRate: options.sampleRate ?? rate(env.RIUS_SAMPLE_RATE, 1),
89
176
  captureContent: options.captureContent ?? bool("RIUS_CAPTURE_CONTENT", env.RIUS_CAPTURE_CONTENT, true),
@@ -94,11 +181,22 @@ function resolveConfig(options = {}, env = process.env) {
94
181
  // Empty falls through to serviceName rather than shipping a blank identity
95
182
  // on every heartbeat payload, so `??` would be wrong here.
96
183
  agentName: options.agentName || env.RIUS_AGENT_NAME || serviceName,
184
+ // Derived from the SAME value, not resolved a second time: the
185
+ // main-agent name is a move of the existing agent name into our
186
+ // namespace, so a new option here would let the two disagree.
187
+ mainAgentName: namedAgent(options.agentName || env.RIUS_AGENT_NAME || serviceName),
188
+ // Empty is unset, as everywhere above. None of the three has a fallback:
189
+ // there is no service-level id, description or agent version to borrow,
190
+ // and inventing one would repeat the `unknown_service` mistake.
191
+ mainAgentId: options.mainAgentId || env.RIUS_MAIN_AGENT_ID || void 0,
192
+ mainAgentDescription: options.mainAgentDescription || env.RIUS_MAIN_AGENT_DESCRIPTION || void 0,
193
+ mainAgentVersion: options.mainAgentVersion || env.RIUS_MAIN_AGENT_VERSION || void 0,
97
194
  partialSpans: options.partialSpans ?? bool("RIUS_PARTIAL_SPANS", env.RIUS_PARTIAL_SPANS, false),
98
195
  partialSpansDelayMs: partialSpansDelaySeconds * 1e3,
99
196
  // Empty is unset, like agentName: a blank session id would group every
100
197
  // span under the meaningless session "".
101
- sessionId: options.sessionId || env.RIUS_SESSION_ID || void 0
198
+ sessionId: options.sessionId || env.RIUS_SESSION_ID || void 0,
199
+ bridgeForeignProvider: options.bridgeForeignProvider ?? bool("RIUS_BRIDGE_FOREIGN_PROVIDER", env.RIUS_BRIDGE_FOREIGN_PROVIDER, false)
102
200
  };
103
201
  }
104
202
  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;
@@ -119,6 +217,511 @@ var init_config = __esm({
119
217
  }
120
218
  });
121
219
 
220
+ // src/semconv.ts
221
+ function requestAttributeKey(parameter) {
222
+ return Object.hasOwn(GEN_AI_REQUEST_PARAMETERS, parameter) ? GEN_AI_REQUEST_PARAMETERS[parameter] : `${RIUS_REQUEST_PREFIX}${parameter}`;
223
+ }
224
+ function operationForKind(kind) {
225
+ return Object.hasOwn(OPERATION_BY_KIND, kind) ? OPERATION_BY_KIND[kind] : void 0;
226
+ }
227
+ function kindForOperation(operation) {
228
+ return Object.hasOwn(KIND_BY_OPERATION, operation) ? KIND_BY_OPERATION[operation] : void 0;
229
+ }
230
+ function otelSpanKind(kind) {
231
+ return OTEL_KIND_BY_KIND[kind];
232
+ }
233
+ function kindAttributes(kind, toolName2) {
234
+ const attributes = { [OPENINFERENCE_SPAN_KIND]: kind };
235
+ const operation = OPERATION_BY_KIND[kind];
236
+ if (operation !== void 0) attributes[GEN_AI_OPERATION_NAME] = operation;
237
+ if (kind === "TOOL" /* TOOL */ && toolName2 !== void 0) attributes[GEN_AI_TOOL_NAME] = toolName2;
238
+ return attributes;
239
+ }
240
+ function composeSpanName(kind, attributes) {
241
+ const operation = attributes[GEN_AI_OPERATION_NAME];
242
+ if (typeof operation !== "string") return CHAIN_SPAN_NAME;
243
+ const targetKey = NAME_TARGET_BY_KIND[kind];
244
+ const target = targetKey === void 0 ? void 0 : attributes[targetKey];
245
+ return typeof target === "string" && target !== "" ? `${operation} ${target}` : operation;
246
+ }
247
+ var import_api2, import_semantic_conventions, 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_RESPONSE_ID, GEN_AI_OUTPUT_TYPE, GEN_AI_REQUEST_STREAM, GEN_AI_RESPONSE_TIME_TO_FIRST_CHUNK, GEN_AI_REQUEST_TEMPERATURE, GEN_AI_REQUEST_TOP_P, GEN_AI_REQUEST_TOP_K, GEN_AI_REQUEST_MAX_TOKENS, GEN_AI_REQUEST_FREQUENCY_PENALTY, GEN_AI_REQUEST_PRESENCE_PENALTY, GEN_AI_REQUEST_SEED, GEN_AI_REQUEST_STOP_SEQUENCES, GEN_AI_REQUEST_CHOICE_COUNT, GEN_AI_REQUEST_ENCODING_FORMATS, GEN_AI_REQUEST_PREVIOUS_RESPONSE_ID, GEN_AI_REQUEST_STREAM_CURSOR, 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_CALL_ID, GEN_AI_TOOL_TYPE, GEN_AI_DATA_SOURCE_ID, GEN_AI_RETRIEVAL_TOP_K, GEN_AI_RETRIEVAL_DOCUMENTS, GEN_AI_AGENT_NAME, GEN_AI_AGENT_ID, GEN_AI_AGENT_VERSION, RIUS_MAIN_AGENT_NAME, RIUS_MAIN_AGENT_ID, RIUS_MAIN_AGENT_DESCRIPTION, RIUS_MAIN_AGENT_VERSION, GEN_AI_TOOL_DEFINITIONS, GEN_AI_REQUEST_PREFIX, RIUS_REQUEST_PREFIX, RIUS_REQUEST_TOOL_CHOICE, GEN_AI_REQUEST_PARAMETERS, MCP_METHOD_NAME, MCP_METHOD_TOOLS_CALL, MCP_PROTOCOL_VERSION, ERROR_TYPE, ERROR_TYPE_TOOL_ERROR, EXCEPTION_EVENT, EXCEPTION_TYPE, MCP_RESULT_TYPE, RIUS_CONTEXT_SIZES, GEN_AI_FIRST_TOKEN_EVENT, SpanKind, OPERATION_BY_KIND, KIND_BY_OPERATION, OTEL_KIND_BY_KIND, NAME_TARGET_BY_KIND, CHAIN_SPAN_NAME, LLM_INPUT_MESSAGES_PREFIX, LLM_OUTPUT_MESSAGES_PREFIX, LLM_MESSAGE_ROLE, LLM_MESSAGE_CONTENT, LLM_MESSAGE_TOOL_CALL_ID, LLM_MESSAGE_TOOL_CALLS_PREFIX, LLM_TOOL_CALL_PREFIX, LLM_TOOL_CALL_ID, LLM_TOOL_CALL_FUNCTION_NAME, LLM_TOOL_CALL_FUNCTION_ARGUMENTS, LLM_MESSAGE_CONTENTS_PREFIX, LLM_MESSAGE_CONTENT_PREFIX, LLM_INVOCATION_PARAMETERS, INVOCATION_PARAMETERS_CONTENT_MEMBERS, THIRD_PARTY_CONTENT_ATTRIBUTES, CONTENT_ATTRIBUTES, CONTENT_ATTRIBUTE_PREFIXES, CONTENT_PREFIX_IDENTITY_ATTRIBUTES, CONTENT_ATTRIBUTE_SUFFIXES, CONTENT_ATTRIBUTE_PREFIXED_SUFFIXES, RIUS_SPAN_PENDING, RIUS_PARENT_FOREIGN, RIUS_SDK_GLOBAL_PROVIDER, PENDING_IDENTITY_ATTRIBUTES, PENDING_IDENTITY_PREFIXES;
248
+ var init_semconv = __esm({
249
+ "src/semconv.ts"() {
250
+ "use strict";
251
+ init_cjs_shims();
252
+ import_api2 = require("@opentelemetry/api");
253
+ import_semantic_conventions = require("@opentelemetry/semantic-conventions");
254
+ TRACER_NAME = "rius";
255
+ SERVICE_INSTANCE_ID = "service.instance.id";
256
+ OPENINFERENCE_SPAN_KIND = "openinference.span.kind";
257
+ INPUT_VALUE = "input.value";
258
+ OUTPUT_VALUE = "output.value";
259
+ SESSION_ID = "session.id";
260
+ USER_ID = "user.id";
261
+ WORKSPACE_ROUTE = "rius.workspace";
262
+ GEN_AI_OPERATION_NAME = "gen_ai.operation.name";
263
+ GEN_AI_PROVIDER_NAME = "gen_ai.provider.name";
264
+ GEN_AI_REQUEST_MODEL = "gen_ai.request.model";
265
+ GEN_AI_REQUEST_REASONING_LEVEL = "gen_ai.request.reasoning.level";
266
+ GEN_AI_RESPONSE_MODEL = "gen_ai.response.model";
267
+ GEN_AI_RESPONSE_ID = "gen_ai.response.id";
268
+ GEN_AI_OUTPUT_TYPE = "gen_ai.output.type";
269
+ GEN_AI_REQUEST_STREAM = "gen_ai.request.stream";
270
+ GEN_AI_RESPONSE_TIME_TO_FIRST_CHUNK = "gen_ai.response.time_to_first_chunk";
271
+ GEN_AI_REQUEST_TEMPERATURE = "gen_ai.request.temperature";
272
+ GEN_AI_REQUEST_TOP_P = "gen_ai.request.top_p";
273
+ GEN_AI_REQUEST_TOP_K = "gen_ai.request.top_k";
274
+ GEN_AI_REQUEST_MAX_TOKENS = "gen_ai.request.max_tokens";
275
+ GEN_AI_REQUEST_FREQUENCY_PENALTY = "gen_ai.request.frequency_penalty";
276
+ GEN_AI_REQUEST_PRESENCE_PENALTY = "gen_ai.request.presence_penalty";
277
+ GEN_AI_REQUEST_SEED = "gen_ai.request.seed";
278
+ GEN_AI_REQUEST_STOP_SEQUENCES = "gen_ai.request.stop_sequences";
279
+ GEN_AI_REQUEST_CHOICE_COUNT = "gen_ai.request.choice.count";
280
+ GEN_AI_REQUEST_ENCODING_FORMATS = "gen_ai.request.encoding_formats";
281
+ GEN_AI_REQUEST_PREVIOUS_RESPONSE_ID = "gen_ai.request.previous_response.id";
282
+ GEN_AI_REQUEST_STREAM_CURSOR = "gen_ai.request.stream_cursor";
283
+ GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens";
284
+ GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens";
285
+ GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens";
286
+ GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS = "gen_ai.usage.cache_write.input_tokens";
287
+ GEN_AI_USAGE_REASONING_OUTPUT_TOKENS = "gen_ai.usage.reasoning.output_tokens";
288
+ GEN_AI_INPUT_MESSAGES = "gen_ai.input.messages";
289
+ GEN_AI_OUTPUT_MESSAGES = "gen_ai.output.messages";
290
+ GEN_AI_RESPONSE_FINISH_REASONS = "gen_ai.response.finish_reasons";
291
+ GEN_AI_TOOL_NAME = "gen_ai.tool.name";
292
+ GEN_AI_TOOL_CALL_ID = "gen_ai.tool.call.id";
293
+ GEN_AI_TOOL_TYPE = "gen_ai.tool.type";
294
+ GEN_AI_DATA_SOURCE_ID = "gen_ai.data_source.id";
295
+ GEN_AI_RETRIEVAL_TOP_K = "gen_ai.retrieval.top_k";
296
+ GEN_AI_RETRIEVAL_DOCUMENTS = "gen_ai.retrieval.documents";
297
+ GEN_AI_AGENT_NAME = "gen_ai.agent.name";
298
+ GEN_AI_AGENT_ID = "gen_ai.agent.id";
299
+ GEN_AI_AGENT_VERSION = "gen_ai.agent.version";
300
+ RIUS_MAIN_AGENT_NAME = "rius.main_agent.name";
301
+ RIUS_MAIN_AGENT_ID = "rius.main_agent.id";
302
+ RIUS_MAIN_AGENT_DESCRIPTION = "rius.main_agent.description";
303
+ RIUS_MAIN_AGENT_VERSION = "rius.main_agent.version";
304
+ GEN_AI_TOOL_DEFINITIONS = "gen_ai.tool.definitions";
305
+ GEN_AI_REQUEST_PREFIX = "gen_ai.request.";
306
+ RIUS_REQUEST_PREFIX = "rius.request.";
307
+ RIUS_REQUEST_TOOL_CHOICE = "rius.request.tool_choice";
308
+ GEN_AI_REQUEST_PARAMETERS = {
309
+ // model: the request's model; also settable via the `model` option.
310
+ model: GEN_AI_REQUEST_MODEL,
311
+ // max_tokens: OpenAI Chat Completions `max_tokens`, its successor
312
+ // `max_completion_tokens`, the Responses API's `max_output_tokens`, and
313
+ // Google's `maxOutputTokens`.
314
+ max_tokens: GEN_AI_REQUEST_MAX_TOKENS,
315
+ maxTokens: GEN_AI_REQUEST_MAX_TOKENS,
316
+ max_completion_tokens: GEN_AI_REQUEST_MAX_TOKENS,
317
+ maxCompletionTokens: GEN_AI_REQUEST_MAX_TOKENS,
318
+ max_output_tokens: GEN_AI_REQUEST_MAX_TOKENS,
319
+ maxOutputTokens: GEN_AI_REQUEST_MAX_TOKENS,
320
+ // choice.count: "the target number of candidate completions to return":
321
+ // OpenAI `n`, Google `candidateCount`, Cohere `num_generations`.
322
+ "choice.count": GEN_AI_REQUEST_CHOICE_COUNT,
323
+ n: GEN_AI_REQUEST_CHOICE_COUNT,
324
+ candidate_count: GEN_AI_REQUEST_CHOICE_COUNT,
325
+ candidateCount: GEN_AI_REQUEST_CHOICE_COUNT,
326
+ num_generations: GEN_AI_REQUEST_CHOICE_COUNT,
327
+ temperature: GEN_AI_REQUEST_TEMPERATURE,
328
+ // top_p: Google `topP`, Cohere `p`.
329
+ top_p: GEN_AI_REQUEST_TOP_P,
330
+ topP: GEN_AI_REQUEST_TOP_P,
331
+ p: GEN_AI_REQUEST_TOP_P,
332
+ // top_k: the registry's own note names Anthropic `top_k`, Cohere `k` and
333
+ // Google `topK`, and says OpenAI's `top_logprobs` MUST NOT be reported here
334
+ // (it shapes the response, not the sampling). So top_logprobs is
335
+ // deliberately absent and lands in rius.request.*.
336
+ top_k: GEN_AI_REQUEST_TOP_K,
337
+ topK: GEN_AI_REQUEST_TOP_K,
338
+ k: GEN_AI_REQUEST_TOP_K,
339
+ // stop_sequences: OpenAI `stop`, Google `stopSequences`. `stop` is listed
340
+ // FIRST because the llm.invocation_parameters rule prefers it, and both
341
+ // paths must keep the same spelling when a request carries two.
342
+ stop: GEN_AI_REQUEST_STOP_SEQUENCES,
343
+ stop_sequences: GEN_AI_REQUEST_STOP_SEQUENCES,
344
+ stopSequences: GEN_AI_REQUEST_STOP_SEQUENCES,
345
+ frequency_penalty: GEN_AI_REQUEST_FREQUENCY_PENALTY,
346
+ frequencyPenalty: GEN_AI_REQUEST_FREQUENCY_PENALTY,
347
+ presence_penalty: GEN_AI_REQUEST_PRESENCE_PENALTY,
348
+ presencePenalty: GEN_AI_REQUEST_PRESENCE_PENALTY,
349
+ // encoding_formats: plural in the registry; OpenAI's embeddings endpoint
350
+ // sends the singular `encoding_format`, and the registry's note says some
351
+ // systems call these "embedding types" (Cohere `embedding_types`).
352
+ encoding_formats: GEN_AI_REQUEST_ENCODING_FORMATS,
353
+ encoding_format: GEN_AI_REQUEST_ENCODING_FORMATS,
354
+ encodingFormat: GEN_AI_REQUEST_ENCODING_FORMATS,
355
+ embedding_types: GEN_AI_REQUEST_ENCODING_FORMATS,
356
+ seed: GEN_AI_REQUEST_SEED,
357
+ stream: GEN_AI_REQUEST_STREAM,
358
+ // reasoning.level: "the exact string value sent to the provider"; OpenAI
359
+ // sends it as `reasoning_effort`.
360
+ "reasoning.level": GEN_AI_REQUEST_REASONING_LEVEL,
361
+ reasoning_level: GEN_AI_REQUEST_REASONING_LEVEL,
362
+ reasoning_effort: GEN_AI_REQUEST_REASONING_LEVEL,
363
+ reasoningEffort: GEN_AI_REQUEST_REASONING_LEVEL,
364
+ // previous_response.id: the registry names OpenAI's `previous_response_id`
365
+ // and Google's `previous_interaction_id`.
366
+ "previous_response.id": GEN_AI_REQUEST_PREVIOUS_RESPONSE_ID,
367
+ previous_response_id: GEN_AI_REQUEST_PREVIOUS_RESPONSE_ID,
368
+ previousResponseId: GEN_AI_REQUEST_PREVIOUS_RESPONSE_ID,
369
+ previous_interaction_id: GEN_AI_REQUEST_PREVIOUS_RESPONSE_ID,
370
+ // stream_cursor: the registry names OpenAI's `starting_after` and Google's
371
+ // `last_event_id`.
372
+ stream_cursor: GEN_AI_REQUEST_STREAM_CURSOR,
373
+ starting_after: GEN_AI_REQUEST_STREAM_CURSOR,
374
+ last_event_id: GEN_AI_REQUEST_STREAM_CURSOR
375
+ };
376
+ MCP_METHOD_NAME = "mcp.method.name";
377
+ MCP_METHOD_TOOLS_CALL = "tools/call";
378
+ MCP_PROTOCOL_VERSION = "mcp.protocol.version";
379
+ ERROR_TYPE = "error.type";
380
+ ERROR_TYPE_TOOL_ERROR = "tool_error";
381
+ EXCEPTION_EVENT = "exception";
382
+ EXCEPTION_TYPE = "exception.type";
383
+ MCP_RESULT_TYPE = "mcp.result_type";
384
+ RIUS_CONTEXT_SIZES = "rius.context.sizes";
385
+ GEN_AI_FIRST_TOKEN_EVENT = "gen_ai.first_token";
386
+ SpanKind = /* @__PURE__ */ ((SpanKind2) => {
387
+ SpanKind2["AGENT"] = "AGENT";
388
+ SpanKind2["LLM"] = "LLM";
389
+ SpanKind2["TOOL"] = "TOOL";
390
+ SpanKind2["RETRIEVER"] = "RETRIEVER";
391
+ SpanKind2["EMBEDDING"] = "EMBEDDING";
392
+ SpanKind2["CHAIN"] = "CHAIN";
393
+ return SpanKind2;
394
+ })(SpanKind || {});
395
+ OPERATION_BY_KIND = {
396
+ ["LLM" /* LLM */]: "chat",
397
+ ["TOOL" /* TOOL */]: "execute_tool",
398
+ ["EMBEDDING" /* EMBEDDING */]: "embeddings",
399
+ ["AGENT" /* AGENT */]: "invoke_agent",
400
+ ["RETRIEVER" /* RETRIEVER */]: "retrieval"
401
+ };
402
+ KIND_BY_OPERATION = {
403
+ chat: "LLM" /* LLM */,
404
+ text_completion: "LLM" /* LLM */,
405
+ generate_content: "LLM" /* LLM */,
406
+ execute_tool: "TOOL" /* TOOL */,
407
+ embeddings: "EMBEDDING" /* EMBEDDING */,
408
+ invoke_agent: "AGENT" /* AGENT */,
409
+ create_agent: "AGENT" /* AGENT */,
410
+ retrieval: "RETRIEVER" /* RETRIEVER */,
411
+ invoke_workflow: "CHAIN" /* CHAIN */,
412
+ plan: "CHAIN" /* CHAIN */
413
+ };
414
+ OTEL_KIND_BY_KIND = {
415
+ ["LLM" /* LLM */]: import_api2.SpanKind.CLIENT,
416
+ ["EMBEDDING" /* EMBEDDING */]: import_api2.SpanKind.CLIENT,
417
+ ["RETRIEVER" /* RETRIEVER */]: import_api2.SpanKind.CLIENT,
418
+ ["TOOL" /* TOOL */]: import_api2.SpanKind.INTERNAL,
419
+ ["AGENT" /* AGENT */]: import_api2.SpanKind.INTERNAL,
420
+ ["CHAIN" /* CHAIN */]: import_api2.SpanKind.INTERNAL
421
+ };
422
+ NAME_TARGET_BY_KIND = {
423
+ ["LLM" /* LLM */]: GEN_AI_REQUEST_MODEL,
424
+ ["EMBEDDING" /* EMBEDDING */]: GEN_AI_REQUEST_MODEL,
425
+ ["TOOL" /* TOOL */]: GEN_AI_TOOL_NAME,
426
+ ["AGENT" /* AGENT */]: GEN_AI_AGENT_NAME,
427
+ ["RETRIEVER" /* RETRIEVER */]: GEN_AI_DATA_SOURCE_ID
428
+ };
429
+ CHAIN_SPAN_NAME = "chain";
430
+ LLM_INPUT_MESSAGES_PREFIX = "llm.input_messages.";
431
+ LLM_OUTPUT_MESSAGES_PREFIX = "llm.output_messages.";
432
+ LLM_MESSAGE_ROLE = "message.role";
433
+ LLM_MESSAGE_CONTENT = "message.content";
434
+ LLM_MESSAGE_TOOL_CALL_ID = "message.tool_call_id";
435
+ LLM_MESSAGE_TOOL_CALLS_PREFIX = "message.tool_calls.";
436
+ LLM_TOOL_CALL_PREFIX = "tool_call.";
437
+ LLM_TOOL_CALL_ID = "id";
438
+ LLM_TOOL_CALL_FUNCTION_NAME = "function.name";
439
+ LLM_TOOL_CALL_FUNCTION_ARGUMENTS = "function.arguments";
440
+ LLM_MESSAGE_CONTENTS_PREFIX = "message.contents.";
441
+ LLM_MESSAGE_CONTENT_PREFIX = "message_content.";
442
+ LLM_INVOCATION_PARAMETERS = "llm.invocation_parameters";
443
+ INVOCATION_PARAMETERS_CONTENT_MEMBERS = ["tools", "functions"];
444
+ THIRD_PARTY_CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set([
445
+ // langfuse 3.15.0, langfuse/_client/attributes.py. Metadata is the caller's
446
+ // arbitrary payload (one key per member, see the prefixes below), and the
447
+ // OpenAI wrapper writes the exception's message into status_message.
448
+ "langfuse.observation.input",
449
+ "langfuse.observation.output",
450
+ "langfuse.trace.input",
451
+ "langfuse.trace.output",
452
+ "langfuse.observation.metadata",
453
+ "langfuse.trace.metadata",
454
+ "langfuse.observation.status_message",
455
+ "langfuse.experiment.metadata",
456
+ "langfuse.experiment.item.metadata",
457
+ "langfuse.experiment.item.expected_output",
458
+ // traceloop-sdk 0.62.3 (opentelemetry-semantic-conventions-ai 0.5.1):
459
+ // workflow/task I/O and the managed prompt's template and variables; the
460
+ // LangChain instrumentation mirrors entity I/O into gen_ai.task.*, and the
461
+ // MCP one writes each tool result to mcp.response.value.
462
+ "traceloop.entity.input",
463
+ "traceloop.entity.output",
464
+ "traceloop.prompt.template",
465
+ "traceloop.prompt.template_variables",
466
+ "gen_ai.task.input",
467
+ "gen_ai.task.output",
468
+ "mcp.response.value",
469
+ // logfire 5.1.1: the OpenAI/Anthropic integrations put the request and the
470
+ // reply in request_data/response_data and the messages in events; the OpenAI
471
+ // Agents one adds raw_input, the Response object, and a function span's
472
+ // arguments and result under bare input/output.
473
+ "request_data",
474
+ "response_data",
475
+ "events",
476
+ "all_messages_events",
477
+ "pydantic_ai.all_messages",
478
+ "raw_input",
479
+ "response",
480
+ "input",
481
+ "output",
482
+ "logfire.msg",
483
+ // mlflow-tracing 3.16.1, mlflow/tracing/constant.py. chunk.value is a
484
+ // streamed chunk, written on span events.
485
+ "mlflow.spanInputs",
486
+ "mlflow.spanOutputs",
487
+ "mlflow.chat.tools",
488
+ "mlflow.trace.intermediate_outputs",
489
+ "mlflow.chunk.value",
490
+ // openlit 1.45.0, openlit/semcov/__init__.py, each one written by at least
491
+ // one of its instrumentations. gen_ai.retrieval.query.text is also a GenAI
492
+ // convention key: the user's query, verbatim.
493
+ "gen_ai.retrieval.query.text",
494
+ "gen_ai.content.reasoning",
495
+ "gen_ai.content.revised_prompt",
496
+ "gen_ai.tool.args",
497
+ "gen_ai.response.tool_calls",
498
+ "gen_ai.workflow.input",
499
+ "gen_ai.workflow.output",
500
+ "gen_ai.framework.pipeline.input_data",
501
+ "gen_ai.framework.pipeline.output_data",
502
+ "gen_ai.framework.error.message",
503
+ "gen_ai.agent.context",
504
+ "gen_ai.agent.instructions",
505
+ "gen_ai.agent.goal",
506
+ "gen_ai.agent.action.tool_input",
507
+ "gen_ai.agent.final_result",
508
+ "gen_ai.agent.next_goal",
509
+ "gen_ai.agent.step_messages",
510
+ "gen_ai.memory.search.query",
511
+ "gen_ai.vectordb.search.query",
512
+ "gen_ai.extraction.instruction",
513
+ "mcp.tool.arguments",
514
+ "mcp.tool.result",
515
+ "mcp.result",
516
+ "mcp.params",
517
+ "mcp.sampling.messages",
518
+ "mcp.fastmcp.prompt.arguments",
519
+ "mcp.completion.argument.value",
520
+ "mcp.completion.context.arguments",
521
+ "mcp.completion.values",
522
+ "mcp.error.message",
523
+ // lmnr 0.7.64 (Laminar; the TypeScript SDK writes the same wire keys): the
524
+ // observed function's arguments and return value, the caller's metadata
525
+ // (one key per member, see the prefixes below), and the provider's whole
526
+ // reply its Anthropic and LiteLLM instrumentations write.
527
+ "lmnr.span.input",
528
+ "lmnr.span.output",
529
+ "lmnr.association.properties.metadata",
530
+ "lmnr.sdk.raw.response"
531
+ ]);
532
+ CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set([
533
+ INPUT_VALUE,
534
+ OUTPUT_VALUE,
535
+ GEN_AI_INPUT_MESSAGES,
536
+ GEN_AI_OUTPUT_MESSAGES,
537
+ // Sensitive per the GenAI conventions (semconv-genai#431): tool definitions
538
+ // routinely embed proprietary prompt engineering, and sometimes credentials
539
+ // or internal URLs in parameter defaults. gen_ai.tool.name stays: it is
540
+ // identity, not content.
541
+ "gen_ai.tool.description",
542
+ GEN_AI_TOOL_DEFINITIONS,
543
+ // GenAI semconv keys emitted by OTel-native instrumentations (@ai-sdk/otel,
544
+ // which init() registers for ai v7, among them; pinned 2026-09-14): the
545
+ // system prompt and each tool call's input and output are content in the
546
+ // same sense messages are. gen_ai.tool.call.id stays: identity.
547
+ "gen_ai.system_instructions",
548
+ "gen_ai.tool.call.arguments",
549
+ "gen_ai.tool.call.result",
550
+ // OpenInference TOOL-kind spans carry the definition under these two bare
551
+ // keys, sensitive for the reason gen_ai.tool.description is.
552
+ "tool.description",
553
+ "tool.parameters",
554
+ "gen_ai.prompt",
555
+ "gen_ai.completion",
556
+ "llm.input_messages",
557
+ "llm.output_messages",
558
+ // The prefix list below covers the flattened "llm.prompts." and
559
+ // "llm.prompt_template." forms, but an instrumentation may emit either as a
560
+ // single unflattened array attribute under the bare key, which no prefix
561
+ // matches. Both bare keys are therefore listed here as well.
562
+ "llm.prompts",
563
+ "llm.prompt_template",
564
+ // Every bundled OpenInference instrumentation emits tool definitions as
565
+ // llm.tools.{i}.tool.json_schema (pinned empirically 2026-09-14); covered
566
+ // by prefix below, bare key listed per the bare-key rule.
567
+ "llm.tools",
568
+ // The Vercel AI SDK's own telemetry keys survive the OpenInference
569
+ // transform untouched, and they carry the full prompt (messages AND tool
570
+ // definitions), response content, and tool-call I/O. Names, ids and models
571
+ // (ai.toolCall.name, ai.response.model, ...) are identity and stay.
572
+ "ai.prompt",
573
+ "ai.response.text",
574
+ "ai.response.object",
575
+ "ai.toolCall.args",
576
+ "ai.toolCall.result",
577
+ // Same family, keys enumerated from the ai v7 telemetry surface
578
+ // (2026-09-14): model reasoning, tool calls in the response, response
579
+ // files, embedding inputs, rerank documents, and the generateObject schema
580
+ // (a tool definition by another name).
581
+ "ai.response.reasoning",
582
+ "ai.response.toolCalls",
583
+ "ai.response.files",
584
+ "ai.value",
585
+ "ai.values",
586
+ "ai.documents",
587
+ "ai.schema",
588
+ "ai.schema.description",
589
+ // The OpenInference request-parameters bag, whole. Its membership is open
590
+ // and provider-defined (litellm and langchain put the request's tools array
591
+ // in it, and providers are free to add anything else), so a per-member
592
+ // blocklist is always one provider behind. Treating the whole bag as
593
+ // content is only safe because normalization runs BEFORE masking and has
594
+ // already promoted every member the rule table recognises onto
595
+ // gen_ai.request.*, so what reaches masking is exactly the set nothing has
596
+ // classified. A knob that matters is recovered by teaching the rule table
597
+ // its name, which is a reviewed decision; the default is that unclassified
598
+ // request data does not leave a process that asked for no content.
599
+ LLM_INVOCATION_PARAMETERS,
600
+ // The NAMED EXCEPTION to the rule that request parameters export in clear.
601
+ // That rule is right for scalar knobs (temperature, top_p, seed), but a
602
+ // parameter bag is not typed: a caller passing modelParameters: { tools }
603
+ // would otherwise export proprietary prompt engineering verbatim with
604
+ // captureContent: false explicitly set, while the very same array is
605
+ // stripped when it arrives as gen_ai.tool.definitions or inside
606
+ // llm.invocation_parameters. Per KEY, not per namespace: both namespaces
607
+ // stay non-content wholesale, which is what keeps their tie intact.
608
+ ...[GEN_AI_REQUEST_PREFIX, RIUS_REQUEST_PREFIX].flatMap(
609
+ (prefix) => INVOCATION_PARAMETERS_CONTENT_MEMBERS.map((member) => `${prefix}${member}`)
610
+ ),
611
+ ...THIRD_PARTY_CONTENT_ATTRIBUTES
612
+ ]);
613
+ CONTENT_ATTRIBUTE_PREFIXES = [
614
+ "llm.input_messages.",
615
+ "llm.output_messages.",
616
+ "gen_ai.prompt.",
617
+ "gen_ai.completion.",
618
+ "llm.prompts.",
619
+ "llm.prompt_template.",
620
+ "llm.tools.",
621
+ "ai.prompt.",
622
+ "langfuse.observation.metadata.",
623
+ "langfuse.trace.metadata.",
624
+ "traceloop.prompt.template_variables.",
625
+ "lmnr.association.properties.metadata."
626
+ ];
627
+ CONTENT_PREFIX_IDENTITY_ATTRIBUTES = /* @__PURE__ */ new Set([
628
+ "lmnr.association.properties.metadata.tool_call_id",
629
+ "lmnr.association.properties.metadata.agent.name",
630
+ "lmnr.association.properties.metadata.service.name"
631
+ ]);
632
+ CONTENT_ATTRIBUTE_SUFFIXES = [
633
+ ".document.content",
634
+ ".embedding.text"
635
+ ];
636
+ CONTENT_ATTRIBUTE_PREFIXED_SUFFIXES = [["llm.request.functions.", [".description", ".parameters", ".arguments"]]];
637
+ RIUS_SPAN_PENDING = "rius.span.pending";
638
+ RIUS_PARENT_FOREIGN = "rius.parent.foreign";
639
+ RIUS_SDK_GLOBAL_PROVIDER = "rius.sdk.global_provider";
640
+ PENDING_IDENTITY_ATTRIBUTES = /* @__PURE__ */ new Set([
641
+ OPENINFERENCE_SPAN_KIND,
642
+ GEN_AI_OPERATION_NAME,
643
+ GEN_AI_PROVIDER_NAME,
644
+ GEN_AI_TOOL_NAME,
645
+ // The two other halves of a tool call's identity, both caller-supplied at
646
+ // creation: which model tool-call this execution answers, and what kind of
647
+ // tool it is. A tool that hangs is exactly the span a live view needs to
648
+ // show attached to its call, so both must survive the snapshot.
649
+ GEN_AI_TOOL_CALL_ID,
650
+ GEN_AI_TOOL_TYPE,
651
+ // A property of the request, like the model parameters the prefix rule
652
+ // covers — but spelled outside `gen_ai.request.`, so it needs its own
653
+ // entry here or it would be silently dropped from every snapshot.
654
+ GEN_AI_OUTPUT_TYPE,
655
+ // The retrieval target is identity in the same sense the tool name is: a
656
+ // still-running retrieval must be attributable to the index it is hitting.
657
+ GEN_AI_DATA_SOURCE_ID,
658
+ // Equally a property of the request, so equally knowable at start. Its
659
+ // counterpart GEN_AI_RETRIEVAL_DOCUMENTS is deliberately absent: what came
660
+ // back cannot be known while the span is open.
661
+ GEN_AI_RETRIEVAL_TOP_K,
662
+ // Which agent a span invokes is chosen before the work starts, so a
663
+ // still-running agent is attributable in the live view. Distinct from the
664
+ // RESOURCE key of the same name, which says which PROCESS is running; these
665
+ // say which agent that process invoked here.
666
+ GEN_AI_AGENT_NAME,
667
+ GEN_AI_AGENT_ID,
668
+ // Same callee, same reason: the version of the agent being invoked is
669
+ // chosen before the work starts, so a still-running agent span can say
670
+ // which version of it is running.
671
+ GEN_AI_AGENT_VERSION,
672
+ // Protocol identity, not content: a still-running MCP call must be
673
+ // distinguishable from a local tool in the live view — the one place
674
+ // setting the marker at creation pays off.
675
+ MCP_METHOD_NAME,
676
+ MCP_PROTOCOL_VERSION,
677
+ // Identity, not content: a pending span must be groupable into its
678
+ // session while still running, that is the live view's whole point.
679
+ SESSION_ID,
680
+ // Same reason: a crashed run's partial spans must still be attributable
681
+ // to the user they served.
682
+ USER_ID,
683
+ // Routing, not content: a crashed run's snapshot must land in the same
684
+ // workspace its final span would have. Stripped at export either way.
685
+ WORKSPACE_ROUTE
686
+ ]);
687
+ PENDING_IDENTITY_PREFIXES = [
688
+ GEN_AI_REQUEST_PREFIX,
689
+ RIUS_REQUEST_PREFIX
690
+ ];
691
+ }
692
+ });
693
+
694
+ // src/agent.ts
695
+ function setConfiguredAgentName(name) {
696
+ configured = name;
697
+ }
698
+ function resolveAgentName(agentName, kind) {
699
+ if (kind !== "AGENT" /* AGENT */) return void 0;
700
+ if (agentName !== void 0) return agentName;
701
+ return namedConfiguredAgent();
702
+ }
703
+ function namedConfiguredAgent() {
704
+ return namedAgent(configured);
705
+ }
706
+ function withAgentScope(agentName, fn) {
707
+ return import_api3.context.with(import_api3.context.active().setValue(AGENT_SCOPE_KEY, agentName), fn);
708
+ }
709
+ function executingAgentName() {
710
+ const scoped = import_api3.context.active().getValue(AGENT_SCOPE_KEY);
711
+ return typeof scoped === "string" ? scoped : namedConfiguredAgent();
712
+ }
713
+ var import_api3, configured, AGENT_SCOPE_KEY;
714
+ var init_agent = __esm({
715
+ "src/agent.ts"() {
716
+ "use strict";
717
+ init_cjs_shims();
718
+ import_api3 = require("@opentelemetry/api");
719
+ init_config();
720
+ init_semconv();
721
+ AGENT_SCOPE_KEY = (0, import_api3.createContextKey)("rius-enclosing-agent-name");
722
+ }
723
+ });
724
+
122
725
  // src/delegatingProcessor.ts
123
726
  var DelegatingSpanProcessor;
124
727
  var init_delegatingProcessor = __esm({
@@ -157,28 +760,6 @@ var init_delegatingProcessor = __esm({
157
760
  }
158
761
  });
159
762
 
160
- // node_modules/@opentelemetry/core/build/esm/ExportResult.js
161
- var ExportResultCode;
162
- var init_ExportResult = __esm({
163
- "node_modules/@opentelemetry/core/build/esm/ExportResult.js"() {
164
- "use strict";
165
- init_cjs_shims();
166
- (function(ExportResultCode2) {
167
- ExportResultCode2[ExportResultCode2["SUCCESS"] = 0] = "SUCCESS";
168
- ExportResultCode2[ExportResultCode2["FAILED"] = 1] = "FAILED";
169
- })(ExportResultCode || (ExportResultCode = {}));
170
- }
171
- });
172
-
173
- // node_modules/@opentelemetry/core/build/esm/index.js
174
- var init_esm = __esm({
175
- "node_modules/@opentelemetry/core/build/esm/index.js"() {
176
- "use strict";
177
- init_cjs_shims();
178
- init_ExportResult();
179
- }
180
- });
181
-
182
763
  // src/exportHealth.ts
183
764
  var ExportOutcomeExporter;
184
765
  var init_exportHealth = __esm({
@@ -227,10 +808,371 @@ var init_exportHealth = __esm({
227
808
  }
228
809
  });
229
810
 
230
- // src/version.ts
231
- var import_node_module, SDK_VERSION;
232
- var init_version = __esm({
233
- "src/version.ts"() {
811
+ // src/foreign.ts
812
+ function delegating(provider) {
813
+ return typeof provider.getDelegate === "function";
814
+ }
815
+ function rememberOwnProvider(provider) {
816
+ ownProviders.add(provider);
817
+ }
818
+ function globalTracerProvider() {
819
+ let provider = import_api4.trace.getTracerProvider();
820
+ for (let hop = 0; hop < 8 && delegating(provider); hop += 1) {
821
+ const delegate = provider.getDelegate();
822
+ if (delegate === provider) break;
823
+ provider = delegate;
824
+ }
825
+ if (provider === NOOP_PROVIDER || provider.constructor?.name === "NoopTracerProvider") {
826
+ return void 0;
827
+ }
828
+ return provider;
829
+ }
830
+ function foreignGlobalProviderName() {
831
+ const provider = globalTracerProvider();
832
+ if (provider === void 0 || ownProviders.has(provider)) return void 0;
833
+ return provider.constructor?.name || "unknown";
834
+ }
835
+ function twinOf(span) {
836
+ if (span === void 0 || span === null) return span;
837
+ return shadows.get(span)?.twin ?? span;
838
+ }
839
+ function riusDecision(span) {
840
+ if (span === void 0 || span === null) return void 0;
841
+ if (dropped.has(span)) return false;
842
+ return shadows.has(span) ? true : void 0;
843
+ }
844
+ function stamped(span, shadow) {
845
+ const attributes = { ...span.attributes };
846
+ for (const [key, value] of Object.entries(shadow.twin.attributes)) {
847
+ const started = shadow.seed[key];
848
+ const own = attributes[key];
849
+ if (value !== started && own === started) attributes[key] = value;
850
+ }
851
+ return {
852
+ name: span.name,
853
+ kind: span.kind,
854
+ spanContext: () => span.spanContext(),
855
+ parentSpanContext: span.parentSpanContext,
856
+ startTime: span.startTime,
857
+ endTime: span.endTime,
858
+ duration: span.duration,
859
+ status: { ...span.status },
860
+ attributes,
861
+ links: span.links.map((link) => ({ ...link, attributes: { ...link.attributes } })),
862
+ events: span.events.map((event) => ({ ...event, attributes: { ...event.attributes } })),
863
+ resource: span.resource,
864
+ instrumentationScope: span.instrumentationScope,
865
+ droppedAttributesCount: span.droppedAttributesCount,
866
+ droppedEventsCount: span.droppedEventsCount,
867
+ droppedLinksCount: span.droppedLinksCount,
868
+ ended: span.ended
869
+ };
870
+ }
871
+ function attachProcessor(provider, processor) {
872
+ const legacy = provider;
873
+ if (typeof legacy.addSpanProcessor === "function") {
874
+ legacy.addSpanProcessor(processor);
875
+ return true;
876
+ }
877
+ const list = provider._activeSpanProcessor?._spanProcessors;
878
+ if (Array.isArray(list)) {
879
+ list.push(processor);
880
+ return true;
881
+ }
882
+ return false;
883
+ }
884
+ function bridge(provider, pipeline, sampler, limits) {
885
+ const shadow = new ShadowPipeline(pipeline, sampler, limits);
886
+ let forwarder = forwarders.get(provider);
887
+ if (forwarder === void 0) {
888
+ forwarder = new Forwarder();
889
+ if (!attachProcessor(provider, forwarder)) return void 0;
890
+ forwarders.set(provider, forwarder);
891
+ }
892
+ forwarder.attach(shadow);
893
+ return new Bridge(forwarder, shadow);
894
+ }
895
+ var import_api4, import_resources, import_sdk_trace_base, ownProviders, NOOP_PROVIDER, ForeignParentDetector, shadows, dropped, ALWAYS_ON, ALWAYS_OFF, BridgeAwareSampler, TwinSpan, ShadowPipeline, Forwarder, forwarders, Bridge, ResourceAdoptingExporter;
896
+ var init_foreign = __esm({
897
+ "src/foreign.ts"() {
898
+ "use strict";
899
+ init_cjs_shims();
900
+ import_api4 = require("@opentelemetry/api");
901
+ import_resources = require("@opentelemetry/resources");
902
+ import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
903
+ init_semconv();
904
+ ownProviders = /* @__PURE__ */ new WeakSet();
905
+ NOOP_PROVIDER = new import_api4.ProxyTracerProvider().getDelegate();
906
+ ForeignParentDetector = class {
907
+ seen = /* @__PURE__ */ new WeakSet();
908
+ openIds = /* @__PURE__ */ new Set();
909
+ globalProvider;
910
+ /** Spans flagged since this detector was built. Cumulative; never reset. */
911
+ count = 0;
912
+ constructor(globalProvider) {
913
+ this.globalProvider = globalProvider;
914
+ }
915
+ onStart(span, parentContext) {
916
+ const parent = span.parentSpanContext;
917
+ const foreign = parent !== void 0 && (0, import_api4.isSpanContextValid)(parent) && // A remote parent belongs to another PROCESS: ordinary distributed
918
+ // tracing, where the parent was never expected here.
919
+ parent.isRemote !== true && !this.startedHere(parent, parentContext);
920
+ this.remember(span);
921
+ if (!foreign) return;
922
+ this.count += 1;
923
+ span.setAttribute(RIUS_PARENT_FOREIGN, true);
924
+ if (this.count === 1) this.warn(span.name);
925
+ }
926
+ startedHere(parent, parentContext) {
927
+ if (this.seen.has(twinOf(import_api4.trace.getSpan(parentContext)))) return true;
928
+ return this.openIds.has(parent.spanId);
929
+ }
930
+ remember(span) {
931
+ this.seen.add(span);
932
+ this.openIds.add(span.spanContext().spanId);
933
+ }
934
+ warn(name) {
935
+ const owner = this.globalProvider === void 0 ? "another OpenTelemetry provider in this process" : `another OpenTelemetry SDK (${this.globalProvider})`;
936
+ const first = `first: ${JSON.stringify(name)}, flagged ${RIUS_PARENT_FOREIGN}`;
937
+ console.warn(
938
+ `[rius] ${owner} owns the global tracer provider; Rius spans have parents it never receives (${first}). Set RIUS_BRIDGE_FOREIGN_PROVIDER=true or init({ bridgeForeignProvider: true }) to send them to Rius.`
939
+ );
940
+ }
941
+ onEnd(span) {
942
+ this.openIds.delete(span.spanContext().spanId);
943
+ }
944
+ async forceFlush() {
945
+ }
946
+ async shutdown() {
947
+ }
948
+ };
949
+ shadows = /* @__PURE__ */ new WeakMap();
950
+ dropped = /* @__PURE__ */ new WeakSet();
951
+ ALWAYS_ON = {
952
+ shouldSample: () => ({ decision: import_sdk_trace_base.SamplingDecision.RECORD_AND_SAMPLED }),
953
+ toString: () => "AlwaysOnSampler"
954
+ };
955
+ ALWAYS_OFF = {
956
+ shouldSample: () => ({ decision: import_sdk_trace_base.SamplingDecision.NOT_RECORD }),
957
+ toString: () => "AlwaysOffSampler"
958
+ };
959
+ BridgeAwareSampler = class {
960
+ inner;
961
+ constructor(inner) {
962
+ this.inner = inner;
963
+ }
964
+ shouldSample(context2, traceId, spanName, spanKind, attributes, links) {
965
+ const kept = riusDecision(import_api4.trace.getSpan(context2));
966
+ const sampler = kept === void 0 ? this.inner : kept ? ALWAYS_ON : ALWAYS_OFF;
967
+ return sampler.shouldSample(context2, traceId, spanName, spanKind, attributes, links);
968
+ }
969
+ toString() {
970
+ return `BridgeAware{${this.inner.toString()}}`;
971
+ }
972
+ };
973
+ TwinSpan = class {
974
+ attributes = {};
975
+ links;
976
+ events = [];
977
+ startTime;
978
+ endTime = [0, 0];
979
+ duration = [0, 0];
980
+ resource;
981
+ instrumentationScope;
982
+ kind;
983
+ parentSpanContext;
984
+ droppedAttributesCount = 0;
985
+ droppedEventsCount = 0;
986
+ droppedLinksCount = 0;
987
+ name;
988
+ status = { code: import_api4.SpanStatusCode.UNSET };
989
+ ended = false;
990
+ context;
991
+ attributeCountLimit;
992
+ attributeValueLengthLimit;
993
+ constructor(span, limits) {
994
+ this.context = span.spanContext();
995
+ this.name = span.name;
996
+ this.kind = span.kind;
997
+ this.parentSpanContext = span.parentSpanContext;
998
+ this.startTime = span.startTime;
999
+ this.resource = span.resource;
1000
+ this.instrumentationScope = span.instrumentationScope;
1001
+ this.links = [...span.links];
1002
+ this.attributeCountLimit = limits?.attributeCountLimit ?? Number.POSITIVE_INFINITY;
1003
+ this.attributeValueLengthLimit = limits?.attributeValueLengthLimit ?? Number.POSITIVE_INFINITY;
1004
+ Object.assign(this.attributes, span.attributes);
1005
+ }
1006
+ spanContext() {
1007
+ return this.context;
1008
+ }
1009
+ setAttribute(key, value) {
1010
+ if (value === void 0 || key.length === 0) return this;
1011
+ if (!(key in this.attributes) && Object.keys(this.attributes).length >= this.attributeCountLimit) {
1012
+ this.droppedAttributesCount += 1;
1013
+ return this;
1014
+ }
1015
+ this.attributes[key] = this.truncate(value);
1016
+ return this;
1017
+ }
1018
+ /**
1019
+ * The value-length limit, applied as the SDK's own Span applies it: strings
1020
+ * are cut, string arrays are cut element by element, everything else passes.
1021
+ */
1022
+ truncate(value) {
1023
+ const limit = this.attributeValueLengthLimit;
1024
+ if (limit === Number.POSITIVE_INFINITY) return value;
1025
+ if (typeof value === "string") return value.substring(0, limit);
1026
+ if (Array.isArray(value)) {
1027
+ return value.map((item) => typeof item === "string" ? item.substring(0, limit) : item);
1028
+ }
1029
+ return value;
1030
+ }
1031
+ setAttributes(attributes) {
1032
+ for (const [key, value] of Object.entries(attributes)) this.setAttribute(key, value);
1033
+ return this;
1034
+ }
1035
+ addEvent(name, attributesOrStartTime, _timeStamp) {
1036
+ const attributes = attributesOrStartTime !== void 0 && typeof attributesOrStartTime === "object" ? attributesOrStartTime : void 0;
1037
+ this.events.push({ name, attributes, time: this.startTime, droppedAttributesCount: 0 });
1038
+ return this;
1039
+ }
1040
+ addLink(link) {
1041
+ this.links.push(link);
1042
+ return this;
1043
+ }
1044
+ addLinks(links) {
1045
+ this.links.push(...links);
1046
+ return this;
1047
+ }
1048
+ setStatus(status) {
1049
+ this.status = status;
1050
+ return this;
1051
+ }
1052
+ updateName(name) {
1053
+ this.name = name;
1054
+ return this;
1055
+ }
1056
+ end(_endTime) {
1057
+ this.ended = true;
1058
+ }
1059
+ isRecording() {
1060
+ return !this.ended;
1061
+ }
1062
+ /**
1063
+ * An own property rather than a prototype method on purpose: errorType.ts
1064
+ * replaces it per span instance, which a readonly method would refuse.
1065
+ */
1066
+ recordException = () => {
1067
+ };
1068
+ };
1069
+ ShadowPipeline = class {
1070
+ pipeline;
1071
+ sampler;
1072
+ limits;
1073
+ constructor(pipeline, sampler, limits) {
1074
+ this.pipeline = pipeline;
1075
+ this.sampler = sampler;
1076
+ this.limits = limits;
1077
+ }
1078
+ onStart(span, parentContext) {
1079
+ const result = this.sampler.shouldSample(
1080
+ parentContext,
1081
+ span.spanContext().traceId,
1082
+ span.name,
1083
+ span.kind,
1084
+ span.attributes,
1085
+ span.links
1086
+ );
1087
+ if (result.decision !== import_sdk_trace_base.SamplingDecision.RECORD_AND_SAMPLED) {
1088
+ dropped.add(span);
1089
+ return;
1090
+ }
1091
+ const twin = new TwinSpan(span, this.limits);
1092
+ shadows.set(span, { twin, seed: { ...span.attributes } });
1093
+ this.pipeline.onStart(twin, parentContext);
1094
+ }
1095
+ onEnd(span) {
1096
+ const shadow = shadows.get(span);
1097
+ if (shadow !== void 0) this.pipeline.onEnd(stamped(span, shadow));
1098
+ }
1099
+ async forceFlush() {
1100
+ }
1101
+ async shutdown() {
1102
+ }
1103
+ };
1104
+ Forwarder = class {
1105
+ target;
1106
+ attach(target) {
1107
+ this.target = target;
1108
+ }
1109
+ release(target) {
1110
+ if (this.target === target) this.target = void 0;
1111
+ }
1112
+ onStart(span, parentContext) {
1113
+ this.target?.onStart(span, parentContext);
1114
+ }
1115
+ onEnd(span) {
1116
+ this.target?.onEnd(span);
1117
+ }
1118
+ async forceFlush() {
1119
+ }
1120
+ async shutdown() {
1121
+ }
1122
+ };
1123
+ forwarders = /* @__PURE__ */ new WeakMap();
1124
+ Bridge = class {
1125
+ forwarder;
1126
+ pipeline;
1127
+ constructor(forwarder, pipeline) {
1128
+ this.forwarder = forwarder;
1129
+ this.pipeline = pipeline;
1130
+ }
1131
+ /** Stop feeding the other provider's spans into this pipeline. Idempotent. */
1132
+ release() {
1133
+ this.forwarder.release(this.pipeline);
1134
+ }
1135
+ };
1136
+ ResourceAdoptingExporter = class {
1137
+ inner;
1138
+ resource;
1139
+ adoptedResources = /* @__PURE__ */ new WeakMap();
1140
+ constructor(inner, resource) {
1141
+ this.inner = inner;
1142
+ this.resource = resource;
1143
+ }
1144
+ export(spans, resultCallback) {
1145
+ this.inner.export(
1146
+ spans.map((span) => this.adopted(span)),
1147
+ resultCallback
1148
+ );
1149
+ }
1150
+ adopted(span) {
1151
+ if (span.resource === this.resource) return span;
1152
+ let merged = this.adoptedResources.get(span.resource);
1153
+ if (merged === void 0) {
1154
+ merged = (0, import_resources.resourceFromAttributes)(
1155
+ { ...span.resource.attributes, ...this.resource.attributes },
1156
+ { schemaUrl: this.resource.schemaUrl ?? span.resource.schemaUrl }
1157
+ );
1158
+ this.adoptedResources.set(span.resource, merged);
1159
+ }
1160
+ return { ...span, spanContext: () => span.spanContext(), resource: merged };
1161
+ }
1162
+ shutdown() {
1163
+ return this.inner.shutdown();
1164
+ }
1165
+ forceFlush() {
1166
+ return this.inner.forceFlush?.() ?? Promise.resolve();
1167
+ }
1168
+ };
1169
+ }
1170
+ });
1171
+
1172
+ // src/version.ts
1173
+ var import_node_module, SDK_VERSION;
1174
+ var init_version = __esm({
1175
+ "src/version.ts"() {
234
1176
  "use strict";
235
1177
  init_cjs_shims();
236
1178
  import_node_module = require("module");
@@ -308,6 +1250,7 @@ var init_heartbeat = __esm({
308
1250
  pingTimeoutMs;
309
1251
  finalPingTimeoutMs;
310
1252
  send;
1253
+ foreignParentSpans;
311
1254
  timer;
312
1255
  stopped = false;
313
1256
  deliveryWarned = false;
@@ -319,6 +1262,7 @@ var init_heartbeat = __esm({
319
1262
  this.pingTimeoutMs = opts.pingTimeoutMs ?? DEFAULT_PING_TIMEOUT_MS;
320
1263
  this.finalPingTimeoutMs = opts.finalPingTimeoutMs ?? DEFAULT_FINAL_PING_TIMEOUT_MS;
321
1264
  this.send = opts.transport ?? httpTransport(opts.url, opts.headers);
1265
+ this.foreignParentSpans = opts.foreignParentSpans;
322
1266
  }
323
1267
  /** Starts pinging: an immediate first ping, then every `intervalMs`. */
324
1268
  start() {
@@ -348,182 +1292,24 @@ var init_heartbeat = __esm({
348
1292
  open_traces: openIds.slice(0, OPEN_TRACES_CAP),
349
1293
  open_trace_count: openIds.length
350
1294
  };
1295
+ if (this.foreignParentSpans !== void 0) {
1296
+ payload.foreign_parent_spans = this.foreignParentSpans();
1297
+ }
351
1298
  if (stopped) payload.stopped = true;
352
1299
  return payload;
353
1300
  }
354
1301
  async ping(timeoutMs, stopped) {
355
1302
  try {
356
- await this.send(this.buildPayload(stopped), timeoutMs);
357
- } catch (error) {
358
- if (!this.deliveryWarned) {
359
- this.deliveryWarned = true;
360
- const message = error instanceof Error ? error.message : String(error);
361
- console.warn(`[rius] heartbeat delivery failed (${message}); further failures are silent.`);
362
- }
363
- }
364
- }
365
- };
366
- }
367
- });
368
-
369
- // src/semconv.ts
370
- function kindAttributes(kind) {
371
- const attributes = { [OPENINFERENCE_SPAN_KIND]: kind };
372
- const operation = OPERATION_BY_KIND[kind];
373
- if (operation !== void 0) attributes[GEN_AI_OPERATION_NAME] = operation;
374
- return attributes;
375
- }
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_METHOD_NAME, MCP_METHOD_TOOLS_CALL, MCP_PROTOCOL_VERSION, ERROR_TYPE, ERROR_TYPE_TOOL_ERROR, 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;
377
- var init_semconv = __esm({
378
- "src/semconv.ts"() {
379
- "use strict";
380
- init_cjs_shims();
381
- TRACER_NAME = "glassflow";
382
- SERVICE_INSTANCE_ID = "service.instance.id";
383
- OPENINFERENCE_SPAN_KIND = "openinference.span.kind";
384
- INPUT_VALUE = "input.value";
385
- OUTPUT_VALUE = "output.value";
386
- SESSION_ID = "session.id";
387
- USER_ID = "user.id";
388
- WORKSPACE_ROUTE = "rius.workspace";
389
- GEN_AI_OPERATION_NAME = "gen_ai.operation.name";
390
- GEN_AI_PROVIDER_NAME = "gen_ai.provider.name";
391
- GEN_AI_REQUEST_MODEL = "gen_ai.request.model";
392
- GEN_AI_REQUEST_REASONING_LEVEL = "gen_ai.request.reasoning.level";
393
- GEN_AI_RESPONSE_MODEL = "gen_ai.response.model";
394
- GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens";
395
- GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens";
396
- GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens";
397
- GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS = "gen_ai.usage.cache_write.input_tokens";
398
- GEN_AI_USAGE_REASONING_OUTPUT_TOKENS = "gen_ai.usage.reasoning.output_tokens";
399
- GEN_AI_INPUT_MESSAGES = "gen_ai.input.messages";
400
- GEN_AI_OUTPUT_MESSAGES = "gen_ai.output.messages";
401
- GEN_AI_RESPONSE_FINISH_REASONS = "gen_ai.response.finish_reasons";
402
- GEN_AI_TOOL_NAME = "gen_ai.tool.name";
403
- GEN_AI_TOOL_DEFINITIONS = "gen_ai.tool.definitions";
404
- GEN_AI_REQUEST_PREFIX = "gen_ai.request.";
405
- MCP_METHOD_NAME = "mcp.method.name";
406
- MCP_METHOD_TOOLS_CALL = "tools/call";
407
- MCP_PROTOCOL_VERSION = "mcp.protocol.version";
408
- ERROR_TYPE = "error.type";
409
- ERROR_TYPE_TOOL_ERROR = "tool_error";
410
- MCP_RESULT_TYPE = "mcp.result_type";
411
- GEN_AI_FIRST_TOKEN_EVENT = "gen_ai.first_token";
412
- SpanKind = /* @__PURE__ */ ((SpanKind2) => {
413
- SpanKind2["AGENT"] = "AGENT";
414
- SpanKind2["LLM"] = "LLM";
415
- SpanKind2["TOOL"] = "TOOL";
416
- SpanKind2["RETRIEVER"] = "RETRIEVER";
417
- SpanKind2["EMBEDDING"] = "EMBEDDING";
418
- SpanKind2["CHAIN"] = "CHAIN";
419
- return SpanKind2;
420
- })(SpanKind || {});
421
- OPERATION_BY_KIND = {
422
- ["LLM" /* LLM */]: "chat",
423
- ["TOOL" /* TOOL */]: "execute_tool",
424
- ["EMBEDDING" /* EMBEDDING */]: "embeddings",
425
- ["AGENT" /* AGENT */]: "invoke_agent"
426
- };
427
- CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set([
428
- INPUT_VALUE,
429
- OUTPUT_VALUE,
430
- GEN_AI_INPUT_MESSAGES,
431
- GEN_AI_OUTPUT_MESSAGES,
432
- // Sensitive per the GenAI conventions (semconv-genai#431): tool definitions
433
- // routinely embed proprietary prompt engineering, and sometimes credentials
434
- // or internal URLs in parameter defaults. gen_ai.tool.name stays: it is
435
- // identity, not content.
436
- "gen_ai.tool.description",
437
- GEN_AI_TOOL_DEFINITIONS,
438
- // GenAI semconv keys emitted by OTel-native instrumentations (@ai-sdk/otel,
439
- // which init() registers for ai v7, among them; pinned 2026-09-14): the
440
- // system prompt and each tool call's input and output are content in the
441
- // same sense messages are. gen_ai.tool.call.id stays: identity.
442
- "gen_ai.system_instructions",
443
- "gen_ai.tool.call.arguments",
444
- "gen_ai.tool.call.result",
445
- // OpenInference TOOL-kind spans carry the definition under these two bare
446
- // keys, sensitive for the reason gen_ai.tool.description is.
447
- "tool.description",
448
- "tool.parameters",
449
- "gen_ai.prompt",
450
- "gen_ai.completion",
451
- "llm.input_messages",
452
- "llm.output_messages",
453
- // The prefix list below covers the flattened "llm.prompts." and
454
- // "llm.prompt_template." forms, but an instrumentation may emit either as a
455
- // single unflattened array attribute under the bare key, which no prefix
456
- // matches. Both bare keys are therefore listed here as well.
457
- "llm.prompts",
458
- "llm.prompt_template",
459
- "mlflow.spanInputs",
460
- "mlflow.spanOutputs",
461
- "traceloop.entity.input",
462
- "traceloop.entity.output",
463
- // Every bundled OpenInference instrumentation emits tool definitions as
464
- // llm.tools.{i}.tool.json_schema (pinned empirically 2026-09-14); covered
465
- // by prefix below, bare key listed per the bare-key rule.
466
- "llm.tools",
467
- // The Vercel AI SDK's own telemetry keys survive the OpenInference
468
- // transform untouched, and they carry the full prompt (messages AND tool
469
- // definitions), response content, and tool-call I/O. Names, ids and models
470
- // (ai.toolCall.name, ai.response.model, ...) are identity and stay.
471
- "ai.prompt",
472
- "ai.response.text",
473
- "ai.response.object",
474
- "ai.toolCall.args",
475
- "ai.toolCall.result",
476
- // Same family, keys enumerated from the ai v7 telemetry surface
477
- // (2026-09-14): model reasoning, tool calls in the response, response
478
- // files, embedding inputs, rerank documents, and the generateObject schema
479
- // (a tool definition by another name).
480
- "ai.response.reasoning",
481
- "ai.response.toolCalls",
482
- "ai.response.files",
483
- "ai.value",
484
- "ai.values",
485
- "ai.documents",
486
- "ai.schema",
487
- "ai.schema.description"
488
- ]);
489
- LLM_INVOCATION_PARAMETERS = "llm.invocation_parameters";
490
- INVOCATION_PARAMETERS_CONTENT_MEMBERS = ["tools", "functions"];
491
- CONTENT_ATTRIBUTE_PREFIXES = [
492
- "llm.input_messages.",
493
- "llm.output_messages.",
494
- "gen_ai.prompt.",
495
- "gen_ai.completion.",
496
- "llm.prompts.",
497
- "llm.prompt_template.",
498
- "llm.tools.",
499
- "ai.prompt."
500
- ];
501
- CONTENT_ATTRIBUTE_SUFFIXES = [
502
- ".document.content",
503
- ".embedding.text"
504
- ];
505
- GLASSFLOW_SPAN_PENDING = "glassflow.span.pending";
506
- PENDING_IDENTITY_ATTRIBUTES = /* @__PURE__ */ new Set([
507
- OPENINFERENCE_SPAN_KIND,
508
- GEN_AI_OPERATION_NAME,
509
- GEN_AI_PROVIDER_NAME,
510
- GEN_AI_TOOL_NAME,
511
- // Protocol identity, not content: a still-running MCP call must be
512
- // distinguishable from a local tool in the live view — the one place
513
- // setting the marker at creation pays off.
514
- MCP_METHOD_NAME,
515
- MCP_PROTOCOL_VERSION,
516
- // Identity, not content: a pending span must be groupable into its
517
- // session while still running, that is the live view's whole point.
518
- SESSION_ID,
519
- // Same reason: a crashed run's partial spans must still be attributable
520
- // to the user they served.
521
- USER_ID,
522
- // Routing, not content: a crashed run's snapshot must land in the same
523
- // workspace its final span would have. Stripped at export either way.
524
- WORKSPACE_ROUTE
525
- ]);
526
- PENDING_IDENTITY_PREFIXES = [GEN_AI_REQUEST_PREFIX];
1303
+ await this.send(this.buildPayload(stopped), timeoutMs);
1304
+ } catch (error) {
1305
+ if (!this.deliveryWarned) {
1306
+ this.deliveryWarned = true;
1307
+ const message = error instanceof Error ? error.message : String(error);
1308
+ console.warn(`[rius] heartbeat delivery failed (${message}); further failures are silent.`);
1309
+ }
1310
+ }
1311
+ }
1312
+ };
527
1313
  }
528
1314
  });
529
1315
 
@@ -568,6 +1354,15 @@ function toAttributeValue(value) {
568
1354
  return "[unserializable]";
569
1355
  }
570
1356
  }
1357
+ function attributeValue(value) {
1358
+ if (value === void 0 || value === null) return void 0;
1359
+ if (Array.isArray(value)) {
1360
+ if (value.every((v) => typeof v === "string")) return value;
1361
+ if (value.every((v) => typeof v === "number")) return value;
1362
+ if (value.every((v) => typeof v === "boolean")) return value;
1363
+ }
1364
+ return toAttributeValue(value);
1365
+ }
571
1366
  var MAX_ATTR_CHARS, TRUNCATION_MARKER;
572
1367
  var init_serde = __esm({
573
1368
  "src/serde.ts"() {
@@ -578,18 +1373,75 @@ var init_serde = __esm({
578
1373
  }
579
1374
  });
580
1375
 
1376
+ // src/errorType.ts
1377
+ function className(value) {
1378
+ const ctor = Object.getPrototypeOf(value)?.constructor;
1379
+ return typeof ctor === "function" && ctor.name !== "" ? ctor.name : void 0;
1380
+ }
1381
+ function packageOf(error) {
1382
+ for (let proto = Object.getPrototypeOf(error); proto !== null; ) {
1383
+ const ctor = Object.hasOwn(proto, "constructor") ? proto.constructor : void 0;
1384
+ if (typeof ctor === "function" && Object.hasOwn(PACKAGE_OF_BASE_CLASS, ctor.name)) {
1385
+ return PACKAGE_OF_BASE_CLASS[ctor.name];
1386
+ }
1387
+ proto = Object.getPrototypeOf(proto);
1388
+ }
1389
+ return void 0;
1390
+ }
1391
+ function errorType(error) {
1392
+ if (!(error instanceof Error)) return typeof error;
1393
+ try {
1394
+ const cls = className(error);
1395
+ const bare = error.name === "Error" && cls !== void 0 ? cls : error.name;
1396
+ const pkg = packageOf(error);
1397
+ return pkg === void 0 ? bare : `${pkg}.${bare}`;
1398
+ } catch {
1399
+ return error.name;
1400
+ }
1401
+ }
1402
+ function exceptionForRecording(exception) {
1403
+ if (!(exception instanceof Error)) return exception;
1404
+ const type = errorType(exception);
1405
+ if (type === exception.name) return exception;
1406
+ return { name: type, message: exception.message, stack: exception.stack };
1407
+ }
1408
+ function qualifyRecordedExceptions(span) {
1409
+ const original = span.recordException;
1410
+ if (typeof original !== "function" || original[WRAPPED] === true) return;
1411
+ const wrapped = Object.assign(
1412
+ (exception, time) => original.call(span, exceptionForRecording(exception), time),
1413
+ { [WRAPPED]: true }
1414
+ );
1415
+ try {
1416
+ span.recordException = wrapped;
1417
+ } catch {
1418
+ }
1419
+ }
1420
+ var PACKAGE_OF_BASE_CLASS, WRAPPED;
1421
+ var init_errorType = __esm({
1422
+ "src/errorType.ts"() {
1423
+ "use strict";
1424
+ init_cjs_shims();
1425
+ PACKAGE_OF_BASE_CLASS = {
1426
+ AnthropicError: "anthropic",
1427
+ OpenAIError: "openai"
1428
+ };
1429
+ WRAPPED = /* @__PURE__ */ Symbol("rius.recordException");
1430
+ }
1431
+ });
1432
+
581
1433
  // src/user.ts
582
1434
  function withUser(userId, fn) {
583
- return import_api.context.with(import_api.context.active().setValue(USER_KEY, userId), () => fn(userId));
1435
+ return import_api5.context.with(import_api5.context.active().setValue(USER_KEY, userId), () => fn(userId));
584
1436
  }
585
- var import_api, USER_KEY, UserSpanProcessor;
1437
+ var import_api5, USER_KEY, UserSpanProcessor;
586
1438
  var init_user = __esm({
587
1439
  "src/user.ts"() {
588
1440
  "use strict";
589
1441
  init_cjs_shims();
590
- import_api = require("@opentelemetry/api");
1442
+ import_api5 = require("@opentelemetry/api");
591
1443
  init_semconv();
592
- USER_KEY = (0, import_api.createContextKey)("rius-user-id");
1444
+ USER_KEY = (0, import_api5.createContextKey)("rius-user-id");
593
1445
  UserSpanProcessor = class {
594
1446
  onStart(span, parentContext) {
595
1447
  const value = parentContext.getValue(USER_KEY);
@@ -610,28 +1462,79 @@ function configure(observation, options) {
610
1462
  if (options.input !== void 0) observation.setInput(options.input);
611
1463
  return observation;
612
1464
  }
613
- function creationAttributes(options) {
614
- const attributes = { ...kindAttributes(options.kind ?? "CHAIN" /* CHAIN */), ...options.attributes };
1465
+ function resolveToolName(options, name) {
1466
+ if (options.toolName !== void 0) return options.toolName;
1467
+ if (name === void 0) return void 0;
1468
+ if (options.kind === "TOOL" /* TOOL */) {
1469
+ warnOnce(
1470
+ `A span named "${name}" with kind TOOL is naming the tool as well as the span; gen_ai.tool.name will be "${name}". Pass toolName to set them separately. A future major release will stop deriving the tool name from the span name.`
1471
+ );
1472
+ }
1473
+ return name;
1474
+ }
1475
+ function warnOnce(message) {
1476
+ if (warned.has(message)) return;
1477
+ warned.add(message);
1478
+ console.warn(`[rius] ${message}`);
1479
+ }
1480
+ function creationAttributes(name, options) {
1481
+ const kind = options.kind ?? "CHAIN" /* CHAIN */;
1482
+ const attributes = {
1483
+ ...kindAttributes(kind, resolveToolName(options, name)),
1484
+ ...options.attributes
1485
+ };
615
1486
  if (options.userId !== void 0) attributes[USER_ID] = options.userId;
1487
+ if (kind === "RETRIEVER" /* RETRIEVER */) {
1488
+ if (options.dataSourceId !== void 0)
1489
+ attributes[GEN_AI_DATA_SOURCE_ID] = options.dataSourceId;
1490
+ if (options.topK !== void 0) attributes[GEN_AI_RETRIEVAL_TOP_K] = options.topK;
1491
+ }
1492
+ if (kind === "TOOL" /* TOOL */) {
1493
+ const executedBy = executingAgentName();
1494
+ if (executedBy !== void 0) attributes[GEN_AI_AGENT_NAME] = executedBy;
1495
+ if (options.toolCallId !== void 0) attributes[GEN_AI_TOOL_CALL_ID] = options.toolCallId;
1496
+ if (options.toolType !== void 0) attributes[GEN_AI_TOOL_TYPE] = options.toolType;
1497
+ }
1498
+ if (kind === "AGENT" /* AGENT */) {
1499
+ const agentName = resolveAgentName(options.agentName, kind);
1500
+ if (agentName !== void 0) attributes[GEN_AI_AGENT_NAME] = agentName;
1501
+ if (options.agentId !== void 0) attributes[GEN_AI_AGENT_ID] = options.agentId;
1502
+ if (options.agentVersion !== void 0) attributes[GEN_AI_AGENT_VERSION] = options.agentVersion;
1503
+ }
616
1504
  return attributes;
617
1505
  }
618
- function startSpan(name, options = {}) {
619
- const span = getTracer().startSpan(name, {
620
- kind: options.otelKind,
621
- attributes: creationAttributes(options)
1506
+ function resolveName(name, kind, attributes) {
1507
+ return name ?? composeSpanName(kind ?? "CHAIN" /* CHAIN */, attributes);
1508
+ }
1509
+ function splitScopedArgs(first, second, third) {
1510
+ if (typeof first === "function") return { name: void 0, options: {}, fn: first };
1511
+ if (typeof first === "string") {
1512
+ return typeof second === "function" ? { name: first, options: {}, fn: second } : { name: first, options: second ?? {}, fn: third };
1513
+ }
1514
+ return { name: void 0, options: first, fn: second };
1515
+ }
1516
+ function startSpan(nameOrOptions, maybeOptions) {
1517
+ const [name, options] = typeof nameOrOptions === "string" ? [nameOrOptions, maybeOptions ?? {}] : [void 0, nameOrOptions ?? {}];
1518
+ const attributes = creationAttributes(name, options);
1519
+ const span = getTracer().startSpan(resolveName(name, options.kind, attributes), {
1520
+ kind: options.otelKind ?? otelSpanKind(options.kind ?? "CHAIN" /* CHAIN */),
1521
+ attributes
622
1522
  });
623
1523
  return configure(new Observation(span), options);
624
1524
  }
625
- function startAsCurrentSpan(name, optionsOrFn, maybeFn) {
626
- const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
627
- return runActive(
628
- name,
629
- creationAttributes(options),
1525
+ function startAsCurrentSpan(first, second, third) {
1526
+ const { name, options, fn } = splitScopedArgs(first, second, third);
1527
+ const attributes = creationAttributes(name, options);
1528
+ const run = () => runActive(
1529
+ resolveName(name, options.kind, attributes),
1530
+ attributes,
630
1531
  options.userId,
631
1532
  (span) => configure(new Observation(span), options),
632
1533
  fn,
633
- options.otelKind
1534
+ options.otelKind ?? otelSpanKind(options.kind ?? "CHAIN" /* CHAIN */)
634
1535
  );
1536
+ const scopeName = (options.kind ?? "CHAIN" /* CHAIN */) === "AGENT" /* AGENT */ ? attributes[GEN_AI_AGENT_NAME] : void 0;
1537
+ return typeof scopeName === "string" ? withAgentScope(scopeName, run) : run();
635
1538
  }
636
1539
  function runActive(name, attributes, userId, makeHandle, fn, otelKind) {
637
1540
  const run = () => getTracer().startActiveSpan(name, { kind: otelKind, attributes }, async (span) => {
@@ -647,13 +1550,15 @@ function runActive(name, attributes, userId, makeHandle, fn, otelKind) {
647
1550
  });
648
1551
  return userId !== void 0 ? withUser(userId, run) : run();
649
1552
  }
650
- var import_api2, Observation;
1553
+ var import_api6, Observation, warned;
651
1554
  var init_spans = __esm({
652
1555
  "src/spans.ts"() {
653
1556
  "use strict";
654
1557
  init_cjs_shims();
655
- import_api2 = require("@opentelemetry/api");
1558
+ import_api6 = require("@opentelemetry/api");
1559
+ init_agent();
656
1560
  init_client();
1561
+ init_errorType();
657
1562
  init_semconv();
658
1563
  init_serde();
659
1564
  init_user();
@@ -672,6 +1577,23 @@ var init_spans = __esm({
672
1577
  this.span.setAttribute(OUTPUT_VALUE, toAttributeValue(value));
673
1578
  return this;
674
1579
  }
1580
+ /**
1581
+ * Record what a retrieval returned, as `gen_ai.retrieval.documents`.
1582
+ *
1583
+ * The conventions define this as an array of objects, each with an optional
1584
+ * `id` and an optional `score`. Identifiers and relevance, never document
1585
+ * text, which is why it is not treated as content: it survives
1586
+ * `captureContent: false` the way token counts do. Put the retrieved text in
1587
+ * `setOutput` if you want it captured, and masking applies to it there.
1588
+ *
1589
+ * Unlike `dataSourceId` and `topK`, which describe the request and are
1590
+ * passed at span creation, this is only knowable once the search has run, so
1591
+ * it never reaches a pending snapshot.
1592
+ */
1593
+ setRetrievedDocuments(documents) {
1594
+ this.span.setAttribute(GEN_AI_RETRIEVAL_DOCUMENTS, toAttributeValue(documents));
1595
+ return this;
1596
+ }
675
1597
  /**
676
1598
  * Set an arbitrary attribute. Primitives and homogeneous primitive arrays
677
1599
  * are passed through as the OTel values they are; objects are JSON-encoded
@@ -679,34 +1601,30 @@ var init_spans = __esm({
679
1601
  * an empty string.
680
1602
  */
681
1603
  setAttribute(key, value) {
682
- if (value === void 0 || value === null) return this;
683
- if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
684
- this.span.setAttribute(key, value);
685
- return this;
686
- }
687
- if (Array.isArray(value) && value.every((v) => typeof v === "number")) {
688
- this.span.setAttribute(key, value);
689
- return this;
690
- }
691
- if (Array.isArray(value) && value.every((v) => typeof v === "boolean")) {
692
- this.span.setAttribute(key, value);
693
- return this;
694
- }
695
- this.span.setAttribute(key, toAttributeValue(value));
1604
+ const coerced = attributeValue(value);
1605
+ if (coerced !== void 0) this.span.setAttribute(key, coerced);
696
1606
  return this;
697
1607
  }
698
1608
  /**
699
- * Record an error on the span and set ERROR status. This is exactly what the
700
- * `startAsCurrent*` helpers do on a thrown error, exposed so the manual
701
- * `start*` path does not have to reach through `.span` to match it.
1609
+ * Record an error on the span, set ERROR status and `error.type`. This is
1610
+ * exactly what the `startAsCurrent*` helpers do on a thrown error, exposed
1611
+ * so the manual `start*` path does not have to reach through `.span` to
1612
+ * match it.
1613
+ *
1614
+ * `error.type` is Conditionally Required by the GenAI conventions on every
1615
+ * span that ends in an error, and every helper's throw path funnels through
1616
+ * here, so this is the one place that sets it. The error's name or class
1617
+ * (see errorType), never the message: it must stay low-cardinality and free
1618
+ * of echoed content.
702
1619
  *
703
1620
  * Accepts `unknown` because that is what a `catch` binding is; a non-Error
704
1621
  * throwable is wrapped so `recordException` still gets a real Error.
705
1622
  */
706
1623
  recordException(error) {
707
1624
  const wrapped = error instanceof Error ? error : new Error(String(error));
708
- this.span.recordException(wrapped);
709
- this.span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message: wrapped.message });
1625
+ this.span.recordException(exceptionForRecording(wrapped));
1626
+ this.span.setStatus({ code: import_api6.SpanStatusCode.ERROR, message: wrapped.message });
1627
+ this.span.setAttribute(ERROR_TYPE, errorType(error));
710
1628
  return this;
711
1629
  }
712
1630
  end() {
@@ -719,6 +1637,7 @@ var init_spans = __esm({
719
1637
  this.end();
720
1638
  }
721
1639
  };
1640
+ warned = /* @__PURE__ */ new Set();
722
1641
  }
723
1642
  });
724
1643
 
@@ -754,22 +1673,18 @@ function recordResult(observation, result) {
754
1673
  observation.setAttribute(OUTPUT_VALUE, serializeResult(result));
755
1674
  if (resultIsError(result)) {
756
1675
  observation.span.setStatus({
757
- code: import_api3.SpanStatusCode.ERROR,
1676
+ code: import_api7.SpanStatusCode.ERROR,
758
1677
  message: "tool returned an error result"
759
1678
  });
760
1679
  observation.setAttribute(ERROR_TYPE, ERROR_TYPE_TOOL_ERROR);
761
1680
  }
762
1681
  }
763
- function errorType(error) {
764
- return error instanceof Error ? error.name : typeof error;
765
- }
766
1682
  function negotiatedProtocolVersion(client) {
767
1683
  const version = asRecord(asRecord(client)?.transport)?.protocolVersion;
768
1684
  return typeof version === "string" ? version : void 0;
769
1685
  }
770
- function callAttributes(toolName, protocolVersion) {
1686
+ function callAttributes(protocolVersion) {
771
1687
  const attributes = {
772
- [GEN_AI_TOOL_NAME]: toolName,
773
1688
  [MCP_METHOD_NAME]: MCP_METHOD_TOOLS_CALL
774
1689
  };
775
1690
  if (protocolVersion !== void 0) attributes[MCP_PROTOCOL_VERSION] = protocolVersion;
@@ -786,24 +1701,18 @@ function instrumentMcpClient(ClientClass) {
786
1701
  const original = ClientClass.prototype.callTool;
787
1702
  const instrumented = function instrumentedCallTool(params, ...rest) {
788
1703
  return startAsCurrentSpan(
789
- `execute_tool ${params.name}`,
790
1704
  {
791
1705
  kind: "TOOL" /* TOOL */,
792
1706
  // The OTel SpanKind FIELD (orthogonal to our taxonomy attribute above):
793
1707
  // a remote tool call is CLIENT under both the MCP and the GenAI
794
1708
  // execute-tool conventions.
795
- otelKind: import_api3.SpanKind.CLIENT,
1709
+ otelKind: import_api7.SpanKind.CLIENT,
1710
+ toolName: params.name,
796
1711
  input: params.arguments,
797
- attributes: callAttributes(params.name, negotiatedProtocolVersion(this))
1712
+ attributes: callAttributes(negotiatedProtocolVersion(this))
798
1713
  },
799
1714
  async (observation) => {
800
- let result;
801
- try {
802
- result = await original.call(this, params, ...rest);
803
- } catch (error) {
804
- observation.setAttribute(ERROR_TYPE, errorType(error));
805
- throw error;
806
- }
1715
+ const result = await original.call(this, params, ...rest);
807
1716
  recordResult(observation, result);
808
1717
  return result;
809
1718
  }
@@ -815,12 +1724,12 @@ function instrumentMcpClient(ClientClass) {
815
1724
  ClientClass.prototype.callTool = original;
816
1725
  };
817
1726
  }
818
- var import_api3, INPUT_REQUIRED;
1727
+ var import_api7, INPUT_REQUIRED;
819
1728
  var init_instrumentationMcp = __esm({
820
1729
  "src/instrumentationMcp.ts"() {
821
1730
  "use strict";
822
1731
  init_cjs_shims();
823
- import_api3 = require("@opentelemetry/api");
1732
+ import_api7 = require("@opentelemetry/api");
824
1733
  init_semconv();
825
1734
  init_serde();
826
1735
  init_spans();
@@ -829,7 +1738,7 @@ var init_instrumentationMcp = __esm({
829
1738
  });
830
1739
 
831
1740
  // src/instrumentation.ts
832
- function packageOf(specifier) {
1741
+ function packageOf2(specifier) {
833
1742
  const segments = specifier.split("/");
834
1743
  const take = specifier.startsWith("@") ? 2 : 1;
835
1744
  return segments.slice(0, take).join("/");
@@ -841,7 +1750,7 @@ function isUnresolved(error, specifier) {
841
1750
  if (typeof error !== "object" || error === null) return false;
842
1751
  const message = error.message;
843
1752
  if (typeof message !== "string") return false;
844
- const alternatives = [specifier, packageOf(specifier)].map(escapeRegExp).join("|");
1753
+ const alternatives = [specifier, packageOf2(specifier)].map(escapeRegExp).join("|");
845
1754
  return new RegExp(
846
1755
  `(?:cannot find (?:module|package)|could not resolve|failed to load url)\\s*['"\`]?(?:${alternatives})['"\`]?(?![\\w./@-])`,
847
1756
  "i"
@@ -910,6 +1819,73 @@ function anthropicPatchable(exports2) {
910
1819
  const cls = exports2.default ?? exports2.Anthropic;
911
1820
  return cls?.Messages?.prototype?.create ? { default: cls } : void 0;
912
1821
  }
1822
+ function openaiPrototypes(moduleExports) {
1823
+ const cls = moduleExports?.OpenAI;
1824
+ const prototypes = [];
1825
+ for (const path of OPENAI_INSTRUMENTED_RESOURCES) {
1826
+ let resource = cls;
1827
+ for (const segment of path) {
1828
+ resource = resource?.[segment];
1829
+ }
1830
+ const proto = resource?.prototype;
1831
+ if (typeof proto?.create === "function") prototypes.push(proto);
1832
+ }
1833
+ return prototypes;
1834
+ }
1835
+ function settlementOf(result) {
1836
+ const raw = result?.responsePromise;
1837
+ if (typeof raw?.then === "function") {
1838
+ return raw;
1839
+ }
1840
+ return result instanceof Promise ? result : void 0;
1841
+ }
1842
+ function failSpan(span, error) {
1843
+ if (!span.isRecording()) return;
1844
+ span.recordException(error instanceof Error ? error : String(error));
1845
+ span.setStatus({
1846
+ code: import_api8.SpanStatusCode.ERROR,
1847
+ message: error instanceof Error ? error.message : String(error)
1848
+ });
1849
+ span.end();
1850
+ }
1851
+ function guardRejections(original) {
1852
+ const guarded = function(...args) {
1853
+ const span = import_api8.trace.getActiveSpan();
1854
+ const result = original.apply(this, args);
1855
+ if (span?.isRecording()) {
1856
+ settlementOf(result)?.then(void 0, (error) => failSpan(span, error));
1857
+ }
1858
+ return result;
1859
+ };
1860
+ guarded[GUARDED_ORIGINAL] = original;
1861
+ return guarded;
1862
+ }
1863
+ function withRejectedCallsEnded(Base) {
1864
+ return class extends Base {
1865
+ patch(moduleExports, moduleVersion) {
1866
+ const installed = [];
1867
+ for (const proto of openaiPrototypes(moduleExports)) {
1868
+ const create = proto.create;
1869
+ if (create[GUARDED_ORIGINAL] !== void 0) continue;
1870
+ const guarded = guardRejections(create);
1871
+ proto.create = guarded;
1872
+ installed.push([proto, guarded]);
1873
+ }
1874
+ const patched = super.patch(moduleExports, moduleVersion);
1875
+ for (const [proto, guarded] of installed) {
1876
+ if (proto.create === guarded) proto.create = guarded[GUARDED_ORIGINAL];
1877
+ }
1878
+ return patched;
1879
+ }
1880
+ unpatch(moduleExports, moduleVersion) {
1881
+ super.unpatch(moduleExports, moduleVersion);
1882
+ for (const proto of openaiPrototypes(moduleExports)) {
1883
+ const original = proto.create[GUARDED_ORIGINAL];
1884
+ if (original !== void 0) proto.create = original;
1885
+ }
1886
+ }
1887
+ };
1888
+ }
913
1889
  function mcpClientPatchable(exports2) {
914
1890
  const cls = exports2.Client;
915
1891
  return typeof cls?.prototype?.callTool === "function" ? { Client: cls } : void 0;
@@ -947,6 +1923,10 @@ async function registerVercelTelemetry(tracerProvider, teardown) {
947
1923
  });
948
1924
  return true;
949
1925
  }
1926
+ function isVercelDialectSpan(span) {
1927
+ const operationId = span.attributes[VERCEL_OPERATION_ID];
1928
+ return typeof operationId === "string" && operationId.startsWith("ai.");
1929
+ }
950
1930
  async function enableInstrumentations(sink, tracerProvider, names, teardown) {
951
1931
  const wanted = names ? REGISTRY.filter((e) => names.includes(e.name)) : REGISTRY;
952
1932
  const enabled = [];
@@ -971,17 +1951,26 @@ async function enableInstrumentations(sink, tracerProvider, names, teardown) {
971
1951
  }
972
1952
  return enabled;
973
1953
  }
974
- var import_node_module2, import_node_path, import_instrumentation, dynamicImport, vercelTelemetryIntegration, REGISTRY;
1954
+ var import_node_module2, import_node_path, import_api8, import_instrumentation, dynamicImport, OPENAI_INSTRUMENTED_RESOURCES, GUARDED_ORIGINAL, vercelTelemetryIntegration, VERCEL_OPERATION_ID, REGISTRY;
975
1955
  var init_instrumentation = __esm({
976
1956
  "src/instrumentation.ts"() {
977
1957
  "use strict";
978
1958
  init_cjs_shims();
979
1959
  import_node_module2 = require("module");
980
1960
  import_node_path = require("path");
1961
+ import_api8 = require("@opentelemetry/api");
981
1962
  import_instrumentation = require("@opentelemetry/instrumentation");
982
1963
  init_semconv();
983
1964
  init_version();
984
1965
  dynamicImport = new Function("s", "return import(s)");
1966
+ OPENAI_INSTRUMENTED_RESOURCES = [
1967
+ ["Chat", "Completions"],
1968
+ ["Completions"],
1969
+ ["Embeddings"],
1970
+ ["Responses"]
1971
+ ];
1972
+ GUARDED_ORIGINAL = /* @__PURE__ */ Symbol("rius.openai.guardedOriginal");
1973
+ VERCEL_OPERATION_ID = "ai.operationId";
985
1974
  REGISTRY = [
986
1975
  {
987
1976
  name: "vercel-ai",
@@ -1000,7 +1989,7 @@ var init_instrumentation = __esm({
1000
1989
  onStart() {
1001
1990
  },
1002
1991
  onEnd(span) {
1003
- add?.(span);
1992
+ if (add !== void 0 && isVercelDialectSpan(span)) add(span);
1004
1993
  },
1005
1994
  async forceFlush() {
1006
1995
  },
@@ -1016,7 +2005,7 @@ var init_instrumentation = __esm({
1016
2005
  const mod = await optional("@arizeai/openinference-instrumentation-openai");
1017
2006
  const Ctor = mod?.OpenAIInstrumentation;
1018
2007
  if (Ctor === void 0) return void 0;
1019
- const instrumentation = new Ctor();
2008
+ const instrumentation = new (withRejectedCallsEnded(Ctor))();
1020
2009
  await patchActiveBuild(instrumentation, "openai", openaiPatchable);
1021
2010
  return instrumentation;
1022
2011
  }
@@ -1065,127 +2054,862 @@ var init_instrumentation = __esm({
1065
2054
  return true;
1066
2055
  }
1067
2056
  }
1068
- ];
2057
+ ];
2058
+ }
2059
+ });
2060
+
2061
+ // src/masking.ts
2062
+ function isContentKey(key) {
2063
+ if (CONTENT_ATTRIBUTES.has(key)) return true;
2064
+ if (CONTENT_ATTRIBUTE_PREFIXES.some((p) => key.startsWith(p)) && !CONTENT_PREFIX_IDENTITY_ATTRIBUTES.has(key))
2065
+ return true;
2066
+ if (CONTENT_ATTRIBUTE_SUFFIXES.some((s) => key.endsWith(s))) return true;
2067
+ if (CONTENT_ATTRIBUTE_PREFIXED_SUFFIXES.some(
2068
+ ([prefix, leaves]) => key.startsWith(prefix) && leaves.some((leaf) => key.endsWith(leaf))
2069
+ ))
2070
+ return true;
2071
+ return key.startsWith(METADATA_PREFIX) && isContentKey(key.slice(METADATA_PREFIX.length));
2072
+ }
2073
+ var import_api9, METADATA_PREFIX, EXCEPTION_CONTENT_KEYS, STATUS_DESCRIPTION_KEY, MaskingSpanExporter;
2074
+ var init_masking = __esm({
2075
+ "src/masking.ts"() {
2076
+ "use strict";
2077
+ init_cjs_shims();
2078
+ import_api9 = require("@opentelemetry/api");
2079
+ init_semconv();
2080
+ init_serde();
2081
+ METADATA_PREFIX = "metadata.";
2082
+ EXCEPTION_CONTENT_KEYS = ["exception.message", "exception.stacktrace"];
2083
+ STATUS_DESCRIPTION_KEY = "status.description";
2084
+ MaskingSpanExporter = class {
2085
+ constructor(inner, opts) {
2086
+ this.inner = inner;
2087
+ this.opts = opts;
2088
+ }
2089
+ inner;
2090
+ opts;
2091
+ export(spans, resultCallback) {
2092
+ this.inner.export(
2093
+ spans.map((s) => this.sanitized(s)),
2094
+ resultCallback
2095
+ );
2096
+ }
2097
+ sanitized(span) {
2098
+ this.sanitizeAttributes(span.attributes);
2099
+ for (const event of span.events ?? []) {
2100
+ const attributes = event.attributes;
2101
+ this.sanitizeAttributes(attributes);
2102
+ if (this.opts.captureContent || attributes === void 0) continue;
2103
+ if (event.name !== EXCEPTION_EVENT) continue;
2104
+ for (const key of EXCEPTION_CONTENT_KEYS) delete attributes[key];
2105
+ }
2106
+ for (const link of span.links ?? []) {
2107
+ this.sanitizeAttributes(link.attributes);
2108
+ }
2109
+ this.sanitizeStatus(span);
2110
+ return span;
2111
+ }
2112
+ /**
2113
+ * Drops or masks the status description, in place. `status` is mutable and
2114
+ * exposed by reference on the SDK span, same as `attributes`, so mutating it
2115
+ * here reaches the span about to be handed to the inner exporter. The ERROR
2116
+ * code itself is left alone so the failure stays visible and classifiable,
2117
+ * the same policy as `exception.type`.
2118
+ */
2119
+ sanitizeStatus(span) {
2120
+ const status = span.status;
2121
+ if (status?.code !== import_api9.SpanStatusCode.ERROR || !status.message) return;
2122
+ if (!this.opts.captureContent) {
2123
+ status.message = void 0;
2124
+ return;
2125
+ }
2126
+ if (this.opts.mask === void 0) return;
2127
+ try {
2128
+ const masked = toAttributeValue(
2129
+ this.opts.mask(status.message, { key: STATUS_DESCRIPTION_KEY })
2130
+ );
2131
+ status.message = typeof masked === "string" ? masked : String(masked);
2132
+ } catch {
2133
+ status.message = "[mask error]";
2134
+ }
2135
+ }
2136
+ /** Strips or masks the content keys of one attribute bag, in place. */
2137
+ sanitizeAttributes(attributes) {
2138
+ if (attributes === void 0) return;
2139
+ for (const key of Object.keys(attributes)) {
2140
+ if (!isContentKey(key)) continue;
2141
+ if (!this.opts.captureContent) {
2142
+ delete attributes[key];
2143
+ continue;
2144
+ }
2145
+ if (this.opts.mask === void 0) continue;
2146
+ try {
2147
+ attributes[key] = toAttributeValue(this.opts.mask(attributes[key], { key }));
2148
+ } catch {
2149
+ attributes[key] = "[mask error]";
2150
+ }
2151
+ }
2152
+ }
2153
+ shutdown() {
2154
+ return this.inner.shutdown();
2155
+ }
2156
+ forceFlush() {
2157
+ return this.inner.forceFlush?.() ?? Promise.resolve();
2158
+ }
2159
+ };
2160
+ }
2161
+ });
2162
+
2163
+ // src/normalize.ts
2164
+ function isExpanding(rule) {
2165
+ return "expand" in rule;
2166
+ }
2167
+ function asNumber(value) {
2168
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2169
+ }
2170
+ function asCount(value) {
2171
+ return typeof value === "number" && Number.isFinite(value) ? Math.trunc(value) : void 0;
2172
+ }
2173
+ function asText(value) {
2174
+ return typeof value === "string" && value !== "" ? value : void 0;
2175
+ }
2176
+ function asFlag(value) {
2177
+ return typeof value === "boolean" ? value : void 0;
2178
+ }
2179
+ function asTextList(value) {
2180
+ if (typeof value === "string") return [value];
2181
+ if (Array.isArray(value) && value.every((v) => typeof v === "string")) return [...value];
2182
+ return void 0;
2183
+ }
2184
+ function wellFormed(text) {
2185
+ return text.replace(LONE_SURROGATE, "\uFFFD");
2186
+ }
2187
+ function jsonString(text) {
2188
+ return JSON.stringify(wellFormed(text));
2189
+ }
2190
+ function compactJson(value) {
2191
+ if (typeof value === "string") return jsonString(value);
2192
+ if (Array.isArray(value)) return `[${value.map(compactJson).join(",")}]`;
2193
+ if (value !== null && typeof value === "object") {
2194
+ const members = Object.entries(value).map(([k, v]) => `${jsonString(k)}:${compactJson(v)}`);
2195
+ return `{${members.join(",")}}`;
2196
+ }
2197
+ return JSON.stringify(value);
2198
+ }
2199
+ function asToolChoice(value) {
2200
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
2201
+ return compactJson(value);
2202
+ }
2203
+ return typeof value === "string" ? asText(wellFormed(value)) : void 0;
2204
+ }
2205
+ function invocationParameters(raw) {
2206
+ if (typeof raw !== "string") return { [LLM_INVOCATION_PARAMETERS]: raw };
2207
+ let payload;
2208
+ try {
2209
+ payload = JSON.parse(raw);
2210
+ } catch {
2211
+ return { [LLM_INVOCATION_PARAMETERS]: raw };
2212
+ }
2213
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
2214
+ return { [LLM_INVOCATION_PARAMETERS]: raw };
2215
+ }
2216
+ const leftover = { ...payload };
2217
+ const produced = {};
2218
+ for (const [member, target, convert] of INVOCATION_PARAMETER_MEMBERS) {
2219
+ if (!Object.hasOwn(leftover, member) || produced[target] !== void 0) continue;
2220
+ const value = convert(leftover[member]);
2221
+ if (value === void 0) continue;
2222
+ produced[target] = value;
2223
+ delete leftover[member];
2224
+ }
2225
+ if (Object.keys(produced).length === 0) return { [LLM_INVOCATION_PARAMETERS]: raw };
2226
+ const expansion = { ...produced };
2227
+ if (Object.keys(leftover).length > 0) {
2228
+ expansion[LLM_INVOCATION_PARAMETERS] = toAttributeValue(leftover);
2229
+ }
2230
+ return expansion;
2231
+ }
2232
+ function taxonomyFromKind(raw) {
2233
+ if (typeof raw !== "string") return {};
2234
+ const operation = operationForKind(raw);
2235
+ const produced = { [OPENINFERENCE_SPAN_KIND]: raw };
2236
+ if (operation !== void 0) produced[GEN_AI_OPERATION_NAME] = operation;
2237
+ return produced;
2238
+ }
2239
+ function taxonomyFromOperation(raw) {
2240
+ if (typeof raw !== "string") return {};
2241
+ const kind = kindForOperation(raw);
2242
+ const produced = { [GEN_AI_OPERATION_NAME]: raw };
2243
+ if (kind !== void 0) produced[OPENINFERENCE_SPAN_KIND] = kind;
2244
+ return produced;
2245
+ }
2246
+ function sourceKeys(rule) {
2247
+ return typeof rule.source === "string" ? [rule.source] : rule.source;
2248
+ }
2249
+ function sourcePrefixes(rules) {
2250
+ const prefixes = /* @__PURE__ */ new Set();
2251
+ for (const rule of rules) {
2252
+ for (const key of sourceKeys(rule)) {
2253
+ const dot = key.indexOf(".");
2254
+ prefixes.add(dot === -1 ? key : key.slice(0, dot + 1));
2255
+ }
2256
+ }
2257
+ return [...prefixes];
2258
+ }
2259
+ function applyRules(attributes, rules) {
2260
+ const mapped = [];
2261
+ const rewritten = [];
2262
+ for (const rule of rules) {
2263
+ const keys = sourceKeys(rule);
2264
+ const present = keys.filter((key) => attributes[key] !== void 0);
2265
+ if (present.length === 0) continue;
2266
+ if (isExpanding(rule)) {
2267
+ let expansion;
2268
+ try {
2269
+ expansion = rule.expand(attributes[rule.source]);
2270
+ } catch {
2271
+ continue;
2272
+ }
2273
+ for (const [key, value2] of Object.entries(expansion)) {
2274
+ if (value2 === void 0) continue;
2275
+ const ownSource = key === rule.source;
2276
+ if (!ownSource && attributes[key] !== void 0) continue;
2277
+ attributes[key] = value2;
2278
+ if (ownSource) rewritten.push(key);
2279
+ }
2280
+ if (expansion[rule.source] === void 0) {
2281
+ const targets = Object.keys(expansion).filter((key) => expansion[key] !== void 0);
2282
+ mapped.push({ sources: [rule.source], targets });
2283
+ }
2284
+ continue;
2285
+ }
2286
+ if (attributes[rule.target] !== void 0) {
2287
+ mapped.push({ sources: present, targets: [rule.target] });
2288
+ continue;
2289
+ }
2290
+ const value = rule.convert(present.map((key) => attributes[key]));
2291
+ if (value === void 0) continue;
2292
+ attributes[rule.target] = value;
2293
+ mapped.push({ sources: present, targets: [rule.target] });
2294
+ }
2295
+ return { mapped, rewritten };
2296
+ }
2297
+ function normalizeFirstTokenEvent(span) {
2298
+ const events = span.events;
2299
+ if (events === void 0 || events.length === 0) return {};
2300
+ if (!events.some((event) => event.name === OPENINFERENCE_FIRST_TOKEN_EVENT)) return {};
2301
+ const attributes = span.attributes ?? {};
2302
+ let canonicalSeen = events.some((event) => event.name === GEN_AI_FIRST_TOKEN_EVENT);
2303
+ let firstTokenTime;
2304
+ const rebuilt = [];
2305
+ for (const event of events) {
2306
+ if (event.name !== OPENINFERENCE_FIRST_TOKEN_EVENT) {
2307
+ rebuilt.push(event);
2308
+ continue;
2309
+ }
2310
+ firstTokenTime ??= event.time;
2311
+ if (canonicalSeen) continue;
2312
+ canonicalSeen = true;
2313
+ rebuilt.push({ ...event, name: GEN_AI_FIRST_TOKEN_EVENT });
2314
+ }
2315
+ span.events.splice(0, events.length, ...rebuilt);
2316
+ const added = {};
2317
+ if (attributes[GEN_AI_REQUEST_STREAM] === void 0) added[GEN_AI_REQUEST_STREAM] = true;
2318
+ if (attributes[GEN_AI_RESPONSE_TIME_TO_FIRST_CHUNK] === void 0 && firstTokenTime) {
2319
+ const elapsedNanos = hrTimeToNanos(firstTokenTime) - hrTimeToNanos(span.startTime);
2320
+ added[GEN_AI_RESPONSE_TIME_TO_FIRST_CHUNK] = Math.max(elapsedNanos, 0) / 1e9;
2321
+ }
2322
+ return added;
2323
+ }
2324
+ function hrTimeToNanos(time) {
2325
+ return time[0] * 1e9 + time[1];
2326
+ }
2327
+ function errorTypeFromExceptionEvent(span) {
2328
+ if (span.status?.code !== import_api10.SpanStatusCode.ERROR) return {};
2329
+ if (span.attributes?.[ERROR_TYPE] !== void 0) return {};
2330
+ for (const event of span.events ?? []) {
2331
+ if (event.name !== EXCEPTION_EVENT) continue;
2332
+ const type = event.attributes?.[EXCEPTION_TYPE];
2333
+ if (typeof type === "string" && type !== "") return { [ERROR_TYPE]: type };
2334
+ }
2335
+ return {};
2336
+ }
2337
+ function parseIndex(text) {
2338
+ if (!INDEX.test(text)) return void 0;
2339
+ const value = BigInt(text.startsWith("+") ? text.slice(1) : text);
2340
+ return value >= 0n && value < INT64_LIMIT ? value : void 0;
2341
+ }
2342
+ function byIndex(entries) {
2343
+ return [...entries].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([, value]) => value);
2344
+ }
2345
+ function byCodePoint(a, b) {
2346
+ const x = [...a];
2347
+ const y = [...b];
2348
+ for (let i = 0; i < Math.min(x.length, y.length); i++) {
2349
+ const diff = (x[i].codePointAt(0) ?? 0) - (y[i].codePointAt(0) ?? 0);
2350
+ if (diff !== 0) return diff;
2351
+ }
2352
+ return x.length - y.length;
2353
+ }
2354
+ function cutIndexed(text) {
2355
+ const dot = text.indexOf(".");
2356
+ if (dot < 0) return void 0;
2357
+ const index = parseIndex(text.slice(0, dot));
2358
+ return index === void 0 ? void 0 : [index, text.slice(dot + 1)];
2359
+ }
2360
+ function fieldRecord() {
2361
+ return /* @__PURE__ */ Object.create(null);
2362
+ }
2363
+ function contentPart(item, toolCalls) {
2364
+ const names = Object.keys(item).sort(byCodePoint);
2365
+ if (names.length === 2 && item.type === "text" && Object.hasOwn(item, "text")) {
2366
+ return { type: "text", content: item.text };
2367
+ }
2368
+ if (item.type === "tool_use" && repeatsToolCall(item, toolCalls)) return void 0;
2369
+ const sorted = fieldRecord();
2370
+ for (const name of names) sorted[name] = item[name];
2371
+ return { type: "text", content: toAttributeValue(sorted) };
2372
+ }
2373
+ function repeatsToolCall(item, toolCalls) {
2374
+ const fields = Object.keys(item).filter((name) => name !== "type");
2375
+ if (!fields.every((name) => TOOL_USE_ITEM_FIELDS.has(name))) return false;
2376
+ return [...toolCalls.values()].some(
2377
+ (call) => fields.every((name) => {
2378
+ const field = TOOL_USE_ITEM_FIELDS.get(name);
2379
+ return (Object.hasOwn(call, field) ? call[field] : "") === item[name];
2380
+ })
2381
+ );
2382
+ }
2383
+ function encodeMessage(message) {
2384
+ const parts = [];
2385
+ if (message.content !== void 0) {
2386
+ parts.push(
2387
+ message.toolCallId ? { type: "tool_call_response", id: message.toolCallId, response: message.content } : { type: "text", content: message.content }
2388
+ );
2389
+ }
2390
+ for (const item of byIndex(message.contents)) {
2391
+ const part = contentPart(item, message.toolCalls);
2392
+ if (part !== void 0) parts.push(part);
2393
+ }
2394
+ for (const call of byIndex(message.toolCalls)) {
2395
+ const part = { type: "tool_call" };
2396
+ for (const [field, name] of TOOL_CALL_FIELDS) {
2397
+ if (Object.hasOwn(call, field) && call[field]) part[name] = call[field];
2398
+ }
2399
+ parts.push(part);
2400
+ }
2401
+ return message.role ? { role: message.role, parts } : { parts };
2402
+ }
2403
+ function reassembleFamily(attributes, prefix) {
2404
+ const messages = /* @__PURE__ */ new Map();
2405
+ const consumed = [];
2406
+ for (const key of Object.keys(attributes)) {
2407
+ if (!key.startsWith(prefix)) continue;
2408
+ const indexed = cutIndexed(key.slice(prefix.length));
2409
+ if (indexed === void 0) continue;
2410
+ const [index, field] = indexed;
2411
+ let call;
2412
+ let item;
2413
+ if (field.startsWith(LLM_MESSAGE_TOOL_CALLS_PREFIX)) {
2414
+ const sub = cutIndexed(field.slice(LLM_MESSAGE_TOOL_CALLS_PREFIX.length));
2415
+ if (sub?.[1].startsWith(LLM_TOOL_CALL_PREFIX)) {
2416
+ call = [sub[0], sub[1].slice(LLM_TOOL_CALL_PREFIX.length)];
2417
+ }
2418
+ } else if (field.startsWith(LLM_MESSAGE_CONTENTS_PREFIX)) {
2419
+ const sub = cutIndexed(field.slice(LLM_MESSAGE_CONTENTS_PREFIX.length));
2420
+ if (sub?.[1].startsWith(LLM_MESSAGE_CONTENT_PREFIX)) {
2421
+ item = [sub[0], sub[1].slice(LLM_MESSAGE_CONTENT_PREFIX.length)];
2422
+ } else if (sub?.[1].startsWith(LLM_TOOL_CALL_PREFIX)) {
2423
+ item = [sub[0], sub[1]];
2424
+ }
2425
+ }
2426
+ if (call === void 0 && item === void 0 && field !== LLM_MESSAGE_ROLE && field !== LLM_MESSAGE_CONTENT && field !== LLM_MESSAGE_TOOL_CALL_ID) {
2427
+ continue;
2428
+ }
2429
+ const value = attributes[key];
2430
+ if (typeof value !== "string") return void 0;
2431
+ let message = messages.get(index);
2432
+ if (message === void 0) {
2433
+ message = {
2434
+ role: "",
2435
+ content: void 0,
2436
+ toolCallId: "",
2437
+ toolCalls: /* @__PURE__ */ new Map(),
2438
+ contents: /* @__PURE__ */ new Map()
2439
+ };
2440
+ messages.set(index, message);
2441
+ }
2442
+ if (call !== void 0) {
2443
+ const fields = message.toolCalls.get(call[0]) ?? fieldRecord();
2444
+ fields[call[1]] = value;
2445
+ message.toolCalls.set(call[0], fields);
2446
+ } else if (item !== void 0) {
2447
+ const fields = message.contents.get(item[0]) ?? fieldRecord();
2448
+ fields[item[1]] = value;
2449
+ message.contents.set(item[0], fields);
2450
+ } else if (field === LLM_MESSAGE_ROLE) {
2451
+ message.role = value;
2452
+ } else if (field === LLM_MESSAGE_TOOL_CALL_ID) {
2453
+ message.toolCallId = value;
2454
+ } else {
2455
+ message.content = value;
2456
+ }
2457
+ consumed.push(key);
2458
+ }
2459
+ if (messages.size === 0) return void 0;
2460
+ return { messages: byIndex(messages).map(encodeMessage), consumed };
2461
+ }
2462
+ function reassembleOpenInferenceMessages(attributes) {
2463
+ const keys = Object.keys(attributes);
2464
+ if (!keys.some((key) => MESSAGE_FAMILIES.some(([prefix]) => key.startsWith(prefix)))) {
2465
+ return false;
2466
+ }
2467
+ const writes = [];
2468
+ for (const [prefix, target] of MESSAGE_FAMILIES) {
2469
+ if (Object.hasOwn(attributes, target) && attributes[target] !== void 0) continue;
2470
+ const family = reassembleFamily(attributes, prefix);
2471
+ if (family === void 0) continue;
2472
+ writes.push({ target, value: toAttributeValue(family.messages), consumed: family.consumed });
2473
+ }
2474
+ for (const { target, value, consumed } of writes) {
2475
+ for (const key of consumed) delete attributes[key];
2476
+ attributes[target] = value;
2477
+ }
2478
+ return writes.length > 0;
2479
+ }
2480
+ function normalizeToolDefinitions(attributes) {
2481
+ const native = attributes[GEN_AI_TOOL_DEFINITIONS] !== void 0;
2482
+ const indexed = /* @__PURE__ */ new Map();
2483
+ for (const key of Object.keys(attributes)) {
2484
+ if (!key.startsWith("llm.tools.")) continue;
2485
+ const match = LLM_TOOL_SCHEMA.exec(key);
2486
+ if (match !== null) indexed.set(Number(match[1]), key);
2487
+ }
2488
+ if (indexed.size > 0) {
2489
+ const schemas = [];
2490
+ for (const index of [...indexed.keys()].sort((a, b) => a - b)) {
2491
+ const raw = attributes[indexed.get(index)];
2492
+ if (typeof raw !== "string") return;
2493
+ try {
2494
+ schemas.push(JSON.parse(raw));
2495
+ } catch {
2496
+ return;
2497
+ }
2498
+ }
2499
+ for (const key of indexed.values()) delete attributes[key];
2500
+ if (!native) attributes[GEN_AI_TOOL_DEFINITIONS] = toAttributeValue(schemas);
2501
+ return;
1069
2502
  }
1070
- });
1071
-
1072
- // src/masking.ts
1073
- function isContentKey(key) {
1074
- if (CONTENT_ATTRIBUTES.has(key)) return true;
1075
- if (CONTENT_ATTRIBUTE_PREFIXES.some((p) => key.startsWith(p))) return true;
1076
- if (CONTENT_ATTRIBUTE_SUFFIXES.some((s) => key.endsWith(s))) return true;
1077
- return key.startsWith(METADATA_PREFIX) && isContentKey(key.slice(METADATA_PREFIX.length));
2503
+ const bag = attributes[LLM_INVOCATION_PARAMETERS];
2504
+ if (typeof bag !== "string") return;
2505
+ let payload;
2506
+ try {
2507
+ payload = JSON.parse(bag);
2508
+ } catch {
2509
+ return;
2510
+ }
2511
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) return;
2512
+ const members = payload;
2513
+ const present = INVOCATION_PARAMETERS_CONTENT_MEMBERS.filter((m) => Object.hasOwn(members, m));
2514
+ if (present.length === 0 || !present.every((m) => Array.isArray(members[m]))) return;
2515
+ const definitions = present.flatMap((m) => members[m]);
2516
+ const leftover = Object.fromEntries(
2517
+ Object.entries(members).filter(([key]) => !present.includes(key))
2518
+ );
2519
+ if (Object.keys(leftover).length > 0) {
2520
+ attributes[LLM_INVOCATION_PARAMETERS] = toAttributeValue(leftover);
2521
+ } else {
2522
+ delete attributes[LLM_INVOCATION_PARAMETERS];
2523
+ }
2524
+ if (!native) attributes[GEN_AI_TOOL_DEFINITIONS] = toAttributeValue(definitions);
2525
+ }
2526
+ function sortedCompactJson(value) {
2527
+ if (typeof value === "string") return jsonString(value);
2528
+ if (Array.isArray(value)) return `[${value.map(sortedCompactJson).join(",")}]`;
2529
+ if (value !== null && typeof value === "object") {
2530
+ const record = value;
2531
+ const members = Object.keys(record).sort(byCodePoint).map((k) => `${jsonString(k)}:${sortedCompactJson(record[k])}`);
2532
+ return `{${members.join(",")}}`;
2533
+ }
2534
+ return JSON.stringify(value);
2535
+ }
2536
+ function systemPart(block) {
2537
+ if (block !== null && typeof block === "object" && !Array.isArray(block)) {
2538
+ const record = block;
2539
+ if (record.type === "text" && typeof record.text === "string") {
2540
+ return { type: "text", content: record.text };
2541
+ }
2542
+ }
2543
+ return { type: "text", content: sortedCompactJson(block) };
1078
2544
  }
1079
- function redactInvocationParameters(value) {
1080
- if (typeof value !== "string") return void 0;
1081
- let parameters;
2545
+ function promoteSystemInstruction(attributes) {
2546
+ const bag = attributes[LLM_INVOCATION_PARAMETERS];
2547
+ if (typeof bag !== "string") return false;
2548
+ let payload;
1082
2549
  try {
1083
- parameters = JSON.parse(value);
2550
+ payload = JSON.parse(bag);
1084
2551
  } catch {
1085
- return void 0;
2552
+ return false;
1086
2553
  }
1087
- if (parameters === null || typeof parameters !== "object" || Array.isArray(parameters)) {
1088
- return void 0;
2554
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) return false;
2555
+ const members = payload;
2556
+ if (!Object.hasOwn(members, "system")) return false;
2557
+ const system = members.system;
2558
+ let parts;
2559
+ if (typeof system === "string" && system !== "") {
2560
+ parts = [{ type: "text", content: system }];
2561
+ } else if (Array.isArray(system) && system.length > 0) {
2562
+ parts = system.map(systemPart);
2563
+ } else {
2564
+ return false;
1089
2565
  }
1090
- const bag = parameters;
1091
- if (!INVOCATION_PARAMETERS_CONTENT_MEMBERS.some((member) => member in bag)) {
1092
- return value;
2566
+ let messages = [];
2567
+ const existing = attributes[GEN_AI_INPUT_MESSAGES];
2568
+ if (existing !== void 0) {
2569
+ if (typeof existing !== "string") return false;
2570
+ try {
2571
+ const parsed = JSON.parse(existing);
2572
+ if (!Array.isArray(parsed)) return false;
2573
+ messages = parsed;
2574
+ } catch {
2575
+ return false;
2576
+ }
1093
2577
  }
1094
- for (const member of INVOCATION_PARAMETERS_CONTENT_MEMBERS) delete bag[member];
1095
- return JSON.stringify(bag);
2578
+ if (messages.some((m) => m?.role === "system")) return false;
2579
+ attributes[GEN_AI_INPUT_MESSAGES] = toAttributeValue([{ role: "system", parts }, ...messages]);
2580
+ const leftover = Object.fromEntries(Object.entries(members).filter(([key]) => key !== "system"));
2581
+ if (Object.keys(leftover).length > 0) {
2582
+ attributes[LLM_INVOCATION_PARAMETERS] = toAttributeValue(leftover);
2583
+ } else {
2584
+ delete attributes[LLM_INVOCATION_PARAMETERS];
2585
+ }
2586
+ return true;
1096
2587
  }
1097
- var import_api4, METADATA_PREFIX, EXCEPTION_EVENT_NAME, EXCEPTION_CONTENT_KEYS, STATUS_DESCRIPTION_KEY, MaskingSpanExporter;
1098
- var init_masking = __esm({
1099
- "src/masking.ts"() {
2588
+ function dropEmbeddingVectors(attributes) {
2589
+ const doomed = Object.keys(attributes).filter(
2590
+ (key) => key.startsWith("embedding.") && EMBEDDING_VECTOR.test(key)
2591
+ );
2592
+ for (const key of doomed) delete attributes[key];
2593
+ }
2594
+ function responseIdFromOutput(attributes) {
2595
+ if (attributes[OPENINFERENCE_SPAN_KIND] !== "LLM" /* LLM */) return {};
2596
+ if (attributes[GEN_AI_RESPONSE_ID] !== void 0) return {};
2597
+ const mime = attributes["output.mime_type"];
2598
+ if (mime !== void 0 && mime !== "application/json") return {};
2599
+ const output = attributes[OUTPUT_VALUE];
2600
+ if (typeof output !== "string" || !output.startsWith("{")) return {};
2601
+ let payload;
2602
+ try {
2603
+ payload = JSON.parse(output);
2604
+ } catch {
2605
+ return {};
2606
+ }
2607
+ const id = payload.id;
2608
+ return typeof id === "string" && id !== "" ? { [GEN_AI_RESPONSE_ID]: id } : {};
2609
+ }
2610
+ var import_api10, copy, toInt, wrapInList, PROVIDER_NAME_ALIASES, providerName, textValue, LONE_SURROGATE, INVOCATION_PARAMETER_MEMBERS, REQUEST_PARAMETER_GUARDS, TAXONOMY_RULES, OPENINFERENCE_RULES, NORMALIZATION_RULES, OPENINFERENCE_FIRST_TOKEN_EVENT, MESSAGE_FAMILIES, TOOL_CALL_FIELDS, INDEX, INT64_LIMIT, TOOL_USE_ITEM_FIELDS, LLM_TOOL_SCHEMA, EMBEDDING_VECTOR, NormalizingSpanProcessor;
2611
+ var init_normalize = __esm({
2612
+ "src/normalize.ts"() {
1100
2613
  "use strict";
1101
2614
  init_cjs_shims();
1102
- import_api4 = require("@opentelemetry/api");
2615
+ import_api10 = require("@opentelemetry/api");
2616
+ init_errorType();
1103
2617
  init_semconv();
1104
2618
  init_serde();
1105
- METADATA_PREFIX = "metadata.";
1106
- EXCEPTION_EVENT_NAME = "exception";
1107
- EXCEPTION_CONTENT_KEYS = ["exception.message", "exception.stacktrace"];
1108
- STATUS_DESCRIPTION_KEY = "status.description";
1109
- MaskingSpanExporter = class {
1110
- constructor(inner, opts) {
1111
- this.inner = inner;
1112
- this.opts = opts;
1113
- }
1114
- inner;
1115
- opts;
1116
- export(spans, resultCallback) {
1117
- this.inner.export(
1118
- spans.map((s) => this.sanitized(s)),
1119
- resultCallback
1120
- );
2619
+ copy = (values) => values[0];
2620
+ toInt = (values) => {
2621
+ const value = values[0];
2622
+ if (typeof value === "number") return Number.isFinite(value) ? Math.trunc(value) : void 0;
2623
+ if (typeof value !== "string") return void 0;
2624
+ const parsed = Number.parseInt(value.trim(), 10);
2625
+ return Number.isNaN(parsed) ? void 0 : parsed;
2626
+ };
2627
+ wrapInList = (values) => {
2628
+ const value = values[0];
2629
+ if (Array.isArray(value)) return value;
2630
+ if (typeof value === "string") return [value];
2631
+ if (typeof value === "number") return [value];
2632
+ if (typeof value === "boolean") return [value];
2633
+ return void 0;
2634
+ };
2635
+ PROVIDER_NAME_ALIASES = {
2636
+ // OpenInference enum values
2637
+ mistralai: "mistral_ai",
2638
+ xai: "x_ai",
2639
+ // legacy gen_ai.system values
2640
+ vertex_ai: "gcp.vertex_ai",
2641
+ gemini: "gcp.gemini",
2642
+ "az.ai.inference": "azure.ai.inference",
2643
+ "az.ai.openai": "azure.ai.openai"
2644
+ };
2645
+ providerName = (values) => {
2646
+ const value = values[0];
2647
+ if (typeof value !== "string") return void 0;
2648
+ const key = value.trim().toLowerCase();
2649
+ return Object.hasOwn(PROVIDER_NAME_ALIASES, key) ? PROVIDER_NAME_ALIASES[key] : value;
2650
+ };
2651
+ textValue = (values) => asText(values[0]);
2652
+ LONE_SURROGATE = /[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g;
2653
+ INVOCATION_PARAMETER_MEMBERS = [
2654
+ // Every instrumentation but the anthropic one keeps `model` in the bag, and
2655
+ // it is the literal request field — unlike llm.model_name, see the
2656
+ // omissions below.
2657
+ ["model", GEN_AI_REQUEST_MODEL, asText],
2658
+ ["temperature", GEN_AI_REQUEST_TEMPERATURE, asNumber],
2659
+ ["top_p", GEN_AI_REQUEST_TOP_P, asNumber],
2660
+ ["top_k", GEN_AI_REQUEST_TOP_K, asCount],
2661
+ ["max_tokens", GEN_AI_REQUEST_MAX_TOKENS, asCount],
2662
+ // OpenAI's replacement for max_tokens on the reasoning models: "an upper
2663
+ // bound for the number of tokens that can be generated for a completion",
2664
+ // which is what gen_ai.request.max_tokens means. Listed second so a request
2665
+ // carrying both keeps the one the provider would honour.
2666
+ ["max_completion_tokens", GEN_AI_REQUEST_MAX_TOKENS, asCount],
2667
+ ["frequency_penalty", GEN_AI_REQUEST_FREQUENCY_PENALTY, asNumber],
2668
+ ["presence_penalty", GEN_AI_REQUEST_PRESENCE_PENALTY, asNumber],
2669
+ ["seed", GEN_AI_REQUEST_SEED, asCount],
2670
+ ["n", GEN_AI_REQUEST_CHOICE_COUNT, asCount],
2671
+ ["stop", GEN_AI_REQUEST_STOP_SEQUENCES, asTextList],
2672
+ ["stop_sequences", GEN_AI_REQUEST_STOP_SEQUENCES, asTextList],
2673
+ ["stream", GEN_AI_REQUEST_STREAM, asFlag],
2674
+ // Not a convention key: the GenAI conventions define no tool_choice, so it
2675
+ // goes where an unnamed request parameter goes, rius.request.*. Promoted
2676
+ // because context attribution reads it to tell a forced tool call from an
2677
+ // automatic one, and the bag is content: left inside, it goes wherever
2678
+ // masking sends the bag. It is a routing parameter, not content, and this
2679
+ // runs before masking.
2680
+ ["tool_choice", RIUS_REQUEST_TOOL_CHOICE, asToolChoice]
2681
+ ];
2682
+ REQUEST_PARAMETER_GUARDS = {
2683
+ [GEN_AI_REQUEST_MODEL]: asText,
2684
+ // string
2685
+ [GEN_AI_REQUEST_MAX_TOKENS]: asCount,
2686
+ // int
2687
+ [GEN_AI_REQUEST_CHOICE_COUNT]: asCount,
2688
+ // int
2689
+ [GEN_AI_REQUEST_TEMPERATURE]: asNumber,
2690
+ // double
2691
+ [GEN_AI_REQUEST_TOP_P]: asNumber,
2692
+ // double
2693
+ [GEN_AI_REQUEST_TOP_K]: asCount,
2694
+ // int
2695
+ [GEN_AI_REQUEST_STOP_SEQUENCES]: asTextList,
2696
+ // string[]
2697
+ [GEN_AI_REQUEST_FREQUENCY_PENALTY]: asNumber,
2698
+ // double
2699
+ [GEN_AI_REQUEST_PRESENCE_PENALTY]: asNumber,
2700
+ // double
2701
+ [GEN_AI_REQUEST_ENCODING_FORMATS]: asTextList,
2702
+ // string[]
2703
+ [GEN_AI_REQUEST_SEED]: asCount,
2704
+ // int
2705
+ [GEN_AI_REQUEST_STREAM]: asFlag,
2706
+ // boolean
2707
+ [GEN_AI_REQUEST_REASONING_LEVEL]: asText,
2708
+ // string
2709
+ [GEN_AI_REQUEST_PREVIOUS_RESPONSE_ID]: asText,
2710
+ // string
2711
+ [GEN_AI_REQUEST_STREAM_CURSOR]: asText
2712
+ // string
2713
+ };
2714
+ TAXONOMY_RULES = [
2715
+ { source: OPENINFERENCE_SPAN_KIND, expand: taxonomyFromKind, identity: true },
2716
+ { source: GEN_AI_OPERATION_NAME, expand: taxonomyFromOperation, identity: true }
2717
+ ];
2718
+ OPENINFERENCE_RULES = [
2719
+ // Provider. One rule, two spellings, first present wins: llm.provider is
2720
+ // OpenInference's and the more specific (azure/aws/google rather than the
2721
+ // product), gen_ai.system is the deprecated GenAI key, which is mapped here
2722
+ // and never emitted. That is why gen_ai.system is spelled inline rather
2723
+ // than in semconv.ts: that module is the set of keys we EMIT.
2724
+ {
2725
+ source: ["llm.provider", "gen_ai.system"],
2726
+ target: GEN_AI_PROVIDER_NAME,
2727
+ convert: providerName,
2728
+ identity: true
2729
+ },
2730
+ // Model. Only the anthropic instrumentation emits this unambiguous pair;
2731
+ // see the omission note on llm.model_name.
2732
+ {
2733
+ source: "llm.request.model_name",
2734
+ target: GEN_AI_REQUEST_MODEL,
2735
+ convert: copy,
2736
+ identity: true
2737
+ },
2738
+ { source: "llm.response.model_name", target: GEN_AI_RESPONSE_MODEL, convert: copy },
2739
+ // Tool identity. OpenInference writes the bare key only on TOOL spans (on an
2740
+ // LLM span the same name sits under llm.tools.N.tool.name instead), so the
2741
+ // rule needs no kind guard. Identity, so it also runs at start and a
2742
+ // still-running third-party tool call is named on its pending snapshot, as
2743
+ // a native one is.
2744
+ //
2745
+ // Deliberately NO fallback to the span name. The native path stopped
2746
+ // deriving the tool name from the span name because a span name is not a
2747
+ // tool name, and a wrong name silently groups unrelated calls, which is
2748
+ // worse than an absent one. A third-party span offers no better guarantee.
2749
+ { source: "tool.name", target: GEN_AI_TOOL_NAME, convert: textValue, identity: true },
2750
+ // Why the model stopped. The source is a SCALAR and the canonical key is an
2751
+ // array (one entry per generation), so wrap rather than copy.
2752
+ //
2753
+ // Known limit: the source only ever holds ONE generation's reason. The
2754
+ // OpenInference OpenAI instrumentation reads choices[0].finish_reason on a
2755
+ // completed response, and whichever choice finished last on a stream. With
2756
+ // n > 1 the array therefore has one entry, not n: a request whose choices
2757
+ // finish ["stop", "length", "stop"] records ["stop"], and nothing on the span
2758
+ // says the list is short. Accepted rather than skipped: n > 1 is rare in
2759
+ // agent code, and the first reason is still the most useful single fact.
2760
+ //
2761
+ // The VALUE is passed through untouched, deliberately. The registry defines
2762
+ // this key as a free-form string array with no enum, and says
2763
+ // instrumentations report whatever the provider supplied. The JS
2764
+ // instrumentations already do: they emit the provider's value verbatim.
2765
+ // OpenInference's PYTHON conversion layer is what lowercases and folds
2766
+ // tool_calls/function_call into tool_call; following it would replace the
2767
+ // string OpenAI actually returned with one neither the provider nor the
2768
+ // conventions use, while leaving Anthropic's end_turn and tool_use alone, so
2769
+ // it would cost fidelity and unify nothing. Grouping "stop" with "end_turn"
2770
+ // is a question for a reader who still has both, not for the SDK that would
2771
+ // destroy one of them. The native path (Generation.setFinishReasons) records
2772
+ // verbatim too, so both paths agree.
2773
+ { source: "llm.finish_reason", target: GEN_AI_RESPONSE_FINISH_REASONS, convert: wrapInList },
2774
+ // Usage. toInt rather than copy: a count under a canonical key must be a
2775
+ // count, and a converter that yields nothing is how a wrongly-shaped value
2776
+ // stays off the wire.
2777
+ { source: "llm.token_count.prompt", target: GEN_AI_USAGE_INPUT_TOKENS, convert: toInt },
2778
+ { source: "llm.token_count.completion", target: GEN_AI_USAGE_OUTPUT_TOKENS, convert: toInt },
2779
+ {
2780
+ source: "llm.token_count.prompt_details.cache_read",
2781
+ target: GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
2782
+ convert: toInt
2783
+ },
2784
+ {
2785
+ source: "llm.token_count.prompt_details.cache_write",
2786
+ target: GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS,
2787
+ convert: toInt
2788
+ },
2789
+ {
2790
+ source: "llm.token_count.completion_details.reasoning",
2791
+ target: GEN_AI_USAGE_REASONING_OUTPUT_TOKENS,
2792
+ convert: toInt
2793
+ },
2794
+ // Two GenAI-shaped spellings of the same counts, each its own rule AFTER
2795
+ // the OpenInference one for its target: the OpenInference count keeps the
2796
+ // target where a span somehow carries both, and a separate rule rather than
2797
+ // a second source means an OpenInference count that won't parse still lets
2798
+ // a usable alternate through (a rule converts only its first present
2799
+ // source). Spelled inline for the reason gen_ai.system is: source spellings
2800
+ // we map and never emit.
2801
+ //
2802
+ // cache_creation is the cache-write count's name before the upstream rename
2803
+ // to cache_write. Permanent, not a transition aid: current third-party
2804
+ // releases still emit it (@ai-sdk/otel for every Vercel AI SDK app, and
2805
+ // pydantic-ai), and the backend prices cache writes from the canonical key
2806
+ // alone. The rename is exact, so nothing is lost.
2807
+ {
2808
+ source: "gen_ai.usage.cache_creation.input_tokens",
2809
+ target: GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS,
2810
+ convert: toInt
2811
+ },
2812
+ // pydantic-ai writes OpenAI's reasoning count as one entry of its usage
2813
+ // details namespace. The other providers' names there for the same split
2814
+ // (Anthropic's thinking_tokens, Google's thoughts_tokens) are not mapped,
2815
+ // matching the sink; the rest of the namespace has no canonical key.
2816
+ {
2817
+ source: "gen_ai.usage.details.reasoning_tokens",
2818
+ target: GEN_AI_USAGE_REASONING_OUTPUT_TOKENS,
2819
+ convert: toInt
2820
+ },
2821
+ // The request bag, last: the dedicated model rules above take precedence
2822
+ // over its `model` member. Identity, because what it promotes is.
2823
+ { source: LLM_INVOCATION_PARAMETERS, expand: invocationParameters, identity: true },
2824
+ // The embedding model: OpenInference's EMBEDDING spans name it here. A
2825
+ // non-empty string only, like the tool name. LAST, as in the Python SDK, so
2826
+ // it only fills the key when no LLM spelling above produced one. Identity,
2827
+ // as the request model is, so a pending embedding span is classifiable.
2828
+ {
2829
+ source: "embedding.model_name",
2830
+ target: GEN_AI_REQUEST_MODEL,
2831
+ convert: textValue,
2832
+ identity: true
1121
2833
  }
1122
- sanitized(span) {
1123
- this.sanitizeAttributes(span.attributes);
1124
- for (const event of span.events ?? []) {
1125
- const attributes = event.attributes;
1126
- this.sanitizeAttributes(attributes);
1127
- if (this.opts.captureContent || attributes === void 0) continue;
1128
- if (event.name !== EXCEPTION_EVENT_NAME) continue;
1129
- for (const key of EXCEPTION_CONTENT_KEYS) delete attributes[key];
1130
- }
1131
- for (const link of span.links ?? []) {
1132
- this.sanitizeAttributes(link.attributes);
1133
- }
1134
- this.sanitizeStatus(span);
1135
- return span;
2834
+ ];
2835
+ NORMALIZATION_RULES = [
2836
+ ...TAXONOMY_RULES,
2837
+ ...OPENINFERENCE_RULES
2838
+ ];
2839
+ OPENINFERENCE_FIRST_TOKEN_EVENT = "First Token Stream Event";
2840
+ MESSAGE_FAMILIES = [
2841
+ [LLM_INPUT_MESSAGES_PREFIX, GEN_AI_INPUT_MESSAGES],
2842
+ [LLM_OUTPUT_MESSAGES_PREFIX, GEN_AI_OUTPUT_MESSAGES]
2843
+ ];
2844
+ TOOL_CALL_FIELDS = [
2845
+ [LLM_TOOL_CALL_ID, "id"],
2846
+ [LLM_TOOL_CALL_FUNCTION_NAME, "name"],
2847
+ [LLM_TOOL_CALL_FUNCTION_ARGUMENTS, "arguments"]
2848
+ ];
2849
+ INDEX = /^[+-]?[0-9]+$/;
2850
+ INT64_LIMIT = 2n ** 63n;
2851
+ TOOL_USE_ITEM_FIELDS = new Map(
2852
+ TOOL_CALL_FIELDS.map(([field]) => [`${LLM_TOOL_CALL_PREFIX}${field}`, field])
2853
+ );
2854
+ LLM_TOOL_SCHEMA = /^llm\.tools\.([0-9]+)\.tool\.json_schema$/;
2855
+ EMBEDDING_VECTOR = /^embedding\.embeddings\.[0-9]+\.embedding\.vector$/;
2856
+ NormalizingSpanProcessor = class {
2857
+ rules;
2858
+ identityRules;
2859
+ prefixes;
2860
+ constructor(rules = NORMALIZATION_RULES) {
2861
+ this.rules = rules;
2862
+ this.identityRules = rules.filter((rule) => rule.identity === true);
2863
+ this.prefixes = sourcePrefixes(rules);
1136
2864
  }
1137
- /**
1138
- * Drops or masks the status description, in place. `status` is mutable and
1139
- * exposed by reference on the SDK span, same as `attributes`, so mutating it
1140
- * here reaches the span about to be handed to the inner exporter. The ERROR
1141
- * code itself is left alone so the failure stays visible and classifiable,
1142
- * the same policy as `exception.type`.
1143
- */
1144
- sanitizeStatus(span) {
1145
- const status = span.status;
1146
- if (status?.code !== import_api4.SpanStatusCode.ERROR || !status.message) return;
1147
- if (!this.opts.captureContent) {
1148
- status.message = void 0;
1149
- return;
1150
- }
1151
- if (this.opts.mask === void 0) return;
1152
- try {
1153
- const masked = toAttributeValue(
1154
- this.opts.mask(status.message, { key: STATUS_DESCRIPTION_KEY })
1155
- );
1156
- status.message = typeof masked === "string" ? masked : String(masked);
1157
- } catch {
1158
- status.message = "[mask error]";
1159
- }
2865
+ onStart(span, _parentContext) {
2866
+ this.normalize(
2867
+ span.attributes,
2868
+ this.identityRules,
2869
+ (key, value) => span.setAttribute(key, value)
2870
+ );
2871
+ qualifyRecordedExceptions(span);
1160
2872
  }
1161
- /** Strips or masks the content keys of one attribute bag, in place. */
1162
- sanitizeAttributes(attributes) {
1163
- if (attributes === void 0) return;
1164
- const sanitizing = !this.opts.captureContent || this.opts.mask !== void 0;
1165
- if (sanitizing && LLM_INVOCATION_PARAMETERS in attributes) {
1166
- const redacted = redactInvocationParameters(attributes[LLM_INVOCATION_PARAMETERS]);
1167
- if (redacted === void 0) delete attributes[LLM_INVOCATION_PARAMETERS];
1168
- else attributes[LLM_INVOCATION_PARAMETERS] = redacted;
2873
+ onEnd(span) {
2874
+ const attributes = span.attributes;
2875
+ this.normalize(attributes, this.rules, (key, value) => {
2876
+ attributes[key] = value;
2877
+ });
2878
+ normalizeToolDefinitions(attributes);
2879
+ dropEmbeddingVectors(attributes);
2880
+ for (const [key, value] of Object.entries({
2881
+ ...normalizeFirstTokenEvent(span),
2882
+ ...errorTypeFromExceptionEvent(span),
2883
+ ...responseIdFromOutput(attributes)
2884
+ })) {
2885
+ if (attributes[key] === void 0) attributes[key] = value;
1169
2886
  }
1170
- for (const key of Object.keys(attributes)) {
1171
- if (!isContentKey(key)) continue;
1172
- if (!this.opts.captureContent) {
1173
- delete attributes[key];
1174
- continue;
2887
+ reassembleOpenInferenceMessages(attributes);
2888
+ promoteSystemInstruction(attributes);
2889
+ }
2890
+ normalize(attributes, rules, write) {
2891
+ if (rules.length === 0) return;
2892
+ const bag = attributes;
2893
+ const keys = Object.keys(bag);
2894
+ if (!keys.some((key) => this.prefixes.some((prefix) => key.startsWith(prefix)))) return;
2895
+ const staged = { ...bag };
2896
+ const { mapped, rewritten } = applyRules(staged, rules);
2897
+ const rewrote = new Set(rewritten);
2898
+ for (const key of Object.keys(staged)) {
2899
+ if (staged[key] === void 0) continue;
2900
+ if (bag[key] === void 0 || rewrote.has(key) && bag[key] !== staged[key]) {
2901
+ write(key, staged[key]);
1175
2902
  }
1176
- if (this.opts.mask === void 0) continue;
1177
- try {
1178
- attributes[key] = toAttributeValue(this.opts.mask(attributes[key], { key }));
1179
- } catch {
1180
- attributes[key] = "[mask error]";
2903
+ }
2904
+ for (const { sources, targets } of mapped) {
2905
+ if (targets.every((key) => bag[key] !== void 0)) {
2906
+ for (const key of sources) delete bag[key];
1181
2907
  }
1182
2908
  }
1183
2909
  }
1184
- shutdown() {
1185
- return this.inner.shutdown();
2910
+ async forceFlush() {
1186
2911
  }
1187
- forceFlush() {
1188
- return this.inner.forceFlush?.() ?? Promise.resolve();
2912
+ async shutdown() {
1189
2913
  }
1190
2914
  };
1191
2915
  }
@@ -1204,7 +2928,7 @@ function identityAttributes(attributes) {
1204
2928
  }
1205
2929
  function buildSnapshot(span) {
1206
2930
  const attributes = identityAttributes(span.attributes);
1207
- attributes[GLASSFLOW_SPAN_PENDING] = true;
2931
+ attributes[RIUS_SPAN_PENDING] = true;
1208
2932
  return {
1209
2933
  name: span.name,
1210
2934
  kind: span.kind,
@@ -1213,7 +2937,7 @@ function buildSnapshot(span) {
1213
2937
  startTime: span.startTime,
1214
2938
  endTime: span.startTime,
1215
2939
  duration: [0, 0],
1216
- status: { code: import_api5.SpanStatusCode.UNSET },
2940
+ status: { code: import_api11.SpanStatusCode.UNSET },
1217
2941
  attributes,
1218
2942
  links: [],
1219
2943
  events: [],
@@ -1228,12 +2952,12 @@ function buildSnapshot(span) {
1228
2952
  function spanKey(context2) {
1229
2953
  return `${context2.traceId}:${context2.spanId}`;
1230
2954
  }
1231
- var import_api5, PendingSpanProcessor;
2955
+ var import_api11, PendingSpanProcessor;
1232
2956
  var init_pending = __esm({
1233
2957
  "src/pending.ts"() {
1234
2958
  "use strict";
1235
2959
  init_cjs_shims();
1236
- import_api5 = require("@opentelemetry/api");
2960
+ import_api11 = require("@opentelemetry/api");
1237
2961
  init_semconv();
1238
2962
  PendingSpanProcessor = class {
1239
2963
  delegate;
@@ -1281,17 +3005,18 @@ var init_pending = __esm({
1281
3005
  // src/session.ts
1282
3006
  function withSession(sessionIdOrFn, maybeFn) {
1283
3007
  const [sessionId, fn] = typeof sessionIdOrFn === "function" ? [(0, import_node_crypto.randomUUID)(), sessionIdOrFn] : [sessionIdOrFn, maybeFn];
1284
- return import_api6.context.with(import_api6.context.active().setValue(SESSION_KEY, sessionId), () => fn(sessionId));
3008
+ return import_api12.context.with(import_api12.context.active().setValue(SESSION_KEY, sessionId), () => fn(sessionId));
1285
3009
  }
1286
- var import_node_crypto, import_api6, SESSION_KEY, SessionSpanProcessor;
3010
+ var import_node_crypto, import_api12, SESSION_KEY, SessionSpanProcessor;
1287
3011
  var init_session = __esm({
1288
3012
  "src/session.ts"() {
1289
3013
  "use strict";
1290
3014
  init_cjs_shims();
1291
3015
  import_node_crypto = require("crypto");
1292
- import_api6 = require("@opentelemetry/api");
3016
+ import_api12 = require("@opentelemetry/api");
3017
+ init_foreign();
1293
3018
  init_semconv();
1294
- SESSION_KEY = (0, import_api6.createContextKey)("rius-session-id");
3019
+ SESSION_KEY = (0, import_api12.createContextKey)("rius-session-id");
1295
3020
  SessionSpanProcessor = class {
1296
3021
  constructor(defaultSessionId) {
1297
3022
  this.defaultSessionId = defaultSessionId;
@@ -1299,7 +3024,7 @@ var init_session = __esm({
1299
3024
  defaultSessionId;
1300
3025
  onStart(span, parentContext) {
1301
3026
  const value = parentContext.getValue(SESSION_KEY);
1302
- const parent = import_api6.trace.getSpan(parentContext);
3027
+ const parent = twinOf(import_api12.trace.getSpan(parentContext));
1303
3028
  const inherited = parent?.attributes?.[SESSION_ID];
1304
3029
  const sessionId = typeof value === "string" ? value : typeof inherited === "string" ? inherited : this.defaultSessionId;
1305
3030
  if (sessionId !== void 0) span.setAttribute(SESSION_ID, sessionId);
@@ -1317,7 +3042,7 @@ var init_session = __esm({
1317
3042
  // src/workspace.ts
1318
3043
  function withWorkspace(alias, fn) {
1319
3044
  if (!alias) throw new Error("workspace alias must be a non-empty string");
1320
- return import_api7.context.with(import_api7.context.active().setValue(WORKSPACE_KEY, alias), () => fn(alias));
3045
+ return import_api13.context.with(import_api13.context.active().setValue(WORKSPACE_KEY, alias), () => fn(alias));
1321
3046
  }
1322
3047
  function setGlobalRouting(routing) {
1323
3048
  globalRouting = routing;
@@ -1330,20 +3055,21 @@ function registerWorkspace(alias, apiKey) {
1330
3055
  }
1331
3056
  globalRouting.register(alias, apiKey);
1332
3057
  }
1333
- var import_api7, WORKSPACE_KEY, WorkspaceSpanProcessor, RoutingSpanExporter, globalRouting;
3058
+ var import_api13, WORKSPACE_KEY, WorkspaceSpanProcessor, RoutingSpanExporter, globalRouting;
1334
3059
  var init_workspace = __esm({
1335
3060
  "src/workspace.ts"() {
1336
3061
  "use strict";
1337
3062
  init_cjs_shims();
1338
- import_api7 = require("@opentelemetry/api");
3063
+ import_api13 = require("@opentelemetry/api");
1339
3064
  init_esm();
3065
+ init_foreign();
1340
3066
  init_semconv();
1341
- WORKSPACE_KEY = (0, import_api7.createContextKey)("rius-workspace-alias");
3067
+ WORKSPACE_KEY = (0, import_api13.createContextKey)("rius-workspace-alias");
1342
3068
  WorkspaceSpanProcessor = class {
1343
3069
  /** Straddles already warned about, as "parentAlias->alias"; one warning each, not one per span. */
1344
3070
  warnedStraddles = /* @__PURE__ */ new Set();
1345
3071
  onStart(span, parentContext) {
1346
- const parent = import_api7.trace.getSpan(parentContext);
3072
+ const parent = twinOf(import_api13.trace.getSpan(parentContext));
1347
3073
  const parentAlias = parent?.attributes?.[WORKSPACE_ROUTE];
1348
3074
  const scoped = parentContext.getValue(WORKSPACE_KEY);
1349
3075
  const alias = typeof scoped === "string" ? scoped : parentAlias;
@@ -1444,6 +3170,19 @@ var init_workspace = __esm({
1444
3170
  });
1445
3171
 
1446
3172
  // src/client.ts
3173
+ function resolveSpanLimits() {
3174
+ const fromEnv = (names) => {
3175
+ for (const name of names) {
3176
+ const value = getNumberFromEnv(name);
3177
+ if (value !== void 0) return value;
3178
+ }
3179
+ return void 0;
3180
+ };
3181
+ return {
3182
+ attributeCountLimit: fromEnv(ATTRIBUTE_COUNT_LIMIT_ENV) ?? DEFAULT_SPAN_ATTRIBUTE_COUNT_LIMIT,
3183
+ attributeValueLengthLimit: fromEnv(ATTRIBUTE_VALUE_LENGTH_LIMIT_ENV) ?? Number.POSITIVE_INFINITY
3184
+ };
3185
+ }
1447
3186
  function init(options = {}) {
1448
3187
  if (globalClient !== void 0) {
1449
3188
  console.warn(
@@ -1453,9 +3192,59 @@ function init(options = {}) {
1453
3192
  }
1454
3193
  const config = resolveConfig(options);
1455
3194
  const processors = new DelegatingSpanProcessor();
3195
+ const foreignGlobal = foreignGlobalProviderName();
3196
+ const instanceId = (0, import_node_crypto2.randomUUID)();
3197
+ const resource = (0, import_resources2.resourceFromAttributes)({
3198
+ [import_semantic_conventions2.ATTR_SERVICE_NAME]: config.serviceName,
3199
+ [SERVICE_INSTANCE_ID]: instanceId,
3200
+ // The agent name the heartbeats already carry. Without it here, spans
3201
+ // fall back to service.name downstream while heartbeats group under the
3202
+ // agent name, so a process that configures the two differently sees its
3203
+ // agents view and its trace list disagree. Resolution defaults the agent
3204
+ // name to the service name, so nothing changes when they are the same.
3205
+ [GEN_AI_AGENT_NAME]: config.agentName,
3206
+ // The same fact in our own namespace, and the one the sink reads first.
3207
+ // `gen_ai.agent.name` above is kept ADDITIVELY and on purpose: it has no
3208
+ // resource-level meaning in the conventions and on a span it names the
3209
+ // agent being INVOKED, so it was one key answering two questions — but
3210
+ // dropping it here would be a flag day, blanking agent identity for
3211
+ // every deployment sitting between this SDK release and the sink
3212
+ // release. A resource rides once per OTLP batch, not once per span, so
3213
+ // carrying both costs essentially nothing.
3214
+ //
3215
+ // Spread like `service.version` below, for the same reason: a process
3216
+ // that named nothing resolves to the `unknown_service` placeholder, and
3217
+ // the main-agent name must then be ABSENT rather than claim an identity
3218
+ // the caller never gave — exactly what the span helpers already do.
3219
+ ...config.mainAgentName === void 0 ? {} : { [RIUS_MAIN_AGENT_NAME]: config.mainAgentName },
3220
+ // Optional and never defaulted; see config.ts. `service.instance.id`
3221
+ // above is the process identity, these describe the AGENT the process
3222
+ // runs, which outlives any one process.
3223
+ ...config.mainAgentId === void 0 ? {} : { [RIUS_MAIN_AGENT_ID]: config.mainAgentId },
3224
+ ...config.mainAgentDescription === void 0 ? {} : { [RIUS_MAIN_AGENT_DESCRIPTION]: config.mainAgentDescription },
3225
+ // The version of the AGENT DEFINITION, never derived from (nor deriving)
3226
+ // `service.version` below: the build and the prompt/tools/policy it runs
3227
+ // move independently.
3228
+ ...config.mainAgentVersion === void 0 ? {} : { [RIUS_MAIN_AGENT_VERSION]: config.mainAgentVersion },
3229
+ // `foreign:<Class>` when another SDK already held the OpenTelemetry global
3230
+ // at init(); absent when Rius registered it. Spread, because the ABSENCE of
3231
+ // the key is what says "Rius owns the global here" — a placeholder value
3232
+ // would make every ordinary process look like a resolved conflict.
3233
+ ...foreignGlobal === void 0 ? {} : { [RIUS_SDK_GLOBAL_PROVIDER]: `foreign:${foreignGlobal}` },
3234
+ "telemetry.distro.name": "glassflow-rius",
3235
+ "telemetry.distro.version": SDK_VERSION,
3236
+ // Spread rather than assigned so an unresolved version leaves the key
3237
+ // OFF the resource entirely. `resourceFromAttributes` keeps an explicit
3238
+ // `undefined` as a raw attribute, and there is no placeholder to fall
3239
+ // back on by design: `service.name`'s `unknown_service` is the standing
3240
+ // argument against inventing one, since every unversioned process would
3241
+ // then claim the same fake version.
3242
+ ...config.serviceVersion === void 0 ? {} : { [import_semantic_conventions2.ATTR_SERVICE_VERSION]: config.serviceVersion }
3243
+ });
1456
3244
  const authHeaders = config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {};
1457
3245
  let health;
1458
3246
  let routing;
3247
+ let detector;
1459
3248
  if (!config.disabled) {
1460
3249
  let base = options.spanExporter ?? new import_exporter_trace_otlp_proto.OTLPTraceExporter({
1461
3250
  url: `${config.endpoint}/v1/traces`,
@@ -1474,7 +3263,11 @@ function init(options = {}) {
1474
3263
  captureContent: config.captureContent,
1475
3264
  mask: config.mask
1476
3265
  }) : health;
1477
- const batch = new import_sdk_trace_base.BatchSpanProcessor(exporter);
3266
+ const adopting = new ResourceAdoptingExporter(exporter, resource);
3267
+ const batch = new import_sdk_trace_base2.BatchSpanProcessor(adopting);
3268
+ detector = new ForeignParentDetector(foreignGlobal);
3269
+ processors.add(detector);
3270
+ processors.add(new NormalizingSpanProcessor());
1478
3271
  processors.add(new SessionSpanProcessor(config.sessionId));
1479
3272
  processors.add(new UserSpanProcessor());
1480
3273
  if (routing !== void 0) {
@@ -1485,27 +3278,31 @@ function init(options = {}) {
1485
3278
  }
1486
3279
  processors.add(batch);
1487
3280
  }
1488
- const instanceId = (0, import_node_crypto2.randomUUID)();
3281
+ const sampler = new BridgeAwareSampler(
3282
+ new import_sdk_trace_base2.ParentBasedSampler({ root: new import_sdk_trace_base2.TraceIdRatioBasedSampler(config.sampleRate) })
3283
+ );
3284
+ const spanLimits = resolveSpanLimits();
1489
3285
  const provider = new import_sdk_trace_node.NodeTracerProvider({
1490
- // telemetry.sdk.* is reserved for the OTel SDK itself; we identify as a
1491
- // distribution via telemetry.distro.*, the same two keys Python stamps.
1492
- resource: (0, import_resources.resourceFromAttributes)({
1493
- [import_semantic_conventions.ATTR_SERVICE_NAME]: config.serviceName,
1494
- [SERVICE_INSTANCE_ID]: instanceId,
1495
- "telemetry.distro.name": "glassflow-rius",
1496
- "telemetry.distro.version": SDK_VERSION
1497
- }),
3286
+ resource,
1498
3287
  // Always ParentBased, with no AlwaysOn shortcut at rate 1. They are not
1499
3288
  // equivalent: ParentBased honours a remote UNSAMPLED parent and drops,
1500
3289
  // while AlwaysOn records regardless, producing children of a span the
1501
3290
  // upstream service dropped. Rate 1 is the default, so the shortcut would
1502
3291
  // have been the default path for every user.
1503
- sampler: new import_sdk_trace_base.ParentBasedSampler({ root: new import_sdk_trace_base.TraceIdRatioBasedSampler(config.sampleRate) }),
3292
+ // Wrapped because ParentBased follows the PARENT's sampled flag, and a
3293
+ // bridged parent's flag is the other SDK's decision (usually always-on),
3294
+ // which would ship every trace it touches whatever sampleRate says.
3295
+ sampler,
3296
+ // Already resolved against the environment; see resolveSpanLimits().
3297
+ spanLimits,
1504
3298
  spanProcessors: [processors]
1505
3299
  });
1506
3300
  const teardown = [];
1507
3301
  const ready = config.disabled ? Promise.resolve([]) : enableInstrumentations(processors, provider, void 0, teardown);
3302
+ rememberOwnProvider(provider);
3303
+ ownTracerProvider = provider;
1508
3304
  provider.register();
3305
+ const bridged = config.disabled ? void 0 : bridgeOrWarn(provider, processors, sampler, spanLimits, config.bridgeForeignProvider);
1509
3306
  let heartbeat;
1510
3307
  const heartbeatCanAuthenticate = config.apiKey !== void 0 || options.heartbeatTransport !== void 0;
1511
3308
  if (config.heartbeat && !config.disabled && heartbeatCanAuthenticate) {
@@ -1518,7 +3315,8 @@ function init(options = {}) {
1518
3315
  agentName: config.agentName,
1519
3316
  instanceId,
1520
3317
  tracker,
1521
- transport: options.heartbeatTransport
3318
+ transport: options.heartbeatTransport,
3319
+ foreignParentSpans: detector === void 0 ? void 0 : () => detector.count
1522
3320
  });
1523
3321
  sender.start();
1524
3322
  const beforeExitHandler = () => {
@@ -1527,31 +3325,63 @@ function init(options = {}) {
1527
3325
  process.once("beforeExit", beforeExitHandler);
1528
3326
  heartbeat = { sender, beforeExitHandler };
1529
3327
  }
1530
- globalClient = createClient({ provider, processors, health, ready, teardown, heartbeat });
3328
+ globalClient = createClient({
3329
+ provider,
3330
+ processors,
3331
+ health,
3332
+ ready,
3333
+ teardown,
3334
+ heartbeat,
3335
+ bridge: bridged
3336
+ });
1531
3337
  setGlobalRouting(routing);
3338
+ setConfiguredAgentName(config.agentName);
1532
3339
  return globalClient;
1533
3340
  }
3341
+ function bridgeOrWarn(own, pipeline, sampler, spanLimits, bridgeForeignProvider) {
3342
+ const existing = globalTracerProvider();
3343
+ if (existing === void 0 || existing === own) return void 0;
3344
+ const foreignGlobal = existing.constructor?.name || "unknown";
3345
+ if (bridgeForeignProvider) {
3346
+ const attached = bridge(existing, pipeline, sampler, spanLimits);
3347
+ if (attached !== void 0) {
3348
+ console.info(
3349
+ `[rius] the OpenTelemetry global tracer provider was already set (${foreignGlobal}); Rius attached its span pipeline to it, so spans started through it are exported to Rius too.`
3350
+ );
3351
+ return attached;
3352
+ }
3353
+ }
3354
+ const why = bridgeForeignProvider ? "it takes no additional span processor" : "bridgeForeignProvider is off";
3355
+ console.warn(
3356
+ `[rius] could not register the Rius tracer provider as the OpenTelemetry global (${foreignGlobal} is already set, or a previous init() claimed it), and Rius is not attached to it (${why}). Rius' own helpers (observe, startSpan, generations) follow this client regardless; third-party code using trace.getTracer() keeps the pre-existing provider, and LLM spans started inside its spans arrive without their parent. Set RIUS_BRIDGE_FOREIGN_PROVIDER=true or init({ bridgeForeignProvider: true }) to send that provider's spans to Rius as well.`
3357
+ );
3358
+ return void 0;
3359
+ }
1534
3360
  function getTracer() {
1535
- return import_api8.trace.getTracer(TRACER_NAME, SDK_VERSION);
3361
+ return (ownTracerProvider ?? import_api14.trace).getTracer(TRACER_NAME, SDK_VERSION);
1536
3362
  }
1537
- 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;
3363
+ var import_node_crypto2, import_api14, import_exporter_trace_otlp_proto, import_resources2, import_sdk_trace_base2, import_sdk_trace_node, import_semantic_conventions2, sinks, heartbeats, bridges, createClient, RiusClient, globalClient, DEFAULT_SPAN_ATTRIBUTE_COUNT_LIMIT, ATTRIBUTE_COUNT_LIMIT_ENV, ATTRIBUTE_VALUE_LENGTH_LIMIT_ENV, ownTracerProvider;
1538
3364
  var init_client = __esm({
1539
3365
  "src/client.ts"() {
1540
3366
  "use strict";
1541
3367
  init_cjs_shims();
1542
3368
  import_node_crypto2 = require("crypto");
1543
- import_api8 = require("@opentelemetry/api");
3369
+ import_api14 = require("@opentelemetry/api");
3370
+ init_esm();
1544
3371
  import_exporter_trace_otlp_proto = require("@opentelemetry/exporter-trace-otlp-proto");
1545
- import_resources = require("@opentelemetry/resources");
1546
- import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
3372
+ import_resources2 = require("@opentelemetry/resources");
3373
+ import_sdk_trace_base2 = require("@opentelemetry/sdk-trace-base");
1547
3374
  import_sdk_trace_node = require("@opentelemetry/sdk-trace-node");
1548
- import_semantic_conventions = require("@opentelemetry/semantic-conventions");
3375
+ import_semantic_conventions2 = require("@opentelemetry/semantic-conventions");
3376
+ init_agent();
1549
3377
  init_config();
1550
3378
  init_delegatingProcessor();
1551
3379
  init_exportHealth();
3380
+ init_foreign();
1552
3381
  init_heartbeat();
1553
3382
  init_instrumentation();
1554
3383
  init_masking();
3384
+ init_normalize();
1555
3385
  init_pending();
1556
3386
  init_semconv();
1557
3387
  init_session();
@@ -1560,6 +3390,7 @@ var init_client = __esm({
1560
3390
  init_workspace();
1561
3391
  sinks = /* @__PURE__ */ new WeakMap();
1562
3392
  heartbeats = /* @__PURE__ */ new WeakMap();
3393
+ bridges = /* @__PURE__ */ new WeakMap();
1563
3394
  RiusClient = class _RiusClient {
1564
3395
  provider;
1565
3396
  health;
@@ -1573,6 +3404,7 @@ var init_client = __esm({
1573
3404
  this.teardown = parts.teardown;
1574
3405
  sinks.set(this, parts.processors);
1575
3406
  if (parts.heartbeat) heartbeats.set(this, parts.heartbeat);
3407
+ if (parts.bridge) bridges.set(this, parts.bridge);
1576
3408
  }
1577
3409
  static {
1578
3410
  createClient = (parts) => new _RiusClient(parts);
@@ -1605,19 +3437,28 @@ var init_client = __esm({
1605
3437
  } catch {
1606
3438
  }
1607
3439
  }
3440
+ bridges.get(this)?.release();
1608
3441
  try {
1609
3442
  await this.provider.shutdown();
1610
3443
  } finally {
1611
3444
  if (globalClient === this) {
1612
3445
  globalClient = void 0;
3446
+ ownTracerProvider = void 0;
1613
3447
  setGlobalRouting(void 0);
1614
- import_api8.trace.disable();
1615
- import_api8.context.disable();
1616
- import_api8.propagation.disable();
3448
+ setConfiguredAgentName(void 0);
3449
+ import_api14.trace.disable();
3450
+ import_api14.context.disable();
3451
+ import_api14.propagation.disable();
1617
3452
  }
1618
3453
  }
1619
3454
  }
1620
3455
  };
3456
+ DEFAULT_SPAN_ATTRIBUTE_COUNT_LIMIT = 4096;
3457
+ ATTRIBUTE_COUNT_LIMIT_ENV = ["OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", "OTEL_ATTRIBUTE_COUNT_LIMIT"];
3458
+ ATTRIBUTE_VALUE_LENGTH_LIMIT_ENV = [
3459
+ "OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT",
3460
+ "OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT"
3461
+ ];
1621
3462
  }
1622
3463
  });
1623
3464
 
@@ -1649,30 +3490,185 @@ init_client();
1649
3490
  init_cjs_shims();
1650
3491
  init_client();
1651
3492
 
3493
+ // src/contextSizes.ts
3494
+ init_cjs_shims();
3495
+ var DETAIL_WINDOW = 50;
3496
+ var MAX_SIZES_BYTES = 8192;
3497
+ var SIZES_VERSION = 1;
3498
+ function isRecord(value) {
3499
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3500
+ }
3501
+ function canonicalBytes(value) {
3502
+ try {
3503
+ const text = JSON.stringify(value);
3504
+ return text === void 0 ? 0 : Buffer.byteLength(text);
3505
+ } catch {
3506
+ return 0;
3507
+ }
3508
+ }
3509
+ function toolName(tool) {
3510
+ if (!isRecord(tool)) return null;
3511
+ const fn = tool.function;
3512
+ if (isRecord(fn) && typeof fn.name === "string") return fn.name;
3513
+ return typeof tool.name === "string" ? tool.name : null;
3514
+ }
3515
+ function asToolName(name) {
3516
+ return typeof name === "string" ? name : null;
3517
+ }
3518
+ function partsOf(message) {
3519
+ if (!isRecord(message)) return [];
3520
+ return Array.isArray(message.parts) ? message.parts : [];
3521
+ }
3522
+ function toolKey(name) {
3523
+ return name === null ? "\0null" : `name:${name}`;
3524
+ }
3525
+ var Context = class {
3526
+ callNames = /* @__PURE__ */ new Map();
3527
+ constructor(messageLists) {
3528
+ for (const messages of messageLists) {
3529
+ for (const message of messages) {
3530
+ for (const part of partsOf(message)) {
3531
+ if (isRecord(part) && part.type === "tool_call" && typeof part.id === "string" && !this.callNames.has(part.id)) {
3532
+ this.callNames.set(part.id, part.name);
3533
+ }
3534
+ }
3535
+ }
3536
+ }
3537
+ }
3538
+ partEntry(part) {
3539
+ if (!isRecord(part)) return { type: "unknown" };
3540
+ switch (part.type) {
3541
+ case "text": {
3542
+ const content = part.content ?? null;
3543
+ const bytes = typeof content === "string" ? Buffer.byteLength(content) : canonicalBytes(content);
3544
+ return { type: "text", bytes };
3545
+ }
3546
+ case "tool_call":
3547
+ return { type: "tool_call", tool: asToolName(part.name), bytes: canonicalBytes(part) };
3548
+ case "tool_call_response": {
3549
+ const name = typeof part.id === "string" ? this.callNames.get(part.id) : void 0;
3550
+ return { type: "tool_call_response", tool: asToolName(name), bytes: canonicalBytes(part) };
3551
+ }
3552
+ default:
3553
+ return { type: typeof part.type === "string" ? part.type : "unknown" };
3554
+ }
3555
+ }
3556
+ messageEntry(message) {
3557
+ const role = isRecord(message) ? message.role : void 0;
3558
+ return {
3559
+ role: typeof role === "string" ? role : String(role),
3560
+ parts: partsOf(message).map((part) => this.partEntry(part))
3561
+ };
3562
+ }
3563
+ };
3564
+ function hasCacheMarker(message) {
3565
+ return partsOf(message).some(
3566
+ (part) => isRecord(part) && part.cache_control !== void 0 && part.cache_control !== null
3567
+ );
3568
+ }
3569
+ function fold(entries) {
3570
+ let system = 0;
3571
+ let user = 0;
3572
+ let assistant = 0;
3573
+ let multimodal = 0;
3574
+ const tools = /* @__PURE__ */ new Map();
3575
+ for (const { role, parts } of entries) {
3576
+ for (const part of parts) {
3577
+ if (!("bytes" in part)) {
3578
+ multimodal += 1;
3579
+ } else if (part.type === "text") {
3580
+ if (role === "system" || role === "developer") system += part.bytes;
3581
+ else if (role === "assistant") assistant += part.bytes;
3582
+ else user += part.bytes;
3583
+ } else if ("tool" in part) {
3584
+ const key = toolKey(part.tool);
3585
+ const sum = tools.get(key);
3586
+ if (sum) sum.bytes += part.bytes;
3587
+ else tools.set(key, { tool: part.tool, bytes: part.bytes });
3588
+ }
3589
+ }
3590
+ }
3591
+ const result = { messages: entries.length };
3592
+ if (system > 0) result.system_bytes = system;
3593
+ if (user > 0) result.user_bytes = user;
3594
+ if (assistant > 0) result.assistant_bytes = assistant;
3595
+ if (tools.size > 0) result.tools = [...tools.values()];
3596
+ if (multimodal > 0) result.multimodal_parts = multimodal;
3597
+ return result;
3598
+ }
3599
+ function assemble(toolEntries, inputEntries, outputEntries, cacheIndex, window) {
3600
+ const sizes = { version: SIZES_VERSION };
3601
+ if (toolEntries !== void 0) sizes.tool_definitions = toolEntries;
3602
+ let folded;
3603
+ if (inputEntries !== void 0) {
3604
+ const excess = inputEntries.length - window;
3605
+ if (excess > 0) {
3606
+ folded = fold(inputEntries.slice(0, excess));
3607
+ sizes.input_messages = inputEntries.slice(excess);
3608
+ } else {
3609
+ sizes.input_messages = inputEntries;
3610
+ }
3611
+ }
3612
+ if (outputEntries !== void 0) sizes.output_messages = outputEntries;
3613
+ if (cacheIndex !== void 0) sizes.cache_marker = cacheIndex;
3614
+ if (folded !== void 0) sizes.folded = folded;
3615
+ return sizes;
3616
+ }
3617
+ function asList(value) {
3618
+ return Array.isArray(value) ? value : void 0;
3619
+ }
3620
+ function contextSizes(tools, inputMessages, outputMessages) {
3621
+ try {
3622
+ const toolList = asList(tools);
3623
+ const inputs = asList(inputMessages);
3624
+ const outputs = asList(outputMessages);
3625
+ const context2 = new Context([inputs ?? [], outputs ?? []]);
3626
+ const toolEntries = toolList?.map(
3627
+ (tool) => ({ name: toolName(tool), bytes: canonicalBytes(tool) })
3628
+ );
3629
+ const inputEntries = inputs?.map((message) => context2.messageEntry(message));
3630
+ const outputEntries = outputs?.map((message) => context2.messageEntry(message));
3631
+ let cacheIndex;
3632
+ inputs?.forEach((message, index) => {
3633
+ if (hasCacheMarker(message)) cacheIndex = index;
3634
+ });
3635
+ let window = DETAIL_WINDOW;
3636
+ for (; ; ) {
3637
+ const text = JSON.stringify(
3638
+ assemble(toolEntries, inputEntries, outputEntries, cacheIndex, window)
3639
+ );
3640
+ if (window === 0 || Buffer.byteLength(text) <= MAX_SIZES_BYTES) return text;
3641
+ window = Math.floor(window / 2);
3642
+ }
3643
+ } catch {
3644
+ return JSON.stringify({ version: SIZES_VERSION });
3645
+ }
3646
+ }
3647
+
1652
3648
  // src/messages.ts
1653
3649
  init_cjs_shims();
1654
3650
  init_serde();
1655
- function isRecord(value) {
3651
+ function isRecord2(value) {
1656
3652
  return typeof value === "object" && value !== null && !Array.isArray(value);
1657
3653
  }
1658
- function asText(value) {
3654
+ function asText2(value) {
1659
3655
  if (typeof value === "string") return value;
1660
3656
  const text = toAttributeValue(value);
1661
3657
  return typeof text === "string" ? text : String(text);
1662
3658
  }
1663
3659
  function normalizePart(item) {
1664
- if (isRecord(item)) {
3660
+ if (isRecord2(item)) {
1665
3661
  if (item.type === "text" && "text" in item) return { type: "text", content: item.text };
1666
3662
  if ("type" in item) return item;
1667
3663
  }
1668
- return { type: "text", content: asText(item) };
3664
+ return { type: "text", content: asText2(item) };
1669
3665
  }
1670
3666
  function normalizeMessage(message, defaultRole) {
1671
3667
  if (typeof message === "string") {
1672
3668
  return { role: defaultRole, parts: [{ type: "text", content: message }] };
1673
3669
  }
1674
- if (!isRecord(message)) {
1675
- return { role: defaultRole, parts: [{ type: "text", content: asText(message) }] };
3670
+ if (!isRecord2(message)) {
3671
+ return { role: defaultRole, parts: [{ type: "text", content: asText2(message) }] };
1676
3672
  }
1677
3673
  if ("parts" in message) {
1678
3674
  return { ...message, role: message.role ?? defaultRole };
@@ -1681,7 +3677,16 @@ function normalizeMessage(message, defaultRole) {
1681
3677
  if (role === "tool" && "tool_call_id" in message) {
1682
3678
  return {
1683
3679
  role: "tool",
1684
- parts: [{ type: "tool_call_response", id: message.tool_call_id, response: message.content }]
3680
+ // Missing fields become null, never absent: Python's dict.get() writes
3681
+ // null for them, and the two SDKs' parts must serialize to the same
3682
+ // bytes for the context sizes to agree.
3683
+ parts: [
3684
+ {
3685
+ type: "tool_call_response",
3686
+ id: message.tool_call_id ?? null,
3687
+ response: message.content ?? null
3688
+ }
3689
+ ]
1685
3690
  };
1686
3691
  }
1687
3692
  const parts = [];
@@ -1691,24 +3696,30 @@ function normalizeMessage(message, defaultRole) {
1691
3696
  } else if (Array.isArray(content)) {
1692
3697
  for (const item of content) parts.push(normalizePart(item));
1693
3698
  } else if (content !== null && content !== void 0) {
1694
- parts.push({ type: "text", content: asText(content) });
3699
+ parts.push({ type: "text", content: asText2(content) });
1695
3700
  }
1696
3701
  const toolCalls = message.tool_calls;
1697
3702
  if (Array.isArray(toolCalls)) {
1698
3703
  for (const call of toolCalls) {
1699
- if (!isRecord(call)) continue;
1700
- const fn = isRecord(call.function) ? call.function : {};
1701
- parts.push({ type: "tool_call", id: call.id, name: fn.name, arguments: fn.arguments });
3704
+ if (!isRecord2(call)) continue;
3705
+ const fn = isRecord2(call.function) ? call.function : {};
3706
+ parts.push({
3707
+ type: "tool_call",
3708
+ id: call.id ?? null,
3709
+ name: fn.name ?? null,
3710
+ arguments: fn.arguments ?? null
3711
+ });
1702
3712
  }
1703
3713
  }
1704
3714
  return { role, parts };
1705
3715
  }
1706
- function serializeMessages(messages, defaultRole) {
3716
+ function normalizeMessages(messages, defaultRole) {
1707
3717
  const list = Array.isArray(messages) ? messages : [messages];
1708
- return toAttributeValue(list.map((message) => normalizeMessage(message, defaultRole)));
3718
+ return list.map((message) => normalizeMessage(message, defaultRole));
1709
3719
  }
1710
3720
 
1711
3721
  // src/generation.ts
3722
+ init_normalize();
1712
3723
  init_semconv();
1713
3724
  init_serde();
1714
3725
  init_spans();
@@ -1720,9 +3731,42 @@ var Generation = class extends Observation {
1720
3731
  constructor(span, provider) {
1721
3732
  super(span);
1722
3733
  this.provider = provider;
3734
+ this.span.setAttribute(RIUS_CONTEXT_SIZES, contextSizes(void 0, void 0, void 0));
1723
3735
  }
1724
3736
  provider;
1725
3737
  firstTokenRecorded = false;
3738
+ /**
3739
+ * Monotonic clock at construction, which is span creation for both
3740
+ * `startGeneration` and the scoped form. The API `Span` exposes no start
3741
+ * time, so this is what `recordFirstToken` measures the first chunk against.
3742
+ */
3743
+ startedAt = performance.now();
3744
+ /**
3745
+ * The latest normalized input / output and the tool definitions, kept so
3746
+ * `rius.context.sizes` can be computed from all three once, in {@link end}.
3747
+ */
3748
+ inputMessages;
3749
+ outputMessages;
3750
+ tools;
3751
+ /**
3752
+ * Computed once, as the span ends, rather than on every content write: a
3753
+ * generation with tools, input and output would otherwise pay for it three
3754
+ * times, and only the last result matters. Derived from the normalized
3755
+ * messages BEFORE truncation, which is what makes it trustworthy when the
3756
+ * content attributes are not.
3757
+ */
3758
+ setContextSizes() {
3759
+ if (!this.inputMessages && !this.outputMessages && !this.tools) return;
3760
+ this.span.setAttribute(
3761
+ RIUS_CONTEXT_SIZES,
3762
+ contextSizes(this.tools, this.inputMessages, this.outputMessages)
3763
+ );
3764
+ }
3765
+ /** Ends the span after recording the context sizes; idempotent like the base. */
3766
+ end() {
3767
+ if (!this.ended) this.setContextSizes();
3768
+ super.end();
3769
+ }
1726
3770
  /**
1727
3771
  * Record the request messages (`gen_ai.input.messages`), normalised to the
1728
3772
  * GenAI `{role, parts}` shape like the Python SDK does: bare strings, OpenAI
@@ -1730,12 +3774,14 @@ var Generation = class extends Observation {
1730
3774
  * lists are all accepted. Bare strings default to the `user` role.
1731
3775
  */
1732
3776
  setInput(value) {
1733
- this.span.setAttribute(GEN_AI_INPUT_MESSAGES, serializeMessages(value, "user"));
3777
+ this.inputMessages = normalizeMessages(value, "user");
3778
+ this.span.setAttribute(GEN_AI_INPUT_MESSAGES, toAttributeValue(this.inputMessages));
1734
3779
  return this;
1735
3780
  }
1736
3781
  /** Record the response messages (`gen_ai.output.messages`); bare strings default to `assistant`. */
1737
3782
  setOutput(value) {
1738
- this.span.setAttribute(GEN_AI_OUTPUT_MESSAGES, serializeMessages(value, "assistant"));
3783
+ this.outputMessages = normalizeMessages(value, "assistant");
3784
+ this.span.setAttribute(GEN_AI_OUTPUT_MESSAGES, toAttributeValue(this.outputMessages));
1739
3785
  return this;
1740
3786
  }
1741
3787
  /**
@@ -1747,6 +3793,7 @@ var Generation = class extends Observation {
1747
3793
  * masked/stripped under `captureContent: false` like messages are.
1748
3794
  */
1749
3795
  setToolDefinitions(tools) {
3796
+ this.tools = tools;
1750
3797
  this.span.setAttribute(GEN_AI_TOOL_DEFINITIONS, toAttributeValue(tools));
1751
3798
  return this;
1752
3799
  }
@@ -1754,6 +3801,18 @@ var Generation = class extends Observation {
1754
3801
  this.span.setAttribute(GEN_AI_RESPONSE_MODEL, model);
1755
3802
  return this;
1756
3803
  }
3804
+ /**
3805
+ * The provider's identifier for this completion (`gen_ai.response.id`) —
3806
+ * OpenAI's `id`, Anthropic's `id`, and so on. A post-call setter and not a
3807
+ * creation option for the reason `setModel` is one: the value arrives WITH
3808
+ * the response, so no span can carry it at creation and no pending snapshot
3809
+ * can either. Recorded verbatim; it is an opaque provider string, and it is
3810
+ * not content, so it survives `captureContent: false`.
3811
+ */
3812
+ setResponseId(id) {
3813
+ this.span.setAttribute(GEN_AI_RESPONSE_ID, id);
3814
+ return this;
3815
+ }
1757
3816
  /**
1758
3817
  * Token usage (`gen_ai.usage.*`). Pass provider-reported values as-is;
1759
3818
  * never pre-add anything. Per the GenAI conventions, `inputTokens` is the
@@ -1797,74 +3856,134 @@ var Generation = class extends Observation {
1797
3856
  return this;
1798
3857
  }
1799
3858
  /**
1800
- * The TTFT anchor: event time minus span start. Idempotent: only the
1801
- * first call records the event, so a streaming loop can call this
1802
- * unconditionally on every chunk without inflating the span. A no-op
1803
- * after the span has ended.
3859
+ * Mark the arrival of the first streamed token. Records the
3860
+ * `gen_ai.first_token` event (the timestamp the backend derives TTFT from)
3861
+ * and, per the GenAI conventions, the derived
3862
+ * `gen_ai.response.time_to_first_chunk` (seconds since span creation) plus
3863
+ * `gen_ai.request.stream = true`: a first chunk arriving is what tells the
3864
+ * SDK the request streamed. Idempotent: only the first call records, so a
3865
+ * streaming loop can call this unconditionally on every chunk without
3866
+ * inflating the span. A no-op after the span has ended.
1804
3867
  */
1805
3868
  recordFirstToken() {
1806
3869
  if (this.firstTokenRecorded || !this.span.isRecording()) {
1807
3870
  return this;
1808
3871
  }
3872
+ const elapsedSeconds = Math.max(performance.now() - this.startedAt, 0) / 1e3;
1809
3873
  this.span.addEvent(GEN_AI_FIRST_TOKEN_EVENT);
1810
3874
  this.firstTokenRecorded = true;
3875
+ this.span.setAttribute(GEN_AI_REQUEST_STREAM, true);
3876
+ this.span.setAttribute(GEN_AI_RESPONSE_TIME_TO_FIRST_CHUNK, elapsedSeconds);
1811
3877
  return this;
1812
3878
  }
1813
3879
  };
3880
+ function requestAttributes(modelParameters) {
3881
+ const parameters = new Map(
3882
+ Object.entries(modelParameters ?? {}).filter(([, value]) => value != null)
3883
+ );
3884
+ const attributes = {};
3885
+ for (const [spelling, canonical] of Object.entries(GEN_AI_REQUEST_PARAMETERS)) {
3886
+ if (!parameters.has(spelling)) continue;
3887
+ const value = parameters.get(spelling);
3888
+ parameters.delete(spelling);
3889
+ const guarded = REQUEST_PARAMETER_GUARDS[canonical](value);
3890
+ if (guarded !== void 0 && !Object.hasOwn(attributes, canonical)) {
3891
+ attributes[canonical] = guarded;
3892
+ } else {
3893
+ const coerced = attributeValue(value);
3894
+ if (coerced !== void 0) attributes[`${RIUS_REQUEST_PREFIX}${spelling}`] = coerced;
3895
+ }
3896
+ }
3897
+ for (const [key, value] of parameters) {
3898
+ const coerced = attributeValue(value);
3899
+ if (coerced !== void 0) attributes[requestAttributeKey(key)] = coerced;
3900
+ }
3901
+ return attributes;
3902
+ }
1814
3903
  function attributesFor(options) {
1815
3904
  const attributes = { ...kindAttributes("LLM" /* LLM */) };
1816
3905
  if (options.operation !== void 0) attributes[GEN_AI_OPERATION_NAME] = options.operation;
3906
+ Object.assign(attributes, requestAttributes(options.modelParameters));
3907
+ if (options.reasoningLevel !== void 0) {
3908
+ attributes[GEN_AI_REQUEST_REASONING_LEVEL] = options.reasoningLevel;
3909
+ }
1817
3910
  if (options.model !== void 0) attributes[GEN_AI_REQUEST_MODEL] = options.model;
1818
3911
  if (options.provider !== void 0) attributes[GEN_AI_PROVIDER_NAME] = options.provider;
3912
+ if (options.outputType !== void 0) attributes[GEN_AI_OUTPUT_TYPE] = options.outputType;
1819
3913
  if (options.userId !== void 0) attributes[USER_ID] = options.userId;
1820
3914
  return attributes;
1821
3915
  }
1822
3916
  function configure2(generation, options) {
1823
- for (const [key, value] of Object.entries(options.modelParameters ?? {})) {
1824
- generation.setAttribute(`${GEN_AI_REQUEST_PREFIX}${key}`, value);
1825
- }
1826
- if (options.reasoningLevel !== void 0) {
1827
- generation.setAttribute(GEN_AI_REQUEST_REASONING_LEVEL, options.reasoningLevel);
1828
- }
1829
3917
  if (options.tools !== void 0) generation.setToolDefinitions(options.tools);
1830
3918
  if (options.input !== void 0) generation.setInput(options.input);
1831
3919
  return generation;
1832
3920
  }
1833
- function startGeneration(name, options = {}) {
1834
- const span = getTracer().startSpan(name, { attributes: attributesFor(options) });
3921
+ function resolveName2(name, attributes) {
3922
+ return name ?? composeSpanName("LLM" /* LLM */, attributes);
3923
+ }
3924
+ function startGeneration(nameOrOptions, maybeOptions) {
3925
+ const [name, options] = typeof nameOrOptions === "string" ? [nameOrOptions, maybeOptions ?? {}] : [void 0, nameOrOptions ?? {}];
3926
+ const attributes = attributesFor(options);
3927
+ const span = getTracer().startSpan(resolveName2(name, attributes), {
3928
+ kind: otelSpanKind("LLM" /* LLM */),
3929
+ attributes
3930
+ });
1835
3931
  return configure2(new Generation(span, options.provider), options);
1836
3932
  }
1837
- function startAsCurrentGeneration(name, optionsOrFn, maybeFn) {
1838
- const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
3933
+ function startAsCurrentGeneration(first, second, third) {
3934
+ const { name, options, fn } = splitScopedArgs(
3935
+ first,
3936
+ second,
3937
+ third
3938
+ );
3939
+ const attributes = attributesFor(options);
1839
3940
  return runActive(
1840
- name,
1841
- attributesFor(options),
3941
+ resolveName2(name, attributes),
3942
+ attributes,
1842
3943
  options.userId,
1843
3944
  (span) => configure2(new Generation(span, options.provider), options),
1844
- fn
3945
+ fn,
3946
+ otelSpanKind("LLM" /* LLM */)
1845
3947
  );
1846
3948
  }
1847
3949
 
1848
3950
  // src/observe.ts
1849
3951
  init_cjs_shims();
3952
+ init_semconv();
1850
3953
  init_spans();
1851
3954
  function observe(fn, options = {}) {
1852
- const name = options.name ?? (fn.name || "anonymous");
3955
+ const kind = options.kind ?? "CHAIN" /* CHAIN */;
3956
+ const name = options.name ?? (kind === "CHAIN" /* CHAIN */ ? fn.name || "anonymous" : void 0);
3957
+ const toolName2 = options.toolName ?? (kind === "TOOL" /* TOOL */ && name === void 0 ? fn.name || void 0 : void 0);
1853
3958
  const captureInput = options.captureInput ?? true;
1854
3959
  const captureOutput = options.captureOutput ?? true;
1855
- const wrapped = (...args) => startAsCurrentSpan(
1856
- name,
1857
- // {args, kwargs} is the Python SDK's shape; JavaScript has no keyword
1858
- // arguments, so kwargs is always empty, but the key stays so a saved
1859
- // search or a console view reads both SDKs' input.value the same way.
1860
- { kind: options.kind, input: captureInput ? { args, kwargs: {} } : void 0 },
1861
- async (observation) => {
3960
+ const wrapped = (...args) => {
3961
+ const spanOptions = {
3962
+ kind: options.kind,
3963
+ toolName: toolName2,
3964
+ toolCallId: options.toolCallId,
3965
+ toolType: options.toolType,
3966
+ dataSourceId: options.dataSourceId,
3967
+ topK: options.topK,
3968
+ agentName: options.agentName,
3969
+ agentId: options.agentId,
3970
+ agentVersion: options.agentVersion,
3971
+ // {args, kwargs} is the Python SDK's shape; JavaScript has no keyword
3972
+ // arguments, so kwargs is always empty, but the key stays so a saved
3973
+ // search or a console view reads both SDKs' input.value the same way.
3974
+ input: captureInput ? { args, kwargs: {} } : void 0
3975
+ };
3976
+ const body = async (observation) => {
1862
3977
  const result = await fn(...args);
1863
3978
  if (captureOutput && result !== void 0) observation.setOutput(result);
1864
3979
  return result;
1865
- }
1866
- );
1867
- Object.defineProperty(wrapped, "name", { value: name, configurable: true });
3980
+ };
3981
+ return name === void 0 ? startAsCurrentSpan(spanOptions, body) : startAsCurrentSpan(name, spanOptions, body);
3982
+ };
3983
+ Object.defineProperty(wrapped, "name", {
3984
+ value: name ?? (fn.name || "anonymous"),
3985
+ configurable: true
3986
+ });
1868
3987
  return wrapped;
1869
3988
  }
1870
3989
 
@@ -1874,7 +3993,7 @@ init_session();
1874
3993
  init_user();
1875
3994
  init_spans();
1876
3995
  init_workspace();
1877
- var VERSION = "0.7.0";
3996
+ var VERSION = "1.0.0";
1878
3997
  // Annotate the CommonJS export names for ESM import in node:
1879
3998
  0 && (module.exports = {
1880
3999
  Generation,