@dbx-tools/appkit-mastra 0.9.95 → 0.9.105
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 +238 -285
- package/index.ts +4 -5
- package/lib/index.d.ts +4 -5
- package/lib/index.js +4 -5
- package/lib/src/agents.d.ts +5 -3
- package/lib/src/agents.js +70 -19
- package/lib/src/chat.d.ts +3 -1
- package/lib/src/chat.js +98 -12
- package/lib/src/config.d.ts +7 -1
- package/lib/src/config.js +6 -2
- package/lib/src/model.d.ts +3 -6
- package/lib/src/model.js +11 -94
- package/lib/src/monty-sandbox.d.ts +3 -7
- package/lib/src/monty-sandbox.js +17 -28
- package/lib/src/openapi-tool.js +3 -12
- package/lib/src/plugin.js +3 -1
- package/lib/src/server.js +1 -1
- package/lib/src/skill-paths.d.ts +1 -1
- package/lib/src/skill-paths.js +2 -2
- package/lib/src/workspaces.d.ts +53 -92
- package/lib/src/workspaces.js +319 -192
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -22
- package/src/agents.ts +69 -18
- package/src/chat.ts +103 -11
- package/src/config.ts +13 -2
- package/src/model.ts +10 -108
- package/src/monty-sandbox.ts +23 -33
- package/src/openapi-tool.ts +2 -11
- package/src/plugin.ts +2 -0
- package/src/server.ts +22 -20
- package/src/skill-paths.ts +1 -1
- package/src/workspaces.ts +485 -266
- package/lib/src/serving-sanitize.d.ts +0 -6
- package/lib/src/serving-sanitize.js +0 -7
- package/src/serving-sanitize.ts +0 -19
package/lib/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/agents.ts","../src/chart.ts","../src/chat.ts","../src/config.ts","../src/defaults.ts","../src/evaluation.ts","../src/filesystems.ts","../src/genie.ts","../src/identity.ts","../src/mcp.ts","../src/memory.ts","../src/mlflow.ts","../src/model.ts","../src/monty-sandbox.ts","../src/observability.ts","../src/openapi-tool.ts","../src/plugin.ts","../src/processors.ts","../src/remote-skills.ts","../src/rest.ts","../src/sandbox.ts","../src/server.ts","../src/serving
|
|
1
|
+
{"root":["../src/agents.ts","../src/chart.ts","../src/chat.ts","../src/config.ts","../src/defaults.ts","../src/evaluation.ts","../src/filesystems.ts","../src/genie.ts","../src/identity.ts","../src/mcp.ts","../src/memory.ts","../src/mlflow.ts","../src/model.ts","../src/monty-sandbox.ts","../src/observability.ts","../src/openapi-tool.ts","../src/plugin.ts","../src/processors.ts","../src/remote-skills.ts","../src/rest.ts","../src/sandbox.ts","../src/server.ts","../src/serving.ts","../src/skill-paths.ts","../src/statement.ts","../src/storage-schema.ts","../src/style.ts","../src/summarize.ts","../src/telemetry.ts","../src/trace-attributes.ts","../src/validation.ts","../src/workspaces.ts","../src/writer.ts","../index.ts"],"version":"5.9.3"}
|
package/package.json
CHANGED
|
@@ -34,19 +34,20 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@databricks/appkit": "0.81.0",
|
|
37
|
-
"@dbx-tools/appkit": "^0.9.
|
|
38
|
-
"@dbx-tools/core": "^0.9.
|
|
39
|
-
"@dbx-tools/databricks": "^0.9.
|
|
40
|
-
"@dbx-tools/fs": "^0.9.
|
|
41
|
-
"@dbx-tools/genie": "^0.9.
|
|
42
|
-
"@dbx-tools/model": "^0.9.
|
|
43
|
-
"@dbx-tools/
|
|
44
|
-
"@dbx-tools/
|
|
45
|
-
"@dbx-tools/
|
|
46
|
-
"@dbx-tools/shared-
|
|
47
|
-
"@dbx-tools/shared-
|
|
48
|
-
"@dbx-tools/shared-
|
|
49
|
-
"@dbx-tools/shared-
|
|
37
|
+
"@dbx-tools/appkit": "^0.9.105",
|
|
38
|
+
"@dbx-tools/core": "^0.9.105",
|
|
39
|
+
"@dbx-tools/databricks": "^0.9.105",
|
|
40
|
+
"@dbx-tools/fs": "^0.9.105",
|
|
41
|
+
"@dbx-tools/genie": "^0.9.105",
|
|
42
|
+
"@dbx-tools/model": "^0.9.105",
|
|
43
|
+
"@dbx-tools/model-protocol": "^0.9.105",
|
|
44
|
+
"@dbx-tools/path": "^0.9.105",
|
|
45
|
+
"@dbx-tools/postgres": "^0.9.105",
|
|
46
|
+
"@dbx-tools/shared-core": "^0.9.105",
|
|
47
|
+
"@dbx-tools/shared-fs": "^0.9.105",
|
|
48
|
+
"@dbx-tools/shared-genie": "^0.9.105",
|
|
49
|
+
"@dbx-tools/shared-mastra": "^0.9.105",
|
|
50
|
+
"@dbx-tools/shared-model": "^0.9.105",
|
|
50
51
|
"@mastra/ai-sdk": "1.10.5",
|
|
51
52
|
"@mastra/core": "1.71.0",
|
|
52
53
|
"@mastra/express": "1.5.15",
|
|
@@ -62,6 +63,7 @@
|
|
|
62
63
|
"@opentelemetry/sdk-trace-base": "2.8.0",
|
|
63
64
|
"@opentelemetry/sdk-trace-node": "2.8.0",
|
|
64
65
|
"@pydantic/monty": "1.0.0",
|
|
66
|
+
"ai": "^7.0.0",
|
|
65
67
|
"express": "^5.1.0",
|
|
66
68
|
"fuse.js": "^7.4.2",
|
|
67
69
|
"lru-cache": "11.5.3",
|
|
@@ -168,10 +170,6 @@
|
|
|
168
170
|
"types": "./lib/src/server.d.ts",
|
|
169
171
|
"default": "./lib/src/server.js"
|
|
170
172
|
},
|
|
171
|
-
"./serving-sanitize": {
|
|
172
|
-
"types": "./lib/src/serving-sanitize.d.ts",
|
|
173
|
-
"default": "./lib/src/serving-sanitize.js"
|
|
174
|
-
},
|
|
175
173
|
"./serving": {
|
|
176
174
|
"types": "./lib/src/serving.d.ts",
|
|
177
175
|
"default": "./lib/src/serving.js"
|
|
@@ -219,7 +217,7 @@
|
|
|
219
217
|
"./package.json": "./package.json"
|
|
220
218
|
}
|
|
221
219
|
},
|
|
222
|
-
"version": "0.9.
|
|
220
|
+
"version": "0.9.105",
|
|
223
221
|
"types": "./lib/index.d.ts",
|
|
224
222
|
"dbxToolsConfig": {
|
|
225
223
|
"tags": [
|
|
@@ -320,10 +318,6 @@
|
|
|
320
318
|
"types": "./lib/src/server.d.ts",
|
|
321
319
|
"default": "./lib/src/server.js"
|
|
322
320
|
},
|
|
323
|
-
"./serving-sanitize": {
|
|
324
|
-
"types": "./lib/src/serving-sanitize.d.ts",
|
|
325
|
-
"default": "./lib/src/serving-sanitize.js"
|
|
326
|
-
},
|
|
327
321
|
"./serving": {
|
|
328
322
|
"types": "./lib/src/serving.d.ts",
|
|
329
323
|
"default": "./lib/src/serving.js"
|
package/src/agents.ts
CHANGED
|
@@ -34,6 +34,7 @@ import type {
|
|
|
34
34
|
ToolsInput,
|
|
35
35
|
} from "@mastra/core/agent";
|
|
36
36
|
import { Agent } from "@mastra/core/agent";
|
|
37
|
+
import { createEventedAgent } from "@mastra/core/agent/durable";
|
|
37
38
|
import { SkillSearchProcessor } from "@mastra/core/processors";
|
|
38
39
|
import type { OutputProcessor } from "@mastra/core/processors";
|
|
39
40
|
import { MASTRA_RESOURCE_ID_KEY } from "@mastra/core/request-context";
|
|
@@ -51,7 +52,7 @@ import { stripStaleChartsProcessor } from "./processors.ts";
|
|
|
51
52
|
import { MASTRA_RESOLVED_MODEL_KEY } from "./serving.ts";
|
|
52
53
|
import { TYPOGRAPHY_RULE } from "./style.ts";
|
|
53
54
|
import { buildSummarizeTool } from "./summarize.ts";
|
|
54
|
-
import {
|
|
55
|
+
import { bindDatabricksWorkspaceContext, databricksWorkspace } from "./workspaces.ts";
|
|
55
56
|
|
|
56
57
|
/**
|
|
57
58
|
* Tool record accepted by every Mastra `Agent.tools` field and by the
|
|
@@ -168,8 +169,8 @@ function deriveToolId(description: string): string {
|
|
|
168
169
|
* ```
|
|
169
170
|
*
|
|
170
171
|
* Adds the package's default workspace when the definition omits one. That
|
|
171
|
-
* workspace carries Databricks skill mounts and
|
|
172
|
-
*
|
|
172
|
+
* workspace carries Databricks skill mounts and Monty command execution. An
|
|
173
|
+
* explicit workspace remains the caller's complete override.
|
|
173
174
|
*/
|
|
174
175
|
export function createAgent<
|
|
175
176
|
TRequestContext extends Record<string, unknown> | unknown = unknown,
|
|
@@ -177,7 +178,7 @@ export function createAgent<
|
|
|
177
178
|
MastraAgentDefinition<TRequestContext>,
|
|
178
179
|
>(def: TDefinition): TDefinition {
|
|
179
180
|
if (def.workspace) return { ...def };
|
|
180
|
-
const workspace =
|
|
181
|
+
const workspace = databricksWorkspace();
|
|
181
182
|
markDefaultWorkspace(workspace);
|
|
182
183
|
return { ...def, workspace };
|
|
183
184
|
}
|
|
@@ -338,7 +339,7 @@ export interface MastraAgentDefinition<
|
|
|
338
339
|
/**
|
|
339
340
|
* Mastra {@link Workspace} for this agent (filesystem, sandbox, or other
|
|
340
341
|
* providers). Assistant skills from Databricks workspace paths are wired
|
|
341
|
-
* via {@link
|
|
342
|
+
* via {@link databricksWorkspace}.
|
|
342
343
|
*/
|
|
343
344
|
workspace?: Workspace | MastraAgentWorkspaceResolver;
|
|
344
345
|
}
|
|
@@ -409,6 +410,41 @@ export const FALLBACK_AGENT_ID = "default";
|
|
|
409
410
|
*/
|
|
410
411
|
export const DEFAULT_AGENT_MAX_STEPS = 25;
|
|
411
412
|
|
|
413
|
+
const EVENTED_CONSTRUCTOR_MODEL = {
|
|
414
|
+
specificationVersion: "v3",
|
|
415
|
+
provider: "dbx-tools",
|
|
416
|
+
modelId: "deferred",
|
|
417
|
+
supportedUrls: {},
|
|
418
|
+
async doGenerate() {
|
|
419
|
+
throw new Error("Deferred evented constructor model must not execute");
|
|
420
|
+
},
|
|
421
|
+
async doStream() {
|
|
422
|
+
throw new Error("Deferred evented constructor model must not execute");
|
|
423
|
+
},
|
|
424
|
+
} as never;
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* `EventedAgent` delegates model resolution to the wrapped agent at execution
|
|
428
|
+
* time, but its constructor also probes an untyped `__model` slot and otherwise
|
|
429
|
+
* calls `getModel()` with an empty request context. Temporarily filling that
|
|
430
|
+
* probe avoids an eager service-principal lookup while preserving the wrapped
|
|
431
|
+
* agent's request-scoped model resolver for real turns.
|
|
432
|
+
*/
|
|
433
|
+
function createBackgroundAgent(agent: Agent, maxSteps: number): Agent {
|
|
434
|
+
const modelSlot = "__model";
|
|
435
|
+
const descriptor = Object.getOwnPropertyDescriptor(agent, modelSlot);
|
|
436
|
+
Object.defineProperty(agent, modelSlot, {
|
|
437
|
+
configurable: true,
|
|
438
|
+
value: EVENTED_CONSTRUCTOR_MODEL,
|
|
439
|
+
});
|
|
440
|
+
try {
|
|
441
|
+
return createEventedAgent({ agent, maxSteps }) as unknown as Agent;
|
|
442
|
+
} finally {
|
|
443
|
+
if (descriptor) Object.defineProperty(agent, modelSlot, descriptor);
|
|
444
|
+
else delete (agent as Agent & { __model?: unknown }).__model;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
412
448
|
const FALLBACK_AGENT_INSTRUCTIONS = `You are a data analyst. The user will ask questions about
|
|
413
449
|
business metrics and may share personal preferences you should remember across turns.
|
|
414
450
|
|
|
@@ -421,6 +457,13 @@ Rules:
|
|
|
421
457
|
3. If you don't have enough information to answer, ask a clarifying
|
|
422
458
|
question instead of guessing.`;
|
|
423
459
|
|
|
460
|
+
/** Tool-call guidance appended to every registered agent. */
|
|
461
|
+
export const DEFAULT_TOOL_WORKFLOW_INSTRUCTIONS = [
|
|
462
|
+
"Tool workflow:",
|
|
463
|
+
"When the active model supports it, call multiple tools in the same turn when their inputs are independent, including multiple calls to the same tool.",
|
|
464
|
+
"Keep calls sequential when a later input depends on an earlier result or when calls mutate the same resource.",
|
|
465
|
+
].join("\n");
|
|
466
|
+
|
|
424
467
|
/**
|
|
425
468
|
* Style guardrails appended to every agent's `instructions` to curb
|
|
426
469
|
* common LLM-isms (em dashes, emojis, sycophantic openers, excessive
|
|
@@ -459,34 +502,36 @@ function resolveStyleInstructions(config: MastraPluginConfig): string | null {
|
|
|
459
502
|
return DEFAULT_STYLE_INSTRUCTIONS;
|
|
460
503
|
}
|
|
461
504
|
|
|
462
|
-
/** Append
|
|
463
|
-
function
|
|
505
|
+
/** Append shared workflow and style blocks without narrowing Mastra's instruction shapes. */
|
|
506
|
+
function appendDefaultInstructions(
|
|
464
507
|
instructions: AgentInstructions,
|
|
465
508
|
style: string | null,
|
|
466
509
|
): AgentInstructions {
|
|
467
|
-
|
|
510
|
+
const suffix = style
|
|
511
|
+
? `${DEFAULT_TOOL_WORKFLOW_INSTRUCTIONS}\n\n${style}`
|
|
512
|
+
: DEFAULT_TOOL_WORKFLOW_INSTRUCTIONS;
|
|
468
513
|
if (typeof instructions === "string") {
|
|
469
|
-
return `${instructions.trimEnd()}\n\n${
|
|
514
|
+
return `${instructions.trimEnd()}\n\n${suffix}`;
|
|
470
515
|
}
|
|
471
516
|
if (Array.isArray(instructions)) {
|
|
472
517
|
if (instructions.every((instruction) => typeof instruction === "string")) {
|
|
473
|
-
return [...instructions,
|
|
518
|
+
return [...instructions, suffix] as AgentInstructions;
|
|
474
519
|
}
|
|
475
|
-
return [...instructions, { role: "system", content:
|
|
520
|
+
return [...instructions, { role: "system", content: suffix }] as AgentInstructions;
|
|
476
521
|
}
|
|
477
|
-
return [instructions, { role: "system", content:
|
|
522
|
+
return [instructions, { role: "system", content: suffix }] as AgentInstructions;
|
|
478
523
|
}
|
|
479
524
|
|
|
480
525
|
/**
|
|
481
|
-
* Compose static or request-context-aware instructions with
|
|
482
|
-
*
|
|
526
|
+
* Compose static or request-context-aware instructions with shared workflow
|
|
527
|
+
* and style guidance while preserving Mastra's native generic inference.
|
|
483
528
|
*/
|
|
484
529
|
function composeInstructions<TRequestContext extends Record<string, unknown> | unknown>(
|
|
485
530
|
agentInstructions: AgentConfig<string, ToolsInput, undefined, TRequestContext>["instructions"],
|
|
486
531
|
style: string | null,
|
|
487
532
|
): AgentConfig<string, ToolsInput, undefined, TRequestContext>["instructions"] {
|
|
488
533
|
if (typeof agentInstructions !== "function") {
|
|
489
|
-
return
|
|
534
|
+
return appendDefaultInstructions(agentInstructions, style) as AgentConfig<
|
|
490
535
|
string,
|
|
491
536
|
ToolsInput,
|
|
492
537
|
undefined,
|
|
@@ -494,7 +539,7 @@ function composeInstructions<TRequestContext extends Record<string, unknown> | u
|
|
|
494
539
|
>["instructions"];
|
|
495
540
|
}
|
|
496
541
|
return (async (args) =>
|
|
497
|
-
|
|
542
|
+
appendDefaultInstructions(await agentInstructions(args), style)) as AgentConfig<
|
|
498
543
|
string,
|
|
499
544
|
ToolsInput,
|
|
500
545
|
undefined,
|
|
@@ -580,18 +625,20 @@ export async function buildAgents(opts: {
|
|
|
580
625
|
context !== undefined) &&
|
|
581
626
|
isDefaultWorkspace(workspace))
|
|
582
627
|
) {
|
|
583
|
-
workspace =
|
|
628
|
+
workspace = databricksWorkspace({
|
|
584
629
|
extraSkillPaths,
|
|
585
630
|
...(config.workspaceTools !== undefined ? { tools: config.workspaceTools } : {}),
|
|
586
631
|
pluginContext: context,
|
|
587
632
|
sandbox: config.sandbox === true ? "databricks" : config.sandbox,
|
|
588
633
|
});
|
|
589
634
|
markDefaultWorkspace(workspace);
|
|
635
|
+
} else if (workspace) {
|
|
636
|
+
workspace = bindDatabricksWorkspaceContext(workspace, context);
|
|
590
637
|
}
|
|
591
638
|
const gated = approvalGatedToolIds(tools);
|
|
592
639
|
if (gated.length > 0) approvalGatedByAgent.push({ agentId: id, toolIds: gated });
|
|
593
640
|
const memory = memoryBuilder?.forAgent(id, def);
|
|
594
|
-
|
|
641
|
+
const agent = new Agent({
|
|
595
642
|
id,
|
|
596
643
|
name: def.name ?? id,
|
|
597
644
|
// Always carry a non-empty description: it's surfaced to MCP
|
|
@@ -615,6 +662,10 @@ export async function buildAgents(opts: {
|
|
|
615
662
|
inputProcessors: [...inputProcessors, ...workspaceSkillInputProcessors(workspace, config)],
|
|
616
663
|
outputProcessors: [toolErrorLoggingProcessor(log)],
|
|
617
664
|
});
|
|
665
|
+
agents[id] =
|
|
666
|
+
config.backgroundTurns === false
|
|
667
|
+
? agent
|
|
668
|
+
: createBackgroundAgent(agent, config.agentMaxSteps ?? DEFAULT_AGENT_MAX_STEPS);
|
|
618
669
|
// Surface the effective default model per agent so operators can
|
|
619
670
|
// see at a glance which endpoint each agent points at without
|
|
620
671
|
// having to fire a request and inspect a trace. The value is the
|
package/src/chat.ts
CHANGED
|
@@ -10,28 +10,120 @@ import {
|
|
|
10
10
|
isStaleMastraResumeError,
|
|
11
11
|
STALE_MASTRA_RESUME_STREAM_TEXT,
|
|
12
12
|
} from "@dbx-tools/shared-mastra/resume";
|
|
13
|
-
import {
|
|
13
|
+
import { handleChatStream, toAISdkStream } from "@mastra/ai-sdk";
|
|
14
|
+
import { isEventedAgent } from "@mastra/core/agent/durable";
|
|
15
|
+
import { registerApiRoute } from "@mastra/core/server";
|
|
16
|
+
import { createUIMessageStreamResponse } from "ai";
|
|
14
17
|
|
|
15
18
|
const logger = log.logger("mastra/chat");
|
|
16
19
|
|
|
17
|
-
/**
|
|
20
|
+
/** Client request cancellation is detached only for evented background agents. */
|
|
21
|
+
export function agentAbortSignal(agent: unknown, signal: AbortSignal): AbortSignal | undefined {
|
|
22
|
+
return isEventedAgent(agent) ? undefined : signal;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Register detached chat, durable observation, and explicit abort routes. */
|
|
18
26
|
export function agentChatRoutes() {
|
|
19
|
-
const
|
|
27
|
+
const chatPath = `${routes.MASTRA_ROUTES.chat}/:agentId` as `${string}:agentId`;
|
|
28
|
+
const runPath =
|
|
29
|
+
`${routes.MASTRA_ROUTES.chat}/:agentId${routes.MASTRA_ROUTES.runs}/:runId` as `${string}:agentId${string}:runId`;
|
|
30
|
+
const abortPath =
|
|
31
|
+
`${runPath}${routes.MASTRA_ROUTES.abort}` as `${string}:agentId${string}:runId${string}`;
|
|
20
32
|
return [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
registerApiRoute(chatPath, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
handler: async (context) => {
|
|
36
|
+
const params = await context.req.json();
|
|
37
|
+
const mastra = context.get("mastra");
|
|
38
|
+
const agentId = context.req.param("agentId");
|
|
39
|
+
if (!agentId) throw new Error("Agent ID is required");
|
|
40
|
+
const middlewareRequestContext = context.get("requestContext");
|
|
41
|
+
const requestContext = middlewareRequestContext || params.requestContext;
|
|
42
|
+
if (middlewareRequestContext && params.requestContext) {
|
|
43
|
+
mastra
|
|
44
|
+
.getLogger()
|
|
45
|
+
?.warn('Multiple "requestContext" sources provided. Using middleware context.');
|
|
46
|
+
}
|
|
47
|
+
const versionId = context.req.query("versionId");
|
|
48
|
+
const rawStatus = context.req.query("status");
|
|
49
|
+
if (versionId && rawStatus) {
|
|
50
|
+
throw new Error('Query parameters "versionId" and "status" are mutually exclusive');
|
|
51
|
+
}
|
|
52
|
+
if (rawStatus && rawStatus !== "draft" && rawStatus !== "published") {
|
|
53
|
+
throw new Error('Query parameter "status" must be "draft" or "published"');
|
|
54
|
+
}
|
|
55
|
+
const status = rawStatus as "draft" | "published" | undefined;
|
|
56
|
+
const abortSignal = agentAbortSignal(mastra.getAgentById(agentId), context.req.raw.signal);
|
|
57
|
+
const stream = await handleChatStream({
|
|
58
|
+
mastra,
|
|
59
|
+
agentId,
|
|
60
|
+
...(versionId
|
|
61
|
+
? { agentVersion: { versionId } }
|
|
62
|
+
: status
|
|
63
|
+
? { agentVersion: { status } }
|
|
64
|
+
: {}),
|
|
65
|
+
params: {
|
|
66
|
+
...params,
|
|
67
|
+
requestContext,
|
|
68
|
+
...(abortSignal ? { abortSignal } : {}),
|
|
69
|
+
},
|
|
70
|
+
version: "v7",
|
|
71
|
+
sendReasoning: true,
|
|
72
|
+
sendSources: true,
|
|
73
|
+
onError: serializeChatStreamError,
|
|
74
|
+
});
|
|
75
|
+
return createUIMessageStreamResponse({ stream });
|
|
76
|
+
},
|
|
77
|
+
}),
|
|
78
|
+
registerApiRoute(runPath, {
|
|
79
|
+
method: "GET",
|
|
80
|
+
handler: async (context) => {
|
|
81
|
+
const mastra = context.get("mastra");
|
|
82
|
+
const agentId = context.req.param("agentId");
|
|
83
|
+
const runId = context.req.param("runId");
|
|
84
|
+
if (!agentId || !runId) throw new Error("Agent ID and run ID are required");
|
|
85
|
+
const agent = mastra.getAgentById(agentId);
|
|
86
|
+
if (!isEventedAgent(agent)) {
|
|
87
|
+
return Response.json(
|
|
88
|
+
{ error: `Agent ${agentId} does not support background turns` },
|
|
89
|
+
{ status: 409 },
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
const observed = await agent.observe(runId);
|
|
93
|
+
const stream = toAISdkStream(observed.output, {
|
|
94
|
+
from: "agent",
|
|
95
|
+
version: "v7",
|
|
96
|
+
sendReasoning: true,
|
|
97
|
+
sendSources: true,
|
|
98
|
+
onError: serializeChatStreamError,
|
|
99
|
+
});
|
|
100
|
+
return createUIMessageStreamResponse({ stream });
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
registerApiRoute(abortPath, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
handler: async (context) => {
|
|
106
|
+
const mastra = context.get("mastra");
|
|
107
|
+
const agentId = context.req.param("agentId");
|
|
108
|
+
const runId = context.req.param("runId");
|
|
109
|
+
if (!agentId || !runId) throw new Error("Agent ID and run ID are required");
|
|
110
|
+
const agent = mastra.getAgentById(agentId);
|
|
111
|
+
if (!isEventedAgent(agent)) {
|
|
112
|
+
return Response.json(
|
|
113
|
+
{ error: `Agent ${agentId} does not support background turns` },
|
|
114
|
+
{ status: 409 },
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return Response.json({ aborted: agent.abortRunStream(runId) });
|
|
118
|
+
},
|
|
26
119
|
}),
|
|
27
120
|
];
|
|
28
121
|
}
|
|
29
122
|
|
|
30
123
|
/**
|
|
31
124
|
* Keep a late or duplicate resume from failing the UI stream. Mastra's
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* surfaces on v5.
|
|
125
|
+
* resume path can surface a workflow error after another client already
|
|
126
|
+
* resolved the same approval.
|
|
35
127
|
*/
|
|
36
128
|
function serializeChatStreamError(error: unknown): string {
|
|
37
129
|
if (isStaleMastraResumeError(error)) {
|
package/src/config.ts
CHANGED
|
@@ -199,7 +199,13 @@ export interface MastraMcpConfig {
|
|
|
199
199
|
|
|
200
200
|
/** Configuration accepted by the Mastra AppKit plugin. */
|
|
201
201
|
export interface MastraPluginConfig extends BasePluginConfig {
|
|
202
|
-
/**
|
|
202
|
+
/**
|
|
203
|
+
* Run turns through Mastra's evented durable agent so execution continues
|
|
204
|
+
* after a browser disconnect and can be observed again by run id. Defaults
|
|
205
|
+
* to `true`.
|
|
206
|
+
*/
|
|
207
|
+
backgroundTurns?: boolean;
|
|
208
|
+
/** AI SDK provider-options namespace. Defaults to `"openai"`; no env fallback. */
|
|
203
209
|
providerId?: string;
|
|
204
210
|
/**
|
|
205
211
|
* PostgresStore for Mastra threads/messages. `true` reuses the
|
|
@@ -641,9 +647,14 @@ export interface MastraPluginConfig extends BasePluginConfig {
|
|
|
641
647
|
export const MASTRA_CONFIG_SCHEMA: ConfigSchema = {
|
|
642
648
|
type: "object",
|
|
643
649
|
properties: {
|
|
650
|
+
backgroundTurns: {
|
|
651
|
+
type: "boolean",
|
|
652
|
+
description:
|
|
653
|
+
"Run turns through Mastra evented durable execution so they continue after browser disconnects and remain reconnectable by run id. Defaults to true.",
|
|
654
|
+
},
|
|
644
655
|
providerId: {
|
|
645
656
|
type: "string",
|
|
646
|
-
description: '
|
|
657
|
+
description: 'AI SDK provider-options namespace. Defaults to "openai".',
|
|
647
658
|
},
|
|
648
659
|
remoteSkills: {
|
|
649
660
|
type: ["string", "array", "object"],
|
package/src/model.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Each agent step calls {@link buildModel} with the active
|
|
5
5
|
* `RequestContext`. The user stamped by `MastraServer` carries an
|
|
6
6
|
* AppKit `WorkspaceClient`; we ask it for the workspace host and a
|
|
7
|
-
* fresh bearer header, then
|
|
8
|
-
*
|
|
7
|
+
* fresh bearer header, then construct a request-scoped Vercel AI SDK provider
|
|
8
|
+
* for the endpoint's native Databricks serving protocol.
|
|
9
9
|
*
|
|
10
10
|
* This module only adds the Mastra-specific glue. The actual model
|
|
11
11
|
* selection - listing the workspace catalogue and resolving an
|
|
@@ -16,26 +16,21 @@
|
|
|
16
16
|
* per-request override under {@link MASTRA_MODEL_OVERRIDE_KEY}, the
|
|
17
17
|
* agent / plugin `modelId`, or `DATABRICKS_SERVING_ENDPOINT_NAME`),
|
|
18
18
|
* pass the plugin's fuzzy / class / fallback knobs through, and wrap
|
|
19
|
-
* the resolved id in the
|
|
20
|
-
* expects. Catalogue fetches fail loud: network / auth errors
|
|
19
|
+
* the resolved id in the ProviderV4 model Mastra accepts. Catalogue fetches fail loud: network / auth errors
|
|
21
20
|
* propagate so callers see the real SDK message.
|
|
22
21
|
*
|
|
23
22
|
* @module
|
|
24
23
|
*/
|
|
25
24
|
|
|
26
25
|
import { getExecutionContext } from "@databricks/appkit";
|
|
27
|
-
import { classes,
|
|
28
|
-
import {
|
|
26
|
+
import { classes, policy, resolve } from "@dbx-tools/model";
|
|
27
|
+
import { createDatabricksLanguageModel } from "@dbx-tools/model-protocol/provider";
|
|
28
|
+
import { log } from "@dbx-tools/shared-core";
|
|
29
29
|
import { model, type ServingEndpointSummary } from "@dbx-tools/shared-model";
|
|
30
30
|
import type { MastraModelConfig } from "@mastra/core/llm";
|
|
31
31
|
import type { RequestContext } from "@mastra/core/request-context";
|
|
32
32
|
|
|
33
33
|
import { MASTRA_USER_KEY, type MastraPluginConfig, type User } from "./config.ts";
|
|
34
|
-
import {
|
|
35
|
-
rewriteServingRequest,
|
|
36
|
-
rewriteServingResponseBody,
|
|
37
|
-
rewriteServingResponseStream,
|
|
38
|
-
} from "./serving-sanitize.ts";
|
|
39
34
|
import {
|
|
40
35
|
MASTRA_MODEL_OVERRIDE_KEY,
|
|
41
36
|
MASTRA_RESOLVED_MODEL_KEY,
|
|
@@ -133,7 +128,6 @@ export async function buildModel(
|
|
|
133
128
|
requestContext: RequestContext,
|
|
134
129
|
overrides: BuildModelOverrides = {},
|
|
135
130
|
): Promise<MastraModelConfig> {
|
|
136
|
-
void setupFetchInterceptor();
|
|
137
131
|
// The chat path stamps the AppKit user on the request context via
|
|
138
132
|
// `MastraServer`. The MCP transport routes don't thread that context
|
|
139
133
|
// into tool execution, so fall back to the ambient execution context
|
|
@@ -145,11 +139,6 @@ export async function buildModel(
|
|
|
145
139
|
const host = (await clientConfig.getHost()).toString();
|
|
146
140
|
const headers = new Headers();
|
|
147
141
|
await clientConfig.authenticate(headers);
|
|
148
|
-
// The OpenAI Node SDK appends paths like `/chat/completions` to whatever
|
|
149
|
-
// URL we hand it. Drop the trailing slash so the resulting URL stays
|
|
150
|
-
// well-formed (`/serving-endpoints/chat/completions`).
|
|
151
|
-
const url = new URL("/serving-endpoints", host).toString().replace(/\/$/, "");
|
|
152
|
-
|
|
153
142
|
const logger = log.logger(config);
|
|
154
143
|
const serving = resolveServingConfig(config);
|
|
155
144
|
const override = serving.allowOverride
|
|
@@ -174,98 +163,11 @@ export async function buildModel(
|
|
|
174
163
|
requestContext.set(MASTRA_RESOLVED_MODEL_KEY, modelId);
|
|
175
164
|
recordActiveTraceModel(modelId);
|
|
176
165
|
|
|
177
|
-
return {
|
|
178
|
-
providerId: config.providerId ?? "databricks",
|
|
166
|
+
return createDatabricksLanguageModel({
|
|
179
167
|
modelId,
|
|
180
|
-
|
|
168
|
+
protocol: servingApi(modelId),
|
|
169
|
+
host,
|
|
181
170
|
headers: Object.fromEntries(headers.entries()),
|
|
182
|
-
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/** Chat Completions route whose provider-specific wire shapes are normalized here. */
|
|
187
|
-
const CHAT_COMPLETIONS_PATH = `/${invoke.CHAT_COMPLETIONS_PATH}`;
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Install a single shared `globalThis.fetch` wrapper for Chat Completions
|
|
191
|
-
* requests. The wrapper does three things:
|
|
192
|
-
*
|
|
193
|
-
* 1. Rewrites outgoing JSON from either `init.body` or a `Request` to repair
|
|
194
|
-
* provider-specific request constraints (see {@link rewriteServingRequest}
|
|
195
|
-
* in `./serving-sanitize.js`).
|
|
196
|
-
* 2. At `LOG_LEVEL=debug`, dumps the (post-sanitize) JSON body so
|
|
197
|
-
* 4xx debugging doesn't have to fight AI SDK's `[Array]`
|
|
198
|
-
* formatter.
|
|
199
|
-
* 3. Repairs buffered and streaming responses where a provider returns
|
|
200
|
-
* `choices[].message.content` or `choices[].delta.content` as a parts
|
|
201
|
-
* array that the AI SDK's OpenAI schema rejects.
|
|
202
|
-
*
|
|
203
|
-
* Safe to call from any hot path: {@link functionUtils.memoize} ensures
|
|
204
|
-
* the wrapper is installed at most once per process, so subsequent
|
|
205
|
-
* calls are a no-op even when {@link buildModel} fires on every agent
|
|
206
|
-
* step.
|
|
207
|
-
*/
|
|
208
|
-
const setupFetchInterceptor = functionUtils.memoize((): void => {
|
|
209
|
-
globalThis.fetch = createServingFetchInterceptor(globalThis.fetch.bind(globalThis));
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
/** Build the serving fetch wrapper; exported for transport-level regression tests. */
|
|
213
|
-
export function createServingFetchInterceptor(original: typeof fetch): typeof fetch {
|
|
214
|
-
const logger = log.logger("mastra/llm");
|
|
215
|
-
return (async (input, init) => {
|
|
216
|
-
const url = net.urlBuilder(input);
|
|
217
|
-
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
218
|
-
if (!url || url.pathname !== CHAT_COMPLETIONS_PATH || method.toUpperCase() !== "POST") {
|
|
219
|
-
return original(input, init);
|
|
220
|
-
}
|
|
221
|
-
const rewritten = await rewriteServingRequest(input, init);
|
|
222
|
-
const parsed = json.parse<unknown>(rewritten.body);
|
|
223
|
-
logger.debug(
|
|
224
|
-
"POST",
|
|
225
|
-
parsed === undefined
|
|
226
|
-
? { url: url.toString(), bodyType: "non-JSON" }
|
|
227
|
-
: { url: url.toString(), body: parsed },
|
|
228
|
-
);
|
|
229
|
-
const response = await original(rewritten.input, rewritten.init);
|
|
230
|
-
return repairServingResponse(response);
|
|
231
|
-
}) as typeof globalThis.fetch;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Rewrite a serving response whose body needs repair, leaving unsupported
|
|
236
|
-
* content types byte-identical.
|
|
237
|
-
*
|
|
238
|
-
* SSE remains a live stream: a TransformStream repairs complete `data:` lines
|
|
239
|
-
* as bytes arrive and preserves partial lines across network chunks. JSON is
|
|
240
|
-
* buffered through the existing response-body repair. A non-JSON/non-SSE body
|
|
241
|
-
* is returned untouched so the caller still sees the original response.
|
|
242
|
-
*/
|
|
243
|
-
async function repairServingResponse(response: Response): Promise<Response> {
|
|
244
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
245
|
-
if (contentType.includes("text/event-stream") && response.body) {
|
|
246
|
-
return rebuildServingResponse(response, rewriteServingResponseStream(response.body));
|
|
247
|
-
}
|
|
248
|
-
if (!contentType.includes("application/json")) return response;
|
|
249
|
-
|
|
250
|
-
const body = await response.clone().text();
|
|
251
|
-
const rewritten = rewriteServingResponseBody(body);
|
|
252
|
-
if (rewritten === body) return response;
|
|
253
|
-
return rebuildServingResponse(response, rewritten);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/** Rebuild a transformed response without stale byte-length/encoding headers. */
|
|
257
|
-
function rebuildServingResponse(
|
|
258
|
-
response: Response,
|
|
259
|
-
body: string | ReadableStream<Uint8Array>,
|
|
260
|
-
): Response {
|
|
261
|
-
// `content-length` / `content-encoding` describe the ORIGINAL bytes, so they
|
|
262
|
-
// are dropped: the rewritten body is a different length and already decoded.
|
|
263
|
-
const headers = new Headers(response.headers);
|
|
264
|
-
headers.delete("content-length");
|
|
265
|
-
headers.delete("content-encoding");
|
|
266
|
-
return new Response(body, {
|
|
267
|
-
status: response.status,
|
|
268
|
-
statusText: response.statusText,
|
|
269
|
-
headers,
|
|
171
|
+
providerName: config.providerId ?? "openai",
|
|
270
172
|
});
|
|
271
173
|
}
|