@dbx-tools/appkit-mastra 0.1.42 → 0.1.48
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 +115 -455
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/src/chart.js +2 -2
- package/dist/src/config.d.ts +7 -5
- package/dist/src/model.d.ts +11 -11
- package/dist/src/model.js +14 -14
- package/dist/src/serving.d.ts +1 -1
- package/dist/src/serving.js +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/index.ts +0 -1
- package/package.json +6 -11
- package/src/chart.ts +2 -2
- package/src/config.ts +7 -5
- package/src/model.ts +23 -23
- package/src/serving.ts +1 -1
- package/dist/src/tools/email.d.ts +0 -83
- package/dist/src/tools/email.js +0 -121
- package/src/tools/email.ts +0 -158
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
"name": "@dbx-tools/appkit-mastra",
|
|
12
|
-
"version": "0.1.
|
|
12
|
+
"version": "0.1.48",
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@databricks/sdk-experimental": "^0.17",
|
|
15
|
-
"@dbx-tools/appkit-mastra-shared": "0.1.
|
|
16
|
-
"@dbx-tools/genie": "0.1.
|
|
17
|
-
"@dbx-tools/genie-shared": "0.1.
|
|
18
|
-
"@dbx-tools/model": "0.1.
|
|
19
|
-
"@dbx-tools/shared": "0.1.
|
|
15
|
+
"@dbx-tools/appkit-mastra-shared": "0.1.48",
|
|
16
|
+
"@dbx-tools/genie": "0.1.48",
|
|
17
|
+
"@dbx-tools/genie-shared": "0.1.48",
|
|
18
|
+
"@dbx-tools/model": "0.1.48",
|
|
19
|
+
"@dbx-tools/shared": "0.1.48",
|
|
20
20
|
"@mastra/ai-sdk": "^1",
|
|
21
21
|
"@mastra/core": "^1",
|
|
22
22
|
"@mastra/express": "^1",
|
|
@@ -28,11 +28,6 @@
|
|
|
28
28
|
"pg": "^8.20.0",
|
|
29
29
|
"zod": "^4.3.6"
|
|
30
30
|
},
|
|
31
|
-
"devDependencies": {
|
|
32
|
-
"@types/express": "^5",
|
|
33
|
-
"@types/pg": "^8",
|
|
34
|
-
"express": "^5"
|
|
35
|
-
},
|
|
36
31
|
"module": "index.ts",
|
|
37
32
|
"peerDependencies": {
|
|
38
33
|
"@databricks/appkit": "^0.41",
|
package/src/chart.ts
CHANGED
|
@@ -39,7 +39,7 @@ import { createTool } from "@mastra/core/tools";
|
|
|
39
39
|
import { z } from "zod";
|
|
40
40
|
|
|
41
41
|
import type { MastraPluginConfig } from "./config.js";
|
|
42
|
-
import { buildModel,
|
|
42
|
+
import { buildModel, ModelClass } from "./model.js";
|
|
43
43
|
|
|
44
44
|
const log = logUtils.logger("mastra/chart");
|
|
45
45
|
|
|
@@ -330,7 +330,7 @@ function getPlannerAgent(config: MastraPluginConfig): Agent {
|
|
|
330
330
|
description: "Picks chart type and axis encodings for a dataset.",
|
|
331
331
|
instructions: CHART_PLANNER_INSTRUCTIONS,
|
|
332
332
|
model: ({ requestContext }) =>
|
|
333
|
-
buildModel(config, requestContext, {
|
|
333
|
+
buildModel(config, requestContext, { modelClass: ModelClass.ChatFast }),
|
|
334
334
|
});
|
|
335
335
|
plannerAgents.set(config, agent);
|
|
336
336
|
}
|
package/src/config.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* `memory.ts` can import them without creating a cycle.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { BasePluginConfig
|
|
8
|
+
import type { BasePluginConfig } from "@databricks/appkit";
|
|
9
|
+
import type { appkitUtils } from "@dbx-tools/shared";
|
|
9
10
|
import type { AgentConfig } from "@mastra/core/agent";
|
|
10
11
|
import {
|
|
11
12
|
MASTRA_RESOURCE_ID_KEY,
|
|
@@ -71,7 +72,7 @@ export const TRACE_REQUEST_CONTEXT_KEYS: readonly string[] = [
|
|
|
71
72
|
/** AppKit execution context plus the canonical user id. */
|
|
72
73
|
export interface User {
|
|
73
74
|
id: string;
|
|
74
|
-
executionContext:
|
|
75
|
+
executionContext: appkitUtils.ExecutionContextLike;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
/** PgVector config with an optional Mastra store id. */
|
|
@@ -207,9 +208,10 @@ export interface MastraPluginConfig extends BasePluginConfig {
|
|
|
207
208
|
*
|
|
208
209
|
* When unset, resolution is driven by the live Foundation Model API
|
|
209
210
|
* `quality` / `speed` / `cost` scores: endpoints are classified into
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* list as the floor when the catalogue can't be
|
|
211
|
+
* chat classes (`classifyEndpoints`) and walked best-first
|
|
212
|
+
* (ChatThinking -> ChatBalanced -> ChatFast), with the small built-in
|
|
213
|
+
* `FALLBACK_MODEL_IDS` list as the floor when the catalogue can't be
|
|
214
|
+
* read. Set this to
|
|
213
215
|
* pin a regulated workspace to an approved subset, or to put custom
|
|
214
216
|
* endpoints in front of the auto-classified catalogue.
|
|
215
217
|
*/
|
package/src/model.ts
CHANGED
|
@@ -9,19 +9,19 @@
|
|
|
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
|
|
12
|
-
* explicit name /
|
|
12
|
+
* explicit name / class / fallback chain to a real endpoint id - lives
|
|
13
13
|
* in `@dbx-tools/model` ({@link selectModel}) so non-Mastra consumers
|
|
14
14
|
* (e.g. a job that just needs a model name) can reuse it. Here we
|
|
15
15
|
* assemble the explicit ask from Mastra's request context (the
|
|
16
16
|
* per-request override under {@link MASTRA_MODEL_OVERRIDE_KEY}, the
|
|
17
17
|
* agent / plugin `modelId`, or `DATABRICKS_SERVING_ENDPOINT_NAME`),
|
|
18
|
-
* pass the plugin's fuzzy /
|
|
18
|
+
* pass the plugin's fuzzy / class / fallback knobs through, and wrap
|
|
19
19
|
* the resolved id in the OpenAI-compatible provider config Mastra
|
|
20
20
|
* expects. Catalogue fetches fail loud: network / auth errors
|
|
21
21
|
* propagate so callers see the real SDK message.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import { type
|
|
24
|
+
import { type ModelClass, parseModelClass, selectModel } from "@dbx-tools/model";
|
|
25
25
|
import { commonUtils, logUtils, netUtils, stringUtils } from "@dbx-tools/shared";
|
|
26
26
|
import type { MastraModelConfig } from "@mastra/core/llm";
|
|
27
27
|
import type { RequestContext } from "@mastra/core/request-context";
|
|
@@ -32,9 +32,9 @@ import { MASTRA_MODEL_OVERRIDE_KEY, resolveServingConfig } from "./serving.js";
|
|
|
32
32
|
export {
|
|
33
33
|
classifyEndpoints,
|
|
34
34
|
FALLBACK_MODEL_IDS,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
ModelClass,
|
|
36
|
+
modelForClass,
|
|
37
|
+
modelsForClass,
|
|
38
38
|
} from "@dbx-tools/model";
|
|
39
39
|
|
|
40
40
|
/** Optional overrides accepted by {@link buildModel}. */
|
|
@@ -42,18 +42,18 @@ export interface BuildModelOverrides {
|
|
|
42
42
|
/**
|
|
43
43
|
* Static model id from the agent / plugin config (string sugar on
|
|
44
44
|
* `def.model` or `config.defaultModel`). Loses to the per-request
|
|
45
|
-
* override but wins over env /
|
|
45
|
+
* override but wins over env / class / fallback.
|
|
46
46
|
*/
|
|
47
47
|
modelId?: string;
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
50
|
-
* Used by internal agents (e.g. the chart planner asks for
|
|
51
|
-
* {@link
|
|
49
|
+
* Chat capability class to resolve when no explicit model id is
|
|
50
|
+
* supplied. Used by internal agents (e.g. the chart planner asks for
|
|
51
|
+
* {@link ModelClass.ChatFast}) to express intent without pinning an
|
|
52
52
|
* endpoint name; the live catalogue is classified and the top
|
|
53
|
-
* available model in the
|
|
54
|
-
*
|
|
53
|
+
* available model in the class is chosen, falling back to the
|
|
54
|
+
* class's static list when the workspace has none.
|
|
55
55
|
*/
|
|
56
|
-
|
|
56
|
+
modelClass?: ModelClass;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
|
@@ -85,23 +85,23 @@ export async function buildModel(
|
|
|
85
85
|
: undefined;
|
|
86
86
|
|
|
87
87
|
// The override / agent default / env value can be either a concrete
|
|
88
|
-
// endpoint name or a
|
|
89
|
-
// "fast"). A
|
|
90
|
-
// pick the best model in that band); anything
|
|
91
|
-
// name fuzzy-matched against the catalogue. An
|
|
92
|
-
// `overrides.
|
|
93
|
-
// was requested.
|
|
88
|
+
// endpoint name or a model class slug ("chat-thinking" /
|
|
89
|
+
// "chat-balanced" / "chat-fast"). A class slug becomes a class intent
|
|
90
|
+
// (let the live catalogue pick the best model in that band); anything
|
|
91
|
+
// else is an explicit name fuzzy-matched against the catalogue. An
|
|
92
|
+
// internal `overrides.modelClass` (e.g. the chart planner) is the
|
|
93
|
+
// floor when nothing was requested.
|
|
94
94
|
const requested =
|
|
95
95
|
override ?? overrides.modelId ?? process.env.DATABRICKS_SERVING_ENDPOINT_NAME;
|
|
96
|
-
const
|
|
97
|
-
const explicit =
|
|
98
|
-
const
|
|
96
|
+
const requestedClass = requested !== undefined ? parseModelClass(requested) : null;
|
|
97
|
+
const explicit = requestedClass === null ? requested : undefined;
|
|
98
|
+
const modelClass = requestedClass ?? overrides.modelClass;
|
|
99
99
|
|
|
100
100
|
const { modelId, source } = await selectModel(user.executionContext.client, host, {
|
|
101
101
|
...(explicit !== undefined ? { explicit } : {}),
|
|
102
102
|
fuzzy: serving.fuzzy,
|
|
103
103
|
threshold: serving.threshold,
|
|
104
|
-
...(
|
|
104
|
+
...(modelClass !== undefined ? { modelClass } : {}),
|
|
105
105
|
fallbacks: serving.fallbacks,
|
|
106
106
|
ttlMs: serving.ttlMs,
|
|
107
107
|
});
|
package/src/serving.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Mastra-specific glue over the generic `@dbx-tools/model` toolkit.
|
|
3
3
|
*
|
|
4
4
|
* The live `/serving-endpoints` catalogue access, fuzzy name
|
|
5
|
-
* resolution, and
|
|
5
|
+
* resolution, and class/fallback selection all live in
|
|
6
6
|
* `@dbx-tools/model`; this module only adds what is specific to the
|
|
7
7
|
* Mastra plugin: pulling a per-request model override off an HTTP
|
|
8
8
|
* request (header / query / body) and projecting the plugin config
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mastra tool: `send_email`. Gated behind {@link requireApproval}
|
|
3
|
-
* so the model can call it freely but execution is paused until a
|
|
4
|
-
* human approves via the chat UI.
|
|
5
|
-
*
|
|
6
|
-
* The execute body is a stub - it logs the would-be email to the
|
|
7
|
-
* server console (via `logUtils.logger`) and returns success. Swap
|
|
8
|
-
* in a real SMTP / SES / Resend / Workspace Mail call later by
|
|
9
|
-
* editing the `execute` body; the tool surface and approval gate
|
|
10
|
-
* stay the same.
|
|
11
|
-
*
|
|
12
|
-
* Approval flow (Mastra + AI SDK V5):
|
|
13
|
-
*
|
|
14
|
-
* 1. Model calls the tool with `{ to, subject, body, ... }`.
|
|
15
|
-
* 2. Mastra evaluates `requireApproval` (here always `true`),
|
|
16
|
-
* pauses the agent loop, and emits a `tool-call-approval`
|
|
17
|
-
* chunk on the response stream.
|
|
18
|
-
* 3. The chat client renders an approve/deny prompt against the
|
|
19
|
-
* `state: 'approval-requested'` tool part. On approve, it sends
|
|
20
|
-
* a `MastraToolApproval` response back; on deny, the tool call
|
|
21
|
-
* is rejected and the model sees an error.
|
|
22
|
-
* 4. On approve, this `execute` runs and logs the email.
|
|
23
|
-
*
|
|
24
|
-
* The tool is intentionally NOT auto-installed on every agent -
|
|
25
|
-
* email is domain-specific, not infrastructure. Spread it into the
|
|
26
|
-
* specific agents that should be able to draft emails.
|
|
27
|
-
*/
|
|
28
|
-
import { z } from "zod";
|
|
29
|
-
declare const emailInputSchema: z.ZodObject<{
|
|
30
|
-
to: z.ZodString;
|
|
31
|
-
subject: z.ZodString;
|
|
32
|
-
body: z.ZodString;
|
|
33
|
-
cc: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
34
|
-
bcc: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
35
|
-
}, z.core.$strip>;
|
|
36
|
-
/** Options accepted by {@link buildEmailTool}. */
|
|
37
|
-
export interface BuildEmailToolOptions {
|
|
38
|
-
/**
|
|
39
|
-
* Override the tool id. Defaults to `"send_email"`. Useful if a
|
|
40
|
-
* caller wants `send_internal_email` / `send_external_email`
|
|
41
|
-
* variants.
|
|
42
|
-
*/
|
|
43
|
-
id?: string;
|
|
44
|
-
/**
|
|
45
|
-
* Replace the default execute body with a real provider call.
|
|
46
|
-
* Receives the validated input and must return `{sent, recipient}`.
|
|
47
|
-
* The console-log default is meant for demos / dev; production
|
|
48
|
-
* deployments should wire SMTP / SES / Resend / Workspace Mail
|
|
49
|
-
* here.
|
|
50
|
-
*/
|
|
51
|
-
send?: (input: z.infer<typeof emailInputSchema>) => Promise<void> | void;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Build the `send_email` tool. Approval-gated by default; the
|
|
55
|
-
* execute body either calls the supplied {@link send} hook or
|
|
56
|
-
* logs the email to the server console as a demo stub.
|
|
57
|
-
*
|
|
58
|
-
* @example
|
|
59
|
-
* ```ts
|
|
60
|
-
* import { buildEmailTool, createAgent, mastra } from "@dbx-tools/appkit-mastra";
|
|
61
|
-
*
|
|
62
|
-
* const support = createAgent({
|
|
63
|
-
* instructions: "...",
|
|
64
|
-
* tools(plugins) {
|
|
65
|
-
* return {
|
|
66
|
-
* ...(plugins.genie?.toolkit() ?? {}),
|
|
67
|
-
* send_email: buildEmailTool(),
|
|
68
|
-
* };
|
|
69
|
-
* },
|
|
70
|
-
* });
|
|
71
|
-
* ```
|
|
72
|
-
*/
|
|
73
|
-
export declare function buildEmailTool(opts?: BuildEmailToolOptions): import("@mastra/core/tools").Tool<{
|
|
74
|
-
to: string;
|
|
75
|
-
subject: string;
|
|
76
|
-
body: string;
|
|
77
|
-
cc?: string[] | undefined;
|
|
78
|
-
bcc?: string[] | undefined;
|
|
79
|
-
}, {
|
|
80
|
-
sent: boolean;
|
|
81
|
-
recipient: string;
|
|
82
|
-
}, unknown, unknown, import("@mastra/core/tools").ToolExecutionContext<unknown, unknown, unknown>, string, unknown>;
|
|
83
|
-
export {};
|
package/dist/src/tools/email.js
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mastra tool: `send_email`. Gated behind {@link requireApproval}
|
|
3
|
-
* so the model can call it freely but execution is paused until a
|
|
4
|
-
* human approves via the chat UI.
|
|
5
|
-
*
|
|
6
|
-
* The execute body is a stub - it logs the would-be email to the
|
|
7
|
-
* server console (via `logUtils.logger`) and returns success. Swap
|
|
8
|
-
* in a real SMTP / SES / Resend / Workspace Mail call later by
|
|
9
|
-
* editing the `execute` body; the tool surface and approval gate
|
|
10
|
-
* stay the same.
|
|
11
|
-
*
|
|
12
|
-
* Approval flow (Mastra + AI SDK V5):
|
|
13
|
-
*
|
|
14
|
-
* 1. Model calls the tool with `{ to, subject, body, ... }`.
|
|
15
|
-
* 2. Mastra evaluates `requireApproval` (here always `true`),
|
|
16
|
-
* pauses the agent loop, and emits a `tool-call-approval`
|
|
17
|
-
* chunk on the response stream.
|
|
18
|
-
* 3. The chat client renders an approve/deny prompt against the
|
|
19
|
-
* `state: 'approval-requested'` tool part. On approve, it sends
|
|
20
|
-
* a `MastraToolApproval` response back; on deny, the tool call
|
|
21
|
-
* is rejected and the model sees an error.
|
|
22
|
-
* 4. On approve, this `execute` runs and logs the email.
|
|
23
|
-
*
|
|
24
|
-
* The tool is intentionally NOT auto-installed on every agent -
|
|
25
|
-
* email is domain-specific, not infrastructure. Spread it into the
|
|
26
|
-
* specific agents that should be able to draft emails.
|
|
27
|
-
*/
|
|
28
|
-
import { logUtils, stringUtils } from "@dbx-tools/shared";
|
|
29
|
-
import { createTool } from "@mastra/core/tools";
|
|
30
|
-
import { z } from "zod";
|
|
31
|
-
const log = logUtils.logger("mastra/tool/send-email");
|
|
32
|
-
const emailInputSchema = z.object({
|
|
33
|
-
to: z.string().describe(stringUtils.toDescription(`
|
|
34
|
-
Single recipient email address (e.g. "alice@example.com"). For
|
|
35
|
-
multiple recipients, comma-separate them yourself.
|
|
36
|
-
`)),
|
|
37
|
-
subject: z.string().describe(stringUtils.toDescription(`
|
|
38
|
-
Subject line.
|
|
39
|
-
`)),
|
|
40
|
-
body: z.string().describe(stringUtils.toDescription(`
|
|
41
|
-
Email body. Plain text or markdown; the renderer downstream decides
|
|
42
|
-
which to honour. Be specific - the recipient may not have any
|
|
43
|
-
context the model has from prior chat turns.
|
|
44
|
-
`)),
|
|
45
|
-
cc: z
|
|
46
|
-
.array(z.string())
|
|
47
|
-
.optional()
|
|
48
|
-
.describe(stringUtils.toDescription(`
|
|
49
|
-
Optional CC recipients.
|
|
50
|
-
`)),
|
|
51
|
-
bcc: z
|
|
52
|
-
.array(z.string())
|
|
53
|
-
.optional()
|
|
54
|
-
.describe(stringUtils.toDescription(`
|
|
55
|
-
Optional BCC recipients.
|
|
56
|
-
`)),
|
|
57
|
-
});
|
|
58
|
-
const emailOutputSchema = z.object({
|
|
59
|
-
sent: z.boolean().describe(stringUtils.toDescription(`
|
|
60
|
-
True when the email was dispatched. The current implementation
|
|
61
|
-
always returns true after console-logging the would-be email; swap
|
|
62
|
-
in a real provider to make this meaningful.
|
|
63
|
-
`)),
|
|
64
|
-
recipient: z.string().describe(stringUtils.toDescription(`
|
|
65
|
-
Echo of the \`to\` field for confirmation.
|
|
66
|
-
`)),
|
|
67
|
-
});
|
|
68
|
-
/**
|
|
69
|
-
* Build the `send_email` tool. Approval-gated by default; the
|
|
70
|
-
* execute body either calls the supplied {@link send} hook or
|
|
71
|
-
* logs the email to the server console as a demo stub.
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* ```ts
|
|
75
|
-
* import { buildEmailTool, createAgent, mastra } from "@dbx-tools/appkit-mastra";
|
|
76
|
-
*
|
|
77
|
-
* const support = createAgent({
|
|
78
|
-
* instructions: "...",
|
|
79
|
-
* tools(plugins) {
|
|
80
|
-
* return {
|
|
81
|
-
* ...(plugins.genie?.toolkit() ?? {}),
|
|
82
|
-
* send_email: buildEmailTool(),
|
|
83
|
-
* };
|
|
84
|
-
* },
|
|
85
|
-
* });
|
|
86
|
-
* ```
|
|
87
|
-
*/
|
|
88
|
-
export function buildEmailTool(opts = {}) {
|
|
89
|
-
return createTool({
|
|
90
|
-
id: opts.id ?? "send_email",
|
|
91
|
-
description: stringUtils.toDescription(`
|
|
92
|
-
Send an email on the user's behalf. Pass a recipient address,
|
|
93
|
-
subject, and body; the user will be prompted to approve the send
|
|
94
|
-
before it goes out (the tool is approval-gated). Use this when
|
|
95
|
-
the user explicitly asks to send / forward / share something via
|
|
96
|
-
email - never autonomously. Keep subjects short and bodies
|
|
97
|
-
focused; the recipient may not have any of the chat context.
|
|
98
|
-
`),
|
|
99
|
-
inputSchema: emailInputSchema,
|
|
100
|
-
outputSchema: emailOutputSchema,
|
|
101
|
-
requireApproval: true,
|
|
102
|
-
execute: async (input) => {
|
|
103
|
-
const { to, subject, body, cc, bcc } = input;
|
|
104
|
-
// Default behaviour: dump the email to the server console so
|
|
105
|
-
// demos can see the gate fire end-to-end without a real
|
|
106
|
-
// provider. Replace by passing `opts.send`.
|
|
107
|
-
log.info("send", {
|
|
108
|
-
to,
|
|
109
|
-
...(cc && cc.length > 0 ? { cc } : {}),
|
|
110
|
-
...(bcc && bcc.length > 0 ? { bcc } : {}),
|
|
111
|
-
subject,
|
|
112
|
-
bodyLength: body.length,
|
|
113
|
-
body,
|
|
114
|
-
});
|
|
115
|
-
if (opts.send) {
|
|
116
|
-
await opts.send(input);
|
|
117
|
-
}
|
|
118
|
-
return { sent: true, recipient: to };
|
|
119
|
-
},
|
|
120
|
-
});
|
|
121
|
-
}
|
package/src/tools/email.ts
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mastra tool: `send_email`. Gated behind {@link requireApproval}
|
|
3
|
-
* so the model can call it freely but execution is paused until a
|
|
4
|
-
* human approves via the chat UI.
|
|
5
|
-
*
|
|
6
|
-
* The execute body is a stub - it logs the would-be email to the
|
|
7
|
-
* server console (via `logUtils.logger`) and returns success. Swap
|
|
8
|
-
* in a real SMTP / SES / Resend / Workspace Mail call later by
|
|
9
|
-
* editing the `execute` body; the tool surface and approval gate
|
|
10
|
-
* stay the same.
|
|
11
|
-
*
|
|
12
|
-
* Approval flow (Mastra + AI SDK V5):
|
|
13
|
-
*
|
|
14
|
-
* 1. Model calls the tool with `{ to, subject, body, ... }`.
|
|
15
|
-
* 2. Mastra evaluates `requireApproval` (here always `true`),
|
|
16
|
-
* pauses the agent loop, and emits a `tool-call-approval`
|
|
17
|
-
* chunk on the response stream.
|
|
18
|
-
* 3. The chat client renders an approve/deny prompt against the
|
|
19
|
-
* `state: 'approval-requested'` tool part. On approve, it sends
|
|
20
|
-
* a `MastraToolApproval` response back; on deny, the tool call
|
|
21
|
-
* is rejected and the model sees an error.
|
|
22
|
-
* 4. On approve, this `execute` runs and logs the email.
|
|
23
|
-
*
|
|
24
|
-
* The tool is intentionally NOT auto-installed on every agent -
|
|
25
|
-
* email is domain-specific, not infrastructure. Spread it into the
|
|
26
|
-
* specific agents that should be able to draft emails.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import { logUtils, stringUtils } from "@dbx-tools/shared";
|
|
30
|
-
import { createTool } from "@mastra/core/tools";
|
|
31
|
-
import { z } from "zod";
|
|
32
|
-
|
|
33
|
-
const log = logUtils.logger("mastra/tool/send-email");
|
|
34
|
-
|
|
35
|
-
const emailInputSchema = z.object({
|
|
36
|
-
to: z.string().describe(
|
|
37
|
-
stringUtils.toDescription(`
|
|
38
|
-
Single recipient email address (e.g. "alice@example.com"). For
|
|
39
|
-
multiple recipients, comma-separate them yourself.
|
|
40
|
-
`),
|
|
41
|
-
),
|
|
42
|
-
subject: z.string().describe(
|
|
43
|
-
stringUtils.toDescription(`
|
|
44
|
-
Subject line.
|
|
45
|
-
`),
|
|
46
|
-
),
|
|
47
|
-
body: z.string().describe(
|
|
48
|
-
stringUtils.toDescription(`
|
|
49
|
-
Email body. Plain text or markdown; the renderer downstream decides
|
|
50
|
-
which to honour. Be specific - the recipient may not have any
|
|
51
|
-
context the model has from prior chat turns.
|
|
52
|
-
`),
|
|
53
|
-
),
|
|
54
|
-
cc: z
|
|
55
|
-
.array(z.string())
|
|
56
|
-
.optional()
|
|
57
|
-
.describe(
|
|
58
|
-
stringUtils.toDescription(`
|
|
59
|
-
Optional CC recipients.
|
|
60
|
-
`),
|
|
61
|
-
),
|
|
62
|
-
bcc: z
|
|
63
|
-
.array(z.string())
|
|
64
|
-
.optional()
|
|
65
|
-
.describe(
|
|
66
|
-
stringUtils.toDescription(`
|
|
67
|
-
Optional BCC recipients.
|
|
68
|
-
`),
|
|
69
|
-
),
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
const emailOutputSchema = z.object({
|
|
73
|
-
sent: z.boolean().describe(
|
|
74
|
-
stringUtils.toDescription(`
|
|
75
|
-
True when the email was dispatched. The current implementation
|
|
76
|
-
always returns true after console-logging the would-be email; swap
|
|
77
|
-
in a real provider to make this meaningful.
|
|
78
|
-
`),
|
|
79
|
-
),
|
|
80
|
-
recipient: z.string().describe(
|
|
81
|
-
stringUtils.toDescription(`
|
|
82
|
-
Echo of the \`to\` field for confirmation.
|
|
83
|
-
`),
|
|
84
|
-
),
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
/** Options accepted by {@link buildEmailTool}. */
|
|
88
|
-
export interface BuildEmailToolOptions {
|
|
89
|
-
/**
|
|
90
|
-
* Override the tool id. Defaults to `"send_email"`. Useful if a
|
|
91
|
-
* caller wants `send_internal_email` / `send_external_email`
|
|
92
|
-
* variants.
|
|
93
|
-
*/
|
|
94
|
-
id?: string;
|
|
95
|
-
/**
|
|
96
|
-
* Replace the default execute body with a real provider call.
|
|
97
|
-
* Receives the validated input and must return `{sent, recipient}`.
|
|
98
|
-
* The console-log default is meant for demos / dev; production
|
|
99
|
-
* deployments should wire SMTP / SES / Resend / Workspace Mail
|
|
100
|
-
* here.
|
|
101
|
-
*/
|
|
102
|
-
send?: (input: z.infer<typeof emailInputSchema>) => Promise<void> | void;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Build the `send_email` tool. Approval-gated by default; the
|
|
107
|
-
* execute body either calls the supplied {@link send} hook or
|
|
108
|
-
* logs the email to the server console as a demo stub.
|
|
109
|
-
*
|
|
110
|
-
* @example
|
|
111
|
-
* ```ts
|
|
112
|
-
* import { buildEmailTool, createAgent, mastra } from "@dbx-tools/appkit-mastra";
|
|
113
|
-
*
|
|
114
|
-
* const support = createAgent({
|
|
115
|
-
* instructions: "...",
|
|
116
|
-
* tools(plugins) {
|
|
117
|
-
* return {
|
|
118
|
-
* ...(plugins.genie?.toolkit() ?? {}),
|
|
119
|
-
* send_email: buildEmailTool(),
|
|
120
|
-
* };
|
|
121
|
-
* },
|
|
122
|
-
* });
|
|
123
|
-
* ```
|
|
124
|
-
*/
|
|
125
|
-
export function buildEmailTool(opts: BuildEmailToolOptions = {}) {
|
|
126
|
-
return createTool({
|
|
127
|
-
id: opts.id ?? "send_email",
|
|
128
|
-
description: stringUtils.toDescription(`
|
|
129
|
-
Send an email on the user's behalf. Pass a recipient address,
|
|
130
|
-
subject, and body; the user will be prompted to approve the send
|
|
131
|
-
before it goes out (the tool is approval-gated). Use this when
|
|
132
|
-
the user explicitly asks to send / forward / share something via
|
|
133
|
-
email - never autonomously. Keep subjects short and bodies
|
|
134
|
-
focused; the recipient may not have any of the chat context.
|
|
135
|
-
`),
|
|
136
|
-
inputSchema: emailInputSchema,
|
|
137
|
-
outputSchema: emailOutputSchema,
|
|
138
|
-
requireApproval: true,
|
|
139
|
-
execute: async (input) => {
|
|
140
|
-
const { to, subject, body, cc, bcc } = input as z.infer<typeof emailInputSchema>;
|
|
141
|
-
// Default behaviour: dump the email to the server console so
|
|
142
|
-
// demos can see the gate fire end-to-end without a real
|
|
143
|
-
// provider. Replace by passing `opts.send`.
|
|
144
|
-
log.info("send", {
|
|
145
|
-
to,
|
|
146
|
-
...(cc && cc.length > 0 ? { cc } : {}),
|
|
147
|
-
...(bcc && bcc.length > 0 ? { bcc } : {}),
|
|
148
|
-
subject,
|
|
149
|
-
bodyLength: body.length,
|
|
150
|
-
body,
|
|
151
|
-
});
|
|
152
|
-
if (opts.send) {
|
|
153
|
-
await opts.send(input as z.infer<typeof emailInputSchema>);
|
|
154
|
-
}
|
|
155
|
-
return { sent: true, recipient: to };
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
}
|