@glassflow-ai/rius 0.3.0 → 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 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
- const base = options.spanExporter ?? new import_exporter_trace_otlp_proto.OTLPTraceExporter({
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 import_api6.trace.getTracer(TRACER_NAME);
1229
+ return import_api7.trace.getTracer(TRACER_NAME);
1083
1230
  }
1084
- var import_node_crypto2, import_api6, import_exporter_trace_otlp_proto, import_resources, import_sdk_trace_base, import_sdk_trace_node, import_semantic_conventions, sinks, heartbeats, createClient, RiusClient, globalClient;
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
- import_api6 = require("@opentelemetry/api");
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
- import_api6.trace.disable();
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
- var VERSION = "0.3.0";
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.0";
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.3.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.3.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
- const base = options.spanExporter ?? new OTLPTraceExporter({
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 trace.getTracer(TRACER_NAME);
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
- trace.disable();
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
- var VERSION = "0.3.0";
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glassflow-ai/rius",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "OpenTelemetry-native tracing for AI agents and LLM applications",
5
5
  "keywords": [
6
6
  "opentelemetry",
@@ -59,9 +59,9 @@
59
59
  "@opentelemetry/semantic-conventions": "^1.43.0"
60
60
  },
61
61
  "peerDependencies": {
62
- "@arizeai/openinference-instrumentation-anthropic": "^0.1.20",
62
+ "@arizeai/openinference-instrumentation-anthropic": "^0.2.1",
63
63
  "@arizeai/openinference-instrumentation-langchain": "^4.0.17",
64
- "@arizeai/openinference-instrumentation-openai": "^4.1.8",
64
+ "@arizeai/openinference-instrumentation-openai": "^4.2.1",
65
65
  "@arizeai/openinference-vercel": "^3.1.4",
66
66
  "@langchain/core": "^1.0.0 || ^0.3.0",
67
67
  "@modelcontextprotocol/sdk": "^1.30.0",
@@ -89,9 +89,9 @@
89
89
  },
90
90
  "devDependencies": {
91
91
  "@anthropic-ai/sdk": "^0.65.0",
92
- "@arizeai/openinference-instrumentation-anthropic": "0.1.20",
92
+ "@arizeai/openinference-instrumentation-anthropic": "0.2.1",
93
93
  "@arizeai/openinference-instrumentation-langchain": "4.0.17",
94
- "@arizeai/openinference-instrumentation-openai": "^4.1.8",
94
+ "@arizeai/openinference-instrumentation-openai": "^4.2.1",
95
95
  "@arizeai/openinference-vercel": "^3.1.4",
96
96
  "@biomejs/biome": "^1.9.0",
97
97
  "@langchain/core": "^1.2.5",