@dbx-tools/appkit-mastra 0.3.28 → 0.3.30
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/README.md +86 -15
- package/index.ts +3 -0
- package/package.json +10 -11
- package/src/agents.ts +12 -11
- package/src/chart.ts +69 -38
- package/src/config.ts +170 -18
- package/src/defaults.ts +133 -0
- package/src/filesystems.ts +29 -15
- package/src/genie.ts +81 -39
- package/src/history.ts +5 -2
- package/src/memory.ts +3 -4
- package/src/model.ts +15 -6
- package/src/plugin.ts +469 -194
- package/src/rest.ts +6 -7
- package/src/server.ts +5 -5
- package/src/serving-sanitize.ts +22 -9
- package/src/storage-schema.ts +4 -1
- package/src/threads.ts +10 -3
- package/src/validation.ts +22 -0
- package/test/config.test.ts +64 -0
package/src/config.ts
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plugin configuration types and shared `RequestContext` keys.
|
|
3
3
|
*
|
|
4
|
+
* Owns the typed {@link MastraPluginConfig} (the plugin's slice of AppKit
|
|
5
|
+
* config) and {@link MASTRA_CONFIG_SCHEMA}, the JSON Schema the manifest
|
|
6
|
+
* publishes for it so scaffolding tools and agents can read the option set.
|
|
7
|
+
*
|
|
8
|
+
* Precedence per field is explicit plugin config, then the environment
|
|
9
|
+
* variable named on the field, then a built-in default.
|
|
10
|
+
*
|
|
4
11
|
* Kept in a leaf module so `plugin.ts`, `server.ts`, `model.ts`, and
|
|
5
12
|
* `memory.ts` can import them without creating a cycle.
|
|
6
13
|
*
|
|
7
14
|
* @module
|
|
8
15
|
*/
|
|
9
16
|
|
|
10
|
-
import type
|
|
17
|
+
import { getExecutionContext, type BasePluginConfig, type ConfigSchema } from "@databricks/appkit";
|
|
11
18
|
import { appkit } from "@dbx-tools/appkit";
|
|
12
19
|
import type { BrandContext } from "@dbx-tools/shared-core";
|
|
13
20
|
import type { AgentConfig } from "@mastra/core/agent";
|
|
21
|
+
import type { RequestContext } from "@mastra/core/request-context";
|
|
14
22
|
import { MASTRA_RESOURCE_ID_KEY, MASTRA_THREAD_ID_KEY } from "@mastra/core/request-context";
|
|
15
23
|
import type { PgVectorConfig, PostgresStoreConfig } from "@mastra/pg";
|
|
16
24
|
|
|
@@ -83,6 +91,28 @@ export interface User {
|
|
|
83
91
|
executionContext: appkit.ExecutionContextLike;
|
|
84
92
|
}
|
|
85
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Canonical identity for an AppKit execution context: the OBO user id on a
|
|
96
|
+
* user-scoped call, the service principal id otherwise.
|
|
97
|
+
*/
|
|
98
|
+
export function executionContextUserId(context: appkit.ExecutionContextLike): string {
|
|
99
|
+
return "userId" in context ? context.userId : context.serviceUserId;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Identity every per-user cache entry is namespaced under, so an OBO result
|
|
104
|
+
* cannot be read back by another caller.
|
|
105
|
+
*
|
|
106
|
+
* Prefers the {@link User} that {@link MastraServer} stamps on the request
|
|
107
|
+
* context. The MCP transport routes do not thread that context into tool
|
|
108
|
+
* execution, so those fall back to the ambient execution context (the active
|
|
109
|
+
* OBO scope, or the service principal).
|
|
110
|
+
*/
|
|
111
|
+
export function resolveUserKey(requestContext?: RequestContext): string {
|
|
112
|
+
const user = requestContext?.get(MASTRA_USER_KEY) as User | undefined;
|
|
113
|
+
return user?.id ?? executionContextUserId(getExecutionContext());
|
|
114
|
+
}
|
|
115
|
+
|
|
86
116
|
/** PgVector config with an optional Mastra store id. */
|
|
87
117
|
export type MastraMemoryConfig = PgVectorConfig & {
|
|
88
118
|
id?: string;
|
|
@@ -129,7 +159,7 @@ export interface MastraMcpConfig {
|
|
|
129
159
|
|
|
130
160
|
/** Configuration accepted by the Mastra AppKit plugin. */
|
|
131
161
|
export interface MastraPluginConfig extends BasePluginConfig {
|
|
132
|
-
/** Mastra OpenAI-compatible provider id. Defaults to `"databricks"
|
|
162
|
+
/** Mastra OpenAI-compatible provider id. Defaults to `"databricks"`; no env fallback. */
|
|
133
163
|
providerId?: string;
|
|
134
164
|
/**
|
|
135
165
|
* PostgresStore for Mastra threads/messages. `true` reuses the
|
|
@@ -213,34 +243,40 @@ export interface MastraPluginConfig extends BasePluginConfig {
|
|
|
213
243
|
* or `providerId`.
|
|
214
244
|
*
|
|
215
245
|
* Resolution order per agent: `def.model` → `defaultModel` →
|
|
216
|
-
* built-in `/serving-endpoints`
|
|
246
|
+
* `DATABRICKS_SERVING_ENDPOINT_NAME` → built-in `/serving-endpoints`
|
|
247
|
+
* resolver.
|
|
217
248
|
*/
|
|
218
249
|
defaultModel?: AgentConfig["model"] | string;
|
|
219
250
|
/**
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
251
|
+
* Fuzzy-match loose model names (`"claude sonnet"`) against the workspace's
|
|
252
|
+
* Model Serving endpoints. Defaults to `true`; no env fallback.
|
|
253
|
+
*
|
|
254
|
+
* Set `false` to require exact endpoint names everywhere.
|
|
223
255
|
*/
|
|
224
256
|
modelFuzzyMatch?: boolean;
|
|
225
257
|
/**
|
|
226
|
-
* Fuse.js score threshold for the fuzzy matcher
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
258
|
+
* Fuse.js score threshold for the fuzzy matcher, 0 (exact) to 1 (anything).
|
|
259
|
+
* Defaults to `0.4`; no env fallback.
|
|
260
|
+
*
|
|
261
|
+
* Lower values reject loose matches; raise it if you have a sprawling
|
|
262
|
+
* endpoint catalogue with similar-looking names.
|
|
230
263
|
*/
|
|
231
264
|
modelFuzzyThreshold?: number;
|
|
232
265
|
/**
|
|
233
|
-
* TTL for the in-memory serving-endpoints list cache, in
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
266
|
+
* TTL for the in-memory serving-endpoints list cache, in milliseconds.
|
|
267
|
+
* Defaults to 5 minutes; no env fallback.
|
|
268
|
+
*
|
|
269
|
+
* The cache is per workspace host and shared across users; concurrent
|
|
270
|
+
* callers coalesce on a single in-flight fetch.
|
|
237
271
|
*/
|
|
238
272
|
modelCacheTtlMs?: number;
|
|
239
273
|
/**
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
274
|
+
* Let clients pick the backing endpoint per request. Defaults to `true`;
|
|
275
|
+
* no env fallback.
|
|
276
|
+
*
|
|
277
|
+
* Reads the `X-Mastra-Model` header, the `?model=` query string, or a
|
|
278
|
+
* `model` body field, in that order. Disable when running multi-tenant
|
|
279
|
+
* where untrusted clients shouldn't choose the endpoint.
|
|
244
280
|
*/
|
|
245
281
|
modelOverride?: boolean;
|
|
246
282
|
/**
|
|
@@ -459,3 +495,119 @@ export interface MastraPluginConfig extends BasePluginConfig {
|
|
|
459
495
|
*/
|
|
460
496
|
brand?: BrandContext;
|
|
461
497
|
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* JSON Schema published on the manifest's `config.schema`, mirroring the
|
|
501
|
+
* documented defaults and environment fallbacks of {@link MastraPluginConfig}.
|
|
502
|
+
*
|
|
503
|
+
* Covers the JSON-expressible options only. `agents`, `tools`, and a
|
|
504
|
+
* `defaultModel` passed as a Mastra `DynamicArgument` are code-defined
|
|
505
|
+
* (functions / class instances), so they carry no schema entry; the
|
|
506
|
+
* `defaultModel` property below describes its string form.
|
|
507
|
+
*/
|
|
508
|
+
export const MASTRA_CONFIG_SCHEMA: ConfigSchema = {
|
|
509
|
+
type: "object",
|
|
510
|
+
properties: {
|
|
511
|
+
providerId: {
|
|
512
|
+
type: "string",
|
|
513
|
+
description: 'Mastra OpenAI-compatible provider id. Defaults to "databricks".',
|
|
514
|
+
},
|
|
515
|
+
storage: {
|
|
516
|
+
type: ["boolean", "object"],
|
|
517
|
+
description:
|
|
518
|
+
"PostgresStore for Mastra threads / messages. `true` reuses the `lakebase` plugin's pool, an object opens a dedicated store. Auto-enabled when the `lakebase` plugin is registered.",
|
|
519
|
+
},
|
|
520
|
+
memory: {
|
|
521
|
+
type: ["boolean", "object"],
|
|
522
|
+
description:
|
|
523
|
+
"PgVector store for Mastra semantic recall. `true` reuses the `lakebase` plugin's pool, an object opens a dedicated store. Auto-enabled when the `lakebase` plugin is registered.",
|
|
524
|
+
},
|
|
525
|
+
defaultAgent: {
|
|
526
|
+
type: "string",
|
|
527
|
+
description:
|
|
528
|
+
"Agent id used when the client names none. Defaults to the first registered agent, else the built-in `default`.",
|
|
529
|
+
},
|
|
530
|
+
defaultModel: {
|
|
531
|
+
type: "string",
|
|
532
|
+
description:
|
|
533
|
+
"Serving endpoint applied to every agent that omits its own model. Falls back to DATABRICKS_SERVING_ENDPOINT_NAME, then the auto-resolved catalogue.",
|
|
534
|
+
},
|
|
535
|
+
defaultModelFallbacks: {
|
|
536
|
+
type: "array",
|
|
537
|
+
items: { type: "string" },
|
|
538
|
+
description:
|
|
539
|
+
"Priority-ordered endpoint names tried before the score-classified catalogue when nothing else pins a model.",
|
|
540
|
+
},
|
|
541
|
+
modelFuzzyMatch: {
|
|
542
|
+
type: "boolean",
|
|
543
|
+
description:
|
|
544
|
+
"Fuzzy-match loose model names against the workspace catalogue. Defaults to true.",
|
|
545
|
+
},
|
|
546
|
+
modelFuzzyThreshold: {
|
|
547
|
+
type: "number",
|
|
548
|
+
description:
|
|
549
|
+
"Fuse.js score threshold for the fuzzy matcher, 0 (exact) to 1 (anything). Defaults to 0.4.",
|
|
550
|
+
},
|
|
551
|
+
modelCacheTtlMs: {
|
|
552
|
+
type: "number",
|
|
553
|
+
description:
|
|
554
|
+
"TTL in ms for the serving-endpoints list cache, per workspace host. Defaults to 5 minutes.",
|
|
555
|
+
},
|
|
556
|
+
modelOverride: {
|
|
557
|
+
type: "boolean",
|
|
558
|
+
description:
|
|
559
|
+
"Honor a per-request model override from the X-Mastra-Model header, ?model= query, or a `model` body field. Defaults to true.",
|
|
560
|
+
},
|
|
561
|
+
genieSpaces: {
|
|
562
|
+
type: "object",
|
|
563
|
+
additionalProperties: { type: ["string", "object"] },
|
|
564
|
+
description:
|
|
565
|
+
"Genie spaces the agents can delegate to, keyed by alias (the tool-name suffix). Each value is a space id or `{ spaceId, hint }`. Falls back to the `genie` plugin's own `spaces` config, then DATABRICKS_GENIE_SPACE_ID under the `default` alias.",
|
|
566
|
+
},
|
|
567
|
+
genieSpaceCacheTtlMs: {
|
|
568
|
+
type: "number",
|
|
569
|
+
description: "TTL in ms for the Genie space metadata cache. Defaults to 5 minutes.",
|
|
570
|
+
},
|
|
571
|
+
agentMaxSteps: {
|
|
572
|
+
type: "number",
|
|
573
|
+
description:
|
|
574
|
+
"Maximum LLM steps each agent gets per turn (a tool call and the final reply each consume one). Defaults to 25.",
|
|
575
|
+
},
|
|
576
|
+
stripStaleCharts: {
|
|
577
|
+
type: "boolean",
|
|
578
|
+
description:
|
|
579
|
+
"Strip chartIds from recalled assistant tool results so the model cannot reuse a turn-scoped chart marker. Defaults to true.",
|
|
580
|
+
},
|
|
581
|
+
styleInstructions: {
|
|
582
|
+
type: ["string", "boolean"],
|
|
583
|
+
description:
|
|
584
|
+
"Style guardrails appended to every agent's instructions. A string replaces the built-in block, `false` disables it.",
|
|
585
|
+
},
|
|
586
|
+
observability: {
|
|
587
|
+
type: "boolean",
|
|
588
|
+
description:
|
|
589
|
+
"Bridge Mastra spans into AppKit's OTel pipeline. Defaults to on only when OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT is set.",
|
|
590
|
+
},
|
|
591
|
+
feedback: {
|
|
592
|
+
type: "boolean",
|
|
593
|
+
description:
|
|
594
|
+
"Log thumbs / comment assessments to MLflow and surface the feedback controls. Defaults to on only when an OTLP endpoint and MLFLOW_EXPERIMENT_ID or MLFLOW_EXPERIMENT_NAME are set.",
|
|
595
|
+
},
|
|
596
|
+
mcp: {
|
|
597
|
+
type: ["boolean", "object"],
|
|
598
|
+
description:
|
|
599
|
+
"Expose the registered agents over MCP. Defaults to agents-only; an object tunes the server id, advertised metadata, and whether ambient tools are exposed.",
|
|
600
|
+
},
|
|
601
|
+
apiAccess: {
|
|
602
|
+
type: "string",
|
|
603
|
+
enum: ["scoped", "full"],
|
|
604
|
+
description:
|
|
605
|
+
'How much of the stock @mastra/express API is reachable through the mount. "scoped" (default) allows agent inference, read-only agent metadata, this plugin\'s own routes, and MCP; "full" dispatches the entire management surface.',
|
|
606
|
+
},
|
|
607
|
+
brand: {
|
|
608
|
+
type: "object",
|
|
609
|
+
description:
|
|
610
|
+
"Portable brand context applied to generated charts (series palette from colors.primary / colors.accent, base font from typography.sans).",
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
};
|
package/src/defaults.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interceptor defaults for every outbound call the plugin's own routes make
|
|
3
|
+
* through `Plugin.execute()`.
|
|
4
|
+
*
|
|
5
|
+
* One constant per call site so the cache / retry / timeout decision lives
|
|
6
|
+
* beside its reasoning instead of at the call site. The interceptor chain runs
|
|
7
|
+
* telemetry, timeout, retry, cache, so a disabled interceptor is a deliberate
|
|
8
|
+
* choice and is commented as such.
|
|
9
|
+
*
|
|
10
|
+
* The cache interceptor is inert without a `cache.cacheKey`, which is
|
|
11
|
+
* request-specific (a statement id, an agent id). Call sites therefore spread
|
|
12
|
+
* the constant and add the key; the identity namespace comes from the
|
|
13
|
+
* execution context's user id, which `execute()` resolves on its own.
|
|
14
|
+
*
|
|
15
|
+
* Cancellation is not expressed here: `execute()` derives a signal from the
|
|
16
|
+
* `timeout` setting only, so a caller's own signal is combined with it at the
|
|
17
|
+
* call site instead.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Per-call interceptor settings. Structural stand-in for AppKit's
|
|
24
|
+
* `PluginExecuteConfig`: the barrel exports `StreamExecutionSettings` but not
|
|
25
|
+
* the non-streaming config, so the nominal type is unimportable here. Declared
|
|
26
|
+
* as a type alias (not an interface) so it keeps the implicit index signature
|
|
27
|
+
* AppKit's own config carries.
|
|
28
|
+
*/
|
|
29
|
+
type ExecuteConfig = {
|
|
30
|
+
cache?: { enabled?: boolean; ttl?: number; cacheKey?: (string | number | object)[] };
|
|
31
|
+
retry?: { enabled?: boolean; attempts?: number; initialDelay?: number; maxDelay?: number };
|
|
32
|
+
timeout?: number;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Default plus optional user-scoped settings, as `execute()` accepts them. */
|
|
36
|
+
type ExecuteSettings = {
|
|
37
|
+
default: ExecuteConfig;
|
|
38
|
+
user?: ExecuteConfig;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** Ceiling on a single Model Serving / Genie / MLflow REST round-trip. */
|
|
42
|
+
const REST_TIMEOUT_MS = 30_000;
|
|
43
|
+
|
|
44
|
+
/** Ceiling on one Statement Execution fetch, which can page a large result. */
|
|
45
|
+
const STATEMENT_TIMEOUT_MS = 60_000;
|
|
46
|
+
|
|
47
|
+
/** TTL for a cached statement result set, in seconds. */
|
|
48
|
+
const STATEMENT_CACHE_TTL_SEC = 5 * 60;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* `GET /models`: the workspace's Model Serving catalogue.
|
|
52
|
+
*/
|
|
53
|
+
export const modelCatalogueDefaults: ExecuteSettings = {
|
|
54
|
+
default: {
|
|
55
|
+
// Cache disabled here because `listServingEndpoints` already memoizes the
|
|
56
|
+
// catalogue through the same `CacheManager`, keyed by workspace host with
|
|
57
|
+
// `config.modelCacheTtlMs`. That entry is shared across identities; a
|
|
58
|
+
// second interceptor cache would duplicate it once per user.
|
|
59
|
+
cache: { enabled: false },
|
|
60
|
+
// Retry enabled: listing endpoints is an idempotent GET, and the listing
|
|
61
|
+
// is on the chat UI's load path, so a transient 5xx should not blank the
|
|
62
|
+
// model picker.
|
|
63
|
+
retry: { enabled: true, attempts: 3 },
|
|
64
|
+
timeout: REST_TIMEOUT_MS,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* `GET /suggestions`: the curated `sample_questions` on each Genie space.
|
|
70
|
+
*/
|
|
71
|
+
export const genieSuggestionDefaults: ExecuteSettings = {
|
|
72
|
+
default: {
|
|
73
|
+
// Cache disabled here because `collectSpaceSuggestions` keeps its own
|
|
74
|
+
// space-id-keyed entry; the questions are authored config, identical for
|
|
75
|
+
// every caller, so caching them per identity would only fan out copies.
|
|
76
|
+
cache: { enabled: false },
|
|
77
|
+
// Retry enabled: reading space metadata is idempotent, and the route
|
|
78
|
+
// degrades to an empty starter list on failure, so one retry is cheaper
|
|
79
|
+
// than an empty chat state.
|
|
80
|
+
retry: { enabled: true, attempts: 2 },
|
|
81
|
+
timeout: REST_TIMEOUT_MS,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* `GET /embed/data/:id`: one Statement Execution result set.
|
|
87
|
+
*/
|
|
88
|
+
export const statementDataDefaults: ExecuteSettings = {
|
|
89
|
+
default: {
|
|
90
|
+
// Cache enabled: a completed statement's result set is immutable, and the
|
|
91
|
+
// same id is re-fetched every time the transcript re-renders a
|
|
92
|
+
// `[data:<statement_id>]` marker. The entry is namespaced by the caller's
|
|
93
|
+
// identity, so an OBO result never crosses users.
|
|
94
|
+
cache: { enabled: true, ttl: STATEMENT_CACHE_TTL_SEC },
|
|
95
|
+
// Retry enabled: fetching a statement by id is idempotent.
|
|
96
|
+
retry: { enabled: true, attempts: 3 },
|
|
97
|
+
timeout: STATEMENT_TIMEOUT_MS,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* `GET /embed/chart/:id`: the cached chart entry behind a `[chart:<id>]`
|
|
103
|
+
* marker.
|
|
104
|
+
*/
|
|
105
|
+
export const chartFetchDefaults: ExecuteSettings = {
|
|
106
|
+
default: {
|
|
107
|
+
// Cache disabled: the read long-polls a cache entry that is expected to
|
|
108
|
+
// change from processing to settled, so a cached answer would pin the
|
|
109
|
+
// client to the pre-planner state.
|
|
110
|
+
cache: { enabled: false },
|
|
111
|
+
// Retry disabled: the helper already polls to its own deadline, so a retry
|
|
112
|
+
// would only restart a wait the caller is already inside.
|
|
113
|
+
retry: { enabled: false },
|
|
114
|
+
// No timeout: the poll budget is per request (`?timeoutMs=`, capped by the
|
|
115
|
+
// route) and cancellation rides the connection's abort signal, so a second
|
|
116
|
+
// ceiling here could cut a legitimate long-poll short.
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* `POST /route/feedback`: an MLflow trace assessment.
|
|
122
|
+
*/
|
|
123
|
+
export const feedbackWriteDefaults: ExecuteSettings = {
|
|
124
|
+
default: {
|
|
125
|
+
// Cache disabled: a write, and each submission is a distinct assessment.
|
|
126
|
+
cache: { enabled: false },
|
|
127
|
+
// Retry disabled: posting an assessment is not idempotent (a retry records
|
|
128
|
+
// a duplicate), and `logFeedback` already retries the one recoverable case
|
|
129
|
+
// itself, the trace not having finished exporting to MLflow yet.
|
|
130
|
+
retry: { enabled: false },
|
|
131
|
+
timeout: REST_TIMEOUT_MS,
|
|
132
|
+
},
|
|
133
|
+
};
|
package/src/filesystems.ts
CHANGED
|
@@ -14,9 +14,20 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { posix as path } from "node:path";
|
|
17
|
-
import { getExecutionContext } from "@databricks/appkit";
|
|
17
|
+
import { ExecutionError, getExecutionContext, ValidationError } from "@databricks/appkit";
|
|
18
18
|
import { WorkspaceClient } from "@databricks/sdk-experimental";
|
|
19
19
|
import { error, functionModule, hash, log } from "@dbx-tools/shared-core";
|
|
20
|
+
import {
|
|
21
|
+
DirectoryNotEmptyError,
|
|
22
|
+
DirectoryNotFoundError,
|
|
23
|
+
FileExistsError,
|
|
24
|
+
FileNotFoundError,
|
|
25
|
+
IsDirectoryError,
|
|
26
|
+
MastraFilesystem,
|
|
27
|
+
NotDirectoryError,
|
|
28
|
+
PermissionError,
|
|
29
|
+
WorkspaceReadOnlyError,
|
|
30
|
+
} from "@mastra/core/workspace";
|
|
20
31
|
import type {
|
|
21
32
|
CopyOptions,
|
|
22
33
|
FileContent,
|
|
@@ -24,20 +35,11 @@ import type {
|
|
|
24
35
|
FileStat,
|
|
25
36
|
FilesystemInfo,
|
|
26
37
|
ListOptions,
|
|
38
|
+
MastraFilesystemOptions,
|
|
27
39
|
ProviderStatus,
|
|
28
40
|
ReadOptions,
|
|
29
41
|
RemoveOptions,
|
|
30
42
|
WriteOptions,
|
|
31
|
-
DirectoryNotEmptyError,
|
|
32
|
-
DirectoryNotFoundError,
|
|
33
|
-
FileExistsError,
|
|
34
|
-
FileNotFoundError,
|
|
35
|
-
IsDirectoryError,
|
|
36
|
-
MastraFilesystem,
|
|
37
|
-
NotDirectoryError,
|
|
38
|
-
PermissionError,
|
|
39
|
-
WorkspaceReadOnlyError,
|
|
40
|
-
type MastraFilesystemOptions,
|
|
41
43
|
} from "@mastra/core/workspace";
|
|
42
44
|
|
|
43
45
|
/* ------------------------------ constants ------------------------------ */
|
|
@@ -99,7 +101,11 @@ export interface DatabricksWorkspaceFilesystemOptions extends MastraFilesystemOp
|
|
|
99
101
|
export function normalizeDatabricksBasePath(basePath: string): string {
|
|
100
102
|
const trimmed = basePath.trim();
|
|
101
103
|
if (!trimmed.startsWith("/")) {
|
|
102
|
-
throw
|
|
104
|
+
throw ValidationError.invalidValue(
|
|
105
|
+
"basePath",
|
|
106
|
+
basePath,
|
|
107
|
+
"an absolute Databricks path, e.g. /Volumes/catalog/schema/volume",
|
|
108
|
+
);
|
|
103
109
|
}
|
|
104
110
|
return trimmed.replace(/\/+$/, "") || "/";
|
|
105
111
|
}
|
|
@@ -295,8 +301,16 @@ export class DatabricksWorkspaceFilesystem extends MastraFilesystem {
|
|
|
295
301
|
if (ErrorType) {
|
|
296
302
|
throw new ErrorType(workspacePath);
|
|
297
303
|
}
|
|
298
|
-
|
|
299
|
-
|
|
304
|
+
// The upstream message is logged rather than raised: it reaches the model
|
|
305
|
+
// (and from there the chat transcript) as the tool's failure text.
|
|
306
|
+
logger.warn("operation-failed", {
|
|
307
|
+
path: workspacePath,
|
|
308
|
+
error: error.errorMessage(err),
|
|
309
|
+
});
|
|
310
|
+
throw new ExecutionError(`Databricks filesystem operation failed for ${workspacePath}`, {
|
|
311
|
+
cause: error.toError(err),
|
|
312
|
+
context: { path: workspacePath },
|
|
313
|
+
});
|
|
300
314
|
}
|
|
301
315
|
|
|
302
316
|
/* --- lifecycle --- */
|
|
@@ -516,7 +530,7 @@ export class DatabricksWorkspaceFilesystem extends MastraFilesystem {
|
|
|
516
530
|
const created = await this.client.dbfs.create({ path: absolutePath, overwrite });
|
|
517
531
|
const handle = created.handle;
|
|
518
532
|
if (handle === undefined) {
|
|
519
|
-
throw
|
|
533
|
+
throw ExecutionError.missingData("DBFS upload handle");
|
|
520
534
|
}
|
|
521
535
|
for (let offset = 0; offset < buffer.length; offset += DBFS_PUT_MAX_BYTES) {
|
|
522
536
|
const slice = buffer.subarray(offset, offset + DBFS_PUT_MAX_BYTES);
|