@glassflow-ai/rius 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -7
- package/dist/index.cjs +498 -115
- package/dist/index.d.cts +137 -11
- package/dist/index.d.ts +137 -11
- package/dist/index.js +484 -102
- package/package.json +4 -1
package/dist/index.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_RESPONSE_MODEL, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS, GEN_AI_INPUT_MESSAGES, GEN_AI_OUTPUT_MESSAGES, GEN_AI_RESPONSE_FINISH_REASONS, GEN_AI_TOOL_NAME, GEN_AI_REQUEST_PREFIX, MCP_RESULT_TYPE, GEN_AI_FIRST_TOKEN_EVENT, SpanKind, OPERATION_BY_KIND, CONTENT_ATTRIBUTES, CONTENT_ATTRIBUTE_PREFIXES, CONTENT_ATTRIBUTE_SUFFIXES, GLASSFLOW_SPAN_PENDING, PENDING_IDENTITY_ATTRIBUTES, PENDING_IDENTITY_PREFIXES;
|
|
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,19 +371,23 @@ 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";
|
|
360
378
|
GEN_AI_REQUEST_MODEL = "gen_ai.request.model";
|
|
379
|
+
GEN_AI_REQUEST_REASONING_LEVEL = "gen_ai.request.reasoning.level";
|
|
361
380
|
GEN_AI_RESPONSE_MODEL = "gen_ai.response.model";
|
|
362
381
|
GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens";
|
|
363
382
|
GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens";
|
|
364
383
|
GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens";
|
|
365
384
|
GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS = "gen_ai.usage.cache_write.input_tokens";
|
|
385
|
+
GEN_AI_USAGE_REASONING_OUTPUT_TOKENS = "gen_ai.usage.reasoning.output_tokens";
|
|
366
386
|
GEN_AI_INPUT_MESSAGES = "gen_ai.input.messages";
|
|
367
387
|
GEN_AI_OUTPUT_MESSAGES = "gen_ai.output.messages";
|
|
368
388
|
GEN_AI_RESPONSE_FINISH_REASONS = "gen_ai.response.finish_reasons";
|
|
369
389
|
GEN_AI_TOOL_NAME = "gen_ai.tool.name";
|
|
390
|
+
GEN_AI_TOOL_DEFINITIONS = "gen_ai.tool.definitions";
|
|
370
391
|
GEN_AI_REQUEST_PREFIX = "gen_ai.request.";
|
|
371
392
|
MCP_RESULT_TYPE = "mcp.result_type";
|
|
372
393
|
GEN_AI_FIRST_TOKEN_EVENT = "gen_ai.first_token";
|
|
@@ -395,7 +416,18 @@ var init_semconv = __esm({
|
|
|
395
416
|
// or internal URLs in parameter defaults. gen_ai.tool.name stays: it is
|
|
396
417
|
// identity, not content.
|
|
397
418
|
"gen_ai.tool.description",
|
|
398
|
-
|
|
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",
|
|
399
431
|
"gen_ai.prompt",
|
|
400
432
|
"gen_ai.completion",
|
|
401
433
|
"llm.input_messages",
|
|
@@ -409,15 +441,44 @@ var init_semconv = __esm({
|
|
|
409
441
|
"mlflow.spanInputs",
|
|
410
442
|
"mlflow.spanOutputs",
|
|
411
443
|
"traceloop.entity.input",
|
|
412
|
-
"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"
|
|
413
470
|
]);
|
|
471
|
+
LLM_INVOCATION_PARAMETERS = "llm.invocation_parameters";
|
|
472
|
+
INVOCATION_PARAMETERS_CONTENT_MEMBERS = ["tools", "functions"];
|
|
414
473
|
CONTENT_ATTRIBUTE_PREFIXES = [
|
|
415
474
|
"llm.input_messages.",
|
|
416
475
|
"llm.output_messages.",
|
|
417
476
|
"gen_ai.prompt.",
|
|
418
477
|
"gen_ai.completion.",
|
|
419
478
|
"llm.prompts.",
|
|
420
|
-
"llm.prompt_template."
|
|
479
|
+
"llm.prompt_template.",
|
|
480
|
+
"llm.tools.",
|
|
481
|
+
"ai.prompt."
|
|
421
482
|
];
|
|
422
483
|
CONTENT_ATTRIBUTE_SUFFIXES = [
|
|
423
484
|
".document.content",
|
|
@@ -432,6 +493,9 @@ var init_semconv = __esm({
|
|
|
432
493
|
// Identity, not content: a pending span must be groupable into its
|
|
433
494
|
// session while still running, that is the live view's whole point.
|
|
434
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,
|
|
435
499
|
// Routing, not content: a crashed run's snapshot must land in the same
|
|
436
500
|
// workspace its final span would have. Stripped at export either way.
|
|
437
501
|
WORKSPACE_ROUTE
|
|
@@ -441,31 +505,77 @@ var init_semconv = __esm({
|
|
|
441
505
|
});
|
|
442
506
|
|
|
443
507
|
// src/serde.ts
|
|
444
|
-
function
|
|
445
|
-
|
|
446
|
-
|
|
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>`;
|
|
447
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;
|
|
448
524
|
if (value === null || value === void 0) return "";
|
|
525
|
+
if (typeof value === "bigint") return String(value);
|
|
449
526
|
const ancestors = [];
|
|
450
527
|
try {
|
|
451
|
-
|
|
452
|
-
|
|
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) {
|
|
453
532
|
while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
|
|
454
533
|
ancestors.pop();
|
|
455
534
|
}
|
|
456
|
-
if (ancestors.includes(
|
|
457
|
-
ancestors.push(
|
|
535
|
+
if (ancestors.includes(rendered)) return "[Circular]";
|
|
536
|
+
ancestors.push(rendered);
|
|
458
537
|
}
|
|
459
|
-
return
|
|
538
|
+
return rendered;
|
|
460
539
|
}) ?? String(value);
|
|
540
|
+
return truncate(text);
|
|
461
541
|
} catch {
|
|
462
542
|
return "[unserializable]";
|
|
463
543
|
}
|
|
464
544
|
}
|
|
545
|
+
var MAX_ATTR_CHARS, TRUNCATION_MARKER;
|
|
465
546
|
var init_serde = __esm({
|
|
466
547
|
"src/serde.ts"() {
|
|
467
548
|
"use strict";
|
|
468
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
|
+
};
|
|
469
579
|
}
|
|
470
580
|
});
|
|
471
581
|
|
|
@@ -475,30 +585,39 @@ function configure(observation, options) {
|
|
|
475
585
|
if (options.input !== void 0) observation.setInput(options.input);
|
|
476
586
|
return observation;
|
|
477
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
|
+
}
|
|
478
593
|
function startSpan(name, options = {}) {
|
|
479
|
-
const span = getTracer().startSpan(name, {
|
|
480
|
-
attributes: kindAttributes(options.kind ?? "CHAIN" /* CHAIN */)
|
|
481
|
-
});
|
|
594
|
+
const span = getTracer().startSpan(name, { attributes: creationAttributes(options) });
|
|
482
595
|
return configure(new Observation(span), options);
|
|
483
596
|
}
|
|
484
597
|
function startAsCurrentSpan(name, optionsOrFn, maybeFn) {
|
|
485
598
|
const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
|
|
486
|
-
return
|
|
599
|
+
return runActive(
|
|
487
600
|
name,
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
return await fn(observation);
|
|
493
|
-
} catch (error) {
|
|
494
|
-
observation.recordException(error);
|
|
495
|
-
throw error;
|
|
496
|
-
} finally {
|
|
497
|
-
observation.end();
|
|
498
|
-
}
|
|
499
|
-
}
|
|
601
|
+
creationAttributes(options),
|
|
602
|
+
options.userId,
|
|
603
|
+
(span) => configure(new Observation(span), options),
|
|
604
|
+
fn
|
|
500
605
|
);
|
|
501
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
|
+
}
|
|
502
621
|
var Observation;
|
|
503
622
|
var init_spans = __esm({
|
|
504
623
|
"src/spans.ts"() {
|
|
@@ -507,6 +626,8 @@ var init_spans = __esm({
|
|
|
507
626
|
init_client();
|
|
508
627
|
init_semconv();
|
|
509
628
|
init_serde();
|
|
629
|
+
init_user();
|
|
630
|
+
Symbol.dispose ??= /* @__PURE__ */ Symbol.for("Symbol.dispose");
|
|
510
631
|
Observation = class {
|
|
511
632
|
constructor(span) {
|
|
512
633
|
this.span = span;
|
|
@@ -521,7 +642,26 @@ var init_spans = __esm({
|
|
|
521
642
|
this.span.setAttribute(OUTPUT_VALUE, toAttributeValue(value));
|
|
522
643
|
return this;
|
|
523
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
|
+
*/
|
|
524
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
|
+
}
|
|
525
665
|
this.span.setAttribute(key, toAttributeValue(value));
|
|
526
666
|
return this;
|
|
527
667
|
}
|
|
@@ -558,6 +698,21 @@ __export(instrumentationMcp_exports, {
|
|
|
558
698
|
instrumentMcpClient: () => instrumentMcpClient
|
|
559
699
|
});
|
|
560
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
|
+
}
|
|
561
716
|
function resultIsError(result) {
|
|
562
717
|
if (typeof result !== "object" || result === null) return false;
|
|
563
718
|
const record = result;
|
|
@@ -569,7 +724,7 @@ function recordResult(observation, result) {
|
|
|
569
724
|
observation.setAttribute(MCP_RESULT_TYPE, INPUT_REQUIRED);
|
|
570
725
|
return;
|
|
571
726
|
}
|
|
572
|
-
observation.
|
|
727
|
+
observation.setAttribute(OUTPUT_VALUE, serializeResult(result));
|
|
573
728
|
if (resultIsError(result)) {
|
|
574
729
|
observation.span.setStatus({
|
|
575
730
|
code: SpanStatusCode2.ERROR,
|
|
@@ -589,9 +744,14 @@ function instrumentMcpClient(ClientClass) {
|
|
|
589
744
|
const instrumented = function instrumentedCallTool(params, ...rest) {
|
|
590
745
|
return startAsCurrentSpan(
|
|
591
746
|
`execute_tool ${params.name}`,
|
|
592
|
-
|
|
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
|
+
},
|
|
593
754
|
async (observation) => {
|
|
594
|
-
observation.setAttribute(GEN_AI_TOOL_NAME, params.name);
|
|
595
755
|
const result = await original.call(this, params, ...rest);
|
|
596
756
|
recordResult(observation, result);
|
|
597
757
|
return result;
|
|
@@ -610,6 +770,7 @@ var init_instrumentationMcp = __esm({
|
|
|
610
770
|
"use strict";
|
|
611
771
|
init_esm_shims();
|
|
612
772
|
init_semconv();
|
|
773
|
+
init_serde();
|
|
613
774
|
init_spans();
|
|
614
775
|
INPUT_REQUIRED = "input_required";
|
|
615
776
|
}
|
|
@@ -704,21 +865,55 @@ function mcpClientPatchable(exports) {
|
|
|
704
865
|
const cls = exports.Client;
|
|
705
866
|
return typeof cls?.prototype?.callTool === "function" ? { Client: cls } : void 0;
|
|
706
867
|
}
|
|
707
|
-
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) {
|
|
708
902
|
const wanted = names ? REGISTRY.filter((e) => names.includes(e.name)) : REGISTRY;
|
|
709
903
|
const enabled = [];
|
|
710
904
|
for (const entry of wanted) {
|
|
711
905
|
try {
|
|
712
|
-
const loaded = await entry.load();
|
|
906
|
+
const loaded = await entry.load(tracerProvider, teardown);
|
|
713
907
|
if (loaded === void 0) continue;
|
|
714
908
|
if (entry.kind === "processor") {
|
|
715
909
|
if (entry.insert === "first") sink.addFirst(loaded);
|
|
716
910
|
else sink.add(loaded);
|
|
717
911
|
} else if (entry.kind === "instrumentation") {
|
|
718
|
-
registerInstrumentations({
|
|
912
|
+
const disable = registerInstrumentations({
|
|
719
913
|
instrumentations: [loaded],
|
|
720
914
|
tracerProvider
|
|
721
915
|
});
|
|
916
|
+
teardown?.push(disable);
|
|
722
917
|
}
|
|
723
918
|
enabled.push(entry.name);
|
|
724
919
|
} catch (error) {
|
|
@@ -727,11 +922,13 @@ async function enableInstrumentations(sink, tracerProvider, names) {
|
|
|
727
922
|
}
|
|
728
923
|
return enabled;
|
|
729
924
|
}
|
|
730
|
-
var dynamicImport, REGISTRY;
|
|
925
|
+
var dynamicImport, vercelTelemetryIntegration, REGISTRY;
|
|
731
926
|
var init_instrumentation = __esm({
|
|
732
927
|
"src/instrumentation.ts"() {
|
|
733
928
|
"use strict";
|
|
734
929
|
init_esm_shims();
|
|
930
|
+
init_semconv();
|
|
931
|
+
init_version();
|
|
735
932
|
dynamicImport = new Function("s", "return import(s)");
|
|
736
933
|
REGISTRY = [
|
|
737
934
|
{
|
|
@@ -742,15 +939,16 @@ var init_instrumentation = __esm({
|
|
|
742
939
|
// transform has to run before the exporting processor sees the span or the
|
|
743
940
|
// attributes it added would never be sanitised.
|
|
744
941
|
insert: "first",
|
|
745
|
-
async load() {
|
|
942
|
+
async load(tracerProvider, teardown) {
|
|
943
|
+
const registered = tracerProvider === void 0 ? false : await registerVercelTelemetry(tracerProvider, teardown);
|
|
746
944
|
const utils = await optional("@arizeai/openinference-vercel/utils");
|
|
747
945
|
const add = utils?.addOpenInferenceAttributesToSpan;
|
|
748
|
-
if (add === void 0) return void 0;
|
|
946
|
+
if (add === void 0 && !registered) return void 0;
|
|
749
947
|
return {
|
|
750
948
|
onStart() {
|
|
751
949
|
},
|
|
752
950
|
onEnd(span) {
|
|
753
|
-
add(span);
|
|
951
|
+
add?.(span);
|
|
754
952
|
},
|
|
755
953
|
async forceFlush() {
|
|
756
954
|
},
|
|
@@ -800,14 +998,18 @@ var init_instrumentation = __esm({
|
|
|
800
998
|
{
|
|
801
999
|
name: "mcp",
|
|
802
1000
|
kind: "self-applying",
|
|
803
|
-
async load() {
|
|
1001
|
+
async load(_tracerProvider, teardown) {
|
|
804
1002
|
const mod = await optional("@modelcontextprotocol/sdk/client/index.js");
|
|
805
1003
|
const ClientClass = mod?.Client;
|
|
806
1004
|
if (ClientClass === void 0) return void 0;
|
|
807
1005
|
const { instrumentMcpClient: instrumentMcpClient2 } = await Promise.resolve().then(() => (init_instrumentationMcp(), instrumentationMcp_exports));
|
|
808
|
-
instrumentMcpClient2(ClientClass);
|
|
1006
|
+
const uninstallEsm = instrumentMcpClient2(ClientClass);
|
|
1007
|
+
teardown?.push(uninstallEsm);
|
|
809
1008
|
const cjs = cachedCjsExports("@modelcontextprotocol/sdk", mcpClientPatchable);
|
|
810
|
-
if (cjs !== void 0)
|
|
1009
|
+
if (cjs !== void 0) {
|
|
1010
|
+
const uninstallCjs = instrumentMcpClient2(cjs.Client);
|
|
1011
|
+
teardown?.push(uninstallCjs);
|
|
1012
|
+
}
|
|
811
1013
|
return true;
|
|
812
1014
|
}
|
|
813
1015
|
}
|
|
@@ -822,7 +1024,25 @@ function isContentKey(key) {
|
|
|
822
1024
|
if (CONTENT_ATTRIBUTE_PREFIXES.some((p) => key.startsWith(p))) return true;
|
|
823
1025
|
return CONTENT_ATTRIBUTE_SUFFIXES.some((s) => key.endsWith(s));
|
|
824
1026
|
}
|
|
825
|
-
|
|
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;
|
|
826
1046
|
var init_masking = __esm({
|
|
827
1047
|
"src/masking.ts"() {
|
|
828
1048
|
"use strict";
|
|
@@ -831,6 +1051,7 @@ var init_masking = __esm({
|
|
|
831
1051
|
init_serde();
|
|
832
1052
|
EXCEPTION_EVENT_NAME = "exception";
|
|
833
1053
|
EXCEPTION_CONTENT_KEYS = ["exception.message", "exception.stacktrace"];
|
|
1054
|
+
STATUS_DESCRIPTION_KEY = "status.description";
|
|
834
1055
|
MaskingSpanExporter = class {
|
|
835
1056
|
constructor(inner, opts) {
|
|
836
1057
|
this.inner = inner;
|
|
@@ -856,14 +1077,42 @@ var init_masking = __esm({
|
|
|
856
1077
|
for (const link of span.links ?? []) {
|
|
857
1078
|
this.sanitizeAttributes(link.attributes);
|
|
858
1079
|
}
|
|
859
|
-
|
|
860
|
-
span.status.message = void 0;
|
|
861
|
-
}
|
|
1080
|
+
this.sanitizeStatus(span);
|
|
862
1081
|
return span;
|
|
863
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
|
+
}
|
|
864
1107
|
/** Strips or masks the content keys of one attribute bag, in place. */
|
|
865
1108
|
sanitizeAttributes(attributes) {
|
|
866
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
|
+
}
|
|
867
1116
|
for (const key of Object.keys(attributes)) {
|
|
868
1117
|
if (!isContentKey(key)) continue;
|
|
869
1118
|
if (!this.opts.captureContent) {
|
|
@@ -923,8 +1172,8 @@ function buildSnapshot(span) {
|
|
|
923
1172
|
ended: true
|
|
924
1173
|
};
|
|
925
1174
|
}
|
|
926
|
-
function spanKey(
|
|
927
|
-
return `${
|
|
1175
|
+
function spanKey(context2) {
|
|
1176
|
+
return `${context2.traceId}:${context2.spanId}`;
|
|
928
1177
|
}
|
|
929
1178
|
var PendingSpanProcessor;
|
|
930
1179
|
var init_pending = __esm({
|
|
@@ -977,10 +1226,10 @@ var init_pending = __esm({
|
|
|
977
1226
|
|
|
978
1227
|
// src/session.ts
|
|
979
1228
|
import { randomUUID } from "crypto";
|
|
980
|
-
import { context as
|
|
1229
|
+
import { context as apiContext2, createContextKey as createContextKey2, trace } from "@opentelemetry/api";
|
|
981
1230
|
function withSession(sessionIdOrFn, maybeFn) {
|
|
982
1231
|
const [sessionId, fn] = typeof sessionIdOrFn === "function" ? [randomUUID(), sessionIdOrFn] : [sessionIdOrFn, maybeFn];
|
|
983
|
-
return
|
|
1232
|
+
return apiContext2.with(apiContext2.active().setValue(SESSION_KEY, sessionId), () => fn(sessionId));
|
|
984
1233
|
}
|
|
985
1234
|
var SESSION_KEY, SessionSpanProcessor;
|
|
986
1235
|
var init_session = __esm({
|
|
@@ -988,7 +1237,7 @@ var init_session = __esm({
|
|
|
988
1237
|
"use strict";
|
|
989
1238
|
init_esm_shims();
|
|
990
1239
|
init_semconv();
|
|
991
|
-
SESSION_KEY =
|
|
1240
|
+
SESSION_KEY = createContextKey2("rius-session-id");
|
|
992
1241
|
SessionSpanProcessor = class {
|
|
993
1242
|
constructor(defaultSessionId) {
|
|
994
1243
|
this.defaultSessionId = defaultSessionId;
|
|
@@ -996,7 +1245,9 @@ var init_session = __esm({
|
|
|
996
1245
|
defaultSessionId;
|
|
997
1246
|
onStart(span, parentContext) {
|
|
998
1247
|
const value = parentContext.getValue(SESSION_KEY);
|
|
999
|
-
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;
|
|
1000
1251
|
if (sessionId !== void 0) span.setAttribute(SESSION_ID, sessionId);
|
|
1001
1252
|
}
|
|
1002
1253
|
onEnd(_span) {
|
|
@@ -1010,10 +1261,10 @@ var init_session = __esm({
|
|
|
1010
1261
|
});
|
|
1011
1262
|
|
|
1012
1263
|
// src/workspace.ts
|
|
1013
|
-
import { context as
|
|
1264
|
+
import { context as apiContext3, createContextKey as createContextKey3, trace as trace2 } from "@opentelemetry/api";
|
|
1014
1265
|
function withWorkspace(alias, fn) {
|
|
1015
1266
|
if (!alias) throw new Error("workspace alias must be a non-empty string");
|
|
1016
|
-
return
|
|
1267
|
+
return apiContext3.with(apiContext3.active().setValue(WORKSPACE_KEY, alias), () => fn(alias));
|
|
1017
1268
|
}
|
|
1018
1269
|
function setGlobalRouting(routing) {
|
|
1019
1270
|
globalRouting = routing;
|
|
@@ -1033,17 +1284,24 @@ var init_workspace = __esm({
|
|
|
1033
1284
|
init_esm_shims();
|
|
1034
1285
|
init_esm();
|
|
1035
1286
|
init_semconv();
|
|
1036
|
-
WORKSPACE_KEY =
|
|
1287
|
+
WORKSPACE_KEY = createContextKey3("rius-workspace-alias");
|
|
1037
1288
|
WorkspaceSpanProcessor = class {
|
|
1289
|
+
/** Straddles already warned about, as "parentAlias->alias"; one warning each, not one per span. */
|
|
1290
|
+
warnedStraddles = /* @__PURE__ */ new Set();
|
|
1038
1291
|
onStart(span, parentContext) {
|
|
1039
|
-
const
|
|
1040
|
-
if (typeof alias !== "string") return;
|
|
1041
|
-
const parent = trace.getSpan(parentContext);
|
|
1292
|
+
const parent = trace2.getSpan(parentContext);
|
|
1042
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;
|
|
1043
1297
|
if (parentAlias !== void 0 && parentAlias !== alias) {
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
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
|
+
}
|
|
1047
1305
|
}
|
|
1048
1306
|
span.setAttribute(WORKSPACE_ROUTE, alias);
|
|
1049
1307
|
}
|
|
@@ -1133,7 +1391,7 @@ var init_workspace = __esm({
|
|
|
1133
1391
|
|
|
1134
1392
|
// src/client.ts
|
|
1135
1393
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1136
|
-
import { trace as
|
|
1394
|
+
import { context, propagation, trace as trace3 } from "@opentelemetry/api";
|
|
1137
1395
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
1138
1396
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
1139
1397
|
import {
|
|
@@ -1175,6 +1433,7 @@ function init(options = {}) {
|
|
|
1175
1433
|
}) : health;
|
|
1176
1434
|
const batch = new BatchSpanProcessor(exporter);
|
|
1177
1435
|
processors.add(new SessionSpanProcessor(config.sessionId));
|
|
1436
|
+
processors.add(new UserSpanProcessor());
|
|
1178
1437
|
if (routing !== void 0) {
|
|
1179
1438
|
processors.add(new WorkspaceSpanProcessor());
|
|
1180
1439
|
}
|
|
@@ -1185,9 +1444,13 @@ function init(options = {}) {
|
|
|
1185
1444
|
}
|
|
1186
1445
|
const instanceId = randomUUID2();
|
|
1187
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.
|
|
1188
1449
|
resource: resourceFromAttributes({
|
|
1189
1450
|
[ATTR_SERVICE_NAME]: config.serviceName,
|
|
1190
|
-
[SERVICE_INSTANCE_ID]: instanceId
|
|
1451
|
+
[SERVICE_INSTANCE_ID]: instanceId,
|
|
1452
|
+
"telemetry.distro.name": "glassflow-rius",
|
|
1453
|
+
"telemetry.distro.version": SDK_VERSION
|
|
1191
1454
|
}),
|
|
1192
1455
|
// Always ParentBased, with no AlwaysOn shortcut at rate 1. They are not
|
|
1193
1456
|
// equivalent: ParentBased honours a remote UNSAMPLED parent and drops,
|
|
@@ -1197,10 +1460,12 @@ function init(options = {}) {
|
|
|
1197
1460
|
sampler: new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(config.sampleRate) }),
|
|
1198
1461
|
spanProcessors: [processors]
|
|
1199
1462
|
});
|
|
1200
|
-
const
|
|
1463
|
+
const teardown = [];
|
|
1464
|
+
const ready = config.disabled ? Promise.resolve([]) : enableInstrumentations(processors, provider, void 0, teardown);
|
|
1201
1465
|
provider.register();
|
|
1202
1466
|
let heartbeat;
|
|
1203
|
-
|
|
1467
|
+
const heartbeatCanAuthenticate = config.apiKey !== void 0 || options.heartbeatTransport !== void 0;
|
|
1468
|
+
if (config.heartbeat && !config.disabled && heartbeatCanAuthenticate) {
|
|
1204
1469
|
const tracker = new OpenRootSpanTracker();
|
|
1205
1470
|
processors.add(tracker);
|
|
1206
1471
|
const sender = new HeartbeatSender({
|
|
@@ -1219,12 +1484,12 @@ function init(options = {}) {
|
|
|
1219
1484
|
process.once("beforeExit", beforeExitHandler);
|
|
1220
1485
|
heartbeat = { sender, beforeExitHandler };
|
|
1221
1486
|
}
|
|
1222
|
-
globalClient = createClient({ provider, processors, health, ready, heartbeat });
|
|
1487
|
+
globalClient = createClient({ provider, processors, health, ready, teardown, heartbeat });
|
|
1223
1488
|
setGlobalRouting(routing);
|
|
1224
1489
|
return globalClient;
|
|
1225
1490
|
}
|
|
1226
1491
|
function getTracer() {
|
|
1227
|
-
return
|
|
1492
|
+
return trace3.getTracer(TRACER_NAME, SDK_VERSION);
|
|
1228
1493
|
}
|
|
1229
1494
|
var sinks, heartbeats, createClient, RiusClient, globalClient;
|
|
1230
1495
|
var init_client = __esm({
|
|
@@ -1240,18 +1505,22 @@ var init_client = __esm({
|
|
|
1240
1505
|
init_pending();
|
|
1241
1506
|
init_semconv();
|
|
1242
1507
|
init_session();
|
|
1508
|
+
init_user();
|
|
1509
|
+
init_version();
|
|
1243
1510
|
init_workspace();
|
|
1244
1511
|
sinks = /* @__PURE__ */ new WeakMap();
|
|
1245
1512
|
heartbeats = /* @__PURE__ */ new WeakMap();
|
|
1246
1513
|
RiusClient = class _RiusClient {
|
|
1247
1514
|
provider;
|
|
1248
1515
|
health;
|
|
1516
|
+
teardown;
|
|
1249
1517
|
/** Resolves with the names of the auto-instrumentations that attached. */
|
|
1250
1518
|
ready;
|
|
1251
1519
|
constructor(parts) {
|
|
1252
1520
|
this.provider = parts.provider;
|
|
1253
1521
|
this.health = parts.health;
|
|
1254
1522
|
this.ready = parts.ready;
|
|
1523
|
+
this.teardown = parts.teardown;
|
|
1255
1524
|
sinks.set(this, parts.processors);
|
|
1256
1525
|
if (parts.heartbeat) heartbeats.set(this, parts.heartbeat);
|
|
1257
1526
|
}
|
|
@@ -1279,13 +1548,22 @@ var init_client = __esm({
|
|
|
1279
1548
|
process.removeListener("beforeExit", heartbeat.beforeExitHandler);
|
|
1280
1549
|
await heartbeat.sender.stop();
|
|
1281
1550
|
}
|
|
1551
|
+
await this.ready;
|
|
1552
|
+
for (const disable of this.teardown.splice(0)) {
|
|
1553
|
+
try {
|
|
1554
|
+
disable();
|
|
1555
|
+
} catch {
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1282
1558
|
try {
|
|
1283
1559
|
await this.provider.shutdown();
|
|
1284
1560
|
} finally {
|
|
1285
1561
|
if (globalClient === this) {
|
|
1286
1562
|
globalClient = void 0;
|
|
1287
1563
|
setGlobalRouting(void 0);
|
|
1288
|
-
|
|
1564
|
+
trace3.disable();
|
|
1565
|
+
context.disable();
|
|
1566
|
+
propagation.disable();
|
|
1289
1567
|
}
|
|
1290
1568
|
}
|
|
1291
1569
|
}
|
|
@@ -1300,17 +1578,106 @@ init_client();
|
|
|
1300
1578
|
// src/generation.ts
|
|
1301
1579
|
init_esm_shims();
|
|
1302
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
|
|
1303
1642
|
init_semconv();
|
|
1304
1643
|
init_serde();
|
|
1305
1644
|
init_spans();
|
|
1306
1645
|
var Generation = class extends Observation {
|
|
1646
|
+
/**
|
|
1647
|
+
* The provider passed at creation; drives the Anthropic input-token summing
|
|
1648
|
+
* in {@link setUsage}. A bare `new Generation(span)` has none and never sums.
|
|
1649
|
+
*/
|
|
1650
|
+
constructor(span, provider) {
|
|
1651
|
+
super(span);
|
|
1652
|
+
this.provider = provider;
|
|
1653
|
+
}
|
|
1654
|
+
provider;
|
|
1307
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
|
+
*/
|
|
1308
1662
|
setInput(value) {
|
|
1309
|
-
this.span.setAttribute(GEN_AI_INPUT_MESSAGES,
|
|
1663
|
+
this.span.setAttribute(GEN_AI_INPUT_MESSAGES, serializeMessages(value, "user"));
|
|
1310
1664
|
return this;
|
|
1311
1665
|
}
|
|
1666
|
+
/** Record the response messages (`gen_ai.output.messages`); bare strings default to `assistant`. */
|
|
1312
1667
|
setOutput(value) {
|
|
1313
|
-
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));
|
|
1314
1681
|
return this;
|
|
1315
1682
|
}
|
|
1316
1683
|
setModel(model) {
|
|
@@ -1318,15 +1685,20 @@ var Generation = class extends Observation {
|
|
|
1318
1685
|
return this;
|
|
1319
1686
|
}
|
|
1320
1687
|
/**
|
|
1321
|
-
* Token usage (`gen_ai.usage.*`). Pass provider-reported values as-is
|
|
1322
|
-
* Per the GenAI conventions, `inputTokens` is the
|
|
1323
|
-
* tokens (the cache counts are subsets of it)
|
|
1324
|
-
*
|
|
1325
|
-
* the
|
|
1688
|
+
* Token usage (`gen_ai.usage.*`). Pass provider-reported values as-is;
|
|
1689
|
+
* never pre-add anything. Per the GenAI conventions, `inputTokens` is the
|
|
1690
|
+
* total including cached tokens (the cache counts are subsets of it).
|
|
1691
|
+
* Anthropic's API reports `input_tokens` excluding the cache counts, and
|
|
1692
|
+
* the conventions require the instrumentation to do the summing, so when
|
|
1693
|
+
* the generation's provider is `"anthropic"` the emitted total is
|
|
1694
|
+
* `inputTokens` plus both cache counts. Every other provider is recorded
|
|
1695
|
+
* verbatim.
|
|
1326
1696
|
*/
|
|
1327
1697
|
setUsage(usage) {
|
|
1328
1698
|
if (usage.inputTokens !== void 0) {
|
|
1329
|
-
this.
|
|
1699
|
+
const sums = this.provider?.toLowerCase() === "anthropic";
|
|
1700
|
+
const total = sums ? usage.inputTokens + (usage.cacheReadInputTokens ?? 0) + (usage.cacheWriteInputTokens ?? 0) : usage.inputTokens;
|
|
1701
|
+
this.span.setAttribute(GEN_AI_USAGE_INPUT_TOKENS, total);
|
|
1330
1702
|
}
|
|
1331
1703
|
if (usage.outputTokens !== void 0) {
|
|
1332
1704
|
this.span.setAttribute(GEN_AI_USAGE_OUTPUT_TOKENS, usage.outputTokens);
|
|
@@ -1337,6 +1709,9 @@ var Generation = class extends Observation {
|
|
|
1337
1709
|
if (usage.cacheWriteInputTokens !== void 0) {
|
|
1338
1710
|
this.span.setAttribute(GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS, usage.cacheWriteInputTokens);
|
|
1339
1711
|
}
|
|
1712
|
+
if (usage.reasoningOutputTokens !== void 0) {
|
|
1713
|
+
this.span.setAttribute(GEN_AI_USAGE_REASONING_OUTPUT_TOKENS, usage.reasoningOutputTokens);
|
|
1714
|
+
}
|
|
1340
1715
|
return this;
|
|
1341
1716
|
}
|
|
1342
1717
|
/**
|
|
@@ -1368,34 +1743,36 @@ var Generation = class extends Observation {
|
|
|
1368
1743
|
};
|
|
1369
1744
|
function attributesFor(options) {
|
|
1370
1745
|
const attributes = { ...kindAttributes("LLM" /* LLM */) };
|
|
1746
|
+
if (options.operation !== void 0) attributes[GEN_AI_OPERATION_NAME] = options.operation;
|
|
1371
1747
|
if (options.model !== void 0) attributes[GEN_AI_REQUEST_MODEL] = options.model;
|
|
1372
1748
|
if (options.provider !== void 0) attributes[GEN_AI_PROVIDER_NAME] = options.provider;
|
|
1749
|
+
if (options.userId !== void 0) attributes[USER_ID] = options.userId;
|
|
1373
1750
|
return attributes;
|
|
1374
1751
|
}
|
|
1375
1752
|
function configure2(generation, options) {
|
|
1376
1753
|
for (const [key, value] of Object.entries(options.modelParameters ?? {})) {
|
|
1377
1754
|
generation.setAttribute(`${GEN_AI_REQUEST_PREFIX}${key}`, value);
|
|
1378
1755
|
}
|
|
1756
|
+
if (options.reasoningLevel !== void 0) {
|
|
1757
|
+
generation.setAttribute(GEN_AI_REQUEST_REASONING_LEVEL, options.reasoningLevel);
|
|
1758
|
+
}
|
|
1759
|
+
if (options.tools !== void 0) generation.setToolDefinitions(options.tools);
|
|
1379
1760
|
if (options.input !== void 0) generation.setInput(options.input);
|
|
1380
1761
|
return generation;
|
|
1381
1762
|
}
|
|
1382
1763
|
function startGeneration(name, options = {}) {
|
|
1383
1764
|
const span = getTracer().startSpan(name, { attributes: attributesFor(options) });
|
|
1384
|
-
return configure2(new Generation(span), options);
|
|
1765
|
+
return configure2(new Generation(span, options.provider), options);
|
|
1385
1766
|
}
|
|
1386
1767
|
function startAsCurrentGeneration(name, optionsOrFn, maybeFn) {
|
|
1387
1768
|
const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
|
|
1388
|
-
return
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
} finally {
|
|
1396
|
-
generation.end();
|
|
1397
|
-
}
|
|
1398
|
-
});
|
|
1769
|
+
return runActive(
|
|
1770
|
+
name,
|
|
1771
|
+
attributesFor(options),
|
|
1772
|
+
options.userId,
|
|
1773
|
+
(span) => configure2(new Generation(span, options.provider), options),
|
|
1774
|
+
fn
|
|
1775
|
+
);
|
|
1399
1776
|
}
|
|
1400
1777
|
|
|
1401
1778
|
// src/observe.ts
|
|
@@ -1407,7 +1784,10 @@ function observe(fn, options = {}) {
|
|
|
1407
1784
|
const captureOutput = options.captureOutput ?? true;
|
|
1408
1785
|
const wrapped = (...args) => startAsCurrentSpan(
|
|
1409
1786
|
name,
|
|
1410
|
-
{
|
|
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 },
|
|
1411
1791
|
async (observation) => {
|
|
1412
1792
|
const result = await fn(...args);
|
|
1413
1793
|
if (captureOutput && result !== void 0) observation.setOutput(result);
|
|
@@ -1421,9 +1801,10 @@ function observe(fn, options = {}) {
|
|
|
1421
1801
|
// src/index.ts
|
|
1422
1802
|
init_semconv();
|
|
1423
1803
|
init_session();
|
|
1804
|
+
init_user();
|
|
1424
1805
|
init_spans();
|
|
1425
1806
|
init_workspace();
|
|
1426
|
-
var VERSION = "0.
|
|
1807
|
+
var VERSION = "0.6.0";
|
|
1427
1808
|
export {
|
|
1428
1809
|
Generation,
|
|
1429
1810
|
Observation,
|
|
@@ -1439,5 +1820,6 @@ export {
|
|
|
1439
1820
|
startGeneration,
|
|
1440
1821
|
startSpan,
|
|
1441
1822
|
withSession,
|
|
1823
|
+
withUser,
|
|
1442
1824
|
withWorkspace
|
|
1443
1825
|
};
|