@dbx-tools/appkit-mastra 0.1.4 → 0.1.12
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 +145 -10
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/src/agents.d.ts +1 -1
- package/dist/src/agents.js +43 -19
- package/dist/src/chart.d.ts +170 -0
- package/dist/src/chart.js +491 -0
- package/dist/src/config.d.ts +13 -0
- package/dist/src/genie.d.ts +36 -14
- package/dist/src/genie.js +434 -75
- package/dist/src/history.d.ts +67 -0
- package/dist/src/history.js +172 -0
- package/dist/src/memory.js +15 -2
- package/dist/src/model.js +18 -14
- package/dist/src/plugin.d.ts +11 -1
- package/dist/src/plugin.js +28 -2
- 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.d.ts +4 -0
- package/dist/src/server.js +59 -45
- 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 +2 -0
- package/package.json +21 -25
- package/src/agents.ts +46 -21
- package/src/chart.ts +593 -0
- package/src/config.ts +13 -0
- package/src/genie.ts +499 -102
- package/src/history.ts +210 -0
- package/src/memory.ts +19 -2
- package/src/model.ts +18 -13
- package/src/plugin.ts +30 -2
- package/src/processors/strip-stale-charts.ts +105 -0
- package/src/server.ts +76 -51
- package/src/serving.ts +21 -2
- package/src/tools/email.ts +147 -0
package/README.md
CHANGED
|
@@ -195,14 +195,148 @@ intact.
|
|
|
195
195
|
streaming-aware tools record built on top of the plugin's
|
|
196
196
|
`exports().sendMessage` AsyncGenerator. Each Genie wire event
|
|
197
197
|
(`FETCHING_METADATA`, `ASKING_AI`, `EXECUTING_QUERY`, attached SQL,
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
pills
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
`genie_get_conversation`.
|
|
205
|
-
|
|
198
|
+
errors) is normalised into a `GenieProgress` payload and pushed
|
|
199
|
+
mid-flight through Mastra's `ctx.writer`, surfacing as
|
|
200
|
+
`tool-output` chunks the React client can render as inline status
|
|
201
|
+
pills and SQL blocks while the LLM is still waiting on the final
|
|
202
|
+
`tool-result`. Tool ids are stable: `genie` for the default alias,
|
|
203
|
+
`genie-<alias>` for additional aliases, and one shared
|
|
204
|
+
`genie_get_conversation`.
|
|
205
|
+
|
|
206
|
+
Genie tool-result shape (LLM-bound):
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
{
|
|
210
|
+
conversationId?: string, // pass back to continue the same Genie thread
|
|
211
|
+
genieAnswer?: string, // Genie's prose answer; pass through verbatim
|
|
212
|
+
datasets?: [{ // metadata only, one per executed SQL statement
|
|
213
|
+
chartId: string, // embed [[chart:<chartId>]] to render inline
|
|
214
|
+
title?: string,
|
|
215
|
+
description?: string,
|
|
216
|
+
columns: string[],
|
|
217
|
+
rowCount: number,
|
|
218
|
+
sql?: string,
|
|
219
|
+
}],
|
|
220
|
+
suggestedFollowUps?: string[], // UI shows as buttons; don't list in reply
|
|
221
|
+
error?: string,
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`datasets[]` is metadata only - column names, row count, the SQL
|
|
226
|
+
Genie ran. The actual rows ride a separate `kind: "chart"` writer
|
|
227
|
+
event so the LLM never has them in context (token cost stays flat
|
|
228
|
+
regardless of dataset size). The model references each dataset by
|
|
229
|
+
its `chartId` via the `[[chart:<chartId>]]` marker to display the
|
|
230
|
+
chart inline; see the `render_data` section below for how the
|
|
231
|
+
client resolves those markers.
|
|
232
|
+
|
|
233
|
+
Genie data flow:
|
|
234
|
+
|
|
235
|
+
- The writer emits `kind: "started" | "status" | "sql" |
|
|
236
|
+
"suggested" | "error"` events for the live loading pill. SQL
|
|
237
|
+
text is shown via a small Shiki-highlighted block.
|
|
238
|
+
- The writer **also** emits two `kind: "chart"` events per
|
|
239
|
+
executed SQL statement, sharing the same `chartId`: the first
|
|
240
|
+
carries `{chartId, title, description?, data}` (rows converted
|
|
241
|
+
to objects keyed by column name with best-effort numeric
|
|
242
|
+
coercion); the second, when the chart-planner agent finishes,
|
|
243
|
+
carries `{chartId, option}` (the resolved Echarts spec). The
|
|
244
|
+
chat client's `<ChartSlot>` merges them by `chartId` and
|
|
245
|
+
renders inline at the matching `[[chart:<chartId>]]` marker.
|
|
246
|
+
This is the exact same wire format as the `render_data` tool,
|
|
247
|
+
so Genie and hand-built charts are indistinguishable on the
|
|
248
|
+
client.
|
|
249
|
+
- After a hard reload, `synthesizeToolEventsFromHistory` rebuilds
|
|
250
|
+
`suggested` events from the persisted tool-result; the SQL pill
|
|
251
|
+
and chart events are live-only and don't replay.
|
|
252
|
+
|
|
253
|
+
### `render_data` (system-default ambient tool)
|
|
254
|
+
|
|
255
|
+
`buildAgents` registers a system-level `render_data` tool on
|
|
256
|
+
every agent so the model can submit any tabular dataset for
|
|
257
|
+
inline charting. Users can shadow it by including a same-named
|
|
258
|
+
tool in `config.tools` or in a per-agent `tools` map; otherwise
|
|
259
|
+
it's just there.
|
|
260
|
+
|
|
261
|
+
The tool is generic - not coupled to Genie or any particular
|
|
262
|
+
upstream. Input is `{ title, description?, data: Row[] }` where
|
|
263
|
+
`data` is an array of objects keyed by column name (a SQL row
|
|
264
|
+
set, an API response, a hand-built array, etc.).
|
|
265
|
+
|
|
266
|
+
#### How it works (shared pipeline with Genie)
|
|
267
|
+
|
|
268
|
+
Both `render_data` and Genie's `drainGenieStream` route through
|
|
269
|
+
one helper, `emitChartWithPlanning`, so the wire format and
|
|
270
|
+
trace shape are consistent everywhere:
|
|
271
|
+
|
|
272
|
+
1. Mint a short `chartId` (8 hex chars).
|
|
273
|
+
2. Push a `{ kind: "chart", chartId, title, description?, data }`
|
|
274
|
+
event to `ctx.writer` immediately. The chat client mounts a
|
|
275
|
+
`<ChartSlot>` showing a "Rendering chart" skeleton.
|
|
276
|
+
3. Kick off the chart-planner agent
|
|
277
|
+
(`modelForTier(ModelTier.Fast)`) with the dataset in the
|
|
278
|
+
background. The agent's `structuredOutput` schema is a compact
|
|
279
|
+
"chart plan" (chart type + axis labels + categories +
|
|
280
|
+
series); the helper expands the plan into an Echarts
|
|
281
|
+
`EChartsOption` JSON.
|
|
282
|
+
4. When the planner resolves, push a follow-up
|
|
283
|
+
`{ kind: "chart", chartId, option }` event. The client's
|
|
284
|
+
`<ChartSlot>` merges it with the dataset event (last write
|
|
285
|
+
wins per field) and swaps in `<ReactECharts>`.
|
|
286
|
+
5. If the planner fails, no follow-up event fires. Once the
|
|
287
|
+
parent tool finishes, the slot transitions to a "couldn't
|
|
288
|
+
render chart" fallback frame.
|
|
289
|
+
|
|
290
|
+
The parent tool (`render_data` or Genie) `await`s the planner
|
|
291
|
+
promise(s) before its `execute` returns, so chart latency shows
|
|
292
|
+
up under the tool's trace span. The dataset event fires
|
|
293
|
+
**immediately**, though, so the calling LLM gets back the
|
|
294
|
+
`chartId` synchronously and the chat client has a layout slot
|
|
295
|
+
ready before the planner resolves.
|
|
296
|
+
|
|
297
|
+
The LLM-bound payload is just `{ chartId }` (for `render_data`)
|
|
298
|
+
or `datasets[]` metadata (for Genie); row data and option JSON
|
|
299
|
+
never reach the LLM, so token cost stays flat regardless of
|
|
300
|
+
dataset size.
|
|
301
|
+
|
|
302
|
+
#### Inline placement contract
|
|
303
|
+
|
|
304
|
+
The model embeds `[[chart:<chartId>]]` on its own line in its
|
|
305
|
+
markdown reply at the position where the chart should appear:
|
|
306
|
+
|
|
307
|
+
```markdown
|
|
308
|
+
## Audit Score
|
|
309
|
+
|
|
310
|
+
Audit Score is stable at ~94%, hovering between 93.5 and 95.0.
|
|
311
|
+
|
|
312
|
+
[[chart:a3f9c1d2]]
|
|
313
|
+
|
|
314
|
+
## Service Time
|
|
315
|
+
|
|
316
|
+
Service time is the outlier at 162.5s, up from a target of 150s.
|
|
317
|
+
|
|
318
|
+
[[chart:b7e2d4f1]]
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
The chat client splits the assistant text on these markers and
|
|
322
|
+
drops a `<ChartSlot>` in at each spot. A marker that arrives
|
|
323
|
+
before its `chart` event (rare; only during fast streaming) shows
|
|
324
|
+
a "Queueing chart" skeleton; a chart whose marker the model
|
|
325
|
+
forgot to place falls through to the end of the reply as a
|
|
326
|
+
fallback. All three states (queueing, rendering, rendered) share
|
|
327
|
+
the same fixed-height frame so the layout doesn't jump as
|
|
328
|
+
charts resolve.
|
|
329
|
+
|
|
330
|
+
#### Trade-offs
|
|
331
|
+
|
|
332
|
+
- Both events ride the writer, not the persisted tool-result, so
|
|
333
|
+
charts don't re-render after a hard reload. The model can call
|
|
334
|
+
`render_data` again (or re-ask Genie) on the next turn if the
|
|
335
|
+
user wants the chart back.
|
|
336
|
+
- The chart-planner is a separate model call per dataset (fast
|
|
337
|
+
tier, but still ~1-3s each). For an N-chart turn, latency is
|
|
338
|
+
`Genie + max(planners)` since the planners run concurrently
|
|
339
|
+
with the rest of Genie's stream and with each other.
|
|
206
340
|
|
|
207
341
|
Plugins that aren't registered (or don't implement the toolkit
|
|
208
342
|
interface) resolve to `undefined` at runtime, so guard with `?.` /
|
|
@@ -256,8 +390,9 @@ const saveDoc = createTool({
|
|
|
256
390
|
```
|
|
257
391
|
|
|
258
392
|
When `tool()`'s `id` is omitted it's auto-derived from a slugified
|
|
259
|
-
description plus a 6-char
|
|
260
|
-
stay readable. Pass an explicit `id` when you want to pin
|
|
393
|
+
description plus a 6-char FNV-1a base-32 suffix - stable across runs
|
|
394
|
+
so traces stay readable. Pass an explicit `id` when you want to pin
|
|
395
|
+
one.
|
|
261
396
|
|
|
262
397
|
Reach for `createTool` when you need Mastra-only fields (`outputSchema`,
|
|
263
398
|
`suspendSchema`, `requireApproval`, `mcp`, etc.).
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ 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";
|
|
18
|
+
export * from "./src/tools/email.js";
|
|
17
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";
|
|
18
20
|
export { FALLBACK_MODEL_IDS, MODEL_CATALOG, modelForTier, modelsForTier, ModelTier, } from "./src/model.js";
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,8 @@ 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";
|
|
18
|
+
export * from "./src/tools/email.js";
|
|
17
19
|
export { clearServingEndpointsCache, extractModelOverride, listServingEndpoints, MASTRA_MODEL_OVERRIDE_KEY, MODEL_OVERRIDE_BODY_FIELDS, MODEL_OVERRIDE_HEADER, MODEL_OVERRIDE_QUERY, resolveModelId, } from "./src/serving.js";
|
|
18
20
|
export { FALLBACK_MODEL_IDS, MODEL_CATALOG, modelForTier, modelsForTier, ModelTier, } from "./src/model.js";
|
package/dist/src/agents.d.ts
CHANGED
|
@@ -284,7 +284,7 @@ export declare const FALLBACK_AGENT_ID = "default";
|
|
|
284
284
|
* Override globally via {@link MastraPluginConfig.styleInstructions}
|
|
285
285
|
* (pass `false` to disable entirely, or a string to replace).
|
|
286
286
|
*/
|
|
287
|
-
export declare const DEFAULT_STYLE_INSTRUCTIONS
|
|
287
|
+
export declare const DEFAULT_STYLE_INSTRUCTIONS: string;
|
|
288
288
|
/**
|
|
289
289
|
* Resolve every entry in `config.agents` into a Mastra `Agent`
|
|
290
290
|
* instance. When `config.agents` is omitted the plugin registers a
|
package/dist/src/agents.js
CHANGED
|
@@ -16,8 +16,10 @@ import { genie } from "@databricks/appkit";
|
|
|
16
16
|
import { logUtils, pluginUtils, stringUtils } from "@dbx-tools/appkit-shared";
|
|
17
17
|
import { Agent } from "@mastra/core/agent";
|
|
18
18
|
import { createTool } from "@mastra/core/tools";
|
|
19
|
+
import { buildRenderDataTool } from "./chart.js";
|
|
19
20
|
import { buildGenieProvider } from "./genie.js";
|
|
20
21
|
import { buildModel } from "./model.js";
|
|
22
|
+
import { stripStaleChartsProcessor } from "./processors/strip-stale-charts.js";
|
|
21
23
|
/** Re-export of Mastra's native `createTool` for full-feature access. */
|
|
22
24
|
export { createTool } from "@mastra/core/tools";
|
|
23
25
|
/**
|
|
@@ -63,8 +65,8 @@ export function tool(opts) {
|
|
|
63
65
|
/**
|
|
64
66
|
* Build a deterministic Mastra tool id from a description.
|
|
65
67
|
* Delegates to {@link stringUtils.toUniqueSlug}: slug + always-on
|
|
66
|
-
*
|
|
67
|
-
* 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.
|
|
68
70
|
*/
|
|
69
71
|
function deriveToolId(description) {
|
|
70
72
|
return stringUtils.toUniqueSlug(description, { fallbackPrefix: "tool" });
|
|
@@ -110,16 +112,21 @@ Rules:
|
|
|
110
112
|
* Override globally via {@link MastraPluginConfig.styleInstructions}
|
|
111
113
|
* (pass `false` to disable entirely, or a string to replace).
|
|
112
114
|
*/
|
|
113
|
-
export const DEFAULT_STYLE_INSTRUCTIONS =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
115
|
+
export const DEFAULT_STYLE_INSTRUCTIONS = [
|
|
116
|
+
"Output style:",
|
|
117
|
+
"",
|
|
118
|
+
"Use markdown formatting, including headings, lists, and code blocks.",
|
|
119
|
+
"Avoid lists and headers for short replies.",
|
|
120
|
+
"Plain prose.",
|
|
121
|
+
"Use hyphens (-) only. Never use em dashes or en dashes.",
|
|
122
|
+
"Never use emojis.",
|
|
123
|
+
"Skip openers like 'Great question', 'Absolutely', and 'I'd be happy to help'.",
|
|
124
|
+
"Skip closers like 'Let me know if you have any questions'.",
|
|
125
|
+
"Skip self-disclaimers like 'I should mention' and 'It's important to note'.",
|
|
126
|
+
"Answer directly.",
|
|
127
|
+
"Do not include a preamble before the actual answer.",
|
|
128
|
+
"Use lists and headers only when they clarify a multi-part answer.",
|
|
129
|
+
].join("\n");
|
|
123
130
|
/**
|
|
124
131
|
* Resolve the style block to append to every agent's instructions.
|
|
125
132
|
* Returns `null` when the caller opted out (`styleInstructions: false`).
|
|
@@ -159,9 +166,22 @@ export async function buildAgents(opts) {
|
|
|
159
166
|
const definitions = resolveDefinitions(config);
|
|
160
167
|
const ids = Object.keys(definitions);
|
|
161
168
|
const defaultAgentId = config.defaultAgent ?? ids[0] ?? FALLBACK_AGENT_ID;
|
|
162
|
-
const plugins = buildPluginsMap(context);
|
|
163
|
-
|
|
169
|
+
const plugins = buildPluginsMap(config, context);
|
|
170
|
+
// System-default ambient tools every agent gets out of the box.
|
|
171
|
+
// Currently just `render_data` for inline visualizations; the
|
|
172
|
+
// user can shadow it by including a same-named tool in their own
|
|
173
|
+
// `config.tools` or per-agent `tools`. Order in {@link resolveTools}
|
|
174
|
+
// is `system -> user-ambient -> per-agent`, last write wins.
|
|
175
|
+
const systemTools = {
|
|
176
|
+
render_data: buildRenderDataTool(config),
|
|
177
|
+
};
|
|
178
|
+
const ambientTools = { ...systemTools, ...(config.tools ?? {}) };
|
|
164
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];
|
|
165
185
|
const agents = {};
|
|
166
186
|
for (const [id, def] of Object.entries(definitions)) {
|
|
167
187
|
const tools = await resolveTools(def.tools, plugins, ambientTools);
|
|
@@ -174,6 +194,7 @@ export async function buildAgents(opts) {
|
|
|
174
194
|
model: resolveModel(config, def.model),
|
|
175
195
|
tools,
|
|
176
196
|
...(memory ? { memory } : {}),
|
|
197
|
+
...(inputProcessors.length > 0 ? { inputProcessors } : {}),
|
|
177
198
|
});
|
|
178
199
|
}
|
|
179
200
|
if (!agents[defaultAgentId]) {
|
|
@@ -304,7 +325,7 @@ async function resolveTools(defTools, plugins, ambientTools) {
|
|
|
304
325
|
* Mastra `ctx.writer`, so the UI gets `tool-output` chunks in real
|
|
305
326
|
* time instead of staring at a spinner for the full Genie round-trip.
|
|
306
327
|
*/
|
|
307
|
-
function buildPluginsMap(context) {
|
|
328
|
+
function buildPluginsMap(config, context) {
|
|
308
329
|
const cache = new Map();
|
|
309
330
|
return new Proxy({}, {
|
|
310
331
|
get(_target, propName) {
|
|
@@ -312,7 +333,7 @@ function buildPluginsMap(context) {
|
|
|
312
333
|
return undefined;
|
|
313
334
|
if (cache.has(propName))
|
|
314
335
|
return cache.get(propName) ?? undefined;
|
|
315
|
-
const provider = resolveProvider(context, propName);
|
|
336
|
+
const provider = resolveProvider(config, context, propName);
|
|
316
337
|
cache.set(propName, provider);
|
|
317
338
|
return provider ?? undefined;
|
|
318
339
|
},
|
|
@@ -322,14 +343,17 @@ function buildPluginsMap(context) {
|
|
|
322
343
|
* Pick the right {@link MastraPluginToolkitProvider} for a sibling
|
|
323
344
|
* plugin lookup. Returns the streaming-aware Genie adapter when the
|
|
324
345
|
* caller asks for `genie`; falls back to the generic AppKit
|
|
325
|
-
* `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.
|
|
326
350
|
*/
|
|
327
|
-
function resolveProvider(context, propName) {
|
|
351
|
+
function resolveProvider(config, context, propName) {
|
|
328
352
|
if (propName === "genie") {
|
|
329
353
|
const geniePlugin = pluginUtils.instance(context, genie);
|
|
330
354
|
if (!geniePlugin)
|
|
331
355
|
return null;
|
|
332
|
-
return buildGenieProvider(geniePlugin);
|
|
356
|
+
return buildGenieProvider(geniePlugin, { config });
|
|
333
357
|
}
|
|
334
358
|
const plugin = context?.getPlugins().get(propName);
|
|
335
359
|
return adaptPluginToolkit(plugin);
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chart-rendering primitives.
|
|
3
|
+
*
|
|
4
|
+
* Three surfaces, one shared brain:
|
|
5
|
+
*
|
|
6
|
+
* - {@link buildRenderDataTool}: a Mastra tool the model calls
|
|
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.
|
|
21
|
+
*
|
|
22
|
+
* - {@link runChartPlanner}: the chart-planner Agent + ECOption
|
|
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.
|
|
27
|
+
*
|
|
28
|
+
* The model wires the chart into its reply by emitting the marker
|
|
29
|
+
* `[[chart:<chartId>]]` on its own line in markdown. The chat
|
|
30
|
+
* client splits the assistant text on these markers and drops a
|
|
31
|
+
* `<ChartSlot>` in at the position the model placed it; the slot
|
|
32
|
+
* shows a skeleton until the second `kind: "chart"` event (with
|
|
33
|
+
* the resolved `EChartsOption`) arrives, then swaps in the
|
|
34
|
+
* rendered Echarts visualisation.
|
|
35
|
+
*/
|
|
36
|
+
import type { RequestContext } from "@mastra/core/request-context";
|
|
37
|
+
import { z } from "zod";
|
|
38
|
+
import type { MastraPluginConfig } from "./config.js";
|
|
39
|
+
/**
|
|
40
|
+
* Compact, model-friendly representation of an Echarts spec. The
|
|
41
|
+
* planner agent emits this; {@link planToEchartsOption} expands it
|
|
42
|
+
* into a real `EChartsOption` JSON. Two layers because letting the
|
|
43
|
+
* model fill in a fully-typed `EChartsOption` is brittle (hundreds
|
|
44
|
+
* of optional fields, deep unions, version-dependent shapes). A
|
|
45
|
+
* small "chart plan" schema is much more reliable for a fast model
|
|
46
|
+
* and keeps animation / tooltip / styling defaults consistent
|
|
47
|
+
* across charts.
|
|
48
|
+
*/
|
|
49
|
+
declare const chartPlanSchema: z.ZodObject<{
|
|
50
|
+
chartType: z.ZodEnum<{
|
|
51
|
+
bar: "bar";
|
|
52
|
+
line: "line";
|
|
53
|
+
area: "area";
|
|
54
|
+
scatter: "scatter";
|
|
55
|
+
pie: "pie";
|
|
56
|
+
}>;
|
|
57
|
+
title: z.ZodOptional<z.ZodString>;
|
|
58
|
+
xAxisLabel: z.ZodOptional<z.ZodString>;
|
|
59
|
+
yAxisLabel: z.ZodOptional<z.ZodString>;
|
|
60
|
+
categories: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
61
|
+
series: z.ZodArray<z.ZodObject<{
|
|
62
|
+
name: z.ZodString;
|
|
63
|
+
data: z.ZodArray<z.ZodUnion<readonly [z.ZodNumber, z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>, z.ZodObject<{
|
|
64
|
+
name: z.ZodString;
|
|
65
|
+
value: z.ZodNumber;
|
|
66
|
+
}, z.core.$strip>]>>;
|
|
67
|
+
}, z.core.$strip>>;
|
|
68
|
+
}, z.core.$strip>;
|
|
69
|
+
type ChartPlan = z.infer<typeof chartPlanSchema>;
|
|
70
|
+
/** Inputs to {@link runChartPlanner}. */
|
|
71
|
+
export interface RunChartPlannerOptions {
|
|
72
|
+
config: MastraPluginConfig;
|
|
73
|
+
requestContext?: RequestContext;
|
|
74
|
+
title: string;
|
|
75
|
+
description?: string;
|
|
76
|
+
data: ReadonlyArray<Record<string, unknown>>;
|
|
77
|
+
}
|
|
78
|
+
/** Output of {@link runChartPlanner}: a fully-formed Echarts spec. */
|
|
79
|
+
export interface RunChartPlannerResult {
|
|
80
|
+
option: Record<string, unknown>;
|
|
81
|
+
chartType: ChartPlan["chartType"];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Run the chart planner against the given dataset and return a
|
|
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).
|
|
89
|
+
*/
|
|
90
|
+
export declare function runChartPlanner(opts: RunChartPlannerOptions): Promise<RunChartPlannerResult>;
|
|
91
|
+
/**
|
|
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
|
+
*/
|
|
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. */
|
|
109
|
+
title: string;
|
|
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>]]`. */
|
|
118
|
+
chartId: string;
|
|
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>;
|
|
170
|
+
export {};
|