@dbx-tools/appkit-mastra 0.1.5 → 0.1.13
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 +735 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/src/agents.js +18 -8
- package/dist/src/chart.d.ts +101 -35
- package/dist/src/chart.js +178 -62
- package/dist/src/config.d.ts +13 -0
- package/dist/src/genie.d.ts +23 -8
- package/dist/src/genie.js +137 -101
- package/dist/src/history.js +14 -0
- package/dist/src/memory.d.ts +21 -0
- package/dist/src/memory.js +47 -2
- package/dist/src/model.js +18 -14
- package/dist/src/observability.d.ts +33 -0
- package/dist/src/observability.js +71 -0
- package/dist/src/plugin.d.ts +1 -1
- package/dist/src/plugin.js +32 -4
- package/dist/src/processors/strip-stale-charts.d.ts +29 -0
- package/dist/src/processors/strip-stale-charts.js +96 -0
- package/dist/src/server.js +10 -0
- package/dist/src/serving.js +19 -2
- package/dist/src/tools/email.d.ts +74 -0
- package/dist/src/tools/email.js +122 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/index.ts +1 -0
- package/package.json +23 -25
- package/src/agents.ts +19 -6
- package/src/chart.ts +232 -64
- package/src/config.ts +13 -0
- package/src/genie.ts +179 -116
- package/src/history.ts +19 -7
- package/src/memory.ts +55 -2
- package/src/model.ts +18 -13
- package/src/observability.ts +92 -0
- package/src/plugin.ts +33 -4
- package/src/processors/strip-stale-charts.ts +105 -0
- package/src/server.ts +11 -0
- package/src/serving.ts +21 -2
- package/src/tools/email.ts +147 -0
- package/dist/src/render-chart-route.d.ts +0 -33
- package/dist/src/render-chart-route.js +0 -120
- package/src/render-chart-route.ts +0 -141
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"main": "dist/index.js",
|
|
3
|
-
"types": "dist/index.d.ts",
|
|
2
|
+
"main": "./dist/index.js",
|
|
3
|
+
"types": "./dist/index.d.ts",
|
|
4
4
|
"exports": {
|
|
5
5
|
".": {
|
|
6
6
|
"source": "./index.ts",
|
|
@@ -8,37 +8,20 @@
|
|
|
8
8
|
"default": "./dist/index.js"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
|
-
"files": [
|
|
12
|
-
"dist",
|
|
13
|
-
"index.ts",
|
|
14
|
-
"src"
|
|
15
|
-
],
|
|
16
|
-
"license": "Apache-2.0",
|
|
17
|
-
"homepage": "https://github.com/reggie-db/dbx-tools-appkit#readme",
|
|
18
|
-
"bugs": {
|
|
19
|
-
"url": "https://github.com/reggie-db/dbx-tools-appkit/issues"
|
|
20
|
-
},
|
|
21
|
-
"publishConfig": {
|
|
22
|
-
"registry": "https://registry.npmjs.org/",
|
|
23
|
-
"access": "public"
|
|
24
|
-
},
|
|
25
|
-
"repository": {
|
|
26
|
-
"type": "git",
|
|
27
|
-
"url": "git+https://github.com/reggie-db/dbx-tools-appkit.git",
|
|
28
|
-
"directory": "packages/mastra"
|
|
29
|
-
},
|
|
30
11
|
"name": "@dbx-tools/appkit-mastra",
|
|
31
|
-
"version": "0.1.
|
|
32
|
-
"module": "index.ts",
|
|
12
|
+
"version": "0.1.13",
|
|
33
13
|
"type": "module",
|
|
14
|
+
"module": "index.ts",
|
|
34
15
|
"dependencies": {
|
|
35
|
-
"@dbx-tools/appkit-mastra-shared": "0.1.
|
|
36
|
-
"@dbx-tools/appkit-shared": "0.1.
|
|
16
|
+
"@dbx-tools/appkit-mastra-shared": "0.1.13",
|
|
17
|
+
"@dbx-tools/appkit-shared": "0.1.13",
|
|
37
18
|
"@mastra/ai-sdk": "^1.3",
|
|
38
19
|
"@mastra/core": "^1.32",
|
|
39
20
|
"@mastra/express": "^1.3",
|
|
40
21
|
"@mastra/fastembed": "^1.0",
|
|
41
22
|
"@mastra/memory": "^1.17",
|
|
23
|
+
"@mastra/observability": "^0.0.0-graph-crash-v3-20260528202217",
|
|
24
|
+
"@mastra/otel-exporter": "^0.0.0-graph-crash-v3-20260528202217",
|
|
42
25
|
"@mastra/pg": "^1.10",
|
|
43
26
|
"fuse.js": "^7.0.0",
|
|
44
27
|
"zod": "^4.3.6"
|
|
@@ -51,5 +34,20 @@
|
|
|
51
34
|
"@types/express": "^5",
|
|
52
35
|
"@types/pg": "^8",
|
|
53
36
|
"express": "^5"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"index*.ts",
|
|
41
|
+
"src"
|
|
42
|
+
],
|
|
43
|
+
"license": "Apache-2.0",
|
|
44
|
+
"homepage": "https://github.com/reggie-db/dbx-tools-appkit#readme",
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/reggie-db/dbx-tools-appkit/issues"
|
|
47
|
+
},
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/reggie-db/dbx-tools-appkit.git",
|
|
51
|
+
"directory": "packages/mastra"
|
|
54
52
|
}
|
|
55
53
|
}
|
package/src/agents.ts
CHANGED
|
@@ -26,6 +26,7 @@ import type { MastraPluginConfig } from "./config.js";
|
|
|
26
26
|
import { buildGenieProvider } from "./genie.js";
|
|
27
27
|
import type { MemoryBuilder } from "./memory.js";
|
|
28
28
|
import { buildModel } from "./model.js";
|
|
29
|
+
import { stripStaleChartsProcessor } from "./processors/strip-stale-charts.js";
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* Tool record accepted by every Mastra `Agent.tools` field and by the
|
|
@@ -122,8 +123,8 @@ export interface AppKitToolOptions {
|
|
|
122
123
|
/**
|
|
123
124
|
* Build a deterministic Mastra tool id from a description.
|
|
124
125
|
* Delegates to {@link stringUtils.toUniqueSlug}: slug + always-on
|
|
125
|
-
*
|
|
126
|
-
* collide in traces. Stable across runs.
|
|
126
|
+
* 6-char FNV-1a base-32 suffix so two tools with the same leading
|
|
127
|
+
* words don't collide in traces. Stable across runs.
|
|
127
128
|
*/
|
|
128
129
|
function deriveToolId(description: string): string {
|
|
129
130
|
return stringUtils.toUniqueSlug(description, { fallbackPrefix: "tool" });
|
|
@@ -407,7 +408,7 @@ export async function buildAgents(opts: {
|
|
|
407
408
|
const ids = Object.keys(definitions);
|
|
408
409
|
const defaultAgentId = config.defaultAgent ?? ids[0] ?? FALLBACK_AGENT_ID;
|
|
409
410
|
|
|
410
|
-
const plugins = buildPluginsMap(context);
|
|
411
|
+
const plugins = buildPluginsMap(config, context);
|
|
411
412
|
// System-default ambient tools every agent gets out of the box.
|
|
412
413
|
// Currently just `render_data` for inline visualizations; the
|
|
413
414
|
// user can shadow it by including a same-named tool in their own
|
|
@@ -418,6 +419,12 @@ export async function buildAgents(opts: {
|
|
|
418
419
|
};
|
|
419
420
|
const ambientTools = { ...systemTools, ...(config.tools ?? {}) };
|
|
420
421
|
const style = resolveStyleInstructions(config);
|
|
422
|
+
// Default-on protection against the model copying turn-scoped
|
|
423
|
+
// chartIds from prior assistant tool results into the new
|
|
424
|
+
// turn's `[[chart:<id>]]` markers. Opt out per-plugin via
|
|
425
|
+
// `config.stripStaleCharts: false`.
|
|
426
|
+
const inputProcessors =
|
|
427
|
+
config.stripStaleCharts === false ? [] : [stripStaleChartsProcessor];
|
|
421
428
|
const agents: Record<string, Agent> = {};
|
|
422
429
|
|
|
423
430
|
for (const [id, def] of Object.entries(definitions)) {
|
|
@@ -431,6 +438,7 @@ export async function buildAgents(opts: {
|
|
|
431
438
|
model: resolveModel(config, def.model),
|
|
432
439
|
tools,
|
|
433
440
|
...(memory ? { memory } : {}),
|
|
441
|
+
...(inputProcessors.length > 0 ? { inputProcessors } : {}),
|
|
434
442
|
});
|
|
435
443
|
}
|
|
436
444
|
|
|
@@ -581,6 +589,7 @@ async function resolveTools(
|
|
|
581
589
|
* time instead of staring at a spinner for the full Genie round-trip.
|
|
582
590
|
*/
|
|
583
591
|
function buildPluginsMap(
|
|
592
|
+
config: MastraPluginConfig,
|
|
584
593
|
context: pluginUtils.PluginContextLike | undefined,
|
|
585
594
|
): MastraPlugins {
|
|
586
595
|
const cache = new Map<string, MastraPluginToolkitProvider | null>();
|
|
@@ -588,7 +597,7 @@ function buildPluginsMap(
|
|
|
588
597
|
get(_target, propName) {
|
|
589
598
|
if (typeof propName !== "string") return undefined;
|
|
590
599
|
if (cache.has(propName)) return cache.get(propName) ?? undefined;
|
|
591
|
-
const provider = resolveProvider(context, propName);
|
|
600
|
+
const provider = resolveProvider(config, context, propName);
|
|
592
601
|
cache.set(propName, provider);
|
|
593
602
|
return provider ?? undefined;
|
|
594
603
|
},
|
|
@@ -599,16 +608,20 @@ function buildPluginsMap(
|
|
|
599
608
|
* Pick the right {@link MastraPluginToolkitProvider} for a sibling
|
|
600
609
|
* plugin lookup. Returns the streaming-aware Genie adapter when the
|
|
601
610
|
* caller asks for `genie`; falls back to the generic AppKit
|
|
602
|
-
* `ToolProvider` adapter for every other plugin name.
|
|
611
|
+
* `ToolProvider` adapter for every other plugin name. `config` is
|
|
612
|
+
* threaded through so Genie's tool can run the chart planner
|
|
613
|
+
* inline against the same model resolver / fallback ladder the
|
|
614
|
+
* agents use.
|
|
603
615
|
*/
|
|
604
616
|
function resolveProvider(
|
|
617
|
+
config: MastraPluginConfig,
|
|
605
618
|
context: pluginUtils.PluginContextLike | undefined,
|
|
606
619
|
propName: string,
|
|
607
620
|
): MastraPluginToolkitProvider | null {
|
|
608
621
|
if (propName === "genie") {
|
|
609
622
|
const geniePlugin = pluginUtils.instance(context, genie);
|
|
610
623
|
if (!geniePlugin) return null;
|
|
611
|
-
return buildGenieProvider(geniePlugin) as MastraPluginToolkitProvider;
|
|
624
|
+
return buildGenieProvider(geniePlugin, { config }) as MastraPluginToolkitProvider;
|
|
612
625
|
}
|
|
613
626
|
const plugin = context?.getPlugins().get(propName);
|
|
614
627
|
return adaptPluginToolkit(plugin);
|
package/src/chart.ts
CHANGED
|
@@ -1,36 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Chart-rendering primitives.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Three surfaces, one shared brain:
|
|
5
5
|
*
|
|
6
6
|
* - {@link buildRenderDataTool}: a Mastra tool the model calls
|
|
7
|
-
* ("here is a dataset, render it as a chart"). The tool
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
7
|
+
* ("here is a dataset, render it as a chart"). The tool's
|
|
8
|
+
* `execute` emits a `kind: "chart"` event with the raw rows to
|
|
9
|
+
* `ctx.writer` synchronously, kicks off the chart-planner agent,
|
|
10
|
+
* and `await`s the planner promise before returning so the
|
|
11
|
+
* planner's latency is attributed to this tool's trace span.
|
|
12
|
+
* The LLM-bound output is just `{ chartId }`, so its context
|
|
13
|
+
* stays flat regardless of dataset size.
|
|
14
|
+
*
|
|
15
|
+
* - {@link emitChartWithPlanning}: the underlying helper that both
|
|
16
|
+
* `render_data` and Genie's `drainGenieStream` call. Mints the
|
|
17
|
+
* `chartId`, fires the dataset event immediately, runs the
|
|
18
|
+
* planner in the background, and returns `{ chartId,
|
|
19
|
+
* plannerPromise }` so callers can choose to await for trace
|
|
20
|
+
* shape or fire-and-forget.
|
|
13
21
|
*
|
|
14
22
|
* - {@link runChartPlanner}: the chart-planner Agent + ECOption
|
|
15
|
-
* expansion as a plain async function.
|
|
16
|
-
* {@link
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* from the tool means the planning latency lives entirely
|
|
20
|
-
* client-side: the model can finish writing its report while
|
|
21
|
-
* the client is still rendering the charts.
|
|
23
|
+
* expansion as a plain async function. Used internally by
|
|
24
|
+
* {@link emitChartWithPlanning}; producers shouldn't reach for
|
|
25
|
+
* it directly so chart events keep a single wire-format
|
|
26
|
+
* contract.
|
|
22
27
|
*
|
|
23
28
|
* The model wires the chart into its reply by emitting the marker
|
|
24
29
|
* `[[chart:<chartId>]]` on its own line in markdown. The chat
|
|
25
30
|
* client splits the assistant text on these markers and drops a
|
|
26
31
|
* `<ChartSlot>` in at the position the model placed it; the slot
|
|
27
|
-
*
|
|
28
|
-
*
|
|
32
|
+
* shows a skeleton until the second `kind: "chart"` event (with
|
|
33
|
+
* the resolved `EChartsOption`) arrives, then swaps in the
|
|
34
|
+
* rendered Echarts visualisation.
|
|
29
35
|
*/
|
|
30
36
|
|
|
31
37
|
import { randomUUID } from "node:crypto";
|
|
32
38
|
|
|
33
|
-
import { stringUtils } from "@dbx-tools/appkit-shared";
|
|
39
|
+
import { logUtils, stringUtils } from "@dbx-tools/appkit-shared";
|
|
34
40
|
import { Agent } from "@mastra/core/agent";
|
|
35
41
|
import type { RequestContext } from "@mastra/core/request-context";
|
|
36
42
|
import { createTool } from "@mastra/core/tools";
|
|
@@ -39,6 +45,15 @@ import { z } from "zod";
|
|
|
39
45
|
import type { MastraPluginConfig } from "./config.js";
|
|
40
46
|
import { ModelTier, modelForTier, buildModel } from "./model.js";
|
|
41
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Module-level logger tagged `[mastra/chart]`. Uses the shared
|
|
50
|
+
* {@link logUtils.logger} so calls below `LOG_LEVEL` are
|
|
51
|
+
* discarded for free. Default `LOG_LEVEL` is `info`; flip to
|
|
52
|
+
* `debug` to see the per-chart timeline (`emit:start` →
|
|
53
|
+
* `write:ok(data)` → `planner:done` → `write:ok(option)`).
|
|
54
|
+
*/
|
|
55
|
+
const log = logUtils.logger("mastra/chart");
|
|
56
|
+
|
|
42
57
|
/**
|
|
43
58
|
* Compact, model-friendly representation of an Echarts spec. The
|
|
44
59
|
* planner agent emits this; {@link planToEchartsOption} expands it
|
|
@@ -211,10 +226,10 @@ function getPlannerAgent(config: MastraPluginConfig): Agent {
|
|
|
211
226
|
|
|
212
227
|
/**
|
|
213
228
|
* Run the chart planner against the given dataset and return a
|
|
214
|
-
* full Echarts `EChartsOption` JSON. Used by
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
229
|
+
* full Echarts `EChartsOption` JSON. Used by
|
|
230
|
+
* {@link emitChartWithPlanning}; tools and producers shouldn't
|
|
231
|
+
* call this directly (use the helper instead so chart events
|
|
232
|
+
* follow the same wire-format contract everywhere).
|
|
218
233
|
*/
|
|
219
234
|
export async function runChartPlanner(
|
|
220
235
|
opts: RunChartPlannerOptions,
|
|
@@ -243,6 +258,170 @@ export async function runChartPlanner(
|
|
|
243
258
|
return { option, chartType: plan.chartType };
|
|
244
259
|
}
|
|
245
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Minimal `ToolStream`-shaped writer surface. Defined locally so
|
|
263
|
+
* helpers can take any object with a `.write` method without
|
|
264
|
+
* importing Mastra's full `ToolStream` (which would also drag in
|
|
265
|
+
* agent / tool types this module doesn't otherwise need).
|
|
266
|
+
*/
|
|
267
|
+
interface MinimalWriter {
|
|
268
|
+
write: (chunk: unknown) => unknown;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Inputs to {@link emitChartWithPlanning}. */
|
|
272
|
+
export interface EmitChartWithPlanningOptions {
|
|
273
|
+
/** Mastra `ctx.writer`; missing or closed writers are tolerated. */
|
|
274
|
+
writer?: MinimalWriter;
|
|
275
|
+
/** Plugin config; used to resolve the planner's model. */
|
|
276
|
+
config: MastraPluginConfig;
|
|
277
|
+
/** Per-request context (OBO auth). */
|
|
278
|
+
requestContext?: RequestContext;
|
|
279
|
+
/** Title shown above the rendered chart. Required. */
|
|
280
|
+
title: string;
|
|
281
|
+
/** Optional one-line intent biasing the planner. */
|
|
282
|
+
description?: string;
|
|
283
|
+
/** Tabular dataset to chart (one object per row). */
|
|
284
|
+
data: ReadonlyArray<Record<string, unknown>>;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Output of {@link emitChartWithPlanning}. */
|
|
288
|
+
export interface EmitChartWithPlanningResult {
|
|
289
|
+
/** Short id matching the marker `[[chart:<chartId>]]`. */
|
|
290
|
+
chartId: string;
|
|
291
|
+
/**
|
|
292
|
+
* Promise that resolves once the planner has finished and the
|
|
293
|
+
* `kind: "chart"` event with the option has been emitted (or
|
|
294
|
+
* once the planner has failed silently). Callers that want
|
|
295
|
+
* trace observability should `await` this before returning
|
|
296
|
+
* from their tool's `execute`; callers that want pure
|
|
297
|
+
* fire-and-forget can ignore it.
|
|
298
|
+
*/
|
|
299
|
+
plannerPromise: Promise<void>;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Shared chart-emission primitive used by both the `render_data`
|
|
304
|
+
* tool and Genie's `drainGenieStream`. Keeps both producers on
|
|
305
|
+
* one wire-format contract so the chat client only ever has to
|
|
306
|
+
* understand a single chart event shape.
|
|
307
|
+
*
|
|
308
|
+
* Behaviour:
|
|
309
|
+
*
|
|
310
|
+
* 1. Generates a short `chartId` (8 hex chars).
|
|
311
|
+
* 2. Immediately emits `{ kind: "chart", chartId, title,
|
|
312
|
+
* description?, data }` via the writer so the chat client can
|
|
313
|
+
* mount its `<ChartSlot>` with the rows in hand.
|
|
314
|
+
* 3. Kicks off the chart-planner agent in the background. On
|
|
315
|
+
* success, emits a second `{ kind: "chart", chartId, option }`
|
|
316
|
+
* event - same `chartId`, just the spec - so the client merges
|
|
317
|
+
* the two into one rendered chart. On failure, no follow-up
|
|
318
|
+
* event fires; the client falls back to whatever it can do
|
|
319
|
+
* with the dataset alone (typically a "render failed" frame
|
|
320
|
+
* after the parent tool finishes).
|
|
321
|
+
*
|
|
322
|
+
* Returns `chartId` synchronously so the caller can include it in
|
|
323
|
+
* the tool result (model uses it in `[[chart:<chartId>]]`
|
|
324
|
+
* markers), and `plannerPromise` so the caller can choose
|
|
325
|
+
* trace-spanning vs. snappy-return semantics.
|
|
326
|
+
*/
|
|
327
|
+
export async function emitChartWithPlanning(
|
|
328
|
+
opts: EmitChartWithPlanningOptions,
|
|
329
|
+
): Promise<EmitChartWithPlanningResult> {
|
|
330
|
+
const { writer, config, requestContext, title, description, data } = opts;
|
|
331
|
+
|
|
332
|
+
// Short, marker-friendly id. The LLM types this verbatim into
|
|
333
|
+
// `[[chart:<id>]]`; an 8-hex-char prefix is unique within a
|
|
334
|
+
// single assistant turn (collision odds ~1 in 4 billion) and
|
|
335
|
+
// much less error-prone for the model to reproduce.
|
|
336
|
+
const chartId = randomUUID().replace(/-/g, "").slice(0, 8);
|
|
337
|
+
|
|
338
|
+
log.debug("emit:start", {
|
|
339
|
+
chartId,
|
|
340
|
+
title,
|
|
341
|
+
rows: data.length,
|
|
342
|
+
columns: data[0] ? Object.keys(data[0]) : [],
|
|
343
|
+
hasWriter: writer !== undefined,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Initial event: rows + metadata, no option yet. The client
|
|
347
|
+
// mounts a chart slot that shows a skeleton until the option
|
|
348
|
+
// event arrives (or until the parent tool finishes without
|
|
349
|
+
// one, in which case it falls back).
|
|
350
|
+
await safeWrite(writer, chartId, "data", {
|
|
351
|
+
kind: "chart",
|
|
352
|
+
chartId,
|
|
353
|
+
title,
|
|
354
|
+
...(description ? { description } : {}),
|
|
355
|
+
data,
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// Background planner. Awaitable for trace observability via the
|
|
359
|
+
// returned `plannerPromise`; safe to ignore for pure
|
|
360
|
+
// fire-and-forget. Failures are intentionally swallowed (only
|
|
361
|
+
// logged): the dataset event already landed, so the client has
|
|
362
|
+
// enough to surface a fallback.
|
|
363
|
+
const plannerPromise = (async () => {
|
|
364
|
+
const startedAt = Date.now();
|
|
365
|
+
try {
|
|
366
|
+
const { option, chartType } = await runChartPlanner({
|
|
367
|
+
config,
|
|
368
|
+
...(requestContext ? { requestContext } : {}),
|
|
369
|
+
title,
|
|
370
|
+
...(description ? { description } : {}),
|
|
371
|
+
data,
|
|
372
|
+
});
|
|
373
|
+
log.debug("planner:done", {
|
|
374
|
+
chartId,
|
|
375
|
+
chartType,
|
|
376
|
+
elapsedMs: Date.now() - startedAt,
|
|
377
|
+
});
|
|
378
|
+
await safeWrite(writer, chartId, "option", { kind: "chart", chartId, option });
|
|
379
|
+
} catch (err) {
|
|
380
|
+
// No follow-up event on failure. The client treats a
|
|
381
|
+
// dataset-only chart slot as "render failed" once the
|
|
382
|
+
// parent tool's status flips to done. Surface as a `warn`
|
|
383
|
+
// so the failure is visible at the default log level
|
|
384
|
+
// without being mistaken for a fatal error.
|
|
385
|
+
log.warn("planner:error", {
|
|
386
|
+
chartId,
|
|
387
|
+
elapsedMs: Date.now() - startedAt,
|
|
388
|
+
error: err instanceof Error ? err.message : String(err),
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
})();
|
|
392
|
+
|
|
393
|
+
return { chartId, plannerPromise };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Best-effort writer.write. Failures are logged at `warn` (a
|
|
398
|
+
* persistently-closed writer is the most likely culprit when
|
|
399
|
+
* chart events go missing client-side) but swallowed so a closed
|
|
400
|
+
* downstream stream (cancelled request, client navigated away)
|
|
401
|
+
* can't take a tool down.
|
|
402
|
+
*/
|
|
403
|
+
async function safeWrite(
|
|
404
|
+
writer: MinimalWriter | undefined,
|
|
405
|
+
chartId: string,
|
|
406
|
+
phase: "data" | "option",
|
|
407
|
+
chunk: unknown,
|
|
408
|
+
): Promise<void> {
|
|
409
|
+
if (!writer) {
|
|
410
|
+
log.debug("write:no-writer", { chartId, phase });
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
await writer.write(chunk);
|
|
415
|
+
log.debug("write:ok", { chartId, phase });
|
|
416
|
+
} catch (err) {
|
|
417
|
+
log.warn("write:error", {
|
|
418
|
+
chartId,
|
|
419
|
+
phase,
|
|
420
|
+
error: err instanceof Error ? err.message : String(err),
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
246
425
|
const renderDataInputSchema = z.object({
|
|
247
426
|
title: z.string().describe(stringUtils.toDescription`
|
|
248
427
|
Title shown above the rendered chart. Use a concise
|
|
@@ -269,31 +448,28 @@ const renderDataInputSchema = z.object({
|
|
|
269
448
|
|
|
270
449
|
const renderDataOutputSchema = z.object({
|
|
271
450
|
chartId: z.string().describe(stringUtils.toDescription`
|
|
272
|
-
Identifier of the queued chart.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
blank lines above and below) where the chart should appear.
|
|
277
|
-
The client renders a skeleton there until the chart is
|
|
278
|
-
ready, then swaps in the visualization in place. You can
|
|
279
|
-
keep writing prose around the marker; the agent does not
|
|
280
|
-
need to wait for the chart to render.
|
|
451
|
+
Identifier of the queued chart. To position the chart in
|
|
452
|
+
your reply, embed the marker \`[[chart:<chartId>]]\` on its
|
|
453
|
+
own line where the chart should appear; the client renders
|
|
454
|
+
it inline.
|
|
281
455
|
`),
|
|
282
456
|
});
|
|
283
457
|
|
|
284
458
|
/**
|
|
285
459
|
* Build the `render_data` tool bound to the given plugin config.
|
|
286
460
|
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
461
|
+
* The tool is a thin wrapper around {@link emitChartWithPlanning}:
|
|
462
|
+
* a single `kind: "chart"` writer event ships the raw rows to
|
|
463
|
+
* the client immediately, the chart-planner agent runs alongside
|
|
464
|
+
* (so the calling LLM stays unblocked while the planner thinks),
|
|
465
|
+
* and a follow-up `kind: "chart"` event with the resolved
|
|
466
|
+
* `EChartsOption` lands when it's ready. The tool's `execute`
|
|
467
|
+
* awaits the planner promise so the planner work shows up under
|
|
468
|
+
* the tool's trace span; the LLM still gets back just
|
|
469
|
+
* `{ chartId }`, so its context stays small regardless of dataset
|
|
470
|
+
* size.
|
|
295
471
|
*/
|
|
296
|
-
export function buildRenderDataTool(
|
|
472
|
+
export function buildRenderDataTool(config: MastraPluginConfig) {
|
|
297
473
|
return createTool({
|
|
298
474
|
id: "render_data",
|
|
299
475
|
description: stringUtils.toDescription`
|
|
@@ -301,9 +477,8 @@ export function buildRenderDataTool(_config: MastraPluginConfig) {
|
|
|
301
477
|
the user's view. Pass a title, the raw rows (array of
|
|
302
478
|
objects keyed by column name), and an optional one-line
|
|
303
479
|
description of the insight to highlight. Returns a short
|
|
304
|
-
\`chartId
|
|
305
|
-
|
|
306
|
-
does not block your prose.
|
|
480
|
+
\`chartId\`; the chart renders inline at the position you
|
|
481
|
+
embed the matching \`[[chart:<chartId>]]\` marker.
|
|
307
482
|
|
|
308
483
|
Placement contract: embed \`[[chart:<chartId>]]\` on its own
|
|
309
484
|
line (blank lines above and below) wherever you want the
|
|
@@ -327,28 +502,21 @@ export function buildRenderDataTool(_config: MastraPluginConfig) {
|
|
|
327
502
|
const { title, description, data } = input as z.infer<
|
|
328
503
|
typeof renderDataInputSchema
|
|
329
504
|
>;
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
...(description ? { description } : {}),
|
|
346
|
-
data,
|
|
347
|
-
});
|
|
348
|
-
} catch {
|
|
349
|
-
// Ignore: the parent stream may have closed downstream.
|
|
350
|
-
}
|
|
351
|
-
|
|
505
|
+
const writer = (ctx as { writer?: MinimalWriter } | undefined)?.writer;
|
|
506
|
+
const requestContext = (ctx as { requestContext?: RequestContext } | undefined)
|
|
507
|
+
?.requestContext;
|
|
508
|
+
const { chartId, plannerPromise } = await emitChartWithPlanning({
|
|
509
|
+
...(writer ? { writer } : {}),
|
|
510
|
+
config,
|
|
511
|
+
...(requestContext ? { requestContext } : {}),
|
|
512
|
+
title,
|
|
513
|
+
...(description ? { description } : {}),
|
|
514
|
+
data,
|
|
515
|
+
});
|
|
516
|
+
// Await the planner so its latency is attributed to this
|
|
517
|
+
// tool's trace span. The promise itself swallows planner
|
|
518
|
+
// failures, so this never throws.
|
|
519
|
+
await plannerPromise;
|
|
352
520
|
return { chartId };
|
|
353
521
|
},
|
|
354
522
|
});
|
package/src/config.ts
CHANGED
|
@@ -161,6 +161,19 @@ export interface MastraPluginConfig extends BasePluginConfig {
|
|
|
161
161
|
* or to add custom endpoints in front of the public catalogue.
|
|
162
162
|
*/
|
|
163
163
|
defaultModelFallbacks?: readonly string[];
|
|
164
|
+
/**
|
|
165
|
+
* When `true` (default), every agent gets a built-in input
|
|
166
|
+
* processor that strips `chartId` fields from prior assistant
|
|
167
|
+
* tool-invocation results before they reach the model. This
|
|
168
|
+
* prevents the model from reusing turn-scoped chartIds it sees
|
|
169
|
+
* in memory recall (which would leave `[[chart:<id>]]` markers
|
|
170
|
+
* pointing at writer events that no longer exist).
|
|
171
|
+
*
|
|
172
|
+
* Set to `false` to opt out - useful if a non-default agent
|
|
173
|
+
* needs full visibility into prior chartIds (e.g. an audit
|
|
174
|
+
* agent reasoning about chart lineage).
|
|
175
|
+
*/
|
|
176
|
+
stripStaleCharts?: boolean;
|
|
164
177
|
/**
|
|
165
178
|
* Style guardrails appended to every agent's `instructions` to curb
|
|
166
179
|
* common LLM-isms (em dashes, emojis, sycophantic openers, throwaway
|