@dbx-tools/appkit-mastra 0.1.40 → 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/index.ts CHANGED
@@ -11,29 +11,18 @@
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 {
19
- FALLBACK_MODEL_IDS,
20
- MODEL_CATALOG,
21
- ModelTier,
22
- modelForTier,
23
- modelsForTier,
24
- } from "./src/model.js";
25
19
  export * from "./src/plugin.js";
26
20
  export {
27
21
  MASTRA_MODEL_OVERRIDE_KEY,
28
22
  MODEL_OVERRIDE_BODY_FIELDS,
29
23
  MODEL_OVERRIDE_HEADER,
30
24
  MODEL_OVERRIDE_QUERY,
31
- clearServingEndpointsCache,
32
25
  extractModelOverride,
33
- listServingEndpoints,
34
- resolveModelId,
35
- type ResolveModelOptions,
36
- type ResolvedModel,
37
- type ServingEndpointSummary,
26
+ type ModelOverrideRequest,
38
27
  } from "./src/serving.js";
39
28
  export * from "./src/tools/email.js";
package/package.json CHANGED
@@ -9,13 +9,14 @@
9
9
  }
10
10
  },
11
11
  "name": "@dbx-tools/appkit-mastra",
12
- "version": "0.1.40",
12
+ "version": "0.1.42",
13
13
  "dependencies": {
14
14
  "@databricks/sdk-experimental": "^0.17",
15
- "@dbx-tools/appkit-mastra-shared": "0.1.40",
16
- "@dbx-tools/genie": "0.1.40",
17
- "@dbx-tools/genie-shared": "0.1.40",
18
- "@dbx-tools/shared": "0.1.40",
15
+ "@dbx-tools/appkit-mastra-shared": "0.1.42",
16
+ "@dbx-tools/genie": "0.1.42",
17
+ "@dbx-tools/genie-shared": "0.1.42",
18
+ "@dbx-tools/model": "0.1.42",
19
+ "@dbx-tools/shared": "0.1.42",
19
20
  "@mastra/ai-sdk": "^1",
20
21
  "@mastra/core": "^1",
21
22
  "@mastra/express": "^1",
@@ -24,7 +25,7 @@
24
25
  "@mastra/observability": "^1",
25
26
  "@mastra/otel-bridge": "^1",
26
27
  "@mastra/pg": "^1",
27
- "fuse.js": "^7.0.0",
28
+ "pg": "^8.20.0",
28
29
  "zod": "^4.3.6"
29
30
  },
30
31
  "devDependencies": {
package/src/agents.ts CHANGED
@@ -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
 
16
16
  import { appkitUtils, logUtils, stringUtils } from "@dbx-tools/shared";
@@ -226,8 +226,8 @@ export type MastraToolsFn = (
226
226
  /**
227
227
  * A code-defined Mastra agent. Mirrors the shape AppKit's `agents`
228
228
  * plugin uses for `AgentDefinition`. The registry key under
229
- * `config.agents` is what `chatRoute` matches on; `name` is purely
230
- * informational (defaults to the key).
229
+ * `config.agents` is the `agentId` the client streams against; `name`
230
+ * is purely informational (defaults to the key).
231
231
  */
232
232
  export interface MastraAgentDefinition {
233
233
  /** Display name used as `Agent.name`. Defaults to the registry key. */
package/src/chart.ts CHANGED
@@ -39,7 +39,7 @@ import { createTool } from "@mastra/core/tools";
39
39
  import { z } from "zod";
40
40
 
41
41
  import type { MastraPluginConfig } from "./config.js";
42
- import { buildModel, modelForTier, ModelTier } from "./model.js";
42
+ import { buildModel, ModelTier } from "./model.js";
43
43
 
44
44
  const log = logUtils.logger("mastra/chart");
45
45
 
@@ -330,9 +330,7 @@ function getPlannerAgent(config: MastraPluginConfig): Agent {
330
330
  description: "Picks chart type and axis encodings for a dataset.",
331
331
  instructions: CHART_PLANNER_INSTRUCTIONS,
332
332
  model: ({ requestContext }) =>
333
- buildModel(config, requestContext, {
334
- modelId: modelForTier(ModelTier.Fast),
335
- }),
333
+ buildModel(config, requestContext, { tier: ModelTier.Fast }),
336
334
  });
337
335
  plannerAgents.set(config, agent);
338
336
  }
@@ -361,7 +359,7 @@ async function runChartPlanner(
361
359
  ...(requestContext ? { requestContext } : {}),
362
360
  ...(abortSignal ? { abortSignal } : {}),
363
361
  });
364
- const plan = result.object as ChartPlan;
362
+ const plan = chartPlanSchema.parse(result.object);
365
363
  const option = planToEchartsOption(plan, title);
366
364
  return { chartType: plan.chartType, option };
367
365
  }
package/src/config.ts CHANGED
@@ -150,7 +150,8 @@ export interface MastraPluginConfig extends BasePluginConfig {
150
150
  */
151
151
  tools?: MastraTools;
152
152
  /**
153
- * Agent id used by `chatRoute` when the client doesn't specify one.
153
+ * Agent id used when the client doesn't specify one (the bare,
154
+ * un-suffixed history / suggestions routes resolve to it).
154
155
  * Defaults to the first key in `agents` (or `"default"` when
155
156
  * `agents` is omitted). Must match an id in `agents` when both are
156
157
  * set; a mismatch throws at setup with the available candidates.
@@ -198,17 +199,19 @@ export interface MastraPluginConfig extends BasePluginConfig {
198
199
  */
199
200
  modelOverride?: boolean;
200
201
  /**
201
- * Priority-ordered list of endpoint names tried when no agent /
202
- * plugin / env / request-override model id is set. The resolver
203
- * picks the first id that is actually present in the workspace's
204
- * `/serving-endpoints` listing - this is what lets a workspace
205
- * without Claude Opus still get a sensible default automatically.
202
+ * Priority-ordered list of endpoint names tried *first* when no
203
+ * agent / plugin / env / request-override model id is set, ahead of
204
+ * the dynamic score-classified catalogue. The resolver picks the
205
+ * first id that is actually present in the workspace's
206
+ * `/serving-endpoints` listing.
206
207
  *
207
- * Defaults to the built-in list in `model.ts` (`FALLBACK_MODEL_IDS`):
208
- * Claude (Opus -> Sonnet -> Haiku), then OpenAI GPT-5 family, then
209
- * open weights (Llama 4, Llama 3.3, GPT-OSS, Qwen, Llama 3.1).
210
- * Override here to pin a regulated workspace to an approved subset
211
- * or to add custom endpoints in front of the public catalogue.
208
+ * When unset, resolution is driven by the live Foundation Model API
209
+ * `quality` / `speed` / `cost` scores: endpoints are classified into
210
+ * tiers (`classifyEndpoints`) and walked best-first (Thinking ->
211
+ * Balanced -> Fast), with the small built-in `FALLBACK_MODEL_IDS`
212
+ * list as the floor when the catalogue can't be read. Set this to
213
+ * pin a regulated workspace to an approved subset, or to put custom
214
+ * endpoints in front of the auto-classified catalogue.
212
215
  */
213
216
  defaultModelFallbacks?: readonly string[];
214
217
  /**
package/src/genie.ts CHANGED
@@ -38,11 +38,11 @@ import { CacheManager, genie } from "@databricks/appkit";
38
38
  import { ApiError, HttpError, WorkspaceClient } from "@databricks/sdk-experimental";
39
39
  import {
40
40
  ChartSchema,
41
- type MinimalWriter,
41
+ type MastraWriter,
42
42
  type StartedEvent,
43
43
  } from "@dbx-tools/appkit-mastra-shared";
44
44
  import { genieEventChat, genieSampleQuestions, getGenieSpace } from "@dbx-tools/genie";
45
- import { type GenieMessage } from "@dbx-tools/genie-shared";
45
+ import { GenieMessageSchema, type GenieMessage } from "@dbx-tools/genie-shared";
46
46
  import { appkitUtils, commonUtils, logUtils, stringUtils } from "@dbx-tools/shared";
47
47
  import type { RequestContext } from "@mastra/core/request-context";
48
48
  import { MASTRA_THREAD_ID_KEY } from "@mastra/core/request-context";
@@ -93,7 +93,7 @@ export type GenieSpacesConfig = Record<string, GenieSpaceConfig | string>;
93
93
  type ToolExecuteCtx =
94
94
  | {
95
95
  requestContext?: RequestContext;
96
- writer?: MinimalWriter;
96
+ writer?: MastraWriter;
97
97
  abortSignal?: AbortSignal;
98
98
  }
99
99
  | undefined;
@@ -421,7 +421,7 @@ function buildAskGenieTool(opts: { spaceId: string; alias: string; hint?: string
421
421
  question: z.string().min(1, "question is required"),
422
422
  }),
423
423
  outputSchema: z.object({
424
- message: z.custom<GenieMessage>(),
424
+ message: GenieMessageSchema,
425
425
  }),
426
426
  execute: async ({ question }, ctxRaw) => {
427
427
  const ctx = ctxRaw as ToolExecuteCtx;
@@ -1033,24 +1033,6 @@ export function buildGenieToolkitProvider(opts: {
1033
1033
  };
1034
1034
  }
1035
1035
 
1036
- /**
1037
- * Returns `true` when at least one Genie space is reachable
1038
- * through {@link resolveGenieSpaces} - either via
1039
- * {@link MastraPluginConfig.genieSpaces}, the AppKit `genie()`
1040
- * plugin instance, or the `DATABRICKS_GENIE_SPACE_ID` env var.
1041
- *
1042
- * Cheap to call from `resolveProvider` to short-circuit `genie`
1043
- * lookups when nothing is wired, so the `plugins.genie` lookup
1044
- * still resolves to `undefined` (matching AppKit's
1045
- * absent-plugin semantics) when neither source is configured.
1046
- */
1047
- export function hasAnyGenieSpaces(
1048
- config: MastraPluginConfig,
1049
- context: appkitUtils.PluginContextLike | undefined,
1050
- ): boolean {
1051
- return Object.keys(resolveGenieSpaces(config, context)).length > 0;
1052
- }
1053
-
1054
1036
  /* --------------------------- starter suggestions --------------------------- */
1055
1037
 
1056
1038
  /**
package/src/history.ts CHANGED
@@ -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`.
@@ -197,10 +197,10 @@ export type HistoryRouteOptions =
197
197
  * anchors the thread id is left alone so the user keeps the
198
198
  * same thread - only the contents go away.
199
199
  *
200
- * Modeled after `chatRoute` from `@mastra/ai-sdk`: pass `agent` for a
201
- * fixed-agent mount, or include `:agentId` in the path for dynamic
202
- * routing. Pairs cleanly with the AppKit Mastra plugin's chat route
203
- * layout (`/route/chat` + `/route/chat/:agentId`).
200
+ * Follows the `@mastra/ai-sdk` `chatRoute` convention for agent
201
+ * binding: pass `agent` for a fixed-agent mount, or include
202
+ * `:agentId` in the path for dynamic routing. The plugin registers
203
+ * both `/route/history` (default agent) and `/route/history/:agentId`.
204
204
  *
205
205
  * The handler reads `threadId` and `resourceId` from `RequestContext`
206
206
  * (populated upstream by `MastraServer.registerAuthMiddleware`), so
package/src/memory.ts CHANGED
@@ -27,23 +27,46 @@
27
27
  * is registered); per-agent settings cascade on top of that.
28
28
  */
29
29
 
30
- import { lakebase } from "@databricks/appkit";
31
- import { appkitUtils, logUtils } from "@dbx-tools/shared";
30
+ import { getUsernameWithApiLookup } from "@databricks/appkit";
31
+ import { logUtils } from "@dbx-tools/shared";
32
32
  import { fastembed } from "@mastra/fastembed";
33
33
  import { Memory } from "@mastra/memory";
34
34
  import { PgVector, PostgresStore } from "@mastra/pg";
35
35
  import { randomUUID } from "node:crypto";
36
- import type { Pool } from "pg";
36
+ import { Pool, type PoolConfig } from "pg";
37
37
 
38
38
  import type { MastraAgentDefinition, MastraMemoryConfigOverride } from "./agents.js";
39
39
  import type { MastraPluginConfig } from "./config.js";
40
40
 
41
41
  const log = logUtils.logger("mastra/memory");
42
42
 
43
- /** Pool handle returned by the AppKit `lakebase` plugin `exports().pool`. */
44
- export type LakebasePool = ReturnType<
45
- InstanceType<ReturnType<typeof lakebase>["plugin"]>["exports"]
46
- >["pool"];
43
+ /**
44
+ * Build a dedicated **service-principal** Lakebase pool for Mastra
45
+ * memory from the lakebase plugin's resolved SP pg config.
46
+ *
47
+ * The plugin's `exports().pool` is a `RoutingPool` that switches to
48
+ * the per-user (OBO) pool whenever a query runs inside an `asUser`
49
+ * scope - exactly the context the mastra plugin establishes around
50
+ * every chat turn. Memory (threads / messages + semantic recall) must
51
+ * instead always act as the app service principal: it owns the
52
+ * auto-created `mastra_*` schemas (a per-user role usually can't
53
+ * `CREATE SCHEMA`) and is shared across users, so it cannot inherit a
54
+ * request's OBO identity.
55
+ *
56
+ * `pgConfig` must be the plugin's `exports().getPgConfig()` evaluated
57
+ * **outside** any `asUser` scope (i.e. during setup), so it carries
58
+ * the SP connection target, OAuth token-refresh `password` callback,
59
+ * and any `lakebase({ pool })` tuning overrides - all of which this
60
+ * pool inherits. See the call site in `plugin.ts`.
61
+ */
62
+ export async function createServicePrincipalPool(pgConfig: PoolConfig): Promise<Pool> {
63
+ // `getPgConfig()` resolves the SP username synchronously from
64
+ // `PGUSER` / `DATABRICKS_CLIENT_ID`; fall back to the async API
65
+ // lookup (e.g. local dev authenticating via PAT) so the pool always
66
+ // has an identity to connect with.
67
+ const user = pgConfig.user ?? (await getUsernameWithApiLookup());
68
+ return new Pool({ ...pgConfig, user });
69
+ }
47
70
 
48
71
  /** Effective per-knob setting after the plugin/agent cascade. */
49
72
  type StorageSetting = MastraAgentDefinition["storage"];
@@ -51,9 +74,9 @@ type MemorySetting = MastraAgentDefinition["memory"];
51
74
 
52
75
  /**
53
76
  * True when any plugin-level or per-agent setting could need the
54
- * Lakebase pool. Used by `plugin.ts` to gate pool acquisition; the
55
- * builder also acquires lazily so missed cases still fail with a
56
- * clear lakebase-not-registered error.
77
+ * Lakebase pool. Used by `plugin.ts` to gate creation of the
78
+ * service-principal pool and the {@link MemoryBuilder} that consumes
79
+ * it; when false neither is built.
57
80
  */
58
81
  export function needsLakebase(config: MastraPluginConfig): boolean {
59
82
  if (settingNeedsSharedPool(config.storage)) return true;
@@ -65,42 +88,29 @@ export function needsLakebase(config: MastraPluginConfig): boolean {
65
88
  }
66
89
 
67
90
  /**
68
- * Look up the `lakebase` plugin and return its managed `pg.Pool`.
69
- * Throws when the sibling plugin is not registered; enabling
70
- * `storage` / `memory` without lakebase is a wiring bug, not a runtime
71
- * condition we can recover from.
72
- */
73
- export function resolveLakebasePool(
74
- context: appkitUtils.PluginContextLike | undefined,
75
- caller: MastraPluginConfig,
76
- ): LakebasePool {
77
- return appkitUtils.require(context, lakebase, caller).exports().pool;
78
- }
79
-
80
- /**
81
- * Construct a per-agent {@link Memory} factory. Caches the shared
82
- * `PgVector` singleton (built on first need) and the lazily-resolved
83
- * Lakebase pool so each agent build is O(1) after the first.
91
+ * Construct a per-agent {@link Memory} factory bound to the supplied
92
+ * service-principal pool (see {@link createServicePrincipalPool}).
93
+ * Caches the shared `PgVector` singleton (built on first need) so each
94
+ * agent build is O(1) after the first.
84
95
  */
85
96
  export function createMemoryBuilder(
86
97
  config: MastraPluginConfig,
87
- context: appkitUtils.PluginContextLike | undefined,
98
+ servicePrincipalPool: Pool,
88
99
  ): MemoryBuilder {
89
- return new MemoryBuilder(config, context);
100
+ return new MemoryBuilder(config, servicePrincipalPool);
90
101
  }
91
102
 
92
103
  /**
93
- * Builds one `Memory` per agent. Per-instance state keeps the shared
94
- * `PgVector` and the resolved Lakebase pool alive across calls so
95
- * registering N agents stays cheap.
104
+ * Builds one `Memory` per agent against a shared service-principal
105
+ * Lakebase pool. Per-instance state keeps the shared `PgVector` alive
106
+ * across calls so registering N agents stays cheap.
96
107
  */
97
108
  export class MemoryBuilder {
98
109
  private sharedVector: PgVector | undefined;
99
- private pool: LakebasePool | undefined;
100
110
 
101
111
  constructor(
102
112
  private readonly config: MastraPluginConfig,
103
- private readonly context: appkitUtils.PluginContextLike | undefined,
113
+ private readonly servicePrincipalPool: Pool,
104
114
  ) {}
105
115
 
106
116
  /**
@@ -133,7 +143,7 @@ export class MemoryBuilder {
133
143
  return new PostgresStore({
134
144
  id: "mastra-store__instance",
135
145
  schemaName: "mastra_instance",
136
- pool: this.requirePool() as Pool,
146
+ pool: this.servicePrincipalPool,
137
147
  });
138
148
  }
139
149
 
@@ -179,7 +189,7 @@ export class MemoryBuilder {
179
189
  return new PostgresStore({
180
190
  id: `mastra-store__${agentId}`,
181
191
  schemaName: `mastra_${agentId}`,
182
- pool: this.requirePool() as Pool,
192
+ pool: this.servicePrincipalPool,
183
193
  });
184
194
  }
185
195
  // Cast: `withId` guarantees `id` is set, but the distributive
@@ -209,17 +219,10 @@ export class MemoryBuilder {
209
219
 
210
220
  private getSharedVector(): PgVector {
211
221
  if (!this.sharedVector) {
212
- this.sharedVector = buildSharedPgVector(this.requirePool());
222
+ this.sharedVector = buildSharedPgVector(this.servicePrincipalPool);
213
223
  }
214
224
  return this.sharedVector;
215
225
  }
216
-
217
- private requirePool(): LakebasePool {
218
- if (!this.pool) {
219
- this.pool = resolveLakebasePool(this.context, this.config);
220
- }
221
- return this.pool;
222
- }
223
226
  }
224
227
 
225
228
  /**
@@ -242,7 +245,7 @@ export class MemoryBuilder {
242
245
  * the lakebase pool from then on. The placeholder pool is `.end()`'d
243
246
  * so its socket book-keeping is released.
244
247
  */
245
- function buildSharedPgVector(pool: LakebasePool): PgVector {
248
+ function buildSharedPgVector(pool: Pool): PgVector {
246
249
  const vector = new PgVector({
247
250
  id: `pg${randomUUID()}`,
248
251
  host: "-1",
@@ -252,7 +255,7 @@ function buildSharedPgVector(pool: LakebasePool): PgVector {
252
255
  password: "_",
253
256
  });
254
257
  const placeholder = vector.pool;
255
- vector.pool = pool as Pool;
258
+ vector.pool = pool;
256
259
  void placeholder.end().catch(() => undefined);
257
260
  return vector;
258
261
  }