@dbx-tools/appkit-mastra 0.1.58 → 0.1.68

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.
Files changed (51) hide show
  1. package/README.md +41 -1
  2. package/dist/index.d.ts +1229 -17
  3. package/dist/index.js +3348 -19
  4. package/package.json +17 -30
  5. package/dist/src/agents.d.ts +0 -316
  6. package/dist/src/agents.js +0 -470
  7. package/dist/src/chart.d.ts +0 -153
  8. package/dist/src/chart.js +0 -570
  9. package/dist/src/config.d.ts +0 -295
  10. package/dist/src/config.js +0 -55
  11. package/dist/src/genie.d.ts +0 -161
  12. package/dist/src/genie.js +0 -973
  13. package/dist/src/history.d.ts +0 -95
  14. package/dist/src/history.js +0 -278
  15. package/dist/src/memory.d.ts +0 -109
  16. package/dist/src/memory.js +0 -253
  17. package/dist/src/model.d.ts +0 -52
  18. package/dist/src/model.js +0 -198
  19. package/dist/src/observability.d.ts +0 -64
  20. package/dist/src/observability.js +0 -83
  21. package/dist/src/plugin.d.ts +0 -177
  22. package/dist/src/plugin.js +0 -554
  23. package/dist/src/processor.d.ts +0 -8
  24. package/dist/src/processor.js +0 -40
  25. package/dist/src/processors/strip-stale-charts.d.ts +0 -32
  26. package/dist/src/processors/strip-stale-charts.js +0 -98
  27. package/dist/src/server.d.ts +0 -51
  28. package/dist/src/server.js +0 -152
  29. package/dist/src/serving.d.ts +0 -65
  30. package/dist/src/serving.js +0 -79
  31. package/dist/src/statement.d.ts +0 -66
  32. package/dist/src/statement.js +0 -111
  33. package/dist/src/writer.d.ts +0 -23
  34. package/dist/src/writer.js +0 -37
  35. package/dist/tsconfig.build.tsbuildinfo +0 -1
  36. package/index.ts +0 -27
  37. package/src/agents.ts +0 -772
  38. package/src/chart.ts +0 -716
  39. package/src/config.ts +0 -320
  40. package/src/genie.ts +0 -1123
  41. package/src/history.ts +0 -322
  42. package/src/memory.ts +0 -293
  43. package/src/model.ts +0 -257
  44. package/src/observability.ts +0 -114
  45. package/src/plugin.ts +0 -623
  46. package/src/processor.ts +0 -46
  47. package/src/processors/strip-stale-charts.ts +0 -102
  48. package/src/server.ts +0 -195
  49. package/src/serving.ts +0 -104
  50. package/src/statement.ts +0 -120
  51. package/src/writer.ts +0 -44
package/src/history.ts DELETED
@@ -1,322 +0,0 @@
1
- /**
2
- * Thread history loader exposed as a Mastra custom API route.
3
- *
4
- * Backed entirely by native Mastra: looks up the active agent by id,
5
- * asks its `Memory` instance to `recall` a page of `MastraDBMessage`s,
6
- * and converts the result to AI SDK V5 `UIMessage`s with the official
7
- * {@link toAISdkV5Messages} helper from `@mastra/ai-sdk/ui`. No direct
8
- * database reads.
9
- *
10
- * The route is registered through {@link historyRoute} as a Mastra
11
- * `registerApiRoute` so it sits in the same dispatcher pipeline as
12
- * the standard agent routes. That means the `MastraServer` auth
13
- * middleware (in `./server.ts`) has already populated `RequestContext` with
14
- * `MASTRA_THREAD_ID_KEY` and `MASTRA_RESOURCE_ID_KEY` by the time
15
- * the handler runs - no cookie or user lookups happen here, and the
16
- * session-cookie logic stays the single source of truth in `server.ts`.
17
- */
18
-
19
- import type {
20
- MastraClearHistoryResponse,
21
- MastraHistoryResponse,
22
- MastraHistoryUIMessage,
23
- } from "@dbx-tools/appkit-mastra-shared";
24
- import { commonUtils, logUtils } from "@dbx-tools/shared";
25
- import { toAISdkV5Messages } from "@mastra/ai-sdk/ui";
26
- import type { Agent } from "@mastra/core/agent";
27
- import type { MastraDBMessage } from "@mastra/core/agent/message-list";
28
- import {
29
- MASTRA_RESOURCE_ID_KEY,
30
- MASTRA_THREAD_ID_KEY,
31
- } from "@mastra/core/request-context";
32
- import type { ContextWithMastra } from "@mastra/core/server";
33
- import { registerApiRoute } from "@mastra/core/server";
34
-
35
- const log = logUtils.logger("mastra/history");
36
-
37
- /** Default history page size; matches the Mastra storage default. */
38
- const DEFAULT_PER_PAGE = 20;
39
- /** Hard cap so a misbehaving client can't fetch the whole thread at once. */
40
- const MAX_PER_PAGE = 200;
41
-
42
- /** Inputs accepted by {@link loadHistory}. */
43
- export interface LoadHistoryOptions {
44
- agent: Agent;
45
- threadId: string;
46
- resourceId?: string;
47
- page?: number;
48
- perPage?: number;
49
- /** When true, returns the *oldest* page first (chronological). */
50
- ascending?: boolean;
51
- }
52
-
53
- /**
54
- * Fetch a page of UI-formatted messages for a thread.
55
- *
56
- * Uses the agent's resolved `Memory` (`getMemory()`) so per-agent
57
- * storage namespaces (`mastra_<agentId>` schemas) and any future
58
- * memory-side filters apply automatically. When the agent has no
59
- * memory configured the response is a successful empty page so
60
- * callers don't have to special-case stateless agents.
61
- *
62
- * Pagination is descending-by-default: page 0 is the most recent
63
- * page, page 1 the page before that, etc. The returned `uiMessages`
64
- * are always re-sorted into chronological order (oldest -> newest)
65
- * so the client can prepend them above the existing transcript
66
- * without sorting locally.
67
- */
68
- export async function loadHistory(
69
- opts: LoadHistoryOptions,
70
- ): Promise<MastraHistoryResponse> {
71
- const perPage = clampPerPage(opts.perPage);
72
- const page = Math.max(0, Math.trunc(opts.page ?? 0));
73
- const memory = await opts.agent.getMemory();
74
- if (!memory) {
75
- log.debug("recall:no-memory", { agentId: opts.agent.id, threadId: opts.threadId });
76
- return { uiMessages: [], page, perPage, total: 0, hasMore: false };
77
- }
78
- const startedAt = Date.now();
79
- const result = await memory.recall({
80
- threadId: opts.threadId,
81
- ...(opts.resourceId ? { resourceId: opts.resourceId } : {}),
82
- page,
83
- perPage,
84
- orderBy: {
85
- field: "createdAt",
86
- direction: opts.ascending ? "ASC" : "DESC",
87
- },
88
- });
89
- const chronological = sortChronological(result.messages);
90
- const uiMessages = toAISdkV5Messages(
91
- chronological,
92
- ) as unknown as MastraHistoryUIMessage[];
93
- log.debug("recall:done", {
94
- agentId: opts.agent.id,
95
- threadId: opts.threadId,
96
- page,
97
- perPage,
98
- returned: uiMessages.length,
99
- total: result.total,
100
- hasMore: result.hasMore,
101
- elapsedMs: Date.now() - startedAt,
102
- });
103
- return {
104
- uiMessages,
105
- page,
106
- perPage,
107
- total: result.total,
108
- hasMore: result.hasMore,
109
- };
110
- }
111
-
112
- /** Inputs accepted by {@link clearHistory}. */
113
- export interface ClearHistoryOptions {
114
- agent: Agent;
115
- threadId: string;
116
- }
117
-
118
- /**
119
- * Wipe every persisted message tied to a thread. Returns the count
120
- * of messages that were on the thread at delete time so the caller
121
- * can render a "cleared N messages" affordance without an
122
- * additional round-trip.
123
- *
124
- * Agents without a configured `Memory` resolve to a no-op (count
125
- * 0), matching {@link loadHistory}'s "stateless agents return an
126
- * empty page" stance so callers don't have to special-case them.
127
- * Threads that don't exist yet are also a successful no-op - the
128
- * operation is idempotent so the UI can fire-and-forget without
129
- * tracking thread existence.
130
- */
131
- export async function clearHistory(
132
- opts: ClearHistoryOptions,
133
- ): Promise<{ cleared: number }> {
134
- const memory = await opts.agent.getMemory();
135
- if (!memory) {
136
- log.debug("clear:no-memory", { agentId: opts.agent.id, threadId: opts.threadId });
137
- return { cleared: 0 };
138
- }
139
- // Mastra's `deleteThread` cascades to the message table, so we
140
- // can't ask for a count after the fact. Read it pre-delete with a
141
- // one-page recall sized to fit common threads in a single round
142
- // trip; the value is for telemetry / UI, not correctness.
143
- let cleared = 0;
144
- try {
145
- const probe = await memory.recall({
146
- threadId: opts.threadId,
147
- page: 0,
148
- perPage: 1,
149
- });
150
- cleared = probe.total;
151
- } catch (err) {
152
- // A missing-thread error is the happy-path "nothing to count";
153
- // every other error is logged but doesn't block the delete.
154
- log.debug("clear:probe-failed", {
155
- agentId: opts.agent.id,
156
- threadId: opts.threadId,
157
- error: commonUtils.errorMessage(err),
158
- });
159
- }
160
-
161
- const startedAt = Date.now();
162
- try {
163
- await memory.deleteThread(opts.threadId);
164
- } catch (err) {
165
- // Mastra's `deleteThread` raises when the thread row was never
166
- // created (e.g. clearing an empty session). Surface as a soft
167
- // warn and treat as success - the user-facing semantic is
168
- // "history is now empty" which is already true.
169
- log.warn("clear:delete-soft-failed", {
170
- agentId: opts.agent.id,
171
- threadId: opts.threadId,
172
- error: commonUtils.errorMessage(err),
173
- });
174
- }
175
- log.info("clear:done", {
176
- agentId: opts.agent.id,
177
- threadId: opts.threadId,
178
- cleared,
179
- elapsedMs: Date.now() - startedAt,
180
- });
181
- return { cleared };
182
- }
183
-
184
- /** Options accepted by {@link historyRoute}. */
185
- export type HistoryRouteOptions =
186
- | { path: `${string}:agentId${string}`; agent?: never }
187
- | { path: string; agent: string };
188
-
189
- /**
190
- * Register the `<path>` Mastra custom API route. Handles two
191
- * methods on the same mount:
192
- *
193
- * - `GET`: return a page of AI SDK V5 `UIMessage`s for the
194
- * caller's current thread ({@link loadHistory}).
195
- * - `DELETE`: wipe every persisted message on the caller's
196
- * thread ({@link clearHistory}). The session cookie that
197
- * anchors the thread id is left alone so the user keeps the
198
- * same thread - only the contents go away.
199
- *
200
- * Follows the `@mastra/ai-sdk` `chatRoute` convention for agent
201
- * binding: pass `agent` for a fixed-agent mount, or include
202
- * `:agentId` in the path for dynamic routing. The plugin registers
203
- * both `/route/history` (default agent) and `/route/history/:agentId`.
204
- *
205
- * The handler reads `threadId` and `resourceId` from `RequestContext`
206
- * (populated upstream by `MastraServer.registerAuthMiddleware`), so
207
- * no cookie or user lookups happen here.
208
- */
209
- export function historyRoute(options: HistoryRouteOptions) {
210
- const { path } = options;
211
- const fixedAgent = "agent" in options ? options.agent : undefined;
212
- if (!fixedAgent && !path.includes(":agentId")) {
213
- throw new Error(
214
- "historyRoute path must include `:agentId` or `agent` must be passed explicitly",
215
- );
216
- }
217
- // Tiny resolver shared by GET / DELETE: derive the active agent
218
- // and thread id, returning a JSON error response when either is
219
- // missing. Keeps both handlers thin and gives them identical
220
- // validation behaviour.
221
- const resolveContext = (c: ContextWithMastra) => {
222
- const mastra = c.get("mastra");
223
- const requestContext = c.get("requestContext");
224
- const agentId = fixedAgent ?? c.req.param("agentId");
225
- if (!agentId) {
226
- return { error: c.json({ error: "agentId is required" }, 400) } as const;
227
- }
228
- const agent = mastra.getAgentById(agentId);
229
- if (!agent) {
230
- return {
231
- error: c.json({ error: `Unknown agent "${agentId}"` }, 404),
232
- } as const;
233
- }
234
- const threadId = requestContext.get(MASTRA_THREAD_ID_KEY) as string | undefined;
235
- if (!threadId) {
236
- return {
237
- error: c.json({ error: "thread id missing from request context" }, 400),
238
- } as const;
239
- }
240
- const resourceId = requestContext.get(MASTRA_RESOURCE_ID_KEY) as string | undefined;
241
- return { agentId, agent, threadId, resourceId } as const;
242
- };
243
-
244
- return [
245
- registerApiRoute(path, {
246
- method: "GET",
247
- handler: async (c: ContextWithMastra) => {
248
- const ctx = resolveContext(c);
249
- if ("error" in ctx) return ctx.error;
250
- const payload = await loadHistory({
251
- agent: ctx.agent,
252
- threadId: ctx.threadId,
253
- ...(ctx.resourceId ? { resourceId: ctx.resourceId } : {}),
254
- page: parseIntParam(c.req.query("page")),
255
- perPage: parseIntParam(c.req.query("perPage")),
256
- });
257
- return c.json(payload);
258
- },
259
- }),
260
- registerApiRoute(path, {
261
- method: "DELETE",
262
- handler: async (c: ContextWithMastra) => {
263
- const ctx = resolveContext(c);
264
- if ("error" in ctx) return ctx.error;
265
- const { cleared } = await clearHistory({
266
- agent: ctx.agent,
267
- threadId: ctx.threadId,
268
- });
269
- const payload: MastraClearHistoryResponse = {
270
- ok: true,
271
- agentId: ctx.agentId,
272
- threadId: ctx.threadId,
273
- cleared,
274
- };
275
- return c.json(payload);
276
- },
277
- }),
278
- ];
279
- }
280
-
281
- /** Coerce / clamp `perPage`; falls back to the page-size default. */
282
- function clampPerPage(value: number | undefined): number {
283
- if (value === undefined || Number.isNaN(value)) return DEFAULT_PER_PAGE;
284
- const n = Math.trunc(value);
285
- if (n <= 0) return DEFAULT_PER_PAGE;
286
- return Math.min(n, MAX_PER_PAGE);
287
- }
288
-
289
- /**
290
- * Sort messages oldest-first by `createdAt`, falling back to whatever
291
- * order the storage returned them in. The native `recall` call honors
292
- * `orderBy` but doesn't guarantee a stable secondary sort, so we
293
- * normalize here before handing the page to the AI SDK converter.
294
- */
295
- function sortChronological(messages: MastraDBMessage[]): MastraDBMessage[] {
296
- return [...messages].sort((a, b) => {
297
- const ta = toEpoch(a.createdAt);
298
- const tb = toEpoch(b.createdAt);
299
- return ta - tb;
300
- });
301
- }
302
-
303
- function toEpoch(value: unknown): number {
304
- if (value instanceof Date) return value.getTime();
305
- if (typeof value === "string" || typeof value === "number") {
306
- const parsed = new Date(value).getTime();
307
- return Number.isNaN(parsed) ? 0 : parsed;
308
- }
309
- return 0;
310
- }
311
-
312
- /**
313
- * Coerce a Hono query value into a non-negative integer. Returns
314
- * `undefined` for empty / non-numeric / negative inputs so
315
- * {@link loadHistory} can apply its built-in defaults.
316
- */
317
- function parseIntParam(value: string | undefined): number | undefined {
318
- if (!value) return undefined;
319
- const n = Number(value);
320
- if (!Number.isFinite(n) || n < 0) return undefined;
321
- return Math.trunc(n);
322
- }
package/src/memory.ts DELETED
@@ -1,293 +0,0 @@
1
- /**
2
- * Lakebase-backed Mastra memory wiring.
3
- *
4
- * Provides a {@link MemoryBuilder} that mints one `Memory` per agent
5
- * with two independent knobs:
6
- *
7
- * - **Storage** (threads / messages via `PostgresStore`): defaults to
8
- * **per-agent** namespacing via `schemaName: "mastra_<agentId>"` so
9
- * conversation history stays isolated between agents in the same
10
- * database. `PostgresStore` auto-creates the schema with
11
- * `CREATE SCHEMA IF NOT EXISTS` on init.
12
- * - **Memory** (semantic recall via `PgVector`): defaults to a single
13
- * **shared** instance across every agent. Cross-agent recall on one
14
- * index is almost always what users want; opt into per-agent recall
15
- * by passing a {@link MastraMemoryConfigOverride} on the agent.
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
- *
25
- * Plugin-level `config.storage` / `config.memory` act as the baseline
26
- * (auto-defaulted to `true` in `plugin.ts` when the `lakebase` plugin
27
- * is registered); per-agent settings cascade on top of that.
28
- */
29
-
30
- import { getUsernameWithApiLookup } from "@databricks/appkit";
31
- import { logUtils } from "@dbx-tools/shared";
32
- import { fastembed } from "@mastra/fastembed";
33
- import { Memory } from "@mastra/memory";
34
- import { PgVector, PostgresStore } from "@mastra/pg";
35
- import { randomUUID } from "node:crypto";
36
- import { Pool, type PoolConfig } from "pg";
37
-
38
- import type { MastraAgentDefinition, MastraMemoryConfigOverride } from "./agents.js";
39
- import type { MastraPluginConfig } from "./config.js";
40
-
41
- const log = logUtils.logger("mastra/memory");
42
-
43
- /**
44
- * Build a dedicated **service-principal** Lakebase pool for Mastra
45
- * memory from the lakebase plugin's resolved SP pg config.
46
- *
47
- * The plugin's `exports().pool` is a `RoutingPool` that switches to
48
- * the per-user (OBO) pool whenever a query runs inside an `asUser`
49
- * scope - exactly the context the mastra plugin establishes around
50
- * every chat turn. Memory (threads / messages + semantic recall) must
51
- * instead always act as the app service principal: it owns the
52
- * auto-created `mastra_*` schemas (a per-user role usually can't
53
- * `CREATE SCHEMA`) and is shared across users, so it cannot inherit a
54
- * request's OBO identity.
55
- *
56
- * `pgConfig` must be the plugin's `exports().getPgConfig()` evaluated
57
- * **outside** any `asUser` scope (i.e. during setup), so it carries
58
- * the SP connection target, OAuth token-refresh `password` callback,
59
- * and any `lakebase({ pool })` tuning overrides - all of which this
60
- * pool inherits. See the call site in `plugin.ts`.
61
- */
62
- export async function createServicePrincipalPool(pgConfig: PoolConfig): Promise<Pool> {
63
- // `getPgConfig()` resolves the SP username synchronously from
64
- // `PGUSER` / `DATABRICKS_CLIENT_ID`; fall back to the async API
65
- // lookup (e.g. local dev authenticating via PAT) so the pool always
66
- // has an identity to connect with.
67
- const user = pgConfig.user ?? (await getUsernameWithApiLookup());
68
- return new Pool({ ...pgConfig, user });
69
- }
70
-
71
- /** Effective per-knob setting after the plugin/agent cascade. */
72
- type StorageSetting = MastraAgentDefinition["storage"];
73
- type MemorySetting = MastraAgentDefinition["memory"];
74
-
75
- /**
76
- * True when any plugin-level or per-agent setting could need the
77
- * Lakebase pool. Used by `plugin.ts` to gate creation of the
78
- * service-principal pool and the {@link MemoryBuilder} that consumes
79
- * it; when false neither is built.
80
- */
81
- export function needsLakebase(config: MastraPluginConfig): boolean {
82
- if (settingNeedsSharedPool(config.storage)) return true;
83
- if (settingNeedsSharedPool(config.memory)) return true;
84
- const defs = collectAgentDefinitions(config);
85
- return defs.some(
86
- (d) => settingNeedsSharedPool(d.storage) || settingNeedsSharedPool(d.memory),
87
- );
88
- }
89
-
90
- /**
91
- * Construct a per-agent {@link Memory} factory bound to the supplied
92
- * service-principal pool (see {@link createServicePrincipalPool}).
93
- * Caches the shared `PgVector` singleton (built on first need) so each
94
- * agent build is O(1) after the first.
95
- */
96
- export function createMemoryBuilder(
97
- config: MastraPluginConfig,
98
- servicePrincipalPool: Pool,
99
- ): MemoryBuilder {
100
- return new MemoryBuilder(config, servicePrincipalPool);
101
- }
102
-
103
- /**
104
- * Builds one `Memory` per agent against a shared service-principal
105
- * Lakebase pool. Per-instance state keeps the shared `PgVector` alive
106
- * across calls so registering N agents stays cheap.
107
- */
108
- export class MemoryBuilder {
109
- private sharedVector: PgVector | undefined;
110
-
111
- constructor(
112
- private readonly config: MastraPluginConfig,
113
- private readonly servicePrincipalPool: Pool,
114
- ) {}
115
-
116
- /**
117
- * Build a `Memory` for `agentId` after the plugin/agent cascade.
118
- * Returns `undefined` when the agent has neither storage nor a
119
- * vector store enabled - Mastra accepts a missing `memory` field
120
- * and treats the agent as stateless.
121
- */
122
- /**
123
- * Build the Mastra-instance-level storage used for workflow
124
- * snapshots. Returns `undefined` when plugin-level `storage` is
125
- * disabled, in which case `agent.resumeStream()` (and therefore
126
- * the `requireApproval` flow) will not be available.
127
- *
128
- * The store lives in a dedicated `mastra_instance` schema so it
129
- * never collides with per-agent `mastra_<agentId>` namespaces.
130
- * Workflow snapshots are not per-agent state; they belong to the
131
- * `Mastra` instance that owns the workflow execution.
132
- */
133
- instanceStorage(): PostgresStore | undefined {
134
- const setting = this.config.storage;
135
- if (!setting) return undefined;
136
- if (typeof setting === "object") {
137
- return new PostgresStore(
138
- withId(setting, "mastra-store__instance") as ConstructorParameters<
139
- typeof PostgresStore
140
- >[0],
141
- );
142
- }
143
- return new PostgresStore({
144
- id: "mastra-store__instance",
145
- schemaName: "mastra_instance",
146
- pool: this.servicePrincipalPool,
147
- });
148
- }
149
-
150
- forAgent(agentId: string, def: MastraAgentDefinition): Memory | undefined {
151
- const storageSetting = def.storage ?? this.config.storage;
152
- const memorySetting = def.memory ?? this.config.memory;
153
-
154
- const storage = this.buildStorage(agentId, storageSetting);
155
- const vector = this.buildVector(memorySetting);
156
- if (!storage && !vector) {
157
- log.debug("agent:stateless", { agentId });
158
- return undefined;
159
- }
160
-
161
- log.debug("agent:configured", {
162
- agentId,
163
- storage: storage !== undefined,
164
- vector: vector !== undefined,
165
- vectorMode:
166
- vector === undefined
167
- ? "off"
168
- : typeof memorySetting === "object"
169
- ? "dedicated"
170
- : "shared",
171
- });
172
-
173
- return new Memory({
174
- ...(storage ? { storage } : {}),
175
- ...(vector ? { vector, embedder: fastembed } : {}),
176
- options: {
177
- lastMessages: 10,
178
- ...(vector ? { semanticRecall: { topK: 3, messageRange: 2 } } : {}),
179
- },
180
- });
181
- }
182
-
183
- private buildStorage(
184
- agentId: string,
185
- setting: StorageSetting,
186
- ): PostgresStore | undefined {
187
- if (!setting) return undefined;
188
- if (typeof setting === "boolean") {
189
- return new PostgresStore({
190
- id: `mastra-store__${agentId}`,
191
- schemaName: `mastra_${agentId}`,
192
- pool: this.servicePrincipalPool,
193
- });
194
- }
195
- // Cast: `withId` guarantees `id` is set, but the distributive
196
- // Omit + `id?: string` shape doesn't structurally narrow to the
197
- // discriminated union members. Runtime shape is identical.
198
- return new PostgresStore(
199
- withId(setting, `mastra-store__${agentId}`) as ConstructorParameters<
200
- typeof PostgresStore
201
- >[0],
202
- );
203
- }
204
-
205
- /**
206
- * Resolve the agent's vector store. Cascade:
207
- *
208
- * - falsy: no vector.
209
- * - `boolean` / `undefined-inheriting-true`: return the shared
210
- * singleton (built lazily on first call). All agents that
211
- * default-enable memory write into and recall from one index.
212
- * - object: build a dedicated `PgVector` for this agent.
213
- */
214
- private buildVector(setting: MemorySetting): PgVector | undefined {
215
- if (!setting) return undefined;
216
- if (typeof setting === "boolean") return this.getSharedVector();
217
- return buildPgVector(setting);
218
- }
219
-
220
- private getSharedVector(): PgVector {
221
- if (!this.sharedVector) {
222
- this.sharedVector = buildSharedPgVector(this.servicePrincipalPool);
223
- }
224
- return this.sharedVector;
225
- }
226
- }
227
-
228
- /**
229
- * Build the shared `PgVector` that backs the default
230
- * `def.memory === true` case across every agent.
231
- *
232
- * `PgVector`'s constructor accepts only connection-style configs
233
- * (`HostConfig` / `ConnectionStringConfig` / `ClientConfig`); there is
234
- * no `{ pool }` shorthand the way `PostgresStore` has one. Worse, the
235
- * constructor synchronously kicks off a `cacheWarmupPromise` IIFE that
236
- * calls `this.pool.connect()` before returning, so we can't cleanly
237
- * hand it an inert config and patch the pool afterwards.
238
- *
239
- * The trick: pass illegal-but-validation-passing placeholders so the
240
- * warmup's `net.connect()` rejects synchronously with `RangeError`
241
- * (Node validates `0 <= port < 65536`). The IIFE's `catch {}` swallows
242
- * it, no DNS lookup or TCP attempt happens, and we then swap
243
- * `pgVector.pool` to the lakebase pool. Every subsequent `PgVector`
244
- * method reads `this.pool` at call time, so all real I/O goes through
245
- * the lakebase pool from then on. The placeholder pool is `.end()`'d
246
- * so its socket book-keeping is released.
247
- */
248
- function buildSharedPgVector(pool: Pool): PgVector {
249
- const vector = new PgVector({
250
- id: `pg${randomUUID()}`,
251
- host: "-1",
252
- port: -1,
253
- database: "_",
254
- user: "_",
255
- password: "_",
256
- });
257
- const placeholder = vector.pool;
258
- vector.pool = pool;
259
- void placeholder.end().catch(() => undefined);
260
- return vector;
261
- }
262
-
263
- /** Per-agent dedicated `PgVector` (rare; opt-in via object override). */
264
- function buildPgVector(setting: MastraMemoryConfigOverride): PgVector {
265
- return new PgVector(
266
- withId(setting, `pg-vector__${randomUUID()}`) as ConstructorParameters<
267
- typeof PgVector
268
- >[0],
269
- );
270
- }
271
-
272
- /** True when this setting requires the shared Lakebase pool. */
273
- function settingNeedsSharedPool(
274
- setting: StorageSetting | MemorySetting | undefined,
275
- ): boolean {
276
- return setting === true;
277
- }
278
-
279
- /** Walk the three shapes of `config.agents` into a flat list. */
280
- function collectAgentDefinitions(config: MastraPluginConfig): MastraAgentDefinition[] {
281
- const agents = config.agents;
282
- if (!agents) return [];
283
- if (Array.isArray(agents)) return agents;
284
- if (typeof (agents as MastraAgentDefinition).instructions === "string") {
285
- return [agents as MastraAgentDefinition];
286
- }
287
- return Object.values(agents as Record<string, MastraAgentDefinition>);
288
- }
289
-
290
- /** Fill in a default `id` when the caller didn't supply one. */
291
- function withId<T extends { id?: string }>(value: T, fallback: string): T {
292
- return value.id ? value : { ...value, id: fallback };
293
- }