@dbx-tools/appkit-mastra 0.1.12 → 0.1.18

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.
@@ -4,9 +4,52 @@
4
4
  * Kept in a leaf module so `plugin.ts`, `server.ts`, `model.ts`, and
5
5
  * `memory.ts` can import them without creating a cycle.
6
6
  */
7
+ import { MASTRA_RESOURCE_ID_KEY, MASTRA_THREAD_ID_KEY, } from "@mastra/core/request-context";
7
8
  /**
8
9
  * `RequestContext` key under which {@link MastraServer} stores the
9
10
  * resolved AppKit user. `model.ts` reads it to mint user-scoped
10
11
  * Databricks tokens.
11
12
  */
12
13
  export const MASTRA_USER_KEY = "mastra__user";
14
+ /**
15
+ * `RequestContext` keys for AppKit user metadata stamped by
16
+ * {@link MastraServer}. Surfaced as trace metadata via
17
+ * {@link TRACE_REQUEST_CONTEXT_KEYS} so traces are filterable by who
18
+ * issued the request without leaking the full user object.
19
+ */
20
+ export const MASTRA_USER_NAME_KEY = "mastra__userName";
21
+ export const MASTRA_USER_EMAIL_KEY = "mastra__userEmail";
22
+ /**
23
+ * `RequestContext` key for the per-HTTP-request id stamped by
24
+ * {@link MastraServer}. Reads `X-Request-Id` from the incoming
25
+ * headers when present (so an upstream load balancer / API gateway
26
+ * can keep its trace correlation), falls back to a freshly minted
27
+ * UUID. Echoed back on the response and surfaced on every span via
28
+ * {@link TRACE_REQUEST_CONTEXT_KEYS} so logs and traces share a
29
+ * join key.
30
+ */
31
+ export const MASTRA_REQUEST_ID_KEY = "mastra__requestId";
32
+ /**
33
+ * Canonical list of `RequestContext` keys we want Mastra to extract
34
+ * as metadata on every observability span (agent runs, model calls,
35
+ * tool invocations, workflow steps).
36
+ *
37
+ * Mirrors {@link https://mastra.ai/docs/observability/tracing/overview#automatic-metadata-from-requestcontext}:
38
+ * passed verbatim into `Observability.configs[*].requestContextKeys`,
39
+ * so any key listed here is read from `RequestContext` at trace
40
+ * start and attached as scalar span metadata. Keep the set to plain
41
+ * scalars - never include {@link MASTRA_USER_KEY} (it carries the
42
+ * full AppKit execution context with a `WorkspaceClient` reference).
43
+ *
44
+ * Order is purely cosmetic; Mastra de-dupes internally.
45
+ */
46
+ export const TRACE_REQUEST_CONTEXT_KEYS = [
47
+ MASTRA_RESOURCE_ID_KEY,
48
+ MASTRA_THREAD_ID_KEY,
49
+ MASTRA_REQUEST_ID_KEY,
50
+ MASTRA_USER_NAME_KEY,
51
+ MASTRA_USER_EMAIL_KEY,
52
+ // Model override key is owned by `serving.ts`; spelled inline here
53
+ // so this module stays leaf-level (no cycles with `serving.ts`).
54
+ "mastra__model_override",
55
+ ];
@@ -1,131 +1,194 @@
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 legacy AppKit `genie` plugin (`@databricks/appkit`'s `genie`)
36
+ * is no longer used at runtime. The inner agent talks to Genie
37
+ * directly via `@dbx-tools/genie` (`genieEventChat`) and the
38
+ * workspace `statementExecution.getStatement` API. The plugin's
39
+ * `spaces` config is still honored so existing AppKit-style wiring
40
+ * keeps working without change.
21
41
  */
22
- import { genie } from "@databricks/appkit";
23
- import { createTool } from "@mastra/core/tools";
42
+ import { type ChartEvent } from "@dbx-tools/appkit-mastra-shared";
43
+ import { appkitUtils } from "@dbx-tools/shared";
44
+ import type { RequestContext } from "@mastra/core/request-context";
45
+ import type { MastraTools } from "./agents.js";
24
46
  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"]>;
47
+ /** Default alias used when a single unnamed Genie space is wired up. */
48
+ export declare const DEFAULT_GENIE_ALIAS = "default";
49
+ /** Per-space Genie agent configuration. */
50
+ export interface GenieSpaceConfig {
51
+ /** Genie `space_id`. Required; resolves via `client.genie.getSpace`. */
52
+ spaceId: string;
53
+ /**
54
+ * Optional human-readable description appended to the Genie
55
+ * tool's description so the calling LLM has hints about
56
+ * *what data* this space covers (e.g. "orders, returns,
57
+ * fulfillment"). When omitted, only the space's own
58
+ * `description` (fetched on first use) is shown.
59
+ */
60
+ hint?: string;
61
+ }
62
+ /** Map of alias -> space config. Accepts either explicit objects or bare space ids. */
63
+ export type GenieSpacesConfig = Record<string, GenieSpaceConfig | string>;
29
64
  /**
30
- * Stream event yielded by `genie.exports().sendMessage`. Discriminated
31
- * by `type` (`"message_start" | "status" | "message_result" |
32
- * "query_result" | "error" | "history_info"`).
65
+ * Get the chart inventory map for this request, creating it on
66
+ * first access. Subsequent reads return the same map so callers
67
+ * mutate in place. The map is request-scoped (collected with the
68
+ * `RequestContext` at end of request), so there's no per-process
69
+ * leak.
33
70
  */
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"]>>;
71
+ export declare function chartInventoryFromContext(requestContext: RequestContext): Map<string, ChartEvent>;
37
72
  /**
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`.
73
+ * Options for {@link createGenieTool}. Only carries config that
74
+ * doesn't vary per request - the per-request {@link WorkspaceClient},
75
+ * `RequestContext`, writer, and abort signal flow through the
76
+ * tool's `execute(_, ctx)` and are not captured here.
51
77
  */
52
- export type GenieProgress = {
53
- kind: "started";
54
- conversationId: string;
55
- messageId: string;
78
+ export interface CreateGenieToolOptions {
79
+ /** Genie space id this tool targets. */
56
80
  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
- };
81
+ /** Plugin config; resolves the LLM and chart planner agent. */
82
+ config: MastraPluginConfig;
83
+ /** Override the registered tool id. Defaults to `"genie"`. */
84
+ toolId?: string;
85
+ /** Override the tool description shown to the calling LLM. */
86
+ toolDescription?: string;
87
+ /**
88
+ * Override the inner agent's max tool-loop steps. Defaults to
89
+ * {@link DEFAULT_MAX_STEPS}.
90
+ */
91
+ maxSteps?: number;
92
+ }
84
93
  /**
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.
94
+ * Build the calling agent's Genie tool. The returned Mastra tool
95
+ * runs end-to-end on each invocation:
96
+ *
97
+ * 1. Pull the per-request `WorkspaceClient` off
98
+ * `ctx.requestContext` (stamped by `MastraServer` under
99
+ * {@link MASTRA_USER_KEY}) and emit a `started` writer
100
+ * event so the host UI shows progress immediately.
101
+ * 2. Spin up the inner Mastra agent + three tools, fresh per
102
+ * call so the row cache stays invocation-scoped.
103
+ * 3. Run the agent with `structuredOutput` against
104
+ * {@link agentSummarySchema}. Mastra's two-pass design keeps
105
+ * the inner loop tools-only (no `response_format`), so the
106
+ * Databricks Model Serving `response_format`+`tools`
107
+ * collision never fires.
108
+ * 4. Walk the returned `[text|data][]`, map `text` items to
109
+ * shared `GenieSummaryItem.string`, and chart every `data`
110
+ * item in parallel via {@link runChartPlanner} to a
111
+ * `GenieSummaryItem.visualize`. Items referencing a missing
112
+ * `statementId` are dropped with a warn log; chart-planner
113
+ * failures leave `dataset.chart` unset so the host UI falls
114
+ * back to a table.
115
+ */
116
+ 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>;
117
+ /**
118
+ * Default tool id for a wired Genie alias. The well-known
119
+ * `default` alias collapses to `genie`; every other alias gets a
120
+ * `genie_` prefix so multi-space registrations stay
121
+ * disambiguated.
90
122
  */
91
123
  export declare function defaultGenieToolName(alias: string): string;
92
124
  /**
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.
125
+ * Normalize the {@link GenieSpacesConfig} record. Bare-string
126
+ * entries (`{ default: "01ef..." }`) get wrapped as
127
+ * `{ spaceId: "01ef..." }`; object entries pass through unchanged.
128
+ * `undefined` and empty-string values are dropped so callers can
129
+ * pass `process.env.X` directly (matches AppKit `genie()`'s
130
+ * defensive treatment of unset env vars).
131
+ */
132
+ export declare function normalizeGenieSpaces(spaces: GenieSpacesConfig | Record<string, string | GenieSpaceConfig | undefined> | undefined): Record<string, GenieSpaceConfig>;
133
+ /**
134
+ * Discover Genie space aliases from every supported source and
135
+ * merge them into a single record. Precedence (highest first):
96
136
  *
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.
137
+ * 1. {@link MastraPluginConfig.genieSpaces} on the `mastra(...)`
138
+ * call. Explicit Mastra wiring always wins so users can
139
+ * override AppKit's defaults per-agent.
140
+ * 2. AppKit `genie({ spaces: { ... } })` plugin instance. Lets
141
+ * users keep using the existing AppKit config format
142
+ * (`genie({ spaces: { sales: "...", ops: "..." } })`)
143
+ * without restating the same record on the Mastra plugin.
144
+ * Read off the live plugin instance via a structural cast
145
+ * since `Plugin.config` is TS-protected (not runtime-private).
146
+ * 3. `DATABRICKS_GENIE_SPACE_ID` env var (registered under the
147
+ * well-known `default` alias). Matches the AppKit `genie()`
148
+ * plugin's fallback behavior so a bare `mastra()` + `genie()`
149
+ * pair just works.
150
+ *
151
+ * Aliases collide cleanly: a higher-precedence source's value
152
+ * replaces a lower one's wholesale. Sources that contribute zero
153
+ * aliases (or contribute only `undefined` / empty entries) are
154
+ * silently ignored.
155
+ */
156
+ export declare function resolveGenieSpaces(config: MastraPluginConfig, context: appkitUtils.PluginContextLike | undefined): Record<string, GenieSpaceConfig>;
157
+ /**
158
+ * Build one Mastra tool per configured Genie space. Each tool is
159
+ * a thin {@link createGenieTool} wrapper with the alias-derived
160
+ * id and a hint-flavored description so the calling LLM knows
161
+ * which space covers what data.
162
+ *
163
+ * Returns a record keyed by tool id, ready to spread into an
164
+ * `Agent`'s `tools` map (or surfaced via
165
+ * `plugins.genie?.toolkit()`).
101
166
  */
102
167
  export declare function buildGenieTools(opts: {
103
- aliases: string[];
104
- exports: GenieExports;
168
+ spaces: GenieSpacesConfig | Record<string, GenieSpaceConfig>;
105
169
  config: MastraPluginConfig;
106
- signal?: AbortSignal;
107
- }): Record<string, ReturnType<typeof createTool>>;
170
+ }): MastraTools;
108
171
  /**
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.
172
+ * Plugin-toolkit adapter so the `plugins.genie?.toolkit()` lookup
173
+ * inside an agent's `tools(plugins)` callback returns the
174
+ * Genie agent-backed tools instead of throwing on missing plugin.
175
+ * Mirrors AppKit's `PluginToolkitProvider` shape.
126
176
  */
127
- export declare function buildGenieProvider(plugin: GeniePluginInstance, opts: {
177
+ export declare function buildGenieToolkitProvider(opts: {
178
+ spaces: GenieSpacesConfig | Record<string, GenieSpaceConfig>;
128
179
  config: MastraPluginConfig;
129
180
  }): {
130
- toolkit(opts?: unknown): Record<string, ReturnType<typeof createTool>>;
181
+ toolkit(opts?: unknown): MastraTools;
131
182
  };
183
+ /**
184
+ * Returns `true` when at least one Genie space is reachable
185
+ * through {@link resolveGenieSpaces} - either via
186
+ * {@link MastraPluginConfig.genieSpaces}, the AppKit `genie()`
187
+ * plugin instance, or the `DATABRICKS_GENIE_SPACE_ID` env var.
188
+ *
189
+ * Cheap to call from `resolveProvider` to short-circuit `genie`
190
+ * lookups when nothing is wired, so the `plugins.genie` lookup
191
+ * still resolves to `undefined` (matching AppKit's
192
+ * absent-plugin semantics) when neither source is configured.
193
+ */
194
+ export declare function hasAnyGenieSpaces(config: MastraPluginConfig, context: appkitUtils.PluginContextLike | undefined): boolean;