@dbx-tools/appkit-mastra 0.1.3 → 0.1.5
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/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/src/agents.d.ts +1 -1
- package/dist/src/agents.js +25 -11
- package/dist/src/chart.d.ts +104 -0
- package/dist/src/chart.js +375 -0
- package/dist/src/genie.d.ts +20 -13
- package/dist/src/genie.js +393 -70
- package/dist/src/history.d.ts +67 -0
- package/dist/src/history.js +158 -0
- package/dist/src/plugin.d.ts +10 -0
- package/dist/src/plugin.js +22 -2
- package/dist/src/render-chart-route.d.ts +33 -0
- package/dist/src/render-chart-route.js +120 -0
- package/dist/src/server.d.ts +4 -0
- package/dist/src/server.js +49 -45
- package/index.ts +1 -0
- package/package.json +4 -4
- package/src/agents.ts +27 -15
- package/src/chart.ts +425 -0
- package/src/genie.ts +431 -97
- package/src/history.ts +198 -0
- package/src/plugin.ts +23 -2
- package/src/render-chart-route.ts +141 -0
- package/src/server.ts +65 -51
- package/README.md +0 -593
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thread history loader exposed as a Mastra custom API route.
|
|
3
|
+
*
|
|
4
|
+
* Backed entirely by native Mastra: looks up the active agent by id,
|
|
5
|
+
* asks its `Memory` instance to `recall` a page of `MastraDBMessage`s,
|
|
6
|
+
* and converts the result to AI SDK V5 `UIMessage`s with the official
|
|
7
|
+
* {@link toAISdkV5Messages} helper from `@mastra/ai-sdk/ui`. No direct
|
|
8
|
+
* database reads.
|
|
9
|
+
*
|
|
10
|
+
* The route is registered through {@link historyRoute} as a Mastra
|
|
11
|
+
* `registerApiRoute` so it sits in the same dispatcher pipeline as
|
|
12
|
+
* `chatRoute`. That means the `MastraServer` auth middleware (in
|
|
13
|
+
* `./server.ts`) has already populated `RequestContext` with
|
|
14
|
+
* `MASTRA_THREAD_ID_KEY` and `MASTRA_RESOURCE_ID_KEY` by the time
|
|
15
|
+
* the handler runs - no cookie or user lookups happen here, and the
|
|
16
|
+
* session-cookie logic stays the single source of truth in `server.ts`.
|
|
17
|
+
*/
|
|
18
|
+
import { toAISdkV5Messages } from "@mastra/ai-sdk/ui";
|
|
19
|
+
import { MASTRA_RESOURCE_ID_KEY, MASTRA_THREAD_ID_KEY, } from "@mastra/core/request-context";
|
|
20
|
+
import { registerApiRoute } from "@mastra/core/server";
|
|
21
|
+
/** Default history page size; matches the Mastra storage default. */
|
|
22
|
+
const DEFAULT_PER_PAGE = 20;
|
|
23
|
+
/** Hard cap so a misbehaving client can't fetch the whole thread at once. */
|
|
24
|
+
const MAX_PER_PAGE = 200;
|
|
25
|
+
/**
|
|
26
|
+
* Fetch a page of UI-formatted messages for a thread.
|
|
27
|
+
*
|
|
28
|
+
* Uses the agent's resolved `Memory` (`getMemory()`) so per-agent
|
|
29
|
+
* storage namespaces (`mastra_<agentId>` schemas) and any future
|
|
30
|
+
* memory-side filters apply automatically. When the agent has no
|
|
31
|
+
* memory configured the response is a successful empty page so
|
|
32
|
+
* callers don't have to special-case stateless agents.
|
|
33
|
+
*
|
|
34
|
+
* Pagination is descending-by-default: page 0 is the most recent
|
|
35
|
+
* page, page 1 the page before that, etc. The returned `uiMessages`
|
|
36
|
+
* are always re-sorted into chronological order (oldest -> newest)
|
|
37
|
+
* so the client can prepend them above the existing transcript
|
|
38
|
+
* without sorting locally.
|
|
39
|
+
*/
|
|
40
|
+
export async function loadHistory(opts) {
|
|
41
|
+
const perPage = clampPerPage(opts.perPage);
|
|
42
|
+
const page = Math.max(0, Math.trunc(opts.page ?? 0));
|
|
43
|
+
const memory = await opts.agent.getMemory();
|
|
44
|
+
if (!memory) {
|
|
45
|
+
return { uiMessages: [], page, perPage, total: 0, hasMore: false };
|
|
46
|
+
}
|
|
47
|
+
const result = await memory.recall({
|
|
48
|
+
threadId: opts.threadId,
|
|
49
|
+
...(opts.resourceId ? { resourceId: opts.resourceId } : {}),
|
|
50
|
+
page,
|
|
51
|
+
perPage,
|
|
52
|
+
orderBy: {
|
|
53
|
+
field: "createdAt",
|
|
54
|
+
direction: opts.ascending ? "ASC" : "DESC",
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
const chronological = sortChronological(result.messages);
|
|
58
|
+
const uiMessages = toAISdkV5Messages(chronological);
|
|
59
|
+
return {
|
|
60
|
+
uiMessages,
|
|
61
|
+
page,
|
|
62
|
+
perPage,
|
|
63
|
+
total: result.total,
|
|
64
|
+
hasMore: result.hasMore,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Register a `GET <path>` Mastra custom API route that returns a page
|
|
69
|
+
* of AI SDK V5 `UIMessage`s for the caller's current thread.
|
|
70
|
+
*
|
|
71
|
+
* Modeled after `chatRoute` from `@mastra/ai-sdk`: pass `agent` for a
|
|
72
|
+
* fixed-agent mount, or include `:agentId` in the path for dynamic
|
|
73
|
+
* routing. Pairs cleanly with the AppKit Mastra plugin's chat route
|
|
74
|
+
* layout (`/route/chat` + `/route/chat/:agentId`).
|
|
75
|
+
*
|
|
76
|
+
* The handler reads `threadId` and `resourceId` from `RequestContext`
|
|
77
|
+
* (populated upstream by `MastraServer.registerAuthMiddleware`), so
|
|
78
|
+
* no cookie or user lookups happen here.
|
|
79
|
+
*/
|
|
80
|
+
export function historyRoute(options) {
|
|
81
|
+
const { path } = options;
|
|
82
|
+
const fixedAgent = "agent" in options ? options.agent : undefined;
|
|
83
|
+
if (!fixedAgent && !path.includes(":agentId")) {
|
|
84
|
+
throw new Error("historyRoute path must include `:agentId` or `agent` must be passed explicitly");
|
|
85
|
+
}
|
|
86
|
+
return registerApiRoute(path, {
|
|
87
|
+
method: "GET",
|
|
88
|
+
handler: async (c) => {
|
|
89
|
+
const mastra = c.get("mastra");
|
|
90
|
+
const requestContext = c.get("requestContext");
|
|
91
|
+
const agentId = fixedAgent ?? c.req.param("agentId");
|
|
92
|
+
if (!agentId) {
|
|
93
|
+
return c.json({ error: "agentId is required" }, 400);
|
|
94
|
+
}
|
|
95
|
+
const agent = mastra.getAgentById(agentId);
|
|
96
|
+
if (!agent) {
|
|
97
|
+
return c.json({ error: `Unknown agent "${agentId}"` }, 404);
|
|
98
|
+
}
|
|
99
|
+
const threadId = requestContext.get(MASTRA_THREAD_ID_KEY);
|
|
100
|
+
if (!threadId) {
|
|
101
|
+
return c.json({ error: "thread id missing from request context" }, 400);
|
|
102
|
+
}
|
|
103
|
+
const resourceId = requestContext.get(MASTRA_RESOURCE_ID_KEY);
|
|
104
|
+
const payload = await loadHistory({
|
|
105
|
+
agent,
|
|
106
|
+
threadId,
|
|
107
|
+
...(resourceId ? { resourceId } : {}),
|
|
108
|
+
page: parseIntParam(c.req.query("page")),
|
|
109
|
+
perPage: parseIntParam(c.req.query("perPage")),
|
|
110
|
+
});
|
|
111
|
+
return c.json(payload);
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/** Coerce / clamp `perPage`; falls back to the page-size default. */
|
|
116
|
+
function clampPerPage(value) {
|
|
117
|
+
if (value === undefined || Number.isNaN(value))
|
|
118
|
+
return DEFAULT_PER_PAGE;
|
|
119
|
+
const n = Math.trunc(value);
|
|
120
|
+
if (n <= 0)
|
|
121
|
+
return DEFAULT_PER_PAGE;
|
|
122
|
+
return Math.min(n, MAX_PER_PAGE);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Sort messages oldest-first by `createdAt`, falling back to whatever
|
|
126
|
+
* order the storage returned them in. The native `recall` call honors
|
|
127
|
+
* `orderBy` but doesn't guarantee a stable secondary sort, so we
|
|
128
|
+
* normalize here before handing the page to the AI SDK converter.
|
|
129
|
+
*/
|
|
130
|
+
function sortChronological(messages) {
|
|
131
|
+
return [...messages].sort((a, b) => {
|
|
132
|
+
const ta = toEpoch(a.createdAt);
|
|
133
|
+
const tb = toEpoch(b.createdAt);
|
|
134
|
+
return ta - tb;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function toEpoch(value) {
|
|
138
|
+
if (value instanceof Date)
|
|
139
|
+
return value.getTime();
|
|
140
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
141
|
+
const parsed = new Date(value).getTime();
|
|
142
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
143
|
+
}
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Coerce a Hono query value into a non-negative integer. Returns
|
|
148
|
+
* `undefined` for empty / non-numeric / negative inputs so
|
|
149
|
+
* {@link loadHistory} can apply its built-in defaults.
|
|
150
|
+
*/
|
|
151
|
+
function parseIntParam(value) {
|
|
152
|
+
if (!value)
|
|
153
|
+
return undefined;
|
|
154
|
+
const n = Number(value);
|
|
155
|
+
if (!Number.isFinite(n) || n < 0)
|
|
156
|
+
return undefined;
|
|
157
|
+
return Math.trunc(n);
|
|
158
|
+
}
|
package/dist/src/plugin.d.ts
CHANGED
|
@@ -109,6 +109,16 @@ export declare class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
109
109
|
};
|
|
110
110
|
clientConfig(): Record<string, unknown>;
|
|
111
111
|
injectRoutes(router: IAppRouter): void;
|
|
112
|
+
/**
|
|
113
|
+
* Return `this.asUser(req)` when the request carries an OBO token,
|
|
114
|
+
* otherwise return `this` directly. Prevents the noisy AppKit warn
|
|
115
|
+
* (`asUser() called without user token in development mode. Skipping
|
|
116
|
+
* user impersonation.`) on every request in local dev where the
|
|
117
|
+
* browser never sends `x-forwarded-access-token`. Behavior is
|
|
118
|
+
* unchanged in production: a missing token always means a real OBO
|
|
119
|
+
* proxy call (and AppKit will throw upstream if that's wrong).
|
|
120
|
+
*/
|
|
121
|
+
private userScopedSelf;
|
|
112
122
|
/**
|
|
113
123
|
* Implementation backing both the `/models` route and the
|
|
114
124
|
* `listModels` export. Runs inside the AppKit user-context proxy so
|
package/dist/src/plugin.js
CHANGED
|
@@ -32,6 +32,8 @@ import { chatRoute } from "@mastra/ai-sdk";
|
|
|
32
32
|
import { Mastra } from "@mastra/core/mastra";
|
|
33
33
|
import express from "express";
|
|
34
34
|
import { buildAgents, FALLBACK_AGENT_ID } from "./agents.js";
|
|
35
|
+
import { historyRoute } from "./history.js";
|
|
36
|
+
import { renderChartRoute } from "./render-chart-route.js";
|
|
35
37
|
import { createMemoryBuilder, needsLakebase } from "./memory.js";
|
|
36
38
|
import { attachRoutePatchMiddleware, MastraServer } from "./server.js";
|
|
37
39
|
import { clearServingEndpointsCache, listServingEndpoints, resolveServingConfig, } from "./serving.js";
|
|
@@ -159,6 +161,9 @@ export class MastraPlugin extends Plugin {
|
|
|
159
161
|
chatPath: `${basePath}/route/chat`,
|
|
160
162
|
chatPathTemplate: `${basePath}/route/chat/:agentId`,
|
|
161
163
|
modelsPath: `${basePath}/models`,
|
|
164
|
+
historyPath: `${basePath}/route/history`,
|
|
165
|
+
historyPathTemplate: `${basePath}/route/history/:agentId`,
|
|
166
|
+
renderChartPath: `${basePath}/route/render-chart`,
|
|
162
167
|
defaultAgent: this.built?.defaultAgentId ?? FALLBACK_AGENT_ID,
|
|
163
168
|
agents: Object.keys(this.built?.agents ?? {}),
|
|
164
169
|
};
|
|
@@ -171,7 +176,7 @@ export class MastraPlugin extends Plugin {
|
|
|
171
176
|
// the Mastra subapp. Errors propagate to Express's default error
|
|
172
177
|
// handler via `next(err)` so callers see the real SDK message.
|
|
173
178
|
router.get("/models", (req, res, next) => {
|
|
174
|
-
this.
|
|
179
|
+
this.userScopedSelf(req)
|
|
175
180
|
.listModels()
|
|
176
181
|
.then((endpoints) => res.json({ endpoints }))
|
|
177
182
|
.catch(next);
|
|
@@ -179,9 +184,21 @@ export class MastraPlugin extends Plugin {
|
|
|
179
184
|
router.use("", (req, res, next) => {
|
|
180
185
|
if (!this.mastraApp)
|
|
181
186
|
return res.status(503).end();
|
|
182
|
-
return this.
|
|
187
|
+
return this.userScopedSelf(req).mastraApp(req, res, next);
|
|
183
188
|
});
|
|
184
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
|
+
}
|
|
185
202
|
/**
|
|
186
203
|
* Implementation backing both the `/models` route and the
|
|
187
204
|
* `listModels` export. Runs inside the AppKit user-context proxy so
|
|
@@ -227,6 +244,9 @@ export class MastraPlugin extends Plugin {
|
|
|
227
244
|
customApiRoutes: [
|
|
228
245
|
chatRoute({ path: "/route/chat", agent: this.built.defaultAgentId }),
|
|
229
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 }),
|
|
230
250
|
],
|
|
231
251
|
});
|
|
232
252
|
await this.mastraServer.init();
|
|
@@ -0,0 +1,33 @@
|
|
|
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;
|
|
@@ -0,0 +1,120 @@
|
|
|
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
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* that lets `@mastra/ai-sdk` `chatRoute` work behind an Express mount
|
|
5
5
|
* point.
|
|
6
6
|
*/
|
|
7
|
+
import { type RequestContext } from "@mastra/core/request-context";
|
|
7
8
|
import { MastraServer as MastraServerExpress } from "@mastra/express";
|
|
8
9
|
import type express from "express";
|
|
9
10
|
import { type MastraPluginConfig } from "./config.js";
|
|
@@ -17,6 +18,9 @@ export declare class MastraServer extends MastraServerExpress {
|
|
|
17
18
|
private log;
|
|
18
19
|
constructor(config: MastraPluginConfig, ...args: ConstructorParameters<typeof MastraServerExpress>);
|
|
19
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;
|
|
20
24
|
}
|
|
21
25
|
/**
|
|
22
26
|
* Patches around `@mastra/express`'s custom-route dispatcher so
|
package/dist/src/server.js
CHANGED
|
@@ -27,55 +27,59 @@ export class MastraServer extends MastraServerExpress {
|
|
|
27
27
|
registerAuthMiddleware() {
|
|
28
28
|
super.registerAuthMiddleware();
|
|
29
29
|
this.app.use((req, res, next) => {
|
|
30
|
-
const executionContext = getExecutionContext();
|
|
31
|
-
const user = {
|
|
32
|
-
id: "userId" in executionContext
|
|
33
|
-
? executionContext.userId
|
|
34
|
-
: executionContext.serviceUserId,
|
|
35
|
-
executionContext,
|
|
36
|
-
};
|
|
37
30
|
const requestContext = res.locals.requestContext;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
requestContext.set(MASTRA_RESOURCE_ID_KEY, user.id);
|
|
42
|
-
}
|
|
43
|
-
const cookies = httpUtils.parseCookies(req.headers.cookie);
|
|
44
|
-
const cookieName = stringUtils.toIdentifierWithOptions({ delimiter: "_", distinct: true }, "appkit", this.config.name, "sessionId");
|
|
45
|
-
let sessionId = cookies[cookieName];
|
|
46
|
-
if (!sessionId) {
|
|
47
|
-
sessionId = randomUUID();
|
|
48
|
-
res.cookie(cookieName, sessionId, {
|
|
49
|
-
httpOnly: true,
|
|
50
|
-
sameSite: "lax",
|
|
51
|
-
secure: req.secure,
|
|
52
|
-
path: "/",
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
res.locals.sessionId = sessionId;
|
|
56
|
-
if (!requestContext.get(MASTRA_THREAD_ID_KEY)) {
|
|
57
|
-
this.log.debug(`Setting thread id: ${sessionId}`);
|
|
58
|
-
requestContext.set(MASTRA_THREAD_ID_KEY, sessionId);
|
|
59
|
-
}
|
|
60
|
-
// Per-request model override: only honored when the plugin
|
|
61
|
-
// opts in (default). Sources, in priority order, are
|
|
62
|
-
// `X-Mastra-Model` header, `?model=` query, and `model` /
|
|
63
|
-
// `modelId` body field; see `serving.ts`.
|
|
64
|
-
const serving = resolveServingConfig(this.config);
|
|
65
|
-
if (serving.allowOverride) {
|
|
66
|
-
const override = extractModelOverride({
|
|
67
|
-
headers: req.headers,
|
|
68
|
-
query: req.query,
|
|
69
|
-
body: req.body,
|
|
70
|
-
});
|
|
71
|
-
if (override) {
|
|
72
|
-
this.log.debug(`Model override: ${override}`);
|
|
73
|
-
requestContext.set(MASTRA_MODEL_OVERRIDE_KEY, override);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
31
|
+
this.configureRequestContextUser(requestContext);
|
|
32
|
+
this.configureRequestContextThreadId(req, res, requestContext);
|
|
33
|
+
this.configureRequestContextModelOverride(req, requestContext);
|
|
76
34
|
next();
|
|
77
35
|
});
|
|
78
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
|
+
}
|
|
79
83
|
}
|
|
80
84
|
/**
|
|
81
85
|
* Patches around `@mastra/express`'s custom-route dispatcher so
|
package/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ export * from "./src/plugin.js";
|
|
|
13
13
|
export * from "@dbx-tools/appkit-mastra-shared";
|
|
14
14
|
export * from "./src/config.js";
|
|
15
15
|
export * from "./src/agents.js";
|
|
16
|
+
export * from "./src/chart.js";
|
|
16
17
|
export * from "./src/genie.js";
|
|
17
18
|
export {
|
|
18
19
|
clearServingEndpointsCache,
|
package/package.json
CHANGED
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
"directory": "packages/mastra"
|
|
29
29
|
},
|
|
30
30
|
"name": "@dbx-tools/appkit-mastra",
|
|
31
|
-
"version": "0.1.
|
|
31
|
+
"version": "0.1.5",
|
|
32
32
|
"module": "index.ts",
|
|
33
33
|
"type": "module",
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@dbx-tools/appkit-shared": "0.1.
|
|
36
|
-
"@dbx-tools/appkit-
|
|
35
|
+
"@dbx-tools/appkit-mastra-shared": "0.1.5",
|
|
36
|
+
"@dbx-tools/appkit-shared": "0.1.5",
|
|
37
37
|
"@mastra/ai-sdk": "^1.3",
|
|
38
38
|
"@mastra/core": "^1.32",
|
|
39
39
|
"@mastra/express": "^1.3",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@mastra/memory": "^1.17",
|
|
42
42
|
"@mastra/pg": "^1.10",
|
|
43
43
|
"fuse.js": "^7.0.0",
|
|
44
|
-
"zod": "^4"
|
|
44
|
+
"zod": "^4.3.6"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@databricks/appkit": "^0.35",
|
package/src/agents.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { createTool } from "@mastra/core/tools";
|
|
|
21
21
|
import type { Tool } from "@mastra/core/tools";
|
|
22
22
|
import type { PgVectorConfig, PostgresStoreConfig } from "@mastra/pg";
|
|
23
23
|
|
|
24
|
+
import { buildRenderDataTool } from "./chart.js";
|
|
24
25
|
import type { MastraPluginConfig } from "./config.js";
|
|
25
26
|
import { buildGenieProvider } from "./genie.js";
|
|
26
27
|
import type { MemoryBuilder } from "./memory.js";
|
|
@@ -344,16 +345,21 @@ Rules:
|
|
|
344
345
|
* Override globally via {@link MastraPluginConfig.styleInstructions}
|
|
345
346
|
* (pass `false` to disable entirely, or a string to replace).
|
|
346
347
|
*/
|
|
347
|
-
export const DEFAULT_STYLE_INSTRUCTIONS =
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
348
|
+
export const DEFAULT_STYLE_INSTRUCTIONS = [
|
|
349
|
+
"Output style:",
|
|
350
|
+
"",
|
|
351
|
+
"Use markdown formatting, including headings, lists, and code blocks.",
|
|
352
|
+
"Avoid lists and headers for short replies.",
|
|
353
|
+
"Plain prose.",
|
|
354
|
+
"Use hyphens (-) only. Never use em dashes or en dashes.",
|
|
355
|
+
"Never use emojis.",
|
|
356
|
+
"Skip openers like 'Great question', 'Absolutely', and 'I'd be happy to help'.",
|
|
357
|
+
"Skip closers like 'Let me know if you have any questions'.",
|
|
358
|
+
"Skip self-disclaimers like 'I should mention' and 'It's important to note'.",
|
|
359
|
+
"Answer directly.",
|
|
360
|
+
"Do not include a preamble before the actual answer.",
|
|
361
|
+
"Use lists and headers only when they clarify a multi-part answer.",
|
|
362
|
+
].join("\n");
|
|
357
363
|
|
|
358
364
|
/**
|
|
359
365
|
* Resolve the style block to append to every agent's instructions.
|
|
@@ -364,6 +370,7 @@ function resolveStyleInstructions(config: MastraPluginConfig): string | null {
|
|
|
364
370
|
if (typeof config.styleInstructions === "string") {
|
|
365
371
|
return config.styleInstructions;
|
|
366
372
|
}
|
|
373
|
+
|
|
367
374
|
return DEFAULT_STYLE_INSTRUCTIONS;
|
|
368
375
|
}
|
|
369
376
|
|
|
@@ -371,10 +378,7 @@ function resolveStyleInstructions(config: MastraPluginConfig): string | null {
|
|
|
371
378
|
* Join an agent's bespoke instructions with the resolved style block.
|
|
372
379
|
* Returns the bespoke text unchanged when the style block is disabled.
|
|
373
380
|
*/
|
|
374
|
-
function composeInstructions(
|
|
375
|
-
agentInstructions: string,
|
|
376
|
-
style: string | null,
|
|
377
|
-
): string {
|
|
381
|
+
function composeInstructions(agentInstructions: string, style: string | null): string {
|
|
378
382
|
if (!style) return agentInstructions;
|
|
379
383
|
return `${agentInstructions.trimEnd()}\n\n${style}`;
|
|
380
384
|
}
|
|
@@ -404,7 +408,15 @@ export async function buildAgents(opts: {
|
|
|
404
408
|
const defaultAgentId = config.defaultAgent ?? ids[0] ?? FALLBACK_AGENT_ID;
|
|
405
409
|
|
|
406
410
|
const plugins = buildPluginsMap(context);
|
|
407
|
-
|
|
411
|
+
// System-default ambient tools every agent gets out of the box.
|
|
412
|
+
// Currently just `render_data` for inline visualizations; the
|
|
413
|
+
// user can shadow it by including a same-named tool in their own
|
|
414
|
+
// `config.tools` or per-agent `tools`. Order in {@link resolveTools}
|
|
415
|
+
// is `system -> user-ambient -> per-agent`, last write wins.
|
|
416
|
+
const systemTools: MastraTools = {
|
|
417
|
+
render_data: buildRenderDataTool(config),
|
|
418
|
+
};
|
|
419
|
+
const ambientTools = { ...systemTools, ...(config.tools ?? {}) };
|
|
408
420
|
const style = resolveStyleInstructions(config);
|
|
409
421
|
const agents: Record<string, Agent> = {};
|
|
410
422
|
|