@dbx-tools/appkit-mastra 0.6.224 → 0.6.225

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/src/agents.ts CHANGED
@@ -24,7 +24,6 @@ import type {
24
24
  ToolProvider,
25
25
  } from "@databricks/appkit/beta";
26
26
  import { plugin } from "@dbx-tools/appkit";
27
- import { fallback } from "@dbx-tools/model";
28
27
  import { log, object, string } from "@dbx-tools/shared-core";
29
28
  import type {
30
29
  AgentConfig,
@@ -368,18 +367,21 @@ export type MastraMemoryConfigOverride = DistributiveOmit<PgVectorConfig, "id">
368
367
  id?: string;
369
368
  };
370
369
 
370
+ /** How an agent's unpinned model is resolved. */
371
+ export type AgentDefaultModel =
372
+ { kind: "auto" } | { kind: "configured"; model: string } | { kind: "dynamic" };
373
+
371
374
  /** Output of {@link buildAgents}: resolved agents plus the default id. */
372
375
  export interface BuiltAgents {
373
376
  agents: Record<string, Agent>;
374
377
  defaultAgentId: string;
375
378
  /**
376
- * Static default serving-endpoint id per agent id, as resolved by
377
- * {@link describeAgentDefaultModel}. `"<dynamic>"` when the model is a
378
- * function decided at call time. Consumed by the plugin's
379
- * `GET /default-model` route handler so the client's model picker can label
380
- * its default option with the real endpoint's humanized name.
379
+ * Default model intent per agent id. An unconfigured agent is `"auto"` and
380
+ * resolves the highest-ranked live endpoint; a string is `"configured"`; a
381
+ * custom function is `"dynamic"`. Consumed by the plugin's
382
+ * `GET /default-model` route.
381
383
  */
382
- defaultModels: Record<string, string>;
384
+ defaultModels: Record<string, AgentDefaultModel>;
383
385
  /**
384
386
  * Ambient tools shared across every agent (the built-in system tools
385
387
  * spread with `config.tools`). Surfaced so the optional MCP server
@@ -559,7 +561,7 @@ export async function buildAgents(opts: {
559
561
  ...(config.stripStaleCharts === false ? [] : [stripStaleChartsProcessor]),
560
562
  ];
561
563
  const agents: Record<string, Agent> = {};
562
- const defaultModels: Record<string, string> = {};
564
+ const defaultModels: Record<string, AgentDefaultModel> = {};
563
565
  const approvalGatedByAgent: Array<{ agentId: string; toolIds: string[] }> = [];
564
566
 
565
567
  for (const [id, def] of Object.entries(definitions)) {
@@ -613,7 +615,8 @@ export async function buildAgents(opts: {
613
615
  log.info("agent registered", {
614
616
  id,
615
617
  name: def.name ?? id,
616
- defaultModel,
618
+ defaultModel:
619
+ defaultModel.kind === "configured" ? defaultModel.model : `<${defaultModel.kind}>`,
617
620
  tools: Object.keys(tools),
618
621
  });
619
622
  }
@@ -682,34 +685,28 @@ function assertApprovalGatedToolsHaveStorage(
682
685
  }
683
686
 
684
687
  /**
685
- * Best-effort description of the *static* default model an agent will
686
- * resolve to at call time. Walks the same precedence ladder as
687
- * {@link resolveModel} / {@link buildModel}:
688
+ * Describe how an agent resolves its unpinned model:
688
689
  *
689
- * 1. Per-agent `def.model` (string sugar -> the literal id;
690
- * function / `DynamicArgument` -> `"<dynamic>"` because the
691
- * resolver decides at call time).
690
+ * 1. Per-agent `def.model` (string sugar is configured; a function is
691
+ * dynamic).
692
692
  * 2. Plugin-level `config.defaultModel` (same rules).
693
693
  * 3. `DATABRICKS_SERVING_ENDPOINT_NAME` env var.
694
- * 4. First entry of `config.defaultModelFallbacks ?? fallback.FALLBACK_MODEL_IDS`.
694
+ * 4. Automatic live-catalogue ranking when none is set.
695
695
  *
696
696
  * Used for the startup `agent registered` log so operators can see
697
- * which endpoint each agent points at by default. Per-request
698
- * overrides (`X-Mastra-Model` etc.) and the workspace-catalogue
699
- * fuzzy match are still applied at runtime.
697
+ * `defaultModelFallbacks` remains an operator-pinned candidate list inside the
698
+ * automatic live lookup; it is not advertised as a model until availability is
699
+ * checked.
700
700
  */
701
701
  function describeAgentDefaultModel(
702
702
  config: MastraPluginConfig,
703
703
  def: AnyMastraAgentDefinition,
704
- ): string {
704
+ ): AgentDefaultModel {
705
705
  const effective = def.model ?? config.defaultModel;
706
- if (typeof effective === "string") return effective;
707
- if (effective !== undefined) return "<dynamic>";
708
- return (
709
- process.env.DATABRICKS_SERVING_ENDPOINT_NAME ??
710
- config.defaultModelFallbacks?.[0] ??
711
- fallback.FALLBACK_MODEL_IDS[0]!
712
- );
706
+ if (typeof effective === "string") return { kind: "configured", model: effective };
707
+ if (effective !== undefined) return { kind: "dynamic" };
708
+ const environment = process.env.DATABRICKS_SERVING_ENDPOINT_NAME;
709
+ return environment ? { kind: "configured", model: environment } : { kind: "auto" };
713
710
  }
714
711
 
715
712
  /**
package/src/config.ts CHANGED
@@ -327,11 +327,10 @@ export interface MastraPluginConfig extends BasePluginConfig {
327
327
  * When unset, resolution is driven by the live Foundation Model API
328
328
  * `quality` / `speed` / `cost` scores: endpoints are classified into
329
329
  * chat classes (`classifyEndpoints`) and walked best-first
330
- * (ChatThinking -> ChatBalanced -> ChatFast), with the small built-in
331
- * `FALLBACK_MODEL_IDS` list as the floor when the catalogue can't be
332
- * read. Set this to
333
- * pin a regulated workspace to an approved subset, or to put custom
334
- * endpoints in front of the auto-classified catalogue.
330
+ * (ChatThinking -> ChatBalanced -> ChatFast). The automatic default is
331
+ * live-only and fails when no matching endpoint is currently available.
332
+ * Set this to pin a regulated workspace to an approved subset, or to put
333
+ * custom endpoints in front of the auto-classified catalogue.
335
334
  */
336
335
  defaultModelFallbacks?: readonly string[];
337
336
  /**
@@ -654,7 +653,7 @@ export const MASTRA_CONFIG_SCHEMA: ConfigSchema = {
654
653
  defaultModel: {
655
654
  type: "string",
656
655
  description:
657
- "Serving endpoint applied to every agent that omits its own model. Falls back to DATABRICKS_SERVING_ENDPOINT_NAME, then the auto-resolved catalogue.",
656
+ "Serving endpoint applied to every agent that omits its own model. Falls back to DATABRICKS_SERVING_ENDPOINT_NAME, then the highest-ranked currently available catalogue endpoint.",
658
657
  },
659
658
  defaultModelFallbacks: {
660
659
  type: "array",
package/src/model.ts CHANGED
@@ -27,7 +27,7 @@ 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
29
  import { functionModule, json, log, net } from "@dbx-tools/shared-core";
30
- import { model } from "@dbx-tools/shared-model";
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";
33
33
 
@@ -74,6 +74,43 @@ export interface BuildModelOverrides {
74
74
  modelClass?: ModelClass;
75
75
  }
76
76
 
77
+ function selectionInput(
78
+ config: MastraPluginConfig,
79
+ requested: string | undefined,
80
+ defaultClass: ModelClass | undefined,
81
+ serving = resolveServingConfig(config),
82
+ ) {
83
+ const requestedClass = requested !== undefined ? parseModelClass(requested) : null;
84
+ const explicit = requestedClass === null ? requested : undefined;
85
+ const modelClass = requestedClass ?? defaultClass;
86
+ return {
87
+ ...(explicit !== undefined ? { explicit } : {}),
88
+ fuzzy: serving.fuzzy,
89
+ threshold: serving.threshold,
90
+ ...(modelClass !== undefined ? { modelClass } : {}),
91
+ fallbacks: serving.fallbacks,
92
+ liveOnly: requested === undefined,
93
+ ttlMs: serving.ttlMs,
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Resolve an agent's unpinned default against an already-loaded live catalogue.
99
+ *
100
+ * With no configured id/class/fallback, the generic model ranker selects the
101
+ * highest-ranked currently available GPT, then falls back to the highest-ranked
102
+ * live chat endpoint when the workspace has no GPT.
103
+ */
104
+ export function resolveDefaultModelId(
105
+ config: MastraPluginConfig,
106
+ endpoints: readonly ServingEndpointSummary[],
107
+ overrides: BuildModelOverrides = {},
108
+ ): string {
109
+ const requested = overrides.modelId ?? process.env.DATABRICKS_SERVING_ENDPOINT_NAME;
110
+ return resolve.resolveModel(endpoints, selectionInput(config, requested, overrides.modelClass))
111
+ .modelId;
112
+ }
113
+
77
114
  /**
78
115
  * Resolve a `MastraModelConfig` for the current agent step. Runs
79
116
  * while `agent.stream` is inside the `asUser(req)` scope so tokens
@@ -122,18 +159,12 @@ export async function buildModel(
122
159
  // internal `overrides.modelClass` (e.g. the chart planner) is the
123
160
  // floor when nothing was requested.
124
161
  const requested = override ?? overrides.modelId ?? process.env.DATABRICKS_SERVING_ENDPOINT_NAME;
125
- const requestedClass = requested !== undefined ? parseModelClass(requested) : null;
126
- const explicit = requestedClass === null ? requested : undefined;
127
- const modelClass = requestedClass ?? overrides.modelClass;
128
162
 
129
- const { modelId, source } = await selectModel(executionContext.client, host, {
130
- ...(explicit !== undefined ? { explicit } : {}),
131
- fuzzy: serving.fuzzy,
132
- threshold: serving.threshold,
133
- ...(modelClass !== undefined ? { modelClass } : {}),
134
- fallbacks: serving.fallbacks,
135
- ttlMs: serving.ttlMs,
136
- });
163
+ const { modelId, source } = await selectModel(
164
+ executionContext.client,
165
+ host,
166
+ selectionInput(config, requested, overrides.modelClass, serving),
167
+ );
137
168
  logger.debug("model selected", { modelId, source, requested });
138
169
 
139
170
  return {
package/src/plugin.ts CHANGED
@@ -95,6 +95,7 @@ import {
95
95
  import { buildMcpServer, type ResolvedMcp } from "./mcp.ts";
96
96
  import { createMemoryBuilder, createServicePrincipalPool, needsLakebase } from "./memory.ts";
97
97
  import { logFeedback, resolveFeedbackEnabled } from "./mlflow.ts";
98
+ import { resolveDefaultModelId } from "./model.ts";
98
99
  import { buildObservability } from "./observability.ts";
99
100
  import { provisionRemoteSkills } from "./remote-skills.ts";
100
101
  import {
@@ -506,16 +507,12 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
506
507
  },
507
508
  });
508
509
 
509
- // `GET /default-model` (and `/default-model/:agentId`) reports the static
510
- // serving-endpoint an agent falls back to when the client pins no model,
511
- // so the picker can label its default option with the model's humanized
512
- // name. Agent-scoped via the optional `/:agentId` suffix (URL symmetry
513
- // with the history / threads / suggestions routes), defaulting to the
514
- // default agent. `model` / `displayName` are null when the agent has no
515
- // static default (a dynamic, call-time model); an unknown agent id is a
516
- // 404, matching the history / threads routes. Reads only in-memory build
517
- // state, so it needs no OBO scoping. Registered before the catch-all,
518
- // same as `/models`.
510
+ // `GET /default-model` (and `/default-model/:agentId`) reports what an
511
+ // unpinned turn will use. A configured string is returned directly; an
512
+ // automatic default resolves the highest-ranked currently available GPT
513
+ // (or general chat fallback) from the same OBO-scoped live catalogue as
514
+ // `/models`; a custom dynamic resolver remains null because it is call-time
515
+ // application code.
519
516
  const handleDefaultModel = async (
520
517
  req: express.Request,
521
518
  res: express.Response,
@@ -526,11 +523,20 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
526
523
  return;
527
524
  }
528
525
  const agentId = requested ?? this.built?.defaultAgentId ?? FALLBACK_AGENT_ID;
529
- const raw = this.built?.defaultModels[agentId];
530
- // `"<dynamic>"` (a call-time function) has no fixed id to advertise.
531
- const model = raw && raw !== "<dynamic>" ? raw : null;
532
- // Return the humanized label too, so the picker shows a friendly name on
533
- // load without waiting on the `/models` catalogue (no raw-id flash).
526
+ const intent = this.built?.defaultModels[agentId];
527
+ let model: string | null = null;
528
+ if (intent?.kind === "configured") {
529
+ model = intent.model;
530
+ } else if (intent?.kind === "auto") {
531
+ const result = await this.scopedSelf(req).listModelsResult();
532
+ if (!result.ok) {
533
+ this.sendFailure(res, result, "default-model", MODEL_CATALOGUE_FAILED_MESSAGE);
534
+ return;
535
+ }
536
+ if (result.data.length > 0) {
537
+ model = resolveDefaultModelId(this.config, result.data);
538
+ }
539
+ }
534
540
  res.json({
535
541
  agentId,
536
542
  model,