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