@dbx-tools/appkit-mastra 0.1.72 → 0.1.74
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/dist/index.d.ts +54 -5
- package/dist/index.js +256 -39
- package/package.json +7 -6
package/dist/index.d.ts
CHANGED
|
@@ -599,6 +599,24 @@ interface MastraPluginConfig extends BasePluginConfig {
|
|
|
599
599
|
* to drill deep into a dataset within a single turn.
|
|
600
600
|
*/
|
|
601
601
|
agentMaxSteps?: number;
|
|
602
|
+
/**
|
|
603
|
+
* Log user feedback (thumbs up/down + freeform comments) to MLflow as
|
|
604
|
+
* trace assessments, and surface the feedback controls in the chat UI.
|
|
605
|
+
*
|
|
606
|
+
* - `undefined` (default, auto): enabled only when MLflow tracing is
|
|
607
|
+
* wired - an OTLP exporter endpoint is set and an MLflow experiment
|
|
608
|
+
* is named (the same signals the observability pipeline needs to
|
|
609
|
+
* ship traces to MLflow). Otherwise off, since there'd be no trace
|
|
610
|
+
* to attach feedback to.
|
|
611
|
+
* - `true`: force on. Feedback controls show and writes are attempted
|
|
612
|
+
* regardless of env detection (use when the env is configured in a
|
|
613
|
+
* way the auto-probe doesn't recognize).
|
|
614
|
+
* - `false`: force off. No trace-id header, no feedback route, no UI.
|
|
615
|
+
*
|
|
616
|
+
* Feedback attaches to a turn's MLflow trace via the OpenTelemetry
|
|
617
|
+
* trace id the server stamps on each response; see `mlflow.ts`.
|
|
618
|
+
*/
|
|
619
|
+
feedback?: boolean;
|
|
602
620
|
/**
|
|
603
621
|
* Expose the plugin's agents (and optionally tools) as a Mastra MCP
|
|
604
622
|
* server so external MCP clients - Claude Desktop, Cursor, the Mastra
|
|
@@ -1292,14 +1310,15 @@ declare function buildMcpServer(opts: {
|
|
|
1292
1310
|
}): ResolvedMcp | null;
|
|
1293
1311
|
//#endregion
|
|
1294
1312
|
//#region packages/appkit-mastra/src/server.d.ts
|
|
1295
|
-
/**
|
|
1296
|
-
* `@mastra/express` subclass that stamps `RequestContext` with the
|
|
1297
|
-
* AppKit user, resource id, and a thread id backed by an HTTP-only
|
|
1298
|
-
* session cookie (`appkit_<plugin-name>_session_id`).
|
|
1299
|
-
*/
|
|
1300
1313
|
declare class MastraServer$1 extends MastraServer {
|
|
1301
1314
|
private config;
|
|
1302
1315
|
private log;
|
|
1316
|
+
/**
|
|
1317
|
+
* Whether to stamp the MLflow trace-id header on responses. Mirrors
|
|
1318
|
+
* the plugin's feedback gate: the explicit `config.feedback` override
|
|
1319
|
+
* wins, else auto-detect from the MLflow tracing env.
|
|
1320
|
+
*/
|
|
1321
|
+
private feedbackEnabled;
|
|
1303
1322
|
constructor(config: MastraPluginConfig, ...args: ConstructorParameters<typeof MastraServer>);
|
|
1304
1323
|
registerAuthMiddleware(): void;
|
|
1305
1324
|
configureRequestContextUser(requestContext: RequestContext): void;
|
|
@@ -1315,6 +1334,20 @@ declare class MastraServer$1 extends MastraServer {
|
|
|
1315
1334
|
* response header so dev tools can copy it from either side.
|
|
1316
1335
|
*/
|
|
1317
1336
|
configureRequestContextRequestId(req: express.Request, res: express.Response, requestContext: RequestContext): void;
|
|
1337
|
+
/**
|
|
1338
|
+
* Stamp the turn's MLflow trace id on the response so the chat client
|
|
1339
|
+
* can attach thumbs / comment feedback to it later. MLflow derives
|
|
1340
|
+
* its trace id from the OpenTelemetry trace id (`tr-<hex>`), and every
|
|
1341
|
+
* Mastra span for this request inherits the ambient OTel context (see
|
|
1342
|
+
* `observability.ts`), so the active span's trace id here is the id
|
|
1343
|
+
* MLflow will record for the turn.
|
|
1344
|
+
*
|
|
1345
|
+
* No-op unless feedback is enabled, and when no live OTel span is
|
|
1346
|
+
* active (e.g. the OTLP SDK isn't registered): in that case the header
|
|
1347
|
+
* is simply absent and the client hides feedback for that message,
|
|
1348
|
+
* degrading gracefully rather than emitting a bogus trace id.
|
|
1349
|
+
*/
|
|
1350
|
+
configureMlflowTraceId(res: express.Response): void;
|
|
1318
1351
|
/**
|
|
1319
1352
|
* Resolve the thread id this request targets and pin it on
|
|
1320
1353
|
* `RequestContext` (consumed by the agent stream for persistence and
|
|
@@ -1452,6 +1485,13 @@ declare class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
1452
1485
|
clearModelsCache: (host?: string) => Promise<void>;
|
|
1453
1486
|
};
|
|
1454
1487
|
clientConfig(): Record<string, unknown>;
|
|
1488
|
+
/**
|
|
1489
|
+
* Whether user feedback can be logged to MLflow. The explicit
|
|
1490
|
+
* `config.feedback` override wins; otherwise auto-detect from the
|
|
1491
|
+
* MLflow tracing env ({@link mlflowEnabled}). Gates both the client
|
|
1492
|
+
* config flag and the feedback route so the two never disagree.
|
|
1493
|
+
*/
|
|
1494
|
+
private feedbackEnabled;
|
|
1455
1495
|
injectRoutes(router: IAppRouter): void;
|
|
1456
1496
|
/**
|
|
1457
1497
|
* Invoke the Mastra express sub-app. Exists as a method (instead of reading
|
|
@@ -1472,6 +1512,15 @@ declare class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
1472
1512
|
* shows a bare empty state instead of built-in example prompts.
|
|
1473
1513
|
*/
|
|
1474
1514
|
private fetchSuggestions;
|
|
1515
|
+
/**
|
|
1516
|
+
* Implementation backing the `/route/feedback` route. Runs inside the
|
|
1517
|
+
* AppKit user-context proxy so `getExecutionContext()` returns the
|
|
1518
|
+
* OBO-scoped client and the assessment is attributed to the signed-in
|
|
1519
|
+
* user (their email / id as the assessment source). Returns the
|
|
1520
|
+
* created assessment id on success, or `undefined` on a soft failure
|
|
1521
|
+
* (see {@link logFeedback} in `./mlflow.js`).
|
|
1522
|
+
*/
|
|
1523
|
+
private logFeedback;
|
|
1475
1524
|
/**
|
|
1476
1525
|
* Implementation backing the `data` embed resolver
|
|
1477
1526
|
* (`GET /embed/data/:id`). Runs inside the AppKit user-context proxy so
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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";
|
|
1
|
+
import { ChartSchema, ChartTypeSchema, DEFAULT_COMMENT_NAME, DEFAULT_FEEDBACK_NAME, MASTRA_ROUTES, MLFLOW_TRACE_ID_HEADER, MODEL_OVERRIDE_BODY_FIELDS, MODEL_OVERRIDE_HEADER, MODEL_OVERRIDE_QUERY, MastraFeedbackRequestSchema, THREAD_ID_HEADER, THREAD_ID_QUERY } from "@dbx-tools/appkit-mastra-shared";
|
|
2
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";
|
|
@@ -22,6 +22,7 @@ import { Pool } from "pg";
|
|
|
22
22
|
import { Observability } from "@mastra/observability";
|
|
23
23
|
import { OtelBridge } from "@mastra/otel-bridge";
|
|
24
24
|
import { MastraServer } from "@mastra/express";
|
|
25
|
+
import { trace } from "@opentelemetry/api";
|
|
25
26
|
|
|
26
27
|
export * from "@dbx-tools/appkit-mastra-shared"
|
|
27
28
|
|
|
@@ -337,7 +338,7 @@ function sanitizeServingMessages(messages) {
|
|
|
337
338
|
* the demo client and any other UI consumer share the exact same
|
|
338
339
|
* shape this module reads and writes.
|
|
339
340
|
*/
|
|
340
|
-
const log$
|
|
341
|
+
const log$9 = logUtils.logger("mastra/chart");
|
|
341
342
|
/**
|
|
342
343
|
* TTL for cached chart entries. One hour balances "long enough for
|
|
343
344
|
* the host UI to fetch the chart well after the model finished
|
|
@@ -593,7 +594,7 @@ async function writeChart(entry) {
|
|
|
593
594
|
const key = await chartCacheKey(entry.chartId);
|
|
594
595
|
await CacheManager.getInstanceSync().set(key, entry, { ttl: CHART_CACHE_TTL_SEC });
|
|
595
596
|
} catch (err) {
|
|
596
|
-
log$
|
|
597
|
+
log$9.warn("write-error", {
|
|
597
598
|
chartId: entry.chartId,
|
|
598
599
|
error: commonUtils.errorMessage(err)
|
|
599
600
|
});
|
|
@@ -608,7 +609,7 @@ async function readChart(chartId) {
|
|
|
608
609
|
const key = await chartCacheKey(chartId);
|
|
609
610
|
return await CacheManager.getInstanceSync().get(key) ?? void 0;
|
|
610
611
|
} catch (err) {
|
|
611
|
-
log$
|
|
612
|
+
log$9.warn("read-error", {
|
|
612
613
|
chartId,
|
|
613
614
|
error: commonUtils.errorMessage(err)
|
|
614
615
|
});
|
|
@@ -633,7 +634,7 @@ async function readChart(chartId) {
|
|
|
633
634
|
async function prepareChart(opts) {
|
|
634
635
|
const chartId = commonUtils.id();
|
|
635
636
|
await writeChart({ chartId });
|
|
636
|
-
log$
|
|
637
|
+
log$9.debug("queued", { chartId });
|
|
637
638
|
runPrepareChart(chartId, opts);
|
|
638
639
|
return { chartId };
|
|
639
640
|
}
|
|
@@ -654,14 +655,14 @@ async function runPrepareChart(chartId, opts) {
|
|
|
654
655
|
chartId,
|
|
655
656
|
result
|
|
656
657
|
});
|
|
657
|
-
log$
|
|
658
|
+
log$9.info("done", {
|
|
658
659
|
chartId,
|
|
659
660
|
chartType: result.chartType,
|
|
660
661
|
elapsedMs: Date.now() - startedAt
|
|
661
662
|
});
|
|
662
663
|
} catch (err) {
|
|
663
664
|
const error = commonUtils.errorMessage(err);
|
|
664
|
-
log$
|
|
665
|
+
log$9.warn("error", {
|
|
665
666
|
chartId,
|
|
666
667
|
error
|
|
667
668
|
});
|
|
@@ -906,8 +907,8 @@ async function rawFetch(client, path, init) {
|
|
|
906
907
|
*/
|
|
907
908
|
async function requestJson(client, path, init) {
|
|
908
909
|
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);
|
|
910
|
+
if (!res.ok) throw new ManagedMemoryError(res.status, res.statusText, await safeText$1(res));
|
|
911
|
+
return safeJson$1(res);
|
|
911
912
|
}
|
|
912
913
|
/**
|
|
913
914
|
* Fetch a store by full name. Returns the raw store payload when it
|
|
@@ -921,8 +922,8 @@ async function getStore(client, fullName, signal) {
|
|
|
921
922
|
...signal ? { signal } : {}
|
|
922
923
|
});
|
|
923
924
|
if (res.status === 404) return null;
|
|
924
|
-
if (!res.ok) throw new ManagedMemoryError(res.status, res.statusText, await safeText(res));
|
|
925
|
-
return safeJson(res);
|
|
925
|
+
if (!res.ok) throw new ManagedMemoryError(res.status, res.statusText, await safeText$1(res));
|
|
926
|
+
return safeJson$1(res);
|
|
926
927
|
}
|
|
927
928
|
/**
|
|
928
929
|
* Probe for the store and create it when missing. Returns `true` when
|
|
@@ -1008,8 +1009,8 @@ function firstString(...values) {
|
|
|
1008
1009
|
for (const v of values) if (typeof v === "string" && v.trim() !== "") return v;
|
|
1009
1010
|
}
|
|
1010
1011
|
/** Parse a response body as JSON, returning `{}` on empty / invalid bodies. */
|
|
1011
|
-
async function safeJson(res) {
|
|
1012
|
-
const text = await safeText(res);
|
|
1012
|
+
async function safeJson$1(res) {
|
|
1013
|
+
const text = await safeText$1(res);
|
|
1013
1014
|
if (!text) return {};
|
|
1014
1015
|
try {
|
|
1015
1016
|
return JSON.parse(text);
|
|
@@ -1018,7 +1019,7 @@ async function safeJson(res) {
|
|
|
1018
1019
|
}
|
|
1019
1020
|
}
|
|
1020
1021
|
/** Read a response body as text, swallowing read errors. */
|
|
1021
|
-
async function safeText(res) {
|
|
1022
|
+
async function safeText$1(res) {
|
|
1022
1023
|
try {
|
|
1023
1024
|
return await res.text();
|
|
1024
1025
|
} catch {
|
|
@@ -1084,7 +1085,7 @@ function resolveMemoryContext(requestContext) {
|
|
|
1084
1085
|
* failure degrades to a short error string rather than aborting the
|
|
1085
1086
|
* turn.
|
|
1086
1087
|
*/
|
|
1087
|
-
const log$
|
|
1088
|
+
const log$8 = logUtils.logger("mastra/managed-memory/tools");
|
|
1088
1089
|
const saveMemoryInput = z.object({
|
|
1089
1090
|
content: z.string().describe("The durable fact or preference to remember."),
|
|
1090
1091
|
summary: z.string().optional().describe("Optional one-line summary used to label the memory.")
|
|
@@ -1121,10 +1122,10 @@ function buildManagedMemoryTools(runtime) {
|
|
|
1121
1122
|
contents: content,
|
|
1122
1123
|
...summary ? { description: summary } : {}
|
|
1123
1124
|
});
|
|
1124
|
-
log$
|
|
1125
|
+
log$8.debug("saved", { scope: ctx.scope });
|
|
1125
1126
|
return "Saved to memory.";
|
|
1126
1127
|
} catch (err) {
|
|
1127
|
-
log$
|
|
1128
|
+
log$8.warn("save:error", { error: commonUtils.errorMessage(err) });
|
|
1128
1129
|
return "Could not save to memory right now.";
|
|
1129
1130
|
}
|
|
1130
1131
|
}
|
|
@@ -1145,7 +1146,7 @@ function buildManagedMemoryTools(runtime) {
|
|
|
1145
1146
|
try {
|
|
1146
1147
|
return { memories: (await search(ctx.client, runtime.storeName, ctx.scope, query, runtime.topK)).map(renderEntry) };
|
|
1147
1148
|
} catch (err) {
|
|
1148
|
-
log$
|
|
1149
|
+
log$8.warn("search:error", { error: commonUtils.errorMessage(err) });
|
|
1149
1150
|
return { memories: [] };
|
|
1150
1151
|
}
|
|
1151
1152
|
}
|
|
@@ -1283,7 +1284,7 @@ async function safeWrite(log, writer, chunk, context = {}) {
|
|
|
1283
1284
|
* `instructions` when you want the canonical "how to drive the
|
|
1284
1285
|
* Genie tools" guidance.
|
|
1285
1286
|
*/
|
|
1286
|
-
const log$
|
|
1287
|
+
const log$7 = logUtils.logger("mastra/genie");
|
|
1287
1288
|
/** Default alias used when a single unnamed Genie space is wired up. */
|
|
1288
1289
|
const DEFAULT_GENIE_ALIAS = "default";
|
|
1289
1290
|
/**
|
|
@@ -1398,7 +1399,7 @@ async function readCachedConversationId(cacheKey) {
|
|
|
1398
1399
|
try {
|
|
1399
1400
|
return await CacheManager.getInstanceSync().get(cacheKey) ?? void 0;
|
|
1400
1401
|
} catch (err) {
|
|
1401
|
-
log$
|
|
1402
|
+
log$7.warn("conversation-cache:read-error", { error: commonUtils.errorMessage(err) });
|
|
1402
1403
|
return;
|
|
1403
1404
|
}
|
|
1404
1405
|
}
|
|
@@ -1414,7 +1415,7 @@ async function saveCachedConversationId(cacheKey, conversationId) {
|
|
|
1414
1415
|
try {
|
|
1415
1416
|
await CacheManager.getInstanceSync().set(cacheKey, conversationId, { ttl: CONVERSATION_TTL_SEC });
|
|
1416
1417
|
} catch (err) {
|
|
1417
|
-
log$
|
|
1418
|
+
log$7.warn("conversation-cache:write-error", { error: commonUtils.errorMessage(err) });
|
|
1418
1419
|
}
|
|
1419
1420
|
}
|
|
1420
1421
|
/** Force-evict a cached conversation id. Used on the stale-id recovery path. */
|
|
@@ -1423,7 +1424,7 @@ async function evictCachedConversationId(cacheKey) {
|
|
|
1423
1424
|
try {
|
|
1424
1425
|
await CacheManager.getInstanceSync().delete(cacheKey);
|
|
1425
1426
|
} catch (err) {
|
|
1426
|
-
log$
|
|
1427
|
+
log$7.warn("conversation-cache:delete-error", { error: commonUtils.errorMessage(err) });
|
|
1427
1428
|
}
|
|
1428
1429
|
}
|
|
1429
1430
|
/**
|
|
@@ -1537,7 +1538,7 @@ function buildAskGenieTool(opts) {
|
|
|
1537
1538
|
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`);
|
|
1538
1539
|
const cacheKey = await conversationCacheKey(spaceId, threadId);
|
|
1539
1540
|
await ensureConversationSeeded(requestContext, spaceId, cacheKey);
|
|
1540
|
-
await safeWrite(log$
|
|
1541
|
+
await safeWrite(log$7, writer, {
|
|
1541
1542
|
type: "started",
|
|
1542
1543
|
spaceId,
|
|
1543
1544
|
content: question
|
|
@@ -1550,7 +1551,7 @@ function buildAskGenieTool(opts) {
|
|
|
1550
1551
|
...seedConversationId ? { conversationId: seedConversationId } : {},
|
|
1551
1552
|
...signal ? { context: signal } : {}
|
|
1552
1553
|
})) {
|
|
1553
|
-
if (event.type !== "message") await safeWrite(log$
|
|
1554
|
+
if (event.type !== "message") await safeWrite(log$7, writer, event);
|
|
1554
1555
|
const eventConversationId = event.conversation_id;
|
|
1555
1556
|
if (eventConversationId) writeContextConversationId(requestContext, spaceId, eventConversationId);
|
|
1556
1557
|
if (event.type === "result") finalMessage = event.message;
|
|
@@ -1564,7 +1565,7 @@ function buildAskGenieTool(opts) {
|
|
|
1564
1565
|
} catch (err) {
|
|
1565
1566
|
const seeded = readContextConversationId(requestContext, spaceId);
|
|
1566
1567
|
if (seeded && apiUtils.isNotFoundError(err)) {
|
|
1567
|
-
log$
|
|
1568
|
+
log$7.warn("conversation-cache:stale, resetting", {
|
|
1568
1569
|
spaceId,
|
|
1569
1570
|
conversationId: seeded,
|
|
1570
1571
|
error: commonUtils.errorMessage(err)
|
|
@@ -2049,7 +2050,7 @@ async function collectSpaceSuggestions(opts) {
|
|
|
2049
2050
|
}));
|
|
2050
2051
|
writeSuggestionCache(spaceId, questions);
|
|
2051
2052
|
} catch (err) {
|
|
2052
|
-
log$
|
|
2053
|
+
log$7.warn("suggestions:fetch-error", {
|
|
2053
2054
|
spaceId,
|
|
2054
2055
|
error: commonUtils.errorMessage(err)
|
|
2055
2056
|
});
|
|
@@ -2110,7 +2111,7 @@ var ResultProcessor = class {
|
|
|
2110
2111
|
* query, or any REST failure the processor passes the messages through
|
|
2111
2112
|
* unchanged so a recall hiccup never blocks the conversation.
|
|
2112
2113
|
*/
|
|
2113
|
-
const log$
|
|
2114
|
+
const log$6 = logUtils.logger("mastra/processor/managed-memory-recall");
|
|
2114
2115
|
/**
|
|
2115
2116
|
* Build the recall processor bound to a managed-memory runtime. Created
|
|
2116
2117
|
* once at setup (when managed memory is active) and wired onto every
|
|
@@ -2129,7 +2130,7 @@ function buildManagedMemoryRecallProcessor(runtime) {
|
|
|
2129
2130
|
const entries = await search(ctx.client, runtime.storeName, ctx.scope, query, runtime.topK, args.abortSignal);
|
|
2130
2131
|
if (entries.length === 0) return args.messages;
|
|
2131
2132
|
const block = ["Relevant memories about the user (from long-term memory):", ...entries.map((e) => `- ${renderEntry(e)}`)].join("\n");
|
|
2132
|
-
log$
|
|
2133
|
+
log$6.debug("recalled", {
|
|
2133
2134
|
scope: ctx.scope,
|
|
2134
2135
|
entries: entries.length
|
|
2135
2136
|
});
|
|
@@ -2141,7 +2142,7 @@ function buildManagedMemoryRecallProcessor(runtime) {
|
|
|
2141
2142
|
}]
|
|
2142
2143
|
};
|
|
2143
2144
|
} catch (err) {
|
|
2144
|
-
log$
|
|
2145
|
+
log$6.warn("recall:error", { error: commonUtils.errorMessage(err) });
|
|
2145
2146
|
return args.messages;
|
|
2146
2147
|
}
|
|
2147
2148
|
}
|
|
@@ -2191,7 +2192,7 @@ function latestUserText(messages) {
|
|
|
2191
2192
|
* legacy `datasets[].chartId` payloads uniformly without coupling
|
|
2192
2193
|
* to specific tool ids.
|
|
2193
2194
|
*/
|
|
2194
|
-
const log$
|
|
2195
|
+
const log$5 = logUtils.logger("mastra/processor/strip-stale-charts");
|
|
2195
2196
|
/**
|
|
2196
2197
|
* Recursively clone `value`, omitting any property whose key is
|
|
2197
2198
|
* `chartId`. Arrays are mapped element-wise; primitives are
|
|
@@ -2239,7 +2240,7 @@ const stripStaleChartsProcessor = {
|
|
|
2239
2240
|
}
|
|
2240
2241
|
}
|
|
2241
2242
|
}
|
|
2242
|
-
if (stripped > 0) log$
|
|
2243
|
+
if (stripped > 0) log$5.debug("stripped", { results: stripped });
|
|
2243
2244
|
return args.messages;
|
|
2244
2245
|
}
|
|
2245
2246
|
};
|
|
@@ -2899,7 +2900,7 @@ function buildMcpServer(opts) {
|
|
|
2899
2900
|
|
|
2900
2901
|
//#endregion
|
|
2901
2902
|
//#region packages/appkit-mastra/src/history.ts
|
|
2902
|
-
const log$
|
|
2903
|
+
const log$4 = logUtils.logger("mastra/history");
|
|
2903
2904
|
/** Default history page size; matches the Mastra storage default. */
|
|
2904
2905
|
const DEFAULT_PER_PAGE$1 = 20;
|
|
2905
2906
|
/** Hard cap so a misbehaving client can't fetch the whole thread at once. */
|
|
@@ -2924,7 +2925,7 @@ async function loadHistory(opts) {
|
|
|
2924
2925
|
const page = Math.max(0, Math.trunc(opts.page ?? 0));
|
|
2925
2926
|
const memory = await opts.agent.getMemory();
|
|
2926
2927
|
if (!memory) {
|
|
2927
|
-
log$
|
|
2928
|
+
log$4.debug("recall:no-memory", {
|
|
2928
2929
|
agentId: opts.agent.id,
|
|
2929
2930
|
threadId: opts.threadId
|
|
2930
2931
|
});
|
|
@@ -2948,7 +2949,7 @@ async function loadHistory(opts) {
|
|
|
2948
2949
|
}
|
|
2949
2950
|
});
|
|
2950
2951
|
const uiMessages = toAISdkV5Messages(sortChronological(result.messages));
|
|
2951
|
-
log$
|
|
2952
|
+
log$4.debug("recall:done", {
|
|
2952
2953
|
agentId: opts.agent.id,
|
|
2953
2954
|
threadId: opts.threadId,
|
|
2954
2955
|
page,
|
|
@@ -2982,7 +2983,7 @@ async function loadHistory(opts) {
|
|
|
2982
2983
|
async function clearHistory(opts) {
|
|
2983
2984
|
const memory = await opts.agent.getMemory();
|
|
2984
2985
|
if (!memory) {
|
|
2985
|
-
log$
|
|
2986
|
+
log$4.debug("clear:no-memory", {
|
|
2986
2987
|
agentId: opts.agent.id,
|
|
2987
2988
|
threadId: opts.threadId
|
|
2988
2989
|
});
|
|
@@ -2996,7 +2997,7 @@ async function clearHistory(opts) {
|
|
|
2996
2997
|
perPage: 1
|
|
2997
2998
|
})).total;
|
|
2998
2999
|
} catch (err) {
|
|
2999
|
-
log$
|
|
3000
|
+
log$4.debug("clear:probe-failed", {
|
|
3000
3001
|
agentId: opts.agent.id,
|
|
3001
3002
|
threadId: opts.threadId,
|
|
3002
3003
|
error: commonUtils.errorMessage(err)
|
|
@@ -3006,13 +3007,13 @@ async function clearHistory(opts) {
|
|
|
3006
3007
|
try {
|
|
3007
3008
|
await memory.deleteThread(opts.threadId);
|
|
3008
3009
|
} catch (err) {
|
|
3009
|
-
log$
|
|
3010
|
+
log$4.warn("clear:delete-soft-failed", {
|
|
3010
3011
|
agentId: opts.agent.id,
|
|
3011
3012
|
threadId: opts.threadId,
|
|
3012
3013
|
error: commonUtils.errorMessage(err)
|
|
3013
3014
|
});
|
|
3014
3015
|
}
|
|
3015
|
-
log$
|
|
3016
|
+
log$4.info("clear:done", {
|
|
3016
3017
|
agentId: opts.agent.id,
|
|
3017
3018
|
threadId: opts.threadId,
|
|
3018
3019
|
cleared,
|
|
@@ -3131,6 +3132,144 @@ function parseIntParam$1(value) {
|
|
|
3131
3132
|
return Math.trunc(n);
|
|
3132
3133
|
}
|
|
3133
3134
|
|
|
3135
|
+
//#endregion
|
|
3136
|
+
//#region packages/appkit-mastra/src/rest.ts
|
|
3137
|
+
/**
|
|
3138
|
+
* Resolve the workspace host + an authenticated header set off the
|
|
3139
|
+
* client and issue a `fetch` against `path` (mounted on the host).
|
|
3140
|
+
* Runs as whatever identity the client carries - the per-request OBO
|
|
3141
|
+
* user when called from a request scope, the service principal
|
|
3142
|
+
* otherwise.
|
|
3143
|
+
*/
|
|
3144
|
+
async function databricksFetch(client, path, init) {
|
|
3145
|
+
const host = (await client.config.getHost()).toString();
|
|
3146
|
+
const headers = new Headers({
|
|
3147
|
+
"Content-Type": "application/json",
|
|
3148
|
+
...init.headers
|
|
3149
|
+
});
|
|
3150
|
+
await client.config.authenticate(headers);
|
|
3151
|
+
const url = new URL(path, host).toString();
|
|
3152
|
+
return fetch(url, {
|
|
3153
|
+
method: init.method,
|
|
3154
|
+
headers,
|
|
3155
|
+
...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
|
|
3156
|
+
...init.signal ? { signal: init.signal } : {}
|
|
3157
|
+
});
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
//#endregion
|
|
3161
|
+
//#region packages/appkit-mastra/src/mlflow.ts
|
|
3162
|
+
/**
|
|
3163
|
+
* MLflow user-feedback logging: detect whether MLflow tracing is wired
|
|
3164
|
+
* for this deployment, and log a thumbs / comment as a trace
|
|
3165
|
+
* *assessment* via the Databricks MLflow REST API.
|
|
3166
|
+
*
|
|
3167
|
+
* Feedback attaches to a trace, and the plugin's spans reach MLflow
|
|
3168
|
+
* through the same OTel pipeline as every other AppKit span (see
|
|
3169
|
+
* `observability.ts`). MLflow derives its trace id from the OpenTelemetry
|
|
3170
|
+
* trace id (`tr-<hex(otelTraceId)>`), so the server stamps the active
|
|
3171
|
+
* trace id on each turn's response and the client sends it back here.
|
|
3172
|
+
*
|
|
3173
|
+
* There is no MLflow JS SDK, so this posts to the assessments REST
|
|
3174
|
+
* endpoint directly using the OBO-scoped workspace client (the feedback
|
|
3175
|
+
* is thus attributed to the signed-in user). Trace export is
|
|
3176
|
+
* asynchronous, so the just-finished trace may not exist in MLflow yet
|
|
3177
|
+
* when the user reacts; the log call retries briefly on "not found"
|
|
3178
|
+
* before giving up softly.
|
|
3179
|
+
*/
|
|
3180
|
+
const log$3 = logUtils.logger("mastra/mlflow");
|
|
3181
|
+
/** Assessments REST path for a trace. `3.0` is the current MLflow API version. */
|
|
3182
|
+
const assessmentsPath = (traceId) => `/api/3.0/mlflow/traces/${encodeURIComponent(traceId)}/assessments`;
|
|
3183
|
+
/** Number of times to retry a "trace not found" response before giving up. */
|
|
3184
|
+
const NOT_FOUND_RETRIES = 3;
|
|
3185
|
+
/** Base backoff between "trace not found" retries, in ms (grows linearly). */
|
|
3186
|
+
const NOT_FOUND_BACKOFF_MS = 1200;
|
|
3187
|
+
/**
|
|
3188
|
+
* Whether MLflow feedback logging is available for this deployment.
|
|
3189
|
+
*
|
|
3190
|
+
* Enabled when an OTLP exporter endpoint is configured (traces are
|
|
3191
|
+
* actually shipped somewhere) AND an MLflow experiment is named - the
|
|
3192
|
+
* two signals that the OTLP backend is MLflow and traces will
|
|
3193
|
+
* materialize there. Both are standard env vars, so no plugin config is
|
|
3194
|
+
* required; a deployment opts in simply by wiring MLflow tracing.
|
|
3195
|
+
*/
|
|
3196
|
+
function mlflowEnabled() {
|
|
3197
|
+
const hasExporter = Boolean(process.env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim() || process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT?.trim());
|
|
3198
|
+
const hasExperiment = Boolean(process.env.MLFLOW_EXPERIMENT_ID?.trim() || process.env.MLFLOW_EXPERIMENT_NAME?.trim());
|
|
3199
|
+
return hasExporter && hasExperiment;
|
|
3200
|
+
}
|
|
3201
|
+
/**
|
|
3202
|
+
* Log a HUMAN feedback assessment to a trace. Returns the created
|
|
3203
|
+
* assessment id on success, or `undefined` when the trace can't be
|
|
3204
|
+
* found (even after retrying for export lag) or the request otherwise
|
|
3205
|
+
* fails - callers surface that as a soft "not recorded" rather than an
|
|
3206
|
+
* error, keeping the chat usable.
|
|
3207
|
+
*/
|
|
3208
|
+
async function logFeedback(client, params) {
|
|
3209
|
+
const hasValue = params.value !== void 0;
|
|
3210
|
+
const name = params.name?.trim() || (hasValue ? DEFAULT_FEEDBACK_NAME : DEFAULT_COMMENT_NAME);
|
|
3211
|
+
const value = hasValue ? params.value : params.comment;
|
|
3212
|
+
const body = { assessment: {
|
|
3213
|
+
trace_id: params.traceId,
|
|
3214
|
+
assessment_name: name,
|
|
3215
|
+
source: {
|
|
3216
|
+
source_type: "HUMAN",
|
|
3217
|
+
source_id: params.sourceId?.trim() || "user"
|
|
3218
|
+
},
|
|
3219
|
+
feedback: { value },
|
|
3220
|
+
...hasValue && params.comment?.trim() ? { rationale: params.comment } : {}
|
|
3221
|
+
} };
|
|
3222
|
+
for (let attempt = 0; attempt <= NOT_FOUND_RETRIES; attempt++) {
|
|
3223
|
+
let res;
|
|
3224
|
+
try {
|
|
3225
|
+
res = await databricksFetch(client, assessmentsPath(params.traceId), {
|
|
3226
|
+
method: "POST",
|
|
3227
|
+
body
|
|
3228
|
+
});
|
|
3229
|
+
} catch (err) {
|
|
3230
|
+
log$3.warn("feedback request failed", {
|
|
3231
|
+
traceId: params.traceId,
|
|
3232
|
+
error: commonUtils.errorMessage(err)
|
|
3233
|
+
});
|
|
3234
|
+
return;
|
|
3235
|
+
}
|
|
3236
|
+
if (res.ok) {
|
|
3237
|
+
const parsed = await safeJson(res);
|
|
3238
|
+
const assessmentId = parsed?.assessment?.assessment_id ?? parsed?.assessment_id;
|
|
3239
|
+
return typeof assessmentId === "string" ? assessmentId : "";
|
|
3240
|
+
}
|
|
3241
|
+
if (res.status === 404 && attempt < NOT_FOUND_RETRIES) {
|
|
3242
|
+
await delay(NOT_FOUND_BACKOFF_MS * (attempt + 1));
|
|
3243
|
+
continue;
|
|
3244
|
+
}
|
|
3245
|
+
log$3.warn("feedback not recorded", {
|
|
3246
|
+
traceId: params.traceId,
|
|
3247
|
+
status: res.status,
|
|
3248
|
+
body: await safeText(res)
|
|
3249
|
+
});
|
|
3250
|
+
return;
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
function delay(ms) {
|
|
3254
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3255
|
+
}
|
|
3256
|
+
async function safeJson(res) {
|
|
3257
|
+
const text = await safeText(res);
|
|
3258
|
+
if (!text) return {};
|
|
3259
|
+
try {
|
|
3260
|
+
return JSON.parse(text);
|
|
3261
|
+
} catch {
|
|
3262
|
+
return {};
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
async function safeText(res) {
|
|
3266
|
+
try {
|
|
3267
|
+
return await res.text();
|
|
3268
|
+
} catch {
|
|
3269
|
+
return "";
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3134
3273
|
//#endregion
|
|
3135
3274
|
//#region packages/appkit-mastra/src/memory.ts
|
|
3136
3275
|
/**
|
|
@@ -3446,12 +3585,26 @@ async function buildObservability(options) {
|
|
|
3446
3585
|
* AppKit user, resource id, and a thread id backed by an HTTP-only
|
|
3447
3586
|
* session cookie (`appkit_<plugin-name>_session_id`).
|
|
3448
3587
|
*/
|
|
3588
|
+
/**
|
|
3589
|
+
* OpenTelemetry's sentinel for "no valid trace" - 32 zero hex chars.
|
|
3590
|
+
* `trace.getActiveSpan()` returns a non-recording span with this id
|
|
3591
|
+
* when no SDK is registered, which must never be surfaced as a trace to
|
|
3592
|
+
* attach feedback to.
|
|
3593
|
+
*/
|
|
3594
|
+
const INVALID_TRACE_ID = "0".repeat(32);
|
|
3449
3595
|
var MastraServer$1 = class extends MastraServer {
|
|
3450
3596
|
log;
|
|
3597
|
+
/**
|
|
3598
|
+
* Whether to stamp the MLflow trace-id header on responses. Mirrors
|
|
3599
|
+
* the plugin's feedback gate: the explicit `config.feedback` override
|
|
3600
|
+
* wins, else auto-detect from the MLflow tracing env.
|
|
3601
|
+
*/
|
|
3602
|
+
feedbackEnabled;
|
|
3451
3603
|
constructor(config, ...args) {
|
|
3452
3604
|
super(...args);
|
|
3453
3605
|
this.config = config;
|
|
3454
3606
|
this.log = logUtils.logger(config);
|
|
3607
|
+
this.feedbackEnabled = config.feedback ?? mlflowEnabled();
|
|
3455
3608
|
}
|
|
3456
3609
|
registerAuthMiddleware() {
|
|
3457
3610
|
super.registerAuthMiddleware();
|
|
@@ -3461,6 +3614,7 @@ var MastraServer$1 = class extends MastraServer {
|
|
|
3461
3614
|
this.configureRequestContextThreadId(req, res, requestContext);
|
|
3462
3615
|
this.configureRequestContextModelOverride(req, requestContext);
|
|
3463
3616
|
this.configureRequestContextRequestId(req, res, requestContext);
|
|
3617
|
+
this.configureMlflowTraceId(res);
|
|
3464
3618
|
this.log.debug("auth:middleware", {
|
|
3465
3619
|
method: req.method,
|
|
3466
3620
|
path: req.path,
|
|
@@ -3507,6 +3661,25 @@ var MastraServer$1 = class extends MastraServer {
|
|
|
3507
3661
|
res.setHeader("X-Request-Id", requestId);
|
|
3508
3662
|
}
|
|
3509
3663
|
/**
|
|
3664
|
+
* Stamp the turn's MLflow trace id on the response so the chat client
|
|
3665
|
+
* can attach thumbs / comment feedback to it later. MLflow derives
|
|
3666
|
+
* its trace id from the OpenTelemetry trace id (`tr-<hex>`), and every
|
|
3667
|
+
* Mastra span for this request inherits the ambient OTel context (see
|
|
3668
|
+
* `observability.ts`), so the active span's trace id here is the id
|
|
3669
|
+
* MLflow will record for the turn.
|
|
3670
|
+
*
|
|
3671
|
+
* No-op unless feedback is enabled, and when no live OTel span is
|
|
3672
|
+
* active (e.g. the OTLP SDK isn't registered): in that case the header
|
|
3673
|
+
* is simply absent and the client hides feedback for that message,
|
|
3674
|
+
* degrading gracefully rather than emitting a bogus trace id.
|
|
3675
|
+
*/
|
|
3676
|
+
configureMlflowTraceId(res) {
|
|
3677
|
+
if (!this.feedbackEnabled || res.headersSent) return;
|
|
3678
|
+
const traceId = trace.getActiveSpan()?.spanContext().traceId;
|
|
3679
|
+
if (!traceId || traceId === INVALID_TRACE_ID) return;
|
|
3680
|
+
res.setHeader(MLFLOW_TRACE_ID_HEADER, `tr-${traceId}`);
|
|
3681
|
+
}
|
|
3682
|
+
/**
|
|
3510
3683
|
* Resolve the thread id this request targets and pin it on
|
|
3511
3684
|
* `RequestContext` (consumed by the agent stream for persistence and
|
|
3512
3685
|
* by the history / threads routes). Resolution order:
|
|
@@ -3938,9 +4111,19 @@ var MastraPlugin = class MastraPlugin extends Plugin {
|
|
|
3938
4111
|
return {
|
|
3939
4112
|
basePath: `/api/${this.name}`,
|
|
3940
4113
|
defaultAgent: this.built?.defaultAgentId ?? FALLBACK_AGENT_ID,
|
|
3941
|
-
agents: Object.keys(this.built?.agents ?? {})
|
|
4114
|
+
agents: Object.keys(this.built?.agents ?? {}),
|
|
4115
|
+
feedbackEnabled: this.feedbackEnabled()
|
|
3942
4116
|
};
|
|
3943
4117
|
}
|
|
4118
|
+
/**
|
|
4119
|
+
* Whether user feedback can be logged to MLflow. The explicit
|
|
4120
|
+
* `config.feedback` override wins; otherwise auto-detect from the
|
|
4121
|
+
* MLflow tracing env ({@link mlflowEnabled}). Gates both the client
|
|
4122
|
+
* config flag and the feedback route so the two never disagree.
|
|
4123
|
+
*/
|
|
4124
|
+
feedbackEnabled() {
|
|
4125
|
+
return this.config.feedback ?? mlflowEnabled();
|
|
4126
|
+
}
|
|
3944
4127
|
injectRoutes(router) {
|
|
3945
4128
|
router.get(MASTRA_ROUTES.models, (req, res, next) => {
|
|
3946
4129
|
this.userScopedSelf(req).listModels().then((endpoints) => res.json({ endpoints })).catch(next);
|
|
@@ -3993,6 +4176,24 @@ var MastraPlugin = class MastraPlugin extends Plugin {
|
|
|
3993
4176
|
};
|
|
3994
4177
|
router.get(MASTRA_ROUTES.suggestions, handleSuggestions);
|
|
3995
4178
|
router.get(`${MASTRA_ROUTES.suggestions}/:agentId`, handleSuggestions);
|
|
4179
|
+
router.post(MASTRA_ROUTES.feedback, (req, res, next) => {
|
|
4180
|
+
if (!this.feedbackEnabled()) {
|
|
4181
|
+
res.status(404).json({ ok: false });
|
|
4182
|
+
return;
|
|
4183
|
+
}
|
|
4184
|
+
const parsed = MastraFeedbackRequestSchema.safeParse(req.body);
|
|
4185
|
+
if (!parsed.success) {
|
|
4186
|
+
res.status(400).json({
|
|
4187
|
+
ok: false,
|
|
4188
|
+
error: parsed.error.message
|
|
4189
|
+
});
|
|
4190
|
+
return;
|
|
4191
|
+
}
|
|
4192
|
+
this.userScopedSelf(req).logFeedback(parsed.data).then((assessmentId) => res.json({
|
|
4193
|
+
ok: assessmentId !== void 0,
|
|
4194
|
+
...assessmentId ? { assessmentId } : {}
|
|
4195
|
+
})).catch(next);
|
|
4196
|
+
});
|
|
3996
4197
|
router.use((req, res, next) => {
|
|
3997
4198
|
if (!this.mastraApp) return res.status(503).end();
|
|
3998
4199
|
return this.userScopedSelf(req).dispatchMastra(req, res, next);
|
|
@@ -4029,6 +4230,22 @@ var MastraPlugin = class MastraPlugin extends Plugin {
|
|
|
4029
4230
|
});
|
|
4030
4231
|
}
|
|
4031
4232
|
/**
|
|
4233
|
+
* Implementation backing the `/route/feedback` route. Runs inside the
|
|
4234
|
+
* AppKit user-context proxy so `getExecutionContext()` returns the
|
|
4235
|
+
* OBO-scoped client and the assessment is attributed to the signed-in
|
|
4236
|
+
* user (their email / id as the assessment source). Returns the
|
|
4237
|
+
* created assessment id on success, or `undefined` on a soft failure
|
|
4238
|
+
* (see {@link logFeedback} in `./mlflow.js`).
|
|
4239
|
+
*/
|
|
4240
|
+
async logFeedback(feedback) {
|
|
4241
|
+
const ctx = getExecutionContext();
|
|
4242
|
+
const sourceId = "userEmail" in ctx && ctx.userEmail ? ctx.userEmail : "userId" in ctx ? ctx.userId : ctx.serviceUserId;
|
|
4243
|
+
return logFeedback(ctx.client, {
|
|
4244
|
+
...feedback,
|
|
4245
|
+
...sourceId ? { sourceId } : {}
|
|
4246
|
+
});
|
|
4247
|
+
}
|
|
4248
|
+
/**
|
|
4032
4249
|
* Implementation backing the `data` embed resolver
|
|
4033
4250
|
* (`GET /embed/data/:id`). Runs inside the AppKit user-context proxy so
|
|
4034
4251
|
* `getExecutionContext()` returns the OBO-scoped workspace
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dbx-tools/appkit-mastra",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.74",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"@databricks/sdk-experimental": "^0.17",
|
|
6
|
-
"@dbx-tools/appkit-mastra-shared": "0.1.
|
|
7
|
-
"@dbx-tools/genie": "0.1.
|
|
8
|
-
"@dbx-tools/genie-shared": "0.1.
|
|
9
|
-
"@dbx-tools/model": "0.1.
|
|
10
|
-
"@dbx-tools/shared": "0.1.
|
|
6
|
+
"@dbx-tools/appkit-mastra-shared": "0.1.74",
|
|
7
|
+
"@dbx-tools/genie": "0.1.74",
|
|
8
|
+
"@dbx-tools/genie-shared": "0.1.74",
|
|
9
|
+
"@dbx-tools/model": "0.1.74",
|
|
10
|
+
"@dbx-tools/shared": "0.1.74",
|
|
11
11
|
"@mastra/ai-sdk": "^1",
|
|
12
12
|
"@mastra/core": "^1",
|
|
13
13
|
"@mastra/express": "^1",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"@mastra/observability": "^1",
|
|
18
18
|
"@mastra/otel-bridge": "^1",
|
|
19
19
|
"@mastra/pg": "^1",
|
|
20
|
+
"@opentelemetry/api": "^1.9.0",
|
|
20
21
|
"pg": "^8.20.0",
|
|
21
22
|
"zod": "^4.3.6"
|
|
22
23
|
},
|