@glassflow-ai/rius 0.5.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 +469 -108
- package/dist/index.d.cts +108 -6
- package/dist/index.d.ts +108 -6
- package/dist/index.js +455 -95
- 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
|
}
|
|
@@ -824,7 +1024,25 @@ function isContentKey(key) {
|
|
|
824
1024
|
if (CONTENT_ATTRIBUTE_PREFIXES.some((p) => key.startsWith(p))) return true;
|
|
825
1025
|
return CONTENT_ATTRIBUTE_SUFFIXES.some((s) => key.endsWith(s));
|
|
826
1026
|
}
|
|
827
|
-
|
|
1027
|
+
function redactInvocationParameters(value) {
|
|
1028
|
+
if (typeof value !== "string") return void 0;
|
|
1029
|
+
let parameters;
|
|
1030
|
+
try {
|
|
1031
|
+
parameters = JSON.parse(value);
|
|
1032
|
+
} catch {
|
|
1033
|
+
return void 0;
|
|
1034
|
+
}
|
|
1035
|
+
if (parameters === null || typeof parameters !== "object" || Array.isArray(parameters)) {
|
|
1036
|
+
return void 0;
|
|
1037
|
+
}
|
|
1038
|
+
const bag = parameters;
|
|
1039
|
+
if (!INVOCATION_PARAMETERS_CONTENT_MEMBERS.some((member) => member in bag)) {
|
|
1040
|
+
return value;
|
|
1041
|
+
}
|
|
1042
|
+
for (const member of INVOCATION_PARAMETERS_CONTENT_MEMBERS) delete bag[member];
|
|
1043
|
+
return JSON.stringify(bag);
|
|
1044
|
+
}
|
|
1045
|
+
var EXCEPTION_EVENT_NAME, EXCEPTION_CONTENT_KEYS, STATUS_DESCRIPTION_KEY, MaskingSpanExporter;
|
|
828
1046
|
var init_masking = __esm({
|
|
829
1047
|
"src/masking.ts"() {
|
|
830
1048
|
"use strict";
|
|
@@ -833,6 +1051,7 @@ var init_masking = __esm({
|
|
|
833
1051
|
init_serde();
|
|
834
1052
|
EXCEPTION_EVENT_NAME = "exception";
|
|
835
1053
|
EXCEPTION_CONTENT_KEYS = ["exception.message", "exception.stacktrace"];
|
|
1054
|
+
STATUS_DESCRIPTION_KEY = "status.description";
|
|
836
1055
|
MaskingSpanExporter = class {
|
|
837
1056
|
constructor(inner, opts) {
|
|
838
1057
|
this.inner = inner;
|
|
@@ -858,14 +1077,42 @@ var init_masking = __esm({
|
|
|
858
1077
|
for (const link of span.links ?? []) {
|
|
859
1078
|
this.sanitizeAttributes(link.attributes);
|
|
860
1079
|
}
|
|
861
|
-
|
|
862
|
-
span.status.message = void 0;
|
|
863
|
-
}
|
|
1080
|
+
this.sanitizeStatus(span);
|
|
864
1081
|
return span;
|
|
865
1082
|
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Drops or masks the status description, in place. `status` is mutable and
|
|
1085
|
+
* exposed by reference on the SDK span, same as `attributes`, so mutating it
|
|
1086
|
+
* here reaches the span about to be handed to the inner exporter. The ERROR
|
|
1087
|
+
* code itself is left alone so the failure stays visible and classifiable,
|
|
1088
|
+
* the same policy as `exception.type`.
|
|
1089
|
+
*/
|
|
1090
|
+
sanitizeStatus(span) {
|
|
1091
|
+
const status = span.status;
|
|
1092
|
+
if (status?.code !== SpanStatusCode3.ERROR || !status.message) return;
|
|
1093
|
+
if (!this.opts.captureContent) {
|
|
1094
|
+
status.message = void 0;
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
if (this.opts.mask === void 0) return;
|
|
1098
|
+
try {
|
|
1099
|
+
const masked = toAttributeValue(
|
|
1100
|
+
this.opts.mask(status.message, { key: STATUS_DESCRIPTION_KEY })
|
|
1101
|
+
);
|
|
1102
|
+
status.message = typeof masked === "string" ? masked : String(masked);
|
|
1103
|
+
} catch {
|
|
1104
|
+
status.message = "[mask error]";
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
866
1107
|
/** Strips or masks the content keys of one attribute bag, in place. */
|
|
867
1108
|
sanitizeAttributes(attributes) {
|
|
868
1109
|
if (attributes === void 0) return;
|
|
1110
|
+
const sanitizing = !this.opts.captureContent || this.opts.mask !== void 0;
|
|
1111
|
+
if (sanitizing && LLM_INVOCATION_PARAMETERS in attributes) {
|
|
1112
|
+
const redacted = redactInvocationParameters(attributes[LLM_INVOCATION_PARAMETERS]);
|
|
1113
|
+
if (redacted === void 0) delete attributes[LLM_INVOCATION_PARAMETERS];
|
|
1114
|
+
else attributes[LLM_INVOCATION_PARAMETERS] = redacted;
|
|
1115
|
+
}
|
|
869
1116
|
for (const key of Object.keys(attributes)) {
|
|
870
1117
|
if (!isContentKey(key)) continue;
|
|
871
1118
|
if (!this.opts.captureContent) {
|
|
@@ -925,8 +1172,8 @@ function buildSnapshot(span) {
|
|
|
925
1172
|
ended: true
|
|
926
1173
|
};
|
|
927
1174
|
}
|
|
928
|
-
function spanKey(
|
|
929
|
-
return `${
|
|
1175
|
+
function spanKey(context2) {
|
|
1176
|
+
return `${context2.traceId}:${context2.spanId}`;
|
|
930
1177
|
}
|
|
931
1178
|
var PendingSpanProcessor;
|
|
932
1179
|
var init_pending = __esm({
|
|
@@ -979,10 +1226,10 @@ var init_pending = __esm({
|
|
|
979
1226
|
|
|
980
1227
|
// src/session.ts
|
|
981
1228
|
import { randomUUID } from "crypto";
|
|
982
|
-
import { context as
|
|
1229
|
+
import { context as apiContext2, createContextKey as createContextKey2, trace } from "@opentelemetry/api";
|
|
983
1230
|
function withSession(sessionIdOrFn, maybeFn) {
|
|
984
1231
|
const [sessionId, fn] = typeof sessionIdOrFn === "function" ? [randomUUID(), sessionIdOrFn] : [sessionIdOrFn, maybeFn];
|
|
985
|
-
return
|
|
1232
|
+
return apiContext2.with(apiContext2.active().setValue(SESSION_KEY, sessionId), () => fn(sessionId));
|
|
986
1233
|
}
|
|
987
1234
|
var SESSION_KEY, SessionSpanProcessor;
|
|
988
1235
|
var init_session = __esm({
|
|
@@ -990,7 +1237,7 @@ var init_session = __esm({
|
|
|
990
1237
|
"use strict";
|
|
991
1238
|
init_esm_shims();
|
|
992
1239
|
init_semconv();
|
|
993
|
-
SESSION_KEY =
|
|
1240
|
+
SESSION_KEY = createContextKey2("rius-session-id");
|
|
994
1241
|
SessionSpanProcessor = class {
|
|
995
1242
|
constructor(defaultSessionId) {
|
|
996
1243
|
this.defaultSessionId = defaultSessionId;
|
|
@@ -998,7 +1245,9 @@ var init_session = __esm({
|
|
|
998
1245
|
defaultSessionId;
|
|
999
1246
|
onStart(span, parentContext) {
|
|
1000
1247
|
const value = parentContext.getValue(SESSION_KEY);
|
|
1001
|
-
const
|
|
1248
|
+
const parent = trace.getSpan(parentContext);
|
|
1249
|
+
const inherited = parent?.attributes?.[SESSION_ID];
|
|
1250
|
+
const sessionId = typeof value === "string" ? value : typeof inherited === "string" ? inherited : this.defaultSessionId;
|
|
1002
1251
|
if (sessionId !== void 0) span.setAttribute(SESSION_ID, sessionId);
|
|
1003
1252
|
}
|
|
1004
1253
|
onEnd(_span) {
|
|
@@ -1012,10 +1261,10 @@ var init_session = __esm({
|
|
|
1012
1261
|
});
|
|
1013
1262
|
|
|
1014
1263
|
// src/workspace.ts
|
|
1015
|
-
import { context as
|
|
1264
|
+
import { context as apiContext3, createContextKey as createContextKey3, trace as trace2 } from "@opentelemetry/api";
|
|
1016
1265
|
function withWorkspace(alias, fn) {
|
|
1017
1266
|
if (!alias) throw new Error("workspace alias must be a non-empty string");
|
|
1018
|
-
return
|
|
1267
|
+
return apiContext3.with(apiContext3.active().setValue(WORKSPACE_KEY, alias), () => fn(alias));
|
|
1019
1268
|
}
|
|
1020
1269
|
function setGlobalRouting(routing) {
|
|
1021
1270
|
globalRouting = routing;
|
|
@@ -1035,17 +1284,24 @@ var init_workspace = __esm({
|
|
|
1035
1284
|
init_esm_shims();
|
|
1036
1285
|
init_esm();
|
|
1037
1286
|
init_semconv();
|
|
1038
|
-
WORKSPACE_KEY =
|
|
1287
|
+
WORKSPACE_KEY = createContextKey3("rius-workspace-alias");
|
|
1039
1288
|
WorkspaceSpanProcessor = class {
|
|
1289
|
+
/** Straddles already warned about, as "parentAlias->alias"; one warning each, not one per span. */
|
|
1290
|
+
warnedStraddles = /* @__PURE__ */ new Set();
|
|
1040
1291
|
onStart(span, parentContext) {
|
|
1041
|
-
const
|
|
1042
|
-
if (typeof alias !== "string") return;
|
|
1043
|
-
const parent = trace.getSpan(parentContext);
|
|
1292
|
+
const parent = trace2.getSpan(parentContext);
|
|
1044
1293
|
const parentAlias = parent?.attributes?.[WORKSPACE_ROUTE];
|
|
1294
|
+
const scoped = parentContext.getValue(WORKSPACE_KEY);
|
|
1295
|
+
const alias = typeof scoped === "string" ? scoped : parentAlias;
|
|
1296
|
+
if (typeof alias !== "string") return;
|
|
1045
1297
|
if (parentAlias !== void 0 && parentAlias !== alias) {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1298
|
+
const straddle = `${String(parentAlias)}->${alias}`;
|
|
1299
|
+
if (!this.warnedStraddles.has(straddle)) {
|
|
1300
|
+
this.warnedStraddles.add(straddle);
|
|
1301
|
+
console.warn(
|
|
1302
|
+
`[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.`
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1049
1305
|
}
|
|
1050
1306
|
span.setAttribute(WORKSPACE_ROUTE, alias);
|
|
1051
1307
|
}
|
|
@@ -1135,7 +1391,7 @@ var init_workspace = __esm({
|
|
|
1135
1391
|
|
|
1136
1392
|
// src/client.ts
|
|
1137
1393
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1138
|
-
import { trace as
|
|
1394
|
+
import { context, propagation, trace as trace3 } from "@opentelemetry/api";
|
|
1139
1395
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
1140
1396
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
1141
1397
|
import {
|
|
@@ -1177,6 +1433,7 @@ function init(options = {}) {
|
|
|
1177
1433
|
}) : health;
|
|
1178
1434
|
const batch = new BatchSpanProcessor(exporter);
|
|
1179
1435
|
processors.add(new SessionSpanProcessor(config.sessionId));
|
|
1436
|
+
processors.add(new UserSpanProcessor());
|
|
1180
1437
|
if (routing !== void 0) {
|
|
1181
1438
|
processors.add(new WorkspaceSpanProcessor());
|
|
1182
1439
|
}
|
|
@@ -1187,9 +1444,13 @@ function init(options = {}) {
|
|
|
1187
1444
|
}
|
|
1188
1445
|
const instanceId = randomUUID2();
|
|
1189
1446
|
const provider = new NodeTracerProvider({
|
|
1447
|
+
// telemetry.sdk.* is reserved for the OTel SDK itself; we identify as a
|
|
1448
|
+
// distribution via telemetry.distro.*, the same two keys Python stamps.
|
|
1190
1449
|
resource: resourceFromAttributes({
|
|
1191
1450
|
[ATTR_SERVICE_NAME]: config.serviceName,
|
|
1192
|
-
[SERVICE_INSTANCE_ID]: instanceId
|
|
1451
|
+
[SERVICE_INSTANCE_ID]: instanceId,
|
|
1452
|
+
"telemetry.distro.name": "glassflow-rius",
|
|
1453
|
+
"telemetry.distro.version": SDK_VERSION
|
|
1193
1454
|
}),
|
|
1194
1455
|
// Always ParentBased, with no AlwaysOn shortcut at rate 1. They are not
|
|
1195
1456
|
// equivalent: ParentBased honours a remote UNSAMPLED parent and drops,
|
|
@@ -1199,10 +1460,12 @@ function init(options = {}) {
|
|
|
1199
1460
|
sampler: new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(config.sampleRate) }),
|
|
1200
1461
|
spanProcessors: [processors]
|
|
1201
1462
|
});
|
|
1202
|
-
const
|
|
1463
|
+
const teardown = [];
|
|
1464
|
+
const ready = config.disabled ? Promise.resolve([]) : enableInstrumentations(processors, provider, void 0, teardown);
|
|
1203
1465
|
provider.register();
|
|
1204
1466
|
let heartbeat;
|
|
1205
|
-
|
|
1467
|
+
const heartbeatCanAuthenticate = config.apiKey !== void 0 || options.heartbeatTransport !== void 0;
|
|
1468
|
+
if (config.heartbeat && !config.disabled && heartbeatCanAuthenticate) {
|
|
1206
1469
|
const tracker = new OpenRootSpanTracker();
|
|
1207
1470
|
processors.add(tracker);
|
|
1208
1471
|
const sender = new HeartbeatSender({
|
|
@@ -1221,12 +1484,12 @@ function init(options = {}) {
|
|
|
1221
1484
|
process.once("beforeExit", beforeExitHandler);
|
|
1222
1485
|
heartbeat = { sender, beforeExitHandler };
|
|
1223
1486
|
}
|
|
1224
|
-
globalClient = createClient({ provider, processors, health, ready, heartbeat });
|
|
1487
|
+
globalClient = createClient({ provider, processors, health, ready, teardown, heartbeat });
|
|
1225
1488
|
setGlobalRouting(routing);
|
|
1226
1489
|
return globalClient;
|
|
1227
1490
|
}
|
|
1228
1491
|
function getTracer() {
|
|
1229
|
-
return
|
|
1492
|
+
return trace3.getTracer(TRACER_NAME, SDK_VERSION);
|
|
1230
1493
|
}
|
|
1231
1494
|
var sinks, heartbeats, createClient, RiusClient, globalClient;
|
|
1232
1495
|
var init_client = __esm({
|
|
@@ -1242,18 +1505,22 @@ var init_client = __esm({
|
|
|
1242
1505
|
init_pending();
|
|
1243
1506
|
init_semconv();
|
|
1244
1507
|
init_session();
|
|
1508
|
+
init_user();
|
|
1509
|
+
init_version();
|
|
1245
1510
|
init_workspace();
|
|
1246
1511
|
sinks = /* @__PURE__ */ new WeakMap();
|
|
1247
1512
|
heartbeats = /* @__PURE__ */ new WeakMap();
|
|
1248
1513
|
RiusClient = class _RiusClient {
|
|
1249
1514
|
provider;
|
|
1250
1515
|
health;
|
|
1516
|
+
teardown;
|
|
1251
1517
|
/** Resolves with the names of the auto-instrumentations that attached. */
|
|
1252
1518
|
ready;
|
|
1253
1519
|
constructor(parts) {
|
|
1254
1520
|
this.provider = parts.provider;
|
|
1255
1521
|
this.health = parts.health;
|
|
1256
1522
|
this.ready = parts.ready;
|
|
1523
|
+
this.teardown = parts.teardown;
|
|
1257
1524
|
sinks.set(this, parts.processors);
|
|
1258
1525
|
if (parts.heartbeat) heartbeats.set(this, parts.heartbeat);
|
|
1259
1526
|
}
|
|
@@ -1281,13 +1548,22 @@ var init_client = __esm({
|
|
|
1281
1548
|
process.removeListener("beforeExit", heartbeat.beforeExitHandler);
|
|
1282
1549
|
await heartbeat.sender.stop();
|
|
1283
1550
|
}
|
|
1551
|
+
await this.ready;
|
|
1552
|
+
for (const disable of this.teardown.splice(0)) {
|
|
1553
|
+
try {
|
|
1554
|
+
disable();
|
|
1555
|
+
} catch {
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1284
1558
|
try {
|
|
1285
1559
|
await this.provider.shutdown();
|
|
1286
1560
|
} finally {
|
|
1287
1561
|
if (globalClient === this) {
|
|
1288
1562
|
globalClient = void 0;
|
|
1289
1563
|
setGlobalRouting(void 0);
|
|
1290
|
-
|
|
1564
|
+
trace3.disable();
|
|
1565
|
+
context.disable();
|
|
1566
|
+
propagation.disable();
|
|
1291
1567
|
}
|
|
1292
1568
|
}
|
|
1293
1569
|
}
|
|
@@ -1302,6 +1578,67 @@ init_client();
|
|
|
1302
1578
|
// src/generation.ts
|
|
1303
1579
|
init_esm_shims();
|
|
1304
1580
|
init_client();
|
|
1581
|
+
|
|
1582
|
+
// src/messages.ts
|
|
1583
|
+
init_esm_shims();
|
|
1584
|
+
init_serde();
|
|
1585
|
+
function isRecord(value) {
|
|
1586
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1587
|
+
}
|
|
1588
|
+
function asText(value) {
|
|
1589
|
+
if (typeof value === "string") return value;
|
|
1590
|
+
const text = toAttributeValue(value);
|
|
1591
|
+
return typeof text === "string" ? text : String(text);
|
|
1592
|
+
}
|
|
1593
|
+
function normalizePart(item) {
|
|
1594
|
+
if (isRecord(item)) {
|
|
1595
|
+
if (item.type === "text" && "text" in item) return { type: "text", content: item.text };
|
|
1596
|
+
if ("type" in item) return item;
|
|
1597
|
+
}
|
|
1598
|
+
return { type: "text", content: asText(item) };
|
|
1599
|
+
}
|
|
1600
|
+
function normalizeMessage(message, defaultRole) {
|
|
1601
|
+
if (typeof message === "string") {
|
|
1602
|
+
return { role: defaultRole, parts: [{ type: "text", content: message }] };
|
|
1603
|
+
}
|
|
1604
|
+
if (!isRecord(message)) {
|
|
1605
|
+
return { role: defaultRole, parts: [{ type: "text", content: asText(message) }] };
|
|
1606
|
+
}
|
|
1607
|
+
if ("parts" in message) {
|
|
1608
|
+
return { ...message, role: message.role ?? defaultRole };
|
|
1609
|
+
}
|
|
1610
|
+
const role = message.role ?? defaultRole;
|
|
1611
|
+
if (role === "tool" && "tool_call_id" in message) {
|
|
1612
|
+
return {
|
|
1613
|
+
role: "tool",
|
|
1614
|
+
parts: [{ type: "tool_call_response", id: message.tool_call_id, response: message.content }]
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
const parts = [];
|
|
1618
|
+
const content = message.content;
|
|
1619
|
+
if (typeof content === "string") {
|
|
1620
|
+
parts.push({ type: "text", content });
|
|
1621
|
+
} else if (Array.isArray(content)) {
|
|
1622
|
+
for (const item of content) parts.push(normalizePart(item));
|
|
1623
|
+
} else if (content !== null && content !== void 0) {
|
|
1624
|
+
parts.push({ type: "text", content: asText(content) });
|
|
1625
|
+
}
|
|
1626
|
+
const toolCalls = message.tool_calls;
|
|
1627
|
+
if (Array.isArray(toolCalls)) {
|
|
1628
|
+
for (const call of toolCalls) {
|
|
1629
|
+
if (!isRecord(call)) continue;
|
|
1630
|
+
const fn = isRecord(call.function) ? call.function : {};
|
|
1631
|
+
parts.push({ type: "tool_call", id: call.id, name: fn.name, arguments: fn.arguments });
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
return { role, parts };
|
|
1635
|
+
}
|
|
1636
|
+
function serializeMessages(messages, defaultRole) {
|
|
1637
|
+
const list = Array.isArray(messages) ? messages : [messages];
|
|
1638
|
+
return toAttributeValue(list.map((message) => normalizeMessage(message, defaultRole)));
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
// src/generation.ts
|
|
1305
1642
|
init_semconv();
|
|
1306
1643
|
init_serde();
|
|
1307
1644
|
init_spans();
|
|
@@ -1316,12 +1653,31 @@ var Generation = class extends Observation {
|
|
|
1316
1653
|
}
|
|
1317
1654
|
provider;
|
|
1318
1655
|
firstTokenRecorded = false;
|
|
1656
|
+
/**
|
|
1657
|
+
* Record the request messages (`gen_ai.input.messages`), normalised to the
|
|
1658
|
+
* GenAI `{role, parts}` shape like the Python SDK does: bare strings, OpenAI
|
|
1659
|
+
* dicts (including `tool_calls` and tool responses) and multimodal content
|
|
1660
|
+
* lists are all accepted. Bare strings default to the `user` role.
|
|
1661
|
+
*/
|
|
1319
1662
|
setInput(value) {
|
|
1320
|
-
this.span.setAttribute(GEN_AI_INPUT_MESSAGES,
|
|
1663
|
+
this.span.setAttribute(GEN_AI_INPUT_MESSAGES, serializeMessages(value, "user"));
|
|
1321
1664
|
return this;
|
|
1322
1665
|
}
|
|
1666
|
+
/** Record the response messages (`gen_ai.output.messages`); bare strings default to `assistant`. */
|
|
1323
1667
|
setOutput(value) {
|
|
1324
|
-
this.span.setAttribute(GEN_AI_OUTPUT_MESSAGES,
|
|
1668
|
+
this.span.setAttribute(GEN_AI_OUTPUT_MESSAGES, serializeMessages(value, "assistant"));
|
|
1669
|
+
return this;
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* The request's tool/function definitions (`gen_ai.tool.definitions`).
|
|
1673
|
+
* Serialized verbatim, in whatever shape the provider request used (OpenAI
|
|
1674
|
+
* nests each tool under `function`, Anthropic uses top-level
|
|
1675
|
+
* `name`/`input_schema`) — no normalization, so what is recorded is exactly
|
|
1676
|
+
* what the model was shown. Definitions are content, not identity: they are
|
|
1677
|
+
* masked/stripped under `captureContent: false` like messages are.
|
|
1678
|
+
*/
|
|
1679
|
+
setToolDefinitions(tools) {
|
|
1680
|
+
this.span.setAttribute(GEN_AI_TOOL_DEFINITIONS, toAttributeValue(tools));
|
|
1325
1681
|
return this;
|
|
1326
1682
|
}
|
|
1327
1683
|
setModel(model) {
|
|
@@ -1387,8 +1743,10 @@ var Generation = class extends Observation {
|
|
|
1387
1743
|
};
|
|
1388
1744
|
function attributesFor(options) {
|
|
1389
1745
|
const attributes = { ...kindAttributes("LLM" /* LLM */) };
|
|
1746
|
+
if (options.operation !== void 0) attributes[GEN_AI_OPERATION_NAME] = options.operation;
|
|
1390
1747
|
if (options.model !== void 0) attributes[GEN_AI_REQUEST_MODEL] = options.model;
|
|
1391
1748
|
if (options.provider !== void 0) attributes[GEN_AI_PROVIDER_NAME] = options.provider;
|
|
1749
|
+
if (options.userId !== void 0) attributes[USER_ID] = options.userId;
|
|
1392
1750
|
return attributes;
|
|
1393
1751
|
}
|
|
1394
1752
|
function configure2(generation, options) {
|
|
@@ -1398,6 +1756,7 @@ function configure2(generation, options) {
|
|
|
1398
1756
|
if (options.reasoningLevel !== void 0) {
|
|
1399
1757
|
generation.setAttribute(GEN_AI_REQUEST_REASONING_LEVEL, options.reasoningLevel);
|
|
1400
1758
|
}
|
|
1759
|
+
if (options.tools !== void 0) generation.setToolDefinitions(options.tools);
|
|
1401
1760
|
if (options.input !== void 0) generation.setInput(options.input);
|
|
1402
1761
|
return generation;
|
|
1403
1762
|
}
|
|
@@ -1407,17 +1766,13 @@ function startGeneration(name, options = {}) {
|
|
|
1407
1766
|
}
|
|
1408
1767
|
function startAsCurrentGeneration(name, optionsOrFn, maybeFn) {
|
|
1409
1768
|
const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
|
|
1410
|
-
return
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
} finally {
|
|
1418
|
-
generation.end();
|
|
1419
|
-
}
|
|
1420
|
-
});
|
|
1769
|
+
return runActive(
|
|
1770
|
+
name,
|
|
1771
|
+
attributesFor(options),
|
|
1772
|
+
options.userId,
|
|
1773
|
+
(span) => configure2(new Generation(span, options.provider), options),
|
|
1774
|
+
fn
|
|
1775
|
+
);
|
|
1421
1776
|
}
|
|
1422
1777
|
|
|
1423
1778
|
// src/observe.ts
|
|
@@ -1429,7 +1784,10 @@ function observe(fn, options = {}) {
|
|
|
1429
1784
|
const captureOutput = options.captureOutput ?? true;
|
|
1430
1785
|
const wrapped = (...args) => startAsCurrentSpan(
|
|
1431
1786
|
name,
|
|
1432
|
-
{
|
|
1787
|
+
// {args, kwargs} is the Python SDK's shape; JavaScript has no keyword
|
|
1788
|
+
// arguments, so kwargs is always empty, but the key stays so a saved
|
|
1789
|
+
// search or a console view reads both SDKs' input.value the same way.
|
|
1790
|
+
{ kind: options.kind, input: captureInput ? { args, kwargs: {} } : void 0 },
|
|
1433
1791
|
async (observation) => {
|
|
1434
1792
|
const result = await fn(...args);
|
|
1435
1793
|
if (captureOutput && result !== void 0) observation.setOutput(result);
|
|
@@ -1443,9 +1801,10 @@ function observe(fn, options = {}) {
|
|
|
1443
1801
|
// src/index.ts
|
|
1444
1802
|
init_semconv();
|
|
1445
1803
|
init_session();
|
|
1804
|
+
init_user();
|
|
1446
1805
|
init_spans();
|
|
1447
1806
|
init_workspace();
|
|
1448
|
-
var VERSION = "0.
|
|
1807
|
+
var VERSION = "0.6.0";
|
|
1449
1808
|
export {
|
|
1450
1809
|
Generation,
|
|
1451
1810
|
Observation,
|
|
@@ -1461,5 +1820,6 @@ export {
|
|
|
1461
1820
|
startGeneration,
|
|
1462
1821
|
startSpan,
|
|
1463
1822
|
withSession,
|
|
1823
|
+
withUser,
|
|
1464
1824
|
withWorkspace
|
|
1465
1825
|
};
|