@dbx-tools/appkit-mastra 0.9.153 → 0.9.157
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 +4 -3
- package/index.ts +32 -24
- package/lib/index.d.ts +32 -24
- package/lib/index.js +32 -23
- package/lib/src/agents.d.ts +5 -6
- package/lib/src/agents.js +12 -15
- package/lib/src/chart.js +33 -33
- package/lib/src/chat.js +10 -13
- package/lib/src/config.d.ts +12 -13
- package/lib/src/config.js +4 -4
- package/lib/src/evaluation.js +7 -9
- package/lib/src/feedback-mode.d.ts +1 -1
- package/lib/src/feedback-mode.js +1 -1
- package/lib/src/filesystems.js +6 -6
- package/lib/src/follow-up-suggestions.js +10 -10
- package/lib/src/genie-chat.js +27 -29
- package/lib/src/genie.d.ts +2 -2
- package/lib/src/genie.js +29 -29
- package/lib/src/identity.d.ts +3 -30
- package/lib/src/identity.js +2 -27
- package/lib/src/memory.d.ts +1 -1
- package/lib/src/memory.js +24 -6
- package/lib/src/mlflow.d.ts +4 -4
- package/lib/src/mlflow.js +15 -18
- package/lib/src/model.d.ts +3 -3
- package/lib/src/model.js +6 -6
- package/lib/src/monty-sandbox.js +8 -8
- package/lib/src/observability.d.ts +6 -5
- package/lib/src/observability.js +8 -6
- package/lib/src/plugin.d.ts +1 -1
- package/lib/src/plugin.js +49 -51
- package/lib/src/remote-skills.d.ts +3 -4
- package/lib/src/remote-skills.js +46 -46
- package/lib/src/rest.d.ts +2 -2
- package/lib/src/rest.js +3 -3
- package/lib/src/server.js +17 -20
- package/lib/src/serving.js +10 -11
- package/lib/src/statement.js +7 -7
- package/lib/src/storage-schema.js +4 -4
- package/lib/src/summarize.js +5 -5
- package/lib/src/telemetry.d.ts +0 -6
- package/lib/src/telemetry.js +34 -41
- package/lib/src/validation.js +3 -4
- package/lib/src/workspaces.d.ts +2 -2
- package/lib/src/workspaces.js +34 -36
- package/lib/src/writer.d.ts +2 -2
- package/lib/src/writer.js +3 -3
- package/package.json +17 -305
- package/src/agents.ts +27 -21
- package/src/chart.ts +43 -37
- package/src/chat.ts +10 -12
- package/src/config.ts +14 -15
- package/src/evaluation.ts +6 -8
- package/src/feedback-mode.ts +1 -1
- package/src/filesystems.ts +5 -5
- package/src/follow-up-suggestions.ts +9 -9
- package/src/genie-chat.ts +32 -31
- package/src/genie.ts +29 -29
- package/src/identity.ts +3 -38
- package/src/memory.ts +24 -6
- package/src/mlflow.ts +21 -21
- package/src/model.ts +7 -7
- package/src/monty-sandbox.ts +7 -7
- package/src/observability.ts +8 -6
- package/src/plugin.ts +78 -65
- package/src/remote-skills.ts +63 -55
- package/src/rest.ts +4 -4
- package/src/server.ts +32 -20
- package/src/serving.ts +14 -11
- package/src/statement.ts +10 -6
- package/src/storage-schema.ts +3 -3
- package/src/summarize.ts +4 -4
- package/src/telemetry.ts +49 -55
- package/src/validation.ts +2 -3
- package/src/workspaces.ts +52 -42
- package/src/writer.ts +3 -3
package/src/telemetry.ts
CHANGED
|
@@ -20,10 +20,25 @@
|
|
|
20
20
|
|
|
21
21
|
import { StringDecoder } from "node:string_decoder";
|
|
22
22
|
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
import {
|
|
24
|
+
USER_TRACE_ATTRIBUTE,
|
|
25
|
+
registerNativeSpanProcessor,
|
|
26
|
+
getRequestTags,
|
|
27
|
+
} from "@dbx-tools/appkit";
|
|
28
|
+
import {
|
|
29
|
+
log,
|
|
30
|
+
isDatabricksAppEnv,
|
|
31
|
+
parse,
|
|
32
|
+
isRecord,
|
|
33
|
+
trimToNull,
|
|
34
|
+
USER_EMAIL_HEADER,
|
|
35
|
+
USER_ID_HEADER,
|
|
36
|
+
} from "@dbx-tools/shared-core";
|
|
37
|
+
import {
|
|
38
|
+
MLFLOW_TRACE_ID_HEADER,
|
|
39
|
+
THREAD_ID_HEADER,
|
|
40
|
+
THREAD_ID_QUERY,
|
|
41
|
+
} from "@dbx-tools/shared-mastra";
|
|
27
42
|
import type { TracingOptions } from "@mastra/core/observability";
|
|
28
43
|
import { MASTRA_RESOURCE_ID_KEY, MASTRA_THREAD_ID_KEY } from "@mastra/core/request-context";
|
|
29
44
|
import {
|
|
@@ -40,7 +55,6 @@ import type { ReadableSpan, Span as SdkSpan, SpanProcessor } from "@opentelemetr
|
|
|
40
55
|
import type express from "express";
|
|
41
56
|
|
|
42
57
|
import { MASTRA_AUTH_MODE_KEY, MASTRA_USER_EMAIL_KEY, MASTRA_USER_NAME_KEY } from "./config.ts";
|
|
43
|
-
import { USER_EMAIL_HEADER, USER_ID_HEADER } from "./identity.ts";
|
|
44
58
|
import {
|
|
45
59
|
appMlflowTraceInfoConfigured,
|
|
46
60
|
directMlflowTracingActive,
|
|
@@ -61,21 +75,6 @@ import {
|
|
|
61
75
|
MLFLOW_TRACE_TAG_PREFIX,
|
|
62
76
|
} from "./trace-attributes.ts";
|
|
63
77
|
|
|
64
|
-
export {
|
|
65
|
-
MLFLOW_AGENT_TAG,
|
|
66
|
-
MLFLOW_AGENT_TAG_ATTR,
|
|
67
|
-
APPKIT_AGENT_TRACE_ROOT_ATTR,
|
|
68
|
-
MLFLOW_GENIE_TAG,
|
|
69
|
-
MLFLOW_GENIE_TAG_ATTR,
|
|
70
|
-
MLFLOW_LOCAL_TAG,
|
|
71
|
-
MLFLOW_LOCAL_TAG_ATTR,
|
|
72
|
-
MLFLOW_OBO_AUTH_TAG,
|
|
73
|
-
MLFLOW_OBO_AUTH_TAG_ATTR,
|
|
74
|
-
MLFLOW_SP_AUTH_TAG,
|
|
75
|
-
MLFLOW_SP_AUTH_TAG_ATTR,
|
|
76
|
-
MLFLOW_TRACE_TAG_PREFIX,
|
|
77
|
-
} from "./trace-attributes.ts";
|
|
78
|
-
|
|
79
78
|
const logger = log.logger("mastra/telemetry");
|
|
80
79
|
|
|
81
80
|
/** Cap on each payload copied onto a span, so one turn cannot bloat the export. */
|
|
@@ -85,7 +84,7 @@ export const TRACE_IO_LIMIT = 8_000;
|
|
|
85
84
|
const RESPONSE_BODY_LIMIT = TRACE_IO_LIMIT * 4;
|
|
86
85
|
|
|
87
86
|
/** Tracer scope used only when AppKit did not create a recording HTTP span. */
|
|
88
|
-
const CHAT_TURN_TRACER = "@dbx-tools/appkit-mastra
|
|
87
|
+
const CHAT_TURN_TRACER = "@dbx-tools/appkit-mastra";
|
|
89
88
|
|
|
90
89
|
/**
|
|
91
90
|
* Mount-relative Mastra agent invoke paths that carry a chat turn body
|
|
@@ -115,11 +114,6 @@ export const CHAT_GENIE_USED_ATTR = "appkit.mastra.genie.used";
|
|
|
115
114
|
export const MLFLOW_SPAN_TYPE_ATTR = "mlflow.spanType";
|
|
116
115
|
/** GenAI semantic-convention operation name, an alternate span-type source. */
|
|
117
116
|
export const GEN_AI_OPERATION_NAME_ATTR = "gen_ai.operation.name";
|
|
118
|
-
/**
|
|
119
|
-
* Root-span user id the MLflow experiment UI's User column reads from OTel
|
|
120
|
-
* traces. Prefer the forwarded email when present so the column is human.
|
|
121
|
-
*/
|
|
122
|
-
export const MLFLOW_USER_ATTR = appkitTelemetry.USER_TRACE_ATTRIBUTE;
|
|
123
117
|
/** Root-span session id that enables the MLflow Sessions tab. */
|
|
124
118
|
export const MLFLOW_SESSION_ATTR = "session.id";
|
|
125
119
|
/** Root-span type for a Mastra agent turn. */
|
|
@@ -170,25 +164,23 @@ export function stampGenieToolSpan(span: Span | undefined = trace.getActiveSpan(
|
|
|
170
164
|
}
|
|
171
165
|
|
|
172
166
|
function requestHeader(req: express.Request, name: string): string | undefined {
|
|
173
|
-
const fromMethod =
|
|
174
|
-
typeof req.header === "function" ? stringUtils.trimToNull(req.header(name)) : undefined;
|
|
167
|
+
const fromMethod = typeof req.header === "function" ? trimToNull(req.header(name)) : undefined;
|
|
175
168
|
if (fromMethod) return fromMethod;
|
|
176
169
|
const raw = req.headers?.[name.toLowerCase()];
|
|
177
170
|
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
178
|
-
return
|
|
171
|
+
return trimToNull(typeof value === "string" ? value : undefined) ?? undefined;
|
|
179
172
|
}
|
|
180
173
|
|
|
181
174
|
function requestQuery(req: express.Request, name: string): string | undefined {
|
|
182
175
|
const raw = req.query?.[name];
|
|
183
176
|
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
184
|
-
return
|
|
177
|
+
return trimToNull(typeof value === "string" ? value : undefined) ?? undefined;
|
|
185
178
|
}
|
|
186
179
|
|
|
187
180
|
function stampMlflowActor(span: Span, req: express.Request): string | undefined {
|
|
188
181
|
const user = requestHeader(req, USER_EMAIL_HEADER) ?? requestHeader(req, USER_ID_HEADER);
|
|
189
|
-
if (user) span.setAttribute(
|
|
190
|
-
const session =
|
|
191
|
-
requestHeader(req, thread.THREAD_ID_HEADER) ?? requestQuery(req, thread.THREAD_ID_QUERY);
|
|
182
|
+
if (user) span.setAttribute(USER_TRACE_ATTRIBUTE, user);
|
|
183
|
+
const session = requestHeader(req, THREAD_ID_HEADER) ?? requestQuery(req, THREAD_ID_QUERY);
|
|
192
184
|
if (session) span.setAttribute(MLFLOW_SESSION_ATTR, session);
|
|
193
185
|
return user;
|
|
194
186
|
}
|
|
@@ -223,7 +215,7 @@ export function activeChatTracingOptions(
|
|
|
223
215
|
}
|
|
224
216
|
|
|
225
217
|
function setChatTraceTag(state: ChatTraceState, name: string, value: ChatTraceTagValue): void {
|
|
226
|
-
const tag =
|
|
218
|
+
const tag = trimToNull(name);
|
|
227
219
|
if (!tag) return;
|
|
228
220
|
state.tags.set(tag, String(value));
|
|
229
221
|
}
|
|
@@ -247,7 +239,7 @@ export function recordActiveTraceTags(
|
|
|
247
239
|
|
|
248
240
|
/** Record the first selected model as the active chat trace's `agent` tag. */
|
|
249
241
|
export function recordActiveTraceModel(modelId: string): void {
|
|
250
|
-
const value =
|
|
242
|
+
const value = trimToNull(modelId);
|
|
251
243
|
if (!value) return;
|
|
252
244
|
const state = activeChatTraceState();
|
|
253
245
|
if (!state || state.firstModel) return;
|
|
@@ -260,7 +252,7 @@ export function recordActiveTraceModel(modelId: string): void {
|
|
|
260
252
|
|
|
261
253
|
/** Record the best resolved user identifier on the active chat trace. */
|
|
262
254
|
export function recordActiveTraceUser(user: string | undefined): void {
|
|
263
|
-
const value =
|
|
255
|
+
const value = trimToNull(user);
|
|
264
256
|
if (!value) return;
|
|
265
257
|
const state = activeChatTraceState();
|
|
266
258
|
if (state) state.user = value;
|
|
@@ -275,7 +267,7 @@ export function recordActiveTraceAuth(auth: "obo" | "service-principal"): void {
|
|
|
275
267
|
function stampMlflowTraceId(res: express.Response, traceId: string): void {
|
|
276
268
|
if (res.headersSent) return;
|
|
277
269
|
if (!traceId || traceId === INVALID_TRACE_ID) return;
|
|
278
|
-
res.setHeader(
|
|
270
|
+
res.setHeader(MLFLOW_TRACE_ID_HEADER, `tr-${traceId}`);
|
|
279
271
|
}
|
|
280
272
|
|
|
281
273
|
/** Return one raw prompt when the request is exactly one text-only user message. */
|
|
@@ -283,7 +275,7 @@ export function textOnlyChatInput(messages: unknown): string | undefined {
|
|
|
283
275
|
if (typeof messages === "string") return messages || undefined;
|
|
284
276
|
if (!Array.isArray(messages) || messages.length !== 1) return undefined;
|
|
285
277
|
const message = messages[0];
|
|
286
|
-
if (!
|
|
278
|
+
if (!isRecord(message) || message.role !== "user") return undefined;
|
|
287
279
|
if (typeof message.content === "string") return message.content || undefined;
|
|
288
280
|
return textFromParts(message.parts) ?? textFromParts(message.content);
|
|
289
281
|
}
|
|
@@ -292,7 +284,7 @@ function textFromParts(value: unknown): string | undefined {
|
|
|
292
284
|
if (!Array.isArray(value) || value.length === 0) return undefined;
|
|
293
285
|
const text: string[] = [];
|
|
294
286
|
for (const part of value) {
|
|
295
|
-
if (!
|
|
287
|
+
if (!isRecord(part) || part.type !== "text" || typeof part.text !== "string") {
|
|
296
288
|
return undefined;
|
|
297
289
|
}
|
|
298
290
|
text.push(part.text);
|
|
@@ -307,13 +299,13 @@ function serialized(value: unknown, limit = TRACE_IO_LIMIT): string {
|
|
|
307
299
|
|
|
308
300
|
function sseFrame(line: string): unknown {
|
|
309
301
|
if (!line.startsWith("data:")) return undefined;
|
|
310
|
-
return
|
|
302
|
+
return parse(line.slice(5));
|
|
311
303
|
}
|
|
312
304
|
|
|
313
305
|
function valueUsesGenie(value: unknown, depth = 0): boolean {
|
|
314
306
|
if (depth > 3) return false;
|
|
315
307
|
if (Array.isArray(value)) return value.some((item) => valueUsesGenie(item, depth + 1));
|
|
316
|
-
if (!
|
|
308
|
+
if (!isRecord(value)) return false;
|
|
317
309
|
if (
|
|
318
310
|
value.type === "data-genie-progress" ||
|
|
319
311
|
value.type === "ask_genie_done" ||
|
|
@@ -332,11 +324,11 @@ function assistantTextFromSseLine(line: string): string {
|
|
|
332
324
|
}
|
|
333
325
|
|
|
334
326
|
function assistantTextFromSseFrame(frame: unknown): string {
|
|
335
|
-
if (!
|
|
327
|
+
if (!isRecord(frame) || frame.type !== "text-delta") return "";
|
|
336
328
|
const text =
|
|
337
329
|
typeof frame.delta === "string"
|
|
338
330
|
? frame.delta
|
|
339
|
-
:
|
|
331
|
+
: isRecord(frame.payload)
|
|
340
332
|
? frame.payload.text
|
|
341
333
|
: undefined;
|
|
342
334
|
return typeof text === "string" ? text : "";
|
|
@@ -361,7 +353,7 @@ export function assistantTextFromSse(body: string): string {
|
|
|
361
353
|
|
|
362
354
|
/** Read the final assistant text from a non-streaming Mastra response. */
|
|
363
355
|
export function assistantTextFromJson(body: unknown): string {
|
|
364
|
-
if (!
|
|
356
|
+
if (!isRecord(body) || typeof body.text !== "string") return "";
|
|
365
357
|
return body.text.slice(0, TRACE_IO_LIMIT);
|
|
366
358
|
}
|
|
367
359
|
|
|
@@ -381,7 +373,7 @@ interface AppMlflowTraceState {
|
|
|
381
373
|
|
|
382
374
|
function attributeString(span: ReadableSpan, name: string): string | undefined {
|
|
383
375
|
const value = span.attributes[name];
|
|
384
|
-
return typeof value === "string" ? (
|
|
376
|
+
return typeof value === "string" ? (trimToNull(value) ?? undefined) : undefined;
|
|
385
377
|
}
|
|
386
378
|
|
|
387
379
|
function metadataAttribute(key: string): string {
|
|
@@ -391,9 +383,9 @@ function metadataAttribute(key: string): string {
|
|
|
391
383
|
function modelFromSpan(span: ReadableSpan): string | undefined {
|
|
392
384
|
const direct = attributeString(span, GEN_AI_REQUEST_MODEL_ATTR);
|
|
393
385
|
if (direct) return direct;
|
|
394
|
-
const metadata =
|
|
395
|
-
return
|
|
396
|
-
? (
|
|
386
|
+
const metadata = parse(attributeString(span, MASTRA_MODEL_METADATA_ATTR));
|
|
387
|
+
return isRecord(metadata) && typeof metadata.modelId === "string"
|
|
388
|
+
? (trimToNull(metadata.modelId) ?? undefined)
|
|
397
389
|
: undefined;
|
|
398
390
|
}
|
|
399
391
|
|
|
@@ -425,13 +417,13 @@ function hrTimeMilliseconds(value: readonly [number, number]): number {
|
|
|
425
417
|
function previewInput(span: ReadableSpan): string | undefined {
|
|
426
418
|
const raw = attributeString(span, MASTRA_AGENT_INPUT_ATTR);
|
|
427
419
|
if (!raw) return undefined;
|
|
428
|
-
return (textOnlyChatInput(
|
|
420
|
+
return (textOnlyChatInput(parse(raw)) ?? raw).slice(0, TRACE_IO_LIMIT);
|
|
429
421
|
}
|
|
430
422
|
|
|
431
423
|
function previewOutput(span: ReadableSpan): string | undefined {
|
|
432
424
|
const raw = attributeString(span, MASTRA_AGENT_OUTPUT_ATTR);
|
|
433
425
|
if (!raw) return undefined;
|
|
434
|
-
const parsed =
|
|
426
|
+
const parsed = parse(raw);
|
|
435
427
|
const text = assistantTextFromJson(parsed) || (typeof parsed === "string" ? parsed : raw);
|
|
436
428
|
return text.slice(0, TRACE_IO_LIMIT);
|
|
437
429
|
}
|
|
@@ -510,7 +502,7 @@ export async function registerAppMlflowTraceInfoProcessor(): Promise<boolean> {
|
|
|
510
502
|
if (appMlflowTraceInfoProcessorRegistered) return true;
|
|
511
503
|
if (!appMlflowTraceInfoConfigured()) return false;
|
|
512
504
|
await initializeAppMlflowTraceInfo();
|
|
513
|
-
await
|
|
505
|
+
await registerNativeSpanProcessor(new AppMlflowTraceInfoSpanProcessor());
|
|
514
506
|
appMlflowTraceInfoProcessorRegistered = true;
|
|
515
507
|
return true;
|
|
516
508
|
}
|
|
@@ -580,7 +572,7 @@ class AssistantResponseCollector {
|
|
|
580
572
|
}
|
|
581
573
|
|
|
582
574
|
private jsonAnswer(): string {
|
|
583
|
-
return assistantTextFromJson(
|
|
575
|
+
return assistantTextFromJson(parse(this.body));
|
|
584
576
|
}
|
|
585
577
|
}
|
|
586
578
|
|
|
@@ -677,7 +669,7 @@ export function chatTurnTelemetryMiddleware(
|
|
|
677
669
|
parentSpanId: target.span.spanContext().spanId,
|
|
678
670
|
};
|
|
679
671
|
const traceRequestTime = Date.now();
|
|
680
|
-
if (!
|
|
672
|
+
if (!isDatabricksAppEnv()) {
|
|
681
673
|
setChatTraceTag(traceState, MLFLOW_LOCAL_TAG, true);
|
|
682
674
|
}
|
|
683
675
|
for (const [name, value] of Object.entries(getRequestTags(req))) {
|
|
@@ -752,14 +744,16 @@ export function chatTurnTelemetryMiddleware(
|
|
|
752
744
|
for (const [name, value] of traceState.tags) {
|
|
753
745
|
target.span.setAttribute(`${MLFLOW_TRACE_TAG_PREFIX}${name}`, value);
|
|
754
746
|
}
|
|
755
|
-
if (traceState.user)
|
|
747
|
+
if (traceState.user) {
|
|
748
|
+
target.span.setAttribute(USER_TRACE_ATTRIBUTE, traceState.user);
|
|
749
|
+
}
|
|
756
750
|
context.with(trace.setSpan(context.active(), target.span), () => {
|
|
757
751
|
updateActiveMlflowTrace({
|
|
758
752
|
...(Object.keys(tags).length > 0 ? { tags } : {}),
|
|
759
753
|
...(traceState.user ? { user: traceState.user } : {}),
|
|
760
754
|
});
|
|
761
755
|
});
|
|
762
|
-
if (!
|
|
756
|
+
if (!isDatabricksAppEnv()) {
|
|
763
757
|
persistAppMlflowTraceInfo({
|
|
764
758
|
traceId: traceState.traceId,
|
|
765
759
|
requestTime: traceRequestTime,
|
package/src/validation.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @module
|
|
11
11
|
*/
|
|
12
|
-
import {
|
|
12
|
+
import { sequence } from "@dbx-tools/shared-core";
|
|
13
13
|
|
|
14
14
|
/** The subset of a failed parse result these helpers read. */
|
|
15
15
|
export interface SchemaIssues {
|
|
@@ -19,8 +19,7 @@ export interface SchemaIssues {
|
|
|
19
19
|
/** Distinct dot-joined field paths a failed parse flagged. */
|
|
20
20
|
export function invalidFields(error: SchemaIssues): string[] {
|
|
21
21
|
return [
|
|
22
|
-
...
|
|
23
|
-
.sequence(error.issues)
|
|
22
|
+
...sequence(error.issues)
|
|
24
23
|
.map((issue) => issue.path.map(String).join("."))
|
|
25
24
|
.filter(Boolean)
|
|
26
25
|
.distinct(),
|
package/src/workspaces.ts
CHANGED
|
@@ -16,16 +16,27 @@ import { createHash } from "node:crypto";
|
|
|
16
16
|
import { tmpdir } from "node:os";
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
import type { WorkspaceClient } from "@databricks/appkit";
|
|
19
|
-
import { appkit, pluginRegistry } from "@dbx-tools/appkit";
|
|
20
19
|
import {
|
|
21
|
-
|
|
20
|
+
tryGetExecutionContext,
|
|
21
|
+
type PluginContextLike,
|
|
22
|
+
instance,
|
|
22
23
|
type FilesCacheExports,
|
|
23
24
|
type FilesCacheScope,
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
filesCache as filesCachePlugin,
|
|
26
|
+
} from "@dbx-tools/appkit";
|
|
27
|
+
import { DatabricksFileSystem, toLegacyWorkspaceClient } from "@dbx-tools/databricks";
|
|
26
28
|
import { LocalFileSystem } from "@dbx-tools/fs";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
+
import { type PathMatchInput, findFiles, toPathMatcher } from "@dbx-tools/path";
|
|
30
|
+
import {
|
|
31
|
+
log,
|
|
32
|
+
toError,
|
|
33
|
+
toStableKey,
|
|
34
|
+
isRecord,
|
|
35
|
+
trimToNull,
|
|
36
|
+
toSlug,
|
|
37
|
+
tokenize,
|
|
38
|
+
includesAccessTokenScope,
|
|
39
|
+
} from "@dbx-tools/shared-core";
|
|
29
40
|
import {
|
|
30
41
|
fs as sharedFS,
|
|
31
42
|
posixPath,
|
|
@@ -34,6 +45,11 @@ import {
|
|
|
34
45
|
type FileSystemCache,
|
|
35
46
|
type FileSystemCacheOptions,
|
|
36
47
|
MemoryFileSystem,
|
|
48
|
+
isAbsolute,
|
|
49
|
+
normalize,
|
|
50
|
+
normalizeRoot,
|
|
51
|
+
isWithinRoot,
|
|
52
|
+
basename,
|
|
37
53
|
} from "@dbx-tools/shared-fs";
|
|
38
54
|
import type { RequestContext } from "@mastra/core/request-context";
|
|
39
55
|
import { createSkill, validateSkillContent, type InlineSkill } from "@mastra/core/skills";
|
|
@@ -200,7 +216,7 @@ export interface DatabricksWorkspaceOptions extends Omit<
|
|
|
200
216
|
/** Filesystem cache filters. Ignored when the AppKit files-cache plugin is absent. */
|
|
201
217
|
cache?: DatabricksWorkspaceCache;
|
|
202
218
|
/** AppKit plugin context used to discover optional sibling capabilities. */
|
|
203
|
-
pluginContext?:
|
|
219
|
+
pluginContext?: PluginContextLike;
|
|
204
220
|
/**
|
|
205
221
|
* Command sandbox. Defaults to Monty. Pass `false` to disable it, or an
|
|
206
222
|
* explicit Mastra sandbox/provider resolver for a remote transport.
|
|
@@ -269,7 +285,7 @@ export function databricksWorkspaceConfig(
|
|
|
269
285
|
const paths = resolveDatabricksWorkspacePaths(options);
|
|
270
286
|
const configuredPaths = paths.length;
|
|
271
287
|
const extraSkillMounts = options.extraSkillMounts ?? [];
|
|
272
|
-
const filesCache =
|
|
288
|
+
const filesCache = instance(options.pluginContext, filesCachePlugin)?.exports();
|
|
273
289
|
const configuredCache = options.cache;
|
|
274
290
|
const mountFilesCache = configuredCache === false ? undefined : filesCache;
|
|
275
291
|
const retainFilesystemSource =
|
|
@@ -369,7 +385,7 @@ export function databricksWorkspace(options: DatabricksWorkspaceOptions = {}): W
|
|
|
369
385
|
/** @internal Bind a caller-created Databricks workspace to its AppKit plugin context. */
|
|
370
386
|
export function bindDatabricksWorkspaceContext(
|
|
371
387
|
workspace: Workspace,
|
|
372
|
-
pluginContext:
|
|
388
|
+
pluginContext: PluginContextLike | undefined,
|
|
373
389
|
excludeSkillNames: readonly string[] = [],
|
|
374
390
|
): Workspace {
|
|
375
391
|
const options = databricksWorkspaceOptions.get(workspace);
|
|
@@ -447,10 +463,7 @@ function sandboxName(selection: WorkspaceSandboxSelection | undefined): string {
|
|
|
447
463
|
* file API access (`workspace` or `all-apis` on {@link MASTRA_SCOPES_KEY}).
|
|
448
464
|
*/
|
|
449
465
|
function hasWorkspaceFileScope(requestContext: RequestContext | undefined): boolean {
|
|
450
|
-
return
|
|
451
|
-
requestContext?.get(MASTRA_SCOPES_KEY),
|
|
452
|
-
WORKSPACE_FILE_SCOPES,
|
|
453
|
-
);
|
|
466
|
+
return includesAccessTokenScope(requestContext?.get(MASTRA_SCOPES_KEY), WORKSPACE_FILE_SCOPES);
|
|
454
467
|
}
|
|
455
468
|
|
|
456
469
|
/** Resolve the request client and authorization-safe files-cache scope. */
|
|
@@ -479,19 +492,19 @@ async function resolveFilesCacheScope(context: DatabricksWorkspaceContext): Prom
|
|
|
479
492
|
|
|
480
493
|
/** Join one relative skill path to its actual composite mount path. */
|
|
481
494
|
function mountedSkillPath(mount: string, skillPath: string): string {
|
|
482
|
-
if (
|
|
495
|
+
if (isAbsolute(skillPath)) {
|
|
483
496
|
throw new TypeError(`Skill path must be relative to its mount: ${skillPath}`);
|
|
484
497
|
}
|
|
485
|
-
const normalized =
|
|
498
|
+
const normalized = normalize(skillPath);
|
|
486
499
|
if (!normalized.ok) {
|
|
487
500
|
throw new TypeError(`Skill path escapes its mount: ${skillPath}`);
|
|
488
501
|
}
|
|
489
|
-
return posixPath.join(
|
|
502
|
+
return posixPath.join(normalizeRoot(mount), normalized.path.slice(1));
|
|
490
503
|
}
|
|
491
504
|
|
|
492
505
|
/** Return whether a Databricks path belongs to the writable Workspace namespace. */
|
|
493
506
|
function isWorkspaceRoot(root: string): boolean {
|
|
494
|
-
const normalized =
|
|
507
|
+
const normalized = normalizeRoot(root);
|
|
495
508
|
return normalized === "/Workspace" || normalized.startsWith("/Workspace/");
|
|
496
509
|
}
|
|
497
510
|
|
|
@@ -522,7 +535,7 @@ async function resolveWorkspacePathMounts(
|
|
|
522
535
|
for (const [index, path] of paths.entries()) {
|
|
523
536
|
const configured = await resolveDatabricksWorkspacePath(path, context);
|
|
524
537
|
if (!configured) continue;
|
|
525
|
-
if (
|
|
538
|
+
if (isWithinRoot(SCRATCH_MOUNT, configured.path)) {
|
|
526
539
|
const mount = configured.mount ?? configured.path;
|
|
527
540
|
mounts[mount] = userTempFilesystem(context, configured.path);
|
|
528
541
|
hasWritableMount = true;
|
|
@@ -622,7 +635,7 @@ async function resolveSkillFolderFilesystem(
|
|
|
622
635
|
});
|
|
623
636
|
return undefined;
|
|
624
637
|
}
|
|
625
|
-
const root =
|
|
638
|
+
const root = trimToNull(folder.path);
|
|
626
639
|
if (!root) return undefined;
|
|
627
640
|
const writable = folder.writable ?? isWorkspaceRoot(root);
|
|
628
641
|
const cacheOptions = cache.fileSystemCache
|
|
@@ -660,10 +673,10 @@ function resolveHomePath(
|
|
|
660
673
|
input: string | undefined,
|
|
661
674
|
requestContext: Record<string, unknown>,
|
|
662
675
|
): string | undefined {
|
|
663
|
-
const value =
|
|
676
|
+
const value = trimToNull(input);
|
|
664
677
|
if (!value || (value !== "~" && !value.startsWith("~/"))) return value ?? undefined;
|
|
665
678
|
const emailValue = requestContext[MASTRA_USER_EMAIL_KEY];
|
|
666
|
-
const email = typeof emailValue === "string" ?
|
|
679
|
+
const email = typeof emailValue === "string" ? trimToNull(emailValue) : undefined;
|
|
667
680
|
if (!email) return undefined;
|
|
668
681
|
const home = personalWorkspacePath(email);
|
|
669
682
|
return value === "~" ? home : `${home}/${value.slice(2)}`;
|
|
@@ -693,12 +706,12 @@ function cacheFilterMatches(
|
|
|
693
706
|
const expanded = resolveHomePath(input, requestContext);
|
|
694
707
|
if (!expanded) continue;
|
|
695
708
|
inputs.push(
|
|
696
|
-
!root ||
|
|
709
|
+
!root || isAbsolute(expanded) || expanded.startsWith("**")
|
|
697
710
|
? expanded
|
|
698
|
-
: `${
|
|
711
|
+
: `${normalizeRoot(root)}/${expanded.replace(/^\.\//, "")}`,
|
|
699
712
|
);
|
|
700
713
|
}
|
|
701
|
-
return inputs.length > 0 &&
|
|
714
|
+
return inputs.length > 0 && toPathMatcher(...inputs)(path);
|
|
702
715
|
}
|
|
703
716
|
|
|
704
717
|
async function resolveFileCacheOptions(
|
|
@@ -715,8 +728,7 @@ async function resolveFileCacheOptions(
|
|
|
715
728
|
return {
|
|
716
729
|
operations: CACHEABLE_FILESYSTEM_OPERATIONS,
|
|
717
730
|
filter: (operation, path) =>
|
|
718
|
-
operation !== "readFile" ||
|
|
719
|
-
roots.some((skillRoot) => posixPath.isWithinRoot(skillRoot, path)),
|
|
731
|
+
operation !== "readFile" || roots.some((skillRoot) => isWithinRoot(skillRoot, path)),
|
|
720
732
|
};
|
|
721
733
|
}
|
|
722
734
|
const policies = toPolicyArray(resolved);
|
|
@@ -746,10 +758,10 @@ function resolveWorkspaceIdentity(options: DatabricksWorkspaceOptions): {
|
|
|
746
758
|
let id = options.id;
|
|
747
759
|
let name = options.name;
|
|
748
760
|
if (!id) {
|
|
749
|
-
id = name ?
|
|
761
|
+
id = name ? toSlug(name) : "workspace";
|
|
750
762
|
}
|
|
751
763
|
if (!name) {
|
|
752
|
-
name = Array.from(
|
|
764
|
+
name = Array.from(tokenize(id)).join(" ");
|
|
753
765
|
}
|
|
754
766
|
return { id, name };
|
|
755
767
|
}
|
|
@@ -788,10 +800,10 @@ function userTempFilesystem(
|
|
|
788
800
|
): WorkspaceFilesystem {
|
|
789
801
|
const userKey = resolveUserKey(context.requestContext);
|
|
790
802
|
const digest = createHash("sha256")
|
|
791
|
-
.update(
|
|
803
|
+
.update(toStableKey({ package: "@dbx-tools/appkit-mastra", userKey }))
|
|
792
804
|
.digest("hex")
|
|
793
805
|
.slice(0, 16);
|
|
794
|
-
const normalized =
|
|
806
|
+
const normalized = normalizeRoot(workspacePath);
|
|
795
807
|
const relative = normalized === SCRATCH_MOUNT ? "" : normalized.slice(`${SCRATCH_MOUNT}/`.length);
|
|
796
808
|
const source = new LocalFileSystem({
|
|
797
809
|
root: join(tmpdir(), "dbx-tools", "mastra", digest, relative),
|
|
@@ -814,7 +826,7 @@ function localSkillMountResolvers(mounts: readonly LocalSkillMount[]): Workspace
|
|
|
814
826
|
unique.set(`${entry.mountPath}\0${identity}`, entry);
|
|
815
827
|
}
|
|
816
828
|
return [...unique.values()].map((entry) => {
|
|
817
|
-
const mount =
|
|
829
|
+
const mount = normalizeRoot(entry.mountPath);
|
|
818
830
|
const filesystem = inMemorySkillFilesystem(entry);
|
|
819
831
|
return async () => {
|
|
820
832
|
const source = await filesystem;
|
|
@@ -866,7 +878,7 @@ export async function inlineSkillsFromLocalMounts(
|
|
|
866
878
|
...(metadata.compatibility !== undefined
|
|
867
879
|
? { compatibility: metadata.compatibility }
|
|
868
880
|
: {}),
|
|
869
|
-
...(
|
|
881
|
+
...(isRecord(metadata.metadata) ? { metadata: metadata.metadata } : {}),
|
|
870
882
|
...(Object.keys(references).length > 0 ? { references } : {}),
|
|
871
883
|
}),
|
|
872
884
|
);
|
|
@@ -903,7 +915,7 @@ async function loadInMemorySkillFilesystem(mount: LocalSkillMount): Promise<File
|
|
|
903
915
|
root: "/",
|
|
904
916
|
});
|
|
905
917
|
await memory.init();
|
|
906
|
-
for await (const relative of
|
|
918
|
+
for await (const relative of findFiles("**/*", { cwd: mount.root, nodir: true })) {
|
|
907
919
|
await memory.writeFile(relative, await source.readFile(relative), { overwrite: true });
|
|
908
920
|
}
|
|
909
921
|
await source.close();
|
|
@@ -941,7 +953,7 @@ async function databricksFilesystem(
|
|
|
941
953
|
| undefined
|
|
942
954
|
> {
|
|
943
955
|
const fs = new DatabricksFileSystem({
|
|
944
|
-
client: () =>
|
|
956
|
+
client: () => toLegacyWorkspaceClient(requestWorkspaceClient(client)),
|
|
945
957
|
root,
|
|
946
958
|
readOnly,
|
|
947
959
|
createRoot,
|
|
@@ -959,7 +971,7 @@ async function databricksFilesystem(
|
|
|
959
971
|
logger.debug("databricks-mount:skipped", {
|
|
960
972
|
root,
|
|
961
973
|
readOnly,
|
|
962
|
-
error:
|
|
974
|
+
error: toError(err),
|
|
963
975
|
});
|
|
964
976
|
}
|
|
965
977
|
return undefined;
|
|
@@ -968,9 +980,7 @@ async function databricksFilesystem(
|
|
|
968
980
|
/** @internal Keep service context from replacing the request-scoped workspace client. */
|
|
969
981
|
export function requestWorkspaceClient(
|
|
970
982
|
initialClient: WorkspaceClient,
|
|
971
|
-
executionContext: ReturnType<
|
|
972
|
-
typeof appkit.tryGetExecutionContext
|
|
973
|
-
> = appkit.tryGetExecutionContext(),
|
|
983
|
+
executionContext: ReturnType<typeof tryGetExecutionContext> = tryGetExecutionContext(),
|
|
974
984
|
): WorkspaceClient {
|
|
975
985
|
if (
|
|
976
986
|
executionContext &&
|
|
@@ -1008,7 +1018,7 @@ function verifyFirstFileSystemAccess<TFileSystem extends FileSystem>(
|
|
|
1008
1018
|
readCheck = undefined;
|
|
1009
1019
|
logger.debug("databricks-mount:read-check-failed", {
|
|
1010
1020
|
root,
|
|
1011
|
-
error:
|
|
1021
|
+
error: toError(error),
|
|
1012
1022
|
});
|
|
1013
1023
|
throw error;
|
|
1014
1024
|
}
|
|
@@ -1039,7 +1049,7 @@ function verifyFirstFileSystemAccess<TFileSystem extends FileSystem>(
|
|
|
1039
1049
|
if (!writeVerified) {
|
|
1040
1050
|
logger.debug("databricks-mount:write-check-failed", {
|
|
1041
1051
|
root,
|
|
1042
|
-
error:
|
|
1052
|
+
error: toError(error),
|
|
1043
1053
|
});
|
|
1044
1054
|
}
|
|
1045
1055
|
throw error;
|
|
@@ -1132,7 +1142,7 @@ async function resolveDistinctSkillPaths(
|
|
|
1132
1142
|
try {
|
|
1133
1143
|
const entries = await filesystem.readdir(path);
|
|
1134
1144
|
if (entries.some((entry) => entry.type === "file" && entry.name === "SKILL.md")) {
|
|
1135
|
-
const name =
|
|
1145
|
+
const name = basename(path);
|
|
1136
1146
|
if (!excludedSkillNames.has(name) && !selected.has(name)) selected.set(name, path);
|
|
1137
1147
|
continue;
|
|
1138
1148
|
}
|
|
@@ -1149,7 +1159,7 @@ async function resolveDistinctSkillPaths(
|
|
|
1149
1159
|
} catch (error) {
|
|
1150
1160
|
logger.debug("skill-path:skipped", {
|
|
1151
1161
|
path,
|
|
1152
|
-
error:
|
|
1162
|
+
error: toError(error),
|
|
1153
1163
|
});
|
|
1154
1164
|
}
|
|
1155
1165
|
}
|
package/src/writer.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @module
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { toError, type Logger } from "@dbx-tools/shared-core";
|
|
14
14
|
import { GENIE_PROGRESS_PART_TYPE, type GenieWriterEvent } from "@dbx-tools/shared-mastra";
|
|
15
15
|
import type { ToolExecutionContext } from "@mastra/core/tools";
|
|
16
16
|
|
|
@@ -25,7 +25,7 @@ import type { ToolExecutionContext } from "@mastra/core/tools";
|
|
|
25
25
|
* Returns when the write resolves or rejects; never throws.
|
|
26
26
|
*/
|
|
27
27
|
export async function safeWriteProgress(
|
|
28
|
-
log:
|
|
28
|
+
log: Logger,
|
|
29
29
|
writer: ToolExecutionContext["writer"],
|
|
30
30
|
toolCallId: string | undefined,
|
|
31
31
|
event: GenieWriterEvent,
|
|
@@ -44,7 +44,7 @@ export async function safeWriteProgress(
|
|
|
44
44
|
} catch (err) {
|
|
45
45
|
log.warn("writer:error", {
|
|
46
46
|
...context,
|
|
47
|
-
error:
|
|
47
|
+
error: toError(err),
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
50
|
}
|