@dbx-tools/appkit-mastra 0.9.64 → 0.9.67

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/package.json CHANGED
@@ -19,8 +19,7 @@
19
19
  "projen": "projen"
20
20
  },
21
21
  "devDependencies": {
22
- "@opentelemetry/sdk-trace-base": "2.8.0",
23
- "@opentelemetry/sdk-trace-node": "2.8.0",
22
+ "@mlflow/core": "0.4.0",
24
23
  "@types/express": "^5.0.5",
25
24
  "@types/node": "^24.6.0",
26
25
  "@types/pg": "^8",
@@ -29,23 +28,24 @@
29
28
  "vitest": "3.2.4"
30
29
  },
31
30
  "peerDependencies": {
31
+ "@mlflow/core": "0.4.0",
32
32
  "skills": "^1"
33
33
  },
34
34
  "dependencies": {
35
35
  "@databricks/appkit": "0.81.0",
36
- "@dbx-tools/appkit": "^0.9.64",
37
- "@dbx-tools/core": "^0.9.64",
38
- "@dbx-tools/databricks": "^0.9.64",
39
- "@dbx-tools/fs": "^0.9.64",
40
- "@dbx-tools/genie": "^0.9.64",
41
- "@dbx-tools/model": "^0.9.64",
42
- "@dbx-tools/path": "^0.9.64",
43
- "@dbx-tools/postgres": "^0.9.64",
44
- "@dbx-tools/shared-core": "^0.9.64",
45
- "@dbx-tools/shared-fs": "^0.9.64",
46
- "@dbx-tools/shared-genie": "^0.9.64",
47
- "@dbx-tools/shared-mastra": "^0.9.64",
48
- "@dbx-tools/shared-model": "^0.9.64",
36
+ "@dbx-tools/appkit": "^0.9.67",
37
+ "@dbx-tools/core": "^0.9.67",
38
+ "@dbx-tools/databricks": "^0.9.67",
39
+ "@dbx-tools/fs": "^0.9.67",
40
+ "@dbx-tools/genie": "^0.9.67",
41
+ "@dbx-tools/model": "^0.9.67",
42
+ "@dbx-tools/path": "^0.9.67",
43
+ "@dbx-tools/postgres": "^0.9.67",
44
+ "@dbx-tools/shared-core": "^0.9.67",
45
+ "@dbx-tools/shared-fs": "^0.9.67",
46
+ "@dbx-tools/shared-genie": "^0.9.67",
47
+ "@dbx-tools/shared-mastra": "^0.9.67",
48
+ "@dbx-tools/shared-model": "^0.9.67",
49
49
  "@mastra/ai-sdk": "1.10.5",
50
50
  "@mastra/core": "1.71.0",
51
51
  "@mastra/express": "1.5.15",
@@ -58,6 +58,8 @@
58
58
  "@modelcontextprotocol/sdk": "^1.29.0",
59
59
  "@opentelemetry/api": "^1.9.1",
60
60
  "@opentelemetry/core": "2.11.0",
61
+ "@opentelemetry/sdk-trace-base": "2.8.0",
62
+ "@opentelemetry/sdk-trace-node": "2.8.0",
61
63
  "@pydantic/monty": "0.0.23",
62
64
  "express": "^5.1.0",
63
65
  "pg": "^8.22.0",
@@ -194,6 +196,10 @@
194
196
  "types": "./lib/src/telemetry.d.ts",
195
197
  "default": "./lib/src/telemetry.js"
196
198
  },
199
+ "./trace-attributes": {
200
+ "types": "./lib/src/trace-attributes.d.ts",
201
+ "default": "./lib/src/trace-attributes.js"
202
+ },
197
203
  "./validation": {
198
204
  "types": "./lib/src/validation.d.ts",
199
205
  "default": "./lib/src/validation.js"
@@ -209,7 +215,7 @@
209
215
  "./package.json": "./package.json"
210
216
  }
211
217
  },
212
- "version": "0.9.64",
218
+ "version": "0.9.67",
213
219
  "types": "./lib/index.d.ts",
214
220
  "dbxToolsConfig": {
215
221
  "tags": [
@@ -342,6 +348,10 @@
342
348
  "types": "./lib/src/telemetry.d.ts",
343
349
  "default": "./lib/src/telemetry.js"
344
350
  },
351
+ "./trace-attributes": {
352
+ "types": "./lib/src/trace-attributes.d.ts",
353
+ "default": "./lib/src/trace-attributes.js"
354
+ },
345
355
  "./validation": {
346
356
  "types": "./lib/src/validation.d.ts",
347
357
  "default": "./lib/src/validation.js"
@@ -359,11 +369,15 @@
359
369
  "files": [
360
370
  "index.ts",
361
371
  "src",
372
+ "exports.ts",
362
373
  "lib"
363
374
  ],
364
375
  "peerDependenciesMeta": {
365
376
  "skills": {
366
377
  "optional": true
378
+ },
379
+ "@mlflow/core": {
380
+ "optional": true
367
381
  }
368
382
  },
369
383
  "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"bunx projen\"."
package/src/genie.ts CHANGED
@@ -59,7 +59,7 @@ import { chartPlannerRequestSchema, chartToolOutputSchema, prepareChart } from "
59
59
  import { MASTRA_USER_KEY, resolveUserKey } from "./config.ts";
60
60
  import type { MastraPluginConfig, User } from "./config.ts";
61
61
  import { fetchStatementData } from "./statement.ts";
62
- import { stampGenieToolSpan } from "./telemetry.ts";
62
+ import { recordActiveTraceAuth, stampGenieToolSpan } from "./telemetry.ts";
63
63
  import { safeWriteProgress } from "./writer.ts";
64
64
 
65
65
  const logger = log.logger("mastra/genie");
@@ -128,6 +128,7 @@ function requireClient(
128
128
  "Invoke the tool from an agent turn served by the mastra plugin, which stamps the AppKit user on the Mastra request context.",
129
129
  );
130
130
  }
131
+ recordActiveTraceAuth("isUserContext" in user.executionContext ? "obo" : "service-principal");
131
132
  return { client: user.executionContext.client, requestContext };
132
133
  }
133
134
 
package/src/mlflow.ts CHANGED
@@ -27,13 +27,92 @@
27
27
 
28
28
  import { ConfigurationError } from "@databricks/appkit";
29
29
  import { appkit } from "@dbx-tools/appkit";
30
- import { asyncUtils, errorUtils, log, object } from "@dbx-tools/shared-core";
30
+ import { asyncUtils, environmentUtils, errorUtils, log, object } from "@dbx-tools/shared-core";
31
31
  import { feedback } from "@dbx-tools/shared-mastra";
32
+ import type { Context } from "@opentelemetry/api";
33
+ import type {
34
+ ReadableSpan,
35
+ Span as SdkSpan,
36
+ SpanProcessor,
37
+ } from "@opentelemetry/sdk-trace-base";
38
+ import type { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
32
39
  import { z } from "zod";
33
40
  import { databricksFetch, readResponseJson, readResponseText } from "./rest.ts";
41
+ import { MLFLOW_AGENT_TAG_ATTR } from "./trace-attributes.ts";
34
42
 
35
43
  const logger = log.logger("mastra/mlflow");
36
44
 
45
+ type MlflowModule = typeof import("@mlflow/core");
46
+ type UpdateCurrentTraceOptions = Parameters<MlflowModule["updateCurrentTrace"]>[0];
47
+
48
+ let directMlflow: MlflowModule | undefined;
49
+ let directMlflowInitStarted = false;
50
+ let directMlflowProvider: NodeTracerProvider | undefined;
51
+ let directMlflowProcessor: SpanProcessor | undefined;
52
+
53
+ const NOISY_INSTRUMENTATION_SCOPES = new Set([
54
+ "@opentelemetry/instrumentation-express",
55
+ "@opentelemetry/instrumentation-http",
56
+ "cache-manager-cache-manager",
57
+ ]);
58
+
59
+ function isAgentTraceRoot(span: SdkSpan): boolean {
60
+ return span.attributes[MLFLOW_AGENT_TAG_ATTR] === "true";
61
+ }
62
+
63
+ function isUsefulAgentDescendant(span: SdkSpan): boolean {
64
+ return !NOISY_INSTRUMENTATION_SCOPES.has(span.instrumentationScope.name);
65
+ }
66
+
67
+ /**
68
+ * Restrict the direct MLflow provider to chat roots and their descendants.
69
+ * AppKit and Mastra create other OTel roots for cache and plugin operations;
70
+ * forwarding those would recreate the one-trace-per-request noise this path is
71
+ * intended to avoid.
72
+ */
73
+ export class AgentTraceSpanProcessor implements SpanProcessor {
74
+ private readonly traceIds = new Set<string>();
75
+ private readonly spanIds = new Set<string>();
76
+
77
+ constructor(private readonly delegate: SpanProcessor) {}
78
+
79
+ onStart(span: SdkSpan, parentContext: Context): void {
80
+ const traceId = span.spanContext().traceId;
81
+ const isRoot = !span.parentSpanContext?.spanId;
82
+ if (isRoot) {
83
+ if (!isAgentTraceRoot(span)) return;
84
+ this.traceIds.add(traceId);
85
+ } else if (!this.traceIds.has(traceId) || !isUsefulAgentDescendant(span)) {
86
+ return;
87
+ }
88
+ this.spanIds.add(span.spanContext().spanId);
89
+ this.delegate.onStart(span, parentContext);
90
+ }
91
+
92
+ onEnding(span: SdkSpan): void {
93
+ if (!this.spanIds.has(span.spanContext().spanId)) return;
94
+ this.delegate.onEnding?.(span);
95
+ }
96
+
97
+ onEnd(span: ReadableSpan): void {
98
+ const traceId = span.spanContext().traceId;
99
+ const spanId = span.spanContext().spanId;
100
+ if (!this.spanIds.delete(spanId)) return;
101
+ this.delegate.onEnd(span);
102
+ if (!span.parentSpanContext?.spanId) this.traceIds.delete(traceId);
103
+ }
104
+
105
+ forceFlush(): Promise<void> {
106
+ return this.delegate.forceFlush();
107
+ }
108
+
109
+ shutdown(): Promise<void> {
110
+ this.traceIds.clear();
111
+ this.spanIds.clear();
112
+ return this.delegate.shutdown();
113
+ }
114
+ }
115
+
37
116
  /** Workspace client carried on an AppKit execution context. */
38
117
  type WorkspaceClient = appkit.WorkspaceClientLike;
39
118
 
@@ -214,14 +293,172 @@ function configuredExperiment(): { id?: string; name?: string } {
214
293
  };
215
294
  }
216
295
 
296
+ /** Tracking URI for direct local MLflow tracing, with CLI-profile auth injected. */
297
+ export function directMlflowTrackingUri(): string | undefined {
298
+ if (environmentUtils.isDatabricksAppEnv()) return undefined;
299
+ const configured = process.env.MLFLOW_TRACKING_URI?.trim();
300
+ if (configured && configured !== "databricks") return configured;
301
+ const profile = process.env.DATABRICKS_CONFIG_PROFILE?.trim();
302
+ return profile ? `databricks://${profile}` : "databricks";
303
+ }
304
+
305
+ /** Parse the optional UC table-prefix location used by direct local tracing. */
306
+ export function directMlflowTraceLocation():
307
+ | { catalogName: string; schemaName: string; tablePrefix: string }
308
+ | undefined {
309
+ const catalogName = process.env.MLFLOW_UC_CATALOG?.trim();
310
+ const schemaName = process.env.MLFLOW_UC_SCHEMA?.trim();
311
+ const tablePrefix = process.env.MLFLOW_UC_TABLE_PREFIX?.trim();
312
+ if (catalogName && schemaName && tablePrefix) {
313
+ return { catalogName, schemaName, tablePrefix };
314
+ }
315
+
316
+ const prefix = normalizeUcPrefix(process.env.MLFLOW_UC_TRACE_PREFIX);
317
+ if (!prefix) return undefined;
318
+ const parts = prefix.split(".");
319
+ if (parts.length !== 3) return undefined;
320
+ const [prefixCatalog, prefixSchema, prefixTable] = parts;
321
+ if (!prefixCatalog || !prefixSchema || !prefixTable) return undefined;
322
+ return {
323
+ catalogName: prefixCatalog,
324
+ schemaName: prefixSchema,
325
+ tablePrefix: prefixTable,
326
+ };
327
+ }
328
+
329
+ /** Whether the local direct-to-experiment MLflow SDK has enough configuration. */
330
+ export function directMlflowTracingConfigured(): boolean {
331
+ return Boolean(
332
+ !environmentUtils.isDatabricksAppEnv() &&
333
+ process.env.MLFLOW_EXPERIMENT_ID?.trim() &&
334
+ directMlflowTrackingUri(),
335
+ );
336
+ }
337
+
338
+ async function detectedDirectMlflowTraceLocation(
339
+ mlflow: MlflowModule,
340
+ trackingUri: string,
341
+ experimentId: string,
342
+ host: string | undefined,
343
+ ): Promise<{ catalogName: string; schemaName: string; tablePrefix: string } | undefined> {
344
+ const explicit = directMlflowTraceLocation();
345
+ if (explicit) return explicit;
346
+ try {
347
+ const authProvider = mlflow.createAuthProvider({
348
+ trackingUri,
349
+ ...(host ? { host } : {}),
350
+ });
351
+ const client = new mlflow.MlflowClient({ trackingUri, authProvider });
352
+ const experiment = await client.getExperiment(experimentId);
353
+ if (!experiment) return undefined;
354
+ const { ucLocationFromExperimentTags } = await import("@mlflow/core/dist/core/destination");
355
+ const location = ucLocationFromExperimentTags(experiment.tags);
356
+ if (!location?.tablePrefix) return undefined;
357
+ return {
358
+ catalogName: location.catalogName,
359
+ schemaName: location.schemaName,
360
+ tablePrefix: location.tablePrefix,
361
+ };
362
+ } catch (err) {
363
+ logger.warn("direct MLflow UC trace-location detection failed", {
364
+ experimentId,
365
+ error: errorUtils.errorMessage(err),
366
+ });
367
+ return undefined;
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Start the MLflow Node tracer for local development when AppKit has no OTLP
373
+ * exporter. This provider is never started in the deployed Apps path, avoiding
374
+ * a second global OTel provider racing AppKit's telemetry manager.
375
+ */
376
+ export async function initializeDirectMlflowTracing(): Promise<boolean> {
377
+ if (directMlflow) return true;
378
+ if (directMlflowInitStarted || !directMlflowTracingConfigured()) return false;
379
+ directMlflowInitStarted = true;
380
+
381
+ const experimentId = process.env.MLFLOW_EXPERIMENT_ID!.trim();
382
+ const trackingUri = directMlflowTrackingUri()!;
383
+ try {
384
+ const mlflow = await import("@mlflow/core");
385
+ const rawHost = process.env.DATABRICKS_HOST?.trim();
386
+ const host = rawHost
387
+ ? /^https?:\/\//i.test(rawHost)
388
+ ? rawHost
389
+ : `https://${rawHost}`
390
+ : undefined;
391
+ const traceLocation = await detectedDirectMlflowTraceLocation(
392
+ mlflow,
393
+ trackingUri,
394
+ experimentId,
395
+ host,
396
+ );
397
+ if (!traceLocation) {
398
+ throw new ConfigurationError(
399
+ "Direct local MLflow tracing requires a UC-linked experiment or MLFLOW_UC_TRACE_PREFIX.",
400
+ );
401
+ }
402
+ const [{ DatabricksUCTableSpanExporter, DatabricksUCTableSpanProcessor }, sdk] =
403
+ await Promise.all([
404
+ import("@mlflow/core/dist/exporters/uc_table"),
405
+ import("@opentelemetry/sdk-trace-node"),
406
+ ]);
407
+ const authProvider = mlflow.createAuthProvider({
408
+ trackingUri,
409
+ ...(host ? { host } : {}),
410
+ });
411
+ const client = new mlflow.MlflowClient({ trackingUri, authProvider });
412
+ const exporter = new DatabricksUCTableSpanExporter(client);
413
+ const processor = new AgentTraceSpanProcessor(
414
+ new DatabricksUCTableSpanProcessor(exporter, traceLocation),
415
+ );
416
+ const provider = new sdk.NodeTracerProvider({ spanProcessors: [processor] });
417
+ provider.register();
418
+ directMlflow = mlflow;
419
+ directMlflowProvider = provider;
420
+ directMlflowProcessor = processor;
421
+ logger.info("direct MLflow tracing enabled", {
422
+ experimentId,
423
+ trackingUri,
424
+ traceLocation,
425
+ });
426
+ return true;
427
+ } catch (err) {
428
+ logger.warn("direct MLflow tracing disabled", {
429
+ error: errorUtils.errorMessage(err),
430
+ });
431
+ return false;
432
+ }
433
+ }
434
+
435
+ /** Persist trace-level fields on the direct MLflow trace in the current OTel context. */
436
+ export function updateActiveMlflowTrace(options: UpdateCurrentTraceOptions): void {
437
+ directMlflow?.updateCurrentTrace(options);
438
+ }
439
+
440
+ /** Persist tags on the direct MLflow trace active in the current OTel context. */
441
+ export function updateActiveMlflowTraceTags(tags: Record<string, string>): void {
442
+ updateActiveMlflowTrace({ tags });
443
+ }
444
+
445
+ /** Whether this process owns the filtered local MLflow tracer provider. */
446
+ export function directMlflowTracingActive(): boolean {
447
+ return directMlflowProcessor !== undefined;
448
+ }
449
+
450
+ /** Flush pending direct local trace exports during graceful shutdown. */
451
+ export async function flushDirectMlflowTracing(): Promise<void> {
452
+ await directMlflowProcessor?.forceFlush();
453
+ await directMlflowProvider?.forceFlush();
454
+ }
455
+
217
456
  /**
218
457
  * Whether MLflow feedback logging is available for this deployment.
219
458
  *
220
- * Enabled when an OTLP exporter endpoint is configured (traces are
221
- * actually shipped somewhere) AND an MLflow experiment is named - the
222
- * two signals that the OTLP backend is MLflow and traces will
223
- * materialize there. Both are standard env vars, so no plugin config is
224
- * required; a deployment opts in simply by wiring MLflow tracing.
459
+ * Enabled when an MLflow experiment is configured and traces have an export
460
+ * path: AppKit OTLP in a Databricks App, or the direct MLflow Node SDK outside
461
+ * one. Both paths are selected from standard environment values.
225
462
  */
226
463
  export function mlflowEnabled(): boolean {
227
464
  const hasExporter = Boolean(
@@ -231,7 +468,7 @@ export function mlflowEnabled(): boolean {
231
468
  const hasExperiment = Boolean(
232
469
  process.env.MLFLOW_EXPERIMENT_ID?.trim() || process.env.MLFLOW_EXPERIMENT_NAME?.trim(),
233
470
  );
234
- return hasExporter && hasExperiment;
471
+ return hasExperiment && (hasExporter || directMlflowTracingConfigured());
235
472
  }
236
473
 
237
474
  /**
package/src/model.ts CHANGED
@@ -37,6 +37,7 @@ import {
37
37
  rewriteServingResponseStream,
38
38
  } from "./serving-sanitize.ts";
39
39
  import { MASTRA_MODEL_OVERRIDE_KEY, resolveServingConfig } from "./serving.ts";
40
+ import { recordActiveTraceAuth, recordActiveTraceModel } from "./telemetry.ts";
40
41
 
41
42
  type ModelClass = model.ModelClass;
42
43
  const { parseModelClass } = classes;
@@ -135,6 +136,7 @@ export async function buildModel(
135
136
  // (the active OBO scope, or the service principal) when it's absent.
136
137
  const user = requestContext.get(MASTRA_USER_KEY) as User | undefined;
137
138
  const executionContext = user?.executionContext ?? getExecutionContext();
139
+ recordActiveTraceAuth("isUserContext" in executionContext ? "obo" : "service-principal");
138
140
  const clientConfig = executionContext.client.config;
139
141
  const host = (await clientConfig.getHost()).toString();
140
142
  const headers = new Headers();
@@ -165,6 +167,7 @@ export async function buildModel(
165
167
  selectionInput(config, requested, overrides.modelClass, serving),
166
168
  );
167
169
  logger.debug("model selected", { modelId, source, requested });
170
+ recordActiveTraceModel(modelId);
168
171
 
169
172
  return {
170
173
  providerId: config.providerId ?? "databricks",
@@ -49,7 +49,7 @@ import { OtelBridge } from "@mastra/otel-bridge";
49
49
  import { propagation } from "@opentelemetry/api";
50
50
 
51
51
  import { TRACE_REQUEST_CONTEXT_KEYS } from "./config.ts";
52
- import { mlflowEnabled } from "./mlflow.ts";
52
+ import { initializeDirectMlflowTracing, mlflowEnabled } from "./mlflow.ts";
53
53
 
54
54
  const logger = log.logger("mastra/observability");
55
55
 
@@ -117,7 +117,8 @@ export function configureOtelPropagation(): boolean {
117
117
  * OTel pipeline via `@mastra/otel-bridge`.
118
118
  *
119
119
  * Returns `undefined` when tracing is off: either the caller passed
120
- * `enabled: false`, or auto mode finds no OTLP endpoint (the default).
120
+ * `enabled: false`, or auto mode finds neither AppKit OTLP nor a local MLflow
121
+ * experiment.
121
122
  * Skipping the bridge in that case avoids `[OtelBridge] No OTEL span
122
123
  * found` log spam from Mastra spans that have no parent on the noop
123
124
  * tracer.
@@ -128,13 +129,17 @@ export async function buildObservability(
128
129
  const otelBase = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
129
130
  const otelTracesOverride = process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT;
130
131
  const otlpConfigured = isOtlpTracingConfigured();
132
+ const directMlflowConfigured =
133
+ options?.enabled === false || otlpConfigured ? false : await initializeDirectMlflowTracing();
134
+ if (directMlflowConfigured) configureOtelPropagation();
135
+ const tracingConfigured = otlpConfigured || directMlflowConfigured;
131
136
 
132
- if (options?.enabled === false || (options?.enabled !== true && !otlpConfigured)) {
137
+ if (options?.enabled === false || (options?.enabled !== true && !tracingConfigured)) {
133
138
  logger.info("Mastra observability off", {
134
139
  reason:
135
140
  options?.enabled === false
136
141
  ? "disabled in plugin config"
137
- : "OTEL_EXPORTER_OTLP_ENDPOINT unset",
142
+ : "no OTLP endpoint or direct MLflow experiment configured",
138
143
  });
139
144
  return undefined;
140
145
  }
@@ -164,7 +169,7 @@ export async function buildObservability(
164
169
  otelBase: otelBase ?? "<unset>",
165
170
  resolvedTracesUrl: resolvedTracesUrl ?? "<unset>",
166
171
  feedback,
167
- observability: feedback ? "mlflow" : "otel",
172
+ observability: directMlflowConfigured ? "mlflow-direct" : feedback ? "mlflow" : "otel",
168
173
  });
169
174
 
170
175
  return new Observability({
package/src/plugin.ts CHANGED
@@ -100,6 +100,7 @@ import {
100
100
  import { buildMcpServer, type ResolvedMcp } from "./mcp.ts";
101
101
  import { createMemoryBuilder, createServicePrincipalPool, needsLakebase } from "./memory.ts";
102
102
  import {
103
+ flushDirectMlflowTracing,
103
104
  logFeedback,
104
105
  mlflowExperimentManagerUrl,
105
106
  resolveFeedbackEnabled,
@@ -304,6 +305,13 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
304
305
  * shutdown budget.
305
306
  */
306
307
  async shutdown(): Promise<void> {
308
+ try {
309
+ await flushDirectMlflowTracing();
310
+ } catch (err) {
311
+ this.logger.warn("error flushing direct MLflow traces", {
312
+ error: errorUtils.errorMessage(err),
313
+ });
314
+ }
307
315
  const pool = this.servicePrincipalPool;
308
316
  if (!pool) return;
309
317
  this.servicePrincipalPool = null;
package/src/server.ts CHANGED
@@ -29,6 +29,7 @@ import {
29
29
  } from "./config.ts";
30
30
  import { requestUserEmail, requestUserId } from "./identity.ts";
31
31
  import { resolveFeedbackEnabled } from "./mlflow.ts";
32
+ import { recordActiveTraceAuth, recordActiveTraceUser } from "./telemetry.ts";
32
33
 
33
34
  import {
34
35
  extractModelOverride,
@@ -222,6 +223,18 @@ export class MastraServer extends ExpressMastraServer {
222
223
  userId: requestUserId(req),
223
224
  email: requestUserEmail(req),
224
225
  });
226
+ const user = requestContext.get(MASTRA_USER_KEY) as User | undefined;
227
+ if (user) {
228
+ recordActiveTraceAuth(
229
+ "isUserContext" in user.executionContext ? "obo" : "service-principal",
230
+ );
231
+ }
232
+ const traceUser = [
233
+ requestContext.get(MASTRA_USER_EMAIL_KEY),
234
+ requestContext.get(MASTRA_USER_NAME_KEY),
235
+ requestContext.get(MASTRA_RESOURCE_ID_KEY),
236
+ ].find((value): value is string => typeof value === "string" && value.trim().length > 0);
237
+ recordActiveTraceUser(traceUser);
225
238
  }
226
239
 
227
240
  /**