@dbx-tools/appkit-mastra 0.1.74 → 0.1.75
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 +67 -4
- package/dist/index.d.ts +32 -7
- package/dist/index.js +264 -246
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -13,10 +13,14 @@ is a one-line import change.
|
|
|
13
13
|
|
|
14
14
|
The implementation lives under `src/`: agent registration in
|
|
15
15
|
[`agents.ts`](src/agents.ts), the plugin + routes in
|
|
16
|
-
[`plugin.ts`](src/plugin.ts) / [`server.ts`](src/server.ts),
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
[`
|
|
16
|
+
[`plugin.ts`](src/plugin.ts) / [`server.ts`](src/server.ts), thread
|
|
17
|
+
history / listing in [`history.ts`](src/history.ts) /
|
|
18
|
+
[`threads.ts`](src/threads.ts), Model Serving resolution in
|
|
19
|
+
[`model.ts`](src/model.ts) / [`serving.ts`](src/serving.ts), Genie
|
|
20
|
+
tooling in [`genie.ts`](src/genie.ts), the chart pipeline in
|
|
21
|
+
[`chart.ts`](src/chart.ts), and MLflow feedback logging in
|
|
22
|
+
[`mlflow.ts`](src/mlflow.ts) (over the shared REST helper in
|
|
23
|
+
[`rest.ts`](src/rest.ts)).
|
|
20
24
|
|
|
21
25
|
## Quick start
|
|
22
26
|
|
|
@@ -267,6 +271,35 @@ entry settles. Cache entries carry a 1h TTL; after expiry the route
|
|
|
267
271
|
placement contract are in [`chart.ts`](src/chart.ts); the wire shapes
|
|
268
272
|
are in [`@dbx-tools/appkit-mastra-shared`](../appkit-mastra-shared).
|
|
269
273
|
|
|
274
|
+
## Feedback (MLflow)
|
|
275
|
+
|
|
276
|
+
When MLflow tracing is wired for the deployment, the plugin lets users
|
|
277
|
+
rate an assistant turn (thumbs up / down) and leave a comment, logged as
|
|
278
|
+
a HUMAN [assessment](https://mlflow.org/docs/latest/genai/assessments/)
|
|
279
|
+
on that turn's trace and attributed to the signed-in (OBO) user.
|
|
280
|
+
|
|
281
|
+
Enablement is auto-detected: it turns on when an OTLP exporter endpoint
|
|
282
|
+
(`OTEL_EXPORTER_OTLP_ENDPOINT` / `..._TRACES_ENDPOINT`) **and** an MLflow
|
|
283
|
+
experiment (`MLFLOW_EXPERIMENT_ID` / `MLFLOW_EXPERIMENT_NAME`) are
|
|
284
|
+
configured - the two signals that traces actually materialize in MLflow.
|
|
285
|
+
Set `config.feedback` to `true` / `false` to force it on or off
|
|
286
|
+
regardless of the env.
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
mastra({ agents: support, feedback: true }); // force-enable
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Under the hood the server derives MLflow's trace id from the active
|
|
293
|
+
OpenTelemetry span (`tr-<hex(otelTraceId)>`) and stamps it on each
|
|
294
|
+
turn's response (`MLFLOW_TRACE_ID_HEADER`); the client sends it back to
|
|
295
|
+
`POST ${basePath}/route/feedback`, which posts the assessment to the
|
|
296
|
+
Databricks MLflow REST API. Trace export is asynchronous, so a
|
|
297
|
+
just-finished trace may not exist yet - the log call retries briefly on
|
|
298
|
+
"not found" and otherwise fails softly (the chat stays usable). The
|
|
299
|
+
current state is published to the client as
|
|
300
|
+
`clientConfig().feedbackEnabled`; see [`mlflow.ts`](src/mlflow.ts) and
|
|
301
|
+
the UI wiring in [`@dbx-tools/appkit-mastra-ui`](../appkit-mastra-ui).
|
|
302
|
+
|
|
270
303
|
## Model resolution
|
|
271
304
|
|
|
272
305
|
Each agent call resolves a model lazily (so concurrent requests keep
|
|
@@ -338,6 +371,36 @@ resolves its model and tools as the calling user. The resolved endpoint
|
|
|
338
371
|
paths are available in-process via
|
|
339
372
|
`appkitUtils.require(this.context, mastra).exports().getMcp()`.
|
|
340
373
|
|
|
374
|
+
## Client API surface (`apiAccess`)
|
|
375
|
+
|
|
376
|
+
`@mastra/express` registers its full management route table under the
|
|
377
|
+
plugin mount - agent inference *plus* admin / mutating routes (direct
|
|
378
|
+
tool execution, workflow control, raw memory read/write, telemetry,
|
|
379
|
+
logs, scores). AppKit authenticates every request as the OBO user, but
|
|
380
|
+
does not restrict *which* of those a browser client may call.
|
|
381
|
+
|
|
382
|
+
`config.apiAccess` gates that surface at the plugin's dispatch point:
|
|
383
|
+
|
|
384
|
+
- `"scoped"` (default): only the routes the chat client needs are
|
|
385
|
+
dispatched to Mastra - agent inference (`stream` / `generate` /
|
|
386
|
+
`network`), read-only agent metadata (`GET /agents`,
|
|
387
|
+
`GET /agents/:id`), this plugin's own OBO- and resource-scoped
|
|
388
|
+
`/route/*` routes (history / threads / feedback), and, when `mcp` is
|
|
389
|
+
enabled, the MCP transport. Everything else is refused with `403`
|
|
390
|
+
before it reaches Mastra.
|
|
391
|
+
- `"full"`: dispatch the entire stock Mastra API. Use only for a trusted
|
|
392
|
+
first-party console that genuinely needs the management surface.
|
|
393
|
+
|
|
394
|
+
```ts
|
|
395
|
+
mastra({ agents: support }); // scoped by default
|
|
396
|
+
mastra({ agents: support, apiAccess: "full" }); // opt into the full API
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
The gate is a pure allowlist (`isMastraRequestAllowed` in
|
|
400
|
+
[`server.ts`](src/server.ts)); the browser chat client only ever uses
|
|
401
|
+
the agent stream and the `/route/*` routes, so the default breaks
|
|
402
|
+
nothing.
|
|
403
|
+
|
|
341
404
|
## License
|
|
342
405
|
|
|
343
406
|
Apache-2.0
|
package/dist/index.d.ts
CHANGED
|
@@ -642,6 +642,28 @@ interface MastraPluginConfig extends BasePluginConfig {
|
|
|
642
642
|
* calling user.
|
|
643
643
|
*/
|
|
644
644
|
mcp?: boolean | MastraMcpConfig;
|
|
645
|
+
/**
|
|
646
|
+
* How much of the stock `@mastra/express` management API is reachable
|
|
647
|
+
* through the plugin mount. `@mastra/express` registers its full route
|
|
648
|
+
* table (agent inference plus admin / mutating routes: direct tool
|
|
649
|
+
* execution, workflow control, raw memory read/write, telemetry, logs,
|
|
650
|
+
* scores). AppKit already authenticates every request as the OBO user,
|
|
651
|
+
* but nothing there restricts *which* of those operations the browser
|
|
652
|
+
* client may invoke.
|
|
653
|
+
*
|
|
654
|
+
* - `"scoped"` (default): only the routes the chat client legitimately
|
|
655
|
+
* needs are dispatched to Mastra - agent inference
|
|
656
|
+
* (`stream` / `generate` / `network`), read-only agent metadata, this
|
|
657
|
+
* plugin's own OBO- and resource-scoped `/route/*` routes (history /
|
|
658
|
+
* threads), and, when {@link mcp} is enabled, the MCP transport.
|
|
659
|
+
* Everything else (tool execution, workflow control, raw memory,
|
|
660
|
+
* telemetry, logs, scores, and other mutations) is rejected with
|
|
661
|
+
* `403` before it reaches Mastra.
|
|
662
|
+
* - `"full"`: dispatch the entire stock Mastra API. Use only for a
|
|
663
|
+
* trusted first-party console that genuinely needs the management
|
|
664
|
+
* surface.
|
|
665
|
+
*/
|
|
666
|
+
apiAccess?: "scoped" | "full";
|
|
645
667
|
}
|
|
646
668
|
//#endregion
|
|
647
669
|
//#region packages/appkit-mastra/src/memory.d.ts
|
|
@@ -1310,13 +1332,17 @@ declare function buildMcpServer(opts: {
|
|
|
1310
1332
|
}): ResolvedMcp | null;
|
|
1311
1333
|
//#endregion
|
|
1312
1334
|
//#region packages/appkit-mastra/src/server.d.ts
|
|
1335
|
+
/**
|
|
1336
|
+
* `@mastra/express` subclass that stamps `RequestContext` with the
|
|
1337
|
+
* AppKit user, resource id, and a thread id backed by an HTTP-only
|
|
1338
|
+
* session cookie (`appkit_<plugin-name>_session_id`).
|
|
1339
|
+
*/
|
|
1313
1340
|
declare class MastraServer$1 extends MastraServer {
|
|
1314
1341
|
private config;
|
|
1315
1342
|
private log;
|
|
1316
1343
|
/**
|
|
1317
|
-
* Whether to stamp the MLflow trace-id header on responses.
|
|
1318
|
-
*
|
|
1319
|
-
* wins, else auto-detect from the MLflow tracing env.
|
|
1344
|
+
* Whether to stamp the MLflow trace-id header on responses. Shares the
|
|
1345
|
+
* plugin's feedback gate via {@link resolveFeedbackEnabled}.
|
|
1320
1346
|
*/
|
|
1321
1347
|
private feedbackEnabled;
|
|
1322
1348
|
constructor(config: MastraPluginConfig, ...args: ConstructorParameters<typeof MastraServer>);
|
|
@@ -1486,10 +1512,9 @@ declare class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
1486
1512
|
};
|
|
1487
1513
|
clientConfig(): Record<string, unknown>;
|
|
1488
1514
|
/**
|
|
1489
|
-
* Whether user feedback can be logged to MLflow.
|
|
1490
|
-
*
|
|
1491
|
-
*
|
|
1492
|
-
* config flag and the feedback route so the two never disagree.
|
|
1515
|
+
* Whether user feedback can be logged to MLflow. Delegates to
|
|
1516
|
+
* {@link resolveFeedbackEnabled} so the client-config flag and the
|
|
1517
|
+
* feedback route share the same gate as the server's trace-id header.
|
|
1493
1518
|
*/
|
|
1494
1519
|
private feedbackEnabled;
|
|
1495
1520
|
injectRoutes(router: IAppRouter): void;
|
package/dist/index.js
CHANGED
|
@@ -849,8 +849,63 @@ function buildRenderDataTool(config) {
|
|
|
849
849
|
});
|
|
850
850
|
}
|
|
851
851
|
|
|
852
|
+
//#endregion
|
|
853
|
+
//#region packages/appkit-mastra/src/rest.ts
|
|
854
|
+
/**
|
|
855
|
+
* Resolve the workspace host + an authenticated header set off the
|
|
856
|
+
* client and issue a `fetch` against `path` (mounted on the host).
|
|
857
|
+
* Runs as whatever identity the client carries - the per-request OBO
|
|
858
|
+
* user when called from a request scope, the service principal
|
|
859
|
+
* otherwise.
|
|
860
|
+
*/
|
|
861
|
+
async function databricksFetch(client, path, init) {
|
|
862
|
+
const host = (await client.config.getHost()).toString();
|
|
863
|
+
const headers = new Headers({
|
|
864
|
+
"Content-Type": "application/json",
|
|
865
|
+
...init.headers
|
|
866
|
+
});
|
|
867
|
+
await client.config.authenticate(headers);
|
|
868
|
+
const url = new URL(path, host).toString();
|
|
869
|
+
return fetch(url, {
|
|
870
|
+
method: init.method,
|
|
871
|
+
headers,
|
|
872
|
+
...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
|
|
873
|
+
...init.signal ? { signal: init.signal } : {}
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
/** Read a response body as text, swallowing read errors (returns `""`). */
|
|
877
|
+
async function readResponseText(res) {
|
|
878
|
+
try {
|
|
879
|
+
return await res.text();
|
|
880
|
+
} catch {
|
|
881
|
+
return "";
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
/** Parse a response body as JSON, returning `{}` on empty / invalid bodies. */
|
|
885
|
+
async function readResponseJson(res) {
|
|
886
|
+
const text = await readResponseText(res);
|
|
887
|
+
if (!text) return {};
|
|
888
|
+
try {
|
|
889
|
+
return JSON.parse(text);
|
|
890
|
+
} catch {
|
|
891
|
+
return {};
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
852
895
|
//#endregion
|
|
853
896
|
//#region packages/appkit-mastra/src/connectors/managed-memory/client.ts
|
|
897
|
+
/**
|
|
898
|
+
* Thin REST client over the Databricks Unity Catalog Managed Agent
|
|
899
|
+
* Memory API (Beta). There is no TS SDK for memory-stores yet, so this
|
|
900
|
+
* issues authenticated requests through the shared {@link databricksFetch}
|
|
901
|
+
* primitive (`rest.ts`) as whatever identity the caller's client carries
|
|
902
|
+
* - the per-request OBO user for tools / recall, the service principal
|
|
903
|
+
* for setup-time probes.
|
|
904
|
+
*
|
|
905
|
+
* Responses are parsed defensively: the Beta wire shape may still drift,
|
|
906
|
+
* so the search parser tolerates missing / renamed entry fields and the
|
|
907
|
+
* store probe maps a 404 to `null` rather than throwing.
|
|
908
|
+
*/
|
|
854
909
|
/** Base path for the Unity Catalog memory-stores REST surface. */
|
|
855
910
|
const MEMORY_STORES_PATH = "/api/2.1/unity-catalog/memory-stores";
|
|
856
911
|
/**
|
|
@@ -883,32 +938,14 @@ function parseStoreName(fullName) {
|
|
|
883
938
|
};
|
|
884
939
|
}
|
|
885
940
|
/**
|
|
886
|
-
* Resolve the workspace host and an authenticated header set off the
|
|
887
|
-
* client, then issue a `fetch`. Returns the raw `Response` so callers
|
|
888
|
-
* decide how to handle status codes (the store probe wants the 404, the
|
|
889
|
-
* mutating calls want a throw).
|
|
890
|
-
*/
|
|
891
|
-
async function rawFetch(client, path, init) {
|
|
892
|
-
const host = (await client.config.getHost()).toString();
|
|
893
|
-
const headers = new Headers({ "Content-Type": "application/json" });
|
|
894
|
-
await client.config.authenticate(headers);
|
|
895
|
-
const url = new URL(path, host).toString();
|
|
896
|
-
return fetch(url, {
|
|
897
|
-
method: init.method,
|
|
898
|
-
headers,
|
|
899
|
-
...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
|
|
900
|
-
...init.signal ? { signal: init.signal } : {}
|
|
901
|
-
});
|
|
902
|
-
}
|
|
903
|
-
/**
|
|
904
941
|
* Issue a request and parse the JSON body, throwing
|
|
905
942
|
* {@link ManagedMemoryError} on any non-2xx status. Used by the calls
|
|
906
943
|
* where anything but success is a real error (create / write / search).
|
|
907
944
|
*/
|
|
908
945
|
async function requestJson(client, path, init) {
|
|
909
|
-
const res = await
|
|
910
|
-
if (!res.ok) throw new ManagedMemoryError(res.status, res.statusText, await
|
|
911
|
-
return
|
|
946
|
+
const res = await databricksFetch(client, path, init);
|
|
947
|
+
if (!res.ok) throw new ManagedMemoryError(res.status, res.statusText, await readResponseText(res));
|
|
948
|
+
return readResponseJson(res);
|
|
912
949
|
}
|
|
913
950
|
/**
|
|
914
951
|
* Fetch a store by full name. Returns the raw store payload when it
|
|
@@ -917,13 +954,13 @@ async function requestJson(client, path, init) {
|
|
|
917
954
|
* Any other non-2xx status throws.
|
|
918
955
|
*/
|
|
919
956
|
async function getStore(client, fullName, signal) {
|
|
920
|
-
const res = await
|
|
957
|
+
const res = await databricksFetch(client, `${MEMORY_STORES_PATH}/${fullName}`, {
|
|
921
958
|
method: "GET",
|
|
922
959
|
...signal ? { signal } : {}
|
|
923
960
|
});
|
|
924
961
|
if (res.status === 404) return null;
|
|
925
|
-
if (!res.ok) throw new ManagedMemoryError(res.status, res.statusText, await
|
|
926
|
-
return
|
|
962
|
+
if (!res.ok) throw new ManagedMemoryError(res.status, res.statusText, await readResponseText(res));
|
|
963
|
+
return readResponseJson(res);
|
|
927
964
|
}
|
|
928
965
|
/**
|
|
929
966
|
* Probe for the store and create it when missing. Returns `true` when
|
|
@@ -990,10 +1027,14 @@ function parseEntries(body) {
|
|
|
990
1027
|
for (const raw of list) {
|
|
991
1028
|
if (!raw || typeof raw !== "object") continue;
|
|
992
1029
|
const obj = raw;
|
|
993
|
-
const contents =
|
|
1030
|
+
const contents = stringUtils.firstNonEmpty([
|
|
1031
|
+
obj["contents"],
|
|
1032
|
+
obj["content"],
|
|
1033
|
+
obj["text"]
|
|
1034
|
+
]);
|
|
994
1035
|
if (!contents) continue;
|
|
995
|
-
const path =
|
|
996
|
-
const description =
|
|
1036
|
+
const path = stringUtils.firstNonEmpty(obj["path"]);
|
|
1037
|
+
const description = stringUtils.firstNonEmpty([obj["description"], obj["summary"]]);
|
|
997
1038
|
const score = typeof obj["score"] === "number" ? obj["score"] : void 0;
|
|
998
1039
|
out.push({
|
|
999
1040
|
contents,
|
|
@@ -1004,28 +1045,6 @@ function parseEntries(body) {
|
|
|
1004
1045
|
}
|
|
1005
1046
|
return out;
|
|
1006
1047
|
}
|
|
1007
|
-
/** Return the first argument that is a non-empty string, else undefined. */
|
|
1008
|
-
function firstString(...values) {
|
|
1009
|
-
for (const v of values) if (typeof v === "string" && v.trim() !== "") return v;
|
|
1010
|
-
}
|
|
1011
|
-
/** Parse a response body as JSON, returning `{}` on empty / invalid bodies. */
|
|
1012
|
-
async function safeJson$1(res) {
|
|
1013
|
-
const text = await safeText$1(res);
|
|
1014
|
-
if (!text) return {};
|
|
1015
|
-
try {
|
|
1016
|
-
return JSON.parse(text);
|
|
1017
|
-
} catch {
|
|
1018
|
-
return {};
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
/** Read a response body as text, swallowing read errors. */
|
|
1022
|
-
async function safeText$1(res) {
|
|
1023
|
-
try {
|
|
1024
|
-
return await res.text();
|
|
1025
|
-
} catch {
|
|
1026
|
-
return "";
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
1048
|
|
|
1030
1049
|
//#endregion
|
|
1031
1050
|
//#region packages/appkit-mastra/src/connectors/managed-memory/context.ts
|
|
@@ -2898,6 +2917,27 @@ function buildMcpServer(opts) {
|
|
|
2898
2917
|
};
|
|
2899
2918
|
}
|
|
2900
2919
|
|
|
2920
|
+
//#endregion
|
|
2921
|
+
//#region packages/appkit-mastra/src/pagination.ts
|
|
2922
|
+
/** Coerce / clamp a `perPage` value, falling back to `bounds.fallback`. */
|
|
2923
|
+
function clampPerPage(value, bounds) {
|
|
2924
|
+
if (value === void 0 || Number.isNaN(value)) return bounds.fallback;
|
|
2925
|
+
const n = Math.trunc(value);
|
|
2926
|
+
if (n <= 0) return bounds.fallback;
|
|
2927
|
+
return Math.min(n, bounds.max);
|
|
2928
|
+
}
|
|
2929
|
+
/**
|
|
2930
|
+
* Coerce a Hono query value into a non-negative integer. Returns
|
|
2931
|
+
* `undefined` for empty / non-numeric / negative inputs so the caller
|
|
2932
|
+
* can apply its built-in defaults.
|
|
2933
|
+
*/
|
|
2934
|
+
function parseIntParam(value) {
|
|
2935
|
+
if (!value) return void 0;
|
|
2936
|
+
const n = Number(value);
|
|
2937
|
+
if (!Number.isFinite(n) || n < 0) return void 0;
|
|
2938
|
+
return Math.trunc(n);
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2901
2941
|
//#endregion
|
|
2902
2942
|
//#region packages/appkit-mastra/src/history.ts
|
|
2903
2943
|
const log$4 = logUtils.logger("mastra/history");
|
|
@@ -2921,7 +2961,10 @@ const MAX_PER_PAGE$1 = 200;
|
|
|
2921
2961
|
* without sorting locally.
|
|
2922
2962
|
*/
|
|
2923
2963
|
async function loadHistory(opts) {
|
|
2924
|
-
const perPage = clampPerPage
|
|
2964
|
+
const perPage = clampPerPage(opts.perPage, {
|
|
2965
|
+
fallback: DEFAULT_PER_PAGE$1,
|
|
2966
|
+
max: MAX_PER_PAGE$1
|
|
2967
|
+
});
|
|
2925
2968
|
const page = Math.max(0, Math.trunc(opts.page ?? 0));
|
|
2926
2969
|
const memory = await opts.agent.getMemory();
|
|
2927
2970
|
if (!memory) {
|
|
@@ -3070,8 +3113,8 @@ function historyRoute(options) {
|
|
|
3070
3113
|
agent: ctx.agent,
|
|
3071
3114
|
threadId: ctx.threadId,
|
|
3072
3115
|
...ctx.resourceId ? { resourceId: ctx.resourceId } : {},
|
|
3073
|
-
page: parseIntParam
|
|
3074
|
-
perPage: parseIntParam
|
|
3116
|
+
page: parseIntParam(c.req.query("page")),
|
|
3117
|
+
perPage: parseIntParam(c.req.query("perPage"))
|
|
3075
3118
|
});
|
|
3076
3119
|
return c.json(payload);
|
|
3077
3120
|
}
|
|
@@ -3094,13 +3137,6 @@ function historyRoute(options) {
|
|
|
3094
3137
|
}
|
|
3095
3138
|
})];
|
|
3096
3139
|
}
|
|
3097
|
-
/** Coerce / clamp `perPage`; falls back to the page-size default. */
|
|
3098
|
-
function clampPerPage$1(value) {
|
|
3099
|
-
if (value === void 0 || Number.isNaN(value)) return DEFAULT_PER_PAGE$1;
|
|
3100
|
-
const n = Math.trunc(value);
|
|
3101
|
-
if (n <= 0) return DEFAULT_PER_PAGE$1;
|
|
3102
|
-
return Math.min(n, MAX_PER_PAGE$1);
|
|
3103
|
-
}
|
|
3104
3140
|
/**
|
|
3105
3141
|
* Sort messages oldest-first by `createdAt`, falling back to whatever
|
|
3106
3142
|
* order the storage returned them in. The native `recall` call honors
|
|
@@ -3120,155 +3156,6 @@ function toEpoch(value) {
|
|
|
3120
3156
|
}
|
|
3121
3157
|
return 0;
|
|
3122
3158
|
}
|
|
3123
|
-
/**
|
|
3124
|
-
* Coerce a Hono query value into a non-negative integer. Returns
|
|
3125
|
-
* `undefined` for empty / non-numeric / negative inputs so
|
|
3126
|
-
* {@link loadHistory} can apply its built-in defaults.
|
|
3127
|
-
*/
|
|
3128
|
-
function parseIntParam$1(value) {
|
|
3129
|
-
if (!value) return void 0;
|
|
3130
|
-
const n = Number(value);
|
|
3131
|
-
if (!Number.isFinite(n) || n < 0) return void 0;
|
|
3132
|
-
return Math.trunc(n);
|
|
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
3159
|
|
|
3273
3160
|
//#endregion
|
|
3274
3161
|
//#region packages/appkit-mastra/src/memory.ts
|
|
@@ -3300,7 +3187,7 @@ async function safeText(res) {
|
|
|
3300
3187
|
* (auto-defaulted to `true` in `plugin.ts` when the `lakebase` plugin
|
|
3301
3188
|
* is registered); per-agent settings cascade on top of that.
|
|
3302
3189
|
*/
|
|
3303
|
-
const log$
|
|
3190
|
+
const log$3 = logUtils.logger("mastra/memory");
|
|
3304
3191
|
/**
|
|
3305
3192
|
* Build a dedicated **service-principal** Lakebase pool for Mastra
|
|
3306
3193
|
* memory from the lakebase plugin's resolved SP pg config.
|
|
@@ -3392,10 +3279,10 @@ var MemoryBuilder = class {
|
|
|
3392
3279
|
const storage = this.buildStorage(agentId, storageSetting);
|
|
3393
3280
|
const vector = this.buildVector(memorySetting);
|
|
3394
3281
|
if (!storage && !vector) {
|
|
3395
|
-
log$
|
|
3282
|
+
log$3.debug("agent:stateless", { agentId });
|
|
3396
3283
|
return;
|
|
3397
3284
|
}
|
|
3398
|
-
log$
|
|
3285
|
+
log$3.debug("agent:configured", {
|
|
3399
3286
|
agentId,
|
|
3400
3287
|
storage: storage !== void 0,
|
|
3401
3288
|
vector: vector !== void 0,
|
|
@@ -3507,6 +3394,110 @@ function withId(value, fallback) {
|
|
|
3507
3394
|
};
|
|
3508
3395
|
}
|
|
3509
3396
|
|
|
3397
|
+
//#endregion
|
|
3398
|
+
//#region packages/appkit-mastra/src/mlflow.ts
|
|
3399
|
+
/**
|
|
3400
|
+
* MLflow user-feedback logging: detect whether MLflow tracing is wired
|
|
3401
|
+
* for this deployment, and log a thumbs / comment as a trace
|
|
3402
|
+
* *assessment* via the Databricks MLflow REST API.
|
|
3403
|
+
*
|
|
3404
|
+
* Feedback attaches to a trace, and the plugin's spans reach MLflow
|
|
3405
|
+
* through the same OTel pipeline as every other AppKit span (see
|
|
3406
|
+
* `observability.ts`). MLflow derives its trace id from the OpenTelemetry
|
|
3407
|
+
* trace id (`tr-<hex(otelTraceId)>`), so the server stamps the active
|
|
3408
|
+
* trace id on each turn's response and the client sends it back here.
|
|
3409
|
+
*
|
|
3410
|
+
* There is no MLflow JS SDK, so this posts to the assessments REST
|
|
3411
|
+
* endpoint directly using the OBO-scoped workspace client (the feedback
|
|
3412
|
+
* is thus attributed to the signed-in user). Trace export is
|
|
3413
|
+
* asynchronous, so the just-finished trace may not exist in MLflow yet
|
|
3414
|
+
* when the user reacts; the log call retries briefly on "not found"
|
|
3415
|
+
* before giving up softly.
|
|
3416
|
+
*/
|
|
3417
|
+
const log$2 = logUtils.logger("mastra/mlflow");
|
|
3418
|
+
/** Assessments REST path for a trace. `3.0` is the current MLflow API version. */
|
|
3419
|
+
const assessmentsPath = (traceId) => `/api/3.0/mlflow/traces/${encodeURIComponent(traceId)}/assessments`;
|
|
3420
|
+
/** Number of times to retry a "trace not found" response before giving up. */
|
|
3421
|
+
const NOT_FOUND_RETRIES = 3;
|
|
3422
|
+
/** Base backoff between "trace not found" retries, in ms (grows linearly). */
|
|
3423
|
+
const NOT_FOUND_BACKOFF_MS = 1200;
|
|
3424
|
+
/**
|
|
3425
|
+
* Whether MLflow feedback logging is available for this deployment.
|
|
3426
|
+
*
|
|
3427
|
+
* Enabled when an OTLP exporter endpoint is configured (traces are
|
|
3428
|
+
* actually shipped somewhere) AND an MLflow experiment is named - the
|
|
3429
|
+
* two signals that the OTLP backend is MLflow and traces will
|
|
3430
|
+
* materialize there. Both are standard env vars, so no plugin config is
|
|
3431
|
+
* required; a deployment opts in simply by wiring MLflow tracing.
|
|
3432
|
+
*/
|
|
3433
|
+
function mlflowEnabled() {
|
|
3434
|
+
const hasExporter = Boolean(process.env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim() || process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT?.trim());
|
|
3435
|
+
const hasExperiment = Boolean(process.env.MLFLOW_EXPERIMENT_ID?.trim() || process.env.MLFLOW_EXPERIMENT_NAME?.trim());
|
|
3436
|
+
return hasExporter && hasExperiment;
|
|
3437
|
+
}
|
|
3438
|
+
/**
|
|
3439
|
+
* Resolve whether user feedback is enabled from the plugin's optional
|
|
3440
|
+
* `config.feedback` override: the explicit boolean wins, otherwise fall
|
|
3441
|
+
* back to auto-detecting MLflow tracing ({@link mlflowEnabled}). Shared
|
|
3442
|
+
* by the plugin's client-config gate and the server's trace-id header so
|
|
3443
|
+
* the two never disagree.
|
|
3444
|
+
*/
|
|
3445
|
+
function resolveFeedbackEnabled(explicit) {
|
|
3446
|
+
return explicit ?? mlflowEnabled();
|
|
3447
|
+
}
|
|
3448
|
+
/**
|
|
3449
|
+
* Log a HUMAN feedback assessment to a trace. Returns the created
|
|
3450
|
+
* assessment id on success, or `undefined` when the trace can't be
|
|
3451
|
+
* found (even after retrying for export lag) or the request otherwise
|
|
3452
|
+
* fails - callers surface that as a soft "not recorded" rather than an
|
|
3453
|
+
* error, keeping the chat usable.
|
|
3454
|
+
*/
|
|
3455
|
+
async function logFeedback(client, params) {
|
|
3456
|
+
const hasValue = params.value !== void 0;
|
|
3457
|
+
const name = params.name?.trim() || (hasValue ? DEFAULT_FEEDBACK_NAME : DEFAULT_COMMENT_NAME);
|
|
3458
|
+
const value = hasValue ? params.value : params.comment;
|
|
3459
|
+
const body = { assessment: {
|
|
3460
|
+
trace_id: params.traceId,
|
|
3461
|
+
assessment_name: name,
|
|
3462
|
+
source: {
|
|
3463
|
+
source_type: "HUMAN",
|
|
3464
|
+
source_id: params.sourceId?.trim() || "user"
|
|
3465
|
+
},
|
|
3466
|
+
feedback: { value },
|
|
3467
|
+
...hasValue && params.comment?.trim() ? { rationale: params.comment } : {}
|
|
3468
|
+
} };
|
|
3469
|
+
for (let attempt = 0; attempt <= NOT_FOUND_RETRIES; attempt++) {
|
|
3470
|
+
let res;
|
|
3471
|
+
try {
|
|
3472
|
+
res = await databricksFetch(client, assessmentsPath(params.traceId), {
|
|
3473
|
+
method: "POST",
|
|
3474
|
+
body
|
|
3475
|
+
});
|
|
3476
|
+
} catch (err) {
|
|
3477
|
+
log$2.warn("feedback request failed", {
|
|
3478
|
+
traceId: params.traceId,
|
|
3479
|
+
error: commonUtils.errorMessage(err)
|
|
3480
|
+
});
|
|
3481
|
+
return;
|
|
3482
|
+
}
|
|
3483
|
+
if (res.ok) {
|
|
3484
|
+
const parsed = await readResponseJson(res);
|
|
3485
|
+
const assessmentId = parsed?.assessment?.assessment_id ?? parsed?.assessment_id;
|
|
3486
|
+
return typeof assessmentId === "string" ? assessmentId : "";
|
|
3487
|
+
}
|
|
3488
|
+
if (res.status === 404 && attempt < NOT_FOUND_RETRIES) {
|
|
3489
|
+
await commonUtils.sleep(NOT_FOUND_BACKOFF_MS * (attempt + 1));
|
|
3490
|
+
continue;
|
|
3491
|
+
}
|
|
3492
|
+
log$2.warn("feedback not recorded", {
|
|
3493
|
+
traceId: params.traceId,
|
|
3494
|
+
status: res.status,
|
|
3495
|
+
body: await readResponseText(res)
|
|
3496
|
+
});
|
|
3497
|
+
return;
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3510
3501
|
//#endregion
|
|
3511
3502
|
//#region packages/appkit-mastra/src/observability.ts
|
|
3512
3503
|
/**
|
|
@@ -3581,30 +3572,29 @@ async function buildObservability(options) {
|
|
|
3581
3572
|
* behind an Express mount point.
|
|
3582
3573
|
*/
|
|
3583
3574
|
/**
|
|
3584
|
-
* `@mastra/express` subclass that stamps `RequestContext` with the
|
|
3585
|
-
* AppKit user, resource id, and a thread id backed by an HTTP-only
|
|
3586
|
-
* session cookie (`appkit_<plugin-name>_session_id`).
|
|
3587
|
-
*/
|
|
3588
|
-
/**
|
|
3589
3575
|
* OpenTelemetry's sentinel for "no valid trace" - 32 zero hex chars.
|
|
3590
3576
|
* `trace.getActiveSpan()` returns a non-recording span with this id
|
|
3591
3577
|
* when no SDK is registered, which must never be surfaced as a trace to
|
|
3592
3578
|
* attach feedback to.
|
|
3593
3579
|
*/
|
|
3594
3580
|
const INVALID_TRACE_ID = "0".repeat(32);
|
|
3581
|
+
/**
|
|
3582
|
+
* `@mastra/express` subclass that stamps `RequestContext` with the
|
|
3583
|
+
* AppKit user, resource id, and a thread id backed by an HTTP-only
|
|
3584
|
+
* session cookie (`appkit_<plugin-name>_session_id`).
|
|
3585
|
+
*/
|
|
3595
3586
|
var MastraServer$1 = class extends MastraServer {
|
|
3596
3587
|
log;
|
|
3597
3588
|
/**
|
|
3598
|
-
* Whether to stamp the MLflow trace-id header on responses.
|
|
3599
|
-
*
|
|
3600
|
-
* wins, else auto-detect from the MLflow tracing env.
|
|
3589
|
+
* Whether to stamp the MLflow trace-id header on responses. Shares the
|
|
3590
|
+
* plugin's feedback gate via {@link resolveFeedbackEnabled}.
|
|
3601
3591
|
*/
|
|
3602
3592
|
feedbackEnabled;
|
|
3603
3593
|
constructor(config, ...args) {
|
|
3604
3594
|
super(...args);
|
|
3605
3595
|
this.config = config;
|
|
3606
3596
|
this.log = logUtils.logger(config);
|
|
3607
|
-
this.feedbackEnabled = config.feedback
|
|
3597
|
+
this.feedbackEnabled = resolveFeedbackEnabled(config.feedback);
|
|
3608
3598
|
}
|
|
3609
3599
|
registerAuthMiddleware() {
|
|
3610
3600
|
super.registerAuthMiddleware();
|
|
@@ -3744,6 +3734,37 @@ var MastraServer$1 = class extends MastraServer {
|
|
|
3744
3734
|
}
|
|
3745
3735
|
};
|
|
3746
3736
|
/**
|
|
3737
|
+
* Mount-relative agent inference paths (`/agents/:id/<verb>...`). Matched
|
|
3738
|
+
* by prefix on the verb so future variants (`streamVNext`, `stream/vnext`,
|
|
3739
|
+
* `generateVNext`) stay covered without a code change. These are the only
|
|
3740
|
+
* *writes* the browser client is allowed to make against stock Mastra.
|
|
3741
|
+
*/
|
|
3742
|
+
const AGENT_INFERENCE = /^\/agents\/[^/]+\/(stream|generate|network)/i;
|
|
3743
|
+
/** Mount-relative read-only agent metadata (`/agents`, `/agents/:id`). */
|
|
3744
|
+
const AGENT_METADATA = /^\/agents(\/[^/]+)?$/;
|
|
3745
|
+
/**
|
|
3746
|
+
* Whether a request to the stock `@mastra/express` sub-app should be
|
|
3747
|
+
* dispatched, given the configured {@link MastraApiGateOptions.access}.
|
|
3748
|
+
*
|
|
3749
|
+
* `path` is mount-relative (what the plugin's catch-all sees, e.g.
|
|
3750
|
+
* `/agents/x/stream`, `/route/history/x`, `/mcp/...`). In `"scoped"`
|
|
3751
|
+
* mode the allowlist is deliberately tight - the chat client only ever
|
|
3752
|
+
* needs agent inference, read-only agent metadata, this plugin's own
|
|
3753
|
+
* OBO/resource-scoped `/route/*` routes, and (when enabled) MCP - so the
|
|
3754
|
+
* whole admin / mutating / bulk-export surface Mastra also exposes is
|
|
3755
|
+
* denied by default rather than enumerated.
|
|
3756
|
+
*/
|
|
3757
|
+
function isMastraRequestAllowed(method, path, opts) {
|
|
3758
|
+
if (opts.access === "full") return true;
|
|
3759
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
3760
|
+
if (p === "/route" || p.startsWith("/route/")) return true;
|
|
3761
|
+
if (opts.mcpEnabled && (p === "/mcp" || p.startsWith("/mcp/"))) return true;
|
|
3762
|
+
const m = method.toUpperCase();
|
|
3763
|
+
if (m === "POST" && AGENT_INFERENCE.test(p)) return true;
|
|
3764
|
+
if (m === "GET" && AGENT_METADATA.test(p)) return true;
|
|
3765
|
+
return false;
|
|
3766
|
+
}
|
|
3767
|
+
/**
|
|
3747
3768
|
* Patches around `@mastra/express`'s custom-route dispatcher so the
|
|
3748
3769
|
* plugin's custom API routes (e.g. `historyRoute`) work when
|
|
3749
3770
|
* `MastraServer` is hosted on an Express subapp mounted under a parent
|
|
@@ -3782,7 +3803,10 @@ const MAX_PER_PAGE = 200;
|
|
|
3782
3803
|
* empty page so callers don't have to special-case stateless agents.
|
|
3783
3804
|
*/
|
|
3784
3805
|
async function listThreads(opts) {
|
|
3785
|
-
const perPage = clampPerPage(opts.perPage
|
|
3806
|
+
const perPage = clampPerPage(opts.perPage, {
|
|
3807
|
+
fallback: DEFAULT_PER_PAGE,
|
|
3808
|
+
max: MAX_PER_PAGE
|
|
3809
|
+
});
|
|
3786
3810
|
const page = Math.max(0, Math.trunc(opts.page ?? 0));
|
|
3787
3811
|
const memory = await opts.agent.getMemory();
|
|
3788
3812
|
if (!memory) {
|
|
@@ -3946,24 +3970,6 @@ function toIso(value) {
|
|
|
3946
3970
|
const parsed = new Date(value);
|
|
3947
3971
|
return Number.isNaN(parsed.getTime()) ? (/* @__PURE__ */ new Date(0)).toISOString() : parsed.toISOString();
|
|
3948
3972
|
}
|
|
3949
|
-
/** Coerce / clamp `perPage`; falls back to the page-size default. */
|
|
3950
|
-
function clampPerPage(value) {
|
|
3951
|
-
if (value === void 0 || Number.isNaN(value)) return DEFAULT_PER_PAGE;
|
|
3952
|
-
const n = Math.trunc(value);
|
|
3953
|
-
if (n <= 0) return DEFAULT_PER_PAGE;
|
|
3954
|
-
return Math.min(n, MAX_PER_PAGE);
|
|
3955
|
-
}
|
|
3956
|
-
/**
|
|
3957
|
-
* Coerce a Hono query value into a non-negative integer. Returns
|
|
3958
|
-
* `undefined` for empty / non-numeric / negative inputs so
|
|
3959
|
-
* {@link listThreads} can apply its built-in defaults.
|
|
3960
|
-
*/
|
|
3961
|
-
function parseIntParam(value) {
|
|
3962
|
-
if (!value) return void 0;
|
|
3963
|
-
const n = Number(value);
|
|
3964
|
-
if (!Number.isFinite(n) || n < 0) return void 0;
|
|
3965
|
-
return Math.trunc(n);
|
|
3966
|
-
}
|
|
3967
3973
|
|
|
3968
3974
|
//#endregion
|
|
3969
3975
|
//#region packages/appkit-mastra/src/plugin.ts
|
|
@@ -3994,8 +4000,13 @@ function parseIntParam(value) {
|
|
|
3994
4000
|
* Mastra agent routes, the plugin registers `/route/history`
|
|
3995
4001
|
* (load + clear a thread's messages), `/route/threads` (list the
|
|
3996
4002
|
* caller's conversations + delete one), `/models`, `/suggestions`,
|
|
3997
|
-
*
|
|
3998
|
-
*
|
|
4003
|
+
* `/route/feedback` (log a thumbs / comment to MLflow when feedback
|
|
4004
|
+
* is enabled), and the generic `/embed/:type/:id` resolver for inline
|
|
4005
|
+
* chart / data markers. The stock `@mastra/express` surface is gated
|
|
4006
|
+
* by `config.apiAccess` (default `"scoped"`): only agent inference,
|
|
4007
|
+
* read-only agent metadata, the `/route/*` routes, and (when enabled)
|
|
4008
|
+
* MCP are dispatched to Mastra; admin / mutating / bulk-export routes
|
|
4009
|
+
* are refused with `403`. See {@link isMastraRequestAllowed}.
|
|
3999
4010
|
* - MCP: opt in with `config.mcp` to expose the agents (and optionally
|
|
4000
4011
|
* tools) as a Mastra `MCPServer`. It is registered on the `Mastra`
|
|
4001
4012
|
* instance via `mcpServers`, so `@mastra/express` serves the stock
|
|
@@ -4116,13 +4127,12 @@ var MastraPlugin = class MastraPlugin extends Plugin {
|
|
|
4116
4127
|
};
|
|
4117
4128
|
}
|
|
4118
4129
|
/**
|
|
4119
|
-
* Whether user feedback can be logged to MLflow.
|
|
4120
|
-
*
|
|
4121
|
-
*
|
|
4122
|
-
* config flag and the feedback route so the two never disagree.
|
|
4130
|
+
* Whether user feedback can be logged to MLflow. Delegates to
|
|
4131
|
+
* {@link resolveFeedbackEnabled} so the client-config flag and the
|
|
4132
|
+
* feedback route share the same gate as the server's trace-id header.
|
|
4123
4133
|
*/
|
|
4124
4134
|
feedbackEnabled() {
|
|
4125
|
-
return this.config.feedback
|
|
4135
|
+
return resolveFeedbackEnabled(this.config.feedback);
|
|
4126
4136
|
}
|
|
4127
4137
|
injectRoutes(router) {
|
|
4128
4138
|
router.get(MASTRA_ROUTES.models, (req, res, next) => {
|
|
@@ -4196,6 +4206,13 @@ var MastraPlugin = class MastraPlugin extends Plugin {
|
|
|
4196
4206
|
});
|
|
4197
4207
|
router.use((req, res, next) => {
|
|
4198
4208
|
if (!this.mastraApp) return res.status(503).end();
|
|
4209
|
+
if (!isMastraRequestAllowed(req.method, req.path, {
|
|
4210
|
+
access: this.config.apiAccess ?? "scoped",
|
|
4211
|
+
mcpEnabled: Boolean(this.config.mcp)
|
|
4212
|
+
})) {
|
|
4213
|
+
res.status(403).json({ error: "Endpoint not exposed to the client (apiAccess=scoped)" });
|
|
4214
|
+
return;
|
|
4215
|
+
}
|
|
4199
4216
|
return this.userScopedSelf(req).dispatchMastra(req, res, next);
|
|
4200
4217
|
});
|
|
4201
4218
|
}
|
|
@@ -4402,6 +4419,7 @@ var MastraPlugin = class MastraPlugin extends Plugin {
|
|
|
4402
4419
|
"/route/threads",
|
|
4403
4420
|
"/models",
|
|
4404
4421
|
"/suggestions",
|
|
4422
|
+
"/route/feedback",
|
|
4405
4423
|
"/embed/:type/:id"
|
|
4406
4424
|
],
|
|
4407
4425
|
instanceStorage: instanceStorage !== void 0,
|
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.75",
|
|
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.75",
|
|
7
|
+
"@dbx-tools/genie": "0.1.75",
|
|
8
|
+
"@dbx-tools/genie-shared": "0.1.75",
|
|
9
|
+
"@dbx-tools/model": "0.1.75",
|
|
10
|
+
"@dbx-tools/shared": "0.1.75",
|
|
11
11
|
"@mastra/ai-sdk": "^1",
|
|
12
12
|
"@mastra/core": "^1",
|
|
13
13
|
"@mastra/express": "^1",
|