@dbx-tools/appkit-mastra 0.1.67 → 0.1.69
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 +7 -9
- package/src/agents.ts +0 -778
- package/src/chart.ts +0 -716
- package/src/config.ts +0 -384
- package/src/genie.ts +0 -1111
- package/src/history.ts +0 -322
- package/src/index.ts +0 -25
- package/src/mcp.ts +0 -89
- package/src/memory.ts +0 -293
- package/src/model.ts +0 -263
- package/src/observability.ts +0 -114
- package/src/plugin.ts +0 -666
- package/src/processor.ts +0 -46
- package/src/processors/strip-stale-charts.ts +0 -102
- package/src/server.ts +0 -195
- package/src/serving.ts +0 -100
- package/src/statement.ts +0 -92
- package/src/writer.ts +0 -44
package/src/processor.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type { Processor } from "@mastra/core/processors";
|
|
2
|
-
|
|
3
|
-
export class ResultProcessor implements Processor {
|
|
4
|
-
id = "result-processor";
|
|
5
|
-
|
|
6
|
-
// Tell Mastra to also route tool/data parts to this processor method
|
|
7
|
-
processDataParts = true;
|
|
8
|
-
|
|
9
|
-
async processOutputStream({ part }: { part: any }): Promise<any | null> {
|
|
10
|
-
// 1. Guard clause: Ensure the chunk is a valid object
|
|
11
|
-
if (!part || typeof part !== "object") {
|
|
12
|
-
return part;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// 2. Filter for the targeted frame types
|
|
16
|
-
const targetedTypes = ["step-finish", "finish", "tool-result", "data-tool-agent"];
|
|
17
|
-
if (!targetedTypes.includes(part.type)) {
|
|
18
|
-
return part; // Return unchanged to pass-through
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// 3. Check for the presence of a payload object
|
|
22
|
-
const payload = part.payload;
|
|
23
|
-
if (!payload || typeof payload !== "object") {
|
|
24
|
-
return part;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// 4. Safely delete the unwanted keys from the payload reference
|
|
28
|
-
const keysToDelete = ["output", "messages", "response", "result"];
|
|
29
|
-
for (const key of keysToDelete) {
|
|
30
|
-
if (key in payload) {
|
|
31
|
-
const value = payload[key];
|
|
32
|
-
if (typeof value === "object") {
|
|
33
|
-
payload[key] = {};
|
|
34
|
-
} else if (Array.isArray(value)) {
|
|
35
|
-
payload[key] = [];
|
|
36
|
-
} else {
|
|
37
|
-
delete payload[key];
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// 5. Return the modified part object. Mastra handles re-serialization
|
|
43
|
-
// for the outbound SSE client stream automatically.
|
|
44
|
-
return part;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
@@ -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,100 +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 {
|
|
13
|
-
MODEL_OVERRIDE_BODY_FIELDS,
|
|
14
|
-
MODEL_OVERRIDE_HEADER,
|
|
15
|
-
MODEL_OVERRIDE_QUERY,
|
|
16
|
-
} from "@dbx-tools/appkit-mastra-shared";
|
|
17
|
-
import { DEFAULT_FUZZY_THRESHOLD, DEFAULT_MODEL_CACHE_TTL_MS } from "@dbx-tools/model";
|
|
18
|
-
import { stringUtils } from "@dbx-tools/shared";
|
|
19
|
-
|
|
20
|
-
import type { MastraPluginConfig } from "./config.js";
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* `RequestContext` key under which {@link MastraServer} stores the
|
|
24
|
-
* per-request model override (header / query / body). `model.ts`
|
|
25
|
-
* reads it before falling back to the agent / plugin default.
|
|
26
|
-
*/
|
|
27
|
-
export const MASTRA_MODEL_OVERRIDE_KEY = "mastra__model_override";
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Minimal Express-ish request shape used by {@link extractModelOverride}.
|
|
31
|
-
* Keeps this module independent of `express` so the helper can be
|
|
32
|
-
* reused from non-Express adapters.
|
|
33
|
-
*/
|
|
34
|
-
export interface ModelOverrideRequest {
|
|
35
|
-
headers?: Record<string, string | string[] | undefined>;
|
|
36
|
-
query?: Record<string, unknown> | undefined;
|
|
37
|
-
body?: unknown;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Pull a model override out of a single HTTP request, checking
|
|
42
|
-
* sources in priority order:
|
|
43
|
-
*
|
|
44
|
-
* 1. `X-Mastra-Model` header
|
|
45
|
-
* 2. `?model=` query string parameter
|
|
46
|
-
* 3. Body field (`model` or `modelId`, in that order)
|
|
47
|
-
*
|
|
48
|
-
* Returns `null` when nothing is set, so callers can wrap with
|
|
49
|
-
* `if (override) ...` without juggling empty strings. Body inspection
|
|
50
|
-
* is lenient - any plain object with one of the configured keys
|
|
51
|
-
* counts, mirroring how AI SDK chat clients pass arbitrary metadata
|
|
52
|
-
* alongside `messages`.
|
|
53
|
-
*/
|
|
54
|
-
export function extractModelOverride(req: ModelOverrideRequest): string | null {
|
|
55
|
-
const headers = req.headers;
|
|
56
|
-
if (headers) {
|
|
57
|
-
const headerVal = stringUtils.firstNonEmpty(
|
|
58
|
-
headers[MODEL_OVERRIDE_HEADER] ?? headers[MODEL_OVERRIDE_HEADER.toLowerCase()],
|
|
59
|
-
);
|
|
60
|
-
if (headerVal) return headerVal;
|
|
61
|
-
}
|
|
62
|
-
if (req.query) {
|
|
63
|
-
const queryVal = stringUtils.firstNonEmpty(req.query[MODEL_OVERRIDE_QUERY]);
|
|
64
|
-
if (queryVal) return queryVal;
|
|
65
|
-
}
|
|
66
|
-
if (req.body && typeof req.body === "object") {
|
|
67
|
-
const record = req.body as Record<string, unknown>;
|
|
68
|
-
for (const field of MODEL_OVERRIDE_BODY_FIELDS) {
|
|
69
|
-
const bodyVal = stringUtils.firstNonEmpty(record[field]);
|
|
70
|
-
if (bodyVal) return bodyVal;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Read the fuzzy-resolution config knobs off the plugin config with
|
|
78
|
-
* `@dbx-tools/model` defaults applied. Kept here so `buildModel` and
|
|
79
|
-
* the `/models` route agree on what "enabled" means.
|
|
80
|
-
*
|
|
81
|
-
* `fallbacks` is the priority-ordered list `resolveModel` walks first
|
|
82
|
-
* when nothing explicit is set; defaults to an empty list so the
|
|
83
|
-
* generic resolver falls through to the live catalogue and its own
|
|
84
|
-
* `FALLBACK_MODEL_IDS` floor.
|
|
85
|
-
*/
|
|
86
|
-
export function resolveServingConfig(config: MastraPluginConfig): {
|
|
87
|
-
ttlMs: number;
|
|
88
|
-
threshold: number;
|
|
89
|
-
fuzzy: boolean;
|
|
90
|
-
allowOverride: boolean;
|
|
91
|
-
fallbacks: readonly string[];
|
|
92
|
-
} {
|
|
93
|
-
return {
|
|
94
|
-
ttlMs: config.modelCacheTtlMs ?? DEFAULT_MODEL_CACHE_TTL_MS,
|
|
95
|
-
threshold: config.modelFuzzyThreshold ?? DEFAULT_FUZZY_THRESHOLD,
|
|
96
|
-
fuzzy: config.modelFuzzyMatch !== false,
|
|
97
|
-
allowOverride: config.modelOverride !== false,
|
|
98
|
-
fallbacks: config.defaultModelFallbacks ?? [],
|
|
99
|
-
};
|
|
100
|
-
}
|
package/src/statement.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Databricks Statement Execution helpers for the Mastra plugin.
|
|
3
|
-
*
|
|
4
|
-
* Wraps `client.statementExecution.getStatement` with the shape and
|
|
5
|
-
* size handling the plugin's tools and the `/embed/data/:id` route
|
|
6
|
-
* both need: a low-level fetch that returns a raw
|
|
7
|
-
* `{columns, rows, rowCount}` shape and coerces numeric strings to
|
|
8
|
-
* numbers so downstream charts and aggregations don't have to, plus a
|
|
9
|
-
* hard row cap callers clamp `limit` to so a runaway result set can't
|
|
10
|
-
* hose a response. Upstream 404s are detected via
|
|
11
|
-
* `apiUtils.isNotFoundError` at the call sites.
|
|
12
|
-
*
|
|
13
|
-
* Not Genie-specific: a Databricks `statement_id` is workspace
|
|
14
|
-
* scoped and lives in the Statement Execution API regardless of
|
|
15
|
-
* which producer (Genie, a tool, a notebook, etc.) submitted the
|
|
16
|
-
* query. Co-located here so consumers can fetch / cap / handle
|
|
17
|
-
* 404s without reaching into the Genie tool module.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { WorkspaceClient } from "@databricks/sdk-experimental";
|
|
21
|
-
import type { GenieDatasetData } from "@dbx-tools/appkit-mastra-shared";
|
|
22
|
-
import { apiUtils } from "@dbx-tools/shared";
|
|
23
|
-
|
|
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
|
-
/**
|
|
34
|
-
* Best-effort numeric coercion for the Statement Execution API's
|
|
35
|
-
* all-strings cells. Leaves non-numeric strings (and explicit
|
|
36
|
-
* `null`s) intact; everything else flows through `Number`.
|
|
37
|
-
*/
|
|
38
|
-
function coerceCell(cell: string | null): unknown {
|
|
39
|
-
if (cell === null) return null;
|
|
40
|
-
if (/^-?\d+(\.\d+)?$/.test(cell)) {
|
|
41
|
-
const n = Number(cell);
|
|
42
|
-
if (Number.isFinite(n)) return n;
|
|
43
|
-
}
|
|
44
|
-
return cell;
|
|
45
|
-
}
|
|
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(
|
|
65
|
-
client: WorkspaceClient,
|
|
66
|
-
statementId: string,
|
|
67
|
-
options?: { limit?: number; signal?: AbortSignal },
|
|
68
|
-
): Promise<GenieDatasetData> {
|
|
69
|
-
const ctx = options?.signal ? apiUtils.toContext(options.signal) : undefined;
|
|
70
|
-
const r = await client.statementExecution.getStatement(
|
|
71
|
-
{ statement_id: statementId },
|
|
72
|
-
ctx,
|
|
73
|
-
);
|
|
74
|
-
const columns = (r.manifest?.schema?.columns ?? []).map((c) => c.name ?? "");
|
|
75
|
-
const dataArray = (r.result?.data_array ?? []) as Array<Array<string | null>>;
|
|
76
|
-
const sliced =
|
|
77
|
-
options?.limit !== undefined && options.limit >= 0
|
|
78
|
-
? dataArray.slice(0, options.limit)
|
|
79
|
-
: dataArray;
|
|
80
|
-
const rows = sliced.map((row) => {
|
|
81
|
-
const obj: Record<string, unknown> = {};
|
|
82
|
-
columns.forEach((col, i) => {
|
|
83
|
-
obj[col] = coerceCell(row[i] ?? null);
|
|
84
|
-
});
|
|
85
|
-
return obj;
|
|
86
|
-
});
|
|
87
|
-
return {
|
|
88
|
-
columns,
|
|
89
|
-
rows,
|
|
90
|
-
rowCount: r.manifest?.total_row_count ?? dataArray.length,
|
|
91
|
-
};
|
|
92
|
-
}
|
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
|
-
}
|