@dbx-tools/appkit-mastra 0.1.13 → 0.1.19

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.
@@ -19,6 +19,18 @@ export declare class MastraServer extends MastraServerExpress {
19
19
  constructor(config: MastraPluginConfig, ...args: ConstructorParameters<typeof MastraServerExpress>);
20
20
  registerAuthMiddleware(): void;
21
21
  configureRequestContextUser(requestContext: RequestContext): void;
22
+ /**
23
+ * Stamp a per-request id and echo it on the response so an upstream
24
+ * proxy / curl client / browser-side log line can pair its view of
25
+ * the request with the matching trace span. Reuses `X-Request-Id`
26
+ * when the upstream already supplies one so multi-hop traces stay
27
+ * joined; otherwise mints a UUIDv4.
28
+ *
29
+ * The id is surfaced as `mastra__requestId` span metadata via
30
+ * {@link TRACE_REQUEST_CONTEXT_KEYS} and as the `X-Request-Id`
31
+ * response header so dev tools can copy it from either side.
32
+ */
33
+ configureRequestContextRequestId(req: express.Request, res: express.Response, requestContext: RequestContext): void;
22
34
  configureRequestContextThreadId(req: express.Request, res: express.Response, requestContext: RequestContext): void;
23
35
  configureRequestContextModelOverride(req: express.Request, requestContext: RequestContext): void;
24
36
  }
@@ -5,11 +5,11 @@
5
5
  * point.
6
6
  */
7
7
  import { getExecutionContext } from "@databricks/appkit";
8
- import { httpUtils, logUtils, stringUtils } from "@dbx-tools/appkit-shared";
8
+ import { httpUtils, logUtils, stringUtils } from "@dbx-tools/shared";
9
9
  import { MASTRA_RESOURCE_ID_KEY, MASTRA_THREAD_ID_KEY, } from "@mastra/core/request-context";
10
10
  import { MastraServer as MastraServerExpress } from "@mastra/express";
11
11
  import { randomUUID } from "node:crypto";
12
- import { MASTRA_USER_KEY } from "./config.js";
12
+ import { MASTRA_REQUEST_ID_KEY, MASTRA_USER_EMAIL_KEY, MASTRA_USER_KEY, MASTRA_USER_NAME_KEY, } from "./config.js";
13
13
  import { extractModelOverride, MASTRA_MODEL_OVERRIDE_KEY, resolveServingConfig, } from "./serving.js";
14
14
  /**
15
15
  * `@mastra/express` subclass that stamps `RequestContext` with the
@@ -31,11 +31,15 @@ export class MastraServer extends MastraServerExpress {
31
31
  this.configureRequestContextUser(requestContext);
32
32
  this.configureRequestContextThreadId(req, res, requestContext);
33
33
  this.configureRequestContextModelOverride(req, requestContext);
34
+ this.configureRequestContextRequestId(req, res, requestContext);
34
35
  this.log.debug("auth:middleware", {
35
36
  method: req.method,
36
37
  path: req.path,
38
+ requestId: requestContext.get(MASTRA_REQUEST_ID_KEY),
37
39
  threadId: requestContext.get(MASTRA_THREAD_ID_KEY),
38
40
  resourceId: requestContext.get(MASTRA_RESOURCE_ID_KEY),
41
+ userName: requestContext.get(MASTRA_USER_NAME_KEY),
42
+ userEmail: requestContext.get(MASTRA_USER_EMAIL_KEY),
39
43
  modelOverride: requestContext.get(
40
44
  // imported below; logged so a misrouted request shows
41
45
  // up alongside its model selection in `LOG_LEVEL=debug`.
@@ -56,6 +60,38 @@ export class MastraServer extends MastraServerExpress {
56
60
  };
57
61
  requestContext.set(MASTRA_USER_KEY, user);
58
62
  requestContext.set(MASTRA_RESOURCE_ID_KEY, user.id);
63
+ // AppKit's `UserContext` surfaces display name / email only on
64
+ // OBO requests. Service-context calls (background tasks, server
65
+ // start-up) leave these undefined and we skip the stamp so
66
+ // downstream trace metadata stays absent rather than empty.
67
+ if ("isUserContext" in executionContext) {
68
+ if (executionContext.userName) {
69
+ requestContext.set(MASTRA_USER_NAME_KEY, executionContext.userName);
70
+ }
71
+ if (executionContext.userEmail) {
72
+ requestContext.set(MASTRA_USER_EMAIL_KEY, executionContext.userEmail);
73
+ }
74
+ }
75
+ }
76
+ /**
77
+ * Stamp a per-request id and echo it on the response so an upstream
78
+ * proxy / curl client / browser-side log line can pair its view of
79
+ * the request with the matching trace span. Reuses `X-Request-Id`
80
+ * when the upstream already supplies one so multi-hop traces stay
81
+ * joined; otherwise mints a UUIDv4.
82
+ *
83
+ * The id is surfaced as `mastra__requestId` span metadata via
84
+ * {@link TRACE_REQUEST_CONTEXT_KEYS} and as the `X-Request-Id`
85
+ * response header so dev tools can copy it from either side.
86
+ */
87
+ configureRequestContextRequestId(req, res, requestContext) {
88
+ if (requestContext.get(MASTRA_REQUEST_ID_KEY))
89
+ return;
90
+ const headerValue = req.headers["x-request-id"];
91
+ const upstream = Array.isArray(headerValue) ? headerValue[0] : headerValue;
92
+ const requestId = upstream?.trim() || randomUUID();
93
+ requestContext.set(MASTRA_REQUEST_ID_KEY, requestId);
94
+ res.setHeader("X-Request-Id", requestId);
59
95
  }
60
96
  configureRequestContextThreadId(req, res, requestContext) {
61
97
  if (requestContext.get(MASTRA_THREAD_ID_KEY))
@@ -20,7 +20,7 @@
20
20
  * `plugin.ts` exposes the cached list at `GET /models`.
21
21
  */
22
22
  import { CacheManager } from "@databricks/appkit";
23
- import { logUtils, stringUtils } from "@dbx-tools/appkit-shared";
23
+ import { logUtils, stringUtils } from "@dbx-tools/shared";
24
24
  import Fuse from "fuse.js";
25
25
  const log = logUtils.logger("mastra/serving");
26
26
  /**
@@ -25,7 +25,7 @@
25
25
  * email is domain-specific, not infrastructure. Spread it into the
26
26
  * specific agents that should be able to draft emails.
27
27
  */
28
- import { logUtils, stringUtils } from "@dbx-tools/appkit-shared";
28
+ import { logUtils, stringUtils } from "@dbx-tools/shared";
29
29
  import { createTool } from "@mastra/core/tools";
30
30
  import { z } from "zod";
31
31
  const log = logUtils.logger("mastra/tool/send-email");
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Shared helper for publishing events through Mastra's
3
+ * `ctx.writer`. Centralizes the "the downstream stream may already
4
+ * be closed, don't take the whole tool down" pattern that the
5
+ * Genie agent and chart tool both need.
6
+ *
7
+ * Failures are logged at `warn` (a persistently-closed writer is
8
+ * the most likely culprit when events go missing client-side) but
9
+ * swallowed so a cancelled request or a client that navigated
10
+ * away can't crash a tool mid-flight.
11
+ */
12
+ import type { MinimalWriter } from "@dbx-tools/appkit-mastra-shared";
13
+ import { type logUtils } from "@dbx-tools/shared";
14
+ /**
15
+ * Best-effort `writer.write`. No-op when `writer` is undefined;
16
+ * caught errors are logged via `log.warn("writer:error", ...)`
17
+ * along with any caller-supplied `context` fields (e.g. a
18
+ * `chartId` or `messageId`) so the warning is greppable per
19
+ * resource.
20
+ *
21
+ * Returns when the write resolves or rejects; never throws.
22
+ */
23
+ export declare function safeWrite(log: logUtils.Logger, writer: MinimalWriter | undefined, chunk: unknown, context?: Record<string, unknown>): Promise<void>;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Shared helper for publishing events through Mastra's
3
+ * `ctx.writer`. Centralizes the "the downstream stream may already
4
+ * be closed, don't take the whole tool down" pattern that the
5
+ * Genie agent and chart tool both need.
6
+ *
7
+ * Failures are logged at `warn` (a persistently-closed writer is
8
+ * the most likely culprit when events go missing client-side) but
9
+ * swallowed so a cancelled request or a client that navigated
10
+ * away can't crash a tool mid-flight.
11
+ */
12
+ import { commonUtils } from "@dbx-tools/shared";
13
+ /**
14
+ * Best-effort `writer.write`. No-op when `writer` is undefined;
15
+ * caught errors are logged via `log.warn("writer:error", ...)`
16
+ * along with any caller-supplied `context` fields (e.g. a
17
+ * `chartId` or `messageId`) so the warning is greppable per
18
+ * resource.
19
+ *
20
+ * Returns when the write resolves or rejects; never throws.
21
+ */
22
+ export async function safeWrite(log, writer, chunk, context = {}) {
23
+ if (!writer) {
24
+ log.debug("writer:no-writer", context);
25
+ return;
26
+ }
27
+ try {
28
+ await writer.write(chunk);
29
+ log.debug("writer:ok", context);
30
+ }
31
+ catch (err) {
32
+ log.warn("writer:error", {
33
+ ...context,
34
+ error: commonUtils.errorMessage(err),
35
+ });
36
+ }
37
+ }