@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.js
CHANGED
|
@@ -40,7 +40,11 @@ function seconds(raw, fallback) {
|
|
|
40
40
|
if (Number.isNaN(parsed)) return fallback;
|
|
41
41
|
return parsed;
|
|
42
42
|
}
|
|
43
|
-
function clamp(name, value, min, max) {
|
|
43
|
+
function clamp(name, value, min, max, fallback) {
|
|
44
|
+
if (!Number.isFinite(value)) {
|
|
45
|
+
console.warn(`[rius] ${name}=${value} is not a finite number; using ${fallback}.`);
|
|
46
|
+
return fallback;
|
|
47
|
+
}
|
|
44
48
|
if (value >= min && value <= max) return value;
|
|
45
49
|
const clamped = Math.min(Math.max(value, min), max);
|
|
46
50
|
console.warn(`[rius] ${name}=${value} is outside [${min}, ${max}]; clamped to ${clamped}.`);
|
|
@@ -53,13 +57,15 @@ function resolveConfig(options = {}, env = process.env) {
|
|
|
53
57
|
"heartbeatInterval",
|
|
54
58
|
options.heartbeatInterval ?? seconds(env.RIUS_HEARTBEAT_INTERVAL, DEFAULT_HEARTBEAT_INTERVAL),
|
|
55
59
|
HEARTBEAT_INTERVAL_MIN,
|
|
56
|
-
HEARTBEAT_INTERVAL_MAX
|
|
60
|
+
HEARTBEAT_INTERVAL_MAX,
|
|
61
|
+
DEFAULT_HEARTBEAT_INTERVAL
|
|
57
62
|
);
|
|
58
63
|
const partialSpansDelaySeconds = clamp(
|
|
59
64
|
"partialSpansDelay",
|
|
60
65
|
options.partialSpansDelay ?? seconds(env.RIUS_PARTIAL_SPANS_DELAY, DEFAULT_PARTIAL_SPANS_DELAY),
|
|
61
66
|
PARTIAL_SPANS_DELAY_MIN,
|
|
62
|
-
PARTIAL_SPANS_DELAY_MAX
|
|
67
|
+
PARTIAL_SPANS_DELAY_MAX,
|
|
68
|
+
DEFAULT_PARTIAL_SPANS_DELAY
|
|
63
69
|
);
|
|
64
70
|
return {
|
|
65
71
|
endpoint,
|
|
@@ -208,8 +214,25 @@ var init_exportHealth = __esm({
|
|
|
208
214
|
}
|
|
209
215
|
});
|
|
210
216
|
|
|
211
|
-
// src/
|
|
217
|
+
// src/version.ts
|
|
212
218
|
import { createRequire } from "module";
|
|
219
|
+
var SDK_VERSION;
|
|
220
|
+
var init_version = __esm({
|
|
221
|
+
"src/version.ts"() {
|
|
222
|
+
"use strict";
|
|
223
|
+
init_esm_shims();
|
|
224
|
+
SDK_VERSION = (() => {
|
|
225
|
+
try {
|
|
226
|
+
const pkg = createRequire(import.meta.url)("../package.json");
|
|
227
|
+
return pkg.version ?? "0.0.0";
|
|
228
|
+
} catch {
|
|
229
|
+
return "0.0.0";
|
|
230
|
+
}
|
|
231
|
+
})();
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// src/heartbeat.ts
|
|
213
236
|
function httpTransport(url, headers) {
|
|
214
237
|
return async (payload, timeoutMs) => {
|
|
215
238
|
const response = await fetch(url, {
|
|
@@ -225,23 +248,16 @@ function httpTransport(url, headers) {
|
|
|
225
248
|
}
|
|
226
249
|
};
|
|
227
250
|
}
|
|
228
|
-
var PAYLOAD_VERSION, OPEN_TRACES_CAP, DEFAULT_PING_TIMEOUT_MS, DEFAULT_FINAL_PING_TIMEOUT_MS,
|
|
251
|
+
var PAYLOAD_VERSION, OPEN_TRACES_CAP, DEFAULT_PING_TIMEOUT_MS, DEFAULT_FINAL_PING_TIMEOUT_MS, OpenRootSpanTracker, HeartbeatSender;
|
|
229
252
|
var init_heartbeat = __esm({
|
|
230
253
|
"src/heartbeat.ts"() {
|
|
231
254
|
"use strict";
|
|
232
255
|
init_esm_shims();
|
|
256
|
+
init_version();
|
|
233
257
|
PAYLOAD_VERSION = 1;
|
|
234
258
|
OPEN_TRACES_CAP = 32;
|
|
235
259
|
DEFAULT_PING_TIMEOUT_MS = 3e3;
|
|
236
260
|
DEFAULT_FINAL_PING_TIMEOUT_MS = 1e3;
|
|
237
|
-
SDK_VERSION = (() => {
|
|
238
|
-
try {
|
|
239
|
-
const pkg = createRequire(import.meta.url)("../package.json");
|
|
240
|
-
return pkg.version ?? "0.0.0";
|
|
241
|
-
} catch {
|
|
242
|
-
return "0.0.0";
|
|
243
|
-
}
|
|
244
|
-
})();
|
|
245
261
|
OpenRootSpanTracker = class {
|
|
246
262
|
// trace id (32-hex) -> count of open root spans in that trace. A trace id
|
|
247
263
|
// normally has one root, but the count guards against duplicates.
|
|
@@ -293,6 +309,7 @@ var init_heartbeat = __esm({
|
|
|
293
309
|
}
|
|
294
310
|
/** Starts pinging: an immediate first ping, then every `intervalMs`. */
|
|
295
311
|
start() {
|
|
312
|
+
if (this.timer !== void 0) return;
|
|
296
313
|
void this.ping(this.pingTimeoutMs, false);
|
|
297
314
|
const timer = setInterval(() => void this.ping(this.pingTimeoutMs, false), this.intervalMs);
|
|
298
315
|
if (typeof timer.unref === "function") timer.unref();
|
|
@@ -343,7 +360,7 @@ function kindAttributes(kind) {
|
|
|
343
360
|
if (operation !== void 0) attributes[GEN_AI_OPERATION_NAME] = operation;
|
|
344
361
|
return attributes;
|
|
345
362
|
}
|
|
346
|
-
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;
|
|
363
|
+
var TRACER_NAME, SERVICE_INSTANCE_ID, OPENINFERENCE_SPAN_KIND, INPUT_VALUE, OUTPUT_VALUE, SESSION_ID, USER_ID, WORKSPACE_ROUTE, GEN_AI_OPERATION_NAME, GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_MODEL, GEN_AI_REQUEST_REASONING_LEVEL, GEN_AI_RESPONSE_MODEL, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS, GEN_AI_USAGE_REASONING_OUTPUT_TOKENS, GEN_AI_INPUT_MESSAGES, GEN_AI_OUTPUT_MESSAGES, GEN_AI_RESPONSE_FINISH_REASONS, GEN_AI_TOOL_NAME, GEN_AI_TOOL_DEFINITIONS, GEN_AI_REQUEST_PREFIX, MCP_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;
|
|
347
364
|
var init_semconv = __esm({
|
|
348
365
|
"src/semconv.ts"() {
|
|
349
366
|
"use strict";
|
|
@@ -354,6 +371,7 @@ var init_semconv = __esm({
|
|
|
354
371
|
INPUT_VALUE = "input.value";
|
|
355
372
|
OUTPUT_VALUE = "output.value";
|
|
356
373
|
SESSION_ID = "session.id";
|
|
374
|
+
USER_ID = "user.id";
|
|
357
375
|
WORKSPACE_ROUTE = "rius.workspace";
|
|
358
376
|
GEN_AI_OPERATION_NAME = "gen_ai.operation.name";
|
|
359
377
|
GEN_AI_PROVIDER_NAME = "gen_ai.provider.name";
|
|
@@ -369,6 +387,7 @@ var init_semconv = __esm({
|
|
|
369
387
|
GEN_AI_OUTPUT_MESSAGES = "gen_ai.output.messages";
|
|
370
388
|
GEN_AI_RESPONSE_FINISH_REASONS = "gen_ai.response.finish_reasons";
|
|
371
389
|
GEN_AI_TOOL_NAME = "gen_ai.tool.name";
|
|
390
|
+
GEN_AI_TOOL_DEFINITIONS = "gen_ai.tool.definitions";
|
|
372
391
|
GEN_AI_REQUEST_PREFIX = "gen_ai.request.";
|
|
373
392
|
MCP_RESULT_TYPE = "mcp.result_type";
|
|
374
393
|
GEN_AI_FIRST_TOKEN_EVENT = "gen_ai.first_token";
|
|
@@ -397,7 +416,18 @@ var init_semconv = __esm({
|
|
|
397
416
|
// or internal URLs in parameter defaults. gen_ai.tool.name stays: it is
|
|
398
417
|
// identity, not content.
|
|
399
418
|
"gen_ai.tool.description",
|
|
400
|
-
|
|
419
|
+
GEN_AI_TOOL_DEFINITIONS,
|
|
420
|
+
// GenAI semconv keys emitted by OTel-native instrumentations (@ai-sdk/otel,
|
|
421
|
+
// which init() registers for ai v7, among them; pinned 2026-09-14): the
|
|
422
|
+
// system prompt and each tool call's input and output are content in the
|
|
423
|
+
// same sense messages are. gen_ai.tool.call.id stays: identity.
|
|
424
|
+
"gen_ai.system_instructions",
|
|
425
|
+
"gen_ai.tool.call.arguments",
|
|
426
|
+
"gen_ai.tool.call.result",
|
|
427
|
+
// OpenInference TOOL-kind spans carry the definition under these two bare
|
|
428
|
+
// keys, sensitive for the reason gen_ai.tool.description is.
|
|
429
|
+
"tool.description",
|
|
430
|
+
"tool.parameters",
|
|
401
431
|
"gen_ai.prompt",
|
|
402
432
|
"gen_ai.completion",
|
|
403
433
|
"llm.input_messages",
|
|
@@ -411,15 +441,44 @@ var init_semconv = __esm({
|
|
|
411
441
|
"mlflow.spanInputs",
|
|
412
442
|
"mlflow.spanOutputs",
|
|
413
443
|
"traceloop.entity.input",
|
|
414
|
-
"traceloop.entity.output"
|
|
444
|
+
"traceloop.entity.output",
|
|
445
|
+
// Every bundled OpenInference instrumentation emits tool definitions as
|
|
446
|
+
// llm.tools.{i}.tool.json_schema (pinned empirically 2026-09-14); covered
|
|
447
|
+
// by prefix below, bare key listed per the bare-key rule.
|
|
448
|
+
"llm.tools",
|
|
449
|
+
// The Vercel AI SDK's own telemetry keys survive the OpenInference
|
|
450
|
+
// transform untouched, and they carry the full prompt (messages AND tool
|
|
451
|
+
// definitions), response content, and tool-call I/O. Names, ids and models
|
|
452
|
+
// (ai.toolCall.name, ai.response.model, ...) are identity and stay.
|
|
453
|
+
"ai.prompt",
|
|
454
|
+
"ai.response.text",
|
|
455
|
+
"ai.response.object",
|
|
456
|
+
"ai.toolCall.args",
|
|
457
|
+
"ai.toolCall.result",
|
|
458
|
+
// Same family, keys enumerated from the ai v7 telemetry surface
|
|
459
|
+
// (2026-09-14): model reasoning, tool calls in the response, response
|
|
460
|
+
// files, embedding inputs, rerank documents, and the generateObject schema
|
|
461
|
+
// (a tool definition by another name).
|
|
462
|
+
"ai.response.reasoning",
|
|
463
|
+
"ai.response.toolCalls",
|
|
464
|
+
"ai.response.files",
|
|
465
|
+
"ai.value",
|
|
466
|
+
"ai.values",
|
|
467
|
+
"ai.documents",
|
|
468
|
+
"ai.schema",
|
|
469
|
+
"ai.schema.description"
|
|
415
470
|
]);
|
|
471
|
+
LLM_INVOCATION_PARAMETERS = "llm.invocation_parameters";
|
|
472
|
+
INVOCATION_PARAMETERS_CONTENT_MEMBERS = ["tools", "functions"];
|
|
416
473
|
CONTENT_ATTRIBUTE_PREFIXES = [
|
|
417
474
|
"llm.input_messages.",
|
|
418
475
|
"llm.output_messages.",
|
|
419
476
|
"gen_ai.prompt.",
|
|
420
477
|
"gen_ai.completion.",
|
|
421
478
|
"llm.prompts.",
|
|
422
|
-
"llm.prompt_template."
|
|
479
|
+
"llm.prompt_template.",
|
|
480
|
+
"llm.tools.",
|
|
481
|
+
"ai.prompt."
|
|
423
482
|
];
|
|
424
483
|
CONTENT_ATTRIBUTE_SUFFIXES = [
|
|
425
484
|
".document.content",
|
|
@@ -434,6 +493,9 @@ var init_semconv = __esm({
|
|
|
434
493
|
// Identity, not content: a pending span must be groupable into its
|
|
435
494
|
// session while still running, that is the live view's whole point.
|
|
436
495
|
SESSION_ID,
|
|
496
|
+
// Same reason: a crashed run's partial spans must still be attributable
|
|
497
|
+
// to the user they served.
|
|
498
|
+
USER_ID,
|
|
437
499
|
// Routing, not content: a crashed run's snapshot must land in the same
|
|
438
500
|
// workspace its final span would have. Stripped at export either way.
|
|
439
501
|
WORKSPACE_ROUTE
|
|
@@ -443,31 +505,77 @@ var init_semconv = __esm({
|
|
|
443
505
|
});
|
|
444
506
|
|
|
445
507
|
// src/serde.ts
|
|
446
|
-
function
|
|
447
|
-
|
|
448
|
-
|
|
508
|
+
function truncate(text) {
|
|
509
|
+
return text.length > MAX_ATTR_CHARS ? text.slice(0, MAX_ATTR_CHARS) + TRUNCATION_MARKER : text;
|
|
510
|
+
}
|
|
511
|
+
function renderable(raw, value) {
|
|
512
|
+
if (typeof raw === "bigint") return String(raw);
|
|
513
|
+
if (raw instanceof Map) return Object.fromEntries(raw);
|
|
514
|
+
if (raw instanceof Set) return [...raw];
|
|
515
|
+
if (ArrayBuffer.isView(raw)) {
|
|
516
|
+
return `<${raw.constructor.name} ${raw.byteLength} bytes>`;
|
|
449
517
|
}
|
|
518
|
+
if (raw instanceof ArrayBuffer) return `<ArrayBuffer ${raw.byteLength} bytes>`;
|
|
519
|
+
return value;
|
|
520
|
+
}
|
|
521
|
+
function toAttributeValue(value) {
|
|
522
|
+
if (typeof value === "string") return truncate(value);
|
|
523
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
450
524
|
if (value === null || value === void 0) return "";
|
|
525
|
+
if (typeof value === "bigint") return String(value);
|
|
451
526
|
const ancestors = [];
|
|
452
527
|
try {
|
|
453
|
-
|
|
454
|
-
|
|
528
|
+
const text = JSON.stringify({ "": value }[""], function(key, v) {
|
|
529
|
+
const raw = this?.[key];
|
|
530
|
+
const rendered = renderable(raw, v);
|
|
531
|
+
if (typeof rendered === "object" && rendered !== null) {
|
|
455
532
|
while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
|
|
456
533
|
ancestors.pop();
|
|
457
534
|
}
|
|
458
|
-
if (ancestors.includes(
|
|
459
|
-
ancestors.push(
|
|
535
|
+
if (ancestors.includes(rendered)) return "[Circular]";
|
|
536
|
+
ancestors.push(rendered);
|
|
460
537
|
}
|
|
461
|
-
return
|
|
538
|
+
return rendered;
|
|
462
539
|
}) ?? String(value);
|
|
540
|
+
return truncate(text);
|
|
463
541
|
} catch {
|
|
464
542
|
return "[unserializable]";
|
|
465
543
|
}
|
|
466
544
|
}
|
|
545
|
+
var MAX_ATTR_CHARS, TRUNCATION_MARKER;
|
|
467
546
|
var init_serde = __esm({
|
|
468
547
|
"src/serde.ts"() {
|
|
469
548
|
"use strict";
|
|
470
549
|
init_esm_shims();
|
|
550
|
+
MAX_ATTR_CHARS = 32 * 1024;
|
|
551
|
+
TRUNCATION_MARKER = "\u2026(truncated)";
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// src/user.ts
|
|
556
|
+
import { context as apiContext, createContextKey } from "@opentelemetry/api";
|
|
557
|
+
function withUser(userId, fn) {
|
|
558
|
+
return apiContext.with(apiContext.active().setValue(USER_KEY, userId), () => fn(userId));
|
|
559
|
+
}
|
|
560
|
+
var USER_KEY, UserSpanProcessor;
|
|
561
|
+
var init_user = __esm({
|
|
562
|
+
"src/user.ts"() {
|
|
563
|
+
"use strict";
|
|
564
|
+
init_esm_shims();
|
|
565
|
+
init_semconv();
|
|
566
|
+
USER_KEY = createContextKey("rius-user-id");
|
|
567
|
+
UserSpanProcessor = class {
|
|
568
|
+
onStart(span, parentContext) {
|
|
569
|
+
const value = parentContext.getValue(USER_KEY);
|
|
570
|
+
if (typeof value === "string") span.setAttribute(USER_ID, value);
|
|
571
|
+
}
|
|
572
|
+
onEnd(_span) {
|
|
573
|
+
}
|
|
574
|
+
async forceFlush() {
|
|
575
|
+
}
|
|
576
|
+
async shutdown() {
|
|
577
|
+
}
|
|
578
|
+
};
|
|
471
579
|
}
|
|
472
580
|
});
|
|
473
581
|
|
|
@@ -477,30 +585,39 @@ function configure(observation, options) {
|
|
|
477
585
|
if (options.input !== void 0) observation.setInput(options.input);
|
|
478
586
|
return observation;
|
|
479
587
|
}
|
|
588
|
+
function creationAttributes(options) {
|
|
589
|
+
const attributes = { ...kindAttributes(options.kind ?? "CHAIN" /* CHAIN */), ...options.attributes };
|
|
590
|
+
if (options.userId !== void 0) attributes[USER_ID] = options.userId;
|
|
591
|
+
return attributes;
|
|
592
|
+
}
|
|
480
593
|
function startSpan(name, options = {}) {
|
|
481
|
-
const span = getTracer().startSpan(name, {
|
|
482
|
-
attributes: kindAttributes(options.kind ?? "CHAIN" /* CHAIN */)
|
|
483
|
-
});
|
|
594
|
+
const span = getTracer().startSpan(name, { attributes: creationAttributes(options) });
|
|
484
595
|
return configure(new Observation(span), options);
|
|
485
596
|
}
|
|
486
597
|
function startAsCurrentSpan(name, optionsOrFn, maybeFn) {
|
|
487
598
|
const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
|
|
488
|
-
return
|
|
599
|
+
return runActive(
|
|
489
600
|
name,
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
return await fn(observation);
|
|
495
|
-
} catch (error) {
|
|
496
|
-
observation.recordException(error);
|
|
497
|
-
throw error;
|
|
498
|
-
} finally {
|
|
499
|
-
observation.end();
|
|
500
|
-
}
|
|
501
|
-
}
|
|
601
|
+
creationAttributes(options),
|
|
602
|
+
options.userId,
|
|
603
|
+
(span) => configure(new Observation(span), options),
|
|
604
|
+
fn
|
|
502
605
|
);
|
|
503
606
|
}
|
|
607
|
+
function runActive(name, attributes, userId, makeHandle, fn) {
|
|
608
|
+
const run = () => getTracer().startActiveSpan(name, { attributes }, async (span) => {
|
|
609
|
+
const handle = makeHandle(span);
|
|
610
|
+
try {
|
|
611
|
+
return await fn(handle);
|
|
612
|
+
} catch (error) {
|
|
613
|
+
handle.recordException(error);
|
|
614
|
+
throw error;
|
|
615
|
+
} finally {
|
|
616
|
+
handle.end();
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
return userId !== void 0 ? withUser(userId, run) : run();
|
|
620
|
+
}
|
|
504
621
|
var Observation;
|
|
505
622
|
var init_spans = __esm({
|
|
506
623
|
"src/spans.ts"() {
|
|
@@ -509,6 +626,8 @@ var init_spans = __esm({
|
|
|
509
626
|
init_client();
|
|
510
627
|
init_semconv();
|
|
511
628
|
init_serde();
|
|
629
|
+
init_user();
|
|
630
|
+
Symbol.dispose ??= /* @__PURE__ */ Symbol.for("Symbol.dispose");
|
|
512
631
|
Observation = class {
|
|
513
632
|
constructor(span) {
|
|
514
633
|
this.span = span;
|
|
@@ -523,7 +642,26 @@ var init_spans = __esm({
|
|
|
523
642
|
this.span.setAttribute(OUTPUT_VALUE, toAttributeValue(value));
|
|
524
643
|
return this;
|
|
525
644
|
}
|
|
645
|
+
/**
|
|
646
|
+
* Set an arbitrary attribute. Primitives and homogeneous primitive arrays
|
|
647
|
+
* are passed through as the OTel values they are; objects are JSON-encoded
|
|
648
|
+
* and bounded; `undefined` and `null` set nothing, since "no value" is not
|
|
649
|
+
* an empty string.
|
|
650
|
+
*/
|
|
526
651
|
setAttribute(key, value) {
|
|
652
|
+
if (value === void 0 || value === null) return this;
|
|
653
|
+
if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
|
|
654
|
+
this.span.setAttribute(key, value);
|
|
655
|
+
return this;
|
|
656
|
+
}
|
|
657
|
+
if (Array.isArray(value) && value.every((v) => typeof v === "number")) {
|
|
658
|
+
this.span.setAttribute(key, value);
|
|
659
|
+
return this;
|
|
660
|
+
}
|
|
661
|
+
if (Array.isArray(value) && value.every((v) => typeof v === "boolean")) {
|
|
662
|
+
this.span.setAttribute(key, value);
|
|
663
|
+
return this;
|
|
664
|
+
}
|
|
527
665
|
this.span.setAttribute(key, toAttributeValue(value));
|
|
528
666
|
return this;
|
|
529
667
|
}
|
|
@@ -560,6 +698,21 @@ __export(instrumentationMcp_exports, {
|
|
|
560
698
|
instrumentMcpClient: () => instrumentMcpClient
|
|
561
699
|
});
|
|
562
700
|
import { SpanStatusCode as SpanStatusCode2 } from "@opentelemetry/api";
|
|
701
|
+
function serializeResult(result) {
|
|
702
|
+
if (typeof result !== "object" || result === null) return toAttributeValue(result);
|
|
703
|
+
const record = result;
|
|
704
|
+
const structured = record.structured_content ?? record.structuredContent;
|
|
705
|
+
if (structured !== void 0 && structured !== null) return toAttributeValue(structured);
|
|
706
|
+
const content = record.content;
|
|
707
|
+
if (Array.isArray(content)) {
|
|
708
|
+
const texts = content.map(
|
|
709
|
+
(block) => typeof block === "object" && block !== null ? block.text : void 0
|
|
710
|
+
).filter((text) => typeof text === "string");
|
|
711
|
+
if (texts.length === 1) return truncate(texts[0]);
|
|
712
|
+
if (texts.length > 1) return toAttributeValue(texts);
|
|
713
|
+
}
|
|
714
|
+
return toAttributeValue(result);
|
|
715
|
+
}
|
|
563
716
|
function resultIsError(result) {
|
|
564
717
|
if (typeof result !== "object" || result === null) return false;
|
|
565
718
|
const record = result;
|
|
@@ -571,7 +724,7 @@ function recordResult(observation, result) {
|
|
|
571
724
|
observation.setAttribute(MCP_RESULT_TYPE, INPUT_REQUIRED);
|
|
572
725
|
return;
|
|
573
726
|
}
|
|
574
|
-
observation.
|
|
727
|
+
observation.setAttribute(OUTPUT_VALUE, serializeResult(result));
|
|
575
728
|
if (resultIsError(result)) {
|
|
576
729
|
observation.span.setStatus({
|
|
577
730
|
code: SpanStatusCode2.ERROR,
|
|
@@ -591,9 +744,14 @@ function instrumentMcpClient(ClientClass) {
|
|
|
591
744
|
const instrumented = function instrumentedCallTool(params, ...rest) {
|
|
592
745
|
return startAsCurrentSpan(
|
|
593
746
|
`execute_tool ${params.name}`,
|
|
594
|
-
|
|
747
|
+
// Tool name at CREATION: pending snapshots are built at start, so an
|
|
748
|
+
// attribute set inside the callback never reaches them.
|
|
749
|
+
{
|
|
750
|
+
kind: "TOOL" /* TOOL */,
|
|
751
|
+
input: params.arguments,
|
|
752
|
+
attributes: { [GEN_AI_TOOL_NAME]: params.name }
|
|
753
|
+
},
|
|
595
754
|
async (observation) => {
|
|
596
|
-
observation.setAttribute(GEN_AI_TOOL_NAME, params.name);
|
|
597
755
|
const result = await original.call(this, params, ...rest);
|
|
598
756
|
recordResult(observation, result);
|
|
599
757
|
return result;
|
|
@@ -612,6 +770,7 @@ var init_instrumentationMcp = __esm({
|
|
|
612
770
|
"use strict";
|
|
613
771
|
init_esm_shims();
|
|
614
772
|
init_semconv();
|
|
773
|
+
init_serde();
|
|
615
774
|
init_spans();
|
|
616
775
|
INPUT_REQUIRED = "input_required";
|
|
617
776
|
}
|
|
@@ -706,21 +865,55 @@ function mcpClientPatchable(exports) {
|
|
|
706
865
|
const cls = exports.Client;
|
|
707
866
|
return typeof cls?.prototype?.callTool === "function" ? { Client: cls } : void 0;
|
|
708
867
|
}
|
|
709
|
-
async function
|
|
868
|
+
async function registerVercelTelemetry(tracerProvider, teardown) {
|
|
869
|
+
const ai = await optional("ai");
|
|
870
|
+
const register = ai?.registerTelemetry;
|
|
871
|
+
if (register === void 0) return false;
|
|
872
|
+
const otel = await optional("@ai-sdk/otel");
|
|
873
|
+
const OpenTelemetryIntegration = otel?.OpenTelemetry;
|
|
874
|
+
if (OpenTelemetryIntegration === void 0) {
|
|
875
|
+
console.warn(
|
|
876
|
+
"[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."
|
|
877
|
+
);
|
|
878
|
+
return false;
|
|
879
|
+
}
|
|
880
|
+
const bag = globalThis;
|
|
881
|
+
if (vercelTelemetryIntegration !== void 0 && bag.AI_SDK_TELEMETRY_INTEGRATIONS) {
|
|
882
|
+
bag.AI_SDK_TELEMETRY_INTEGRATIONS = bag.AI_SDK_TELEMETRY_INTEGRATIONS.filter(
|
|
883
|
+
(integration2) => integration2 !== vercelTelemetryIntegration
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
const integration = new OpenTelemetryIntegration({
|
|
887
|
+
tracer: tracerProvider.getTracer(TRACER_NAME, SDK_VERSION)
|
|
888
|
+
});
|
|
889
|
+
vercelTelemetryIntegration = integration;
|
|
890
|
+
register(integration);
|
|
891
|
+
teardown?.push(() => {
|
|
892
|
+
if (bag.AI_SDK_TELEMETRY_INTEGRATIONS) {
|
|
893
|
+
bag.AI_SDK_TELEMETRY_INTEGRATIONS = bag.AI_SDK_TELEMETRY_INTEGRATIONS.filter(
|
|
894
|
+
(candidate) => candidate !== integration
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
if (vercelTelemetryIntegration === integration) vercelTelemetryIntegration = void 0;
|
|
898
|
+
});
|
|
899
|
+
return true;
|
|
900
|
+
}
|
|
901
|
+
async function enableInstrumentations(sink, tracerProvider, names, teardown) {
|
|
710
902
|
const wanted = names ? REGISTRY.filter((e) => names.includes(e.name)) : REGISTRY;
|
|
711
903
|
const enabled = [];
|
|
712
904
|
for (const entry of wanted) {
|
|
713
905
|
try {
|
|
714
|
-
const loaded = await entry.load();
|
|
906
|
+
const loaded = await entry.load(tracerProvider, teardown);
|
|
715
907
|
if (loaded === void 0) continue;
|
|
716
908
|
if (entry.kind === "processor") {
|
|
717
909
|
if (entry.insert === "first") sink.addFirst(loaded);
|
|
718
910
|
else sink.add(loaded);
|
|
719
911
|
} else if (entry.kind === "instrumentation") {
|
|
720
|
-
registerInstrumentations({
|
|
912
|
+
const disable = registerInstrumentations({
|
|
721
913
|
instrumentations: [loaded],
|
|
722
914
|
tracerProvider
|
|
723
915
|
});
|
|
916
|
+
teardown?.push(disable);
|
|
724
917
|
}
|
|
725
918
|
enabled.push(entry.name);
|
|
726
919
|
} catch (error) {
|
|
@@ -729,11 +922,13 @@ async function enableInstrumentations(sink, tracerProvider, names) {
|
|
|
729
922
|
}
|
|
730
923
|
return enabled;
|
|
731
924
|
}
|
|
732
|
-
var dynamicImport, REGISTRY;
|
|
925
|
+
var dynamicImport, vercelTelemetryIntegration, REGISTRY;
|
|
733
926
|
var init_instrumentation = __esm({
|
|
734
927
|
"src/instrumentation.ts"() {
|
|
735
928
|
"use strict";
|
|
736
929
|
init_esm_shims();
|
|
930
|
+
init_semconv();
|
|
931
|
+
init_version();
|
|
737
932
|
dynamicImport = new Function("s", "return import(s)");
|
|
738
933
|
REGISTRY = [
|
|
739
934
|
{
|
|
@@ -744,15 +939,16 @@ var init_instrumentation = __esm({
|
|
|
744
939
|
// transform has to run before the exporting processor sees the span or the
|
|
745
940
|
// attributes it added would never be sanitised.
|
|
746
941
|
insert: "first",
|
|
747
|
-
async load() {
|
|
942
|
+
async load(tracerProvider, teardown) {
|
|
943
|
+
const registered = tracerProvider === void 0 ? false : await registerVercelTelemetry(tracerProvider, teardown);
|
|
748
944
|
const utils = await optional("@arizeai/openinference-vercel/utils");
|
|
749
945
|
const add = utils?.addOpenInferenceAttributesToSpan;
|
|
750
|
-
if (add === void 0) return void 0;
|
|
946
|
+
if (add === void 0 && !registered) return void 0;
|
|
751
947
|
return {
|
|
752
948
|
onStart() {
|
|
753
949
|
},
|
|
754
950
|
onEnd(span) {
|
|
755
|
-
add(span);
|
|
951
|
+
add?.(span);
|
|
756
952
|
},
|
|
757
953
|
async forceFlush() {
|
|
758
954
|
},
|
|
@@ -802,14 +998,18 @@ var init_instrumentation = __esm({
|
|
|
802
998
|
{
|
|
803
999
|
name: "mcp",
|
|
804
1000
|
kind: "self-applying",
|
|
805
|
-
async load() {
|
|
1001
|
+
async load(_tracerProvider, teardown) {
|
|
806
1002
|
const mod = await optional("@modelcontextprotocol/sdk/client/index.js");
|
|
807
1003
|
const ClientClass = mod?.Client;
|
|
808
1004
|
if (ClientClass === void 0) return void 0;
|
|
809
1005
|
const { instrumentMcpClient: instrumentMcpClient2 } = await Promise.resolve().then(() => (init_instrumentationMcp(), instrumentationMcp_exports));
|
|
810
|
-
instrumentMcpClient2(ClientClass);
|
|
1006
|
+
const uninstallEsm = instrumentMcpClient2(ClientClass);
|
|
1007
|
+
teardown?.push(uninstallEsm);
|
|
811
1008
|
const cjs = cachedCjsExports("@modelcontextprotocol/sdk", mcpClientPatchable);
|
|
812
|
-
if (cjs !== void 0)
|
|
1009
|
+
if (cjs !== void 0) {
|
|
1010
|
+
const uninstallCjs = instrumentMcpClient2(cjs.Client);
|
|
1011
|
+
teardown?.push(uninstallCjs);
|
|
1012
|
+
}
|
|
813
1013
|
return true;
|
|
814
1014
|
}
|
|
815
1015
|
}
|
|
@@ -822,17 +1022,38 @@ import { SpanStatusCode as SpanStatusCode3 } from "@opentelemetry/api";
|
|
|
822
1022
|
function isContentKey(key) {
|
|
823
1023
|
if (CONTENT_ATTRIBUTES.has(key)) return true;
|
|
824
1024
|
if (CONTENT_ATTRIBUTE_PREFIXES.some((p) => key.startsWith(p))) return true;
|
|
825
|
-
|
|
1025
|
+
if (CONTENT_ATTRIBUTE_SUFFIXES.some((s) => key.endsWith(s))) return true;
|
|
1026
|
+
return key.startsWith(METADATA_PREFIX) && isContentKey(key.slice(METADATA_PREFIX.length));
|
|
826
1027
|
}
|
|
827
|
-
|
|
1028
|
+
function redactInvocationParameters(value) {
|
|
1029
|
+
if (typeof value !== "string") return void 0;
|
|
1030
|
+
let parameters;
|
|
1031
|
+
try {
|
|
1032
|
+
parameters = JSON.parse(value);
|
|
1033
|
+
} catch {
|
|
1034
|
+
return void 0;
|
|
1035
|
+
}
|
|
1036
|
+
if (parameters === null || typeof parameters !== "object" || Array.isArray(parameters)) {
|
|
1037
|
+
return void 0;
|
|
1038
|
+
}
|
|
1039
|
+
const bag = parameters;
|
|
1040
|
+
if (!INVOCATION_PARAMETERS_CONTENT_MEMBERS.some((member) => member in bag)) {
|
|
1041
|
+
return value;
|
|
1042
|
+
}
|
|
1043
|
+
for (const member of INVOCATION_PARAMETERS_CONTENT_MEMBERS) delete bag[member];
|
|
1044
|
+
return JSON.stringify(bag);
|
|
1045
|
+
}
|
|
1046
|
+
var METADATA_PREFIX, EXCEPTION_EVENT_NAME, EXCEPTION_CONTENT_KEYS, STATUS_DESCRIPTION_KEY, MaskingSpanExporter;
|
|
828
1047
|
var init_masking = __esm({
|
|
829
1048
|
"src/masking.ts"() {
|
|
830
1049
|
"use strict";
|
|
831
1050
|
init_esm_shims();
|
|
832
1051
|
init_semconv();
|
|
833
1052
|
init_serde();
|
|
1053
|
+
METADATA_PREFIX = "metadata.";
|
|
834
1054
|
EXCEPTION_EVENT_NAME = "exception";
|
|
835
1055
|
EXCEPTION_CONTENT_KEYS = ["exception.message", "exception.stacktrace"];
|
|
1056
|
+
STATUS_DESCRIPTION_KEY = "status.description";
|
|
836
1057
|
MaskingSpanExporter = class {
|
|
837
1058
|
constructor(inner, opts) {
|
|
838
1059
|
this.inner = inner;
|
|
@@ -858,14 +1079,42 @@ var init_masking = __esm({
|
|
|
858
1079
|
for (const link of span.links ?? []) {
|
|
859
1080
|
this.sanitizeAttributes(link.attributes);
|
|
860
1081
|
}
|
|
861
|
-
|
|
862
|
-
span.status.message = void 0;
|
|
863
|
-
}
|
|
1082
|
+
this.sanitizeStatus(span);
|
|
864
1083
|
return span;
|
|
865
1084
|
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Drops or masks the status description, in place. `status` is mutable and
|
|
1087
|
+
* exposed by reference on the SDK span, same as `attributes`, so mutating it
|
|
1088
|
+
* here reaches the span about to be handed to the inner exporter. The ERROR
|
|
1089
|
+
* code itself is left alone so the failure stays visible and classifiable,
|
|
1090
|
+
* the same policy as `exception.type`.
|
|
1091
|
+
*/
|
|
1092
|
+
sanitizeStatus(span) {
|
|
1093
|
+
const status = span.status;
|
|
1094
|
+
if (status?.code !== SpanStatusCode3.ERROR || !status.message) return;
|
|
1095
|
+
if (!this.opts.captureContent) {
|
|
1096
|
+
status.message = void 0;
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
if (this.opts.mask === void 0) return;
|
|
1100
|
+
try {
|
|
1101
|
+
const masked = toAttributeValue(
|
|
1102
|
+
this.opts.mask(status.message, { key: STATUS_DESCRIPTION_KEY })
|
|
1103
|
+
);
|
|
1104
|
+
status.message = typeof masked === "string" ? masked : String(masked);
|
|
1105
|
+
} catch {
|
|
1106
|
+
status.message = "[mask error]";
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
866
1109
|
/** Strips or masks the content keys of one attribute bag, in place. */
|
|
867
1110
|
sanitizeAttributes(attributes) {
|
|
868
1111
|
if (attributes === void 0) return;
|
|
1112
|
+
const sanitizing = !this.opts.captureContent || this.opts.mask !== void 0;
|
|
1113
|
+
if (sanitizing && LLM_INVOCATION_PARAMETERS in attributes) {
|
|
1114
|
+
const redacted = redactInvocationParameters(attributes[LLM_INVOCATION_PARAMETERS]);
|
|
1115
|
+
if (redacted === void 0) delete attributes[LLM_INVOCATION_PARAMETERS];
|
|
1116
|
+
else attributes[LLM_INVOCATION_PARAMETERS] = redacted;
|
|
1117
|
+
}
|
|
869
1118
|
for (const key of Object.keys(attributes)) {
|
|
870
1119
|
if (!isContentKey(key)) continue;
|
|
871
1120
|
if (!this.opts.captureContent) {
|
|
@@ -925,8 +1174,8 @@ function buildSnapshot(span) {
|
|
|
925
1174
|
ended: true
|
|
926
1175
|
};
|
|
927
1176
|
}
|
|
928
|
-
function spanKey(
|
|
929
|
-
return `${
|
|
1177
|
+
function spanKey(context2) {
|
|
1178
|
+
return `${context2.traceId}:${context2.spanId}`;
|
|
930
1179
|
}
|
|
931
1180
|
var PendingSpanProcessor;
|
|
932
1181
|
var init_pending = __esm({
|
|
@@ -979,10 +1228,10 @@ var init_pending = __esm({
|
|
|
979
1228
|
|
|
980
1229
|
// src/session.ts
|
|
981
1230
|
import { randomUUID } from "crypto";
|
|
982
|
-
import { context as
|
|
1231
|
+
import { context as apiContext2, createContextKey as createContextKey2, trace } from "@opentelemetry/api";
|
|
983
1232
|
function withSession(sessionIdOrFn, maybeFn) {
|
|
984
1233
|
const [sessionId, fn] = typeof sessionIdOrFn === "function" ? [randomUUID(), sessionIdOrFn] : [sessionIdOrFn, maybeFn];
|
|
985
|
-
return
|
|
1234
|
+
return apiContext2.with(apiContext2.active().setValue(SESSION_KEY, sessionId), () => fn(sessionId));
|
|
986
1235
|
}
|
|
987
1236
|
var SESSION_KEY, SessionSpanProcessor;
|
|
988
1237
|
var init_session = __esm({
|
|
@@ -990,7 +1239,7 @@ var init_session = __esm({
|
|
|
990
1239
|
"use strict";
|
|
991
1240
|
init_esm_shims();
|
|
992
1241
|
init_semconv();
|
|
993
|
-
SESSION_KEY =
|
|
1242
|
+
SESSION_KEY = createContextKey2("rius-session-id");
|
|
994
1243
|
SessionSpanProcessor = class {
|
|
995
1244
|
constructor(defaultSessionId) {
|
|
996
1245
|
this.defaultSessionId = defaultSessionId;
|
|
@@ -998,7 +1247,9 @@ var init_session = __esm({
|
|
|
998
1247
|
defaultSessionId;
|
|
999
1248
|
onStart(span, parentContext) {
|
|
1000
1249
|
const value = parentContext.getValue(SESSION_KEY);
|
|
1001
|
-
const
|
|
1250
|
+
const parent = trace.getSpan(parentContext);
|
|
1251
|
+
const inherited = parent?.attributes?.[SESSION_ID];
|
|
1252
|
+
const sessionId = typeof value === "string" ? value : typeof inherited === "string" ? inherited : this.defaultSessionId;
|
|
1002
1253
|
if (sessionId !== void 0) span.setAttribute(SESSION_ID, sessionId);
|
|
1003
1254
|
}
|
|
1004
1255
|
onEnd(_span) {
|
|
@@ -1012,10 +1263,10 @@ var init_session = __esm({
|
|
|
1012
1263
|
});
|
|
1013
1264
|
|
|
1014
1265
|
// src/workspace.ts
|
|
1015
|
-
import { context as
|
|
1266
|
+
import { context as apiContext3, createContextKey as createContextKey3, trace as trace2 } from "@opentelemetry/api";
|
|
1016
1267
|
function withWorkspace(alias, fn) {
|
|
1017
1268
|
if (!alias) throw new Error("workspace alias must be a non-empty string");
|
|
1018
|
-
return
|
|
1269
|
+
return apiContext3.with(apiContext3.active().setValue(WORKSPACE_KEY, alias), () => fn(alias));
|
|
1019
1270
|
}
|
|
1020
1271
|
function setGlobalRouting(routing) {
|
|
1021
1272
|
globalRouting = routing;
|
|
@@ -1035,17 +1286,24 @@ var init_workspace = __esm({
|
|
|
1035
1286
|
init_esm_shims();
|
|
1036
1287
|
init_esm();
|
|
1037
1288
|
init_semconv();
|
|
1038
|
-
WORKSPACE_KEY =
|
|
1289
|
+
WORKSPACE_KEY = createContextKey3("rius-workspace-alias");
|
|
1039
1290
|
WorkspaceSpanProcessor = class {
|
|
1291
|
+
/** Straddles already warned about, as "parentAlias->alias"; one warning each, not one per span. */
|
|
1292
|
+
warnedStraddles = /* @__PURE__ */ new Set();
|
|
1040
1293
|
onStart(span, parentContext) {
|
|
1041
|
-
const
|
|
1042
|
-
if (typeof alias !== "string") return;
|
|
1043
|
-
const parent = trace.getSpan(parentContext);
|
|
1294
|
+
const parent = trace2.getSpan(parentContext);
|
|
1044
1295
|
const parentAlias = parent?.attributes?.[WORKSPACE_ROUTE];
|
|
1296
|
+
const scoped = parentContext.getValue(WORKSPACE_KEY);
|
|
1297
|
+
const alias = typeof scoped === "string" ? scoped : parentAlias;
|
|
1298
|
+
if (typeof alias !== "string") return;
|
|
1045
1299
|
if (parentAlias !== void 0 && parentAlias !== alias) {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1300
|
+
const straddle = `${String(parentAlias)}->${alias}`;
|
|
1301
|
+
if (!this.warnedStraddles.has(straddle)) {
|
|
1302
|
+
this.warnedStraddles.add(straddle);
|
|
1303
|
+
console.warn(
|
|
1304
|
+
`[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.`
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1049
1307
|
}
|
|
1050
1308
|
span.setAttribute(WORKSPACE_ROUTE, alias);
|
|
1051
1309
|
}
|
|
@@ -1135,7 +1393,7 @@ var init_workspace = __esm({
|
|
|
1135
1393
|
|
|
1136
1394
|
// src/client.ts
|
|
1137
1395
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1138
|
-
import { trace as
|
|
1396
|
+
import { context, propagation, trace as trace3 } from "@opentelemetry/api";
|
|
1139
1397
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
1140
1398
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
1141
1399
|
import {
|
|
@@ -1177,6 +1435,7 @@ function init(options = {}) {
|
|
|
1177
1435
|
}) : health;
|
|
1178
1436
|
const batch = new 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 = randomUUID2();
|
|
1189
1448
|
const provider = new 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: resourceFromAttributes({
|
|
1191
1452
|
[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 ParentBasedSampler({ root: new 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,12 +1486,12 @@ 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 trace3.getTracer(TRACER_NAME, SDK_VERSION);
|
|
1230
1495
|
}
|
|
1231
1496
|
var sinks, heartbeats, createClient, RiusClient, globalClient;
|
|
1232
1497
|
var init_client = __esm({
|
|
@@ -1242,18 +1507,22 @@ var init_client = __esm({
|
|
|
1242
1507
|
init_pending();
|
|
1243
1508
|
init_semconv();
|
|
1244
1509
|
init_session();
|
|
1510
|
+
init_user();
|
|
1511
|
+
init_version();
|
|
1245
1512
|
init_workspace();
|
|
1246
1513
|
sinks = /* @__PURE__ */ new WeakMap();
|
|
1247
1514
|
heartbeats = /* @__PURE__ */ new WeakMap();
|
|
1248
1515
|
RiusClient = class _RiusClient {
|
|
1249
1516
|
provider;
|
|
1250
1517
|
health;
|
|
1518
|
+
teardown;
|
|
1251
1519
|
/** Resolves with the names of the auto-instrumentations that attached. */
|
|
1252
1520
|
ready;
|
|
1253
1521
|
constructor(parts) {
|
|
1254
1522
|
this.provider = parts.provider;
|
|
1255
1523
|
this.health = parts.health;
|
|
1256
1524
|
this.ready = parts.ready;
|
|
1525
|
+
this.teardown = parts.teardown;
|
|
1257
1526
|
sinks.set(this, parts.processors);
|
|
1258
1527
|
if (parts.heartbeat) heartbeats.set(this, parts.heartbeat);
|
|
1259
1528
|
}
|
|
@@ -1281,13 +1550,22 @@ var init_client = __esm({
|
|
|
1281
1550
|
process.removeListener("beforeExit", heartbeat.beforeExitHandler);
|
|
1282
1551
|
await heartbeat.sender.stop();
|
|
1283
1552
|
}
|
|
1553
|
+
await this.ready;
|
|
1554
|
+
for (const disable of this.teardown.splice(0)) {
|
|
1555
|
+
try {
|
|
1556
|
+
disable();
|
|
1557
|
+
} catch {
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1284
1560
|
try {
|
|
1285
1561
|
await this.provider.shutdown();
|
|
1286
1562
|
} finally {
|
|
1287
1563
|
if (globalClient === this) {
|
|
1288
1564
|
globalClient = void 0;
|
|
1289
1565
|
setGlobalRouting(void 0);
|
|
1290
|
-
|
|
1566
|
+
trace3.disable();
|
|
1567
|
+
context.disable();
|
|
1568
|
+
propagation.disable();
|
|
1291
1569
|
}
|
|
1292
1570
|
}
|
|
1293
1571
|
}
|
|
@@ -1302,6 +1580,67 @@ init_client();
|
|
|
1302
1580
|
// src/generation.ts
|
|
1303
1581
|
init_esm_shims();
|
|
1304
1582
|
init_client();
|
|
1583
|
+
|
|
1584
|
+
// src/messages.ts
|
|
1585
|
+
init_esm_shims();
|
|
1586
|
+
init_serde();
|
|
1587
|
+
function isRecord(value) {
|
|
1588
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1589
|
+
}
|
|
1590
|
+
function asText(value) {
|
|
1591
|
+
if (typeof value === "string") return value;
|
|
1592
|
+
const text = toAttributeValue(value);
|
|
1593
|
+
return typeof text === "string" ? text : String(text);
|
|
1594
|
+
}
|
|
1595
|
+
function normalizePart(item) {
|
|
1596
|
+
if (isRecord(item)) {
|
|
1597
|
+
if (item.type === "text" && "text" in item) return { type: "text", content: item.text };
|
|
1598
|
+
if ("type" in item) return item;
|
|
1599
|
+
}
|
|
1600
|
+
return { type: "text", content: asText(item) };
|
|
1601
|
+
}
|
|
1602
|
+
function normalizeMessage(message, defaultRole) {
|
|
1603
|
+
if (typeof message === "string") {
|
|
1604
|
+
return { role: defaultRole, parts: [{ type: "text", content: message }] };
|
|
1605
|
+
}
|
|
1606
|
+
if (!isRecord(message)) {
|
|
1607
|
+
return { role: defaultRole, parts: [{ type: "text", content: asText(message) }] };
|
|
1608
|
+
}
|
|
1609
|
+
if ("parts" in message) {
|
|
1610
|
+
return { ...message, role: message.role ?? defaultRole };
|
|
1611
|
+
}
|
|
1612
|
+
const role = message.role ?? defaultRole;
|
|
1613
|
+
if (role === "tool" && "tool_call_id" in message) {
|
|
1614
|
+
return {
|
|
1615
|
+
role: "tool",
|
|
1616
|
+
parts: [{ type: "tool_call_response", id: message.tool_call_id, response: message.content }]
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
const parts = [];
|
|
1620
|
+
const content = message.content;
|
|
1621
|
+
if (typeof content === "string") {
|
|
1622
|
+
parts.push({ type: "text", content });
|
|
1623
|
+
} else if (Array.isArray(content)) {
|
|
1624
|
+
for (const item of content) parts.push(normalizePart(item));
|
|
1625
|
+
} else if (content !== null && content !== void 0) {
|
|
1626
|
+
parts.push({ type: "text", content: asText(content) });
|
|
1627
|
+
}
|
|
1628
|
+
const toolCalls = message.tool_calls;
|
|
1629
|
+
if (Array.isArray(toolCalls)) {
|
|
1630
|
+
for (const call of toolCalls) {
|
|
1631
|
+
if (!isRecord(call)) continue;
|
|
1632
|
+
const fn = isRecord(call.function) ? call.function : {};
|
|
1633
|
+
parts.push({ type: "tool_call", id: call.id, name: fn.name, arguments: fn.arguments });
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
return { role, parts };
|
|
1637
|
+
}
|
|
1638
|
+
function serializeMessages(messages, defaultRole) {
|
|
1639
|
+
const list = Array.isArray(messages) ? messages : [messages];
|
|
1640
|
+
return toAttributeValue(list.map((message) => normalizeMessage(message, defaultRole)));
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
// src/generation.ts
|
|
1305
1644
|
init_semconv();
|
|
1306
1645
|
init_serde();
|
|
1307
1646
|
init_spans();
|
|
@@ -1316,12 +1655,31 @@ var Generation = class extends Observation {
|
|
|
1316
1655
|
}
|
|
1317
1656
|
provider;
|
|
1318
1657
|
firstTokenRecorded = false;
|
|
1658
|
+
/**
|
|
1659
|
+
* Record the request messages (`gen_ai.input.messages`), normalised to the
|
|
1660
|
+
* GenAI `{role, parts}` shape like the Python SDK does: bare strings, OpenAI
|
|
1661
|
+
* dicts (including `tool_calls` and tool responses) and multimodal content
|
|
1662
|
+
* lists are all accepted. Bare strings default to the `user` role.
|
|
1663
|
+
*/
|
|
1319
1664
|
setInput(value) {
|
|
1320
|
-
this.span.setAttribute(GEN_AI_INPUT_MESSAGES,
|
|
1665
|
+
this.span.setAttribute(GEN_AI_INPUT_MESSAGES, serializeMessages(value, "user"));
|
|
1321
1666
|
return this;
|
|
1322
1667
|
}
|
|
1668
|
+
/** Record the response messages (`gen_ai.output.messages`); bare strings default to `assistant`. */
|
|
1323
1669
|
setOutput(value) {
|
|
1324
|
-
this.span.setAttribute(GEN_AI_OUTPUT_MESSAGES,
|
|
1670
|
+
this.span.setAttribute(GEN_AI_OUTPUT_MESSAGES, serializeMessages(value, "assistant"));
|
|
1671
|
+
return this;
|
|
1672
|
+
}
|
|
1673
|
+
/**
|
|
1674
|
+
* The request's tool/function definitions (`gen_ai.tool.definitions`).
|
|
1675
|
+
* Serialized verbatim, in whatever shape the provider request used (OpenAI
|
|
1676
|
+
* nests each tool under `function`, Anthropic uses top-level
|
|
1677
|
+
* `name`/`input_schema`) — no normalization, so what is recorded is exactly
|
|
1678
|
+
* what the model was shown. Definitions are content, not identity: they are
|
|
1679
|
+
* masked/stripped under `captureContent: false` like messages are.
|
|
1680
|
+
*/
|
|
1681
|
+
setToolDefinitions(tools) {
|
|
1682
|
+
this.span.setAttribute(GEN_AI_TOOL_DEFINITIONS, toAttributeValue(tools));
|
|
1325
1683
|
return this;
|
|
1326
1684
|
}
|
|
1327
1685
|
setModel(model) {
|
|
@@ -1387,8 +1745,10 @@ var Generation = class extends Observation {
|
|
|
1387
1745
|
};
|
|
1388
1746
|
function attributesFor(options) {
|
|
1389
1747
|
const attributes = { ...kindAttributes("LLM" /* LLM */) };
|
|
1748
|
+
if (options.operation !== void 0) attributes[GEN_AI_OPERATION_NAME] = options.operation;
|
|
1390
1749
|
if (options.model !== void 0) attributes[GEN_AI_REQUEST_MODEL] = options.model;
|
|
1391
1750
|
if (options.provider !== void 0) attributes[GEN_AI_PROVIDER_NAME] = options.provider;
|
|
1751
|
+
if (options.userId !== void 0) attributes[USER_ID] = options.userId;
|
|
1392
1752
|
return attributes;
|
|
1393
1753
|
}
|
|
1394
1754
|
function configure2(generation, options) {
|
|
@@ -1398,6 +1758,7 @@ function configure2(generation, options) {
|
|
|
1398
1758
|
if (options.reasoningLevel !== void 0) {
|
|
1399
1759
|
generation.setAttribute(GEN_AI_REQUEST_REASONING_LEVEL, options.reasoningLevel);
|
|
1400
1760
|
}
|
|
1761
|
+
if (options.tools !== void 0) generation.setToolDefinitions(options.tools);
|
|
1401
1762
|
if (options.input !== void 0) generation.setInput(options.input);
|
|
1402
1763
|
return generation;
|
|
1403
1764
|
}
|
|
@@ -1407,17 +1768,13 @@ function startGeneration(name, options = {}) {
|
|
|
1407
1768
|
}
|
|
1408
1769
|
function startAsCurrentGeneration(name, optionsOrFn, maybeFn) {
|
|
1409
1770
|
const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
|
|
1410
|
-
return
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
} finally {
|
|
1418
|
-
generation.end();
|
|
1419
|
-
}
|
|
1420
|
-
});
|
|
1771
|
+
return runActive(
|
|
1772
|
+
name,
|
|
1773
|
+
attributesFor(options),
|
|
1774
|
+
options.userId,
|
|
1775
|
+
(span) => configure2(new Generation(span, options.provider), options),
|
|
1776
|
+
fn
|
|
1777
|
+
);
|
|
1421
1778
|
}
|
|
1422
1779
|
|
|
1423
1780
|
// src/observe.ts
|
|
@@ -1429,7 +1786,10 @@ function observe(fn, options = {}) {
|
|
|
1429
1786
|
const captureOutput = options.captureOutput ?? true;
|
|
1430
1787
|
const wrapped = (...args) => startAsCurrentSpan(
|
|
1431
1788
|
name,
|
|
1432
|
-
{
|
|
1789
|
+
// {args, kwargs} is the Python SDK's shape; JavaScript has no keyword
|
|
1790
|
+
// arguments, so kwargs is always empty, but the key stays so a saved
|
|
1791
|
+
// search or a console view reads both SDKs' input.value the same way.
|
|
1792
|
+
{ kind: options.kind, input: captureInput ? { args, kwargs: {} } : void 0 },
|
|
1433
1793
|
async (observation) => {
|
|
1434
1794
|
const result = await fn(...args);
|
|
1435
1795
|
if (captureOutput && result !== void 0) observation.setOutput(result);
|
|
@@ -1443,9 +1803,10 @@ function observe(fn, options = {}) {
|
|
|
1443
1803
|
// src/index.ts
|
|
1444
1804
|
init_semconv();
|
|
1445
1805
|
init_session();
|
|
1806
|
+
init_user();
|
|
1446
1807
|
init_spans();
|
|
1447
1808
|
init_workspace();
|
|
1448
|
-
var VERSION = "0.
|
|
1809
|
+
var VERSION = "0.6.1";
|
|
1449
1810
|
export {
|
|
1450
1811
|
Generation,
|
|
1451
1812
|
Observation,
|
|
@@ -1461,5 +1822,6 @@ export {
|
|
|
1461
1822
|
startGeneration,
|
|
1462
1823
|
startSpan,
|
|
1463
1824
|
withSession,
|
|
1825
|
+
withUser,
|
|
1464
1826
|
withWorkspace
|
|
1465
1827
|
};
|