@dbx-tools/appkit-mastra 0.1.58 → 0.1.68

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.
Files changed (51) hide show
  1. package/README.md +41 -1
  2. package/dist/index.d.ts +1229 -17
  3. package/dist/index.js +3348 -19
  4. package/package.json +17 -30
  5. package/dist/src/agents.d.ts +0 -316
  6. package/dist/src/agents.js +0 -470
  7. package/dist/src/chart.d.ts +0 -153
  8. package/dist/src/chart.js +0 -570
  9. package/dist/src/config.d.ts +0 -295
  10. package/dist/src/config.js +0 -55
  11. package/dist/src/genie.d.ts +0 -161
  12. package/dist/src/genie.js +0 -973
  13. package/dist/src/history.d.ts +0 -95
  14. package/dist/src/history.js +0 -278
  15. package/dist/src/memory.d.ts +0 -109
  16. package/dist/src/memory.js +0 -253
  17. package/dist/src/model.d.ts +0 -52
  18. package/dist/src/model.js +0 -198
  19. package/dist/src/observability.d.ts +0 -64
  20. package/dist/src/observability.js +0 -83
  21. package/dist/src/plugin.d.ts +0 -177
  22. package/dist/src/plugin.js +0 -554
  23. package/dist/src/processor.d.ts +0 -8
  24. package/dist/src/processor.js +0 -40
  25. package/dist/src/processors/strip-stale-charts.d.ts +0 -32
  26. package/dist/src/processors/strip-stale-charts.js +0 -98
  27. package/dist/src/server.d.ts +0 -51
  28. package/dist/src/server.js +0 -152
  29. package/dist/src/serving.d.ts +0 -65
  30. package/dist/src/serving.js +0 -79
  31. package/dist/src/statement.d.ts +0 -66
  32. package/dist/src/statement.js +0 -111
  33. package/dist/src/writer.d.ts +0 -23
  34. package/dist/src/writer.js +0 -37
  35. package/dist/tsconfig.build.tsbuildinfo +0 -1
  36. package/index.ts +0 -27
  37. package/src/agents.ts +0 -772
  38. package/src/chart.ts +0 -716
  39. package/src/config.ts +0 -320
  40. package/src/genie.ts +0 -1123
  41. package/src/history.ts +0 -322
  42. package/src/memory.ts +0 -293
  43. package/src/model.ts +0 -257
  44. package/src/observability.ts +0 -114
  45. package/src/plugin.ts +0 -623
  46. package/src/processor.ts +0 -46
  47. package/src/processors/strip-stale-charts.ts +0 -102
  48. package/src/server.ts +0 -195
  49. package/src/serving.ts +0 -104
  50. package/src/statement.ts +0 -120
  51. package/src/writer.ts +0 -44
@@ -1,102 +0,0 @@
1
- /**
2
- * Mastra input processor that strips `chartId` fields from every
3
- * tool-invocation result in prior assistant messages before they
4
- * reach the model.
5
- *
6
- * Why: chartIds are turn-scoped from the model's point of view -
7
- * each `prepare_chart` / `render_data` call mints a fresh id and
8
- * the host UI binds it to that turn's reply. Mastra Memory
9
- * replays prior tool results into the prompt; if old chartIds
10
- * leak through, the model is tempted to copy them verbatim into
11
- * the new turn's `[chart:<id>]` markers and the host UI ends up
12
- * rendering an unrelated chart from the chart cache (or
13
- * a 404 once the 1h TTL elapsed). This processor removes the
14
- * temptation by deleting `chartId` keys from every assistant
15
- * message's tool results before the prompt is built. The current
16
- * turn's tool results don't exist yet at `processInput` time, so
17
- * they pass through unmodified.
18
- *
19
- * The strip is recursive - any nested `chartId` field is removed,
20
- * regardless of which tool produced the result. This covers
21
- * `prepare_chart` / `render_data` top-level chartIds and any
22
- * legacy `datasets[].chartId` payloads uniformly without coupling
23
- * to specific tool ids.
24
- */
25
-
26
- import { logUtils } from "@dbx-tools/shared";
27
- import type { InputProcessor, ProcessInputArgs } from "@mastra/core/processors";
28
-
29
- const log = logUtils.logger("mastra/processor/strip-stale-charts");
30
-
31
- /**
32
- * Recursively clone `value`, omitting any property whose key is
33
- * `chartId`. Arrays are mapped element-wise; primitives are
34
- * returned as-is. The result is structurally identical to the
35
- * input minus chartIds, so downstream message-shape consumers
36
- * keep working.
37
- */
38
- function stripChartIds(value: unknown): unknown {
39
- if (Array.isArray(value)) {
40
- return value.map(stripChartIds);
41
- }
42
- if (value && typeof value === "object") {
43
- const obj = value as Record<string, unknown>;
44
- const out: Record<string, unknown> = {};
45
- for (const [key, val] of Object.entries(obj)) {
46
- if (key === "chartId") continue;
47
- out[key] = stripChartIds(val);
48
- }
49
- return out;
50
- }
51
- return value;
52
- }
53
-
54
- /**
55
- * Input processor that scrubs `chartId` from every tool-invocation
56
- * result in the message list. Wired onto every agent by default
57
- * via {@link buildAgents}; opt out with
58
- * `MastraPluginConfig.stripStaleCharts: false`.
59
- */
60
- export const stripStaleChartsProcessor: InputProcessor = {
61
- id: "strip-stale-charts",
62
- description:
63
- "Removes chartId fields from prior tool-invocation results so the model can't reuse turn-scoped ids from memory.",
64
- processInput(args: ProcessInputArgs) {
65
- let stripped = 0;
66
- for (const message of args.messages) {
67
- if (message.role !== "assistant") continue;
68
- const parts = message.content?.parts;
69
- if (!Array.isArray(parts)) continue;
70
- for (const part of parts) {
71
- // Tool-invocation parts hold the persisted tool result.
72
- // We don't scrub the input args (`rawInput` / `args`) because
73
- // the chartId there is the model's outgoing claim, not
74
- // anything it could re-reference; only `result` carries
75
- // ids that subsequent turns might copy.
76
- if ((part as { type?: unknown }).type !== "tool-invocation") {
77
- continue;
78
- }
79
- const inv = (part as { toolInvocation?: { result?: unknown } }).toolInvocation;
80
- if (!inv || inv.result === undefined) continue;
81
- const before = inv.result;
82
- const after = stripChartIds(before);
83
- // Cheap structural check via JSON length - the actual
84
- // strip writes a fresh object only when chartId keys
85
- // existed, so different stringification length is a
86
- // reliable signal that something was removed.
87
- if (
88
- typeof before === "object" &&
89
- before !== null &&
90
- JSON.stringify(before).length !== JSON.stringify(after).length
91
- ) {
92
- inv.result = after;
93
- stripped += 1;
94
- }
95
- }
96
- }
97
- if (stripped > 0) {
98
- log.debug("stripped", { results: stripped });
99
- }
100
- return args.messages;
101
- },
102
- };
package/src/server.ts DELETED
@@ -1,195 +0,0 @@
1
- /**
2
- * Express-layer plumbing for the Mastra plugin: a `MastraServer` that
3
- * stamps the per-request `RequestContext`, and a route-patch middleware
4
- * that lets the plugin's custom API routes (e.g. `historyRoute`) work
5
- * behind an Express mount point.
6
- */
7
-
8
- import { getExecutionContext } from "@databricks/appkit";
9
- import { httpUtils, logUtils, stringUtils } from "@dbx-tools/shared";
10
- import {
11
- MASTRA_RESOURCE_ID_KEY,
12
- MASTRA_THREAD_ID_KEY,
13
- type RequestContext,
14
- } from "@mastra/core/request-context";
15
- import { MastraServer as MastraServerExpress } from "@mastra/express";
16
- import type express from "express";
17
- import { randomUUID } from "node:crypto";
18
-
19
- import {
20
- MASTRA_REQUEST_ID_KEY,
21
- MASTRA_USER_EMAIL_KEY,
22
- MASTRA_USER_KEY,
23
- MASTRA_USER_NAME_KEY,
24
- type MastraPluginConfig,
25
- type User,
26
- } from "./config.js";
27
- import {
28
- extractModelOverride,
29
- MASTRA_MODEL_OVERRIDE_KEY,
30
- resolveServingConfig,
31
- } from "./serving.js";
32
-
33
- /**
34
- * `@mastra/express` subclass that stamps `RequestContext` with the
35
- * AppKit user, resource id, and a thread id backed by an HTTP-only
36
- * session cookie (`appkit_<plugin-name>_session_id`).
37
- */
38
- export class MastraServer extends MastraServerExpress {
39
- private log: logUtils.Logger;
40
-
41
- constructor(
42
- private config: MastraPluginConfig,
43
- ...args: ConstructorParameters<typeof MastraServerExpress>
44
- ) {
45
- super(...args);
46
- this.log = logUtils.logger(config);
47
- }
48
-
49
- override registerAuthMiddleware(): void {
50
- super.registerAuthMiddleware();
51
- this.app.use((req, res, next) => {
52
- const requestContext = res.locals.requestContext! as RequestContext;
53
- this.configureRequestContextUser(requestContext);
54
- this.configureRequestContextThreadId(req, res, requestContext);
55
- this.configureRequestContextModelOverride(req, requestContext);
56
- this.configureRequestContextRequestId(req, res, requestContext);
57
- this.log.debug("auth:middleware", {
58
- method: req.method,
59
- path: req.path,
60
- requestId: requestContext.get(MASTRA_REQUEST_ID_KEY),
61
- threadId: requestContext.get(MASTRA_THREAD_ID_KEY),
62
- resourceId: requestContext.get(MASTRA_RESOURCE_ID_KEY),
63
- userName: requestContext.get(MASTRA_USER_NAME_KEY),
64
- userEmail: requestContext.get(MASTRA_USER_EMAIL_KEY),
65
- modelOverride: requestContext.get(
66
- // imported below; logged so a misrouted request shows
67
- // up alongside its model selection in `LOG_LEVEL=debug`.
68
- "mastra__model_override",
69
- ),
70
- });
71
- next();
72
- });
73
- }
74
-
75
- configureRequestContextUser(requestContext: RequestContext) {
76
- if (
77
- [MASTRA_USER_KEY, MASTRA_RESOURCE_ID_KEY].every((key) => requestContext.get(key))
78
- )
79
- return;
80
- const executionContext = getExecutionContext();
81
- const user: User = {
82
- id:
83
- "userId" in executionContext
84
- ? executionContext.userId
85
- : executionContext.serviceUserId,
86
- executionContext,
87
- };
88
- requestContext.set(MASTRA_USER_KEY, user);
89
- requestContext.set(MASTRA_RESOURCE_ID_KEY, user.id);
90
- // AppKit's `UserContext` surfaces display name / email only on
91
- // OBO requests. Service-context calls (background tasks, server
92
- // start-up) leave these undefined and we skip the stamp so
93
- // downstream trace metadata stays absent rather than empty.
94
- if ("isUserContext" in executionContext) {
95
- if (executionContext.userName) {
96
- requestContext.set(MASTRA_USER_NAME_KEY, executionContext.userName);
97
- }
98
- if (executionContext.userEmail) {
99
- requestContext.set(MASTRA_USER_EMAIL_KEY, executionContext.userEmail);
100
- }
101
- }
102
- }
103
-
104
- /**
105
- * Stamp a per-request id and echo it on the response so an upstream
106
- * proxy / curl client / browser-side log line can pair its view of
107
- * the request with the matching trace span. Reuses `X-Request-Id`
108
- * when the upstream already supplies one so multi-hop traces stay
109
- * joined; otherwise mints a UUIDv4.
110
- *
111
- * The id is surfaced as `mastra__requestId` span metadata via
112
- * {@link TRACE_REQUEST_CONTEXT_KEYS} and as the `X-Request-Id`
113
- * response header so dev tools can copy it from either side.
114
- */
115
- configureRequestContextRequestId(
116
- req: express.Request,
117
- res: express.Response,
118
- requestContext: RequestContext,
119
- ) {
120
- if (requestContext.get(MASTRA_REQUEST_ID_KEY)) return;
121
- const headerValue = req.headers["x-request-id"];
122
- const upstream = Array.isArray(headerValue) ? headerValue[0] : headerValue;
123
- const requestId = upstream?.trim() || randomUUID();
124
- requestContext.set(MASTRA_REQUEST_ID_KEY, requestId);
125
- res.setHeader("X-Request-Id", requestId);
126
- }
127
-
128
- configureRequestContextThreadId(
129
- req: express.Request,
130
- res: express.Response,
131
- requestContext: RequestContext,
132
- ) {
133
- if (requestContext.get(MASTRA_THREAD_ID_KEY)) return;
134
- const cookies = httpUtils.parseCookies(req.headers.cookie);
135
- const cookieName = stringUtils.toIdentifierWithOptions(
136
- { delimiter: "_", distinct: true },
137
- "appkit",
138
- this.config.name!,
139
- "sessionId",
140
- );
141
- let sessionId = cookies[cookieName];
142
- if (!sessionId) {
143
- sessionId = randomUUID();
144
- res.cookie(cookieName, sessionId, {
145
- httpOnly: true,
146
- sameSite: "lax",
147
- secure: req.secure,
148
- path: "/",
149
- });
150
- }
151
- requestContext.set(MASTRA_THREAD_ID_KEY, sessionId);
152
- }
153
-
154
- configureRequestContextModelOverride(
155
- req: express.Request,
156
- requestContext: RequestContext,
157
- ) {
158
- // Per-request model override: only honored when the plugin
159
- // opts in (default). Sources, in priority order, are
160
- // `X-Mastra-Model` header, `?model=` query, and `model` /
161
- // `modelId` body field; see `serving.ts`.
162
- const serving = resolveServingConfig(this.config);
163
- if (serving.allowOverride) {
164
- const override = extractModelOverride({
165
- headers: req.headers as Record<string, string | string[] | undefined>,
166
- query: req.query as Record<string, unknown>,
167
- body: req.body,
168
- });
169
- if (override) requestContext.set(MASTRA_MODEL_OVERRIDE_KEY, override);
170
- }
171
- }
172
- }
173
-
174
- /**
175
- * Patches around `@mastra/express`'s custom-route dispatcher so the
176
- * plugin's custom API routes (e.g. `historyRoute`) work when
177
- * `MastraServer` is hosted on an Express subapp mounted under a parent
178
- * path (e.g. `/api/mastra`).
179
- *
180
- * The adapter's `registerCustomApiRoutes` matches against `req.path`
181
- * (mount-relative, correct) but dispatches to its internal Hono
182
- * mini-app using `req.originalUrl`, which still contains the parent
183
- * mount prefix. The Hono app registers the literal route paths
184
- * (for example `/route/history`), so the absolute URL never matches
185
- * until we overwrite `originalUrl` for `/route` and `/route/*` to the
186
- * mount-relative path.
187
- */
188
- export function attachRoutePatchMiddleware(app: express.Express): void {
189
- app.use((req, _res, next) => {
190
- const isCustomRoute = req.path === "/route" || req.path.startsWith("/route/");
191
- if (!isCustomRoute) return next();
192
- req.originalUrl = req.path;
193
- next();
194
- });
195
- }
package/src/serving.ts DELETED
@@ -1,104 +0,0 @@
1
- /**
2
- * Mastra-specific glue over the generic `@dbx-tools/model` toolkit.
3
- *
4
- * The live `/serving-endpoints` catalogue access, fuzzy name
5
- * resolution, and class/fallback selection all live in
6
- * `@dbx-tools/model`; this module only adds what is specific to the
7
- * Mastra plugin: pulling a per-request model override off an HTTP
8
- * request (header / query / body) and projecting the plugin config
9
- * onto the knobs `buildModel` and the `/models` route share.
10
- */
11
-
12
- import { DEFAULT_FUZZY_THRESHOLD, DEFAULT_MODEL_CACHE_TTL_MS } from "@dbx-tools/model";
13
- import { stringUtils } from "@dbx-tools/shared";
14
-
15
- import type { MastraPluginConfig } from "./config.js";
16
-
17
- /**
18
- * `RequestContext` key under which {@link MastraServer} stores the
19
- * per-request model override (header / query / body). `model.ts`
20
- * reads it before falling back to the agent / plugin default.
21
- */
22
- export const MASTRA_MODEL_OVERRIDE_KEY = "mastra__model_override";
23
-
24
- /** HTTP header inspected for a per-request model override. */
25
- export const MODEL_OVERRIDE_HEADER = "x-mastra-model";
26
-
27
- /** Query string parameter inspected for a per-request model override. */
28
- export const MODEL_OVERRIDE_QUERY = "model";
29
-
30
- /** Body fields (in priority order) inspected for a per-request model override. */
31
- export const MODEL_OVERRIDE_BODY_FIELDS = ["model", "modelId"] as const;
32
-
33
- /**
34
- * Minimal Express-ish request shape used by {@link extractModelOverride}.
35
- * Keeps this module independent of `express` so the helper can be
36
- * reused from non-Express adapters.
37
- */
38
- export interface ModelOverrideRequest {
39
- headers?: Record<string, string | string[] | undefined>;
40
- query?: Record<string, unknown> | undefined;
41
- body?: unknown;
42
- }
43
-
44
- /**
45
- * Pull a model override out of a single HTTP request, checking
46
- * sources in priority order:
47
- *
48
- * 1. `X-Mastra-Model` header
49
- * 2. `?model=` query string parameter
50
- * 3. Body field (`model` or `modelId`, in that order)
51
- *
52
- * Returns `null` when nothing is set, so callers can wrap with
53
- * `if (override) ...` without juggling empty strings. Body inspection
54
- * is lenient - any plain object with one of the configured keys
55
- * counts, mirroring how AI SDK chat clients pass arbitrary metadata
56
- * alongside `messages`.
57
- */
58
- export function extractModelOverride(req: ModelOverrideRequest): string | null {
59
- const headers = req.headers;
60
- if (headers) {
61
- const headerVal = stringUtils.firstNonEmpty(
62
- headers[MODEL_OVERRIDE_HEADER] ?? headers[MODEL_OVERRIDE_HEADER.toLowerCase()],
63
- );
64
- if (headerVal) return headerVal;
65
- }
66
- if (req.query) {
67
- const queryVal = stringUtils.firstNonEmpty(req.query[MODEL_OVERRIDE_QUERY]);
68
- if (queryVal) return queryVal;
69
- }
70
- if (req.body && typeof req.body === "object") {
71
- const record = req.body as Record<string, unknown>;
72
- for (const field of MODEL_OVERRIDE_BODY_FIELDS) {
73
- const bodyVal = stringUtils.firstNonEmpty(record[field]);
74
- if (bodyVal) return bodyVal;
75
- }
76
- }
77
- return null;
78
- }
79
-
80
- /**
81
- * Read the fuzzy-resolution config knobs off the plugin config with
82
- * `@dbx-tools/model` defaults applied. Kept here so `buildModel` and
83
- * the `/models` route agree on what "enabled" means.
84
- *
85
- * `fallbacks` is the priority-ordered list `resolveModel` walks first
86
- * when nothing explicit is set; defaults to an empty list so the
87
- * generic resolver falls through to the live catalogue and its own
88
- * `FALLBACK_MODEL_IDS` floor.
89
- */
90
- export function resolveServingConfig(config: MastraPluginConfig): {
91
- ttlMs: number;
92
- threshold: number;
93
- fuzzy: boolean;
94
- allowOverride: boolean;
95
- fallbacks: readonly string[];
96
- } {
97
- return {
98
- ttlMs: config.modelCacheTtlMs ?? DEFAULT_MODEL_CACHE_TTL_MS,
99
- threshold: config.modelFuzzyThreshold ?? DEFAULT_FUZZY_THRESHOLD,
100
- fuzzy: config.modelFuzzyMatch !== false,
101
- allowOverride: config.modelOverride !== false,
102
- fallbacks: config.defaultModelFallbacks ?? [],
103
- };
104
- }
package/src/statement.ts DELETED
@@ -1,120 +0,0 @@
1
- /**
2
- * Databricks Statement Execution helpers for the Mastra plugin.
3
- *
4
- * Wraps `client.statementExecution.getStatement` with the shape,
5
- * size, and error handling the plugin's tools and the
6
- * `/embed/data/:id` route both need: a low-level fetch that returns a
7
- * raw `{columns, rows, rowCount}` shape and coerces numeric strings
8
- * to numbers so downstream charts and aggregations don't have to, a
9
- * hard row cap callers clamp `limit` to so a runaway result set can't
10
- * hose a response, and a structural not-found detector that
11
- * normalizes the SDK's error classes and loose `does not exist` /
12
- * `not found` message shapes into one boolean so the route can map
13
- * upstream 404s to a clean HTTP 404 without coupling to SDK
14
- * error-class identity.
15
- *
16
- * Not Genie-specific: a Databricks `statement_id` is workspace
17
- * scoped and lives in the Statement Execution API regardless of
18
- * which producer (Genie, a tool, a notebook, etc.) submitted the
19
- * query. Co-located here so consumers can fetch / cap / handle
20
- * 404s without reaching into the Genie tool module.
21
- */
22
-
23
- import { ApiError, HttpError, WorkspaceClient } from "@databricks/sdk-experimental";
24
- import type { GenieDatasetData } from "@dbx-tools/appkit-mastra-shared";
25
- import { apiUtils } from "@dbx-tools/shared";
26
-
27
- /**
28
- * Hard server-side cap on rows returned by the
29
- * `/embed/data/:id` route. Sized to keep responses small
30
- * enough for inline tables to render snappily; the route surfaces
31
- * a `truncated` flag whenever the upstream `rowCount` exceeds
32
- * this so end users know they're seeing a sample.
33
- */
34
- export const STATEMENT_ROW_CAP = 500;
35
-
36
- /**
37
- * Best-effort numeric coercion for the Statement Execution API's
38
- * all-strings cells. Leaves non-numeric strings (and explicit
39
- * `null`s) intact; everything else flows through `Number`.
40
- */
41
- function coerceCell(cell: string | null): unknown {
42
- if (cell === null) return null;
43
- if (/^-?\d+(\.\d+)?$/.test(cell)) {
44
- const n = Number(cell);
45
- if (Number.isFinite(n)) return n;
46
- }
47
- return cell;
48
- }
49
-
50
- /**
51
- * Fetch a single statement's rows via the Statement Execution API
52
- * and reshape into the shared {@link GenieDatasetData} shape
53
- * (column array + row records).
54
- *
55
- * Optional `limit` slices the returned `rows` client-side so the
56
- * agent can scan a small sample without paging the full result
57
- * set into context. `rowCount` always reflects the upstream total
58
- * so callers know when the slice truncated.
59
- *
60
- * Exported because every consumer in the plugin (the
61
- * `get_statement` tool, the `prepare_chart` dataset resolver, and
62
- * the `/embed/data/:id` route) needs the exact same
63
- * fetch + coercion pipeline so LLM-side `get_statement` output
64
- * and UI-side `[data:<id>]` rendering stay shape-identical for
65
- * the same `statement_id`.
66
- */
67
- export async function fetchStatementData(
68
- client: WorkspaceClient,
69
- statementId: string,
70
- options?: { limit?: number; signal?: AbortSignal },
71
- ): Promise<GenieDatasetData> {
72
- const ctx = options?.signal ? apiUtils.toContext(options.signal) : undefined;
73
- const r = await client.statementExecution.getStatement(
74
- { statement_id: statementId },
75
- ctx,
76
- );
77
- const columns = (r.manifest?.schema?.columns ?? []).map((c) => c.name ?? "");
78
- const dataArray = (r.result?.data_array ?? []) as Array<Array<string | null>>;
79
- const sliced =
80
- options?.limit !== undefined && options.limit >= 0
81
- ? dataArray.slice(0, options.limit)
82
- : dataArray;
83
- const rows = sliced.map((row) => {
84
- const obj: Record<string, unknown> = {};
85
- columns.forEach((col, i) => {
86
- obj[col] = coerceCell(row[i] ?? null);
87
- });
88
- return obj;
89
- });
90
- return {
91
- columns,
92
- rows,
93
- rowCount: r.manifest?.total_row_count ?? dataArray.length,
94
- };
95
- }
96
-
97
- /**
98
- * True when `err` looks like the Databricks SDK's "statement not
99
- * found" error. Matches the typed {@link ApiError} 404 /
100
- * `RESOURCE_DOES_NOT_EXIST` shape first, then falls back to the
101
- * lower-level {@link HttpError} 404, then to a loose `does not
102
- * exist` / `not found` message sniff for SDK shapes we haven't
103
- * catalogued.
104
- *
105
- * Pulled into its own helper so callers (notably the
106
- * `/embed/data/:id` route) stay decoupled from SDK
107
- * error-class identity, and the conversion logic stays testable
108
- * in isolation.
109
- */
110
- export function isStatementNotFoundError(err: unknown): boolean {
111
- if (err instanceof ApiError) {
112
- if (err.statusCode === 404) return true;
113
- if (err.errorCode === "RESOURCE_DOES_NOT_EXIST") return true;
114
- }
115
- if (err instanceof HttpError && err.code === 404) return true;
116
- if (err instanceof Error && /does not exist|not found/i.test(err.message)) {
117
- return true;
118
- }
119
- return false;
120
- }
package/src/writer.ts DELETED
@@ -1,44 +0,0 @@
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
-
13
- import type { MastraWriter } from "@dbx-tools/appkit-mastra-shared";
14
- import { commonUtils, type logUtils } from "@dbx-tools/shared";
15
-
16
- /**
17
- * Best-effort `writer.write`. No-op when `writer` is undefined;
18
- * caught errors are logged via `log.warn("writer:error", ...)`
19
- * along with any caller-supplied `context` fields (e.g. a
20
- * `chartId` or `messageId`) so the warning is greppable per
21
- * resource.
22
- *
23
- * Returns when the write resolves or rejects; never throws.
24
- */
25
- export async function safeWrite(
26
- log: logUtils.Logger,
27
- writer: MastraWriter | undefined,
28
- chunk: unknown,
29
- context: Record<string, unknown> = {},
30
- ): Promise<void> {
31
- if (!writer) {
32
- log.debug("writer:no-writer", context);
33
- return;
34
- }
35
- try {
36
- await writer.write(chunk);
37
- log.debug("writer:ok", context);
38
- } catch (err) {
39
- log.warn("writer:error", {
40
- ...context,
41
- error: commonUtils.errorMessage(err),
42
- });
43
- }
44
- }