@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,554 +0,0 @@
1
- /**
2
- * AppKit plugin that builds one or more Mastra `Agent` instances and
3
- * mounts the `@mastra/express` server. Clients drive the conversation
4
- * over the standard Mastra agent stream (`@mastra/client-js`'s
5
- * `getAgent(id).stream()`), so there's no bespoke chat transport to
6
- * keep in sync.
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`. Alongside the
25
- * Mastra agent routes, the plugin registers `/route/history`
26
- * (load + clear thread history), `/models`, `/suggestions`, and the
27
- * generic `/embed/:type/:id` resolver for inline chart / data
28
- * markers.
29
- */
30
- import { genie, getExecutionContext, lakebase, Plugin, toPlugin, } from "@databricks/appkit";
31
- import { appkitUtils, commonUtils, logUtils } from "@dbx-tools/shared";
32
- import { Mastra } from "@mastra/core/mastra";
33
- import express from "express";
34
- import { MASTRA_ROUTES, } from "@dbx-tools/appkit-mastra-shared";
35
- import { clearServingEndpointsCache, listServingEndpoints, } from "@dbx-tools/model";
36
- import { buildAgents, FALLBACK_AGENT_ID } from "./agents.js";
37
- import { fetchChart } from "./chart.js";
38
- import { collectSpaceSuggestions, resolveGenieSpaces } from "./genie.js";
39
- import { historyRoute } from "./history.js";
40
- import { createMemoryBuilder, createServicePrincipalPool, needsLakebase, } from "./memory.js";
41
- import { buildObservability } from "./observability.js";
42
- import { attachRoutePatchMiddleware, MastraServer } from "./server.js";
43
- import { resolveServingConfig } from "./serving.js";
44
- import { fetchStatementData, isStatementNotFoundError, STATEMENT_ROW_CAP, } from "./statement.js";
45
- const GENIE_MANIFEST = appkitUtils.data(genie).plugin.manifest;
46
- const LAKEBASE_MANIFEST = appkitUtils.data(lakebase).plugin.manifest;
47
- /**
48
- * AppKit plugin (registered name: `mastra`) that hosts Mastra agents
49
- * with optional Lakebase-backed memory and AI SDK chat routes under
50
- * the plugin mount (typically `/api/mastra`).
51
- */
52
- export class MastraPlugin extends Plugin {
53
- static manifest = {
54
- name: "mastra",
55
- displayName: "Mastra",
56
- description: "Builds a Mastra Agent with user-scoped workspace auth (asUser) " +
57
- "and optional Postgres-backed Mastra Memory via the `lakebase` plugin.",
58
- stability: "beta",
59
- resources: {
60
- required: [],
61
- optional: [
62
- // Surface the Genie resource binding (space id) declared by
63
- // AppKit's `genie` plugin manifest. The Mastra plugin no
64
- // longer uses the genie plugin's tools at runtime - the
65
- // built-in Genie agent talks to Genie directly via
66
- // `@dbx-tools/genie` - but reusing the manifest keeps the
67
- // resource-binding shape identical to AppKit's so existing
68
- // `app.yaml` configs and `genie({ spaces })` wiring keep
69
- // working without change.
70
- ...GENIE_MANIFEST.resources.required,
71
- ...LAKEBASE_MANIFEST.resources.required,
72
- ],
73
- },
74
- };
75
- /**
76
- * Tighten resource requirements based on which features are enabled.
77
- * AppKit calls this at registration time (config-aware) so disabled
78
- * features don't surface their resource asks to the host app.
79
- */
80
- static getResourceRequirements(config) {
81
- const resources = [];
82
- const enabledManifests = [];
83
- if (needsLakebase(config)) {
84
- enabledManifests.push(LAKEBASE_MANIFEST);
85
- }
86
- for (const m of enabledManifests) {
87
- for (const resource of m.resources.required) {
88
- resources.push({ ...resource, required: true });
89
- }
90
- }
91
- return resources;
92
- }
93
- log = logUtils.logger(this);
94
- built = null;
95
- mastra = null;
96
- mastraApp = null;
97
- mastraServer = null;
98
- /**
99
- * Dedicated service-principal Lakebase pool backing Mastra memory /
100
- * storage. Built once in {@link buildAgentAndServer} (outside any
101
- * `asUser` scope, so it never inherits a request's OBO identity) and
102
- * drained in {@link abortActiveOperations}. `null` until setup runs
103
- * or when Lakebase isn't needed.
104
- */
105
- servicePrincipalPool = null;
106
- async setup() {
107
- // Wait until sibling plugins (e.g. `lakebase`) finish `setup()` so
108
- // the lakebase pool is valid when storage/memory are enabled.
109
- this.context?.onLifecycle("setup:complete", async () => {
110
- this.applyLakebaseAutoDefaults();
111
- this.log.info("setup:complete");
112
- await this.buildAgentAndServer();
113
- });
114
- }
115
- /**
116
- * When the `lakebase` plugin is registered, auto-enable `storage`
117
- * and `memory` unless the caller opted out explicitly (`false` or a
118
- * custom config object). Run after `setup:complete` so the lookup
119
- * is reliable: any plugin that registers itself synchronously is
120
- * already in the registry by the time this fires.
121
- */
122
- applyLakebaseAutoDefaults() {
123
- const hasLakebase = appkitUtils.instance(this.context, lakebase) !== undefined;
124
- if (!hasLakebase)
125
- return;
126
- if (this.config.storage === undefined)
127
- this.config.storage = true;
128
- if (this.config.memory === undefined)
129
- this.config.memory = true;
130
- }
131
- /**
132
- * Drain the memory service-principal pool on shutdown. AppKit calls
133
- * this during teardown; the lakebase plugin closes its own SP / OBO
134
- * pools the same way. Fire-and-forget so shutdown isn't blocked on a
135
- * slow drain, and clear the handle so a re-`setup()` rebuilds it.
136
- */
137
- abortActiveOperations() {
138
- super.abortActiveOperations();
139
- if (this.servicePrincipalPool) {
140
- this.log.info("closing memory SP pool");
141
- const pool = this.servicePrincipalPool;
142
- this.servicePrincipalPool = null;
143
- pool.end().catch((err) => {
144
- this.log.error("error closing memory SP pool", {
145
- error: commonUtils.errorMessage(err),
146
- });
147
- });
148
- }
149
- }
150
- exports() {
151
- return {
152
- /**
153
- * Ids of every registered agent in registration order. Matches
154
- * AppKit `agents.list()` so callers can iterate the registry the
155
- * same way under both plugins.
156
- */
157
- list: () => Object.keys(this.built?.agents ?? {}),
158
- /**
159
- * Look up a registered agent by id. Returns `null` (not
160
- * undefined) when unknown so call sites can early-return without
161
- * a separate `in` check.
162
- */
163
- get: (id) => this.built?.agents[id] ?? null,
164
- /**
165
- * The agent the client converses with when it doesn't name one.
166
- * Resolves to `config.defaultAgent`, the first registered id, or
167
- * the built-in `default` fallback.
168
- */
169
- getDefault: () => (this.built && this.built.agents[this.built.defaultAgentId]) ?? null,
170
- /** Underlying Mastra instance for advanced use (custom routes etc.). */
171
- getMastra: () => this.mastra,
172
- /** Express subapp Mastra is mounted on; mostly for tests. */
173
- getMastraServer: () => this.mastraServer,
174
- /**
175
- * Fetch the workspace's Model Serving endpoints (cached). Same
176
- * payload the `GET /models` route returns; surfaced here so
177
- * other plugins / scripts can introspect the catalogue without
178
- * an HTTP round-trip. AppKit wraps this with `asUser(req)` for
179
- * OBO scoping automatically.
180
- */
181
- listModels: () => this.listModels(),
182
- /**
183
- * Force-evict cached endpoint listings via AppKit's
184
- * `CacheManager`. Useful in tests or right after an admin
185
- * deploys a new endpoint and doesn't want to wait for the TTL.
186
- * Returns the underlying `CacheManager.delete`/`clear` promise.
187
- */
188
- clearModelsCache: (host) => clearServingEndpointsCache(host),
189
- };
190
- }
191
- clientConfig() {
192
- // AppKit mounts every plugin at `/api/<plugin.name>`. `this.name`
193
- // honors `config.name` overrides, so publishing `basePath` is
194
- // enough for the client to stay correct under a custom mount id -
195
- // the per-route segments are fixed (`MASTRA_ROUTES`) and the
196
- // client (`MastraPluginClient`) derives every endpoint from
197
- // `basePath`.
198
- // Return widens to `Record<string, unknown>` to satisfy the
199
- // base-class signature; consumers read it through the typed
200
- // `MastraClientConfig` shape via `usePluginClientConfig<...>(...)`.
201
- const config = {
202
- basePath: `/api/${this.name}`,
203
- defaultAgent: this.built?.defaultAgentId ?? FALLBACK_AGENT_ID,
204
- agents: Object.keys(this.built?.agents ?? {}),
205
- };
206
- return config;
207
- }
208
- injectRoutes(router) {
209
- // `GET /models` exposes the cached endpoint list so clients can
210
- // populate model pickers, validate `?model=` choices, etc. Must
211
- // be registered before the catch-all that forwards everything to
212
- // the Mastra subapp. Errors propagate to Express's default error
213
- // handler via `next(err)` so callers see the real SDK message.
214
- router.get(MASTRA_ROUTES.models, (req, res, next) => {
215
- this.userScopedSelf(req)
216
- .listModels()
217
- .then((endpoints) => res.json({ endpoints }))
218
- .catch(next);
219
- });
220
- // `GET /embed/:type/:id` is the single resolver for every embed
221
- // marker the agent emits in prose (`[chart:<id>]`,
222
- // `[data:<id>]`, ...). `:type` selects a resolver from the
223
- // registry below; `:id` is that resolver's lookup key. The
224
- // grammar (see `marker.ts`) is type-agnostic on purpose - new
225
- // embed kinds are added by registering a resolver here, with no
226
- // client or grammar change.
227
- //
228
- // Status codes:
229
- // - 200 with the resolver's JSON body when the id resolves.
230
- // - 404 when `:type` isn't registered (unsupported embed
231
- // type) OR a registered resolver can't find `:id` (unknown
232
- // / expired - e.g. a chart past its 1h TTL or a fabricated
233
- // id the model never minted).
234
- // - 400 when `:id` is empty.
235
- //
236
- // Per-type query knobs and behavior:
237
- // - `chart`: long-polls the chart cache until the entry
238
- // settles (`result` / `error`) or the budget elapses (then
239
- // returns the still-processing entry to poll again).
240
- // `?timeoutMs=<n>` (default 60s, capped 5min) tunes it.
241
- // - `data`: one OBO-scoped Statement Execution fetch.
242
- // `?limit=<n>` caps rows (clamped to STATEMENT_ROW_CAP).
243
- //
244
- // Built once (this handler is registered once) and keyed by the
245
- // raw `:type` token. Each resolver gets the request (for query
246
- // parsing + OBO scoping) and an `AbortSignal` bridged off the
247
- // connection `close` event so a long-poll unblocks the instant
248
- // the client disconnects. `undefined` from a resolver maps to a
249
- // clean 404; thrown errors bubble through `next(err)`.
250
- const embedResolvers = {
251
- chart: (req, id, signal) => {
252
- const timeoutMs = parseTimeoutMs(req.query["timeoutMs"]);
253
- return fetchChart(id, {
254
- ...(timeoutMs !== undefined ? { timeoutMs } : {}),
255
- signal,
256
- });
257
- },
258
- data: (req, id, signal) => {
259
- const limit = parseStatementLimit(req.query["limit"]);
260
- return this.userScopedSelf(req).fetchStatement(id, {
261
- ...(limit !== undefined ? { limit } : {}),
262
- signal,
263
- });
264
- },
265
- };
266
- router.get(`${MASTRA_ROUTES.embed}/:type/:id`, (req, res, next) => {
267
- const type = req.params["type"] ?? "";
268
- const id = req.params["id"];
269
- const resolve = embedResolvers[type];
270
- if (!resolve) {
271
- res.status(404).json({ error: `unsupported embed type: ${type}` });
272
- return;
273
- }
274
- if (!id) {
275
- res.status(400).json({ error: "id is required" });
276
- return;
277
- }
278
- // Express's `req` predates `AbortSignal`; bridge the `close`
279
- // event onto an `AbortController` so a closed connection
280
- // unblocks any long-poll immediately and frees the request
281
- // thread. The listener is GC'd with the request on normal
282
- // completion.
283
- const controller = new AbortController();
284
- req.on("close", () => controller.abort());
285
- resolve(req, id, controller.signal)
286
- .then((entry) => {
287
- if (entry === undefined) {
288
- res.status(404).json({ error: `${type} not found` });
289
- return;
290
- }
291
- res.json(entry);
292
- })
293
- .catch(next);
294
- });
295
- // `GET /suggestions` (and `/suggestions/:agentId`) returns the
296
- // curated starter questions for the agent's Genie space(s) - the
297
- // author-configured `sample_questions`, surfaced as one-tap
298
- // prompts on the chat empty state. Returns `{ questions: [] }`
299
- // when no Genie space is wired so the client renders a bare
300
- // empty state (no built-in example prompts). The `:agentId`
301
- // segment is accepted for URL symmetry with the chat / history
302
- // routes; Genie spaces are resolved per-plugin, not per-agent,
303
- // so it doesn't change the result. OBO-scoped like the other
304
- // data routes so the space lookup runs as the calling user.
305
- const handleSuggestions = (req, res) => {
306
- const controller = new AbortController();
307
- req.on("close", () => controller.abort());
308
- this.userScopedSelf(req)
309
- .fetchSuggestions(controller.signal)
310
- .then((questions) => res.json({ questions }))
311
- .catch((err) => {
312
- // Suggestions are a non-critical enhancement; a lookup
313
- // failure should leave the chat usable with a bare empty
314
- // state rather than surfacing a 500. Log and degrade.
315
- this.log.warn("suggestions:error", {
316
- error: err instanceof Error ? err.message : String(err),
317
- });
318
- res.json({ questions: [] });
319
- });
320
- };
321
- router.get(MASTRA_ROUTES.suggestions, handleSuggestions);
322
- router.get(`${MASTRA_ROUTES.suggestions}/:agentId`, handleSuggestions);
323
- router.use((req, res, next) => {
324
- if (!this.mastraApp)
325
- return res.status(503).end();
326
- // Dispatch through a real method, NOT the `mastraApp` property. The
327
- // AppKit `asUser(req)` proxy wraps function-valued props with
328
- // `value.bind(target)`. `mastraApp` is an express app whose `.bind` is
329
- // the HTTP BIND route registrar (express defines a method per HTTP verb,
330
- // and BIND is one), not `Function.prototype.bind` - so binding it through
331
- // the proxy registers a bogus route and crashes `pathToRegexp`
332
- // ("path must be a string ..."). This only manifests in production where
333
- // an OBO token makes `userScopedSelf` return the proxy. `dispatchMastra`
334
- // is a plain method (its `.bind` is the normal one) and invokes
335
- // `this.mastraApp` off the real target, keeping the OBO scope active.
336
- return this.userScopedSelf(req).dispatchMastra(req, res, next);
337
- });
338
- }
339
- /**
340
- * Invoke the Mastra express sub-app. Exists as a method (instead of reading
341
- * `this.mastraApp` through the `asUser(req)` proxy at the call site) so the
342
- * proxy binds this plain method - whose `.bind` is `Function.prototype.bind`
343
- * - rather than the express app, whose `.bind` is the HTTP BIND route
344
- * registrar (see the note in `injectRoutes`). Runs inside the user scope so
345
- * `getExecutionContext()` returns the OBO client for the agent/model
346
- * resolvers.
347
- */
348
- dispatchMastra(req, res, next) {
349
- this.mastraApp(req, res, next);
350
- }
351
- /**
352
- * Implementation backing the `/suggestions` route. Runs inside the
353
- * AppKit user-context proxy so `getExecutionContext()` returns the
354
- * OBO-scoped client. Resolves the plugin's Genie spaces and merges
355
- * their curated `sample_questions` (see {@link collectSpaceSuggestions}).
356
- * Returns `[]` when no Genie space is configured so the client
357
- * shows a bare empty state instead of built-in example prompts.
358
- */
359
- async fetchSuggestions(signal) {
360
- const spaces = resolveGenieSpaces(this.config, this.context);
361
- if (Object.keys(spaces).length === 0)
362
- return [];
363
- const client = getExecutionContext().client;
364
- return collectSpaceSuggestions({
365
- spaces,
366
- client,
367
- ...(signal ? { signal } : {}),
368
- });
369
- }
370
- /**
371
- * Implementation backing the `data` embed resolver
372
- * (`GET /embed/data/:id`). Runs inside the AppKit user-context proxy so
373
- * `getExecutionContext()` returns the OBO-scoped workspace
374
- * client, then reuses the same `fetchStatementData` pipeline
375
- * the `get_statement` tool runs so the LLM and the UI see the
376
- * exact same shape for the same statement.
377
- *
378
- * Returns `undefined` for upstream 404s so the route can map
379
- * them to a clean HTTP 404; any other failure bubbles up.
380
- */
381
- async fetchStatement(statementId, options = {}) {
382
- const client = getExecutionContext().client;
383
- const limit = Math.min(options.limit ?? STATEMENT_ROW_CAP, STATEMENT_ROW_CAP);
384
- try {
385
- const data = await fetchStatementData(client, statementId, {
386
- limit,
387
- ...(options.signal ? { signal: options.signal } : {}),
388
- });
389
- return {
390
- columns: data.columns,
391
- rows: data.rows,
392
- rowCount: data.rowCount,
393
- truncated: data.rows.length < data.rowCount,
394
- };
395
- }
396
- catch (err) {
397
- // The Databricks SDK throws on 404; surface as `undefined`
398
- // so the route maps to a clean HTTP 404 instead of a 500.
399
- if (isStatementNotFoundError(err))
400
- return undefined;
401
- throw err;
402
- }
403
- }
404
- /**
405
- * Return `this.asUser(req)` when the request carries an OBO token,
406
- * otherwise return `this` directly. Prevents the noisy AppKit warn
407
- * (`asUser() called without user token in development mode. Skipping
408
- * user impersonation.`) on every request in local dev where the
409
- * browser never sends `x-forwarded-access-token`. Behavior is
410
- * unchanged in production: a missing token always means a real OBO
411
- * proxy call (and AppKit will throw upstream if that's wrong).
412
- */
413
- userScopedSelf(req) {
414
- return req.header("x-forwarded-access-token") ? this.asUser(req) : this;
415
- }
416
- /**
417
- * Implementation backing both the `/models` route and the
418
- * `listModels` export. Runs inside the AppKit user-context proxy so
419
- * `getExecutionContext()` returns the OBO-scoped client.
420
- */
421
- async listModels() {
422
- const client = getExecutionContext().client;
423
- const host = (await client.config.getHost()).toString();
424
- const serving = resolveServingConfig(this.config);
425
- return listServingEndpoints(client, host, { ttlMs: serving.ttlMs });
426
- }
427
- async buildAgentAndServer() {
428
- // Per-agent memory factory. When any storage / memory setting needs
429
- // Postgres, stand up a dedicated service-principal pool first so
430
- // memory acts as the app SP (owner of the `mastra_*` schemas),
431
- // never the per-request OBO identity the chat turn runs under.
432
- // `getPgConfig()` is read here, outside any `asUser` scope, so it
433
- // returns the SP connection target + token refresh plus any
434
- // `lakebase({ pool })` overrides; `require` turns a missing
435
- // sibling into a clear wiring error. The builder caches the shared
436
- // `PgVector` singleton so registering N agents stays cheap. See
437
- // `./memory.js`.
438
- if (needsLakebase(this.config)) {
439
- const spPgConfig = appkitUtils
440
- .require(this.context, lakebase, this.config)
441
- .exports()
442
- .getPgConfig();
443
- this.servicePrincipalPool = await createServicePrincipalPool(spPgConfig);
444
- }
445
- const memoryBuilder = this.servicePrincipalPool
446
- ? createMemoryBuilder(this.config, this.servicePrincipalPool)
447
- : undefined;
448
- this.log.debug("build:start", {
449
- lakebase: memoryBuilder !== undefined,
450
- stripStaleCharts: this.config.stripStaleCharts !== false,
451
- });
452
- // Build every agent declared in `config.agents` (or the built-in
453
- // fallback when none are declared). Each agent's `model` resolves
454
- // workspace URL + bearer at call time so concurrent requests get
455
- // distinct user identities; the `asUser(req)` scope around
456
- // `handleChat` is what lets `getExecutionContext()` return the
457
- // right user inside the resolver.
458
- this.built = await buildAgents({
459
- config: this.config,
460
- context: this.context,
461
- memoryBuilder,
462
- log: this.log,
463
- });
464
- // `mastra.server.apiRoutes` is only honored by Mastra's standalone
465
- // dev server. Since we're hosting Mastra inside our own Express
466
- // subapp via `@mastra/express`, custom routes must be passed to
467
- // the `MastraServer` constructor directly.
468
- //
469
- // `storage` here is *Mastra-instance-level* and persists workflow
470
- // snapshots (where suspended `requireApproval` tool calls live).
471
- // It's separate from each agent's `Memory.storage`, which only
472
- // covers thread / message history. Without it,
473
- // `agent.resumeStream()` errors with "could not find a suspended
474
- // run" and the approval UI hangs after the user clicks Approve.
475
- const instanceStorage = memoryBuilder?.instanceStorage();
476
- // Wire Mastra's tracer into AppKit's global OTel pipeline via
477
- // `@mastra/otel-bridge`. Mastra spans become native OTel spans on
478
- // whatever tracer provider `TelemetryManager` registered during
479
- // `createApp`, so the OTLP endpoint / headers / sampling are
480
- // env-driven and shared with every other AppKit plugin.
481
- const observability = await buildObservability({ serviceName: this.name });
482
- this.mastra = new Mastra({
483
- agents: this.built.agents,
484
- ...(instanceStorage ? { storage: instanceStorage } : {}),
485
- ...(observability ? { observability } : {}),
486
- });
487
- this.mastraApp = express();
488
- attachRoutePatchMiddleware(this.mastraApp);
489
- this.mastraServer = new MastraServer(this.config, {
490
- app: this.mastraApp,
491
- mastra: this.mastra,
492
- prefix: "",
493
- customApiRoutes: [
494
- // `historyRoute` registers both GET (load) and DELETE
495
- // (clear) on the same path, so it returns an array we
496
- // splice in.
497
- ...historyRoute({
498
- path: MASTRA_ROUTES.history,
499
- agent: this.built.defaultAgentId,
500
- }),
501
- // Assert the `:agentId` template type: the per-package build's
502
- // NodeNext resolution widens the imported `MASTRA_ROUTES.history`
503
- // to `string` (the source/bundler typecheck keeps it a literal),
504
- // which would otherwise drop this out of the dynamic-agent
505
- // overload and demand a fixed `agent`.
506
- ...historyRoute({
507
- path: `${MASTRA_ROUTES.history}/:agentId`,
508
- }),
509
- ],
510
- });
511
- await this.mastraServer.init();
512
- this.log.debug("build:done", {
513
- agents: Object.keys(this.built.agents),
514
- defaultAgent: this.built.defaultAgentId,
515
- routes: ["/route/history", "/models", "/suggestions", "/embed/:type/:id"],
516
- instanceStorage: instanceStorage !== undefined,
517
- observability: observability !== undefined ? "mlflow" : "off",
518
- });
519
- }
520
- }
521
- /**
522
- * Parse the optional `?timeoutMs=<n>` query parameter from a
523
- * `GET /embed/chart/:id` request. Accepts a positive integer up
524
- * to 5 minutes (clamped) and rejects everything else as
525
- * `undefined` so {@link fetchChart} falls back to its default.
526
- * Express produces `string | string[] | undefined`; we normalize
527
- * to the first scalar before parsing.
528
- */
529
- function parseTimeoutMs(raw) {
530
- const v = Array.isArray(raw) ? raw[0] : raw;
531
- if (typeof v !== "string")
532
- return undefined;
533
- const n = Number(v);
534
- if (!Number.isFinite(n) || n <= 0)
535
- return undefined;
536
- return Math.min(Math.floor(n), 5 * 60_000);
537
- }
538
- /**
539
- * Parse the optional `?limit=<n>` query parameter from a
540
- * `GET /embed/data/:id` request. Accepts a non-negative
541
- * integer and lets the route clamp to `STATEMENT_ROW_CAP`;
542
- * rejects anything else as `undefined` so the route falls back
543
- * to the server-side cap.
544
- */
545
- function parseStatementLimit(raw) {
546
- const v = Array.isArray(raw) ? raw[0] : raw;
547
- if (typeof v !== "string")
548
- return undefined;
549
- const n = Number(v);
550
- if (!Number.isFinite(n) || n < 0)
551
- return undefined;
552
- return Math.floor(n);
553
- }
554
- export const mastra = toPlugin(MastraPlugin);
@@ -1,8 +0,0 @@
1
- import type { Processor } from "@mastra/core/processors";
2
- export declare class ResultProcessor implements Processor {
3
- id: string;
4
- processDataParts: boolean;
5
- processOutputStream({ part }: {
6
- part: any;
7
- }): Promise<any | null>;
8
- }
@@ -1,40 +0,0 @@
1
- export class ResultProcessor {
2
- id = "result-processor";
3
- // Tell Mastra to also route tool/data parts to this processor method
4
- processDataParts = true;
5
- async processOutputStream({ part }) {
6
- // 1. Guard clause: Ensure the chunk is a valid object
7
- if (!part || typeof part !== "object") {
8
- return part;
9
- }
10
- // 2. Filter for the targeted frame types
11
- const targetedTypes = ["step-finish", "finish", "tool-result", "data-tool-agent"];
12
- if (!targetedTypes.includes(part.type)) {
13
- return part; // Return unchanged to pass-through
14
- }
15
- // 3. Check for the presence of a payload object
16
- const payload = part.payload;
17
- if (!payload || typeof payload !== "object") {
18
- return part;
19
- }
20
- // 4. Safely delete the unwanted keys from the payload reference
21
- const keysToDelete = ["output", "messages", "response", "result"];
22
- for (const key of keysToDelete) {
23
- if (key in payload) {
24
- const value = payload[key];
25
- if (typeof value === "object") {
26
- payload[key] = {};
27
- }
28
- else if (Array.isArray(value)) {
29
- payload[key] = [];
30
- }
31
- else {
32
- delete payload[key];
33
- }
34
- }
35
- }
36
- // 5. Return the modified part object. Mastra handles re-serialization
37
- // for the outbound SSE client stream automatically.
38
- return part;
39
- }
40
- }
@@ -1,32 +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 type { InputProcessor } from "@mastra/core/processors";
26
- /**
27
- * Input processor that scrubs `chartId` from every tool-invocation
28
- * result in the message list. Wired onto every agent by default
29
- * via {@link buildAgents}; opt out with
30
- * `MastraPluginConfig.stripStaleCharts: false`.
31
- */
32
- export declare const stripStaleChartsProcessor: InputProcessor;