@dbx-tools/appkit-mastra 0.1.12 → 0.1.18
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 +47 -45
- package/dist/src/agents.d.ts +2 -2
- package/dist/src/agents.js +66 -14
- package/dist/src/chart.d.ts +39 -105
- package/dist/src/chart.js +199 -194
- package/dist/src/config.d.ts +104 -0
- package/dist/src/config.js +43 -0
- package/dist/src/genie.d.ts +170 -107
- package/dist/src/genie.js +1003 -577
- package/dist/src/history.d.ts +31 -3
- package/dist/src/history.js +137 -31
- package/dist/src/memory.d.ts +25 -4
- package/dist/src/memory.js +34 -2
- package/dist/src/model.js +2 -2
- package/dist/src/observability.d.ts +64 -0
- package/dist/src/observability.js +85 -0
- package/dist/src/plugin.js +39 -7
- package/dist/src/processors/strip-stale-charts.js +1 -1
- package/dist/src/server.d.ts +12 -0
- package/dist/src/server.js +38 -2
- package/dist/src/serving.js +1 -1
- package/dist/src/tools/email.js +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +21 -16
- package/src/agents.ts +73 -17
- package/src/chart.ts +221 -251
- package/src/config.ts +120 -0
- package/src/genie.ts +1199 -654
- package/src/history.ts +147 -33
- package/src/memory.ts +41 -5
- package/src/model.ts +3 -3
- package/src/observability.ts +116 -0
- package/src/plugin.ts +39 -7
- package/src/processors/strip-stale-charts.ts +1 -1
- package/src/server.ts +49 -2
- package/src/serving.ts +1 -1
- package/src/tools/email.ts +1 -1
package/dist/src/history.d.ts
CHANGED
|
@@ -43,6 +43,27 @@ export interface LoadHistoryOptions {
|
|
|
43
43
|
* without sorting locally.
|
|
44
44
|
*/
|
|
45
45
|
export declare function loadHistory(opts: LoadHistoryOptions): Promise<MastraHistoryResponse>;
|
|
46
|
+
/** Inputs accepted by {@link clearHistory}. */
|
|
47
|
+
export interface ClearHistoryOptions {
|
|
48
|
+
agent: Agent;
|
|
49
|
+
threadId: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Wipe every persisted message tied to a thread. Returns the count
|
|
53
|
+
* of messages that were on the thread at delete time so the caller
|
|
54
|
+
* can render a "cleared N messages" affordance without an
|
|
55
|
+
* additional round-trip.
|
|
56
|
+
*
|
|
57
|
+
* Agents without a configured `Memory` resolve to a no-op (count
|
|
58
|
+
* 0), matching {@link loadHistory}'s "stateless agents return an
|
|
59
|
+
* empty page" stance so callers don't have to special-case them.
|
|
60
|
+
* Threads that don't exist yet are also a successful no-op - the
|
|
61
|
+
* operation is idempotent so the UI can fire-and-forget without
|
|
62
|
+
* tracking thread existence.
|
|
63
|
+
*/
|
|
64
|
+
export declare function clearHistory(opts: ClearHistoryOptions): Promise<{
|
|
65
|
+
cleared: number;
|
|
66
|
+
}>;
|
|
46
67
|
/** Options accepted by {@link historyRoute}. */
|
|
47
68
|
export type HistoryRouteOptions = {
|
|
48
69
|
path: `${string}:agentId${string}`;
|
|
@@ -52,8 +73,15 @@ export type HistoryRouteOptions = {
|
|
|
52
73
|
agent: string;
|
|
53
74
|
};
|
|
54
75
|
/**
|
|
55
|
-
* Register
|
|
56
|
-
*
|
|
76
|
+
* Register the `<path>` Mastra custom API route. Handles two
|
|
77
|
+
* methods on the same mount:
|
|
78
|
+
*
|
|
79
|
+
* - `GET`: return a page of AI SDK V5 `UIMessage`s for the
|
|
80
|
+
* caller's current thread ({@link loadHistory}).
|
|
81
|
+
* - `DELETE`: wipe every persisted message on the caller's
|
|
82
|
+
* thread ({@link clearHistory}). The session cookie that
|
|
83
|
+
* anchors the thread id is left alone so the user keeps the
|
|
84
|
+
* same thread - only the contents go away.
|
|
57
85
|
*
|
|
58
86
|
* Modeled after `chatRoute` from `@mastra/ai-sdk`: pass `agent` for a
|
|
59
87
|
* fixed-agent mount, or include `:agentId` in the path for dynamic
|
|
@@ -64,4 +92,4 @@ export type HistoryRouteOptions = {
|
|
|
64
92
|
* (populated upstream by `MastraServer.registerAuthMiddleware`), so
|
|
65
93
|
* no cookie or user lookups happen here.
|
|
66
94
|
*/
|
|
67
|
-
export declare function historyRoute(options: HistoryRouteOptions): import("@mastra/core/server").ApiRoute;
|
|
95
|
+
export declare function historyRoute(options: HistoryRouteOptions): import("@mastra/core/server").ApiRoute[];
|
package/dist/src/history.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* the handler runs - no cookie or user lookups happen here, and the
|
|
16
16
|
* session-cookie logic stays the single source of truth in `server.ts`.
|
|
17
17
|
*/
|
|
18
|
-
import { logUtils } from "@dbx-tools/
|
|
18
|
+
import { logUtils } from "@dbx-tools/shared";
|
|
19
19
|
import { toAISdkV5Messages } from "@mastra/ai-sdk/ui";
|
|
20
20
|
import { MASTRA_RESOURCE_ID_KEY, MASTRA_THREAD_ID_KEY, } from "@mastra/core/request-context";
|
|
21
21
|
import { registerApiRoute } from "@mastra/core/server";
|
|
@@ -79,8 +79,79 @@ export async function loadHistory(opts) {
|
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
82
|
-
*
|
|
83
|
-
* of
|
|
82
|
+
* Wipe every persisted message tied to a thread. Returns the count
|
|
83
|
+
* of messages that were on the thread at delete time so the caller
|
|
84
|
+
* can render a "cleared N messages" affordance without an
|
|
85
|
+
* additional round-trip.
|
|
86
|
+
*
|
|
87
|
+
* Agents without a configured `Memory` resolve to a no-op (count
|
|
88
|
+
* 0), matching {@link loadHistory}'s "stateless agents return an
|
|
89
|
+
* empty page" stance so callers don't have to special-case them.
|
|
90
|
+
* Threads that don't exist yet are also a successful no-op - the
|
|
91
|
+
* operation is idempotent so the UI can fire-and-forget without
|
|
92
|
+
* tracking thread existence.
|
|
93
|
+
*/
|
|
94
|
+
export async function clearHistory(opts) {
|
|
95
|
+
const memory = await opts.agent.getMemory();
|
|
96
|
+
if (!memory) {
|
|
97
|
+
log.debug("clear:no-memory", { agentId: opts.agent.id, threadId: opts.threadId });
|
|
98
|
+
return { cleared: 0 };
|
|
99
|
+
}
|
|
100
|
+
// Mastra's `deleteThread` cascades to the message table, so we
|
|
101
|
+
// can't ask for a count after the fact. Read it pre-delete with a
|
|
102
|
+
// one-page recall sized to fit common threads in a single round
|
|
103
|
+
// trip; the value is for telemetry / UI, not correctness.
|
|
104
|
+
let cleared = 0;
|
|
105
|
+
try {
|
|
106
|
+
const probe = await memory.recall({
|
|
107
|
+
threadId: opts.threadId,
|
|
108
|
+
page: 0,
|
|
109
|
+
perPage: 1,
|
|
110
|
+
});
|
|
111
|
+
cleared = probe.total;
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
// A missing-thread error is the happy-path "nothing to count";
|
|
115
|
+
// every other error is logged but doesn't block the delete.
|
|
116
|
+
log.debug("clear:probe-failed", {
|
|
117
|
+
agentId: opts.agent.id,
|
|
118
|
+
threadId: opts.threadId,
|
|
119
|
+
error: err instanceof Error ? err.message : String(err),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const startedAt = Date.now();
|
|
123
|
+
try {
|
|
124
|
+
await memory.deleteThread(opts.threadId);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
// Mastra's `deleteThread` raises when the thread row was never
|
|
128
|
+
// created (e.g. clearing an empty session). Surface as a soft
|
|
129
|
+
// warn and treat as success - the user-facing semantic is
|
|
130
|
+
// "history is now empty" which is already true.
|
|
131
|
+
log.warn("clear:delete-soft-failed", {
|
|
132
|
+
agentId: opts.agent.id,
|
|
133
|
+
threadId: opts.threadId,
|
|
134
|
+
error: err instanceof Error ? err.message : String(err),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
log.info("clear:done", {
|
|
138
|
+
agentId: opts.agent.id,
|
|
139
|
+
threadId: opts.threadId,
|
|
140
|
+
cleared,
|
|
141
|
+
elapsedMs: Date.now() - startedAt,
|
|
142
|
+
});
|
|
143
|
+
return { cleared };
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Register the `<path>` Mastra custom API route. Handles two
|
|
147
|
+
* methods on the same mount:
|
|
148
|
+
*
|
|
149
|
+
* - `GET`: return a page of AI SDK V5 `UIMessage`s for the
|
|
150
|
+
* caller's current thread ({@link loadHistory}).
|
|
151
|
+
* - `DELETE`: wipe every persisted message on the caller's
|
|
152
|
+
* thread ({@link clearHistory}). The session cookie that
|
|
153
|
+
* anchors the thread id is left alone so the user keeps the
|
|
154
|
+
* same thread - only the contents go away.
|
|
84
155
|
*
|
|
85
156
|
* Modeled after `chatRoute` from `@mastra/ai-sdk`: pass `agent` for a
|
|
86
157
|
* fixed-agent mount, or include `:agentId` in the path for dynamic
|
|
@@ -97,34 +168,69 @@ export function historyRoute(options) {
|
|
|
97
168
|
if (!fixedAgent && !path.includes(":agentId")) {
|
|
98
169
|
throw new Error("historyRoute path must include `:agentId` or `agent` must be passed explicitly");
|
|
99
170
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
171
|
+
// Tiny resolver shared by GET / DELETE: derive the active agent
|
|
172
|
+
// and thread id, returning a JSON error response when either is
|
|
173
|
+
// missing. Keeps both handlers thin and gives them identical
|
|
174
|
+
// validation behaviour.
|
|
175
|
+
const resolveContext = (c) => {
|
|
176
|
+
const mastra = c.get("mastra");
|
|
177
|
+
const requestContext = c.get("requestContext");
|
|
178
|
+
const agentId = fixedAgent ?? c.req.param("agentId");
|
|
179
|
+
if (!agentId) {
|
|
180
|
+
return { error: c.json({ error: "agentId is required" }, 400) };
|
|
181
|
+
}
|
|
182
|
+
const agent = mastra.getAgentById(agentId);
|
|
183
|
+
if (!agent) {
|
|
184
|
+
return {
|
|
185
|
+
error: c.json({ error: `Unknown agent "${agentId}"` }, 404),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const threadId = requestContext.get(MASTRA_THREAD_ID_KEY);
|
|
189
|
+
if (!threadId) {
|
|
190
|
+
return {
|
|
191
|
+
error: c.json({ error: "thread id missing from request context" }, 400),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const resourceId = requestContext.get(MASTRA_RESOURCE_ID_KEY);
|
|
195
|
+
return { agentId, agent, threadId, resourceId };
|
|
196
|
+
};
|
|
197
|
+
return [
|
|
198
|
+
registerApiRoute(path, {
|
|
199
|
+
method: "GET",
|
|
200
|
+
handler: async (c) => {
|
|
201
|
+
const ctx = resolveContext(c);
|
|
202
|
+
if ("error" in ctx)
|
|
203
|
+
return ctx.error;
|
|
204
|
+
const payload = await loadHistory({
|
|
205
|
+
agent: ctx.agent,
|
|
206
|
+
threadId: ctx.threadId,
|
|
207
|
+
...(ctx.resourceId ? { resourceId: ctx.resourceId } : {}),
|
|
208
|
+
page: parseIntParam(c.req.query("page")),
|
|
209
|
+
perPage: parseIntParam(c.req.query("perPage")),
|
|
210
|
+
});
|
|
211
|
+
return c.json(payload);
|
|
212
|
+
},
|
|
213
|
+
}),
|
|
214
|
+
registerApiRoute(path, {
|
|
215
|
+
method: "DELETE",
|
|
216
|
+
handler: async (c) => {
|
|
217
|
+
const ctx = resolveContext(c);
|
|
218
|
+
if ("error" in ctx)
|
|
219
|
+
return ctx.error;
|
|
220
|
+
const { cleared } = await clearHistory({
|
|
221
|
+
agent: ctx.agent,
|
|
222
|
+
threadId: ctx.threadId,
|
|
223
|
+
});
|
|
224
|
+
const payload = {
|
|
225
|
+
ok: true,
|
|
226
|
+
agentId: ctx.agentId,
|
|
227
|
+
threadId: ctx.threadId,
|
|
228
|
+
cleared,
|
|
229
|
+
};
|
|
230
|
+
return c.json(payload);
|
|
231
|
+
},
|
|
232
|
+
}),
|
|
233
|
+
];
|
|
128
234
|
}
|
|
129
235
|
/** Coerce / clamp `perPage`; falls back to the page-size default. */
|
|
130
236
|
function clampPerPage(value) {
|
package/dist/src/memory.d.ts
CHANGED
|
@@ -14,13 +14,22 @@
|
|
|
14
14
|
* index is almost always what users want; opt into per-agent recall
|
|
15
15
|
* by passing a {@link MastraMemoryConfigOverride} on the agent.
|
|
16
16
|
*
|
|
17
|
+
* Additionally, {@link MemoryBuilder.instanceStorage} returns a
|
|
18
|
+
* **Mastra-instance-level** `PostgresStore` (schema `mastra_instance`)
|
|
19
|
+
* used for workflow snapshots - the persistence layer
|
|
20
|
+
* `agent.resumeStream()` reads from when waking a suspended
|
|
21
|
+
* `requireApproval` tool call. Per-agent stores are not enough for
|
|
22
|
+
* this: workflow runs are scoped to the Mastra instance, not an
|
|
23
|
+
* individual agent's `Memory`.
|
|
24
|
+
*
|
|
17
25
|
* Plugin-level `config.storage` / `config.memory` act as the baseline
|
|
18
26
|
* (auto-defaulted to `true` in `plugin.ts` when the `lakebase` plugin
|
|
19
27
|
* is registered); per-agent settings cascade on top of that.
|
|
20
28
|
*/
|
|
21
29
|
import { lakebase } from "@databricks/appkit";
|
|
22
|
-
import {
|
|
30
|
+
import { appkitUtils } from "@dbx-tools/shared";
|
|
23
31
|
import { Memory } from "@mastra/memory";
|
|
32
|
+
import { PostgresStore } from "@mastra/pg";
|
|
24
33
|
import type { MastraAgentDefinition } from "./agents.js";
|
|
25
34
|
import type { MastraPluginConfig } from "./config.js";
|
|
26
35
|
/** Pool handle returned by the AppKit `lakebase` plugin `exports().pool`. */
|
|
@@ -38,13 +47,13 @@ export declare function needsLakebase(config: MastraPluginConfig): boolean;
|
|
|
38
47
|
* `storage` / `memory` without lakebase is a wiring bug, not a runtime
|
|
39
48
|
* condition we can recover from.
|
|
40
49
|
*/
|
|
41
|
-
export declare function resolveLakebasePool(context:
|
|
50
|
+
export declare function resolveLakebasePool(context: appkitUtils.PluginContextLike | undefined, caller: MastraPluginConfig): LakebasePool;
|
|
42
51
|
/**
|
|
43
52
|
* Construct a per-agent {@link Memory} factory. Caches the shared
|
|
44
53
|
* `PgVector` singleton (built on first need) and the lazily-resolved
|
|
45
54
|
* Lakebase pool so each agent build is O(1) after the first.
|
|
46
55
|
*/
|
|
47
|
-
export declare function createMemoryBuilder(config: MastraPluginConfig, context:
|
|
56
|
+
export declare function createMemoryBuilder(config: MastraPluginConfig, context: appkitUtils.PluginContextLike | undefined): MemoryBuilder;
|
|
48
57
|
/**
|
|
49
58
|
* Builds one `Memory` per agent. Per-instance state keeps the shared
|
|
50
59
|
* `PgVector` and the resolved Lakebase pool alive across calls so
|
|
@@ -55,13 +64,25 @@ export declare class MemoryBuilder {
|
|
|
55
64
|
private readonly context;
|
|
56
65
|
private sharedVector;
|
|
57
66
|
private pool;
|
|
58
|
-
constructor(config: MastraPluginConfig, context:
|
|
67
|
+
constructor(config: MastraPluginConfig, context: appkitUtils.PluginContextLike | undefined);
|
|
59
68
|
/**
|
|
60
69
|
* Build a `Memory` for `agentId` after the plugin/agent cascade.
|
|
61
70
|
* Returns `undefined` when the agent has neither storage nor a
|
|
62
71
|
* vector store enabled - Mastra accepts a missing `memory` field
|
|
63
72
|
* and treats the agent as stateless.
|
|
64
73
|
*/
|
|
74
|
+
/**
|
|
75
|
+
* Build the Mastra-instance-level storage used for workflow
|
|
76
|
+
* snapshots. Returns `undefined` when plugin-level `storage` is
|
|
77
|
+
* disabled, in which case `agent.resumeStream()` (and therefore
|
|
78
|
+
* the `requireApproval` flow) will not be available.
|
|
79
|
+
*
|
|
80
|
+
* The store lives in a dedicated `mastra_instance` schema so it
|
|
81
|
+
* never collides with per-agent `mastra_<agentId>` namespaces.
|
|
82
|
+
* Workflow snapshots are not per-agent state; they belong to the
|
|
83
|
+
* `Mastra` instance that owns the workflow execution.
|
|
84
|
+
*/
|
|
85
|
+
instanceStorage(): PostgresStore | undefined;
|
|
65
86
|
forAgent(agentId: string, def: MastraAgentDefinition): Memory | undefined;
|
|
66
87
|
private buildStorage;
|
|
67
88
|
/**
|
package/dist/src/memory.js
CHANGED
|
@@ -14,12 +14,20 @@
|
|
|
14
14
|
* index is almost always what users want; opt into per-agent recall
|
|
15
15
|
* by passing a {@link MastraMemoryConfigOverride} on the agent.
|
|
16
16
|
*
|
|
17
|
+
* Additionally, {@link MemoryBuilder.instanceStorage} returns a
|
|
18
|
+
* **Mastra-instance-level** `PostgresStore` (schema `mastra_instance`)
|
|
19
|
+
* used for workflow snapshots - the persistence layer
|
|
20
|
+
* `agent.resumeStream()` reads from when waking a suspended
|
|
21
|
+
* `requireApproval` tool call. Per-agent stores are not enough for
|
|
22
|
+
* this: workflow runs are scoped to the Mastra instance, not an
|
|
23
|
+
* individual agent's `Memory`.
|
|
24
|
+
*
|
|
17
25
|
* Plugin-level `config.storage` / `config.memory` act as the baseline
|
|
18
26
|
* (auto-defaulted to `true` in `plugin.ts` when the `lakebase` plugin
|
|
19
27
|
* is registered); per-agent settings cascade on top of that.
|
|
20
28
|
*/
|
|
21
29
|
import { lakebase } from "@databricks/appkit";
|
|
22
|
-
import {
|
|
30
|
+
import { appkitUtils, logUtils } from "@dbx-tools/shared";
|
|
23
31
|
import { fastembed } from "@mastra/fastembed";
|
|
24
32
|
import { Memory } from "@mastra/memory";
|
|
25
33
|
import { PgVector, PostgresStore } from "@mastra/pg";
|
|
@@ -46,7 +54,7 @@ export function needsLakebase(config) {
|
|
|
46
54
|
* condition we can recover from.
|
|
47
55
|
*/
|
|
48
56
|
export function resolveLakebasePool(context, caller) {
|
|
49
|
-
return
|
|
57
|
+
return appkitUtils.require(context, lakebase, caller).exports().pool;
|
|
50
58
|
}
|
|
51
59
|
/**
|
|
52
60
|
* Construct a per-agent {@link Memory} factory. Caches the shared
|
|
@@ -76,6 +84,30 @@ export class MemoryBuilder {
|
|
|
76
84
|
* vector store enabled - Mastra accepts a missing `memory` field
|
|
77
85
|
* and treats the agent as stateless.
|
|
78
86
|
*/
|
|
87
|
+
/**
|
|
88
|
+
* Build the Mastra-instance-level storage used for workflow
|
|
89
|
+
* snapshots. Returns `undefined` when plugin-level `storage` is
|
|
90
|
+
* disabled, in which case `agent.resumeStream()` (and therefore
|
|
91
|
+
* the `requireApproval` flow) will not be available.
|
|
92
|
+
*
|
|
93
|
+
* The store lives in a dedicated `mastra_instance` schema so it
|
|
94
|
+
* never collides with per-agent `mastra_<agentId>` namespaces.
|
|
95
|
+
* Workflow snapshots are not per-agent state; they belong to the
|
|
96
|
+
* `Mastra` instance that owns the workflow execution.
|
|
97
|
+
*/
|
|
98
|
+
instanceStorage() {
|
|
99
|
+
const setting = this.config.storage;
|
|
100
|
+
if (!setting)
|
|
101
|
+
return undefined;
|
|
102
|
+
if (typeof setting === "object") {
|
|
103
|
+
return new PostgresStore(withId(setting, "mastra-store__instance"));
|
|
104
|
+
}
|
|
105
|
+
return new PostgresStore({
|
|
106
|
+
id: "mastra-store__instance",
|
|
107
|
+
schemaName: "mastra_instance",
|
|
108
|
+
pool: this.requirePool(),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
79
111
|
forAgent(agentId, def) {
|
|
80
112
|
const storageSetting = def.storage ?? this.config.storage;
|
|
81
113
|
const memorySetting = def.memory ?? this.config.memory;
|
package/dist/src/model.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* (network blip, expired token at cache-fill time) we fall back to
|
|
25
25
|
* the input verbatim and let Databricks return the canonical error.
|
|
26
26
|
*/
|
|
27
|
-
import { commonUtils,
|
|
27
|
+
import { commonUtils, logUtils, netUtils, stringUtils, } from "@dbx-tools/shared";
|
|
28
28
|
import { MASTRA_USER_KEY } from "./config.js";
|
|
29
29
|
import { listServingEndpoints, MASTRA_MODEL_OVERRIDE_KEY, resolveModelId, resolveServingConfig, } from "./serving.js";
|
|
30
30
|
/**
|
|
@@ -325,7 +325,7 @@ const setupFetchInterceptor = commonUtils.memoize(() => {
|
|
|
325
325
|
const log = logUtils.logger("mastra/llm");
|
|
326
326
|
const original = globalThis.fetch.bind(globalThis);
|
|
327
327
|
globalThis.fetch = (async (input, init) => {
|
|
328
|
-
const url =
|
|
328
|
+
const url = netUtils.parseUrl(input);
|
|
329
329
|
if (!url ||
|
|
330
330
|
!url.pathname.startsWith(SERVING_ENDPOINTS_PATH_PREFIX) ||
|
|
331
331
|
typeof init?.body !== "string") {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mastra observability wired through the same OTel pipeline AppKit's
|
|
3
|
+
* built-in plugins (e.g. `agents`) use, via `@mastra/otel-bridge`.
|
|
4
|
+
*
|
|
5
|
+
* How traces flow:
|
|
6
|
+
*
|
|
7
|
+
* 1. `@databricks/appkit` boots a global `NodeSDK` in
|
|
8
|
+
* `TelemetryManager.initialize()` (during `createApp`) when
|
|
9
|
+
* `OTEL_EXPORTER_OTLP_ENDPOINT` is set in the process env.
|
|
10
|
+
* 2. Every AppKit plugin span (e.g. the `agents` plugin's
|
|
11
|
+
* `executeStream`) is created via the global OTel tracer
|
|
12
|
+
* (`trace.getTracer(<plugin>)`), so it lands on that NodeSDK and
|
|
13
|
+
* is shipped through its OTLP exporter.
|
|
14
|
+
* 3. The Mastra `OtelBridge` ALSO creates real OTel spans on the same
|
|
15
|
+
* global tracer for every Mastra operation (agent runs, model
|
|
16
|
+
* calls, tool invocations, workflow steps). They inherit the
|
|
17
|
+
* ambient OTel context, so when Mastra is invoked from inside an
|
|
18
|
+
* AppKit HTTP span the trace stays connected.
|
|
19
|
+
*
|
|
20
|
+
* Net effect: Mastra spans get exactly the treatment AppKit's
|
|
21
|
+
* `agents` plugin gets. No custom OTLP pipeline lives in this
|
|
22
|
+
* package; the OTLP endpoint, headers, and resource attributes are
|
|
23
|
+
* driven by the standard OTel env vars
|
|
24
|
+
* (`OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,
|
|
25
|
+
* `OTEL_SERVICE_NAME`, `OTEL_RESOURCE_ATTRIBUTES`, ...) and consumed
|
|
26
|
+
* by AppKit's `TelemetryManager`. Set those once and both AppKit and
|
|
27
|
+
* Mastra spans end up at the same backend.
|
|
28
|
+
*
|
|
29
|
+
* When `OTEL_EXPORTER_OTLP_ENDPOINT` is unset the bridge's spans go
|
|
30
|
+
* to the global noop tracer, mirroring how the `agents` plugin
|
|
31
|
+
* silently no-ops in the same situation.
|
|
32
|
+
*/
|
|
33
|
+
import { Observability } from "@mastra/observability";
|
|
34
|
+
export interface BuildObservabilityOptions {
|
|
35
|
+
/**
|
|
36
|
+
* Service name attached to the Mastra `Observability` config. Used
|
|
37
|
+
* as the tracer scope name on bridged OTel spans (the `service.name`
|
|
38
|
+
* resource attribute is owned by AppKit's `TelemetryManager` instead
|
|
39
|
+
* - it reads `OTEL_SERVICE_NAME` / `DATABRICKS_APP_NAME` at
|
|
40
|
+
* `createApp` time).
|
|
41
|
+
*
|
|
42
|
+
* Defaults to project name then `"mastra"`.
|
|
43
|
+
*/
|
|
44
|
+
serviceName?: string;
|
|
45
|
+
/**
|
|
46
|
+
* `RequestContext` keys to extract as span metadata on every Mastra
|
|
47
|
+
* trace. Defaults to {@link TRACE_REQUEST_CONTEXT_KEYS} (user id,
|
|
48
|
+
* thread id, request id, environment, model override, ...).
|
|
49
|
+
*
|
|
50
|
+
* Supports dot notation for nested values per the Mastra docs.
|
|
51
|
+
*/
|
|
52
|
+
requestContextKeys?: readonly string[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Build a Mastra `Observability` whose spans ride AppKit's global
|
|
56
|
+
* OTel pipeline via `@mastra/otel-bridge`.
|
|
57
|
+
*
|
|
58
|
+
* Returns `undefined` only if someone explicitly opts out in the
|
|
59
|
+
* future; today it always returns an `Observability` because the
|
|
60
|
+
* bridge degrades gracefully (no-op tracer) when no global OTel SDK
|
|
61
|
+
* is registered. Callers can spread `...(observability ? { observability } : {})`
|
|
62
|
+
* either way to stay forward-compatible.
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildObservability(options?: BuildObservabilityOptions): Promise<Observability | undefined>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mastra observability wired through the same OTel pipeline AppKit's
|
|
3
|
+
* built-in plugins (e.g. `agents`) use, via `@mastra/otel-bridge`.
|
|
4
|
+
*
|
|
5
|
+
* How traces flow:
|
|
6
|
+
*
|
|
7
|
+
* 1. `@databricks/appkit` boots a global `NodeSDK` in
|
|
8
|
+
* `TelemetryManager.initialize()` (during `createApp`) when
|
|
9
|
+
* `OTEL_EXPORTER_OTLP_ENDPOINT` is set in the process env.
|
|
10
|
+
* 2. Every AppKit plugin span (e.g. the `agents` plugin's
|
|
11
|
+
* `executeStream`) is created via the global OTel tracer
|
|
12
|
+
* (`trace.getTracer(<plugin>)`), so it lands on that NodeSDK and
|
|
13
|
+
* is shipped through its OTLP exporter.
|
|
14
|
+
* 3. The Mastra `OtelBridge` ALSO creates real OTel spans on the same
|
|
15
|
+
* global tracer for every Mastra operation (agent runs, model
|
|
16
|
+
* calls, tool invocations, workflow steps). They inherit the
|
|
17
|
+
* ambient OTel context, so when Mastra is invoked from inside an
|
|
18
|
+
* AppKit HTTP span the trace stays connected.
|
|
19
|
+
*
|
|
20
|
+
* Net effect: Mastra spans get exactly the treatment AppKit's
|
|
21
|
+
* `agents` plugin gets. No custom OTLP pipeline lives in this
|
|
22
|
+
* package; the OTLP endpoint, headers, and resource attributes are
|
|
23
|
+
* driven by the standard OTel env vars
|
|
24
|
+
* (`OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,
|
|
25
|
+
* `OTEL_SERVICE_NAME`, `OTEL_RESOURCE_ATTRIBUTES`, ...) and consumed
|
|
26
|
+
* by AppKit's `TelemetryManager`. Set those once and both AppKit and
|
|
27
|
+
* Mastra spans end up at the same backend.
|
|
28
|
+
*
|
|
29
|
+
* When `OTEL_EXPORTER_OTLP_ENDPOINT` is unset the bridge's spans go
|
|
30
|
+
* to the global noop tracer, mirroring how the `agents` plugin
|
|
31
|
+
* silently no-ops in the same situation.
|
|
32
|
+
*/
|
|
33
|
+
import { logUtils, projectUtils } from "@dbx-tools/shared";
|
|
34
|
+
import { Observability } from "@mastra/observability";
|
|
35
|
+
import { OtelBridge } from "@mastra/otel-bridge";
|
|
36
|
+
import { TRACE_REQUEST_CONTEXT_KEYS } from "./config.js";
|
|
37
|
+
const log = logUtils.logger("mastra/observability");
|
|
38
|
+
const DEFAULT_SERVICE_NAME = "mastra";
|
|
39
|
+
/**
|
|
40
|
+
* Build a Mastra `Observability` whose spans ride AppKit's global
|
|
41
|
+
* OTel pipeline via `@mastra/otel-bridge`.
|
|
42
|
+
*
|
|
43
|
+
* Returns `undefined` only if someone explicitly opts out in the
|
|
44
|
+
* future; today it always returns an `Observability` because the
|
|
45
|
+
* bridge degrades gracefully (no-op tracer) when no global OTel SDK
|
|
46
|
+
* is registered. Callers can spread `...(observability ? { observability } : {})`
|
|
47
|
+
* either way to stay forward-compatible.
|
|
48
|
+
*/
|
|
49
|
+
export async function buildObservability(options) {
|
|
50
|
+
const serviceName = options?.serviceName ??
|
|
51
|
+
(await projectUtils.name()) ??
|
|
52
|
+
DEFAULT_SERVICE_NAME;
|
|
53
|
+
const requestContextKeys = [
|
|
54
|
+
...(options?.requestContextKeys ?? TRACE_REQUEST_CONTEXT_KEYS),
|
|
55
|
+
];
|
|
56
|
+
// The OTel HTTP exporter treats `OTEL_EXPORTER_OTLP_ENDPOINT` as a
|
|
57
|
+
// *base* URL and appends the signal path itself (e.g.
|
|
58
|
+
// `http://localhost:6006` -> `http://localhost:6006/v1/traces`). Log
|
|
59
|
+
// the resolved POST URL so misconfigurations (e.g. accidentally
|
|
60
|
+
// setting the base var to a `/v1/traces`-suffixed URL, which makes
|
|
61
|
+
// the SDK POST to `.../v1/traces/v1/traces` and Phoenix 404s) are
|
|
62
|
+
// obvious in startup output.
|
|
63
|
+
const otelBase = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
64
|
+
const otelTracesOverride = process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT;
|
|
65
|
+
const resolvedTracesUrl = otelTracesOverride
|
|
66
|
+
? otelTracesOverride
|
|
67
|
+
: otelBase
|
|
68
|
+
? `${otelBase.replace(/\/+$/, "")}/v1/traces`
|
|
69
|
+
: undefined;
|
|
70
|
+
log.info("Mastra observability wired through OTel bridge", {
|
|
71
|
+
serviceName,
|
|
72
|
+
requestContextKeys,
|
|
73
|
+
otelBase: otelBase ?? "<unset>",
|
|
74
|
+
resolvedTracesUrl: resolvedTracesUrl ?? "<noop; OTLP endpoint unset>",
|
|
75
|
+
});
|
|
76
|
+
return new Observability({
|
|
77
|
+
configs: {
|
|
78
|
+
serviceName: {
|
|
79
|
+
serviceName,
|
|
80
|
+
bridge: new OtelBridge(),
|
|
81
|
+
requestContextKeys,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
package/dist/src/plugin.js
CHANGED
|
@@ -27,17 +27,18 @@
|
|
|
27
27
|
* AI SDK transport URL is `/api/mastra/route/chat/<agentId>`.
|
|
28
28
|
*/
|
|
29
29
|
import { genie, getExecutionContext, lakebase, Plugin, toPlugin, } from "@databricks/appkit";
|
|
30
|
-
import {
|
|
30
|
+
import { appkitUtils, logUtils } from "@dbx-tools/shared";
|
|
31
31
|
import { chatRoute } from "@mastra/ai-sdk";
|
|
32
32
|
import { Mastra } from "@mastra/core/mastra";
|
|
33
33
|
import express from "express";
|
|
34
34
|
import { buildAgents, FALLBACK_AGENT_ID } from "./agents.js";
|
|
35
35
|
import { historyRoute } from "./history.js";
|
|
36
36
|
import { createMemoryBuilder, needsLakebase } from "./memory.js";
|
|
37
|
+
import { buildObservability } from "./observability.js";
|
|
37
38
|
import { attachRoutePatchMiddleware, MastraServer } from "./server.js";
|
|
38
39
|
import { clearServingEndpointsCache, listServingEndpoints, resolveServingConfig, } from "./serving.js";
|
|
39
|
-
const GENIE_MANIFEST =
|
|
40
|
-
const LAKEBASE_MANIFEST =
|
|
40
|
+
const GENIE_MANIFEST = appkitUtils.data(genie).plugin.manifest;
|
|
41
|
+
const LAKEBASE_MANIFEST = appkitUtils.data(lakebase).plugin.manifest;
|
|
41
42
|
/**
|
|
42
43
|
* AppKit plugin (registered name: `mastra`) that hosts Mastra agents
|
|
43
44
|
* with optional Lakebase-backed memory and AI SDK chat routes under
|
|
@@ -53,6 +54,14 @@ export class MastraPlugin extends Plugin {
|
|
|
53
54
|
resources: {
|
|
54
55
|
required: [],
|
|
55
56
|
optional: [
|
|
57
|
+
// Surface the Genie resource binding (space id) declared by
|
|
58
|
+
// AppKit's `genie` plugin manifest. The Mastra plugin no
|
|
59
|
+
// longer uses the genie plugin's tools at runtime - the
|
|
60
|
+
// built-in Genie agent talks to Genie directly via
|
|
61
|
+
// `@dbx-tools/genie` - but reusing the manifest keeps the
|
|
62
|
+
// resource-binding shape identical to AppKit's so existing
|
|
63
|
+
// `app.yaml` configs and `genie({ spaces })` wiring keep
|
|
64
|
+
// working without change.
|
|
56
65
|
...GENIE_MANIFEST.resources.required,
|
|
57
66
|
...LAKEBASE_MANIFEST.resources.required,
|
|
58
67
|
],
|
|
@@ -98,7 +107,7 @@ export class MastraPlugin extends Plugin {
|
|
|
98
107
|
* already in the registry by the time this fires.
|
|
99
108
|
*/
|
|
100
109
|
applyLakebaseAutoDefaults() {
|
|
101
|
-
const hasLakebase =
|
|
110
|
+
const hasLakebase = appkitUtils.instance(this.context, lakebase) !== undefined;
|
|
102
111
|
if (!hasLakebase)
|
|
103
112
|
return;
|
|
104
113
|
if (this.config.storage === undefined)
|
|
@@ -236,7 +245,25 @@ export class MastraPlugin extends Plugin {
|
|
|
236
245
|
// dev server. Since we're hosting Mastra inside our own Express
|
|
237
246
|
// subapp via `@mastra/express`, custom routes must be passed to
|
|
238
247
|
// the `MastraServer` constructor directly.
|
|
239
|
-
|
|
248
|
+
//
|
|
249
|
+
// `storage` here is *Mastra-instance-level* and persists workflow
|
|
250
|
+
// snapshots (where suspended `requireApproval` tool calls live).
|
|
251
|
+
// It's separate from each agent's `Memory.storage`, which only
|
|
252
|
+
// covers thread / message history. Without it,
|
|
253
|
+
// `agent.resumeStream()` errors with "could not find a suspended
|
|
254
|
+
// run" and the approval UI hangs after the user clicks Approve.
|
|
255
|
+
const instanceStorage = memoryBuilder?.instanceStorage();
|
|
256
|
+
// Wire Mastra's tracer into AppKit's global OTel pipeline via
|
|
257
|
+
// `@mastra/otel-bridge`. Mastra spans become native OTel spans on
|
|
258
|
+
// whatever tracer provider `TelemetryManager` registered during
|
|
259
|
+
// `createApp`, so the OTLP endpoint / headers / sampling are
|
|
260
|
+
// env-driven and shared with every other AppKit plugin.
|
|
261
|
+
const observability = await buildObservability({ serviceName: this.name });
|
|
262
|
+
this.mastra = new Mastra({
|
|
263
|
+
agents: this.built.agents,
|
|
264
|
+
...(instanceStorage ? { storage: instanceStorage } : {}),
|
|
265
|
+
...(observability ? { observability } : {}),
|
|
266
|
+
});
|
|
240
267
|
this.mastraApp = express();
|
|
241
268
|
attachRoutePatchMiddleware(this.mastraApp);
|
|
242
269
|
this.mastraServer = new MastraServer(this.config, {
|
|
@@ -246,8 +273,11 @@ export class MastraPlugin extends Plugin {
|
|
|
246
273
|
customApiRoutes: [
|
|
247
274
|
chatRoute({ path: "/route/chat", agent: this.built.defaultAgentId }),
|
|
248
275
|
chatRoute({ path: "/route/chat/:agentId" }),
|
|
249
|
-
historyRoute
|
|
250
|
-
|
|
276
|
+
// `historyRoute` registers both GET (load) and DELETE
|
|
277
|
+
// (clear) on the same path, so it returns an array we
|
|
278
|
+
// splice in.
|
|
279
|
+
...historyRoute({ path: "/route/history", agent: this.built.defaultAgentId }),
|
|
280
|
+
...historyRoute({ path: "/route/history/:agentId" }),
|
|
251
281
|
],
|
|
252
282
|
});
|
|
253
283
|
await this.mastraServer.init();
|
|
@@ -255,6 +285,8 @@ export class MastraPlugin extends Plugin {
|
|
|
255
285
|
agents: Object.keys(this.built.agents),
|
|
256
286
|
defaultAgent: this.built.defaultAgentId,
|
|
257
287
|
routes: ["/route/chat", "/route/history", "/models"],
|
|
288
|
+
instanceStorage: instanceStorage !== undefined,
|
|
289
|
+
observability: observability !== undefined ? "mlflow" : "off",
|
|
258
290
|
});
|
|
259
291
|
}
|
|
260
292
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* `datasets[].chartId` and `render_data`'s top-level `chartId`
|
|
20
20
|
* uniformly without coupling to specific tool ids.
|
|
21
21
|
*/
|
|
22
|
-
import { logUtils } from "@dbx-tools/
|
|
22
|
+
import { logUtils } from "@dbx-tools/shared";
|
|
23
23
|
const log = logUtils.logger("mastra/processor/strip-stale-charts");
|
|
24
24
|
/**
|
|
25
25
|
* Recursively clone `value`, omitting any property whose key is
|
package/dist/src/server.d.ts
CHANGED
|
@@ -19,6 +19,18 @@ export declare class MastraServer extends MastraServerExpress {
|
|
|
19
19
|
constructor(config: MastraPluginConfig, ...args: ConstructorParameters<typeof MastraServerExpress>);
|
|
20
20
|
registerAuthMiddleware(): void;
|
|
21
21
|
configureRequestContextUser(requestContext: RequestContext): void;
|
|
22
|
+
/**
|
|
23
|
+
* Stamp a per-request id and echo it on the response so an upstream
|
|
24
|
+
* proxy / curl client / browser-side log line can pair its view of
|
|
25
|
+
* the request with the matching trace span. Reuses `X-Request-Id`
|
|
26
|
+
* when the upstream already supplies one so multi-hop traces stay
|
|
27
|
+
* joined; otherwise mints a UUIDv4.
|
|
28
|
+
*
|
|
29
|
+
* The id is surfaced as `mastra__requestId` span metadata via
|
|
30
|
+
* {@link TRACE_REQUEST_CONTEXT_KEYS} and as the `X-Request-Id`
|
|
31
|
+
* response header so dev tools can copy it from either side.
|
|
32
|
+
*/
|
|
33
|
+
configureRequestContextRequestId(req: express.Request, res: express.Response, requestContext: RequestContext): void;
|
|
22
34
|
configureRequestContextThreadId(req: express.Request, res: express.Response, requestContext: RequestContext): void;
|
|
23
35
|
configureRequestContextModelOverride(req: express.Request, requestContext: RequestContext): void;
|
|
24
36
|
}
|