@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.
Files changed (46) hide show
  1. package/README.md +72 -25
  2. package/index.ts +4 -7
  3. package/lib/index.d.ts +4 -7
  4. package/lib/index.js +4 -5
  5. package/lib/src/agents.d.ts +15 -6
  6. package/lib/src/agents.js +30 -12
  7. package/lib/src/chat.d.ts +7 -0
  8. package/lib/src/chat.js +19 -0
  9. package/lib/src/config.d.ts +2 -2
  10. package/lib/src/config.js +1 -1
  11. package/lib/src/genie.js +5 -4
  12. package/lib/src/memory.d.ts +2 -2
  13. package/lib/src/memory.js +2 -2
  14. package/lib/src/model.d.ts +2 -0
  15. package/lib/src/model.js +7 -1
  16. package/lib/src/plugin.d.ts +1 -1
  17. package/lib/src/plugin.js +3 -31
  18. package/lib/src/processors.d.ts +4 -28
  19. package/lib/src/processors.js +4 -61
  20. package/lib/src/server.d.ts +10 -21
  21. package/lib/src/server.js +52 -28
  22. package/lib/src/serving-sanitize.d.ts +8 -5
  23. package/lib/src/serving-sanitize.js +16 -11
  24. package/lib/src/trace-io.js +7 -3
  25. package/lib/src/writer.d.ts +7 -7
  26. package/lib/src/writer.js +12 -9
  27. package/lib/tsconfig.tsbuildinfo +1 -1
  28. package/package.json +23 -22
  29. package/src/agents.ts +92 -27
  30. package/src/chat.ts +20 -0
  31. package/src/config.ts +5 -2
  32. package/src/genie.ts +7 -11
  33. package/src/memory.ts +8 -8
  34. package/src/model.ts +7 -0
  35. package/src/plugin.ts +2 -30
  36. package/src/processors.ts +4 -66
  37. package/src/server.ts +49 -27
  38. package/src/serving-sanitize.ts +14 -10
  39. package/src/trace-io.ts +7 -2
  40. package/src/writer.ts +15 -11
  41. package/lib/src/history.d.ts +0 -93
  42. package/lib/src/history.js +0 -231
  43. package/lib/src/threads.d.ts +0 -104
  44. package/lib/src/threads.js +0 -264
  45. package/src/history.ts +0 -270
  46. 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`, and a route-patch middleware
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 the history / threads routes). Resolution order:
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. These are the
369
- * only *writes* the browser client is allowed to make against stock Mastra.
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
- * `/agents/x/stream`, `/route/history/x`, `/mcp/...`). In `"scoped"`
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, this plugin's own
385
- * OBO/resource-scoped `/route/*` routes, and (when enabled) MCP - so the
386
- * whole admin / mutating / bulk-export surface Mastra also exposes is
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
- // This plugin's own custom routes are individually OBO- and
397
- // resource-scoped (see history.ts / threads.ts), so every method is safe.
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
- * Patches around `@mastra/express`'s custom-route dispatcher so the
408
- * plugin's custom API routes (e.g. `historyRoute`) work when
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 = req.path === "/route" || req.path.startsWith("/route/");
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();
@@ -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 Astra requires
9
- * `reasoning_effort: "none"` when Chat Completions carries function tools.
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 astraTools = applyAstraToolCompatibility(parsed);
61
- let changed = openaiChat.stripUnsupportedChatFields(parsed).length > 0 || astraTools;
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
- * Add the Chat Completions option Databricks-hosted GPT Astra requires when
107
- * function tools are present. An explicit caller value always wins.
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 applyAstraToolCompatibility(body: Record<string, unknown>): boolean {
110
- if ("reasoning_effort" in body || !Array.isArray(body.tools) || body.tools.length === 0) {
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
- const tokens = new Set(string.tokenizeWithOptions({ lowerCase: true }, model));
116
- if (!tokens.has("gpt") || !tokens.has("astra")) return false;
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 = /^\/agents\/[^/]+\/(stream|generate)(\/|$)/i;
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 = object.isRecord(frame.payload) ? frame.payload.text : undefined;
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 events through Mastra's
3
- * `ctx.writer`. Centralizes the "the downstream stream may already
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 { MastraWriter } from "@dbx-tools/shared-mastra";
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 `writer.write`. No-op when `writer` is undefined;
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 safeWrite(
27
+ export async function safeWriteProgress(
28
28
  log: log.Logger,
29
- writer: MastraWriter | undefined,
30
- chunk: unknown,
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.write(chunk);
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", {
@@ -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[];
@@ -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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGlzdG9yeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9oaXN0b3J5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FrQkc7QUFFSCxPQUFPLEVBQUUsS0FBSyxFQUFFLEdBQUcsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBTXBELE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBSXRELE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRXZELE9BQU8sRUFDTCwwQkFBMEIsRUFDMUIsd0JBQXdCLEdBRXpCLE1BQU0sMkJBQTJCLENBQUM7QUFDbkMsT0FBTyxFQUFFLFlBQVksRUFBRSxhQUFhLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUU5RCxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLENBQUM7QUFFNUMscUVBQXFFO0FBQ3JFLE1BQU0sZ0JBQWdCLEdBQUcsRUFBRSxDQUFDO0FBQzVCLDZFQUE2RTtBQUM3RSxNQUFNLFlBQVksR0FBRyxHQUFHLENBQUM7QUFhekI7Ozs7Ozs7Ozs7Ozs7O0dBY0c7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLFdBQVcsQ0FBQyxJQUF3QjtJQUN4RCxNQUFNLE9BQU8sR0FBRyxZQUFZLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRTtRQUN6QyxRQUFRLEVBQUUsZ0JBQWdCO1FBQzFCLEdBQUcsRUFBRSxZQUFZO0tBQ2xCLENBQUMsQ0FBQztJQUNILE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3JELE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxTQUFTLEVBQUUsQ0FBQztJQUM1QyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDWixNQUFNLENBQUMsS0FBSyxDQUFDLGtCQUFrQixFQUFFLEVBQUUsT0FBTyxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxFQUFFLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztRQUN0RixPQUFPLEVBQUUsVUFBVSxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxDQUFDLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxDQUFDO0lBQ3JFLENBQUM7SUFDRCxNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7SUFDN0IsTUFBTSxNQUFNLEdBQUcsTUFBTSxNQUFNLENBQUMsTUFBTSxDQUFDO1FBQ2pDLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUTtRQUN2QixVQUFVLEVBQUUsSUFBSSxDQUFDLFVBQVU7UUFDM0IsSUFBSTtRQUNKLE9BQU87UUFDUCxPQUFPLEVBQUU7WUFDUCxLQUFLLEVBQUUsV0FBVztZQUNsQixTQUFTLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxNQUFNO1NBQzNDO0tBQ0YsQ0FBQyxDQUFDO0lBQ0gsTUFBTSxhQUFhLEdBQUcsaUJBQWlCLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ3pELE1BQU0sVUFBVSxHQUFHLGlCQUFpQixDQUFDLGFBQWEsQ0FBd0MsQ0FBQztJQUMzRixNQUFNLENBQUMsS0FBSyxDQUFDLGFBQWEsRUFBRTtRQUMxQixPQUFPLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFO1FBQ3RCLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUTtRQUN2QixJQUFJO1FBQ0osT0FBTztRQUNQLFFBQVEsRUFBRSxVQUFVLENBQUMsTUFBTTtRQUMzQixLQUFLLEVBQUUsTUFBTSxDQUFDLEtBQUs7UUFDbkIsT0FBTyxFQUFFLE1BQU0sQ0FBQyxPQUFPO1FBQ3ZCLFNBQVMsRUFBRSxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsU0FBUztLQUNsQyxDQUFDLENBQUM7SUFDSCxPQUFPO1FBQ0wsVUFBVTtRQUNWLElBQUk7UUFDSixPQUFPO1FBQ1AsS0FBSyxFQUFFLE1BQU0sQ0FBQyxLQUFLO1FBQ25CLE9BQU8sRUFBRSxNQUFNLENBQUMsT0FBTztLQUN4QixDQUFDO0FBQ0osQ0FBQztBQVNEOzs7Ozs7Ozs7Ozs7R0FZRztBQUNILE1BQU0sQ0FBQyxLQUFLLFVBQVUsWUFBWSxDQUFDLElBQXlCO0lBQzFELE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxTQUFTLEVBQUUsQ0FBQztJQUM1QyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDWixNQUFNLENBQUMsS0FBSyxDQUFDLGlCQUFpQixFQUFFLEVBQUUsT0FBTyxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxFQUFFLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztRQUNyRixPQUFPLEVBQUUsT0FBTyxFQUFFLENBQUMsRUFBRSxDQUFDO0lBQ3hCLENBQUM7SUFDRCxNQUFNLFFBQVEsR0FBRyxNQUFNLE1BQU0sQ0FBQyxhQUFhLENBQUMsRUFBRSxRQUFRLEVBQUUsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7SUFDekUsSUFBSSxDQUFDLFFBQVEsSUFBSSxRQUFRLENBQUMsVUFBVSxLQUFLLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUN6RCxNQUFNLENBQUMsS0FBSyxDQUFDLGlCQUFpQixFQUFFO1lBQzlCLE9BQU8sRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUU7WUFDdEIsUUFBUSxFQUFFLElBQUksQ0FBQyxRQUFRO1lBQ3ZCLEtBQUssRUFBRSxRQUFRLEtBQUssSUFBSTtTQUN6QixDQUFDLENBQUM7UUFDSCxPQUFPLEVBQUUsT0FBTyxFQUFFLENBQUMsRUFBRSxDQUFDO0lBQ3hCLENBQUM7SUFDRCwrREFBK0Q7SUFDL0Qsa0VBQWtFO0lBQ2xFLGdFQUFnRTtJQUNoRSwwREFBMEQ7SUFDMUQsSUFBSSxPQUFPLEdBQUcsQ0FBQyxDQUFDO0lBQ2hCLElBQUksQ0FBQztRQUNILE1BQU0sS0FBSyxHQUFHLE1BQU0sTUFBTSxDQUFDLE1BQU0sQ0FBQztZQUNoQyxRQUFRLEVBQUUsSUFBSSxDQUFDLFFBQVE7WUFDdkIsVUFBVSxFQUFFLElBQUksQ0FBQyxVQUFVO1lBQzNCLElBQUksRUFBRSxDQUFDO1lBQ1AsT0FBTyxFQUFFLENBQUM7U0FDWCxDQUFDLENBQUM7UUFDSCxPQUFPLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQztJQUN4QixDQUFDO0lBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQztRQUNiLCtEQUErRDtRQUMvRCw0REFBNEQ7UUFDNUQsTUFBTSxDQUFDLEtBQUssQ0FBQyxvQkFBb0IsRUFBRTtZQUNqQyxPQUFPLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFO1lBQ3RCLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUTtZQUN2QixLQUFLLEVBQUUsS0FBSyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUM7U0FDL0IsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVELE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztJQUM3QixNQUFNLE1BQU0sQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ3pDLE1BQU0sQ0FBQyxJQUFJLENBQUMsWUFBWSxFQUFFO1FBQ3hCLE9BQU8sRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUU7UUFDdEIsUUFBUSxFQUFFLElBQUksQ0FBQyxRQUFRO1FBQ3ZCLE9BQU87UUFDUCxTQUFTLEVBQUUsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLFNBQVM7S0FDbEMsQ0FBQyxDQUFDO0lBQ0gsT0FBTyxFQUFFLE9BQU8sRUFBRSxDQUFDO0FBQ3JCLENBQUM7QUFLRDs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW1CRztBQUNILE1BQU0sVUFBVSxZQUFZLENBQUMsT0FBNEI7SUFDdkQsTUFBTSxFQUFFLElBQUksRUFBRSxVQUFVLEVBQUUsR0FBRyx3QkFBd0IsQ0FBQyxPQUFPLEVBQUUsbUJBQW1CLENBQUMsQ0FBQztJQUVwRixPQUFPO1FBQ0wsZ0JBQWdCLENBQUMsSUFBSSxFQUFFO1lBQ3JCLE1BQU0sRUFBRSxLQUFLO1lBQ2IsT0FBTyxFQUFFLEtBQUssRUFBRSxDQUFvQixFQUFFLEVBQUU7Z0JBQ3RDLE1BQU0sR0FBRyxHQUFHLDBCQUEwQixDQUFDLENBQUMsRUFBRSxFQUFFLFVBQVUsRUFBRSxRQUFRLEVBQUUsVUFBVSxFQUFFLENBQUMsQ0FBQztnQkFDaEYsSUFBSSxPQUFPLElBQUksR0FBRztvQkFBRSxPQUFPLEdBQUcsQ0FBQyxLQUFLLENBQUM7Z0JBQ3JDLE1BQU0sT0FBTyxHQUFHLE1BQU0sV0FBVyxDQUFDO29CQUNoQyxLQUFLLEVBQUUsR0FBRyxDQUFDLEtBQUs7b0JBQ2hCLFFBQVEsRUFBRSxHQUFHLENBQUMsUUFBUTtvQkFDdEIsVUFBVSxFQUFFLEdBQUcsQ0FBQyxVQUFVO29CQUMxQixJQUFJLEVBQUUsYUFBYSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO29CQUN4QyxPQUFPLEVBQUUsYUFBYSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQyxDQUFDO2lCQUMvQyxDQUFDLENBQUM7Z0JBQ0gsT0FBTyxDQUFDLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBQ3pCLENBQUM7U0FDRixDQUFDO1FBQ0YsZ0JBQWdCLENBQUMsSUFBSSxFQUFFO1lBQ3JCLE1BQU0sRUFBRSxRQUFRO1lBQ2hCLE9BQU8sRUFBRSxLQUFLLEVBQUUsQ0FBb0IsRUFBRSxFQUFFO2dCQUN0QyxNQUFNLEdBQUcsR0FBRywwQkFBMEIsQ0FBQyxDQUFDLEVBQUUsRUFBRSxVQUFVLEVBQUUsUUFBUSxFQUFFLFVBQVUsRUFBRSxDQUFDLENBQUM7Z0JBQ2hGLElBQUksT0FBTyxJQUFJLEdBQUc7b0JBQUUsT0FBTyxHQUFHLENBQUMsS0FBSyxDQUFDO2dCQUNyQyxNQUFNLEVBQUUsT0FBTyxFQUFFLEdBQUcsTUFBTSxZQUFZLENBQUM7b0JBQ3JDLEtBQUssRUFBRSxHQUFHLENBQUMsS0FBSztvQkFDaEIsUUFBUSxFQUFFLEdBQUcsQ0FBQyxRQUFRO29CQUN0QixVQUFVLEVBQUUsR0FBRyxDQUFDLFVBQVU7aUJBQzNCLENBQUMsQ0FBQztnQkFDSCxNQUFNLE9BQU8sR0FBK0I7b0JBQzFDLEVBQUUsRUFBRSxJQUFJO29CQUNSLE9BQU8sRUFBRSxHQUFHLENBQUMsT0FBTztvQkFDcEIsUUFBUSxFQUFFLEdBQUcsQ0FBQyxRQUFRO29CQUN0QixPQUFPO2lCQUNSLENBQUM7Z0JBQ0YsT0FBTyxDQUFDLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBQ3pCLENBQUM7U0FDRixDQUFDO0tBQ0gsQ0FBQztBQUNKLENBQUM7QUFFRDs7Ozs7R0FLRztBQUNILFNBQVMsaUJBQWlCLENBQUMsUUFBMkI7SUFDcEQsT0FBTyxDQUFDLEdBQUcsUUFBUSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFO1FBQ2pDLE1BQU0sRUFBRSxHQUFHLE9BQU8sQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDaEMsTUFBTSxFQUFFLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUNoQyxPQUFPLEVBQUUsR0FBRyxFQUFFLENBQUM7SUFDakIsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDO0FBRUQsU0FBUyxPQUFPLENBQUMsS0FBYztJQUM3QixJQUFJLEtBQUssWUFBWSxJQUFJO1FBQUUsT0FBTyxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUM7SUFDbEQsSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxFQUFFLENBQUM7UUFDM0QsTUFBTSxNQUFNLEdBQUcsSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDekMsT0FBTyxNQUFNLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQztJQUMzQyxDQUFDO0lBQ0QsT0FBTyxDQUFDLENBQUM7QUFDWCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBUaHJlYWQgaGlzdG9yeSBsb2FkZXIgZXhwb3NlZCBhcyBhIE1hc3RyYSBjdXN0b20gQVBJIHJvdXRlLlxuICpcbiAqIEJhY2tlZCBlbnRpcmVseSBieSBuYXRpdmUgTWFzdHJhOiBsb29rcyB1cCB0aGUgYWN0aXZlIGFnZW50IGJ5IGlkLFxuICogYXNrcyBpdHMgYE1lbW9yeWAgaW5zdGFuY2UgdG8gYHJlY2FsbGAgYSBwYWdlIG9mIGBNYXN0cmFEQk1lc3NhZ2VgcyxcbiAqIGFuZCBjb252ZXJ0cyB0aGUgcmVzdWx0IHRvIEFJIFNESyBWNSBgVUlNZXNzYWdlYHMgd2l0aCB0aGUgb2ZmaWNpYWxcbiAqIHtAbGluayB0b0FJU2RrVjVNZXNzYWdlc30gaGVscGVyIGZyb20gYEBtYXN0cmEvYWktc2RrL3VpYC4gTm8gZGlyZWN0XG4gKiBkYXRhYmFzZSByZWFkcy5cbiAqXG4gKiBUaGUgcm91dGUgaXMgcmVnaXN0ZXJlZCB0aHJvdWdoIHtAbGluayBoaXN0b3J5Um91dGV9IGFzIGEgTWFzdHJhXG4gKiBgcmVnaXN0ZXJBcGlSb3V0ZWAgc28gaXQgc2l0cyBpbiB0aGUgc2FtZSBkaXNwYXRjaGVyIHBpcGVsaW5lIGFzXG4gKiB0aGUgc3RhbmRhcmQgYWdlbnQgcm91dGVzLiBUaGF0IG1lYW5zIHRoZSBgTWFzdHJhU2VydmVyYCBhdXRoXG4gKiBtaWRkbGV3YXJlIChpbiBgLi9zZXJ2ZXIudHNgKSBoYXMgYWxyZWFkeSBwb3B1bGF0ZWQgYFJlcXVlc3RDb250ZXh0YCB3aXRoXG4gKiBgTUFTVFJBX1RIUkVBRF9JRF9LRVlgIGFuZCBgTUFTVFJBX1JFU09VUkNFX0lEX0tFWWAgYnkgdGhlIHRpbWVcbiAqIHRoZSBoYW5kbGVyIHJ1bnMgLSBubyBjb29raWUgb3IgdXNlciBsb29rdXBzIGhhcHBlbiBoZXJlLCBhbmQgdGhlXG4gKiBzZXNzaW9uLWNvb2tpZSBsb2dpYyBzdGF5cyB0aGUgc2luZ2xlIHNvdXJjZSBvZiB0cnV0aCBpbiBgc2VydmVyLnRzYC5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHsgZXJyb3IsIGxvZyB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1jb3JlXCI7XG5pbXBvcnQgdHlwZSB7XG4gIE1hc3RyYUNsZWFySGlzdG9yeVJlc3BvbnNlLFxuICBNYXN0cmFIaXN0b3J5UmVzcG9uc2UsXG4gIE1hc3RyYUhpc3RvcnlVSU1lc3NhZ2UsXG59IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1tYXN0cmFcIjtcbmltcG9ydCB7IHRvQUlTZGtWNU1lc3NhZ2VzIH0gZnJvbSBcIkBtYXN0cmEvYWktc2RrL3VpXCI7XG5pbXBvcnQgdHlwZSB7IEFnZW50IH0gZnJvbSBcIkBtYXN0cmEvY29yZS9hZ2VudFwiO1xuaW1wb3J0IHR5cGUgeyBNYXN0cmFEQk1lc3NhZ2UgfSBmcm9tIFwiQG1hc3RyYS9jb3JlL2FnZW50L21lc3NhZ2UtbGlzdFwiO1xuaW1wb3J0IHR5cGUgeyBDb250ZXh0V2l0aE1hc3RyYSB9IGZyb20gXCJAbWFzdHJhL2NvcmUvc2VydmVyXCI7XG5pbXBvcnQgeyByZWdpc3RlckFwaVJvdXRlIH0gZnJvbSBcIkBtYXN0cmEvY29yZS9zZXJ2ZXJcIjtcblxuaW1wb3J0IHtcbiAgcmVzb2x2ZUFnZW50UmVxdWVzdENvbnRleHQsXG4gIHJlc29sdmVBZ2VudFJvdXRlT3B0aW9ucyxcbiAgdHlwZSBBZ2VudFJvdXRlT3B0aW9ucyxcbn0gZnJvbSBcIi4vX2FnZW50LXJvdXRlLWNvbnRleHQudHNcIjtcbmltcG9ydCB7IGNsYW1wUGVyUGFnZSwgcGFyc2VJbnRQYXJhbSB9IGZyb20gXCIuL3BhZ2luYXRpb24udHNcIjtcblxuY29uc3QgbG9nZ2VyID0gbG9nLmxvZ2dlcihcIm1hc3RyYS9oaXN0b3J5XCIpO1xuXG4vKiogRGVmYXVsdCBoaXN0b3J5IHBhZ2Ugc2l6ZTsgbWF0Y2hlcyB0aGUgTWFzdHJhIHN0b3JhZ2UgZGVmYXVsdC4gKi9cbmNvbnN0IERFRkFVTFRfUEVSX1BBR0UgPSAyMDtcbi8qKiBIYXJkIGNhcCBzbyBhIG1pc2JlaGF2aW5nIGNsaWVudCBjYW4ndCBmZXRjaCB0aGUgd2hvbGUgdGhyZWFkIGF0IG9uY2UuICovXG5jb25zdCBNQVhfUEVSX1BBR0UgPSAyMDA7XG5cbi8qKiBJbnB1dHMgYWNjZXB0ZWQgYnkge0BsaW5rIGxvYWRIaXN0b3J5fS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgTG9hZEhpc3RvcnlPcHRpb25zIHtcbiAgYWdlbnQ6IEFnZW50O1xuICB0aHJlYWRJZDogc3RyaW5nO1xuICByZXNvdXJjZUlkOiBzdHJpbmc7XG4gIHBhZ2U/OiBudW1iZXI7XG4gIHBlclBhZ2U/OiBudW1iZXI7XG4gIC8qKiBXaGVuIHRydWUsIHJldHVybnMgdGhlICpvbGRlc3QqIHBhZ2UgZmlyc3QgKGNocm9ub2xvZ2ljYWwpLiAqL1xuICBhc2NlbmRpbmc/OiBib29sZWFuO1xufVxuXG4vKipcbiAqIEZldGNoIGEgcGFnZSBvZiBVSS1mb3JtYXR0ZWQgbWVzc2FnZXMgZm9yIGEgdGhyZWFkLlxuICpcbiAqIFVzZXMgdGhlIGFnZW50J3MgcmVzb2x2ZWQgYE1lbW9yeWAgKGBnZXRNZW1vcnkoKWApIHNvIHBlci1hZ2VudFxuICogc3RvcmFnZSBuYW1lc3BhY2VzIChgbWFzdHJhXzxhZ2VudElkPmAgc2NoZW1hcykgYW5kIGFueSBmdXR1cmVcbiAqIG1lbW9yeS1zaWRlIGZpbHRlcnMgYXBwbHkgYXV0b21hdGljYWxseS4gV2hlbiB0aGUgYWdlbnQgaGFzIG5vXG4gKiBtZW1vcnkgY29uZmlndXJlZCB0aGUgcmVzcG9uc2UgaXMgYSBzdWNjZXNzZnVsIGVtcHR5IHBhZ2Ugc29cbiAqIGNhbGxlcnMgZG9uJ3QgaGF2ZSB0byBzcGVjaWFsLWNhc2Ugc3RhdGVsZXNzIGFnZW50cy5cbiAqXG4gKiBQYWdpbmF0aW9uIGlzIGRlc2NlbmRpbmctYnktZGVmYXVsdDogcGFnZSAwIGlzIHRoZSBtb3N0IHJlY2VudFxuICogcGFnZSwgcGFnZSAxIHRoZSBwYWdlIGJlZm9yZSB0aGF0LCBldGMuIFRoZSByZXR1cm5lZCBgdWlNZXNzYWdlc2BcbiAqIGFyZSBhbHdheXMgcmUtc29ydGVkIGludG8gY2hyb25vbG9naWNhbCBvcmRlciAob2xkZXN0IC0+IG5ld2VzdClcbiAqIHNvIHRoZSBjbGllbnQgY2FuIHByZXBlbmQgdGhlbSBhYm92ZSB0aGUgZXhpc3RpbmcgdHJhbnNjcmlwdFxuICogd2l0aG91dCBzb3J0aW5nIGxvY2FsbHkuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBsb2FkSGlzdG9yeShvcHRzOiBMb2FkSGlzdG9yeU9wdGlvbnMpOiBQcm9taXNlPE1hc3RyYUhpc3RvcnlSZXNwb25zZT4ge1xuICBjb25zdCBwZXJQYWdlID0gY2xhbXBQZXJQYWdlKG9wdHMucGVyUGFnZSwge1xuICAgIGZhbGxiYWNrOiBERUZBVUxUX1BFUl9QQUdFLFxuICAgIG1heDogTUFYX1BFUl9QQUdFLFxuICB9KTtcbiAgY29uc3QgcGFnZSA9IE1hdGgubWF4KDAsIE1hdGgudHJ1bmMob3B0cy5wYWdlID8/IDApKTtcbiAgY29uc3QgbWVtb3J5ID0gYXdhaXQgb3B0cy5hZ2VudC5nZXRNZW1vcnkoKTtcbiAgaWYgKCFtZW1vcnkpIHtcbiAgICBsb2dnZXIuZGVidWcoXCJyZWNhbGw6bm8tbWVtb3J5XCIsIHsgYWdlbnRJZDogb3B0cy5hZ2VudC5pZCwgdGhyZWFkSWQ6IG9wdHMudGhyZWFkSWQgfSk7XG4gICAgcmV0dXJuIHsgdWlNZXNzYWdlczogW10sIHBhZ2UsIHBlclBhZ2UsIHRvdGFsOiAwLCBoYXNNb3JlOiBmYWxzZSB9O1xuICB9XG4gIGNvbnN0IHN0YXJ0ZWRBdCA9IERhdGUubm93KCk7XG4gIGNvbnN0IHJlc3VsdCA9IGF3YWl0IG1lbW9yeS5yZWNhbGwoe1xuICAgIHRocmVhZElkOiBvcHRzLnRocmVhZElkLFxuICAgIHJlc291cmNlSWQ6IG9wdHMucmVzb3VyY2VJZCxcbiAgICBwYWdlLFxuICAgIHBlclBhZ2UsXG4gICAgb3JkZXJCeToge1xuICAgICAgZmllbGQ6IFwiY3JlYXRlZEF0XCIsXG4gICAgICBkaXJlY3Rpb246IG9wdHMuYXNjZW5kaW5nID8gXCJBU0NcIiA6IFwiREVTQ1wiLFxuICAgIH0sXG4gIH0pO1xuICBjb25zdCBjaHJvbm9sb2dpY2FsID0gc29ydENocm9ub2xvZ2ljYWwocmVzdWx0Lm1lc3NhZ2VzKTtcbiAgY29uc3QgdWlNZXNzYWdlcyA9IHRvQUlTZGtWNU1lc3NhZ2VzKGNocm9ub2xvZ2ljYWwpIGFzIHVua25vd24gYXMgTWFzdHJhSGlzdG9yeVVJTWVzc2FnZVtdO1xuICBsb2dnZXIuZGVidWcoXCJyZWNhbGw6ZG9uZVwiLCB7XG4gICAgYWdlbnRJZDogb3B0cy5hZ2VudC5pZCxcbiAgICB0aHJlYWRJZDogb3B0cy50aHJlYWRJZCxcbiAgICBwYWdlLFxuICAgIHBlclBhZ2UsXG4gICAgcmV0dXJuZWQ6IHVpTWVzc2FnZXMubGVuZ3RoLFxuICAgIHRvdGFsOiByZXN1bHQudG90YWwsXG4gICAgaGFzTW9yZTogcmVzdWx0Lmhhc01vcmUsXG4gICAgZWxhcHNlZE1zOiBEYXRlLm5vdygpIC0gc3RhcnRlZEF0LFxuICB9KTtcbiAgcmV0dXJuIHtcbiAgICB1aU1lc3NhZ2VzLFxuICAgIHBhZ2UsXG4gICAgcGVyUGFnZSxcbiAgICB0b3RhbDogcmVzdWx0LnRvdGFsLFxuICAgIGhhc01vcmU6IHJlc3VsdC5oYXNNb3JlLFxuICB9O1xufVxuXG4vKiogSW5wdXRzIGFjY2VwdGVkIGJ5IHtAbGluayBjbGVhckhpc3Rvcnl9LiAqL1xuZXhwb3J0IGludGVyZmFjZSBDbGVhckhpc3RvcnlPcHRpb25zIHtcbiAgYWdlbnQ6IEFnZW50O1xuICB0aHJlYWRJZDogc3RyaW5nO1xuICByZXNvdXJjZUlkOiBzdHJpbmc7XG59XG5cbi8qKlxuICogV2lwZSBldmVyeSBwZXJzaXN0ZWQgbWVzc2FnZSB0aWVkIHRvIGEgdGhyZWFkLiBSZXR1cm5zIHRoZSBjb3VudFxuICogb2YgbWVzc2FnZXMgdGhhdCB3ZXJlIG9uIHRoZSB0aHJlYWQgYXQgZGVsZXRlIHRpbWUgc28gdGhlIGNhbGxlclxuICogY2FuIHJlbmRlciBhIFwiY2xlYXJlZCBOIG1lc3NhZ2VzXCIgYWZmb3JkYW5jZSB3aXRob3V0IGFuXG4gKiBhZGRpdGlvbmFsIHJvdW5kLXRyaXAuXG4gKlxuICogQWdlbnRzIHdpdGhvdXQgYSBjb25maWd1cmVkIGBNZW1vcnlgIHJlc29sdmUgdG8gYSBuby1vcCAoY291bnRcbiAqIDApLCBtYXRjaGluZyB7QGxpbmsgbG9hZEhpc3Rvcnl9J3MgXCJzdGF0ZWxlc3MgYWdlbnRzIHJldHVybiBhblxuICogZW1wdHkgcGFnZVwiIHN0YW5jZSBzbyBjYWxsZXJzIGRvbid0IGhhdmUgdG8gc3BlY2lhbC1jYXNlIHRoZW0uXG4gKiBUaHJlYWRzIHRoYXQgZG9uJ3QgZXhpc3QgeWV0IGFyZSBhbHNvIGEgc3VjY2Vzc2Z1bCBuby1vcCAtIHRoZVxuICogb3BlcmF0aW9uIGlzIGlkZW1wb3RlbnQgc28gdGhlIFVJIGNhbiBmaXJlLWFuZC1mb3JnZXQgd2l0aG91dFxuICogdHJhY2tpbmcgdGhyZWFkIGV4aXN0ZW5jZS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNsZWFySGlzdG9yeShvcHRzOiBDbGVhckhpc3RvcnlPcHRpb25zKTogUHJvbWlzZTx7IGNsZWFyZWQ6IG51bWJlciB9PiB7XG4gIGNvbnN0IG1lbW9yeSA9IGF3YWl0IG9wdHMuYWdlbnQuZ2V0TWVtb3J5KCk7XG4gIGlmICghbWVtb3J5KSB7XG4gICAgbG9nZ2VyLmRlYnVnKFwiY2xlYXI6bm8tbWVtb3J5XCIsIHsgYWdlbnRJZDogb3B0cy5hZ2VudC5pZCwgdGhyZWFkSWQ6IG9wdHMudGhyZWFkSWQgfSk7XG4gICAgcmV0dXJuIHsgY2xlYXJlZDogMCB9O1xuICB9XG4gIGNvbnN0IGV4aXN0aW5nID0gYXdhaXQgbWVtb3J5LmdldFRocmVhZEJ5SWQoeyB0aHJlYWRJZDogb3B0cy50aHJlYWRJZCB9KTtcbiAgaWYgKCFleGlzdGluZyB8fCBleGlzdGluZy5yZXNvdXJjZUlkICE9PSBvcHRzLnJlc291cmNlSWQpIHtcbiAgICBsb2dnZXIuZGVidWcoXCJjbGVhcjpub3Qtb3duZWRcIiwge1xuICAgICAgYWdlbnRJZDogb3B0cy5hZ2VudC5pZCxcbiAgICAgIHRocmVhZElkOiBvcHRzLnRocmVhZElkLFxuICAgICAgZm91bmQ6IGV4aXN0aW5nICE9PSBudWxsLFxuICAgIH0pO1xuICAgIHJldHVybiB7IGNsZWFyZWQ6IDAgfTtcbiAgfVxuICAvLyBNYXN0cmEncyBgZGVsZXRlVGhyZWFkYCBjYXNjYWRlcyB0byB0aGUgbWVzc2FnZSB0YWJsZSwgc28gd2VcbiAgLy8gY2FuJ3QgYXNrIGZvciBhIGNvdW50IGFmdGVyIHRoZSBmYWN0LiBSZWFkIGl0IHByZS1kZWxldGUgd2l0aCBhXG4gIC8vIG9uZS1wYWdlIHJlY2FsbCBzaXplZCB0byBmaXQgY29tbW9uIHRocmVhZHMgaW4gYSBzaW5nbGUgcm91bmRcbiAgLy8gdHJpcDsgdGhlIHZhbHVlIGlzIGZvciB0ZWxlbWV0cnkgLyBVSSwgbm90IGNvcnJlY3RuZXNzLlxuICBsZXQgY2xlYXJlZCA9IDA7XG4gIHRyeSB7XG4gICAgY29uc3QgcHJvYmUgPSBhd2FpdCBtZW1vcnkucmVjYWxsKHtcbiAgICAgIHRocmVhZElkOiBvcHRzLnRocmVhZElkLFxuICAgICAgcmVzb3VyY2VJZDogb3B0cy5yZXNvdXJjZUlkLFxuICAgICAgcGFnZTogMCxcbiAgICAgIHBlclBhZ2U6IDEsXG4gICAgfSk7XG4gICAgY2xlYXJlZCA9IHByb2JlLnRvdGFsO1xuICB9IGNhdGNoIChlcnIpIHtcbiAgICAvLyBBIG1pc3NpbmctdGhyZWFkIGVycm9yIGlzIHRoZSBoYXBweS1wYXRoIFwibm90aGluZyB0byBjb3VudFwiO1xuICAgIC8vIGV2ZXJ5IG90aGVyIGVycm9yIGlzIGxvZ2dlZCBidXQgZG9lc24ndCBibG9jayB0aGUgZGVsZXRlLlxuICAgIGxvZ2dlci5kZWJ1ZyhcImNsZWFyOnByb2JlLWZhaWxlZFwiLCB7XG4gICAgICBhZ2VudElkOiBvcHRzLmFnZW50LmlkLFxuICAgICAgdGhyZWFkSWQ6IG9wdHMudGhyZWFkSWQsXG4gICAgICBlcnJvcjogZXJyb3IuZXJyb3JNZXNzYWdlKGVyciksXG4gICAgfSk7XG4gIH1cblxuICBjb25zdCBzdGFydGVkQXQgPSBEYXRlLm5vdygpO1xuICBhd2FpdCBtZW1vcnkuZGVsZXRlVGhyZWFkKG9wdHMudGhyZWFkSWQpO1xuICBsb2dnZXIuaW5mbyhcImNsZWFyOmRvbmVcIiwge1xuICAgIGFnZW50SWQ6IG9wdHMuYWdlbnQuaWQsXG4gICAgdGhyZWFkSWQ6IG9wdHMudGhyZWFkSWQsXG4gICAgY2xlYXJlZCxcbiAgICBlbGFwc2VkTXM6IERhdGUubm93KCkgLSBzdGFydGVkQXQsXG4gIH0pO1xuICByZXR1cm4geyBjbGVhcmVkIH07XG59XG5cbi8qKiBPcHRpb25zIGFjY2VwdGVkIGJ5IHtAbGluayBoaXN0b3J5Um91dGV9LiAqL1xuZXhwb3J0IHR5cGUgSGlzdG9yeVJvdXRlT3B0aW9ucyA9IEFnZW50Um91dGVPcHRpb25zO1xuXG4vKipcbiAqIFJlZ2lzdGVyIHRoZSBgPHBhdGg+YCBNYXN0cmEgY3VzdG9tIEFQSSByb3V0ZS4gSGFuZGxlcyB0d29cbiAqIG1ldGhvZHMgb24gdGhlIHNhbWUgbW91bnQ6XG4gKlxuICogICAtIGBHRVRgOiByZXR1cm4gYSBwYWdlIG9mIEFJIFNESyBWNSBgVUlNZXNzYWdlYHMgZm9yIHRoZVxuICogICAgIGNhbGxlcidzIGN1cnJlbnQgdGhyZWFkICh7QGxpbmsgbG9hZEhpc3Rvcnl9KS5cbiAqICAgLSBgREVMRVRFYDogd2lwZSBldmVyeSBwZXJzaXN0ZWQgbWVzc2FnZSBvbiB0aGUgY2FsbGVyJ3NcbiAqICAgICB0aHJlYWQgKHtAbGluayBjbGVhckhpc3Rvcnl9KS4gVGhlIHNlc3Npb24gY29va2llIHRoYXRcbiAqICAgICBhbmNob3JzIHRoZSB0aHJlYWQgaWQgaXMgbGVmdCBhbG9uZSBzbyB0aGUgdXNlciBrZWVwcyB0aGVcbiAqICAgICBzYW1lIHRocmVhZCAtIG9ubHkgdGhlIGNvbnRlbnRzIGdvIGF3YXkuXG4gKlxuICogRm9sbG93cyB0aGUgYEBtYXN0cmEvYWktc2RrYCBgY2hhdFJvdXRlYCBjb252ZW50aW9uIGZvciBhZ2VudFxuICogYmluZGluZzogcGFzcyBgYWdlbnRgIGZvciBhIGZpeGVkLWFnZW50IG1vdW50LCBvciBpbmNsdWRlXG4gKiBgOmFnZW50SWRgIGluIHRoZSBwYXRoIGZvciBkeW5hbWljIHJvdXRpbmcuIFRoZSBwbHVnaW4gcmVnaXN0ZXJzXG4gKiBib3RoIGAvcm91dGUvaGlzdG9yeWAgKGRlZmF1bHQgYWdlbnQpIGFuZCBgL3JvdXRlL2hpc3RvcnkvOmFnZW50SWRgLlxuICpcbiAqIFRoZSBoYW5kbGVyIHJlYWRzIGB0aHJlYWRJZGAgYW5kIGByZXNvdXJjZUlkYCBmcm9tIGBSZXF1ZXN0Q29udGV4dGBcbiAqIChwb3B1bGF0ZWQgdXBzdHJlYW0gYnkgYE1hc3RyYVNlcnZlci5yZWdpc3RlckF1dGhNaWRkbGV3YXJlYCksIHNvXG4gKiBubyBjb29raWUgb3IgdXNlciBsb29rdXBzIGhhcHBlbiBoZXJlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gaGlzdG9yeVJvdXRlKG9wdGlvbnM6IEhpc3RvcnlSb3V0ZU9wdGlvbnMpIHtcbiAgY29uc3QgeyBwYXRoLCBmaXhlZEFnZW50IH0gPSByZXNvbHZlQWdlbnRSb3V0ZU9wdGlvbnMob3B0aW9ucywgXCJoaXN0b3J5Um91dGUucGF0aFwiKTtcblxuICByZXR1cm4gW1xuICAgIHJlZ2lzdGVyQXBpUm91dGUocGF0aCwge1xuICAgICAgbWV0aG9kOiBcIkdFVFwiLFxuICAgICAgaGFuZGxlcjogYXN5bmMgKGM6IENvbnRleHRXaXRoTWFzdHJhKSA9PiB7XG4gICAgICAgIGNvbnN0IGN0eCA9IHJlc29sdmVBZ2VudFJlcXVlc3RDb250ZXh0KGMsIHsgZml4ZWRBZ2VudCwgdGhyZWFkSWQ6IFwicmVxdWlyZWRcIiB9KTtcbiAgICAgICAgaWYgKFwiZXJyb3JcIiBpbiBjdHgpIHJldHVybiBjdHguZXJyb3I7XG4gICAgICAgIGNvbnN0IHBheWxvYWQgPSBhd2FpdCBsb2FkSGlzdG9yeSh7XG4gICAgICAgICAgYWdlbnQ6IGN0eC5hZ2VudCxcbiAgICAgICAgICB0aHJlYWRJZDogY3R4LnRocmVhZElkLFxuICAgICAgICAgIHJlc291cmNlSWQ6IGN0eC5yZXNvdXJjZUlkLFxuICAgICAgICAgIHBhZ2U6IHBhcnNlSW50UGFyYW0oYy5yZXEucXVlcnkoXCJwYWdlXCIpKSxcbiAgICAgICAgICBwZXJQYWdlOiBwYXJzZUludFBhcmFtKGMucmVxLnF1ZXJ5KFwicGVyUGFnZVwiKSksXG4gICAgICAgIH0pO1xuICAgICAgICByZXR1cm4gYy5qc29uKHBheWxvYWQpO1xuICAgICAgfSxcbiAgICB9KSxcbiAgICByZWdpc3RlckFwaVJvdXRlKHBhdGgsIHtcbiAgICAgIG1ldGhvZDogXCJERUxFVEVcIixcbiAgICAgIGhhbmRsZXI6IGFzeW5jIChjOiBDb250ZXh0V2l0aE1hc3RyYSkgPT4ge1xuICAgICAgICBjb25zdCBjdHggPSByZXNvbHZlQWdlbnRSZXF1ZXN0Q29udGV4dChjLCB7IGZpeGVkQWdlbnQsIHRocmVhZElkOiBcInJlcXVpcmVkXCIgfSk7XG4gICAgICAgIGlmIChcImVycm9yXCIgaW4gY3R4KSByZXR1cm4gY3R4LmVycm9yO1xuICAgICAgICBjb25zdCB7IGNsZWFyZWQgfSA9IGF3YWl0IGNsZWFySGlzdG9yeSh7XG4gICAgICAgICAgYWdlbnQ6IGN0eC5hZ2VudCxcbiAgICAgICAgICB0aHJlYWRJZDogY3R4LnRocmVhZElkLFxuICAgICAgICAgIHJlc291cmNlSWQ6IGN0eC5yZXNvdXJjZUlkLFxuICAgICAgICB9KTtcbiAgICAgICAgY29uc3QgcGF5bG9hZDogTWFzdHJhQ2xlYXJIaXN0b3J5UmVzcG9uc2UgPSB7XG4gICAgICAgICAgb2s6IHRydWUsXG4gICAgICAgICAgYWdlbnRJZDogY3R4LmFnZW50SWQsXG4gICAgICAgICAgdGhyZWFkSWQ6IGN0eC50aHJlYWRJZCxcbiAgICAgICAgICBjbGVhcmVkLFxuICAgICAgICB9O1xuICAgICAgICByZXR1cm4gYy5qc29uKHBheWxvYWQpO1xuICAgICAgfSxcbiAgICB9KSxcbiAgXTtcbn1cblxuLyoqXG4gKiBTb3J0IG1lc3NhZ2VzIG9sZGVzdC1maXJzdCBieSBgY3JlYXRlZEF0YCwgZmFsbGluZyBiYWNrIHRvIHdoYXRldmVyXG4gKiBvcmRlciB0aGUgc3RvcmFnZSByZXR1cm5lZCB0aGVtIGluLiBUaGUgbmF0aXZlIGByZWNhbGxgIGNhbGwgaG9ub3JzXG4gKiBgb3JkZXJCeWAgYnV0IGRvZXNuJ3QgZ3VhcmFudGVlIGEgc3RhYmxlIHNlY29uZGFyeSBzb3J0LCBzbyB3ZVxuICogbm9ybWFsaXplIGhlcmUgYmVmb3JlIGhhbmRpbmcgdGhlIHBhZ2UgdG8gdGhlIEFJIFNESyBjb252ZXJ0ZXIuXG4gKi9cbmZ1bmN0aW9uIHNvcnRDaHJvbm9sb2dpY2FsKG1lc3NhZ2VzOiBNYXN0cmFEQk1lc3NhZ2VbXSk6IE1hc3RyYURCTWVzc2FnZVtdIHtcbiAgcmV0dXJuIFsuLi5tZXNzYWdlc10uc29ydCgoYSwgYikgPT4ge1xuICAgIGNvbnN0IHRhID0gdG9FcG9jaChhLmNyZWF0ZWRBdCk7XG4gICAgY29uc3QgdGIgPSB0b0Vwb2NoKGIuY3JlYXRlZEF0KTtcbiAgICByZXR1cm4gdGEgLSB0YjtcbiAgfSk7XG59XG5cbmZ1bmN0aW9uIHRvRXBvY2godmFsdWU6IHVua25vd24pOiBudW1iZXIge1xuICBpZiAodmFsdWUgaW5zdGFuY2VvZiBEYXRlKSByZXR1cm4gdmFsdWUuZ2V0VGltZSgpO1xuICBpZiAodHlwZW9mIHZhbHVlID09PSBcInN0cmluZ1wiIHx8IHR5cGVvZiB2YWx1ZSA9PT0gXCJudW1iZXJcIikge1xuICAgIGNvbnN0IHBhcnNlZCA9IG5ldyBEYXRlKHZhbHVlKS5nZXRUaW1lKCk7XG4gICAgcmV0dXJuIE51bWJlci5pc05hTihwYXJzZWQpID8gMCA6IHBhcnNlZDtcbiAgfVxuICByZXR1cm4gMDtcbn1cbiJdfQ==