@glassflow-ai/rius 0.4.0 → 0.6.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/README.md +12 -7
- package/dist/index.cjs +498 -115
- package/dist/index.d.cts +137 -11
- package/dist/index.d.ts +137 -11
- package/dist/index.js +484 -102
- 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_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_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,19 +384,23 @@ 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";
|
|
373
391
|
GEN_AI_REQUEST_MODEL = "gen_ai.request.model";
|
|
392
|
+
GEN_AI_REQUEST_REASONING_LEVEL = "gen_ai.request.reasoning.level";
|
|
374
393
|
GEN_AI_RESPONSE_MODEL = "gen_ai.response.model";
|
|
375
394
|
GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens";
|
|
376
395
|
GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens";
|
|
377
396
|
GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens";
|
|
378
397
|
GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS = "gen_ai.usage.cache_write.input_tokens";
|
|
398
|
+
GEN_AI_USAGE_REASONING_OUTPUT_TOKENS = "gen_ai.usage.reasoning.output_tokens";
|
|
379
399
|
GEN_AI_INPUT_MESSAGES = "gen_ai.input.messages";
|
|
380
400
|
GEN_AI_OUTPUT_MESSAGES = "gen_ai.output.messages";
|
|
381
401
|
GEN_AI_RESPONSE_FINISH_REASONS = "gen_ai.response.finish_reasons";
|
|
382
402
|
GEN_AI_TOOL_NAME = "gen_ai.tool.name";
|
|
403
|
+
GEN_AI_TOOL_DEFINITIONS = "gen_ai.tool.definitions";
|
|
383
404
|
GEN_AI_REQUEST_PREFIX = "gen_ai.request.";
|
|
384
405
|
MCP_RESULT_TYPE = "mcp.result_type";
|
|
385
406
|
GEN_AI_FIRST_TOKEN_EVENT = "gen_ai.first_token";
|
|
@@ -408,7 +429,18 @@ var init_semconv = __esm({
|
|
|
408
429
|
// or internal URLs in parameter defaults. gen_ai.tool.name stays: it is
|
|
409
430
|
// identity, not content.
|
|
410
431
|
"gen_ai.tool.description",
|
|
411
|
-
|
|
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",
|
|
412
444
|
"gen_ai.prompt",
|
|
413
445
|
"gen_ai.completion",
|
|
414
446
|
"llm.input_messages",
|
|
@@ -422,15 +454,44 @@ var init_semconv = __esm({
|
|
|
422
454
|
"mlflow.spanInputs",
|
|
423
455
|
"mlflow.spanOutputs",
|
|
424
456
|
"traceloop.entity.input",
|
|
425
|
-
"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"
|
|
426
483
|
]);
|
|
484
|
+
LLM_INVOCATION_PARAMETERS = "llm.invocation_parameters";
|
|
485
|
+
INVOCATION_PARAMETERS_CONTENT_MEMBERS = ["tools", "functions"];
|
|
427
486
|
CONTENT_ATTRIBUTE_PREFIXES = [
|
|
428
487
|
"llm.input_messages.",
|
|
429
488
|
"llm.output_messages.",
|
|
430
489
|
"gen_ai.prompt.",
|
|
431
490
|
"gen_ai.completion.",
|
|
432
491
|
"llm.prompts.",
|
|
433
|
-
"llm.prompt_template."
|
|
492
|
+
"llm.prompt_template.",
|
|
493
|
+
"llm.tools.",
|
|
494
|
+
"ai.prompt."
|
|
434
495
|
];
|
|
435
496
|
CONTENT_ATTRIBUTE_SUFFIXES = [
|
|
436
497
|
".document.content",
|
|
@@ -445,6 +506,9 @@ var init_semconv = __esm({
|
|
|
445
506
|
// Identity, not content: a pending span must be groupable into its
|
|
446
507
|
// session while still running, that is the live view's whole point.
|
|
447
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,
|
|
448
512
|
// Routing, not content: a crashed run's snapshot must land in the same
|
|
449
513
|
// workspace its final span would have. Stripped at export either way.
|
|
450
514
|
WORKSPACE_ROUTE
|
|
@@ -454,31 +518,77 @@ var init_semconv = __esm({
|
|
|
454
518
|
});
|
|
455
519
|
|
|
456
520
|
// src/serde.ts
|
|
457
|
-
function
|
|
458
|
-
|
|
459
|
-
|
|
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>`;
|
|
460
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;
|
|
461
537
|
if (value === null || value === void 0) return "";
|
|
538
|
+
if (typeof value === "bigint") return String(value);
|
|
462
539
|
const ancestors = [];
|
|
463
540
|
try {
|
|
464
|
-
|
|
465
|
-
|
|
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) {
|
|
466
545
|
while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
|
|
467
546
|
ancestors.pop();
|
|
468
547
|
}
|
|
469
|
-
if (ancestors.includes(
|
|
470
|
-
ancestors.push(
|
|
548
|
+
if (ancestors.includes(rendered)) return "[Circular]";
|
|
549
|
+
ancestors.push(rendered);
|
|
471
550
|
}
|
|
472
|
-
return
|
|
551
|
+
return rendered;
|
|
473
552
|
}) ?? String(value);
|
|
553
|
+
return truncate(text);
|
|
474
554
|
} catch {
|
|
475
555
|
return "[unserializable]";
|
|
476
556
|
}
|
|
477
557
|
}
|
|
558
|
+
var MAX_ATTR_CHARS, TRUNCATION_MARKER;
|
|
478
559
|
var init_serde = __esm({
|
|
479
560
|
"src/serde.ts"() {
|
|
480
561
|
"use strict";
|
|
481
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
|
+
};
|
|
482
592
|
}
|
|
483
593
|
});
|
|
484
594
|
|
|
@@ -487,39 +597,50 @@ function configure(observation, options) {
|
|
|
487
597
|
if (options.input !== void 0) observation.setInput(options.input);
|
|
488
598
|
return observation;
|
|
489
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
|
+
}
|
|
490
605
|
function startSpan(name, options = {}) {
|
|
491
|
-
const span = getTracer().startSpan(name, {
|
|
492
|
-
attributes: kindAttributes(options.kind ?? "CHAIN" /* CHAIN */)
|
|
493
|
-
});
|
|
606
|
+
const span = getTracer().startSpan(name, { attributes: creationAttributes(options) });
|
|
494
607
|
return configure(new Observation(span), options);
|
|
495
608
|
}
|
|
496
609
|
function startAsCurrentSpan(name, optionsOrFn, maybeFn) {
|
|
497
610
|
const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
|
|
498
|
-
return
|
|
611
|
+
return runActive(
|
|
499
612
|
name,
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
return await fn(observation);
|
|
505
|
-
} catch (error) {
|
|
506
|
-
observation.recordException(error);
|
|
507
|
-
throw error;
|
|
508
|
-
} finally {
|
|
509
|
-
observation.end();
|
|
510
|
-
}
|
|
511
|
-
}
|
|
613
|
+
creationAttributes(options),
|
|
614
|
+
options.userId,
|
|
615
|
+
(span) => configure(new Observation(span), options),
|
|
616
|
+
fn
|
|
512
617
|
);
|
|
513
618
|
}
|
|
514
|
-
|
|
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;
|
|
515
634
|
var init_spans = __esm({
|
|
516
635
|
"src/spans.ts"() {
|
|
517
636
|
"use strict";
|
|
518
637
|
init_cjs_shims();
|
|
519
|
-
|
|
638
|
+
import_api2 = require("@opentelemetry/api");
|
|
520
639
|
init_client();
|
|
521
640
|
init_semconv();
|
|
522
641
|
init_serde();
|
|
642
|
+
init_user();
|
|
643
|
+
Symbol.dispose ??= /* @__PURE__ */ Symbol.for("Symbol.dispose");
|
|
523
644
|
Observation = class {
|
|
524
645
|
constructor(span) {
|
|
525
646
|
this.span = span;
|
|
@@ -534,7 +655,26 @@ var init_spans = __esm({
|
|
|
534
655
|
this.span.setAttribute(OUTPUT_VALUE, toAttributeValue(value));
|
|
535
656
|
return this;
|
|
536
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
|
+
*/
|
|
537
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
|
+
}
|
|
538
678
|
this.span.setAttribute(key, toAttributeValue(value));
|
|
539
679
|
return this;
|
|
540
680
|
}
|
|
@@ -549,7 +689,7 @@ var init_spans = __esm({
|
|
|
549
689
|
recordException(error) {
|
|
550
690
|
const wrapped = error instanceof Error ? error : new Error(String(error));
|
|
551
691
|
this.span.recordException(wrapped);
|
|
552
|
-
this.span.setStatus({ code:
|
|
692
|
+
this.span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message: wrapped.message });
|
|
553
693
|
return this;
|
|
554
694
|
}
|
|
555
695
|
end() {
|
|
@@ -570,6 +710,21 @@ var instrumentationMcp_exports = {};
|
|
|
570
710
|
__export(instrumentationMcp_exports, {
|
|
571
711
|
instrumentMcpClient: () => instrumentMcpClient
|
|
572
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
|
+
}
|
|
573
728
|
function resultIsError(result) {
|
|
574
729
|
if (typeof result !== "object" || result === null) return false;
|
|
575
730
|
const record = result;
|
|
@@ -581,10 +736,10 @@ function recordResult(observation, result) {
|
|
|
581
736
|
observation.setAttribute(MCP_RESULT_TYPE, INPUT_REQUIRED);
|
|
582
737
|
return;
|
|
583
738
|
}
|
|
584
|
-
observation.
|
|
739
|
+
observation.setAttribute(OUTPUT_VALUE, serializeResult(result));
|
|
585
740
|
if (resultIsError(result)) {
|
|
586
741
|
observation.span.setStatus({
|
|
587
|
-
code:
|
|
742
|
+
code: import_api3.SpanStatusCode.ERROR,
|
|
588
743
|
message: "tool returned an error result"
|
|
589
744
|
});
|
|
590
745
|
}
|
|
@@ -601,9 +756,14 @@ function instrumentMcpClient(ClientClass) {
|
|
|
601
756
|
const instrumented = function instrumentedCallTool(params, ...rest) {
|
|
602
757
|
return startAsCurrentSpan(
|
|
603
758
|
`execute_tool ${params.name}`,
|
|
604
|
-
|
|
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
|
+
},
|
|
605
766
|
async (observation) => {
|
|
606
|
-
observation.setAttribute(GEN_AI_TOOL_NAME, params.name);
|
|
607
767
|
const result = await original.call(this, params, ...rest);
|
|
608
768
|
recordResult(observation, result);
|
|
609
769
|
return result;
|
|
@@ -616,13 +776,14 @@ function instrumentMcpClient(ClientClass) {
|
|
|
616
776
|
ClientClass.prototype.callTool = original;
|
|
617
777
|
};
|
|
618
778
|
}
|
|
619
|
-
var
|
|
779
|
+
var import_api3, INPUT_REQUIRED;
|
|
620
780
|
var init_instrumentationMcp = __esm({
|
|
621
781
|
"src/instrumentationMcp.ts"() {
|
|
622
782
|
"use strict";
|
|
623
783
|
init_cjs_shims();
|
|
624
|
-
|
|
784
|
+
import_api3 = require("@opentelemetry/api");
|
|
625
785
|
init_semconv();
|
|
786
|
+
init_serde();
|
|
626
787
|
init_spans();
|
|
627
788
|
INPUT_REQUIRED = "input_required";
|
|
628
789
|
}
|
|
@@ -714,21 +875,55 @@ function mcpClientPatchable(exports2) {
|
|
|
714
875
|
const cls = exports2.Client;
|
|
715
876
|
return typeof cls?.prototype?.callTool === "function" ? { Client: cls } : void 0;
|
|
716
877
|
}
|
|
717
|
-
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) {
|
|
718
912
|
const wanted = names ? REGISTRY.filter((e) => names.includes(e.name)) : REGISTRY;
|
|
719
913
|
const enabled = [];
|
|
720
914
|
for (const entry of wanted) {
|
|
721
915
|
try {
|
|
722
|
-
const loaded = await entry.load();
|
|
916
|
+
const loaded = await entry.load(tracerProvider, teardown);
|
|
723
917
|
if (loaded === void 0) continue;
|
|
724
918
|
if (entry.kind === "processor") {
|
|
725
919
|
if (entry.insert === "first") sink.addFirst(loaded);
|
|
726
920
|
else sink.add(loaded);
|
|
727
921
|
} else if (entry.kind === "instrumentation") {
|
|
728
|
-
(0, import_instrumentation.registerInstrumentations)({
|
|
922
|
+
const disable = (0, import_instrumentation.registerInstrumentations)({
|
|
729
923
|
instrumentations: [loaded],
|
|
730
924
|
tracerProvider
|
|
731
925
|
});
|
|
926
|
+
teardown?.push(disable);
|
|
732
927
|
}
|
|
733
928
|
enabled.push(entry.name);
|
|
734
929
|
} catch (error) {
|
|
@@ -737,7 +932,7 @@ async function enableInstrumentations(sink, tracerProvider, names) {
|
|
|
737
932
|
}
|
|
738
933
|
return enabled;
|
|
739
934
|
}
|
|
740
|
-
var import_node_module2, import_node_path, import_instrumentation, dynamicImport, REGISTRY;
|
|
935
|
+
var import_node_module2, import_node_path, import_instrumentation, dynamicImport, vercelTelemetryIntegration, REGISTRY;
|
|
741
936
|
var init_instrumentation = __esm({
|
|
742
937
|
"src/instrumentation.ts"() {
|
|
743
938
|
"use strict";
|
|
@@ -745,6 +940,8 @@ var init_instrumentation = __esm({
|
|
|
745
940
|
import_node_module2 = require("module");
|
|
746
941
|
import_node_path = require("path");
|
|
747
942
|
import_instrumentation = require("@opentelemetry/instrumentation");
|
|
943
|
+
init_semconv();
|
|
944
|
+
init_version();
|
|
748
945
|
dynamicImport = new Function("s", "return import(s)");
|
|
749
946
|
REGISTRY = [
|
|
750
947
|
{
|
|
@@ -755,15 +952,16 @@ var init_instrumentation = __esm({
|
|
|
755
952
|
// transform has to run before the exporting processor sees the span or the
|
|
756
953
|
// attributes it added would never be sanitised.
|
|
757
954
|
insert: "first",
|
|
758
|
-
async load() {
|
|
955
|
+
async load(tracerProvider, teardown) {
|
|
956
|
+
const registered = tracerProvider === void 0 ? false : await registerVercelTelemetry(tracerProvider, teardown);
|
|
759
957
|
const utils = await optional("@arizeai/openinference-vercel/utils");
|
|
760
958
|
const add = utils?.addOpenInferenceAttributesToSpan;
|
|
761
|
-
if (add === void 0) return void 0;
|
|
959
|
+
if (add === void 0 && !registered) return void 0;
|
|
762
960
|
return {
|
|
763
961
|
onStart() {
|
|
764
962
|
},
|
|
765
963
|
onEnd(span) {
|
|
766
|
-
add(span);
|
|
964
|
+
add?.(span);
|
|
767
965
|
},
|
|
768
966
|
async forceFlush() {
|
|
769
967
|
},
|
|
@@ -813,14 +1011,18 @@ var init_instrumentation = __esm({
|
|
|
813
1011
|
{
|
|
814
1012
|
name: "mcp",
|
|
815
1013
|
kind: "self-applying",
|
|
816
|
-
async load() {
|
|
1014
|
+
async load(_tracerProvider, teardown) {
|
|
817
1015
|
const mod = await optional("@modelcontextprotocol/sdk/client/index.js");
|
|
818
1016
|
const ClientClass = mod?.Client;
|
|
819
1017
|
if (ClientClass === void 0) return void 0;
|
|
820
1018
|
const { instrumentMcpClient: instrumentMcpClient2 } = await Promise.resolve().then(() => (init_instrumentationMcp(), instrumentationMcp_exports));
|
|
821
|
-
instrumentMcpClient2(ClientClass);
|
|
1019
|
+
const uninstallEsm = instrumentMcpClient2(ClientClass);
|
|
1020
|
+
teardown?.push(uninstallEsm);
|
|
822
1021
|
const cjs = cachedCjsExports("@modelcontextprotocol/sdk", mcpClientPatchable);
|
|
823
|
-
if (cjs !== void 0)
|
|
1022
|
+
if (cjs !== void 0) {
|
|
1023
|
+
const uninstallCjs = instrumentMcpClient2(cjs.Client);
|
|
1024
|
+
teardown?.push(uninstallCjs);
|
|
1025
|
+
}
|
|
824
1026
|
return true;
|
|
825
1027
|
}
|
|
826
1028
|
}
|
|
@@ -834,16 +1036,35 @@ function isContentKey(key) {
|
|
|
834
1036
|
if (CONTENT_ATTRIBUTE_PREFIXES.some((p) => key.startsWith(p))) return true;
|
|
835
1037
|
return CONTENT_ATTRIBUTE_SUFFIXES.some((s) => key.endsWith(s));
|
|
836
1038
|
}
|
|
837
|
-
|
|
1039
|
+
function redactInvocationParameters(value) {
|
|
1040
|
+
if (typeof value !== "string") return void 0;
|
|
1041
|
+
let parameters;
|
|
1042
|
+
try {
|
|
1043
|
+
parameters = JSON.parse(value);
|
|
1044
|
+
} catch {
|
|
1045
|
+
return void 0;
|
|
1046
|
+
}
|
|
1047
|
+
if (parameters === null || typeof parameters !== "object" || Array.isArray(parameters)) {
|
|
1048
|
+
return void 0;
|
|
1049
|
+
}
|
|
1050
|
+
const bag = parameters;
|
|
1051
|
+
if (!INVOCATION_PARAMETERS_CONTENT_MEMBERS.some((member) => member in bag)) {
|
|
1052
|
+
return value;
|
|
1053
|
+
}
|
|
1054
|
+
for (const member of INVOCATION_PARAMETERS_CONTENT_MEMBERS) delete bag[member];
|
|
1055
|
+
return JSON.stringify(bag);
|
|
1056
|
+
}
|
|
1057
|
+
var import_api4, EXCEPTION_EVENT_NAME, EXCEPTION_CONTENT_KEYS, STATUS_DESCRIPTION_KEY, MaskingSpanExporter;
|
|
838
1058
|
var init_masking = __esm({
|
|
839
1059
|
"src/masking.ts"() {
|
|
840
1060
|
"use strict";
|
|
841
1061
|
init_cjs_shims();
|
|
842
|
-
|
|
1062
|
+
import_api4 = require("@opentelemetry/api");
|
|
843
1063
|
init_semconv();
|
|
844
1064
|
init_serde();
|
|
845
1065
|
EXCEPTION_EVENT_NAME = "exception";
|
|
846
1066
|
EXCEPTION_CONTENT_KEYS = ["exception.message", "exception.stacktrace"];
|
|
1067
|
+
STATUS_DESCRIPTION_KEY = "status.description";
|
|
847
1068
|
MaskingSpanExporter = class {
|
|
848
1069
|
constructor(inner, opts) {
|
|
849
1070
|
this.inner = inner;
|
|
@@ -869,14 +1090,42 @@ var init_masking = __esm({
|
|
|
869
1090
|
for (const link of span.links ?? []) {
|
|
870
1091
|
this.sanitizeAttributes(link.attributes);
|
|
871
1092
|
}
|
|
872
|
-
|
|
873
|
-
span.status.message = void 0;
|
|
874
|
-
}
|
|
1093
|
+
this.sanitizeStatus(span);
|
|
875
1094
|
return span;
|
|
876
1095
|
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Drops or masks the status description, in place. `status` is mutable and
|
|
1098
|
+
* exposed by reference on the SDK span, same as `attributes`, so mutating it
|
|
1099
|
+
* here reaches the span about to be handed to the inner exporter. The ERROR
|
|
1100
|
+
* code itself is left alone so the failure stays visible and classifiable,
|
|
1101
|
+
* the same policy as `exception.type`.
|
|
1102
|
+
*/
|
|
1103
|
+
sanitizeStatus(span) {
|
|
1104
|
+
const status = span.status;
|
|
1105
|
+
if (status?.code !== import_api4.SpanStatusCode.ERROR || !status.message) return;
|
|
1106
|
+
if (!this.opts.captureContent) {
|
|
1107
|
+
status.message = void 0;
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
if (this.opts.mask === void 0) return;
|
|
1111
|
+
try {
|
|
1112
|
+
const masked = toAttributeValue(
|
|
1113
|
+
this.opts.mask(status.message, { key: STATUS_DESCRIPTION_KEY })
|
|
1114
|
+
);
|
|
1115
|
+
status.message = typeof masked === "string" ? masked : String(masked);
|
|
1116
|
+
} catch {
|
|
1117
|
+
status.message = "[mask error]";
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
877
1120
|
/** Strips or masks the content keys of one attribute bag, in place. */
|
|
878
1121
|
sanitizeAttributes(attributes) {
|
|
879
1122
|
if (attributes === void 0) return;
|
|
1123
|
+
const sanitizing = !this.opts.captureContent || this.opts.mask !== void 0;
|
|
1124
|
+
if (sanitizing && LLM_INVOCATION_PARAMETERS in attributes) {
|
|
1125
|
+
const redacted = redactInvocationParameters(attributes[LLM_INVOCATION_PARAMETERS]);
|
|
1126
|
+
if (redacted === void 0) delete attributes[LLM_INVOCATION_PARAMETERS];
|
|
1127
|
+
else attributes[LLM_INVOCATION_PARAMETERS] = redacted;
|
|
1128
|
+
}
|
|
880
1129
|
for (const key of Object.keys(attributes)) {
|
|
881
1130
|
if (!isContentKey(key)) continue;
|
|
882
1131
|
if (!this.opts.captureContent) {
|
|
@@ -923,7 +1172,7 @@ function buildSnapshot(span) {
|
|
|
923
1172
|
startTime: span.startTime,
|
|
924
1173
|
endTime: span.startTime,
|
|
925
1174
|
duration: [0, 0],
|
|
926
|
-
status: { code:
|
|
1175
|
+
status: { code: import_api5.SpanStatusCode.UNSET },
|
|
927
1176
|
attributes,
|
|
928
1177
|
links: [],
|
|
929
1178
|
events: [],
|
|
@@ -935,15 +1184,15 @@ function buildSnapshot(span) {
|
|
|
935
1184
|
ended: true
|
|
936
1185
|
};
|
|
937
1186
|
}
|
|
938
|
-
function spanKey(
|
|
939
|
-
return `${
|
|
1187
|
+
function spanKey(context2) {
|
|
1188
|
+
return `${context2.traceId}:${context2.spanId}`;
|
|
940
1189
|
}
|
|
941
|
-
var
|
|
1190
|
+
var import_api5, PendingSpanProcessor;
|
|
942
1191
|
var init_pending = __esm({
|
|
943
1192
|
"src/pending.ts"() {
|
|
944
1193
|
"use strict";
|
|
945
1194
|
init_cjs_shims();
|
|
946
|
-
|
|
1195
|
+
import_api5 = require("@opentelemetry/api");
|
|
947
1196
|
init_semconv();
|
|
948
1197
|
PendingSpanProcessor = class {
|
|
949
1198
|
delegate;
|
|
@@ -991,17 +1240,17 @@ var init_pending = __esm({
|
|
|
991
1240
|
// src/session.ts
|
|
992
1241
|
function withSession(sessionIdOrFn, maybeFn) {
|
|
993
1242
|
const [sessionId, fn] = typeof sessionIdOrFn === "function" ? [(0, import_node_crypto.randomUUID)(), sessionIdOrFn] : [sessionIdOrFn, maybeFn];
|
|
994
|
-
return
|
|
1243
|
+
return import_api6.context.with(import_api6.context.active().setValue(SESSION_KEY, sessionId), () => fn(sessionId));
|
|
995
1244
|
}
|
|
996
|
-
var import_node_crypto,
|
|
1245
|
+
var import_node_crypto, import_api6, SESSION_KEY, SessionSpanProcessor;
|
|
997
1246
|
var init_session = __esm({
|
|
998
1247
|
"src/session.ts"() {
|
|
999
1248
|
"use strict";
|
|
1000
1249
|
init_cjs_shims();
|
|
1001
1250
|
import_node_crypto = require("crypto");
|
|
1002
|
-
|
|
1251
|
+
import_api6 = require("@opentelemetry/api");
|
|
1003
1252
|
init_semconv();
|
|
1004
|
-
SESSION_KEY = (0,
|
|
1253
|
+
SESSION_KEY = (0, import_api6.createContextKey)("rius-session-id");
|
|
1005
1254
|
SessionSpanProcessor = class {
|
|
1006
1255
|
constructor(defaultSessionId) {
|
|
1007
1256
|
this.defaultSessionId = defaultSessionId;
|
|
@@ -1009,7 +1258,9 @@ var init_session = __esm({
|
|
|
1009
1258
|
defaultSessionId;
|
|
1010
1259
|
onStart(span, parentContext) {
|
|
1011
1260
|
const value = parentContext.getValue(SESSION_KEY);
|
|
1012
|
-
const
|
|
1261
|
+
const parent = import_api6.trace.getSpan(parentContext);
|
|
1262
|
+
const inherited = parent?.attributes?.[SESSION_ID];
|
|
1263
|
+
const sessionId = typeof value === "string" ? value : typeof inherited === "string" ? inherited : this.defaultSessionId;
|
|
1013
1264
|
if (sessionId !== void 0) span.setAttribute(SESSION_ID, sessionId);
|
|
1014
1265
|
}
|
|
1015
1266
|
onEnd(_span) {
|
|
@@ -1025,7 +1276,7 @@ var init_session = __esm({
|
|
|
1025
1276
|
// src/workspace.ts
|
|
1026
1277
|
function withWorkspace(alias, fn) {
|
|
1027
1278
|
if (!alias) throw new Error("workspace alias must be a non-empty string");
|
|
1028
|
-
return
|
|
1279
|
+
return import_api7.context.with(import_api7.context.active().setValue(WORKSPACE_KEY, alias), () => fn(alias));
|
|
1029
1280
|
}
|
|
1030
1281
|
function setGlobalRouting(routing) {
|
|
1031
1282
|
globalRouting = routing;
|
|
@@ -1038,25 +1289,32 @@ function registerWorkspace(alias, apiKey) {
|
|
|
1038
1289
|
}
|
|
1039
1290
|
globalRouting.register(alias, apiKey);
|
|
1040
1291
|
}
|
|
1041
|
-
var
|
|
1292
|
+
var import_api7, WORKSPACE_KEY, WorkspaceSpanProcessor, RoutingSpanExporter, globalRouting;
|
|
1042
1293
|
var init_workspace = __esm({
|
|
1043
1294
|
"src/workspace.ts"() {
|
|
1044
1295
|
"use strict";
|
|
1045
1296
|
init_cjs_shims();
|
|
1046
|
-
|
|
1297
|
+
import_api7 = require("@opentelemetry/api");
|
|
1047
1298
|
init_esm();
|
|
1048
1299
|
init_semconv();
|
|
1049
|
-
WORKSPACE_KEY = (0,
|
|
1300
|
+
WORKSPACE_KEY = (0, import_api7.createContextKey)("rius-workspace-alias");
|
|
1050
1301
|
WorkspaceSpanProcessor = class {
|
|
1302
|
+
/** Straddles already warned about, as "parentAlias->alias"; one warning each, not one per span. */
|
|
1303
|
+
warnedStraddles = /* @__PURE__ */ new Set();
|
|
1051
1304
|
onStart(span, parentContext) {
|
|
1052
|
-
const
|
|
1053
|
-
if (typeof alias !== "string") return;
|
|
1054
|
-
const parent = import_api6.trace.getSpan(parentContext);
|
|
1305
|
+
const parent = import_api7.trace.getSpan(parentContext);
|
|
1055
1306
|
const parentAlias = parent?.attributes?.[WORKSPACE_ROUTE];
|
|
1307
|
+
const scoped = parentContext.getValue(WORKSPACE_KEY);
|
|
1308
|
+
const alias = typeof scoped === "string" ? scoped : parentAlias;
|
|
1309
|
+
if (typeof alias !== "string") return;
|
|
1056
1310
|
if (parentAlias !== void 0 && parentAlias !== alias) {
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1311
|
+
const straddle = `${String(parentAlias)}->${alias}`;
|
|
1312
|
+
if (!this.warnedStraddles.has(straddle)) {
|
|
1313
|
+
this.warnedStraddles.add(straddle);
|
|
1314
|
+
console.warn(
|
|
1315
|
+
`[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.`
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1060
1318
|
}
|
|
1061
1319
|
span.setAttribute(WORKSPACE_ROUTE, alias);
|
|
1062
1320
|
}
|
|
@@ -1177,6 +1435,7 @@ function init(options = {}) {
|
|
|
1177
1435
|
}) : health;
|
|
1178
1436
|
const batch = new import_sdk_trace_base.BatchSpanProcessor(exporter);
|
|
1179
1437
|
processors.add(new SessionSpanProcessor(config.sessionId));
|
|
1438
|
+
processors.add(new UserSpanProcessor());
|
|
1180
1439
|
if (routing !== void 0) {
|
|
1181
1440
|
processors.add(new WorkspaceSpanProcessor());
|
|
1182
1441
|
}
|
|
@@ -1187,9 +1446,13 @@ function init(options = {}) {
|
|
|
1187
1446
|
}
|
|
1188
1447
|
const instanceId = (0, import_node_crypto2.randomUUID)();
|
|
1189
1448
|
const provider = new import_sdk_trace_node.NodeTracerProvider({
|
|
1449
|
+
// telemetry.sdk.* is reserved for the OTel SDK itself; we identify as a
|
|
1450
|
+
// distribution via telemetry.distro.*, the same two keys Python stamps.
|
|
1190
1451
|
resource: (0, import_resources.resourceFromAttributes)({
|
|
1191
1452
|
[import_semantic_conventions.ATTR_SERVICE_NAME]: config.serviceName,
|
|
1192
|
-
[SERVICE_INSTANCE_ID]: instanceId
|
|
1453
|
+
[SERVICE_INSTANCE_ID]: instanceId,
|
|
1454
|
+
"telemetry.distro.name": "glassflow-rius",
|
|
1455
|
+
"telemetry.distro.version": SDK_VERSION
|
|
1193
1456
|
}),
|
|
1194
1457
|
// Always ParentBased, with no AlwaysOn shortcut at rate 1. They are not
|
|
1195
1458
|
// equivalent: ParentBased honours a remote UNSAMPLED parent and drops,
|
|
@@ -1199,10 +1462,12 @@ function init(options = {}) {
|
|
|
1199
1462
|
sampler: new import_sdk_trace_base.ParentBasedSampler({ root: new import_sdk_trace_base.TraceIdRatioBasedSampler(config.sampleRate) }),
|
|
1200
1463
|
spanProcessors: [processors]
|
|
1201
1464
|
});
|
|
1202
|
-
const
|
|
1465
|
+
const teardown = [];
|
|
1466
|
+
const ready = config.disabled ? Promise.resolve([]) : enableInstrumentations(processors, provider, void 0, teardown);
|
|
1203
1467
|
provider.register();
|
|
1204
1468
|
let heartbeat;
|
|
1205
|
-
|
|
1469
|
+
const heartbeatCanAuthenticate = config.apiKey !== void 0 || options.heartbeatTransport !== void 0;
|
|
1470
|
+
if (config.heartbeat && !config.disabled && heartbeatCanAuthenticate) {
|
|
1206
1471
|
const tracker = new OpenRootSpanTracker();
|
|
1207
1472
|
processors.add(tracker);
|
|
1208
1473
|
const sender = new HeartbeatSender({
|
|
@@ -1221,20 +1486,20 @@ function init(options = {}) {
|
|
|
1221
1486
|
process.once("beforeExit", beforeExitHandler);
|
|
1222
1487
|
heartbeat = { sender, beforeExitHandler };
|
|
1223
1488
|
}
|
|
1224
|
-
globalClient = createClient({ provider, processors, health, ready, heartbeat });
|
|
1489
|
+
globalClient = createClient({ provider, processors, health, ready, teardown, heartbeat });
|
|
1225
1490
|
setGlobalRouting(routing);
|
|
1226
1491
|
return globalClient;
|
|
1227
1492
|
}
|
|
1228
1493
|
function getTracer() {
|
|
1229
|
-
return
|
|
1494
|
+
return import_api8.trace.getTracer(TRACER_NAME, SDK_VERSION);
|
|
1230
1495
|
}
|
|
1231
|
-
var import_node_crypto2,
|
|
1496
|
+
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;
|
|
1232
1497
|
var init_client = __esm({
|
|
1233
1498
|
"src/client.ts"() {
|
|
1234
1499
|
"use strict";
|
|
1235
1500
|
init_cjs_shims();
|
|
1236
1501
|
import_node_crypto2 = require("crypto");
|
|
1237
|
-
|
|
1502
|
+
import_api8 = require("@opentelemetry/api");
|
|
1238
1503
|
import_exporter_trace_otlp_proto = require("@opentelemetry/exporter-trace-otlp-proto");
|
|
1239
1504
|
import_resources = require("@opentelemetry/resources");
|
|
1240
1505
|
import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
@@ -1249,18 +1514,22 @@ var init_client = __esm({
|
|
|
1249
1514
|
init_pending();
|
|
1250
1515
|
init_semconv();
|
|
1251
1516
|
init_session();
|
|
1517
|
+
init_user();
|
|
1518
|
+
init_version();
|
|
1252
1519
|
init_workspace();
|
|
1253
1520
|
sinks = /* @__PURE__ */ new WeakMap();
|
|
1254
1521
|
heartbeats = /* @__PURE__ */ new WeakMap();
|
|
1255
1522
|
RiusClient = class _RiusClient {
|
|
1256
1523
|
provider;
|
|
1257
1524
|
health;
|
|
1525
|
+
teardown;
|
|
1258
1526
|
/** Resolves with the names of the auto-instrumentations that attached. */
|
|
1259
1527
|
ready;
|
|
1260
1528
|
constructor(parts) {
|
|
1261
1529
|
this.provider = parts.provider;
|
|
1262
1530
|
this.health = parts.health;
|
|
1263
1531
|
this.ready = parts.ready;
|
|
1532
|
+
this.teardown = parts.teardown;
|
|
1264
1533
|
sinks.set(this, parts.processors);
|
|
1265
1534
|
if (parts.heartbeat) heartbeats.set(this, parts.heartbeat);
|
|
1266
1535
|
}
|
|
@@ -1288,13 +1557,22 @@ var init_client = __esm({
|
|
|
1288
1557
|
process.removeListener("beforeExit", heartbeat.beforeExitHandler);
|
|
1289
1558
|
await heartbeat.sender.stop();
|
|
1290
1559
|
}
|
|
1560
|
+
await this.ready;
|
|
1561
|
+
for (const disable of this.teardown.splice(0)) {
|
|
1562
|
+
try {
|
|
1563
|
+
disable();
|
|
1564
|
+
} catch {
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1291
1567
|
try {
|
|
1292
1568
|
await this.provider.shutdown();
|
|
1293
1569
|
} finally {
|
|
1294
1570
|
if (globalClient === this) {
|
|
1295
1571
|
globalClient = void 0;
|
|
1296
1572
|
setGlobalRouting(void 0);
|
|
1297
|
-
|
|
1573
|
+
import_api8.trace.disable();
|
|
1574
|
+
import_api8.context.disable();
|
|
1575
|
+
import_api8.propagation.disable();
|
|
1298
1576
|
}
|
|
1299
1577
|
}
|
|
1300
1578
|
}
|
|
@@ -1319,6 +1597,7 @@ __export(index_exports, {
|
|
|
1319
1597
|
startGeneration: () => startGeneration,
|
|
1320
1598
|
startSpan: () => startSpan,
|
|
1321
1599
|
withSession: () => withSession,
|
|
1600
|
+
withUser: () => withUser,
|
|
1322
1601
|
withWorkspace: () => withWorkspace
|
|
1323
1602
|
});
|
|
1324
1603
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -1328,17 +1607,106 @@ init_client();
|
|
|
1328
1607
|
// src/generation.ts
|
|
1329
1608
|
init_cjs_shims();
|
|
1330
1609
|
init_client();
|
|
1610
|
+
|
|
1611
|
+
// src/messages.ts
|
|
1612
|
+
init_cjs_shims();
|
|
1613
|
+
init_serde();
|
|
1614
|
+
function isRecord(value) {
|
|
1615
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1616
|
+
}
|
|
1617
|
+
function asText(value) {
|
|
1618
|
+
if (typeof value === "string") return value;
|
|
1619
|
+
const text = toAttributeValue(value);
|
|
1620
|
+
return typeof text === "string" ? text : String(text);
|
|
1621
|
+
}
|
|
1622
|
+
function normalizePart(item) {
|
|
1623
|
+
if (isRecord(item)) {
|
|
1624
|
+
if (item.type === "text" && "text" in item) return { type: "text", content: item.text };
|
|
1625
|
+
if ("type" in item) return item;
|
|
1626
|
+
}
|
|
1627
|
+
return { type: "text", content: asText(item) };
|
|
1628
|
+
}
|
|
1629
|
+
function normalizeMessage(message, defaultRole) {
|
|
1630
|
+
if (typeof message === "string") {
|
|
1631
|
+
return { role: defaultRole, parts: [{ type: "text", content: message }] };
|
|
1632
|
+
}
|
|
1633
|
+
if (!isRecord(message)) {
|
|
1634
|
+
return { role: defaultRole, parts: [{ type: "text", content: asText(message) }] };
|
|
1635
|
+
}
|
|
1636
|
+
if ("parts" in message) {
|
|
1637
|
+
return { ...message, role: message.role ?? defaultRole };
|
|
1638
|
+
}
|
|
1639
|
+
const role = message.role ?? defaultRole;
|
|
1640
|
+
if (role === "tool" && "tool_call_id" in message) {
|
|
1641
|
+
return {
|
|
1642
|
+
role: "tool",
|
|
1643
|
+
parts: [{ type: "tool_call_response", id: message.tool_call_id, response: message.content }]
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
const parts = [];
|
|
1647
|
+
const content = message.content;
|
|
1648
|
+
if (typeof content === "string") {
|
|
1649
|
+
parts.push({ type: "text", content });
|
|
1650
|
+
} else if (Array.isArray(content)) {
|
|
1651
|
+
for (const item of content) parts.push(normalizePart(item));
|
|
1652
|
+
} else if (content !== null && content !== void 0) {
|
|
1653
|
+
parts.push({ type: "text", content: asText(content) });
|
|
1654
|
+
}
|
|
1655
|
+
const toolCalls = message.tool_calls;
|
|
1656
|
+
if (Array.isArray(toolCalls)) {
|
|
1657
|
+
for (const call of toolCalls) {
|
|
1658
|
+
if (!isRecord(call)) continue;
|
|
1659
|
+
const fn = isRecord(call.function) ? call.function : {};
|
|
1660
|
+
parts.push({ type: "tool_call", id: call.id, name: fn.name, arguments: fn.arguments });
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
return { role, parts };
|
|
1664
|
+
}
|
|
1665
|
+
function serializeMessages(messages, defaultRole) {
|
|
1666
|
+
const list = Array.isArray(messages) ? messages : [messages];
|
|
1667
|
+
return toAttributeValue(list.map((message) => normalizeMessage(message, defaultRole)));
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// src/generation.ts
|
|
1331
1671
|
init_semconv();
|
|
1332
1672
|
init_serde();
|
|
1333
1673
|
init_spans();
|
|
1334
1674
|
var Generation = class extends Observation {
|
|
1675
|
+
/**
|
|
1676
|
+
* The provider passed at creation; drives the Anthropic input-token summing
|
|
1677
|
+
* in {@link setUsage}. A bare `new Generation(span)` has none and never sums.
|
|
1678
|
+
*/
|
|
1679
|
+
constructor(span, provider) {
|
|
1680
|
+
super(span);
|
|
1681
|
+
this.provider = provider;
|
|
1682
|
+
}
|
|
1683
|
+
provider;
|
|
1335
1684
|
firstTokenRecorded = false;
|
|
1685
|
+
/**
|
|
1686
|
+
* Record the request messages (`gen_ai.input.messages`), normalised to the
|
|
1687
|
+
* GenAI `{role, parts}` shape like the Python SDK does: bare strings, OpenAI
|
|
1688
|
+
* dicts (including `tool_calls` and tool responses) and multimodal content
|
|
1689
|
+
* lists are all accepted. Bare strings default to the `user` role.
|
|
1690
|
+
*/
|
|
1336
1691
|
setInput(value) {
|
|
1337
|
-
this.span.setAttribute(GEN_AI_INPUT_MESSAGES,
|
|
1692
|
+
this.span.setAttribute(GEN_AI_INPUT_MESSAGES, serializeMessages(value, "user"));
|
|
1338
1693
|
return this;
|
|
1339
1694
|
}
|
|
1695
|
+
/** Record the response messages (`gen_ai.output.messages`); bare strings default to `assistant`. */
|
|
1340
1696
|
setOutput(value) {
|
|
1341
|
-
this.span.setAttribute(GEN_AI_OUTPUT_MESSAGES,
|
|
1697
|
+
this.span.setAttribute(GEN_AI_OUTPUT_MESSAGES, serializeMessages(value, "assistant"));
|
|
1698
|
+
return this;
|
|
1699
|
+
}
|
|
1700
|
+
/**
|
|
1701
|
+
* The request's tool/function definitions (`gen_ai.tool.definitions`).
|
|
1702
|
+
* Serialized verbatim, in whatever shape the provider request used (OpenAI
|
|
1703
|
+
* nests each tool under `function`, Anthropic uses top-level
|
|
1704
|
+
* `name`/`input_schema`) — no normalization, so what is recorded is exactly
|
|
1705
|
+
* what the model was shown. Definitions are content, not identity: they are
|
|
1706
|
+
* masked/stripped under `captureContent: false` like messages are.
|
|
1707
|
+
*/
|
|
1708
|
+
setToolDefinitions(tools) {
|
|
1709
|
+
this.span.setAttribute(GEN_AI_TOOL_DEFINITIONS, toAttributeValue(tools));
|
|
1342
1710
|
return this;
|
|
1343
1711
|
}
|
|
1344
1712
|
setModel(model) {
|
|
@@ -1346,15 +1714,20 @@ var Generation = class extends Observation {
|
|
|
1346
1714
|
return this;
|
|
1347
1715
|
}
|
|
1348
1716
|
/**
|
|
1349
|
-
* Token usage (`gen_ai.usage.*`). Pass provider-reported values as-is
|
|
1350
|
-
* Per the GenAI conventions, `inputTokens` is the
|
|
1351
|
-
* tokens (the cache counts are subsets of it)
|
|
1352
|
-
*
|
|
1353
|
-
* the
|
|
1717
|
+
* Token usage (`gen_ai.usage.*`). Pass provider-reported values as-is;
|
|
1718
|
+
* never pre-add anything. Per the GenAI conventions, `inputTokens` is the
|
|
1719
|
+
* total including cached tokens (the cache counts are subsets of it).
|
|
1720
|
+
* Anthropic's API reports `input_tokens` excluding the cache counts, and
|
|
1721
|
+
* the conventions require the instrumentation to do the summing, so when
|
|
1722
|
+
* the generation's provider is `"anthropic"` the emitted total is
|
|
1723
|
+
* `inputTokens` plus both cache counts. Every other provider is recorded
|
|
1724
|
+
* verbatim.
|
|
1354
1725
|
*/
|
|
1355
1726
|
setUsage(usage) {
|
|
1356
1727
|
if (usage.inputTokens !== void 0) {
|
|
1357
|
-
this.
|
|
1728
|
+
const sums = this.provider?.toLowerCase() === "anthropic";
|
|
1729
|
+
const total = sums ? usage.inputTokens + (usage.cacheReadInputTokens ?? 0) + (usage.cacheWriteInputTokens ?? 0) : usage.inputTokens;
|
|
1730
|
+
this.span.setAttribute(GEN_AI_USAGE_INPUT_TOKENS, total);
|
|
1358
1731
|
}
|
|
1359
1732
|
if (usage.outputTokens !== void 0) {
|
|
1360
1733
|
this.span.setAttribute(GEN_AI_USAGE_OUTPUT_TOKENS, usage.outputTokens);
|
|
@@ -1365,6 +1738,9 @@ var Generation = class extends Observation {
|
|
|
1365
1738
|
if (usage.cacheWriteInputTokens !== void 0) {
|
|
1366
1739
|
this.span.setAttribute(GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS, usage.cacheWriteInputTokens);
|
|
1367
1740
|
}
|
|
1741
|
+
if (usage.reasoningOutputTokens !== void 0) {
|
|
1742
|
+
this.span.setAttribute(GEN_AI_USAGE_REASONING_OUTPUT_TOKENS, usage.reasoningOutputTokens);
|
|
1743
|
+
}
|
|
1368
1744
|
return this;
|
|
1369
1745
|
}
|
|
1370
1746
|
/**
|
|
@@ -1396,34 +1772,36 @@ var Generation = class extends Observation {
|
|
|
1396
1772
|
};
|
|
1397
1773
|
function attributesFor(options) {
|
|
1398
1774
|
const attributes = { ...kindAttributes("LLM" /* LLM */) };
|
|
1775
|
+
if (options.operation !== void 0) attributes[GEN_AI_OPERATION_NAME] = options.operation;
|
|
1399
1776
|
if (options.model !== void 0) attributes[GEN_AI_REQUEST_MODEL] = options.model;
|
|
1400
1777
|
if (options.provider !== void 0) attributes[GEN_AI_PROVIDER_NAME] = options.provider;
|
|
1778
|
+
if (options.userId !== void 0) attributes[USER_ID] = options.userId;
|
|
1401
1779
|
return attributes;
|
|
1402
1780
|
}
|
|
1403
1781
|
function configure2(generation, options) {
|
|
1404
1782
|
for (const [key, value] of Object.entries(options.modelParameters ?? {})) {
|
|
1405
1783
|
generation.setAttribute(`${GEN_AI_REQUEST_PREFIX}${key}`, value);
|
|
1406
1784
|
}
|
|
1785
|
+
if (options.reasoningLevel !== void 0) {
|
|
1786
|
+
generation.setAttribute(GEN_AI_REQUEST_REASONING_LEVEL, options.reasoningLevel);
|
|
1787
|
+
}
|
|
1788
|
+
if (options.tools !== void 0) generation.setToolDefinitions(options.tools);
|
|
1407
1789
|
if (options.input !== void 0) generation.setInput(options.input);
|
|
1408
1790
|
return generation;
|
|
1409
1791
|
}
|
|
1410
1792
|
function startGeneration(name, options = {}) {
|
|
1411
1793
|
const span = getTracer().startSpan(name, { attributes: attributesFor(options) });
|
|
1412
|
-
return configure2(new Generation(span), options);
|
|
1794
|
+
return configure2(new Generation(span, options.provider), options);
|
|
1413
1795
|
}
|
|
1414
1796
|
function startAsCurrentGeneration(name, optionsOrFn, maybeFn) {
|
|
1415
1797
|
const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
|
|
1416
|
-
return
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
} finally {
|
|
1424
|
-
generation.end();
|
|
1425
|
-
}
|
|
1426
|
-
});
|
|
1798
|
+
return runActive(
|
|
1799
|
+
name,
|
|
1800
|
+
attributesFor(options),
|
|
1801
|
+
options.userId,
|
|
1802
|
+
(span) => configure2(new Generation(span, options.provider), options),
|
|
1803
|
+
fn
|
|
1804
|
+
);
|
|
1427
1805
|
}
|
|
1428
1806
|
|
|
1429
1807
|
// src/observe.ts
|
|
@@ -1435,7 +1813,10 @@ function observe(fn, options = {}) {
|
|
|
1435
1813
|
const captureOutput = options.captureOutput ?? true;
|
|
1436
1814
|
const wrapped = (...args) => startAsCurrentSpan(
|
|
1437
1815
|
name,
|
|
1438
|
-
{
|
|
1816
|
+
// {args, kwargs} is the Python SDK's shape; JavaScript has no keyword
|
|
1817
|
+
// arguments, so kwargs is always empty, but the key stays so a saved
|
|
1818
|
+
// search or a console view reads both SDKs' input.value the same way.
|
|
1819
|
+
{ kind: options.kind, input: captureInput ? { args, kwargs: {} } : void 0 },
|
|
1439
1820
|
async (observation) => {
|
|
1440
1821
|
const result = await fn(...args);
|
|
1441
1822
|
if (captureOutput && result !== void 0) observation.setOutput(result);
|
|
@@ -1449,9 +1830,10 @@ function observe(fn, options = {}) {
|
|
|
1449
1830
|
// src/index.ts
|
|
1450
1831
|
init_semconv();
|
|
1451
1832
|
init_session();
|
|
1833
|
+
init_user();
|
|
1452
1834
|
init_spans();
|
|
1453
1835
|
init_workspace();
|
|
1454
|
-
var VERSION = "0.
|
|
1836
|
+
var VERSION = "0.6.0";
|
|
1455
1837
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1456
1838
|
0 && (module.exports = {
|
|
1457
1839
|
Generation,
|
|
@@ -1468,5 +1850,6 @@ var VERSION = "0.4.0";
|
|
|
1468
1850
|
startGeneration,
|
|
1469
1851
|
startSpan,
|
|
1470
1852
|
withSession,
|
|
1853
|
+
withUser,
|
|
1471
1854
|
withWorkspace
|
|
1472
1855
|
});
|