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