@dbx-tools/appkit-mastra 0.1.5 → 0.1.10

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.
Files changed (53) hide show
  1. package/README.md +394 -0
  2. package/index.ts +46 -37
  3. package/package.json +58 -47
  4. package/src/agents.ts +233 -62
  5. package/src/chart.ts +555 -285
  6. package/src/config.ts +282 -18
  7. package/src/filesystems.ts +1090 -0
  8. package/src/genie.ts +1004 -601
  9. package/src/history.ts +178 -79
  10. package/src/mcp.ts +105 -0
  11. package/src/memory.ts +129 -74
  12. package/src/mlflow.ts +149 -0
  13. package/src/model.ts +80 -408
  14. package/src/observability.ts +144 -0
  15. package/src/pagination.ts +34 -0
  16. package/src/plugin.ts +573 -59
  17. package/src/processors.ts +168 -0
  18. package/src/rest.ts +67 -0
  19. package/src/server.ts +243 -45
  20. package/src/serving-sanitize.ts +167 -0
  21. package/src/serving.ts +27 -224
  22. package/src/statement.ts +89 -0
  23. package/src/storage-schema.ts +41 -0
  24. package/src/summarize.ts +176 -0
  25. package/src/threads.ts +338 -0
  26. package/src/workspaces.ts +346 -0
  27. package/src/writer.ts +44 -0
  28. package/tsconfig.json +41 -0
  29. package/dist/index.d.ts +0 -19
  30. package/dist/index.js +0 -19
  31. package/dist/src/agents.d.ts +0 -306
  32. package/dist/src/agents.js +0 -393
  33. package/dist/src/chart.d.ts +0 -104
  34. package/dist/src/chart.js +0 -375
  35. package/dist/src/config.d.ts +0 -170
  36. package/dist/src/config.js +0 -12
  37. package/dist/src/genie.d.ts +0 -116
  38. package/dist/src/genie.js +0 -594
  39. package/dist/src/history.d.ts +0 -67
  40. package/dist/src/history.js +0 -158
  41. package/dist/src/memory.d.ts +0 -79
  42. package/dist/src/memory.js +0 -197
  43. package/dist/src/model.d.ts +0 -159
  44. package/dist/src/model.js +0 -423
  45. package/dist/src/plugin.d.ts +0 -130
  46. package/dist/src/plugin.js +0 -255
  47. package/dist/src/render-chart-route.d.ts +0 -33
  48. package/dist/src/render-chart-route.js +0 -120
  49. package/dist/src/server.d.ts +0 -46
  50. package/dist/src/server.js +0 -113
  51. package/dist/src/serving.d.ts +0 -156
  52. package/dist/src/serving.js +0 -214
  53. package/src/render-chart-route.ts +0 -141
package/src/agents.ts CHANGED
@@ -10,22 +10,27 @@
10
10
  *
11
11
  * When no agents are registered the plugin falls back to a single
12
12
  * built-in analyst so the bare `mastra()` call still mounts a working
13
- * `chatRoute` agent for demos.
13
+ * streamable agent for demos.
14
14
  */
15
15
 
16
- import { genie } from "@databricks/appkit";
17
- import { logUtils, pluginUtils, stringUtils } from "@dbx-tools/appkit-shared";
18
- import { Agent } from "@mastra/core/agent";
19
16
  import type { AgentConfig, ToolsInput } from "@mastra/core/agent";
20
- import { createTool } from "@mastra/core/tools";
17
+ import { Agent } from "@mastra/core/agent";
21
18
  import type { Tool } from "@mastra/core/tools";
19
+ import { createTool } from "@mastra/core/tools";
20
+ import type { Workspace } from "@mastra/core/workspace";
22
21
  import type { PgVectorConfig, PostgresStoreConfig } from "@mastra/pg";
23
22
 
24
- import { buildRenderDataTool } from "./chart.js";
25
- import type { MastraPluginConfig } from "./config.js";
26
- import { buildGenieProvider } from "./genie.js";
27
- import type { MemoryBuilder } from "./memory.js";
28
- import { buildModel } from "./model.js";
23
+ import { buildRenderDataTool } from "./chart";
24
+ import type { MastraPluginConfig } from "./config";
25
+ import { buildGenieToolkitProvider, resolveGenieSpaces } from "./genie";
26
+ import type { MemoryBuilder } from "./memory";
27
+ import { buildModel } from "./model";
28
+ import { fallback } from "@dbx-tools/model";
29
+ import { ResultProcessor, stripStaleChartsProcessor } from "./processors";
30
+ import { buildSummarizeTool } from "./summarize";
31
+ import { createWorkspace } from "./workspaces";
32
+ import { log, string } from "@dbx-tools/shared-core";
33
+ import { plugin } from "@dbx-tools/appkit";
29
34
 
30
35
  /**
31
36
  * Tool record accepted by every Mastra `Agent.tools` field and by the
@@ -121,12 +126,12 @@ export interface AppKitToolOptions {
121
126
 
122
127
  /**
123
128
  * Build a deterministic Mastra tool id from a description.
124
- * Delegates to {@link stringUtils.toUniqueSlug}: slug + always-on
125
- * SHA-1 suffix so two tools with the same leading words don't
126
- * collide in traces. Stable across runs.
129
+ * Delegates to {@link string.toUniqueSlug}: slug + always-on
130
+ * 6-char FNV-1a base-32 suffix so two tools with the same leading
131
+ * words don't collide in traces. Stable across runs.
127
132
  */
128
133
  function deriveToolId(description: string): string {
129
- return stringUtils.toUniqueSlug(description, { fallbackPrefix: "tool" });
134
+ return string.toUniqueSlug(description, { fallbackPrefix: "tool" });
130
135
  }
131
136
 
132
137
  /**
@@ -145,7 +150,8 @@ function deriveToolId(description: string): string {
145
150
  * type inference and to match the AppKit API surface.
146
151
  */
147
152
  export function createAgent<T extends MastraAgentDefinition>(def: T): T {
148
- return def;
153
+ const workspace = def.workspace ?? createWorkspace();
154
+ return { ...def, workspace };
149
155
  }
150
156
 
151
157
  /**
@@ -218,15 +224,16 @@ export interface MastraPluginToolkitProvider {
218
224
  export type MastraPlugins = Record<string, MastraPluginToolkitProvider>;
219
225
 
220
226
  /** Function form of {@link MastraAgentDefinition.tools}. */
221
- export type MastraToolsFn = (
222
- plugins: MastraPlugins,
223
- ) => MastraTools | Promise<MastraTools>;
227
+ export type MastraToolsFn = (plugins: MastraPlugins) => MastraTools | Promise<MastraTools>;
228
+
229
+ /** Function form of {@link MastraAgentDefinition.workspace}. */
230
+ export type MastraAgentWorkspaceResolver = () => Workspace | undefined;
224
231
 
225
232
  /**
226
233
  * A code-defined Mastra agent. Mirrors the shape AppKit's `agents`
227
234
  * plugin uses for `AgentDefinition`. The registry key under
228
- * `config.agents` is what `chatRoute` matches on; `name` is purely
229
- * informational (defaults to the key).
235
+ * `config.agents` is the `agentId` the client streams against; `name`
236
+ * is purely informational (defaults to the key).
230
237
  */
231
238
  export interface MastraAgentDefinition {
232
239
  /** Display name used as `Agent.name`. Defaults to the registry key. */
@@ -275,7 +282,7 @@ export interface MastraAgentDefinition {
275
282
  *
276
283
  * - `undefined` (default): inherit `config.storage`. When that's
277
284
  * enabled, the agent gets its **own per-agent `PostgresStore`**
278
- * keyed by `schemaName: "mastra_<agentId>"` so threads and
285
+ * keyed by {@link agentStorageSchemaName} so threads and
279
286
  * messages stay isolated between agents in the same database.
280
287
  * - `false`: disable storage for this agent only (purely in-memory).
281
288
  * - `true`: enable with the per-agent default schema.
@@ -283,6 +290,12 @@ export interface MastraAgentDefinition {
283
290
  * `PostgresStore` config (custom schema, connection, etc.).
284
291
  */
285
292
  storage?: boolean | MastraStorageConfigOverride;
293
+ /**
294
+ * Mastra {@link Workspace} for this agent (filesystem, sandbox, or other
295
+ * providers). Assistant skills from Databricks workspace paths are wired
296
+ * via {@link createWorkspace}.
297
+ */
298
+ workspace?: Workspace | MastraAgentWorkspaceResolver;
286
299
  }
287
300
 
288
301
  /**
@@ -291,19 +304,16 @@ export interface MastraAgentDefinition {
291
304
  * strip `id`. The built-in `Omit` collapses unions to one shape with
292
305
  * common fields only, which loses the connection-style discriminants.
293
306
  */
294
- type DistributiveOmit<T, K extends keyof never> = T extends unknown
295
- ? Omit<T, K>
296
- : never;
307
+ type DistributiveOmit<T, K extends keyof never> = T extends unknown ? Omit<T, K> : never;
297
308
 
298
309
  /**
299
310
  * `PostgresStoreConfig` minus `id` - per-agent overrides accept any
300
311
  * Mastra-supported storage shape. `id` is filled in automatically
301
312
  * from the agent registry key so traces stay stable.
302
313
  */
303
- export type MastraStorageConfigOverride = DistributiveOmit<
304
- PostgresStoreConfig,
305
- "id"
306
- > & { id?: string };
314
+ export type MastraStorageConfigOverride = DistributiveOmit<PostgresStoreConfig, "id"> & {
315
+ id?: string;
316
+ };
307
317
 
308
318
  /**
309
319
  * `PgVectorConfig` minus `id` - per-agent overrides accept any
@@ -318,11 +328,28 @@ export type MastraMemoryConfigOverride = DistributiveOmit<PgVectorConfig, "id">
318
328
  export interface BuiltAgents {
319
329
  agents: Record<string, Agent>;
320
330
  defaultAgentId: string;
331
+ /**
332
+ * Ambient tools shared across every agent (the built-in system tools
333
+ * spread with `config.tools`). Surfaced so the optional MCP server
334
+ * can re-expose them when {@link MastraMcpConfig.tools} is enabled.
335
+ */
336
+ ambientTools: MastraTools;
321
337
  }
322
338
 
323
339
  /** Fallback agent id used when `config.agents` is omitted entirely. */
324
340
  export const FALLBACK_AGENT_ID = "default";
325
341
 
342
+ /**
343
+ * Default per-turn step ceiling applied to every registered agent
344
+ * when {@link MastraPluginConfig.agentMaxSteps} is unset. Sized to
345
+ * fit a decomposed Genie turn (grounding + several `ask_genie`
346
+ * calls + `prepare_chart` per dataset + the final-text reply) with
347
+ * headroom for the model to chain a couple of follow-ups before
348
+ * answering - well above Mastra's own `agent.generate` default of
349
+ * 5, which would cut multi-step orchestration off mid-loop.
350
+ */
351
+ export const DEFAULT_AGENT_MAX_STEPS = 25;
352
+
326
353
  const FALLBACK_AGENT_INSTRUCTIONS = `You are a data analyst. The user will ask questions about
327
354
  business metrics and may share personal preferences you should remember across turns.
328
355
 
@@ -383,6 +410,16 @@ function composeInstructions(agentInstructions: string, style: string | null): s
383
410
  return `${agentInstructions.trimEnd()}\n\n${style}`;
384
411
  }
385
412
 
413
+ /**
414
+ * Generated description for an agent whose definition omits one. Kept
415
+ * generic (the agent's own name is the only reliable signal at build
416
+ * time) so it reads sensibly as the `ask_<id>` MCP tool's description
417
+ * and in agent metadata.
418
+ */
419
+ function defaultAgentDescription(name: string): string {
420
+ return `Conversational AI agent "${name}".`;
421
+ }
422
+
386
423
  /**
387
424
  * Resolve every entry in `config.agents` into a Mastra `Agent`
388
425
  * instance. When `config.agents` is omitted the plugin registers a
@@ -398,39 +435,78 @@ function composeInstructions(agentInstructions: string, style: string | null): s
398
435
  */
399
436
  export async function buildAgents(opts: {
400
437
  config: MastraPluginConfig;
401
- context: pluginUtils.PluginContextLike | undefined;
438
+ context: plugin.PluginContextLike | undefined;
402
439
  memoryBuilder?: MemoryBuilder;
403
- log: logUtils.Logger;
440
+ log: log.Logger;
404
441
  }): Promise<BuiltAgents> {
405
442
  const { config, context, memoryBuilder, log } = opts;
406
443
  const definitions = resolveDefinitions(config);
407
444
  const ids = Object.keys(definitions);
408
445
  const defaultAgentId = config.defaultAgent ?? ids[0] ?? FALLBACK_AGENT_ID;
409
446
 
410
- const plugins = buildPluginsMap(context);
411
- // System-default ambient tools every agent gets out of the box.
412
- // Currently just `render_data` for inline visualizations; the
413
- // user can shadow it by including a same-named tool in their own
447
+ const plugins = buildPluginsMap(config, context);
448
+ // System-default ambient tools every agent gets out of the box:
449
+ // `render_data` for inline visualizations and `summarize` for
450
+ // offloading text condensing to the fast / small chat tier. The
451
+ // user can shadow either by including a same-named tool in their own
414
452
  // `config.tools` or per-agent `tools`. Order in {@link resolveTools}
415
453
  // is `system -> user-ambient -> per-agent`, last write wins.
416
454
  const systemTools: MastraTools = {
417
455
  render_data: buildRenderDataTool(config),
456
+ summarize: buildSummarizeTool(config),
457
+ };
458
+ const ambientTools = {
459
+ ...systemTools,
460
+ ...(config.tools ?? {}),
418
461
  };
419
- const ambientTools = { ...systemTools, ...(config.tools ?? {}) };
420
462
  const style = resolveStyleInstructions(config);
463
+ // Default-on protection against the model copying turn-scoped
464
+ // chartIds from prior assistant tool results into the new
465
+ // turn's `[chart:<id>]` markers. Opt out per-plugin via
466
+ // `config.stripStaleCharts: false`.
467
+ const outputProcessors = [new ResultProcessor()];
468
+ const inputProcessors = [
469
+ ...(config.stripStaleCharts === false ? [] : [stripStaleChartsProcessor]),
470
+ ];
421
471
  const agents: Record<string, Agent> = {};
472
+ const approvalGatedByAgent: Array<{ agentId: string; toolIds: string[] }> = [];
422
473
 
423
474
  for (const [id, def] of Object.entries(definitions)) {
424
475
  const tools = await resolveTools(def.tools, plugins, ambientTools);
476
+ const workspace = resolveAgentWorkspace(def.workspace);
477
+ const gated = approvalGatedToolIds(tools);
478
+ if (gated.length > 0) approvalGatedByAgent.push({ agentId: id, toolIds: gated });
425
479
  const memory = memoryBuilder?.forAgent(id, def);
426
480
  agents[id] = new Agent({
427
481
  id,
428
482
  name: def.name ?? id,
429
- ...(def.description !== undefined ? { description: def.description } : {}),
483
+ // Always carry a non-empty description: it's surfaced to MCP
484
+ // clients (which reject a description-less agent) and in agent
485
+ // metadata. Fall back to a generated one when the definition
486
+ // omits it.
487
+ description: def.description?.trim() || defaultAgentDescription(def.name ?? id),
430
488
  instructions: composeInstructions(def.instructions, style),
431
489
  model: resolveModel(config, def.model),
490
+ defaultOptions: {
491
+ maxSteps: config.agentMaxSteps ?? DEFAULT_AGENT_MAX_STEPS,
492
+ },
432
493
  tools,
433
494
  ...(memory ? { memory } : {}),
495
+ ...(workspace ? { workspace } : {}),
496
+ inputProcessors,
497
+ outputProcessors,
498
+ });
499
+ // Surface the effective default model per agent so operators can
500
+ // see at a glance which endpoint each agent points at without
501
+ // having to fire a request and inspect a trace. The value is the
502
+ // *static* default; per-request overrides (header / query /
503
+ // body) and the workspace-catalogue fuzzy match still apply at
504
+ // call time.
505
+ log.info("agent registered", {
506
+ id,
507
+ name: def.name ?? id,
508
+ defaultModel: describeAgentDefaultModel(config, def),
509
+ tools: Object.keys(tools),
434
510
  });
435
511
  }
436
512
 
@@ -440,8 +516,88 @@ export async function buildAgents(opts: {
440
516
  );
441
517
  }
442
518
 
443
- log.info("agents registered", { ids, defaultAgentId });
444
- return { agents, defaultAgentId };
519
+ assertApprovalGatedToolsHaveStorage(approvalGatedByAgent, memoryBuilder);
520
+
521
+ log.info("agents ready", { ids, defaultAgentId });
522
+ return { agents, defaultAgentId, ambientTools };
523
+ }
524
+
525
+ /**
526
+ * Tool ids on `tools` that are approval-gated (`requireApproval: true`).
527
+ * Keys are used as a fallback when a tool omits an explicit `id`.
528
+ */
529
+ export function approvalGatedToolIds(tools: MastraTools): string[] {
530
+ if (!tools || typeof tools !== "object") return [];
531
+ const ids: string[] = [];
532
+ for (const [key, tool] of Object.entries(tools)) {
533
+ if (!isApprovalGatedTool(tool)) continue;
534
+ ids.push(resolveToolId(tool, key));
535
+ }
536
+ return ids;
537
+ }
538
+
539
+ /** True when a Mastra / AI SDK tool pauses for human approval before execute. */
540
+ function isApprovalGatedTool(tool: unknown): boolean {
541
+ if (!tool || typeof tool !== "object") return false;
542
+ return (tool as { requireApproval?: boolean }).requireApproval === true;
543
+ }
544
+
545
+ function resolveToolId(tool: unknown, fallbackKey: string): string {
546
+ if (tool && typeof tool === "object" && typeof (tool as { id?: string }).id === "string") {
547
+ return (tool as { id: string }).id;
548
+ }
549
+ return fallbackKey;
550
+ }
551
+
552
+ /**
553
+ * Approval-gated tools suspend the agent loop until a human approves.
554
+ * Mastra persists those suspended runs in Mastra-instance-level storage
555
+ * (`memoryBuilder.instanceStorage()`), not per-agent memory - so boot
556
+ * must fail fast when such a tool is registered without storage.
557
+ */
558
+ function assertApprovalGatedToolsHaveStorage(
559
+ gated: Array<{ agentId: string; toolIds: string[] }>,
560
+ memoryBuilder: MemoryBuilder | undefined,
561
+ ): void {
562
+ if (gated.length === 0) return;
563
+ if (memoryBuilder?.instanceStorage()) return;
564
+
565
+ const detail = gated
566
+ .map(({ agentId, toolIds }) => `${agentId}: ${toolIds.join(", ")}`)
567
+ .join("; ");
568
+ throw new Error(
569
+ "mastra: approval-gated tools require plugin storage (PostgresStore) to persist suspended runs. " +
570
+ `Affected agents/tools: ${detail}. ` +
571
+ "Register lakebase() before mastra() so storage auto-enables, or pass storage: true explicitly.",
572
+ );
573
+ }
574
+
575
+ /**
576
+ * Best-effort description of the *static* default model an agent will
577
+ * resolve to at call time. Walks the same precedence ladder as
578
+ * {@link resolveModel} / {@link buildModel}:
579
+ *
580
+ * 1. Per-agent `def.model` (string sugar -> the literal id;
581
+ * function / `DynamicArgument` -> `"<dynamic>"` because the
582
+ * resolver decides at call time).
583
+ * 2. Plugin-level `config.defaultModel` (same rules).
584
+ * 3. `DATABRICKS_SERVING_ENDPOINT_NAME` env var.
585
+ * 4. First entry of `config.defaultModelFallbacks ?? fallback.FALLBACK_MODEL_IDS`.
586
+ *
587
+ * Used for the startup `agent registered` log so operators can see
588
+ * which endpoint each agent points at by default. Per-request
589
+ * overrides (`X-Mastra-Model` etc.) and the workspace-catalogue
590
+ * fuzzy match are still applied at runtime.
591
+ */
592
+ function describeAgentDefaultModel(config: MastraPluginConfig, def: MastraAgentDefinition): string {
593
+ const effective = def.model ?? config.defaultModel;
594
+ if (typeof effective === "string") return effective;
595
+ if (effective !== undefined) return "<dynamic>";
596
+ return (
597
+ process.env.DATABRICKS_SERVING_ENDPOINT_NAME ??
598
+ config.defaultModelFallbacks?.[0] ??
599
+ fallback.FALLBACK_MODEL_IDS[0]!
600
+ );
445
601
  }
446
602
 
447
603
  /**
@@ -458,9 +614,7 @@ export async function buildAgents(opts: {
458
614
  * Omitted or empty inputs fall back to a single built-in analyst so
459
615
  * the bare `mastra()` call still mounts a working chat route.
460
616
  */
461
- function resolveDefinitions(
462
- config: MastraPluginConfig,
463
- ): Record<string, MastraAgentDefinition> {
617
+ function resolveDefinitions(config: MastraPluginConfig): Record<string, MastraAgentDefinition> {
464
618
  const input = config.agents;
465
619
  if (!input) return fallbackDefinitions();
466
620
 
@@ -496,7 +650,7 @@ function resolveDefinitions(
496
650
  /** Derive a registry id from a definition's `name`, with a fallback. */
497
651
  function deriveAgentKey(def: MastraAgentDefinition, index?: number): string {
498
652
  if (def.name) {
499
- const slug = stringUtils.toIdentifier(def.name);
653
+ const slug = string.toIdentifier(def.name);
500
654
  if (slug) return slug;
501
655
  }
502
656
  return index === undefined ? FALLBACK_AGENT_ID : `agent_${index}`;
@@ -560,6 +714,13 @@ async function resolveTools(
560
714
  return { ...ambientTools, ...resolved };
561
715
  }
562
716
 
717
+ function resolveAgentWorkspace(
718
+ workspace: MastraAgentDefinition["workspace"],
719
+ ): Workspace | undefined {
720
+ if (!workspace) return undefined;
721
+ return typeof workspace === "function" ? workspace() : workspace;
722
+ }
723
+
563
724
  /**
564
725
  * Build the {@link MastraPlugins} runtime proxy handed to
565
726
  * `tools(plugins)` callbacks.
@@ -581,14 +742,15 @@ async function resolveTools(
581
742
  * time instead of staring at a spinner for the full Genie round-trip.
582
743
  */
583
744
  function buildPluginsMap(
584
- context: pluginUtils.PluginContextLike | undefined,
745
+ config: MastraPluginConfig,
746
+ context: plugin.PluginContextLike | undefined,
585
747
  ): MastraPlugins {
586
748
  const cache = new Map<string, MastraPluginToolkitProvider | null>();
587
749
  return new Proxy({} as MastraPlugins, {
588
750
  get(_target, propName) {
589
751
  if (typeof propName !== "string") return undefined;
590
752
  if (cache.has(propName)) return cache.get(propName) ?? undefined;
591
- const provider = resolveProvider(context, propName);
753
+ const provider = resolveProvider(config, context, propName);
592
754
  cache.set(propName, provider);
593
755
  return provider ?? undefined;
594
756
  },
@@ -597,18 +759,35 @@ function buildPluginsMap(
597
759
 
598
760
  /**
599
761
  * Pick the right {@link MastraPluginToolkitProvider} for a sibling
600
- * plugin lookup. Returns the streaming-aware Genie adapter when the
601
- * caller asks for `genie`; falls back to the generic AppKit
602
- * `ToolProvider` adapter for every other plugin name.
762
+ * plugin lookup. Returns the Genie agent-backed adapter when
763
+ * the caller asks for `genie` AND at least one space is reachable
764
+ * via {@link resolveGenieSpaces} (the explicit
765
+ * `config.genieSpaces`, the registered AppKit `genie()` plugin's
766
+ * `spaces` config, or the `DATABRICKS_GENIE_SPACE_ID` env var).
767
+ * Falls back to the generic AppKit `ToolProvider` adapter for
768
+ * every other plugin name. `config` is threaded through so the
769
+ * Genie agent inherits the same model resolver / fallback
770
+ * ladder the calling agents use.
771
+ *
772
+ * The Genie agent talks to Genie directly via `@dbx-tools/genie`
773
+ * (`genieEventChat`) and the workspace
774
+ * `statementExecution.getStatement` API. AppKit's stock `genie`
775
+ * plugin is honored only for its resource manifest and `spaces`
776
+ * config so existing `app.yaml` configs and `genie({ spaces })`
777
+ * wiring keep working without change.
603
778
  */
604
779
  function resolveProvider(
605
- context: pluginUtils.PluginContextLike | undefined,
780
+ config: MastraPluginConfig,
781
+ context: plugin.PluginContextLike | undefined,
606
782
  propName: string,
607
783
  ): MastraPluginToolkitProvider | null {
608
784
  if (propName === "genie") {
609
- const geniePlugin = pluginUtils.instance(context, genie);
610
- if (!geniePlugin) return null;
611
- return buildGenieProvider(geniePlugin) as MastraPluginToolkitProvider;
785
+ const spaces = resolveGenieSpaces(config, context);
786
+ if (Object.keys(spaces).length === 0) return null;
787
+ return buildGenieToolkitProvider({
788
+ spaces,
789
+ config,
790
+ }) as MastraPluginToolkitProvider;
612
791
  }
613
792
  const plugin = context?.getPlugins().get(propName);
614
793
  return adaptPluginToolkit(plugin);
@@ -621,11 +800,7 @@ function resolveProvider(
621
800
  */
622
801
  interface AppKitToolkitProvider {
623
802
  toolkit?: (opts?: ToolkitOptions) => Record<string, AppKitToolkitEntry>;
624
- executeAgentTool?: (
625
- name: string,
626
- args: unknown,
627
- signal?: AbortSignal,
628
- ) => Promise<unknown>;
803
+ executeAgentTool?: (name: string, args: unknown, signal?: AbortSignal) => Promise<unknown>;
629
804
  }
630
805
 
631
806
  /** Single entry returned by an AppKit plugin's `.toolkit(opts)` call. */
@@ -670,17 +845,13 @@ function adaptPluginToolkit(plugin: unknown): MastraPluginToolkitProvider | null
670
845
  * Schema parameters pass through unchanged - Mastra's `PublicSchema`
671
846
  * accepts `JSONSchema7` directly via `@mastra/schema-compat`.
672
847
  */
673
- function toolkitEntryToMastraTool(
674
- entry: AppKitToolkitEntry,
675
- plugin: AppKitToolkitProvider,
676
- ): Tool {
848
+ function toolkitEntryToMastraTool(entry: AppKitToolkitEntry, plugin: AppKitToolkitProvider): Tool {
677
849
  return createTool({
678
850
  id: `${entry.pluginName}__${entry.localName}`,
679
851
  description: entry.def.description,
680
852
  ...(entry.def.parameters ? { inputSchema: entry.def.parameters as never } : {}),
681
853
  execute: async (input: unknown, context: unknown) => {
682
- const signal = (context as { abortSignal?: AbortSignal } | undefined)
683
- ?.abortSignal;
854
+ const signal = (context as { abortSignal?: AbortSignal } | undefined)?.abortSignal;
684
855
  return plugin.executeAgentTool!(entry.localName, input, signal);
685
856
  },
686
857
  });