@dbx-tools/appkit-mastra 0.1.5 → 0.1.10
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/README.md +394 -0
- package/index.ts +46 -37
- package/package.json +58 -47
- package/src/agents.ts +233 -62
- package/src/chart.ts +555 -285
- package/src/config.ts +282 -18
- package/src/filesystems.ts +1090 -0
- package/src/genie.ts +1004 -601
- package/src/history.ts +178 -79
- package/src/mcp.ts +105 -0
- package/src/memory.ts +129 -74
- package/src/mlflow.ts +149 -0
- package/src/model.ts +80 -408
- package/src/observability.ts +144 -0
- package/src/pagination.ts +34 -0
- package/src/plugin.ts +573 -59
- package/src/processors.ts +168 -0
- package/src/rest.ts +67 -0
- package/src/server.ts +243 -45
- package/src/serving-sanitize.ts +167 -0
- package/src/serving.ts +27 -224
- package/src/statement.ts +89 -0
- package/src/storage-schema.ts +41 -0
- package/src/summarize.ts +176 -0
- package/src/threads.ts +338 -0
- package/src/workspaces.ts +346 -0
- package/src/writer.ts +44 -0
- package/tsconfig.json +41 -0
- package/dist/index.d.ts +0 -19
- package/dist/index.js +0 -19
- package/dist/src/agents.d.ts +0 -306
- package/dist/src/agents.js +0 -393
- package/dist/src/chart.d.ts +0 -104
- package/dist/src/chart.js +0 -375
- package/dist/src/config.d.ts +0 -170
- package/dist/src/config.js +0 -12
- package/dist/src/genie.d.ts +0 -116
- package/dist/src/genie.js +0 -594
- package/dist/src/history.d.ts +0 -67
- package/dist/src/history.js +0 -158
- package/dist/src/memory.d.ts +0 -79
- package/dist/src/memory.js +0 -197
- package/dist/src/model.d.ts +0 -159
- package/dist/src/model.js +0 -423
- package/dist/src/plugin.d.ts +0 -130
- package/dist/src/plugin.js +0 -255
- package/dist/src/render-chart-route.d.ts +0 -33
- package/dist/src/render-chart-route.js +0 -120
- package/dist/src/server.d.ts +0 -46
- package/dist/src/server.js +0 -113
- package/dist/src/serving.d.ts +0 -156
- package/dist/src/serving.js +0 -214
- package/src/render-chart-route.ts +0 -141
package/dist/src/plugin.js
DELETED
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AppKit plugin that builds one or more Mastra `Agent` instances and
|
|
3
|
-
* mounts the `@mastra/express` server plus `@mastra/ai-sdk` `chatRoute`
|
|
4
|
-
* handlers. The UI message stream matches what `chatRoute()` emits, so
|
|
5
|
-
* the client can use `useChat()` from `@ai-sdk/react` without custom
|
|
6
|
-
* parsing.
|
|
7
|
-
*
|
|
8
|
-
* - Agents: registered through `config.agents` at plugin creation
|
|
9
|
-
* ({@link MastraAgentDefinition}). Each entry's `tools` field accepts
|
|
10
|
-
* either a plain record or a `(plugins) => tools` callback that gets
|
|
11
|
-
* a typed sibling-plugin index ({@link MastraPlugins}). Omit
|
|
12
|
-
* `config.agents` to get a single built-in `default` analyst.
|
|
13
|
-
* - Model: each agent call resolves a `MastraModelConfig` via
|
|
14
|
-
* {@link buildModel} from `./model.js`. Per-agent `model` overrides
|
|
15
|
-
* (`AgentConfig["model"]` or a `modelId` string) flow through
|
|
16
|
-
* {@link buildAgents}.
|
|
17
|
-
* - Memory / storage: per-agent, built by {@link createMemoryBuilder}
|
|
18
|
-
* from `./memory.js`. Both auto-default to `true` when the
|
|
19
|
-
* `lakebase` plugin is registered (unless the caller passed
|
|
20
|
-
* `false` or a custom config). Storage namespaces per agent via
|
|
21
|
-
* `schemaName: "mastra_<agentId>"`; the vector store is a single
|
|
22
|
-
* shared singleton across every agent.
|
|
23
|
-
* - Server: the Express subapp wiring lives in `./server.js`.
|
|
24
|
-
* - HTTP: AppKit mounts this plugin under `/api/mastra`. `chatRoute`
|
|
25
|
-
* is registered at `/route/chat` (bound to `config.defaultAgent` or
|
|
26
|
-
* the first registered id) and `/route/chat/:agentId`, so the
|
|
27
|
-
* AI SDK transport URL is `/api/mastra/route/chat/<agentId>`.
|
|
28
|
-
*/
|
|
29
|
-
import { genie, getExecutionContext, lakebase, Plugin, toPlugin, } from "@databricks/appkit";
|
|
30
|
-
import { logUtils, pluginUtils } from "@dbx-tools/appkit-shared";
|
|
31
|
-
import { chatRoute } from "@mastra/ai-sdk";
|
|
32
|
-
import { Mastra } from "@mastra/core/mastra";
|
|
33
|
-
import express from "express";
|
|
34
|
-
import { buildAgents, FALLBACK_AGENT_ID } from "./agents.js";
|
|
35
|
-
import { historyRoute } from "./history.js";
|
|
36
|
-
import { renderChartRoute } from "./render-chart-route.js";
|
|
37
|
-
import { createMemoryBuilder, needsLakebase } from "./memory.js";
|
|
38
|
-
import { attachRoutePatchMiddleware, MastraServer } from "./server.js";
|
|
39
|
-
import { clearServingEndpointsCache, listServingEndpoints, resolveServingConfig, } from "./serving.js";
|
|
40
|
-
const GENIE_MANIFEST = pluginUtils.data(genie).plugin.manifest;
|
|
41
|
-
const LAKEBASE_MANIFEST = pluginUtils.data(lakebase).plugin.manifest;
|
|
42
|
-
/**
|
|
43
|
-
* AppKit plugin (registered name: `mastra`) that hosts Mastra agents
|
|
44
|
-
* with optional Lakebase-backed memory and AI SDK chat routes under
|
|
45
|
-
* the plugin mount (typically `/api/mastra`).
|
|
46
|
-
*/
|
|
47
|
-
export class MastraPlugin extends Plugin {
|
|
48
|
-
static manifest = {
|
|
49
|
-
name: "mastra",
|
|
50
|
-
displayName: "Mastra",
|
|
51
|
-
description: "Builds a Mastra Agent with user-scoped workspace auth (asUser) " +
|
|
52
|
-
"and optional Postgres-backed Mastra Memory via the `lakebase` plugin.",
|
|
53
|
-
stability: "beta",
|
|
54
|
-
resources: {
|
|
55
|
-
required: [],
|
|
56
|
-
optional: [
|
|
57
|
-
...GENIE_MANIFEST.resources.required,
|
|
58
|
-
...LAKEBASE_MANIFEST.resources.required,
|
|
59
|
-
],
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
/**
|
|
63
|
-
* Tighten resource requirements based on which features are enabled.
|
|
64
|
-
* AppKit calls this at registration time (config-aware) so disabled
|
|
65
|
-
* features don't surface their resource asks to the host app.
|
|
66
|
-
*/
|
|
67
|
-
static getResourceRequirements(config) {
|
|
68
|
-
const resources = [];
|
|
69
|
-
const enabledManifests = [];
|
|
70
|
-
if (needsLakebase(config)) {
|
|
71
|
-
enabledManifests.push(LAKEBASE_MANIFEST);
|
|
72
|
-
}
|
|
73
|
-
for (const m of enabledManifests) {
|
|
74
|
-
for (const resource of m.resources.required) {
|
|
75
|
-
resources.push({ ...resource, required: true });
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return resources;
|
|
79
|
-
}
|
|
80
|
-
log = logUtils.logger(this);
|
|
81
|
-
built = null;
|
|
82
|
-
mastra = null;
|
|
83
|
-
mastraApp = null;
|
|
84
|
-
mastraServer = null;
|
|
85
|
-
async setup() {
|
|
86
|
-
// Wait until sibling plugins (e.g. `lakebase`) finish `setup()` so
|
|
87
|
-
// the lakebase pool is valid when storage/memory are enabled.
|
|
88
|
-
this.context?.onLifecycle("setup:complete", async () => {
|
|
89
|
-
this.applyLakebaseAutoDefaults();
|
|
90
|
-
this.log.info("setup:complete");
|
|
91
|
-
await this.buildAgentAndServer();
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* When the `lakebase` plugin is registered, auto-enable `storage`
|
|
96
|
-
* and `memory` unless the caller opted out explicitly (`false` or a
|
|
97
|
-
* custom config object). Run after `setup:complete` so the lookup
|
|
98
|
-
* is reliable: any plugin that registers itself synchronously is
|
|
99
|
-
* already in the registry by the time this fires.
|
|
100
|
-
*/
|
|
101
|
-
applyLakebaseAutoDefaults() {
|
|
102
|
-
const hasLakebase = pluginUtils.instance(this.context, lakebase) !== undefined;
|
|
103
|
-
if (!hasLakebase)
|
|
104
|
-
return;
|
|
105
|
-
if (this.config.storage === undefined)
|
|
106
|
-
this.config.storage = true;
|
|
107
|
-
if (this.config.memory === undefined)
|
|
108
|
-
this.config.memory = true;
|
|
109
|
-
}
|
|
110
|
-
exports() {
|
|
111
|
-
return {
|
|
112
|
-
/**
|
|
113
|
-
* Ids of every registered agent in registration order. Matches
|
|
114
|
-
* AppKit `agents.list()` so callers can iterate the registry the
|
|
115
|
-
* same way under both plugins.
|
|
116
|
-
*/
|
|
117
|
-
list: () => Object.keys(this.built?.agents ?? {}),
|
|
118
|
-
/**
|
|
119
|
-
* Look up a registered agent by id. Returns `null` (not
|
|
120
|
-
* undefined) when unknown so call sites can early-return without
|
|
121
|
-
* a separate `in` check.
|
|
122
|
-
*/
|
|
123
|
-
get: (id) => this.built?.agents[id] ?? null,
|
|
124
|
-
/**
|
|
125
|
-
* The agent `chatRoute` binds to when the client doesn't name
|
|
126
|
-
* one. Resolves to `config.defaultAgent`, the first registered
|
|
127
|
-
* id, or the built-in `default` fallback.
|
|
128
|
-
*/
|
|
129
|
-
getDefault: () => (this.built && this.built.agents[this.built.defaultAgentId]) ?? null,
|
|
130
|
-
/** Underlying Mastra instance for advanced use (custom routes etc.). */
|
|
131
|
-
getMastra: () => this.mastra,
|
|
132
|
-
/** Express subapp Mastra is mounted on; mostly for tests. */
|
|
133
|
-
getMastraServer: () => this.mastraServer,
|
|
134
|
-
/**
|
|
135
|
-
* Fetch the workspace's Model Serving endpoints (cached). Same
|
|
136
|
-
* payload the `GET /models` route returns; surfaced here so
|
|
137
|
-
* other plugins / scripts can introspect the catalogue without
|
|
138
|
-
* an HTTP round-trip. AppKit wraps this with `asUser(req)` for
|
|
139
|
-
* OBO scoping automatically.
|
|
140
|
-
*/
|
|
141
|
-
listModels: () => this.listModels(),
|
|
142
|
-
/**
|
|
143
|
-
* Force-evict cached endpoint listings via AppKit's
|
|
144
|
-
* `CacheManager`. Useful in tests or right after an admin
|
|
145
|
-
* deploys a new endpoint and doesn't want to wait for the TTL.
|
|
146
|
-
* Returns the underlying `CacheManager.delete`/`clear` promise.
|
|
147
|
-
*/
|
|
148
|
-
clearModelsCache: (host) => clearServingEndpointsCache(host),
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
clientConfig() {
|
|
152
|
-
// AppKit mounts every plugin at `/api/<plugin.name>`. `this.name`
|
|
153
|
-
// honors `config.name` overrides, so the published paths stay
|
|
154
|
-
// accurate if someone remounts the plugin under a custom id.
|
|
155
|
-
// Return widens to `Record<string, unknown>` to satisfy the
|
|
156
|
-
// base-class signature; consumers read it through the typed
|
|
157
|
-
// `MastraClientConfig` shape via `usePluginClientConfig<...>(...)`.
|
|
158
|
-
const basePath = `/api/${this.name}`;
|
|
159
|
-
const config = {
|
|
160
|
-
basePath,
|
|
161
|
-
chatPath: `${basePath}/route/chat`,
|
|
162
|
-
chatPathTemplate: `${basePath}/route/chat/:agentId`,
|
|
163
|
-
modelsPath: `${basePath}/models`,
|
|
164
|
-
historyPath: `${basePath}/route/history`,
|
|
165
|
-
historyPathTemplate: `${basePath}/route/history/:agentId`,
|
|
166
|
-
renderChartPath: `${basePath}/route/render-chart`,
|
|
167
|
-
defaultAgent: this.built?.defaultAgentId ?? FALLBACK_AGENT_ID,
|
|
168
|
-
agents: Object.keys(this.built?.agents ?? {}),
|
|
169
|
-
};
|
|
170
|
-
return config;
|
|
171
|
-
}
|
|
172
|
-
injectRoutes(router) {
|
|
173
|
-
// `GET /models` exposes the cached endpoint list so clients can
|
|
174
|
-
// populate model pickers, validate `?model=` choices, etc. Must
|
|
175
|
-
// be registered before the catch-all that forwards everything to
|
|
176
|
-
// the Mastra subapp. Errors propagate to Express's default error
|
|
177
|
-
// handler via `next(err)` so callers see the real SDK message.
|
|
178
|
-
router.get("/models", (req, res, next) => {
|
|
179
|
-
this.userScopedSelf(req)
|
|
180
|
-
.listModels()
|
|
181
|
-
.then((endpoints) => res.json({ endpoints }))
|
|
182
|
-
.catch(next);
|
|
183
|
-
});
|
|
184
|
-
router.use("", (req, res, next) => {
|
|
185
|
-
if (!this.mastraApp)
|
|
186
|
-
return res.status(503).end();
|
|
187
|
-
return this.userScopedSelf(req).mastraApp(req, res, next);
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Return `this.asUser(req)` when the request carries an OBO token,
|
|
192
|
-
* otherwise return `this` directly. Prevents the noisy AppKit warn
|
|
193
|
-
* (`asUser() called without user token in development mode. Skipping
|
|
194
|
-
* user impersonation.`) on every request in local dev where the
|
|
195
|
-
* browser never sends `x-forwarded-access-token`. Behavior is
|
|
196
|
-
* unchanged in production: a missing token always means a real OBO
|
|
197
|
-
* proxy call (and AppKit will throw upstream if that's wrong).
|
|
198
|
-
*/
|
|
199
|
-
userScopedSelf(req) {
|
|
200
|
-
return req.header("x-forwarded-access-token") ? this.asUser(req) : this;
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Implementation backing both the `/models` route and the
|
|
204
|
-
* `listModels` export. Runs inside the AppKit user-context proxy so
|
|
205
|
-
* `getExecutionContext()` returns the OBO-scoped client.
|
|
206
|
-
*/
|
|
207
|
-
async listModels() {
|
|
208
|
-
const client = getExecutionContext().client;
|
|
209
|
-
const host = (await client.config.getHost()).toString();
|
|
210
|
-
const serving = resolveServingConfig(this.config);
|
|
211
|
-
return listServingEndpoints(client, host, { ttlMs: serving.ttlMs });
|
|
212
|
-
}
|
|
213
|
-
async buildAgentAndServer() {
|
|
214
|
-
// Per-agent memory factory. The builder resolves the Lakebase pool
|
|
215
|
-
// lazily (on first agent that actually needs storage / vector) and
|
|
216
|
-
// caches both the pool and the shared `PgVector` singleton so
|
|
217
|
-
// registering N agents stays cheap. See `./memory.js`.
|
|
218
|
-
const memoryBuilder = needsLakebase(this.config)
|
|
219
|
-
? createMemoryBuilder(this.config, this.context)
|
|
220
|
-
: undefined;
|
|
221
|
-
// Build every agent declared in `config.agents` (or the built-in
|
|
222
|
-
// fallback when none are declared). Each agent's `model` resolves
|
|
223
|
-
// workspace URL + bearer at call time so concurrent requests get
|
|
224
|
-
// distinct user identities; the `asUser(req)` scope around
|
|
225
|
-
// `handleChat` is what lets `getExecutionContext()` return the
|
|
226
|
-
// right user inside the resolver.
|
|
227
|
-
this.built = await buildAgents({
|
|
228
|
-
config: this.config,
|
|
229
|
-
context: this.context,
|
|
230
|
-
memoryBuilder,
|
|
231
|
-
log: this.log,
|
|
232
|
-
});
|
|
233
|
-
// `mastra.server.apiRoutes` is only honored by Mastra's standalone
|
|
234
|
-
// dev server. Since we're hosting Mastra inside our own Express
|
|
235
|
-
// subapp via `@mastra/express`, custom routes must be passed to
|
|
236
|
-
// the `MastraServer` constructor directly.
|
|
237
|
-
this.mastra = new Mastra({ agents: this.built.agents });
|
|
238
|
-
this.mastraApp = express();
|
|
239
|
-
attachRoutePatchMiddleware(this.mastraApp);
|
|
240
|
-
this.mastraServer = new MastraServer(this.config, {
|
|
241
|
-
app: this.mastraApp,
|
|
242
|
-
mastra: this.mastra,
|
|
243
|
-
prefix: "",
|
|
244
|
-
customApiRoutes: [
|
|
245
|
-
chatRoute({ path: "/route/chat", agent: this.built.defaultAgentId }),
|
|
246
|
-
chatRoute({ path: "/route/chat/:agentId" }),
|
|
247
|
-
historyRoute({ path: "/route/history", agent: this.built.defaultAgentId }),
|
|
248
|
-
historyRoute({ path: "/route/history/:agentId" }),
|
|
249
|
-
renderChartRoute({ path: "/route/render-chart", config: this.config }),
|
|
250
|
-
],
|
|
251
|
-
});
|
|
252
|
-
await this.mastraServer.init();
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
export const mastra = toPlugin(MastraPlugin);
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Chart-render HTTP endpoint for the Mastra plugin.
|
|
3
|
-
*
|
|
4
|
-
* The `render_data` tool returns immediately with a `chartId` and
|
|
5
|
-
* emits the dataset over `ctx.writer`; the client then POSTs that
|
|
6
|
-
* dataset to this endpoint to actually run the chart-planner
|
|
7
|
-
* agent and get back an Echarts `EChartsOption` JSON. Planning
|
|
8
|
-
* happens out-of-band so the calling agent's response stream
|
|
9
|
-
* doesn't sit idle waiting for it - the model can finish the
|
|
10
|
-
* report while the client is still rendering charts.
|
|
11
|
-
*
|
|
12
|
-
* Auth flows through the standard Mastra middleware: the route
|
|
13
|
-
* sits in the same dispatcher pipeline as `chatRoute` /
|
|
14
|
-
* `historyRoute`, so by the time the handler runs the
|
|
15
|
-
* `RequestContext` is populated with the workspace user and
|
|
16
|
-
* the chart-planner's model resolver has the OBO token it
|
|
17
|
-
* needs.
|
|
18
|
-
*/
|
|
19
|
-
import type { MastraPluginConfig } from "./config.js";
|
|
20
|
-
/** Options accepted by {@link renderChartRoute}. */
|
|
21
|
-
export interface RenderChartRouteOptions {
|
|
22
|
-
path: string;
|
|
23
|
-
config: MastraPluginConfig;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Register a `POST <path>` Mastra custom API route that runs the
|
|
27
|
-
* chart-planner agent against a dataset and returns an Echarts
|
|
28
|
-
* `EChartsOption` JSON.
|
|
29
|
-
*
|
|
30
|
-
* Body shape: {@link RenderChartRequest}; response:
|
|
31
|
-
* {@link RenderChartResponse}.
|
|
32
|
-
*/
|
|
33
|
-
export declare function renderChartRoute(options: RenderChartRouteOptions): import("@mastra/core/server").ApiRoute;
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Chart-render HTTP endpoint for the Mastra plugin.
|
|
3
|
-
*
|
|
4
|
-
* The `render_data` tool returns immediately with a `chartId` and
|
|
5
|
-
* emits the dataset over `ctx.writer`; the client then POSTs that
|
|
6
|
-
* dataset to this endpoint to actually run the chart-planner
|
|
7
|
-
* agent and get back an Echarts `EChartsOption` JSON. Planning
|
|
8
|
-
* happens out-of-band so the calling agent's response stream
|
|
9
|
-
* doesn't sit idle waiting for it - the model can finish the
|
|
10
|
-
* report while the client is still rendering charts.
|
|
11
|
-
*
|
|
12
|
-
* Auth flows through the standard Mastra middleware: the route
|
|
13
|
-
* sits in the same dispatcher pipeline as `chatRoute` /
|
|
14
|
-
* `historyRoute`, so by the time the handler runs the
|
|
15
|
-
* `RequestContext` is populated with the workspace user and
|
|
16
|
-
* the chart-planner's model resolver has the OBO token it
|
|
17
|
-
* needs.
|
|
18
|
-
*/
|
|
19
|
-
import { registerApiRoute } from "@mastra/core/server";
|
|
20
|
-
import { runChartPlanner } from "./chart.js";
|
|
21
|
-
/** Hard cap so a misbehaving client can't hand us a million-row payload. */
|
|
22
|
-
const MAX_ROWS = 5_000;
|
|
23
|
-
/**
|
|
24
|
-
* Hard cap on the JSON body the route accepts (in bytes). Mirrors
|
|
25
|
-
* the same intent as {@link MAX_ROWS}: bound the chart-planner's
|
|
26
|
-
* prompt size and protect against accidental denial-of-service
|
|
27
|
-
* from a runaway tool that ships an enormous payload.
|
|
28
|
-
*/
|
|
29
|
-
const MAX_BODY_BYTES = 2 * 1024 * 1024;
|
|
30
|
-
/**
|
|
31
|
-
* Register a `POST <path>` Mastra custom API route that runs the
|
|
32
|
-
* chart-planner agent against a dataset and returns an Echarts
|
|
33
|
-
* `EChartsOption` JSON.
|
|
34
|
-
*
|
|
35
|
-
* Body shape: {@link RenderChartRequest}; response:
|
|
36
|
-
* {@link RenderChartResponse}.
|
|
37
|
-
*/
|
|
38
|
-
export function renderChartRoute(options) {
|
|
39
|
-
const { path, config } = options;
|
|
40
|
-
return registerApiRoute(path, {
|
|
41
|
-
method: "POST",
|
|
42
|
-
handler: async (c) => {
|
|
43
|
-
const requestContext = c.get("requestContext");
|
|
44
|
-
// Hono parses the body as JSON; we still validate shape /
|
|
45
|
-
// size since the tool's structured output is a contract,
|
|
46
|
-
// not a guarantee, and the route is publicly mountable.
|
|
47
|
-
const raw = (await c.req.json().catch(() => null));
|
|
48
|
-
const validation = validateBody(raw);
|
|
49
|
-
if ("error" in validation) {
|
|
50
|
-
return c.json({ error: validation.error }, 400);
|
|
51
|
-
}
|
|
52
|
-
const { title, description, data } = validation.body;
|
|
53
|
-
try {
|
|
54
|
-
const result = await runChartPlanner({
|
|
55
|
-
config,
|
|
56
|
-
...(requestContext ? { requestContext } : {}),
|
|
57
|
-
title,
|
|
58
|
-
...(description ? { description } : {}),
|
|
59
|
-
data,
|
|
60
|
-
});
|
|
61
|
-
const payload = {
|
|
62
|
-
option: result.option,
|
|
63
|
-
chartType: result.chartType,
|
|
64
|
-
};
|
|
65
|
-
return c.json(payload);
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
69
|
-
return c.json({ error: message }, 500);
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Best-effort body validation. Surfaces a 400 for malformed input
|
|
76
|
-
* instead of letting a downstream `.map` / `.length` blow up
|
|
77
|
-
* inside the planner agent. Field-level shape mirrors
|
|
78
|
-
* {@link RenderChartRequest}.
|
|
79
|
-
*/
|
|
80
|
-
function validateBody(raw) {
|
|
81
|
-
if (!raw || typeof raw !== "object") {
|
|
82
|
-
return { error: "request body must be a JSON object" };
|
|
83
|
-
}
|
|
84
|
-
const r = raw;
|
|
85
|
-
const title = r.title;
|
|
86
|
-
if (typeof title !== "string" || title.length === 0) {
|
|
87
|
-
return { error: "`title` must be a non-empty string" };
|
|
88
|
-
}
|
|
89
|
-
if (r.description !== undefined && typeof r.description !== "string") {
|
|
90
|
-
return { error: "`description` must be a string when provided" };
|
|
91
|
-
}
|
|
92
|
-
if (!Array.isArray(r.data)) {
|
|
93
|
-
return { error: "`data` must be an array of row objects" };
|
|
94
|
-
}
|
|
95
|
-
if (r.data.length === 0) {
|
|
96
|
-
return { error: "`data` must contain at least one row" };
|
|
97
|
-
}
|
|
98
|
-
if (r.data.length > MAX_ROWS) {
|
|
99
|
-
return { error: `\`data\` exceeds the per-request limit of ${MAX_ROWS} rows` };
|
|
100
|
-
}
|
|
101
|
-
for (const [i, row] of r.data.entries()) {
|
|
102
|
-
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
|
103
|
-
return { error: `data[${i}] must be a plain object` };
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// Approximate body-size check; spares us pulling Buffer in.
|
|
107
|
-
const approximateBytes = JSON.stringify(r.data).length;
|
|
108
|
-
if (approximateBytes > MAX_BODY_BYTES) {
|
|
109
|
-
return {
|
|
110
|
-
error: `\`data\` exceeds the per-request size limit of ${MAX_BODY_BYTES} bytes`,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
return {
|
|
114
|
-
body: {
|
|
115
|
-
title,
|
|
116
|
-
...(typeof r.description === "string" ? { description: r.description } : {}),
|
|
117
|
-
data: r.data,
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
}
|
package/dist/src/server.d.ts
DELETED
|
@@ -1,46 +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 `@mastra/ai-sdk` `chatRoute` work behind an Express mount
|
|
5
|
-
* 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
|
-
configureRequestContextThreadId(req: express.Request, res: express.Response, requestContext: RequestContext): void;
|
|
23
|
-
configureRequestContextModelOverride(req: express.Request, requestContext: RequestContext): void;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Patches around `@mastra/express`'s custom-route dispatcher so
|
|
27
|
-
* `chatRoute` works when `MastraServer` is hosted on an Express subapp
|
|
28
|
-
* mounted under a parent path (e.g. `/api/mastra`).
|
|
29
|
-
*
|
|
30
|
-
* Two concerns:
|
|
31
|
-
*
|
|
32
|
-
* 1. The adapter's `registerCustomApiRoutes` matches against `req.path`
|
|
33
|
-
* (mount-relative, correct) but dispatches to its internal Hono
|
|
34
|
-
* mini-app using `req.originalUrl`, which still contains the parent
|
|
35
|
-
* mount prefix. The Hono app registers the literal `chatRoute` paths
|
|
36
|
-
* (for example `/route/chat`), so the absolute URL never matches
|
|
37
|
-
* until we overwrite `originalUrl` for `/route` and `/route/*` to
|
|
38
|
-
* the mount-relative path.
|
|
39
|
-
*
|
|
40
|
-
* 2. `memory.resource` must be the authenticated user, not whatever the
|
|
41
|
-
* client posts. The custom-route forwarder re-serializes `req.body`
|
|
42
|
-
* into the Request body it hands Hono, so mutating the parsed body
|
|
43
|
-
* here would propagate into `handleChatStream`'s params (kept for
|
|
44
|
-
* future use; `express.json()` runs first so `req.body` is parsed).
|
|
45
|
-
*/
|
|
46
|
-
export declare function attachRoutePatchMiddleware(app: express.Express): void;
|
package/dist/src/server.js
DELETED
|
@@ -1,113 +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 `@mastra/ai-sdk` `chatRoute` work behind an Express mount
|
|
5
|
-
* point.
|
|
6
|
-
*/
|
|
7
|
-
import { getExecutionContext } from "@databricks/appkit";
|
|
8
|
-
import { httpUtils, logUtils, stringUtils } from "@dbx-tools/appkit-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_USER_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
|
-
next();
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
configureRequestContextUser(requestContext) {
|
|
38
|
-
if ([MASTRA_USER_KEY, MASTRA_RESOURCE_ID_KEY].every((key) => requestContext.get(key)))
|
|
39
|
-
return;
|
|
40
|
-
const executionContext = getExecutionContext();
|
|
41
|
-
const user = {
|
|
42
|
-
id: "userId" in executionContext
|
|
43
|
-
? executionContext.userId
|
|
44
|
-
: executionContext.serviceUserId,
|
|
45
|
-
executionContext,
|
|
46
|
-
};
|
|
47
|
-
requestContext.set(MASTRA_USER_KEY, user);
|
|
48
|
-
requestContext.set(MASTRA_RESOURCE_ID_KEY, user.id);
|
|
49
|
-
}
|
|
50
|
-
configureRequestContextThreadId(req, res, requestContext) {
|
|
51
|
-
if (requestContext.get(MASTRA_THREAD_ID_KEY))
|
|
52
|
-
return;
|
|
53
|
-
const cookies = httpUtils.parseCookies(req.headers.cookie);
|
|
54
|
-
const cookieName = stringUtils.toIdentifierWithOptions({ delimiter: "_", distinct: true }, "appkit", this.config.name, "sessionId");
|
|
55
|
-
let sessionId = cookies[cookieName];
|
|
56
|
-
if (!sessionId) {
|
|
57
|
-
sessionId = randomUUID();
|
|
58
|
-
res.cookie(cookieName, sessionId, {
|
|
59
|
-
httpOnly: true,
|
|
60
|
-
sameSite: "lax",
|
|
61
|
-
secure: req.secure,
|
|
62
|
-
path: "/",
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
requestContext.set(MASTRA_THREAD_ID_KEY, sessionId);
|
|
66
|
-
}
|
|
67
|
-
configureRequestContextModelOverride(req, requestContext) {
|
|
68
|
-
// Per-request model override: only honored when the plugin
|
|
69
|
-
// opts in (default). Sources, in priority order, are
|
|
70
|
-
// `X-Mastra-Model` header, `?model=` query, and `model` /
|
|
71
|
-
// `modelId` body field; see `serving.ts`.
|
|
72
|
-
const serving = resolveServingConfig(this.config);
|
|
73
|
-
if (serving.allowOverride) {
|
|
74
|
-
const override = extractModelOverride({
|
|
75
|
-
headers: req.headers,
|
|
76
|
-
query: req.query,
|
|
77
|
-
body: req.body,
|
|
78
|
-
});
|
|
79
|
-
if (override)
|
|
80
|
-
requestContext.set(MASTRA_MODEL_OVERRIDE_KEY, override);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Patches around `@mastra/express`'s custom-route dispatcher so
|
|
86
|
-
* `chatRoute` works when `MastraServer` is hosted on an Express subapp
|
|
87
|
-
* mounted under a parent path (e.g. `/api/mastra`).
|
|
88
|
-
*
|
|
89
|
-
* Two concerns:
|
|
90
|
-
*
|
|
91
|
-
* 1. The adapter's `registerCustomApiRoutes` matches against `req.path`
|
|
92
|
-
* (mount-relative, correct) but dispatches to its internal Hono
|
|
93
|
-
* mini-app using `req.originalUrl`, which still contains the parent
|
|
94
|
-
* mount prefix. The Hono app registers the literal `chatRoute` paths
|
|
95
|
-
* (for example `/route/chat`), so the absolute URL never matches
|
|
96
|
-
* until we overwrite `originalUrl` for `/route` and `/route/*` to
|
|
97
|
-
* the mount-relative path.
|
|
98
|
-
*
|
|
99
|
-
* 2. `memory.resource` must be the authenticated user, not whatever the
|
|
100
|
-
* client posts. The custom-route forwarder re-serializes `req.body`
|
|
101
|
-
* into the Request body it hands Hono, so mutating the parsed body
|
|
102
|
-
* here would propagate into `handleChatStream`'s params (kept for
|
|
103
|
-
* future use; `express.json()` runs first so `req.body` is parsed).
|
|
104
|
-
*/
|
|
105
|
-
export function attachRoutePatchMiddleware(app) {
|
|
106
|
-
app.use((req, _res, next) => {
|
|
107
|
-
const isChat = req.path === "/route" || req.path.startsWith("/route/");
|
|
108
|
-
if (!isChat)
|
|
109
|
-
return next();
|
|
110
|
-
req.originalUrl = req.path;
|
|
111
|
-
next();
|
|
112
|
-
});
|
|
113
|
-
}
|