@glassflow-ai/rius 0.3.1 → 0.5.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/dist/index.cjs +202 -13
- package/dist/index.d.cts +108 -2
- package/dist/index.d.ts +108 -2
- package/dist/index.js +198 -11
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -356,7 +356,7 @@ function kindAttributes(kind) {
|
|
|
356
356
|
if (operation !== void 0) attributes[GEN_AI_OPERATION_NAME] = operation;
|
|
357
357
|
return attributes;
|
|
358
358
|
}
|
|
359
|
-
var TRACER_NAME, SERVICE_INSTANCE_ID, OPENINFERENCE_SPAN_KIND, INPUT_VALUE, OUTPUT_VALUE, SESSION_ID, 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_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;
|
|
359
|
+
var TRACER_NAME, SERVICE_INSTANCE_ID, OPENINFERENCE_SPAN_KIND, INPUT_VALUE, OUTPUT_VALUE, SESSION_ID, WORKSPACE_ROUTE, GEN_AI_OPERATION_NAME, GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_MODEL, GEN_AI_REQUEST_REASONING_LEVEL, GEN_AI_RESPONSE_MODEL, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS, GEN_AI_USAGE_REASONING_OUTPUT_TOKENS, GEN_AI_INPUT_MESSAGES, GEN_AI_OUTPUT_MESSAGES, GEN_AI_RESPONSE_FINISH_REASONS, GEN_AI_TOOL_NAME, GEN_AI_REQUEST_PREFIX, MCP_RESULT_TYPE, GEN_AI_FIRST_TOKEN_EVENT, SpanKind, OPERATION_BY_KIND, CONTENT_ATTRIBUTES, CONTENT_ATTRIBUTE_PREFIXES, CONTENT_ATTRIBUTE_SUFFIXES, GLASSFLOW_SPAN_PENDING, PENDING_IDENTITY_ATTRIBUTES, PENDING_IDENTITY_PREFIXES;
|
|
360
360
|
var init_semconv = __esm({
|
|
361
361
|
"src/semconv.ts"() {
|
|
362
362
|
"use strict";
|
|
@@ -367,12 +367,17 @@ var init_semconv = __esm({
|
|
|
367
367
|
INPUT_VALUE = "input.value";
|
|
368
368
|
OUTPUT_VALUE = "output.value";
|
|
369
369
|
SESSION_ID = "session.id";
|
|
370
|
+
WORKSPACE_ROUTE = "rius.workspace";
|
|
370
371
|
GEN_AI_OPERATION_NAME = "gen_ai.operation.name";
|
|
371
372
|
GEN_AI_PROVIDER_NAME = "gen_ai.provider.name";
|
|
372
373
|
GEN_AI_REQUEST_MODEL = "gen_ai.request.model";
|
|
374
|
+
GEN_AI_REQUEST_REASONING_LEVEL = "gen_ai.request.reasoning.level";
|
|
373
375
|
GEN_AI_RESPONSE_MODEL = "gen_ai.response.model";
|
|
374
376
|
GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens";
|
|
375
377
|
GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens";
|
|
378
|
+
GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens";
|
|
379
|
+
GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS = "gen_ai.usage.cache_write.input_tokens";
|
|
380
|
+
GEN_AI_USAGE_REASONING_OUTPUT_TOKENS = "gen_ai.usage.reasoning.output_tokens";
|
|
376
381
|
GEN_AI_INPUT_MESSAGES = "gen_ai.input.messages";
|
|
377
382
|
GEN_AI_OUTPUT_MESSAGES = "gen_ai.output.messages";
|
|
378
383
|
GEN_AI_RESPONSE_FINISH_REASONS = "gen_ai.response.finish_reasons";
|
|
@@ -400,6 +405,12 @@ var init_semconv = __esm({
|
|
|
400
405
|
OUTPUT_VALUE,
|
|
401
406
|
GEN_AI_INPUT_MESSAGES,
|
|
402
407
|
GEN_AI_OUTPUT_MESSAGES,
|
|
408
|
+
// Sensitive per the GenAI conventions (semconv-genai#431): tool definitions
|
|
409
|
+
// routinely embed proprietary prompt engineering, and sometimes credentials
|
|
410
|
+
// or internal URLs in parameter defaults. gen_ai.tool.name stays: it is
|
|
411
|
+
// identity, not content.
|
|
412
|
+
"gen_ai.tool.description",
|
|
413
|
+
"gen_ai.tool.definitions",
|
|
403
414
|
"gen_ai.prompt",
|
|
404
415
|
"gen_ai.completion",
|
|
405
416
|
"llm.input_messages",
|
|
@@ -435,7 +446,10 @@ var init_semconv = __esm({
|
|
|
435
446
|
GEN_AI_TOOL_NAME,
|
|
436
447
|
// Identity, not content: a pending span must be groupable into its
|
|
437
448
|
// session while still running, that is the live view's whole point.
|
|
438
|
-
SESSION_ID
|
|
449
|
+
SESSION_ID,
|
|
450
|
+
// Routing, not content: a crashed run's snapshot must land in the same
|
|
451
|
+
// workspace its final span would have. Stripped at export either way.
|
|
452
|
+
WORKSPACE_ROUTE
|
|
439
453
|
]);
|
|
440
454
|
PENDING_IDENTITY_PREFIXES = [GEN_AI_REQUEST_PREFIX];
|
|
441
455
|
}
|
|
@@ -1010,6 +1024,128 @@ var init_session = __esm({
|
|
|
1010
1024
|
}
|
|
1011
1025
|
});
|
|
1012
1026
|
|
|
1027
|
+
// src/workspace.ts
|
|
1028
|
+
function withWorkspace(alias, fn) {
|
|
1029
|
+
if (!alias) throw new Error("workspace alias must be a non-empty string");
|
|
1030
|
+
return import_api6.context.with(import_api6.context.active().setValue(WORKSPACE_KEY, alias), () => fn(alias));
|
|
1031
|
+
}
|
|
1032
|
+
function setGlobalRouting(routing) {
|
|
1033
|
+
globalRouting = routing;
|
|
1034
|
+
}
|
|
1035
|
+
function registerWorkspace(alias, apiKey) {
|
|
1036
|
+
if (globalRouting === void 0) {
|
|
1037
|
+
throw new Error(
|
|
1038
|
+
"[rius] workspace routing is not enabled: pass workspaces (an empty object is fine) to init() to opt in before registering destinations"
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
globalRouting.register(alias, apiKey);
|
|
1042
|
+
}
|
|
1043
|
+
var import_api6, WORKSPACE_KEY, WorkspaceSpanProcessor, RoutingSpanExporter, globalRouting;
|
|
1044
|
+
var init_workspace = __esm({
|
|
1045
|
+
"src/workspace.ts"() {
|
|
1046
|
+
"use strict";
|
|
1047
|
+
init_cjs_shims();
|
|
1048
|
+
import_api6 = require("@opentelemetry/api");
|
|
1049
|
+
init_esm();
|
|
1050
|
+
init_semconv();
|
|
1051
|
+
WORKSPACE_KEY = (0, import_api6.createContextKey)("rius-workspace-alias");
|
|
1052
|
+
WorkspaceSpanProcessor = class {
|
|
1053
|
+
onStart(span, parentContext) {
|
|
1054
|
+
const alias = parentContext.getValue(WORKSPACE_KEY);
|
|
1055
|
+
if (typeof alias !== "string") return;
|
|
1056
|
+
const parent = import_api6.trace.getSpan(parentContext);
|
|
1057
|
+
const parentAlias = parent?.attributes?.[WORKSPACE_ROUTE];
|
|
1058
|
+
if (parentAlias !== void 0 && parentAlias !== alias) {
|
|
1059
|
+
console.warn(
|
|
1060
|
+
`[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.`
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
span.setAttribute(WORKSPACE_ROUTE, alias);
|
|
1064
|
+
}
|
|
1065
|
+
onEnd(_span) {
|
|
1066
|
+
}
|
|
1067
|
+
async forceFlush() {
|
|
1068
|
+
}
|
|
1069
|
+
async shutdown() {
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
RoutingSpanExporter = class {
|
|
1073
|
+
constructor(defaultExporter, factory, routes = {}) {
|
|
1074
|
+
this.defaultExporter = defaultExporter;
|
|
1075
|
+
this.factory = factory;
|
|
1076
|
+
this.routes = new Map(Object.entries(routes));
|
|
1077
|
+
}
|
|
1078
|
+
defaultExporter;
|
|
1079
|
+
factory;
|
|
1080
|
+
routes;
|
|
1081
|
+
exporters = /* @__PURE__ */ new Map();
|
|
1082
|
+
warnedAliases = /* @__PURE__ */ new Set();
|
|
1083
|
+
/** Add or replace a route. Replacing supports key rotation. */
|
|
1084
|
+
register(alias, apiKey) {
|
|
1085
|
+
if (!alias || !apiKey) {
|
|
1086
|
+
throw new Error("workspace alias and apiKey must be non-empty strings");
|
|
1087
|
+
}
|
|
1088
|
+
this.routes.set(alias, apiKey);
|
|
1089
|
+
this.warnedAliases.delete(alias);
|
|
1090
|
+
}
|
|
1091
|
+
export(spans, resultCallback) {
|
|
1092
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1093
|
+
for (const span of spans) {
|
|
1094
|
+
const attributes = span.attributes;
|
|
1095
|
+
const raw = attributes[WORKSPACE_ROUTE];
|
|
1096
|
+
let destination = this.defaultExporter;
|
|
1097
|
+
if (raw !== void 0) {
|
|
1098
|
+
delete attributes[WORKSPACE_ROUTE];
|
|
1099
|
+
destination = this.resolve(String(raw));
|
|
1100
|
+
}
|
|
1101
|
+
const group = groups.get(destination);
|
|
1102
|
+
if (group === void 0) groups.set(destination, [span]);
|
|
1103
|
+
else group.push(span);
|
|
1104
|
+
}
|
|
1105
|
+
let pending = groups.size;
|
|
1106
|
+
if (pending === 0) {
|
|
1107
|
+
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
let failed;
|
|
1111
|
+
for (const [exporter, group] of groups) {
|
|
1112
|
+
exporter.export(group, (result) => {
|
|
1113
|
+
if (result.code !== ExportResultCode.SUCCESS) failed = result;
|
|
1114
|
+
pending -= 1;
|
|
1115
|
+
if (pending === 0) resultCallback(failed ?? { code: ExportResultCode.SUCCESS });
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
resolve(alias) {
|
|
1120
|
+
const apiKey = this.routes.get(alias);
|
|
1121
|
+
if (apiKey === void 0) {
|
|
1122
|
+
if (!this.warnedAliases.has(alias)) {
|
|
1123
|
+
this.warnedAliases.add(alias);
|
|
1124
|
+
console.warn(
|
|
1125
|
+
`[rius] no workspace registered for alias "${alias}"; its spans go to the default destination. Register it with registerWorkspace("${alias}", apiKey) or in init({ workspaces }).`
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
return this.defaultExporter;
|
|
1129
|
+
}
|
|
1130
|
+
let exporter = this.exporters.get(apiKey);
|
|
1131
|
+
if (exporter === void 0) {
|
|
1132
|
+
exporter = this.factory(apiKey);
|
|
1133
|
+
this.exporters.set(apiKey, exporter);
|
|
1134
|
+
}
|
|
1135
|
+
return exporter;
|
|
1136
|
+
}
|
|
1137
|
+
async forceFlush() {
|
|
1138
|
+
await this.defaultExporter.forceFlush?.();
|
|
1139
|
+
await Promise.all([...this.exporters.values()].map((e) => e.forceFlush?.()));
|
|
1140
|
+
}
|
|
1141
|
+
async shutdown() {
|
|
1142
|
+
await this.defaultExporter.shutdown();
|
|
1143
|
+
await Promise.all([...this.exporters.values()].map((e) => e.shutdown()));
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1013
1149
|
// src/client.ts
|
|
1014
1150
|
function init(options = {}) {
|
|
1015
1151
|
if (globalClient !== void 0) {
|
|
@@ -1022,11 +1158,20 @@ function init(options = {}) {
|
|
|
1022
1158
|
const processors = new DelegatingSpanProcessor();
|
|
1023
1159
|
const authHeaders = config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {};
|
|
1024
1160
|
let health;
|
|
1161
|
+
let routing;
|
|
1025
1162
|
if (!config.disabled) {
|
|
1026
|
-
|
|
1163
|
+
let base = options.spanExporter ?? new import_exporter_trace_otlp_proto.OTLPTraceExporter({
|
|
1027
1164
|
url: `${config.endpoint}/v1/traces`,
|
|
1028
1165
|
headers: config.apiKey ? authHeaders : void 0
|
|
1029
1166
|
});
|
|
1167
|
+
if (options.workspaces !== void 0) {
|
|
1168
|
+
const factory = options.workspaceExporterFactory ?? ((apiKey) => new import_exporter_trace_otlp_proto.OTLPTraceExporter({
|
|
1169
|
+
url: `${config.endpoint}/v1/traces`,
|
|
1170
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1171
|
+
}));
|
|
1172
|
+
routing = new RoutingSpanExporter(base, factory, options.workspaces);
|
|
1173
|
+
base = routing;
|
|
1174
|
+
}
|
|
1030
1175
|
health = new ExportOutcomeExporter(base);
|
|
1031
1176
|
const exporter = !config.captureContent || config.mask !== void 0 ? new MaskingSpanExporter(health, {
|
|
1032
1177
|
captureContent: config.captureContent,
|
|
@@ -1034,6 +1179,9 @@ function init(options = {}) {
|
|
|
1034
1179
|
}) : health;
|
|
1035
1180
|
const batch = new import_sdk_trace_base.BatchSpanProcessor(exporter);
|
|
1036
1181
|
processors.add(new SessionSpanProcessor(config.sessionId));
|
|
1182
|
+
if (routing !== void 0) {
|
|
1183
|
+
processors.add(new WorkspaceSpanProcessor());
|
|
1184
|
+
}
|
|
1037
1185
|
if (config.partialSpans) {
|
|
1038
1186
|
processors.add(new PendingSpanProcessor(batch, { delayMs: config.partialSpansDelayMs }));
|
|
1039
1187
|
}
|
|
@@ -1076,18 +1224,19 @@ function init(options = {}) {
|
|
|
1076
1224
|
heartbeat = { sender, beforeExitHandler };
|
|
1077
1225
|
}
|
|
1078
1226
|
globalClient = createClient({ provider, processors, health, ready, heartbeat });
|
|
1227
|
+
setGlobalRouting(routing);
|
|
1079
1228
|
return globalClient;
|
|
1080
1229
|
}
|
|
1081
1230
|
function getTracer() {
|
|
1082
|
-
return
|
|
1231
|
+
return import_api7.trace.getTracer(TRACER_NAME);
|
|
1083
1232
|
}
|
|
1084
|
-
var import_node_crypto2,
|
|
1233
|
+
var import_node_crypto2, import_api7, import_exporter_trace_otlp_proto, import_resources, import_sdk_trace_base, import_sdk_trace_node, import_semantic_conventions, sinks, heartbeats, createClient, RiusClient, globalClient;
|
|
1085
1234
|
var init_client = __esm({
|
|
1086
1235
|
"src/client.ts"() {
|
|
1087
1236
|
"use strict";
|
|
1088
1237
|
init_cjs_shims();
|
|
1089
1238
|
import_node_crypto2 = require("crypto");
|
|
1090
|
-
|
|
1239
|
+
import_api7 = require("@opentelemetry/api");
|
|
1091
1240
|
import_exporter_trace_otlp_proto = require("@opentelemetry/exporter-trace-otlp-proto");
|
|
1092
1241
|
import_resources = require("@opentelemetry/resources");
|
|
1093
1242
|
import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
@@ -1102,6 +1251,7 @@ var init_client = __esm({
|
|
|
1102
1251
|
init_pending();
|
|
1103
1252
|
init_semconv();
|
|
1104
1253
|
init_session();
|
|
1254
|
+
init_workspace();
|
|
1105
1255
|
sinks = /* @__PURE__ */ new WeakMap();
|
|
1106
1256
|
heartbeats = /* @__PURE__ */ new WeakMap();
|
|
1107
1257
|
RiusClient = class _RiusClient {
|
|
@@ -1145,7 +1295,8 @@ var init_client = __esm({
|
|
|
1145
1295
|
} finally {
|
|
1146
1296
|
if (globalClient === this) {
|
|
1147
1297
|
globalClient = void 0;
|
|
1148
|
-
|
|
1298
|
+
setGlobalRouting(void 0);
|
|
1299
|
+
import_api7.trace.disable();
|
|
1149
1300
|
}
|
|
1150
1301
|
}
|
|
1151
1302
|
}
|
|
@@ -1164,11 +1315,13 @@ __export(index_exports, {
|
|
|
1164
1315
|
getTracer: () => getTracer,
|
|
1165
1316
|
init: () => init,
|
|
1166
1317
|
observe: () => observe,
|
|
1318
|
+
registerWorkspace: () => registerWorkspace,
|
|
1167
1319
|
startAsCurrentGeneration: () => startAsCurrentGeneration,
|
|
1168
1320
|
startAsCurrentSpan: () => startAsCurrentSpan,
|
|
1169
1321
|
startGeneration: () => startGeneration,
|
|
1170
1322
|
startSpan: () => startSpan,
|
|
1171
|
-
withSession: () => withSession
|
|
1323
|
+
withSession: () => withSession,
|
|
1324
|
+
withWorkspace: () => withWorkspace
|
|
1172
1325
|
});
|
|
1173
1326
|
module.exports = __toCommonJS(index_exports);
|
|
1174
1327
|
init_cjs_shims();
|
|
@@ -1181,6 +1334,15 @@ init_semconv();
|
|
|
1181
1334
|
init_serde();
|
|
1182
1335
|
init_spans();
|
|
1183
1336
|
var Generation = class extends Observation {
|
|
1337
|
+
/**
|
|
1338
|
+
* The provider passed at creation; drives the Anthropic input-token summing
|
|
1339
|
+
* in {@link setUsage}. A bare `new Generation(span)` has none and never sums.
|
|
1340
|
+
*/
|
|
1341
|
+
constructor(span, provider) {
|
|
1342
|
+
super(span);
|
|
1343
|
+
this.provider = provider;
|
|
1344
|
+
}
|
|
1345
|
+
provider;
|
|
1184
1346
|
firstTokenRecorded = false;
|
|
1185
1347
|
setInput(value) {
|
|
1186
1348
|
this.span.setAttribute(GEN_AI_INPUT_MESSAGES, toAttributeValue(value));
|
|
@@ -1194,13 +1356,34 @@ var Generation = class extends Observation {
|
|
|
1194
1356
|
this.span.setAttribute(GEN_AI_RESPONSE_MODEL, model);
|
|
1195
1357
|
return this;
|
|
1196
1358
|
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Token usage (`gen_ai.usage.*`). Pass provider-reported values as-is;
|
|
1361
|
+
* never pre-add anything. Per the GenAI conventions, `inputTokens` is the
|
|
1362
|
+
* total including cached tokens (the cache counts are subsets of it).
|
|
1363
|
+
* Anthropic's API reports `input_tokens` excluding the cache counts, and
|
|
1364
|
+
* the conventions require the instrumentation to do the summing, so when
|
|
1365
|
+
* the generation's provider is `"anthropic"` the emitted total is
|
|
1366
|
+
* `inputTokens` plus both cache counts. Every other provider is recorded
|
|
1367
|
+
* verbatim.
|
|
1368
|
+
*/
|
|
1197
1369
|
setUsage(usage) {
|
|
1198
1370
|
if (usage.inputTokens !== void 0) {
|
|
1199
|
-
this.
|
|
1371
|
+
const sums = this.provider?.toLowerCase() === "anthropic";
|
|
1372
|
+
const total = sums ? usage.inputTokens + (usage.cacheReadInputTokens ?? 0) + (usage.cacheWriteInputTokens ?? 0) : usage.inputTokens;
|
|
1373
|
+
this.span.setAttribute(GEN_AI_USAGE_INPUT_TOKENS, total);
|
|
1200
1374
|
}
|
|
1201
1375
|
if (usage.outputTokens !== void 0) {
|
|
1202
1376
|
this.span.setAttribute(GEN_AI_USAGE_OUTPUT_TOKENS, usage.outputTokens);
|
|
1203
1377
|
}
|
|
1378
|
+
if (usage.cacheReadInputTokens !== void 0) {
|
|
1379
|
+
this.span.setAttribute(GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, usage.cacheReadInputTokens);
|
|
1380
|
+
}
|
|
1381
|
+
if (usage.cacheWriteInputTokens !== void 0) {
|
|
1382
|
+
this.span.setAttribute(GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS, usage.cacheWriteInputTokens);
|
|
1383
|
+
}
|
|
1384
|
+
if (usage.reasoningOutputTokens !== void 0) {
|
|
1385
|
+
this.span.setAttribute(GEN_AI_USAGE_REASONING_OUTPUT_TOKENS, usage.reasoningOutputTokens);
|
|
1386
|
+
}
|
|
1204
1387
|
return this;
|
|
1205
1388
|
}
|
|
1206
1389
|
/**
|
|
@@ -1240,17 +1423,20 @@ function configure2(generation, options) {
|
|
|
1240
1423
|
for (const [key, value] of Object.entries(options.modelParameters ?? {})) {
|
|
1241
1424
|
generation.setAttribute(`${GEN_AI_REQUEST_PREFIX}${key}`, value);
|
|
1242
1425
|
}
|
|
1426
|
+
if (options.reasoningLevel !== void 0) {
|
|
1427
|
+
generation.setAttribute(GEN_AI_REQUEST_REASONING_LEVEL, options.reasoningLevel);
|
|
1428
|
+
}
|
|
1243
1429
|
if (options.input !== void 0) generation.setInput(options.input);
|
|
1244
1430
|
return generation;
|
|
1245
1431
|
}
|
|
1246
1432
|
function startGeneration(name, options = {}) {
|
|
1247
1433
|
const span = getTracer().startSpan(name, { attributes: attributesFor(options) });
|
|
1248
|
-
return configure2(new Generation(span), options);
|
|
1434
|
+
return configure2(new Generation(span, options.provider), options);
|
|
1249
1435
|
}
|
|
1250
1436
|
function startAsCurrentGeneration(name, optionsOrFn, maybeFn) {
|
|
1251
1437
|
const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
|
|
1252
1438
|
return getTracer().startActiveSpan(name, { attributes: attributesFor(options) }, async (span) => {
|
|
1253
|
-
const generation = configure2(new Generation(span), options);
|
|
1439
|
+
const generation = configure2(new Generation(span, options.provider), options);
|
|
1254
1440
|
try {
|
|
1255
1441
|
return await fn(generation);
|
|
1256
1442
|
} catch (error) {
|
|
@@ -1286,7 +1472,8 @@ function observe(fn, options = {}) {
|
|
|
1286
1472
|
init_semconv();
|
|
1287
1473
|
init_session();
|
|
1288
1474
|
init_spans();
|
|
1289
|
-
|
|
1475
|
+
init_workspace();
|
|
1476
|
+
var VERSION = "0.5.0";
|
|
1290
1477
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1291
1478
|
0 && (module.exports = {
|
|
1292
1479
|
Generation,
|
|
@@ -1297,9 +1484,11 @@ var VERSION = "0.3.1";
|
|
|
1297
1484
|
getTracer,
|
|
1298
1485
|
init,
|
|
1299
1486
|
observe,
|
|
1487
|
+
registerWorkspace,
|
|
1300
1488
|
startAsCurrentGeneration,
|
|
1301
1489
|
startAsCurrentSpan,
|
|
1302
1490
|
startGeneration,
|
|
1303
1491
|
startSpan,
|
|
1304
|
-
withSession
|
|
1492
|
+
withSession,
|
|
1493
|
+
withWorkspace
|
|
1305
1494
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -35,12 +35,80 @@ interface RiusOptions {
|
|
|
35
35
|
|
|
36
36
|
type HeartbeatTransport = (payload: Record<string, unknown>, timeoutMs: number) => Promise<void>;
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Workspaces: route spans from one process to per-customer destinations.
|
|
40
|
+
*
|
|
41
|
+
* One client, one provider, one batch pipeline; the *destination* is a
|
|
42
|
+
* context-scoped property. `withWorkspace(alias, fn)` sets an OTel context
|
|
43
|
+
* key (exactly like `withSession`), `WorkspaceSpanProcessor` stamps it as a
|
|
44
|
+
* transient attribute at span start, and `RoutingSpanExporter` partitions
|
|
45
|
+
* each export batch by that attribute, strips it, and forwards every
|
|
46
|
+
* partition to the exporter registered for its alias. Spans started outside
|
|
47
|
+
* any scope go to the default destination.
|
|
48
|
+
*
|
|
49
|
+
* Because the alias rides OTel context, everything started in scope routes
|
|
50
|
+
* together: `observe` wrappers, generations, sessions, and spans created by
|
|
51
|
+
* auto-instrumentation. That is the property a second client could never
|
|
52
|
+
* give, which is why this SDK has no scoped-client mode at all.
|
|
53
|
+
*
|
|
54
|
+
* Two rules the design enforces or warns about:
|
|
55
|
+
*
|
|
56
|
+
* - The routing attribute never reaches the wire. The destination's API key
|
|
57
|
+
* is what tells the backend which workspace a span belongs to; the alias
|
|
58
|
+
* is process-local configuration, so the exporter strips it before
|
|
59
|
+
* delegating.
|
|
60
|
+
* - One trace, one workspace. The backend derives the workspace from the API
|
|
61
|
+
* key per request, so a trace split across scopes would come apart.
|
|
62
|
+
* Starting a span under a different alias than its parent's logs a
|
|
63
|
+
* warning; switch workspaces at request boundaries, not inside a trace.
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
type WorkspaceExporterFactory = (apiKey: string) => SpanExporter;
|
|
67
|
+
/**
|
|
68
|
+
* Scope every span started inside `fn` to one workspace destination.
|
|
69
|
+
*
|
|
70
|
+
* `alias` names a workspace registered via `init({ workspaces })` or
|
|
71
|
+
* `registerWorkspace()`; the scope's spans are exported with that
|
|
72
|
+
* workspace's API key. Scopes nest and follow async continuations the way
|
|
73
|
+
* all OTel context does, but a trace must stay inside one workspace: enter
|
|
74
|
+
* the scope at a request boundary, before the root span starts.
|
|
75
|
+
*
|
|
76
|
+
* ```typescript
|
|
77
|
+
* await withWorkspace("acme", async () => {
|
|
78
|
+
* await handle(request) // every span of the request lands in acme's workspace
|
|
79
|
+
* })
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
declare function withWorkspace<T>(alias: string, fn: (alias: string) => T): T;
|
|
83
|
+
/**
|
|
84
|
+
* Add (or rotate the key of) a workspace destination on the global client.
|
|
85
|
+
*
|
|
86
|
+
* Requires `init({ workspaces })` to have opted into routing (an empty
|
|
87
|
+
* object opts in with no static routes). Spans started inside
|
|
88
|
+
* `withWorkspace(alias, ...)` are then exported with `apiKey`.
|
|
89
|
+
*/
|
|
90
|
+
declare function registerWorkspace(alias: string, apiKey: string): void;
|
|
91
|
+
|
|
38
92
|
/** Options accepted by {@link init}, extending the shared configuration. */
|
|
39
93
|
interface InitOptions extends RiusOptions {
|
|
40
94
|
/** Inject an exporter instead of OTLP. The test seam; prefer this to mocking. */
|
|
41
95
|
spanExporter?: SpanExporter;
|
|
42
96
|
/** Override the heartbeat HTTP transport. The test seam; prefer this to mocking fetch. */
|
|
43
97
|
heartbeatTransport?: HeartbeatTransport;
|
|
98
|
+
/**
|
|
99
|
+
* Enable multi-workspace routing: a map of alias to API key. Spans started
|
|
100
|
+
* inside `withWorkspace(alias, fn)` are exported with that workspace's
|
|
101
|
+
* key; spans outside any scope use the default `apiKey`. Pass `{}` to opt
|
|
102
|
+
* in with no static routes and register destinations later via
|
|
103
|
+
* `registerWorkspace()`. One trace must stay inside one workspace.
|
|
104
|
+
*/
|
|
105
|
+
workspaces?: Record<string, string>;
|
|
106
|
+
/**
|
|
107
|
+
* Override how per-workspace exporters are built from an API key. The
|
|
108
|
+
* test seam, like `spanExporter`; defaults to the standard OTLP exporter
|
|
109
|
+
* against the configured endpoint.
|
|
110
|
+
*/
|
|
111
|
+
workspaceExporterFactory?: WorkspaceExporterFactory;
|
|
44
112
|
}
|
|
45
113
|
/**
|
|
46
114
|
* Handle over a configured tracer pipeline, returned by {@link init}.
|
|
@@ -155,16 +223,54 @@ interface GenerationOptions {
|
|
|
155
223
|
* so use the provider's own parameter names.
|
|
156
224
|
*/
|
|
157
225
|
modelParameters?: Record<string, unknown>;
|
|
226
|
+
/**
|
|
227
|
+
* Requested reasoning/thinking effort level
|
|
228
|
+
* (`gen_ai.request.reasoning.level`), e.g. OpenAI's `reasoning.effort`
|
|
229
|
+
* values. Provider-defined string, recorded verbatim. A first-class option
|
|
230
|
+
* because the `modelParameters` pass-through would spell the key
|
|
231
|
+
* `gen_ai.request.reasoning_level`, which is not the convention's name.
|
|
232
|
+
*/
|
|
233
|
+
reasoningLevel?: string;
|
|
158
234
|
}
|
|
159
235
|
/** An LLM call. Content uses gen_ai message keys, never input.value. */
|
|
160
236
|
declare class Generation extends Observation {
|
|
237
|
+
private readonly provider?;
|
|
161
238
|
private firstTokenRecorded;
|
|
239
|
+
/**
|
|
240
|
+
* The provider passed at creation; drives the Anthropic input-token summing
|
|
241
|
+
* in {@link setUsage}. A bare `new Generation(span)` has none and never sums.
|
|
242
|
+
*/
|
|
243
|
+
constructor(span: ConstructorParameters<typeof Observation>[0], provider?: string | undefined);
|
|
162
244
|
setInput(value: unknown): this;
|
|
163
245
|
setOutput(value: unknown): this;
|
|
164
246
|
setModel(model: string): this;
|
|
247
|
+
/**
|
|
248
|
+
* Token usage (`gen_ai.usage.*`). Pass provider-reported values as-is;
|
|
249
|
+
* never pre-add anything. Per the GenAI conventions, `inputTokens` is the
|
|
250
|
+
* total including cached tokens (the cache counts are subsets of it).
|
|
251
|
+
* Anthropic's API reports `input_tokens` excluding the cache counts, and
|
|
252
|
+
* the conventions require the instrumentation to do the summing, so when
|
|
253
|
+
* the generation's provider is `"anthropic"` the emitted total is
|
|
254
|
+
* `inputTokens` plus both cache counts. Every other provider is recorded
|
|
255
|
+
* verbatim.
|
|
256
|
+
*/
|
|
165
257
|
setUsage(usage: {
|
|
166
258
|
inputTokens?: number;
|
|
167
259
|
outputTokens?: number;
|
|
260
|
+
/** Input tokens served from a provider-managed prompt cache. */
|
|
261
|
+
cacheReadInputTokens?: number;
|
|
262
|
+
/**
|
|
263
|
+
* Input tokens written to a provider-managed prompt cache
|
|
264
|
+
* (called "cache creation" by Anthropic).
|
|
265
|
+
*/
|
|
266
|
+
cacheWriteInputTokens?: number;
|
|
267
|
+
/**
|
|
268
|
+
* Output tokens spent on reasoning / extended thinking. A subset of
|
|
269
|
+
* `outputTokens`, never in addition to it: providers already include
|
|
270
|
+
* reasoning tokens in the output total, so pass both as reported and
|
|
271
|
+
* do no arithmetic.
|
|
272
|
+
*/
|
|
273
|
+
reasoningOutputTokens?: number;
|
|
168
274
|
}): this;
|
|
169
275
|
/**
|
|
170
276
|
* Why generation stopped (`gen_ai.response.finish_reasons`), e.g. `"stop"`,
|
|
@@ -233,6 +339,6 @@ declare function observe<F extends (...args: never[]) => unknown>(fn: F, options
|
|
|
233
339
|
declare function withSession<T>(fn: (sessionId: string) => T): T;
|
|
234
340
|
declare function withSession<T>(sessionId: string, fn: (sessionId: string) => T): T;
|
|
235
341
|
|
|
236
|
-
declare const VERSION = "0.
|
|
342
|
+
declare const VERSION = "0.5.0";
|
|
237
343
|
|
|
238
|
-
export { Generation, type GenerationBody, type GenerationOptions, type InitOptions, type Mask, Observation, type ObserveOptions, RiusClient, type RiusOptions, type SpanBody, SpanKind, type SpanOptions, VERSION, getTracer, init, observe, startAsCurrentGeneration, startAsCurrentSpan, startGeneration, startSpan, withSession };
|
|
344
|
+
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, withWorkspace };
|
package/dist/index.d.ts
CHANGED
|
@@ -35,12 +35,80 @@ interface RiusOptions {
|
|
|
35
35
|
|
|
36
36
|
type HeartbeatTransport = (payload: Record<string, unknown>, timeoutMs: number) => Promise<void>;
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Workspaces: route spans from one process to per-customer destinations.
|
|
40
|
+
*
|
|
41
|
+
* One client, one provider, one batch pipeline; the *destination* is a
|
|
42
|
+
* context-scoped property. `withWorkspace(alias, fn)` sets an OTel context
|
|
43
|
+
* key (exactly like `withSession`), `WorkspaceSpanProcessor` stamps it as a
|
|
44
|
+
* transient attribute at span start, and `RoutingSpanExporter` partitions
|
|
45
|
+
* each export batch by that attribute, strips it, and forwards every
|
|
46
|
+
* partition to the exporter registered for its alias. Spans started outside
|
|
47
|
+
* any scope go to the default destination.
|
|
48
|
+
*
|
|
49
|
+
* Because the alias rides OTel context, everything started in scope routes
|
|
50
|
+
* together: `observe` wrappers, generations, sessions, and spans created by
|
|
51
|
+
* auto-instrumentation. That is the property a second client could never
|
|
52
|
+
* give, which is why this SDK has no scoped-client mode at all.
|
|
53
|
+
*
|
|
54
|
+
* Two rules the design enforces or warns about:
|
|
55
|
+
*
|
|
56
|
+
* - The routing attribute never reaches the wire. The destination's API key
|
|
57
|
+
* is what tells the backend which workspace a span belongs to; the alias
|
|
58
|
+
* is process-local configuration, so the exporter strips it before
|
|
59
|
+
* delegating.
|
|
60
|
+
* - One trace, one workspace. The backend derives the workspace from the API
|
|
61
|
+
* key per request, so a trace split across scopes would come apart.
|
|
62
|
+
* Starting a span under a different alias than its parent's logs a
|
|
63
|
+
* warning; switch workspaces at request boundaries, not inside a trace.
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
type WorkspaceExporterFactory = (apiKey: string) => SpanExporter;
|
|
67
|
+
/**
|
|
68
|
+
* Scope every span started inside `fn` to one workspace destination.
|
|
69
|
+
*
|
|
70
|
+
* `alias` names a workspace registered via `init({ workspaces })` or
|
|
71
|
+
* `registerWorkspace()`; the scope's spans are exported with that
|
|
72
|
+
* workspace's API key. Scopes nest and follow async continuations the way
|
|
73
|
+
* all OTel context does, but a trace must stay inside one workspace: enter
|
|
74
|
+
* the scope at a request boundary, before the root span starts.
|
|
75
|
+
*
|
|
76
|
+
* ```typescript
|
|
77
|
+
* await withWorkspace("acme", async () => {
|
|
78
|
+
* await handle(request) // every span of the request lands in acme's workspace
|
|
79
|
+
* })
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
declare function withWorkspace<T>(alias: string, fn: (alias: string) => T): T;
|
|
83
|
+
/**
|
|
84
|
+
* Add (or rotate the key of) a workspace destination on the global client.
|
|
85
|
+
*
|
|
86
|
+
* Requires `init({ workspaces })` to have opted into routing (an empty
|
|
87
|
+
* object opts in with no static routes). Spans started inside
|
|
88
|
+
* `withWorkspace(alias, ...)` are then exported with `apiKey`.
|
|
89
|
+
*/
|
|
90
|
+
declare function registerWorkspace(alias: string, apiKey: string): void;
|
|
91
|
+
|
|
38
92
|
/** Options accepted by {@link init}, extending the shared configuration. */
|
|
39
93
|
interface InitOptions extends RiusOptions {
|
|
40
94
|
/** Inject an exporter instead of OTLP. The test seam; prefer this to mocking. */
|
|
41
95
|
spanExporter?: SpanExporter;
|
|
42
96
|
/** Override the heartbeat HTTP transport. The test seam; prefer this to mocking fetch. */
|
|
43
97
|
heartbeatTransport?: HeartbeatTransport;
|
|
98
|
+
/**
|
|
99
|
+
* Enable multi-workspace routing: a map of alias to API key. Spans started
|
|
100
|
+
* inside `withWorkspace(alias, fn)` are exported with that workspace's
|
|
101
|
+
* key; spans outside any scope use the default `apiKey`. Pass `{}` to opt
|
|
102
|
+
* in with no static routes and register destinations later via
|
|
103
|
+
* `registerWorkspace()`. One trace must stay inside one workspace.
|
|
104
|
+
*/
|
|
105
|
+
workspaces?: Record<string, string>;
|
|
106
|
+
/**
|
|
107
|
+
* Override how per-workspace exporters are built from an API key. The
|
|
108
|
+
* test seam, like `spanExporter`; defaults to the standard OTLP exporter
|
|
109
|
+
* against the configured endpoint.
|
|
110
|
+
*/
|
|
111
|
+
workspaceExporterFactory?: WorkspaceExporterFactory;
|
|
44
112
|
}
|
|
45
113
|
/**
|
|
46
114
|
* Handle over a configured tracer pipeline, returned by {@link init}.
|
|
@@ -155,16 +223,54 @@ interface GenerationOptions {
|
|
|
155
223
|
* so use the provider's own parameter names.
|
|
156
224
|
*/
|
|
157
225
|
modelParameters?: Record<string, unknown>;
|
|
226
|
+
/**
|
|
227
|
+
* Requested reasoning/thinking effort level
|
|
228
|
+
* (`gen_ai.request.reasoning.level`), e.g. OpenAI's `reasoning.effort`
|
|
229
|
+
* values. Provider-defined string, recorded verbatim. A first-class option
|
|
230
|
+
* because the `modelParameters` pass-through would spell the key
|
|
231
|
+
* `gen_ai.request.reasoning_level`, which is not the convention's name.
|
|
232
|
+
*/
|
|
233
|
+
reasoningLevel?: string;
|
|
158
234
|
}
|
|
159
235
|
/** An LLM call. Content uses gen_ai message keys, never input.value. */
|
|
160
236
|
declare class Generation extends Observation {
|
|
237
|
+
private readonly provider?;
|
|
161
238
|
private firstTokenRecorded;
|
|
239
|
+
/**
|
|
240
|
+
* The provider passed at creation; drives the Anthropic input-token summing
|
|
241
|
+
* in {@link setUsage}. A bare `new Generation(span)` has none and never sums.
|
|
242
|
+
*/
|
|
243
|
+
constructor(span: ConstructorParameters<typeof Observation>[0], provider?: string | undefined);
|
|
162
244
|
setInput(value: unknown): this;
|
|
163
245
|
setOutput(value: unknown): this;
|
|
164
246
|
setModel(model: string): this;
|
|
247
|
+
/**
|
|
248
|
+
* Token usage (`gen_ai.usage.*`). Pass provider-reported values as-is;
|
|
249
|
+
* never pre-add anything. Per the GenAI conventions, `inputTokens` is the
|
|
250
|
+
* total including cached tokens (the cache counts are subsets of it).
|
|
251
|
+
* Anthropic's API reports `input_tokens` excluding the cache counts, and
|
|
252
|
+
* the conventions require the instrumentation to do the summing, so when
|
|
253
|
+
* the generation's provider is `"anthropic"` the emitted total is
|
|
254
|
+
* `inputTokens` plus both cache counts. Every other provider is recorded
|
|
255
|
+
* verbatim.
|
|
256
|
+
*/
|
|
165
257
|
setUsage(usage: {
|
|
166
258
|
inputTokens?: number;
|
|
167
259
|
outputTokens?: number;
|
|
260
|
+
/** Input tokens served from a provider-managed prompt cache. */
|
|
261
|
+
cacheReadInputTokens?: number;
|
|
262
|
+
/**
|
|
263
|
+
* Input tokens written to a provider-managed prompt cache
|
|
264
|
+
* (called "cache creation" by Anthropic).
|
|
265
|
+
*/
|
|
266
|
+
cacheWriteInputTokens?: number;
|
|
267
|
+
/**
|
|
268
|
+
* Output tokens spent on reasoning / extended thinking. A subset of
|
|
269
|
+
* `outputTokens`, never in addition to it: providers already include
|
|
270
|
+
* reasoning tokens in the output total, so pass both as reported and
|
|
271
|
+
* do no arithmetic.
|
|
272
|
+
*/
|
|
273
|
+
reasoningOutputTokens?: number;
|
|
168
274
|
}): this;
|
|
169
275
|
/**
|
|
170
276
|
* Why generation stopped (`gen_ai.response.finish_reasons`), e.g. `"stop"`,
|
|
@@ -233,6 +339,6 @@ declare function observe<F extends (...args: never[]) => unknown>(fn: F, options
|
|
|
233
339
|
declare function withSession<T>(fn: (sessionId: string) => T): T;
|
|
234
340
|
declare function withSession<T>(sessionId: string, fn: (sessionId: string) => T): T;
|
|
235
341
|
|
|
236
|
-
declare const VERSION = "0.
|
|
342
|
+
declare const VERSION = "0.5.0";
|
|
237
343
|
|
|
238
|
-
export { Generation, type GenerationBody, type GenerationOptions, type InitOptions, type Mask, Observation, type ObserveOptions, RiusClient, type RiusOptions, type SpanBody, SpanKind, type SpanOptions, VERSION, getTracer, init, observe, startAsCurrentGeneration, startAsCurrentSpan, startGeneration, startSpan, withSession };
|
|
344
|
+
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, withWorkspace };
|
package/dist/index.js
CHANGED
|
@@ -343,7 +343,7 @@ function kindAttributes(kind) {
|
|
|
343
343
|
if (operation !== void 0) attributes[GEN_AI_OPERATION_NAME] = operation;
|
|
344
344
|
return attributes;
|
|
345
345
|
}
|
|
346
|
-
var TRACER_NAME, SERVICE_INSTANCE_ID, OPENINFERENCE_SPAN_KIND, INPUT_VALUE, OUTPUT_VALUE, SESSION_ID, 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_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;
|
|
346
|
+
var TRACER_NAME, SERVICE_INSTANCE_ID, OPENINFERENCE_SPAN_KIND, INPUT_VALUE, OUTPUT_VALUE, SESSION_ID, WORKSPACE_ROUTE, GEN_AI_OPERATION_NAME, GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_MODEL, GEN_AI_REQUEST_REASONING_LEVEL, GEN_AI_RESPONSE_MODEL, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS, GEN_AI_USAGE_REASONING_OUTPUT_TOKENS, GEN_AI_INPUT_MESSAGES, GEN_AI_OUTPUT_MESSAGES, GEN_AI_RESPONSE_FINISH_REASONS, GEN_AI_TOOL_NAME, GEN_AI_REQUEST_PREFIX, MCP_RESULT_TYPE, GEN_AI_FIRST_TOKEN_EVENT, SpanKind, OPERATION_BY_KIND, CONTENT_ATTRIBUTES, CONTENT_ATTRIBUTE_PREFIXES, CONTENT_ATTRIBUTE_SUFFIXES, GLASSFLOW_SPAN_PENDING, PENDING_IDENTITY_ATTRIBUTES, PENDING_IDENTITY_PREFIXES;
|
|
347
347
|
var init_semconv = __esm({
|
|
348
348
|
"src/semconv.ts"() {
|
|
349
349
|
"use strict";
|
|
@@ -354,12 +354,17 @@ var init_semconv = __esm({
|
|
|
354
354
|
INPUT_VALUE = "input.value";
|
|
355
355
|
OUTPUT_VALUE = "output.value";
|
|
356
356
|
SESSION_ID = "session.id";
|
|
357
|
+
WORKSPACE_ROUTE = "rius.workspace";
|
|
357
358
|
GEN_AI_OPERATION_NAME = "gen_ai.operation.name";
|
|
358
359
|
GEN_AI_PROVIDER_NAME = "gen_ai.provider.name";
|
|
359
360
|
GEN_AI_REQUEST_MODEL = "gen_ai.request.model";
|
|
361
|
+
GEN_AI_REQUEST_REASONING_LEVEL = "gen_ai.request.reasoning.level";
|
|
360
362
|
GEN_AI_RESPONSE_MODEL = "gen_ai.response.model";
|
|
361
363
|
GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens";
|
|
362
364
|
GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens";
|
|
365
|
+
GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens";
|
|
366
|
+
GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS = "gen_ai.usage.cache_write.input_tokens";
|
|
367
|
+
GEN_AI_USAGE_REASONING_OUTPUT_TOKENS = "gen_ai.usage.reasoning.output_tokens";
|
|
363
368
|
GEN_AI_INPUT_MESSAGES = "gen_ai.input.messages";
|
|
364
369
|
GEN_AI_OUTPUT_MESSAGES = "gen_ai.output.messages";
|
|
365
370
|
GEN_AI_RESPONSE_FINISH_REASONS = "gen_ai.response.finish_reasons";
|
|
@@ -387,6 +392,12 @@ var init_semconv = __esm({
|
|
|
387
392
|
OUTPUT_VALUE,
|
|
388
393
|
GEN_AI_INPUT_MESSAGES,
|
|
389
394
|
GEN_AI_OUTPUT_MESSAGES,
|
|
395
|
+
// Sensitive per the GenAI conventions (semconv-genai#431): tool definitions
|
|
396
|
+
// routinely embed proprietary prompt engineering, and sometimes credentials
|
|
397
|
+
// or internal URLs in parameter defaults. gen_ai.tool.name stays: it is
|
|
398
|
+
// identity, not content.
|
|
399
|
+
"gen_ai.tool.description",
|
|
400
|
+
"gen_ai.tool.definitions",
|
|
390
401
|
"gen_ai.prompt",
|
|
391
402
|
"gen_ai.completion",
|
|
392
403
|
"llm.input_messages",
|
|
@@ -422,7 +433,10 @@ var init_semconv = __esm({
|
|
|
422
433
|
GEN_AI_TOOL_NAME,
|
|
423
434
|
// Identity, not content: a pending span must be groupable into its
|
|
424
435
|
// session while still running, that is the live view's whole point.
|
|
425
|
-
SESSION_ID
|
|
436
|
+
SESSION_ID,
|
|
437
|
+
// Routing, not content: a crashed run's snapshot must land in the same
|
|
438
|
+
// workspace its final span would have. Stripped at export either way.
|
|
439
|
+
WORKSPACE_ROUTE
|
|
426
440
|
]);
|
|
427
441
|
PENDING_IDENTITY_PREFIXES = [GEN_AI_REQUEST_PREFIX];
|
|
428
442
|
}
|
|
@@ -997,9 +1011,131 @@ var init_session = __esm({
|
|
|
997
1011
|
}
|
|
998
1012
|
});
|
|
999
1013
|
|
|
1014
|
+
// src/workspace.ts
|
|
1015
|
+
import { context as apiContext2, createContextKey as createContextKey2, trace } from "@opentelemetry/api";
|
|
1016
|
+
function withWorkspace(alias, fn) {
|
|
1017
|
+
if (!alias) throw new Error("workspace alias must be a non-empty string");
|
|
1018
|
+
return apiContext2.with(apiContext2.active().setValue(WORKSPACE_KEY, alias), () => fn(alias));
|
|
1019
|
+
}
|
|
1020
|
+
function setGlobalRouting(routing) {
|
|
1021
|
+
globalRouting = routing;
|
|
1022
|
+
}
|
|
1023
|
+
function registerWorkspace(alias, apiKey) {
|
|
1024
|
+
if (globalRouting === void 0) {
|
|
1025
|
+
throw new Error(
|
|
1026
|
+
"[rius] workspace routing is not enabled: pass workspaces (an empty object is fine) to init() to opt in before registering destinations"
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
globalRouting.register(alias, apiKey);
|
|
1030
|
+
}
|
|
1031
|
+
var WORKSPACE_KEY, WorkspaceSpanProcessor, RoutingSpanExporter, globalRouting;
|
|
1032
|
+
var init_workspace = __esm({
|
|
1033
|
+
"src/workspace.ts"() {
|
|
1034
|
+
"use strict";
|
|
1035
|
+
init_esm_shims();
|
|
1036
|
+
init_esm();
|
|
1037
|
+
init_semconv();
|
|
1038
|
+
WORKSPACE_KEY = createContextKey2("rius-workspace-alias");
|
|
1039
|
+
WorkspaceSpanProcessor = class {
|
|
1040
|
+
onStart(span, parentContext) {
|
|
1041
|
+
const alias = parentContext.getValue(WORKSPACE_KEY);
|
|
1042
|
+
if (typeof alias !== "string") return;
|
|
1043
|
+
const parent = trace.getSpan(parentContext);
|
|
1044
|
+
const parentAlias = parent?.attributes?.[WORKSPACE_ROUTE];
|
|
1045
|
+
if (parentAlias !== void 0 && parentAlias !== alias) {
|
|
1046
|
+
console.warn(
|
|
1047
|
+
`[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.`
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
span.setAttribute(WORKSPACE_ROUTE, alias);
|
|
1051
|
+
}
|
|
1052
|
+
onEnd(_span) {
|
|
1053
|
+
}
|
|
1054
|
+
async forceFlush() {
|
|
1055
|
+
}
|
|
1056
|
+
async shutdown() {
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
RoutingSpanExporter = class {
|
|
1060
|
+
constructor(defaultExporter, factory, routes = {}) {
|
|
1061
|
+
this.defaultExporter = defaultExporter;
|
|
1062
|
+
this.factory = factory;
|
|
1063
|
+
this.routes = new Map(Object.entries(routes));
|
|
1064
|
+
}
|
|
1065
|
+
defaultExporter;
|
|
1066
|
+
factory;
|
|
1067
|
+
routes;
|
|
1068
|
+
exporters = /* @__PURE__ */ new Map();
|
|
1069
|
+
warnedAliases = /* @__PURE__ */ new Set();
|
|
1070
|
+
/** Add or replace a route. Replacing supports key rotation. */
|
|
1071
|
+
register(alias, apiKey) {
|
|
1072
|
+
if (!alias || !apiKey) {
|
|
1073
|
+
throw new Error("workspace alias and apiKey must be non-empty strings");
|
|
1074
|
+
}
|
|
1075
|
+
this.routes.set(alias, apiKey);
|
|
1076
|
+
this.warnedAliases.delete(alias);
|
|
1077
|
+
}
|
|
1078
|
+
export(spans, resultCallback) {
|
|
1079
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1080
|
+
for (const span of spans) {
|
|
1081
|
+
const attributes = span.attributes;
|
|
1082
|
+
const raw = attributes[WORKSPACE_ROUTE];
|
|
1083
|
+
let destination = this.defaultExporter;
|
|
1084
|
+
if (raw !== void 0) {
|
|
1085
|
+
delete attributes[WORKSPACE_ROUTE];
|
|
1086
|
+
destination = this.resolve(String(raw));
|
|
1087
|
+
}
|
|
1088
|
+
const group = groups.get(destination);
|
|
1089
|
+
if (group === void 0) groups.set(destination, [span]);
|
|
1090
|
+
else group.push(span);
|
|
1091
|
+
}
|
|
1092
|
+
let pending = groups.size;
|
|
1093
|
+
if (pending === 0) {
|
|
1094
|
+
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
let failed;
|
|
1098
|
+
for (const [exporter, group] of groups) {
|
|
1099
|
+
exporter.export(group, (result) => {
|
|
1100
|
+
if (result.code !== ExportResultCode.SUCCESS) failed = result;
|
|
1101
|
+
pending -= 1;
|
|
1102
|
+
if (pending === 0) resultCallback(failed ?? { code: ExportResultCode.SUCCESS });
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
resolve(alias) {
|
|
1107
|
+
const apiKey = this.routes.get(alias);
|
|
1108
|
+
if (apiKey === void 0) {
|
|
1109
|
+
if (!this.warnedAliases.has(alias)) {
|
|
1110
|
+
this.warnedAliases.add(alias);
|
|
1111
|
+
console.warn(
|
|
1112
|
+
`[rius] no workspace registered for alias "${alias}"; its spans go to the default destination. Register it with registerWorkspace("${alias}", apiKey) or in init({ workspaces }).`
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
return this.defaultExporter;
|
|
1116
|
+
}
|
|
1117
|
+
let exporter = this.exporters.get(apiKey);
|
|
1118
|
+
if (exporter === void 0) {
|
|
1119
|
+
exporter = this.factory(apiKey);
|
|
1120
|
+
this.exporters.set(apiKey, exporter);
|
|
1121
|
+
}
|
|
1122
|
+
return exporter;
|
|
1123
|
+
}
|
|
1124
|
+
async forceFlush() {
|
|
1125
|
+
await this.defaultExporter.forceFlush?.();
|
|
1126
|
+
await Promise.all([...this.exporters.values()].map((e) => e.forceFlush?.()));
|
|
1127
|
+
}
|
|
1128
|
+
async shutdown() {
|
|
1129
|
+
await this.defaultExporter.shutdown();
|
|
1130
|
+
await Promise.all([...this.exporters.values()].map((e) => e.shutdown()));
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1000
1136
|
// src/client.ts
|
|
1001
1137
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1002
|
-
import { trace } from "@opentelemetry/api";
|
|
1138
|
+
import { trace as trace2 } from "@opentelemetry/api";
|
|
1003
1139
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
1004
1140
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
1005
1141
|
import {
|
|
@@ -1020,11 +1156,20 @@ function init(options = {}) {
|
|
|
1020
1156
|
const processors = new DelegatingSpanProcessor();
|
|
1021
1157
|
const authHeaders = config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {};
|
|
1022
1158
|
let health;
|
|
1159
|
+
let routing;
|
|
1023
1160
|
if (!config.disabled) {
|
|
1024
|
-
|
|
1161
|
+
let base = options.spanExporter ?? new OTLPTraceExporter({
|
|
1025
1162
|
url: `${config.endpoint}/v1/traces`,
|
|
1026
1163
|
headers: config.apiKey ? authHeaders : void 0
|
|
1027
1164
|
});
|
|
1165
|
+
if (options.workspaces !== void 0) {
|
|
1166
|
+
const factory = options.workspaceExporterFactory ?? ((apiKey) => new OTLPTraceExporter({
|
|
1167
|
+
url: `${config.endpoint}/v1/traces`,
|
|
1168
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1169
|
+
}));
|
|
1170
|
+
routing = new RoutingSpanExporter(base, factory, options.workspaces);
|
|
1171
|
+
base = routing;
|
|
1172
|
+
}
|
|
1028
1173
|
health = new ExportOutcomeExporter(base);
|
|
1029
1174
|
const exporter = !config.captureContent || config.mask !== void 0 ? new MaskingSpanExporter(health, {
|
|
1030
1175
|
captureContent: config.captureContent,
|
|
@@ -1032,6 +1177,9 @@ function init(options = {}) {
|
|
|
1032
1177
|
}) : health;
|
|
1033
1178
|
const batch = new BatchSpanProcessor(exporter);
|
|
1034
1179
|
processors.add(new SessionSpanProcessor(config.sessionId));
|
|
1180
|
+
if (routing !== void 0) {
|
|
1181
|
+
processors.add(new WorkspaceSpanProcessor());
|
|
1182
|
+
}
|
|
1035
1183
|
if (config.partialSpans) {
|
|
1036
1184
|
processors.add(new PendingSpanProcessor(batch, { delayMs: config.partialSpansDelayMs }));
|
|
1037
1185
|
}
|
|
@@ -1074,10 +1222,11 @@ function init(options = {}) {
|
|
|
1074
1222
|
heartbeat = { sender, beforeExitHandler };
|
|
1075
1223
|
}
|
|
1076
1224
|
globalClient = createClient({ provider, processors, health, ready, heartbeat });
|
|
1225
|
+
setGlobalRouting(routing);
|
|
1077
1226
|
return globalClient;
|
|
1078
1227
|
}
|
|
1079
1228
|
function getTracer() {
|
|
1080
|
-
return
|
|
1229
|
+
return trace2.getTracer(TRACER_NAME);
|
|
1081
1230
|
}
|
|
1082
1231
|
var sinks, heartbeats, createClient, RiusClient, globalClient;
|
|
1083
1232
|
var init_client = __esm({
|
|
@@ -1093,6 +1242,7 @@ var init_client = __esm({
|
|
|
1093
1242
|
init_pending();
|
|
1094
1243
|
init_semconv();
|
|
1095
1244
|
init_session();
|
|
1245
|
+
init_workspace();
|
|
1096
1246
|
sinks = /* @__PURE__ */ new WeakMap();
|
|
1097
1247
|
heartbeats = /* @__PURE__ */ new WeakMap();
|
|
1098
1248
|
RiusClient = class _RiusClient {
|
|
@@ -1136,7 +1286,8 @@ var init_client = __esm({
|
|
|
1136
1286
|
} finally {
|
|
1137
1287
|
if (globalClient === this) {
|
|
1138
1288
|
globalClient = void 0;
|
|
1139
|
-
|
|
1289
|
+
setGlobalRouting(void 0);
|
|
1290
|
+
trace2.disable();
|
|
1140
1291
|
}
|
|
1141
1292
|
}
|
|
1142
1293
|
}
|
|
@@ -1155,6 +1306,15 @@ init_semconv();
|
|
|
1155
1306
|
init_serde();
|
|
1156
1307
|
init_spans();
|
|
1157
1308
|
var Generation = class extends Observation {
|
|
1309
|
+
/**
|
|
1310
|
+
* The provider passed at creation; drives the Anthropic input-token summing
|
|
1311
|
+
* in {@link setUsage}. A bare `new Generation(span)` has none and never sums.
|
|
1312
|
+
*/
|
|
1313
|
+
constructor(span, provider) {
|
|
1314
|
+
super(span);
|
|
1315
|
+
this.provider = provider;
|
|
1316
|
+
}
|
|
1317
|
+
provider;
|
|
1158
1318
|
firstTokenRecorded = false;
|
|
1159
1319
|
setInput(value) {
|
|
1160
1320
|
this.span.setAttribute(GEN_AI_INPUT_MESSAGES, toAttributeValue(value));
|
|
@@ -1168,13 +1328,34 @@ var Generation = class extends Observation {
|
|
|
1168
1328
|
this.span.setAttribute(GEN_AI_RESPONSE_MODEL, model);
|
|
1169
1329
|
return this;
|
|
1170
1330
|
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Token usage (`gen_ai.usage.*`). Pass provider-reported values as-is;
|
|
1333
|
+
* never pre-add anything. Per the GenAI conventions, `inputTokens` is the
|
|
1334
|
+
* total including cached tokens (the cache counts are subsets of it).
|
|
1335
|
+
* Anthropic's API reports `input_tokens` excluding the cache counts, and
|
|
1336
|
+
* the conventions require the instrumentation to do the summing, so when
|
|
1337
|
+
* the generation's provider is `"anthropic"` the emitted total is
|
|
1338
|
+
* `inputTokens` plus both cache counts. Every other provider is recorded
|
|
1339
|
+
* verbatim.
|
|
1340
|
+
*/
|
|
1171
1341
|
setUsage(usage) {
|
|
1172
1342
|
if (usage.inputTokens !== void 0) {
|
|
1173
|
-
this.
|
|
1343
|
+
const sums = this.provider?.toLowerCase() === "anthropic";
|
|
1344
|
+
const total = sums ? usage.inputTokens + (usage.cacheReadInputTokens ?? 0) + (usage.cacheWriteInputTokens ?? 0) : usage.inputTokens;
|
|
1345
|
+
this.span.setAttribute(GEN_AI_USAGE_INPUT_TOKENS, total);
|
|
1174
1346
|
}
|
|
1175
1347
|
if (usage.outputTokens !== void 0) {
|
|
1176
1348
|
this.span.setAttribute(GEN_AI_USAGE_OUTPUT_TOKENS, usage.outputTokens);
|
|
1177
1349
|
}
|
|
1350
|
+
if (usage.cacheReadInputTokens !== void 0) {
|
|
1351
|
+
this.span.setAttribute(GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, usage.cacheReadInputTokens);
|
|
1352
|
+
}
|
|
1353
|
+
if (usage.cacheWriteInputTokens !== void 0) {
|
|
1354
|
+
this.span.setAttribute(GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS, usage.cacheWriteInputTokens);
|
|
1355
|
+
}
|
|
1356
|
+
if (usage.reasoningOutputTokens !== void 0) {
|
|
1357
|
+
this.span.setAttribute(GEN_AI_USAGE_REASONING_OUTPUT_TOKENS, usage.reasoningOutputTokens);
|
|
1358
|
+
}
|
|
1178
1359
|
return this;
|
|
1179
1360
|
}
|
|
1180
1361
|
/**
|
|
@@ -1214,17 +1395,20 @@ function configure2(generation, options) {
|
|
|
1214
1395
|
for (const [key, value] of Object.entries(options.modelParameters ?? {})) {
|
|
1215
1396
|
generation.setAttribute(`${GEN_AI_REQUEST_PREFIX}${key}`, value);
|
|
1216
1397
|
}
|
|
1398
|
+
if (options.reasoningLevel !== void 0) {
|
|
1399
|
+
generation.setAttribute(GEN_AI_REQUEST_REASONING_LEVEL, options.reasoningLevel);
|
|
1400
|
+
}
|
|
1217
1401
|
if (options.input !== void 0) generation.setInput(options.input);
|
|
1218
1402
|
return generation;
|
|
1219
1403
|
}
|
|
1220
1404
|
function startGeneration(name, options = {}) {
|
|
1221
1405
|
const span = getTracer().startSpan(name, { attributes: attributesFor(options) });
|
|
1222
|
-
return configure2(new Generation(span), options);
|
|
1406
|
+
return configure2(new Generation(span, options.provider), options);
|
|
1223
1407
|
}
|
|
1224
1408
|
function startAsCurrentGeneration(name, optionsOrFn, maybeFn) {
|
|
1225
1409
|
const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
|
|
1226
1410
|
return getTracer().startActiveSpan(name, { attributes: attributesFor(options) }, async (span) => {
|
|
1227
|
-
const generation = configure2(new Generation(span), options);
|
|
1411
|
+
const generation = configure2(new Generation(span, options.provider), options);
|
|
1228
1412
|
try {
|
|
1229
1413
|
return await fn(generation);
|
|
1230
1414
|
} catch (error) {
|
|
@@ -1260,7 +1444,8 @@ function observe(fn, options = {}) {
|
|
|
1260
1444
|
init_semconv();
|
|
1261
1445
|
init_session();
|
|
1262
1446
|
init_spans();
|
|
1263
|
-
|
|
1447
|
+
init_workspace();
|
|
1448
|
+
var VERSION = "0.5.0";
|
|
1264
1449
|
export {
|
|
1265
1450
|
Generation,
|
|
1266
1451
|
Observation,
|
|
@@ -1270,9 +1455,11 @@ export {
|
|
|
1270
1455
|
getTracer,
|
|
1271
1456
|
init,
|
|
1272
1457
|
observe,
|
|
1458
|
+
registerWorkspace,
|
|
1273
1459
|
startAsCurrentGeneration,
|
|
1274
1460
|
startAsCurrentSpan,
|
|
1275
1461
|
startGeneration,
|
|
1276
1462
|
startSpan,
|
|
1277
|
-
withSession
|
|
1463
|
+
withSession,
|
|
1464
|
+
withWorkspace
|
|
1278
1465
|
};
|