@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/README.md
ADDED
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
# @dbx-tools/appkit-mastra
|
|
2
|
+
|
|
3
|
+
An AppKit plugin that hosts [Mastra](https://mastra.ai) agents inside a
|
|
4
|
+
Databricks App with user-scoped workspace auth (OBO), optional
|
|
5
|
+
Lakebase-backed memory, and an AI SDK chat route the React client can
|
|
6
|
+
consume with `useChat()`.
|
|
7
|
+
|
|
8
|
+
The plugin is designed so that wiring it up looks the same as the
|
|
9
|
+
AppKit
|
|
10
|
+
`[agents](https://developers.databricks.com/docs/appkit/v0/plugins/agents)`
|
|
11
|
+
plugin - same `createAgent` / `tool` helpers, same `tools(plugins)`
|
|
12
|
+
callback shape, same `ToolkitOptions`. Switching between the two for a
|
|
13
|
+
given agent is a one-line import change.
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
The pattern below is the direct counterpart of AppKit's `agents` plugin
|
|
18
|
+
example - swap `agents` for `mastra` and the imports stay structurally
|
|
19
|
+
identical:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { analytics, createApp, files, lakebase, server } from "@databricks/appkit";
|
|
23
|
+
import { createAgent, mastra, tool } from "@dbx-tools/appkit-mastra";
|
|
24
|
+
import { z } from "zod";
|
|
25
|
+
|
|
26
|
+
const support = createAgent({
|
|
27
|
+
instructions: "You help customers with data and files.",
|
|
28
|
+
tools(plugins) {
|
|
29
|
+
return {
|
|
30
|
+
...plugins.analytics.toolkit(), // every analytics tool
|
|
31
|
+
...plugins.files.toolkit({ only: ["uploads.read"] }), // filtered subset
|
|
32
|
+
get_weather: tool({
|
|
33
|
+
description: "Weather",
|
|
34
|
+
schema: z.object({ city: z.string() }),
|
|
35
|
+
execute: async ({ city }) => `Sunny in ${city}`,
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await createApp({
|
|
42
|
+
plugins: [
|
|
43
|
+
server(),
|
|
44
|
+
analytics(),
|
|
45
|
+
files(),
|
|
46
|
+
// Drop `lakebase()` in and `mastra` auto-enables per-agent thread
|
|
47
|
+
// storage (`schemaName: "mastra_<agentId>"`) plus a shared
|
|
48
|
+
// semantic-recall vector index. Skip it for a stateless agent.
|
|
49
|
+
lakebase(),
|
|
50
|
+
mastra({ agents: support }),
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`createAgent` is a no-op identity helper that anchors type inference.
|
|
56
|
+
`tool` is the AppKit-shaped factory (`{ description, schema, execute }`)
|
|
57
|
+
that auto-adapts to Mastra's `createTool` under the hood.
|
|
58
|
+
|
|
59
|
+
Memory + storage cascades:
|
|
60
|
+
|
|
61
|
+
- **No `lakebase()` registered** ▸ agent is fully stateless. No threads,
|
|
62
|
+
no recall. Same as `mastra()` alone.
|
|
63
|
+
- `**lakebase()` registered, no `storage` / `memory` config** ▸ both
|
|
64
|
+
auto-turn on. Each agent gets its own `PostgresStore` schema; every
|
|
65
|
+
agent shares one `PgVector` recall index.
|
|
66
|
+
- **Per-agent opt-out** ▸ `createAgent({ ..., memory: false, storage: false })`
|
|
67
|
+
for routing / one-shot agents that don't need history.
|
|
68
|
+
- **Per-agent override** ▸ pass a `PgVectorConfig` / `PostgresStoreConfig` object on the agent for a private index or a shared external schema.
|
|
69
|
+
|
|
70
|
+
See [Memory + storage](#memory--storage) for the full cascade and worked
|
|
71
|
+
examples.
|
|
72
|
+
|
|
73
|
+
On the React side, never hardcode `/api/mastra/...`. Pull the published
|
|
74
|
+
paths from `usePluginClientConfig` and use the `chatUrl` helper. Import
|
|
75
|
+
them from the dependency-free `@dbx-tools/appkit-mastra-shared` package
|
|
76
|
+
so your browser bundle doesn't pull in `pg`, `fastembed`, or Mastra:
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import { usePluginClientConfig } from "@databricks/appkit-ui/react";
|
|
80
|
+
import { chatUrl, type MastraClientConfig } from "@dbx-tools/appkit-mastra-shared";
|
|
81
|
+
import { useChat } from "@ai-sdk/react";
|
|
82
|
+
import { DefaultChatTransport } from "ai";
|
|
83
|
+
import { useMemo } from "react";
|
|
84
|
+
|
|
85
|
+
function Chat() {
|
|
86
|
+
const config = usePluginClientConfig<MastraClientConfig>("mastra");
|
|
87
|
+
const transport = useMemo(
|
|
88
|
+
() => new DefaultChatTransport({ api: chatUrl(config) }),
|
|
89
|
+
[config],
|
|
90
|
+
);
|
|
91
|
+
const { messages, sendMessage } = useChat({ transport });
|
|
92
|
+
// ...
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
See [Client wiring](#client-wiring) for the full `MastraClientConfig`
|
|
97
|
+
shape and per-agent selection.
|
|
98
|
+
|
|
99
|
+
## Sensible defaults
|
|
100
|
+
|
|
101
|
+
The plugin is opinionated about what "no config" should mean. Everything
|
|
102
|
+
below can be overridden, but the bare path works:
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
| Scenario | What you get |
|
|
106
|
+
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
107
|
+
| `mastra()` | One built-in `default` analyst agent, model from `/serving-endpoints`; memory + storage auto-on if the `lakebase` plugin is registered |
|
|
108
|
+
| `mastra({ agents: def })` | Single-agent shorthand - `def` is registered and marked as default |
|
|
109
|
+
| `mastra({ agents: [def1, def2] })` | Array shorthand - keys come from each `def.name` (or `agent_<i>`); first one is default |
|
|
110
|
+
| `mastra({ agents: { x: def, y: def }})` | Record - keys are the registered ids; first key is the default |
|
|
111
|
+
| No `defaultAgent` set | First registered agent wins |
|
|
112
|
+
| No `model` on an agent | Falls back to `config.defaultModel`, then `DATABRICKS_SERVING_ENDPOINT_NAME`, then walks `defaultModelFallbacks` (Thinking → Balanced → Fast tiers, Claude ↔ GPT ↔ Gemini interleaved within each, then open-weights) and picks the first endpoint actually present in the workspace |
|
|
113
|
+
| No `name` on a definition | Uses the registry key as `Agent.name` |
|
|
114
|
+
| No `tools` on an agent | Inherits plugin-level `config.tools` ambient set (if any) |
|
|
115
|
+
| No `storage` / `memory` and `lakebase()` registered | Both auto-default to `true`. Pass `false` (or a custom config object) on the plugin or an agent to opt out / override. |
|
|
116
|
+
| `storage` / `memory` on an agent | Cascades from plugin: storage namespaces **per-agent** (`schemaName: "mastra_<agentId>"`), vector recall is **shared** across agents on one `PgVector` singleton. |
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
Every field on `MastraAgentDefinition`:
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
| Field | Description |
|
|
123
|
+
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
124
|
+
| `name` | Display name. Defaults to the registry key. |
|
|
125
|
+
| `description` | Long-form description, surfaced as `Agent.description`. |
|
|
126
|
+
| `instructions` | System prompt body. Required. |
|
|
127
|
+
| `model` | Per-agent model override. String = `modelId` sugar; otherwise a Mastra `DynamicArgument`. |
|
|
128
|
+
| `tools` | Plain record OR `(plugins) => tools` callback (see below). |
|
|
129
|
+
| `memory` | `false`, `true`, or a `PgVectorConfig`. Cascades from `config.memory`. **Default: shared singleton `PgVector` across every agent** - object override switches to a dedicated index. |
|
|
130
|
+
| `storage` | `false`, `true`, or a `PostgresStoreConfig`. Cascades from `config.storage`. **Default: per-agent namespace** via `schemaName: "mastra_<agentId>"` so threads stay isolated. |
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
Plugin-level fields:
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
| Field | Description |
|
|
137
|
+
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
138
|
+
| `agents` | The registry. Omit for a built-in `default` analyst. |
|
|
139
|
+
| `defaultAgent` | Id that `chatRoute` binds to when no `:agentId` is supplied. Defaults to first registered. |
|
|
140
|
+
| `defaultModel` | Fallback for any agent that omits `model`. Same shape (string sugar or `DynamicArgument`). |
|
|
141
|
+
| `defaultModelFallbacks` | Priority-ordered list walked when no `model` / env / override is set. First entry whose endpoint exists in the workspace wins. Default chains the three `ModelTier`s (Thinking → Balanced → Fast); within each tier providers are interleaved Claude ↔ GPT ↔ Gemini with open-weights appended. Compose your own with `modelsForTier(ModelTier.Fast)` or read straight from `MODEL_CATALOG`. |
|
|
142
|
+
| `tools` | Ambient tools merged into every agent (per-agent tools win on collisions). |
|
|
143
|
+
| `storage` | `undefined` (default) auto-enables when `lakebase()` is registered; `true` does the same explicitly; `false` opts out; an object opens a dedicated `PostgresStore`. Per-agent default is `schemaName: "mastra_<agentId>"`. |
|
|
144
|
+
| `memory` | `undefined` (default) auto-enables when `lakebase()` is registered; `true` does the same explicitly; `false` opts out; an object opens a dedicated `PgVector`. Default behavior: one shared `PgVector` singleton across every agent. |
|
|
145
|
+
| `modelFuzzyMatch` | `false` to disable fuzzy snapping of model ids against the workspace's Model Serving catalogue. Defaults to `true`. |
|
|
146
|
+
| `modelFuzzyThreshold` | Fuse.js score threshold (`0` exact, `1` anything). Defaults to `0.4`. |
|
|
147
|
+
| `modelCacheTtlMs` | TTL for the cached endpoint list, per workspace host. Defaults to 5 minutes. Concurrent callers share one in-flight fetch. |
|
|
148
|
+
| `modelOverride` | `false` to disable per-request `X-Mastra-Model` / `?model=` / body overrides. Defaults to `true`. |
|
|
149
|
+
| `styleInstructions` | Style guardrails appended to every agent's `instructions` to curb LLM-isms (em dashes, emojis, sycophantic openers, throwaway closers). `undefined` (default) uses the built-in `DEFAULT_STYLE_INSTRUCTIONS`; a string replaces it; `false` disables. |
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
## The `tools(plugins)` callback
|
|
153
|
+
|
|
154
|
+
Each registered agent can supply either a static `tools: { ... }` record
|
|
155
|
+
or a `tools(plugins)` callback. The returned record accepts **any**
|
|
156
|
+
tool shape Mastra understands:
|
|
157
|
+
|
|
158
|
+
- Mastra tools built with `createTool` or `new Tool(...)`
|
|
159
|
+
- AppKit-shaped tools built with the `tool()` wrapper (below)
|
|
160
|
+
- Vercel AI SDK tools (`tool({...})` from `ai`)
|
|
161
|
+
- Provider-defined tools (e.g. `openai.tools.webSearch(...)`)
|
|
162
|
+
- Toolkits returned from `plugins.<name>.toolkit(...)`
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
tools(plugins) {
|
|
166
|
+
return {
|
|
167
|
+
// Sibling plugin toolkits.
|
|
168
|
+
...plugins.analytics.toolkit(), // every analytics tool
|
|
169
|
+
...plugins.files.toolkit({ only: ["uploads.read"] }), // filtered subset
|
|
170
|
+
|
|
171
|
+
// AppKit-shaped inline tool.
|
|
172
|
+
get_weather: tool({
|
|
173
|
+
description: "Weather",
|
|
174
|
+
schema: z.object({ city: z.string() }),
|
|
175
|
+
execute: async ({ city }) => `Sunny in ${city}`,
|
|
176
|
+
}),
|
|
177
|
+
|
|
178
|
+
// Existing Mastra tool dropped in unchanged.
|
|
179
|
+
save_doc: existingMastraTool,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`plugins` is a typed `Record<string, { toolkit(opts?): tools }>` matching
|
|
185
|
+
AppKit's `Plugins` type. It's backed by a runtime Proxy that
|
|
186
|
+
**auto-discovers any registered AppKit `ToolProvider` plugin** -
|
|
187
|
+
`analytics`, `files`, `lakebase`, `genie`, plus any third-party plugin
|
|
188
|
+
that implements the standard `getAgentTools()` + `executeAgentTool()` +
|
|
189
|
+
`toolkit()` interface. Tool calls dispatch through the plugin's
|
|
190
|
+
`executeAgentTool`, so OBO auth (`asUser`) and telemetry spans stay
|
|
191
|
+
intact.
|
|
192
|
+
|
|
193
|
+
`plugins.genie` is special-cased: it swaps the generic AppKit toolkit
|
|
194
|
+
(which only emits a single final result chunk per call) for a
|
|
195
|
+
streaming-aware tools record built on top of the plugin's
|
|
196
|
+
`exports().sendMessage` AsyncGenerator. Each Genie wire event
|
|
197
|
+
(`FETCHING_METADATA`, `ASKING_AI`, `EXECUTING_QUERY`, attached SQL,
|
|
198
|
+
`query_result` row counts, errors) is normalised into a `GenieProgress`
|
|
199
|
+
payload and pushed mid-flight through Mastra's `ctx.writer`, surfacing
|
|
200
|
+
as `tool-output` chunks the React client can render as inline status
|
|
201
|
+
pills, SQL blocks, and row-count badges while the LLM is still waiting
|
|
202
|
+
on the final `tool-result`. Tool ids are stable: `genie` for the
|
|
203
|
+
default alias, `genie-<alias>` for additional aliases, and one shared
|
|
204
|
+
`genie_get_conversation`. The LLM still receives a single clean final
|
|
205
|
+
payload so the streaming UI doesn't leak into the assistant message.
|
|
206
|
+
|
|
207
|
+
Plugins that aren't registered (or don't implement the toolkit
|
|
208
|
+
interface) resolve to `undefined` at runtime, so guard with `?.` /
|
|
209
|
+
`?? {}` when a backing plugin is optional in some environments:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
tools(plugins) {
|
|
213
|
+
return {
|
|
214
|
+
...(plugins.analytics?.toolkit() ?? {}),
|
|
215
|
+
...(plugins.genie?.toolkit({ prefix: "g_" }) ?? {}),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`plugins.<name>.toolkit(opts)` accepts the same `ToolkitOptions` shape
|
|
221
|
+
AppKit's own toolkits expose (passed through verbatim):
|
|
222
|
+
|
|
223
|
+
- `prefix?: string` - prepended to every key (AppKit default: `${pluginName}.`)
|
|
224
|
+
- `only?: string[]` / `except?: string[]` - allow/deny list against the
|
|
225
|
+
local tool name
|
|
226
|
+
- `rename?: Record<string, string>` - remap individual keys
|
|
227
|
+
|
|
228
|
+
### `tool()` vs `createTool()`
|
|
229
|
+
|
|
230
|
+
The `tool()` factory mirrors `@databricks/appkit/beta`'s shape so
|
|
231
|
+
sharing tool code between the AppKit `agents` plugin and this one is a
|
|
232
|
+
single-line import change:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
import { tool, createTool } from "@dbx-tools/appkit-mastra";
|
|
236
|
+
|
|
237
|
+
// AppKit-shaped (description / schema / flat-arg execute).
|
|
238
|
+
const weather = tool({
|
|
239
|
+
description: "Weather",
|
|
240
|
+
schema: z.object({ city: z.string() }),
|
|
241
|
+
execute: async ({ city }) => `Sunny in ${city}`,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Full Mastra `createTool` (id required, inputSchema, advanced fields).
|
|
245
|
+
const saveDoc = createTool({
|
|
246
|
+
id: "save-doc",
|
|
247
|
+
description: "Persist a document",
|
|
248
|
+
inputSchema: z.object({ key: z.string(), value: z.any() }),
|
|
249
|
+
outputSchema: z.object({ saved: z.boolean() }),
|
|
250
|
+
requireApproval: true,
|
|
251
|
+
execute: async (input, ctx) => {
|
|
252
|
+
await ctx.mastra?.getStorage()?.set(input.key, input.value);
|
|
253
|
+
return { saved: true };
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
When `tool()`'s `id` is omitted it's auto-derived from a slugified
|
|
259
|
+
description plus a 6-char SHA-1 prefix - stable across runs so traces
|
|
260
|
+
stay readable. Pass an explicit `id` when you want to pin one.
|
|
261
|
+
|
|
262
|
+
Reach for `createTool` when you need Mastra-only fields (`outputSchema`,
|
|
263
|
+
`suspendSchema`, `requireApproval`, `mcp`, etc.).
|
|
264
|
+
|
|
265
|
+
## Model resolution
|
|
266
|
+
|
|
267
|
+
Each agent call resolves a `MastraModelConfig` lazily so concurrent
|
|
268
|
+
requests get distinct user identities. There are two paths through
|
|
269
|
+
the resolver depending on whether the caller asked for a specific
|
|
270
|
+
model.
|
|
271
|
+
|
|
272
|
+
### Explicit ask (override / agent / plugin / env)
|
|
273
|
+
|
|
274
|
+
When any of these is set the resolver fuzzy-matches that single id
|
|
275
|
+
against the live `/serving-endpoints` list, in priority order:
|
|
276
|
+
|
|
277
|
+
1. Per-request override (`X-Mastra-Model` header, `?model=` query,
|
|
278
|
+
or `model` / `modelId` body field; see below)
|
|
279
|
+
2. Per-agent `def.model` (string sugar or `DynamicArgument`)
|
|
280
|
+
3. Plugin-level `config.defaultModel`
|
|
281
|
+
4. `DATABRICKS_SERVING_ENDPOINT_NAME` env var
|
|
282
|
+
|
|
283
|
+
The matcher is `fuse.js` extended search with tokens split on
|
|
284
|
+
non-word characters and AND-joined. Exact matches win immediately;
|
|
285
|
+
loose tokens like `"claude sonnet"` snap to
|
|
286
|
+
`databricks-claude-sonnet-4-6`, `"llama 70b"` to
|
|
287
|
+
`databricks-meta-llama-3-3-70b-instruct`, `"DBRX"` to
|
|
288
|
+
`databricks-dbrx-instruct`, and so on. If no candidate scores below
|
|
289
|
+
`modelFuzzyThreshold` (default `0.4`) the input is returned verbatim
|
|
290
|
+
and Databricks surfaces the canonical 404.
|
|
291
|
+
|
|
292
|
+
### No explicit ask (tier-aware fallback list)
|
|
293
|
+
|
|
294
|
+
When nothing is set, the resolver walks an opinionated
|
|
295
|
+
priority-ordered list and returns **the first id that is actually
|
|
296
|
+
present in the workspace's endpoint listing**. This is how a workspace
|
|
297
|
+
without Claude Opus still gets a sensible default automatically -
|
|
298
|
+
the resolver skips ahead to whichever Sonnet / GPT-5 / Gemini / Llama
|
|
299
|
+
variant is wired up.
|
|
300
|
+
|
|
301
|
+
The catalogue is grouped two ways:
|
|
302
|
+
|
|
303
|
+
- By **capability tier** via the `ModelTier` enum:
|
|
304
|
+
`ModelTier.Thinking` (deepest reasoning), `ModelTier.Balanced`
|
|
305
|
+
(cost/latency sweet spot), `ModelTier.Fast` (cheap & quick for
|
|
306
|
+
classification / routing / simple summarisation).
|
|
307
|
+
- By **provider** within each tier: `claude`, `gpt`, `gemini`,
|
|
308
|
+
`openSource`.
|
|
309
|
+
|
|
310
|
+
Both views live on `MODEL_CATALOG[tier][provider]`. The walked
|
|
311
|
+
`FALLBACK_MODEL_IDS` chains the three tiers in descending power
|
|
312
|
+
(Thinking → Balanced → Fast); within each tier providers are
|
|
313
|
+
round-robin-zipped (Claude ↔ GPT ↔ Gemini) before the open-weights
|
|
314
|
+
tail is appended as the universal floor.
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
| Tier (most powerful first) | Claude | GPT | Gemini | Open weights |
|
|
318
|
+
| -------------------------- | -------------------------------- | ------------------------------------- | ------------------------------- | ---------------------------------------------- |
|
|
319
|
+
| `ModelTier.Thinking` | Opus 4.8 → 4.7 → 4.6 → 4.5 → 4.1 | 5.5 Pro | 3.1 Pro → 3 Pro → 2.5 Pro | Llama 4 Maverick, GPT-OSS 120B, Llama 3.1 405B |
|
|
320
|
+
| `ModelTier.Balanced` | Sonnet 4.6 → 4.5 → 4 | 5.5 → 5.4 → 5.2 → 5.1 → 5 | 3.5 Flash → 3 Flash → 2.5 Flash | Llama 3.3 70B, Qwen3-Next 80B, Qwen35 122B |
|
|
321
|
+
| `ModelTier.Fast` | Haiku 4.5 | 5.4 mini → 5.4 nano → 5 mini → 5 nano | 3.1 Flash Lite | GPT-OSS 20B, Gemma 3 12B, Llama 3.1 8B |
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
#### Pick a tier-appropriate model for one agent
|
|
325
|
+
|
|
326
|
+
Use `modelForTier(tier)` to grab the top of a tier as a string; the
|
|
327
|
+
agent-step resolver fuzzy-matches it against the live catalogue at
|
|
328
|
+
call time so it still works when the literal top pick isn't deployed.
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
import { createAgent, ModelTier, modelForTier } from "@dbx-tools/appkit-mastra";
|
|
332
|
+
|
|
333
|
+
const classifier = createAgent({
|
|
334
|
+
instructions: "Classify this email into one of: billing, support, spam.",
|
|
335
|
+
model: modelForTier(ModelTier.Fast),
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const planner = createAgent({
|
|
339
|
+
instructions: "Plan a multi-step data migration.",
|
|
340
|
+
model: modelForTier(ModelTier.Thinking),
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
#### Bias the plugin-level fallback toward a tier
|
|
345
|
+
|
|
346
|
+
`modelsForTier(tier)` returns the priority-ordered list for one tier;
|
|
347
|
+
pass it to `defaultModelFallbacks` to scope the auto-resolver:
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
import { mastra, ModelTier, modelsForTier } from "@dbx-tools/appkit-mastra";
|
|
351
|
+
|
|
352
|
+
mastra({
|
|
353
|
+
// All agents that omit `model` will land on a Fast-tier endpoint.
|
|
354
|
+
defaultModelFallbacks: modelsForTier(ModelTier.Fast),
|
|
355
|
+
});
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
#### Pin a custom approved subset
|
|
359
|
+
|
|
360
|
+
Mix in your own endpoint names (internal fine-tunes, regulated
|
|
361
|
+
allowlists, etc) in front of the catalogue:
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
mastra({
|
|
365
|
+
defaultModelFallbacks: [
|
|
366
|
+
"my-org-finetune-v2", // try internal endpoint first
|
|
367
|
+
"databricks-claude-sonnet-4-6", // approved fallback
|
|
368
|
+
],
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
If the workspace has none of the listed ids, the top fallback is
|
|
373
|
+
returned and Databricks surfaces the canonical error.
|
|
374
|
+
|
|
375
|
+
The endpoint list is cached per workspace host through AppKit's
|
|
376
|
+
built-in `CacheManager` (`CacheManager.getInstanceSync().getOrExecute`),
|
|
377
|
+
which is the TypeScript counterpart of Python's `cachetools.TTLCache`
|
|
378
|
+
plus `cachetools-async` rolled into one: per-entry TTL (default 5
|
|
379
|
+
minutes via `modelCacheTtlMs`), bounded size, in-flight request
|
|
380
|
+
coalescing (the manager's internal `inFlightRequests` map shares one
|
|
381
|
+
fetch across every concurrent caller), telemetry spans, and optional
|
|
382
|
+
Lakebase persistence when the `lakebase` plugin is wired up. No extra
|
|
383
|
+
dependency lives in this package; the catalogue piggybacks on whatever
|
|
384
|
+
storage backend AppKit picked at boot.
|
|
385
|
+
|
|
386
|
+
String values (`"databricks-claude-sonnet-4-6"`) are `modelId` sugar
|
|
387
|
+
layered on top of the auto-resolver - workspace URL, provider, and OBO
|
|
388
|
+
auth stay default. Pass a `DynamicArgument<MastraModelConfig>` on
|
|
389
|
+
`def.model` / `config.defaultModel` when you need full control over
|
|
390
|
+
auth, provider, or URL; that path bypasses the fuzzy matcher and
|
|
391
|
+
per-request override.
|
|
392
|
+
|
|
393
|
+
### `GET /api/mastra/models`
|
|
394
|
+
|
|
395
|
+
The plugin exposes the cached endpoint catalogue at `/models` (mounted
|
|
396
|
+
under the plugin prefix, default `/api/mastra`) so clients can populate
|
|
397
|
+
model pickers and validate `?model=` choices without a separate
|
|
398
|
+
Databricks SDK round-trip:
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
curl -s http://localhost:8000/api/mastra/models | jq
|
|
402
|
+
# {
|
|
403
|
+
# "endpoints": [
|
|
404
|
+
# { "name": "databricks-claude-sonnet-4-6", "task": "llm/v1/chat", "state": "READY", ... },
|
|
405
|
+
# { "name": "databricks-meta-llama-3-3-70b-instruct", ... },
|
|
406
|
+
# ...
|
|
407
|
+
# ]
|
|
408
|
+
# }
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Same payload from a sibling plugin or script (no HTTP round-trip):
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
import { pluginUtils } from "@dbx-tools/appkit-shared";
|
|
415
|
+
import { mastra } from "@dbx-tools/appkit-mastra";
|
|
416
|
+
|
|
417
|
+
const m = pluginUtils.require(this.context, mastra).exports();
|
|
418
|
+
const endpoints = await m.asUser(req).listModels(); // user-scoped
|
|
419
|
+
m.clearModelsCache(); // force the next call to re-fetch
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Per-request model override
|
|
423
|
+
|
|
424
|
+
Any in-flight request can pick a different backing endpoint without
|
|
425
|
+
redeploying. Sources, checked in priority order:
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
| Source | Example |
|
|
429
|
+
| ------------------------- | ------------------------------------------------ |
|
|
430
|
+
| `X-Mastra-Model` header | `curl -H 'X-Mastra-Model: claude-haiku' ...` |
|
|
431
|
+
| `?model=` query parameter | `POST /api/mastra/route/chat?model=llama-70b` |
|
|
432
|
+
| Body `model` or `modelId` | `{ "messages": [...], "model": "claude-haiku" }` |
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
The override flows through the same fuzzy matcher as static ids, so
|
|
436
|
+
`X-Mastra-Model: claude sonnet` still snaps to
|
|
437
|
+
`databricks-claude-sonnet-4-6`. Set `modelOverride: false` on the
|
|
438
|
+
plugin config to disable the override path entirely (e.g. for a
|
|
439
|
+
multi-tenant deployment where untrusted clients shouldn't pick the
|
|
440
|
+
endpoint).
|
|
441
|
+
|
|
442
|
+
## Memory + storage
|
|
443
|
+
|
|
444
|
+
Memory and storage are split into two independent knobs and both auto-on
|
|
445
|
+
the moment the `lakebase` plugin is registered. Bare `mastra()` next to
|
|
446
|
+
`lakebase()` already gets you per-agent threads + shared semantic recall;
|
|
447
|
+
zero extra config required.
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
| Knob | Default when `lakebase()` is registered | What it backs |
|
|
451
|
+
| --------- | ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
452
|
+
| `storage` | **Per-agent** `PostgresStore` namespaced by `schemaName: "mastra_<agentId>"` so threads + messages stay isolated. | Mastra threads, messages, working memory. |
|
|
453
|
+
| `memory` | **Shared singleton** `PgVector` across every agent (cross-agent semantic recall on one index). | RAG-style recall over past messages via FastEmbed vectors. |
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
Override either at the plugin level, the agent level, or both. The agent
|
|
457
|
+
value wins when set; otherwise the plugin value cascades.
|
|
458
|
+
|
|
459
|
+
```ts
|
|
460
|
+
mastra({
|
|
461
|
+
// Plugin defaults. Either field becomes the cascading baseline.
|
|
462
|
+
// Omit entirely to inherit "auto-on when lakebase is present".
|
|
463
|
+
storage: true, // (default behavior when lakebase is registered)
|
|
464
|
+
memory: true, // (default behavior when lakebase is registered)
|
|
465
|
+
|
|
466
|
+
agents: {
|
|
467
|
+
analyst: createAgent({
|
|
468
|
+
instructions: "...",
|
|
469
|
+
// No overrides: inherits the auto-on defaults above.
|
|
470
|
+
// - threads stored under schema "mastra_analyst"
|
|
471
|
+
// - recalls from the shared vector index
|
|
472
|
+
}),
|
|
473
|
+
|
|
474
|
+
router: createAgent({
|
|
475
|
+
instructions: "Stateless routing agent.",
|
|
476
|
+
// Opt out of both for a fully stateless agent.
|
|
477
|
+
storage: false,
|
|
478
|
+
memory: false,
|
|
479
|
+
}),
|
|
480
|
+
|
|
481
|
+
legal: createAgent({
|
|
482
|
+
instructions: "Compliance-bounded assistant.",
|
|
483
|
+
// Private vector index so legal's recall doesn't bleed into
|
|
484
|
+
// analyst's. Threads still get their own per-agent schema.
|
|
485
|
+
memory: { connectionString: process.env.LEGAL_PG_URL!, /* ... */ },
|
|
486
|
+
}),
|
|
487
|
+
|
|
488
|
+
archive: createAgent({
|
|
489
|
+
instructions: "Read-only archive viewer.",
|
|
490
|
+
// Pin to a specific schema (e.g. shared with another service).
|
|
491
|
+
storage: {
|
|
492
|
+
schemaName: "shared_history",
|
|
493
|
+
pool: archivePool,
|
|
494
|
+
},
|
|
495
|
+
}),
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
Notes:
|
|
501
|
+
|
|
502
|
+
- `PostgresStore` runs `CREATE SCHEMA IF NOT EXISTS` on `init()`, so
|
|
503
|
+
per-agent schemas spring into existence the first time an agent saves
|
|
504
|
+
a message. No bundle / migration step required.
|
|
505
|
+
- Disabling `lakebase()` from your plugin list while leaving `storage` /
|
|
506
|
+
`memory` truthy fails fast at setup with a clear "lakebase plugin not
|
|
507
|
+
registered" error.
|
|
508
|
+
- The `lakebase` plugin is declared as a **required** resource only when
|
|
509
|
+
`storage` / `memory` is explicitly truthy at registration time. Auto-on
|
|
510
|
+
defaults activate inside `setup:complete`, after lakebase is already
|
|
511
|
+
proven to be present.
|
|
512
|
+
|
|
513
|
+
## Runtime exports
|
|
514
|
+
|
|
515
|
+
Other plugins / route handlers can introspect the registry via the
|
|
516
|
+
`exports()` surface, modeled on AppKit's:
|
|
517
|
+
|
|
518
|
+
```ts
|
|
519
|
+
import { pluginUtils } from "@dbx-tools/appkit-shared";
|
|
520
|
+
import { mastra } from "@dbx-tools/appkit-mastra";
|
|
521
|
+
|
|
522
|
+
const m = pluginUtils.require(this.context, mastra).exports();
|
|
523
|
+
m.list(); // ["analyst", "helper"]
|
|
524
|
+
m.get("analyst"); // Agent | null
|
|
525
|
+
m.getDefault(); // Agent | null
|
|
526
|
+
m.getMastra(); // underlying Mastra instance (advanced)
|
|
527
|
+
m.listModels(); // Promise<ServingEndpointSummary[]> - cached + OBO when wrapped with asUser(req)
|
|
528
|
+
m.clearModelsCache(); // force the next listModels() to re-fetch
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
## Client wiring
|
|
532
|
+
|
|
533
|
+
`clientConfig()` publishes the mount paths, default agent id, and the
|
|
534
|
+
full registry to `usePluginClientConfig("mastra")` so the React client
|
|
535
|
+
never has to hardcode `/api/mastra` or rely on `DEFAULT_AGENT_ID`
|
|
536
|
+
constants. A tiny URL helper (`chatUrl`) and the `MastraClientConfig`
|
|
537
|
+
type ship from the standalone `@dbx-tools/appkit-mastra-shared`
|
|
538
|
+
package; that package is pure (no `pg` / `fastembed` / Mastra
|
|
539
|
+
dependencies) so it imports cleanly into Vite / Webpack / esbuild
|
|
540
|
+
builds.
|
|
541
|
+
|
|
542
|
+
```tsx
|
|
543
|
+
import { usePluginClientConfig } from "@databricks/appkit-ui/react";
|
|
544
|
+
import { chatUrl, type MastraClientConfig } from "@dbx-tools/appkit-mastra-shared";
|
|
545
|
+
import { useChat } from "@ai-sdk/react";
|
|
546
|
+
import { DefaultChatTransport } from "ai";
|
|
547
|
+
import { useMemo, useState } from "react";
|
|
548
|
+
|
|
549
|
+
function Chat() {
|
|
550
|
+
const config = usePluginClientConfig<MastraClientConfig>("mastra");
|
|
551
|
+
const [selected, setSelected] = useState<string>();
|
|
552
|
+
const api = chatUrl(config, selected); // defaults to config.defaultAgent
|
|
553
|
+
|
|
554
|
+
const transport = useMemo(() => new DefaultChatTransport({ api }), [api]);
|
|
555
|
+
const { messages, sendMessage } = useChat({ transport });
|
|
556
|
+
|
|
557
|
+
return (
|
|
558
|
+
<>
|
|
559
|
+
<select onChange={(e) => setSelected(e.target.value)}>
|
|
560
|
+
{config.agents.map((id) => (
|
|
561
|
+
<option key={id} value={id}>
|
|
562
|
+
{id}
|
|
563
|
+
</option>
|
|
564
|
+
))}
|
|
565
|
+
</select>
|
|
566
|
+
{/* render messages, etc. */}
|
|
567
|
+
</>
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
`MastraClientConfig` fields (all derived from the server-side plugin
|
|
573
|
+
mount, so a custom `mastra({ name: "myMastra" })` rewrites every path):
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
| Field | Example | Description |
|
|
577
|
+
| ------------------ | ----------------------------------- | -------------------------------------------------------- |
|
|
578
|
+
| `basePath` | `"/api/mastra"` | Plugin mount path. |
|
|
579
|
+
| `chatPath` | `"/api/mastra/route/chat"` | Default-agent chat URL. Use `chatUrl(config)` to get it. |
|
|
580
|
+
| `chatPathTemplate` | `"/api/mastra/route/chat/:agentId"` | OpenAPI-style template for tools / docs. |
|
|
581
|
+
| `modelsPath` | `"/api/mastra/models"` | `GET` cached endpoint catalogue. |
|
|
582
|
+
| `defaultAgent` | `"analyst"` | Agent id `chatRoute` binds to when none is supplied. |
|
|
583
|
+
| `agents` | `["analyst", "helper"]` | Every registered agent id in order. |
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
`chatUrl(config, agentId?)` returns `config.chatPath` for the default
|
|
587
|
+
agent (the registered `chatRoute` mount that omits `:agentId`), and
|
|
588
|
+
`${config.chatPath}/${encodeURIComponent(agentId)}` otherwise. Pure
|
|
589
|
+
function: no React, no hooks, safe in service workers and SSR.
|
|
590
|
+
|
|
591
|
+
## License
|
|
592
|
+
|
|
593
|
+
Apache-2.0
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppKit Mastra integration: {@link MastraPlugin} / {@link mastra},
|
|
3
|
+
* plugin config types, agent registration helpers, Genie tool
|
|
4
|
+
* builders, and dynamic Model Serving endpoint resolution.
|
|
5
|
+
*
|
|
6
|
+
* Client-side consumers should import URL helpers and the
|
|
7
|
+
* {@link MastraClientConfig} type from `@dbx-tools/appkit-mastra-shared`
|
|
8
|
+
* instead - that package is pure (no pg / fastembed / Mastra deps) and
|
|
9
|
+
* is the right surface for browser bundles and `usePluginClientConfig`
|
|
10
|
+
* consumers.
|
|
11
|
+
*/
|
|
12
|
+
export * from "./src/plugin.js";
|
|
13
|
+
export * from "@dbx-tools/appkit-mastra-shared";
|
|
14
|
+
export * from "./src/config.js";
|
|
15
|
+
export * from "./src/agents.js";
|
|
16
|
+
export * from "./src/genie.js";
|
|
17
|
+
export { clearServingEndpointsCache, extractModelOverride, listServingEndpoints, MASTRA_MODEL_OVERRIDE_KEY, MODEL_OVERRIDE_BODY_FIELDS, MODEL_OVERRIDE_HEADER, MODEL_OVERRIDE_QUERY, resolveModelId, type ResolvedModel, type ResolveModelOptions, type ServingEndpointSummary, } from "./src/serving.js";
|
|
18
|
+
export { FALLBACK_MODEL_IDS, MODEL_CATALOG, modelForTier, modelsForTier, ModelTier, } from "./src/model.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppKit Mastra integration: {@link MastraPlugin} / {@link mastra},
|
|
3
|
+
* plugin config types, agent registration helpers, Genie tool
|
|
4
|
+
* builders, and dynamic Model Serving endpoint resolution.
|
|
5
|
+
*
|
|
6
|
+
* Client-side consumers should import URL helpers and the
|
|
7
|
+
* {@link MastraClientConfig} type from `@dbx-tools/appkit-mastra-shared`
|
|
8
|
+
* instead - that package is pure (no pg / fastembed / Mastra deps) and
|
|
9
|
+
* is the right surface for browser bundles and `usePluginClientConfig`
|
|
10
|
+
* consumers.
|
|
11
|
+
*/
|
|
12
|
+
export * from "./src/plugin.js";
|
|
13
|
+
export * from "@dbx-tools/appkit-mastra-shared";
|
|
14
|
+
export * from "./src/config.js";
|
|
15
|
+
export * from "./src/agents.js";
|
|
16
|
+
export * from "./src/genie.js";
|
|
17
|
+
export { clearServingEndpointsCache, extractModelOverride, listServingEndpoints, MASTRA_MODEL_OVERRIDE_KEY, MODEL_OVERRIDE_BODY_FIELDS, MODEL_OVERRIDE_HEADER, MODEL_OVERRIDE_QUERY, resolveModelId, } from "./src/serving.js";
|
|
18
|
+
export { FALLBACK_MODEL_IDS, MODEL_CATALOG, modelForTier, modelsForTier, ModelTier, } from "./src/model.js";
|