@dbx-tools/appkit-mastra 0.1.0
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 +593 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +18 -0
- package/dist/src/agents.d.ts +306 -0
- package/dist/src/agents.js +379 -0
- package/dist/src/config.d.ts +170 -0
- package/dist/src/config.js +12 -0
- package/dist/src/genie.d.ts +109 -0
- package/dist/src/genie.js +271 -0
- package/dist/src/memory.d.ts +79 -0
- package/dist/src/memory.js +197 -0
- package/dist/src/model.d.ts +159 -0
- package/dist/src/model.js +423 -0
- package/dist/src/plugin.d.ts +120 -0
- package/dist/src/plugin.js +235 -0
- package/dist/src/server.d.ts +42 -0
- package/dist/src/server.js +109 -0
- package/dist/src/serving.d.ts +156 -0
- package/dist/src/serving.js +214 -0
- package/index.ts +36 -0
- package/package.json +55 -0
- package/src/agents.ts +675 -0
- package/src/config.ts +179 -0
- package/src/genie.ts +354 -0
- package/src/memory.ts +245 -0
- package/src/model.ts +491 -0
- package/src/plugin.ts +269 -0
- package/src/server.ts +130 -0
- package/src/serving.ts +294 -0
package/src/agents.ts
ADDED
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent registration for the Mastra AppKit plugin.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the shape of the AppKit `agents` plugin (`config.agents` map
|
|
5
|
+
* of {@link MastraAgentDefinition}, dual-form `tools` accepting a plain
|
|
6
|
+
* record or a `(plugins) => tools` callback). Resolves each definition
|
|
7
|
+
* into a Mastra `Agent` instance during plugin setup; user-supplied
|
|
8
|
+
* tool callbacks are invoked exactly once with a typed
|
|
9
|
+
* {@link MastraPlugins} map built from registered sibling plugins.
|
|
10
|
+
*
|
|
11
|
+
* When no agents are registered the plugin falls back to a single
|
|
12
|
+
* built-in analyst so the bare `mastra()` call still mounts a working
|
|
13
|
+
* `chatRoute` agent for demos.
|
|
14
|
+
*/
|
|
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
|
+
import type { AgentConfig, ToolsInput } from "@mastra/core/agent";
|
|
20
|
+
import { createTool } from "@mastra/core/tools";
|
|
21
|
+
import type { Tool } from "@mastra/core/tools";
|
|
22
|
+
import type { PgVectorConfig, PostgresStoreConfig } from "@mastra/pg";
|
|
23
|
+
|
|
24
|
+
import type { MastraPluginConfig } from "./config.js";
|
|
25
|
+
import { buildGenieProvider } from "./genie.js";
|
|
26
|
+
import type { MemoryBuilder } from "./memory.js";
|
|
27
|
+
import { buildModel } from "./model.js";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Tool record accepted by every Mastra `Agent.tools` field and by the
|
|
31
|
+
* `tools(plugins)` callback on {@link MastraAgentDefinition}.
|
|
32
|
+
*
|
|
33
|
+
* Alias of Mastra's `ToolsInput`, so it already accepts:
|
|
34
|
+
*
|
|
35
|
+
* - Mastra tools built with {@link createTool} (or `new Tool(...)`)
|
|
36
|
+
* - Mastra tools built with the AppKit-shaped {@link tool} wrapper
|
|
37
|
+
* below
|
|
38
|
+
* - Vercel AI SDK tools (`tool({ ... })` from `ai`)
|
|
39
|
+
* - Provider-defined tools (e.g. `openai.tools.webSearch(...)`)
|
|
40
|
+
*
|
|
41
|
+
* Existing tool libraries drop in as-is - nothing in this package
|
|
42
|
+
* forces a rebuild.
|
|
43
|
+
*/
|
|
44
|
+
export type MastraTools = ToolsInput;
|
|
45
|
+
|
|
46
|
+
/** Re-export of Mastra's native `createTool` for full-feature access. */
|
|
47
|
+
export { createTool } from "@mastra/core/tools";
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* AppKit-shaped tool factory. Lets users mix-and-match tools across
|
|
51
|
+
* AppKit's `agents` plugin and `mastra` with a single import:
|
|
52
|
+
*
|
|
53
|
+
* ```ts
|
|
54
|
+
* import { tool } from "@dbx-tools/appkit-mastra";
|
|
55
|
+
* import { z } from "zod";
|
|
56
|
+
*
|
|
57
|
+
* get_weather: tool({
|
|
58
|
+
* description: "Weather",
|
|
59
|
+
* schema: z.object({ city: z.string() }),
|
|
60
|
+
* execute: async ({ city }) => `Sunny in ${city}`,
|
|
61
|
+
* }),
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* Maps onto Mastra's `createTool`:
|
|
65
|
+
*
|
|
66
|
+
* - `description` -> `description` (required)
|
|
67
|
+
* - `schema` -> `inputSchema` (optional)
|
|
68
|
+
* - `execute(input)` -> `execute(input, ctx)` - Mastra already calls
|
|
69
|
+
* the first arg with the parsed inputs, so the body shape is
|
|
70
|
+
* identical. The Mastra `context` arg is forwarded as the second
|
|
71
|
+
* parameter when the caller declares it.
|
|
72
|
+
* - `id`: optional. Defaults to a stable identifier derived from
|
|
73
|
+
* `description` (slugified, with a short hash suffix for
|
|
74
|
+
* uniqueness). Pass an explicit `id` when you need a stable string
|
|
75
|
+
* for tracing or MCP exposure.
|
|
76
|
+
*
|
|
77
|
+
* Reach for {@link createTool} when you need Mastra-only fields
|
|
78
|
+
* (`outputSchema`, `suspendSchema`, `requireApproval`, `mcp`, etc.).
|
|
79
|
+
*/
|
|
80
|
+
export function tool(opts: AppKitToolOptions): Tool {
|
|
81
|
+
const id = opts.id ?? deriveToolId(opts.description);
|
|
82
|
+
return createTool({
|
|
83
|
+
id,
|
|
84
|
+
description: opts.description,
|
|
85
|
+
...(opts.schema ? { inputSchema: opts.schema as never } : {}),
|
|
86
|
+
execute: opts.execute as never,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Input shape for the AppKit-style {@link tool} factory. A trimmed
|
|
92
|
+
* subset of Mastra's `createTool` options that mirrors the
|
|
93
|
+
* `@databricks/appkit/beta` `tool({ description, schema, execute })`
|
|
94
|
+
* signature.
|
|
95
|
+
*
|
|
96
|
+
* Generics are intentionally absent - inference flows through the
|
|
97
|
+
* caller's `schema` (typically a Zod object), and the `execute` body
|
|
98
|
+
* destructures naturally from that. Reach for {@link createTool} when
|
|
99
|
+
* you need the fully-typed input/output schemas wired explicitly.
|
|
100
|
+
*/
|
|
101
|
+
export interface AppKitToolOptions {
|
|
102
|
+
/** Optional stable identifier; auto-derived from `description` when omitted. */
|
|
103
|
+
id?: string;
|
|
104
|
+
/** Human-readable description shown to the model. Required. */
|
|
105
|
+
description: string;
|
|
106
|
+
/**
|
|
107
|
+
* Optional input schema (any Standard Schema instance, e.g. Zod).
|
|
108
|
+
* Maps to Mastra's `inputSchema`; passed through to the model
|
|
109
|
+
* verbatim.
|
|
110
|
+
*/
|
|
111
|
+
schema?: unknown;
|
|
112
|
+
/**
|
|
113
|
+
* Execute body. First arg is the parsed input (typed off `schema`
|
|
114
|
+
* when supplied), second arg is the full Mastra execution context
|
|
115
|
+
* (request context, abort signal, mastra instance) if you need it.
|
|
116
|
+
*/
|
|
117
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
118
|
+
execute: (input: any, context?: unknown) => unknown;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Build a deterministic Mastra tool id from a description.
|
|
123
|
+
* Delegates to {@link stringUtils.toUniqueSlug}: slug + always-on
|
|
124
|
+
* SHA-1 suffix so two tools with the same leading words don't
|
|
125
|
+
* collide in traces. Stable across runs.
|
|
126
|
+
*/
|
|
127
|
+
function deriveToolId(description: string): string {
|
|
128
|
+
return stringUtils.toUniqueSlug(description, { fallbackPrefix: "tool" });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Identity helper that brands a definition as a Mastra agent. Mirrors
|
|
133
|
+
* AppKit's `createAgent(def)` so the registration shape matches:
|
|
134
|
+
*
|
|
135
|
+
* ```ts
|
|
136
|
+
* const support = createAgent({
|
|
137
|
+
* instructions: "...",
|
|
138
|
+
* model: "databricks-claude-sonnet-4-6",
|
|
139
|
+
* tools(plugins) { return { ... }; },
|
|
140
|
+
* });
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* Returns the definition unchanged - the wrapper exists only to anchor
|
|
144
|
+
* type inference and to match the AppKit API surface.
|
|
145
|
+
*/
|
|
146
|
+
export function createAgent<T extends MastraAgentDefinition>(def: T): T {
|
|
147
|
+
return def;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Filter / rename options accepted by every plugin's `.toolkit()`
|
|
152
|
+
* method. Mirrors AppKit's `ToolkitOptions` verbatim so options pass
|
|
153
|
+
* through unchanged - the underlying AppKit plugin does the filtering
|
|
154
|
+
* and we just adapt the resulting entries into Mastra tools.
|
|
155
|
+
*/
|
|
156
|
+
export interface ToolkitOptions {
|
|
157
|
+
/**
|
|
158
|
+
* Key prefix prepended to every tool name. AppKit's default is
|
|
159
|
+
* `${pluginName}.` when omitted; pass an explicit `""` to drop it.
|
|
160
|
+
*/
|
|
161
|
+
prefix?: string;
|
|
162
|
+
/** Allowlist of local tool names. */
|
|
163
|
+
only?: string[];
|
|
164
|
+
/** Denylist of local tool names. */
|
|
165
|
+
except?: string[];
|
|
166
|
+
/** Remap specific local names to different keys. */
|
|
167
|
+
rename?: Record<string, string>;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Toolkit provider shape every entry in the {@link MastraPlugins} map
|
|
172
|
+
* exposes. Identical to AppKit's `PluginToolkitProvider` - any AppKit
|
|
173
|
+
* plugin that implements the standard `ToolProvider` interface
|
|
174
|
+
* (`getAgentTools` + `executeAgentTool` + `toolkit`) is reachable
|
|
175
|
+
* through this surface automatically.
|
|
176
|
+
*/
|
|
177
|
+
export interface MastraPluginToolkitProvider {
|
|
178
|
+
/**
|
|
179
|
+
* Returns a Mastra-shaped tools record adapted from the plugin's
|
|
180
|
+
* agent tools. Each tool dispatches back through the plugin's
|
|
181
|
+
* `executeAgentTool` so OBO auth and telemetry spans stay intact.
|
|
182
|
+
*/
|
|
183
|
+
toolkit(opts?: ToolkitOptions): MastraTools;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Plugin map handed to the function form of
|
|
188
|
+
* {@link MastraAgentDefinition.tools}. Mirrors AppKit's `Plugins`
|
|
189
|
+
* type exactly: a string-keyed record where every value exposes
|
|
190
|
+
* `.toolkit(opts)`.
|
|
191
|
+
*
|
|
192
|
+
* Implemented as a runtime Proxy that auto-discovers any registered
|
|
193
|
+
* AppKit plugin implementing the standard `ToolProvider` interface
|
|
194
|
+
* (`analytics`, `files`, `lakebase`, `genie`, plus any third-party
|
|
195
|
+
* plugin that does the same). Unknown names resolve to `undefined`
|
|
196
|
+
* at runtime, so guard with `?.` and `?? {}` when spreading from a
|
|
197
|
+
* plugin that may not be registered in every environment.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```ts
|
|
201
|
+
* createAgent({
|
|
202
|
+
* instructions: "...",
|
|
203
|
+
* tools(plugins) {
|
|
204
|
+
* return {
|
|
205
|
+
* ...plugins.analytics.toolkit(),
|
|
206
|
+
* ...plugins.files.toolkit({ only: ["uploads.read"] }),
|
|
207
|
+
* get_weather: tool({
|
|
208
|
+
* description: "Weather",
|
|
209
|
+
* schema: z.object({ city: z.string() }),
|
|
210
|
+
* execute: async ({ city }) => `Sunny in ${city}`,
|
|
211
|
+
* }),
|
|
212
|
+
* };
|
|
213
|
+
* },
|
|
214
|
+
* });
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
export type MastraPlugins = Record<string, MastraPluginToolkitProvider>;
|
|
218
|
+
|
|
219
|
+
/** Function form of {@link MastraAgentDefinition.tools}. */
|
|
220
|
+
export type MastraToolsFn = (
|
|
221
|
+
plugins: MastraPlugins,
|
|
222
|
+
) => MastraTools | Promise<MastraTools>;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* A code-defined Mastra agent. Mirrors the shape AppKit's `agents`
|
|
226
|
+
* plugin uses for `AgentDefinition`. The registry key under
|
|
227
|
+
* `config.agents` is what `chatRoute` matches on; `name` is purely
|
|
228
|
+
* informational (defaults to the key).
|
|
229
|
+
*/
|
|
230
|
+
export interface MastraAgentDefinition {
|
|
231
|
+
/** Display name used as `Agent.name`. Defaults to the registry key. */
|
|
232
|
+
name?: string;
|
|
233
|
+
/** Optional long-form description; surfaced as `Agent.description`. */
|
|
234
|
+
description?: string;
|
|
235
|
+
/** System prompt body. */
|
|
236
|
+
instructions: string;
|
|
237
|
+
/**
|
|
238
|
+
* Per-agent model override.
|
|
239
|
+
*
|
|
240
|
+
* - `undefined` (default): falls back to the workspace
|
|
241
|
+
* `/serving-endpoints` resolver that {@link buildModel} configures
|
|
242
|
+
* from the per-request `WorkspaceClient`.
|
|
243
|
+
* - `string`: shorthand for "use the default resolver but swap the
|
|
244
|
+
* `modelId`" (e.g. `"databricks-meta-llama-3-3-70b-instruct"`).
|
|
245
|
+
* - Any other Mastra `DynamicArgument<MastraModelConfig>`: passed
|
|
246
|
+
* straight through to `Agent.model`. Use this when you need full
|
|
247
|
+
* control over auth or providerId.
|
|
248
|
+
*/
|
|
249
|
+
model?: AgentConfig["model"] | string;
|
|
250
|
+
/**
|
|
251
|
+
* Per-agent tool record. Either a plain map or a callback that
|
|
252
|
+
* receives the typed {@link MastraPlugins} sibling-plugin index and
|
|
253
|
+
* returns a map. The callback runs exactly once at agent setup; the
|
|
254
|
+
* result is cached for the agent's lifetime.
|
|
255
|
+
*/
|
|
256
|
+
tools?: MastraTools | MastraToolsFn;
|
|
257
|
+
/**
|
|
258
|
+
* Per-agent semantic recall (PgVector) override. Cascades from
|
|
259
|
+
* `config.memory`; the agent value wins when set.
|
|
260
|
+
*
|
|
261
|
+
* - `undefined` (default): inherit `config.memory`. When that's
|
|
262
|
+
* enabled, the agent **shares the plugin-level singleton `PgVector`
|
|
263
|
+
* instance** (cross-agent semantic recall across the same index).
|
|
264
|
+
* - `false`: disable semantic recall for this agent only.
|
|
265
|
+
* - `true`: enable using the shared singleton (same as default when
|
|
266
|
+
* plugin memory is enabled; useful to opt in when plugin disabled).
|
|
267
|
+
* - {@link MastraMemoryConfig} object: dedicated `PgVector` for this
|
|
268
|
+
* agent (private recall index). Bypasses the shared singleton.
|
|
269
|
+
*/
|
|
270
|
+
memory?: boolean | MastraMemoryConfigOverride;
|
|
271
|
+
/**
|
|
272
|
+
* Per-agent thread/message storage (`PostgresStore`) override.
|
|
273
|
+
* Cascades from `config.storage`; the agent value wins when set.
|
|
274
|
+
*
|
|
275
|
+
* - `undefined` (default): inherit `config.storage`. When that's
|
|
276
|
+
* enabled, the agent gets its **own per-agent `PostgresStore`**
|
|
277
|
+
* keyed by `schemaName: "mastra_<agentId>"` so threads and
|
|
278
|
+
* messages stay isolated between agents in the same database.
|
|
279
|
+
* - `false`: disable storage for this agent only (purely in-memory).
|
|
280
|
+
* - `true`: enable with the per-agent default schema.
|
|
281
|
+
* - {@link MastraStorageConfigOverride} object: dedicated
|
|
282
|
+
* `PostgresStore` config (custom schema, connection, etc.).
|
|
283
|
+
*/
|
|
284
|
+
storage?: boolean | MastraStorageConfigOverride;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Distributive `Omit` so unions in `PostgresStoreConfig` /
|
|
289
|
+
* `PgVectorConfig` keep their discriminants after the override types
|
|
290
|
+
* strip `id`. The built-in `Omit` collapses unions to one shape with
|
|
291
|
+
* common fields only, which loses the connection-style discriminants.
|
|
292
|
+
*/
|
|
293
|
+
type DistributiveOmit<T, K extends keyof never> = T extends unknown
|
|
294
|
+
? Omit<T, K>
|
|
295
|
+
: never;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* `PostgresStoreConfig` minus `id` - per-agent overrides accept any
|
|
299
|
+
* Mastra-supported storage shape. `id` is filled in automatically
|
|
300
|
+
* from the agent registry key so traces stay stable.
|
|
301
|
+
*/
|
|
302
|
+
export type MastraStorageConfigOverride = DistributiveOmit<
|
|
303
|
+
PostgresStoreConfig,
|
|
304
|
+
"id"
|
|
305
|
+
> & { id?: string };
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* `PgVectorConfig` minus `id` - per-agent overrides accept any
|
|
309
|
+
* Mastra-supported vector shape. `id` is filled in automatically
|
|
310
|
+
* from the agent registry key.
|
|
311
|
+
*/
|
|
312
|
+
export type MastraMemoryConfigOverride = DistributiveOmit<PgVectorConfig, "id"> & {
|
|
313
|
+
id?: string;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/** Output of {@link buildAgents}: resolved agents plus the default id. */
|
|
317
|
+
export interface BuiltAgents {
|
|
318
|
+
agents: Record<string, Agent>;
|
|
319
|
+
defaultAgentId: string;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Fallback agent id used when `config.agents` is omitted entirely. */
|
|
323
|
+
export const FALLBACK_AGENT_ID = "default";
|
|
324
|
+
|
|
325
|
+
const FALLBACK_AGENT_INSTRUCTIONS = `You are a data analyst. The user will ask questions about
|
|
326
|
+
business metrics and may share personal preferences you should remember across turns.
|
|
327
|
+
|
|
328
|
+
Rules:
|
|
329
|
+
|
|
330
|
+
1. Quote numbers exactly. Never invent data.
|
|
331
|
+
2. When the user states a preference or durable fact about themselves
|
|
332
|
+
("I'm in EU so use EUR", "always show me the SQL"), acknowledge that
|
|
333
|
+
you will remember it.
|
|
334
|
+
3. If you don't have enough information to answer, ask a clarifying
|
|
335
|
+
question instead of guessing.`;
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Style guardrails appended to every agent's `instructions` to curb
|
|
339
|
+
* common LLM-isms (em dashes, emojis, sycophantic openers, excessive
|
|
340
|
+
* hedging, throwaway closers). Appended rather than prepended so the
|
|
341
|
+
* agent's role/context comes first; the model's recency bias then
|
|
342
|
+
* helps the style rules dominate the response surface.
|
|
343
|
+
*
|
|
344
|
+
* Override globally via {@link MastraPluginConfig.styleInstructions}
|
|
345
|
+
* (pass `false` to disable entirely, or a string to replace).
|
|
346
|
+
*/
|
|
347
|
+
export const DEFAULT_STYLE_INSTRUCTIONS = `Output style:
|
|
348
|
+
|
|
349
|
+
- Plain prose. Use hyphens (-) only. Never use em dashes (—) or en dashes (–).
|
|
350
|
+
- Never use emojis.
|
|
351
|
+
- Skip openers like "Great question", "Absolutely", "I'd be happy to help".
|
|
352
|
+
- Skip closers like "Let me know if you have any questions".
|
|
353
|
+
- Skip self-disclaimers ("I should mention", "It's important to note").
|
|
354
|
+
- Answer directly. No preamble before the actual answer.
|
|
355
|
+
- Use lists and headers only when they clarify a multi-part answer; not for short replies.
|
|
356
|
+
- Quote numbers, code, identifiers, and tool output verbatim. Never paraphrase them.`;
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Resolve the style block to append to every agent's instructions.
|
|
360
|
+
* Returns `null` when the caller opted out (`styleInstructions: false`).
|
|
361
|
+
*/
|
|
362
|
+
function resolveStyleInstructions(config: MastraPluginConfig): string | null {
|
|
363
|
+
if (config.styleInstructions === false) return null;
|
|
364
|
+
if (typeof config.styleInstructions === "string") {
|
|
365
|
+
return config.styleInstructions;
|
|
366
|
+
}
|
|
367
|
+
return DEFAULT_STYLE_INSTRUCTIONS;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Join an agent's bespoke instructions with the resolved style block.
|
|
372
|
+
* Returns the bespoke text unchanged when the style block is disabled.
|
|
373
|
+
*/
|
|
374
|
+
function composeInstructions(
|
|
375
|
+
agentInstructions: string,
|
|
376
|
+
style: string | null,
|
|
377
|
+
): string {
|
|
378
|
+
if (!style) return agentInstructions;
|
|
379
|
+
return `${agentInstructions.trimEnd()}\n\n${style}`;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Resolve every entry in `config.agents` into a Mastra `Agent`
|
|
384
|
+
* instance. When `config.agents` is omitted the plugin registers a
|
|
385
|
+
* single built-in `default` analyst so the bare `mastra()` call still
|
|
386
|
+
* yields a working agent.
|
|
387
|
+
*
|
|
388
|
+
* Per-agent tool callbacks are invoked once with a typed
|
|
389
|
+
* {@link MastraPlugins} index built from registered sibling plugins
|
|
390
|
+
* (currently `genie`; extend `MastraPlugins` to surface more).
|
|
391
|
+
*
|
|
392
|
+
* @throws when `config.defaultAgent` is set to an id that isn't in the
|
|
393
|
+
* resolved registry; this is a wiring bug, not a runtime condition.
|
|
394
|
+
*/
|
|
395
|
+
export async function buildAgents(opts: {
|
|
396
|
+
config: MastraPluginConfig;
|
|
397
|
+
context: pluginUtils.PluginContextLike | undefined;
|
|
398
|
+
memoryBuilder?: MemoryBuilder;
|
|
399
|
+
log: logUtils.Logger;
|
|
400
|
+
}): Promise<BuiltAgents> {
|
|
401
|
+
const { config, context, memoryBuilder, log } = opts;
|
|
402
|
+
const definitions = resolveDefinitions(config);
|
|
403
|
+
const ids = Object.keys(definitions);
|
|
404
|
+
const defaultAgentId = config.defaultAgent ?? ids[0] ?? FALLBACK_AGENT_ID;
|
|
405
|
+
|
|
406
|
+
const plugins = buildPluginsMap(context);
|
|
407
|
+
const ambientTools = config.tools ?? {};
|
|
408
|
+
const style = resolveStyleInstructions(config);
|
|
409
|
+
const agents: Record<string, Agent> = {};
|
|
410
|
+
|
|
411
|
+
for (const [id, def] of Object.entries(definitions)) {
|
|
412
|
+
const tools = await resolveTools(def.tools, plugins, ambientTools);
|
|
413
|
+
const memory = memoryBuilder?.forAgent(id, def);
|
|
414
|
+
agents[id] = new Agent({
|
|
415
|
+
id,
|
|
416
|
+
name: def.name ?? id,
|
|
417
|
+
...(def.description !== undefined ? { description: def.description } : {}),
|
|
418
|
+
instructions: composeInstructions(def.instructions, style),
|
|
419
|
+
model: resolveModel(config, def.model),
|
|
420
|
+
tools,
|
|
421
|
+
...(memory ? { memory } : {}),
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (!agents[defaultAgentId]) {
|
|
426
|
+
throw new Error(
|
|
427
|
+
`mastra: defaultAgent "${defaultAgentId}" not found in registered agents (${ids.join(", ") || "none"})`,
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
log.info("agents registered", { ids, defaultAgentId });
|
|
432
|
+
return { agents, defaultAgentId };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Normalize `config.agents` into a `Record<id, definition>`. Accepts
|
|
437
|
+
* any of the three shapes documented on
|
|
438
|
+
* {@link MastraPluginConfig.agents}:
|
|
439
|
+
*
|
|
440
|
+
* - Record - returned as-is when non-empty.
|
|
441
|
+
* - Single definition (detected via the required `instructions`
|
|
442
|
+
* field) - keyed by `slugify(def.name)` or `FALLBACK_AGENT_ID`.
|
|
443
|
+
* - Array - keyed by `slugify(def.name)` or `agent_${i}`; duplicate
|
|
444
|
+
* slugs fail loudly so users know to set explicit names.
|
|
445
|
+
*
|
|
446
|
+
* Omitted or empty inputs fall back to a single built-in analyst so
|
|
447
|
+
* the bare `mastra()` call still mounts a working chat route.
|
|
448
|
+
*/
|
|
449
|
+
function resolveDefinitions(
|
|
450
|
+
config: MastraPluginConfig,
|
|
451
|
+
): Record<string, MastraAgentDefinition> {
|
|
452
|
+
const input = config.agents;
|
|
453
|
+
if (!input) return fallbackDefinitions();
|
|
454
|
+
|
|
455
|
+
if (Array.isArray(input)) {
|
|
456
|
+
if (input.length === 0) return fallbackDefinitions();
|
|
457
|
+
const out: Record<string, MastraAgentDefinition> = {};
|
|
458
|
+
input.forEach((def, i) => {
|
|
459
|
+
const key = deriveAgentKey(def, i);
|
|
460
|
+
if (out[key]) {
|
|
461
|
+
throw new Error(
|
|
462
|
+
`mastra: duplicate agent id "${key}" derived from name "${def.name ?? ""}"; ` +
|
|
463
|
+
`set unique \`name\`s on each definition`,
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
out[key] = def;
|
|
467
|
+
});
|
|
468
|
+
return out;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Single-definition shorthand: an agent always has `instructions: string`,
|
|
472
|
+
// a record-of-agents never has that field directly.
|
|
473
|
+
if (typeof (input as MastraAgentDefinition).instructions === "string") {
|
|
474
|
+
const def = input as MastraAgentDefinition;
|
|
475
|
+
const key = deriveAgentKey(def);
|
|
476
|
+
return { [key]: def };
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const record = input as Record<string, MastraAgentDefinition>;
|
|
480
|
+
if (Object.keys(record).length === 0) return fallbackDefinitions();
|
|
481
|
+
return record;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/** Derive a registry id from a definition's `name`, with a fallback. */
|
|
485
|
+
function deriveAgentKey(def: MastraAgentDefinition, index?: number): string {
|
|
486
|
+
if (def.name) {
|
|
487
|
+
const slug = stringUtils.toIdentifier(def.name);
|
|
488
|
+
if (slug) return slug;
|
|
489
|
+
}
|
|
490
|
+
return index === undefined ? FALLBACK_AGENT_ID : `agent_${index}`;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/** Built-in fallback registry used when `agents` is omitted / empty. */
|
|
494
|
+
function fallbackDefinitions(): Record<string, MastraAgentDefinition> {
|
|
495
|
+
return {
|
|
496
|
+
[FALLBACK_AGENT_ID]: {
|
|
497
|
+
name: "Default Agent",
|
|
498
|
+
instructions: FALLBACK_AGENT_INSTRUCTIONS,
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Pick the effective model spec for an agent. Fallback ladder, in
|
|
505
|
+
* order:
|
|
506
|
+
*
|
|
507
|
+
* 1. Per-agent `def.model` (string sugar or `DynamicArgument`).
|
|
508
|
+
* 2. Plugin-level `config.defaultModel` (string sugar or
|
|
509
|
+
* `DynamicArgument`) - mirrors AppKit's `agents({ defaultModel })`.
|
|
510
|
+
* 3. The auto-resolver that mints user-scoped tokens against
|
|
511
|
+
* `/serving-endpoints` via {@link buildModel}.
|
|
512
|
+
*
|
|
513
|
+
* String values are treated as `modelId` sugar and threaded through
|
|
514
|
+
* `buildModel`'s override hook so the runtime fuzzy matcher and the
|
|
515
|
+
* per-request `X-Mastra-Model` override layer on top of the static
|
|
516
|
+
* choice. Non-string `DynamicArgument`s are passed through verbatim;
|
|
517
|
+
* callers that need full control over `providerId` / `headers` /
|
|
518
|
+
* `modelId` bypass the resolver pipeline entirely.
|
|
519
|
+
*/
|
|
520
|
+
function resolveModel(
|
|
521
|
+
config: MastraPluginConfig,
|
|
522
|
+
override: MastraAgentDefinition["model"],
|
|
523
|
+
): AgentConfig["model"] {
|
|
524
|
+
const effective = override ?? config.defaultModel;
|
|
525
|
+
if (effective === undefined) {
|
|
526
|
+
return ({ requestContext }) => buildModel(config, requestContext);
|
|
527
|
+
}
|
|
528
|
+
if (typeof effective === "string") {
|
|
529
|
+
const modelId = effective;
|
|
530
|
+
return ({ requestContext }) => buildModel(config, requestContext, { modelId });
|
|
531
|
+
}
|
|
532
|
+
return effective;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Resolve a definition's `tools` field to a flat `MastraTools` record,
|
|
537
|
+
* merging in plugin-level ambient tools (per-agent tools win on key
|
|
538
|
+
* collision). Callback errors propagate verbatim so the original stack
|
|
539
|
+
* survives - the caller already knows which agent was registering.
|
|
540
|
+
*/
|
|
541
|
+
async function resolveTools(
|
|
542
|
+
defTools: MastraAgentDefinition["tools"],
|
|
543
|
+
plugins: MastraPlugins,
|
|
544
|
+
ambientTools: MastraTools,
|
|
545
|
+
): Promise<MastraTools> {
|
|
546
|
+
if (!defTools) return { ...ambientTools };
|
|
547
|
+
const resolved = typeof defTools === "function" ? await defTools(plugins) : defTools;
|
|
548
|
+
return { ...ambientTools, ...resolved };
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Build the {@link MastraPlugins} runtime proxy handed to
|
|
553
|
+
* `tools(plugins)` callbacks.
|
|
554
|
+
*
|
|
555
|
+
* Implemented as a `Proxy` over the AppKit plugin context so
|
|
556
|
+
* `plugins.<name>` resolves at first access. Any sibling plugin that
|
|
557
|
+
* implements AppKit's standard `ToolProvider` interface
|
|
558
|
+
* (`toolkit(opts?)` + `executeAgentTool(name, args, signal?)`) is
|
|
559
|
+
* auto-adapted into Mastra tools. Unknown names return `undefined`,
|
|
560
|
+
* matching AppKit's `Plugins` semantics so `plugins.foo?.toolkit()`
|
|
561
|
+
* remains safe in environments where `foo` isn't registered.
|
|
562
|
+
*
|
|
563
|
+
* `genie` is special-cased to swap the generic AppKit toolkit (which
|
|
564
|
+
* runs `executeAgentTool` and only emits a single final `tool-result`
|
|
565
|
+
* chunk per call) for the streaming-aware tools built by
|
|
566
|
+
* {@link buildGenieProvider}. The streaming variant forwards each
|
|
567
|
+
* Genie wire event (status, SQL, row counts, errors) out through the
|
|
568
|
+
* Mastra `ctx.writer`, so the UI gets `tool-output` chunks in real
|
|
569
|
+
* time instead of staring at a spinner for the full Genie round-trip.
|
|
570
|
+
*/
|
|
571
|
+
function buildPluginsMap(
|
|
572
|
+
context: pluginUtils.PluginContextLike | undefined,
|
|
573
|
+
): MastraPlugins {
|
|
574
|
+
const cache = new Map<string, MastraPluginToolkitProvider | null>();
|
|
575
|
+
return new Proxy({} as MastraPlugins, {
|
|
576
|
+
get(_target, propName) {
|
|
577
|
+
if (typeof propName !== "string") return undefined;
|
|
578
|
+
if (cache.has(propName)) return cache.get(propName) ?? undefined;
|
|
579
|
+
const provider = resolveProvider(context, propName);
|
|
580
|
+
cache.set(propName, provider);
|
|
581
|
+
return provider ?? undefined;
|
|
582
|
+
},
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Pick the right {@link MastraPluginToolkitProvider} for a sibling
|
|
588
|
+
* plugin lookup. Returns the streaming-aware Genie adapter when the
|
|
589
|
+
* caller asks for `genie`; falls back to the generic AppKit
|
|
590
|
+
* `ToolProvider` adapter for every other plugin name.
|
|
591
|
+
*/
|
|
592
|
+
function resolveProvider(
|
|
593
|
+
context: pluginUtils.PluginContextLike | undefined,
|
|
594
|
+
propName: string,
|
|
595
|
+
): MastraPluginToolkitProvider | null {
|
|
596
|
+
if (propName === "genie") {
|
|
597
|
+
const geniePlugin = pluginUtils.instance(context, genie);
|
|
598
|
+
if (!geniePlugin) return null;
|
|
599
|
+
return buildGenieProvider(geniePlugin) as MastraPluginToolkitProvider;
|
|
600
|
+
}
|
|
601
|
+
const plugin = context?.getPlugins().get(propName);
|
|
602
|
+
return adaptPluginToolkit(plugin);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* AppKit `ToolProvider` shape we duck-type against any registered
|
|
607
|
+
* plugin. Defined structurally to avoid coupling to AppKit's internal
|
|
608
|
+
* type module layout.
|
|
609
|
+
*/
|
|
610
|
+
interface AppKitToolkitProvider {
|
|
611
|
+
toolkit?: (opts?: ToolkitOptions) => Record<string, AppKitToolkitEntry>;
|
|
612
|
+
executeAgentTool?: (
|
|
613
|
+
name: string,
|
|
614
|
+
args: unknown,
|
|
615
|
+
signal?: AbortSignal,
|
|
616
|
+
) => Promise<unknown>;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/** Single entry returned by an AppKit plugin's `.toolkit(opts)` call. */
|
|
620
|
+
interface AppKitToolkitEntry {
|
|
621
|
+
pluginName: string;
|
|
622
|
+
localName: string;
|
|
623
|
+
def: {
|
|
624
|
+
name: string;
|
|
625
|
+
description: string;
|
|
626
|
+
parameters: unknown;
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Adapt an AppKit `ToolProvider` plugin instance into a
|
|
632
|
+
* {@link MastraPluginToolkitProvider}. Returns `null` for any plugin
|
|
633
|
+
* that doesn't implement both `toolkit` and `executeAgentTool` (e.g.
|
|
634
|
+
* `server`, `lakebase` when used only as a Postgres pool, etc.).
|
|
635
|
+
*/
|
|
636
|
+
function adaptPluginToolkit(plugin: unknown): MastraPluginToolkitProvider | null {
|
|
637
|
+
if (!plugin || typeof plugin !== "object") return null;
|
|
638
|
+
const p = plugin as AppKitToolkitProvider;
|
|
639
|
+
if (typeof p.toolkit !== "function" || typeof p.executeAgentTool !== "function") {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
return {
|
|
643
|
+
toolkit(opts?: ToolkitOptions): MastraTools {
|
|
644
|
+
const entries = p.toolkit!(opts);
|
|
645
|
+
const tools: MastraTools = {};
|
|
646
|
+
for (const [key, entry] of Object.entries(entries)) {
|
|
647
|
+
tools[key] = toolkitEntryToMastraTool(entry, p);
|
|
648
|
+
}
|
|
649
|
+
return tools;
|
|
650
|
+
},
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Wrap a single {@link AppKitToolkitEntry} as a Mastra tool whose
|
|
656
|
+
* `execute` dispatches back through `plugin.executeAgentTool(...)` so
|
|
657
|
+
* AppKit's OBO auth (`asUser`) and telemetry spans stay intact. JSON
|
|
658
|
+
* Schema parameters pass through unchanged - Mastra's `PublicSchema`
|
|
659
|
+
* accepts `JSONSchema7` directly via `@mastra/schema-compat`.
|
|
660
|
+
*/
|
|
661
|
+
function toolkitEntryToMastraTool(
|
|
662
|
+
entry: AppKitToolkitEntry,
|
|
663
|
+
plugin: AppKitToolkitProvider,
|
|
664
|
+
): Tool {
|
|
665
|
+
return createTool({
|
|
666
|
+
id: `${entry.pluginName}__${entry.localName}`,
|
|
667
|
+
description: entry.def.description,
|
|
668
|
+
...(entry.def.parameters ? { inputSchema: entry.def.parameters as never } : {}),
|
|
669
|
+
execute: async (input: unknown, context: unknown) => {
|
|
670
|
+
const signal = (context as { abortSignal?: AbortSignal } | undefined)
|
|
671
|
+
?.abortSignal;
|
|
672
|
+
return plugin.executeAgentTool!(entry.localName, input, signal);
|
|
673
|
+
},
|
|
674
|
+
});
|
|
675
|
+
}
|