@dbx-tools/appkit-mastra 0.9.5 → 0.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +23 -24
  2. package/index.ts +1 -3
  3. package/lib/index.d.ts +1 -3
  4. package/lib/index.js +2 -3
  5. package/lib/src/agents.d.ts +2 -2
  6. package/lib/src/agents.js +6 -6
  7. package/lib/src/chart.js +23 -23
  8. package/lib/src/evaluation.js +3 -3
  9. package/lib/src/genie.d.ts +2 -2
  10. package/lib/src/genie.js +20 -20
  11. package/lib/src/memory.js +9 -26
  12. package/lib/src/mlflow.js +4 -4
  13. package/lib/src/model.js +4 -4
  14. package/lib/src/monty-sandbox.js +6 -6
  15. package/lib/src/observability.js +3 -3
  16. package/lib/src/plugin.js +26 -21
  17. package/lib/src/remote-skills.js +20 -30
  18. package/lib/src/sandbox.js +10 -10
  19. package/lib/src/server.js +5 -5
  20. package/lib/src/serving-sanitize.js +12 -9
  21. package/lib/src/serving.js +8 -8
  22. package/lib/src/storage-schema.js +4 -4
  23. package/lib/src/summarize.js +3 -3
  24. package/lib/src/workspaces.js +9 -9
  25. package/lib/src/writer.js +3 -3
  26. package/lib/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +17 -17
  28. package/src/agents.ts +8 -8
  29. package/src/chart.ts +24 -24
  30. package/src/evaluation.ts +2 -2
  31. package/src/genie.ts +20 -20
  32. package/src/memory.ts +11 -27
  33. package/src/mlflow.ts +3 -3
  34. package/src/model.ts +3 -3
  35. package/src/monty-sandbox.ts +5 -5
  36. package/src/observability.ts +2 -2
  37. package/src/plugin.ts +30 -20
  38. package/src/remote-skills.ts +36 -38
  39. package/src/sandbox.ts +9 -9
  40. package/src/server.ts +4 -4
  41. package/src/serving-sanitize.ts +11 -13
  42. package/src/serving.ts +7 -7
  43. package/src/storage-schema.ts +3 -3
  44. package/src/summarize.ts +2 -2
  45. package/src/workspaces.ts +8 -8
  46. package/src/writer.ts +2 -2
  47. package/lib/src/pagination.d.ts +0 -33
  48. package/lib/src/pagination.js +0 -36
  49. package/src/pagination.ts +0 -47
package/src/genie.ts CHANGED
@@ -44,9 +44,9 @@ import {
44
44
  ValidationError,
45
45
  } from "@databricks/appkit";
46
46
  import type { WorkspaceClient } from "@databricks/appkit";
47
- import { plugin } from "@dbx-tools/appkit";
47
+ import { pluginRegistry } from "@dbx-tools/appkit";
48
48
  import { chat, space as genieSpace } from "@dbx-tools/genie";
49
- import { error, log, string } from "@dbx-tools/shared-core";
49
+ import { errorUtils, log, stringUtils } from "@dbx-tools/shared-core";
50
50
  import { genieModel, type GenieMessage } from "@dbx-tools/shared-genie";
51
51
  import type { StartedEvent } from "@dbx-tools/shared-mastra";
52
52
  import type { RequestContext } from "@mastra/core/request-context";
@@ -245,7 +245,7 @@ async function readCachedConversationId(cacheKey: string | undefined): Promise<s
245
245
  return v ?? undefined;
246
246
  } catch (err) {
247
247
  logger.warn("conversation-cache:read-error", {
248
- error: error.errorMessage(err),
248
+ error: errorUtils.errorMessage(err),
249
249
  });
250
250
  return undefined;
251
251
  }
@@ -269,7 +269,7 @@ async function saveCachedConversationId(
269
269
  });
270
270
  } catch (err) {
271
271
  logger.warn("conversation-cache:write-error", {
272
- error: error.errorMessage(err),
272
+ error: errorUtils.errorMessage(err),
273
273
  });
274
274
  }
275
275
  }
@@ -281,7 +281,7 @@ async function evictCachedConversationId(cacheKey: string | undefined): Promise<
281
281
  await CacheManager.getInstanceSync().delete(cacheKey);
282
282
  } catch (err) {
283
283
  logger.warn("conversation-cache:delete-error", {
284
- error: error.errorMessage(err),
284
+ error: errorUtils.errorMessage(err),
285
285
  });
286
286
  }
287
287
  }
@@ -324,7 +324,7 @@ const prepareChartRequestSchema = chartPlannerRequestSchema
324
324
  .string()
325
325
  .min(1, "statement_id is required")
326
326
  .describe(
327
- string.toDescription(`
327
+ stringUtils.toDescription(`
328
328
  Genie \`statement_id\` to chart. Read from
329
329
  \`message.query_result.statement_id\` or
330
330
  \`message.attachments[*].query.statement_id\` returned by
@@ -345,7 +345,7 @@ const prepareChartRequestSchema = chartPlannerRequestSchema
345
345
  */
346
346
  function aliasSuffix(alias: string): string {
347
347
  if (alias === DEFAULT_GENIE_ALIAS) return "";
348
- const slug = string.toIdentifier(alias);
348
+ const slug = stringUtils.toIdentifier(alias);
349
349
  return slug ? `_${slug}` : "";
350
350
  }
351
351
 
@@ -381,7 +381,7 @@ function buildAskGenieTool(opts: {
381
381
  const hintLine = hint ? ` (${hint})` : "";
382
382
  return createTool({
383
383
  id: toolId,
384
- description: string.toDescription(`
384
+ description: stringUtils.toDescription(`
385
385
  Ask the Genie space "${alias}"${hintLine} ONE focused
386
386
  natural-language sub-question and wait for the turn to
387
387
  complete. Genie answers best when each call covers a
@@ -414,7 +414,7 @@ function buildAskGenieTool(opts: {
414
414
  .string()
415
415
  .min(1, "question is required")
416
416
  .describe(
417
- string.toDescription(`
417
+ stringUtils.toDescription(`
418
418
  ONE focused natural-language question about the data in this
419
419
  space, covering a single metric / dimension / time window
420
420
  (e.g. "What was Q3 revenue by region?"). Decompose a
@@ -507,11 +507,11 @@ function buildAskGenieTool(opts: {
507
507
  // A rejected conversation id is stale or inaccessible. Reset this
508
508
  // lane and retry once with a fresh Genie conversation.
509
509
  const rejectedConversationId = conversationId;
510
- if (rejectedConversationId && error.errorContext(err).notAccessible) {
510
+ if (rejectedConversationId && errorUtils.errorContext(err).notAccessible) {
511
511
  logger.warn("conversation-cache:stale, resetting", {
512
512
  spaceId,
513
513
  conversationId: rejectedConversationId,
514
- error: error.errorMessage(err),
514
+ error: errorUtils.errorMessage(err),
515
515
  });
516
516
  if (ownsReusableConversation) {
517
517
  await evictCachedConversationId(cacheKey);
@@ -541,7 +541,7 @@ function buildSpaceDescriptionTool(opts: { spaceId: string; alias: string }) {
541
541
  const toolId = `get_space_description${aliasSuffix(alias)}`;
542
542
  return createTool({
543
543
  id: toolId,
544
- description: string.toDescription(`
544
+ description: stringUtils.toDescription(`
545
545
  Return the Genie space "${alias}"'s title, description, and
546
546
  warehouse id. Cheap (single REST call, no LLM round-trip).
547
547
  Call this FIRST on any user turn that's going to touch
@@ -586,7 +586,7 @@ function buildSpaceSerializedTool(opts: { spaceId: string; alias: string }) {
586
586
  const toolId = `get_space_serialized${aliasSuffix(alias)}`;
587
587
  return createTool({
588
588
  id: toolId,
589
- description: string.toDescription(`
589
+ description: stringUtils.toDescription(`
590
590
  Return the full \`GenieSpace\` JSON for the "${alias}" space.
591
591
  Use only when you need exact column / table identifiers
592
592
  \`get_space_description\` doesn't expose. Larger payload, so
@@ -625,7 +625,7 @@ function buildGetStatementTool() {
625
625
  const toolId = "get_statement";
626
626
  return createTool({
627
627
  id: toolId,
628
- description: string.toDescription(`
628
+ description: stringUtils.toDescription(`
629
629
  Fetch the rows of a Genie statement by its \`statement_id\` (the
630
630
  value at \`message.query_result.statement_id\` or
631
631
  \`message.attachments[*].query.statement_id\` returned from
@@ -645,7 +645,7 @@ function buildGetStatementTool() {
645
645
  .string()
646
646
  .min(1, "statement_id is required")
647
647
  .describe(
648
- string.toDescription(`
648
+ stringUtils.toDescription(`
649
649
  Genie \`statement_id\` whose rows to read. Take it from
650
650
  \`message.query_result.statement_id\` or
651
651
  \`message.attachments[*].query.statement_id\` on an
@@ -710,7 +710,7 @@ function buildPrepareChartTool(opts: { config: MastraPluginConfig }) {
710
710
  const toolId = "prepare_chart";
711
711
  return createTool({
712
712
  id: toolId,
713
- description: string.toDescription([
713
+ description: stringUtils.toDescription([
714
714
  `
715
715
  Queue a chart for the rows of a Genie statement. Mints a
716
716
  \`chartId\` plus its complete \`marker\` synchronously and
@@ -782,7 +782,7 @@ function buildPrepareChartTool(opts: { config: MastraPluginConfig }) {
782
782
  * variant that names the suffixed per-space tools
783
783
  * (e.g. `ask_genie_sales`).
784
784
  */
785
- export const GENIE_INSTRUCTIONS = string.toDescription([
785
+ export const GENIE_INSTRUCTIONS = stringUtils.toDescription([
786
786
  "Genie orchestration. For every user question that needs SQL-backed data:",
787
787
  {
788
788
  numbered: [
@@ -975,7 +975,7 @@ type AppKitGenieConfig = NonNullable<Parameters<typeof genie>[0]>;
975
975
  */
976
976
  export function resolveGenieSpaces(
977
977
  config: MastraPluginConfig,
978
- context: plugin.PluginContextLike | undefined,
978
+ context: pluginRegistry.PluginContextLike | undefined,
979
979
  ): Record<string, GenieSpaceConfig> {
980
980
  const merged: Record<string, GenieSpaceConfig> = {};
981
981
 
@@ -988,7 +988,7 @@ export function resolveGenieSpaces(
988
988
  // Source 2: AppKit `genie()` plugin instance config. Use a
989
989
  // structural cast - `Plugin.config` is `protected` in TS only,
990
990
  // and the runtime layout is plain object property access.
991
- const geniePlugin = plugin.instance(context, genie);
991
+ const geniePlugin = pluginRegistry.instance(context, genie);
992
992
  if (geniePlugin) {
993
993
  const pluginSpaces = (geniePlugin as unknown as { config?: AppKitGenieConfig }).config?.spaces;
994
994
  if (pluginSpaces) {
@@ -1142,7 +1142,7 @@ export async function collectSpaceSuggestions(opts: {
1142
1142
  } catch (err) {
1143
1143
  logger.warn("suggestions:fetch-error", {
1144
1144
  spaceId,
1145
- error: error.errorMessage(err),
1145
+ error: errorUtils.errorMessage(err),
1146
1146
  });
1147
1147
  questions = [];
1148
1148
  }
package/src/memory.ts CHANGED
@@ -33,7 +33,8 @@
33
33
  */
34
34
 
35
35
  import { getUsernameWithApiLookup } from "@databricks/appkit";
36
- import { error, hash, log } from "@dbx-tools/shared-core";
36
+ import { migration as appkitMigration } from "@dbx-tools/appkit";
37
+ import { hash, log } from "@dbx-tools/shared-core";
37
38
  import { fastembed } from "@mastra/fastembed";
38
39
  import { Memory } from "@mastra/memory";
39
40
  import { PgVector, PostgresStore } from "@mastra/pg";
@@ -46,19 +47,6 @@ import { summaryModel, TITLE_INSTRUCTIONS } from "./summarize.ts";
46
47
 
47
48
  const logger = log.logger("mastra/memory");
48
49
 
49
- /** Process-wide dedupe so instance + per-agent stores share one ownership log. */
50
- const loggedMigrationErrors = new Set<string>();
51
-
52
- /** Whether `LOG_LEVEL` is currently at or below debug. */
53
- function isDebugEnabled(): boolean {
54
- return log.isLevelEnabled("debug");
55
- }
56
-
57
- /** Whether a migration failed only because this role does not own an existing object. */
58
- function isOwnershipMigrationError(err: unknown): boolean {
59
- return error.errorContext(err).hasMessage("must be owner");
60
- }
61
-
62
50
  /**
63
51
  * Soften {@link PostgresStore.init} so a failed migration (e.g. Lakebase
64
52
  * `must be owner of table …` on `ALTER`) is logged once instead of rethrown
@@ -87,19 +75,15 @@ export function withSoftStorageInit(store: PostgresStore): PostgresStore {
87
75
  try {
88
76
  await originalInit();
89
77
  } catch (err) {
90
- if (!isOwnershipMigrationError(err)) throw err;
91
- const message = error.errorMessage(err);
92
- if (!loggedMigrationErrors.has(message)) {
93
- loggedMigrationErrors.add(message);
94
- if (isDebugEnabled()) {
95
- logger.error("mastra storage migration failed", err);
96
- } else {
97
- logger.warn("mastra storage migration failed", {
98
- error: message,
99
- storeId: soft.id,
100
- schema: soft.schema,
101
- });
102
- }
78
+ if (
79
+ !appkitMigration.handleOwnershipMigrationError(err, {
80
+ scope: "mastra-storage",
81
+ logger,
82
+ event: "mastra storage migration failed",
83
+ context: { storeId: soft.id, schema: soft.schema },
84
+ })
85
+ ) {
86
+ throw err;
103
87
  }
104
88
  }
105
89
  })();
package/src/mlflow.ts CHANGED
@@ -22,7 +22,7 @@
22
22
  */
23
23
 
24
24
  import { appkit } from "@dbx-tools/appkit";
25
- import { async, error, log } from "@dbx-tools/shared-core";
25
+ import { asyncUtils, errorUtils, log } from "@dbx-tools/shared-core";
26
26
  import { feedback } from "@dbx-tools/shared-mastra";
27
27
  import { databricksFetch, readResponseJson, readResponseText } from "./rest.ts";
28
28
 
@@ -125,7 +125,7 @@ export async function logFeedback(
125
125
  } catch (err) {
126
126
  logger.warn("feedback request failed", {
127
127
  traceId: params.traceId,
128
- error: error.errorMessage(err),
128
+ error: errorUtils.errorMessage(err),
129
129
  });
130
130
  return undefined;
131
131
  }
@@ -139,7 +139,7 @@ export async function logFeedback(
139
139
  // Trace export is async; a fresh trace may not exist yet. Retry a
140
140
  // few times with a short backoff before giving up softly.
141
141
  if (res.status === 404 && attempt < NOT_FOUND_RETRIES) {
142
- await async.sleep(NOT_FOUND_BACKOFF_MS * (attempt + 1));
142
+ await asyncUtils.sleep(NOT_FOUND_BACKOFF_MS * (attempt + 1));
143
143
  continue;
144
144
  }
145
145
  logger.warn("feedback not recorded", {
package/src/model.ts CHANGED
@@ -26,7 +26,7 @@
26
26
  import { getExecutionContext } from "@databricks/appkit";
27
27
  import { classes, invoke, resolve } from "@dbx-tools/model";
28
28
  import { ModelServingApi, modelServingApi } from "@dbx-tools/model-rs";
29
- import { functionModule, json, log, net } from "@dbx-tools/shared-core";
29
+ import { functionUtils, json, log, net } from "@dbx-tools/shared-core";
30
30
  import { model, type ServingEndpointSummary } from "@dbx-tools/shared-model";
31
31
  import type { MastraModelConfig } from "@mastra/core/llm";
32
32
  import type { RequestContext } from "@mastra/core/request-context";
@@ -193,12 +193,12 @@ const CHAT_COMPLETIONS_PATH = `/${invoke.CHAT_COMPLETIONS_PATH}`;
193
193
  * `choices[].message.content` or `choices[].delta.content` as a parts
194
194
  * array that the AI SDK's OpenAI schema rejects.
195
195
  *
196
- * Safe to call from any hot path: {@link functionModule.memoize} ensures
196
+ * Safe to call from any hot path: {@link functionUtils.memoize} ensures
197
197
  * the wrapper is installed at most once per process, so subsequent
198
198
  * calls are a no-op even when {@link buildModel} fires on every agent
199
199
  * step.
200
200
  */
201
- const setupFetchInterceptor = functionModule.memoize((): void => {
201
+ const setupFetchInterceptor = functionUtils.memoize((): void => {
202
202
  globalThis.fetch = createServingFetchInterceptor(globalThis.fetch.bind(globalThis));
203
203
  });
204
204
 
@@ -8,7 +8,7 @@
8
8
  * @module
9
9
  */
10
10
 
11
- import { error, functionModule } from "@dbx-tools/shared-core";
11
+ import { errorUtils, functionUtils } from "@dbx-tools/shared-core";
12
12
  import type {
13
13
  CommandResult,
14
14
  ExecuteCommandOptions,
@@ -20,8 +20,8 @@ import type { CheckoutOptions, Monty as MontyPool, MontySession } from "@pydanti
20
20
  const DEFAULT_COMMAND_TIMEOUT_MS = 30_000;
21
21
  const DEFAULT_MAX_MEMORY_BYTES = 10_000_000;
22
22
 
23
- const montyModule = functionModule.memoize(() => import("@pydantic/monty/node"));
24
- const montyPool = functionModule.memoize(async (): Promise<MontyPool> => {
23
+ const montyModule = functionUtils.memoize(() => import("@pydantic/monty/node"));
24
+ const montyPool = functionUtils.memoize(async (): Promise<MontyPool> => {
25
25
  const { Monty } = await montyModule();
26
26
  return Monty.create({
27
27
  minProcesses: 1,
@@ -76,7 +76,7 @@ export class MontySandbox implements WorkspaceSandbox {
76
76
  this.status = "running";
77
77
  } catch (caught) {
78
78
  this.status = "error";
79
- this.error = error.errorMessage(caught);
79
+ this.error = errorUtils.errorMessage(caught);
80
80
  throw caught;
81
81
  }
82
82
  }
@@ -181,7 +181,7 @@ export class MontySandbox implements WorkspaceSandbox {
181
181
  if (aborted || options.abortSignal?.aborted) {
182
182
  throw abortReason(options.abortSignal);
183
183
  }
184
- const message = error.errorMessage(caught);
184
+ const message = errorUtils.errorMessage(caught);
185
185
  output.emit("stderr", output.stderr.value ? `\n${message}` : message);
186
186
  const { MontyCrashedError } = await montyModule();
187
187
  const timedOut = caught instanceof MontyCrashedError && caught.timedOut;
@@ -42,7 +42,7 @@
42
42
  * @module
43
43
  */
44
44
 
45
- import { project } from "@dbx-tools/core";
45
+ import { projectUtils } from "@dbx-tools/core";
46
46
  import { log } from "@dbx-tools/shared-core";
47
47
  import { Observability } from "@mastra/observability";
48
48
  import { OtelBridge } from "@mastra/otel-bridge";
@@ -122,7 +122,7 @@ export async function buildObservability(
122
122
  return undefined;
123
123
  }
124
124
 
125
- const serviceName = options?.serviceName ?? (await project.name()) ?? DEFAULT_SERVICE_NAME;
125
+ const serviceName = options?.serviceName ?? (await projectUtils.name()) ?? DEFAULT_SERVICE_NAME;
126
126
  const requestContextKeys = [...(options?.requestContextKeys ?? TRACE_REQUEST_CONTEXT_KEYS)];
127
127
 
128
128
  // The OTel HTTP exporter treats `OTEL_EXPORTER_OTLP_ENDPOINT` as a
package/src/plugin.ts CHANGED
@@ -56,9 +56,9 @@ import {
56
56
  type PluginManifest,
57
57
  type ResourceRequirement,
58
58
  } from "@databricks/appkit";
59
- import { plugin } from "@dbx-tools/appkit";
60
- import { serving as modelServing } from "@dbx-tools/model";
61
- import { async, error, log, object, string } from "@dbx-tools/shared-core";
59
+ import { pluginRegistry } from "@dbx-tools/appkit";
60
+ import { modelCatalog } from "@dbx-tools/model";
61
+ import { asyncUtils, errorUtils, log, object, stringUtils } from "@dbx-tools/shared-core";
62
62
  import {
63
63
  feedback,
64
64
  routes,
@@ -77,7 +77,12 @@ import type { Pool } from "pg";
77
77
  import { buildAgents, FALLBACK_AGENT_ID, type BuiltAgents } from "./agents.ts";
78
78
  import { fetchChart } from "./chart.ts";
79
79
  import { agentChatRoutes } from "./chat.ts";
80
- import { attributedUserId, MASTRA_CONFIG_SCHEMA, type MastraPluginConfig } from "./config.ts";
80
+ import {
81
+ attributedUserId,
82
+ executionContextUserId,
83
+ MASTRA_CONFIG_SCHEMA,
84
+ type MastraPluginConfig,
85
+ } from "./config.ts";
81
86
  import {
82
87
  chartFetchDefaults,
83
88
  feedbackWriteDefaults,
@@ -109,8 +114,8 @@ import { fetchStatementData, STATEMENT_ROW_CAP } from "./statement.ts";
109
114
  import { attachChatTurnTraceIo } from "./trace-io.ts";
110
115
  import { invalidFields } from "./validation.ts";
111
116
 
112
- const GENIE_MANIFEST = plugin.data(genie).plugin.manifest;
113
- const LAKEBASE_MANIFEST = plugin.data(lakebase).plugin.manifest;
117
+ const GENIE_MANIFEST = pluginRegistry.data(genie).plugin.manifest;
118
+ const LAKEBASE_MANIFEST = pluginRegistry.data(lakebase).plugin.manifest;
114
119
 
115
120
  /**
116
121
  * Budget for draining the memory service-principal pool on shutdown. Well
@@ -278,7 +283,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
278
283
  * already in the registry by the time this fires.
279
284
  */
280
285
  private applyLakebaseAutoDefaults(): void {
281
- const hasLakebase = plugin.instance(this.context, lakebase) !== undefined;
286
+ const hasLakebase = pluginRegistry.instance(this.context, lakebase) !== undefined;
282
287
  if (!hasLakebase) return;
283
288
  if (this.config.storage === undefined) this.config.storage = true;
284
289
  if (this.config.memory === undefined) this.config.memory = true;
@@ -300,10 +305,10 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
300
305
  // early cannot hold the event loop open for the rest of the window.
301
306
  const budget = new AbortController();
302
307
  try {
303
- await Promise.race([pool.end(), async.sleep(POOL_DRAIN_TIMEOUT_MS, budget.signal)]);
308
+ await Promise.race([pool.end(), asyncUtils.sleep(POOL_DRAIN_TIMEOUT_MS, budget.signal)]);
304
309
  } catch (err) {
305
310
  this.logger.error("error closing memory SP pool", {
306
- error: error.errorMessage(err),
311
+ error: errorUtils.errorMessage(err),
307
312
  });
308
313
  } finally {
309
314
  budget.abort();
@@ -411,7 +416,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
411
416
  * Returns the underlying `CacheManager.delete`/`clear` promise.
412
417
  */
413
418
  clearModelsCache: (host?: string): Promise<void> =>
414
- modelServing.clearServingEndpointsCache(host),
419
+ modelCatalog.clearServingEndpointsCache(host),
415
420
  };
416
421
  }
417
422
 
@@ -517,7 +522,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
517
522
  req: express.Request,
518
523
  res: express.Response,
519
524
  ): Promise<void> => {
520
- const requested = string.firstNonEmpty(req.params.agentId);
525
+ const requested = stringUtils.firstNonEmpty(req.params.agentId);
521
526
  if (requested !== null && !this.built?.agents[requested]) {
522
527
  res.status(404).json({ error: this.unknownAgentMessage(requested) });
523
528
  return;
@@ -611,8 +616,8 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
611
616
  method: "get",
612
617
  path: `${routes.MASTRA_ROUTES.embed}/:type/:id`,
613
618
  handler: async (req, res) => {
614
- const type = string.firstNonEmpty(req.params.type) ?? "";
615
- const id = string.firstNonEmpty(req.params.id);
619
+ const type = stringUtils.firstNonEmpty(req.params.type) ?? "";
620
+ const id = stringUtils.firstNonEmpty(req.params.id);
616
621
  const resolve = embedResolvers[type];
617
622
  if (!resolve) {
618
623
  this.logger.warn("embed:unsupported", {
@@ -874,7 +879,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
874
879
  if (Object.keys(spaces).length === 0) return { ok: true, data: [] };
875
880
  const client = getExecutionContext().client;
876
881
  return this.execute((executeSignal) => {
877
- const combined = async.combineAbortSignals(signal, executeSignal);
882
+ const combined = asyncUtils.combineAbortSignals(signal, executeSignal);
878
883
  return collectSpaceSuggestions({
879
884
  spaces,
880
885
  client,
@@ -931,7 +936,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
931
936
  const limit = Math.min(options.limit ?? STATEMENT_ROW_CAP, STATEMENT_ROW_CAP);
932
937
  return this.execute(
933
938
  async (executeSignal) => {
934
- const combined = async.combineAbortSignals(options.signal, executeSignal);
939
+ const combined = asyncUtils.combineAbortSignals(options.signal, executeSignal);
935
940
  try {
936
941
  const data = await fetchStatementData(client, statementId, {
937
942
  limit,
@@ -946,7 +951,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
946
951
  } catch (err) {
947
952
  // The Databricks SDK throws on 404; surface as `undefined`
948
953
  // so the route maps to a clean HTTP 404 instead of a 500.
949
- if (error.errorContext(err).notAccessible) return undefined;
954
+ if (errorUtils.errorContext(err).notAccessible) return undefined;
950
955
  throw err;
951
956
  }
952
957
  },
@@ -990,7 +995,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
990
995
  fetchChart(chartId, {
991
996
  userKey,
992
997
  ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
993
- signal: async.combineAbortSignals(options.signal, executeSignal) ?? options.signal,
998
+ signal: asyncUtils.combineAbortSignals(options.signal, executeSignal) ?? options.signal,
994
999
  }),
995
1000
  chartFetchDefaults,
996
1001
  );
@@ -1003,10 +1008,15 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
1003
1008
  */
1004
1009
  private async listModelsResult(): Promise<ExecutionResult<ServingEndpointSummary[]>> {
1005
1010
  return this.execute(async () => {
1006
- const client = getExecutionContext().client;
1011
+ const context = getExecutionContext();
1012
+ const client = context.client;
1013
+ const cacheIdentity = executionContextUserId(context);
1007
1014
  const host = (await client.config.getHost()).toString();
1008
1015
  const serving = resolveServingConfig(this.config);
1009
- return modelServing.listServingEndpoints(client, host, { ttlMs: serving.ttlMs });
1016
+ return modelCatalog.listServingEndpoints(client, host, {
1017
+ ttlMs: serving.ttlMs,
1018
+ cacheIdentity,
1019
+ });
1010
1020
  }, modelCatalogueDefaults);
1011
1021
  }
1012
1022
 
@@ -1037,7 +1047,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
1037
1047
  // `PgVector` singleton so registering N agents stays cheap. See
1038
1048
  // `./memory.js`.
1039
1049
  if (needsLakebase(this.config)) {
1040
- const spPgConfig = plugin
1050
+ const spPgConfig = pluginRegistry
1041
1051
  .require(this.context, lakebase, this.config)
1042
1052
  .exports()
1043
1053
  .getPgConfig();
@@ -57,10 +57,19 @@ import type { WorkspaceClient } from "@databricks/appkit";
57
57
  import { appkit } from "@dbx-tools/appkit";
58
58
  import type { WorkspaceClientLike } from "@dbx-tools/appkit";
59
59
  import { exec } from "@dbx-tools/core";
60
- import { DatabricksFileSystem, workspace as databricksWorkspace } from "@dbx-tools/databricks";
60
+ import { DatabricksFileSystem, workspaceClient } from "@dbx-tools/databricks";
61
61
  import { localFS, type LocalFileSystem } from "@dbx-tools/fs";
62
62
  import { find } from "@dbx-tools/path";
63
- import { error, hash, json, log, net, object, string } from "@dbx-tools/shared-core";
63
+ import {
64
+ asyncUtils,
65
+ errorUtils,
66
+ hash,
67
+ json,
68
+ log,
69
+ net,
70
+ object,
71
+ stringUtils,
72
+ } from "@dbx-tools/shared-core";
64
73
  import type { OneOrMany } from "@dbx-tools/shared-core";
65
74
  import type { FileSystem } from "@dbx-tools/shared-fs";
66
75
 
@@ -312,11 +321,11 @@ function isAiToolsSource(source: string): boolean {
312
321
  * not what a success produces.
313
322
  */
314
323
  function cachePolicy(sourceOptions: NormalizedSource): RemoteSkillCacheEntry["policy"] {
315
- const skills = string.parseList(sourceOptions.skills);
324
+ const skills = stringUtils.parseList(sourceOptions.skills);
316
325
  const policy = {
317
326
  ...(skills.length > 0 ? { skills: [...skills].sort() } : {}),
318
327
  ...(sourceOptions.experimental === true ? { experimental: true } : {}),
319
- ...(string.trimToNull(sourceOptions.ref) ? { ref: sourceOptions.ref!.trim() } : {}),
328
+ ...(stringUtils.trimToNull(sourceOptions.ref) ? { ref: sourceOptions.ref!.trim() } : {}),
320
329
  };
321
330
  return Object.keys(policy).length > 0 ? policy : undefined;
322
331
  }
@@ -359,7 +368,7 @@ async function readMetadata(fs: FileSystem): Promise<RemoteSkillsMetadata | unde
359
368
  if (!object.isRecord(parsed) || parsed.version !== METADATA_VERSION) return undefined;
360
369
  return object.isRecord(parsed.sources) ? parsed : undefined;
361
370
  } catch (err) {
362
- logger.debug("cache:unreadable", { error: error.errorMessage(err) });
371
+ logger.debug("cache:unreadable", { error: errorUtils.errorMessage(err) });
363
372
  return undefined;
364
373
  }
365
374
  }
@@ -490,13 +499,13 @@ export async function provisionRemoteSkills(
490
499
  } catch (err) {
491
500
  if (failOnError) {
492
501
  throw new Error(
493
- `failed to provision remote skill source "${sourceOptions.source}": ${error.errorMessage(err)}`,
494
- { cause: error.toError(err) },
502
+ `failed to provision remote skill source "${sourceOptions.source}": ${errorUtils.errorMessage(err)}`,
503
+ { cause: errorUtils.toError(err) },
495
504
  );
496
505
  }
497
506
  logger.warn("source:skipped", {
498
507
  source: sourceOptions.source,
499
- error: error.errorMessage(err),
508
+ error: errorUtils.errorMessage(err),
500
509
  });
501
510
  }
502
511
  }
@@ -533,7 +542,7 @@ async function openWritableWorkspace(
533
542
  client: WorkspaceClient,
534
543
  ): Promise<DatabricksFileSystem | undefined> {
535
544
  const fs = new DatabricksFileSystem({
536
- client: databricksWorkspace.toLegacyWorkspaceClient(client),
545
+ client: workspaceClient.toLegacyWorkspaceClient(client),
537
546
  root: basePath,
538
547
  readOnly: false,
539
548
  createRoot: true,
@@ -544,7 +553,7 @@ async function openWritableWorkspace(
544
553
  } catch (err) {
545
554
  logger.warn("destination:unwritable", {
546
555
  destination: basePath,
547
- error: error.errorMessage(err),
556
+ error: errorUtils.errorMessage(err),
548
557
  });
549
558
  return undefined;
550
559
  }
@@ -559,7 +568,7 @@ function resolveDatabricksBasePath(
559
568
  ): string | undefined {
560
569
  if (!client) return undefined;
561
570
  if (options.databricksBasePath) return options.databricksBasePath.trim() || undefined;
562
- const email = string.trimToNull(options.userEmail);
571
+ const email = stringUtils.trimToNull(options.userEmail);
563
572
  // A named user targets their personal Assistant tree (the "save a skill"
564
573
  // target); otherwise the shared workspace Assistant tree, which the built-in
565
574
  // Assistant-skills mount already scans.
@@ -598,13 +607,13 @@ async function stageAiTools(
598
607
  options: ProvisionRemoteSkillsOptions,
599
608
  ): Promise<string> {
600
609
  const maxBytes = resolveMaxBytes(sourceOptions, options);
601
- const ref = string.trimToNull(sourceOptions.ref) ?? AITOOLS_REF;
610
+ const ref = stringUtils.trimToNull(sourceOptions.ref) ?? AITOOLS_REF;
602
611
  const manifest = json.parse(
603
612
  (await download(aiToolsRawUrl(ref, "manifest.json"), maxBytes)).toString("utf8"),
604
613
  undefined,
605
614
  ) as AiToolsManifest | undefined;
606
615
 
607
- const wanted = new Set(string.parseList(sourceOptions.skills));
616
+ const wanted = new Set(stringUtils.parseList(sourceOptions.skills));
608
617
  const selected = Object.entries(manifest?.skills ?? {}).filter(([name, entry]) =>
609
618
  wanted.size > 0
610
619
  ? wanted.has(name)
@@ -620,11 +629,15 @@ async function stageAiTools(
620
629
  path: join(target, name, ...file.split("/")),
621
630
  })),
622
631
  );
623
- await mapConcurrent(files, AITOOLS_CONCURRENCY, async (item) => {
624
- const body = await download(item.url, maxBytes);
625
- await mkdir(dirname(item.path), { recursive: true });
626
- await writeFile(item.path, body);
627
- });
632
+ await asyncUtils.mapConcurrent(
633
+ files,
634
+ async (item) => {
635
+ const body = await download(item.url, maxBytes);
636
+ await mkdir(dirname(item.path), { recursive: true });
637
+ await writeFile(item.path, body);
638
+ },
639
+ { concurrency: AITOOLS_CONCURRENCY },
640
+ );
628
641
 
629
642
  logger.debug("aitools:staged", { ref, skills: selected.length, files: files.length });
630
643
  return target;
@@ -635,21 +648,6 @@ function aiToolsRawUrl(ref: string, path: string): string {
635
648
  return `https://raw.githubusercontent.com/${AITOOLS_REPO}/${ref}/${path}`;
636
649
  }
637
650
 
638
- /** Run `worker` over `items`, at most `limit` in flight. */
639
- async function mapConcurrent<T>(
640
- items: readonly T[],
641
- limit: number,
642
- worker: (item: T) => Promise<void>,
643
- ): Promise<void> {
644
- let cursor = 0;
645
- const runners = Array.from({ length: Math.min(limit, items.length) }, async () => {
646
- while (cursor < items.length) {
647
- await worker(items[cursor++]!);
648
- }
649
- });
650
- await Promise.all(runners);
651
- }
652
-
653
651
  /**
654
652
  * Copy a source into `target` with the `skills` CLI when it is installed.
655
653
  * Returns the dir holding the copied `SKILL.md` trees, or `undefined` when the
@@ -671,10 +669,10 @@ async function stageViaSkillsCli(
671
669
  "--copy",
672
670
  "-y",
673
671
  ];
674
- for (const skill of string.parseList(sourceOptions.skills)) {
672
+ for (const skill of stringUtils.parseList(sourceOptions.skills)) {
675
673
  args.push("--skill", skill);
676
674
  }
677
- if (string.parseList(sourceOptions.skills).length === 0) args.push("--skill", "*");
675
+ if (stringUtils.parseList(sourceOptions.skills).length === 0) args.push("--skill", "*");
678
676
 
679
677
  const result = await exec.spawn(cli.command, args, {
680
678
  cwd: target,
@@ -723,7 +721,7 @@ async function stageViaFetch(
723
721
  );
724
722
  }
725
723
  const buffer = await download(url.toString(), resolveMaxBytes(sourceOptions, options));
726
- const name = string.toSlug(deriveSkillName(url.toString())) || "remote-skill";
724
+ const name = stringUtils.toSlug(deriveSkillName(url.toString())) || "remote-skill";
727
725
  const skillDir = join(target, name);
728
726
  await mkdir(skillDir, { recursive: true });
729
727
  await writeFile(join(skillDir, "SKILL.md"), buffer);
@@ -743,8 +741,8 @@ async function download(url: string, maxBytes: number): Promise<Buffer> {
743
741
  const response = await fetch(url, {
744
742
  signal: AbortSignal.timeout(DEFAULT_DOWNLOAD_TIMEOUT_MS),
745
743
  }).catch((err) => {
746
- throw new Error(`download failed (${url}): ${error.errorMessage(err)}`, {
747
- cause: error.toError(err),
744
+ throw new Error(`download failed (${url}): ${errorUtils.errorMessage(err)}`, {
745
+ cause: errorUtils.toError(err),
748
746
  });
749
747
  });
750
748
  if (!response.ok) {