@glassflow-ai/rius 0.3.1 → 0.4.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 +177 -10
- package/dist/index.d.cts +84 -2
- package/dist/index.d.ts +84 -2
- package/dist/index.js +173 -8
- 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_RESPONSE_MODEL, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS, GEN_AI_INPUT_MESSAGES, GEN_AI_OUTPUT_MESSAGES, GEN_AI_RESPONSE_FINISH_REASONS, GEN_AI_TOOL_NAME, GEN_AI_REQUEST_PREFIX, MCP_RESULT_TYPE, GEN_AI_FIRST_TOKEN_EVENT, SpanKind, OPERATION_BY_KIND, CONTENT_ATTRIBUTES, CONTENT_ATTRIBUTE_PREFIXES, CONTENT_ATTRIBUTE_SUFFIXES, GLASSFLOW_SPAN_PENDING, PENDING_IDENTITY_ATTRIBUTES, PENDING_IDENTITY_PREFIXES;
|
|
360
360
|
var init_semconv = __esm({
|
|
361
361
|
"src/semconv.ts"() {
|
|
362
362
|
"use strict";
|
|
@@ -367,12 +367,15 @@ 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";
|
|
373
374
|
GEN_AI_RESPONSE_MODEL = "gen_ai.response.model";
|
|
374
375
|
GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens";
|
|
375
376
|
GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens";
|
|
377
|
+
GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens";
|
|
378
|
+
GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS = "gen_ai.usage.cache_write.input_tokens";
|
|
376
379
|
GEN_AI_INPUT_MESSAGES = "gen_ai.input.messages";
|
|
377
380
|
GEN_AI_OUTPUT_MESSAGES = "gen_ai.output.messages";
|
|
378
381
|
GEN_AI_RESPONSE_FINISH_REASONS = "gen_ai.response.finish_reasons";
|
|
@@ -400,6 +403,12 @@ var init_semconv = __esm({
|
|
|
400
403
|
OUTPUT_VALUE,
|
|
401
404
|
GEN_AI_INPUT_MESSAGES,
|
|
402
405
|
GEN_AI_OUTPUT_MESSAGES,
|
|
406
|
+
// Sensitive per the GenAI conventions (semconv-genai#431): tool definitions
|
|
407
|
+
// routinely embed proprietary prompt engineering, and sometimes credentials
|
|
408
|
+
// or internal URLs in parameter defaults. gen_ai.tool.name stays: it is
|
|
409
|
+
// identity, not content.
|
|
410
|
+
"gen_ai.tool.description",
|
|
411
|
+
"gen_ai.tool.definitions",
|
|
403
412
|
"gen_ai.prompt",
|
|
404
413
|
"gen_ai.completion",
|
|
405
414
|
"llm.input_messages",
|
|
@@ -435,7 +444,10 @@ var init_semconv = __esm({
|
|
|
435
444
|
GEN_AI_TOOL_NAME,
|
|
436
445
|
// Identity, not content: a pending span must be groupable into its
|
|
437
446
|
// session while still running, that is the live view's whole point.
|
|
438
|
-
SESSION_ID
|
|
447
|
+
SESSION_ID,
|
|
448
|
+
// Routing, not content: a crashed run's snapshot must land in the same
|
|
449
|
+
// workspace its final span would have. Stripped at export either way.
|
|
450
|
+
WORKSPACE_ROUTE
|
|
439
451
|
]);
|
|
440
452
|
PENDING_IDENTITY_PREFIXES = [GEN_AI_REQUEST_PREFIX];
|
|
441
453
|
}
|
|
@@ -1010,6 +1022,128 @@ var init_session = __esm({
|
|
|
1010
1022
|
}
|
|
1011
1023
|
});
|
|
1012
1024
|
|
|
1025
|
+
// src/workspace.ts
|
|
1026
|
+
function withWorkspace(alias, fn) {
|
|
1027
|
+
if (!alias) throw new Error("workspace alias must be a non-empty string");
|
|
1028
|
+
return import_api6.context.with(import_api6.context.active().setValue(WORKSPACE_KEY, alias), () => fn(alias));
|
|
1029
|
+
}
|
|
1030
|
+
function setGlobalRouting(routing) {
|
|
1031
|
+
globalRouting = routing;
|
|
1032
|
+
}
|
|
1033
|
+
function registerWorkspace(alias, apiKey) {
|
|
1034
|
+
if (globalRouting === void 0) {
|
|
1035
|
+
throw new Error(
|
|
1036
|
+
"[rius] workspace routing is not enabled: pass workspaces (an empty object is fine) to init() to opt in before registering destinations"
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
globalRouting.register(alias, apiKey);
|
|
1040
|
+
}
|
|
1041
|
+
var import_api6, WORKSPACE_KEY, WorkspaceSpanProcessor, RoutingSpanExporter, globalRouting;
|
|
1042
|
+
var init_workspace = __esm({
|
|
1043
|
+
"src/workspace.ts"() {
|
|
1044
|
+
"use strict";
|
|
1045
|
+
init_cjs_shims();
|
|
1046
|
+
import_api6 = require("@opentelemetry/api");
|
|
1047
|
+
init_esm();
|
|
1048
|
+
init_semconv();
|
|
1049
|
+
WORKSPACE_KEY = (0, import_api6.createContextKey)("rius-workspace-alias");
|
|
1050
|
+
WorkspaceSpanProcessor = class {
|
|
1051
|
+
onStart(span, parentContext) {
|
|
1052
|
+
const alias = parentContext.getValue(WORKSPACE_KEY);
|
|
1053
|
+
if (typeof alias !== "string") return;
|
|
1054
|
+
const parent = import_api6.trace.getSpan(parentContext);
|
|
1055
|
+
const parentAlias = parent?.attributes?.[WORKSPACE_ROUTE];
|
|
1056
|
+
if (parentAlias !== void 0 && parentAlias !== alias) {
|
|
1057
|
+
console.warn(
|
|
1058
|
+
`[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.`
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
span.setAttribute(WORKSPACE_ROUTE, alias);
|
|
1062
|
+
}
|
|
1063
|
+
onEnd(_span) {
|
|
1064
|
+
}
|
|
1065
|
+
async forceFlush() {
|
|
1066
|
+
}
|
|
1067
|
+
async shutdown() {
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
RoutingSpanExporter = class {
|
|
1071
|
+
constructor(defaultExporter, factory, routes = {}) {
|
|
1072
|
+
this.defaultExporter = defaultExporter;
|
|
1073
|
+
this.factory = factory;
|
|
1074
|
+
this.routes = new Map(Object.entries(routes));
|
|
1075
|
+
}
|
|
1076
|
+
defaultExporter;
|
|
1077
|
+
factory;
|
|
1078
|
+
routes;
|
|
1079
|
+
exporters = /* @__PURE__ */ new Map();
|
|
1080
|
+
warnedAliases = /* @__PURE__ */ new Set();
|
|
1081
|
+
/** Add or replace a route. Replacing supports key rotation. */
|
|
1082
|
+
register(alias, apiKey) {
|
|
1083
|
+
if (!alias || !apiKey) {
|
|
1084
|
+
throw new Error("workspace alias and apiKey must be non-empty strings");
|
|
1085
|
+
}
|
|
1086
|
+
this.routes.set(alias, apiKey);
|
|
1087
|
+
this.warnedAliases.delete(alias);
|
|
1088
|
+
}
|
|
1089
|
+
export(spans, resultCallback) {
|
|
1090
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1091
|
+
for (const span of spans) {
|
|
1092
|
+
const attributes = span.attributes;
|
|
1093
|
+
const raw = attributes[WORKSPACE_ROUTE];
|
|
1094
|
+
let destination = this.defaultExporter;
|
|
1095
|
+
if (raw !== void 0) {
|
|
1096
|
+
delete attributes[WORKSPACE_ROUTE];
|
|
1097
|
+
destination = this.resolve(String(raw));
|
|
1098
|
+
}
|
|
1099
|
+
const group = groups.get(destination);
|
|
1100
|
+
if (group === void 0) groups.set(destination, [span]);
|
|
1101
|
+
else group.push(span);
|
|
1102
|
+
}
|
|
1103
|
+
let pending = groups.size;
|
|
1104
|
+
if (pending === 0) {
|
|
1105
|
+
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
let failed;
|
|
1109
|
+
for (const [exporter, group] of groups) {
|
|
1110
|
+
exporter.export(group, (result) => {
|
|
1111
|
+
if (result.code !== ExportResultCode.SUCCESS) failed = result;
|
|
1112
|
+
pending -= 1;
|
|
1113
|
+
if (pending === 0) resultCallback(failed ?? { code: ExportResultCode.SUCCESS });
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
resolve(alias) {
|
|
1118
|
+
const apiKey = this.routes.get(alias);
|
|
1119
|
+
if (apiKey === void 0) {
|
|
1120
|
+
if (!this.warnedAliases.has(alias)) {
|
|
1121
|
+
this.warnedAliases.add(alias);
|
|
1122
|
+
console.warn(
|
|
1123
|
+
`[rius] no workspace registered for alias "${alias}"; its spans go to the default destination. Register it with registerWorkspace("${alias}", apiKey) or in init({ workspaces }).`
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
return this.defaultExporter;
|
|
1127
|
+
}
|
|
1128
|
+
let exporter = this.exporters.get(apiKey);
|
|
1129
|
+
if (exporter === void 0) {
|
|
1130
|
+
exporter = this.factory(apiKey);
|
|
1131
|
+
this.exporters.set(apiKey, exporter);
|
|
1132
|
+
}
|
|
1133
|
+
return exporter;
|
|
1134
|
+
}
|
|
1135
|
+
async forceFlush() {
|
|
1136
|
+
await this.defaultExporter.forceFlush?.();
|
|
1137
|
+
await Promise.all([...this.exporters.values()].map((e) => e.forceFlush?.()));
|
|
1138
|
+
}
|
|
1139
|
+
async shutdown() {
|
|
1140
|
+
await this.defaultExporter.shutdown();
|
|
1141
|
+
await Promise.all([...this.exporters.values()].map((e) => e.shutdown()));
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1013
1147
|
// src/client.ts
|
|
1014
1148
|
function init(options = {}) {
|
|
1015
1149
|
if (globalClient !== void 0) {
|
|
@@ -1022,11 +1156,20 @@ function init(options = {}) {
|
|
|
1022
1156
|
const processors = new DelegatingSpanProcessor();
|
|
1023
1157
|
const authHeaders = config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {};
|
|
1024
1158
|
let health;
|
|
1159
|
+
let routing;
|
|
1025
1160
|
if (!config.disabled) {
|
|
1026
|
-
|
|
1161
|
+
let base = options.spanExporter ?? new import_exporter_trace_otlp_proto.OTLPTraceExporter({
|
|
1027
1162
|
url: `${config.endpoint}/v1/traces`,
|
|
1028
1163
|
headers: config.apiKey ? authHeaders : void 0
|
|
1029
1164
|
});
|
|
1165
|
+
if (options.workspaces !== void 0) {
|
|
1166
|
+
const factory = options.workspaceExporterFactory ?? ((apiKey) => new import_exporter_trace_otlp_proto.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
|
+
}
|
|
1030
1173
|
health = new ExportOutcomeExporter(base);
|
|
1031
1174
|
const exporter = !config.captureContent || config.mask !== void 0 ? new MaskingSpanExporter(health, {
|
|
1032
1175
|
captureContent: config.captureContent,
|
|
@@ -1034,6 +1177,9 @@ function init(options = {}) {
|
|
|
1034
1177
|
}) : health;
|
|
1035
1178
|
const batch = new import_sdk_trace_base.BatchSpanProcessor(exporter);
|
|
1036
1179
|
processors.add(new SessionSpanProcessor(config.sessionId));
|
|
1180
|
+
if (routing !== void 0) {
|
|
1181
|
+
processors.add(new WorkspaceSpanProcessor());
|
|
1182
|
+
}
|
|
1037
1183
|
if (config.partialSpans) {
|
|
1038
1184
|
processors.add(new PendingSpanProcessor(batch, { delayMs: config.partialSpansDelayMs }));
|
|
1039
1185
|
}
|
|
@@ -1076,18 +1222,19 @@ function init(options = {}) {
|
|
|
1076
1222
|
heartbeat = { sender, beforeExitHandler };
|
|
1077
1223
|
}
|
|
1078
1224
|
globalClient = createClient({ provider, processors, health, ready, heartbeat });
|
|
1225
|
+
setGlobalRouting(routing);
|
|
1079
1226
|
return globalClient;
|
|
1080
1227
|
}
|
|
1081
1228
|
function getTracer() {
|
|
1082
|
-
return
|
|
1229
|
+
return import_api7.trace.getTracer(TRACER_NAME);
|
|
1083
1230
|
}
|
|
1084
|
-
var import_node_crypto2,
|
|
1231
|
+
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
1232
|
var init_client = __esm({
|
|
1086
1233
|
"src/client.ts"() {
|
|
1087
1234
|
"use strict";
|
|
1088
1235
|
init_cjs_shims();
|
|
1089
1236
|
import_node_crypto2 = require("crypto");
|
|
1090
|
-
|
|
1237
|
+
import_api7 = require("@opentelemetry/api");
|
|
1091
1238
|
import_exporter_trace_otlp_proto = require("@opentelemetry/exporter-trace-otlp-proto");
|
|
1092
1239
|
import_resources = require("@opentelemetry/resources");
|
|
1093
1240
|
import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
@@ -1102,6 +1249,7 @@ var init_client = __esm({
|
|
|
1102
1249
|
init_pending();
|
|
1103
1250
|
init_semconv();
|
|
1104
1251
|
init_session();
|
|
1252
|
+
init_workspace();
|
|
1105
1253
|
sinks = /* @__PURE__ */ new WeakMap();
|
|
1106
1254
|
heartbeats = /* @__PURE__ */ new WeakMap();
|
|
1107
1255
|
RiusClient = class _RiusClient {
|
|
@@ -1145,7 +1293,8 @@ var init_client = __esm({
|
|
|
1145
1293
|
} finally {
|
|
1146
1294
|
if (globalClient === this) {
|
|
1147
1295
|
globalClient = void 0;
|
|
1148
|
-
|
|
1296
|
+
setGlobalRouting(void 0);
|
|
1297
|
+
import_api7.trace.disable();
|
|
1149
1298
|
}
|
|
1150
1299
|
}
|
|
1151
1300
|
}
|
|
@@ -1164,11 +1313,13 @@ __export(index_exports, {
|
|
|
1164
1313
|
getTracer: () => getTracer,
|
|
1165
1314
|
init: () => init,
|
|
1166
1315
|
observe: () => observe,
|
|
1316
|
+
registerWorkspace: () => registerWorkspace,
|
|
1167
1317
|
startAsCurrentGeneration: () => startAsCurrentGeneration,
|
|
1168
1318
|
startAsCurrentSpan: () => startAsCurrentSpan,
|
|
1169
1319
|
startGeneration: () => startGeneration,
|
|
1170
1320
|
startSpan: () => startSpan,
|
|
1171
|
-
withSession: () => withSession
|
|
1321
|
+
withSession: () => withSession,
|
|
1322
|
+
withWorkspace: () => withWorkspace
|
|
1172
1323
|
});
|
|
1173
1324
|
module.exports = __toCommonJS(index_exports);
|
|
1174
1325
|
init_cjs_shims();
|
|
@@ -1194,6 +1345,13 @@ var Generation = class extends Observation {
|
|
|
1194
1345
|
this.span.setAttribute(GEN_AI_RESPONSE_MODEL, model);
|
|
1195
1346
|
return this;
|
|
1196
1347
|
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Token usage (`gen_ai.usage.*`). Pass provider-reported values as-is.
|
|
1350
|
+
* Per the GenAI conventions, `inputTokens` is the total including cached
|
|
1351
|
+
* tokens (the cache counts are subsets of it); providers that report an
|
|
1352
|
+
* exclusive `inputTokens` (e.g. Anthropic) are detected and normalized by
|
|
1353
|
+
* the backend, so no client-side arithmetic is needed.
|
|
1354
|
+
*/
|
|
1197
1355
|
setUsage(usage) {
|
|
1198
1356
|
if (usage.inputTokens !== void 0) {
|
|
1199
1357
|
this.span.setAttribute(GEN_AI_USAGE_INPUT_TOKENS, usage.inputTokens);
|
|
@@ -1201,6 +1359,12 @@ var Generation = class extends Observation {
|
|
|
1201
1359
|
if (usage.outputTokens !== void 0) {
|
|
1202
1360
|
this.span.setAttribute(GEN_AI_USAGE_OUTPUT_TOKENS, usage.outputTokens);
|
|
1203
1361
|
}
|
|
1362
|
+
if (usage.cacheReadInputTokens !== void 0) {
|
|
1363
|
+
this.span.setAttribute(GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, usage.cacheReadInputTokens);
|
|
1364
|
+
}
|
|
1365
|
+
if (usage.cacheWriteInputTokens !== void 0) {
|
|
1366
|
+
this.span.setAttribute(GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS, usage.cacheWriteInputTokens);
|
|
1367
|
+
}
|
|
1204
1368
|
return this;
|
|
1205
1369
|
}
|
|
1206
1370
|
/**
|
|
@@ -1286,7 +1450,8 @@ function observe(fn, options = {}) {
|
|
|
1286
1450
|
init_semconv();
|
|
1287
1451
|
init_session();
|
|
1288
1452
|
init_spans();
|
|
1289
|
-
|
|
1453
|
+
init_workspace();
|
|
1454
|
+
var VERSION = "0.4.0";
|
|
1290
1455
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1291
1456
|
0 && (module.exports = {
|
|
1292
1457
|
Generation,
|
|
@@ -1297,9 +1462,11 @@ var VERSION = "0.3.1";
|
|
|
1297
1462
|
getTracer,
|
|
1298
1463
|
init,
|
|
1299
1464
|
observe,
|
|
1465
|
+
registerWorkspace,
|
|
1300
1466
|
startAsCurrentGeneration,
|
|
1301
1467
|
startAsCurrentSpan,
|
|
1302
1468
|
startGeneration,
|
|
1303
1469
|
startSpan,
|
|
1304
|
-
withSession
|
|
1470
|
+
withSession,
|
|
1471
|
+
withWorkspace
|
|
1305
1472
|
});
|
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}.
|
|
@@ -162,9 +230,23 @@ declare class Generation extends Observation {
|
|
|
162
230
|
setInput(value: unknown): this;
|
|
163
231
|
setOutput(value: unknown): this;
|
|
164
232
|
setModel(model: string): this;
|
|
233
|
+
/**
|
|
234
|
+
* Token usage (`gen_ai.usage.*`). Pass provider-reported values as-is.
|
|
235
|
+
* Per the GenAI conventions, `inputTokens` is the total including cached
|
|
236
|
+
* tokens (the cache counts are subsets of it); providers that report an
|
|
237
|
+
* exclusive `inputTokens` (e.g. Anthropic) are detected and normalized by
|
|
238
|
+
* the backend, so no client-side arithmetic is needed.
|
|
239
|
+
*/
|
|
165
240
|
setUsage(usage: {
|
|
166
241
|
inputTokens?: number;
|
|
167
242
|
outputTokens?: number;
|
|
243
|
+
/** Input tokens served from a provider-managed prompt cache. */
|
|
244
|
+
cacheReadInputTokens?: number;
|
|
245
|
+
/**
|
|
246
|
+
* Input tokens written to a provider-managed prompt cache
|
|
247
|
+
* (called "cache creation" by Anthropic).
|
|
248
|
+
*/
|
|
249
|
+
cacheWriteInputTokens?: number;
|
|
168
250
|
}): this;
|
|
169
251
|
/**
|
|
170
252
|
* Why generation stopped (`gen_ai.response.finish_reasons`), e.g. `"stop"`,
|
|
@@ -233,6 +315,6 @@ declare function observe<F extends (...args: never[]) => unknown>(fn: F, options
|
|
|
233
315
|
declare function withSession<T>(fn: (sessionId: string) => T): T;
|
|
234
316
|
declare function withSession<T>(sessionId: string, fn: (sessionId: string) => T): T;
|
|
235
317
|
|
|
236
|
-
declare const VERSION = "0.
|
|
318
|
+
declare const VERSION = "0.4.0";
|
|
237
319
|
|
|
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 };
|
|
320
|
+
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}.
|
|
@@ -162,9 +230,23 @@ declare class Generation extends Observation {
|
|
|
162
230
|
setInput(value: unknown): this;
|
|
163
231
|
setOutput(value: unknown): this;
|
|
164
232
|
setModel(model: string): this;
|
|
233
|
+
/**
|
|
234
|
+
* Token usage (`gen_ai.usage.*`). Pass provider-reported values as-is.
|
|
235
|
+
* Per the GenAI conventions, `inputTokens` is the total including cached
|
|
236
|
+
* tokens (the cache counts are subsets of it); providers that report an
|
|
237
|
+
* exclusive `inputTokens` (e.g. Anthropic) are detected and normalized by
|
|
238
|
+
* the backend, so no client-side arithmetic is needed.
|
|
239
|
+
*/
|
|
165
240
|
setUsage(usage: {
|
|
166
241
|
inputTokens?: number;
|
|
167
242
|
outputTokens?: number;
|
|
243
|
+
/** Input tokens served from a provider-managed prompt cache. */
|
|
244
|
+
cacheReadInputTokens?: number;
|
|
245
|
+
/**
|
|
246
|
+
* Input tokens written to a provider-managed prompt cache
|
|
247
|
+
* (called "cache creation" by Anthropic).
|
|
248
|
+
*/
|
|
249
|
+
cacheWriteInputTokens?: number;
|
|
168
250
|
}): this;
|
|
169
251
|
/**
|
|
170
252
|
* Why generation stopped (`gen_ai.response.finish_reasons`), e.g. `"stop"`,
|
|
@@ -233,6 +315,6 @@ declare function observe<F extends (...args: never[]) => unknown>(fn: F, options
|
|
|
233
315
|
declare function withSession<T>(fn: (sessionId: string) => T): T;
|
|
234
316
|
declare function withSession<T>(sessionId: string, fn: (sessionId: string) => T): T;
|
|
235
317
|
|
|
236
|
-
declare const VERSION = "0.
|
|
318
|
+
declare const VERSION = "0.4.0";
|
|
237
319
|
|
|
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 };
|
|
320
|
+
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_RESPONSE_MODEL, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS, GEN_AI_INPUT_MESSAGES, GEN_AI_OUTPUT_MESSAGES, GEN_AI_RESPONSE_FINISH_REASONS, GEN_AI_TOOL_NAME, GEN_AI_REQUEST_PREFIX, MCP_RESULT_TYPE, GEN_AI_FIRST_TOKEN_EVENT, SpanKind, OPERATION_BY_KIND, CONTENT_ATTRIBUTES, CONTENT_ATTRIBUTE_PREFIXES, CONTENT_ATTRIBUTE_SUFFIXES, GLASSFLOW_SPAN_PENDING, PENDING_IDENTITY_ATTRIBUTES, PENDING_IDENTITY_PREFIXES;
|
|
347
347
|
var init_semconv = __esm({
|
|
348
348
|
"src/semconv.ts"() {
|
|
349
349
|
"use strict";
|
|
@@ -354,12 +354,15 @@ 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";
|
|
360
361
|
GEN_AI_RESPONSE_MODEL = "gen_ai.response.model";
|
|
361
362
|
GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens";
|
|
362
363
|
GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens";
|
|
364
|
+
GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens";
|
|
365
|
+
GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS = "gen_ai.usage.cache_write.input_tokens";
|
|
363
366
|
GEN_AI_INPUT_MESSAGES = "gen_ai.input.messages";
|
|
364
367
|
GEN_AI_OUTPUT_MESSAGES = "gen_ai.output.messages";
|
|
365
368
|
GEN_AI_RESPONSE_FINISH_REASONS = "gen_ai.response.finish_reasons";
|
|
@@ -387,6 +390,12 @@ var init_semconv = __esm({
|
|
|
387
390
|
OUTPUT_VALUE,
|
|
388
391
|
GEN_AI_INPUT_MESSAGES,
|
|
389
392
|
GEN_AI_OUTPUT_MESSAGES,
|
|
393
|
+
// Sensitive per the GenAI conventions (semconv-genai#431): tool definitions
|
|
394
|
+
// routinely embed proprietary prompt engineering, and sometimes credentials
|
|
395
|
+
// or internal URLs in parameter defaults. gen_ai.tool.name stays: it is
|
|
396
|
+
// identity, not content.
|
|
397
|
+
"gen_ai.tool.description",
|
|
398
|
+
"gen_ai.tool.definitions",
|
|
390
399
|
"gen_ai.prompt",
|
|
391
400
|
"gen_ai.completion",
|
|
392
401
|
"llm.input_messages",
|
|
@@ -422,7 +431,10 @@ var init_semconv = __esm({
|
|
|
422
431
|
GEN_AI_TOOL_NAME,
|
|
423
432
|
// Identity, not content: a pending span must be groupable into its
|
|
424
433
|
// session while still running, that is the live view's whole point.
|
|
425
|
-
SESSION_ID
|
|
434
|
+
SESSION_ID,
|
|
435
|
+
// Routing, not content: a crashed run's snapshot must land in the same
|
|
436
|
+
// workspace its final span would have. Stripped at export either way.
|
|
437
|
+
WORKSPACE_ROUTE
|
|
426
438
|
]);
|
|
427
439
|
PENDING_IDENTITY_PREFIXES = [GEN_AI_REQUEST_PREFIX];
|
|
428
440
|
}
|
|
@@ -997,9 +1009,131 @@ var init_session = __esm({
|
|
|
997
1009
|
}
|
|
998
1010
|
});
|
|
999
1011
|
|
|
1012
|
+
// src/workspace.ts
|
|
1013
|
+
import { context as apiContext2, createContextKey as createContextKey2, trace } from "@opentelemetry/api";
|
|
1014
|
+
function withWorkspace(alias, fn) {
|
|
1015
|
+
if (!alias) throw new Error("workspace alias must be a non-empty string");
|
|
1016
|
+
return apiContext2.with(apiContext2.active().setValue(WORKSPACE_KEY, alias), () => fn(alias));
|
|
1017
|
+
}
|
|
1018
|
+
function setGlobalRouting(routing) {
|
|
1019
|
+
globalRouting = routing;
|
|
1020
|
+
}
|
|
1021
|
+
function registerWorkspace(alias, apiKey) {
|
|
1022
|
+
if (globalRouting === void 0) {
|
|
1023
|
+
throw new Error(
|
|
1024
|
+
"[rius] workspace routing is not enabled: pass workspaces (an empty object is fine) to init() to opt in before registering destinations"
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
globalRouting.register(alias, apiKey);
|
|
1028
|
+
}
|
|
1029
|
+
var WORKSPACE_KEY, WorkspaceSpanProcessor, RoutingSpanExporter, globalRouting;
|
|
1030
|
+
var init_workspace = __esm({
|
|
1031
|
+
"src/workspace.ts"() {
|
|
1032
|
+
"use strict";
|
|
1033
|
+
init_esm_shims();
|
|
1034
|
+
init_esm();
|
|
1035
|
+
init_semconv();
|
|
1036
|
+
WORKSPACE_KEY = createContextKey2("rius-workspace-alias");
|
|
1037
|
+
WorkspaceSpanProcessor = class {
|
|
1038
|
+
onStart(span, parentContext) {
|
|
1039
|
+
const alias = parentContext.getValue(WORKSPACE_KEY);
|
|
1040
|
+
if (typeof alias !== "string") return;
|
|
1041
|
+
const parent = trace.getSpan(parentContext);
|
|
1042
|
+
const parentAlias = parent?.attributes?.[WORKSPACE_ROUTE];
|
|
1043
|
+
if (parentAlias !== void 0 && parentAlias !== alias) {
|
|
1044
|
+
console.warn(
|
|
1045
|
+
`[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.`
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
span.setAttribute(WORKSPACE_ROUTE, alias);
|
|
1049
|
+
}
|
|
1050
|
+
onEnd(_span) {
|
|
1051
|
+
}
|
|
1052
|
+
async forceFlush() {
|
|
1053
|
+
}
|
|
1054
|
+
async shutdown() {
|
|
1055
|
+
}
|
|
1056
|
+
};
|
|
1057
|
+
RoutingSpanExporter = class {
|
|
1058
|
+
constructor(defaultExporter, factory, routes = {}) {
|
|
1059
|
+
this.defaultExporter = defaultExporter;
|
|
1060
|
+
this.factory = factory;
|
|
1061
|
+
this.routes = new Map(Object.entries(routes));
|
|
1062
|
+
}
|
|
1063
|
+
defaultExporter;
|
|
1064
|
+
factory;
|
|
1065
|
+
routes;
|
|
1066
|
+
exporters = /* @__PURE__ */ new Map();
|
|
1067
|
+
warnedAliases = /* @__PURE__ */ new Set();
|
|
1068
|
+
/** Add or replace a route. Replacing supports key rotation. */
|
|
1069
|
+
register(alias, apiKey) {
|
|
1070
|
+
if (!alias || !apiKey) {
|
|
1071
|
+
throw new Error("workspace alias and apiKey must be non-empty strings");
|
|
1072
|
+
}
|
|
1073
|
+
this.routes.set(alias, apiKey);
|
|
1074
|
+
this.warnedAliases.delete(alias);
|
|
1075
|
+
}
|
|
1076
|
+
export(spans, resultCallback) {
|
|
1077
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1078
|
+
for (const span of spans) {
|
|
1079
|
+
const attributes = span.attributes;
|
|
1080
|
+
const raw = attributes[WORKSPACE_ROUTE];
|
|
1081
|
+
let destination = this.defaultExporter;
|
|
1082
|
+
if (raw !== void 0) {
|
|
1083
|
+
delete attributes[WORKSPACE_ROUTE];
|
|
1084
|
+
destination = this.resolve(String(raw));
|
|
1085
|
+
}
|
|
1086
|
+
const group = groups.get(destination);
|
|
1087
|
+
if (group === void 0) groups.set(destination, [span]);
|
|
1088
|
+
else group.push(span);
|
|
1089
|
+
}
|
|
1090
|
+
let pending = groups.size;
|
|
1091
|
+
if (pending === 0) {
|
|
1092
|
+
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
let failed;
|
|
1096
|
+
for (const [exporter, group] of groups) {
|
|
1097
|
+
exporter.export(group, (result) => {
|
|
1098
|
+
if (result.code !== ExportResultCode.SUCCESS) failed = result;
|
|
1099
|
+
pending -= 1;
|
|
1100
|
+
if (pending === 0) resultCallback(failed ?? { code: ExportResultCode.SUCCESS });
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
resolve(alias) {
|
|
1105
|
+
const apiKey = this.routes.get(alias);
|
|
1106
|
+
if (apiKey === void 0) {
|
|
1107
|
+
if (!this.warnedAliases.has(alias)) {
|
|
1108
|
+
this.warnedAliases.add(alias);
|
|
1109
|
+
console.warn(
|
|
1110
|
+
`[rius] no workspace registered for alias "${alias}"; its spans go to the default destination. Register it with registerWorkspace("${alias}", apiKey) or in init({ workspaces }).`
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
return this.defaultExporter;
|
|
1114
|
+
}
|
|
1115
|
+
let exporter = this.exporters.get(apiKey);
|
|
1116
|
+
if (exporter === void 0) {
|
|
1117
|
+
exporter = this.factory(apiKey);
|
|
1118
|
+
this.exporters.set(apiKey, exporter);
|
|
1119
|
+
}
|
|
1120
|
+
return exporter;
|
|
1121
|
+
}
|
|
1122
|
+
async forceFlush() {
|
|
1123
|
+
await this.defaultExporter.forceFlush?.();
|
|
1124
|
+
await Promise.all([...this.exporters.values()].map((e) => e.forceFlush?.()));
|
|
1125
|
+
}
|
|
1126
|
+
async shutdown() {
|
|
1127
|
+
await this.defaultExporter.shutdown();
|
|
1128
|
+
await Promise.all([...this.exporters.values()].map((e) => e.shutdown()));
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1000
1134
|
// src/client.ts
|
|
1001
1135
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1002
|
-
import { trace } from "@opentelemetry/api";
|
|
1136
|
+
import { trace as trace2 } from "@opentelemetry/api";
|
|
1003
1137
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
1004
1138
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
1005
1139
|
import {
|
|
@@ -1020,11 +1154,20 @@ function init(options = {}) {
|
|
|
1020
1154
|
const processors = new DelegatingSpanProcessor();
|
|
1021
1155
|
const authHeaders = config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {};
|
|
1022
1156
|
let health;
|
|
1157
|
+
let routing;
|
|
1023
1158
|
if (!config.disabled) {
|
|
1024
|
-
|
|
1159
|
+
let base = options.spanExporter ?? new OTLPTraceExporter({
|
|
1025
1160
|
url: `${config.endpoint}/v1/traces`,
|
|
1026
1161
|
headers: config.apiKey ? authHeaders : void 0
|
|
1027
1162
|
});
|
|
1163
|
+
if (options.workspaces !== void 0) {
|
|
1164
|
+
const factory = options.workspaceExporterFactory ?? ((apiKey) => new OTLPTraceExporter({
|
|
1165
|
+
url: `${config.endpoint}/v1/traces`,
|
|
1166
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1167
|
+
}));
|
|
1168
|
+
routing = new RoutingSpanExporter(base, factory, options.workspaces);
|
|
1169
|
+
base = routing;
|
|
1170
|
+
}
|
|
1028
1171
|
health = new ExportOutcomeExporter(base);
|
|
1029
1172
|
const exporter = !config.captureContent || config.mask !== void 0 ? new MaskingSpanExporter(health, {
|
|
1030
1173
|
captureContent: config.captureContent,
|
|
@@ -1032,6 +1175,9 @@ function init(options = {}) {
|
|
|
1032
1175
|
}) : health;
|
|
1033
1176
|
const batch = new BatchSpanProcessor(exporter);
|
|
1034
1177
|
processors.add(new SessionSpanProcessor(config.sessionId));
|
|
1178
|
+
if (routing !== void 0) {
|
|
1179
|
+
processors.add(new WorkspaceSpanProcessor());
|
|
1180
|
+
}
|
|
1035
1181
|
if (config.partialSpans) {
|
|
1036
1182
|
processors.add(new PendingSpanProcessor(batch, { delayMs: config.partialSpansDelayMs }));
|
|
1037
1183
|
}
|
|
@@ -1074,10 +1220,11 @@ function init(options = {}) {
|
|
|
1074
1220
|
heartbeat = { sender, beforeExitHandler };
|
|
1075
1221
|
}
|
|
1076
1222
|
globalClient = createClient({ provider, processors, health, ready, heartbeat });
|
|
1223
|
+
setGlobalRouting(routing);
|
|
1077
1224
|
return globalClient;
|
|
1078
1225
|
}
|
|
1079
1226
|
function getTracer() {
|
|
1080
|
-
return
|
|
1227
|
+
return trace2.getTracer(TRACER_NAME);
|
|
1081
1228
|
}
|
|
1082
1229
|
var sinks, heartbeats, createClient, RiusClient, globalClient;
|
|
1083
1230
|
var init_client = __esm({
|
|
@@ -1093,6 +1240,7 @@ var init_client = __esm({
|
|
|
1093
1240
|
init_pending();
|
|
1094
1241
|
init_semconv();
|
|
1095
1242
|
init_session();
|
|
1243
|
+
init_workspace();
|
|
1096
1244
|
sinks = /* @__PURE__ */ new WeakMap();
|
|
1097
1245
|
heartbeats = /* @__PURE__ */ new WeakMap();
|
|
1098
1246
|
RiusClient = class _RiusClient {
|
|
@@ -1136,7 +1284,8 @@ var init_client = __esm({
|
|
|
1136
1284
|
} finally {
|
|
1137
1285
|
if (globalClient === this) {
|
|
1138
1286
|
globalClient = void 0;
|
|
1139
|
-
|
|
1287
|
+
setGlobalRouting(void 0);
|
|
1288
|
+
trace2.disable();
|
|
1140
1289
|
}
|
|
1141
1290
|
}
|
|
1142
1291
|
}
|
|
@@ -1168,6 +1317,13 @@ var Generation = class extends Observation {
|
|
|
1168
1317
|
this.span.setAttribute(GEN_AI_RESPONSE_MODEL, model);
|
|
1169
1318
|
return this;
|
|
1170
1319
|
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Token usage (`gen_ai.usage.*`). Pass provider-reported values as-is.
|
|
1322
|
+
* Per the GenAI conventions, `inputTokens` is the total including cached
|
|
1323
|
+
* tokens (the cache counts are subsets of it); providers that report an
|
|
1324
|
+
* exclusive `inputTokens` (e.g. Anthropic) are detected and normalized by
|
|
1325
|
+
* the backend, so no client-side arithmetic is needed.
|
|
1326
|
+
*/
|
|
1171
1327
|
setUsage(usage) {
|
|
1172
1328
|
if (usage.inputTokens !== void 0) {
|
|
1173
1329
|
this.span.setAttribute(GEN_AI_USAGE_INPUT_TOKENS, usage.inputTokens);
|
|
@@ -1175,6 +1331,12 @@ var Generation = class extends Observation {
|
|
|
1175
1331
|
if (usage.outputTokens !== void 0) {
|
|
1176
1332
|
this.span.setAttribute(GEN_AI_USAGE_OUTPUT_TOKENS, usage.outputTokens);
|
|
1177
1333
|
}
|
|
1334
|
+
if (usage.cacheReadInputTokens !== void 0) {
|
|
1335
|
+
this.span.setAttribute(GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, usage.cacheReadInputTokens);
|
|
1336
|
+
}
|
|
1337
|
+
if (usage.cacheWriteInputTokens !== void 0) {
|
|
1338
|
+
this.span.setAttribute(GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS, usage.cacheWriteInputTokens);
|
|
1339
|
+
}
|
|
1178
1340
|
return this;
|
|
1179
1341
|
}
|
|
1180
1342
|
/**
|
|
@@ -1260,7 +1422,8 @@ function observe(fn, options = {}) {
|
|
|
1260
1422
|
init_semconv();
|
|
1261
1423
|
init_session();
|
|
1262
1424
|
init_spans();
|
|
1263
|
-
|
|
1425
|
+
init_workspace();
|
|
1426
|
+
var VERSION = "0.4.0";
|
|
1264
1427
|
export {
|
|
1265
1428
|
Generation,
|
|
1266
1429
|
Observation,
|
|
@@ -1270,9 +1433,11 @@ export {
|
|
|
1270
1433
|
getTracer,
|
|
1271
1434
|
init,
|
|
1272
1435
|
observe,
|
|
1436
|
+
registerWorkspace,
|
|
1273
1437
|
startAsCurrentGeneration,
|
|
1274
1438
|
startAsCurrentSpan,
|
|
1275
1439
|
startGeneration,
|
|
1276
1440
|
startSpan,
|
|
1277
|
-
withSession
|
|
1441
|
+
withSession,
|
|
1442
|
+
withWorkspace
|
|
1278
1443
|
};
|