@glassflow-ai/rius 0.7.0 → 0.8.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 +24 -2
- package/dist/index.cjs +97 -61
- package/dist/index.d.cts +26 -10
- package/dist/index.d.ts +26 -10
- package/dist/index.js +66 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -118,8 +118,30 @@ automatically and recording any thrown exception. The options argument is
|
|
|
118
118
|
optional in both, so `startAsCurrentSpan("step", async (span) => { ... })`
|
|
119
119
|
works when you have nothing to configure.
|
|
120
120
|
|
|
121
|
+
`kind` sets the span's taxonomy (`openinference.span.kind`, our `SpanKind`
|
|
122
|
+
enum: CHAIN by default, or TOOL, RETRIEVER, EMBEDDING, AGENT, LLM). A TOOL
|
|
123
|
+
span also carries `gen_ai.tool.name`, set to the span name. Each kind also
|
|
124
|
+
decides the OpenTelemetry `SpanKind` field the conventions expect: LLM,
|
|
125
|
+
EMBEDDING and RETRIEVER spans are CLIENT, everything else INTERNAL. Pass
|
|
126
|
+
`otelKind` to override it, for example CLIENT for a call to a hosted agent.
|
|
127
|
+
Note the name collision: `SpanKind` exported by this package is the taxonomy
|
|
128
|
+
enum; the value for `otelKind` is `SpanKind` from `@opentelemetry/api`, so
|
|
129
|
+
import that one under an alias.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { SpanKind as OtelSpanKind } from "@opentelemetry/api";
|
|
133
|
+
import { SpanKind, startAsCurrentSpan } from "@glassflow-ai/rius";
|
|
134
|
+
|
|
135
|
+
await startAsCurrentSpan(
|
|
136
|
+
"delegate-to-planner",
|
|
137
|
+
{ kind: SpanKind.AGENT, otelKind: OtelSpanKind.CLIENT, input: task },
|
|
138
|
+
async (span) => { ... },
|
|
139
|
+
);
|
|
140
|
+
```
|
|
141
|
+
|
|
121
142
|
On the manual path, `recordException()` does what the scoped form does for
|
|
122
|
-
you: it records the error
|
|
143
|
+
you: it records the error, sets ERROR status and sets `error.type` to the
|
|
144
|
+
error's name.
|
|
123
145
|
|
|
124
146
|
```ts
|
|
125
147
|
const span = startSpan("fetch-documents");
|
|
@@ -182,7 +204,7 @@ Install only the ones you use:
|
|
|
182
204
|
- **`anthropic`** wraps the Anthropic SDK, via `@arizeai/openinference-instrumentation-anthropic`, turning Messages calls into LLM spans with model, messages and token counts.
|
|
183
205
|
- **`langchain`** traces LangChain.js chains, models, tools and retrievers, via `@arizeai/openinference-instrumentation-langchain`. It patches the callback manager in `@langchain/core`, which every LangChain.js application already has, and the SDK resolves that module for you so nothing has to be passed in at `init()`.
|
|
184
206
|
- **`vercel-ai`** attaches to spans the Vercel AI SDK's own OpenTelemetry integration produces, via `@arizeai/openinference-vercel`, adding OpenInference attributes to them. This package requires Node 22 or newer.
|
|
185
|
-
- **`mcp`** patches `@modelcontextprotocol/sdk`'s `Client.callTool` so every MCP tool call becomes a TOOL span
|
|
207
|
+
- **`mcp`** patches `@modelcontextprotocol/sdk`'s `Client.callTool` so every MCP tool call becomes a TOOL span named `execute_tool <tool>`, carrying the tool name, arguments, result, latency, and error status. The span follows the OpenTelemetry MCP conventions: OTel `SpanKind` CLIENT, `mcp.method.name` set to `tools/call`, `mcp.protocol.version` set to the negotiated version when the transport exposes it (Streamable HTTP does; stdio and SSE do not), and `error.type` set to `tool_error` when the server returns an `isError` result.
|
|
186
208
|
|
|
187
209
|
There is no selection option: `init()` always attempts every bundled
|
|
188
210
|
integration, so which ones actually attach is determined entirely by
|
package/dist/index.cjs
CHANGED
|
@@ -367,17 +367,22 @@ var init_heartbeat = __esm({
|
|
|
367
367
|
});
|
|
368
368
|
|
|
369
369
|
// src/semconv.ts
|
|
370
|
-
function
|
|
370
|
+
function otelSpanKind(kind) {
|
|
371
|
+
return OTEL_KIND_BY_KIND[kind];
|
|
372
|
+
}
|
|
373
|
+
function kindAttributes(kind, name) {
|
|
371
374
|
const attributes = { [OPENINFERENCE_SPAN_KIND]: kind };
|
|
372
375
|
const operation = OPERATION_BY_KIND[kind];
|
|
373
376
|
if (operation !== void 0) attributes[GEN_AI_OPERATION_NAME] = operation;
|
|
377
|
+
if (kind === "TOOL" /* TOOL */ && name !== void 0) attributes[GEN_AI_TOOL_NAME] = name;
|
|
374
378
|
return attributes;
|
|
375
379
|
}
|
|
376
|
-
var TRACER_NAME, SERVICE_INSTANCE_ID, OPENINFERENCE_SPAN_KIND, INPUT_VALUE, OUTPUT_VALUE, SESSION_ID, USER_ID, WORKSPACE_ROUTE, GEN_AI_OPERATION_NAME, GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_MODEL, GEN_AI_REQUEST_REASONING_LEVEL, GEN_AI_RESPONSE_MODEL, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS, GEN_AI_USAGE_REASONING_OUTPUT_TOKENS, GEN_AI_INPUT_MESSAGES, GEN_AI_OUTPUT_MESSAGES, GEN_AI_RESPONSE_FINISH_REASONS, GEN_AI_TOOL_NAME, GEN_AI_TOOL_DEFINITIONS, GEN_AI_REQUEST_PREFIX, MCP_METHOD_NAME, MCP_METHOD_TOOLS_CALL, MCP_PROTOCOL_VERSION, ERROR_TYPE, ERROR_TYPE_TOOL_ERROR, 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;
|
|
380
|
+
var import_api, 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_REQUEST_STREAM, GEN_AI_RESPONSE_TIME_TO_FIRST_CHUNK, 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_METHOD_NAME, MCP_METHOD_TOOLS_CALL, MCP_PROTOCOL_VERSION, ERROR_TYPE, ERROR_TYPE_TOOL_ERROR, MCP_RESULT_TYPE, GEN_AI_FIRST_TOKEN_EVENT, SpanKind, OPERATION_BY_KIND, OTEL_KIND_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;
|
|
377
381
|
var init_semconv = __esm({
|
|
378
382
|
"src/semconv.ts"() {
|
|
379
383
|
"use strict";
|
|
380
384
|
init_cjs_shims();
|
|
385
|
+
import_api = require("@opentelemetry/api");
|
|
381
386
|
TRACER_NAME = "glassflow";
|
|
382
387
|
SERVICE_INSTANCE_ID = "service.instance.id";
|
|
383
388
|
OPENINFERENCE_SPAN_KIND = "openinference.span.kind";
|
|
@@ -391,6 +396,8 @@ var init_semconv = __esm({
|
|
|
391
396
|
GEN_AI_REQUEST_MODEL = "gen_ai.request.model";
|
|
392
397
|
GEN_AI_REQUEST_REASONING_LEVEL = "gen_ai.request.reasoning.level";
|
|
393
398
|
GEN_AI_RESPONSE_MODEL = "gen_ai.response.model";
|
|
399
|
+
GEN_AI_REQUEST_STREAM = "gen_ai.request.stream";
|
|
400
|
+
GEN_AI_RESPONSE_TIME_TO_FIRST_CHUNK = "gen_ai.response.time_to_first_chunk";
|
|
394
401
|
GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens";
|
|
395
402
|
GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens";
|
|
396
403
|
GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens";
|
|
@@ -424,6 +431,14 @@ var init_semconv = __esm({
|
|
|
424
431
|
["EMBEDDING" /* EMBEDDING */]: "embeddings",
|
|
425
432
|
["AGENT" /* AGENT */]: "invoke_agent"
|
|
426
433
|
};
|
|
434
|
+
OTEL_KIND_BY_KIND = {
|
|
435
|
+
["LLM" /* LLM */]: import_api.SpanKind.CLIENT,
|
|
436
|
+
["EMBEDDING" /* EMBEDDING */]: import_api.SpanKind.CLIENT,
|
|
437
|
+
["RETRIEVER" /* RETRIEVER */]: import_api.SpanKind.CLIENT,
|
|
438
|
+
["TOOL" /* TOOL */]: import_api.SpanKind.INTERNAL,
|
|
439
|
+
["AGENT" /* AGENT */]: import_api.SpanKind.INTERNAL,
|
|
440
|
+
["CHAIN" /* CHAIN */]: import_api.SpanKind.INTERNAL
|
|
441
|
+
};
|
|
427
442
|
CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
428
443
|
INPUT_VALUE,
|
|
429
444
|
OUTPUT_VALUE,
|
|
@@ -568,6 +583,9 @@ function toAttributeValue(value) {
|
|
|
568
583
|
return "[unserializable]";
|
|
569
584
|
}
|
|
570
585
|
}
|
|
586
|
+
function errorType(error) {
|
|
587
|
+
return error instanceof Error ? error.name : typeof error;
|
|
588
|
+
}
|
|
571
589
|
var MAX_ATTR_CHARS, TRUNCATION_MARKER;
|
|
572
590
|
var init_serde = __esm({
|
|
573
591
|
"src/serde.ts"() {
|
|
@@ -580,16 +598,16 @@ var init_serde = __esm({
|
|
|
580
598
|
|
|
581
599
|
// src/user.ts
|
|
582
600
|
function withUser(userId, fn) {
|
|
583
|
-
return
|
|
601
|
+
return import_api2.context.with(import_api2.context.active().setValue(USER_KEY, userId), () => fn(userId));
|
|
584
602
|
}
|
|
585
|
-
var
|
|
603
|
+
var import_api2, USER_KEY, UserSpanProcessor;
|
|
586
604
|
var init_user = __esm({
|
|
587
605
|
"src/user.ts"() {
|
|
588
606
|
"use strict";
|
|
589
607
|
init_cjs_shims();
|
|
590
|
-
|
|
608
|
+
import_api2 = require("@opentelemetry/api");
|
|
591
609
|
init_semconv();
|
|
592
|
-
USER_KEY = (0,
|
|
610
|
+
USER_KEY = (0, import_api2.createContextKey)("rius-user-id");
|
|
593
611
|
UserSpanProcessor = class {
|
|
594
612
|
onStart(span, parentContext) {
|
|
595
613
|
const value = parentContext.getValue(USER_KEY);
|
|
@@ -610,15 +628,18 @@ function configure(observation, options) {
|
|
|
610
628
|
if (options.input !== void 0) observation.setInput(options.input);
|
|
611
629
|
return observation;
|
|
612
630
|
}
|
|
613
|
-
function creationAttributes(options) {
|
|
614
|
-
const attributes = {
|
|
631
|
+
function creationAttributes(name, options) {
|
|
632
|
+
const attributes = {
|
|
633
|
+
...kindAttributes(options.kind ?? "CHAIN" /* CHAIN */, name),
|
|
634
|
+
...options.attributes
|
|
635
|
+
};
|
|
615
636
|
if (options.userId !== void 0) attributes[USER_ID] = options.userId;
|
|
616
637
|
return attributes;
|
|
617
638
|
}
|
|
618
639
|
function startSpan(name, options = {}) {
|
|
619
640
|
const span = getTracer().startSpan(name, {
|
|
620
|
-
kind: options.otelKind,
|
|
621
|
-
attributes: creationAttributes(options)
|
|
641
|
+
kind: options.otelKind ?? otelSpanKind(options.kind ?? "CHAIN" /* CHAIN */),
|
|
642
|
+
attributes: creationAttributes(name, options)
|
|
622
643
|
});
|
|
623
644
|
return configure(new Observation(span), options);
|
|
624
645
|
}
|
|
@@ -626,11 +647,11 @@ function startAsCurrentSpan(name, optionsOrFn, maybeFn) {
|
|
|
626
647
|
const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
|
|
627
648
|
return runActive(
|
|
628
649
|
name,
|
|
629
|
-
creationAttributes(options),
|
|
650
|
+
creationAttributes(name, options),
|
|
630
651
|
options.userId,
|
|
631
652
|
(span) => configure(new Observation(span), options),
|
|
632
653
|
fn,
|
|
633
|
-
options.otelKind
|
|
654
|
+
options.otelKind ?? otelSpanKind(options.kind ?? "CHAIN" /* CHAIN */)
|
|
634
655
|
);
|
|
635
656
|
}
|
|
636
657
|
function runActive(name, attributes, userId, makeHandle, fn, otelKind) {
|
|
@@ -647,12 +668,12 @@ function runActive(name, attributes, userId, makeHandle, fn, otelKind) {
|
|
|
647
668
|
});
|
|
648
669
|
return userId !== void 0 ? withUser(userId, run) : run();
|
|
649
670
|
}
|
|
650
|
-
var
|
|
671
|
+
var import_api3, Observation;
|
|
651
672
|
var init_spans = __esm({
|
|
652
673
|
"src/spans.ts"() {
|
|
653
674
|
"use strict";
|
|
654
675
|
init_cjs_shims();
|
|
655
|
-
|
|
676
|
+
import_api3 = require("@opentelemetry/api");
|
|
656
677
|
init_client();
|
|
657
678
|
init_semconv();
|
|
658
679
|
init_serde();
|
|
@@ -696,9 +717,15 @@ var init_spans = __esm({
|
|
|
696
717
|
return this;
|
|
697
718
|
}
|
|
698
719
|
/**
|
|
699
|
-
* Record an error on the span
|
|
700
|
-
* `startAsCurrent*` helpers do on a thrown error, exposed
|
|
701
|
-
* `start*` path does not have to reach through `.span` to
|
|
720
|
+
* Record an error on the span, set ERROR status and `error.type`. This is
|
|
721
|
+
* exactly what the `startAsCurrent*` helpers do on a thrown error, exposed
|
|
722
|
+
* so the manual `start*` path does not have to reach through `.span` to
|
|
723
|
+
* match it.
|
|
724
|
+
*
|
|
725
|
+
* `error.type` is Conditionally Required by the GenAI conventions on every
|
|
726
|
+
* span that ends in an error, and every helper's throw path funnels through
|
|
727
|
+
* here, so this is the one place that sets it. The error's name only, never
|
|
728
|
+
* the message: it must stay low-cardinality and free of echoed content.
|
|
702
729
|
*
|
|
703
730
|
* Accepts `unknown` because that is what a `catch` binding is; a non-Error
|
|
704
731
|
* throwable is wrapped so `recordException` still gets a real Error.
|
|
@@ -706,7 +733,8 @@ var init_spans = __esm({
|
|
|
706
733
|
recordException(error) {
|
|
707
734
|
const wrapped = error instanceof Error ? error : new Error(String(error));
|
|
708
735
|
this.span.recordException(wrapped);
|
|
709
|
-
this.span.setStatus({ code:
|
|
736
|
+
this.span.setStatus({ code: import_api3.SpanStatusCode.ERROR, message: wrapped.message });
|
|
737
|
+
this.span.setAttribute(ERROR_TYPE, errorType(error));
|
|
710
738
|
return this;
|
|
711
739
|
}
|
|
712
740
|
end() {
|
|
@@ -754,15 +782,12 @@ function recordResult(observation, result) {
|
|
|
754
782
|
observation.setAttribute(OUTPUT_VALUE, serializeResult(result));
|
|
755
783
|
if (resultIsError(result)) {
|
|
756
784
|
observation.span.setStatus({
|
|
757
|
-
code:
|
|
785
|
+
code: import_api4.SpanStatusCode.ERROR,
|
|
758
786
|
message: "tool returned an error result"
|
|
759
787
|
});
|
|
760
788
|
observation.setAttribute(ERROR_TYPE, ERROR_TYPE_TOOL_ERROR);
|
|
761
789
|
}
|
|
762
790
|
}
|
|
763
|
-
function errorType(error) {
|
|
764
|
-
return error instanceof Error ? error.name : typeof error;
|
|
765
|
-
}
|
|
766
791
|
function negotiatedProtocolVersion(client) {
|
|
767
792
|
const version = asRecord(asRecord(client)?.transport)?.protocolVersion;
|
|
768
793
|
return typeof version === "string" ? version : void 0;
|
|
@@ -792,18 +817,12 @@ function instrumentMcpClient(ClientClass) {
|
|
|
792
817
|
// The OTel SpanKind FIELD (orthogonal to our taxonomy attribute above):
|
|
793
818
|
// a remote tool call is CLIENT under both the MCP and the GenAI
|
|
794
819
|
// execute-tool conventions.
|
|
795
|
-
otelKind:
|
|
820
|
+
otelKind: import_api4.SpanKind.CLIENT,
|
|
796
821
|
input: params.arguments,
|
|
797
822
|
attributes: callAttributes(params.name, negotiatedProtocolVersion(this))
|
|
798
823
|
},
|
|
799
824
|
async (observation) => {
|
|
800
|
-
|
|
801
|
-
try {
|
|
802
|
-
result = await original.call(this, params, ...rest);
|
|
803
|
-
} catch (error) {
|
|
804
|
-
observation.setAttribute(ERROR_TYPE, errorType(error));
|
|
805
|
-
throw error;
|
|
806
|
-
}
|
|
825
|
+
const result = await original.call(this, params, ...rest);
|
|
807
826
|
recordResult(observation, result);
|
|
808
827
|
return result;
|
|
809
828
|
}
|
|
@@ -815,12 +834,12 @@ function instrumentMcpClient(ClientClass) {
|
|
|
815
834
|
ClientClass.prototype.callTool = original;
|
|
816
835
|
};
|
|
817
836
|
}
|
|
818
|
-
var
|
|
837
|
+
var import_api4, INPUT_REQUIRED;
|
|
819
838
|
var init_instrumentationMcp = __esm({
|
|
820
839
|
"src/instrumentationMcp.ts"() {
|
|
821
840
|
"use strict";
|
|
822
841
|
init_cjs_shims();
|
|
823
|
-
|
|
842
|
+
import_api4 = require("@opentelemetry/api");
|
|
824
843
|
init_semconv();
|
|
825
844
|
init_serde();
|
|
826
845
|
init_spans();
|
|
@@ -1094,12 +1113,12 @@ function redactInvocationParameters(value) {
|
|
|
1094
1113
|
for (const member of INVOCATION_PARAMETERS_CONTENT_MEMBERS) delete bag[member];
|
|
1095
1114
|
return JSON.stringify(bag);
|
|
1096
1115
|
}
|
|
1097
|
-
var
|
|
1116
|
+
var import_api5, METADATA_PREFIX, EXCEPTION_EVENT_NAME, EXCEPTION_CONTENT_KEYS, STATUS_DESCRIPTION_KEY, MaskingSpanExporter;
|
|
1098
1117
|
var init_masking = __esm({
|
|
1099
1118
|
"src/masking.ts"() {
|
|
1100
1119
|
"use strict";
|
|
1101
1120
|
init_cjs_shims();
|
|
1102
|
-
|
|
1121
|
+
import_api5 = require("@opentelemetry/api");
|
|
1103
1122
|
init_semconv();
|
|
1104
1123
|
init_serde();
|
|
1105
1124
|
METADATA_PREFIX = "metadata.";
|
|
@@ -1143,7 +1162,7 @@ var init_masking = __esm({
|
|
|
1143
1162
|
*/
|
|
1144
1163
|
sanitizeStatus(span) {
|
|
1145
1164
|
const status = span.status;
|
|
1146
|
-
if (status?.code !==
|
|
1165
|
+
if (status?.code !== import_api5.SpanStatusCode.ERROR || !status.message) return;
|
|
1147
1166
|
if (!this.opts.captureContent) {
|
|
1148
1167
|
status.message = void 0;
|
|
1149
1168
|
return;
|
|
@@ -1213,7 +1232,7 @@ function buildSnapshot(span) {
|
|
|
1213
1232
|
startTime: span.startTime,
|
|
1214
1233
|
endTime: span.startTime,
|
|
1215
1234
|
duration: [0, 0],
|
|
1216
|
-
status: { code:
|
|
1235
|
+
status: { code: import_api6.SpanStatusCode.UNSET },
|
|
1217
1236
|
attributes,
|
|
1218
1237
|
links: [],
|
|
1219
1238
|
events: [],
|
|
@@ -1228,12 +1247,12 @@ function buildSnapshot(span) {
|
|
|
1228
1247
|
function spanKey(context2) {
|
|
1229
1248
|
return `${context2.traceId}:${context2.spanId}`;
|
|
1230
1249
|
}
|
|
1231
|
-
var
|
|
1250
|
+
var import_api6, PendingSpanProcessor;
|
|
1232
1251
|
var init_pending = __esm({
|
|
1233
1252
|
"src/pending.ts"() {
|
|
1234
1253
|
"use strict";
|
|
1235
1254
|
init_cjs_shims();
|
|
1236
|
-
|
|
1255
|
+
import_api6 = require("@opentelemetry/api");
|
|
1237
1256
|
init_semconv();
|
|
1238
1257
|
PendingSpanProcessor = class {
|
|
1239
1258
|
delegate;
|
|
@@ -1281,17 +1300,17 @@ var init_pending = __esm({
|
|
|
1281
1300
|
// src/session.ts
|
|
1282
1301
|
function withSession(sessionIdOrFn, maybeFn) {
|
|
1283
1302
|
const [sessionId, fn] = typeof sessionIdOrFn === "function" ? [(0, import_node_crypto.randomUUID)(), sessionIdOrFn] : [sessionIdOrFn, maybeFn];
|
|
1284
|
-
return
|
|
1303
|
+
return import_api7.context.with(import_api7.context.active().setValue(SESSION_KEY, sessionId), () => fn(sessionId));
|
|
1285
1304
|
}
|
|
1286
|
-
var import_node_crypto,
|
|
1305
|
+
var import_node_crypto, import_api7, SESSION_KEY, SessionSpanProcessor;
|
|
1287
1306
|
var init_session = __esm({
|
|
1288
1307
|
"src/session.ts"() {
|
|
1289
1308
|
"use strict";
|
|
1290
1309
|
init_cjs_shims();
|
|
1291
1310
|
import_node_crypto = require("crypto");
|
|
1292
|
-
|
|
1311
|
+
import_api7 = require("@opentelemetry/api");
|
|
1293
1312
|
init_semconv();
|
|
1294
|
-
SESSION_KEY = (0,
|
|
1313
|
+
SESSION_KEY = (0, import_api7.createContextKey)("rius-session-id");
|
|
1295
1314
|
SessionSpanProcessor = class {
|
|
1296
1315
|
constructor(defaultSessionId) {
|
|
1297
1316
|
this.defaultSessionId = defaultSessionId;
|
|
@@ -1299,7 +1318,7 @@ var init_session = __esm({
|
|
|
1299
1318
|
defaultSessionId;
|
|
1300
1319
|
onStart(span, parentContext) {
|
|
1301
1320
|
const value = parentContext.getValue(SESSION_KEY);
|
|
1302
|
-
const parent =
|
|
1321
|
+
const parent = import_api7.trace.getSpan(parentContext);
|
|
1303
1322
|
const inherited = parent?.attributes?.[SESSION_ID];
|
|
1304
1323
|
const sessionId = typeof value === "string" ? value : typeof inherited === "string" ? inherited : this.defaultSessionId;
|
|
1305
1324
|
if (sessionId !== void 0) span.setAttribute(SESSION_ID, sessionId);
|
|
@@ -1317,7 +1336,7 @@ var init_session = __esm({
|
|
|
1317
1336
|
// src/workspace.ts
|
|
1318
1337
|
function withWorkspace(alias, fn) {
|
|
1319
1338
|
if (!alias) throw new Error("workspace alias must be a non-empty string");
|
|
1320
|
-
return
|
|
1339
|
+
return import_api8.context.with(import_api8.context.active().setValue(WORKSPACE_KEY, alias), () => fn(alias));
|
|
1321
1340
|
}
|
|
1322
1341
|
function setGlobalRouting(routing) {
|
|
1323
1342
|
globalRouting = routing;
|
|
@@ -1330,20 +1349,20 @@ function registerWorkspace(alias, apiKey) {
|
|
|
1330
1349
|
}
|
|
1331
1350
|
globalRouting.register(alias, apiKey);
|
|
1332
1351
|
}
|
|
1333
|
-
var
|
|
1352
|
+
var import_api8, WORKSPACE_KEY, WorkspaceSpanProcessor, RoutingSpanExporter, globalRouting;
|
|
1334
1353
|
var init_workspace = __esm({
|
|
1335
1354
|
"src/workspace.ts"() {
|
|
1336
1355
|
"use strict";
|
|
1337
1356
|
init_cjs_shims();
|
|
1338
|
-
|
|
1357
|
+
import_api8 = require("@opentelemetry/api");
|
|
1339
1358
|
init_esm();
|
|
1340
1359
|
init_semconv();
|
|
1341
|
-
WORKSPACE_KEY = (0,
|
|
1360
|
+
WORKSPACE_KEY = (0, import_api8.createContextKey)("rius-workspace-alias");
|
|
1342
1361
|
WorkspaceSpanProcessor = class {
|
|
1343
1362
|
/** Straddles already warned about, as "parentAlias->alias"; one warning each, not one per span. */
|
|
1344
1363
|
warnedStraddles = /* @__PURE__ */ new Set();
|
|
1345
1364
|
onStart(span, parentContext) {
|
|
1346
|
-
const parent =
|
|
1365
|
+
const parent = import_api8.trace.getSpan(parentContext);
|
|
1347
1366
|
const parentAlias = parent?.attributes?.[WORKSPACE_ROUTE];
|
|
1348
1367
|
const scoped = parentContext.getValue(WORKSPACE_KEY);
|
|
1349
1368
|
const alias = typeof scoped === "string" ? scoped : parentAlias;
|
|
@@ -1532,15 +1551,15 @@ function init(options = {}) {
|
|
|
1532
1551
|
return globalClient;
|
|
1533
1552
|
}
|
|
1534
1553
|
function getTracer() {
|
|
1535
|
-
return
|
|
1554
|
+
return import_api9.trace.getTracer(TRACER_NAME, SDK_VERSION);
|
|
1536
1555
|
}
|
|
1537
|
-
var import_node_crypto2,
|
|
1556
|
+
var import_node_crypto2, import_api9, import_exporter_trace_otlp_proto, import_resources, import_sdk_trace_base, import_sdk_trace_node, import_semantic_conventions, sinks, heartbeats, createClient, RiusClient, globalClient;
|
|
1538
1557
|
var init_client = __esm({
|
|
1539
1558
|
"src/client.ts"() {
|
|
1540
1559
|
"use strict";
|
|
1541
1560
|
init_cjs_shims();
|
|
1542
1561
|
import_node_crypto2 = require("crypto");
|
|
1543
|
-
|
|
1562
|
+
import_api9 = require("@opentelemetry/api");
|
|
1544
1563
|
import_exporter_trace_otlp_proto = require("@opentelemetry/exporter-trace-otlp-proto");
|
|
1545
1564
|
import_resources = require("@opentelemetry/resources");
|
|
1546
1565
|
import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
@@ -1611,9 +1630,9 @@ var init_client = __esm({
|
|
|
1611
1630
|
if (globalClient === this) {
|
|
1612
1631
|
globalClient = void 0;
|
|
1613
1632
|
setGlobalRouting(void 0);
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1633
|
+
import_api9.trace.disable();
|
|
1634
|
+
import_api9.context.disable();
|
|
1635
|
+
import_api9.propagation.disable();
|
|
1617
1636
|
}
|
|
1618
1637
|
}
|
|
1619
1638
|
}
|
|
@@ -1723,6 +1742,12 @@ var Generation = class extends Observation {
|
|
|
1723
1742
|
}
|
|
1724
1743
|
provider;
|
|
1725
1744
|
firstTokenRecorded = false;
|
|
1745
|
+
/**
|
|
1746
|
+
* Monotonic clock at construction, which is span creation for both
|
|
1747
|
+
* `startGeneration` and the scoped form. The API `Span` exposes no start
|
|
1748
|
+
* time, so this is what `recordFirstToken` measures the first chunk against.
|
|
1749
|
+
*/
|
|
1750
|
+
startedAt = performance.now();
|
|
1726
1751
|
/**
|
|
1727
1752
|
* Record the request messages (`gen_ai.input.messages`), normalised to the
|
|
1728
1753
|
* GenAI `{role, parts}` shape like the Python SDK does: bare strings, OpenAI
|
|
@@ -1797,17 +1822,24 @@ var Generation = class extends Observation {
|
|
|
1797
1822
|
return this;
|
|
1798
1823
|
}
|
|
1799
1824
|
/**
|
|
1800
|
-
*
|
|
1801
|
-
*
|
|
1802
|
-
*
|
|
1803
|
-
*
|
|
1825
|
+
* Mark the arrival of the first streamed token. Records the
|
|
1826
|
+
* `gen_ai.first_token` event (the timestamp the backend derives TTFT from)
|
|
1827
|
+
* and, per the GenAI conventions, the derived
|
|
1828
|
+
* `gen_ai.response.time_to_first_chunk` (seconds since span creation) plus
|
|
1829
|
+
* `gen_ai.request.stream = true`: a first chunk arriving is what tells the
|
|
1830
|
+
* SDK the request streamed. Idempotent: only the first call records, so a
|
|
1831
|
+
* streaming loop can call this unconditionally on every chunk without
|
|
1832
|
+
* inflating the span. A no-op after the span has ended.
|
|
1804
1833
|
*/
|
|
1805
1834
|
recordFirstToken() {
|
|
1806
1835
|
if (this.firstTokenRecorded || !this.span.isRecording()) {
|
|
1807
1836
|
return this;
|
|
1808
1837
|
}
|
|
1838
|
+
const elapsedSeconds = Math.max(performance.now() - this.startedAt, 0) / 1e3;
|
|
1809
1839
|
this.span.addEvent(GEN_AI_FIRST_TOKEN_EVENT);
|
|
1810
1840
|
this.firstTokenRecorded = true;
|
|
1841
|
+
this.span.setAttribute(GEN_AI_REQUEST_STREAM, true);
|
|
1842
|
+
this.span.setAttribute(GEN_AI_RESPONSE_TIME_TO_FIRST_CHUNK, elapsedSeconds);
|
|
1811
1843
|
return this;
|
|
1812
1844
|
}
|
|
1813
1845
|
};
|
|
@@ -1831,7 +1863,10 @@ function configure2(generation, options) {
|
|
|
1831
1863
|
return generation;
|
|
1832
1864
|
}
|
|
1833
1865
|
function startGeneration(name, options = {}) {
|
|
1834
|
-
const span = getTracer().startSpan(name, {
|
|
1866
|
+
const span = getTracer().startSpan(name, {
|
|
1867
|
+
kind: otelSpanKind("LLM" /* LLM */),
|
|
1868
|
+
attributes: attributesFor(options)
|
|
1869
|
+
});
|
|
1835
1870
|
return configure2(new Generation(span, options.provider), options);
|
|
1836
1871
|
}
|
|
1837
1872
|
function startAsCurrentGeneration(name, optionsOrFn, maybeFn) {
|
|
@@ -1841,7 +1876,8 @@ function startAsCurrentGeneration(name, optionsOrFn, maybeFn) {
|
|
|
1841
1876
|
attributesFor(options),
|
|
1842
1877
|
options.userId,
|
|
1843
1878
|
(span) => configure2(new Generation(span, options.provider), options),
|
|
1844
|
-
fn
|
|
1879
|
+
fn,
|
|
1880
|
+
otelSpanKind("LLM" /* LLM */)
|
|
1845
1881
|
);
|
|
1846
1882
|
}
|
|
1847
1883
|
|
|
@@ -1874,7 +1910,7 @@ init_session();
|
|
|
1874
1910
|
init_user();
|
|
1875
1911
|
init_spans();
|
|
1876
1912
|
init_workspace();
|
|
1877
|
-
var VERSION = "0.
|
|
1913
|
+
var VERSION = "0.8.0";
|
|
1878
1914
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1879
1915
|
0 && (module.exports = {
|
|
1880
1916
|
Generation,
|
package/dist/index.d.cts
CHANGED
|
@@ -174,8 +174,8 @@ interface SpanOptions {
|
|
|
174
174
|
/**
|
|
175
175
|
* The OpenTelemetry `SpanKind` FIELD (INTERNAL, CLIENT, …), orthogonal to
|
|
176
176
|
* `kind` above, which is our taxonomy attribute. Conventions set it per
|
|
177
|
-
* operation
|
|
178
|
-
*
|
|
177
|
+
* operation, so by default it is derived from `kind` (see `otelSpanKind`);
|
|
178
|
+
* set this to override, as the MCP wrapper does for a remote tool call.
|
|
179
179
|
*/
|
|
180
180
|
otelKind?: SpanKind$1;
|
|
181
181
|
input?: unknown;
|
|
@@ -210,9 +210,15 @@ declare class Observation {
|
|
|
210
210
|
*/
|
|
211
211
|
setAttribute(key: string, value: unknown): this;
|
|
212
212
|
/**
|
|
213
|
-
* Record an error on the span
|
|
214
|
-
* `startAsCurrent*` helpers do on a thrown error, exposed
|
|
215
|
-
* `start*` path does not have to reach through `.span` to
|
|
213
|
+
* Record an error on the span, set ERROR status and `error.type`. This is
|
|
214
|
+
* exactly what the `startAsCurrent*` helpers do on a thrown error, exposed
|
|
215
|
+
* so the manual `start*` path does not have to reach through `.span` to
|
|
216
|
+
* match it.
|
|
217
|
+
*
|
|
218
|
+
* `error.type` is Conditionally Required by the GenAI conventions on every
|
|
219
|
+
* span that ends in an error, and every helper's throw path funnels through
|
|
220
|
+
* here, so this is the one place that sets it. The error's name only, never
|
|
221
|
+
* the message: it must stay low-cardinality and free of echoed content.
|
|
216
222
|
*
|
|
217
223
|
* Accepts `unknown` because that is what a `catch` binding is; a non-Error
|
|
218
224
|
* throwable is wrapped so `recordException` still gets a real Error.
|
|
@@ -282,6 +288,12 @@ interface GenerationOptions {
|
|
|
282
288
|
declare class Generation extends Observation {
|
|
283
289
|
private readonly provider?;
|
|
284
290
|
private firstTokenRecorded;
|
|
291
|
+
/**
|
|
292
|
+
* Monotonic clock at construction, which is span creation for both
|
|
293
|
+
* `startGeneration` and the scoped form. The API `Span` exposes no start
|
|
294
|
+
* time, so this is what `recordFirstToken` measures the first chunk against.
|
|
295
|
+
*/
|
|
296
|
+
private readonly startedAt;
|
|
285
297
|
/**
|
|
286
298
|
* The provider passed at creation; drives the Anthropic input-token summing
|
|
287
299
|
* in {@link setUsage}. A bare `new Generation(span)` has none and never sums.
|
|
@@ -341,10 +353,14 @@ declare class Generation extends Observation {
|
|
|
341
353
|
*/
|
|
342
354
|
setFinishReasons(reasons: string | string[]): this;
|
|
343
355
|
/**
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
356
|
+
* Mark the arrival of the first streamed token. Records the
|
|
357
|
+
* `gen_ai.first_token` event (the timestamp the backend derives TTFT from)
|
|
358
|
+
* and, per the GenAI conventions, the derived
|
|
359
|
+
* `gen_ai.response.time_to_first_chunk` (seconds since span creation) plus
|
|
360
|
+
* `gen_ai.request.stream = true`: a first chunk arriving is what tells the
|
|
361
|
+
* SDK the request streamed. Idempotent: only the first call records, so a
|
|
362
|
+
* streaming loop can call this unconditionally on every chunk without
|
|
363
|
+
* inflating the span. A no-op after the span has ended.
|
|
348
364
|
*/
|
|
349
365
|
recordFirstToken(): this;
|
|
350
366
|
}
|
|
@@ -448,6 +464,6 @@ declare function withSession<T>(sessionId: string, fn: (sessionId: string) => T)
|
|
|
448
464
|
*/
|
|
449
465
|
declare function withUser<T>(userId: string, fn: (userId: string) => T): T;
|
|
450
466
|
|
|
451
|
-
declare const VERSION = "0.
|
|
467
|
+
declare const VERSION = "0.8.0";
|
|
452
468
|
|
|
453
469
|
export { Generation, type GenerationBody, type GenerationOptions, type InitOptions, type Mask, Observation, type ObserveOptions, RiusClient, type RiusOptions, type SpanBody, SpanKind, type SpanOptions, VERSION, type WorkspaceExporterFactory, getTracer, init, observe, registerWorkspace, startAsCurrentGeneration, startAsCurrentSpan, startGeneration, startSpan, withSession, withUser, withWorkspace };
|
package/dist/index.d.ts
CHANGED
|
@@ -174,8 +174,8 @@ interface SpanOptions {
|
|
|
174
174
|
/**
|
|
175
175
|
* The OpenTelemetry `SpanKind` FIELD (INTERNAL, CLIENT, …), orthogonal to
|
|
176
176
|
* `kind` above, which is our taxonomy attribute. Conventions set it per
|
|
177
|
-
* operation
|
|
178
|
-
*
|
|
177
|
+
* operation, so by default it is derived from `kind` (see `otelSpanKind`);
|
|
178
|
+
* set this to override, as the MCP wrapper does for a remote tool call.
|
|
179
179
|
*/
|
|
180
180
|
otelKind?: SpanKind$1;
|
|
181
181
|
input?: unknown;
|
|
@@ -210,9 +210,15 @@ declare class Observation {
|
|
|
210
210
|
*/
|
|
211
211
|
setAttribute(key: string, value: unknown): this;
|
|
212
212
|
/**
|
|
213
|
-
* Record an error on the span
|
|
214
|
-
* `startAsCurrent*` helpers do on a thrown error, exposed
|
|
215
|
-
* `start*` path does not have to reach through `.span` to
|
|
213
|
+
* Record an error on the span, set ERROR status and `error.type`. This is
|
|
214
|
+
* exactly what the `startAsCurrent*` helpers do on a thrown error, exposed
|
|
215
|
+
* so the manual `start*` path does not have to reach through `.span` to
|
|
216
|
+
* match it.
|
|
217
|
+
*
|
|
218
|
+
* `error.type` is Conditionally Required by the GenAI conventions on every
|
|
219
|
+
* span that ends in an error, and every helper's throw path funnels through
|
|
220
|
+
* here, so this is the one place that sets it. The error's name only, never
|
|
221
|
+
* the message: it must stay low-cardinality and free of echoed content.
|
|
216
222
|
*
|
|
217
223
|
* Accepts `unknown` because that is what a `catch` binding is; a non-Error
|
|
218
224
|
* throwable is wrapped so `recordException` still gets a real Error.
|
|
@@ -282,6 +288,12 @@ interface GenerationOptions {
|
|
|
282
288
|
declare class Generation extends Observation {
|
|
283
289
|
private readonly provider?;
|
|
284
290
|
private firstTokenRecorded;
|
|
291
|
+
/**
|
|
292
|
+
* Monotonic clock at construction, which is span creation for both
|
|
293
|
+
* `startGeneration` and the scoped form. The API `Span` exposes no start
|
|
294
|
+
* time, so this is what `recordFirstToken` measures the first chunk against.
|
|
295
|
+
*/
|
|
296
|
+
private readonly startedAt;
|
|
285
297
|
/**
|
|
286
298
|
* The provider passed at creation; drives the Anthropic input-token summing
|
|
287
299
|
* in {@link setUsage}. A bare `new Generation(span)` has none and never sums.
|
|
@@ -341,10 +353,14 @@ declare class Generation extends Observation {
|
|
|
341
353
|
*/
|
|
342
354
|
setFinishReasons(reasons: string | string[]): this;
|
|
343
355
|
/**
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
356
|
+
* Mark the arrival of the first streamed token. Records the
|
|
357
|
+
* `gen_ai.first_token` event (the timestamp the backend derives TTFT from)
|
|
358
|
+
* and, per the GenAI conventions, the derived
|
|
359
|
+
* `gen_ai.response.time_to_first_chunk` (seconds since span creation) plus
|
|
360
|
+
* `gen_ai.request.stream = true`: a first chunk arriving is what tells the
|
|
361
|
+
* SDK the request streamed. Idempotent: only the first call records, so a
|
|
362
|
+
* streaming loop can call this unconditionally on every chunk without
|
|
363
|
+
* inflating the span. A no-op after the span has ended.
|
|
348
364
|
*/
|
|
349
365
|
recordFirstToken(): this;
|
|
350
366
|
}
|
|
@@ -448,6 +464,6 @@ declare function withSession<T>(sessionId: string, fn: (sessionId: string) => T)
|
|
|
448
464
|
*/
|
|
449
465
|
declare function withUser<T>(userId: string, fn: (userId: string) => T): T;
|
|
450
466
|
|
|
451
|
-
declare const VERSION = "0.
|
|
467
|
+
declare const VERSION = "0.8.0";
|
|
452
468
|
|
|
453
469
|
export { Generation, type GenerationBody, type GenerationOptions, type InitOptions, type Mask, Observation, type ObserveOptions, RiusClient, type RiusOptions, type SpanBody, SpanKind, type SpanOptions, VERSION, type WorkspaceExporterFactory, getTracer, init, observe, registerWorkspace, startAsCurrentGeneration, startAsCurrentSpan, startGeneration, startSpan, withSession, withUser, withWorkspace };
|
package/dist/index.js
CHANGED
|
@@ -354,13 +354,18 @@ var init_heartbeat = __esm({
|
|
|
354
354
|
});
|
|
355
355
|
|
|
356
356
|
// src/semconv.ts
|
|
357
|
-
|
|
357
|
+
import { SpanKind as OtelSpanKind } from "@opentelemetry/api";
|
|
358
|
+
function otelSpanKind(kind) {
|
|
359
|
+
return OTEL_KIND_BY_KIND[kind];
|
|
360
|
+
}
|
|
361
|
+
function kindAttributes(kind, name) {
|
|
358
362
|
const attributes = { [OPENINFERENCE_SPAN_KIND]: kind };
|
|
359
363
|
const operation = OPERATION_BY_KIND[kind];
|
|
360
364
|
if (operation !== void 0) attributes[GEN_AI_OPERATION_NAME] = operation;
|
|
365
|
+
if (kind === "TOOL" /* TOOL */ && name !== void 0) attributes[GEN_AI_TOOL_NAME] = name;
|
|
361
366
|
return attributes;
|
|
362
367
|
}
|
|
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_METHOD_NAME, MCP_METHOD_TOOLS_CALL, MCP_PROTOCOL_VERSION, ERROR_TYPE, ERROR_TYPE_TOOL_ERROR, 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;
|
|
368
|
+
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_REQUEST_STREAM, GEN_AI_RESPONSE_TIME_TO_FIRST_CHUNK, 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_METHOD_NAME, MCP_METHOD_TOOLS_CALL, MCP_PROTOCOL_VERSION, ERROR_TYPE, ERROR_TYPE_TOOL_ERROR, MCP_RESULT_TYPE, GEN_AI_FIRST_TOKEN_EVENT, SpanKind, OPERATION_BY_KIND, OTEL_KIND_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;
|
|
364
369
|
var init_semconv = __esm({
|
|
365
370
|
"src/semconv.ts"() {
|
|
366
371
|
"use strict";
|
|
@@ -378,6 +383,8 @@ var init_semconv = __esm({
|
|
|
378
383
|
GEN_AI_REQUEST_MODEL = "gen_ai.request.model";
|
|
379
384
|
GEN_AI_REQUEST_REASONING_LEVEL = "gen_ai.request.reasoning.level";
|
|
380
385
|
GEN_AI_RESPONSE_MODEL = "gen_ai.response.model";
|
|
386
|
+
GEN_AI_REQUEST_STREAM = "gen_ai.request.stream";
|
|
387
|
+
GEN_AI_RESPONSE_TIME_TO_FIRST_CHUNK = "gen_ai.response.time_to_first_chunk";
|
|
381
388
|
GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens";
|
|
382
389
|
GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens";
|
|
383
390
|
GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens";
|
|
@@ -411,6 +418,14 @@ var init_semconv = __esm({
|
|
|
411
418
|
["EMBEDDING" /* EMBEDDING */]: "embeddings",
|
|
412
419
|
["AGENT" /* AGENT */]: "invoke_agent"
|
|
413
420
|
};
|
|
421
|
+
OTEL_KIND_BY_KIND = {
|
|
422
|
+
["LLM" /* LLM */]: OtelSpanKind.CLIENT,
|
|
423
|
+
["EMBEDDING" /* EMBEDDING */]: OtelSpanKind.CLIENT,
|
|
424
|
+
["RETRIEVER" /* RETRIEVER */]: OtelSpanKind.CLIENT,
|
|
425
|
+
["TOOL" /* TOOL */]: OtelSpanKind.INTERNAL,
|
|
426
|
+
["AGENT" /* AGENT */]: OtelSpanKind.INTERNAL,
|
|
427
|
+
["CHAIN" /* CHAIN */]: OtelSpanKind.INTERNAL
|
|
428
|
+
};
|
|
414
429
|
CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
415
430
|
INPUT_VALUE,
|
|
416
431
|
OUTPUT_VALUE,
|
|
@@ -555,6 +570,9 @@ function toAttributeValue(value) {
|
|
|
555
570
|
return "[unserializable]";
|
|
556
571
|
}
|
|
557
572
|
}
|
|
573
|
+
function errorType(error) {
|
|
574
|
+
return error instanceof Error ? error.name : typeof error;
|
|
575
|
+
}
|
|
558
576
|
var MAX_ATTR_CHARS, TRUNCATION_MARKER;
|
|
559
577
|
var init_serde = __esm({
|
|
560
578
|
"src/serde.ts"() {
|
|
@@ -598,15 +616,18 @@ function configure(observation, options) {
|
|
|
598
616
|
if (options.input !== void 0) observation.setInput(options.input);
|
|
599
617
|
return observation;
|
|
600
618
|
}
|
|
601
|
-
function creationAttributes(options) {
|
|
602
|
-
const attributes = {
|
|
619
|
+
function creationAttributes(name, options) {
|
|
620
|
+
const attributes = {
|
|
621
|
+
...kindAttributes(options.kind ?? "CHAIN" /* CHAIN */, name),
|
|
622
|
+
...options.attributes
|
|
623
|
+
};
|
|
603
624
|
if (options.userId !== void 0) attributes[USER_ID] = options.userId;
|
|
604
625
|
return attributes;
|
|
605
626
|
}
|
|
606
627
|
function startSpan(name, options = {}) {
|
|
607
628
|
const span = getTracer().startSpan(name, {
|
|
608
|
-
kind: options.otelKind,
|
|
609
|
-
attributes: creationAttributes(options)
|
|
629
|
+
kind: options.otelKind ?? otelSpanKind(options.kind ?? "CHAIN" /* CHAIN */),
|
|
630
|
+
attributes: creationAttributes(name, options)
|
|
610
631
|
});
|
|
611
632
|
return configure(new Observation(span), options);
|
|
612
633
|
}
|
|
@@ -614,11 +635,11 @@ function startAsCurrentSpan(name, optionsOrFn, maybeFn) {
|
|
|
614
635
|
const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
|
|
615
636
|
return runActive(
|
|
616
637
|
name,
|
|
617
|
-
creationAttributes(options),
|
|
638
|
+
creationAttributes(name, options),
|
|
618
639
|
options.userId,
|
|
619
640
|
(span) => configure(new Observation(span), options),
|
|
620
641
|
fn,
|
|
621
|
-
options.otelKind
|
|
642
|
+
options.otelKind ?? otelSpanKind(options.kind ?? "CHAIN" /* CHAIN */)
|
|
622
643
|
);
|
|
623
644
|
}
|
|
624
645
|
function runActive(name, attributes, userId, makeHandle, fn, otelKind) {
|
|
@@ -683,9 +704,15 @@ var init_spans = __esm({
|
|
|
683
704
|
return this;
|
|
684
705
|
}
|
|
685
706
|
/**
|
|
686
|
-
* Record an error on the span
|
|
687
|
-
* `startAsCurrent*` helpers do on a thrown error, exposed
|
|
688
|
-
* `start*` path does not have to reach through `.span` to
|
|
707
|
+
* Record an error on the span, set ERROR status and `error.type`. This is
|
|
708
|
+
* exactly what the `startAsCurrent*` helpers do on a thrown error, exposed
|
|
709
|
+
* so the manual `start*` path does not have to reach through `.span` to
|
|
710
|
+
* match it.
|
|
711
|
+
*
|
|
712
|
+
* `error.type` is Conditionally Required by the GenAI conventions on every
|
|
713
|
+
* span that ends in an error, and every helper's throw path funnels through
|
|
714
|
+
* here, so this is the one place that sets it. The error's name only, never
|
|
715
|
+
* the message: it must stay low-cardinality and free of echoed content.
|
|
689
716
|
*
|
|
690
717
|
* Accepts `unknown` because that is what a `catch` binding is; a non-Error
|
|
691
718
|
* throwable is wrapped so `recordException` still gets a real Error.
|
|
@@ -694,6 +721,7 @@ var init_spans = __esm({
|
|
|
694
721
|
const wrapped = error instanceof Error ? error : new Error(String(error));
|
|
695
722
|
this.span.recordException(wrapped);
|
|
696
723
|
this.span.setStatus({ code: SpanStatusCode.ERROR, message: wrapped.message });
|
|
724
|
+
this.span.setAttribute(ERROR_TYPE, errorType(error));
|
|
697
725
|
return this;
|
|
698
726
|
}
|
|
699
727
|
end() {
|
|
@@ -714,7 +742,7 @@ var instrumentationMcp_exports = {};
|
|
|
714
742
|
__export(instrumentationMcp_exports, {
|
|
715
743
|
instrumentMcpClient: () => instrumentMcpClient
|
|
716
744
|
});
|
|
717
|
-
import { SpanKind as
|
|
745
|
+
import { SpanKind as OtelSpanKind2, SpanStatusCode as SpanStatusCode2 } from "@opentelemetry/api";
|
|
718
746
|
function serializeResult(result) {
|
|
719
747
|
const record = asRecord(result);
|
|
720
748
|
if (record === void 0) return toAttributeValue(result);
|
|
@@ -748,9 +776,6 @@ function recordResult(observation, result) {
|
|
|
748
776
|
observation.setAttribute(ERROR_TYPE, ERROR_TYPE_TOOL_ERROR);
|
|
749
777
|
}
|
|
750
778
|
}
|
|
751
|
-
function errorType(error) {
|
|
752
|
-
return error instanceof Error ? error.name : typeof error;
|
|
753
|
-
}
|
|
754
779
|
function negotiatedProtocolVersion(client) {
|
|
755
780
|
const version = asRecord(asRecord(client)?.transport)?.protocolVersion;
|
|
756
781
|
return typeof version === "string" ? version : void 0;
|
|
@@ -780,18 +805,12 @@ function instrumentMcpClient(ClientClass) {
|
|
|
780
805
|
// The OTel SpanKind FIELD (orthogonal to our taxonomy attribute above):
|
|
781
806
|
// a remote tool call is CLIENT under both the MCP and the GenAI
|
|
782
807
|
// execute-tool conventions.
|
|
783
|
-
otelKind:
|
|
808
|
+
otelKind: OtelSpanKind2.CLIENT,
|
|
784
809
|
input: params.arguments,
|
|
785
810
|
attributes: callAttributes(params.name, negotiatedProtocolVersion(this))
|
|
786
811
|
},
|
|
787
812
|
async (observation) => {
|
|
788
|
-
|
|
789
|
-
try {
|
|
790
|
-
result = await original.call(this, params, ...rest);
|
|
791
|
-
} catch (error) {
|
|
792
|
-
observation.setAttribute(ERROR_TYPE, errorType(error));
|
|
793
|
-
throw error;
|
|
794
|
-
}
|
|
813
|
+
const result = await original.call(this, params, ...rest);
|
|
795
814
|
recordResult(observation, result);
|
|
796
815
|
return result;
|
|
797
816
|
}
|
|
@@ -1694,6 +1713,12 @@ var Generation = class extends Observation {
|
|
|
1694
1713
|
}
|
|
1695
1714
|
provider;
|
|
1696
1715
|
firstTokenRecorded = false;
|
|
1716
|
+
/**
|
|
1717
|
+
* Monotonic clock at construction, which is span creation for both
|
|
1718
|
+
* `startGeneration` and the scoped form. The API `Span` exposes no start
|
|
1719
|
+
* time, so this is what `recordFirstToken` measures the first chunk against.
|
|
1720
|
+
*/
|
|
1721
|
+
startedAt = performance.now();
|
|
1697
1722
|
/**
|
|
1698
1723
|
* Record the request messages (`gen_ai.input.messages`), normalised to the
|
|
1699
1724
|
* GenAI `{role, parts}` shape like the Python SDK does: bare strings, OpenAI
|
|
@@ -1768,17 +1793,24 @@ var Generation = class extends Observation {
|
|
|
1768
1793
|
return this;
|
|
1769
1794
|
}
|
|
1770
1795
|
/**
|
|
1771
|
-
*
|
|
1772
|
-
*
|
|
1773
|
-
*
|
|
1774
|
-
*
|
|
1796
|
+
* Mark the arrival of the first streamed token. Records the
|
|
1797
|
+
* `gen_ai.first_token` event (the timestamp the backend derives TTFT from)
|
|
1798
|
+
* and, per the GenAI conventions, the derived
|
|
1799
|
+
* `gen_ai.response.time_to_first_chunk` (seconds since span creation) plus
|
|
1800
|
+
* `gen_ai.request.stream = true`: a first chunk arriving is what tells the
|
|
1801
|
+
* SDK the request streamed. Idempotent: only the first call records, so a
|
|
1802
|
+
* streaming loop can call this unconditionally on every chunk without
|
|
1803
|
+
* inflating the span. A no-op after the span has ended.
|
|
1775
1804
|
*/
|
|
1776
1805
|
recordFirstToken() {
|
|
1777
1806
|
if (this.firstTokenRecorded || !this.span.isRecording()) {
|
|
1778
1807
|
return this;
|
|
1779
1808
|
}
|
|
1809
|
+
const elapsedSeconds = Math.max(performance.now() - this.startedAt, 0) / 1e3;
|
|
1780
1810
|
this.span.addEvent(GEN_AI_FIRST_TOKEN_EVENT);
|
|
1781
1811
|
this.firstTokenRecorded = true;
|
|
1812
|
+
this.span.setAttribute(GEN_AI_REQUEST_STREAM, true);
|
|
1813
|
+
this.span.setAttribute(GEN_AI_RESPONSE_TIME_TO_FIRST_CHUNK, elapsedSeconds);
|
|
1782
1814
|
return this;
|
|
1783
1815
|
}
|
|
1784
1816
|
};
|
|
@@ -1802,7 +1834,10 @@ function configure2(generation, options) {
|
|
|
1802
1834
|
return generation;
|
|
1803
1835
|
}
|
|
1804
1836
|
function startGeneration(name, options = {}) {
|
|
1805
|
-
const span = getTracer().startSpan(name, {
|
|
1837
|
+
const span = getTracer().startSpan(name, {
|
|
1838
|
+
kind: otelSpanKind("LLM" /* LLM */),
|
|
1839
|
+
attributes: attributesFor(options)
|
|
1840
|
+
});
|
|
1806
1841
|
return configure2(new Generation(span, options.provider), options);
|
|
1807
1842
|
}
|
|
1808
1843
|
function startAsCurrentGeneration(name, optionsOrFn, maybeFn) {
|
|
@@ -1812,7 +1847,8 @@ function startAsCurrentGeneration(name, optionsOrFn, maybeFn) {
|
|
|
1812
1847
|
attributesFor(options),
|
|
1813
1848
|
options.userId,
|
|
1814
1849
|
(span) => configure2(new Generation(span, options.provider), options),
|
|
1815
|
-
fn
|
|
1850
|
+
fn,
|
|
1851
|
+
otelSpanKind("LLM" /* LLM */)
|
|
1816
1852
|
);
|
|
1817
1853
|
}
|
|
1818
1854
|
|
|
@@ -1845,7 +1881,7 @@ init_session();
|
|
|
1845
1881
|
init_user();
|
|
1846
1882
|
init_spans();
|
|
1847
1883
|
init_workspace();
|
|
1848
|
-
var VERSION = "0.
|
|
1884
|
+
var VERSION = "0.8.0";
|
|
1849
1885
|
export {
|
|
1850
1886
|
Generation,
|
|
1851
1887
|
Observation,
|