@dbx-tools/appkit-mastra 0.1.13 → 0.1.14
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 +304 -645
- package/index.ts +46 -38
- package/package.json +58 -45
- package/src/agents.ts +224 -66
- package/src/chart.ts +531 -429
- package/src/config.ts +270 -19
- package/src/filesystems.ts +1090 -0
- package/src/genie.ts +1000 -660
- package/src/history.ts +166 -79
- package/src/mcp.ts +105 -0
- package/src/memory.ts +94 -92
- package/src/mlflow.ts +149 -0
- package/src/model.ts +75 -408
- package/src/observability.ts +121 -69
- package/src/pagination.ts +34 -0
- package/src/plugin.ts +552 -67
- package/src/processors.ts +168 -0
- package/src/rest.ts +67 -0
- package/src/server.ts +232 -45
- package/src/serving-sanitize.ts +167 -0
- package/src/serving.ts +27 -243
- package/src/statement.ts +89 -0
- package/src/storage-schema.ts +41 -0
- package/src/summarize.ts +176 -0
- package/src/threads.ts +338 -0
- package/src/workspaces.ts +346 -0
- package/src/writer.ts +44 -0
- package/tsconfig.json +41 -0
- package/dist/index.d.ts +0 -20
- package/dist/index.js +0 -20
- package/dist/src/agents.d.ts +0 -306
- package/dist/src/agents.js +0 -403
- package/dist/src/chart.d.ts +0 -170
- package/dist/src/chart.js +0 -491
- package/dist/src/config.d.ts +0 -183
- package/dist/src/config.js +0 -12
- package/dist/src/genie.d.ts +0 -131
- package/dist/src/genie.js +0 -630
- package/dist/src/history.d.ts +0 -67
- package/dist/src/history.js +0 -172
- package/dist/src/memory.d.ts +0 -100
- package/dist/src/memory.js +0 -242
- package/dist/src/model.d.ts +0 -159
- package/dist/src/model.js +0 -427
- package/dist/src/observability.d.ts +0 -33
- package/dist/src/observability.js +0 -71
- package/dist/src/plugin.d.ts +0 -130
- package/dist/src/plugin.js +0 -283
- package/dist/src/processors/strip-stale-charts.d.ts +0 -29
- package/dist/src/processors/strip-stale-charts.js +0 -96
- package/dist/src/server.d.ts +0 -46
- package/dist/src/server.js +0 -123
- package/dist/src/serving.d.ts +0 -156
- package/dist/src/serving.js +0 -231
- package/dist/src/tools/email.d.ts +0 -74
- package/dist/src/tools/email.js +0 -122
- package/dist/tsconfig.build.tsbuildinfo +0 -1
- package/src/processors/strip-stale-charts.ts +0 -105
- package/src/tools/email.ts +0 -147
package/dist/src/serving.d.ts
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dynamic model resolution against Databricks Model Serving.
|
|
3
|
-
*
|
|
4
|
-
* Three concerns live here:
|
|
5
|
-
*
|
|
6
|
-
* 1. **Listing** - {@link listServingEndpoints} pulls the workspace's
|
|
7
|
-
* `/serving-endpoints` via the SDK and caches the result per host
|
|
8
|
-
* with a TTL. Concurrent callers share one in-flight promise (the
|
|
9
|
-
* same coalescing pattern as Python's `cachetools-async`).
|
|
10
|
-
* 2. **Fuzzy matching** - {@link resolveModelId} runs the user's input
|
|
11
|
-
* through `fuse.js` extended search so loose tokens like
|
|
12
|
-
* `"claude sonnet"` snap to `databricks-claude-sonnet-4-6` even
|
|
13
|
-
* when typed without the full endpoint name.
|
|
14
|
-
* 3. **Per-request override** - {@link extractModelOverride} pulls a
|
|
15
|
-
* model name from the `X-Mastra-Model` header, `?model=` query
|
|
16
|
-
* string, or `model` body field so the same agent can be exercised
|
|
17
|
-
* against different endpoints without redeploying.
|
|
18
|
-
*
|
|
19
|
-
* `model.ts` glues these together inside the per-step model resolver;
|
|
20
|
-
* `plugin.ts` exposes the cached list at `GET /models`.
|
|
21
|
-
*/
|
|
22
|
-
import { type getExecutionContext } from "@databricks/appkit";
|
|
23
|
-
import type { ServingEndpointSummary } from "@dbx-tools/appkit-mastra-shared";
|
|
24
|
-
import type { MastraPluginConfig } from "./config.js";
|
|
25
|
-
export type { ServingEndpointSummary };
|
|
26
|
-
/**
|
|
27
|
-
* Structural type for the Databricks workspace client. Derived from
|
|
28
|
-
* AppKit's `ExecutionContext` so this module doesn't take a direct
|
|
29
|
-
* dependency on `@databricks/sdk-experimental`; the dep flows in
|
|
30
|
-
* transitively through `@databricks/appkit`.
|
|
31
|
-
*/
|
|
32
|
-
type WorkspaceClientLike = ReturnType<typeof getExecutionContext>["client"];
|
|
33
|
-
/**
|
|
34
|
-
* `RequestContext` key under which {@link MastraServer} stores the
|
|
35
|
-
* per-request model override (header / query / body). `model.ts`
|
|
36
|
-
* reads it before falling back to the agent / plugin default.
|
|
37
|
-
*/
|
|
38
|
-
export declare const MASTRA_MODEL_OVERRIDE_KEY = "mastra__model_override";
|
|
39
|
-
/** HTTP header inspected for a per-request model override. */
|
|
40
|
-
export declare const MODEL_OVERRIDE_HEADER = "x-mastra-model";
|
|
41
|
-
/** Query string parameter inspected for a per-request model override. */
|
|
42
|
-
export declare const MODEL_OVERRIDE_QUERY = "model";
|
|
43
|
-
/** Body fields (in priority order) inspected for a per-request model override. */
|
|
44
|
-
export declare const MODEL_OVERRIDE_BODY_FIELDS: readonly ["model", "modelId"];
|
|
45
|
-
/**
|
|
46
|
-
* List Model Serving endpoints for the workspace owning `client`,
|
|
47
|
-
* routed through AppKit's `CacheManager`. The manager gives us
|
|
48
|
-
* everything `cachetools.TTLCache` provides plus what
|
|
49
|
-
* `cachetools-async` adds on top: per-entry TTL, in-flight request
|
|
50
|
-
* coalescing (concurrent callers share one fetch via the manager's
|
|
51
|
-
* internal `inFlightRequests` map), bounded size, telemetry spans
|
|
52
|
-
* (`cache.getOrExecute`), and optional Lakebase persistence so the
|
|
53
|
-
* catalogue survives restarts when the lakebase plugin is wired up.
|
|
54
|
-
*
|
|
55
|
-
* Returns plain {@link ServingEndpointSummary} objects (a stable
|
|
56
|
-
* subset of the SDK type) so cache hits never expose stale SDK
|
|
57
|
-
* internals. Errors from `CacheManager` or the SDK fetch propagate
|
|
58
|
-
* to the caller - we don't swallow them so users see the real
|
|
59
|
-
* auth / network issue.
|
|
60
|
-
*
|
|
61
|
-
* @param host - Workspace host used as the cache key. Pass the value
|
|
62
|
-
* resolved from `client.config.getHost()` so multi-host apps share
|
|
63
|
-
* one entry per workspace.
|
|
64
|
-
* @param opts.ttlMs - Override the default TTL just for this call.
|
|
65
|
-
* Forwarded to `CacheManager` as seconds.
|
|
66
|
-
*/
|
|
67
|
-
export declare function listServingEndpoints(client: WorkspaceClientLike, host: string, opts?: {
|
|
68
|
-
ttlMs?: number;
|
|
69
|
-
}): Promise<ServingEndpointSummary[]>;
|
|
70
|
-
/**
|
|
71
|
-
* Force-evict cached endpoint listings via AppKit's `CacheManager`.
|
|
72
|
-
* With a `host` deletes that one workspace's entry; without one
|
|
73
|
-
* clears every cache entry on the manager (since `CacheManager`
|
|
74
|
-
* doesn't expose a namespace-scoped clear, this is the brute-force
|
|
75
|
-
* path - fine for tests, avoid in steady-state code).
|
|
76
|
-
*/
|
|
77
|
-
export declare function clearServingEndpointsCache(host?: string): Promise<void>;
|
|
78
|
-
/**
|
|
79
|
-
* Result of fuzzy-resolving a user-supplied model name against the
|
|
80
|
-
* live endpoint list. `score` is Fuse.js's distance (`0` is exact,
|
|
81
|
-
* `1` is no match); `matched` is `false` when the score exceeds the
|
|
82
|
-
* configured threshold so callers can fall back to the original
|
|
83
|
-
* input (Databricks will then return a clean 404).
|
|
84
|
-
*/
|
|
85
|
-
export interface ResolvedModel {
|
|
86
|
-
modelId: string;
|
|
87
|
-
matched: boolean;
|
|
88
|
-
score?: number;
|
|
89
|
-
}
|
|
90
|
-
/** Options accepted by {@link resolveModelId}. */
|
|
91
|
-
export interface ResolveModelOptions {
|
|
92
|
-
/** Fuse.js threshold (0 = exact, 1 = anything). Default `0.4`. */
|
|
93
|
-
threshold?: number;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Snap a user-supplied model name to the closest configured serving
|
|
97
|
-
* endpoint:
|
|
98
|
-
*
|
|
99
|
-
* 1. Exact name match wins immediately (no fuzzy needed).
|
|
100
|
-
* 2. Otherwise the input is tokenized (dashes / underscores / spaces
|
|
101
|
-
* become separators) and fed through Fuse.js extended search,
|
|
102
|
-
* which AND-s each token with fuzzy matching enabled. This is the
|
|
103
|
-
* "tokenized fuzzy match" the user reaches for when they type
|
|
104
|
-
* `"claude sonnet"` instead of the full endpoint name.
|
|
105
|
-
* 3. If the best Fuse score is above `threshold`, return the input
|
|
106
|
-
* unchanged and let the upstream call surface the 404. This keeps
|
|
107
|
-
* deliberate model ids (e.g. brand new endpoints) from being
|
|
108
|
-
* silently rewritten to a similar-looking neighbour.
|
|
109
|
-
*
|
|
110
|
-
* Pass an empty endpoint list to short-circuit fuzzy matching - the
|
|
111
|
-
* input is returned verbatim. This is what {@link buildModel} does
|
|
112
|
-
* when the workspace client can't be reached at resolve time.
|
|
113
|
-
*/
|
|
114
|
-
export declare function resolveModelId(input: string, endpoints: readonly ServingEndpointSummary[], opts?: ResolveModelOptions): ResolvedModel;
|
|
115
|
-
/**
|
|
116
|
-
* Minimal Express-ish request shape used by {@link extractModelOverride}.
|
|
117
|
-
* Keeps this module independent of `express` so the helper can be
|
|
118
|
-
* reused from non-Express adapters.
|
|
119
|
-
*/
|
|
120
|
-
export interface ModelOverrideRequest {
|
|
121
|
-
headers?: Record<string, string | string[] | undefined>;
|
|
122
|
-
query?: Record<string, unknown> | undefined;
|
|
123
|
-
body?: unknown;
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Pull a model override out of a single HTTP request, checking
|
|
127
|
-
* sources in priority order:
|
|
128
|
-
*
|
|
129
|
-
* 1. `X-Mastra-Model` header
|
|
130
|
-
* 2. `?model=` query string parameter
|
|
131
|
-
* 3. Body field (`model` or `modelId`, in that order)
|
|
132
|
-
*
|
|
133
|
-
* Returns `null` when nothing is set, so callers can wrap with
|
|
134
|
-
* `if (override) ...` without juggling empty strings. Body inspection
|
|
135
|
-
* is lenient - any plain object with one of the configured keys
|
|
136
|
-
* counts, mirroring how AI SDK chat clients pass arbitrary metadata
|
|
137
|
-
* alongside `messages`.
|
|
138
|
-
*/
|
|
139
|
-
export declare function extractModelOverride(req: ModelOverrideRequest): string | null;
|
|
140
|
-
/**
|
|
141
|
-
* Read the fuzzy-resolution config knobs off the plugin config with
|
|
142
|
-
* defaults applied. Kept here so `buildModel` and the `/models` route
|
|
143
|
-
* agree on what "enabled" means.
|
|
144
|
-
*
|
|
145
|
-
* `fallbacks` is the priority-ordered list `pickModelId` walks when
|
|
146
|
-
* nothing explicit is set; defaults live in `model.ts`
|
|
147
|
-
* (`FALLBACK_MODEL_IDS`) and are passed in by callers to avoid a
|
|
148
|
-
* circular import between `serving.ts` and `model.ts`.
|
|
149
|
-
*/
|
|
150
|
-
export declare function resolveServingConfig(config: MastraPluginConfig, defaultFallbacks?: readonly string[]): {
|
|
151
|
-
ttlMs: number;
|
|
152
|
-
threshold: number;
|
|
153
|
-
fuzzy: boolean;
|
|
154
|
-
allowOverride: boolean;
|
|
155
|
-
fallbacks: readonly string[];
|
|
156
|
-
};
|
package/dist/src/serving.js
DELETED
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dynamic model resolution against Databricks Model Serving.
|
|
3
|
-
*
|
|
4
|
-
* Three concerns live here:
|
|
5
|
-
*
|
|
6
|
-
* 1. **Listing** - {@link listServingEndpoints} pulls the workspace's
|
|
7
|
-
* `/serving-endpoints` via the SDK and caches the result per host
|
|
8
|
-
* with a TTL. Concurrent callers share one in-flight promise (the
|
|
9
|
-
* same coalescing pattern as Python's `cachetools-async`).
|
|
10
|
-
* 2. **Fuzzy matching** - {@link resolveModelId} runs the user's input
|
|
11
|
-
* through `fuse.js` extended search so loose tokens like
|
|
12
|
-
* `"claude sonnet"` snap to `databricks-claude-sonnet-4-6` even
|
|
13
|
-
* when typed without the full endpoint name.
|
|
14
|
-
* 3. **Per-request override** - {@link extractModelOverride} pulls a
|
|
15
|
-
* model name from the `X-Mastra-Model` header, `?model=` query
|
|
16
|
-
* string, or `model` body field so the same agent can be exercised
|
|
17
|
-
* against different endpoints without redeploying.
|
|
18
|
-
*
|
|
19
|
-
* `model.ts` glues these together inside the per-step model resolver;
|
|
20
|
-
* `plugin.ts` exposes the cached list at `GET /models`.
|
|
21
|
-
*/
|
|
22
|
-
import { CacheManager } from "@databricks/appkit";
|
|
23
|
-
import { logUtils, stringUtils } from "@dbx-tools/appkit-shared";
|
|
24
|
-
import Fuse from "fuse.js";
|
|
25
|
-
const log = logUtils.logger("mastra/serving");
|
|
26
|
-
/**
|
|
27
|
-
* `RequestContext` key under which {@link MastraServer} stores the
|
|
28
|
-
* per-request model override (header / query / body). `model.ts`
|
|
29
|
-
* reads it before falling back to the agent / plugin default.
|
|
30
|
-
*/
|
|
31
|
-
export const MASTRA_MODEL_OVERRIDE_KEY = "mastra__model_override";
|
|
32
|
-
/** HTTP header inspected for a per-request model override. */
|
|
33
|
-
export const MODEL_OVERRIDE_HEADER = "x-mastra-model";
|
|
34
|
-
/** Query string parameter inspected for a per-request model override. */
|
|
35
|
-
export const MODEL_OVERRIDE_QUERY = "model";
|
|
36
|
-
/** Body fields (in priority order) inspected for a per-request model override. */
|
|
37
|
-
export const MODEL_OVERRIDE_BODY_FIELDS = ["model", "modelId"];
|
|
38
|
-
/** Default TTL for the in-memory endpoint cache. Matches the Databricks SDK's session lifetime budget. */
|
|
39
|
-
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
40
|
-
/** Default Fuse.js score threshold below which a fuzzy match is accepted. */
|
|
41
|
-
const DEFAULT_FUZZY_THRESHOLD = 0.4;
|
|
42
|
-
/** Cache key parts under which endpoint listings are stored. */
|
|
43
|
-
const CACHE_KEY_NAMESPACE = "mastra:serving-endpoints";
|
|
44
|
-
/**
|
|
45
|
-
* Stable `userKey` arg for AppKit's `CacheManager.getOrExecute`.
|
|
46
|
-
* Endpoint visibility is effectively workspace-scoped (we cache by
|
|
47
|
-
* host in the key parts), so a single shared key lets every user of
|
|
48
|
-
* the same workspace share one cached fetch and coalesce on the
|
|
49
|
-
* in-flight promise. Permissions can differ in theory, but the
|
|
50
|
-
* Foundation Model API catalogue is the same view for every caller.
|
|
51
|
-
*/
|
|
52
|
-
const SHARED_USER_KEY = "mastra-shared";
|
|
53
|
-
/**
|
|
54
|
-
* List Model Serving endpoints for the workspace owning `client`,
|
|
55
|
-
* routed through AppKit's `CacheManager`. The manager gives us
|
|
56
|
-
* everything `cachetools.TTLCache` provides plus what
|
|
57
|
-
* `cachetools-async` adds on top: per-entry TTL, in-flight request
|
|
58
|
-
* coalescing (concurrent callers share one fetch via the manager's
|
|
59
|
-
* internal `inFlightRequests` map), bounded size, telemetry spans
|
|
60
|
-
* (`cache.getOrExecute`), and optional Lakebase persistence so the
|
|
61
|
-
* catalogue survives restarts when the lakebase plugin is wired up.
|
|
62
|
-
*
|
|
63
|
-
* Returns plain {@link ServingEndpointSummary} objects (a stable
|
|
64
|
-
* subset of the SDK type) so cache hits never expose stale SDK
|
|
65
|
-
* internals. Errors from `CacheManager` or the SDK fetch propagate
|
|
66
|
-
* to the caller - we don't swallow them so users see the real
|
|
67
|
-
* auth / network issue.
|
|
68
|
-
*
|
|
69
|
-
* @param host - Workspace host used as the cache key. Pass the value
|
|
70
|
-
* resolved from `client.config.getHost()` so multi-host apps share
|
|
71
|
-
* one entry per workspace.
|
|
72
|
-
* @param opts.ttlMs - Override the default TTL just for this call.
|
|
73
|
-
* Forwarded to `CacheManager` as seconds.
|
|
74
|
-
*/
|
|
75
|
-
export async function listServingEndpoints(client, host, opts = {}) {
|
|
76
|
-
const ttlSec = Math.max(1, Math.round((opts.ttlMs ?? DEFAULT_TTL_MS) / 1000));
|
|
77
|
-
return CacheManager.getInstanceSync().getOrExecute([CACHE_KEY_NAMESPACE, host], () => fetchEndpoints(client), SHARED_USER_KEY, { ttl: ttlSec });
|
|
78
|
-
}
|
|
79
|
-
async function fetchEndpoints(client) {
|
|
80
|
-
const startedAt = Date.now();
|
|
81
|
-
const out = [];
|
|
82
|
-
for await (const ep of client.servingEndpoints.list()) {
|
|
83
|
-
if (!ep.name)
|
|
84
|
-
continue;
|
|
85
|
-
out.push({
|
|
86
|
-
name: ep.name,
|
|
87
|
-
...(ep.task !== undefined ? { task: ep.task } : {}),
|
|
88
|
-
...(ep.state?.ready !== undefined ? { state: String(ep.state.ready) } : {}),
|
|
89
|
-
...(ep.description !== undefined ? { description: ep.description } : {}),
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
log.debug("listed", { count: out.length, elapsedMs: Date.now() - startedAt });
|
|
93
|
-
return out;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Force-evict cached endpoint listings via AppKit's `CacheManager`.
|
|
97
|
-
* With a `host` deletes that one workspace's entry; without one
|
|
98
|
-
* clears every cache entry on the manager (since `CacheManager`
|
|
99
|
-
* doesn't expose a namespace-scoped clear, this is the brute-force
|
|
100
|
-
* path - fine for tests, avoid in steady-state code).
|
|
101
|
-
*/
|
|
102
|
-
export async function clearServingEndpointsCache(host) {
|
|
103
|
-
const cache = CacheManager.getInstanceSync();
|
|
104
|
-
if (host) {
|
|
105
|
-
const key = cache.generateKey([CACHE_KEY_NAMESPACE, host], SHARED_USER_KEY);
|
|
106
|
-
await cache.delete(key);
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
await cache.clear();
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Snap a user-supplied model name to the closest configured serving
|
|
114
|
-
* endpoint:
|
|
115
|
-
*
|
|
116
|
-
* 1. Exact name match wins immediately (no fuzzy needed).
|
|
117
|
-
* 2. Otherwise the input is tokenized (dashes / underscores / spaces
|
|
118
|
-
* become separators) and fed through Fuse.js extended search,
|
|
119
|
-
* which AND-s each token with fuzzy matching enabled. This is the
|
|
120
|
-
* "tokenized fuzzy match" the user reaches for when they type
|
|
121
|
-
* `"claude sonnet"` instead of the full endpoint name.
|
|
122
|
-
* 3. If the best Fuse score is above `threshold`, return the input
|
|
123
|
-
* unchanged and let the upstream call surface the 404. This keeps
|
|
124
|
-
* deliberate model ids (e.g. brand new endpoints) from being
|
|
125
|
-
* silently rewritten to a similar-looking neighbour.
|
|
126
|
-
*
|
|
127
|
-
* Pass an empty endpoint list to short-circuit fuzzy matching - the
|
|
128
|
-
* input is returned verbatim. This is what {@link buildModel} does
|
|
129
|
-
* when the workspace client can't be reached at resolve time.
|
|
130
|
-
*/
|
|
131
|
-
export function resolveModelId(input, endpoints, opts = {}) {
|
|
132
|
-
if (endpoints.length === 0) {
|
|
133
|
-
log.debug("resolve:no-endpoints", { input });
|
|
134
|
-
return { modelId: input, matched: false };
|
|
135
|
-
}
|
|
136
|
-
for (const ep of endpoints) {
|
|
137
|
-
if (ep.name === input) {
|
|
138
|
-
log.debug("resolve:exact", { input });
|
|
139
|
-
return { modelId: ep.name, matched: true, score: 0 };
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
const threshold = opts.threshold ?? DEFAULT_FUZZY_THRESHOLD;
|
|
143
|
-
const fuse = new Fuse(endpoints, {
|
|
144
|
-
keys: ["name"],
|
|
145
|
-
threshold,
|
|
146
|
-
ignoreLocation: true,
|
|
147
|
-
includeScore: true,
|
|
148
|
-
useExtendedSearch: true,
|
|
149
|
-
isCaseSensitive: false,
|
|
150
|
-
});
|
|
151
|
-
// Fuse 7.3 has no built-in tokenize hook; in extended search,
|
|
152
|
-
// space-separated tokens are AND-ed with fuzzy matching enabled. We
|
|
153
|
-
// lean on the shared tokenizer so the splitting rules stay
|
|
154
|
-
// consistent with the rest of the toolkit.
|
|
155
|
-
const query = Array.from(stringUtils.tokenizeWithOptions({ lowerCase: true, camelCase: false }, input)).join(" ");
|
|
156
|
-
if (!query) {
|
|
157
|
-
log.debug("resolve:empty-tokens", { input });
|
|
158
|
-
return { modelId: input, matched: false };
|
|
159
|
-
}
|
|
160
|
-
const results = fuse.search(query);
|
|
161
|
-
const best = results[0];
|
|
162
|
-
if (best?.item.name && (best.score ?? 0) <= threshold) {
|
|
163
|
-
log.debug("resolve:fuzzy-match", {
|
|
164
|
-
input,
|
|
165
|
-
modelId: best.item.name,
|
|
166
|
-
score: best.score,
|
|
167
|
-
});
|
|
168
|
-
return { modelId: best.item.name, matched: true, score: best.score };
|
|
169
|
-
}
|
|
170
|
-
log.debug("resolve:no-match", {
|
|
171
|
-
input,
|
|
172
|
-
bestScore: best?.score,
|
|
173
|
-
threshold,
|
|
174
|
-
});
|
|
175
|
-
return { modelId: input, matched: false };
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Pull a model override out of a single HTTP request, checking
|
|
179
|
-
* sources in priority order:
|
|
180
|
-
*
|
|
181
|
-
* 1. `X-Mastra-Model` header
|
|
182
|
-
* 2. `?model=` query string parameter
|
|
183
|
-
* 3. Body field (`model` or `modelId`, in that order)
|
|
184
|
-
*
|
|
185
|
-
* Returns `null` when nothing is set, so callers can wrap with
|
|
186
|
-
* `if (override) ...` without juggling empty strings. Body inspection
|
|
187
|
-
* is lenient - any plain object with one of the configured keys
|
|
188
|
-
* counts, mirroring how AI SDK chat clients pass arbitrary metadata
|
|
189
|
-
* alongside `messages`.
|
|
190
|
-
*/
|
|
191
|
-
export function extractModelOverride(req) {
|
|
192
|
-
const headers = req.headers;
|
|
193
|
-
if (headers) {
|
|
194
|
-
const headerVal = stringUtils.firstNonEmpty(headers[MODEL_OVERRIDE_HEADER] ?? headers[MODEL_OVERRIDE_HEADER.toLowerCase()]);
|
|
195
|
-
if (headerVal)
|
|
196
|
-
return headerVal;
|
|
197
|
-
}
|
|
198
|
-
if (req.query) {
|
|
199
|
-
const queryVal = stringUtils.firstNonEmpty(req.query[MODEL_OVERRIDE_QUERY]);
|
|
200
|
-
if (queryVal)
|
|
201
|
-
return queryVal;
|
|
202
|
-
}
|
|
203
|
-
if (req.body && typeof req.body === "object") {
|
|
204
|
-
const record = req.body;
|
|
205
|
-
for (const field of MODEL_OVERRIDE_BODY_FIELDS) {
|
|
206
|
-
const bodyVal = stringUtils.firstNonEmpty(record[field]);
|
|
207
|
-
if (bodyVal)
|
|
208
|
-
return bodyVal;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Read the fuzzy-resolution config knobs off the plugin config with
|
|
215
|
-
* defaults applied. Kept here so `buildModel` and the `/models` route
|
|
216
|
-
* agree on what "enabled" means.
|
|
217
|
-
*
|
|
218
|
-
* `fallbacks` is the priority-ordered list `pickModelId` walks when
|
|
219
|
-
* nothing explicit is set; defaults live in `model.ts`
|
|
220
|
-
* (`FALLBACK_MODEL_IDS`) and are passed in by callers to avoid a
|
|
221
|
-
* circular import between `serving.ts` and `model.ts`.
|
|
222
|
-
*/
|
|
223
|
-
export function resolveServingConfig(config, defaultFallbacks = []) {
|
|
224
|
-
return {
|
|
225
|
-
ttlMs: config.modelCacheTtlMs ?? DEFAULT_TTL_MS,
|
|
226
|
-
threshold: config.modelFuzzyThreshold ?? DEFAULT_FUZZY_THRESHOLD,
|
|
227
|
-
fuzzy: config.modelFuzzyMatch !== false,
|
|
228
|
-
allowOverride: config.modelOverride !== false,
|
|
229
|
-
fallbacks: config.defaultModelFallbacks ?? defaultFallbacks,
|
|
230
|
-
};
|
|
231
|
-
}
|
|
@@ -1,74 +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<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, string, unknown>;
|
|
74
|
-
export {};
|
package/dist/src/tools/email.js
DELETED
|
@@ -1,122 +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/appkit-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
|
|
42
|
-
decides which to honour. Be specific - the recipient may not
|
|
43
|
-
have any 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;
|
|
62
|
-
swap 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
|
|
93
|
-
address, subject, and body; the user will be prompted to
|
|
94
|
-
approve the send before it goes out (the tool is
|
|
95
|
-
approval-gated). Use this when the user explicitly asks
|
|
96
|
-
to send / forward / share something via email - never
|
|
97
|
-
autonomously. Keep subjects short and bodies focused; the
|
|
98
|
-
recipient may not have any of the chat context.
|
|
99
|
-
`,
|
|
100
|
-
inputSchema: emailInputSchema,
|
|
101
|
-
outputSchema: emailOutputSchema,
|
|
102
|
-
requireApproval: true,
|
|
103
|
-
execute: async (input) => {
|
|
104
|
-
const { to, subject, body, cc, bcc } = input;
|
|
105
|
-
// Default behaviour: dump the email to the server console so
|
|
106
|
-
// demos can see the gate fire end-to-end without a real
|
|
107
|
-
// provider. Replace by passing `opts.send`.
|
|
108
|
-
log.info("send", {
|
|
109
|
-
to,
|
|
110
|
-
...(cc && cc.length > 0 ? { cc } : {}),
|
|
111
|
-
...(bcc && bcc.length > 0 ? { bcc } : {}),
|
|
112
|
-
subject,
|
|
113
|
-
bodyLength: body.length,
|
|
114
|
-
body,
|
|
115
|
-
});
|
|
116
|
-
if (opts.send) {
|
|
117
|
-
await opts.send(input);
|
|
118
|
-
}
|
|
119
|
-
return { sent: true, recipient: to };
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
}
|