@dbx-tools/appkit-mastra 0.1.41 → 0.1.42

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 CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  An AppKit plugin that hosts [Mastra](https://mastra.ai) agents inside a
4
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()`.
5
+ Lakebase-backed memory, and the standard Mastra agent stream the React
6
+ client drives via `@mastra/client-js` (`getAgent(id).stream()`).
7
7
 
8
8
  The plugin is designed so that wiring it up looks the same as the
9
9
  AppKit
@@ -70,29 +70,25 @@ for routing / one-shot agents that don't need history.
70
70
  See [Memory + storage](#memory--storage) for the full cascade and worked
71
71
  examples.
72
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:
73
+ On the React side, drop in the prebuilt chat UI from
74
+ [`@dbx-tools/appkit-mastra-ui`](../appkit-mastra-ui) - it wires itself
75
+ from the plugin's published client config and streams over
76
+ `@mastra/client-js`, so there's no transport code to write:
77
77
 
78
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
- // ...
79
+ import { MastraChat } from "@dbx-tools/appkit-mastra-ui/react";
80
+
81
+ export default function ChatPage() {
82
+ return <MastraChat showModelPicker />;
93
83
  }
94
84
  ```
95
85
 
86
+ Under the hood that's `useMastraClient()` -> a `MastraPluginClient`
87
+ (a `@mastra/client-js` `MastraClient` subclass) that streams turns and
88
+ adds the plugin's custom routes (history, models, suggestions, embeds).
89
+ Never hardcode `/api/mastra/...`; the client derives every URL from the
90
+ `basePath` published in `clientConfig()`.
91
+
96
92
  See [Client wiring](#client-wiring) for the full `MastraClientConfig`
97
93
  shape and per-agent selection.
98
94
 
@@ -136,9 +132,9 @@ Plugin-level fields:
136
132
  | Field | Description |
137
133
  | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
138
134
  | `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. |
135
+ | `defaultAgent` | Id the client talks to when no `:agentId` is supplied. Defaults to first registered. |
140
136
  | `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`. |
137
+ | `defaultModelFallbacks` | Priority-ordered list tried first when no `model` / env / override is set, ahead of the dynamic score-classified catalogue. First entry whose endpoint exists in the workspace wins. When unset, resolution is driven by the live Foundation Model API scores (see [Model resolution](#model-resolution)); set this to pin a regulated workspace to an approved subset. Compose one with `modelsForTier(ModelTier.Fast)`. |
142
138
  | `tools` | Ambient tools merged into every agent (per-agent tools win on collisions). |
143
139
  | `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
140
  | `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. |
@@ -254,7 +250,7 @@ Genie writer event flow:
254
250
  - Charts ride out-of-band: `prepare_chart` mints a `chartId`
255
251
  synchronously, the planner runs in the background and writes
256
252
  the spec to the chart cache (1h TTL). The host UI long-polls
257
- `${MastraClientConfig.embedPathTemplate}` (`/embed/chart/:id`)
253
+ `${basePath}/embed/chart/:id` (via `MastraPluginClient.chart(id)`)
258
254
  by id and renders inline at the matching `[chart:<chartId>]`
259
255
  marker.
260
256
  - After a hard reload, the live `started` / `status` / `sql`
@@ -426,43 +422,48 @@ loose tokens like `"claude sonnet"` snap to
426
422
  `modelFuzzyThreshold` (default `0.4`) the input is returned verbatim
427
423
  and Databricks surfaces the canonical 404.
428
424
 
429
- ### No explicit ask (tier-aware fallback list)
430
-
431
- When nothing is set, the resolver walks an opinionated
432
- priority-ordered list and returns **the first id that is actually
433
- present in the workspace's endpoint listing**. This is how a workspace
434
- without Claude Opus still gets a sensible default automatically -
435
- the resolver skips ahead to whichever Sonnet / GPT-5 / Gemini / Llama
436
- variant is wired up.
437
-
438
- The catalogue is grouped two ways:
439
-
440
- - By **capability tier** via the `ModelTier` enum:
441
- `ModelTier.Thinking` (deepest reasoning), `ModelTier.Balanced`
442
- (cost/latency sweet spot), `ModelTier.Fast` (cheap & quick for
443
- classification / routing / simple summarisation).
444
- - By **provider** within each tier: `claude`, `gpt`, `gemini`,
445
- `openSource`.
446
-
447
- Both views live on `MODEL_CATALOG[tier][provider]`. The walked
448
- `FALLBACK_MODEL_IDS` chains the three tiers in descending power
449
- (Thinking Balanced Fast); within each tier providers are
450
- round-robin-zipped (Claude ↔ GPT ↔ Gemini) before the open-weights
451
- tail is appended as the universal floor.
452
-
453
-
454
- | Tier (most powerful first) | Claude | GPT | Gemini | Open weights |
455
- | -------------------------- | -------------------------------- | ------------------------------------- | ------------------------------- | ---------------------------------------------- |
456
- | `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 |
457
- | `ModelTier.Balanced` | Sonnet 4.64.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 |
458
- | `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 |
459
-
425
+ ### No explicit ask (dynamic, score-based tiers)
426
+
427
+ When nothing is set, the resolver classifies the **live** workspace
428
+ catalogue and returns **the first id that is actually present in the
429
+ endpoint listing**. This is how a workspace without Claude Opus still
430
+ gets a sensible default automatically - the resolver skips ahead to
431
+ whichever model is the best fit and actually wired up.
432
+
433
+ Tiers are derived, not hard-coded. Databricks publishes a
434
+ `quality` / `speed` / `cost` profile per Foundation Model API endpoint
435
+ (the bars in the AI Playground), surfaced on `ServingEndpointSummary.profile`.
436
+ `classifyEndpoints(endpoints)` buckets the scored chat models by the
437
+ **relative distribution** of `quality`: the observed scores are split
438
+ at their 1/3 and 2/3 quantiles, so the top third is
439
+ `ModelTier.Thinking`, the bottom third `ModelTier.Fast`, and the
440
+ middle `ModelTier.Balanced`. Because the thresholds come from the data,
441
+ the split adapts as Databricks adds or rescores models - nothing is
442
+ pinned to a fixed score band, and a brand-new model that lands outside
443
+ today's range still slots in next to its peers.
444
+
445
+ Two escape hatches cover missing scores:
446
+
447
+ - **Unscored but recognizable** endpoints (a model Databricks hasn't
448
+ scored yet, e.g. a fresh release) are placed by a small **family
449
+ heuristic** keyed on provider + variant words (`opus`/`sonnet`/`haiku`,
450
+ `pro`/`mini`/`nano`, `flash`/`flash-lite`, Llama parameter sizes).
451
+ Unrecognized, unscored endpoints are never auto-selected.
452
+ - **Catalogue unreachable**: the resolver falls back to the small,
453
+ family-classified `FALLBACK_MODEL_IDS` floor (ThinkingBalanced
454
+ Fast).
455
+
456
+ The default chain is therefore: any `defaultModelFallbacks` you set,
457
+ then the live score-classified catalogue in descending tier order,
458
+ then the `FALLBACK_MODEL_IDS` floor.
460
459
 
461
460
  #### Pick a tier-appropriate model for one agent
462
461
 
463
- Use `modelForTier(tier)` to grab the top of a tier as a string; the
464
- agent-step resolver fuzzy-matches it against the live catalogue at
465
- call time so it still works when the literal top pick isn't deployed.
462
+ `modelForTier(tier)` / `modelsForTier(tier)` return the **static
463
+ fallback opinion** for a tier (the small built-in list, no workspace
464
+ call). They're handy for seeding a default; the agent-step resolver
465
+ still fuzzy-matches the result against the live catalogue at call time
466
+ so it works even when the literal pick isn't deployed.
466
467
 
467
468
  ```ts
468
469
  import { createAgent, ModelTier, modelForTier } from "@dbx-tools/appkit-mastra";
@@ -480,7 +481,7 @@ const planner = createAgent({
480
481
 
481
482
  #### Bias the plugin-level fallback toward a tier
482
483
 
483
- `modelsForTier(tier)` returns the priority-ordered list for one tier;
484
+ `modelsForTier(tier)` returns the static fallback list for one tier;
484
485
  pass it to `defaultModelFallbacks` to scope the auto-resolver:
485
486
 
486
487
  ```ts
package/dist/index.d.ts CHANGED
@@ -11,11 +11,11 @@
11
11
  * consumers.
12
12
  */
13
13
  export * from "@dbx-tools/appkit-mastra-shared";
14
+ export * from "@dbx-tools/model";
14
15
  export * from "./src/agents.js";
15
16
  export * from "./src/chart.js";
16
17
  export * from "./src/config.js";
17
18
  export * from "./src/genie.js";
18
- export { FALLBACK_MODEL_IDS, MODEL_CATALOG, ModelTier, modelForTier, modelsForTier, } from "./src/model.js";
19
19
  export * from "./src/plugin.js";
20
- export { MASTRA_MODEL_OVERRIDE_KEY, MODEL_OVERRIDE_BODY_FIELDS, MODEL_OVERRIDE_HEADER, MODEL_OVERRIDE_QUERY, clearServingEndpointsCache, extractModelOverride, listServingEndpoints, resolveModelId, type ResolveModelOptions, type ResolvedModel, type ServingEndpointSummary, } from "./src/serving.js";
20
+ export { MASTRA_MODEL_OVERRIDE_KEY, MODEL_OVERRIDE_BODY_FIELDS, MODEL_OVERRIDE_HEADER, MODEL_OVERRIDE_QUERY, extractModelOverride, type ModelOverrideRequest, } from "./src/serving.js";
21
21
  export * from "./src/tools/email.js";
package/dist/index.js CHANGED
@@ -11,11 +11,11 @@
11
11
  * consumers.
12
12
  */
13
13
  export * from "@dbx-tools/appkit-mastra-shared";
14
+ export * from "@dbx-tools/model";
14
15
  export * from "./src/agents.js";
15
16
  export * from "./src/chart.js";
16
17
  export * from "./src/config.js";
17
18
  export * from "./src/genie.js";
18
- export { FALLBACK_MODEL_IDS, MODEL_CATALOG, ModelTier, modelForTier, modelsForTier, } from "./src/model.js";
19
19
  export * from "./src/plugin.js";
20
- export { MASTRA_MODEL_OVERRIDE_KEY, MODEL_OVERRIDE_BODY_FIELDS, MODEL_OVERRIDE_HEADER, MODEL_OVERRIDE_QUERY, clearServingEndpointsCache, extractModelOverride, listServingEndpoints, resolveModelId, } from "./src/serving.js";
20
+ export { MASTRA_MODEL_OVERRIDE_KEY, MODEL_OVERRIDE_BODY_FIELDS, MODEL_OVERRIDE_HEADER, MODEL_OVERRIDE_QUERY, extractModelOverride, } from "./src/serving.js";
21
21
  export * from "./src/tools/email.js";
@@ -10,7 +10,7 @@
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
  import { appkitUtils, logUtils } from "@dbx-tools/shared";
16
16
  import type { AgentConfig, ToolsInput } from "@mastra/core/agent";
@@ -185,8 +185,8 @@ export type MastraToolsFn = (plugins: MastraPlugins) => MastraTools | Promise<Ma
185
185
  /**
186
186
  * A code-defined Mastra agent. Mirrors the shape AppKit's `agents`
187
187
  * plugin uses for `AgentDefinition`. The registry key under
188
- * `config.agents` is what `chatRoute` matches on; `name` is purely
189
- * informational (defaults to the key).
188
+ * `config.agents` is the `agentId` the client streams against; `name`
189
+ * is purely informational (defaults to the key).
190
190
  */
191
191
  export interface MastraAgentDefinition {
192
192
  /** Display name used as `Agent.name`. Defaults to the registry key. */
@@ -10,7 +10,7 @@
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
  import { appkitUtils, logUtils, stringUtils } from "@dbx-tools/shared";
16
16
  import { Agent } from "@mastra/core/agent";
@@ -144,4 +144,10 @@ export declare function fetchChart(chartId: string, options?: FetchChartOptions)
144
144
  * `prepare_chart` tool, which accepts a `statement_id` and
145
145
  * resolves the rows lazily.
146
146
  */
147
- export declare function buildRenderDataTool(config: MastraPluginConfig): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "render_data", unknown>;
147
+ export declare function buildRenderDataTool(config: MastraPluginConfig): import("@mastra/core/tools").Tool<{
148
+ title: string;
149
+ data: readonly Record<string, unknown>[];
150
+ description?: string | undefined;
151
+ }, {
152
+ chartId: string;
153
+ }, unknown, unknown, import("@mastra/core/tools").ToolExecutionContext<unknown, unknown, unknown>, "render_data", unknown>;
package/dist/src/chart.js CHANGED
@@ -30,7 +30,7 @@ import { commonUtils, logUtils, stringUtils } from "@dbx-tools/shared";
30
30
  import { Agent } from "@mastra/core/agent";
31
31
  import { createTool } from "@mastra/core/tools";
32
32
  import { z } from "zod";
33
- import { buildModel, modelForTier, ModelTier } from "./model.js";
33
+ import { buildModel, ModelTier } from "./model.js";
34
34
  const log = logUtils.logger("mastra/chart");
35
35
  /* ------------------------------ constants ------------------------------ */
36
36
  /**
@@ -276,9 +276,7 @@ function getPlannerAgent(config) {
276
276
  name: "Chart Planner",
277
277
  description: "Picks chart type and axis encodings for a dataset.",
278
278
  instructions: CHART_PLANNER_INSTRUCTIONS,
279
- model: ({ requestContext }) => buildModel(config, requestContext, {
280
- modelId: modelForTier(ModelTier.Fast),
281
- }),
279
+ model: ({ requestContext }) => buildModel(config, requestContext, { tier: ModelTier.Fast }),
282
280
  });
283
281
  plannerAgents.set(config, agent);
284
282
  }
@@ -302,7 +300,7 @@ async function runChartPlanner(config, request, options = {}) {
302
300
  ...(requestContext ? { requestContext } : {}),
303
301
  ...(abortSignal ? { abortSignal } : {}),
304
302
  });
305
- const plan = result.object;
303
+ const plan = chartPlanSchema.parse(result.object);
306
304
  const option = planToEchartsOption(plan, title);
307
305
  return { chartType: plan.chartType, option };
308
306
  }
@@ -125,7 +125,8 @@ export interface MastraPluginConfig extends BasePluginConfig {
125
125
  */
126
126
  tools?: MastraTools;
127
127
  /**
128
- * Agent id used by `chatRoute` when the client doesn't specify one.
128
+ * Agent id used when the client doesn't specify one (the bare,
129
+ * un-suffixed history / suggestions routes resolve to it).
129
130
  * Defaults to the first key in `agents` (or `"default"` when
130
131
  * `agents` is omitted). Must match an id in `agents` when both are
131
132
  * set; a mismatch throws at setup with the available candidates.
@@ -173,17 +174,19 @@ export interface MastraPluginConfig extends BasePluginConfig {
173
174
  */
174
175
  modelOverride?: boolean;
175
176
  /**
176
- * Priority-ordered list of endpoint names tried when no agent /
177
- * plugin / env / request-override model id is set. The resolver
178
- * picks the first id that is actually present in the workspace's
179
- * `/serving-endpoints` listing - this is what lets a workspace
180
- * without Claude Opus still get a sensible default automatically.
177
+ * Priority-ordered list of endpoint names tried *first* when no
178
+ * agent / plugin / env / request-override model id is set, ahead of
179
+ * the dynamic score-classified catalogue. The resolver picks the
180
+ * first id that is actually present in the workspace's
181
+ * `/serving-endpoints` listing.
181
182
  *
182
- * Defaults to the built-in list in `model.ts` (`FALLBACK_MODEL_IDS`):
183
- * Claude (Opus -> Sonnet -> Haiku), then OpenAI GPT-5 family, then
184
- * open weights (Llama 4, Llama 3.3, GPT-OSS, Qwen, Llama 3.1).
185
- * Override here to pin a regulated workspace to an approved subset
186
- * or to add custom endpoints in front of the public catalogue.
183
+ * When unset, resolution is driven by the live Foundation Model API
184
+ * `quality` / `speed` / `cost` scores: endpoints are classified into
185
+ * tiers (`classifyEndpoints`) and walked best-first (Thinking ->
186
+ * Balanced -> Fast), with the small built-in `FALLBACK_MODEL_IDS`
187
+ * list as the floor when the catalogue can't be read. Set this to
188
+ * pin a regulated workspace to an approved subset, or to put custom
189
+ * endpoints in front of the auto-classified catalogue.
187
190
  */
188
191
  defaultModelFallbacks?: readonly string[];
189
192
  /**
@@ -142,18 +142,6 @@ export declare function buildGenieToolkitProvider(opts: {
142
142
  }): {
143
143
  toolkit(opts?: unknown): MastraTools;
144
144
  };
145
- /**
146
- * Returns `true` when at least one Genie space is reachable
147
- * through {@link resolveGenieSpaces} - either via
148
- * {@link MastraPluginConfig.genieSpaces}, the AppKit `genie()`
149
- * plugin instance, or the `DATABRICKS_GENIE_SPACE_ID` env var.
150
- *
151
- * Cheap to call from `resolveProvider` to short-circuit `genie`
152
- * lookups when nothing is wired, so the `plugins.genie` lookup
153
- * still resolves to `undefined` (matching AppKit's
154
- * absent-plugin semantics) when neither source is configured.
155
- */
156
- export declare function hasAnyGenieSpaces(config: MastraPluginConfig, context: appkitUtils.PluginContextLike | undefined): boolean;
157
145
  /**
158
146
  * Collect the curated starter questions across every resolved Genie
159
147
  * space, deduped and capped. Each space's `sample_questions` are
package/dist/src/genie.js CHANGED
@@ -37,7 +37,7 @@ import { CacheManager, genie } from "@databricks/appkit";
37
37
  import { ApiError, HttpError, WorkspaceClient } from "@databricks/sdk-experimental";
38
38
  import { ChartSchema, } from "@dbx-tools/appkit-mastra-shared";
39
39
  import { genieEventChat, genieSampleQuestions, getGenieSpace } from "@dbx-tools/genie";
40
- import {} from "@dbx-tools/genie-shared";
40
+ import { GenieMessageSchema } from "@dbx-tools/genie-shared";
41
41
  import { appkitUtils, commonUtils, logUtils, stringUtils } from "@dbx-tools/shared";
42
42
  import { MASTRA_THREAD_ID_KEY } from "@mastra/core/request-context";
43
43
  import { createTool } from "@mastra/core/tools";
@@ -334,7 +334,7 @@ function buildAskGenieTool(opts) {
334
334
  question: z.string().min(1, "question is required"),
335
335
  }),
336
336
  outputSchema: z.object({
337
- message: z.custom(),
337
+ message: GenieMessageSchema,
338
338
  }),
339
339
  execute: async ({ question }, ctxRaw) => {
340
340
  const ctx = ctxRaw;
@@ -891,20 +891,6 @@ export function buildGenieToolkitProvider(opts) {
891
891
  },
892
892
  };
893
893
  }
894
- /**
895
- * Returns `true` when at least one Genie space is reachable
896
- * through {@link resolveGenieSpaces} - either via
897
- * {@link MastraPluginConfig.genieSpaces}, the AppKit `genie()`
898
- * plugin instance, or the `DATABRICKS_GENIE_SPACE_ID` env var.
899
- *
900
- * Cheap to call from `resolveProvider` to short-circuit `genie`
901
- * lookups when nothing is wired, so the `plugins.genie` lookup
902
- * still resolves to `undefined` (matching AppKit's
903
- * absent-plugin semantics) when neither source is configured.
904
- */
905
- export function hasAnyGenieSpaces(config, context) {
906
- return Object.keys(resolveGenieSpaces(config, context)).length > 0;
907
- }
908
894
  /* --------------------------- starter suggestions --------------------------- */
909
895
  /**
910
896
  * Default cap on starter suggestions surfaced to the chat empty
@@ -9,8 +9,8 @@
9
9
  *
10
10
  * The route is registered through {@link historyRoute} as a Mastra
11
11
  * `registerApiRoute` so it sits in the same dispatcher pipeline as
12
- * `chatRoute`. That means the `MastraServer` auth middleware (in
13
- * `./server.ts`) has already populated `RequestContext` with
12
+ * the standard agent routes. That means the `MastraServer` auth
13
+ * middleware (in `./server.ts`) has already populated `RequestContext` with
14
14
  * `MASTRA_THREAD_ID_KEY` and `MASTRA_RESOURCE_ID_KEY` by the time
15
15
  * the handler runs - no cookie or user lookups happen here, and the
16
16
  * session-cookie logic stays the single source of truth in `server.ts`.
@@ -83,10 +83,10 @@ export type HistoryRouteOptions = {
83
83
  * anchors the thread id is left alone so the user keeps the
84
84
  * same thread - only the contents go away.
85
85
  *
86
- * Modeled after `chatRoute` from `@mastra/ai-sdk`: pass `agent` for a
87
- * fixed-agent mount, or include `:agentId` in the path for dynamic
88
- * routing. Pairs cleanly with the AppKit Mastra plugin's chat route
89
- * layout (`/route/chat` + `/route/chat/:agentId`).
86
+ * Follows the `@mastra/ai-sdk` `chatRoute` convention for agent
87
+ * binding: pass `agent` for a fixed-agent mount, or include
88
+ * `:agentId` in the path for dynamic routing. The plugin registers
89
+ * both `/route/history` (default agent) and `/route/history/:agentId`.
90
90
  *
91
91
  * The handler reads `threadId` and `resourceId` from `RequestContext`
92
92
  * (populated upstream by `MastraServer.registerAuthMiddleware`), so
@@ -9,8 +9,8 @@
9
9
  *
10
10
  * The route is registered through {@link historyRoute} as a Mastra
11
11
  * `registerApiRoute` so it sits in the same dispatcher pipeline as
12
- * `chatRoute`. That means the `MastraServer` auth middleware (in
13
- * `./server.ts`) has already populated `RequestContext` with
12
+ * the standard agent routes. That means the `MastraServer` auth
13
+ * middleware (in `./server.ts`) has already populated `RequestContext` with
14
14
  * `MASTRA_THREAD_ID_KEY` and `MASTRA_RESOURCE_ID_KEY` by the time
15
15
  * the handler runs - no cookie or user lookups happen here, and the
16
16
  * session-cookie logic stays the single source of truth in `server.ts`.
@@ -153,10 +153,10 @@ export async function clearHistory(opts) {
153
153
  * anchors the thread id is left alone so the user keeps the
154
154
  * same thread - only the contents go away.
155
155
  *
156
- * Modeled after `chatRoute` from `@mastra/ai-sdk`: pass `agent` for a
157
- * fixed-agent mount, or include `:agentId` in the path for dynamic
158
- * routing. Pairs cleanly with the AppKit Mastra plugin's chat route
159
- * layout (`/route/chat` + `/route/chat/:agentId`).
156
+ * Follows the `@mastra/ai-sdk` `chatRoute` convention for agent
157
+ * binding: pass `agent` for a fixed-agent mount, or include
158
+ * `:agentId` in the path for dynamic routing. The plugin registers
159
+ * both `/route/history` (default agent) and `/route/history/:agentId`.
160
160
  *
161
161
  * The handler reads `threadId` and `resourceId` from `RequestContext`
162
162
  * (populated upstream by `MastraServer.registerAuthMiddleware`), so
@@ -7,148 +7,41 @@
7
7
  * fresh bearer header, then point Mastra's OpenAI-compatible provider
8
8
  * at `/serving-endpoints` on that host.
9
9
  *
10
- * Model id resolution walks three sources before falling back to the
11
- * hard-coded default, **in this priority order**:
12
- *
13
- * 1. Per-request override stashed by the auth middleware under
14
- * {@link MASTRA_MODEL_OVERRIDE_KEY} (header / query / body).
15
- * 2. The static `modelId` passed in by the agent / plugin (string
16
- * sugar on `def.model` or `config.defaultModel`).
17
- * 3. `DATABRICKS_SERVING_ENDPOINT_NAME` env var.
18
- * 4. {@link FALLBACK_MODEL_ID}.
19
- *
20
- * Whatever wins is then fuzzy-matched against the live
21
- * `/serving-endpoints` list ({@link listServingEndpoints}) so loose
22
- * names like `"claude sonnet"` resolve to the real endpoint name.
23
- * Fuzzy matching is best-effort: when the workspace client throws
24
- * (network blip, expired token at cache-fill time) we fall back to
25
- * the input verbatim and let Databricks return the canonical error.
10
+ * This module only adds the Mastra-specific glue. The actual model
11
+ * selection - listing the workspace catalogue and resolving an
12
+ * explicit name / tier / fallback chain to a real endpoint id - lives
13
+ * in `@dbx-tools/model` ({@link selectModel}) so non-Mastra consumers
14
+ * (e.g. a job that just needs a model name) can reuse it. Here we
15
+ * assemble the explicit ask from Mastra's request context (the
16
+ * per-request override under {@link MASTRA_MODEL_OVERRIDE_KEY}, the
17
+ * agent / plugin `modelId`, or `DATABRICKS_SERVING_ENDPOINT_NAME`),
18
+ * pass the plugin's fuzzy / tier / fallback knobs through, and wrap
19
+ * the resolved id in the OpenAI-compatible provider config Mastra
20
+ * expects. Catalogue fetches fail loud: network / auth errors
21
+ * propagate so callers see the real SDK message.
26
22
  */
23
+ import { type ModelTier } from "@dbx-tools/model";
27
24
  import type { MastraModelConfig } from "@mastra/core/llm";
28
25
  import type { RequestContext } from "@mastra/core/request-context";
29
26
  import { type MastraPluginConfig } from "./config.js";
30
- /**
31
- * Capability tiers for Databricks Foundation Model API endpoints.
32
- *
33
- * - {@link ModelTier.Thinking}: deepest reasoning / "thinking" models
34
- * (Claude Opus, GPT-5.5 Pro, Gemini Pro, Llama 4 Maverick, etc).
35
- * Highest cost and latency; reserve for hard multi-step reasoning.
36
- * - {@link ModelTier.Balanced}: cost/latency sweet spot for general
37
- * agent work (Claude Sonnet, GPT-5.x, Gemini Flash, Llama 3.3 70B).
38
- * The right default for most agents.
39
- * - {@link ModelTier.Fast}: cheap and quick; classification, routing,
40
- * tool-arg extraction, simple summarisation (Claude Haiku, GPT-5
41
- * mini/nano, Gemini Flash Lite, GPT-OSS 20B, Llama 3.1 8B).
42
- *
43
- * String enum so the value is the slug we use in cache keys, logs,
44
- * and as the value users see in serialized configs.
45
- */
46
- export declare enum ModelTier {
47
- Thinking = "thinking",
48
- Balanced = "balanced",
49
- Fast = "fast"
50
- }
51
- /**
52
- * Catalogue of Databricks-hosted Foundation Model API endpoints,
53
- * grouped by capability {@link ModelTier} and then by provider. Each
54
- * inner array is priority-ordered (most powerful first within the
55
- * same provider+tier).
56
- *
57
- * Provider buckets:
58
- *
59
- * - `claude`: Anthropic Claude family (closed; flagship reasoning).
60
- * - `gpt`: OpenAI GPT-5 family (closed; "ChatGPT" on Databricks FMAPI).
61
- * - `gemini`: Google Gemini family (closed; multimodal + web-search).
62
- * - `openSource`: open-weights models (widest regional / SKU availability).
63
- *
64
- * The list is curated by hand; refresh from the Databricks "supported
65
- * foundation models" doc when new endpoints land.
66
- */
67
- export declare const MODEL_CATALOG: {
68
- readonly thinking: {
69
- readonly claude: readonly ["databricks-claude-opus-4-8", "databricks-claude-opus-4-7", "databricks-claude-opus-4-6", "databricks-claude-opus-4-5", "databricks-claude-opus-4-1"];
70
- readonly gpt: readonly ["databricks-gpt-5-5-pro"];
71
- readonly gemini: readonly ["databricks-gemini-3-1-pro", "databricks-gemini-3-pro", "databricks-gemini-2-5-pro"];
72
- readonly openSource: readonly ["databricks-llama-4-maverick", "databricks-gpt-oss-120b", "databricks-meta-llama-3-1-405b-instruct"];
73
- };
74
- readonly balanced: {
75
- readonly claude: readonly ["databricks-claude-sonnet-4-6", "databricks-claude-sonnet-4-5", "databricks-claude-sonnet-4"];
76
- readonly gpt: readonly ["databricks-gpt-5-5", "databricks-gpt-5-4", "databricks-gpt-5-2", "databricks-gpt-5-1", "databricks-gpt-5"];
77
- readonly gemini: readonly ["databricks-gemini-3-5-flash", "databricks-gemini-3-flash", "databricks-gemini-2-5-flash"];
78
- readonly openSource: readonly ["databricks-meta-llama-3-3-70b-instruct", "databricks-qwen3-next-80b-a3b-instruct", "databricks-qwen35-122b-a10b"];
79
- };
80
- readonly fast: {
81
- readonly claude: readonly ["databricks-claude-haiku-4-5"];
82
- readonly gpt: readonly ["databricks-gpt-5-4-mini", "databricks-gpt-5-4-nano", "databricks-gpt-5-mini", "databricks-gpt-5-nano"];
83
- readonly gemini: readonly ["databricks-gemini-3-1-flash-lite"];
84
- readonly openSource: readonly ["databricks-gpt-oss-20b", "databricks-gemma-3-12b", "databricks-meta-llama-3-1-8b-instruct"];
85
- };
86
- };
87
- /**
88
- * Priority-ordered model ids for a single capability {@link ModelTier},
89
- * interleaved across providers so a workspace missing the top Claude
90
- * still lands on a flagship GPT / Gemini on the next probe.
91
- *
92
- * Provider order within the interleave: Claude, GPT, Gemini, then the
93
- * open-weights tail appended verbatim as the universal floor (widest
94
- * regional availability).
95
- *
96
- * @example
97
- * ```ts
98
- * mastra({
99
- * defaultModelFallbacks: modelsForTier(ModelTier.Fast),
100
- * });
101
- * ```
102
- */
103
- export declare function modelsForTier(tier: ModelTier): readonly string[];
104
- /**
105
- * Top model id at the given {@link ModelTier}. Sync; the agent-step
106
- * resolver fuzzy-matches it against the workspace catalogue at call
107
- * time, so this works even when the literal top pick isn't deployed.
108
- *
109
- * Use when wiring a tier-appropriate model into an agent definition:
110
- *
111
- * @example
112
- * ```ts
113
- * const classifier = createAgent({
114
- * instructions: "Classify this email",
115
- * model: modelForTier(ModelTier.Fast), // cheap, quick
116
- * });
117
- *
118
- * const planner = createAgent({
119
- * instructions: "Plan a multi-step migration",
120
- * model: modelForTier(ModelTier.Thinking), // deep reasoning
121
- * });
122
- * ```
123
- */
124
- export declare function modelForTier(tier: ModelTier): string;
125
- /**
126
- * Last-resort model ids used when neither `config.defaultModel`,
127
- * per-agent `model`, nor `DATABRICKS_SERVING_ENDPOINT_NAME` is set.
128
- *
129
- * Walked in order at resolve time: the first id whose endpoint is
130
- * actually present in the workspace's `/serving-endpoints` listing
131
- * wins. Workspaces vary - not every region / SKU has every model,
132
- * and the list of Foundation Model APIs evolves quickly - so the
133
- * resolver degrades all the way from "best thinking model" down to
134
- * "smallest commodity Llama" before giving up.
135
- *
136
- * Built by chaining the per-tier interleaves (Thinking -> Balanced
137
- * -> Fast); within each tier the providers are round-robin-zipped
138
- * (Claude, GPT, Gemini, then open-weights tail). Override the entire
139
- * list via `MastraPluginConfig.defaultModelFallbacks` (e.g. to pin a
140
- * regulated workspace to a specific approved subset, or to bias the
141
- * priority toward a particular tier).
142
- */
143
- export declare const FALLBACK_MODEL_IDS: readonly string[];
27
+ export { classifyEndpoints, FALLBACK_MODEL_IDS, modelForTier, modelsForTier, ModelTier, } from "@dbx-tools/model";
144
28
  /** Optional overrides accepted by {@link buildModel}. */
145
29
  export interface BuildModelOverrides {
146
30
  /**
147
31
  * Static model id from the agent / plugin config (string sugar on
148
32
  * `def.model` or `config.defaultModel`). Loses to the per-request
149
- * override but wins over env / fallback.
33
+ * override but wins over env / tier / fallback.
150
34
  */
151
35
  modelId?: string;
36
+ /**
37
+ * Capability tier to resolve when no explicit model id is supplied.
38
+ * Used by internal agents (e.g. the chart planner asks for
39
+ * {@link ModelTier.Fast}) to express intent without pinning an
40
+ * endpoint name; the live catalogue is classified and the top
41
+ * available model in the tier is chosen, falling back to the
42
+ * tier's static list when the workspace has none.
43
+ */
44
+ tier?: ModelTier;
152
45
  }
153
46
  /**
154
47
  * Resolve a `MastraModelConfig` for the current agent step. Runs