@dbx-tools/appkit-mastra 0.1.13 → 0.1.19

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 CHANGED
@@ -190,65 +190,72 @@ that implements the standard `getAgentTools()` + `executeAgentTool()` +
190
190
  `executeAgentTool`, so OBO auth (`asUser`) and telemetry spans stay
191
191
  intact.
192
192
 
193
- `plugins.genie` is special-cased: it swaps the generic AppKit toolkit
194
- (which only emits a single final result chunk per call) for a
195
- streaming-aware tools record built on top of the plugin's
196
- `exports().sendMessage` AsyncGenerator. Each Genie wire event
197
- (`FETCHING_METADATA`, `ASKING_AI`, `EXECUTING_QUERY`, attached SQL,
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):
193
+ `plugins.genie` is special-cased: it talks to Genie directly via
194
+ `@dbx-tools/genie` (`genieEventChat`) rather than calling AppKit's
195
+ stock `genie` toolkit. Each invocation spins up a per-call inner
196
+ Mastra `Agent` with three tools (`ask_genie`,
197
+ `get_space_description`, `get_space_serialized`), runs it with
198
+ `structuredOutput`, and produces a hydrated
199
+ {@link GenieAgentResult}. AppKit's `genie()` plugin is honored
200
+ only for its `spaces` config (and the matching
201
+ `app.yaml`-declared resources). Tool ids are stable: `genie` for
202
+ the default alias, `genie_<alias>` for additional aliases.
203
+
204
+ Genie tool-result shape (LLM-bound) is the
205
+ [`GenieAgentResult`](../appkit-mastra-shared/src/genie.ts) type:
207
206
 
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
- }
207
+ ```ts
208
+ type GenieAgentResult = {
209
+ spaceId: string;
210
+ conversationId?: string; // thread back to continue the same Genie thread
211
+ summary: Array< // ordered prose + visualize slots
212
+ | { type: "string"; text: string }
213
+ | {
214
+ type: "visualize";
215
+ statementId: string;
216
+ title?: string;
217
+ description?: string;
218
+ dataset: {
219
+ data: { columns: string[]; rows: Row[]; rowCount: number };
220
+ chart?: { chartId: string; chartType: "bar" | "line" | "area" | "scatter" | "pie" };
221
+ };
222
+ }
223
+ >;
224
+ error?: string;
225
+ };
223
226
  ```
224
227
 
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.
228
+ The `summary` is the user-facing renderable: a mixed sequence of
229
+ prose paragraphs (`type: "string"`) and `visualize` slots that
230
+ mark where a chart should appear in the model's final answer.
231
+ Visualize slots embed the dataset (rows + columns) plus a slim
232
+ `chart` reference (just `chartId` + `chartType`); the resolved
233
+ Echarts spec rides a separate `type: "chart"` writer event so the
234
+ LLM never holds it in context. The host UI joins the dataset and
235
+ the writer event by `chartId` and renders inline at the matching
236
+ `[[chart:<chartId>]]` marker.
237
+
238
+ Genie writer event flow:
239
+
240
+ - The writer emits the flat
241
+ [`GenieWriterEvent`](../appkit-mastra-shared/src/genie.ts)
242
+ union for the live loading pill - the wire-derived
243
+ `GenieChatEvent` (status / thinking / sql / rows / suggested)
244
+ plus the Mastra-only lifecycle events (`started`,
245
+ `ask_genie_done`, `summary`, `chart`, `error`).
246
+ - The writer emits one `type: "chart"` event per executed SQL
247
+ statement carrying `{chartId, title, description?, data,
248
+ option, statementId, messageId}`. The chat client's
249
+ `<ChartSlot>` renders inline at the matching
250
+ `[[chart:<chartId>]]` marker. This is the exact same wire
251
+ format as the `render_data` tool, so Genie and hand-built
252
+ charts are indistinguishable on the client.
253
+ - After a hard reload, `synthesizeToolEventsFromHistory`
254
+ reconstructs lifecycle events from the persisted summary
255
+ (`error` events via `genieResultToWriterEvents`); live-only
256
+ events (rows, SQL pill, chart specs) don't replay because the
257
+ resolved Echarts spec is held off-band on the per-request
258
+ `RequestContext`.
252
259
 
253
260
  ### `render_data` (system-default ambient tool)
254
261
 
@@ -263,41 +270,36 @@ upstream. Input is `{ title, description?, data: Row[] }` where
263
270
  `data` is an array of objects keyed by column name (a SQL row
264
271
  set, an API response, a hand-built array, etc.).
265
272
 
266
- #### How it works (shared pipeline with Genie)
273
+ #### How it works
267
274
 
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:
275
+ `render_data` is a thin wrapper around `runChartPlanner`, a
276
+ pure async function that takes a dataset and returns a fully-
277
+ resolved Echarts `EChartsOption`. No id-then-promise dance, no
278
+ background work: the planner runs inline, and the tool emits a
279
+ single chart event with everything attached.
271
280
 
272
281
  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.
282
+ 2. `await runChartPlanner({ title, description?, data })`. The
283
+ planner is its own Mastra `Agent` (`modelForTier(ModelTier.Fast)`)
284
+ whose `structuredOutput` schema is a compact "chart plan"
285
+ (chart type + axis labels + categories + series); the helper
286
+ expands the plan into an `EChartsOption` JSON.
287
+ 3. Push one `{ type: "chart", chartId, title, description?, data,
288
+ option }` event to `ctx.writer`. The chat client's
289
+ `<ChartSlot>` mounts straight to the rendered Echarts
290
+ visualisation - no skeleton frame, because the option arrives
291
+ in the same event as the dataset.
292
+ 4. If the planner throws, the tool emits a `type: "error"` writer
293
+ event instead. The slot transitions to a "couldn't render
294
+ chart" fallback frame.
295
+
296
+ Planner latency lands under the tool's trace span (it's
297
+ `await`ed directly), and the LLM-bound payload is just
298
+ `{ chartId }` so the model's context stays flat regardless of
299
+ dataset size. The Genie agent reuses `runChartPlanner` the
300
+ same way to embed charts in its structured summary, so the
301
+ backbone is shared without coupling the two paths to a
302
+ fire-and-forget writer contract.
301
303
 
302
304
  #### Inline placement contract
303
305
 
@@ -546,10 +548,10 @@ curl -s http://localhost:8000/api/mastra/models | jq
546
548
  Same payload from a sibling plugin or script (no HTTP round-trip):
547
549
 
548
550
  ```ts
549
- import { pluginUtils } from "@dbx-tools/appkit-shared";
551
+ import { appkitUtils } from "@dbx-tools/shared";
550
552
  import { mastra } from "@dbx-tools/appkit-mastra";
551
553
 
552
- const m = pluginUtils.require(this.context, mastra).exports();
554
+ const m = appkitUtils.require(this.context, mastra).exports();
553
555
  const endpoints = await m.asUser(req).listModels(); // user-scoped
554
556
  m.clearModelsCache(); // force the next call to re-fetch
555
557
  ```
@@ -658,10 +660,10 @@ Other plugins / route handlers can introspect the registry via the
658
660
  `exports()` surface, modeled on AppKit's:
659
661
 
660
662
  ```ts
661
- import { pluginUtils } from "@dbx-tools/appkit-shared";
663
+ import { appkitUtils } from "@dbx-tools/shared";
662
664
  import { mastra } from "@dbx-tools/appkit-mastra";
663
665
 
664
- const m = pluginUtils.require(this.context, mastra).exports();
666
+ const m = appkitUtils.require(this.context, mastra).exports();
665
667
  m.list(); // ["analyst", "helper"]
666
668
  m.get("analyst"); // Agent | null
667
669
  m.getDefault(); // Agent | null
@@ -715,20 +717,25 @@ function Chat() {
715
717
  mount, so a custom `mastra({ name: "myMastra" })` rewrites every path):
716
718
 
717
719
 
718
- | Field | Example | Description |
719
- | ------------------ | ----------------------------------- | -------------------------------------------------------- |
720
- | `basePath` | `"/api/mastra"` | Plugin mount path. |
721
- | `chatPath` | `"/api/mastra/route/chat"` | Default-agent chat URL. Use `chatUrl(config)` to get it. |
722
- | `chatPathTemplate` | `"/api/mastra/route/chat/:agentId"` | OpenAPI-style template for tools / docs. |
723
- | `modelsPath` | `"/api/mastra/models"` | `GET` cached endpoint catalogue. |
724
- | `defaultAgent` | `"analyst"` | Agent id `chatRoute` binds to when none is supplied. |
725
- | `agents` | `["analyst", "helper"]` | Every registered agent id in order. |
720
+ | Field | Example | Description |
721
+ | --------------------- | -------------------------------------- | -------------------------------------------------------------------- |
722
+ | `basePath` | `"/api/mastra"` | Plugin mount path. |
723
+ | `chatPath` | `"/api/mastra/route/chat"` | Default-agent chat URL. Use `chatUrl(config)` to get it. |
724
+ | `chatPathTemplate` | `"/api/mastra/route/chat/:agentId"` | OpenAPI-style template for tools / docs. |
725
+ | `modelsPath` | `"/api/mastra/models"` | `GET` cached endpoint catalogue. |
726
+ | `historyPath` | `"/api/mastra/route/history"` | Default-agent thread history. Use `historyUrl(config, opts)`. |
727
+ | `historyPathTemplate` | `"/api/mastra/route/history/:agentId"` | OpenAPI-style template for tools / docs. |
728
+ | `defaultAgent` | `"analyst"` | Agent id `chatRoute` binds to when none is supplied. |
729
+ | `agents` | `["analyst", "helper"]` | Every registered agent id in order. |
726
730
 
727
731
 
728
732
  `chatUrl(config, agentId?)` returns `config.chatPath` for the default
729
733
  agent (the registered `chatRoute` mount that omits `:agentId`), and
730
- `${config.chatPath}/${encodeURIComponent(agentId)}` otherwise. Pure
731
- function: no React, no hooks, safe in service workers and SSR.
734
+ `${config.chatPath}/${encodeURIComponent(agentId)}` otherwise. The
735
+ sibling `historyUrl(config, { agentId?, page?, perPage? })` mirrors
736
+ that pattern for the `/history` endpoint and appends pagination
737
+ query parameters when provided. Both are pure functions: no React,
738
+ no hooks, safe in service workers and SSR.
732
739
 
733
740
  ## License
734
741
 
@@ -12,7 +12,7 @@
12
12
  * built-in analyst so the bare `mastra()` call still mounts a working
13
13
  * `chatRoute` agent for demos.
14
14
  */
15
- import { logUtils, pluginUtils } from "@dbx-tools/appkit-shared";
15
+ import { appkitUtils, logUtils } from "@dbx-tools/shared";
16
16
  import { Agent } from "@mastra/core/agent";
17
17
  import type { AgentConfig, ToolsInput } from "@mastra/core/agent";
18
18
  import type { Tool } from "@mastra/core/tools";
@@ -300,7 +300,7 @@ export declare const DEFAULT_STYLE_INSTRUCTIONS: string;
300
300
  */
301
301
  export declare function buildAgents(opts: {
302
302
  config: MastraPluginConfig;
303
- context: pluginUtils.PluginContextLike | undefined;
303
+ context: appkitUtils.PluginContextLike | undefined;
304
304
  memoryBuilder?: MemoryBuilder;
305
305
  log: logUtils.Logger;
306
306
  }): Promise<BuiltAgents>;
@@ -12,13 +12,12 @@
12
12
  * built-in analyst so the bare `mastra()` call still mounts a working
13
13
  * `chatRoute` agent for demos.
14
14
  */
15
- import { genie } from "@databricks/appkit";
16
- import { logUtils, pluginUtils, stringUtils } from "@dbx-tools/appkit-shared";
15
+ import { appkitUtils, logUtils, stringUtils } from "@dbx-tools/shared";
17
16
  import { Agent } from "@mastra/core/agent";
18
17
  import { createTool } from "@mastra/core/tools";
19
18
  import { buildRenderDataTool } from "./chart.js";
20
- import { buildGenieProvider } from "./genie.js";
21
- import { buildModel } from "./model.js";
19
+ import { buildGenieToolkitProvider, resolveGenieSpaces } from "./genie.js";
20
+ import { buildModel, FALLBACK_MODEL_IDS } from "./model.js";
22
21
  import { stripStaleChartsProcessor } from "./processors/strip-stale-charts.js";
23
22
  /** Re-export of Mastra's native `createTool` for full-feature access. */
24
23
  export { createTool } from "@mastra/core/tools";
@@ -196,13 +195,52 @@ export async function buildAgents(opts) {
196
195
  ...(memory ? { memory } : {}),
197
196
  ...(inputProcessors.length > 0 ? { inputProcessors } : {}),
198
197
  });
198
+ // Surface the effective default model per agent so operators can
199
+ // see at a glance which endpoint each agent points at without
200
+ // having to fire a request and inspect a trace. The value is the
201
+ // *static* default; per-request overrides (header / query /
202
+ // body) and the workspace-catalogue fuzzy match still apply at
203
+ // call time.
204
+ log.info("agent registered", {
205
+ id,
206
+ name: def.name ?? id,
207
+ defaultModel: describeAgentDefaultModel(config, def),
208
+ tools: Object.keys(tools),
209
+ });
199
210
  }
200
211
  if (!agents[defaultAgentId]) {
201
212
  throw new Error(`mastra: defaultAgent "${defaultAgentId}" not found in registered agents (${ids.join(", ") || "none"})`);
202
213
  }
203
- log.info("agents registered", { ids, defaultAgentId });
214
+ log.info("agents ready", { ids, defaultAgentId });
204
215
  return { agents, defaultAgentId };
205
216
  }
217
+ /**
218
+ * Best-effort description of the *static* default model an agent will
219
+ * resolve to at call time. Walks the same precedence ladder as
220
+ * {@link resolveModel} / {@link buildModel}:
221
+ *
222
+ * 1. Per-agent `def.model` (string sugar -> the literal id;
223
+ * function / `DynamicArgument` -> `"<dynamic>"` because the
224
+ * resolver decides at call time).
225
+ * 2. Plugin-level `config.defaultModel` (same rules).
226
+ * 3. `DATABRICKS_SERVING_ENDPOINT_NAME` env var.
227
+ * 4. First entry of `config.defaultModelFallbacks ?? FALLBACK_MODEL_IDS`.
228
+ *
229
+ * Used for the startup `agent registered` log so operators can see
230
+ * which endpoint each agent points at by default. Per-request
231
+ * overrides (`X-Mastra-Model` etc.) and the workspace-catalogue
232
+ * fuzzy match are still applied at runtime.
233
+ */
234
+ function describeAgentDefaultModel(config, def) {
235
+ const effective = def.model ?? config.defaultModel;
236
+ if (typeof effective === "string")
237
+ return effective;
238
+ if (effective !== undefined)
239
+ return "<dynamic>";
240
+ return (process.env.DATABRICKS_SERVING_ENDPOINT_NAME ??
241
+ config.defaultModelFallbacks?.[0] ??
242
+ FALLBACK_MODEL_IDS[0]);
243
+ }
206
244
  /**
207
245
  * Normalize `config.agents` into a `Record<id, definition>`. Accepts
208
246
  * any of the three shapes documented on
@@ -341,19 +379,32 @@ function buildPluginsMap(config, context) {
341
379
  }
342
380
  /**
343
381
  * Pick the right {@link MastraPluginToolkitProvider} for a sibling
344
- * plugin lookup. Returns the streaming-aware Genie adapter when the
345
- * caller asks for `genie`; falls back to the generic AppKit
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.
382
+ * plugin lookup. Returns the Genie agent-backed adapter when
383
+ * the caller asks for `genie` AND at least one space is reachable
384
+ * via {@link resolveGenieSpaces} (the explicit
385
+ * `config.genieSpaces`, the registered AppKit `genie()` plugin's
386
+ * `spaces` config, or the `DATABRICKS_GENIE_SPACE_ID` env var).
387
+ * Falls back to the generic AppKit `ToolProvider` adapter for
388
+ * every other plugin name. `config` is threaded through so the
389
+ * Genie agent inherits the same model resolver / fallback
390
+ * ladder the calling agents use.
391
+ *
392
+ * The Genie agent talks to Genie directly via `@dbx-tools/genie`
393
+ * (`genieEventChat`) and the workspace
394
+ * `statementExecution.getStatement` API. AppKit's stock `genie`
395
+ * plugin is honored only for its resource manifest and `spaces`
396
+ * config so existing `app.yaml` configs and `genie({ spaces })`
397
+ * wiring keep working without change.
350
398
  */
351
399
  function resolveProvider(config, context, propName) {
352
400
  if (propName === "genie") {
353
- const geniePlugin = pluginUtils.instance(context, genie);
354
- if (!geniePlugin)
401
+ const spaces = resolveGenieSpaces(config, context);
402
+ if (Object.keys(spaces).length === 0)
355
403
  return null;
356
- return buildGenieProvider(geniePlugin, { config });
404
+ return buildGenieToolkitProvider({
405
+ spaces,
406
+ config,
407
+ });
357
408
  }
358
409
  const plugin = context?.getPlugins().get(propName);
359
410
  return adaptPluginToolkit(plugin);
@@ -1,37 +1,30 @@
1
1
  /**
2
2
  * Chart-rendering primitives.
3
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.
4
+ * Two surfaces, one shared brain:
21
5
  *
22
6
  * - {@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.
7
+ * expansion as a plain async function. Takes a dataset and
8
+ * returns a promise that resolves to a full `EChartsOption`
9
+ * JSON plus the chosen `chartType`. No background work, no
10
+ * writer side-effects, no id allocation - callers stitch the
11
+ * result into whatever shape their producer needs.
12
+ *
13
+ * - {@link buildRenderDataTool}: a Mastra tool the model calls
14
+ * ("here is a dataset, render it as a chart"). Mints a short
15
+ * `chartId`, `await`s {@link runChartPlanner} so the planner
16
+ * latency is attributed to this tool's trace span, emits one
17
+ * `type: "chart"` writer event carrying the dataset + resolved
18
+ * `option`, and returns `{ chartId }` to the model. The
19
+ * LLM-bound output stays flat regardless of dataset size.
27
20
  *
28
21
  * The model wires the chart into its reply by emitting the marker
29
22
  * `[[chart:<chartId>]]` on its own line in markdown. The chat
30
23
  * 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.
24
+ * `<ChartSlot>` in at the position the model placed it. The slot
25
+ * resolves directly to the rendered Echarts visualisation - no
26
+ * skeleton state, because the option is in the same event as the
27
+ * dataset.
35
28
  */
36
29
  import type { RequestContext } from "@mastra/core/request-context";
37
30
  import { z } from "zod";
@@ -60,10 +53,10 @@ declare const chartPlanSchema: z.ZodObject<{
60
53
  categories: z.ZodOptional<z.ZodArray<z.ZodString>>;
61
54
  series: z.ZodArray<z.ZodObject<{
62
55
  name: z.ZodString;
63
- data: z.ZodArray<z.ZodUnion<readonly [z.ZodNumber, z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>, z.ZodObject<{
56
+ data: z.ZodArray<z.ZodCatch<z.ZodPreprocess<z.ZodUnion<readonly [z.ZodNumber, z.ZodNull, z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>, z.ZodObject<{
64
57
  name: z.ZodString;
65
58
  value: z.ZodNumber;
66
- }, z.core.$strip>]>>;
59
+ }, z.core.$strip>]>>>>;
67
60
  }, z.core.$strip>>;
68
61
  }, z.core.$strip>;
69
62
  type ChartPlan = z.infer<typeof chartPlanSchema>;
@@ -74,6 +67,12 @@ export interface RunChartPlannerOptions {
74
67
  title: string;
75
68
  description?: string;
76
69
  data: ReadonlyArray<Record<string, unknown>>;
70
+ /**
71
+ * Cooperative cancellation. Forwarded to the planner agent's
72
+ * `generate({ abortSignal })` call so concurrent renders can be
73
+ * aborted as a group when the parent Genie agent's signal fires.
74
+ */
75
+ signal?: AbortSignal;
77
76
  }
78
77
  /** Output of {@link runChartPlanner}: a fully-formed Echarts spec. */
79
78
  export interface RunChartPlannerResult {
@@ -82,89 +81,24 @@ export interface RunChartPlannerResult {
82
81
  }
83
82
  /**
84
83
  * 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).
84
+ * full Echarts `EChartsOption` JSON. Pure async function: no
85
+ * writer side-effects, no id minting, no background work.
86
+ * Producers (the `render_data` tool, the Genie agent,
87
+ * anything else that needs a chart) await this and stitch the
88
+ * result into whatever shape their wire contract needs.
89
89
  */
90
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
91
  /**
156
92
  * Build the `render_data` tool bound to the given plugin config.
157
93
  *
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.
94
+ * The tool awaits {@link runChartPlanner} so the planner's
95
+ * latency is attributed to this tool's trace span, then emits
96
+ * one `type: "chart"` writer event carrying the dataset and the
97
+ * resolved `EChartsOption`. The LLM-bound output is just
98
+ * `{ chartId }` so the model's context stays flat regardless of
99
+ * dataset size. Planner failures are caught and surfaced as a
100
+ * `type: "error"` writer event so the slot can fall back to
101
+ * "couldn't render chart" without taking the parent agent down.
168
102
  */
169
103
  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
104
  export {};