@dbx-tools/appkit-mastra 0.9.38 → 0.9.43
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 +7 -3
- package/index.ts +4 -3
- package/lib/index.d.ts +4 -3
- package/lib/index.js +4 -4
- package/lib/src/observability.d.ts +1 -1
- package/lib/src/observability.js +2 -2
- package/lib/src/plugin.js +5 -3
- package/lib/src/{trace-io.d.ts → telemetry.d.ts} +17 -3
- package/lib/src/telemetry.js +326 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +19 -19
- package/src/observability.ts +1 -1
- package/src/plugin.ts +5 -2
- package/src/{trace-io.ts → telemetry.ts} +113 -11
- package/lib/src/trace-io.js +0 -235
|
@@ -25,7 +25,7 @@ import { context, SpanKind, trace, type Span } from "@opentelemetry/api";
|
|
|
25
25
|
import { getRPCMetadata, RPCType } from "@opentelemetry/core";
|
|
26
26
|
import type express from "express";
|
|
27
27
|
|
|
28
|
-
const logger = log.logger("mastra/
|
|
28
|
+
const logger = log.logger("mastra/telemetry");
|
|
29
29
|
|
|
30
30
|
/** Cap on each payload copied onto a span, so one turn cannot bloat the export. */
|
|
31
31
|
export const TRACE_IO_LIMIT = 8_000;
|
|
@@ -34,7 +34,7 @@ export const TRACE_IO_LIMIT = 8_000;
|
|
|
34
34
|
const RESPONSE_BODY_LIMIT = TRACE_IO_LIMIT * 4;
|
|
35
35
|
|
|
36
36
|
/** Tracer scope used only when AppKit did not create a recording HTTP span. */
|
|
37
|
-
const CHAT_TURN_TRACER = "@dbx-tools/appkit-mastra/
|
|
37
|
+
const CHAT_TURN_TRACER = "@dbx-tools/appkit-mastra/telemetry";
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* Mount-relative Mastra agent invoke paths that carry a chat turn body
|
|
@@ -48,16 +48,80 @@ const AGENT_TURN_ROUTE = /^(?:\/agents\/[^/]+\/(?:stream|generate)(?:\/|$)|\/cha
|
|
|
48
48
|
export const MLFLOW_SPAN_INPUTS_ATTR = "mlflow.spanInputs";
|
|
49
49
|
/** Root-span attribute consumed as chat output by MLflow unified trace views. */
|
|
50
50
|
export const MLFLOW_SPAN_OUTPUTS_ATTR = "mlflow.spanOutputs";
|
|
51
|
+
/** Full serialized request messages retained when the MLflow preview is plain text. */
|
|
52
|
+
export const CHAT_MESSAGES_ATTR = "appkit.mastra.chat.messages";
|
|
53
|
+
/** Full serialized response retained when the MLflow preview is plain text. */
|
|
54
|
+
export const CHAT_RESPONSE_ATTR = "appkit.mastra.chat.response";
|
|
55
|
+
/** Credential mode used by Databricks calls in this chat turn. */
|
|
56
|
+
export const CHAT_IDENTITY_ATTR = "appkit.mastra.identity.mode";
|
|
57
|
+
/** Whether the streamed turn emitted a Genie tool or progress event. */
|
|
58
|
+
export const CHAT_GENIE_USED_ATTR = "appkit.mastra.genie.used";
|
|
59
|
+
|
|
60
|
+
/** Root-span metadata known before a chat request is dispatched. */
|
|
61
|
+
export interface ChatTurnTelemetryOptions {
|
|
62
|
+
readonly identity?:
|
|
63
|
+
"obo" | "service-principal" | ((request: express.Request) => "obo" | "service-principal");
|
|
64
|
+
}
|
|
51
65
|
|
|
52
66
|
interface TraceTarget {
|
|
53
67
|
span: Span;
|
|
54
68
|
owned: boolean;
|
|
55
69
|
}
|
|
56
70
|
|
|
71
|
+
/** Return one raw prompt when the request is exactly one text-only user message. */
|
|
72
|
+
export function textOnlyChatInput(messages: unknown): string | undefined {
|
|
73
|
+
if (typeof messages === "string") return messages || undefined;
|
|
74
|
+
if (!Array.isArray(messages) || messages.length !== 1) return undefined;
|
|
75
|
+
const message = messages[0];
|
|
76
|
+
if (!object.isRecord(message) || message.role !== "user") return undefined;
|
|
77
|
+
if (typeof message.content === "string") return message.content || undefined;
|
|
78
|
+
return textFromParts(message.parts) ?? textFromParts(message.content);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function textFromParts(value: unknown): string | undefined {
|
|
82
|
+
if (!Array.isArray(value) || value.length === 0) return undefined;
|
|
83
|
+
const text: string[] = [];
|
|
84
|
+
for (const part of value) {
|
|
85
|
+
if (!object.isRecord(part) || part.type !== "text" || typeof part.text !== "string") {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
text.push(part.text);
|
|
89
|
+
}
|
|
90
|
+
const joined = text.join("");
|
|
91
|
+
return joined || undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function serialized(value: unknown, limit = TRACE_IO_LIMIT): string {
|
|
95
|
+
return JSON.stringify(value)?.slice(0, limit) ?? String(value).slice(0, limit);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function sseFrame(line: string): unknown {
|
|
99
|
+
if (!line.startsWith("data:")) return undefined;
|
|
100
|
+
return json.parse(line.slice(5));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function valueUsesGenie(value: unknown, depth = 0): boolean {
|
|
104
|
+
if (depth > 3) return false;
|
|
105
|
+
if (Array.isArray(value)) return value.some((item) => valueUsesGenie(item, depth + 1));
|
|
106
|
+
if (!object.isRecord(value)) return false;
|
|
107
|
+
if (
|
|
108
|
+
value.type === "data-genie-progress" ||
|
|
109
|
+
value.type === "ask_genie_done" ||
|
|
110
|
+
value.toolName === "ask_genie" ||
|
|
111
|
+
value.tool_name === "ask_genie" ||
|
|
112
|
+
value.name === "ask_genie"
|
|
113
|
+
) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return ["data", "event", "payload"].some((key) => valueUsesGenie(value[key], depth + 1));
|
|
117
|
+
}
|
|
118
|
+
|
|
57
119
|
/** Return the assistant text from one SSE line, or an empty string. */
|
|
58
120
|
function assistantTextFromSseLine(line: string): string {
|
|
59
|
-
|
|
60
|
-
|
|
121
|
+
return assistantTextFromSseFrame(sseFrame(line));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function assistantTextFromSseFrame(frame: unknown): string {
|
|
61
125
|
if (!object.isRecord(frame) || frame.type !== "text-delta") return "";
|
|
62
126
|
const text =
|
|
63
127
|
typeof frame.delta === "string"
|
|
@@ -98,6 +162,7 @@ class AssistantResponseCollector {
|
|
|
98
162
|
private body = "";
|
|
99
163
|
private pendingLine = "";
|
|
100
164
|
private finished = false;
|
|
165
|
+
private genie = false;
|
|
101
166
|
|
|
102
167
|
write(chunk: unknown): void {
|
|
103
168
|
if (this.finished) return;
|
|
@@ -121,6 +186,14 @@ class AssistantResponseCollector {
|
|
|
121
186
|
return this.answer || this.jsonAnswer();
|
|
122
187
|
}
|
|
123
188
|
|
|
189
|
+
rawBody(): string {
|
|
190
|
+
return this.body.slice(0, TRACE_IO_LIMIT);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
usedGenie(): boolean {
|
|
194
|
+
return this.genie;
|
|
195
|
+
}
|
|
196
|
+
|
|
124
197
|
private consume(text: string): void {
|
|
125
198
|
if (!text) return;
|
|
126
199
|
if (this.body.length < RESPONSE_BODY_LIMIT) {
|
|
@@ -139,8 +212,10 @@ class AssistantResponseCollector {
|
|
|
139
212
|
}
|
|
140
213
|
|
|
141
214
|
private consumeSseLine(line: string): void {
|
|
215
|
+
const frame = sseFrame(line);
|
|
216
|
+
this.genie ||= valueUsesGenie(frame);
|
|
142
217
|
if (this.answer.length >= TRACE_IO_LIMIT) return;
|
|
143
|
-
const text =
|
|
218
|
+
const text = assistantTextFromSseFrame(frame);
|
|
144
219
|
this.answer += text.slice(0, TRACE_IO_LIMIT - this.answer.length);
|
|
145
220
|
}
|
|
146
221
|
|
|
@@ -186,10 +261,11 @@ function resolveTraceTarget(): TraceTarget | undefined {
|
|
|
186
261
|
* `path` is mount-relative (what the Mastra sub-app sees), e.g.
|
|
187
262
|
* `/agents/support/stream`.
|
|
188
263
|
*/
|
|
189
|
-
export function
|
|
264
|
+
export function chatTurnTelemetryMiddleware(
|
|
190
265
|
req: express.Request,
|
|
191
266
|
res: express.Response,
|
|
192
267
|
next: express.NextFunction,
|
|
268
|
+
options: ChatTurnTelemetryOptions = {},
|
|
193
269
|
): void {
|
|
194
270
|
if (req.method !== "POST" || !AGENT_TURN_ROUTE.test(req.path)) {
|
|
195
271
|
next();
|
|
@@ -200,17 +276,26 @@ export function chatTurnTraceIoMiddleware(
|
|
|
200
276
|
next();
|
|
201
277
|
return;
|
|
202
278
|
}
|
|
279
|
+
const identity =
|
|
280
|
+
typeof options.identity === "function" ? options.identity(req) : options.identity;
|
|
281
|
+
if (identity) {
|
|
282
|
+
target.span.setAttribute(CHAT_IDENTITY_ATTR, identity);
|
|
283
|
+
}
|
|
284
|
+
target.span.setAttribute(CHAT_GENIE_USED_ATTR, false);
|
|
203
285
|
|
|
204
286
|
const messages = (req.body as { messages?: unknown } | undefined)?.messages;
|
|
205
287
|
if (messages !== undefined) {
|
|
288
|
+
const fullInput = serialized(messages);
|
|
289
|
+
target.span.setAttribute(CHAT_MESSAGES_ATTR, fullInput);
|
|
206
290
|
target.span.setAttribute(
|
|
207
291
|
MLFLOW_SPAN_INPUTS_ATTR,
|
|
208
|
-
|
|
292
|
+
(textOnlyChatInput(messages) ?? fullInput).slice(0, TRACE_IO_LIMIT),
|
|
209
293
|
);
|
|
210
294
|
}
|
|
211
295
|
|
|
212
296
|
const collector = new AssistantResponseCollector();
|
|
213
297
|
let outputRecorded = false;
|
|
298
|
+
let fullOutputRecorded = false;
|
|
214
299
|
let ownedSpanEnded = false;
|
|
215
300
|
const passThroughWrite = res.write.bind(res) as (...args: unknown[]) => boolean;
|
|
216
301
|
const passThroughEnd = res.end.bind(res) as (...args: unknown[]) => unknown;
|
|
@@ -220,6 +305,14 @@ export function chatTurnTraceIoMiddleware(
|
|
|
220
305
|
target.span.setAttribute(MLFLOW_SPAN_OUTPUTS_ATTR, answer.slice(0, TRACE_IO_LIMIT));
|
|
221
306
|
outputRecorded = true;
|
|
222
307
|
};
|
|
308
|
+
const recordFullOutput = (response: string): void => {
|
|
309
|
+
if (fullOutputRecorded || !response) return;
|
|
310
|
+
target.span.setAttribute(CHAT_RESPONSE_ATTR, response.slice(0, TRACE_IO_LIMIT));
|
|
311
|
+
fullOutputRecorded = true;
|
|
312
|
+
};
|
|
313
|
+
const recordGenie = (used: boolean): void => {
|
|
314
|
+
if (used) target.span.setAttribute(CHAT_GENIE_USED_ATTR, true);
|
|
315
|
+
};
|
|
223
316
|
const endOwnedSpan = (): void => {
|
|
224
317
|
if (!target.owned || ownedSpanEnded) return;
|
|
225
318
|
ownedSpanEnded = true;
|
|
@@ -232,6 +325,8 @@ export function chatTurnTraceIoMiddleware(
|
|
|
232
325
|
}) as typeof res.write;
|
|
233
326
|
|
|
234
327
|
res.json = ((body?: unknown) => {
|
|
328
|
+
recordFullOutput(serialized(body));
|
|
329
|
+
recordGenie(valueUsesGenie(body));
|
|
235
330
|
recordOutput(assistantTextFromJson(body));
|
|
236
331
|
return passThroughJson(body);
|
|
237
332
|
}) as typeof res.json;
|
|
@@ -239,6 +334,8 @@ export function chatTurnTraceIoMiddleware(
|
|
|
239
334
|
res.end = ((chunk?: unknown, ...rest: unknown[]) => {
|
|
240
335
|
collector.write(chunk);
|
|
241
336
|
recordOutput(collector.finish());
|
|
337
|
+
recordFullOutput(collector.rawBody());
|
|
338
|
+
recordGenie(collector.usedGenie());
|
|
242
339
|
try {
|
|
243
340
|
return passThroughEnd(chunk, ...rest);
|
|
244
341
|
} finally {
|
|
@@ -248,6 +345,8 @@ export function chatTurnTraceIoMiddleware(
|
|
|
248
345
|
|
|
249
346
|
res.once("close", () => {
|
|
250
347
|
recordOutput(collector.finish());
|
|
348
|
+
recordFullOutput(collector.rawBody());
|
|
349
|
+
recordGenie(collector.usedGenie());
|
|
251
350
|
endOwnedSpan();
|
|
252
351
|
});
|
|
253
352
|
|
|
@@ -255,13 +354,16 @@ export function chatTurnTraceIoMiddleware(
|
|
|
255
354
|
}
|
|
256
355
|
|
|
257
356
|
/**
|
|
258
|
-
* Install {@link
|
|
357
|
+
* Install {@link chatTurnTelemetryMiddleware} on a Mastra Express sub-app.
|
|
259
358
|
*
|
|
260
359
|
* Call before `MastraServer.init()` so the layer sits ahead of the agent
|
|
261
360
|
* routes. Safe to call unconditionally: when no recording tracer is registered,
|
|
262
361
|
* the middleware is a no-op.
|
|
263
362
|
*/
|
|
264
|
-
export function
|
|
265
|
-
app.
|
|
266
|
-
|
|
363
|
+
export function attachChatTurnTelemetry(
|
|
364
|
+
app: express.Express,
|
|
365
|
+
options: ChatTurnTelemetryOptions = {},
|
|
366
|
+
): void {
|
|
367
|
+
app.use((req, res, next) => chatTurnTelemetryMiddleware(req, res, next, options));
|
|
368
|
+
logger.info("chat turn telemetry middleware attached", options);
|
|
267
369
|
}
|
package/lib/src/trace-io.js
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copy each Mastra chat turn's request/response onto its OTel root span
|
|
3
|
-
* so Databricks-managed MLflow can display them.
|
|
4
|
-
*
|
|
5
|
-
* Why this exists: MLflow's UC `<target>_trace_unified` view picks the
|
|
6
|
-
* one span whose `parent_span_id` is empty and reads request/response
|
|
7
|
-
* from `mlflow.spanInputs` / `mlflow.spanOutputs` (or the `gen_ai.*`
|
|
8
|
-
* equivalents) on THAT span only. Mastra records the turn on its
|
|
9
|
-
* `invoke_agent` child under `mastra.agent_run.input` / `.output`, keys
|
|
10
|
-
* the view never reads, so chat traces arrive with both columns null
|
|
11
|
-
* even when the spans themselves look healthy.
|
|
12
|
-
*
|
|
13
|
-
* AppKit's HTTP instrumentation records the server span in OTel RPC metadata,
|
|
14
|
-
* which remains available even when Express makes a middleware-layer child
|
|
15
|
-
* active. When instrumentation is unavailable, this module creates one
|
|
16
|
-
* request-lifetime server span and runs Mastra under it.
|
|
17
|
-
*
|
|
18
|
-
* @module
|
|
19
|
-
*/
|
|
20
|
-
import { StringDecoder } from "node:string_decoder";
|
|
21
|
-
import { json, log, object } from "@dbx-tools/shared-core";
|
|
22
|
-
import { context, SpanKind, trace } from "@opentelemetry/api";
|
|
23
|
-
import { getRPCMetadata, RPCType } from "@opentelemetry/core";
|
|
24
|
-
const logger = log.logger("mastra/trace-io");
|
|
25
|
-
/** Cap on each payload copied onto a span, so one turn cannot bloat the export. */
|
|
26
|
-
export const TRACE_IO_LIMIT = 8_000;
|
|
27
|
-
/** Bound for a non-streaming body captured through `res.end` instead of `res.json`. */
|
|
28
|
-
const RESPONSE_BODY_LIMIT = TRACE_IO_LIMIT * 4;
|
|
29
|
-
/** Tracer scope used only when AppKit did not create a recording HTTP span. */
|
|
30
|
-
const CHAT_TURN_TRACER = "@dbx-tools/appkit-mastra/trace-io";
|
|
31
|
-
/**
|
|
32
|
-
* Mount-relative Mastra agent invoke paths that carry a chat turn body
|
|
33
|
-
* (`messages`) and stream an assistant answer. Resume / approve verbs
|
|
34
|
-
* are intentionally excluded: their bodies are tool decisions, not the
|
|
35
|
-
* user prompt, and they rarely produce a fresh answer worth surfacing.
|
|
36
|
-
*/
|
|
37
|
-
const AGENT_TURN_ROUTE = /^(?:\/agents\/[^/]+\/(?:stream|generate)(?:\/|$)|\/chat\/[^/]+$)/i;
|
|
38
|
-
/** Attribute keys the MLflow UC `*_trace_unified` view reads from the root span. */
|
|
39
|
-
export const MLFLOW_SPAN_INPUTS_ATTR = "mlflow.spanInputs";
|
|
40
|
-
/** Root-span attribute consumed as chat output by MLflow unified trace views. */
|
|
41
|
-
export const MLFLOW_SPAN_OUTPUTS_ATTR = "mlflow.spanOutputs";
|
|
42
|
-
/** Return the assistant text from one SSE line, or an empty string. */
|
|
43
|
-
function assistantTextFromSseLine(line) {
|
|
44
|
-
if (!line.startsWith("data:"))
|
|
45
|
-
return "";
|
|
46
|
-
const frame = json.parse(line.slice(5));
|
|
47
|
-
if (!object.isRecord(frame) || frame.type !== "text-delta")
|
|
48
|
-
return "";
|
|
49
|
-
const text = typeof frame.delta === "string"
|
|
50
|
-
? frame.delta
|
|
51
|
-
: object.isRecord(frame.payload)
|
|
52
|
-
? frame.payload.text
|
|
53
|
-
: undefined;
|
|
54
|
-
return typeof text === "string" ? text : "";
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Concatenate the assistant's answer out of an AI SDK SSE transcript.
|
|
58
|
-
*
|
|
59
|
-
* The agent streams its reply as `text-delta` frames
|
|
60
|
-
* (`data: {"type":"text-delta","payload":{"text":"..."}}`), so the full
|
|
61
|
-
* answer only ever exists as deltas on the wire and has to be
|
|
62
|
-
* reassembled here.
|
|
63
|
-
*/
|
|
64
|
-
export function assistantTextFromSse(body) {
|
|
65
|
-
const parts = [];
|
|
66
|
-
for (const line of body.split("\n")) {
|
|
67
|
-
const text = assistantTextFromSseLine(line);
|
|
68
|
-
if (text)
|
|
69
|
-
parts.push(text);
|
|
70
|
-
}
|
|
71
|
-
return parts.join("").slice(0, TRACE_IO_LIMIT);
|
|
72
|
-
}
|
|
73
|
-
/** Read the final assistant text from a non-streaming Mastra response. */
|
|
74
|
-
export function assistantTextFromJson(body) {
|
|
75
|
-
if (!object.isRecord(body) || typeof body.text !== "string")
|
|
76
|
-
return "";
|
|
77
|
-
return body.text.slice(0, TRACE_IO_LIMIT);
|
|
78
|
-
}
|
|
79
|
-
/** Incrementally assemble UTF-8 SSE text while retaining a bounded JSON fallback. */
|
|
80
|
-
class AssistantResponseCollector {
|
|
81
|
-
decoder = new StringDecoder("utf8");
|
|
82
|
-
answer = "";
|
|
83
|
-
body = "";
|
|
84
|
-
pendingLine = "";
|
|
85
|
-
finished = false;
|
|
86
|
-
write(chunk) {
|
|
87
|
-
if (this.finished)
|
|
88
|
-
return;
|
|
89
|
-
if (typeof chunk === "string") {
|
|
90
|
-
this.consume(chunk);
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
if (ArrayBuffer.isView(chunk)) {
|
|
94
|
-
this.consume(this.decoder.write(Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength)));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
finish() {
|
|
98
|
-
if (this.finished)
|
|
99
|
-
return this.answer || this.jsonAnswer();
|
|
100
|
-
this.finished = true;
|
|
101
|
-
this.consume(this.decoder.end());
|
|
102
|
-
this.consumeSseLine(this.pendingLine);
|
|
103
|
-
this.pendingLine = "";
|
|
104
|
-
return this.answer || this.jsonAnswer();
|
|
105
|
-
}
|
|
106
|
-
consume(text) {
|
|
107
|
-
if (!text)
|
|
108
|
-
return;
|
|
109
|
-
if (this.body.length < RESPONSE_BODY_LIMIT) {
|
|
110
|
-
this.body += text.slice(0, RESPONSE_BODY_LIMIT - this.body.length);
|
|
111
|
-
}
|
|
112
|
-
this.pendingLine += text;
|
|
113
|
-
let newline = this.pendingLine.indexOf("\n");
|
|
114
|
-
while (newline >= 0) {
|
|
115
|
-
this.consumeSseLine(this.pendingLine.slice(0, newline).replace(/\r$/, ""));
|
|
116
|
-
this.pendingLine = this.pendingLine.slice(newline + 1);
|
|
117
|
-
newline = this.pendingLine.indexOf("\n");
|
|
118
|
-
}
|
|
119
|
-
if (this.pendingLine.length > RESPONSE_BODY_LIMIT) {
|
|
120
|
-
this.pendingLine = this.pendingLine.slice(-RESPONSE_BODY_LIMIT);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
consumeSseLine(line) {
|
|
124
|
-
if (this.answer.length >= TRACE_IO_LIMIT)
|
|
125
|
-
return;
|
|
126
|
-
const text = assistantTextFromSseLine(line);
|
|
127
|
-
this.answer += text.slice(0, TRACE_IO_LIMIT - this.answer.length);
|
|
128
|
-
}
|
|
129
|
-
jsonAnswer() {
|
|
130
|
-
return assistantTextFromJson(json.parse(this.body));
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Select the span MLflow treats as the request root.
|
|
135
|
-
*
|
|
136
|
-
* OTel HTTP instrumentation stores its server span in RPC metadata. That span
|
|
137
|
-
* wins over an active Express child. If no recording local span exists, create
|
|
138
|
-
* one under the current context so enabled W3C propagation remains intact.
|
|
139
|
-
*/
|
|
140
|
-
function resolveTraceTarget() {
|
|
141
|
-
const activeContext = context.active();
|
|
142
|
-
const rpc = getRPCMetadata(activeContext);
|
|
143
|
-
if (rpc?.type === RPCType.HTTP && rpc.span.isRecording()) {
|
|
144
|
-
return { span: rpc.span, owned: false };
|
|
145
|
-
}
|
|
146
|
-
const active = trace.getActiveSpan();
|
|
147
|
-
if (active?.isRecording())
|
|
148
|
-
return { span: active, owned: false };
|
|
149
|
-
const span = trace
|
|
150
|
-
.getTracer(CHAT_TURN_TRACER)
|
|
151
|
-
.startSpan("mastra.chat_turn", { kind: SpanKind.SERVER }, activeContext);
|
|
152
|
-
if (!span.isRecording()) {
|
|
153
|
-
span.end();
|
|
154
|
-
return undefined;
|
|
155
|
-
}
|
|
156
|
-
return { span, owned: true };
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Express middleware that stamps chat turn I/O onto the exported OTel root.
|
|
160
|
-
*
|
|
161
|
-
* Tee `res.write`, `res.json`, and `res.end` instead of listening for `finish`:
|
|
162
|
-
* HTTP instrumentation ends its server span on finish, and attributes set
|
|
163
|
-
* after a span ends never reach the exporter.
|
|
164
|
-
*
|
|
165
|
-
* `path` is mount-relative (what the Mastra sub-app sees), e.g.
|
|
166
|
-
* `/agents/support/stream`.
|
|
167
|
-
*/
|
|
168
|
-
export function chatTurnTraceIoMiddleware(req, res, next) {
|
|
169
|
-
if (req.method !== "POST" || !AGENT_TURN_ROUTE.test(req.path)) {
|
|
170
|
-
next();
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
const target = resolveTraceTarget();
|
|
174
|
-
if (!target) {
|
|
175
|
-
next();
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
const messages = req.body?.messages;
|
|
179
|
-
if (messages !== undefined) {
|
|
180
|
-
target.span.setAttribute(MLFLOW_SPAN_INPUTS_ATTR, JSON.stringify(messages).slice(0, TRACE_IO_LIMIT));
|
|
181
|
-
}
|
|
182
|
-
const collector = new AssistantResponseCollector();
|
|
183
|
-
let outputRecorded = false;
|
|
184
|
-
let ownedSpanEnded = false;
|
|
185
|
-
const passThroughWrite = res.write.bind(res);
|
|
186
|
-
const passThroughEnd = res.end.bind(res);
|
|
187
|
-
const passThroughJson = res.json.bind(res);
|
|
188
|
-
const recordOutput = (answer) => {
|
|
189
|
-
if (outputRecorded || !answer)
|
|
190
|
-
return;
|
|
191
|
-
target.span.setAttribute(MLFLOW_SPAN_OUTPUTS_ATTR, answer.slice(0, TRACE_IO_LIMIT));
|
|
192
|
-
outputRecorded = true;
|
|
193
|
-
};
|
|
194
|
-
const endOwnedSpan = () => {
|
|
195
|
-
if (!target.owned || ownedSpanEnded)
|
|
196
|
-
return;
|
|
197
|
-
ownedSpanEnded = true;
|
|
198
|
-
target.span.end();
|
|
199
|
-
};
|
|
200
|
-
res.write = ((chunk, ...rest) => {
|
|
201
|
-
collector.write(chunk);
|
|
202
|
-
return passThroughWrite(chunk, ...rest);
|
|
203
|
-
});
|
|
204
|
-
res.json = ((body) => {
|
|
205
|
-
recordOutput(assistantTextFromJson(body));
|
|
206
|
-
return passThroughJson(body);
|
|
207
|
-
});
|
|
208
|
-
res.end = ((chunk, ...rest) => {
|
|
209
|
-
collector.write(chunk);
|
|
210
|
-
recordOutput(collector.finish());
|
|
211
|
-
try {
|
|
212
|
-
return passThroughEnd(chunk, ...rest);
|
|
213
|
-
}
|
|
214
|
-
finally {
|
|
215
|
-
endOwnedSpan();
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
res.once("close", () => {
|
|
219
|
-
recordOutput(collector.finish());
|
|
220
|
-
endOwnedSpan();
|
|
221
|
-
});
|
|
222
|
-
context.with(trace.setSpan(context.active(), target.span), next);
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* Install {@link chatTurnTraceIoMiddleware} on a Mastra Express sub-app.
|
|
226
|
-
*
|
|
227
|
-
* Call before `MastraServer.init()` so the layer sits ahead of the agent
|
|
228
|
-
* routes. Safe to call unconditionally: when no recording tracer is registered,
|
|
229
|
-
* the middleware is a no-op.
|
|
230
|
-
*/
|
|
231
|
-
export function attachChatTurnTraceIo(app) {
|
|
232
|
-
app.use(chatTurnTraceIoMiddleware);
|
|
233
|
-
logger.info("chat turn I/O middleware attached");
|
|
234
|
-
}
|
|
235
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJhY2UtaW8uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdHJhY2UtaW8udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQWtCRztBQUVILE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUVwRCxPQUFPLEVBQUUsSUFBSSxFQUFFLEdBQUcsRUFBRSxNQUFNLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUMzRCxPQUFPLEVBQUUsT0FBTyxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQWEsTUFBTSxvQkFBb0IsQ0FBQztBQUN6RSxPQUFPLEVBQUUsY0FBYyxFQUFFLE9BQU8sRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRzlELE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsaUJBQWlCLENBQUMsQ0FBQztBQUU3QyxtRkFBbUY7QUFDbkYsTUFBTSxDQUFDLE1BQU0sY0FBYyxHQUFHLEtBQUssQ0FBQztBQUVwQyx1RkFBdUY7QUFDdkYsTUFBTSxtQkFBbUIsR0FBRyxjQUFjLEdBQUcsQ0FBQyxDQUFDO0FBRS9DLCtFQUErRTtBQUMvRSxNQUFNLGdCQUFnQixHQUFHLG1DQUFtQyxDQUFDO0FBRTdEOzs7OztHQUtHO0FBQ0gsTUFBTSxnQkFBZ0IsR0FBRyxtRUFBbUUsQ0FBQztBQUU3RixvRkFBb0Y7QUFDcEYsTUFBTSxDQUFDLE1BQU0sdUJBQXVCLEdBQUcsbUJBQW1CLENBQUM7QUFDM0QsaUZBQWlGO0FBQ2pGLE1BQU0sQ0FBQyxNQUFNLHdCQUF3QixHQUFHLG9CQUFvQixDQUFDO0FBTzdELHVFQUF1RTtBQUN2RSxTQUFTLHdCQUF3QixDQUFDLElBQVk7SUFDNUMsSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDO1FBQUUsT0FBTyxFQUFFLENBQUM7SUFDekMsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDeEMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLElBQUksS0FBSyxDQUFDLElBQUksS0FBSyxZQUFZO1FBQUUsT0FBTyxFQUFFLENBQUM7SUFDdEUsTUFBTSxJQUFJLEdBQ1IsT0FBTyxLQUFLLENBQUMsS0FBSyxLQUFLLFFBQVE7UUFDN0IsQ0FBQyxDQUFDLEtBQUssQ0FBQyxLQUFLO1FBQ2IsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQztZQUM5QixDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxJQUFJO1lBQ3BCLENBQUMsQ0FBQyxTQUFTLENBQUM7SUFDbEIsT0FBTyxPQUFPLElBQUksS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0FBQzlDLENBQUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxVQUFVLG9CQUFvQixDQUFDLElBQVk7SUFDL0MsTUFBTSxLQUFLLEdBQWEsRUFBRSxDQUFDO0lBQzNCLEtBQUssTUFBTSxJQUFJLElBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1FBQ3BDLE1BQU0sSUFBSSxHQUFHLHdCQUF3QixDQUFDLElBQUksQ0FBQyxDQUFDO1FBQzVDLElBQUksSUFBSTtZQUFFLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDN0IsQ0FBQztJQUNELE9BQU8sS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLGNBQWMsQ0FBQyxDQUFDO0FBQ2pELENBQUM7QUFFRCwwRUFBMEU7QUFDMUUsTUFBTSxVQUFVLHFCQUFxQixDQUFDLElBQWE7SUFDakQsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksT0FBTyxJQUFJLENBQUMsSUFBSSxLQUFLLFFBQVE7UUFBRSxPQUFPLEVBQUUsQ0FBQztJQUN2RSxPQUFPLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxjQUFjLENBQUMsQ0FBQztBQUM1QyxDQUFDO0FBRUQscUZBQXFGO0FBQ3JGLE1BQU0sMEJBQTBCO0lBQ2IsT0FBTyxHQUFHLElBQUksYUFBYSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQzdDLE1BQU0sR0FBRyxFQUFFLENBQUM7SUFDWixJQUFJLEdBQUcsRUFBRSxDQUFDO0lBQ1YsV0FBVyxHQUFHLEVBQUUsQ0FBQztJQUNqQixRQUFRLEdBQUcsS0FBSyxDQUFDO0lBRXpCLEtBQUssQ0FBQyxLQUFjO1FBQ2xCLElBQUksSUFBSSxDQUFDLFFBQVE7WUFBRSxPQUFPO1FBQzFCLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDOUIsSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUNwQixPQUFPO1FBQ1QsQ0FBQztRQUNELElBQUksV0FBVyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQzlCLElBQUksQ0FBQyxPQUFPLENBQ1YsSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxFQUFFLEtBQUssQ0FBQyxVQUFVLEVBQUUsS0FBSyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQ2xGLENBQUM7UUFDSixDQUFDO0lBQ0gsQ0FBQztJQUVELE1BQU07UUFDSixJQUFJLElBQUksQ0FBQyxRQUFRO1lBQUUsT0FBTyxJQUFJLENBQUMsTUFBTSxJQUFJLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUMzRCxJQUFJLENBQUMsUUFBUSxHQUFHLElBQUksQ0FBQztRQUNyQixJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQztRQUNqQyxJQUFJLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUN0QyxJQUFJLENBQUMsV0FBVyxHQUFHLEVBQUUsQ0FBQztRQUN0QixPQUFPLElBQUksQ0FBQyxNQUFNLElBQUksSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO0lBQzFDLENBQUM7SUFFTyxPQUFPLENBQUMsSUFBWTtRQUMxQixJQUFJLENBQUMsSUFBSTtZQUFFLE9BQU87UUFDbEIsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sR0FBRyxtQkFBbUIsRUFBRSxDQUFDO1lBQzNDLElBQUksQ0FBQyxJQUFJLElBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsbUJBQW1CLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNyRSxDQUFDO1FBQ0QsSUFBSSxDQUFDLFdBQVcsSUFBSSxJQUFJLENBQUM7UUFDekIsSUFBSSxPQUFPLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDN0MsT0FBTyxPQUFPLElBQUksQ0FBQyxFQUFFLENBQUM7WUFDcEIsSUFBSSxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsT0FBTyxDQUFDLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO1lBQzNFLElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsT0FBTyxHQUFHLENBQUMsQ0FBQyxDQUFDO1lBQ3ZELE9BQU8sR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUMzQyxDQUFDO1FBQ0QsSUFBSSxJQUFJLENBQUMsV0FBVyxDQUFDLE1BQU0sR0FBRyxtQkFBbUIsRUFBRSxDQUFDO1lBQ2xELElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO1FBQ2xFLENBQUM7SUFDSCxDQUFDO0lBRU8sY0FBYyxDQUFDLElBQVk7UUFDakMsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sSUFBSSxjQUFjO1lBQUUsT0FBTztRQUNqRCxNQUFNLElBQUksR0FBRyx3QkFBd0IsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUM1QyxJQUFJLENBQUMsTUFBTSxJQUFJLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLGNBQWMsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ3BFLENBQUM7SUFFTyxVQUFVO1FBQ2hCLE9BQU8scUJBQXFCLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUN0RCxDQUFDO0NBQ0Y7QUFFRDs7Ozs7O0dBTUc7QUFDSCxTQUFTLGtCQUFrQjtJQUN6QixNQUFNLGFBQWEsR0FBRyxPQUFPLENBQUMsTUFBTSxFQUFFLENBQUM7SUFDdkMsTUFBTSxHQUFHLEdBQUcsY0FBYyxDQUFDLGFBQWEsQ0FBQyxDQUFDO0lBQzFDLElBQUksR0FBRyxFQUFFLElBQUksS0FBSyxPQUFPLENBQUMsSUFBSSxJQUFJLEdBQUcsQ0FBQyxJQUFJLENBQUMsV0FBVyxFQUFFLEVBQUUsQ0FBQztRQUN6RCxPQUFPLEVBQUUsSUFBSSxFQUFFLEdBQUcsQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxDQUFDO0lBQzFDLENBQUM7SUFFRCxNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUMsYUFBYSxFQUFFLENBQUM7SUFDckMsSUFBSSxNQUFNLEVBQUUsV0FBVyxFQUFFO1FBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxDQUFDO0lBRWpFLE1BQU0sSUFBSSxHQUFHLEtBQUs7U0FDZixTQUFTLENBQUMsZ0JBQWdCLENBQUM7U0FDM0IsU0FBUyxDQUFDLGtCQUFrQixFQUFFLEVBQUUsSUFBSSxFQUFFLFFBQVEsQ0FBQyxNQUFNLEVBQUUsRUFBRSxhQUFhLENBQUMsQ0FBQztJQUMzRSxJQUFJLENBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxFQUFFLENBQUM7UUFDeEIsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ1gsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQztJQUNELE9BQU8sRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLElBQUksRUFBRSxDQUFDO0FBQy9CLENBQUM7QUFFRDs7Ozs7Ozs7O0dBU0c7QUFDSCxNQUFNLFVBQVUseUJBQXlCLENBQ3ZDLEdBQW9CLEVBQ3BCLEdBQXFCLEVBQ3JCLElBQTBCO0lBRTFCLElBQUksR0FBRyxDQUFDLE1BQU0sS0FBSyxNQUFNLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7UUFDOUQsSUFBSSxFQUFFLENBQUM7UUFDUCxPQUFPO0lBQ1QsQ0FBQztJQUNELE1BQU0sTUFBTSxHQUFHLGtCQUFrQixFQUFFLENBQUM7SUFDcEMsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ1osSUFBSSxFQUFFLENBQUM7UUFDUCxPQUFPO0lBQ1QsQ0FBQztJQUVELE1BQU0sUUFBUSxHQUFJLEdBQUcsQ0FBQyxJQUEyQyxFQUFFLFFBQVEsQ0FBQztJQUM1RSxJQUFJLFFBQVEsS0FBSyxTQUFTLEVBQUUsQ0FBQztRQUMzQixNQUFNLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FDdEIsdUJBQXVCLEVBQ3ZCLElBQUksQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxjQUFjLENBQUMsQ0FDbEQsQ0FBQztJQUNKLENBQUM7SUFFRCxNQUFNLFNBQVMsR0FBRyxJQUFJLDBCQUEwQixFQUFFLENBQUM7SUFDbkQsSUFBSSxjQUFjLEdBQUcsS0FBSyxDQUFDO0lBQzNCLElBQUksY0FBYyxHQUFHLEtBQUssQ0FBQztJQUMzQixNQUFNLGdCQUFnQixHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBb0MsQ0FBQztJQUNoRixNQUFNLGNBQWMsR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQW9DLENBQUM7SUFDNUUsTUFBTSxlQUFlLEdBQUcsR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUF5QyxDQUFDO0lBQ25GLE1BQU0sWUFBWSxHQUFHLENBQUMsTUFBYyxFQUFRLEVBQUU7UUFDNUMsSUFBSSxjQUFjLElBQUksQ0FBQyxNQUFNO1lBQUUsT0FBTztRQUN0QyxNQUFNLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyx3QkFBd0IsRUFBRSxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxjQUFjLENBQUMsQ0FBQyxDQUFDO1FBQ3BGLGNBQWMsR0FBRyxJQUFJLENBQUM7SUFDeEIsQ0FBQyxDQUFDO0lBQ0YsTUFBTSxZQUFZLEdBQUcsR0FBUyxFQUFFO1FBQzlCLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxJQUFJLGNBQWM7WUFBRSxPQUFPO1FBQzVDLGNBQWMsR0FBRyxJQUFJLENBQUM7UUFDdEIsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztJQUNwQixDQUFDLENBQUM7SUFFRixHQUFHLENBQUMsS0FBSyxHQUFHLENBQUMsQ0FBQyxLQUFjLEVBQUUsR0FBRyxJQUFlLEVBQUUsRUFBRTtRQUNsRCxTQUFTLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3ZCLE9BQU8sZ0JBQWdCLENBQUMsS0FBSyxFQUFFLEdBQUcsSUFBSSxDQUFDLENBQUM7SUFDMUMsQ0FBQyxDQUFxQixDQUFDO0lBRXZCLEdBQUcsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxDQUFDLElBQWMsRUFBRSxFQUFFO1FBQzdCLFlBQVksQ0FBQyxxQkFBcUIsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1FBQzFDLE9BQU8sZUFBZSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQy9CLENBQUMsQ0FBb0IsQ0FBQztJQUV0QixHQUFHLENBQUMsR0FBRyxHQUFHLENBQUMsQ0FBQyxLQUFlLEVBQUUsR0FBRyxJQUFlLEVBQUUsRUFBRTtRQUNqRCxTQUFTLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3ZCLFlBQVksQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztRQUNqQyxJQUFJLENBQUM7WUFDSCxPQUFPLGNBQWMsQ0FBQyxLQUFLLEVBQUUsR0FBRyxJQUFJLENBQUMsQ0FBQztRQUN4QyxDQUFDO2dCQUFTLENBQUM7WUFDVCxZQUFZLEVBQUUsQ0FBQztRQUNqQixDQUFDO0lBQ0gsQ0FBQyxDQUFtQixDQUFDO0lBRXJCLEdBQUcsQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLEdBQUcsRUFBRTtRQUNyQixZQUFZLENBQUMsU0FBUyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUM7UUFDakMsWUFBWSxFQUFFLENBQUM7SUFDakIsQ0FBQyxDQUFDLENBQUM7SUFFSCxPQUFPLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxJQUFJLENBQUMsQ0FBQztBQUNuRSxDQUFDO0FBRUQ7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLHFCQUFxQixDQUFDLEdBQW9CO0lBQ3hELEdBQUcsQ0FBQyxHQUFHLENBQUMseUJBQXlCLENBQUMsQ0FBQztJQUNuQyxNQUFNLENBQUMsSUFBSSxDQUFDLG1DQUFtQyxDQUFDLENBQUM7QUFDbkQsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQ29weSBlYWNoIE1hc3RyYSBjaGF0IHR1cm4ncyByZXF1ZXN0L3Jlc3BvbnNlIG9udG8gaXRzIE9UZWwgcm9vdCBzcGFuXG4gKiBzbyBEYXRhYnJpY2tzLW1hbmFnZWQgTUxmbG93IGNhbiBkaXNwbGF5IHRoZW0uXG4gKlxuICogV2h5IHRoaXMgZXhpc3RzOiBNTGZsb3cncyBVQyBgPHRhcmdldD5fdHJhY2VfdW5pZmllZGAgdmlldyBwaWNrcyB0aGVcbiAqIG9uZSBzcGFuIHdob3NlIGBwYXJlbnRfc3Bhbl9pZGAgaXMgZW1wdHkgYW5kIHJlYWRzIHJlcXVlc3QvcmVzcG9uc2VcbiAqIGZyb20gYG1sZmxvdy5zcGFuSW5wdXRzYCAvIGBtbGZsb3cuc3Bhbk91dHB1dHNgIChvciB0aGUgYGdlbl9haS4qYFxuICogZXF1aXZhbGVudHMpIG9uIFRIQVQgc3BhbiBvbmx5LiBNYXN0cmEgcmVjb3JkcyB0aGUgdHVybiBvbiBpdHNcbiAqIGBpbnZva2VfYWdlbnRgIGNoaWxkIHVuZGVyIGBtYXN0cmEuYWdlbnRfcnVuLmlucHV0YCAvIGAub3V0cHV0YCwga2V5c1xuICogdGhlIHZpZXcgbmV2ZXIgcmVhZHMsIHNvIGNoYXQgdHJhY2VzIGFycml2ZSB3aXRoIGJvdGggY29sdW1ucyBudWxsXG4gKiBldmVuIHdoZW4gdGhlIHNwYW5zIHRoZW1zZWx2ZXMgbG9vayBoZWFsdGh5LlxuICpcbiAqIEFwcEtpdCdzIEhUVFAgaW5zdHJ1bWVudGF0aW9uIHJlY29yZHMgdGhlIHNlcnZlciBzcGFuIGluIE9UZWwgUlBDIG1ldGFkYXRhLFxuICogd2hpY2ggcmVtYWlucyBhdmFpbGFibGUgZXZlbiB3aGVuIEV4cHJlc3MgbWFrZXMgYSBtaWRkbGV3YXJlLWxheWVyIGNoaWxkXG4gKiBhY3RpdmUuIFdoZW4gaW5zdHJ1bWVudGF0aW9uIGlzIHVuYXZhaWxhYmxlLCB0aGlzIG1vZHVsZSBjcmVhdGVzIG9uZVxuICogcmVxdWVzdC1saWZldGltZSBzZXJ2ZXIgc3BhbiBhbmQgcnVucyBNYXN0cmEgdW5kZXIgaXQuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IFN0cmluZ0RlY29kZXIgfSBmcm9tIFwibm9kZTpzdHJpbmdfZGVjb2RlclwiO1xuXG5pbXBvcnQgeyBqc29uLCBsb2csIG9iamVjdCB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1jb3JlXCI7XG5pbXBvcnQgeyBjb250ZXh0LCBTcGFuS2luZCwgdHJhY2UsIHR5cGUgU3BhbiB9IGZyb20gXCJAb3BlbnRlbGVtZXRyeS9hcGlcIjtcbmltcG9ydCB7IGdldFJQQ01ldGFkYXRhLCBSUENUeXBlIH0gZnJvbSBcIkBvcGVudGVsZW1ldHJ5L2NvcmVcIjtcbmltcG9ydCB0eXBlIGV4cHJlc3MgZnJvbSBcImV4cHJlc3NcIjtcblxuY29uc3QgbG9nZ2VyID0gbG9nLmxvZ2dlcihcIm1hc3RyYS90cmFjZS1pb1wiKTtcblxuLyoqIENhcCBvbiBlYWNoIHBheWxvYWQgY29waWVkIG9udG8gYSBzcGFuLCBzbyBvbmUgdHVybiBjYW5ub3QgYmxvYXQgdGhlIGV4cG9ydC4gKi9cbmV4cG9ydCBjb25zdCBUUkFDRV9JT19MSU1JVCA9IDhfMDAwO1xuXG4vKiogQm91bmQgZm9yIGEgbm9uLXN0cmVhbWluZyBib2R5IGNhcHR1cmVkIHRocm91Z2ggYHJlcy5lbmRgIGluc3RlYWQgb2YgYHJlcy5qc29uYC4gKi9cbmNvbnN0IFJFU1BPTlNFX0JPRFlfTElNSVQgPSBUUkFDRV9JT19MSU1JVCAqIDQ7XG5cbi8qKiBUcmFjZXIgc2NvcGUgdXNlZCBvbmx5IHdoZW4gQXBwS2l0IGRpZCBub3QgY3JlYXRlIGEgcmVjb3JkaW5nIEhUVFAgc3Bhbi4gKi9cbmNvbnN0IENIQVRfVFVSTl9UUkFDRVIgPSBcIkBkYngtdG9vbHMvYXBwa2l0LW1hc3RyYS90cmFjZS1pb1wiO1xuXG4vKipcbiAqIE1vdW50LXJlbGF0aXZlIE1hc3RyYSBhZ2VudCBpbnZva2UgcGF0aHMgdGhhdCBjYXJyeSBhIGNoYXQgdHVybiBib2R5XG4gKiAoYG1lc3NhZ2VzYCkgYW5kIHN0cmVhbSBhbiBhc3Npc3RhbnQgYW5zd2VyLiBSZXN1bWUgLyBhcHByb3ZlIHZlcmJzXG4gKiBhcmUgaW50ZW50aW9uYWxseSBleGNsdWRlZDogdGhlaXIgYm9kaWVzIGFyZSB0b29sIGRlY2lzaW9ucywgbm90IHRoZVxuICogdXNlciBwcm9tcHQsIGFuZCB0aGV5IHJhcmVseSBwcm9kdWNlIGEgZnJlc2ggYW5zd2VyIHdvcnRoIHN1cmZhY2luZy5cbiAqL1xuY29uc3QgQUdFTlRfVFVSTl9ST1VURSA9IC9eKD86XFwvYWdlbnRzXFwvW14vXStcXC8oPzpzdHJlYW18Z2VuZXJhdGUpKD86XFwvfCQpfFxcL2NoYXRcXC9bXi9dKyQpL2k7XG5cbi8qKiBBdHRyaWJ1dGUga2V5cyB0aGUgTUxmbG93IFVDIGAqX3RyYWNlX3VuaWZpZWRgIHZpZXcgcmVhZHMgZnJvbSB0aGUgcm9vdCBzcGFuLiAqL1xuZXhwb3J0IGNvbnN0IE1MRkxPV19TUEFOX0lOUFVUU19BVFRSID0gXCJtbGZsb3cuc3BhbklucHV0c1wiO1xuLyoqIFJvb3Qtc3BhbiBhdHRyaWJ1dGUgY29uc3VtZWQgYXMgY2hhdCBvdXRwdXQgYnkgTUxmbG93IHVuaWZpZWQgdHJhY2Ugdmlld3MuICovXG5leHBvcnQgY29uc3QgTUxGTE9XX1NQQU5fT1VUUFVUU19BVFRSID0gXCJtbGZsb3cuc3Bhbk91dHB1dHNcIjtcblxuaW50ZXJmYWNlIFRyYWNlVGFyZ2V0IHtcbiAgc3BhbjogU3BhbjtcbiAgb3duZWQ6IGJvb2xlYW47XG59XG5cbi8qKiBSZXR1cm4gdGhlIGFzc2lzdGFudCB0ZXh0IGZyb20gb25lIFNTRSBsaW5lLCBvciBhbiBlbXB0eSBzdHJpbmcuICovXG5mdW5jdGlvbiBhc3Npc3RhbnRUZXh0RnJvbVNzZUxpbmUobGluZTogc3RyaW5nKTogc3RyaW5nIHtcbiAgaWYgKCFsaW5lLnN0YXJ0c1dpdGgoXCJkYXRhOlwiKSkgcmV0dXJuIFwiXCI7XG4gIGNvbnN0IGZyYW1lID0ganNvbi5wYXJzZShsaW5lLnNsaWNlKDUpKTtcbiAgaWYgKCFvYmplY3QuaXNSZWNvcmQoZnJhbWUpIHx8IGZyYW1lLnR5cGUgIT09IFwidGV4dC1kZWx0YVwiKSByZXR1cm4gXCJcIjtcbiAgY29uc3QgdGV4dCA9XG4gICAgdHlwZW9mIGZyYW1lLmRlbHRhID09PSBcInN0cmluZ1wiXG4gICAgICA/IGZyYW1lLmRlbHRhXG4gICAgICA6IG9iamVjdC5pc1JlY29yZChmcmFtZS5wYXlsb2FkKVxuICAgICAgICA/IGZyYW1lLnBheWxvYWQudGV4dFxuICAgICAgICA6IHVuZGVmaW5lZDtcbiAgcmV0dXJuIHR5cGVvZiB0ZXh0ID09PSBcInN0cmluZ1wiID8gdGV4dCA6IFwiXCI7XG59XG5cbi8qKlxuICogQ29uY2F0ZW5hdGUgdGhlIGFzc2lzdGFudCdzIGFuc3dlciBvdXQgb2YgYW4gQUkgU0RLIFNTRSB0cmFuc2NyaXB0LlxuICpcbiAqIFRoZSBhZ2VudCBzdHJlYW1zIGl0cyByZXBseSBhcyBgdGV4dC1kZWx0YWAgZnJhbWVzXG4gKiAoYGRhdGE6IHtcInR5cGVcIjpcInRleHQtZGVsdGFcIixcInBheWxvYWRcIjp7XCJ0ZXh0XCI6XCIuLi5cIn19YCksIHNvIHRoZSBmdWxsXG4gKiBhbnN3ZXIgb25seSBldmVyIGV4aXN0cyBhcyBkZWx0YXMgb24gdGhlIHdpcmUgYW5kIGhhcyB0byBiZVxuICogcmVhc3NlbWJsZWQgaGVyZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGFzc2lzdGFudFRleHRGcm9tU3NlKGJvZHk6IHN0cmluZyk6IHN0cmluZyB7XG4gIGNvbnN0IHBhcnRzOiBzdHJpbmdbXSA9IFtdO1xuICBmb3IgKGNvbnN0IGxpbmUgb2YgYm9keS5zcGxpdChcIlxcblwiKSkge1xuICAgIGNvbnN0IHRleHQgPSBhc3Npc3RhbnRUZXh0RnJvbVNzZUxpbmUobGluZSk7XG4gICAgaWYgKHRleHQpIHBhcnRzLnB1c2godGV4dCk7XG4gIH1cbiAgcmV0dXJuIHBhcnRzLmpvaW4oXCJcIikuc2xpY2UoMCwgVFJBQ0VfSU9fTElNSVQpO1xufVxuXG4vKiogUmVhZCB0aGUgZmluYWwgYXNzaXN0YW50IHRleHQgZnJvbSBhIG5vbi1zdHJlYW1pbmcgTWFzdHJhIHJlc3BvbnNlLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGFzc2lzdGFudFRleHRGcm9tSnNvbihib2R5OiB1bmtub3duKTogc3RyaW5nIHtcbiAgaWYgKCFvYmplY3QuaXNSZWNvcmQoYm9keSkgfHwgdHlwZW9mIGJvZHkudGV4dCAhPT0gXCJzdHJpbmdcIikgcmV0dXJuIFwiXCI7XG4gIHJldHVybiBib2R5LnRleHQuc2xpY2UoMCwgVFJBQ0VfSU9fTElNSVQpO1xufVxuXG4vKiogSW5jcmVtZW50YWxseSBhc3NlbWJsZSBVVEYtOCBTU0UgdGV4dCB3aGlsZSByZXRhaW5pbmcgYSBib3VuZGVkIEpTT04gZmFsbGJhY2suICovXG5jbGFzcyBBc3Npc3RhbnRSZXNwb25zZUNvbGxlY3RvciB7XG4gIHByaXZhdGUgcmVhZG9ubHkgZGVjb2RlciA9IG5ldyBTdHJpbmdEZWNvZGVyKFwidXRmOFwiKTtcbiAgcHJpdmF0ZSBhbnN3ZXIgPSBcIlwiO1xuICBwcml2YXRlIGJvZHkgPSBcIlwiO1xuICBwcml2YXRlIHBlbmRpbmdMaW5lID0gXCJcIjtcbiAgcHJpdmF0ZSBmaW5pc2hlZCA9IGZhbHNlO1xuXG4gIHdyaXRlKGNodW5rOiB1bmtub3duKTogdm9pZCB7XG4gICAgaWYgKHRoaXMuZmluaXNoZWQpIHJldHVybjtcbiAgICBpZiAodHlwZW9mIGNodW5rID09PSBcInN0cmluZ1wiKSB7XG4gICAgICB0aGlzLmNvbnN1bWUoY2h1bmspO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpZiAoQXJyYXlCdWZmZXIuaXNWaWV3KGNodW5rKSkge1xuICAgICAgdGhpcy5jb25zdW1lKFxuICAgICAgICB0aGlzLmRlY29kZXIud3JpdGUoQnVmZmVyLmZyb20oY2h1bmsuYnVmZmVyLCBjaHVuay5ieXRlT2Zmc2V0LCBjaHVuay5ieXRlTGVuZ3RoKSksXG4gICAgICApO1xuICAgIH1cbiAgfVxuXG4gIGZpbmlzaCgpOiBzdHJpbmcge1xuICAgIGlmICh0aGlzLmZpbmlzaGVkKSByZXR1cm4gdGhpcy5hbnN3ZXIgfHwgdGhpcy5qc29uQW5zd2VyKCk7XG4gICAgdGhpcy5maW5pc2hlZCA9IHRydWU7XG4gICAgdGhpcy5jb25zdW1lKHRoaXMuZGVjb2Rlci5lbmQoKSk7XG4gICAgdGhpcy5jb25zdW1lU3NlTGluZSh0aGlzLnBlbmRpbmdMaW5lKTtcbiAgICB0aGlzLnBlbmRpbmdMaW5lID0gXCJcIjtcbiAgICByZXR1cm4gdGhpcy5hbnN3ZXIgfHwgdGhpcy5qc29uQW5zd2VyKCk7XG4gIH1cblxuICBwcml2YXRlIGNvbnN1bWUodGV4dDogc3RyaW5nKTogdm9pZCB7XG4gICAgaWYgKCF0ZXh0KSByZXR1cm47XG4gICAgaWYgKHRoaXMuYm9keS5sZW5ndGggPCBSRVNQT05TRV9CT0RZX0xJTUlUKSB7XG4gICAgICB0aGlzLmJvZHkgKz0gdGV4dC5zbGljZSgwLCBSRVNQT05TRV9CT0RZX0xJTUlUIC0gdGhpcy5ib2R5Lmxlbmd0aCk7XG4gICAgfVxuICAgIHRoaXMucGVuZGluZ0xpbmUgKz0gdGV4dDtcbiAgICBsZXQgbmV3bGluZSA9IHRoaXMucGVuZGluZ0xpbmUuaW5kZXhPZihcIlxcblwiKTtcbiAgICB3aGlsZSAobmV3bGluZSA+PSAwKSB7XG4gICAgICB0aGlzLmNvbnN1bWVTc2VMaW5lKHRoaXMucGVuZGluZ0xpbmUuc2xpY2UoMCwgbmV3bGluZSkucmVwbGFjZSgvXFxyJC8sIFwiXCIpKTtcbiAgICAgIHRoaXMucGVuZGluZ0xpbmUgPSB0aGlzLnBlbmRpbmdMaW5lLnNsaWNlKG5ld2xpbmUgKyAxKTtcbiAgICAgIG5ld2xpbmUgPSB0aGlzLnBlbmRpbmdMaW5lLmluZGV4T2YoXCJcXG5cIik7XG4gICAgfVxuICAgIGlmICh0aGlzLnBlbmRpbmdMaW5lLmxlbmd0aCA+IFJFU1BPTlNFX0JPRFlfTElNSVQpIHtcbiAgICAgIHRoaXMucGVuZGluZ0xpbmUgPSB0aGlzLnBlbmRpbmdMaW5lLnNsaWNlKC1SRVNQT05TRV9CT0RZX0xJTUlUKTtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIGNvbnN1bWVTc2VMaW5lKGxpbmU6IHN0cmluZyk6IHZvaWQge1xuICAgIGlmICh0aGlzLmFuc3dlci5sZW5ndGggPj0gVFJBQ0VfSU9fTElNSVQpIHJldHVybjtcbiAgICBjb25zdCB0ZXh0ID0gYXNzaXN0YW50VGV4dEZyb21Tc2VMaW5lKGxpbmUpO1xuICAgIHRoaXMuYW5zd2VyICs9IHRleHQuc2xpY2UoMCwgVFJBQ0VfSU9fTElNSVQgLSB0aGlzLmFuc3dlci5sZW5ndGgpO1xuICB9XG5cbiAgcHJpdmF0ZSBqc29uQW5zd2VyKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIGFzc2lzdGFudFRleHRGcm9tSnNvbihqc29uLnBhcnNlKHRoaXMuYm9keSkpO1xuICB9XG59XG5cbi8qKlxuICogU2VsZWN0IHRoZSBzcGFuIE1MZmxvdyB0cmVhdHMgYXMgdGhlIHJlcXVlc3Qgcm9vdC5cbiAqXG4gKiBPVGVsIEhUVFAgaW5zdHJ1bWVudGF0aW9uIHN0b3JlcyBpdHMgc2VydmVyIHNwYW4gaW4gUlBDIG1ldGFkYXRhLiBUaGF0IHNwYW5cbiAqIHdpbnMgb3ZlciBhbiBhY3RpdmUgRXhwcmVzcyBjaGlsZC4gSWYgbm8gcmVjb3JkaW5nIGxvY2FsIHNwYW4gZXhpc3RzLCBjcmVhdGVcbiAqIG9uZSB1bmRlciB0aGUgY3VycmVudCBjb250ZXh0IHNvIGVuYWJsZWQgVzNDIHByb3BhZ2F0aW9uIHJlbWFpbnMgaW50YWN0LlxuICovXG5mdW5jdGlvbiByZXNvbHZlVHJhY2VUYXJnZXQoKTogVHJhY2VUYXJnZXQgfCB1bmRlZmluZWQge1xuICBjb25zdCBhY3RpdmVDb250ZXh0ID0gY29udGV4dC5hY3RpdmUoKTtcbiAgY29uc3QgcnBjID0gZ2V0UlBDTWV0YWRhdGEoYWN0aXZlQ29udGV4dCk7XG4gIGlmIChycGM/LnR5cGUgPT09IFJQQ1R5cGUuSFRUUCAmJiBycGMuc3Bhbi5pc1JlY29yZGluZygpKSB7XG4gICAgcmV0dXJuIHsgc3BhbjogcnBjLnNwYW4sIG93bmVkOiBmYWxzZSB9O1xuICB9XG5cbiAgY29uc3QgYWN0aXZlID0gdHJhY2UuZ2V0QWN0aXZlU3BhbigpO1xuICBpZiAoYWN0aXZlPy5pc1JlY29yZGluZygpKSByZXR1cm4geyBzcGFuOiBhY3RpdmUsIG93bmVkOiBmYWxzZSB9O1xuXG4gIGNvbnN0IHNwYW4gPSB0cmFjZVxuICAgIC5nZXRUcmFjZXIoQ0hBVF9UVVJOX1RSQUNFUilcbiAgICAuc3RhcnRTcGFuKFwibWFzdHJhLmNoYXRfdHVyblwiLCB7IGtpbmQ6IFNwYW5LaW5kLlNFUlZFUiB9LCBhY3RpdmVDb250ZXh0KTtcbiAgaWYgKCFzcGFuLmlzUmVjb3JkaW5nKCkpIHtcbiAgICBzcGFuLmVuZCgpO1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cbiAgcmV0dXJuIHsgc3Bhbiwgb3duZWQ6IHRydWUgfTtcbn1cblxuLyoqXG4gKiBFeHByZXNzIG1pZGRsZXdhcmUgdGhhdCBzdGFtcHMgY2hhdCB0dXJuIEkvTyBvbnRvIHRoZSBleHBvcnRlZCBPVGVsIHJvb3QuXG4gKlxuICogVGVlIGByZXMud3JpdGVgLCBgcmVzLmpzb25gLCBhbmQgYHJlcy5lbmRgIGluc3RlYWQgb2YgbGlzdGVuaW5nIGZvciBgZmluaXNoYDpcbiAqIEhUVFAgaW5zdHJ1bWVudGF0aW9uIGVuZHMgaXRzIHNlcnZlciBzcGFuIG9uIGZpbmlzaCwgYW5kIGF0dHJpYnV0ZXMgc2V0XG4gKiBhZnRlciBhIHNwYW4gZW5kcyBuZXZlciByZWFjaCB0aGUgZXhwb3J0ZXIuXG4gKlxuICogYHBhdGhgIGlzIG1vdW50LXJlbGF0aXZlICh3aGF0IHRoZSBNYXN0cmEgc3ViLWFwcCBzZWVzKSwgZS5nLlxuICogYC9hZ2VudHMvc3VwcG9ydC9zdHJlYW1gLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY2hhdFR1cm5UcmFjZUlvTWlkZGxld2FyZShcbiAgcmVxOiBleHByZXNzLlJlcXVlc3QsXG4gIHJlczogZXhwcmVzcy5SZXNwb25zZSxcbiAgbmV4dDogZXhwcmVzcy5OZXh0RnVuY3Rpb24sXG4pOiB2b2lkIHtcbiAgaWYgKHJlcS5tZXRob2QgIT09IFwiUE9TVFwiIHx8ICFBR0VOVF9UVVJOX1JPVVRFLnRlc3QocmVxLnBhdGgpKSB7XG4gICAgbmV4dCgpO1xuICAgIHJldHVybjtcbiAgfVxuICBjb25zdCB0YXJnZXQgPSByZXNvbHZlVHJhY2VUYXJnZXQoKTtcbiAgaWYgKCF0YXJnZXQpIHtcbiAgICBuZXh0KCk7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgY29uc3QgbWVzc2FnZXMgPSAocmVxLmJvZHkgYXMgeyBtZXNzYWdlcz86IHVua25vd24gfSB8IHVuZGVmaW5lZCk/Lm1lc3NhZ2VzO1xuICBpZiAobWVzc2FnZXMgIT09IHVuZGVmaW5lZCkge1xuICAgIHRhcmdldC5zcGFuLnNldEF0dHJpYnV0ZShcbiAgICAgIE1MRkxPV19TUEFOX0lOUFVUU19BVFRSLFxuICAgICAgSlNPTi5zdHJpbmdpZnkobWVzc2FnZXMpLnNsaWNlKDAsIFRSQUNFX0lPX0xJTUlUKSxcbiAgICApO1xuICB9XG5cbiAgY29uc3QgY29sbGVjdG9yID0gbmV3IEFzc2lzdGFudFJlc3BvbnNlQ29sbGVjdG9yKCk7XG4gIGxldCBvdXRwdXRSZWNvcmRlZCA9IGZhbHNlO1xuICBsZXQgb3duZWRTcGFuRW5kZWQgPSBmYWxzZTtcbiAgY29uc3QgcGFzc1Rocm91Z2hXcml0ZSA9IHJlcy53cml0ZS5iaW5kKHJlcykgYXMgKC4uLmFyZ3M6IHVua25vd25bXSkgPT4gYm9vbGVhbjtcbiAgY29uc3QgcGFzc1Rocm91Z2hFbmQgPSByZXMuZW5kLmJpbmQocmVzKSBhcyAoLi4uYXJnczogdW5rbm93bltdKSA9PiB1bmtub3duO1xuICBjb25zdCBwYXNzVGhyb3VnaEpzb24gPSByZXMuanNvbi5iaW5kKHJlcykgYXMgKGJvZHk/OiB1bmtub3duKSA9PiBleHByZXNzLlJlc3BvbnNlO1xuICBjb25zdCByZWNvcmRPdXRwdXQgPSAoYW5zd2VyOiBzdHJpbmcpOiB2b2lkID0+IHtcbiAgICBpZiAob3V0cHV0UmVjb3JkZWQgfHwgIWFuc3dlcikgcmV0dXJuO1xuICAgIHRhcmdldC5zcGFuLnNldEF0dHJpYnV0ZShNTEZMT1dfU1BBTl9PVVRQVVRTX0FUVFIsIGFuc3dlci5zbGljZSgwLCBUUkFDRV9JT19MSU1JVCkpO1xuICAgIG91dHB1dFJlY29yZGVkID0gdHJ1ZTtcbiAgfTtcbiAgY29uc3QgZW5kT3duZWRTcGFuID0gKCk6IHZvaWQgPT4ge1xuICAgIGlmICghdGFyZ2V0Lm93bmVkIHx8IG93bmVkU3BhbkVuZGVkKSByZXR1cm47XG4gICAgb3duZWRTcGFuRW5kZWQgPSB0cnVlO1xuICAgIHRhcmdldC5zcGFuLmVuZCgpO1xuICB9O1xuXG4gIHJlcy53cml0ZSA9ICgoY2h1bms6IHVua25vd24sIC4uLnJlc3Q6IHVua25vd25bXSkgPT4ge1xuICAgIGNvbGxlY3Rvci53cml0ZShjaHVuayk7XG4gICAgcmV0dXJuIHBhc3NUaHJvdWdoV3JpdGUoY2h1bmssIC4uLnJlc3QpO1xuICB9KSBhcyB0eXBlb2YgcmVzLndyaXRlO1xuXG4gIHJlcy5qc29uID0gKChib2R5PzogdW5rbm93bikgPT4ge1xuICAgIHJlY29yZE91dHB1dChhc3Npc3RhbnRUZXh0RnJvbUpzb24oYm9keSkpO1xuICAgIHJldHVybiBwYXNzVGhyb3VnaEpzb24oYm9keSk7XG4gIH0pIGFzIHR5cGVvZiByZXMuanNvbjtcblxuICByZXMuZW5kID0gKChjaHVuaz86IHVua25vd24sIC4uLnJlc3Q6IHVua25vd25bXSkgPT4ge1xuICAgIGNvbGxlY3Rvci53cml0ZShjaHVuayk7XG4gICAgcmVjb3JkT3V0cHV0KGNvbGxlY3Rvci5maW5pc2goKSk7XG4gICAgdHJ5IHtcbiAgICAgIHJldHVybiBwYXNzVGhyb3VnaEVuZChjaHVuaywgLi4ucmVzdCk7XG4gICAgfSBmaW5hbGx5IHtcbiAgICAgIGVuZE93bmVkU3BhbigpO1xuICAgIH1cbiAgfSkgYXMgdHlwZW9mIHJlcy5lbmQ7XG5cbiAgcmVzLm9uY2UoXCJjbG9zZVwiLCAoKSA9PiB7XG4gICAgcmVjb3JkT3V0cHV0KGNvbGxlY3Rvci5maW5pc2goKSk7XG4gICAgZW5kT3duZWRTcGFuKCk7XG4gIH0pO1xuXG4gIGNvbnRleHQud2l0aCh0cmFjZS5zZXRTcGFuKGNvbnRleHQuYWN0aXZlKCksIHRhcmdldC5zcGFuKSwgbmV4dCk7XG59XG5cbi8qKlxuICogSW5zdGFsbCB7QGxpbmsgY2hhdFR1cm5UcmFjZUlvTWlkZGxld2FyZX0gb24gYSBNYXN0cmEgRXhwcmVzcyBzdWItYXBwLlxuICpcbiAqIENhbGwgYmVmb3JlIGBNYXN0cmFTZXJ2ZXIuaW5pdCgpYCBzbyB0aGUgbGF5ZXIgc2l0cyBhaGVhZCBvZiB0aGUgYWdlbnRcbiAqIHJvdXRlcy4gU2FmZSB0byBjYWxsIHVuY29uZGl0aW9uYWxseTogd2hlbiBubyByZWNvcmRpbmcgdHJhY2VyIGlzIHJlZ2lzdGVyZWQsXG4gKiB0aGUgbWlkZGxld2FyZSBpcyBhIG5vLW9wLlxuICovXG5leHBvcnQgZnVuY3Rpb24gYXR0YWNoQ2hhdFR1cm5UcmFjZUlvKGFwcDogZXhwcmVzcy5FeHByZXNzKTogdm9pZCB7XG4gIGFwcC51c2UoY2hhdFR1cm5UcmFjZUlvTWlkZGxld2FyZSk7XG4gIGxvZ2dlci5pbmZvKFwiY2hhdCB0dXJuIEkvTyBtaWRkbGV3YXJlIGF0dGFjaGVkXCIpO1xufVxuIl19
|