@dbx-tools/appkit-mastra 0.9.5 → 0.9.7
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 +23 -24
- package/index.ts +1 -3
- package/lib/index.d.ts +1 -3
- package/lib/index.js +2 -3
- package/lib/src/agents.d.ts +2 -2
- package/lib/src/agents.js +6 -6
- package/lib/src/chart.js +23 -23
- package/lib/src/evaluation.js +3 -3
- package/lib/src/genie.d.ts +2 -2
- package/lib/src/genie.js +20 -20
- package/lib/src/memory.js +9 -26
- package/lib/src/mlflow.js +4 -4
- package/lib/src/model.js +4 -4
- package/lib/src/monty-sandbox.js +6 -6
- package/lib/src/observability.js +3 -3
- package/lib/src/plugin.js +26 -21
- package/lib/src/remote-skills.js +20 -30
- package/lib/src/sandbox.js +10 -10
- package/lib/src/server.js +5 -5
- package/lib/src/serving-sanitize.js +12 -9
- package/lib/src/serving.js +8 -8
- package/lib/src/storage-schema.js +4 -4
- package/lib/src/summarize.js +3 -3
- package/lib/src/workspaces.js +9 -9
- package/lib/src/writer.js +3 -3
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -17
- package/src/agents.ts +8 -8
- package/src/chart.ts +24 -24
- package/src/evaluation.ts +2 -2
- package/src/genie.ts +20 -20
- package/src/memory.ts +11 -27
- package/src/mlflow.ts +3 -3
- package/src/model.ts +3 -3
- package/src/monty-sandbox.ts +5 -5
- package/src/observability.ts +2 -2
- package/src/plugin.ts +30 -20
- package/src/remote-skills.ts +36 -38
- package/src/sandbox.ts +9 -9
- package/src/server.ts +4 -4
- package/src/serving-sanitize.ts +11 -13
- package/src/serving.ts +7 -7
- package/src/storage-schema.ts +3 -3
- package/src/summarize.ts +2 -2
- package/src/workspaces.ts +8 -8
- package/src/writer.ts +2 -2
- package/lib/src/pagination.d.ts +0 -33
- package/lib/src/pagination.js +0 -36
- package/src/pagination.ts +0 -47
package/src/genie.ts
CHANGED
|
@@ -44,9 +44,9 @@ import {
|
|
|
44
44
|
ValidationError,
|
|
45
45
|
} from "@databricks/appkit";
|
|
46
46
|
import type { WorkspaceClient } from "@databricks/appkit";
|
|
47
|
-
import {
|
|
47
|
+
import { pluginRegistry } from "@dbx-tools/appkit";
|
|
48
48
|
import { chat, space as genieSpace } from "@dbx-tools/genie";
|
|
49
|
-
import {
|
|
49
|
+
import { errorUtils, log, stringUtils } from "@dbx-tools/shared-core";
|
|
50
50
|
import { genieModel, type GenieMessage } from "@dbx-tools/shared-genie";
|
|
51
51
|
import type { StartedEvent } from "@dbx-tools/shared-mastra";
|
|
52
52
|
import type { RequestContext } from "@mastra/core/request-context";
|
|
@@ -245,7 +245,7 @@ async function readCachedConversationId(cacheKey: string | undefined): Promise<s
|
|
|
245
245
|
return v ?? undefined;
|
|
246
246
|
} catch (err) {
|
|
247
247
|
logger.warn("conversation-cache:read-error", {
|
|
248
|
-
error:
|
|
248
|
+
error: errorUtils.errorMessage(err),
|
|
249
249
|
});
|
|
250
250
|
return undefined;
|
|
251
251
|
}
|
|
@@ -269,7 +269,7 @@ async function saveCachedConversationId(
|
|
|
269
269
|
});
|
|
270
270
|
} catch (err) {
|
|
271
271
|
logger.warn("conversation-cache:write-error", {
|
|
272
|
-
error:
|
|
272
|
+
error: errorUtils.errorMessage(err),
|
|
273
273
|
});
|
|
274
274
|
}
|
|
275
275
|
}
|
|
@@ -281,7 +281,7 @@ async function evictCachedConversationId(cacheKey: string | undefined): Promise<
|
|
|
281
281
|
await CacheManager.getInstanceSync().delete(cacheKey);
|
|
282
282
|
} catch (err) {
|
|
283
283
|
logger.warn("conversation-cache:delete-error", {
|
|
284
|
-
error:
|
|
284
|
+
error: errorUtils.errorMessage(err),
|
|
285
285
|
});
|
|
286
286
|
}
|
|
287
287
|
}
|
|
@@ -324,7 +324,7 @@ const prepareChartRequestSchema = chartPlannerRequestSchema
|
|
|
324
324
|
.string()
|
|
325
325
|
.min(1, "statement_id is required")
|
|
326
326
|
.describe(
|
|
327
|
-
|
|
327
|
+
stringUtils.toDescription(`
|
|
328
328
|
Genie \`statement_id\` to chart. Read from
|
|
329
329
|
\`message.query_result.statement_id\` or
|
|
330
330
|
\`message.attachments[*].query.statement_id\` returned by
|
|
@@ -345,7 +345,7 @@ const prepareChartRequestSchema = chartPlannerRequestSchema
|
|
|
345
345
|
*/
|
|
346
346
|
function aliasSuffix(alias: string): string {
|
|
347
347
|
if (alias === DEFAULT_GENIE_ALIAS) return "";
|
|
348
|
-
const slug =
|
|
348
|
+
const slug = stringUtils.toIdentifier(alias);
|
|
349
349
|
return slug ? `_${slug}` : "";
|
|
350
350
|
}
|
|
351
351
|
|
|
@@ -381,7 +381,7 @@ function buildAskGenieTool(opts: {
|
|
|
381
381
|
const hintLine = hint ? ` (${hint})` : "";
|
|
382
382
|
return createTool({
|
|
383
383
|
id: toolId,
|
|
384
|
-
description:
|
|
384
|
+
description: stringUtils.toDescription(`
|
|
385
385
|
Ask the Genie space "${alias}"${hintLine} ONE focused
|
|
386
386
|
natural-language sub-question and wait for the turn to
|
|
387
387
|
complete. Genie answers best when each call covers a
|
|
@@ -414,7 +414,7 @@ function buildAskGenieTool(opts: {
|
|
|
414
414
|
.string()
|
|
415
415
|
.min(1, "question is required")
|
|
416
416
|
.describe(
|
|
417
|
-
|
|
417
|
+
stringUtils.toDescription(`
|
|
418
418
|
ONE focused natural-language question about the data in this
|
|
419
419
|
space, covering a single metric / dimension / time window
|
|
420
420
|
(e.g. "What was Q3 revenue by region?"). Decompose a
|
|
@@ -507,11 +507,11 @@ function buildAskGenieTool(opts: {
|
|
|
507
507
|
// A rejected conversation id is stale or inaccessible. Reset this
|
|
508
508
|
// lane and retry once with a fresh Genie conversation.
|
|
509
509
|
const rejectedConversationId = conversationId;
|
|
510
|
-
if (rejectedConversationId &&
|
|
510
|
+
if (rejectedConversationId && errorUtils.errorContext(err).notAccessible) {
|
|
511
511
|
logger.warn("conversation-cache:stale, resetting", {
|
|
512
512
|
spaceId,
|
|
513
513
|
conversationId: rejectedConversationId,
|
|
514
|
-
error:
|
|
514
|
+
error: errorUtils.errorMessage(err),
|
|
515
515
|
});
|
|
516
516
|
if (ownsReusableConversation) {
|
|
517
517
|
await evictCachedConversationId(cacheKey);
|
|
@@ -541,7 +541,7 @@ function buildSpaceDescriptionTool(opts: { spaceId: string; alias: string }) {
|
|
|
541
541
|
const toolId = `get_space_description${aliasSuffix(alias)}`;
|
|
542
542
|
return createTool({
|
|
543
543
|
id: toolId,
|
|
544
|
-
description:
|
|
544
|
+
description: stringUtils.toDescription(`
|
|
545
545
|
Return the Genie space "${alias}"'s title, description, and
|
|
546
546
|
warehouse id. Cheap (single REST call, no LLM round-trip).
|
|
547
547
|
Call this FIRST on any user turn that's going to touch
|
|
@@ -586,7 +586,7 @@ function buildSpaceSerializedTool(opts: { spaceId: string; alias: string }) {
|
|
|
586
586
|
const toolId = `get_space_serialized${aliasSuffix(alias)}`;
|
|
587
587
|
return createTool({
|
|
588
588
|
id: toolId,
|
|
589
|
-
description:
|
|
589
|
+
description: stringUtils.toDescription(`
|
|
590
590
|
Return the full \`GenieSpace\` JSON for the "${alias}" space.
|
|
591
591
|
Use only when you need exact column / table identifiers
|
|
592
592
|
\`get_space_description\` doesn't expose. Larger payload, so
|
|
@@ -625,7 +625,7 @@ function buildGetStatementTool() {
|
|
|
625
625
|
const toolId = "get_statement";
|
|
626
626
|
return createTool({
|
|
627
627
|
id: toolId,
|
|
628
|
-
description:
|
|
628
|
+
description: stringUtils.toDescription(`
|
|
629
629
|
Fetch the rows of a Genie statement by its \`statement_id\` (the
|
|
630
630
|
value at \`message.query_result.statement_id\` or
|
|
631
631
|
\`message.attachments[*].query.statement_id\` returned from
|
|
@@ -645,7 +645,7 @@ function buildGetStatementTool() {
|
|
|
645
645
|
.string()
|
|
646
646
|
.min(1, "statement_id is required")
|
|
647
647
|
.describe(
|
|
648
|
-
|
|
648
|
+
stringUtils.toDescription(`
|
|
649
649
|
Genie \`statement_id\` whose rows to read. Take it from
|
|
650
650
|
\`message.query_result.statement_id\` or
|
|
651
651
|
\`message.attachments[*].query.statement_id\` on an
|
|
@@ -710,7 +710,7 @@ function buildPrepareChartTool(opts: { config: MastraPluginConfig }) {
|
|
|
710
710
|
const toolId = "prepare_chart";
|
|
711
711
|
return createTool({
|
|
712
712
|
id: toolId,
|
|
713
|
-
description:
|
|
713
|
+
description: stringUtils.toDescription([
|
|
714
714
|
`
|
|
715
715
|
Queue a chart for the rows of a Genie statement. Mints a
|
|
716
716
|
\`chartId\` plus its complete \`marker\` synchronously and
|
|
@@ -782,7 +782,7 @@ function buildPrepareChartTool(opts: { config: MastraPluginConfig }) {
|
|
|
782
782
|
* variant that names the suffixed per-space tools
|
|
783
783
|
* (e.g. `ask_genie_sales`).
|
|
784
784
|
*/
|
|
785
|
-
export const GENIE_INSTRUCTIONS =
|
|
785
|
+
export const GENIE_INSTRUCTIONS = stringUtils.toDescription([
|
|
786
786
|
"Genie orchestration. For every user question that needs SQL-backed data:",
|
|
787
787
|
{
|
|
788
788
|
numbered: [
|
|
@@ -975,7 +975,7 @@ type AppKitGenieConfig = NonNullable<Parameters<typeof genie>[0]>;
|
|
|
975
975
|
*/
|
|
976
976
|
export function resolveGenieSpaces(
|
|
977
977
|
config: MastraPluginConfig,
|
|
978
|
-
context:
|
|
978
|
+
context: pluginRegistry.PluginContextLike | undefined,
|
|
979
979
|
): Record<string, GenieSpaceConfig> {
|
|
980
980
|
const merged: Record<string, GenieSpaceConfig> = {};
|
|
981
981
|
|
|
@@ -988,7 +988,7 @@ export function resolveGenieSpaces(
|
|
|
988
988
|
// Source 2: AppKit `genie()` plugin instance config. Use a
|
|
989
989
|
// structural cast - `Plugin.config` is `protected` in TS only,
|
|
990
990
|
// and the runtime layout is plain object property access.
|
|
991
|
-
const geniePlugin =
|
|
991
|
+
const geniePlugin = pluginRegistry.instance(context, genie);
|
|
992
992
|
if (geniePlugin) {
|
|
993
993
|
const pluginSpaces = (geniePlugin as unknown as { config?: AppKitGenieConfig }).config?.spaces;
|
|
994
994
|
if (pluginSpaces) {
|
|
@@ -1142,7 +1142,7 @@ export async function collectSpaceSuggestions(opts: {
|
|
|
1142
1142
|
} catch (err) {
|
|
1143
1143
|
logger.warn("suggestions:fetch-error", {
|
|
1144
1144
|
spaceId,
|
|
1145
|
-
error:
|
|
1145
|
+
error: errorUtils.errorMessage(err),
|
|
1146
1146
|
});
|
|
1147
1147
|
questions = [];
|
|
1148
1148
|
}
|
package/src/memory.ts
CHANGED
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
35
|
import { getUsernameWithApiLookup } from "@databricks/appkit";
|
|
36
|
-
import {
|
|
36
|
+
import { migration as appkitMigration } from "@dbx-tools/appkit";
|
|
37
|
+
import { hash, log } from "@dbx-tools/shared-core";
|
|
37
38
|
import { fastembed } from "@mastra/fastembed";
|
|
38
39
|
import { Memory } from "@mastra/memory";
|
|
39
40
|
import { PgVector, PostgresStore } from "@mastra/pg";
|
|
@@ -46,19 +47,6 @@ import { summaryModel, TITLE_INSTRUCTIONS } from "./summarize.ts";
|
|
|
46
47
|
|
|
47
48
|
const logger = log.logger("mastra/memory");
|
|
48
49
|
|
|
49
|
-
/** Process-wide dedupe so instance + per-agent stores share one ownership log. */
|
|
50
|
-
const loggedMigrationErrors = new Set<string>();
|
|
51
|
-
|
|
52
|
-
/** Whether `LOG_LEVEL` is currently at or below debug. */
|
|
53
|
-
function isDebugEnabled(): boolean {
|
|
54
|
-
return log.isLevelEnabled("debug");
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Whether a migration failed only because this role does not own an existing object. */
|
|
58
|
-
function isOwnershipMigrationError(err: unknown): boolean {
|
|
59
|
-
return error.errorContext(err).hasMessage("must be owner");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
50
|
/**
|
|
63
51
|
* Soften {@link PostgresStore.init} so a failed migration (e.g. Lakebase
|
|
64
52
|
* `must be owner of table …` on `ALTER`) is logged once instead of rethrown
|
|
@@ -87,19 +75,15 @@ export function withSoftStorageInit(store: PostgresStore): PostgresStore {
|
|
|
87
75
|
try {
|
|
88
76
|
await originalInit();
|
|
89
77
|
} catch (err) {
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
storeId: soft.id,
|
|
100
|
-
schema: soft.schema,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
78
|
+
if (
|
|
79
|
+
!appkitMigration.handleOwnershipMigrationError(err, {
|
|
80
|
+
scope: "mastra-storage",
|
|
81
|
+
logger,
|
|
82
|
+
event: "mastra storage migration failed",
|
|
83
|
+
context: { storeId: soft.id, schema: soft.schema },
|
|
84
|
+
})
|
|
85
|
+
) {
|
|
86
|
+
throw err;
|
|
103
87
|
}
|
|
104
88
|
}
|
|
105
89
|
})();
|
package/src/mlflow.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { appkit } from "@dbx-tools/appkit";
|
|
25
|
-
import {
|
|
25
|
+
import { asyncUtils, errorUtils, log } from "@dbx-tools/shared-core";
|
|
26
26
|
import { feedback } from "@dbx-tools/shared-mastra";
|
|
27
27
|
import { databricksFetch, readResponseJson, readResponseText } from "./rest.ts";
|
|
28
28
|
|
|
@@ -125,7 +125,7 @@ export async function logFeedback(
|
|
|
125
125
|
} catch (err) {
|
|
126
126
|
logger.warn("feedback request failed", {
|
|
127
127
|
traceId: params.traceId,
|
|
128
|
-
error:
|
|
128
|
+
error: errorUtils.errorMessage(err),
|
|
129
129
|
});
|
|
130
130
|
return undefined;
|
|
131
131
|
}
|
|
@@ -139,7 +139,7 @@ export async function logFeedback(
|
|
|
139
139
|
// Trace export is async; a fresh trace may not exist yet. Retry a
|
|
140
140
|
// few times with a short backoff before giving up softly.
|
|
141
141
|
if (res.status === 404 && attempt < NOT_FOUND_RETRIES) {
|
|
142
|
-
await
|
|
142
|
+
await asyncUtils.sleep(NOT_FOUND_BACKOFF_MS * (attempt + 1));
|
|
143
143
|
continue;
|
|
144
144
|
}
|
|
145
145
|
logger.warn("feedback not recorded", {
|
package/src/model.ts
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
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
|
-
import {
|
|
29
|
+
import { functionUtils, json, log, net } from "@dbx-tools/shared-core";
|
|
30
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";
|
|
@@ -193,12 +193,12 @@ const CHAT_COMPLETIONS_PATH = `/${invoke.CHAT_COMPLETIONS_PATH}`;
|
|
|
193
193
|
* `choices[].message.content` or `choices[].delta.content` as a parts
|
|
194
194
|
* array that the AI SDK's OpenAI schema rejects.
|
|
195
195
|
*
|
|
196
|
-
* Safe to call from any hot path: {@link
|
|
196
|
+
* Safe to call from any hot path: {@link functionUtils.memoize} ensures
|
|
197
197
|
* the wrapper is installed at most once per process, so subsequent
|
|
198
198
|
* calls are a no-op even when {@link buildModel} fires on every agent
|
|
199
199
|
* step.
|
|
200
200
|
*/
|
|
201
|
-
const setupFetchInterceptor =
|
|
201
|
+
const setupFetchInterceptor = functionUtils.memoize((): void => {
|
|
202
202
|
globalThis.fetch = createServingFetchInterceptor(globalThis.fetch.bind(globalThis));
|
|
203
203
|
});
|
|
204
204
|
|
package/src/monty-sandbox.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { errorUtils, functionUtils } from "@dbx-tools/shared-core";
|
|
12
12
|
import type {
|
|
13
13
|
CommandResult,
|
|
14
14
|
ExecuteCommandOptions,
|
|
@@ -20,8 +20,8 @@ import type { CheckoutOptions, Monty as MontyPool, MontySession } from "@pydanti
|
|
|
20
20
|
const DEFAULT_COMMAND_TIMEOUT_MS = 30_000;
|
|
21
21
|
const DEFAULT_MAX_MEMORY_BYTES = 10_000_000;
|
|
22
22
|
|
|
23
|
-
const montyModule =
|
|
24
|
-
const montyPool =
|
|
23
|
+
const montyModule = functionUtils.memoize(() => import("@pydantic/monty/node"));
|
|
24
|
+
const montyPool = functionUtils.memoize(async (): Promise<MontyPool> => {
|
|
25
25
|
const { Monty } = await montyModule();
|
|
26
26
|
return Monty.create({
|
|
27
27
|
minProcesses: 1,
|
|
@@ -76,7 +76,7 @@ export class MontySandbox implements WorkspaceSandbox {
|
|
|
76
76
|
this.status = "running";
|
|
77
77
|
} catch (caught) {
|
|
78
78
|
this.status = "error";
|
|
79
|
-
this.error =
|
|
79
|
+
this.error = errorUtils.errorMessage(caught);
|
|
80
80
|
throw caught;
|
|
81
81
|
}
|
|
82
82
|
}
|
|
@@ -181,7 +181,7 @@ export class MontySandbox implements WorkspaceSandbox {
|
|
|
181
181
|
if (aborted || options.abortSignal?.aborted) {
|
|
182
182
|
throw abortReason(options.abortSignal);
|
|
183
183
|
}
|
|
184
|
-
const message =
|
|
184
|
+
const message = errorUtils.errorMessage(caught);
|
|
185
185
|
output.emit("stderr", output.stderr.value ? `\n${message}` : message);
|
|
186
186
|
const { MontyCrashedError } = await montyModule();
|
|
187
187
|
const timedOut = caught instanceof MontyCrashedError && caught.timedOut;
|
package/src/observability.ts
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
* @module
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
|
-
import {
|
|
45
|
+
import { projectUtils } from "@dbx-tools/core";
|
|
46
46
|
import { log } from "@dbx-tools/shared-core";
|
|
47
47
|
import { Observability } from "@mastra/observability";
|
|
48
48
|
import { OtelBridge } from "@mastra/otel-bridge";
|
|
@@ -122,7 +122,7 @@ export async function buildObservability(
|
|
|
122
122
|
return undefined;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
const serviceName = options?.serviceName ?? (await
|
|
125
|
+
const serviceName = options?.serviceName ?? (await projectUtils.name()) ?? DEFAULT_SERVICE_NAME;
|
|
126
126
|
const requestContextKeys = [...(options?.requestContextKeys ?? TRACE_REQUEST_CONTEXT_KEYS)];
|
|
127
127
|
|
|
128
128
|
// The OTel HTTP exporter treats `OTEL_EXPORTER_OTLP_ENDPOINT` as a
|
package/src/plugin.ts
CHANGED
|
@@ -56,9 +56,9 @@ import {
|
|
|
56
56
|
type PluginManifest,
|
|
57
57
|
type ResourceRequirement,
|
|
58
58
|
} from "@databricks/appkit";
|
|
59
|
-
import {
|
|
60
|
-
import {
|
|
61
|
-
import {
|
|
59
|
+
import { pluginRegistry } from "@dbx-tools/appkit";
|
|
60
|
+
import { modelCatalog } from "@dbx-tools/model";
|
|
61
|
+
import { asyncUtils, errorUtils, log, object, stringUtils } from "@dbx-tools/shared-core";
|
|
62
62
|
import {
|
|
63
63
|
feedback,
|
|
64
64
|
routes,
|
|
@@ -77,7 +77,12 @@ import type { Pool } from "pg";
|
|
|
77
77
|
import { buildAgents, FALLBACK_AGENT_ID, type BuiltAgents } from "./agents.ts";
|
|
78
78
|
import { fetchChart } from "./chart.ts";
|
|
79
79
|
import { agentChatRoutes } from "./chat.ts";
|
|
80
|
-
import {
|
|
80
|
+
import {
|
|
81
|
+
attributedUserId,
|
|
82
|
+
executionContextUserId,
|
|
83
|
+
MASTRA_CONFIG_SCHEMA,
|
|
84
|
+
type MastraPluginConfig,
|
|
85
|
+
} from "./config.ts";
|
|
81
86
|
import {
|
|
82
87
|
chartFetchDefaults,
|
|
83
88
|
feedbackWriteDefaults,
|
|
@@ -109,8 +114,8 @@ import { fetchStatementData, STATEMENT_ROW_CAP } from "./statement.ts";
|
|
|
109
114
|
import { attachChatTurnTraceIo } from "./trace-io.ts";
|
|
110
115
|
import { invalidFields } from "./validation.ts";
|
|
111
116
|
|
|
112
|
-
const GENIE_MANIFEST =
|
|
113
|
-
const LAKEBASE_MANIFEST =
|
|
117
|
+
const GENIE_MANIFEST = pluginRegistry.data(genie).plugin.manifest;
|
|
118
|
+
const LAKEBASE_MANIFEST = pluginRegistry.data(lakebase).plugin.manifest;
|
|
114
119
|
|
|
115
120
|
/**
|
|
116
121
|
* Budget for draining the memory service-principal pool on shutdown. Well
|
|
@@ -278,7 +283,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
278
283
|
* already in the registry by the time this fires.
|
|
279
284
|
*/
|
|
280
285
|
private applyLakebaseAutoDefaults(): void {
|
|
281
|
-
const hasLakebase =
|
|
286
|
+
const hasLakebase = pluginRegistry.instance(this.context, lakebase) !== undefined;
|
|
282
287
|
if (!hasLakebase) return;
|
|
283
288
|
if (this.config.storage === undefined) this.config.storage = true;
|
|
284
289
|
if (this.config.memory === undefined) this.config.memory = true;
|
|
@@ -300,10 +305,10 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
300
305
|
// early cannot hold the event loop open for the rest of the window.
|
|
301
306
|
const budget = new AbortController();
|
|
302
307
|
try {
|
|
303
|
-
await Promise.race([pool.end(),
|
|
308
|
+
await Promise.race([pool.end(), asyncUtils.sleep(POOL_DRAIN_TIMEOUT_MS, budget.signal)]);
|
|
304
309
|
} catch (err) {
|
|
305
310
|
this.logger.error("error closing memory SP pool", {
|
|
306
|
-
error:
|
|
311
|
+
error: errorUtils.errorMessage(err),
|
|
307
312
|
});
|
|
308
313
|
} finally {
|
|
309
314
|
budget.abort();
|
|
@@ -411,7 +416,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
411
416
|
* Returns the underlying `CacheManager.delete`/`clear` promise.
|
|
412
417
|
*/
|
|
413
418
|
clearModelsCache: (host?: string): Promise<void> =>
|
|
414
|
-
|
|
419
|
+
modelCatalog.clearServingEndpointsCache(host),
|
|
415
420
|
};
|
|
416
421
|
}
|
|
417
422
|
|
|
@@ -517,7 +522,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
517
522
|
req: express.Request,
|
|
518
523
|
res: express.Response,
|
|
519
524
|
): Promise<void> => {
|
|
520
|
-
const requested =
|
|
525
|
+
const requested = stringUtils.firstNonEmpty(req.params.agentId);
|
|
521
526
|
if (requested !== null && !this.built?.agents[requested]) {
|
|
522
527
|
res.status(404).json({ error: this.unknownAgentMessage(requested) });
|
|
523
528
|
return;
|
|
@@ -611,8 +616,8 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
611
616
|
method: "get",
|
|
612
617
|
path: `${routes.MASTRA_ROUTES.embed}/:type/:id`,
|
|
613
618
|
handler: async (req, res) => {
|
|
614
|
-
const type =
|
|
615
|
-
const id =
|
|
619
|
+
const type = stringUtils.firstNonEmpty(req.params.type) ?? "";
|
|
620
|
+
const id = stringUtils.firstNonEmpty(req.params.id);
|
|
616
621
|
const resolve = embedResolvers[type];
|
|
617
622
|
if (!resolve) {
|
|
618
623
|
this.logger.warn("embed:unsupported", {
|
|
@@ -874,7 +879,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
874
879
|
if (Object.keys(spaces).length === 0) return { ok: true, data: [] };
|
|
875
880
|
const client = getExecutionContext().client;
|
|
876
881
|
return this.execute((executeSignal) => {
|
|
877
|
-
const combined =
|
|
882
|
+
const combined = asyncUtils.combineAbortSignals(signal, executeSignal);
|
|
878
883
|
return collectSpaceSuggestions({
|
|
879
884
|
spaces,
|
|
880
885
|
client,
|
|
@@ -931,7 +936,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
931
936
|
const limit = Math.min(options.limit ?? STATEMENT_ROW_CAP, STATEMENT_ROW_CAP);
|
|
932
937
|
return this.execute(
|
|
933
938
|
async (executeSignal) => {
|
|
934
|
-
const combined =
|
|
939
|
+
const combined = asyncUtils.combineAbortSignals(options.signal, executeSignal);
|
|
935
940
|
try {
|
|
936
941
|
const data = await fetchStatementData(client, statementId, {
|
|
937
942
|
limit,
|
|
@@ -946,7 +951,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
946
951
|
} catch (err) {
|
|
947
952
|
// The Databricks SDK throws on 404; surface as `undefined`
|
|
948
953
|
// so the route maps to a clean HTTP 404 instead of a 500.
|
|
949
|
-
if (
|
|
954
|
+
if (errorUtils.errorContext(err).notAccessible) return undefined;
|
|
950
955
|
throw err;
|
|
951
956
|
}
|
|
952
957
|
},
|
|
@@ -990,7 +995,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
990
995
|
fetchChart(chartId, {
|
|
991
996
|
userKey,
|
|
992
997
|
...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
|
|
993
|
-
signal:
|
|
998
|
+
signal: asyncUtils.combineAbortSignals(options.signal, executeSignal) ?? options.signal,
|
|
994
999
|
}),
|
|
995
1000
|
chartFetchDefaults,
|
|
996
1001
|
);
|
|
@@ -1003,10 +1008,15 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
1003
1008
|
*/
|
|
1004
1009
|
private async listModelsResult(): Promise<ExecutionResult<ServingEndpointSummary[]>> {
|
|
1005
1010
|
return this.execute(async () => {
|
|
1006
|
-
const
|
|
1011
|
+
const context = getExecutionContext();
|
|
1012
|
+
const client = context.client;
|
|
1013
|
+
const cacheIdentity = executionContextUserId(context);
|
|
1007
1014
|
const host = (await client.config.getHost()).toString();
|
|
1008
1015
|
const serving = resolveServingConfig(this.config);
|
|
1009
|
-
return
|
|
1016
|
+
return modelCatalog.listServingEndpoints(client, host, {
|
|
1017
|
+
ttlMs: serving.ttlMs,
|
|
1018
|
+
cacheIdentity,
|
|
1019
|
+
});
|
|
1010
1020
|
}, modelCatalogueDefaults);
|
|
1011
1021
|
}
|
|
1012
1022
|
|
|
@@ -1037,7 +1047,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
1037
1047
|
// `PgVector` singleton so registering N agents stays cheap. See
|
|
1038
1048
|
// `./memory.js`.
|
|
1039
1049
|
if (needsLakebase(this.config)) {
|
|
1040
|
-
const spPgConfig =
|
|
1050
|
+
const spPgConfig = pluginRegistry
|
|
1041
1051
|
.require(this.context, lakebase, this.config)
|
|
1042
1052
|
.exports()
|
|
1043
1053
|
.getPgConfig();
|
package/src/remote-skills.ts
CHANGED
|
@@ -57,10 +57,19 @@ import type { WorkspaceClient } from "@databricks/appkit";
|
|
|
57
57
|
import { appkit } from "@dbx-tools/appkit";
|
|
58
58
|
import type { WorkspaceClientLike } from "@dbx-tools/appkit";
|
|
59
59
|
import { exec } from "@dbx-tools/core";
|
|
60
|
-
import { DatabricksFileSystem,
|
|
60
|
+
import { DatabricksFileSystem, workspaceClient } from "@dbx-tools/databricks";
|
|
61
61
|
import { localFS, type LocalFileSystem } from "@dbx-tools/fs";
|
|
62
62
|
import { find } from "@dbx-tools/path";
|
|
63
|
-
import {
|
|
63
|
+
import {
|
|
64
|
+
asyncUtils,
|
|
65
|
+
errorUtils,
|
|
66
|
+
hash,
|
|
67
|
+
json,
|
|
68
|
+
log,
|
|
69
|
+
net,
|
|
70
|
+
object,
|
|
71
|
+
stringUtils,
|
|
72
|
+
} from "@dbx-tools/shared-core";
|
|
64
73
|
import type { OneOrMany } from "@dbx-tools/shared-core";
|
|
65
74
|
import type { FileSystem } from "@dbx-tools/shared-fs";
|
|
66
75
|
|
|
@@ -312,11 +321,11 @@ function isAiToolsSource(source: string): boolean {
|
|
|
312
321
|
* not what a success produces.
|
|
313
322
|
*/
|
|
314
323
|
function cachePolicy(sourceOptions: NormalizedSource): RemoteSkillCacheEntry["policy"] {
|
|
315
|
-
const skills =
|
|
324
|
+
const skills = stringUtils.parseList(sourceOptions.skills);
|
|
316
325
|
const policy = {
|
|
317
326
|
...(skills.length > 0 ? { skills: [...skills].sort() } : {}),
|
|
318
327
|
...(sourceOptions.experimental === true ? { experimental: true } : {}),
|
|
319
|
-
...(
|
|
328
|
+
...(stringUtils.trimToNull(sourceOptions.ref) ? { ref: sourceOptions.ref!.trim() } : {}),
|
|
320
329
|
};
|
|
321
330
|
return Object.keys(policy).length > 0 ? policy : undefined;
|
|
322
331
|
}
|
|
@@ -359,7 +368,7 @@ async function readMetadata(fs: FileSystem): Promise<RemoteSkillsMetadata | unde
|
|
|
359
368
|
if (!object.isRecord(parsed) || parsed.version !== METADATA_VERSION) return undefined;
|
|
360
369
|
return object.isRecord(parsed.sources) ? parsed : undefined;
|
|
361
370
|
} catch (err) {
|
|
362
|
-
logger.debug("cache:unreadable", { error:
|
|
371
|
+
logger.debug("cache:unreadable", { error: errorUtils.errorMessage(err) });
|
|
363
372
|
return undefined;
|
|
364
373
|
}
|
|
365
374
|
}
|
|
@@ -490,13 +499,13 @@ export async function provisionRemoteSkills(
|
|
|
490
499
|
} catch (err) {
|
|
491
500
|
if (failOnError) {
|
|
492
501
|
throw new Error(
|
|
493
|
-
`failed to provision remote skill source "${sourceOptions.source}": ${
|
|
494
|
-
{ cause:
|
|
502
|
+
`failed to provision remote skill source "${sourceOptions.source}": ${errorUtils.errorMessage(err)}`,
|
|
503
|
+
{ cause: errorUtils.toError(err) },
|
|
495
504
|
);
|
|
496
505
|
}
|
|
497
506
|
logger.warn("source:skipped", {
|
|
498
507
|
source: sourceOptions.source,
|
|
499
|
-
error:
|
|
508
|
+
error: errorUtils.errorMessage(err),
|
|
500
509
|
});
|
|
501
510
|
}
|
|
502
511
|
}
|
|
@@ -533,7 +542,7 @@ async function openWritableWorkspace(
|
|
|
533
542
|
client: WorkspaceClient,
|
|
534
543
|
): Promise<DatabricksFileSystem | undefined> {
|
|
535
544
|
const fs = new DatabricksFileSystem({
|
|
536
|
-
client:
|
|
545
|
+
client: workspaceClient.toLegacyWorkspaceClient(client),
|
|
537
546
|
root: basePath,
|
|
538
547
|
readOnly: false,
|
|
539
548
|
createRoot: true,
|
|
@@ -544,7 +553,7 @@ async function openWritableWorkspace(
|
|
|
544
553
|
} catch (err) {
|
|
545
554
|
logger.warn("destination:unwritable", {
|
|
546
555
|
destination: basePath,
|
|
547
|
-
error:
|
|
556
|
+
error: errorUtils.errorMessage(err),
|
|
548
557
|
});
|
|
549
558
|
return undefined;
|
|
550
559
|
}
|
|
@@ -559,7 +568,7 @@ function resolveDatabricksBasePath(
|
|
|
559
568
|
): string | undefined {
|
|
560
569
|
if (!client) return undefined;
|
|
561
570
|
if (options.databricksBasePath) return options.databricksBasePath.trim() || undefined;
|
|
562
|
-
const email =
|
|
571
|
+
const email = stringUtils.trimToNull(options.userEmail);
|
|
563
572
|
// A named user targets their personal Assistant tree (the "save a skill"
|
|
564
573
|
// target); otherwise the shared workspace Assistant tree, which the built-in
|
|
565
574
|
// Assistant-skills mount already scans.
|
|
@@ -598,13 +607,13 @@ async function stageAiTools(
|
|
|
598
607
|
options: ProvisionRemoteSkillsOptions,
|
|
599
608
|
): Promise<string> {
|
|
600
609
|
const maxBytes = resolveMaxBytes(sourceOptions, options);
|
|
601
|
-
const ref =
|
|
610
|
+
const ref = stringUtils.trimToNull(sourceOptions.ref) ?? AITOOLS_REF;
|
|
602
611
|
const manifest = json.parse(
|
|
603
612
|
(await download(aiToolsRawUrl(ref, "manifest.json"), maxBytes)).toString("utf8"),
|
|
604
613
|
undefined,
|
|
605
614
|
) as AiToolsManifest | undefined;
|
|
606
615
|
|
|
607
|
-
const wanted = new Set(
|
|
616
|
+
const wanted = new Set(stringUtils.parseList(sourceOptions.skills));
|
|
608
617
|
const selected = Object.entries(manifest?.skills ?? {}).filter(([name, entry]) =>
|
|
609
618
|
wanted.size > 0
|
|
610
619
|
? wanted.has(name)
|
|
@@ -620,11 +629,15 @@ async function stageAiTools(
|
|
|
620
629
|
path: join(target, name, ...file.split("/")),
|
|
621
630
|
})),
|
|
622
631
|
);
|
|
623
|
-
await mapConcurrent(
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
632
|
+
await asyncUtils.mapConcurrent(
|
|
633
|
+
files,
|
|
634
|
+
async (item) => {
|
|
635
|
+
const body = await download(item.url, maxBytes);
|
|
636
|
+
await mkdir(dirname(item.path), { recursive: true });
|
|
637
|
+
await writeFile(item.path, body);
|
|
638
|
+
},
|
|
639
|
+
{ concurrency: AITOOLS_CONCURRENCY },
|
|
640
|
+
);
|
|
628
641
|
|
|
629
642
|
logger.debug("aitools:staged", { ref, skills: selected.length, files: files.length });
|
|
630
643
|
return target;
|
|
@@ -635,21 +648,6 @@ function aiToolsRawUrl(ref: string, path: string): string {
|
|
|
635
648
|
return `https://raw.githubusercontent.com/${AITOOLS_REPO}/${ref}/${path}`;
|
|
636
649
|
}
|
|
637
650
|
|
|
638
|
-
/** Run `worker` over `items`, at most `limit` in flight. */
|
|
639
|
-
async function mapConcurrent<T>(
|
|
640
|
-
items: readonly T[],
|
|
641
|
-
limit: number,
|
|
642
|
-
worker: (item: T) => Promise<void>,
|
|
643
|
-
): Promise<void> {
|
|
644
|
-
let cursor = 0;
|
|
645
|
-
const runners = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
646
|
-
while (cursor < items.length) {
|
|
647
|
-
await worker(items[cursor++]!);
|
|
648
|
-
}
|
|
649
|
-
});
|
|
650
|
-
await Promise.all(runners);
|
|
651
|
-
}
|
|
652
|
-
|
|
653
651
|
/**
|
|
654
652
|
* Copy a source into `target` with the `skills` CLI when it is installed.
|
|
655
653
|
* Returns the dir holding the copied `SKILL.md` trees, or `undefined` when the
|
|
@@ -671,10 +669,10 @@ async function stageViaSkillsCli(
|
|
|
671
669
|
"--copy",
|
|
672
670
|
"-y",
|
|
673
671
|
];
|
|
674
|
-
for (const skill of
|
|
672
|
+
for (const skill of stringUtils.parseList(sourceOptions.skills)) {
|
|
675
673
|
args.push("--skill", skill);
|
|
676
674
|
}
|
|
677
|
-
if (
|
|
675
|
+
if (stringUtils.parseList(sourceOptions.skills).length === 0) args.push("--skill", "*");
|
|
678
676
|
|
|
679
677
|
const result = await exec.spawn(cli.command, args, {
|
|
680
678
|
cwd: target,
|
|
@@ -723,7 +721,7 @@ async function stageViaFetch(
|
|
|
723
721
|
);
|
|
724
722
|
}
|
|
725
723
|
const buffer = await download(url.toString(), resolveMaxBytes(sourceOptions, options));
|
|
726
|
-
const name =
|
|
724
|
+
const name = stringUtils.toSlug(deriveSkillName(url.toString())) || "remote-skill";
|
|
727
725
|
const skillDir = join(target, name);
|
|
728
726
|
await mkdir(skillDir, { recursive: true });
|
|
729
727
|
await writeFile(join(skillDir, "SKILL.md"), buffer);
|
|
@@ -743,8 +741,8 @@ async function download(url: string, maxBytes: number): Promise<Buffer> {
|
|
|
743
741
|
const response = await fetch(url, {
|
|
744
742
|
signal: AbortSignal.timeout(DEFAULT_DOWNLOAD_TIMEOUT_MS),
|
|
745
743
|
}).catch((err) => {
|
|
746
|
-
throw new Error(`download failed (${url}): ${
|
|
747
|
-
cause:
|
|
744
|
+
throw new Error(`download failed (${url}): ${errorUtils.errorMessage(err)}`, {
|
|
745
|
+
cause: errorUtils.toError(err),
|
|
748
746
|
});
|
|
749
747
|
});
|
|
750
748
|
if (!response.ok) {
|