@dbx-tools/appkit-mastra 0.6.9 → 0.6.10

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/config.ts CHANGED
@@ -23,10 +23,10 @@ import { MASTRA_RESOURCE_ID_KEY, MASTRA_THREAD_ID_KEY } from "@mastra/core/reque
23
23
  import type { PgVectorConfig, PostgresStoreConfig } from "@mastra/pg";
24
24
 
25
25
  import type { MastraAgentDefinition, MastraTools } from "./agents.ts";
26
+ import type { DatabricksAIToolsOption } from "./databricks-aitools.ts";
26
27
  import type { GenieSpacesConfig } from "./genie.ts";
27
28
  import type { MastraIdentityMode } from "./identity.ts";
28
29
  import type { RemoteSkillsOption } from "./remote-skills.ts";
29
- import type { DatabricksAIToolsOption } from "./databricks-aitools.ts";
30
30
 
31
31
  /**
32
32
  * `RequestContext` key under which {@link MastraServer} stores the
@@ -586,15 +586,14 @@ export interface MastraPluginConfig extends BasePluginConfig {
586
586
  * Fold Databricks AI Tools skills (`databricks aitools`) into every
587
587
  * default-workspace agent as extra local skill scan paths.
588
588
  *
589
- * `false` (default) off; `"auto"` enables only when the CLI's installed
590
- * skills tree (`~/.databricks/aitools/skills`) exists or the `databricks`
591
- * CLI is resolvable, else silently no-op; `true` requires one of those and
592
- * fails startup otherwise. Pass an options bag to select `skills`, include
593
- * `experimental` skills, `refresh` from the CLI, or set an explicit `path`.
589
+ * `false` (default) off; `"auto"` runs the `databricks` CLI when it's
590
+ * installed and logs-and-continues when it isn't (never fails startup);
591
+ * `true` requires the CLI and fails startup otherwise. Pass an options bag to
592
+ * select `skills`, include `experimental` skills, or set an explicit `path`.
594
593
  *
595
- * Reuses the already-installed global tree when present (no CLI call);
596
- * otherwise runs `databricks aitools install --path <dir> --skills-only` to
597
- * materialize a resolved, agent-agnostic set into a temp dir.
594
+ * Runs `databricks aitools install --path <dir>` to materialize a resolved,
595
+ * agent-agnostic set (`<name>/SKILL.md` trees) into a temp dir the agents
596
+ * then scan. The CLI is the source of truth.
598
597
  *
599
598
  * @example
600
599
  * ```ts
@@ -629,7 +628,7 @@ export const MASTRA_CONFIG_SCHEMA: ConfigSchema = {
629
628
  databricksAITools: {
630
629
  type: ["boolean", "string", "object"],
631
630
  description:
632
- 'Fold Databricks AI Tools skills (databricks aitools) into agents as extra local skill paths. false (default) off; "auto" enables when the installed tree or the databricks CLI is available; true requires one and fails startup otherwise. An object selects skills, experimental, refresh, or path.',
631
+ 'Fold Databricks AI Tools skills (databricks aitools) into agents as extra local skill paths. false (default) off; "auto" runs the databricks CLI when installed and logs-and-continues otherwise; true requires the CLI and fails startup otherwise. An object selects skills, experimental, or path.',
633
632
  },
634
633
  storage: {
635
634
  type: ["boolean", "object"],
@@ -3,47 +3,40 @@
3
3
  *
4
4
  * Databricks AI Tools (`databricks aitools`) are Databricks-owned Agent-Skill
5
5
  * (`SKILL.md`) trees installed and kept up to date through the Databricks CLI.
6
- * The CLI installs them globally under `~/.databricks/aitools/skills/<name>/`
7
- * (with a `.state.json` manifest) and can also write a resolved, agent-agnostic
8
- * copy to any directory with `databricks aitools install --path <dir>
9
- * --skills-only`.
10
- *
11
- * {@link provisionDatabricksAITools} folds those skills into a Mastra workspace
12
- * as extra LOCAL skill scan paths so an agent can use them without anyone
13
- * hand-copying `SKILL.md` files. It never reimplements the CLI's sourcing: it
14
- * either points Mastra at the already-installed global tree, or shells out to
15
- * the CLI to materialize a curated subset into a temp dir.
6
+ * {@link provisionDatabricksAITools} shells out to the CLI
7
+ * (`databricks aitools install --path <dir>`) to materialize a resolved,
8
+ * agent-agnostic skill set into a directory, then hands it to Mastra as an
9
+ * extra LOCAL skill scan path - so an agent gets first-class Databricks skills
10
+ * without anyone hand-copying `SKILL.md` files. It never reimplements the CLI's
11
+ * sourcing; the CLI is the source of truth.
16
12
  *
17
13
  * The option is `false | true | "auto" | DatabricksAIToolsOptions`:
18
14
  *
19
15
  * - `false` (default) - off.
20
- * - `"auto"` - enable only when the global tree already exists OR the
21
- * `databricks` CLI is resolvable; otherwise silently no-op. The "on if the
22
- * CLI is around" default.
23
- * - `true` - required: fail startup (subject to `failOnError`) when neither the
24
- * installed tree nor the CLI can supply skills.
25
- * - an options bag for a specific `skills` subset, `experimental` skills, a
26
- * `refresh` that re-runs the CLI even when a tree exists, or an explicit
27
- * `path`.
16
+ * - `"auto"` - run the CLI when it's installed; if the `databricks` CLI isn't
17
+ * available, log and move on (never fails startup). The "on if the CLI is
18
+ * around" default.
19
+ * - `true` (`"require"`) - require the CLI: fail startup (subject to
20
+ * `failOnError`) when it isn't available or produces nothing.
21
+ * - an options bag for a specific `skills` subset, `experimental` skills, an
22
+ * explicit output `path`, or a custom `cli` path.
28
23
  *
29
24
  * @module
30
25
  */
31
26
 
32
- import { mkdtemp } from "node:fs/promises";
33
27
  import { existsSync } from "node:fs";
34
- import { homedir, tmpdir } from "node:os";
35
- import { join } from "node:path";
36
- import { spawn } from "@dbx-tools/core";
28
+ import { exec } from "@dbx-tools/core";
29
+ import { localFS } from "@dbx-tools/fs";
37
30
  import { error, log, string } from "@dbx-tools/shared-core";
38
31
 
39
32
  const logger = log.logger("mastra/databricks-aitools");
40
33
 
41
- /** Global skills tree the `databricks aitools` CLI installs into. */
42
- const GLOBAL_AITOOLS_SKILLS_PATH = join(homedir(), ".databricks", "aitools", "skills");
43
-
44
34
  /** The Databricks CLI binary name; resolved on `PATH`. */
45
35
  const DATABRICKS_CLI = "databricks";
46
36
 
37
+ /** Stable temp directory the CLI's skill tree is rebuilt into each boot. */
38
+ const SKILLS_CACHE_DIR = "databricks-aitools";
39
+
47
40
  /* -------------------------------- types -------------------------------- */
48
41
 
49
42
  /** How aggressively to enable Databricks AI Tools skills. */
@@ -52,22 +45,17 @@ export type DatabricksAIToolsMode = "auto" | "require";
52
45
  /** Options for {@link provisionDatabricksAITools}. */
53
46
  export interface DatabricksAIToolsOptions {
54
47
  /**
55
- * `"auto"` (default) enables only when the installed tree exists or the CLI
56
- * is resolvable; `"require"` fails startup when neither can supply skills.
48
+ * `"auto"` (default) runs the CLI when installed and logs-and-continues when
49
+ * it isn't; `"require"` fails startup when the CLI can't supply skills.
57
50
  */
58
51
  mode?: DatabricksAIToolsMode;
59
52
  /** Install only these skill names (comma/space list or array). */
60
53
  skills?: string | string[];
61
54
  /** Include experimental skills when the CLI has to fetch. */
62
55
  experimental?: boolean;
63
- /**
64
- * Re-run the CLI to (re)materialize skills into a fresh dir even when the
65
- * global tree already exists. Defaults to `false` (reuse the installed tree).
66
- */
67
- refresh?: boolean;
68
56
  /**
69
57
  * Explicit directory to materialize skills into with the CLI. Defaults to a
70
- * temp dir. Ignored when the installed global tree is used as-is.
58
+ * fresh {@link localFS.tmpFS} root.
71
59
  */
72
60
  path?: string;
73
61
  /** Absolute path to the `databricks` CLI. Defaults to `databricks` on PATH. */
@@ -87,7 +75,7 @@ export interface ProvisionedDatabricksAITools {
87
75
  /** Extra LOCAL skill scan paths to hand Mastra. Empty when disabled/unresolved. */
88
76
  localSkillPaths: string[];
89
77
  /** How the skills were sourced, for logging. */
90
- source?: "installed" | "cli";
78
+ source?: "cli";
91
79
  }
92
80
 
93
81
  /* ------------------------------- helpers ------------------------------- */
@@ -121,36 +109,25 @@ export async function provisionDatabricksAITools(
121
109
 
122
110
  const mode = options.mode ?? "auto";
123
111
  const failOnError = options.failOnError ?? mode === "require";
124
- const wantsCliFetch =
125
- options.refresh === true ||
126
- options.path !== undefined ||
127
- string.parseList(options.skills).length > 0 ||
128
- options.experimental === true;
129
112
 
130
113
  try {
131
- // Fast path: reuse the already-installed global tree, no CLI call.
132
- if (!wantsCliFetch && installedTreeExists()) {
133
- logger.debug("using installed aitools tree", { path: GLOBAL_AITOOLS_SKILLS_PATH });
134
- return { localSkillPaths: [GLOBAL_AITOOLS_SKILLS_PATH], source: "installed" };
135
- }
136
-
114
+ // The `databricks` CLI is the source of truth. When it's resolvable, run
115
+ // `databricks aitools install --path <dir>` to materialize a fresh,
116
+ // agent-agnostic skill set into a dir Mastra can scan.
137
117
  const cliPath = await resolveCli(options.cli);
138
118
  if (cliPath) {
139
119
  const dir = await materializeViaCli(cliPath, options);
140
120
  if (dir) return { localSkillPaths: [dir], source: "cli" };
141
121
  }
142
122
 
143
- // Nothing fetched. Fall back to the installed tree if it's there.
144
- if (installedTreeExists()) {
145
- return { localSkillPaths: [GLOBAL_AITOOLS_SKILLS_PATH], source: "installed" };
146
- }
147
-
123
+ // CLI not installed (or produced nothing). "auto" logs and moves on;
124
+ // "require" fails startup unless `failOnError: false`.
148
125
  if (failOnError) {
149
126
  throw new Error(
150
- `Databricks AI Tools requested but no installed skills tree at "${GLOBAL_AITOOLS_SKILLS_PATH}" and the "${DATABRICKS_CLI}" CLI is not resolvable. Install with \`databricks aitools install\`, or set databricksAITools: "auto".`,
127
+ `Databricks AI Tools required but the "${DATABRICKS_CLI}" CLI is not available. Install the Databricks CLI (\`databricks aitools install\`), or set databricksAITools: "auto".`,
151
128
  );
152
129
  }
153
- logger.debug("databricks aitools unavailable; skipping");
130
+ logger.info("databricks CLI not available; skipping AI Tools skills");
154
131
  return empty;
155
132
  } catch (err) {
156
133
  if (failOnError) {
@@ -164,16 +141,11 @@ export async function provisionDatabricksAITools(
164
141
  }
165
142
  }
166
143
 
167
- /** Whether the CLI's global skills tree exists and holds at least one skill. */
168
- function installedTreeExists(): boolean {
169
- return existsSync(GLOBAL_AITOOLS_SKILLS_PATH);
170
- }
171
-
172
144
  /** Resolve the `databricks` CLI, returning its invocation path or `undefined`. */
173
145
  async function resolveCli(cli: string | undefined): Promise<string | undefined> {
174
146
  const command = string.trimToNull(cli) ?? DATABRICKS_CLI;
175
147
  try {
176
- const result = await spawn(command, ["aitools", "version"], {
148
+ const result = await exec.spawn(command, ["aitools", "version"], {
177
149
  stdout: "capture",
178
150
  stderr: "capture",
179
151
  check: false,
@@ -186,21 +158,52 @@ async function resolveCli(cli: string | undefined): Promise<string | undefined>
186
158
  }
187
159
 
188
160
  /**
189
- * Run `databricks aitools install --path <dir> --skills-only` to materialize a
190
- * resolved, agent-agnostic skill set into a directory (no agents, no state).
161
+ * Run `databricks aitools install --path <dir>` to materialize a resolved,
162
+ * agent-agnostic skill set into a directory (writes `<name>/SKILL.md` trees,
163
+ * no agents, no state) Mastra can then scan.
191
164
  */
192
165
  async function materializeViaCli(
193
166
  cli: string,
194
167
  options: DatabricksAIToolsOptions,
195
168
  ): Promise<string | undefined> {
196
- const dir =
197
- string.trimToNull(options.path) ?? (await mkdtemp(join(tmpdir(), "databricks-aitools-")));
198
- const args = ["aitools", "install", "--path", dir, "--skills-only"];
169
+ const explicit = string.trimToNull(options.path);
170
+ let dir: string;
171
+
172
+ if (explicit) {
173
+ // An explicit path is the caller's to own - install straight into it.
174
+ await installSkills(cli, options, explicit);
175
+ dir = explicit;
176
+ } else {
177
+ // The resolved skill set is the same on almost every boot, so rebuild ONE
178
+ // stable temp tree rather than leaving a fresh scratch dir behind each
179
+ // time. The CLI writes into a throwaway root that only replaces the stable
180
+ // one on success, so a failed install keeps the previous skills usable.
181
+ const stable = await localFS.rebuildFS(SKILLS_CACHE_DIR, (scratch) =>
182
+ installSkills(cli, options, scratch.root),
183
+ );
184
+ dir = stable.root;
185
+ }
186
+
187
+ if (!existsSync(dir)) return undefined;
188
+ logger.debug("materialized aitools skills via CLI", {
189
+ path: dir,
190
+ skills: string.parseList(options.skills),
191
+ });
192
+ return dir;
193
+ }
194
+
195
+ /** Run `databricks aitools install --path <dir>`, failing loudly on a non-zero exit. */
196
+ async function installSkills(
197
+ cli: string,
198
+ options: DatabricksAIToolsOptions,
199
+ dir: string,
200
+ ): Promise<void> {
201
+ const args = ["aitools", "install", "--path", dir];
199
202
  const skills = string.parseList(options.skills);
200
203
  if (skills.length > 0) args.push("--skills", skills.join(","));
201
204
  if (options.experimental) args.push("--experimental");
202
205
 
203
- const result = await spawn(cli, args, {
206
+ const result = await exec.spawn(cli, args, {
204
207
  stdout: "capture",
205
208
  stderr: "capture",
206
209
  check: false,
@@ -210,7 +213,4 @@ async function materializeViaCli(
210
213
  `databricks aitools install failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`,
211
214
  );
212
215
  }
213
- if (!existsSync(dir)) return undefined;
214
- logger.debug("materialized aitools skills via CLI", { path: dir, skills });
215
- return dir;
216
216
  }