@dbx-tools/appkit-mastra 0.6.8 → 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/package.json CHANGED
@@ -32,22 +32,25 @@
32
32
  "express": "^5.1.0",
33
33
  "pg": "^8.22.0",
34
34
  "zod": "4.3.6",
35
- "@dbx-tools/model": "0.6.8",
36
- "@dbx-tools/appkit": "0.6.8",
37
- "@dbx-tools/genie": "0.6.8",
38
- "@dbx-tools/core": "0.6.8",
39
- "@dbx-tools/path": "0.6.8",
40
- "@dbx-tools/shared-core": "0.6.8",
41
- "@dbx-tools/shared-genie": "0.6.8",
42
- "@dbx-tools/shared-mastra": "0.6.8",
43
- "@dbx-tools/shared-model": "0.6.8"
35
+ "@dbx-tools/appkit": "0.6.10",
36
+ "@dbx-tools/databricks": "0.6.10",
37
+ "@dbx-tools/genie": "0.6.10",
38
+ "@dbx-tools/fs": "0.6.10",
39
+ "@dbx-tools/model": "0.6.10",
40
+ "@dbx-tools/core": "0.6.10",
41
+ "@dbx-tools/path": "0.6.10",
42
+ "@dbx-tools/shared-core": "0.6.10",
43
+ "@dbx-tools/shared-fs": "0.6.10",
44
+ "@dbx-tools/shared-model": "0.6.10",
45
+ "@dbx-tools/shared-genie": "0.6.10",
46
+ "@dbx-tools/shared-mastra": "0.6.10"
44
47
  },
45
48
  "main": "./lib/index.js",
46
49
  "license": "UNLICENSED",
47
50
  "publishConfig": {
48
51
  "access": "public"
49
52
  },
50
- "version": "0.6.8",
53
+ "version": "0.6.10",
51
54
  "types": "./lib/index.d.ts",
52
55
  "type": "module",
53
56
  "exports": {
package/src/config.ts CHANGED
@@ -23,6 +23,7 @@ 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";
@@ -580,6 +581,27 @@ export interface MastraPluginConfig extends BasePluginConfig {
580
581
  * ```
581
582
  */
582
583
  remoteSkills?: RemoteSkillsOption;
584
+
585
+ /**
586
+ * Fold Databricks AI Tools skills (`databricks aitools`) into every
587
+ * default-workspace agent as extra local skill scan paths.
588
+ *
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`.
593
+ *
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.
597
+ *
598
+ * @example
599
+ * ```ts
600
+ * mastra({ databricksAITools: "auto" });
601
+ * mastra({ databricksAITools: { skills: ["databricks-core", "databricks-jobs"] } });
602
+ * ```
603
+ */
604
+ databricksAITools?: DatabricksAIToolsOption;
583
605
  }
584
606
 
585
607
  /**
@@ -603,6 +625,11 @@ export const MASTRA_CONFIG_SCHEMA: ConfigSchema = {
603
625
  description:
604
626
  "Remote Agent-Skill sources provisioned at startup (GitHub owner/repo, git/GitLab URL, or a direct SKILL.md/archive URL). Prefers the optional `skills` npm CLI, else a direct fetch; fails startup on error unless failOnError:false. Written to the Databricks user Assistant skills folder, or a local temp dir when no workspace is writable.",
605
627
  },
628
+ databricksAITools: {
629
+ type: ["boolean", "string", "object"],
630
+ description:
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.',
632
+ },
606
633
  storage: {
607
634
  type: ["boolean", "object"],
608
635
  description:
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Startup provisioning of Databricks AI Tools skills for a Mastra workspace.
3
+ *
4
+ * Databricks AI Tools (`databricks aitools`) are Databricks-owned Agent-Skill
5
+ * (`SKILL.md`) trees installed and kept up to date through the Databricks CLI.
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.
12
+ *
13
+ * The option is `false | true | "auto" | DatabricksAIToolsOptions`:
14
+ *
15
+ * - `false` (default) - off.
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.
23
+ *
24
+ * @module
25
+ */
26
+
27
+ import { existsSync } from "node:fs";
28
+ import { exec } from "@dbx-tools/core";
29
+ import { localFS } from "@dbx-tools/fs";
30
+ import { error, log, string } from "@dbx-tools/shared-core";
31
+
32
+ const logger = log.logger("mastra/databricks-aitools");
33
+
34
+ /** The Databricks CLI binary name; resolved on `PATH`. */
35
+ const DATABRICKS_CLI = "databricks";
36
+
37
+ /** Stable temp directory the CLI's skill tree is rebuilt into each boot. */
38
+ const SKILLS_CACHE_DIR = "databricks-aitools";
39
+
40
+ /* -------------------------------- types -------------------------------- */
41
+
42
+ /** How aggressively to enable Databricks AI Tools skills. */
43
+ export type DatabricksAIToolsMode = "auto" | "require";
44
+
45
+ /** Options for {@link provisionDatabricksAITools}. */
46
+ export interface DatabricksAIToolsOptions {
47
+ /**
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.
50
+ */
51
+ mode?: DatabricksAIToolsMode;
52
+ /** Install only these skill names (comma/space list or array). */
53
+ skills?: string | string[];
54
+ /** Include experimental skills when the CLI has to fetch. */
55
+ experimental?: boolean;
56
+ /**
57
+ * Explicit directory to materialize skills into with the CLI. Defaults to a
58
+ * fresh {@link localFS.tmpFS} root.
59
+ */
60
+ path?: string;
61
+ /** Absolute path to the `databricks` CLI. Defaults to `databricks` on PATH. */
62
+ cli?: string;
63
+ /**
64
+ * Fail app startup when required skills can't be provisioned. Defaults to
65
+ * `true` for `mode: "require"` and `false` for `"auto"`.
66
+ */
67
+ failOnError?: boolean;
68
+ }
69
+
70
+ /** The `databricksAITools` config option: a toggle, `"auto"`, or an options bag. */
71
+ export type DatabricksAIToolsOption = boolean | "auto" | DatabricksAIToolsOptions;
72
+
73
+ /** What {@link provisionDatabricksAITools} resolved. */
74
+ export interface ProvisionedDatabricksAITools {
75
+ /** Extra LOCAL skill scan paths to hand Mastra. Empty when disabled/unresolved. */
76
+ localSkillPaths: string[];
77
+ /** How the skills were sourced, for logging. */
78
+ source?: "cli";
79
+ }
80
+
81
+ /* ------------------------------- helpers ------------------------------- */
82
+
83
+ /** Normalize the `databricksAITools` option into a flat options bag, or `undefined` when off. */
84
+ export function normalizeDatabricksAIToolsOption(
85
+ option: DatabricksAIToolsOption | undefined,
86
+ ): DatabricksAIToolsOptions | undefined {
87
+ if (option === undefined || option === false) return undefined;
88
+ if (option === true) return { mode: "require" };
89
+ if (option === "auto") return { mode: "auto" };
90
+ return { mode: option.mode ?? "auto", ...option };
91
+ }
92
+
93
+ /**
94
+ * Materialize Databricks AI Tools skills at app startup and return them as
95
+ * extra local skill scan paths.
96
+ *
97
+ * Resolution: reuse the installed global tree when present (no CLI call) unless
98
+ * `refresh`/`skills`/`path` ask for a fresh CLI materialization; otherwise run
99
+ * `databricks aitools install --path <dir> --skills-only` when the CLI resolves.
100
+ * A `"require"` provision that yields nothing fails startup unless
101
+ * `failOnError: false`.
102
+ */
103
+ export async function provisionDatabricksAITools(
104
+ option: DatabricksAIToolsOption | undefined,
105
+ ): Promise<ProvisionedDatabricksAITools> {
106
+ const options = normalizeDatabricksAIToolsOption(option);
107
+ const empty: ProvisionedDatabricksAITools = { localSkillPaths: [] };
108
+ if (!options) return empty;
109
+
110
+ const mode = options.mode ?? "auto";
111
+ const failOnError = options.failOnError ?? mode === "require";
112
+
113
+ try {
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.
117
+ const cliPath = await resolveCli(options.cli);
118
+ if (cliPath) {
119
+ const dir = await materializeViaCli(cliPath, options);
120
+ if (dir) return { localSkillPaths: [dir], source: "cli" };
121
+ }
122
+
123
+ // CLI not installed (or produced nothing). "auto" logs and moves on;
124
+ // "require" fails startup unless `failOnError: false`.
125
+ if (failOnError) {
126
+ throw new Error(
127
+ `Databricks AI Tools required but the "${DATABRICKS_CLI}" CLI is not available. Install the Databricks CLI (\`databricks aitools install\`), or set databricksAITools: "auto".`,
128
+ );
129
+ }
130
+ logger.info("databricks CLI not available; skipping AI Tools skills");
131
+ return empty;
132
+ } catch (err) {
133
+ if (failOnError) {
134
+ throw new Error(
135
+ `failed to provision Databricks AI Tools skills: ${error.errorMessage(err)}`,
136
+ { cause: error.toError(err) },
137
+ );
138
+ }
139
+ logger.warn("skipped", { error: error.errorMessage(err) });
140
+ return empty;
141
+ }
142
+ }
143
+
144
+ /** Resolve the `databricks` CLI, returning its invocation path or `undefined`. */
145
+ async function resolveCli(cli: string | undefined): Promise<string | undefined> {
146
+ const command = string.trimToNull(cli) ?? DATABRICKS_CLI;
147
+ try {
148
+ const result = await exec.spawn(command, ["aitools", "version"], {
149
+ stdout: "capture",
150
+ stderr: "capture",
151
+ check: false,
152
+ });
153
+ if (result.exitCode === 0) return command;
154
+ } catch {
155
+ // CLI absent or `aitools` unsupported - caller falls back / fails.
156
+ }
157
+ return undefined;
158
+ }
159
+
160
+ /**
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.
164
+ */
165
+ async function materializeViaCli(
166
+ cli: string,
167
+ options: DatabricksAIToolsOptions,
168
+ ): Promise<string | undefined> {
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];
202
+ const skills = string.parseList(options.skills);
203
+ if (skills.length > 0) args.push("--skills", skills.join(","));
204
+ if (options.experimental) args.push("--experimental");
205
+
206
+ const result = await exec.spawn(cli, args, {
207
+ stdout: "capture",
208
+ stderr: "capture",
209
+ check: false,
210
+ });
211
+ if (result.exitCode !== 0) {
212
+ throw new Error(
213
+ `databricks aitools install failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`,
214
+ );
215
+ }
216
+ }