@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,98 +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
- import { logUtils } from "@dbx-tools/shared";
26
- const log = logUtils.logger("mastra/processor/strip-stale-charts");
27
- /**
28
- * Recursively clone `value`, omitting any property whose key is
29
- * `chartId`. Arrays are mapped element-wise; primitives are
30
- * returned as-is. The result is structurally identical to the
31
- * input minus chartIds, so downstream message-shape consumers
32
- * keep working.
33
- */
34
- function stripChartIds(value) {
35
- if (Array.isArray(value)) {
36
- return value.map(stripChartIds);
37
- }
38
- if (value && typeof value === "object") {
39
- const obj = value;
40
- const out = {};
41
- for (const [key, val] of Object.entries(obj)) {
42
- if (key === "chartId")
43
- continue;
44
- out[key] = stripChartIds(val);
45
- }
46
- return out;
47
- }
48
- return value;
49
- }
50
- /**
51
- * Input processor that scrubs `chartId` from every tool-invocation
52
- * result in the message list. Wired onto every agent by default
53
- * via {@link buildAgents}; opt out with
54
- * `MastraPluginConfig.stripStaleCharts: false`.
55
- */
56
- export const stripStaleChartsProcessor = {
57
- id: "strip-stale-charts",
58
- description: "Removes chartId fields from prior tool-invocation results so the model can't reuse turn-scoped ids from memory.",
59
- processInput(args) {
60
- let stripped = 0;
61
- for (const message of args.messages) {
62
- if (message.role !== "assistant")
63
- continue;
64
- const parts = message.content?.parts;
65
- if (!Array.isArray(parts))
66
- continue;
67
- for (const part of parts) {
68
- // Tool-invocation parts hold the persisted tool result.
69
- // We don't scrub the input args (`rawInput` / `args`) because
70
- // the chartId there is the model's outgoing claim, not
71
- // anything it could re-reference; only `result` carries
72
- // ids that subsequent turns might copy.
73
- if (part.type !== "tool-invocation") {
74
- continue;
75
- }
76
- const inv = part.toolInvocation;
77
- if (!inv || inv.result === undefined)
78
- continue;
79
- const before = inv.result;
80
- const after = stripChartIds(before);
81
- // Cheap structural check via JSON length - the actual
82
- // strip writes a fresh object only when chartId keys
83
- // existed, so different stringification length is a
84
- // reliable signal that something was removed.
85
- if (typeof before === "object" &&
86
- before !== null &&
87
- JSON.stringify(before).length !== JSON.stringify(after).length) {
88
- inv.result = after;
89
- stripped += 1;
90
- }
91
- }
92
- }
93
- if (stripped > 0) {
94
- log.debug("stripped", { results: stripped });
95
- }
96
- return args.messages;
97
- },
98
- };
@@ -1,51 +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
- import { type RequestContext } from "@mastra/core/request-context";
8
- import { MastraServer as MastraServerExpress } from "@mastra/express";
9
- import type express from "express";
10
- import { type MastraPluginConfig } from "./config.js";
11
- /**
12
- * `@mastra/express` subclass that stamps `RequestContext` with the
13
- * AppKit user, resource id, and a thread id backed by an HTTP-only
14
- * session cookie (`appkit_<plugin-name>_session_id`).
15
- */
16
- export declare class MastraServer extends MastraServerExpress {
17
- private config;
18
- private log;
19
- constructor(config: MastraPluginConfig, ...args: ConstructorParameters<typeof MastraServerExpress>);
20
- registerAuthMiddleware(): void;
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;
34
- configureRequestContextThreadId(req: express.Request, res: express.Response, requestContext: RequestContext): void;
35
- configureRequestContextModelOverride(req: express.Request, requestContext: RequestContext): void;
36
- }
37
- /**
38
- * Patches around `@mastra/express`'s custom-route dispatcher so the
39
- * plugin's custom API routes (e.g. `historyRoute`) work when
40
- * `MastraServer` is hosted on an Express subapp mounted under a parent
41
- * path (e.g. `/api/mastra`).
42
- *
43
- * The adapter's `registerCustomApiRoutes` matches against `req.path`
44
- * (mount-relative, correct) but dispatches to its internal Hono
45
- * mini-app using `req.originalUrl`, which still contains the parent
46
- * mount prefix. The Hono app registers the literal route paths
47
- * (for example `/route/history`), so the absolute URL never matches
48
- * until we overwrite `originalUrl` for `/route` and `/route/*` to the
49
- * mount-relative path.
50
- */
51
- export declare function attachRoutePatchMiddleware(app: express.Express): void;
@@ -1,152 +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
- import { getExecutionContext } from "@databricks/appkit";
8
- import { httpUtils, logUtils, stringUtils } from "@dbx-tools/shared";
9
- import { MASTRA_RESOURCE_ID_KEY, MASTRA_THREAD_ID_KEY, } from "@mastra/core/request-context";
10
- import { MastraServer as MastraServerExpress } from "@mastra/express";
11
- import { randomUUID } from "node:crypto";
12
- import { MASTRA_REQUEST_ID_KEY, MASTRA_USER_EMAIL_KEY, MASTRA_USER_KEY, MASTRA_USER_NAME_KEY, } from "./config.js";
13
- import { extractModelOverride, MASTRA_MODEL_OVERRIDE_KEY, resolveServingConfig, } from "./serving.js";
14
- /**
15
- * `@mastra/express` subclass that stamps `RequestContext` with the
16
- * AppKit user, resource id, and a thread id backed by an HTTP-only
17
- * session cookie (`appkit_<plugin-name>_session_id`).
18
- */
19
- export class MastraServer extends MastraServerExpress {
20
- config;
21
- log;
22
- constructor(config, ...args) {
23
- super(...args);
24
- this.config = config;
25
- this.log = logUtils.logger(config);
26
- }
27
- registerAuthMiddleware() {
28
- super.registerAuthMiddleware();
29
- this.app.use((req, res, next) => {
30
- const requestContext = res.locals.requestContext;
31
- this.configureRequestContextUser(requestContext);
32
- this.configureRequestContextThreadId(req, res, requestContext);
33
- this.configureRequestContextModelOverride(req, requestContext);
34
- this.configureRequestContextRequestId(req, res, requestContext);
35
- this.log.debug("auth:middleware", {
36
- method: req.method,
37
- path: req.path,
38
- requestId: requestContext.get(MASTRA_REQUEST_ID_KEY),
39
- threadId: requestContext.get(MASTRA_THREAD_ID_KEY),
40
- resourceId: requestContext.get(MASTRA_RESOURCE_ID_KEY),
41
- userName: requestContext.get(MASTRA_USER_NAME_KEY),
42
- userEmail: requestContext.get(MASTRA_USER_EMAIL_KEY),
43
- modelOverride: requestContext.get(
44
- // imported below; logged so a misrouted request shows
45
- // up alongside its model selection in `LOG_LEVEL=debug`.
46
- "mastra__model_override"),
47
- });
48
- next();
49
- });
50
- }
51
- configureRequestContextUser(requestContext) {
52
- if ([MASTRA_USER_KEY, MASTRA_RESOURCE_ID_KEY].every((key) => requestContext.get(key)))
53
- return;
54
- const executionContext = getExecutionContext();
55
- const user = {
56
- id: "userId" in executionContext
57
- ? executionContext.userId
58
- : executionContext.serviceUserId,
59
- executionContext,
60
- };
61
- requestContext.set(MASTRA_USER_KEY, user);
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);
95
- }
96
- configureRequestContextThreadId(req, res, requestContext) {
97
- if (requestContext.get(MASTRA_THREAD_ID_KEY))
98
- return;
99
- const cookies = httpUtils.parseCookies(req.headers.cookie);
100
- const cookieName = stringUtils.toIdentifierWithOptions({ delimiter: "_", distinct: true }, "appkit", this.config.name, "sessionId");
101
- let sessionId = cookies[cookieName];
102
- if (!sessionId) {
103
- sessionId = randomUUID();
104
- res.cookie(cookieName, sessionId, {
105
- httpOnly: true,
106
- sameSite: "lax",
107
- secure: req.secure,
108
- path: "/",
109
- });
110
- }
111
- requestContext.set(MASTRA_THREAD_ID_KEY, sessionId);
112
- }
113
- configureRequestContextModelOverride(req, requestContext) {
114
- // Per-request model override: only honored when the plugin
115
- // opts in (default). Sources, in priority order, are
116
- // `X-Mastra-Model` header, `?model=` query, and `model` /
117
- // `modelId` body field; see `serving.ts`.
118
- const serving = resolveServingConfig(this.config);
119
- if (serving.allowOverride) {
120
- const override = extractModelOverride({
121
- headers: req.headers,
122
- query: req.query,
123
- body: req.body,
124
- });
125
- if (override)
126
- requestContext.set(MASTRA_MODEL_OVERRIDE_KEY, override);
127
- }
128
- }
129
- }
130
- /**
131
- * Patches around `@mastra/express`'s custom-route dispatcher so the
132
- * plugin's custom API routes (e.g. `historyRoute`) work when
133
- * `MastraServer` is hosted on an Express subapp mounted under a parent
134
- * path (e.g. `/api/mastra`).
135
- *
136
- * The adapter's `registerCustomApiRoutes` matches against `req.path`
137
- * (mount-relative, correct) but dispatches to its internal Hono
138
- * mini-app using `req.originalUrl`, which still contains the parent
139
- * mount prefix. The Hono app registers the literal route paths
140
- * (for example `/route/history`), so the absolute URL never matches
141
- * until we overwrite `originalUrl` for `/route` and `/route/*` to the
142
- * mount-relative path.
143
- */
144
- export function attachRoutePatchMiddleware(app) {
145
- app.use((req, _res, next) => {
146
- const isCustomRoute = req.path === "/route" || req.path.startsWith("/route/");
147
- if (!isCustomRoute)
148
- return next();
149
- req.originalUrl = req.path;
150
- next();
151
- });
152
- }
@@ -1,65 +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
- import type { MastraPluginConfig } from "./config.js";
12
- /**
13
- * `RequestContext` key under which {@link MastraServer} stores the
14
- * per-request model override (header / query / body). `model.ts`
15
- * reads it before falling back to the agent / plugin default.
16
- */
17
- export declare const MASTRA_MODEL_OVERRIDE_KEY = "mastra__model_override";
18
- /** HTTP header inspected for a per-request model override. */
19
- export declare const MODEL_OVERRIDE_HEADER = "x-mastra-model";
20
- /** Query string parameter inspected for a per-request model override. */
21
- export declare const MODEL_OVERRIDE_QUERY = "model";
22
- /** Body fields (in priority order) inspected for a per-request model override. */
23
- export declare const MODEL_OVERRIDE_BODY_FIELDS: readonly ["model", "modelId"];
24
- /**
25
- * Minimal Express-ish request shape used by {@link extractModelOverride}.
26
- * Keeps this module independent of `express` so the helper can be
27
- * reused from non-Express adapters.
28
- */
29
- export interface ModelOverrideRequest {
30
- headers?: Record<string, string | string[] | undefined>;
31
- query?: Record<string, unknown> | undefined;
32
- body?: unknown;
33
- }
34
- /**
35
- * Pull a model override out of a single HTTP request, checking
36
- * sources in priority order:
37
- *
38
- * 1. `X-Mastra-Model` header
39
- * 2. `?model=` query string parameter
40
- * 3. Body field (`model` or `modelId`, in that order)
41
- *
42
- * Returns `null` when nothing is set, so callers can wrap with
43
- * `if (override) ...` without juggling empty strings. Body inspection
44
- * is lenient - any plain object with one of the configured keys
45
- * counts, mirroring how AI SDK chat clients pass arbitrary metadata
46
- * alongside `messages`.
47
- */
48
- export declare function extractModelOverride(req: ModelOverrideRequest): string | null;
49
- /**
50
- * Read the fuzzy-resolution config knobs off the plugin config with
51
- * `@dbx-tools/model` defaults applied. Kept here so `buildModel` and
52
- * the `/models` route agree on what "enabled" means.
53
- *
54
- * `fallbacks` is the priority-ordered list `resolveModel` walks first
55
- * when nothing explicit is set; defaults to an empty list so the
56
- * generic resolver falls through to the live catalogue and its own
57
- * `FALLBACK_MODEL_IDS` floor.
58
- */
59
- export declare function resolveServingConfig(config: MastraPluginConfig): {
60
- ttlMs: number;
61
- threshold: number;
62
- fuzzy: boolean;
63
- allowOverride: boolean;
64
- fallbacks: readonly string[];
65
- };
@@ -1,79 +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
- import { DEFAULT_FUZZY_THRESHOLD, DEFAULT_MODEL_CACHE_TTL_MS } from "@dbx-tools/model";
12
- import { stringUtils } from "@dbx-tools/shared";
13
- /**
14
- * `RequestContext` key under which {@link MastraServer} stores the
15
- * per-request model override (header / query / body). `model.ts`
16
- * reads it before falling back to the agent / plugin default.
17
- */
18
- export const MASTRA_MODEL_OVERRIDE_KEY = "mastra__model_override";
19
- /** HTTP header inspected for a per-request model override. */
20
- export const MODEL_OVERRIDE_HEADER = "x-mastra-model";
21
- /** Query string parameter inspected for a per-request model override. */
22
- export const MODEL_OVERRIDE_QUERY = "model";
23
- /** Body fields (in priority order) inspected for a per-request model override. */
24
- export const MODEL_OVERRIDE_BODY_FIELDS = ["model", "modelId"];
25
- /**
26
- * Pull a model override out of a single HTTP request, checking
27
- * sources in priority order:
28
- *
29
- * 1. `X-Mastra-Model` header
30
- * 2. `?model=` query string parameter
31
- * 3. Body field (`model` or `modelId`, in that order)
32
- *
33
- * Returns `null` when nothing is set, so callers can wrap with
34
- * `if (override) ...` without juggling empty strings. Body inspection
35
- * is lenient - any plain object with one of the configured keys
36
- * counts, mirroring how AI SDK chat clients pass arbitrary metadata
37
- * alongside `messages`.
38
- */
39
- export function extractModelOverride(req) {
40
- const headers = req.headers;
41
- if (headers) {
42
- const headerVal = stringUtils.firstNonEmpty(headers[MODEL_OVERRIDE_HEADER] ?? headers[MODEL_OVERRIDE_HEADER.toLowerCase()]);
43
- if (headerVal)
44
- return headerVal;
45
- }
46
- if (req.query) {
47
- const queryVal = stringUtils.firstNonEmpty(req.query[MODEL_OVERRIDE_QUERY]);
48
- if (queryVal)
49
- return queryVal;
50
- }
51
- if (req.body && typeof req.body === "object") {
52
- const record = req.body;
53
- for (const field of MODEL_OVERRIDE_BODY_FIELDS) {
54
- const bodyVal = stringUtils.firstNonEmpty(record[field]);
55
- if (bodyVal)
56
- return bodyVal;
57
- }
58
- }
59
- return null;
60
- }
61
- /**
62
- * Read the fuzzy-resolution config knobs off the plugin config with
63
- * `@dbx-tools/model` defaults applied. Kept here so `buildModel` and
64
- * the `/models` route agree on what "enabled" means.
65
- *
66
- * `fallbacks` is the priority-ordered list `resolveModel` walks first
67
- * when nothing explicit is set; defaults to an empty list so the
68
- * generic resolver falls through to the live catalogue and its own
69
- * `FALLBACK_MODEL_IDS` floor.
70
- */
71
- export function resolveServingConfig(config) {
72
- return {
73
- ttlMs: config.modelCacheTtlMs ?? DEFAULT_MODEL_CACHE_TTL_MS,
74
- threshold: config.modelFuzzyThreshold ?? DEFAULT_FUZZY_THRESHOLD,
75
- fuzzy: config.modelFuzzyMatch !== false,
76
- allowOverride: config.modelOverride !== false,
77
- fallbacks: config.defaultModelFallbacks ?? [],
78
- };
79
- }
@@ -1,66 +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
- import { WorkspaceClient } from "@databricks/sdk-experimental";
23
- import type { GenieDatasetData } from "@dbx-tools/appkit-mastra-shared";
24
- /**
25
- * Hard server-side cap on rows returned by the
26
- * `/embed/data/:id` route. Sized to keep responses small
27
- * enough for inline tables to render snappily; the route surfaces
28
- * a `truncated` flag whenever the upstream `rowCount` exceeds
29
- * this so end users know they're seeing a sample.
30
- */
31
- export declare const STATEMENT_ROW_CAP = 500;
32
- /**
33
- * Fetch a single statement's rows via the Statement Execution API
34
- * and reshape into the shared {@link GenieDatasetData} shape
35
- * (column array + row records).
36
- *
37
- * Optional `limit` slices the returned `rows` client-side so the
38
- * agent can scan a small sample without paging the full result
39
- * set into context. `rowCount` always reflects the upstream total
40
- * so callers know when the slice truncated.
41
- *
42
- * Exported because every consumer in the plugin (the
43
- * `get_statement` tool, the `prepare_chart` dataset resolver, and
44
- * the `/embed/data/:id` route) needs the exact same
45
- * fetch + coercion pipeline so LLM-side `get_statement` output
46
- * and UI-side `[data:<id>]` rendering stay shape-identical for
47
- * the same `statement_id`.
48
- */
49
- export declare function fetchStatementData(client: WorkspaceClient, statementId: string, options?: {
50
- limit?: number;
51
- signal?: AbortSignal;
52
- }): Promise<GenieDatasetData>;
53
- /**
54
- * True when `err` looks like the Databricks SDK's "statement not
55
- * found" error. Matches the typed {@link ApiError} 404 /
56
- * `RESOURCE_DOES_NOT_EXIST` shape first, then falls back to the
57
- * lower-level {@link HttpError} 404, then to a loose `does not
58
- * exist` / `not found` message sniff for SDK shapes we haven't
59
- * catalogued.
60
- *
61
- * Pulled into its own helper so callers (notably the
62
- * `/embed/data/:id` route) stay decoupled from SDK
63
- * error-class identity, and the conversion logic stays testable
64
- * in isolation.
65
- */
66
- export declare function isStatementNotFoundError(err: unknown): boolean;
@@ -1,111 +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
- import { ApiError, HttpError, WorkspaceClient } from "@databricks/sdk-experimental";
23
- import { apiUtils } from "@dbx-tools/shared";
24
- /**
25
- * Hard server-side cap on rows returned by the
26
- * `/embed/data/:id` route. Sized to keep responses small
27
- * enough for inline tables to render snappily; the route surfaces
28
- * a `truncated` flag whenever the upstream `rowCount` exceeds
29
- * this so end users know they're seeing a sample.
30
- */
31
- export const STATEMENT_ROW_CAP = 500;
32
- /**
33
- * Best-effort numeric coercion for the Statement Execution API's
34
- * all-strings cells. Leaves non-numeric strings (and explicit
35
- * `null`s) intact; everything else flows through `Number`.
36
- */
37
- function coerceCell(cell) {
38
- if (cell === null)
39
- return null;
40
- if (/^-?\d+(\.\d+)?$/.test(cell)) {
41
- const n = Number(cell);
42
- if (Number.isFinite(n))
43
- return n;
44
- }
45
- return cell;
46
- }
47
- /**
48
- * Fetch a single statement's rows via the Statement Execution API
49
- * and reshape into the shared {@link GenieDatasetData} shape
50
- * (column array + row records).
51
- *
52
- * Optional `limit` slices the returned `rows` client-side so the
53
- * agent can scan a small sample without paging the full result
54
- * set into context. `rowCount` always reflects the upstream total
55
- * so callers know when the slice truncated.
56
- *
57
- * Exported because every consumer in the plugin (the
58
- * `get_statement` tool, the `prepare_chart` dataset resolver, and
59
- * the `/embed/data/:id` route) needs the exact same
60
- * fetch + coercion pipeline so LLM-side `get_statement` output
61
- * and UI-side `[data:<id>]` rendering stay shape-identical for
62
- * the same `statement_id`.
63
- */
64
- export async function fetchStatementData(client, statementId, options) {
65
- const ctx = options?.signal ? apiUtils.toContext(options.signal) : undefined;
66
- const r = await client.statementExecution.getStatement({ statement_id: statementId }, ctx);
67
- const columns = (r.manifest?.schema?.columns ?? []).map((c) => c.name ?? "");
68
- const dataArray = (r.result?.data_array ?? []);
69
- const sliced = options?.limit !== undefined && options.limit >= 0
70
- ? dataArray.slice(0, options.limit)
71
- : dataArray;
72
- const rows = sliced.map((row) => {
73
- const obj = {};
74
- columns.forEach((col, i) => {
75
- obj[col] = coerceCell(row[i] ?? null);
76
- });
77
- return obj;
78
- });
79
- return {
80
- columns,
81
- rows,
82
- rowCount: r.manifest?.total_row_count ?? dataArray.length,
83
- };
84
- }
85
- /**
86
- * True when `err` looks like the Databricks SDK's "statement not
87
- * found" error. Matches the typed {@link ApiError} 404 /
88
- * `RESOURCE_DOES_NOT_EXIST` shape first, then falls back to the
89
- * lower-level {@link HttpError} 404, then to a loose `does not
90
- * exist` / `not found` message sniff for SDK shapes we haven't
91
- * catalogued.
92
- *
93
- * Pulled into its own helper so callers (notably the
94
- * `/embed/data/:id` route) stay decoupled from SDK
95
- * error-class identity, and the conversion logic stays testable
96
- * in isolation.
97
- */
98
- export function isStatementNotFoundError(err) {
99
- if (err instanceof ApiError) {
100
- if (err.statusCode === 404)
101
- return true;
102
- if (err.errorCode === "RESOURCE_DOES_NOT_EXIST")
103
- return true;
104
- }
105
- if (err instanceof HttpError && err.code === 404)
106
- return true;
107
- if (err instanceof Error && /does not exist|not found/i.test(err.message)) {
108
- return true;
109
- }
110
- return false;
111
- }
@@ -1,23 +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
- import type { MastraWriter } 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: MastraWriter | undefined, chunk: unknown, context?: Record<string, unknown>): Promise<void>;