@dbx-tools/appkit-mastra 0.1.71 → 0.1.73
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 +88 -0
- package/dist/index.d.ts +362 -3
- package/dist/index.js +935 -56
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ChartSchema, ChartTypeSchema, MASTRA_ROUTES, MODEL_OVERRIDE_BODY_FIELDS, MODEL_OVERRIDE_HEADER, MODEL_OVERRIDE_QUERY } from "@dbx-tools/appkit-mastra-shared";
|
|
2
|
-
import { DEFAULT_FUZZY_THRESHOLD, DEFAULT_MODEL_CACHE_TTL_MS, FALLBACK_MODEL_IDS, ModelClass, clearServingEndpointsCache, listServingEndpoints, parseModelClass, selectModel } from "@dbx-tools/model";
|
|
1
|
+
import { ChartSchema, ChartTypeSchema, MASTRA_ROUTES, MODEL_OVERRIDE_BODY_FIELDS, MODEL_OVERRIDE_HEADER, MODEL_OVERRIDE_QUERY, THREAD_ID_HEADER, THREAD_ID_QUERY } from "@dbx-tools/appkit-mastra-shared";
|
|
2
|
+
import { DEFAULT_FUZZY_THRESHOLD, DEFAULT_MODEL_CACHE_TTL_MS, FALLBACK_MODEL_IDS, ModelClass, ModelClass as ModelClass$1, clearServingEndpointsCache, listServingEndpoints, parseModelClass, selectModel } from "@dbx-tools/model";
|
|
3
3
|
import { apiUtils, appkitUtils, commonUtils, httpUtils, logUtils, netUtils, projectUtils, stringUtils } from "@dbx-tools/shared";
|
|
4
4
|
import { Agent } from "@mastra/core/agent";
|
|
5
5
|
import { createTool, createTool as createTool$1 } from "@mastra/core/tools";
|
|
@@ -337,7 +337,7 @@ function sanitizeServingMessages(messages) {
|
|
|
337
337
|
* the demo client and any other UI consumer share the exact same
|
|
338
338
|
* shape this module reads and writes.
|
|
339
339
|
*/
|
|
340
|
-
const log$
|
|
340
|
+
const log$8 = logUtils.logger("mastra/chart");
|
|
341
341
|
/**
|
|
342
342
|
* TTL for cached chart entries. One hour balances "long enough for
|
|
343
343
|
* the host UI to fetch the chart well after the model finished
|
|
@@ -547,7 +547,7 @@ function getPlannerAgent(config) {
|
|
|
547
547
|
name: "Chart Planner",
|
|
548
548
|
description: "Picks chart type and axis encodings for a dataset.",
|
|
549
549
|
instructions: CHART_PLANNER_INSTRUCTIONS,
|
|
550
|
-
model: ({ requestContext }) => buildModel(config, requestContext, { modelClass: ModelClass.ChatFast })
|
|
550
|
+
model: ({ requestContext }) => buildModel(config, requestContext, { modelClass: ModelClass$1.ChatFast })
|
|
551
551
|
});
|
|
552
552
|
plannerAgents.set(config, agent);
|
|
553
553
|
}
|
|
@@ -593,7 +593,7 @@ async function writeChart(entry) {
|
|
|
593
593
|
const key = await chartCacheKey(entry.chartId);
|
|
594
594
|
await CacheManager.getInstanceSync().set(key, entry, { ttl: CHART_CACHE_TTL_SEC });
|
|
595
595
|
} catch (err) {
|
|
596
|
-
log$
|
|
596
|
+
log$8.warn("write-error", {
|
|
597
597
|
chartId: entry.chartId,
|
|
598
598
|
error: commonUtils.errorMessage(err)
|
|
599
599
|
});
|
|
@@ -608,7 +608,7 @@ async function readChart(chartId) {
|
|
|
608
608
|
const key = await chartCacheKey(chartId);
|
|
609
609
|
return await CacheManager.getInstanceSync().get(key) ?? void 0;
|
|
610
610
|
} catch (err) {
|
|
611
|
-
log$
|
|
611
|
+
log$8.warn("read-error", {
|
|
612
612
|
chartId,
|
|
613
613
|
error: commonUtils.errorMessage(err)
|
|
614
614
|
});
|
|
@@ -633,7 +633,7 @@ async function readChart(chartId) {
|
|
|
633
633
|
async function prepareChart(opts) {
|
|
634
634
|
const chartId = commonUtils.id();
|
|
635
635
|
await writeChart({ chartId });
|
|
636
|
-
log$
|
|
636
|
+
log$8.debug("queued", { chartId });
|
|
637
637
|
runPrepareChart(chartId, opts);
|
|
638
638
|
return { chartId };
|
|
639
639
|
}
|
|
@@ -654,14 +654,14 @@ async function runPrepareChart(chartId, opts) {
|
|
|
654
654
|
chartId,
|
|
655
655
|
result
|
|
656
656
|
});
|
|
657
|
-
log$
|
|
657
|
+
log$8.info("done", {
|
|
658
658
|
chartId,
|
|
659
659
|
chartType: result.chartType,
|
|
660
660
|
elapsedMs: Date.now() - startedAt
|
|
661
661
|
});
|
|
662
662
|
} catch (err) {
|
|
663
663
|
const error = commonUtils.errorMessage(err);
|
|
664
|
-
log$
|
|
664
|
+
log$8.warn("error", {
|
|
665
665
|
chartId,
|
|
666
666
|
error
|
|
667
667
|
});
|
|
@@ -848,6 +848,319 @@ function buildRenderDataTool(config) {
|
|
|
848
848
|
});
|
|
849
849
|
}
|
|
850
850
|
|
|
851
|
+
//#endregion
|
|
852
|
+
//#region packages/appkit-mastra/src/connectors/managed-memory/client.ts
|
|
853
|
+
/** Base path for the Unity Catalog memory-stores REST surface. */
|
|
854
|
+
const MEMORY_STORES_PATH = "/api/2.1/unity-catalog/memory-stores";
|
|
855
|
+
/**
|
|
856
|
+
* Error thrown for a non-2xx memory-store response. Carries the HTTP
|
|
857
|
+
* status so callers can distinguish "feature unavailable / store
|
|
858
|
+
* missing" (probe falls back) from genuine failures.
|
|
859
|
+
*/
|
|
860
|
+
var ManagedMemoryError = class extends Error {
|
|
861
|
+
constructor(status, statusText, body) {
|
|
862
|
+
super(`managed-memory: ${status} ${statusText}${body ? ` - ${body}` : ""}`);
|
|
863
|
+
this.status = status;
|
|
864
|
+
this.statusText = statusText;
|
|
865
|
+
this.name = "ManagedMemoryError";
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
/**
|
|
869
|
+
* Split a three-level `catalog.schema.name` into its parts. Throws when
|
|
870
|
+
* the input isn't exactly three dot-separated, non-empty segments so a
|
|
871
|
+
* misconfigured `MEMORY_STORE` fails loudly at setup rather than
|
|
872
|
+
* silently hitting a malformed URL.
|
|
873
|
+
*/
|
|
874
|
+
function parseStoreName(fullName) {
|
|
875
|
+
const parts = fullName.split(".");
|
|
876
|
+
if (parts.length !== 3 || parts.some((p) => p.trim() === "")) throw new Error(`managed-memory: store name must be three-level "catalog.schema.name", got "${fullName}"`);
|
|
877
|
+
const [catalog, schema, name] = parts;
|
|
878
|
+
return {
|
|
879
|
+
catalog,
|
|
880
|
+
schema,
|
|
881
|
+
name
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Resolve the workspace host and an authenticated header set off the
|
|
886
|
+
* client, then issue a `fetch`. Returns the raw `Response` so callers
|
|
887
|
+
* decide how to handle status codes (the store probe wants the 404, the
|
|
888
|
+
* mutating calls want a throw).
|
|
889
|
+
*/
|
|
890
|
+
async function rawFetch(client, path, init) {
|
|
891
|
+
const host = (await client.config.getHost()).toString();
|
|
892
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
893
|
+
await client.config.authenticate(headers);
|
|
894
|
+
const url = new URL(path, host).toString();
|
|
895
|
+
return fetch(url, {
|
|
896
|
+
method: init.method,
|
|
897
|
+
headers,
|
|
898
|
+
...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
|
|
899
|
+
...init.signal ? { signal: init.signal } : {}
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Issue a request and parse the JSON body, throwing
|
|
904
|
+
* {@link ManagedMemoryError} on any non-2xx status. Used by the calls
|
|
905
|
+
* where anything but success is a real error (create / write / search).
|
|
906
|
+
*/
|
|
907
|
+
async function requestJson(client, path, init) {
|
|
908
|
+
const res = await rawFetch(client, path, init);
|
|
909
|
+
if (!res.ok) throw new ManagedMemoryError(res.status, res.statusText, await safeText(res));
|
|
910
|
+
return safeJson(res);
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Fetch a store by full name. Returns the raw store payload when it
|
|
914
|
+
* exists, or `null` on 404 so callers can treat "missing" as a normal
|
|
915
|
+
* condition (the capability probe and `ensureStore` both rely on this).
|
|
916
|
+
* Any other non-2xx status throws.
|
|
917
|
+
*/
|
|
918
|
+
async function getStore(client, fullName, signal) {
|
|
919
|
+
const res = await rawFetch(client, `${MEMORY_STORES_PATH}/${fullName}`, {
|
|
920
|
+
method: "GET",
|
|
921
|
+
...signal ? { signal } : {}
|
|
922
|
+
});
|
|
923
|
+
if (res.status === 404) return null;
|
|
924
|
+
if (!res.ok) throw new ManagedMemoryError(res.status, res.statusText, await safeText(res));
|
|
925
|
+
return safeJson(res);
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Probe for the store and create it when missing. Returns `true` when
|
|
929
|
+
* the store exists (found or created). The create body splits the
|
|
930
|
+
* three-level name into the catalog / schema / name fields the API
|
|
931
|
+
* expects. A `getStore` 404 followed by a successful create is the
|
|
932
|
+
* common first-run path.
|
|
933
|
+
*/
|
|
934
|
+
async function ensureStore(client, fullName, description, signal) {
|
|
935
|
+
if (await getStore(client, fullName, signal) !== null) return true;
|
|
936
|
+
const { catalog, schema, name } = parseStoreName(fullName);
|
|
937
|
+
await requestJson(client, MEMORY_STORES_PATH, {
|
|
938
|
+
method: "POST",
|
|
939
|
+
body: {
|
|
940
|
+
name,
|
|
941
|
+
catalog_name: catalog,
|
|
942
|
+
schema_name: schema,
|
|
943
|
+
description
|
|
944
|
+
},
|
|
945
|
+
...signal ? { signal } : {}
|
|
946
|
+
});
|
|
947
|
+
return true;
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Write (upsert) an entry into the store under `scope`. Scope is the
|
|
951
|
+
* OBO user id resolved in trusted code; it travels as a query parameter
|
|
952
|
+
* so the API isolates one user's memories from another's.
|
|
953
|
+
*/
|
|
954
|
+
async function writeEntry(client, fullName, scope, entry, signal) {
|
|
955
|
+
await requestJson(client, `${MEMORY_STORES_PATH}/${fullName}/entries?scope=${encodeURIComponent(scope)}`, {
|
|
956
|
+
method: "POST",
|
|
957
|
+
body: {
|
|
958
|
+
path: entry.path,
|
|
959
|
+
contents: entry.contents,
|
|
960
|
+
...entry.description ? { description: entry.description } : {}
|
|
961
|
+
},
|
|
962
|
+
...signal ? { signal } : {}
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Semantic search over a user's entries within `scope`. Returns up to
|
|
967
|
+
* `topK` entries, parsed defensively so a renamed / missing Beta field
|
|
968
|
+
* degrades to an empty list rather than crashing the turn.
|
|
969
|
+
*/
|
|
970
|
+
async function search(client, fullName, scope, query, topK, signal) {
|
|
971
|
+
return parseEntries(await requestJson(client, `${MEMORY_STORES_PATH}/${fullName}/entries:search?scope=${encodeURIComponent(scope)}`, {
|
|
972
|
+
method: "POST",
|
|
973
|
+
body: {
|
|
974
|
+
query,
|
|
975
|
+
top_k: topK
|
|
976
|
+
},
|
|
977
|
+
...signal ? { signal } : {}
|
|
978
|
+
})).slice(0, topK);
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Defensively pull a `MemoryEntry[]` out of a search response. Accepts
|
|
982
|
+
* the documented `{ entries: [...] }` envelope plus a bare array, and
|
|
983
|
+
* tolerates snake_case / camelCase field drift. Entries without any
|
|
984
|
+
* textual content are dropped.
|
|
985
|
+
*/
|
|
986
|
+
function parseEntries(body) {
|
|
987
|
+
const list = Array.isArray(body) ? body : Array.isArray(body?.entries) ? body.entries : Array.isArray(body?.results) ? body.results : [];
|
|
988
|
+
const out = [];
|
|
989
|
+
for (const raw of list) {
|
|
990
|
+
if (!raw || typeof raw !== "object") continue;
|
|
991
|
+
const obj = raw;
|
|
992
|
+
const contents = firstString(obj["contents"], obj["content"], obj["text"]);
|
|
993
|
+
if (!contents) continue;
|
|
994
|
+
const path = firstString(obj["path"]);
|
|
995
|
+
const description = firstString(obj["description"], obj["summary"]);
|
|
996
|
+
const score = typeof obj["score"] === "number" ? obj["score"] : void 0;
|
|
997
|
+
out.push({
|
|
998
|
+
contents,
|
|
999
|
+
...path ? { path } : {},
|
|
1000
|
+
...description ? { description } : {},
|
|
1001
|
+
...score !== void 0 ? { score } : {}
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
return out;
|
|
1005
|
+
}
|
|
1006
|
+
/** Return the first argument that is a non-empty string, else undefined. */
|
|
1007
|
+
function firstString(...values) {
|
|
1008
|
+
for (const v of values) if (typeof v === "string" && v.trim() !== "") return v;
|
|
1009
|
+
}
|
|
1010
|
+
/** Parse a response body as JSON, returning `{}` on empty / invalid bodies. */
|
|
1011
|
+
async function safeJson(res) {
|
|
1012
|
+
const text = await safeText(res);
|
|
1013
|
+
if (!text) return {};
|
|
1014
|
+
try {
|
|
1015
|
+
return JSON.parse(text);
|
|
1016
|
+
} catch {
|
|
1017
|
+
return {};
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
/** Read a response body as text, swallowing read errors. */
|
|
1021
|
+
async function safeText(res) {
|
|
1022
|
+
try {
|
|
1023
|
+
return await res.text();
|
|
1024
|
+
} catch {
|
|
1025
|
+
return "";
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
//#endregion
|
|
1030
|
+
//#region packages/appkit-mastra/src/connectors/managed-memory/context.ts
|
|
1031
|
+
/**
|
|
1032
|
+
* Per-request resolution of the OBO workspace client and the memory
|
|
1033
|
+
* scope (the user id memories are partitioned by). Shared by the
|
|
1034
|
+
* managed-memory tools and the recall processor so the "who is this
|
|
1035
|
+
* turn for, and as whom do we call Databricks" logic lives in one
|
|
1036
|
+
* place.
|
|
1037
|
+
*
|
|
1038
|
+
* Scope is always derived from trusted server state - the AppKit user
|
|
1039
|
+
* stamped on the request context, or the active execution context - and
|
|
1040
|
+
* never from anything the model controls. Returns `null` when there is
|
|
1041
|
+
* no resolvable user (a stateless turn, or an MCP call with no request
|
|
1042
|
+
* context), so callers cleanly no-op instead of leaking memories across
|
|
1043
|
+
* users.
|
|
1044
|
+
*/
|
|
1045
|
+
/**
|
|
1046
|
+
* Resolve the OBO-scoped workspace client and the memory scope for the
|
|
1047
|
+
* current turn. Mirrors `model.ts`: prefer the AppKit user stamped on
|
|
1048
|
+
* the request context, fall back to the ambient execution context (the
|
|
1049
|
+
* active OBO scope or the service principal). Scope is the Mastra
|
|
1050
|
+
* resource id (the per-user thread owner) when present, else the user
|
|
1051
|
+
* id. Returns `null` when neither yields a usable scope.
|
|
1052
|
+
*/
|
|
1053
|
+
function resolveMemoryContext(requestContext) {
|
|
1054
|
+
const user = requestContext?.get(MASTRA_USER_KEY);
|
|
1055
|
+
let executionContext;
|
|
1056
|
+
try {
|
|
1057
|
+
executionContext = user?.executionContext ?? getExecutionContext();
|
|
1058
|
+
} catch {
|
|
1059
|
+
return null;
|
|
1060
|
+
}
|
|
1061
|
+
const resourceId = requestContext?.get(MASTRA_RESOURCE_ID_KEY);
|
|
1062
|
+
const scope = stringUtils.trimToNull(resourceId) ?? stringUtils.trimToNull(user?.id);
|
|
1063
|
+
if (!scope) return null;
|
|
1064
|
+
return {
|
|
1065
|
+
client: executionContext.client,
|
|
1066
|
+
scope
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
//#endregion
|
|
1071
|
+
//#region packages/appkit-mastra/src/connectors/managed-memory/tools.ts
|
|
1072
|
+
/**
|
|
1073
|
+
* Ambient Mastra tools for Databricks Managed Agent Memory:
|
|
1074
|
+
* `save_memory` (persist a durable fact) and `search_memory` (look up
|
|
1075
|
+
* prior memories). Built only when managed memory is the active
|
|
1076
|
+
* long-term backend and merged into every agent's tool set by
|
|
1077
|
+
* `buildAgents`.
|
|
1078
|
+
*
|
|
1079
|
+
* Both tools resolve the OBO user scope from trusted request state via
|
|
1080
|
+
* {@link resolveMemoryContext} - the model passes only the content /
|
|
1081
|
+
* query, never the scope (honoring the Managed Memory security
|
|
1082
|
+
* guidance). On a stateless turn (no resolvable user) the tools no-op
|
|
1083
|
+
* with a clear message instead of writing cross-user data, and any REST
|
|
1084
|
+
* failure degrades to a short error string rather than aborting the
|
|
1085
|
+
* turn.
|
|
1086
|
+
*/
|
|
1087
|
+
const log$7 = logUtils.logger("mastra/managed-memory/tools");
|
|
1088
|
+
const saveMemoryInput = z.object({
|
|
1089
|
+
content: z.string().describe("The durable fact or preference to remember."),
|
|
1090
|
+
summary: z.string().optional().describe("Optional one-line summary used to label the memory.")
|
|
1091
|
+
});
|
|
1092
|
+
const searchMemoryInput = z.object({ query: z.string().describe("What to look up in the user's saved memories.") });
|
|
1093
|
+
/**
|
|
1094
|
+
* Build the `save_memory` / `search_memory` tool pair bound to a
|
|
1095
|
+
* resolved managed-memory runtime. The runtime supplies the store name,
|
|
1096
|
+
* recall size, and default entry path; the per-request client and scope
|
|
1097
|
+
* are resolved inside each `execute`.
|
|
1098
|
+
*/
|
|
1099
|
+
function buildManagedMemoryTools(runtime) {
|
|
1100
|
+
return {
|
|
1101
|
+
save_memory: createTool$1({
|
|
1102
|
+
id: "save_memory",
|
|
1103
|
+
description: stringUtils.toDescription([`
|
|
1104
|
+
Save a durable fact or preference about the user to long-term
|
|
1105
|
+
memory so it can be recalled in future conversations. Use when
|
|
1106
|
+
the user states something worth remembering across sessions
|
|
1107
|
+
("I'm in the EU, use EUR", "always show the SQL"). Pass the
|
|
1108
|
+
fact as \`content\` and an optional one-line \`summary\`.
|
|
1109
|
+
`, `
|
|
1110
|
+
The memory is automatically scoped to the current user - never
|
|
1111
|
+
include another person's data.
|
|
1112
|
+
`]),
|
|
1113
|
+
inputSchema: saveMemoryInput,
|
|
1114
|
+
execute: async (input, ctxRaw) => {
|
|
1115
|
+
const { content, summary } = input;
|
|
1116
|
+
const ctx = resolveMemoryContext(requestContextOf(ctxRaw));
|
|
1117
|
+
if (!ctx) return "Memory is not available for this conversation.";
|
|
1118
|
+
try {
|
|
1119
|
+
await writeEntry(ctx.client, runtime.storeName, ctx.scope, {
|
|
1120
|
+
path: runtime.entryPath,
|
|
1121
|
+
contents: content,
|
|
1122
|
+
...summary ? { description: summary } : {}
|
|
1123
|
+
});
|
|
1124
|
+
log$7.debug("saved", { scope: ctx.scope });
|
|
1125
|
+
return "Saved to memory.";
|
|
1126
|
+
} catch (err) {
|
|
1127
|
+
log$7.warn("save:error", { error: commonUtils.errorMessage(err) });
|
|
1128
|
+
return "Could not save to memory right now.";
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}),
|
|
1132
|
+
search_memory: createTool$1({
|
|
1133
|
+
id: "search_memory",
|
|
1134
|
+
description: stringUtils.toDescription([`
|
|
1135
|
+
Search the user's long-term memory for relevant saved facts or
|
|
1136
|
+
preferences. Use before answering when prior context might
|
|
1137
|
+
help. Pass what you're looking for as \`query\`; returns the
|
|
1138
|
+
most relevant saved memories (scoped to the current user).
|
|
1139
|
+
`]),
|
|
1140
|
+
inputSchema: searchMemoryInput,
|
|
1141
|
+
execute: async (input, ctxRaw) => {
|
|
1142
|
+
const { query } = input;
|
|
1143
|
+
const ctx = resolveMemoryContext(requestContextOf(ctxRaw));
|
|
1144
|
+
if (!ctx) return { memories: [] };
|
|
1145
|
+
try {
|
|
1146
|
+
return { memories: (await search(ctx.client, runtime.storeName, ctx.scope, query, runtime.topK)).map(renderEntry) };
|
|
1147
|
+
} catch (err) {
|
|
1148
|
+
log$7.warn("search:error", { error: commonUtils.errorMessage(err) });
|
|
1149
|
+
return { memories: [] };
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
})
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
/** Render an entry to a single recall line (summary prefix when present). */
|
|
1156
|
+
function renderEntry(entry) {
|
|
1157
|
+
return entry.description ? `${entry.description}: ${entry.contents}` : entry.contents;
|
|
1158
|
+
}
|
|
1159
|
+
/** Pull the Mastra `RequestContext` out of a tool's execution context arg. */
|
|
1160
|
+
function requestContextOf(ctxRaw) {
|
|
1161
|
+
return ctxRaw?.requestContext;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
851
1164
|
//#endregion
|
|
852
1165
|
//#region packages/appkit-mastra/src/statement.ts
|
|
853
1166
|
/**
|
|
@@ -970,7 +1283,7 @@ async function safeWrite(log, writer, chunk, context = {}) {
|
|
|
970
1283
|
* `instructions` when you want the canonical "how to drive the
|
|
971
1284
|
* Genie tools" guidance.
|
|
972
1285
|
*/
|
|
973
|
-
const log$
|
|
1286
|
+
const log$6 = logUtils.logger("mastra/genie");
|
|
974
1287
|
/** Default alias used when a single unnamed Genie space is wired up. */
|
|
975
1288
|
const DEFAULT_GENIE_ALIAS = "default";
|
|
976
1289
|
/**
|
|
@@ -1085,7 +1398,7 @@ async function readCachedConversationId(cacheKey) {
|
|
|
1085
1398
|
try {
|
|
1086
1399
|
return await CacheManager.getInstanceSync().get(cacheKey) ?? void 0;
|
|
1087
1400
|
} catch (err) {
|
|
1088
|
-
log$
|
|
1401
|
+
log$6.warn("conversation-cache:read-error", { error: commonUtils.errorMessage(err) });
|
|
1089
1402
|
return;
|
|
1090
1403
|
}
|
|
1091
1404
|
}
|
|
@@ -1101,7 +1414,7 @@ async function saveCachedConversationId(cacheKey, conversationId) {
|
|
|
1101
1414
|
try {
|
|
1102
1415
|
await CacheManager.getInstanceSync().set(cacheKey, conversationId, { ttl: CONVERSATION_TTL_SEC });
|
|
1103
1416
|
} catch (err) {
|
|
1104
|
-
log$
|
|
1417
|
+
log$6.warn("conversation-cache:write-error", { error: commonUtils.errorMessage(err) });
|
|
1105
1418
|
}
|
|
1106
1419
|
}
|
|
1107
1420
|
/** Force-evict a cached conversation id. Used on the stale-id recovery path. */
|
|
@@ -1110,7 +1423,7 @@ async function evictCachedConversationId(cacheKey) {
|
|
|
1110
1423
|
try {
|
|
1111
1424
|
await CacheManager.getInstanceSync().delete(cacheKey);
|
|
1112
1425
|
} catch (err) {
|
|
1113
|
-
log$
|
|
1426
|
+
log$6.warn("conversation-cache:delete-error", { error: commonUtils.errorMessage(err) });
|
|
1114
1427
|
}
|
|
1115
1428
|
}
|
|
1116
1429
|
/**
|
|
@@ -1224,7 +1537,7 @@ function buildAskGenieTool(opts) {
|
|
|
1224
1537
|
if (trimmed.length === 0 || PLACEHOLDER_QUESTIONS.has(trimmed.toLowerCase())) throw new Error(`${toolId}: refusing placeholder question "${question}" - call ${toolId} only with a real natural-language question, or skip the call entirely`);
|
|
1225
1538
|
const cacheKey = await conversationCacheKey(spaceId, threadId);
|
|
1226
1539
|
await ensureConversationSeeded(requestContext, spaceId, cacheKey);
|
|
1227
|
-
await safeWrite(log$
|
|
1540
|
+
await safeWrite(log$6, writer, {
|
|
1228
1541
|
type: "started",
|
|
1229
1542
|
spaceId,
|
|
1230
1543
|
content: question
|
|
@@ -1237,7 +1550,7 @@ function buildAskGenieTool(opts) {
|
|
|
1237
1550
|
...seedConversationId ? { conversationId: seedConversationId } : {},
|
|
1238
1551
|
...signal ? { context: signal } : {}
|
|
1239
1552
|
})) {
|
|
1240
|
-
if (event.type !== "message") await safeWrite(log$
|
|
1553
|
+
if (event.type !== "message") await safeWrite(log$6, writer, event);
|
|
1241
1554
|
const eventConversationId = event.conversation_id;
|
|
1242
1555
|
if (eventConversationId) writeContextConversationId(requestContext, spaceId, eventConversationId);
|
|
1243
1556
|
if (event.type === "result") finalMessage = event.message;
|
|
@@ -1251,7 +1564,7 @@ function buildAskGenieTool(opts) {
|
|
|
1251
1564
|
} catch (err) {
|
|
1252
1565
|
const seeded = readContextConversationId(requestContext, spaceId);
|
|
1253
1566
|
if (seeded && apiUtils.isNotFoundError(err)) {
|
|
1254
|
-
log$
|
|
1567
|
+
log$6.warn("conversation-cache:stale, resetting", {
|
|
1255
1568
|
spaceId,
|
|
1256
1569
|
conversationId: seeded,
|
|
1257
1570
|
error: commonUtils.errorMessage(err)
|
|
@@ -1736,7 +2049,7 @@ async function collectSpaceSuggestions(opts) {
|
|
|
1736
2049
|
}));
|
|
1737
2050
|
writeSuggestionCache(spaceId, questions);
|
|
1738
2051
|
} catch (err) {
|
|
1739
|
-
log$
|
|
2052
|
+
log$6.warn("suggestions:fetch-error", {
|
|
1740
2053
|
spaceId,
|
|
1741
2054
|
error: commonUtils.errorMessage(err)
|
|
1742
2055
|
});
|
|
@@ -1782,6 +2095,76 @@ var ResultProcessor = class {
|
|
|
1782
2095
|
}
|
|
1783
2096
|
};
|
|
1784
2097
|
|
|
2098
|
+
//#endregion
|
|
2099
|
+
//#region packages/appkit-mastra/src/processors/managed-memory-recall.ts
|
|
2100
|
+
/**
|
|
2101
|
+
* Mastra input processor that auto-recalls Databricks Managed Agent
|
|
2102
|
+
* Memory before each turn. This is the managed-memory replacement for
|
|
2103
|
+
* Postgres `PgVector` semantic recall: instead of Mastra reading a
|
|
2104
|
+
* vector index, this processor searches the user's memory-store scope
|
|
2105
|
+
* with the latest user message and injects the top entries as an
|
|
2106
|
+
* appended system message so the model has the user's durable context.
|
|
2107
|
+
*
|
|
2108
|
+
* Scope comes from trusted request state via {@link resolveMemoryContext}
|
|
2109
|
+
* (the OBO user), never from the model. On a stateless turn, an empty
|
|
2110
|
+
* query, or any REST failure the processor passes the messages through
|
|
2111
|
+
* unchanged so a recall hiccup never blocks the conversation.
|
|
2112
|
+
*/
|
|
2113
|
+
const log$5 = logUtils.logger("mastra/processor/managed-memory-recall");
|
|
2114
|
+
/**
|
|
2115
|
+
* Build the recall processor bound to a managed-memory runtime. Created
|
|
2116
|
+
* once at setup (when managed memory is active) and wired onto every
|
|
2117
|
+
* agent's `inputProcessors` by {@link buildAgents}.
|
|
2118
|
+
*/
|
|
2119
|
+
function buildManagedMemoryRecallProcessor(runtime) {
|
|
2120
|
+
return {
|
|
2121
|
+
id: "managed-memory-recall",
|
|
2122
|
+
description: "Recalls the user's Databricks Managed Memory entries and injects them as context before the model runs.",
|
|
2123
|
+
async processInput(args) {
|
|
2124
|
+
const ctx = resolveMemoryContext(args.requestContext);
|
|
2125
|
+
if (!ctx) return args.messages;
|
|
2126
|
+
const query = latestUserText(args.messages);
|
|
2127
|
+
if (!query) return args.messages;
|
|
2128
|
+
try {
|
|
2129
|
+
const entries = await search(ctx.client, runtime.storeName, ctx.scope, query, runtime.topK, args.abortSignal);
|
|
2130
|
+
if (entries.length === 0) return args.messages;
|
|
2131
|
+
const block = ["Relevant memories about the user (from long-term memory):", ...entries.map((e) => `- ${renderEntry(e)}`)].join("\n");
|
|
2132
|
+
log$5.debug("recalled", {
|
|
2133
|
+
scope: ctx.scope,
|
|
2134
|
+
entries: entries.length
|
|
2135
|
+
});
|
|
2136
|
+
return {
|
|
2137
|
+
messages: args.messages,
|
|
2138
|
+
systemMessages: [...args.systemMessages, {
|
|
2139
|
+
role: "system",
|
|
2140
|
+
content: block
|
|
2141
|
+
}]
|
|
2142
|
+
};
|
|
2143
|
+
} catch (err) {
|
|
2144
|
+
log$5.warn("recall:error", { error: commonUtils.errorMessage(err) });
|
|
2145
|
+
return args.messages;
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
/**
|
|
2151
|
+
* Extract the most recent user message's text. Walks the message list
|
|
2152
|
+
* from the end, collecting `text` parts off the first `user` message.
|
|
2153
|
+
* Returns `null` when there is no user text to search with.
|
|
2154
|
+
*/
|
|
2155
|
+
function latestUserText(messages) {
|
|
2156
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2157
|
+
const message = messages[i];
|
|
2158
|
+
if (!message || message.role !== "user") continue;
|
|
2159
|
+
const content = message.content;
|
|
2160
|
+
const parts = content?.parts;
|
|
2161
|
+
if (!Array.isArray(parts)) return stringUtils.trimToNull(content?.content);
|
|
2162
|
+
const text = parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join(" ");
|
|
2163
|
+
return stringUtils.trimToNull(text);
|
|
2164
|
+
}
|
|
2165
|
+
return null;
|
|
2166
|
+
}
|
|
2167
|
+
|
|
1785
2168
|
//#endregion
|
|
1786
2169
|
//#region packages/appkit-mastra/src/processors/strip-stale-charts.ts
|
|
1787
2170
|
/**
|
|
@@ -1808,7 +2191,7 @@ var ResultProcessor = class {
|
|
|
1808
2191
|
* legacy `datasets[].chartId` payloads uniformly without coupling
|
|
1809
2192
|
* to specific tool ids.
|
|
1810
2193
|
*/
|
|
1811
|
-
const log$
|
|
2194
|
+
const log$4 = logUtils.logger("mastra/processor/strip-stale-charts");
|
|
1812
2195
|
/**
|
|
1813
2196
|
* Recursively clone `value`, omitting any property whose key is
|
|
1814
2197
|
* `chartId`. Arrays are mapped element-wise; primitives are
|
|
@@ -1856,11 +2239,135 @@ const stripStaleChartsProcessor = {
|
|
|
1856
2239
|
}
|
|
1857
2240
|
}
|
|
1858
2241
|
}
|
|
1859
|
-
if (stripped > 0) log$
|
|
2242
|
+
if (stripped > 0) log$4.debug("stripped", { results: stripped });
|
|
1860
2243
|
return args.messages;
|
|
1861
2244
|
}
|
|
1862
2245
|
};
|
|
1863
2246
|
|
|
2247
|
+
//#endregion
|
|
2248
|
+
//#region packages/appkit-mastra/src/summarize.ts
|
|
2249
|
+
/**
|
|
2250
|
+
* Small-tier summarization for the Mastra plugin.
|
|
2251
|
+
*
|
|
2252
|
+
* Two surfaces, both backed by the fast / small chat tier
|
|
2253
|
+
* ({@link ModelClass.ChatFast}) resolved through the same
|
|
2254
|
+
* `/serving-endpoints` pipeline as the main agents:
|
|
2255
|
+
*
|
|
2256
|
+
* - A dedicated `summarize` tool (see {@link buildSummarizeTool})
|
|
2257
|
+
* agents can call to condense arbitrary text without burning the
|
|
2258
|
+
* heavyweight chat model.
|
|
2259
|
+
* - The model + instructions Mastra's memory uses to auto-name
|
|
2260
|
+
* conversation threads (`generateTitle`), so titling reuses the
|
|
2261
|
+
* same small tier rather than the agent's primary model.
|
|
2262
|
+
*
|
|
2263
|
+
* Mirrors the chart-planner wiring in `chart.ts`: a per-config cached
|
|
2264
|
+
* `Agent` on the fast tier, invoked via `agent.generate(...)` inside
|
|
2265
|
+
* the active `asUser` scope so tokens stay user-scoped.
|
|
2266
|
+
*/
|
|
2267
|
+
/** Fast / small chat tier used for both titling and summaries. */
|
|
2268
|
+
const SUMMARY_MODEL_CLASS = ModelClass.ChatFast;
|
|
2269
|
+
/** System prompt for the summarizer agent (and the `summarize` tool). */
|
|
2270
|
+
const SUMMARIZER_INSTRUCTIONS = [
|
|
2271
|
+
"You are a summarization engine.",
|
|
2272
|
+
"Given a block of text, produce a faithful, concise summary of it.",
|
|
2273
|
+
"Default to a few sentences; follow any length guidance the caller gives.",
|
|
2274
|
+
"Plain prose. No preamble, no headers, no bullet points unless asked.",
|
|
2275
|
+
"Never use emojis. Use hyphens (-) only, never em dashes or en dashes.",
|
|
2276
|
+
"Never add information, opinions, or details not present in the input.",
|
|
2277
|
+
"Output only the summary."
|
|
2278
|
+
].join("\n");
|
|
2279
|
+
/**
|
|
2280
|
+
* Instructions Mastra's `generateTitle` hands the small-tier model to
|
|
2281
|
+
* name a conversation thread from its opening turn. Kept terse so the
|
|
2282
|
+
* model returns a bare title with no decoration.
|
|
2283
|
+
*/
|
|
2284
|
+
const TITLE_INSTRUCTIONS = [
|
|
2285
|
+
"Generate a short, specific title for this conversation, 3 to 6 words.",
|
|
2286
|
+
"Capture the user's topic, not the assistant's response.",
|
|
2287
|
+
"Plain text only: no surrounding quotes, no trailing punctuation,",
|
|
2288
|
+
"no emojis, and no em dashes. Output only the title."
|
|
2289
|
+
].join(" ");
|
|
2290
|
+
/**
|
|
2291
|
+
* Resolve the small-tier model for summarization / titling. Reused by
|
|
2292
|
+
* both the summarizer agent and Mastra memory's `generateTitle`.
|
|
2293
|
+
*
|
|
2294
|
+
* Returned as a `requestContext`-taking function (a Mastra
|
|
2295
|
+
* `DynamicArgument<MastraModelConfig>`) so each call mints user-scoped
|
|
2296
|
+
* tokens via {@link buildModel}, exactly like the primary agents.
|
|
2297
|
+
*/
|
|
2298
|
+
function summaryModel(config) {
|
|
2299
|
+
return ({ requestContext }) => buildModel(config, requestContext, { modelClass: SUMMARY_MODEL_CLASS });
|
|
2300
|
+
}
|
|
2301
|
+
/**
|
|
2302
|
+
* One summarizer `Agent` per plugin config, cached on config-object
|
|
2303
|
+
* identity so a hot tool path doesn't pay the constructor cost each
|
|
2304
|
+
* call. `WeakMap` lets retired configs (e.g. test reconfigurations)
|
|
2305
|
+
* release their agent without manual eviction.
|
|
2306
|
+
*/
|
|
2307
|
+
const summarizerAgents = /* @__PURE__ */ new WeakMap();
|
|
2308
|
+
function getSummarizerAgent(config) {
|
|
2309
|
+
let agent = summarizerAgents.get(config);
|
|
2310
|
+
if (!agent) {
|
|
2311
|
+
agent = new Agent({
|
|
2312
|
+
id: "summarizer",
|
|
2313
|
+
name: "Summarizer",
|
|
2314
|
+
description: "Condenses text into a short summary using a fast, small model.",
|
|
2315
|
+
instructions: SUMMARIZER_INSTRUCTIONS,
|
|
2316
|
+
model: summaryModel(config)
|
|
2317
|
+
});
|
|
2318
|
+
summarizerAgents.set(config, agent);
|
|
2319
|
+
}
|
|
2320
|
+
return agent;
|
|
2321
|
+
}
|
|
2322
|
+
/** Inputs accepted by the `summarize` tool. */
|
|
2323
|
+
const summarizeInput = z.object({
|
|
2324
|
+
text: z.string().min(1).describe("The text to summarize."),
|
|
2325
|
+
instructions: z.string().optional().describe("Optional extra guidance for the summary, e.g. 'one sentence' or 'list the action items'."),
|
|
2326
|
+
maxWords: z.number().int().positive().optional().describe("Optional soft cap on the summary length, in words.")
|
|
2327
|
+
});
|
|
2328
|
+
/**
|
|
2329
|
+
* Summarize `text` with the small-tier summarizer agent, returning the
|
|
2330
|
+
* trimmed summary string. Throws on model failure (the caller decides
|
|
2331
|
+
* how to degrade).
|
|
2332
|
+
*/
|
|
2333
|
+
async function summarizeText(config, text, options = {}) {
|
|
2334
|
+
const { instructions, maxWords, requestContext, abortSignal } = options;
|
|
2335
|
+
const prompt = stringUtils.toDescription({
|
|
2336
|
+
...instructions ? { Guidance: instructions } : {},
|
|
2337
|
+
...maxWords !== void 0 ? { "Max length (words)": String(maxWords) } : {},
|
|
2338
|
+
Text: text
|
|
2339
|
+
});
|
|
2340
|
+
return (await getSummarizerAgent(config).generate(prompt, {
|
|
2341
|
+
...requestContext ? { requestContext } : {},
|
|
2342
|
+
...abortSignal ? { abortSignal } : {}
|
|
2343
|
+
})).text.trim();
|
|
2344
|
+
}
|
|
2345
|
+
/**
|
|
2346
|
+
* Build the `summarize` tool. Exposed as an ambient system tool (like
|
|
2347
|
+
* `render_data`) so every agent can offload condensing long content,
|
|
2348
|
+
* notes, transcripts, or bulky tool results to the fast tier instead of
|
|
2349
|
+
* spending its primary chat model. The tool reads the live
|
|
2350
|
+
* `requestContext` / `abortSignal` off the Mastra execution context so
|
|
2351
|
+
* its model call stays user-scoped and cancels with the turn.
|
|
2352
|
+
*/
|
|
2353
|
+
function buildSummarizeTool(config) {
|
|
2354
|
+
return createTool$1({
|
|
2355
|
+
id: "summarize",
|
|
2356
|
+
description: "Summarize a block of text using a fast, small model. Use it to condense long content, notes, transcripts, or tool results into a short summary without spending the main chat model.",
|
|
2357
|
+
inputSchema: summarizeInput,
|
|
2358
|
+
execute: async (input, context) => {
|
|
2359
|
+
const { text, instructions, maxWords } = input;
|
|
2360
|
+
const ctx = context;
|
|
2361
|
+
return { summary: await summarizeText(config, text, {
|
|
2362
|
+
...instructions ? { instructions } : {},
|
|
2363
|
+
...maxWords !== void 0 ? { maxWords } : {},
|
|
2364
|
+
...ctx?.requestContext ? { requestContext: ctx.requestContext } : {},
|
|
2365
|
+
...ctx?.abortSignal ? { abortSignal: ctx.abortSignal } : {}
|
|
2366
|
+
}) };
|
|
2367
|
+
}
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
|
|
1864
2371
|
//#endregion
|
|
1865
2372
|
//#region packages/appkit-mastra/src/agents.ts
|
|
1866
2373
|
/**
|
|
@@ -2022,18 +2529,24 @@ function composeInstructions(agentInstructions, style) {
|
|
|
2022
2529
|
* resolved registry; this is a wiring bug, not a runtime condition.
|
|
2023
2530
|
*/
|
|
2024
2531
|
async function buildAgents(opts) {
|
|
2025
|
-
const { config, context, memoryBuilder, log } = opts;
|
|
2532
|
+
const { config, context, memoryBuilder, managedMemory, log } = opts;
|
|
2026
2533
|
const definitions = resolveDefinitions(config);
|
|
2027
2534
|
const ids = Object.keys(definitions);
|
|
2028
2535
|
const defaultAgentId = config.defaultAgent ?? ids[0] ?? FALLBACK_AGENT_ID;
|
|
2029
2536
|
const plugins = buildPluginsMap(config, context);
|
|
2030
|
-
const
|
|
2537
|
+
const systemTools = {
|
|
2031
2538
|
render_data: buildRenderDataTool(config),
|
|
2539
|
+
summarize: buildSummarizeTool(config)
|
|
2540
|
+
};
|
|
2541
|
+
const managedMemoryTools = managedMemory?.tools ? buildManagedMemoryTools(managedMemory) : {};
|
|
2542
|
+
const ambientTools = {
|
|
2543
|
+
...systemTools,
|
|
2544
|
+
...managedMemoryTools,
|
|
2032
2545
|
...config.tools ?? {}
|
|
2033
2546
|
};
|
|
2034
2547
|
const style = resolveStyleInstructions(config);
|
|
2035
2548
|
const outputProcessors = [new ResultProcessor()];
|
|
2036
|
-
const inputProcessors = config.stripStaleCharts === false ? [] : [stripStaleChartsProcessor];
|
|
2549
|
+
const inputProcessors = [...config.stripStaleCharts === false ? [] : [stripStaleChartsProcessor], ...managedMemory?.recall ? [buildManagedMemoryRecallProcessor(managedMemory)] : []];
|
|
2037
2550
|
const agents = {};
|
|
2038
2551
|
for (const [id, def] of Object.entries(definitions)) {
|
|
2039
2552
|
const tools = await resolveTools(def.tools, plugins, ambientTools);
|
|
@@ -2278,6 +2791,72 @@ function toolkitEntryToMastraTool(entry, plugin) {
|
|
|
2278
2791
|
});
|
|
2279
2792
|
}
|
|
2280
2793
|
|
|
2794
|
+
//#endregion
|
|
2795
|
+
//#region packages/appkit-mastra/src/connectors/managed-memory/defaults.ts
|
|
2796
|
+
/**
|
|
2797
|
+
* Default values and environment variable names for the managed-memory
|
|
2798
|
+
* connector. Kept in a leaf module so the client, tools, recall
|
|
2799
|
+
* processor, and plugin setup can share them without a cycle.
|
|
2800
|
+
*/
|
|
2801
|
+
/**
|
|
2802
|
+
* Environment variable holding the three-level Unity Catalog store name
|
|
2803
|
+
* (`catalog.schema.name`). Read when {@link ManagedMemoryConfig.store}
|
|
2804
|
+
* is omitted; also the sole enable signal in prefer-if-available mode
|
|
2805
|
+
* (managed memory stays off when it is unset).
|
|
2806
|
+
*/
|
|
2807
|
+
const MEMORY_STORE_ENV = "MEMORY_STORE";
|
|
2808
|
+
/**
|
|
2809
|
+
* Default number of entries the recall processor injects and
|
|
2810
|
+
* `search_memory` returns. Matches the prior `PgVector`
|
|
2811
|
+
* `semanticRecall.topK` so behavior is unchanged after the swap.
|
|
2812
|
+
*/
|
|
2813
|
+
const DEFAULT_TOPK = 3;
|
|
2814
|
+
/**
|
|
2815
|
+
* Default path `save_memory` writes land under. Managed Memory keys
|
|
2816
|
+
* entries by path within a scope, so a single stable path accumulates a
|
|
2817
|
+
* user's notes rather than scattering them.
|
|
2818
|
+
*/
|
|
2819
|
+
const DEFAULT_ENTRY_PATH = "/memories/notes.md";
|
|
2820
|
+
/** Description stamped on the store when the connector auto-creates it. */
|
|
2821
|
+
const DEFAULT_STORE_DESCRIPTION = "Long-term agent memory managed by @dbx-tools/appkit-mastra.";
|
|
2822
|
+
|
|
2823
|
+
//#endregion
|
|
2824
|
+
//#region packages/appkit-mastra/src/connectors/managed-memory/resolve.ts
|
|
2825
|
+
/**
|
|
2826
|
+
* Resolve the managed-memory target from config + env, or `null` when
|
|
2827
|
+
* managed memory is disabled. Throws when explicitly enabled but no
|
|
2828
|
+
* store name can be resolved (config or `MEMORY_STORE`), so a
|
|
2829
|
+
* misconfiguration surfaces at setup rather than silently falling back.
|
|
2830
|
+
*/
|
|
2831
|
+
function resolveManagedMemoryTarget(config) {
|
|
2832
|
+
const setting = config.managedMemory;
|
|
2833
|
+
if (setting === false) return null;
|
|
2834
|
+
const envStore = trimmedEnv(MEMORY_STORE_ENV);
|
|
2835
|
+
const obj = typeof setting === "object" ? setting : {};
|
|
2836
|
+
if (!(setting === true || typeof setting === "object") && !envStore) return null;
|
|
2837
|
+
const storeName = trimmed(obj.store) ?? envStore;
|
|
2838
|
+
if (!storeName) throw new Error(`mastra: managedMemory is enabled but no store name is set. Set managedMemory.store or the ${MEMORY_STORE_ENV} env var to a three-level Unity Catalog name (catalog.schema.name).`);
|
|
2839
|
+
return {
|
|
2840
|
+
storeName,
|
|
2841
|
+
description: trimmed(obj.description) ?? DEFAULT_STORE_DESCRIPTION,
|
|
2842
|
+
topK: obj.topK ?? DEFAULT_TOPK,
|
|
2843
|
+
entryPath: trimmed(obj.entryPath) ?? DEFAULT_ENTRY_PATH,
|
|
2844
|
+
autoCreate: obj.autoCreate ?? true,
|
|
2845
|
+
recall: obj.recall ?? true,
|
|
2846
|
+
tools: obj.tools ?? true
|
|
2847
|
+
};
|
|
2848
|
+
}
|
|
2849
|
+
/** Trimmed env var value, or undefined when unset / blank. */
|
|
2850
|
+
function trimmedEnv(name) {
|
|
2851
|
+
return trimmed(process.env[name]);
|
|
2852
|
+
}
|
|
2853
|
+
/** Trimmed string, or undefined when undefined / blank. */
|
|
2854
|
+
function trimmed(value) {
|
|
2855
|
+
if (typeof value !== "string") return void 0;
|
|
2856
|
+
const t = value.trim();
|
|
2857
|
+
return t === "" ? void 0 : t;
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2281
2860
|
//#endregion
|
|
2282
2861
|
//#region packages/appkit-mastra/src/mcp.ts
|
|
2283
2862
|
/** MCP server version advertised when the caller doesn't pin one. */
|
|
@@ -2320,11 +2899,11 @@ function buildMcpServer(opts) {
|
|
|
2320
2899
|
|
|
2321
2900
|
//#endregion
|
|
2322
2901
|
//#region packages/appkit-mastra/src/history.ts
|
|
2323
|
-
const log$
|
|
2902
|
+
const log$3 = logUtils.logger("mastra/history");
|
|
2324
2903
|
/** Default history page size; matches the Mastra storage default. */
|
|
2325
|
-
const DEFAULT_PER_PAGE = 20;
|
|
2904
|
+
const DEFAULT_PER_PAGE$1 = 20;
|
|
2326
2905
|
/** Hard cap so a misbehaving client can't fetch the whole thread at once. */
|
|
2327
|
-
const MAX_PER_PAGE = 200;
|
|
2906
|
+
const MAX_PER_PAGE$1 = 200;
|
|
2328
2907
|
/**
|
|
2329
2908
|
* Fetch a page of UI-formatted messages for a thread.
|
|
2330
2909
|
*
|
|
@@ -2341,11 +2920,11 @@ const MAX_PER_PAGE = 200;
|
|
|
2341
2920
|
* without sorting locally.
|
|
2342
2921
|
*/
|
|
2343
2922
|
async function loadHistory(opts) {
|
|
2344
|
-
const perPage = clampPerPage(opts.perPage);
|
|
2923
|
+
const perPage = clampPerPage$1(opts.perPage);
|
|
2345
2924
|
const page = Math.max(0, Math.trunc(opts.page ?? 0));
|
|
2346
2925
|
const memory = await opts.agent.getMemory();
|
|
2347
2926
|
if (!memory) {
|
|
2348
|
-
log$
|
|
2927
|
+
log$3.debug("recall:no-memory", {
|
|
2349
2928
|
agentId: opts.agent.id,
|
|
2350
2929
|
threadId: opts.threadId
|
|
2351
2930
|
});
|
|
@@ -2369,7 +2948,7 @@ async function loadHistory(opts) {
|
|
|
2369
2948
|
}
|
|
2370
2949
|
});
|
|
2371
2950
|
const uiMessages = toAISdkV5Messages(sortChronological(result.messages));
|
|
2372
|
-
log$
|
|
2951
|
+
log$3.debug("recall:done", {
|
|
2373
2952
|
agentId: opts.agent.id,
|
|
2374
2953
|
threadId: opts.threadId,
|
|
2375
2954
|
page,
|
|
@@ -2403,7 +2982,7 @@ async function loadHistory(opts) {
|
|
|
2403
2982
|
async function clearHistory(opts) {
|
|
2404
2983
|
const memory = await opts.agent.getMemory();
|
|
2405
2984
|
if (!memory) {
|
|
2406
|
-
log$
|
|
2985
|
+
log$3.debug("clear:no-memory", {
|
|
2407
2986
|
agentId: opts.agent.id,
|
|
2408
2987
|
threadId: opts.threadId
|
|
2409
2988
|
});
|
|
@@ -2417,7 +2996,7 @@ async function clearHistory(opts) {
|
|
|
2417
2996
|
perPage: 1
|
|
2418
2997
|
})).total;
|
|
2419
2998
|
} catch (err) {
|
|
2420
|
-
log$
|
|
2999
|
+
log$3.debug("clear:probe-failed", {
|
|
2421
3000
|
agentId: opts.agent.id,
|
|
2422
3001
|
threadId: opts.threadId,
|
|
2423
3002
|
error: commonUtils.errorMessage(err)
|
|
@@ -2427,13 +3006,13 @@ async function clearHistory(opts) {
|
|
|
2427
3006
|
try {
|
|
2428
3007
|
await memory.deleteThread(opts.threadId);
|
|
2429
3008
|
} catch (err) {
|
|
2430
|
-
log$
|
|
3009
|
+
log$3.warn("clear:delete-soft-failed", {
|
|
2431
3010
|
agentId: opts.agent.id,
|
|
2432
3011
|
threadId: opts.threadId,
|
|
2433
3012
|
error: commonUtils.errorMessage(err)
|
|
2434
3013
|
});
|
|
2435
3014
|
}
|
|
2436
|
-
log$
|
|
3015
|
+
log$3.info("clear:done", {
|
|
2437
3016
|
agentId: opts.agent.id,
|
|
2438
3017
|
threadId: opts.threadId,
|
|
2439
3018
|
cleared,
|
|
@@ -2490,8 +3069,8 @@ function historyRoute(options) {
|
|
|
2490
3069
|
agent: ctx.agent,
|
|
2491
3070
|
threadId: ctx.threadId,
|
|
2492
3071
|
...ctx.resourceId ? { resourceId: ctx.resourceId } : {},
|
|
2493
|
-
page: parseIntParam(c.req.query("page")),
|
|
2494
|
-
perPage: parseIntParam(c.req.query("perPage"))
|
|
3072
|
+
page: parseIntParam$1(c.req.query("page")),
|
|
3073
|
+
perPage: parseIntParam$1(c.req.query("perPage"))
|
|
2495
3074
|
});
|
|
2496
3075
|
return c.json(payload);
|
|
2497
3076
|
}
|
|
@@ -2515,11 +3094,11 @@ function historyRoute(options) {
|
|
|
2515
3094
|
})];
|
|
2516
3095
|
}
|
|
2517
3096
|
/** Coerce / clamp `perPage`; falls back to the page-size default. */
|
|
2518
|
-
function clampPerPage(value) {
|
|
2519
|
-
if (value === void 0 || Number.isNaN(value)) return DEFAULT_PER_PAGE;
|
|
3097
|
+
function clampPerPage$1(value) {
|
|
3098
|
+
if (value === void 0 || Number.isNaN(value)) return DEFAULT_PER_PAGE$1;
|
|
2520
3099
|
const n = Math.trunc(value);
|
|
2521
|
-
if (n <= 0) return DEFAULT_PER_PAGE;
|
|
2522
|
-
return Math.min(n, MAX_PER_PAGE);
|
|
3100
|
+
if (n <= 0) return DEFAULT_PER_PAGE$1;
|
|
3101
|
+
return Math.min(n, MAX_PER_PAGE$1);
|
|
2523
3102
|
}
|
|
2524
3103
|
/**
|
|
2525
3104
|
* Sort messages oldest-first by `createdAt`, falling back to whatever
|
|
@@ -2545,7 +3124,7 @@ function toEpoch(value) {
|
|
|
2545
3124
|
* `undefined` for empty / non-numeric / negative inputs so
|
|
2546
3125
|
* {@link loadHistory} can apply its built-in defaults.
|
|
2547
3126
|
*/
|
|
2548
|
-
function parseIntParam(value) {
|
|
3127
|
+
function parseIntParam$1(value) {
|
|
2549
3128
|
if (!value) return void 0;
|
|
2550
3129
|
const n = Number(value);
|
|
2551
3130
|
if (!Number.isFinite(n) || n < 0) return void 0;
|
|
@@ -2582,7 +3161,7 @@ function parseIntParam(value) {
|
|
|
2582
3161
|
* (auto-defaulted to `true` in `plugin.ts` when the `lakebase` plugin
|
|
2583
3162
|
* is registered); per-agent settings cascade on top of that.
|
|
2584
3163
|
*/
|
|
2585
|
-
const log$
|
|
3164
|
+
const log$2 = logUtils.logger("mastra/memory");
|
|
2586
3165
|
/**
|
|
2587
3166
|
* Build a dedicated **service-principal** Lakebase pool for Mastra
|
|
2588
3167
|
* memory from the lakebase plugin's resolved SP pg config.
|
|
@@ -2626,8 +3205,8 @@ function needsLakebase(config) {
|
|
|
2626
3205
|
* Caches the shared `PgVector` singleton (built on first need) so each
|
|
2627
3206
|
* agent build is O(1) after the first.
|
|
2628
3207
|
*/
|
|
2629
|
-
function createMemoryBuilder(config, servicePrincipalPool) {
|
|
2630
|
-
return new MemoryBuilder(config, servicePrincipalPool);
|
|
3208
|
+
function createMemoryBuilder(config, servicePrincipalPool, options = {}) {
|
|
3209
|
+
return new MemoryBuilder(config, servicePrincipalPool, options);
|
|
2631
3210
|
}
|
|
2632
3211
|
/**
|
|
2633
3212
|
* Builds one `Memory` per agent against a shared service-principal
|
|
@@ -2636,9 +3215,10 @@ function createMemoryBuilder(config, servicePrincipalPool) {
|
|
|
2636
3215
|
*/
|
|
2637
3216
|
var MemoryBuilder = class {
|
|
2638
3217
|
sharedVector;
|
|
2639
|
-
constructor(config, servicePrincipalPool) {
|
|
3218
|
+
constructor(config, servicePrincipalPool, options = {}) {
|
|
2640
3219
|
this.config = config;
|
|
2641
3220
|
this.servicePrincipalPool = servicePrincipalPool;
|
|
3221
|
+
this.options = options;
|
|
2642
3222
|
}
|
|
2643
3223
|
/**
|
|
2644
3224
|
* Build a `Memory` for `agentId` after the plugin/agent cascade.
|
|
@@ -2673,10 +3253,10 @@ var MemoryBuilder = class {
|
|
|
2673
3253
|
const storage = this.buildStorage(agentId, storageSetting);
|
|
2674
3254
|
const vector = this.buildVector(memorySetting);
|
|
2675
3255
|
if (!storage && !vector) {
|
|
2676
|
-
log$
|
|
3256
|
+
log$2.debug("agent:stateless", { agentId });
|
|
2677
3257
|
return;
|
|
2678
3258
|
}
|
|
2679
|
-
log$
|
|
3259
|
+
log$2.debug("agent:configured", {
|
|
2680
3260
|
agentId,
|
|
2681
3261
|
storage: storage !== void 0,
|
|
2682
3262
|
vector: vector !== void 0,
|
|
@@ -2693,6 +3273,10 @@ var MemoryBuilder = class {
|
|
|
2693
3273
|
...vector ? { semanticRecall: {
|
|
2694
3274
|
topK: 3,
|
|
2695
3275
|
messageRange: 2
|
|
3276
|
+
} } : {},
|
|
3277
|
+
...storage ? { generateTitle: {
|
|
3278
|
+
model: summaryModel(this.config),
|
|
3279
|
+
instructions: TITLE_INSTRUCTIONS
|
|
2696
3280
|
} } : {}
|
|
2697
3281
|
}
|
|
2698
3282
|
});
|
|
@@ -2716,6 +3300,7 @@ var MemoryBuilder = class {
|
|
|
2716
3300
|
* - object: build a dedicated `PgVector` for this agent.
|
|
2717
3301
|
*/
|
|
2718
3302
|
buildVector(setting) {
|
|
3303
|
+
if (this.options.suppressVector) return void 0;
|
|
2719
3304
|
if (!setting) return void 0;
|
|
2720
3305
|
if (typeof setting === "boolean") return this.getSharedVector();
|
|
2721
3306
|
return buildPgVector(setting);
|
|
@@ -2817,7 +3402,7 @@ function withId(value, fallback) {
|
|
|
2817
3402
|
* to the global noop tracer, mirroring how the `agents` plugin
|
|
2818
3403
|
* silently no-ops in the same situation.
|
|
2819
3404
|
*/
|
|
2820
|
-
const log = logUtils.logger("mastra/observability");
|
|
3405
|
+
const log$1 = logUtils.logger("mastra/observability");
|
|
2821
3406
|
const DEFAULT_SERVICE_NAME = "mastra";
|
|
2822
3407
|
/**
|
|
2823
3408
|
* Build a Mastra `Observability` whose spans ride AppKit's global
|
|
@@ -2835,7 +3420,7 @@ async function buildObservability(options) {
|
|
|
2835
3420
|
const otelBase = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
2836
3421
|
const otelTracesOverride = process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT;
|
|
2837
3422
|
const resolvedTracesUrl = otelTracesOverride ? otelTracesOverride : otelBase ? `${otelBase.replace(/\/+$/, "")}/v1/traces` : void 0;
|
|
2838
|
-
log.info("Mastra observability wired through OTel bridge", {
|
|
3423
|
+
log$1.info("Mastra observability wired through OTel bridge", {
|
|
2839
3424
|
serviceName,
|
|
2840
3425
|
requestContextKeys,
|
|
2841
3426
|
otelBase: otelBase ?? "<unset>",
|
|
@@ -2921,8 +3506,31 @@ var MastraServer$1 = class extends MastraServer {
|
|
|
2921
3506
|
requestContext.set(MASTRA_REQUEST_ID_KEY, requestId);
|
|
2922
3507
|
res.setHeader("X-Request-Id", requestId);
|
|
2923
3508
|
}
|
|
3509
|
+
/**
|
|
3510
|
+
* Resolve the thread id this request targets and pin it on
|
|
3511
|
+
* `RequestContext` (consumed by the agent stream for persistence and
|
|
3512
|
+
* by the history / threads routes). Resolution order:
|
|
3513
|
+
*
|
|
3514
|
+
* 1. A client-supplied thread id (the thread-selection header /
|
|
3515
|
+
* `?threadId=` query). This is how the chat UI references a
|
|
3516
|
+
* specific conversation among the many a user owns - it picks a
|
|
3517
|
+
* thread id from the `/threads` listing (or mints one for a new
|
|
3518
|
+
* conversation) and stamps it here. The id is scoped to the
|
|
3519
|
+
* caller's resource by the recall / list routes, so a client
|
|
3520
|
+
* can only ever read or write its own threads.
|
|
3521
|
+
* 2. The per-session cookie (`appkit_<plugin-name>_session_id`),
|
|
3522
|
+
* minted on first contact. This is the default single-thread
|
|
3523
|
+
* fallback for clients that don't manage threads explicitly, so
|
|
3524
|
+
* existing embeds keep one stable conversation per session with
|
|
3525
|
+
* no client changes.
|
|
3526
|
+
*/
|
|
2924
3527
|
configureRequestContextThreadId(req, res, requestContext) {
|
|
2925
3528
|
if (requestContext.get(MASTRA_THREAD_ID_KEY)) return;
|
|
3529
|
+
const requested = this.readRequestedThreadId(req);
|
|
3530
|
+
if (requested) {
|
|
3531
|
+
requestContext.set(MASTRA_THREAD_ID_KEY, requested);
|
|
3532
|
+
return;
|
|
3533
|
+
}
|
|
2926
3534
|
const cookies = httpUtils.parseCookies(req.headers.cookie);
|
|
2927
3535
|
const cookieName = stringUtils.toIdentifierWithOptions({
|
|
2928
3536
|
delimiter: "_",
|
|
@@ -2940,6 +3548,17 @@ var MastraServer$1 = class extends MastraServer {
|
|
|
2940
3548
|
}
|
|
2941
3549
|
requestContext.set(MASTRA_THREAD_ID_KEY, sessionId);
|
|
2942
3550
|
}
|
|
3551
|
+
/**
|
|
3552
|
+
* Read the client-selected thread id from the request, preferring
|
|
3553
|
+
* the thread-selection header over the `?threadId=` query. Returns
|
|
3554
|
+
* `null` when neither carries a non-empty value so the caller falls
|
|
3555
|
+
* back to the session cookie.
|
|
3556
|
+
*/
|
|
3557
|
+
readRequestedThreadId(req) {
|
|
3558
|
+
const headerValue = req.headers[THREAD_ID_HEADER];
|
|
3559
|
+
const queryValue = req.query[THREAD_ID_QUERY];
|
|
3560
|
+
return stringUtils.trimToNull(Array.isArray(headerValue) ? headerValue[0] : headerValue) ?? stringUtils.trimToNull(Array.isArray(queryValue) ? queryValue[0] : queryValue);
|
|
3561
|
+
}
|
|
2943
3562
|
configureRequestContextModelOverride(req, requestContext) {
|
|
2944
3563
|
if (resolveServingConfig(this.config).allowOverride) {
|
|
2945
3564
|
const override = extractModelOverride({
|
|
@@ -2973,6 +3592,206 @@ function attachRoutePatchMiddleware(app) {
|
|
|
2973
3592
|
});
|
|
2974
3593
|
}
|
|
2975
3594
|
|
|
3595
|
+
//#endregion
|
|
3596
|
+
//#region packages/appkit-mastra/src/threads.ts
|
|
3597
|
+
const log = logUtils.logger("mastra/threads");
|
|
3598
|
+
/** Default threads page size. */
|
|
3599
|
+
const DEFAULT_PER_PAGE = 30;
|
|
3600
|
+
/** Hard cap so a misbehaving client can't fetch every thread at once. */
|
|
3601
|
+
const MAX_PER_PAGE = 200;
|
|
3602
|
+
/**
|
|
3603
|
+
* Fetch a page of the resource's conversation threads, newest
|
|
3604
|
+
* (`updatedAt` DESC) first.
|
|
3605
|
+
*
|
|
3606
|
+
* Uses the agent's resolved `Memory` (`getMemory()`) so the per-agent
|
|
3607
|
+
* storage namespace (`mastra_<agentId>` schema) applies automatically.
|
|
3608
|
+
* When the agent has no memory configured the response is a successful
|
|
3609
|
+
* empty page so callers don't have to special-case stateless agents.
|
|
3610
|
+
*/
|
|
3611
|
+
async function listThreads(opts) {
|
|
3612
|
+
const perPage = clampPerPage(opts.perPage);
|
|
3613
|
+
const page = Math.max(0, Math.trunc(opts.page ?? 0));
|
|
3614
|
+
const memory = await opts.agent.getMemory();
|
|
3615
|
+
if (!memory) {
|
|
3616
|
+
log.debug("list:no-memory", { agentId: opts.agent.id });
|
|
3617
|
+
return {
|
|
3618
|
+
threads: [],
|
|
3619
|
+
page,
|
|
3620
|
+
perPage,
|
|
3621
|
+
total: 0,
|
|
3622
|
+
hasMore: false
|
|
3623
|
+
};
|
|
3624
|
+
}
|
|
3625
|
+
const startedAt = Date.now();
|
|
3626
|
+
const result = await memory.listThreads({
|
|
3627
|
+
filter: { resourceId: opts.resourceId },
|
|
3628
|
+
page,
|
|
3629
|
+
perPage,
|
|
3630
|
+
orderBy: {
|
|
3631
|
+
field: "updatedAt",
|
|
3632
|
+
direction: "DESC"
|
|
3633
|
+
}
|
|
3634
|
+
});
|
|
3635
|
+
const threads = result.threads.map(toWireThread);
|
|
3636
|
+
log.debug("list:done", {
|
|
3637
|
+
agentId: opts.agent.id,
|
|
3638
|
+
resourceId: opts.resourceId,
|
|
3639
|
+
page,
|
|
3640
|
+
perPage,
|
|
3641
|
+
returned: threads.length,
|
|
3642
|
+
total: result.total,
|
|
3643
|
+
hasMore: result.hasMore,
|
|
3644
|
+
elapsedMs: Date.now() - startedAt
|
|
3645
|
+
});
|
|
3646
|
+
return {
|
|
3647
|
+
threads,
|
|
3648
|
+
page,
|
|
3649
|
+
perPage,
|
|
3650
|
+
total: result.total,
|
|
3651
|
+
hasMore: result.hasMore
|
|
3652
|
+
};
|
|
3653
|
+
}
|
|
3654
|
+
/**
|
|
3655
|
+
* Delete a single named thread (and every message on it).
|
|
3656
|
+
*
|
|
3657
|
+
* Ownership is enforced: the thread is only removed when it belongs to
|
|
3658
|
+
* the calling resource, so a client can't delete another user's
|
|
3659
|
+
* conversation by guessing its id. A thread that doesn't exist (or
|
|
3660
|
+
* isn't owned by the caller) is a successful no-op (`deleted: false`)
|
|
3661
|
+
* so the UI can fire-and-forget.
|
|
3662
|
+
*/
|
|
3663
|
+
async function deleteThread(opts) {
|
|
3664
|
+
const memory = await opts.agent.getMemory();
|
|
3665
|
+
if (!memory) {
|
|
3666
|
+
log.debug("delete:no-memory", { agentId: opts.agent.id });
|
|
3667
|
+
return { deleted: false };
|
|
3668
|
+
}
|
|
3669
|
+
const existing = await memory.getThreadById({ threadId: opts.threadId });
|
|
3670
|
+
if (!existing || existing.resourceId !== opts.resourceId) {
|
|
3671
|
+
log.debug("delete:not-owned", {
|
|
3672
|
+
agentId: opts.agent.id,
|
|
3673
|
+
threadId: opts.threadId,
|
|
3674
|
+
found: existing !== null
|
|
3675
|
+
});
|
|
3676
|
+
return { deleted: false };
|
|
3677
|
+
}
|
|
3678
|
+
const startedAt = Date.now();
|
|
3679
|
+
await memory.deleteThread(opts.threadId);
|
|
3680
|
+
log.info("delete:done", {
|
|
3681
|
+
agentId: opts.agent.id,
|
|
3682
|
+
threadId: opts.threadId,
|
|
3683
|
+
elapsedMs: Date.now() - startedAt
|
|
3684
|
+
});
|
|
3685
|
+
return { deleted: true };
|
|
3686
|
+
}
|
|
3687
|
+
/**
|
|
3688
|
+
* Register the `<path>` Mastra custom API route. Handles two methods
|
|
3689
|
+
* on the same mount:
|
|
3690
|
+
*
|
|
3691
|
+
* - `GET`: a page of the resource's conversation threads
|
|
3692
|
+
* ({@link listThreads}).
|
|
3693
|
+
* - `DELETE`: remove the thread named by the thread-selection header
|
|
3694
|
+
* / `?threadId=` query ({@link deleteThread}). The id is read from
|
|
3695
|
+
* `RequestContext` (the auth middleware resolves it the same way it
|
|
3696
|
+
* does for streaming and history), so the client deletes any of its
|
|
3697
|
+
* threads by stamping the target id - no separate path param.
|
|
3698
|
+
*
|
|
3699
|
+
* Follows the `@mastra/ai-sdk` agent-binding convention: pass `agent`
|
|
3700
|
+
* for a fixed-agent mount, or include `:agentId` in the path for
|
|
3701
|
+
* dynamic routing. The plugin registers both `/route/threads` (default
|
|
3702
|
+
* agent) and `/route/threads/:agentId`.
|
|
3703
|
+
*/
|
|
3704
|
+
function threadsRoute(options) {
|
|
3705
|
+
const { path } = options;
|
|
3706
|
+
const fixedAgent = "agent" in options ? options.agent : void 0;
|
|
3707
|
+
if (!fixedAgent && !path.includes(":agentId")) throw new Error("threadsRoute path must include `:agentId` or `agent` must be passed explicitly");
|
|
3708
|
+
const resolveContext = (c) => {
|
|
3709
|
+
const mastra = c.get("mastra");
|
|
3710
|
+
const requestContext = c.get("requestContext");
|
|
3711
|
+
const agentId = fixedAgent ?? c.req.param("agentId");
|
|
3712
|
+
if (!agentId) return { error: c.json({ error: "agentId is required" }, 400) };
|
|
3713
|
+
const agent = mastra.getAgentById(agentId);
|
|
3714
|
+
if (!agent) return { error: c.json({ error: `Unknown agent "${agentId}"` }, 404) };
|
|
3715
|
+
const resourceId = requestContext.get(MASTRA_RESOURCE_ID_KEY);
|
|
3716
|
+
if (!resourceId) return { error: c.json({ error: "resource id missing from request context" }, 400) };
|
|
3717
|
+
return {
|
|
3718
|
+
agentId,
|
|
3719
|
+
agent,
|
|
3720
|
+
requestContext,
|
|
3721
|
+
resourceId
|
|
3722
|
+
};
|
|
3723
|
+
};
|
|
3724
|
+
return [registerApiRoute(path, {
|
|
3725
|
+
method: "GET",
|
|
3726
|
+
handler: async (c) => {
|
|
3727
|
+
const ctx = resolveContext(c);
|
|
3728
|
+
if ("error" in ctx) return ctx.error;
|
|
3729
|
+
const payload = await listThreads({
|
|
3730
|
+
agent: ctx.agent,
|
|
3731
|
+
resourceId: ctx.resourceId,
|
|
3732
|
+
page: parseIntParam(c.req.query("page")),
|
|
3733
|
+
perPage: parseIntParam(c.req.query("perPage"))
|
|
3734
|
+
});
|
|
3735
|
+
return c.json(payload);
|
|
3736
|
+
}
|
|
3737
|
+
}), registerApiRoute(path, {
|
|
3738
|
+
method: "DELETE",
|
|
3739
|
+
handler: async (c) => {
|
|
3740
|
+
const ctx = resolveContext(c);
|
|
3741
|
+
if ("error" in ctx) return ctx.error;
|
|
3742
|
+
const threadId = ctx.requestContext.get(MASTRA_THREAD_ID_KEY);
|
|
3743
|
+
if (!threadId) return c.json({ error: "thread id missing from request context" }, 400);
|
|
3744
|
+
const { deleted } = await deleteThread({
|
|
3745
|
+
agent: ctx.agent,
|
|
3746
|
+
threadId,
|
|
3747
|
+
resourceId: ctx.resourceId
|
|
3748
|
+
});
|
|
3749
|
+
const payload = {
|
|
3750
|
+
ok: true,
|
|
3751
|
+
agentId: ctx.agentId,
|
|
3752
|
+
threadId,
|
|
3753
|
+
deleted
|
|
3754
|
+
};
|
|
3755
|
+
return c.json(payload);
|
|
3756
|
+
}
|
|
3757
|
+
})];
|
|
3758
|
+
}
|
|
3759
|
+
/** Coerce a Mastra `StorageThreadType` into the JSON-safe wire shape. */
|
|
3760
|
+
function toWireThread(thread) {
|
|
3761
|
+
return {
|
|
3762
|
+
id: thread.id,
|
|
3763
|
+
...thread.title ? { title: thread.title } : {},
|
|
3764
|
+
resourceId: thread.resourceId,
|
|
3765
|
+
createdAt: toIso(thread.createdAt),
|
|
3766
|
+
updatedAt: toIso(thread.updatedAt),
|
|
3767
|
+
...thread.metadata ? { metadata: thread.metadata } : {}
|
|
3768
|
+
};
|
|
3769
|
+
}
|
|
3770
|
+
/** Render a storage timestamp as an ISO-8601 string for the wire. */
|
|
3771
|
+
function toIso(value) {
|
|
3772
|
+
if (value instanceof Date) return value.toISOString();
|
|
3773
|
+
const parsed = new Date(value);
|
|
3774
|
+
return Number.isNaN(parsed.getTime()) ? (/* @__PURE__ */ new Date(0)).toISOString() : parsed.toISOString();
|
|
3775
|
+
}
|
|
3776
|
+
/** Coerce / clamp `perPage`; falls back to the page-size default. */
|
|
3777
|
+
function clampPerPage(value) {
|
|
3778
|
+
if (value === void 0 || Number.isNaN(value)) return DEFAULT_PER_PAGE;
|
|
3779
|
+
const n = Math.trunc(value);
|
|
3780
|
+
if (n <= 0) return DEFAULT_PER_PAGE;
|
|
3781
|
+
return Math.min(n, MAX_PER_PAGE);
|
|
3782
|
+
}
|
|
3783
|
+
/**
|
|
3784
|
+
* Coerce a Hono query value into a non-negative integer. Returns
|
|
3785
|
+
* `undefined` for empty / non-numeric / negative inputs so
|
|
3786
|
+
* {@link listThreads} can apply its built-in defaults.
|
|
3787
|
+
*/
|
|
3788
|
+
function parseIntParam(value) {
|
|
3789
|
+
if (!value) return void 0;
|
|
3790
|
+
const n = Number(value);
|
|
3791
|
+
if (!Number.isFinite(n) || n < 0) return void 0;
|
|
3792
|
+
return Math.trunc(n);
|
|
3793
|
+
}
|
|
3794
|
+
|
|
2976
3795
|
//#endregion
|
|
2977
3796
|
//#region packages/appkit-mastra/src/plugin.ts
|
|
2978
3797
|
/**
|
|
@@ -3000,9 +3819,10 @@ function attachRoutePatchMiddleware(app) {
|
|
|
3000
3819
|
* - Server: the Express subapp wiring lives in `./server.js`.
|
|
3001
3820
|
* - HTTP: AppKit mounts this plugin under `/api/mastra`. Alongside the
|
|
3002
3821
|
* Mastra agent routes, the plugin registers `/route/history`
|
|
3003
|
-
* (load + clear thread
|
|
3004
|
-
*
|
|
3005
|
-
*
|
|
3822
|
+
* (load + clear a thread's messages), `/route/threads` (list the
|
|
3823
|
+
* caller's conversations + delete one), `/models`, `/suggestions`,
|
|
3824
|
+
* and the generic `/embed/:type/:id` resolver for inline chart /
|
|
3825
|
+
* data markers.
|
|
3006
3826
|
* - MCP: opt in with `config.mcp` to expose the agents (and optionally
|
|
3007
3827
|
* tools) as a Mastra `MCPServer`. It is registered on the `Mastra`
|
|
3008
3828
|
* instance via `mcpServers`, so `@mastra/express` serves the stock
|
|
@@ -3259,17 +4079,67 @@ var MastraPlugin = class MastraPlugin extends Plugin {
|
|
|
3259
4079
|
const client = getExecutionContext().client;
|
|
3260
4080
|
return listServingEndpoints(client, (await client.config.getHost()).toString(), { ttlMs: resolveServingConfig(this.config).ttlMs });
|
|
3261
4081
|
}
|
|
4082
|
+
/**
|
|
4083
|
+
* Resolve and probe the Databricks Managed Memory backend at setup.
|
|
4084
|
+
*
|
|
4085
|
+
* Returns the runtime (store + recall sizing + tool / recall toggles)
|
|
4086
|
+
* when managed memory is enabled and the workspace memory-stores API
|
|
4087
|
+
* is reachable, else `undefined` so the caller uses the PgVector path.
|
|
4088
|
+
*
|
|
4089
|
+
* The probe runs as the service principal (setup is outside any
|
|
4090
|
+
* `asUser` scope, so `getExecutionContext()` yields the SP client):
|
|
4091
|
+
* the SP owns the store and is the identity that can `CREATE MEMORY
|
|
4092
|
+
* STORE`. When `autoCreate` is on the store is created if missing.
|
|
4093
|
+
*
|
|
4094
|
+
* A misconfiguration where managed memory is explicitly enabled but no
|
|
4095
|
+
* store name is resolvable throws ({@link resolveManagedMemoryTarget})
|
|
4096
|
+
* - that is a wiring bug, not a runtime condition. Every other failure
|
|
4097
|
+
* (feature unavailable, permission denied, network) is caught, logged,
|
|
4098
|
+
* and degraded to a PgVector fallback so chat stays available.
|
|
4099
|
+
*/
|
|
4100
|
+
async resolveManagedMemory() {
|
|
4101
|
+
const target = resolveManagedMemoryTarget(this.config);
|
|
4102
|
+
if (!target) return void 0;
|
|
4103
|
+
try {
|
|
4104
|
+
const client = getExecutionContext().client;
|
|
4105
|
+
if (!(target.autoCreate ? await ensureStore(client, target.storeName, target.description) : await getStore(client, target.storeName) !== null)) {
|
|
4106
|
+
this.log.warn("managed-memory:unavailable; using PgVector fallback", { store: target.storeName });
|
|
4107
|
+
return;
|
|
4108
|
+
}
|
|
4109
|
+
this.log.info("managed-memory:active", {
|
|
4110
|
+
store: target.storeName,
|
|
4111
|
+
topK: target.topK,
|
|
4112
|
+
autoCreate: target.autoCreate
|
|
4113
|
+
});
|
|
4114
|
+
return {
|
|
4115
|
+
storeName: target.storeName,
|
|
4116
|
+
topK: target.topK,
|
|
4117
|
+
entryPath: target.entryPath,
|
|
4118
|
+
tools: target.tools,
|
|
4119
|
+
recall: target.recall
|
|
4120
|
+
};
|
|
4121
|
+
} catch (err) {
|
|
4122
|
+
this.log.warn("managed-memory:probe failed; using PgVector fallback", {
|
|
4123
|
+
store: target.storeName,
|
|
4124
|
+
error: commonUtils.errorMessage(err)
|
|
4125
|
+
});
|
|
4126
|
+
return;
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
3262
4129
|
async buildAgentAndServer() {
|
|
3263
4130
|
if (needsLakebase(this.config)) this.servicePrincipalPool = await createServicePrincipalPool(appkitUtils.require(this.context, lakebase, this.config).exports().getPgConfig());
|
|
3264
|
-
const
|
|
4131
|
+
const managedMemory = await this.resolveManagedMemory();
|
|
4132
|
+
const memoryBuilder = this.servicePrincipalPool ? createMemoryBuilder(this.config, this.servicePrincipalPool, { suppressVector: managedMemory !== void 0 }) : void 0;
|
|
3265
4133
|
this.log.debug("build:start", {
|
|
3266
4134
|
lakebase: memoryBuilder !== void 0,
|
|
4135
|
+
managedMemory: managedMemory !== void 0,
|
|
3267
4136
|
stripStaleCharts: this.config.stripStaleCharts !== false
|
|
3268
4137
|
});
|
|
3269
4138
|
this.built = await buildAgents({
|
|
3270
4139
|
config: this.config,
|
|
3271
4140
|
context: this.context,
|
|
3272
4141
|
memoryBuilder,
|
|
4142
|
+
...managedMemory ? { managedMemory } : {},
|
|
3273
4143
|
log: this.log
|
|
3274
4144
|
});
|
|
3275
4145
|
const instanceStorage = memoryBuilder?.instanceStorage();
|
|
@@ -3293,10 +4163,18 @@ var MastraPlugin = class MastraPlugin extends Plugin {
|
|
|
3293
4163
|
app: this.mastraApp,
|
|
3294
4164
|
mastra: this.mastra,
|
|
3295
4165
|
prefix: "",
|
|
3296
|
-
customApiRoutes: [
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
4166
|
+
customApiRoutes: [
|
|
4167
|
+
...historyRoute({
|
|
4168
|
+
path: MASTRA_ROUTES.history,
|
|
4169
|
+
agent: this.built.defaultAgentId
|
|
4170
|
+
}),
|
|
4171
|
+
...historyRoute({ path: `${MASTRA_ROUTES.history}/:agentId` }),
|
|
4172
|
+
...threadsRoute({
|
|
4173
|
+
path: MASTRA_ROUTES.threads,
|
|
4174
|
+
agent: this.built.defaultAgentId
|
|
4175
|
+
}),
|
|
4176
|
+
...threadsRoute({ path: `${MASTRA_ROUTES.threads}/:agentId` })
|
|
4177
|
+
]
|
|
3300
4178
|
});
|
|
3301
4179
|
await this.mastraServer.init();
|
|
3302
4180
|
this.log.debug("build:done", {
|
|
@@ -3304,6 +4182,7 @@ var MastraPlugin = class MastraPlugin extends Plugin {
|
|
|
3304
4182
|
defaultAgent: this.built.defaultAgentId,
|
|
3305
4183
|
routes: [
|
|
3306
4184
|
"/route/history",
|
|
4185
|
+
"/route/threads",
|
|
3307
4186
|
"/models",
|
|
3308
4187
|
"/suggestions",
|
|
3309
4188
|
"/embed/:type/:id"
|
|
@@ -3346,4 +4225,4 @@ function parseStatementLimit(raw) {
|
|
|
3346
4225
|
const mastra = toPlugin(MastraPlugin);
|
|
3347
4226
|
|
|
3348
4227
|
//#endregion
|
|
3349
|
-
export { DEFAULT_AGENT_MAX_STEPS, DEFAULT_GENIE_ALIAS, DEFAULT_STYLE_INSTRUCTIONS, FALLBACK_AGENT_ID, GENIE_INSTRUCTIONS, MASTRA_MODEL_OVERRIDE_KEY, MASTRA_REQUEST_ID_KEY, MASTRA_USER_EMAIL_KEY, MASTRA_USER_KEY, MASTRA_USER_NAME_KEY, MastraPlugin, TRACE_REQUEST_CONTEXT_KEYS, buildAgents, buildGenieToolkitProvider, buildGenieTools, buildMcpServer, buildRenderDataTool, chartPlannerRequestSchema, collectSpaceSuggestions, createAgent, createTool, extractModelOverride, fetchChart, mastra, normalizeGenieSpaces, prepareChart, resolveGenieSpaces, tool };
|
|
4228
|
+
export { DEFAULT_AGENT_MAX_STEPS, DEFAULT_ENTRY_PATH, DEFAULT_GENIE_ALIAS, DEFAULT_STORE_DESCRIPTION, DEFAULT_STYLE_INSTRUCTIONS, DEFAULT_TOPK, FALLBACK_AGENT_ID, GENIE_INSTRUCTIONS, MASTRA_MODEL_OVERRIDE_KEY, MASTRA_REQUEST_ID_KEY, MASTRA_USER_EMAIL_KEY, MASTRA_USER_KEY, MASTRA_USER_NAME_KEY, MEMORY_STORE_ENV, ManagedMemoryError, MastraPlugin, TRACE_REQUEST_CONTEXT_KEYS, buildAgents, buildGenieToolkitProvider, buildGenieTools, buildManagedMemoryTools, buildMcpServer, buildRenderDataTool, buildSummarizeTool, chartPlannerRequestSchema, collectSpaceSuggestions, createAgent, createTool, ensureStore, extractModelOverride, fetchChart, getStore, mastra, normalizeGenieSpaces, parseStoreName, prepareChart, renderEntry, resolveGenieSpaces, resolveManagedMemoryTarget, resolveMemoryContext, search, summarizeText, tool, writeEntry };
|