@dbx-tools/appkit-mastra 0.6.218 → 0.6.220
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 +72 -25
- package/index.ts +4 -7
- package/lib/index.d.ts +4 -7
- package/lib/index.js +4 -5
- package/lib/src/agents.d.ts +15 -6
- package/lib/src/agents.js +30 -12
- package/lib/src/chat.d.ts +7 -0
- package/lib/src/chat.js +19 -0
- package/lib/src/config.d.ts +2 -2
- package/lib/src/config.js +1 -1
- package/lib/src/genie.js +5 -4
- package/lib/src/memory.d.ts +2 -2
- package/lib/src/memory.js +2 -2
- package/lib/src/model.d.ts +2 -0
- package/lib/src/model.js +7 -1
- package/lib/src/plugin.d.ts +1 -1
- package/lib/src/plugin.js +3 -31
- package/lib/src/processors.d.ts +4 -28
- package/lib/src/processors.js +4 -61
- package/lib/src/server.d.ts +10 -21
- package/lib/src/server.js +52 -28
- package/lib/src/serving-sanitize.d.ts +8 -5
- package/lib/src/serving-sanitize.js +16 -11
- package/lib/src/trace-io.js +7 -3
- package/lib/src/writer.d.ts +7 -7
- package/lib/src/writer.js +12 -9
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +23 -22
- package/src/agents.ts +92 -27
- package/src/chat.ts +20 -0
- package/src/config.ts +5 -2
- package/src/genie.ts +7 -11
- package/src/memory.ts +8 -8
- package/src/model.ts +7 -0
- package/src/plugin.ts +2 -30
- package/src/processors.ts +4 -66
- package/src/server.ts +49 -27
- package/src/serving-sanitize.ts +14 -10
- package/src/trace-io.ts +7 -2
- package/src/writer.ts +15 -11
- package/lib/src/history.d.ts +0 -93
- package/lib/src/history.js +0 -231
- package/lib/src/threads.d.ts +0 -104
- package/lib/src/threads.js +0 -264
- package/src/history.ts +0 -270
- package/src/threads.ts +0 -309
package/src/server.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Express-layer plumbing for the Mastra plugin: a `MastraServer` that
|
|
3
|
-
* stamps the per-request `RequestContext
|
|
4
|
-
* that lets the plugin's custom API routes (e.g. `historyRoute`) work
|
|
5
|
-
* behind an Express mount point.
|
|
3
|
+
* stamps the per-request `RequestContext` and a scoped native API gate.
|
|
6
4
|
*
|
|
7
5
|
* @module
|
|
8
6
|
*/
|
|
@@ -44,6 +42,23 @@ import {
|
|
|
44
42
|
* attach feedback to.
|
|
45
43
|
*/
|
|
46
44
|
const INVALID_TRACE_ID = "0".repeat(32);
|
|
45
|
+
const TRUSTED_REQUEST_CONTEXT_KEYS = [
|
|
46
|
+
MASTRA_RESOURCE_ID_KEY,
|
|
47
|
+
MASTRA_THREAD_ID_KEY,
|
|
48
|
+
MASTRA_USER_KEY,
|
|
49
|
+
MASTRA_USER_NAME_KEY,
|
|
50
|
+
MASTRA_USER_EMAIL_KEY,
|
|
51
|
+
MASTRA_REQUEST_ID_KEY,
|
|
52
|
+
MASTRA_SCOPES_KEY,
|
|
53
|
+
MASTRA_MODEL_OVERRIDE_KEY,
|
|
54
|
+
] as const;
|
|
55
|
+
|
|
56
|
+
/** Remove browser-supplied values for fields owned by trusted server middleware. */
|
|
57
|
+
export function clearTrustedRequestContext(requestContext: RequestContext): void {
|
|
58
|
+
for (const key of TRUSTED_REQUEST_CONTEXT_KEYS) {
|
|
59
|
+
requestContext.deleteRaw(key);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
47
62
|
|
|
48
63
|
/**
|
|
49
64
|
* Who a turn is ATTRIBUTED to, independent of which Databricks credential runs
|
|
@@ -173,6 +188,7 @@ export class MastraServer extends ExpressMastraServer {
|
|
|
173
188
|
super.registerAuthMiddleware();
|
|
174
189
|
this.app.use(async (req, res, next) => {
|
|
175
190
|
const requestContext = res.locals.requestContext! as RequestContext;
|
|
191
|
+
clearTrustedRequestContext(requestContext);
|
|
176
192
|
await this.configureRequestContextUser(req, requestContext);
|
|
177
193
|
this.configureRequestContextThreadId(req, res, requestContext);
|
|
178
194
|
this.configureRequestContextModelOverride(req, requestContext);
|
|
@@ -268,7 +284,7 @@ export class MastraServer extends ExpressMastraServer {
|
|
|
268
284
|
/**
|
|
269
285
|
* Resolve the thread id this request targets and pin it on
|
|
270
286
|
* `RequestContext` (consumed by the agent stream for persistence and
|
|
271
|
-
* by
|
|
287
|
+
* by native memory routes). Resolution order:
|
|
272
288
|
*
|
|
273
289
|
* 1. A client-supplied thread id (the thread-selection header /
|
|
274
290
|
* `?threadId=` query). This is how the chat UI references a
|
|
@@ -365,26 +381,34 @@ export interface MastraApiGateOptions {
|
|
|
365
381
|
* prefix their `-generate` non-streaming variants), the `-network-` variants,
|
|
366
382
|
* and `resume-stream` (covers `resume-stream-until-idle`). Without these an
|
|
367
383
|
* approval-gated tool (e.g. `send_email`) can be requested but never approved
|
|
368
|
-
* from the browser - the resume `POST` 403s under scoped mode.
|
|
369
|
-
*
|
|
384
|
+
* from the browser - the resume `POST` 403s under scoped mode. Together with
|
|
385
|
+
* the resource-scoped memory mutations below, these are the only stock Mastra
|
|
386
|
+
* writes the browser client can make.
|
|
370
387
|
*/
|
|
371
388
|
const AGENT_INFERENCE =
|
|
372
389
|
/^\/agents\/[^/]+\/(stream|generate|network|resume-stream|approve-tool-call|decline-tool-call|approve-network-tool-call|decline-network-tool-call)/i;
|
|
373
390
|
|
|
374
391
|
/** Mount-relative read-only agent metadata (`/agents`, `/agents/:id`). */
|
|
375
392
|
const AGENT_METADATA = /^\/agents(\/[^/]+)?$/;
|
|
393
|
+
/** Persisted approval discovery for one agent. */
|
|
394
|
+
const AGENT_SUSPENDED_RUNS = /^\/agents\/[^/]+\/suspended-runs$/i;
|
|
395
|
+
/** Official AI SDK UI stream route, including `resumeData` continuations. */
|
|
396
|
+
const AGENT_CHAT = /^\/chat\/[^/]+$/i;
|
|
397
|
+
/** Native resource-scoped memory reads used by history and the thread list. */
|
|
398
|
+
const MEMORY_READ = /^\/memory\/threads(?:\/[^/]+(?:\/messages)?)?$/i;
|
|
399
|
+
/** Native thread rename/delete routes. */
|
|
400
|
+
const MEMORY_THREAD_MUTATION = /^\/memory\/threads\/[^/]+$/i;
|
|
376
401
|
|
|
377
402
|
/**
|
|
378
403
|
* Whether a request to the stock `@mastra/express` sub-app should be
|
|
379
404
|
* dispatched, given the configured {@link MastraApiGateOptions.access}.
|
|
380
405
|
*
|
|
381
406
|
* `path` is mount-relative (what the plugin's catch-all sees, e.g.
|
|
382
|
-
* `/
|
|
407
|
+
* `/chat/x`, `/memory/threads`, `/mcp/...`). In `"scoped"`
|
|
383
408
|
* mode the allowlist is deliberately tight - the chat client only ever
|
|
384
|
-
* needs agent inference, read-only agent metadata,
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
* denied by default rather than enumerated.
|
|
409
|
+
* needs agent inference, read-only agent metadata, resource-scoped memory,
|
|
410
|
+
* this plugin's own `/route/*` routes, and (when enabled) MCP. The remaining
|
|
411
|
+
* admin, mutating, and bulk-export surface stays denied by default.
|
|
388
412
|
*/
|
|
389
413
|
export function isMastraRequestAllowed(
|
|
390
414
|
method: string,
|
|
@@ -393,33 +417,31 @@ export function isMastraRequestAllowed(
|
|
|
393
417
|
): boolean {
|
|
394
418
|
if (opts.access === "full") return true;
|
|
395
419
|
const p = path.startsWith("/") ? path : `/${path}`;
|
|
396
|
-
//
|
|
397
|
-
//
|
|
420
|
+
// The plugin's `/route/*` additions validate their own inputs and trusted
|
|
421
|
+
// AppKit request context before reaching product behavior.
|
|
398
422
|
if (p === "/route" || p.startsWith("/route/")) return true;
|
|
399
423
|
if (opts.mcpEnabled && (p === "/mcp" || p.startsWith("/mcp/"))) return true;
|
|
400
424
|
const m = method.toUpperCase();
|
|
425
|
+
if (m === "POST" && AGENT_CHAT.test(p)) return true;
|
|
401
426
|
if (m === "POST" && AGENT_INFERENCE.test(p)) return true;
|
|
402
|
-
if (m === "GET" && AGENT_METADATA.test(p)) return true;
|
|
427
|
+
if (m === "GET" && (AGENT_METADATA.test(p) || AGENT_SUSPENDED_RUNS.test(p))) return true;
|
|
428
|
+
if (m === "GET" && MEMORY_READ.test(p)) return true;
|
|
429
|
+
if ((m === "PATCH" || m === "DELETE") && MEMORY_THREAD_MUTATION.test(p)) return true;
|
|
430
|
+
if (m === "POST" && p === "/memory/messages/delete") return true;
|
|
403
431
|
return false;
|
|
404
432
|
}
|
|
405
433
|
|
|
406
434
|
/**
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
* `MastraServer` is hosted on an Express subapp mounted under a parent
|
|
410
|
-
* path (e.g. `/api/mastra`).
|
|
411
|
-
*
|
|
412
|
-
* The adapter's `registerCustomApiRoutes` matches against `req.path`
|
|
413
|
-
* (mount-relative, correct) but dispatches to its internal Hono
|
|
414
|
-
* mini-app using `req.originalUrl`, which still contains the parent
|
|
415
|
-
* mount prefix. The Hono app registers the literal route paths
|
|
416
|
-
* (for example `/route/history`), so the absolute URL never matches
|
|
417
|
-
* until we overwrite `originalUrl` for `/route` and `/route/*` to the
|
|
418
|
-
* mount-relative path.
|
|
435
|
+
* Keep Mastra custom API routes mount-relative when the Express subapp is
|
|
436
|
+
* hosted under AppKit's `/api/<plugin>` path.
|
|
419
437
|
*/
|
|
420
438
|
export function attachRoutePatchMiddleware(app: express.Express): void {
|
|
421
439
|
app.use((req, _res, next) => {
|
|
422
|
-
const isCustomRoute =
|
|
440
|
+
const isCustomRoute =
|
|
441
|
+
req.path === "/chat" ||
|
|
442
|
+
req.path.startsWith("/chat/") ||
|
|
443
|
+
req.path === "/route" ||
|
|
444
|
+
req.path.startsWith("/route/");
|
|
423
445
|
if (!isCustomRoute) return next();
|
|
424
446
|
req.originalUrl = req.path;
|
|
425
447
|
next();
|
package/src/serving-sanitize.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Outbound ({@link rewriteServingBody}), because the transcript Mastra
|
|
6
6
|
* persists is not always a transcript the provider will accept back:
|
|
7
7
|
* Databricks-hosted Claude rejects replayed extended-thinking blocks and reads
|
|
8
|
-
* a trailing assistant message as a prefill request, while GPT
|
|
9
|
-
*
|
|
8
|
+
* a trailing assistant message as a prefill request, while GPT tool calls need
|
|
9
|
+
* model-specific reasoning-effort normalization on Chat Completions.
|
|
10
10
|
*
|
|
11
11
|
* Inbound ({@link rewriteServingResponseBody} and
|
|
12
12
|
* {@link rewriteServingResponseStream}), because Databricks-hosted Gemini and
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { json, string } from "@dbx-tools/shared-core";
|
|
25
|
+
import { chatToolReasoningEffort, ReasoningEffort } from "@dbx-tools/model-rs";
|
|
25
26
|
import {
|
|
26
27
|
type ChatMessage,
|
|
27
28
|
type ChatRole,
|
|
@@ -57,8 +58,8 @@ export function rewriteServingBody(body: string): string {
|
|
|
57
58
|
|
|
58
59
|
// Runs regardless of `messages`: Databricks refuses to parse a body carrying
|
|
59
60
|
// an unknown top-level field, so this failure is not specific to a transcript.
|
|
60
|
-
const
|
|
61
|
-
let changed = openaiChat.stripUnsupportedChatFields(parsed).length > 0 ||
|
|
61
|
+
const toolReasoning = applyToolReasoningCompatibility(parsed);
|
|
62
|
+
let changed = openaiChat.stripUnsupportedChatFields(parsed).length > 0 || toolReasoning;
|
|
62
63
|
|
|
63
64
|
if (Array.isArray(parsed.messages)) {
|
|
64
65
|
const messages = parsed.messages as ServingChatMessage[];
|
|
@@ -103,17 +104,20 @@ export async function rewriteServingRequest(
|
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
107
|
+
* Normalize Chat Completions reasoning for GPT models carrying function tools.
|
|
108
|
+
*
|
|
109
|
+
* GPT 5.6 requires reasoning disabled for tool calls on this route, so `none`
|
|
110
|
+
* wins over a caller/default effort. Responses-only models are routed away
|
|
111
|
+
* from Chat Completions before this sanitizer runs.
|
|
108
112
|
*/
|
|
109
|
-
export function
|
|
110
|
-
if (
|
|
113
|
+
export function applyToolReasoningCompatibility(body: Record<string, unknown>): boolean {
|
|
114
|
+
if (!Array.isArray(body.tools) || body.tools.length === 0) {
|
|
111
115
|
return false;
|
|
112
116
|
}
|
|
113
117
|
const model = string.trimToNull(body.model);
|
|
114
118
|
if (!model) return false;
|
|
115
|
-
|
|
116
|
-
if (
|
|
119
|
+
if (chatToolReasoningEffort(model) !== ReasoningEffort.None) return false;
|
|
120
|
+
if (body.reasoning_effort === "none") return false;
|
|
117
121
|
body.reasoning_effort = "none";
|
|
118
122
|
return true;
|
|
119
123
|
}
|
package/src/trace-io.ts
CHANGED
|
@@ -33,7 +33,7 @@ export const TRACE_IO_LIMIT = 8_000;
|
|
|
33
33
|
* are intentionally excluded: their bodies are tool decisions, not the
|
|
34
34
|
* user prompt, and they rarely produce a fresh answer worth surfacing.
|
|
35
35
|
*/
|
|
36
|
-
const AGENT_TURN_ROUTE =
|
|
36
|
+
const AGENT_TURN_ROUTE = /^(?:\/agents\/[^/]+\/(?:stream|generate)(?:\/|$)|\/chat\/[^/]+$)/i;
|
|
37
37
|
|
|
38
38
|
/** Attribute keys the MLflow UC `*_trace_unified` view reads from the root span. */
|
|
39
39
|
export const MLFLOW_SPAN_INPUTS_ATTR = "mlflow.spanInputs";
|
|
@@ -55,7 +55,12 @@ export function assistantTextFromSse(body: string): string {
|
|
|
55
55
|
// assistant text, so skipping them is the intended path rather than an error.
|
|
56
56
|
const frame = json.parse(line.slice(5));
|
|
57
57
|
if (!object.isRecord(frame) || frame.type !== "text-delta") continue;
|
|
58
|
-
const text =
|
|
58
|
+
const text =
|
|
59
|
+
typeof frame.delta === "string"
|
|
60
|
+
? frame.delta
|
|
61
|
+
: object.isRecord(frame.payload)
|
|
62
|
+
? frame.payload.text
|
|
63
|
+
: undefined;
|
|
59
64
|
if (typeof text === "string") parts.push(text);
|
|
60
65
|
}
|
|
61
66
|
return parts.join("");
|
package/src/writer.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared helper for publishing
|
|
3
|
-
* `ctx.writer
|
|
4
|
-
* be closed, don't take the whole tool down" pattern that the
|
|
5
|
-
* Genie agent and chart tool both need.
|
|
2
|
+
* Shared helper for publishing Genie progress through Mastra's native
|
|
3
|
+
* `ctx.writer.custom()` data-part surface.
|
|
6
4
|
*
|
|
7
5
|
* Failures are logged at `warn` (a persistently-closed writer is
|
|
8
6
|
* the most likely culprit when events go missing client-side) but
|
|
@@ -13,10 +11,12 @@
|
|
|
13
11
|
*/
|
|
14
12
|
|
|
15
13
|
import { error, log } from "@dbx-tools/shared-core";
|
|
16
|
-
import type
|
|
14
|
+
import { GENIE_PROGRESS_PART_TYPE, type GenieWriterEvent } from "@dbx-tools/shared-mastra";
|
|
15
|
+
import type { ToolExecutionContext } from "@mastra/core/tools";
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
|
-
* Best-effort
|
|
18
|
+
* Best-effort native custom progress write. No-op when the writer or owning
|
|
19
|
+
* tool call id is unavailable;
|
|
20
20
|
* caught errors are logged via `log.warn("writer:error", ...)`
|
|
21
21
|
* along with any caller-supplied `context` fields (e.g. a
|
|
22
22
|
* `chartId` or `messageId`) so the warning is greppable per
|
|
@@ -24,18 +24,22 @@ import type { MastraWriter } from "@dbx-tools/shared-mastra";
|
|
|
24
24
|
*
|
|
25
25
|
* Returns when the write resolves or rejects; never throws.
|
|
26
26
|
*/
|
|
27
|
-
export async function
|
|
27
|
+
export async function safeWriteProgress(
|
|
28
28
|
log: log.Logger,
|
|
29
|
-
writer:
|
|
30
|
-
|
|
29
|
+
writer: ToolExecutionContext["writer"],
|
|
30
|
+
toolCallId: string | undefined,
|
|
31
|
+
event: GenieWriterEvent,
|
|
31
32
|
context: Record<string, unknown> = {},
|
|
32
33
|
): Promise<void> {
|
|
33
|
-
if (!writer) {
|
|
34
|
+
if (!writer || !toolCallId) {
|
|
34
35
|
log.debug("writer:no-writer", context);
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
37
38
|
try {
|
|
38
|
-
await writer.
|
|
39
|
+
await writer.custom({
|
|
40
|
+
type: GENIE_PROGRESS_PART_TYPE,
|
|
41
|
+
data: { toolCallId, event },
|
|
42
|
+
});
|
|
39
43
|
log.debug("writer:ok", context);
|
|
40
44
|
} catch (err) {
|
|
41
45
|
log.warn("writer:error", {
|
package/lib/src/history.d.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Thread history loader exposed as a Mastra custom API route.
|
|
3
|
-
*
|
|
4
|
-
* Backed entirely by native Mastra: looks up the active agent by id,
|
|
5
|
-
* asks its `Memory` instance to `recall` a page of `MastraDBMessage`s,
|
|
6
|
-
* and converts the result to AI SDK V5 `UIMessage`s with the official
|
|
7
|
-
* {@link toAISdkV5Messages} helper from `@mastra/ai-sdk/ui`. No direct
|
|
8
|
-
* database reads.
|
|
9
|
-
*
|
|
10
|
-
* The route is registered through {@link historyRoute} as a Mastra
|
|
11
|
-
* `registerApiRoute` so it sits in the same dispatcher pipeline as
|
|
12
|
-
* the standard agent routes. That means the `MastraServer` auth
|
|
13
|
-
* middleware (in `./server.ts`) has already populated `RequestContext` with
|
|
14
|
-
* `MASTRA_THREAD_ID_KEY` and `MASTRA_RESOURCE_ID_KEY` by the time
|
|
15
|
-
* the handler runs - no cookie or user lookups happen here, and the
|
|
16
|
-
* session-cookie logic stays the single source of truth in `server.ts`.
|
|
17
|
-
*
|
|
18
|
-
* @module
|
|
19
|
-
*/
|
|
20
|
-
import type { MastraHistoryResponse } from "@dbx-tools/shared-mastra";
|
|
21
|
-
import type { Agent } from "@mastra/core/agent";
|
|
22
|
-
import { type AgentRouteOptions } from "./_agent-route-context.ts";
|
|
23
|
-
/** Inputs accepted by {@link loadHistory}. */
|
|
24
|
-
export interface LoadHistoryOptions {
|
|
25
|
-
agent: Agent;
|
|
26
|
-
threadId: string;
|
|
27
|
-
resourceId: string;
|
|
28
|
-
page?: number;
|
|
29
|
-
perPage?: number;
|
|
30
|
-
/** When true, returns the *oldest* page first (chronological). */
|
|
31
|
-
ascending?: boolean;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Fetch a page of UI-formatted messages for a thread.
|
|
35
|
-
*
|
|
36
|
-
* Uses the agent's resolved `Memory` (`getMemory()`) so per-agent
|
|
37
|
-
* storage namespaces (`mastra_<agentId>` schemas) and any future
|
|
38
|
-
* memory-side filters apply automatically. When the agent has no
|
|
39
|
-
* memory configured the response is a successful empty page so
|
|
40
|
-
* callers don't have to special-case stateless agents.
|
|
41
|
-
*
|
|
42
|
-
* Pagination is descending-by-default: page 0 is the most recent
|
|
43
|
-
* page, page 1 the page before that, etc. The returned `uiMessages`
|
|
44
|
-
* are always re-sorted into chronological order (oldest -> newest)
|
|
45
|
-
* so the client can prepend them above the existing transcript
|
|
46
|
-
* without sorting locally.
|
|
47
|
-
*/
|
|
48
|
-
export declare function loadHistory(opts: LoadHistoryOptions): Promise<MastraHistoryResponse>;
|
|
49
|
-
/** Inputs accepted by {@link clearHistory}. */
|
|
50
|
-
export interface ClearHistoryOptions {
|
|
51
|
-
agent: Agent;
|
|
52
|
-
threadId: string;
|
|
53
|
-
resourceId: string;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Wipe every persisted message tied to a thread. Returns the count
|
|
57
|
-
* of messages that were on the thread at delete time so the caller
|
|
58
|
-
* can render a "cleared N messages" affordance without an
|
|
59
|
-
* additional round-trip.
|
|
60
|
-
*
|
|
61
|
-
* Agents without a configured `Memory` resolve to a no-op (count
|
|
62
|
-
* 0), matching {@link loadHistory}'s "stateless agents return an
|
|
63
|
-
* empty page" stance so callers don't have to special-case them.
|
|
64
|
-
* Threads that don't exist yet are also a successful no-op - the
|
|
65
|
-
* operation is idempotent so the UI can fire-and-forget without
|
|
66
|
-
* tracking thread existence.
|
|
67
|
-
*/
|
|
68
|
-
export declare function clearHistory(opts: ClearHistoryOptions): Promise<{
|
|
69
|
-
cleared: number;
|
|
70
|
-
}>;
|
|
71
|
-
/** Options accepted by {@link historyRoute}. */
|
|
72
|
-
export type HistoryRouteOptions = AgentRouteOptions;
|
|
73
|
-
/**
|
|
74
|
-
* Register the `<path>` Mastra custom API route. Handles two
|
|
75
|
-
* methods on the same mount:
|
|
76
|
-
*
|
|
77
|
-
* - `GET`: return a page of AI SDK V5 `UIMessage`s for the
|
|
78
|
-
* caller's current thread ({@link loadHistory}).
|
|
79
|
-
* - `DELETE`: wipe every persisted message on the caller's
|
|
80
|
-
* thread ({@link clearHistory}). The session cookie that
|
|
81
|
-
* anchors the thread id is left alone so the user keeps the
|
|
82
|
-
* same thread - only the contents go away.
|
|
83
|
-
*
|
|
84
|
-
* Follows the `@mastra/ai-sdk` `chatRoute` convention for agent
|
|
85
|
-
* binding: pass `agent` for a fixed-agent mount, or include
|
|
86
|
-
* `:agentId` in the path for dynamic routing. The plugin registers
|
|
87
|
-
* both `/route/history` (default agent) and `/route/history/:agentId`.
|
|
88
|
-
*
|
|
89
|
-
* The handler reads `threadId` and `resourceId` from `RequestContext`
|
|
90
|
-
* (populated upstream by `MastraServer.registerAuthMiddleware`), so
|
|
91
|
-
* no cookie or user lookups happen here.
|
|
92
|
-
*/
|
|
93
|
-
export declare function historyRoute(options: HistoryRouteOptions): import("@mastra/core/server").ApiRoute[];
|
package/lib/src/history.js
DELETED
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Thread history loader exposed as a Mastra custom API route.
|
|
3
|
-
*
|
|
4
|
-
* Backed entirely by native Mastra: looks up the active agent by id,
|
|
5
|
-
* asks its `Memory` instance to `recall` a page of `MastraDBMessage`s,
|
|
6
|
-
* and converts the result to AI SDK V5 `UIMessage`s with the official
|
|
7
|
-
* {@link toAISdkV5Messages} helper from `@mastra/ai-sdk/ui`. No direct
|
|
8
|
-
* database reads.
|
|
9
|
-
*
|
|
10
|
-
* The route is registered through {@link historyRoute} as a Mastra
|
|
11
|
-
* `registerApiRoute` so it sits in the same dispatcher pipeline as
|
|
12
|
-
* the standard agent routes. That means the `MastraServer` auth
|
|
13
|
-
* middleware (in `./server.ts`) has already populated `RequestContext` with
|
|
14
|
-
* `MASTRA_THREAD_ID_KEY` and `MASTRA_RESOURCE_ID_KEY` by the time
|
|
15
|
-
* the handler runs - no cookie or user lookups happen here, and the
|
|
16
|
-
* session-cookie logic stays the single source of truth in `server.ts`.
|
|
17
|
-
*
|
|
18
|
-
* @module
|
|
19
|
-
*/
|
|
20
|
-
import { error, log } from "@dbx-tools/shared-core";
|
|
21
|
-
import { toAISdkV5Messages } from "@mastra/ai-sdk/ui";
|
|
22
|
-
import { registerApiRoute } from "@mastra/core/server";
|
|
23
|
-
import { resolveAgentRequestContext, resolveAgentRouteOptions, } from "./_agent-route-context.js";
|
|
24
|
-
import { clampPerPage, parseIntParam } from "./pagination.js";
|
|
25
|
-
const logger = log.logger("mastra/history");
|
|
26
|
-
/** Default history page size; matches the Mastra storage default. */
|
|
27
|
-
const DEFAULT_PER_PAGE = 20;
|
|
28
|
-
/** Hard cap so a misbehaving client can't fetch the whole thread at once. */
|
|
29
|
-
const MAX_PER_PAGE = 200;
|
|
30
|
-
/**
|
|
31
|
-
* Fetch a page of UI-formatted messages for a thread.
|
|
32
|
-
*
|
|
33
|
-
* Uses the agent's resolved `Memory` (`getMemory()`) so per-agent
|
|
34
|
-
* storage namespaces (`mastra_<agentId>` schemas) and any future
|
|
35
|
-
* memory-side filters apply automatically. When the agent has no
|
|
36
|
-
* memory configured the response is a successful empty page so
|
|
37
|
-
* callers don't have to special-case stateless agents.
|
|
38
|
-
*
|
|
39
|
-
* Pagination is descending-by-default: page 0 is the most recent
|
|
40
|
-
* page, page 1 the page before that, etc. The returned `uiMessages`
|
|
41
|
-
* are always re-sorted into chronological order (oldest -> newest)
|
|
42
|
-
* so the client can prepend them above the existing transcript
|
|
43
|
-
* without sorting locally.
|
|
44
|
-
*/
|
|
45
|
-
export async function loadHistory(opts) {
|
|
46
|
-
const perPage = clampPerPage(opts.perPage, {
|
|
47
|
-
fallback: DEFAULT_PER_PAGE,
|
|
48
|
-
max: MAX_PER_PAGE,
|
|
49
|
-
});
|
|
50
|
-
const page = Math.max(0, Math.trunc(opts.page ?? 0));
|
|
51
|
-
const memory = await opts.agent.getMemory();
|
|
52
|
-
if (!memory) {
|
|
53
|
-
logger.debug("recall:no-memory", { agentId: opts.agent.id, threadId: opts.threadId });
|
|
54
|
-
return { uiMessages: [], page, perPage, total: 0, hasMore: false };
|
|
55
|
-
}
|
|
56
|
-
const startedAt = Date.now();
|
|
57
|
-
const result = await memory.recall({
|
|
58
|
-
threadId: opts.threadId,
|
|
59
|
-
resourceId: opts.resourceId,
|
|
60
|
-
page,
|
|
61
|
-
perPage,
|
|
62
|
-
orderBy: {
|
|
63
|
-
field: "createdAt",
|
|
64
|
-
direction: opts.ascending ? "ASC" : "DESC",
|
|
65
|
-
},
|
|
66
|
-
});
|
|
67
|
-
const chronological = sortChronological(result.messages);
|
|
68
|
-
const uiMessages = toAISdkV5Messages(chronological);
|
|
69
|
-
logger.debug("recall:done", {
|
|
70
|
-
agentId: opts.agent.id,
|
|
71
|
-
threadId: opts.threadId,
|
|
72
|
-
page,
|
|
73
|
-
perPage,
|
|
74
|
-
returned: uiMessages.length,
|
|
75
|
-
total: result.total,
|
|
76
|
-
hasMore: result.hasMore,
|
|
77
|
-
elapsedMs: Date.now() - startedAt,
|
|
78
|
-
});
|
|
79
|
-
return {
|
|
80
|
-
uiMessages,
|
|
81
|
-
page,
|
|
82
|
-
perPage,
|
|
83
|
-
total: result.total,
|
|
84
|
-
hasMore: result.hasMore,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Wipe every persisted message tied to a thread. Returns the count
|
|
89
|
-
* of messages that were on the thread at delete time so the caller
|
|
90
|
-
* can render a "cleared N messages" affordance without an
|
|
91
|
-
* additional round-trip.
|
|
92
|
-
*
|
|
93
|
-
* Agents without a configured `Memory` resolve to a no-op (count
|
|
94
|
-
* 0), matching {@link loadHistory}'s "stateless agents return an
|
|
95
|
-
* empty page" stance so callers don't have to special-case them.
|
|
96
|
-
* Threads that don't exist yet are also a successful no-op - the
|
|
97
|
-
* operation is idempotent so the UI can fire-and-forget without
|
|
98
|
-
* tracking thread existence.
|
|
99
|
-
*/
|
|
100
|
-
export async function clearHistory(opts) {
|
|
101
|
-
const memory = await opts.agent.getMemory();
|
|
102
|
-
if (!memory) {
|
|
103
|
-
logger.debug("clear:no-memory", { agentId: opts.agent.id, threadId: opts.threadId });
|
|
104
|
-
return { cleared: 0 };
|
|
105
|
-
}
|
|
106
|
-
const existing = await memory.getThreadById({ threadId: opts.threadId });
|
|
107
|
-
if (!existing || existing.resourceId !== opts.resourceId) {
|
|
108
|
-
logger.debug("clear:not-owned", {
|
|
109
|
-
agentId: opts.agent.id,
|
|
110
|
-
threadId: opts.threadId,
|
|
111
|
-
found: existing !== null,
|
|
112
|
-
});
|
|
113
|
-
return { cleared: 0 };
|
|
114
|
-
}
|
|
115
|
-
// Mastra's `deleteThread` cascades to the message table, so we
|
|
116
|
-
// can't ask for a count after the fact. Read it pre-delete with a
|
|
117
|
-
// one-page recall sized to fit common threads in a single round
|
|
118
|
-
// trip; the value is for telemetry / UI, not correctness.
|
|
119
|
-
let cleared = 0;
|
|
120
|
-
try {
|
|
121
|
-
const probe = await memory.recall({
|
|
122
|
-
threadId: opts.threadId,
|
|
123
|
-
resourceId: opts.resourceId,
|
|
124
|
-
page: 0,
|
|
125
|
-
perPage: 1,
|
|
126
|
-
});
|
|
127
|
-
cleared = probe.total;
|
|
128
|
-
}
|
|
129
|
-
catch (err) {
|
|
130
|
-
// A missing-thread error is the happy-path "nothing to count";
|
|
131
|
-
// every other error is logged but doesn't block the delete.
|
|
132
|
-
logger.debug("clear:probe-failed", {
|
|
133
|
-
agentId: opts.agent.id,
|
|
134
|
-
threadId: opts.threadId,
|
|
135
|
-
error: error.errorMessage(err),
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
const startedAt = Date.now();
|
|
139
|
-
await memory.deleteThread(opts.threadId);
|
|
140
|
-
logger.info("clear:done", {
|
|
141
|
-
agentId: opts.agent.id,
|
|
142
|
-
threadId: opts.threadId,
|
|
143
|
-
cleared,
|
|
144
|
-
elapsedMs: Date.now() - startedAt,
|
|
145
|
-
});
|
|
146
|
-
return { cleared };
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Register the `<path>` Mastra custom API route. Handles two
|
|
150
|
-
* methods on the same mount:
|
|
151
|
-
*
|
|
152
|
-
* - `GET`: return a page of AI SDK V5 `UIMessage`s for the
|
|
153
|
-
* caller's current thread ({@link loadHistory}).
|
|
154
|
-
* - `DELETE`: wipe every persisted message on the caller's
|
|
155
|
-
* thread ({@link clearHistory}). The session cookie that
|
|
156
|
-
* anchors the thread id is left alone so the user keeps the
|
|
157
|
-
* same thread - only the contents go away.
|
|
158
|
-
*
|
|
159
|
-
* Follows the `@mastra/ai-sdk` `chatRoute` convention for agent
|
|
160
|
-
* binding: pass `agent` for a fixed-agent mount, or include
|
|
161
|
-
* `:agentId` in the path for dynamic routing. The plugin registers
|
|
162
|
-
* both `/route/history` (default agent) and `/route/history/:agentId`.
|
|
163
|
-
*
|
|
164
|
-
* The handler reads `threadId` and `resourceId` from `RequestContext`
|
|
165
|
-
* (populated upstream by `MastraServer.registerAuthMiddleware`), so
|
|
166
|
-
* no cookie or user lookups happen here.
|
|
167
|
-
*/
|
|
168
|
-
export function historyRoute(options) {
|
|
169
|
-
const { path, fixedAgent } = resolveAgentRouteOptions(options, "historyRoute.path");
|
|
170
|
-
return [
|
|
171
|
-
registerApiRoute(path, {
|
|
172
|
-
method: "GET",
|
|
173
|
-
handler: async (c) => {
|
|
174
|
-
const ctx = resolveAgentRequestContext(c, { fixedAgent, threadId: "required" });
|
|
175
|
-
if ("error" in ctx)
|
|
176
|
-
return ctx.error;
|
|
177
|
-
const payload = await loadHistory({
|
|
178
|
-
agent: ctx.agent,
|
|
179
|
-
threadId: ctx.threadId,
|
|
180
|
-
resourceId: ctx.resourceId,
|
|
181
|
-
page: parseIntParam(c.req.query("page")),
|
|
182
|
-
perPage: parseIntParam(c.req.query("perPage")),
|
|
183
|
-
});
|
|
184
|
-
return c.json(payload);
|
|
185
|
-
},
|
|
186
|
-
}),
|
|
187
|
-
registerApiRoute(path, {
|
|
188
|
-
method: "DELETE",
|
|
189
|
-
handler: async (c) => {
|
|
190
|
-
const ctx = resolveAgentRequestContext(c, { fixedAgent, threadId: "required" });
|
|
191
|
-
if ("error" in ctx)
|
|
192
|
-
return ctx.error;
|
|
193
|
-
const { cleared } = await clearHistory({
|
|
194
|
-
agent: ctx.agent,
|
|
195
|
-
threadId: ctx.threadId,
|
|
196
|
-
resourceId: ctx.resourceId,
|
|
197
|
-
});
|
|
198
|
-
const payload = {
|
|
199
|
-
ok: true,
|
|
200
|
-
agentId: ctx.agentId,
|
|
201
|
-
threadId: ctx.threadId,
|
|
202
|
-
cleared,
|
|
203
|
-
};
|
|
204
|
-
return c.json(payload);
|
|
205
|
-
},
|
|
206
|
-
}),
|
|
207
|
-
];
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Sort messages oldest-first by `createdAt`, falling back to whatever
|
|
211
|
-
* order the storage returned them in. The native `recall` call honors
|
|
212
|
-
* `orderBy` but doesn't guarantee a stable secondary sort, so we
|
|
213
|
-
* normalize here before handing the page to the AI SDK converter.
|
|
214
|
-
*/
|
|
215
|
-
function sortChronological(messages) {
|
|
216
|
-
return [...messages].sort((a, b) => {
|
|
217
|
-
const ta = toEpoch(a.createdAt);
|
|
218
|
-
const tb = toEpoch(b.createdAt);
|
|
219
|
-
return ta - tb;
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
function toEpoch(value) {
|
|
223
|
-
if (value instanceof Date)
|
|
224
|
-
return value.getTime();
|
|
225
|
-
if (typeof value === "string" || typeof value === "number") {
|
|
226
|
-
const parsed = new Date(value).getTime();
|
|
227
|
-
return Number.isNaN(parsed) ? 0 : parsed;
|
|
228
|
-
}
|
|
229
|
-
return 0;
|
|
230
|
-
}
|
|
231
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGlzdG9yeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9oaXN0b3J5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FrQkc7QUFFSCxPQUFPLEVBQUUsS0FBSyxFQUFFLEdBQUcsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBTXBELE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBSXRELE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRXZELE9BQU8sRUFDTCwwQkFBMEIsRUFDMUIsd0JBQXdCLEdBRXpCLE1BQU0sMkJBQTJCLENBQUM7QUFDbkMsT0FBTyxFQUFFLFlBQVksRUFBRSxhQUFhLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUU5RCxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLENBQUM7QUFFNUMscUVBQXFFO0FBQ3JFLE1BQU0sZ0JBQWdCLEdBQUcsRUFBRSxDQUFDO0FBQzVCLDZFQUE2RTtBQUM3RSxNQUFNLFlBQVksR0FBRyxHQUFHLENBQUM7QUFhekI7Ozs7Ozs7Ozs7Ozs7O0dBY0c7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLFdBQVcsQ0FBQyxJQUF3QjtJQUN4RCxNQUFNLE9BQU8sR0FBRyxZQUFZLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRTtRQUN6QyxRQUFRLEVBQUUsZ0JBQWdCO1FBQzFCLEdBQUcsRUFBRSxZQUFZO0tBQ2xCLENBQUMsQ0FBQztJQUNILE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3JELE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxTQUFTLEVBQUUsQ0FBQztJQUM1QyxJQUFJLENBQUMs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TyxNQUFNLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQztJQUMzQyxDQUFDO0lBQ0QsT0FBTyxDQUFDLENBQUM7QUFDWCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBUaHJlYWQgaGlzdG9yeSBsb2FkZXIgZXhwb3NlZCBhcyBhIE1hc3RyYSBjdXN0b20gQVBJIHJvdXRlLlxuICpcbiAqIEJhY2tlZCBlbnRpcmVseSBieSBuYXRpdmUgTWFzdHJhOiBsb29rcyB1cCB0aGUgYWN0aXZlIGFnZW50IGJ5IGlkLFxuICogYXNrcyBpdHMgYE1lbW9yeWAgaW5zdGFuY2UgdG8gYHJlY2FsbGAgYSBwYWdlIG9mIGBNYXN0cmFEQk1lc3NhZ2VgcyxcbiAqIGFuZCBjb252ZXJ0cyB0aGUgcmVzdWx0IHRvIEFJIFNESyBWNSBgVUlNZXNzYWdlYHMgd2l0aCB0aGUgb2ZmaWNpYWxcbiAqIHtAbGluayB0b0FJU2RrVjVNZXNzYWdlc30gaGVscGVyIGZyb20gYEBtYXN0cmEvYWktc2RrL3VpYC4gTm8gZGlyZWN0XG4gKiBkYXRhYmFzZSByZWFkcy5cbiAqXG4gKiBUaGUgcm91dGUgaXMgcmVnaXN0ZXJlZCB0aHJvdWdoIHtAbGluayBoaXN0b3J5Um91dGV9IGFzIGEgTWFzdHJhXG4gKiBgcmVnaXN0ZXJBcGlSb3V0ZWAgc28gaXQgc2l0cyBpbiB0aGUgc2FtZSBkaXNwYXRjaGVyIHBpcGVsaW5lIGFzXG4gKiB0aGUgc3RhbmRhcmQgYWdlbnQgcm91dGVzLiBUaGF0IG1lYW5zIHRoZSBgTWFzdHJhU2VydmVyYCBhdXRoXG4gKiBtaWRkbGV3YXJlIChpbiBgLi9zZXJ2ZXIudHNgKSBoYXMgYWxyZWFkeSBwb3B1bGF0ZWQgYFJlcXVlc3RDb250ZXh0YCB3aXRoXG4gKiBgTUFTVFJBX1RIUkVBRF9JRF9LRVlgIGFuZCBgTUFTVFJBX1JFU09VUkNFX0lEX0tFWWAgYnkgdGhlIHRpbWVcbiAqIHRoZSBoYW5kbGVyIHJ1bnMgLSBubyBjb29raWUgb3IgdXNlciBsb29rdXBzIGhhcHBlbiBoZXJlLCBhbmQgdGhlXG4gKiBzZXNzaW9uLWNvb2tpZSBsb2dpYyBzdGF5cyB0aGUgc2luZ2xlIHNvdXJjZSBvZiB0cnV0aCBpbiBgc2VydmVyLnRzYC5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHsgZXJyb3IsIGxvZyB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1jb3JlXCI7XG5pbXBvcnQgdHlwZSB7XG4gIE1hc3RyYUNsZWFySGlzdG9yeVJlc3BvbnNlLFxuICBNYXN0cmFIaXN0b3J5UmVzcG9uc2UsXG4gIE1hc3RyYUhpc3RvcnlVSU1lc3NhZ2UsXG59IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1tYXN0cmFcIjtcbmltcG9ydCB7IHRvQUlTZGtWNU1lc3NhZ2VzIH0gZnJvbSBcIkBtYXN0cmEvYWktc2RrL3VpXCI7XG5pbXBvcnQgdHlwZSB7IEFnZW50IH0gZnJvbSBcIkBtYXN0cmEvY29yZS9hZ2VudFwiO1xuaW1wb3J0IHR5cGUgeyBNYXN0cmFEQk1lc3NhZ2UgfSBmcm9tIFwiQG1hc3RyYS9jb3JlL2FnZW50L21lc3NhZ2UtbGlzdFwiO1xuaW1wb3J0IHR5cGUgeyBDb250ZXh0V2l0aE1hc3RyYSB9IGZyb20gXCJAbWFzdHJhL2NvcmUvc2VydmVyXCI7XG5pbXBvcnQgeyByZWdpc3RlckFwaVJvdXRlIH0gZnJvbSBcIkBtYXN0cmEvY29yZS9zZXJ2ZXJcIjtcblxuaW1wb3J0IHtcbiAgcmVzb2x2ZUFnZW50UmVxdWVzdENvbnRleHQsXG4gIHJlc29sdmVBZ2VudFJvdXRlT3B0aW9ucyxcbiAgdHlwZSBBZ2VudFJvdXRlT3B0aW9ucyxcbn0gZnJvbSBcIi4vX2FnZW50LXJvdXRlLWNvbnRleHQudHNcIjtcbmltcG9ydCB7IGNsYW1wUGVyUGFnZSwgcGFyc2VJbnRQYXJhbSB9IGZyb20gXCIuL3BhZ2luYXRpb24udHNcIjtcblxuY29uc3QgbG9nZ2VyID0gbG9nLmxvZ2dlcihcIm1hc3RyYS9oaXN0b3J5XCIpO1xuXG4vKiogRGVmYXVsdCBoaXN0b3J5IHBhZ2Ugc2l6ZTsgbWF0Y2hlcyB0aGUgTWFzdHJhIHN0b3JhZ2UgZGVmYXVsdC4gKi9cbmNvbnN0IERFRkFVTFRfUEVSX1BBR0UgPSAyMDtcbi8qKiBIYXJkIGNhcCBzbyBhIG1pc2JlaGF2aW5nIGNsaWVudCBjYW4ndCBmZXRjaCB0aGUgd2hvbGUgdGhyZWFkIGF0IG9uY2UuICovXG5jb25zdCBNQVhfUEVSX1BBR0UgPSAyMDA7XG5cbi8qKiBJbnB1dHMgYWNjZXB0ZWQgYnkge0BsaW5rIGxvYWRIaXN0b3J5fS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgTG9hZEhpc3RvcnlPcHRpb25zIHtcbiAgYWdlbnQ6IEFnZW50O1xuICB0aHJlYWRJZDogc3RyaW5nO1xuICByZXNvdXJjZUlkOiBzdHJpbmc7XG4gIHBhZ2U/OiBudW1iZXI7XG4gIHBlclBhZ2U/OiBudW1iZXI7XG4gIC8qKiBXaGVuIHRydWUsIHJldHVybnMgdGhlICpvbGRlc3QqIHBhZ2UgZmlyc3QgKGNocm9ub2xvZ2ljYWwpLiAqL1xuICBhc2NlbmRpbmc/OiBib29sZWFuO1xufVxuXG4vKipcbiAqIEZldGNoIGEgcGFnZSBvZiBVSS1mb3JtYXR0ZWQgbWVzc2FnZXMgZm9yIGEgdGhyZWFkLlxuICpcbiAqIFVzZXMgdGhlIGFnZW50J3MgcmVzb2x2ZWQgYE1lbW9yeWAgKGBnZXRNZW1vcnkoKWApIHNvIHBlci1hZ2VudFxuICogc3RvcmFnZSBuYW1lc3BhY2VzIChgbWFzdHJhXzxhZ2VudElkPmAgc2NoZW1hcykgYW5kIGFueSBmdXR1cmVcbiAqIG1lbW9yeS1zaWRlIGZpbHRlcnMgYXBwbHkgYXV0b21hdGljYWxseS4gV2hlbiB0aGUgYWdlbnQgaGFzIG5vXG4gKiBtZW1vcnkgY29uZmlndXJlZCB0aGUgcmVzcG9uc2UgaXMgYSBzdWNjZXNzZnVsIGVtcHR5IHBhZ2Ugc29cbiAqIGNhbGxlcnMgZG9uJ3QgaGF2ZSB0byBzcGVjaWFsLWNhc2Ugc3RhdGVsZXNzIGFnZW50cy5cbiAqXG4gKiBQYWdpbmF0aW9uIGlzIGRlc2NlbmRpbmctYnktZGVmYXVsdDogcGFnZSAwIGlzIHRoZSBtb3N0IHJlY2VudFxuICogcGFnZSwgcGFnZSAxIHRoZSBwYWdlIGJlZm9yZSB0aGF0LCBldGMuIFRoZSByZXR1cm5lZCBgdWlNZXNzYWdlc2BcbiAqIGFyZSBhbHdheXMgcmUtc29ydGVkIGludG8gY2hyb25vbG9naWNhbCBvcmRlciAob2xkZXN0IC0+IG5ld2VzdClcbiAqIHNvIHRoZSBjbGllbnQgY2FuIHByZXBlbmQgdGhlbSBhYm92ZSB0aGUgZXhpc3RpbmcgdHJhbnNjcmlwdFxuICogd2l0aG91dCBzb3J0aW5nIGxvY2FsbHkuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBsb2FkSGlzdG9yeShvcHRzOiBMb2FkSGlzdG9yeU9wdGlvbnMpOiBQcm9taXNlPE1hc3RyYUhpc3RvcnlSZXNwb25zZT4ge1xuICBjb25zdCBwZXJQYWdlID0gY2xhbXBQZXJQYWdlKG9wdHMucGVyUGFnZSwge1xuICAgIGZhbGxiYWNrOiBERUZBVUxUX1BFUl9QQUdFLFxuICAgIG1heDogTUFYX1BFUl9QQUdFLFxuICB9KTtcbiAgY29uc3QgcGFnZSA9IE1hdGgubWF4KDAsIE1hdGgudHJ1bmMob3B0cy5wYWdlID8/IDApKTtcbiAgY29uc3QgbWVtb3J5ID0gYXdhaXQgb3B0cy5hZ2VudC5nZXRNZW1vcnkoKTtcbiAgaWYgKCFtZW1vcnkpIHtcbiAgICBsb2dnZXIuZGVidWcoXCJyZWNhbGw6bm8tbWVtb3J5XCIsIHsgYWdlbnRJZDogb3B0cy5hZ2VudC5pZCwgdGhyZWFkSWQ6IG9wdHMudGhyZWFkSWQgfSk7XG4gICAgcmV0dXJuIHsgdWlNZXNzYWdlczogW10sIHBhZ2UsIHBlclBhZ2UsIHRvdGFsOiAwLCBoYXNNb3JlOiBmYWxzZSB9O1xuICB9XG4gIGNvbnN0IHN0YXJ0ZWRBdCA9IERhdGUubm93KCk7XG4gIGNvbnN0IHJlc3VsdCA9IGF3YWl0IG1lbW9yeS5yZWNhbGwoe1xuICAgIHRocmVhZElkOiBvcHRzLnRocmVhZElkLFxuICAgIHJlc291cmNlSWQ6IG9wdHMucmVzb3VyY2VJZCxcbiAgICBwYWdlLFxuICAgIHBlclBhZ2UsXG4gICAgb3JkZXJCeToge1xuICAgICAgZmllbGQ6IFwiY3JlYXRlZEF0XCIsXG4gICAgICBkaXJlY3Rpb246IG9wdHMuYXNjZW5kaW5nID8gXCJBU0NcIiA6IFwiREVTQ1wiLFxuICAgIH0sXG4gIH0pO1xuICBjb25zdCBjaHJvbm9sb2dpY2FsID0gc29ydENocm9ub2xvZ2ljYWwocmVzdWx0Lm1lc3NhZ2VzKTtcbiAgY29uc3QgdWlNZXNzYWdlcyA9IHRvQUlTZGtWNU1lc3NhZ2VzKGNocm9ub2xvZ2ljYWwpIGFzIHVua25vd24gYXMgTWFzdHJhSGlzdG9yeVVJTWVzc2FnZVtdO1xuICBsb2dnZXIuZGVidWcoXCJyZWNhbGw6ZG9uZVwiLCB7XG4gICAgYWdlbnRJZDogb3B0cy5hZ2VudC5pZCxcbiAgICB0aHJlYWRJZDogb3B0cy50aHJlYWRJZCxcbiAgICBwYWdlLFxuICAgIHBlclBhZ2UsXG4gICAgcmV0dXJuZWQ6IHVpTWVzc2FnZXMubGVuZ3RoLFxuICAgIHRvdGFsOiByZXN1bHQudG90YWwsXG4gICAgaGFzTW9yZTogcmVzdWx0Lmhhc01vcmUsXG4gICAgZWxhcHNlZE1zOiBEYXRlLm5vdygpIC0gc3RhcnRlZEF0LFxuICB9KTtcbiAgcmV0dXJuIHtcbiAgICB1aU1lc3NhZ2VzLFxuICAgIHBhZ2UsXG4gICAgcGVyUGFnZSxcbiAgICB0b3RhbDogcmVzdWx0LnRvdGFsLFxuICAgIGhhc01vcmU6IHJlc3VsdC5oYXNNb3JlLFxuICB9O1xufVxuXG4vKiogSW5wdXRzIGFjY2VwdGVkIGJ5IHtAbGluayBjbGVhckhpc3Rvcnl9LiAqL1xuZXhwb3J0IGludGVyZmFjZSBDbGVhckhpc3RvcnlPcHRpb25zIHtcbiAgYWdlbnQ6IEFnZW50O1xuICB0aHJlYWRJZDogc3RyaW5nO1xuICByZXNvdXJjZUlkOiBzdHJpbmc7XG59XG5cbi8qKlxuICogV2lwZSBldmVyeSBwZXJzaXN0ZWQgbWVzc2FnZSB0aWVkIHRvIGEgdGhyZWFkLiBSZXR1cm5zIHRoZSBjb3VudFxuICogb2YgbWVzc2FnZXMgdGhhdCB3ZXJlIG9uIHRoZSB0aHJlYWQgYXQgZGVsZXRlIHRpbWUgc28gdGhlIGNhbGxlclxuICogY2FuIHJlbmRlciBhIFwiY2xlYXJlZCBOIG1lc3NhZ2VzXCIgYWZmb3JkYW5jZSB3aXRob3V0IGFuXG4gKiBhZGRpdGlvbmFsIHJvdW5kLXRyaXAuXG4gKlxuICogQWdlbnRzIHdpdGhvdXQgYSBjb25maWd1cmVkIGBNZW1vcnlgIHJlc29sdmUgdG8gYSBuby1vcCAoY291bnRcbiAqIDApLCBtYXRjaGluZyB7QGxpbmsgbG9hZEhpc3Rvcnl9J3MgXCJzdGF0ZWxlc3MgYWdlbnRzIHJldHVybiBhblxuICogZW1wdHkgcGFnZVwiIHN0YW5jZSBzbyBjYWxsZXJzIGRvbid0IGhhdmUgdG8gc3BlY2lhbC1jYXNlIHRoZW0uXG4gKiBUaHJlYWRzIHRoYXQgZG9uJ3QgZXhpc3QgeWV0IGFyZSBhbHNvIGEgc3VjY2Vzc2Z1bCBuby1vcCAtIHRoZVxuICogb3BlcmF0aW9uIGlzIGlkZW1wb3RlbnQgc28gdGhlIFVJIGNhbiBmaXJlLWFuZC1mb3JnZXQgd2l0aG91dFxuICogdHJhY2tpbmcgdGhyZWFkIGV4aXN0ZW5jZS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNsZWFySGlzdG9yeShvcHRzOiBDbGVhckhpc3RvcnlPcHRpb25zKTogUHJvbWlzZTx7IGNsZWFyZWQ6IG51bWJlciB9PiB7XG4gIGNvbnN0IG1lbW9yeSA9IGF3YWl0IG9wdHMuYWdlbnQuZ2V0TWVtb3J5KCk7XG4gIGlmICghbWVtb3J5KSB7XG4gICAgbG9nZ2VyLmRlYnVnKFwiY2xlYXI6bm8tbWVtb3J5XCIsIHsgYWdlbnRJZDogb3B0cy5hZ2VudC5pZCwgdGhyZWFkSWQ6IG9wdHMudGhyZWFkSWQgfSk7XG4gICAgcmV0dXJuIHsgY2xlYXJlZDogMCB9O1xuICB9XG4gIGNvbnN0IGV4aXN0aW5nID0gYXdhaXQgbWVtb3J5LmdldFRocmVhZEJ5SWQoeyB0aHJlYWRJZDogb3B0cy50aHJlYWRJZCB9KTtcbiAgaWYgKCFleGlzdGluZyB8fCBleGlzdGluZy5yZXNvdXJjZUlkICE9PSBvcHRzLnJlc291cmNlSWQpIHtcbiAgICBsb2dnZXIuZGVidWcoXCJjbGVhcjpub3Qtb3duZWRcIiwge1xuICAgICAgYWdlbnRJZDogb3B0cy5hZ2VudC5pZCxcbiAgICAgIHRocmVhZElkOiBvcHRzLnRocmVhZElkLFxuICAgICAgZm91bmQ6IGV4aXN0aW5nICE9PSBudWxsLFxuICAgIH0pO1xuICAgIHJldHVybiB7IGNsZWFyZWQ6IDAgfTtcbiAgfVxuICAvLyBNYXN0cmEncyBgZGVsZXRlVGhyZWFkYCBjYXNjYWRlcyB0byB0aGUgbWVzc2FnZSB0YWJsZSwgc28gd2VcbiAgLy8gY2FuJ3QgYXNrIGZvciBhIGNvdW50IGFmdGVyIHRoZSBmYWN0LiBSZWFkIGl0IHByZS1kZWxldGUgd2l0aCBhXG4gIC8vIG9uZS1wYWdlIHJlY2FsbCBzaXplZCB0byBmaXQgY29tbW9uIHRocmVhZHMgaW4gYSBzaW5nbGUgcm91bmRcbiAgLy8gdHJpcDsgdGhlIHZhbHVlIGlzIGZvciB0ZWxlbWV0cnkgLyBVSSwgbm90IGNvcnJlY3RuZXNzLlxuICBsZXQgY2xlYXJlZCA9IDA7XG4gIHRyeSB7XG4gICAgY29uc3QgcHJvYmUgPSBhd2FpdCBtZW1vcnkucmVjYWxsKHtcbiAgICAgIHRocmVhZElkOiBvcHRzLnRocmVhZElkLFxuICAgICAgcmVzb3VyY2VJZDogb3B0cy5yZXNvdXJjZUlkLFxuICAgICAgcGFnZTogMCxcbiAgICAgIHBlclBhZ2U6IDEsXG4gICAgfSk7XG4gICAgY2xlYXJlZCA9IHByb2JlLnRvdGFsO1xuICB9IGNhdGNoIChlcnIpIHtcbiAgICAvLyBBIG1pc3NpbmctdGhyZWFkIGVycm9yIGlzIHRoZSBoYXBweS1wYXRoIFwibm90aGluZyB0byBjb3VudFwiO1xuICAgIC8vIGV2ZXJ5IG90aGVyIGVycm9yIGlzIGxvZ2dlZCBidXQgZG9lc24ndCBibG9jayB0aGUgZGVsZXRlLlxuICAgIGxvZ2dlci5kZWJ1ZyhcImNsZWFyOnByb2JlLWZhaWxlZFwiLCB7XG4gICAgICBhZ2VudElkOiBvcHRzLmFnZW50LmlkLFxuICAgICAgdGhyZWFkSWQ6IG9wdHMudGhyZWFkSWQsXG4gICAgICBlcnJvcjogZXJyb3IuZXJyb3JNZXNzYWdlKGVyciksXG4gICAgfSk7XG4gIH1cblxuICBjb25zdCBzdGFydGVkQXQgPSBEYXRlLm5vdygpO1xuICBhd2FpdCBtZW1vcnkuZGVsZXRlVGhyZWFkKG9wdHMudGhyZWFkSWQpO1xuICBsb2dnZXIuaW5mbyhcImNsZWFyOmRvbmVcIiwge1xuICAgIGFnZW50SWQ6IG9wdHMuYWdlbnQuaWQsXG4gICAgdGhyZWFkSWQ6IG9wdHMudGhyZWFkSWQsXG4gICAgY2xlYXJlZCxcbiAgICBlbGFwc2VkTXM6IERhdGUubm93KCkgLSBzdGFydGVkQXQsXG4gIH0pO1xuICByZXR1cm4geyBjbGVhcmVkIH07XG59XG5cbi8qKiBPcHRpb25zIGFjY2VwdGVkIGJ5IHtAbGluayBoaXN0b3J5Um91dGV9LiAqL1xuZXhwb3J0IHR5cGUgSGlzdG9yeVJvdXRlT3B0aW9ucyA9IEFnZW50Um91dGVPcHRpb25zO1xuXG4vKipcbiAqIFJlZ2lzdGVyIHRoZSBgPHBhdGg+YCBNYXN0cmEgY3VzdG9tIEFQSSByb3V0ZS4gSGFuZGxlcyB0d29cbiAqIG1ldGhvZHMgb24gdGhlIHNhbWUgbW91bnQ6XG4gKlxuICogICAtIGBHRVRgOiByZXR1cm4gYSBwYWdlIG9mIEFJIFNESyBWNSBgVUlNZXNzYWdlYHMgZm9yIHRoZVxuICogICAgIGNhbGxlcidzIGN1cnJlbnQgdGhyZWFkICh7QGxpbmsgbG9hZEhpc3Rvcnl9KS5cbiAqICAgLSBgREVMRVRFYDogd2lwZSBldmVyeSBwZXJzaXN0ZWQgbWVzc2FnZSBvbiB0aGUgY2FsbGVyJ3NcbiAqICAgICB0aHJlYWQgKHtAbGluayBjbGVhckhpc3Rvcnl9KS4gVGhlIHNlc3Npb24gY29va2llIHRoYXRcbiAqICAgICBhbmNob3JzIHRoZSB0aHJlYWQgaWQgaXMgbGVmdCBhbG9uZSBzbyB0aGUgdXNlciBrZWVwcyB0aGVcbiAqICAgICBzYW1lIHRocmVhZCAtIG9ubHkgdGhlIGNvbnRlbnRzIGdvIGF3YXkuXG4gKlxuICogRm9sbG93cyB0aGUgYEBtYXN0cmEvYWktc2RrYCBgY2hhdFJvdXRlYCBjb252ZW50aW9uIGZvciBhZ2VudFxuICogYmluZGluZzogcGFzcyBgYWdlbnRgIGZvciBhIGZpeGVkLWFnZW50IG1vdW50LCBvciBpbmNsdWRlXG4gKiBgOmFnZW50SWRgIGluIHRoZSBwYXRoIGZvciBkeW5hbWljIHJvdXRpbmcuIFRoZSBwbHVnaW4gcmVnaXN0ZXJzXG4gKiBib3RoIGAvcm91dGUvaGlzdG9yeWAgKGRlZmF1bHQgYWdlbnQpIGFuZCBgL3JvdXRlL2hpc3RvcnkvOmFnZW50SWRgLlxuICpcbiAqIFRoZSBoYW5kbGVyIHJlYWRzIGB0aHJlYWRJZGAgYW5kIGByZXNvdXJjZUlkYCBmcm9tIGBSZXF1ZXN0Q29udGV4dGBcbiAqIChwb3B1bGF0ZWQgdXBzdHJlYW0gYnkgYE1hc3RyYVNlcnZlci5yZWdpc3RlckF1dGhNaWRkbGV3YXJlYCksIHNvXG4gKiBubyBjb29raWUgb3IgdXNlciBsb29rdXBzIGhhcHBlbiBoZXJlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gaGlzdG9yeVJvdXRlKG9wdGlvbnM6IEhpc3RvcnlSb3V0ZU9wdGlvbnMpIHtcbiAgY29uc3QgeyBwYXRoLCBmaXhlZEFnZW50IH0gPSByZXNvbHZlQWdlbnRSb3V0ZU9wdGlvbnMob3B0aW9ucywgXCJoaXN0b3J5Um91dGUucGF0aFwiKTtcblxuICByZXR1cm4gW1xuICAgIHJlZ2lzdGVyQXBpUm91dGUocGF0aCwge1xuICAgICAgbWV0aG9kOiBcIkdFVFwiLFxuICAgICAgaGFuZGxlcjogYXN5bmMgKGM6IENvbnRleHRXaXRoTWFzdHJhKSA9PiB7XG4gICAgICAgIGNvbnN0IGN0eCA9IHJlc29sdmVBZ2VudFJlcXVlc3RDb250ZXh0KGMsIHsgZml4ZWRBZ2VudCwgdGhyZWFkSWQ6IFwicmVxdWlyZWRcIiB9KTtcbiAgICAgICAgaWYgKFwiZXJyb3JcIiBpbiBjdHgpIHJldHVybiBjdHguZXJyb3I7XG4gICAgICAgIGNvbnN0IHBheWxvYWQgPSBhd2FpdCBsb2FkSGlzdG9yeSh7XG4gICAgICAgICAgYWdlbnQ6IGN0eC5hZ2VudCxcbiAgICAgICAgICB0aHJlYWRJZDogY3R4LnRocmVhZElkLFxuICAgICAgICAgIHJlc291cmNlSWQ6IGN0eC5yZXNvdXJjZUlkLFxuICAgICAgICAgIHBhZ2U6IHBhcnNlSW50UGFyYW0oYy5yZXEucXVlcnkoXCJwYWdlXCIpKSxcbiAgICAgICAgICBwZXJQYWdlOiBwYXJzZUludFBhcmFtKGMucmVxLnF1ZXJ5KFwicGVyUGFnZVwiKSksXG4gICAgICAgIH0pO1xuICAgICAgICByZXR1cm4gYy5qc29uKHBheWxvYWQpO1xuICAgICAgfSxcbiAgICB9KSxcbiAgICByZWdpc3RlckFwaVJvdXRlKHBhdGgsIHtcbiAgICAgIG1ldGhvZDogXCJERUxFVEVcIixcbiAgICAgIGhhbmRsZXI6IGFzeW5jIChjOiBDb250ZXh0V2l0aE1hc3RyYSkgPT4ge1xuICAgICAgICBjb25zdCBjdHggPSByZXNvbHZlQWdlbnRSZXF1ZXN0Q29udGV4dChjLCB7IGZpeGVkQWdlbnQsIHRocmVhZElkOiBcInJlcXVpcmVkXCIgfSk7XG4gICAgICAgIGlmIChcImVycm9yXCIgaW4gY3R4KSByZXR1cm4gY3R4LmVycm9yO1xuICAgICAgICBjb25zdCB7IGNsZWFyZWQgfSA9IGF3YWl0IGNsZWFySGlzdG9yeSh7XG4gICAgICAgICAgYWdlbnQ6IGN0eC5hZ2VudCxcbiAgICAgICAgICB0aHJlYWRJZDogY3R4LnRocmVhZElkLFxuICAgICAgICAgIHJlc291cmNlSWQ6IGN0eC5yZXNvdXJjZUlkLFxuICAgICAgICB9KTtcbiAgICAgICAgY29uc3QgcGF5bG9hZDogTWFzdHJhQ2xlYXJIaXN0b3J5UmVzcG9uc2UgPSB7XG4gICAgICAgICAgb2s6IHRydWUsXG4gICAgICAgICAgYWdlbnRJZDogY3R4LmFnZW50SWQsXG4gICAgICAgICAgdGhyZWFkSWQ6IGN0eC50aHJlYWRJZCxcbiAgICAgICAgICBjbGVhcmVkLFxuICAgICAgICB9O1xuICAgICAgICByZXR1cm4gYy5qc29uKHBheWxvYWQpO1xuICAgICAgfSxcbiAgICB9KSxcbiAgXTtcbn1cblxuLyoqXG4gKiBTb3J0IG1lc3NhZ2VzIG9sZGVzdC1maXJzdCBieSBgY3JlYXRlZEF0YCwgZmFsbGluZyBiYWNrIHRvIHdoYXRldmVyXG4gKiBvcmRlciB0aGUgc3RvcmFnZSByZXR1cm5lZCB0aGVtIGluLiBUaGUgbmF0aXZlIGByZWNhbGxgIGNhbGwgaG9ub3JzXG4gKiBgb3JkZXJCeWAgYnV0IGRvZXNuJ3QgZ3VhcmFudGVlIGEgc3RhYmxlIHNlY29uZGFyeSBzb3J0LCBzbyB3ZVxuICogbm9ybWFsaXplIGhlcmUgYmVmb3JlIGhhbmRpbmcgdGhlIHBhZ2UgdG8gdGhlIEFJIFNESyBjb252ZXJ0ZXIuXG4gKi9cbmZ1bmN0aW9uIHNvcnRDaHJvbm9sb2dpY2FsKG1lc3NhZ2VzOiBNYXN0cmFEQk1lc3NhZ2VbXSk6IE1hc3RyYURCTWVzc2FnZVtdIHtcbiAgcmV0dXJuIFsuLi5tZXNzYWdlc10uc29ydCgoYSwgYikgPT4ge1xuICAgIGNvbnN0IHRhID0gdG9FcG9jaChhLmNyZWF0ZWRBdCk7XG4gICAgY29uc3QgdGIgPSB0b0Vwb2NoKGIuY3JlYXRlZEF0KTtcbiAgICByZXR1cm4gdGEgLSB0YjtcbiAgfSk7XG59XG5cbmZ1bmN0aW9uIHRvRXBvY2godmFsdWU6IHVua25vd24pOiBudW1iZXIge1xuICBpZiAodmFsdWUgaW5zdGFuY2VvZiBEYXRlKSByZXR1cm4gdmFsdWUuZ2V0VGltZSgpO1xuICBpZiAodHlwZW9mIHZhbHVlID09PSBcInN0cmluZ1wiIHx8IHR5cGVvZiB2YWx1ZSA9PT0gXCJudW1iZXJcIikge1xuICAgIGNvbnN0IHBhcnNlZCA9IG5ldyBEYXRlKHZhbHVlKS5nZXRUaW1lKCk7XG4gICAgcmV0dXJuIE51bWJlci5pc05hTihwYXJzZWQpID8gMCA6IHBhcnNlZDtcbiAgfVxuICByZXR1cm4gMDtcbn1cbiJdfQ==
|