@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.
@@ -1,131 +1,193 @@
1
1
  /**
2
- * Mastra tool wrappers around the AppKit `genie` plugin's exports.
2
+ * Genie agent for Mastra.
3
3
  *
4
- * One `sendMessage` tool is registered per configured space alias so
5
- * the LLM picks the space by tool selection (the description bakes the
6
- * alias in). `getConversation` is registered once, taking `alias` as a
7
- * parameter.
4
+ * Each configured Genie space exposes a single Mastra tool to the
5
+ * calling agent (`genie` for the `"default"` alias, `genie_<alias>`
6
+ * otherwise). When invoked, the tool runs end-to-end:
8
7
  *
9
- * All Genie payload types are inferred from the public `genie` factory
10
- * (`genie().plugin` constructor `exports()` return type), so any
11
- * upstream change in `@databricks/appkit` flows in automatically.
8
+ * 1. Pulls the per-request {@link WorkspaceClient} off
9
+ * `ctx.requestContext` (stamped by `MastraServer`) and emits a
10
+ * `started` writer event so the host UI can show progress
11
+ * immediately, before any LLM round-trip.
12
+ * 2. Spins up a per-call inner Mastra `Agent` with three tools:
13
+ * - `ask_genie`: drives one `genieEventChat` turn, fetches
14
+ * the matching statement's rows when the turn ran SQL,
15
+ * and forwards every wire event (status, thinking, sql,
16
+ * rows) through `ctx.writer` for streaming UI updates.
17
+ * - `get_space_description`: cheap title / description /
18
+ * warehouse id lookup for grounding.
19
+ * - `get_space_serialized`: full `GenieSpace` JSON for
20
+ * column-level grounding when the description isn't
21
+ * enough.
22
+ * 3. Runs the inner agent with `structuredOutput` (Mastra's
23
+ * two-pass mode + `jsonPromptInjection`) to coerce the
24
+ * agent's final answer into a tagged
25
+ * `[{type:"text"|"data", ...}]` array. The two-pass design
26
+ * avoids Databricks Model Serving's `response_format` +
27
+ * `tools` collision; prompt injection sidesteps the
28
+ * separate `response_format` + streaming collision in the
29
+ * structuring agent.
30
+ * 4. Charts every `data` item in parallel via
31
+ * {@link runChartPlanner}, maps `text` items to the shared
32
+ * {@link GenieSummaryItem} `string` variant, and returns the
33
+ * hydrated {@link GenieAgentResult}.
12
34
  *
13
- * As Genie streams its long-running events (`FETCHING_METADATA`
14
- * `ASKING_AI` `EXECUTING_QUERY` `COMPLETED`, plus SQL text and
15
- * follow-ups in `message_result.attachments`), the tool forwards a
16
- * normalised {@link GenieProgress} discriminated union out through
17
- * `ctx.writer` so the client can render an incremental loading pill.
18
- * Row payloads from `query_result` are intentionally discarded - the
19
- * LLM never sees rows, and charts come from the separate
20
- * `render_data` tool when the model decides one is useful.
35
+ * The inner agent talks to Genie directly via
36
+ * `@dbx-tools/genie` (`genieEventChat`) and the workspace
37
+ * `statementExecution.getStatement` API. AppKit's stock `genie`
38
+ * plugin is honored only for its `spaces` config so existing
39
+ * AppKit-style wiring keeps working without change.
21
40
  */
22
- import { genie } from "@databricks/appkit";
23
- import { createTool } from "@mastra/core/tools";
41
+ import { type ChartEvent } from "@dbx-tools/appkit-mastra-shared";
42
+ import { appkitUtils } from "@dbx-tools/shared";
43
+ import type { RequestContext } from "@mastra/core/request-context";
44
+ import type { MastraTools } from "./agents.js";
24
45
  import type { MastraPluginConfig } from "./config.js";
25
- /** Live AppKit `GeniePlugin` instance. */
26
- export type GeniePluginInstance = InstanceType<ReturnType<typeof genie>["plugin"]>;
27
- /** Full `exports()` shape of the AppKit `genie` plugin. */
28
- export type GenieExports = ReturnType<GeniePluginInstance["exports"]>;
46
+ /** Default alias used when a single unnamed Genie space is wired up. */
47
+ export declare const DEFAULT_GENIE_ALIAS = "default";
48
+ /** Per-space Genie agent configuration. */
49
+ export interface GenieSpaceConfig {
50
+ /** Genie `space_id`. Required; resolves via `client.genie.getSpace`. */
51
+ spaceId: string;
52
+ /**
53
+ * Optional human-readable description appended to the Genie
54
+ * tool's description so the calling LLM has hints about
55
+ * *what data* this space covers (e.g. "orders, returns,
56
+ * fulfillment"). When omitted, only the space's own
57
+ * `description` (fetched on first use) is shown.
58
+ */
59
+ hint?: string;
60
+ }
61
+ /** Map of alias -> space config. Accepts either explicit objects or bare space ids. */
62
+ export type GenieSpacesConfig = Record<string, GenieSpaceConfig | string>;
29
63
  /**
30
- * Stream event yielded by `genie.exports().sendMessage`. Discriminated
31
- * by `type` (`"message_start" | "status" | "message_result" |
32
- * "query_result" | "error" | "history_info"`).
64
+ * Get the chart inventory map for this request, creating it on
65
+ * first access. Subsequent reads return the same map so callers
66
+ * mutate in place. The map is request-scoped (collected with the
67
+ * `RequestContext` at end of request), so there's no per-process
68
+ * leak.
33
69
  */
34
- export type GenieStreamEvent = ReturnType<GenieExports["sendMessage"]> extends AsyncGenerator<infer E> ? E : never;
35
- /** Conversation history returned by `genie.exports().getConversation`. */
36
- export type GenieConversation = Awaited<ReturnType<GenieExports["getConversation"]>>;
70
+ export declare function chartInventoryFromContext(requestContext: RequestContext): Map<string, ChartEvent>;
37
71
  /**
38
- * Normalised progress event surfaced to the UI as a Mastra
39
- * `tool-output` chunk. Loading pill events (`started`, `status`,
40
- * `sql`, `suggested`, `error`) are pure UI metadata and never reach
41
- * the LLM.
42
- *
43
- * The `chart` variant is the wire shape emitted by
44
- * {@link emitChartWithPlanning} (used by both this Genie
45
- * draining loop and the system-level `render_data` tool). All
46
- * fields except `chartId` are optional because two events per
47
- * chartId arrive on the wire: the first carries the rows
48
- * (`title` + `description?` + `data`); the second, on planner
49
- * success, carries just the resolved Echarts spec (`option`).
50
- * The host UI's `<ChartSlot>` merges them by `chartId`.
72
+ * Options for {@link createGenieTool}. Only carries config that
73
+ * doesn't vary per request - the per-request {@link WorkspaceClient},
74
+ * `RequestContext`, writer, and abort signal flow through the
75
+ * tool's `execute(_, ctx)` and are not captured here.
51
76
  */
52
- export type GenieProgress = {
53
- kind: "started";
54
- conversationId: string;
55
- messageId: string;
77
+ export interface CreateGenieToolOptions {
78
+ /** Genie space id this tool targets. */
56
79
  spaceId: string;
57
- } | {
58
- kind: "status";
59
- status: string;
60
- label: string;
61
- } | {
62
- kind: "sql";
63
- sql: string;
64
- title?: string;
65
- description?: string;
66
- statementId?: string;
67
- } | {
68
- kind: "chart";
69
- chartId: string;
70
- title?: string;
71
- description?: string;
72
- data?: Array<Record<string, unknown>>;
73
- option?: Record<string, unknown>;
74
- } | {
75
- kind: "text";
76
- content: string;
77
- } | {
78
- kind: "suggested";
79
- questions: string[];
80
- } | {
81
- kind: "error";
82
- error: string;
83
- };
80
+ /** Plugin config; resolves the LLM and chart planner agent. */
81
+ config: MastraPluginConfig;
82
+ /** Override the registered tool id. Defaults to `"genie"`. */
83
+ toolId?: string;
84
+ /** Override the tool description shown to the calling LLM. */
85
+ toolDescription?: string;
86
+ /**
87
+ * Override the inner agent's max tool-loop steps. Defaults to
88
+ * {@link DEFAULT_MAX_STEPS}.
89
+ */
90
+ maxSteps?: number;
91
+ }
84
92
  /**
85
- * Default tool name for a wired Genie alias. The well-known `default`
86
- * alias collapses to `genie`; everything else gets a `genie_` prefix so
87
- * multiple spaces stay disambiguated when an agent has more than one
88
- * wired. Matches the `genie` / `genie_<alias>` naming used elsewhere in
89
- * dbx-tools AppKit demos.
93
+ * Build the calling agent's Genie tool. The returned Mastra tool
94
+ * runs end-to-end on each invocation:
95
+ *
96
+ * 1. Pull the per-request `WorkspaceClient` off
97
+ * `ctx.requestContext` (stamped by `MastraServer` under
98
+ * {@link MASTRA_USER_KEY}) and emit a `started` writer
99
+ * event so the host UI shows progress immediately.
100
+ * 2. Spin up the inner Mastra agent + three tools, fresh per
101
+ * call so the row cache stays invocation-scoped.
102
+ * 3. Run the agent with `structuredOutput` against
103
+ * {@link agentSummarySchema}. Mastra's two-pass design keeps
104
+ * the inner loop tools-only (no `response_format`), so the
105
+ * Databricks Model Serving `response_format`+`tools`
106
+ * collision never fires.
107
+ * 4. Walk the returned `[text|data][]`, map `text` items to
108
+ * shared `GenieSummaryItem.string`, and chart every `data`
109
+ * item in parallel via {@link runChartPlanner} to a
110
+ * `GenieSummaryItem.visualize`. Items referencing a missing
111
+ * `statementId` are dropped with a warn log; chart-planner
112
+ * failures leave `dataset.chart` unset so the host UI falls
113
+ * back to a table.
114
+ */
115
+ export declare function createGenieTool(opts: CreateGenieToolOptions): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, string, unknown>;
116
+ /**
117
+ * Default tool id for a wired Genie alias. The well-known
118
+ * `default` alias collapses to `genie`; every other alias gets a
119
+ * `genie_` prefix so multi-space registrations stay
120
+ * disambiguated.
90
121
  */
91
122
  export declare function defaultGenieToolName(alias: string): string;
92
123
  /**
93
- * Build one `sendMessage` tool per configured Genie alias plus a single
94
- * `getConversation` tool. Returns a record keyed by tool id, ready to
95
- * spread into an `Agent`'s `tools` map.
124
+ * Normalize the {@link GenieSpacesConfig} record. Bare-string
125
+ * entries (`{ default: "01ef..." }`) get wrapped as
126
+ * `{ spaceId: "01ef..." }`; object entries pass through unchanged.
127
+ * `undefined` and empty-string values are dropped so callers can
128
+ * pass `process.env.X` directly (matches AppKit `genie()`'s
129
+ * defensive treatment of unset env vars).
130
+ */
131
+ export declare function normalizeGenieSpaces(spaces: GenieSpacesConfig | Record<string, string | GenieSpaceConfig | undefined> | undefined): Record<string, GenieSpaceConfig>;
132
+ /**
133
+ * Discover Genie space aliases from every supported source and
134
+ * merge them into a single record. Precedence (highest first):
96
135
  *
97
- * `config` must be the active plugin config; Genie's
98
- * `query_result` events are routed through
99
- * {@link emitChartWithPlanning} which uses it to resolve the
100
- * chart-planner's model.
136
+ * 1. {@link MastraPluginConfig.genieSpaces} on the `mastra(...)`
137
+ * call. Explicit Mastra wiring always wins so users can
138
+ * override AppKit's defaults per-agent.
139
+ * 2. AppKit `genie({ spaces: { ... } })` plugin instance. Lets
140
+ * users keep using the existing AppKit config format
141
+ * (`genie({ spaces: { sales: "...", ops: "..." } })`)
142
+ * without restating the same record on the Mastra plugin.
143
+ * Read off the live plugin instance via a structural cast
144
+ * since `Plugin.config` is TS-protected (not runtime-private).
145
+ * 3. `DATABRICKS_GENIE_SPACE_ID` env var (registered under the
146
+ * well-known `default` alias). Matches the AppKit `genie()`
147
+ * plugin's fallback behavior so a bare `mastra()` + `genie()`
148
+ * pair just works.
149
+ *
150
+ * Aliases collide cleanly: a higher-precedence source's value
151
+ * replaces a lower one's wholesale. Sources that contribute zero
152
+ * aliases (or contribute only `undefined` / empty entries) are
153
+ * silently ignored.
154
+ */
155
+ export declare function resolveGenieSpaces(config: MastraPluginConfig, context: appkitUtils.PluginContextLike | undefined): Record<string, GenieSpaceConfig>;
156
+ /**
157
+ * Build one Mastra tool per configured Genie space. Each tool is
158
+ * a thin {@link createGenieTool} wrapper with the alias-derived
159
+ * id and a hint-flavored description so the calling LLM knows
160
+ * which space covers what data.
161
+ *
162
+ * Returns a record keyed by tool id, ready to spread into an
163
+ * `Agent`'s `tools` map (or surfaced via
164
+ * `plugins.genie?.toolkit()`).
101
165
  */
102
166
  export declare function buildGenieTools(opts: {
103
- aliases: string[];
104
- exports: GenieExports;
167
+ spaces: GenieSpacesConfig | Record<string, GenieSpaceConfig>;
105
168
  config: MastraPluginConfig;
106
- signal?: AbortSignal;
107
- }): Record<string, ReturnType<typeof createTool>>;
169
+ }): MastraTools;
108
170
  /**
109
- * Toolkit provider built from a live AppKit `GeniePlugin` instance.
110
- * Returned by {@link buildGenieProvider} so that
111
- * `plugins.genie?.toolkit()` inside an agent's `tools(plugins)` callback
112
- * resolves to the streaming-aware {@link buildGenieTools} record instead
113
- * of the AppKit default (which does one blocking call per tool with no
114
- * mid-flight events).
115
- *
116
- * The returned `toolkit()` reads alias names off the plugin's
117
- * `getAgentTools()` registry (each entry is `${alias}.sendMessage` or
118
- * `${alias}.getConversation`), then mints one `sendMessage` tool per
119
- * alias plus a shared `getConversation`. `sendMessage` / `getConversation`
120
- * are bound back to the plugin instance so they keep their `this`
121
- * (they are class methods, not free functions).
122
- *
123
- * `_opts` is accepted but unused for now - the streaming tools are an
124
- * all-or-nothing bundle. Wire `only` / `except` / `prefix` / `rename`
125
- * later if a caller needs them.
171
+ * Plugin-toolkit adapter so the `plugins.genie?.toolkit()` lookup
172
+ * inside an agent's `tools(plugins)` callback returns the
173
+ * Genie agent-backed tools instead of throwing on missing plugin.
174
+ * Mirrors AppKit's `PluginToolkitProvider` shape.
126
175
  */
127
- export declare function buildGenieProvider(plugin: GeniePluginInstance, opts: {
176
+ export declare function buildGenieToolkitProvider(opts: {
177
+ spaces: GenieSpacesConfig | Record<string, GenieSpaceConfig>;
128
178
  config: MastraPluginConfig;
129
179
  }): {
130
- toolkit(opts?: unknown): Record<string, ReturnType<typeof createTool>>;
180
+ toolkit(opts?: unknown): MastraTools;
131
181
  };
182
+ /**
183
+ * Returns `true` when at least one Genie space is reachable
184
+ * through {@link resolveGenieSpaces} - either via
185
+ * {@link MastraPluginConfig.genieSpaces}, the AppKit `genie()`
186
+ * plugin instance, or the `DATABRICKS_GENIE_SPACE_ID` env var.
187
+ *
188
+ * Cheap to call from `resolveProvider` to short-circuit `genie`
189
+ * lookups when nothing is wired, so the `plugins.genie` lookup
190
+ * still resolves to `undefined` (matching AppKit's
191
+ * absent-plugin semantics) when neither source is configured.
192
+ */
193
+ export declare function hasAnyGenieSpaces(config: MastraPluginConfig, context: appkitUtils.PluginContextLike | undefined): boolean;