@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/dist/index.d.ts
CHANGED
|
@@ -15,5 +15,6 @@ export * from "./src/config.js";
|
|
|
15
15
|
export * from "./src/agents.js";
|
|
16
16
|
export * from "./src/chart.js";
|
|
17
17
|
export * from "./src/genie.js";
|
|
18
|
+
export * from "./src/tools/email.js";
|
|
18
19
|
export { clearServingEndpointsCache, extractModelOverride, listServingEndpoints, MASTRA_MODEL_OVERRIDE_KEY, MODEL_OVERRIDE_BODY_FIELDS, MODEL_OVERRIDE_HEADER, MODEL_OVERRIDE_QUERY, resolveModelId, type ResolvedModel, type ResolveModelOptions, type ServingEndpointSummary, } from "./src/serving.js";
|
|
19
20
|
export { FALLBACK_MODEL_IDS, MODEL_CATALOG, modelForTier, modelsForTier, ModelTier, } from "./src/model.js";
|
package/dist/index.js
CHANGED
|
@@ -15,5 +15,6 @@ export * from "./src/config.js";
|
|
|
15
15
|
export * from "./src/agents.js";
|
|
16
16
|
export * from "./src/chart.js";
|
|
17
17
|
export * from "./src/genie.js";
|
|
18
|
+
export * from "./src/tools/email.js";
|
|
18
19
|
export { clearServingEndpointsCache, extractModelOverride, listServingEndpoints, MASTRA_MODEL_OVERRIDE_KEY, MODEL_OVERRIDE_BODY_FIELDS, MODEL_OVERRIDE_HEADER, MODEL_OVERRIDE_QUERY, resolveModelId, } from "./src/serving.js";
|
|
19
20
|
export { FALLBACK_MODEL_IDS, MODEL_CATALOG, modelForTier, modelsForTier, ModelTier, } from "./src/model.js";
|
package/dist/src/agents.js
CHANGED
|
@@ -19,6 +19,7 @@ import { createTool } from "@mastra/core/tools";
|
|
|
19
19
|
import { buildRenderDataTool } from "./chart.js";
|
|
20
20
|
import { buildGenieProvider } from "./genie.js";
|
|
21
21
|
import { buildModel } from "./model.js";
|
|
22
|
+
import { stripStaleChartsProcessor } from "./processors/strip-stale-charts.js";
|
|
22
23
|
/** Re-export of Mastra's native `createTool` for full-feature access. */
|
|
23
24
|
export { createTool } from "@mastra/core/tools";
|
|
24
25
|
/**
|
|
@@ -64,8 +65,8 @@ export function tool(opts) {
|
|
|
64
65
|
/**
|
|
65
66
|
* Build a deterministic Mastra tool id from a description.
|
|
66
67
|
* Delegates to {@link stringUtils.toUniqueSlug}: slug + always-on
|
|
67
|
-
*
|
|
68
|
-
* collide in traces. Stable across runs.
|
|
68
|
+
* 6-char FNV-1a base-32 suffix so two tools with the same leading
|
|
69
|
+
* words don't collide in traces. Stable across runs.
|
|
69
70
|
*/
|
|
70
71
|
function deriveToolId(description) {
|
|
71
72
|
return stringUtils.toUniqueSlug(description, { fallbackPrefix: "tool" });
|
|
@@ -165,7 +166,7 @@ export async function buildAgents(opts) {
|
|
|
165
166
|
const definitions = resolveDefinitions(config);
|
|
166
167
|
const ids = Object.keys(definitions);
|
|
167
168
|
const defaultAgentId = config.defaultAgent ?? ids[0] ?? FALLBACK_AGENT_ID;
|
|
168
|
-
const plugins = buildPluginsMap(context);
|
|
169
|
+
const plugins = buildPluginsMap(config, context);
|
|
169
170
|
// System-default ambient tools every agent gets out of the box.
|
|
170
171
|
// Currently just `render_data` for inline visualizations; the
|
|
171
172
|
// user can shadow it by including a same-named tool in their own
|
|
@@ -176,6 +177,11 @@ export async function buildAgents(opts) {
|
|
|
176
177
|
};
|
|
177
178
|
const ambientTools = { ...systemTools, ...(config.tools ?? {}) };
|
|
178
179
|
const style = resolveStyleInstructions(config);
|
|
180
|
+
// Default-on protection against the model copying turn-scoped
|
|
181
|
+
// chartIds from prior assistant tool results into the new
|
|
182
|
+
// turn's `[[chart:<id>]]` markers. Opt out per-plugin via
|
|
183
|
+
// `config.stripStaleCharts: false`.
|
|
184
|
+
const inputProcessors = config.stripStaleCharts === false ? [] : [stripStaleChartsProcessor];
|
|
179
185
|
const agents = {};
|
|
180
186
|
for (const [id, def] of Object.entries(definitions)) {
|
|
181
187
|
const tools = await resolveTools(def.tools, plugins, ambientTools);
|
|
@@ -188,6 +194,7 @@ export async function buildAgents(opts) {
|
|
|
188
194
|
model: resolveModel(config, def.model),
|
|
189
195
|
tools,
|
|
190
196
|
...(memory ? { memory } : {}),
|
|
197
|
+
...(inputProcessors.length > 0 ? { inputProcessors } : {}),
|
|
191
198
|
});
|
|
192
199
|
}
|
|
193
200
|
if (!agents[defaultAgentId]) {
|
|
@@ -318,7 +325,7 @@ async function resolveTools(defTools, plugins, ambientTools) {
|
|
|
318
325
|
* Mastra `ctx.writer`, so the UI gets `tool-output` chunks in real
|
|
319
326
|
* time instead of staring at a spinner for the full Genie round-trip.
|
|
320
327
|
*/
|
|
321
|
-
function buildPluginsMap(context) {
|
|
328
|
+
function buildPluginsMap(config, context) {
|
|
322
329
|
const cache = new Map();
|
|
323
330
|
return new Proxy({}, {
|
|
324
331
|
get(_target, propName) {
|
|
@@ -326,7 +333,7 @@ function buildPluginsMap(context) {
|
|
|
326
333
|
return undefined;
|
|
327
334
|
if (cache.has(propName))
|
|
328
335
|
return cache.get(propName) ?? undefined;
|
|
329
|
-
const provider = resolveProvider(context, propName);
|
|
336
|
+
const provider = resolveProvider(config, context, propName);
|
|
330
337
|
cache.set(propName, provider);
|
|
331
338
|
return provider ?? undefined;
|
|
332
339
|
},
|
|
@@ -336,14 +343,17 @@ function buildPluginsMap(context) {
|
|
|
336
343
|
* Pick the right {@link MastraPluginToolkitProvider} for a sibling
|
|
337
344
|
* plugin lookup. Returns the streaming-aware Genie adapter when the
|
|
338
345
|
* caller asks for `genie`; falls back to the generic AppKit
|
|
339
|
-
* `ToolProvider` adapter for every other plugin name.
|
|
346
|
+
* `ToolProvider` adapter for every other plugin name. `config` is
|
|
347
|
+
* threaded through so Genie's tool can run the chart planner
|
|
348
|
+
* inline against the same model resolver / fallback ladder the
|
|
349
|
+
* agents use.
|
|
340
350
|
*/
|
|
341
|
-
function resolveProvider(context, propName) {
|
|
351
|
+
function resolveProvider(config, context, propName) {
|
|
342
352
|
if (propName === "genie") {
|
|
343
353
|
const geniePlugin = pluginUtils.instance(context, genie);
|
|
344
354
|
if (!geniePlugin)
|
|
345
355
|
return null;
|
|
346
|
-
return buildGenieProvider(geniePlugin);
|
|
356
|
+
return buildGenieProvider(geniePlugin, { config });
|
|
347
357
|
}
|
|
348
358
|
const plugin = context?.getPlugins().get(propName);
|
|
349
359
|
return adaptPluginToolkit(plugin);
|
package/dist/src/chart.d.ts
CHANGED
|
@@ -1,31 +1,37 @@
|
|
|
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
|
import type { RequestContext } from "@mastra/core/request-context";
|
|
31
37
|
import { z } from "zod";
|
|
@@ -76,29 +82,89 @@ export interface RunChartPlannerResult {
|
|
|
76
82
|
}
|
|
77
83
|
/**
|
|
78
84
|
* Run the chart planner against the given dataset and return a
|
|
79
|
-
* full Echarts `EChartsOption` JSON. Used by
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
85
|
+
* full Echarts `EChartsOption` JSON. Used by
|
|
86
|
+
* {@link emitChartWithPlanning}; tools and producers shouldn't
|
|
87
|
+
* call this directly (use the helper instead so chart events
|
|
88
|
+
* follow the same wire-format contract everywhere).
|
|
83
89
|
*/
|
|
84
90
|
export declare function runChartPlanner(opts: RunChartPlannerOptions): Promise<RunChartPlannerResult>;
|
|
85
91
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* `ctx.writer` carrying the raw dataset for the client. The
|
|
91
|
-
* client's chart slot then POSTs the data to
|
|
92
|
-
* `/route/render-chart` to get an `EChartsOption` back from the
|
|
93
|
-
* planner agent. This keeps the calling LLM unblocked - it can
|
|
94
|
-
* write the report referencing the chart by id while the client
|
|
95
|
-
* is still rendering it.
|
|
92
|
+
* Minimal `ToolStream`-shaped writer surface. Defined locally so
|
|
93
|
+
* helpers can take any object with a `.write` method without
|
|
94
|
+
* importing Mastra's full `ToolStream` (which would also drag in
|
|
95
|
+
* agent / tool types this module doesn't otherwise need).
|
|
96
96
|
*/
|
|
97
|
-
|
|
97
|
+
interface MinimalWriter {
|
|
98
|
+
write: (chunk: unknown) => unknown;
|
|
99
|
+
}
|
|
100
|
+
/** Inputs to {@link emitChartWithPlanning}. */
|
|
101
|
+
export interface EmitChartWithPlanningOptions {
|
|
102
|
+
/** Mastra `ctx.writer`; missing or closed writers are tolerated. */
|
|
103
|
+
writer?: MinimalWriter;
|
|
104
|
+
/** Plugin config; used to resolve the planner's model. */
|
|
105
|
+
config: MastraPluginConfig;
|
|
106
|
+
/** Per-request context (OBO auth). */
|
|
107
|
+
requestContext?: RequestContext;
|
|
108
|
+
/** Title shown above the rendered chart. Required. */
|
|
98
109
|
title: string;
|
|
99
|
-
|
|
100
|
-
description?: string
|
|
101
|
-
|
|
110
|
+
/** Optional one-line intent biasing the planner. */
|
|
111
|
+
description?: string;
|
|
112
|
+
/** Tabular dataset to chart (one object per row). */
|
|
113
|
+
data: ReadonlyArray<Record<string, unknown>>;
|
|
114
|
+
}
|
|
115
|
+
/** Output of {@link emitChartWithPlanning}. */
|
|
116
|
+
export interface EmitChartWithPlanningResult {
|
|
117
|
+
/** Short id matching the marker `[[chart:<chartId>]]`. */
|
|
102
118
|
chartId: string;
|
|
103
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Promise that resolves once the planner has finished and the
|
|
121
|
+
* `kind: "chart"` event with the option has been emitted (or
|
|
122
|
+
* once the planner has failed silently). Callers that want
|
|
123
|
+
* trace observability should `await` this before returning
|
|
124
|
+
* from their tool's `execute`; callers that want pure
|
|
125
|
+
* fire-and-forget can ignore it.
|
|
126
|
+
*/
|
|
127
|
+
plannerPromise: Promise<void>;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Shared chart-emission primitive used by both the `render_data`
|
|
131
|
+
* tool and Genie's `drainGenieStream`. Keeps both producers on
|
|
132
|
+
* one wire-format contract so the chat client only ever has to
|
|
133
|
+
* understand a single chart event shape.
|
|
134
|
+
*
|
|
135
|
+
* Behaviour:
|
|
136
|
+
*
|
|
137
|
+
* 1. Generates a short `chartId` (8 hex chars).
|
|
138
|
+
* 2. Immediately emits `{ kind: "chart", chartId, title,
|
|
139
|
+
* description?, data }` via the writer so the chat client can
|
|
140
|
+
* mount its `<ChartSlot>` with the rows in hand.
|
|
141
|
+
* 3. Kicks off the chart-planner agent in the background. On
|
|
142
|
+
* success, emits a second `{ kind: "chart", chartId, option }`
|
|
143
|
+
* event - same `chartId`, just the spec - so the client merges
|
|
144
|
+
* the two into one rendered chart. On failure, no follow-up
|
|
145
|
+
* event fires; the client falls back to whatever it can do
|
|
146
|
+
* with the dataset alone (typically a "render failed" frame
|
|
147
|
+
* after the parent tool finishes).
|
|
148
|
+
*
|
|
149
|
+
* Returns `chartId` synchronously so the caller can include it in
|
|
150
|
+
* the tool result (model uses it in `[[chart:<chartId>]]`
|
|
151
|
+
* markers), and `plannerPromise` so the caller can choose
|
|
152
|
+
* trace-spanning vs. snappy-return semantics.
|
|
153
|
+
*/
|
|
154
|
+
export declare function emitChartWithPlanning(opts: EmitChartWithPlanningOptions): Promise<EmitChartWithPlanningResult>;
|
|
155
|
+
/**
|
|
156
|
+
* Build the `render_data` tool bound to the given plugin config.
|
|
157
|
+
*
|
|
158
|
+
* The tool is a thin wrapper around {@link emitChartWithPlanning}:
|
|
159
|
+
* a single `kind: "chart"` writer event ships the raw rows to
|
|
160
|
+
* the client immediately, the chart-planner agent runs alongside
|
|
161
|
+
* (so the calling LLM stays unblocked while the planner thinks),
|
|
162
|
+
* and a follow-up `kind: "chart"` event with the resolved
|
|
163
|
+
* `EChartsOption` lands when it's ready. The tool's `execute`
|
|
164
|
+
* awaits the planner promise so the planner work shows up under
|
|
165
|
+
* the tool's trace span; the LLM still gets back just
|
|
166
|
+
* `{ chartId }`, so its context stays small regardless of dataset
|
|
167
|
+
* size.
|
|
168
|
+
*/
|
|
169
|
+
export declare function buildRenderDataTool(config: MastraPluginConfig): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "render_data", unknown>;
|
|
104
170
|
export {};
|
package/dist/src/chart.js
CHANGED
|
@@ -1,38 +1,52 @@
|
|
|
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
|
import { randomUUID } from "node:crypto";
|
|
31
|
-
import { stringUtils } from "@dbx-tools/appkit-shared";
|
|
37
|
+
import { logUtils, stringUtils } from "@dbx-tools/appkit-shared";
|
|
32
38
|
import { Agent } from "@mastra/core/agent";
|
|
33
39
|
import { createTool } from "@mastra/core/tools";
|
|
34
40
|
import { z } from "zod";
|
|
35
41
|
import { ModelTier, modelForTier, buildModel } from "./model.js";
|
|
42
|
+
/**
|
|
43
|
+
* Module-level logger tagged `[mastra/chart]`. Uses the shared
|
|
44
|
+
* {@link logUtils.logger} so calls below `LOG_LEVEL` are
|
|
45
|
+
* discarded for free. Default `LOG_LEVEL` is `info`; flip to
|
|
46
|
+
* `debug` to see the per-chart timeline (`emit:start` →
|
|
47
|
+
* `write:ok(data)` → `planner:done` → `write:ok(option)`).
|
|
48
|
+
*/
|
|
49
|
+
const log = logUtils.logger("mastra/chart");
|
|
36
50
|
/**
|
|
37
51
|
* Compact, model-friendly representation of an Echarts spec. The
|
|
38
52
|
* planner agent emits this; {@link planToEchartsOption} expands it
|
|
@@ -179,10 +193,10 @@ function getPlannerAgent(config) {
|
|
|
179
193
|
}
|
|
180
194
|
/**
|
|
181
195
|
* Run the chart planner against the given dataset and return a
|
|
182
|
-
* full Echarts `EChartsOption` JSON. Used by
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
196
|
+
* full Echarts `EChartsOption` JSON. Used by
|
|
197
|
+
* {@link emitChartWithPlanning}; tools and producers shouldn't
|
|
198
|
+
* call this directly (use the helper instead so chart events
|
|
199
|
+
* follow the same wire-format contract everywhere).
|
|
186
200
|
*/
|
|
187
201
|
export async function runChartPlanner(opts) {
|
|
188
202
|
const { config, requestContext, title, description, data } = opts;
|
|
@@ -206,6 +220,117 @@ export async function runChartPlanner(opts) {
|
|
|
206
220
|
const option = planToEchartsOption(plan, title);
|
|
207
221
|
return { option, chartType: plan.chartType };
|
|
208
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Shared chart-emission primitive used by both the `render_data`
|
|
225
|
+
* tool and Genie's `drainGenieStream`. Keeps both producers on
|
|
226
|
+
* one wire-format contract so the chat client only ever has to
|
|
227
|
+
* understand a single chart event shape.
|
|
228
|
+
*
|
|
229
|
+
* Behaviour:
|
|
230
|
+
*
|
|
231
|
+
* 1. Generates a short `chartId` (8 hex chars).
|
|
232
|
+
* 2. Immediately emits `{ kind: "chart", chartId, title,
|
|
233
|
+
* description?, data }` via the writer so the chat client can
|
|
234
|
+
* mount its `<ChartSlot>` with the rows in hand.
|
|
235
|
+
* 3. Kicks off the chart-planner agent in the background. On
|
|
236
|
+
* success, emits a second `{ kind: "chart", chartId, option }`
|
|
237
|
+
* event - same `chartId`, just the spec - so the client merges
|
|
238
|
+
* the two into one rendered chart. On failure, no follow-up
|
|
239
|
+
* event fires; the client falls back to whatever it can do
|
|
240
|
+
* with the dataset alone (typically a "render failed" frame
|
|
241
|
+
* after the parent tool finishes).
|
|
242
|
+
*
|
|
243
|
+
* Returns `chartId` synchronously so the caller can include it in
|
|
244
|
+
* the tool result (model uses it in `[[chart:<chartId>]]`
|
|
245
|
+
* markers), and `plannerPromise` so the caller can choose
|
|
246
|
+
* trace-spanning vs. snappy-return semantics.
|
|
247
|
+
*/
|
|
248
|
+
export async function emitChartWithPlanning(opts) {
|
|
249
|
+
const { writer, config, requestContext, title, description, data } = opts;
|
|
250
|
+
// Short, marker-friendly id. The LLM types this verbatim into
|
|
251
|
+
// `[[chart:<id>]]`; an 8-hex-char prefix is unique within a
|
|
252
|
+
// single assistant turn (collision odds ~1 in 4 billion) and
|
|
253
|
+
// much less error-prone for the model to reproduce.
|
|
254
|
+
const chartId = randomUUID().replace(/-/g, "").slice(0, 8);
|
|
255
|
+
log.debug("emit:start", {
|
|
256
|
+
chartId,
|
|
257
|
+
title,
|
|
258
|
+
rows: data.length,
|
|
259
|
+
columns: data[0] ? Object.keys(data[0]) : [],
|
|
260
|
+
hasWriter: writer !== undefined,
|
|
261
|
+
});
|
|
262
|
+
// Initial event: rows + metadata, no option yet. The client
|
|
263
|
+
// mounts a chart slot that shows a skeleton until the option
|
|
264
|
+
// event arrives (or until the parent tool finishes without
|
|
265
|
+
// one, in which case it falls back).
|
|
266
|
+
await safeWrite(writer, chartId, "data", {
|
|
267
|
+
kind: "chart",
|
|
268
|
+
chartId,
|
|
269
|
+
title,
|
|
270
|
+
...(description ? { description } : {}),
|
|
271
|
+
data,
|
|
272
|
+
});
|
|
273
|
+
// Background planner. Awaitable for trace observability via the
|
|
274
|
+
// returned `plannerPromise`; safe to ignore for pure
|
|
275
|
+
// fire-and-forget. Failures are intentionally swallowed (only
|
|
276
|
+
// logged): the dataset event already landed, so the client has
|
|
277
|
+
// enough to surface a fallback.
|
|
278
|
+
const plannerPromise = (async () => {
|
|
279
|
+
const startedAt = Date.now();
|
|
280
|
+
try {
|
|
281
|
+
const { option, chartType } = await runChartPlanner({
|
|
282
|
+
config,
|
|
283
|
+
...(requestContext ? { requestContext } : {}),
|
|
284
|
+
title,
|
|
285
|
+
...(description ? { description } : {}),
|
|
286
|
+
data,
|
|
287
|
+
});
|
|
288
|
+
log.debug("planner:done", {
|
|
289
|
+
chartId,
|
|
290
|
+
chartType,
|
|
291
|
+
elapsedMs: Date.now() - startedAt,
|
|
292
|
+
});
|
|
293
|
+
await safeWrite(writer, chartId, "option", { kind: "chart", chartId, option });
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
// No follow-up event on failure. The client treats a
|
|
297
|
+
// dataset-only chart slot as "render failed" once the
|
|
298
|
+
// parent tool's status flips to done. Surface as a `warn`
|
|
299
|
+
// so the failure is visible at the default log level
|
|
300
|
+
// without being mistaken for a fatal error.
|
|
301
|
+
log.warn("planner:error", {
|
|
302
|
+
chartId,
|
|
303
|
+
elapsedMs: Date.now() - startedAt,
|
|
304
|
+
error: err instanceof Error ? err.message : String(err),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
})();
|
|
308
|
+
return { chartId, plannerPromise };
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Best-effort writer.write. Failures are logged at `warn` (a
|
|
312
|
+
* persistently-closed writer is the most likely culprit when
|
|
313
|
+
* chart events go missing client-side) but swallowed so a closed
|
|
314
|
+
* downstream stream (cancelled request, client navigated away)
|
|
315
|
+
* can't take a tool down.
|
|
316
|
+
*/
|
|
317
|
+
async function safeWrite(writer, chartId, phase, chunk) {
|
|
318
|
+
if (!writer) {
|
|
319
|
+
log.debug("write:no-writer", { chartId, phase });
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
await writer.write(chunk);
|
|
324
|
+
log.debug("write:ok", { chartId, phase });
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
log.warn("write:error", {
|
|
328
|
+
chartId,
|
|
329
|
+
phase,
|
|
330
|
+
error: err instanceof Error ? err.message : String(err),
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
209
334
|
const renderDataInputSchema = z.object({
|
|
210
335
|
title: z.string().describe(stringUtils.toDescription `
|
|
211
336
|
Title shown above the rendered chart. Use a concise
|
|
@@ -231,30 +356,27 @@ const renderDataInputSchema = z.object({
|
|
|
231
356
|
});
|
|
232
357
|
const renderDataOutputSchema = z.object({
|
|
233
358
|
chartId: z.string().describe(stringUtils.toDescription `
|
|
234
|
-
Identifier of the queued chart.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
blank lines above and below) where the chart should appear.
|
|
239
|
-
The client renders a skeleton there until the chart is
|
|
240
|
-
ready, then swaps in the visualization in place. You can
|
|
241
|
-
keep writing prose around the marker; the agent does not
|
|
242
|
-
need to wait for the chart to render.
|
|
359
|
+
Identifier of the queued chart. To position the chart in
|
|
360
|
+
your reply, embed the marker \`[[chart:<chartId>]]\` on its
|
|
361
|
+
own line where the chart should appear; the client renders
|
|
362
|
+
it inline.
|
|
243
363
|
`),
|
|
244
364
|
});
|
|
245
365
|
/**
|
|
246
366
|
* Build the `render_data` tool bound to the given plugin config.
|
|
247
367
|
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
368
|
+
* The tool is a thin wrapper around {@link emitChartWithPlanning}:
|
|
369
|
+
* a single `kind: "chart"` writer event ships the raw rows to
|
|
370
|
+
* the client immediately, the chart-planner agent runs alongside
|
|
371
|
+
* (so the calling LLM stays unblocked while the planner thinks),
|
|
372
|
+
* and a follow-up `kind: "chart"` event with the resolved
|
|
373
|
+
* `EChartsOption` lands when it's ready. The tool's `execute`
|
|
374
|
+
* awaits the planner promise so the planner work shows up under
|
|
375
|
+
* the tool's trace span; the LLM still gets back just
|
|
376
|
+
* `{ chartId }`, so its context stays small regardless of dataset
|
|
377
|
+
* size.
|
|
256
378
|
*/
|
|
257
|
-
export function buildRenderDataTool(
|
|
379
|
+
export function buildRenderDataTool(config) {
|
|
258
380
|
return createTool({
|
|
259
381
|
id: "render_data",
|
|
260
382
|
description: stringUtils.toDescription `
|
|
@@ -262,9 +384,8 @@ export function buildRenderDataTool(_config) {
|
|
|
262
384
|
the user's view. Pass a title, the raw rows (array of
|
|
263
385
|
objects keyed by column name), and an optional one-line
|
|
264
386
|
description of the insight to highlight. Returns a short
|
|
265
|
-
\`chartId
|
|
266
|
-
|
|
267
|
-
does not block your prose.
|
|
387
|
+
\`chartId\`; the chart renders inline at the position you
|
|
388
|
+
embed the matching \`[[chart:<chartId>]]\` marker.
|
|
268
389
|
|
|
269
390
|
Placement contract: embed \`[[chart:<chartId>]]\` on its own
|
|
270
391
|
line (blank lines above and below) wherever you want the
|
|
@@ -286,26 +407,21 @@ export function buildRenderDataTool(_config) {
|
|
|
286
407
|
outputSchema: renderDataOutputSchema,
|
|
287
408
|
execute: async (input, ctx) => {
|
|
288
409
|
const { title, description, data } = input;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
catch {
|
|
307
|
-
// Ignore: the parent stream may have closed downstream.
|
|
308
|
-
}
|
|
410
|
+
const writer = ctx?.writer;
|
|
411
|
+
const requestContext = ctx
|
|
412
|
+
?.requestContext;
|
|
413
|
+
const { chartId, plannerPromise } = await emitChartWithPlanning({
|
|
414
|
+
...(writer ? { writer } : {}),
|
|
415
|
+
config,
|
|
416
|
+
...(requestContext ? { requestContext } : {}),
|
|
417
|
+
title,
|
|
418
|
+
...(description ? { description } : {}),
|
|
419
|
+
data,
|
|
420
|
+
});
|
|
421
|
+
// Await the planner so its latency is attributed to this
|
|
422
|
+
// tool's trace span. The promise itself swallows planner
|
|
423
|
+
// failures, so this never throws.
|
|
424
|
+
await plannerPromise;
|
|
309
425
|
return { chartId };
|
|
310
426
|
},
|
|
311
427
|
});
|
package/dist/src/config.d.ts
CHANGED
|
@@ -152,6 +152,19 @@ export interface MastraPluginConfig extends BasePluginConfig {
|
|
|
152
152
|
* or to add custom endpoints in front of the public catalogue.
|
|
153
153
|
*/
|
|
154
154
|
defaultModelFallbacks?: readonly string[];
|
|
155
|
+
/**
|
|
156
|
+
* When `true` (default), every agent gets a built-in input
|
|
157
|
+
* processor that strips `chartId` fields from prior assistant
|
|
158
|
+
* tool-invocation results before they reach the model. This
|
|
159
|
+
* prevents the model from reusing turn-scoped chartIds it sees
|
|
160
|
+
* in memory recall (which would leave `[[chart:<id>]]` markers
|
|
161
|
+
* pointing at writer events that no longer exist).
|
|
162
|
+
*
|
|
163
|
+
* Set to `false` to opt out - useful if a non-default agent
|
|
164
|
+
* needs full visibility into prior chartIds (e.g. an audit
|
|
165
|
+
* agent reasoning about chart lineage).
|
|
166
|
+
*/
|
|
167
|
+
stripStaleCharts?: boolean;
|
|
155
168
|
/**
|
|
156
169
|
* Style guardrails appended to every agent's `instructions` to curb
|
|
157
170
|
* common LLM-isms (em dashes, emojis, sycophantic openers, throwaway
|