@dbx-tools/appkit-mastra 0.1.13 → 0.1.14

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 (59) hide show
  1. package/README.md +304 -645
  2. package/index.ts +46 -38
  3. package/package.json +58 -45
  4. package/src/agents.ts +224 -66
  5. package/src/chart.ts +531 -429
  6. package/src/config.ts +270 -19
  7. package/src/filesystems.ts +1090 -0
  8. package/src/genie.ts +1000 -660
  9. package/src/history.ts +166 -79
  10. package/src/mcp.ts +105 -0
  11. package/src/memory.ts +94 -92
  12. package/src/mlflow.ts +149 -0
  13. package/src/model.ts +75 -408
  14. package/src/observability.ts +121 -69
  15. package/src/pagination.ts +34 -0
  16. package/src/plugin.ts +552 -67
  17. package/src/processors.ts +168 -0
  18. package/src/rest.ts +67 -0
  19. package/src/server.ts +232 -45
  20. package/src/serving-sanitize.ts +167 -0
  21. package/src/serving.ts +27 -243
  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 -20
  30. package/dist/index.js +0 -20
  31. package/dist/src/agents.d.ts +0 -306
  32. package/dist/src/agents.js +0 -403
  33. package/dist/src/chart.d.ts +0 -170
  34. package/dist/src/chart.js +0 -491
  35. package/dist/src/config.d.ts +0 -183
  36. package/dist/src/config.js +0 -12
  37. package/dist/src/genie.d.ts +0 -131
  38. package/dist/src/genie.js +0 -630
  39. package/dist/src/history.d.ts +0 -67
  40. package/dist/src/history.js +0 -172
  41. package/dist/src/memory.d.ts +0 -100
  42. package/dist/src/memory.js +0 -242
  43. package/dist/src/model.d.ts +0 -159
  44. package/dist/src/model.js +0 -427
  45. package/dist/src/observability.d.ts +0 -33
  46. package/dist/src/observability.js +0 -71
  47. package/dist/src/plugin.d.ts +0 -130
  48. package/dist/src/plugin.js +0 -283
  49. package/dist/src/processors/strip-stale-charts.d.ts +0 -29
  50. package/dist/src/processors/strip-stale-charts.js +0 -96
  51. package/dist/src/server.d.ts +0 -46
  52. package/dist/src/server.js +0 -123
  53. package/dist/src/serving.d.ts +0 -156
  54. package/dist/src/serving.js +0 -231
  55. package/dist/src/tools/email.d.ts +0 -74
  56. package/dist/src/tools/email.js +0 -122
  57. package/dist/tsconfig.build.tsbuildinfo +0 -1
  58. package/src/processors/strip-stale-charts.ts +0 -105
  59. package/src/tools/email.ts +0 -147
package/src/agents.ts CHANGED
@@ -10,23 +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";
29
- import { stripStaleChartsProcessor } from "./processors/strip-stale-charts.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";
30
34
 
31
35
  /**
32
36
  * Tool record accepted by every Mastra `Agent.tools` field and by the
@@ -122,12 +126,12 @@ export interface AppKitToolOptions {
122
126
 
123
127
  /**
124
128
  * Build a deterministic Mastra tool id from a description.
125
- * Delegates to {@link stringUtils.toUniqueSlug}: slug + always-on
129
+ * Delegates to {@link string.toUniqueSlug}: slug + always-on
126
130
  * 6-char FNV-1a base-32 suffix so two tools with the same leading
127
131
  * words don't collide in traces. Stable across runs.
128
132
  */
129
133
  function deriveToolId(description: string): string {
130
- return stringUtils.toUniqueSlug(description, { fallbackPrefix: "tool" });
134
+ return string.toUniqueSlug(description, { fallbackPrefix: "tool" });
131
135
  }
132
136
 
133
137
  /**
@@ -146,7 +150,8 @@ function deriveToolId(description: string): string {
146
150
  * type inference and to match the AppKit API surface.
147
151
  */
148
152
  export function createAgent<T extends MastraAgentDefinition>(def: T): T {
149
- return def;
153
+ const workspace = def.workspace ?? createWorkspace();
154
+ return { ...def, workspace };
150
155
  }
151
156
 
152
157
  /**
@@ -219,15 +224,16 @@ export interface MastraPluginToolkitProvider {
219
224
  export type MastraPlugins = Record<string, MastraPluginToolkitProvider>;
220
225
 
221
226
  /** Function form of {@link MastraAgentDefinition.tools}. */
222
- export type MastraToolsFn = (
223
- plugins: MastraPlugins,
224
- ) => 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;
225
231
 
226
232
  /**
227
233
  * A code-defined Mastra agent. Mirrors the shape AppKit's `agents`
228
234
  * plugin uses for `AgentDefinition`. The registry key under
229
- * `config.agents` is what `chatRoute` matches on; `name` is purely
230
- * informational (defaults to the key).
235
+ * `config.agents` is the `agentId` the client streams against; `name`
236
+ * is purely informational (defaults to the key).
231
237
  */
232
238
  export interface MastraAgentDefinition {
233
239
  /** Display name used as `Agent.name`. Defaults to the registry key. */
@@ -276,7 +282,7 @@ export interface MastraAgentDefinition {
276
282
  *
277
283
  * - `undefined` (default): inherit `config.storage`. When that's
278
284
  * enabled, the agent gets its **own per-agent `PostgresStore`**
279
- * keyed by `schemaName: "mastra_<agentId>"` so threads and
285
+ * keyed by {@link agentStorageSchemaName} so threads and
280
286
  * messages stay isolated between agents in the same database.
281
287
  * - `false`: disable storage for this agent only (purely in-memory).
282
288
  * - `true`: enable with the per-agent default schema.
@@ -284,6 +290,12 @@ export interface MastraAgentDefinition {
284
290
  * `PostgresStore` config (custom schema, connection, etc.).
285
291
  */
286
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;
287
299
  }
288
300
 
289
301
  /**
@@ -292,19 +304,16 @@ export interface MastraAgentDefinition {
292
304
  * strip `id`. The built-in `Omit` collapses unions to one shape with
293
305
  * common fields only, which loses the connection-style discriminants.
294
306
  */
295
- type DistributiveOmit<T, K extends keyof never> = T extends unknown
296
- ? Omit<T, K>
297
- : never;
307
+ type DistributiveOmit<T, K extends keyof never> = T extends unknown ? Omit<T, K> : never;
298
308
 
299
309
  /**
300
310
  * `PostgresStoreConfig` minus `id` - per-agent overrides accept any
301
311
  * Mastra-supported storage shape. `id` is filled in automatically
302
312
  * from the agent registry key so traces stay stable.
303
313
  */
304
- export type MastraStorageConfigOverride = DistributiveOmit<
305
- PostgresStoreConfig,
306
- "id"
307
- > & { id?: string };
314
+ export type MastraStorageConfigOverride = DistributiveOmit<PostgresStoreConfig, "id"> & {
315
+ id?: string;
316
+ };
308
317
 
309
318
  /**
310
319
  * `PgVectorConfig` minus `id` - per-agent overrides accept any
@@ -319,11 +328,28 @@ export type MastraMemoryConfigOverride = DistributiveOmit<PgVectorConfig, "id">
319
328
  export interface BuiltAgents {
320
329
  agents: Record<string, Agent>;
321
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;
322
337
  }
323
338
 
324
339
  /** Fallback agent id used when `config.agents` is omitted entirely. */
325
340
  export const FALLBACK_AGENT_ID = "default";
326
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
+
327
353
  const FALLBACK_AGENT_INSTRUCTIONS = `You are a data analyst. The user will ask questions about
328
354
  business metrics and may share personal preferences you should remember across turns.
329
355
 
@@ -384,6 +410,16 @@ function composeInstructions(agentInstructions: string, style: string | null): s
384
410
  return `${agentInstructions.trimEnd()}\n\n${style}`;
385
411
  }
386
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
+
387
423
  /**
388
424
  * Resolve every entry in `config.agents` into a Mastra `Agent`
389
425
  * instance. When `config.agents` is omitted the plugin registers a
@@ -399,9 +435,9 @@ function composeInstructions(agentInstructions: string, style: string | null): s
399
435
  */
400
436
  export async function buildAgents(opts: {
401
437
  config: MastraPluginConfig;
402
- context: pluginUtils.PluginContextLike | undefined;
438
+ context: plugin.PluginContextLike | undefined;
403
439
  memoryBuilder?: MemoryBuilder;
404
- log: logUtils.Logger;
440
+ log: log.Logger;
405
441
  }): Promise<BuiltAgents> {
406
442
  const { config, context, memoryBuilder, log } = opts;
407
443
  const definitions = resolveDefinitions(config);
@@ -409,36 +445,68 @@ export async function buildAgents(opts: {
409
445
  const defaultAgentId = config.defaultAgent ?? ids[0] ?? FALLBACK_AGENT_ID;
410
446
 
411
447
  const plugins = buildPluginsMap(config, context);
412
- // System-default ambient tools every agent gets out of the box.
413
- // Currently just `render_data` for inline visualizations; the
414
- // user can shadow it by including a same-named tool in their own
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
415
452
  // `config.tools` or per-agent `tools`. Order in {@link resolveTools}
416
453
  // is `system -> user-ambient -> per-agent`, last write wins.
417
454
  const systemTools: MastraTools = {
418
455
  render_data: buildRenderDataTool(config),
456
+ summarize: buildSummarizeTool(config),
457
+ };
458
+ const ambientTools = {
459
+ ...systemTools,
460
+ ...(config.tools ?? {}),
419
461
  };
420
- const ambientTools = { ...systemTools, ...(config.tools ?? {}) };
421
462
  const style = resolveStyleInstructions(config);
422
463
  // Default-on protection against the model copying turn-scoped
423
464
  // chartIds from prior assistant tool results into the new
424
- // turn's `[[chart:<id>]]` markers. Opt out per-plugin via
465
+ // turn's `[chart:<id>]` markers. Opt out per-plugin via
425
466
  // `config.stripStaleCharts: false`.
426
- const inputProcessors =
427
- config.stripStaleCharts === false ? [] : [stripStaleChartsProcessor];
467
+ const outputProcessors = [new ResultProcessor()];
468
+ const inputProcessors = [
469
+ ...(config.stripStaleCharts === false ? [] : [stripStaleChartsProcessor]),
470
+ ];
428
471
  const agents: Record<string, Agent> = {};
472
+ const approvalGatedByAgent: Array<{ agentId: string; toolIds: string[] }> = [];
429
473
 
430
474
  for (const [id, def] of Object.entries(definitions)) {
431
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 });
432
479
  const memory = memoryBuilder?.forAgent(id, def);
433
480
  agents[id] = new Agent({
434
481
  id,
435
482
  name: def.name ?? id,
436
- ...(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),
437
488
  instructions: composeInstructions(def.instructions, style),
438
489
  model: resolveModel(config, def.model),
490
+ defaultOptions: {
491
+ maxSteps: config.agentMaxSteps ?? DEFAULT_AGENT_MAX_STEPS,
492
+ },
439
493
  tools,
440
494
  ...(memory ? { memory } : {}),
441
- ...(inputProcessors.length > 0 ? { inputProcessors } : {}),
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),
442
510
  });
443
511
  }
444
512
 
@@ -448,8 +516,88 @@ export async function buildAgents(opts: {
448
516
  );
449
517
  }
450
518
 
451
- log.info("agents registered", { ids, defaultAgentId });
452
- 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
+ );
453
601
  }
454
602
 
455
603
  /**
@@ -466,9 +614,7 @@ export async function buildAgents(opts: {
466
614
  * Omitted or empty inputs fall back to a single built-in analyst so
467
615
  * the bare `mastra()` call still mounts a working chat route.
468
616
  */
469
- function resolveDefinitions(
470
- config: MastraPluginConfig,
471
- ): Record<string, MastraAgentDefinition> {
617
+ function resolveDefinitions(config: MastraPluginConfig): Record<string, MastraAgentDefinition> {
472
618
  const input = config.agents;
473
619
  if (!input) return fallbackDefinitions();
474
620
 
@@ -504,7 +650,7 @@ function resolveDefinitions(
504
650
  /** Derive a registry id from a definition's `name`, with a fallback. */
505
651
  function deriveAgentKey(def: MastraAgentDefinition, index?: number): string {
506
652
  if (def.name) {
507
- const slug = stringUtils.toIdentifier(def.name);
653
+ const slug = string.toIdentifier(def.name);
508
654
  if (slug) return slug;
509
655
  }
510
656
  return index === undefined ? FALLBACK_AGENT_ID : `agent_${index}`;
@@ -568,6 +714,13 @@ async function resolveTools(
568
714
  return { ...ambientTools, ...resolved };
569
715
  }
570
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
+
571
724
  /**
572
725
  * Build the {@link MastraPlugins} runtime proxy handed to
573
726
  * `tools(plugins)` callbacks.
@@ -590,7 +743,7 @@ async function resolveTools(
590
743
  */
591
744
  function buildPluginsMap(
592
745
  config: MastraPluginConfig,
593
- context: pluginUtils.PluginContextLike | undefined,
746
+ context: plugin.PluginContextLike | undefined,
594
747
  ): MastraPlugins {
595
748
  const cache = new Map<string, MastraPluginToolkitProvider | null>();
596
749
  return new Proxy({} as MastraPlugins, {
@@ -606,22 +759,35 @@ function buildPluginsMap(
606
759
 
607
760
  /**
608
761
  * Pick the right {@link MastraPluginToolkitProvider} for a sibling
609
- * plugin lookup. Returns the streaming-aware Genie adapter when the
610
- * caller asks for `genie`; falls back to the generic AppKit
611
- * `ToolProvider` adapter for every other plugin name. `config` is
612
- * threaded through so Genie's tool can run the chart planner
613
- * inline against the same model resolver / fallback ladder the
614
- * agents use.
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.
615
778
  */
616
779
  function resolveProvider(
617
780
  config: MastraPluginConfig,
618
- context: pluginUtils.PluginContextLike | undefined,
781
+ context: plugin.PluginContextLike | undefined,
619
782
  propName: string,
620
783
  ): MastraPluginToolkitProvider | null {
621
784
  if (propName === "genie") {
622
- const geniePlugin = pluginUtils.instance(context, genie);
623
- if (!geniePlugin) return null;
624
- return buildGenieProvider(geniePlugin, { config }) 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;
625
791
  }
626
792
  const plugin = context?.getPlugins().get(propName);
627
793
  return adaptPluginToolkit(plugin);
@@ -634,11 +800,7 @@ function resolveProvider(
634
800
  */
635
801
  interface AppKitToolkitProvider {
636
802
  toolkit?: (opts?: ToolkitOptions) => Record<string, AppKitToolkitEntry>;
637
- executeAgentTool?: (
638
- name: string,
639
- args: unknown,
640
- signal?: AbortSignal,
641
- ) => Promise<unknown>;
803
+ executeAgentTool?: (name: string, args: unknown, signal?: AbortSignal) => Promise<unknown>;
642
804
  }
643
805
 
644
806
  /** Single entry returned by an AppKit plugin's `.toolkit(opts)` call. */
@@ -683,17 +845,13 @@ function adaptPluginToolkit(plugin: unknown): MastraPluginToolkitProvider | null
683
845
  * Schema parameters pass through unchanged - Mastra's `PublicSchema`
684
846
  * accepts `JSONSchema7` directly via `@mastra/schema-compat`.
685
847
  */
686
- function toolkitEntryToMastraTool(
687
- entry: AppKitToolkitEntry,
688
- plugin: AppKitToolkitProvider,
689
- ): Tool {
848
+ function toolkitEntryToMastraTool(entry: AppKitToolkitEntry, plugin: AppKitToolkitProvider): Tool {
690
849
  return createTool({
691
850
  id: `${entry.pluginName}__${entry.localName}`,
692
851
  description: entry.def.description,
693
852
  ...(entry.def.parameters ? { inputSchema: entry.def.parameters as never } : {}),
694
853
  execute: async (input: unknown, context: unknown) => {
695
- const signal = (context as { abortSignal?: AbortSignal } | undefined)
696
- ?.abortSignal;
854
+ const signal = (context as { abortSignal?: AbortSignal } | undefined)?.abortSignal;
697
855
  return plugin.executeAgentTool!(entry.localName, input, signal);
698
856
  },
699
857
  });