@dbx-tools/appkit-mastra 0.9.129 → 0.9.132

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 (51) hide show
  1. package/README.md +182 -117
  2. package/index.ts +10 -8
  3. package/lib/index.d.ts +10 -8
  4. package/lib/index.js +8 -7
  5. package/lib/src/agents.d.ts +4 -0
  6. package/lib/src/agents.js +12 -3
  7. package/lib/src/config.d.ts +7 -2
  8. package/lib/src/config.js +6 -3
  9. package/lib/src/defaults.d.ts +9 -0
  10. package/lib/src/defaults.js +29 -1
  11. package/lib/src/genie-chat.d.ts +71 -0
  12. package/lib/src/genie-chat.js +272 -0
  13. package/lib/src/genie.d.ts +2 -0
  14. package/lib/src/genie.js +11 -8
  15. package/lib/src/memory.d.ts +5 -5
  16. package/lib/src/memory.js +10 -3
  17. package/lib/src/observability.d.ts +3 -0
  18. package/lib/src/observability.js +2 -2
  19. package/lib/src/plugin.d.ts +36 -7
  20. package/lib/src/plugin.js +220 -33
  21. package/lib/src/request-context.d.ts +2 -0
  22. package/lib/src/request-context.js +3 -0
  23. package/lib/src/server.d.ts +4 -0
  24. package/lib/src/server.js +40 -2
  25. package/lib/src/statement.d.ts +16 -1
  26. package/lib/src/statement.js +50 -18
  27. package/lib/src/telemetry.d.ts +22 -1
  28. package/lib/src/telemetry.js +174 -18
  29. package/lib/src/workspaces.d.ts +2 -2
  30. package/lib/src/workspaces.js +3 -2
  31. package/lib/tsconfig.tsbuildinfo +1 -1
  32. package/package.json +32 -25
  33. package/src/agents.ts +15 -1
  34. package/src/config.ts +10 -4
  35. package/src/defaults.ts +30 -0
  36. package/src/genie-chat.ts +341 -0
  37. package/src/genie.ts +11 -7
  38. package/src/memory.ts +9 -4
  39. package/src/observability.ts +6 -2
  40. package/src/plugin.ts +262 -31
  41. package/src/request-context.ts +2 -0
  42. package/src/server.ts +35 -0
  43. package/src/statement.ts +71 -19
  44. package/src/telemetry.ts +206 -16
  45. package/src/workspaces.ts +4 -3
  46. package/exports.ts +0 -7
  47. package/lib/exports.d.ts +0 -1
  48. package/lib/exports.js +0 -2
  49. package/lib/src/openapi-tool.d.ts +0 -29
  50. package/lib/src/openapi-tool.js +0 -175
  51. package/src/openapi-tool.ts +0 -245
package/src/telemetry.ts CHANGED
@@ -20,16 +20,30 @@
20
20
 
21
21
  import { StringDecoder } from "node:string_decoder";
22
22
 
23
+ import { telemetry as appkitTelemetry } from "@dbx-tools/appkit";
23
24
  import { getRequestTags } from "@dbx-tools/appkit/request-tags";
24
25
  import { environmentUtils, json, log, object, stringUtils } from "@dbx-tools/shared-core";
25
26
  import { feedback, thread } from "@dbx-tools/shared-mastra";
26
- import { context, createContextKey, SpanKind, trace, type Span } from "@opentelemetry/api";
27
+ import type { TracingOptions } from "@mastra/core/observability";
28
+ import { MASTRA_RESOURCE_ID_KEY, MASTRA_THREAD_ID_KEY } from "@mastra/core/request-context";
29
+ import {
30
+ context,
31
+ createContextKey,
32
+ SpanKind,
33
+ SpanStatusCode,
34
+ trace,
35
+ type Context,
36
+ type Span,
37
+ } from "@opentelemetry/api";
27
38
  import { getRPCMetadata, RPCType } from "@opentelemetry/core";
39
+ import type { ReadableSpan, Span as SdkSpan, SpanProcessor } from "@opentelemetry/sdk-trace-base";
28
40
  import type express from "express";
29
41
 
42
+ import { MASTRA_AUTH_MODE_KEY, MASTRA_USER_EMAIL_KEY, MASTRA_USER_NAME_KEY } from "./config.ts";
30
43
  import { USER_EMAIL_HEADER, USER_ID_HEADER } from "./identity.ts";
31
44
  import {
32
45
  directMlflowTracingActive,
46
+ initializeAppMlflowTraceInfo,
33
47
  persistAppMlflowTraceInfo,
34
48
  updateActiveMlflowTrace,
35
49
  updateActiveMlflowTraceTags,
@@ -78,7 +92,7 @@ const CHAT_TURN_TRACER = "@dbx-tools/appkit-mastra/telemetry";
78
92
  * are intentionally excluded: their bodies are tool decisions, not the
79
93
  * user prompt, and they rarely produce a fresh answer worth surfacing.
80
94
  */
81
- const AGENT_TURN_ROUTE = /^(?:\/agents\/[^/]+\/(?:stream|generate)(?:\/|$)|\/chat\/[^/]+$)/i;
95
+ const AGENT_TURN_ROUTE = /(?:^|\/)(?:agents\/[^/]+\/(?:stream|generate)|chat\/[^/]+)\/?$/i;
82
96
 
83
97
  /** Attribute keys the MLflow UC `*_trace_unified` view reads from the root span. */
84
98
  export const MLFLOW_SPAN_INPUTS_ATTR = "mlflow.spanInputs";
@@ -129,6 +143,8 @@ interface TraceTarget {
129
143
 
130
144
  interface ChatTraceState {
131
145
  readonly tags: Map<string, string>;
146
+ readonly traceId: string;
147
+ readonly parentSpanId?: string;
132
148
  firstModel?: string;
133
149
  user?: string;
134
150
  }
@@ -185,6 +201,26 @@ function activeChatTraceState(): ChatTraceState | undefined {
185
201
  return context.active().getValue(CHAT_TRACE_STATE_KEY) as ChatTraceState | undefined;
186
202
  }
187
203
 
204
+ /**
205
+ * Bind a Mastra agent run to the trace selected by chat telemetry.
206
+ *
207
+ * Databricks Apps uses a dedicated agent trace because evented agents finish
208
+ * before the HTTP server span. Promoting MLflow trace info after the agent root
209
+ * closes avoids the sidecar replacing clean previews and tags with empty HTTP
210
+ * trace metadata. Other runtimes retain the existing HTTP/local parentage.
211
+ */
212
+ export function activeChatTracingOptions(
213
+ options: TracingOptions | undefined,
214
+ ): TracingOptions | undefined {
215
+ const state = activeChatTraceState();
216
+ if (!state) return options;
217
+ return {
218
+ ...options,
219
+ traceId: state.traceId,
220
+ ...(state.parentSpanId ? { parentSpanId: state.parentSpanId } : { parentSpanId: undefined }),
221
+ };
222
+ }
223
+
188
224
  function setChatTraceTag(state: ChatTraceState, name: string, value: ChatTraceTagValue): void {
189
225
  const tag = stringUtils.trimToNull(name);
190
226
  if (!tag) return;
@@ -235,9 +271,8 @@ export function recordActiveTraceAuth(auth: "obo" | "service-principal"): void {
235
271
  }
236
272
 
237
273
  /** Publish the turn's MLflow trace id before the handler writes the body. */
238
- function stampMlflowTraceId(res: express.Response, span: Span): void {
274
+ function stampMlflowTraceId(res: express.Response, traceId: string): void {
239
275
  if (res.headersSent) return;
240
- const traceId = span.spanContext().traceId;
241
276
  if (!traceId || traceId === INVALID_TRACE_ID) return;
242
277
  res.setHeader(feedback.MLFLOW_TRACE_ID_HEADER, `tr-${traceId}`);
243
278
  }
@@ -329,6 +364,155 @@ export function assistantTextFromJson(body: unknown): string {
329
364
  return body.text.slice(0, TRACE_IO_LIMIT);
330
365
  }
331
366
 
367
+ const MASTRA_AGENT_INPUT_ATTR = "mastra.agent_run.input";
368
+ const MASTRA_AGENT_OUTPUT_ATTR = "mastra.agent_run.output";
369
+ const GEN_AI_REQUEST_MODEL_ATTR = "gen_ai.request.model";
370
+ const GEN_AI_AGENT_ID_ATTR = "gen_ai.agent.id";
371
+ const GEN_AI_AGENT_NAME_ATTR = "gen_ai.agent.name";
372
+ const MASTRA_MODEL_METADATA_ATTR = "mastra.metadata.modelMetadata";
373
+ const MASTRA_METADATA_PREFIX = "mastra.metadata.";
374
+
375
+ interface AppMlflowTraceState {
376
+ readonly tags: Map<string, string>;
377
+ genie: boolean;
378
+ model?: string;
379
+ }
380
+
381
+ function attributeString(span: ReadableSpan, name: string): string | undefined {
382
+ const value = span.attributes[name];
383
+ return typeof value === "string" ? (stringUtils.trimToNull(value) ?? undefined) : undefined;
384
+ }
385
+
386
+ function metadataAttribute(key: string): string {
387
+ return `${MASTRA_METADATA_PREFIX}${key}`;
388
+ }
389
+
390
+ function modelFromSpan(span: ReadableSpan): string | undefined {
391
+ const direct = attributeString(span, GEN_AI_REQUEST_MODEL_ATTR);
392
+ if (direct) return direct;
393
+ const metadata = json.parse(attributeString(span, MASTRA_MODEL_METADATA_ATTR));
394
+ return object.isRecord(metadata) && typeof metadata.modelId === "string"
395
+ ? (stringUtils.trimToNull(metadata.modelId) ?? undefined)
396
+ : undefined;
397
+ }
398
+
399
+ function spanUsesGenie(span: ReadableSpan): boolean {
400
+ if (span.attributes[MLFLOW_GENIE_TAG_ATTR] === "true") return true;
401
+ if (/\bask_genie(?:_|\b)/i.test(span.name)) return true;
402
+ return ["gen_ai.tool.name", "mastra.tool_call.name"].some((name) =>
403
+ /^ask_genie(?:_|$)/i.test(attributeString(span, name) ?? ""),
404
+ );
405
+ }
406
+
407
+ function collectTraceTags(span: ReadableSpan, state: AppMlflowTraceState): void {
408
+ for (const [name, value] of Object.entries(span.attributes)) {
409
+ if (!name.startsWith(MLFLOW_TRACE_TAG_PREFIX)) continue;
410
+ const tag = name.slice(MLFLOW_TRACE_TAG_PREFIX.length);
411
+ if (
412
+ tag &&
413
+ (typeof value === "string" || typeof value === "number" || typeof value === "boolean")
414
+ ) {
415
+ state.tags.set(tag, String(value));
416
+ }
417
+ }
418
+ }
419
+
420
+ function hrTimeMilliseconds(value: readonly [number, number]): number {
421
+ return value[0] * 1_000 + value[1] / 1_000_000;
422
+ }
423
+
424
+ function previewInput(span: ReadableSpan): string | undefined {
425
+ const raw = attributeString(span, MASTRA_AGENT_INPUT_ATTR);
426
+ if (!raw) return undefined;
427
+ return (textOnlyChatInput(json.parse(raw)) ?? raw).slice(0, TRACE_IO_LIMIT);
428
+ }
429
+
430
+ function previewOutput(span: ReadableSpan): string | undefined {
431
+ const raw = attributeString(span, MASTRA_AGENT_OUTPUT_ATTR);
432
+ if (!raw) return undefined;
433
+ const parsed = json.parse(raw);
434
+ const text = assistantTextFromJson(parsed) || (typeof parsed === "string" ? parsed : raw);
435
+ return text.slice(0, TRACE_IO_LIMIT);
436
+ }
437
+
438
+ /** Promote the actual Mastra `invoke_agent` trace exported by AppKit's provider. */
439
+ export class AppMlflowTraceInfoSpanProcessor implements SpanProcessor {
440
+ private readonly traces = new Map<string, AppMlflowTraceState>();
441
+
442
+ onStart(span: SdkSpan, _parentContext: Context): void {
443
+ const traceId = span.spanContext().traceId;
444
+ if (!this.traces.has(traceId)) {
445
+ this.traces.set(traceId, { tags: new Map(), genie: false });
446
+ }
447
+ }
448
+
449
+ onEnd(span: ReadableSpan): void {
450
+ const traceId = span.spanContext().traceId;
451
+ const state: AppMlflowTraceState = this.traces.get(traceId) ?? {
452
+ tags: new Map(),
453
+ genie: false,
454
+ };
455
+ this.traces.set(traceId, state);
456
+ collectTraceTags(span, state);
457
+ state.model ??= modelFromSpan(span);
458
+ state.genie ||= spanUsesGenie(span);
459
+
460
+ if (attributeString(span, GEN_AI_OPERATION_NAME_ATTR) !== "invoke_agent") {
461
+ if (!span.parentSpanContext?.spanId) this.traces.delete(traceId);
462
+ return;
463
+ }
464
+
465
+ const agent =
466
+ state.model ??
467
+ attributeString(span, GEN_AI_AGENT_ID_ATTR) ??
468
+ attributeString(span, GEN_AI_AGENT_NAME_ATTR);
469
+ if (agent) state.tags.set(MLFLOW_AGENT_TAG, agent);
470
+ if (state.genie) state.tags.set(MLFLOW_GENIE_TAG, "true");
471
+ const auth = attributeString(span, metadataAttribute(MASTRA_AUTH_MODE_KEY));
472
+ if (auth === "obo") state.tags.set(MLFLOW_OBO_AUTH_TAG, "true");
473
+ else if (auth === "service-principal") state.tags.set(MLFLOW_SP_AUTH_TAG, "true");
474
+
475
+ const user =
476
+ attributeString(span, metadataAttribute(MASTRA_USER_EMAIL_KEY)) ??
477
+ attributeString(span, metadataAttribute(MASTRA_USER_NAME_KEY)) ??
478
+ attributeString(span, metadataAttribute(MASTRA_RESOURCE_ID_KEY));
479
+ const sessionId =
480
+ attributeString(span, metadataAttribute(MASTRA_THREAD_ID_KEY)) ??
481
+ attributeString(span, "gen_ai.conversation.id");
482
+ const requestPreview = previewInput(span);
483
+ const responsePreview = previewOutput(span);
484
+ persistAppMlflowTraceInfo({
485
+ traceId,
486
+ requestTime: hrTimeMilliseconds(span.startTime),
487
+ executionDuration: hrTimeMilliseconds(span.duration),
488
+ error: span.status.code === SpanStatusCode.ERROR,
489
+ tags: Object.fromEntries(state.tags),
490
+ ...(user ? { user } : {}),
491
+ ...(sessionId ? { sessionId } : {}),
492
+ ...(requestPreview ? { requestPreview } : {}),
493
+ ...(responsePreview ? { responsePreview } : {}),
494
+ });
495
+ this.traces.delete(traceId);
496
+ }
497
+
498
+ async forceFlush(): Promise<void> {}
499
+
500
+ async shutdown(): Promise<void> {
501
+ this.traces.clear();
502
+ }
503
+ }
504
+
505
+ let appMlflowTraceInfoProcessorRegistered = false;
506
+
507
+ /** Register trace enrichment before AppKit constructs its native provider. */
508
+ export async function registerAppMlflowTraceInfoProcessor(): Promise<boolean> {
509
+ if (appMlflowTraceInfoProcessorRegistered) return true;
510
+ if (!(await initializeAppMlflowTraceInfo())) return false;
511
+ await appkitTelemetry.registerNativeSpanProcessor(new AppMlflowTraceInfoSpanProcessor());
512
+ appMlflowTraceInfoProcessorRegistered = true;
513
+ return true;
514
+ }
515
+
332
516
  /** Incrementally assemble UTF-8 SSE text while retaining a bounded JSON fallback. */
333
517
  class AssistantResponseCollector {
334
518
  private readonly decoder = new StringDecoder("utf8");
@@ -485,7 +669,11 @@ export function chatTurnTelemetryMiddleware(
485
669
  next();
486
670
  return;
487
671
  }
488
- const traceState: ChatTraceState = { tags: new Map() };
672
+ const traceState: ChatTraceState = {
673
+ tags: new Map(),
674
+ traceId: targetTraceId,
675
+ parentSpanId: target.span.spanContext().spanId,
676
+ };
489
677
  const traceRequestTime = Date.now();
490
678
  if (!environmentUtils.isDatabricksAppEnv()) {
491
679
  setChatTraceTag(traceState, MLFLOW_LOCAL_TAG, true);
@@ -499,7 +687,7 @@ export function chatTurnTelemetryMiddleware(
499
687
  if (value !== undefined) setChatTraceTag(traceState, name, value);
500
688
  }
501
689
  }
502
- stampMlflowTraceId(res, target.span);
690
+ stampMlflowTraceId(res, traceState.traceId);
503
691
  traceState.user = stampMlflowActor(target.span, req);
504
692
  target.span.setAttribute(MLFLOW_SPAN_TYPE_ATTR, MLFLOW_SPAN_TYPE_AGENT);
505
693
  target.span.setAttribute(GEN_AI_OPERATION_NAME_ATTR, "invoke_agent");
@@ -569,16 +757,18 @@ export function chatTurnTelemetryMiddleware(
569
757
  ...(traceState.user ? { user: traceState.user } : {}),
570
758
  });
571
759
  });
572
- persistAppMlflowTraceInfo({
573
- traceId: targetTraceId,
574
- requestTime: traceRequestTime,
575
- executionDuration: Date.now() - traceRequestTime,
576
- error: res.statusCode >= 500,
577
- tags,
578
- ...(traceState.user ? { user: traceState.user } : {}),
579
- ...(requestPreview ? { requestPreview } : {}),
580
- ...(responsePreview ? { responsePreview } : {}),
581
- });
760
+ if (!environmentUtils.isDatabricksAppEnv()) {
761
+ persistAppMlflowTraceInfo({
762
+ traceId: traceState.traceId,
763
+ requestTime: traceRequestTime,
764
+ executionDuration: Date.now() - traceRequestTime,
765
+ error: res.statusCode >= 500,
766
+ tags,
767
+ ...(traceState.user ? { user: traceState.user } : {}),
768
+ ...(requestPreview ? { requestPreview } : {}),
769
+ ...(responsePreview ? { responsePreview } : {}),
770
+ });
771
+ }
582
772
  };
583
773
  const endOwnedSpan = (): void => {
584
774
  if (!target.owned || ownedSpanEnded) return;
package/src/workspaces.ts CHANGED
@@ -209,8 +209,8 @@ export interface DatabricksWorkspaceOptions extends Omit<
209
209
  /** Add user-scoped `/tmp` only when no configured Databricks path mounts writable. */
210
210
  fallbackToTmp?: boolean;
211
211
  /**
212
- * Extra local skill trees mounted read-only for file access. The plugin also
213
- * converts them to native agent-level skills without workspace scanning.
212
+ * Extra local skill trees mounted read-only for file access and workspace
213
+ * skill search. The plugin also converts them to native agent-level skills.
214
214
  */
215
215
  extraSkillMounts?: LocalSkillMount[];
216
216
  /** Skill names provided directly to agents and excluded from workspace search. */
@@ -292,7 +292,7 @@ export function databricksWorkspaceConfig(
292
292
  retainFilesystemSource ? mountFilesCache : undefined,
293
293
  filesystemSourceKey,
294
294
  );
295
- const hasSkillPaths = paths.some(pathMayProvideSkills);
295
+ const hasSkillPaths = extraSkillMounts.length > 0 || paths.some(pathMayProvideSkills);
296
296
  const excludedSkillNames = new Set(options.excludeSkillNames ?? []);
297
297
  const skills: SkillsResolver =
298
298
  options.skills ??
@@ -820,6 +820,7 @@ function localSkillMountResolvers(mounts: readonly LocalSkillMount[]): Workspace
820
820
  const source = await filesystem;
821
821
  return {
822
822
  mounts: { [mount]: filesystems(source, { readOnly: true }) },
823
+ skillPaths: [mount],
823
824
  };
824
825
  };
825
826
  });
package/exports.ts DELETED
@@ -1,7 +0,0 @@
1
- export {
2
- recordActiveTraceAuth,
3
- recordActiveTraceModel,
4
- recordActiveTraceTag,
5
- recordActiveTraceTags,
6
- recordActiveTraceUser,
7
- } from "./src/telemetry.ts";
package/lib/exports.d.ts DELETED
@@ -1 +0,0 @@
1
- export { recordActiveTraceAuth, recordActiveTraceModel, recordActiveTraceTag, recordActiveTraceTags, recordActiveTraceUser, } from "./src/telemetry.ts";
package/lib/exports.js DELETED
@@ -1,2 +0,0 @@
1
- export { recordActiveTraceAuth, recordActiveTraceModel, recordActiveTraceTag, recordActiveTraceTags, recordActiveTraceUser, } from "./src/telemetry.js";
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhwb3J0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL2V4cG9ydHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUNMLHFCQUFxQixFQUNyQixzQkFBc0IsRUFDdEIsb0JBQW9CLEVBQ3BCLHFCQUFxQixFQUNyQixxQkFBcUIsR0FDdEIsTUFBTSxvQkFBb0IsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCB7XG4gIHJlY29yZEFjdGl2ZVRyYWNlQXV0aCxcbiAgcmVjb3JkQWN0aXZlVHJhY2VNb2RlbCxcbiAgcmVjb3JkQWN0aXZlVHJhY2VUYWcsXG4gIHJlY29yZEFjdGl2ZVRyYWNlVGFncyxcbiAgcmVjb3JkQWN0aXZlVHJhY2VVc2VyLFxufSBmcm9tIFwiLi9zcmMvdGVsZW1ldHJ5LnRzXCI7XG4iXX0=
@@ -1,29 +0,0 @@
1
- import type { JSONSchema7 } from "json-schema";
2
- declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD", "TRACE"];
3
- /** HTTP method supported by an {@link OpenApiTool}. */
4
- export type OpenApiHttpMethod = (typeof HTTP_METHODS)[number];
5
- /** One executable HTTP operation derived from an OpenAPI document. */
6
- export interface OpenApiTool {
7
- /** Stable operation identifier. */
8
- id: string;
9
- /** Model-facing operation description. */
10
- description: string;
11
- /** Resolved operation URL, or the OpenAPI path for a local document without a server URL. */
12
- url: string;
13
- /** HTTP method used by the operation. */
14
- method: OpenApiHttpMethod;
15
- /** JSON Schema describing operation input. */
16
- inputSchema: JSONSchema7;
17
- /** JSON Schema describing a successful operation result. */
18
- outputSchema: JSONSchema7;
19
- }
20
- /** Request options used while loading an OpenAPI document. */
21
- export interface OpenApiToolsOptions {
22
- /** Cancels an HTTP request or file read. */
23
- signal?: AbortSignal;
24
- /** Headers sent when `source` is an HTTP(S) URL. */
25
- headers?: RequestInit["headers"];
26
- }
27
- /** Load an HTTP(S) URL or local file and return its OpenAPI operations as tool schemas. */
28
- export declare function openApiTools(source: string, options?: OpenApiToolsOptions): Promise<OpenApiTool[]>;
29
- export {};
@@ -1,175 +0,0 @@
1
- /**
2
- * OpenAPI operation loading and JSON Schema extraction.
3
- *
4
- * @module
5
- */
6
- import { readFile } from "node:fs/promises";
7
- import { object } from "@dbx-tools/shared-core";
8
- import { urlBuilder } from "@dbx-tools/shared-core/net";
9
- import { z } from "zod";
10
- const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD", "TRACE"];
11
- const JsonObjectSchema = z
12
- .record(z.string(), z.unknown())
13
- .describe("A JSON object embedded in an OpenAPI document.");
14
- const OpenApiMediaTypeSchema = z
15
- .object({
16
- schema: JsonObjectSchema.describe("The JSON Schema for this media type."),
17
- })
18
- .describe("One OpenAPI media type.");
19
- const OpenApiRequestBodySchema = z
20
- .object({
21
- content: z
22
- .record(z.string(), OpenApiMediaTypeSchema)
23
- .describe("Request media types keyed by content type."),
24
- })
25
- .describe("An OpenAPI request body.");
26
- const OpenApiResponseSchema = z
27
- .object({
28
- content: z
29
- .record(z.string(), OpenApiMediaTypeSchema)
30
- .optional()
31
- .describe("Response media types keyed by content type."),
32
- })
33
- .describe("One OpenAPI response.");
34
- const OpenApiOperationSchema = z
35
- .object({
36
- operationId: z.string().describe("The stable operation identifier."),
37
- summary: z.string().optional().describe("The short operation summary."),
38
- description: z.string().optional().describe("The full operation description."),
39
- requestBody: OpenApiRequestBodySchema.optional().describe("The optional operation request body."),
40
- responses: z
41
- .record(z.string(), OpenApiResponseSchema)
42
- .describe("Operation responses keyed by HTTP status."),
43
- })
44
- .describe("The OpenAPI fields required to define a tool.");
45
- const OpenApiPathSchema = z
46
- .object({
47
- get: OpenApiOperationSchema.optional(),
48
- post: OpenApiOperationSchema.optional(),
49
- put: OpenApiOperationSchema.optional(),
50
- patch: OpenApiOperationSchema.optional(),
51
- delete: OpenApiOperationSchema.optional(),
52
- options: OpenApiOperationSchema.optional(),
53
- head: OpenApiOperationSchema.optional(),
54
- trace: OpenApiOperationSchema.optional(),
55
- })
56
- .describe("Operations available at one OpenAPI path.");
57
- const OpenApiDocumentSchema = z
58
- .object({
59
- paths: z
60
- .record(z.string(), OpenApiPathSchema)
61
- .refine((paths) => Object.keys(paths).length > 0, "OpenAPI paths must not be empty")
62
- .describe("OpenAPI path items keyed by URL path."),
63
- components: JsonObjectSchema.optional().describe("Reusable OpenAPI components."),
64
- servers: z
65
- .array(z.object({
66
- url: z.string().describe("Server base URL."),
67
- }))
68
- .optional()
69
- .describe("OpenAPI server definitions."),
70
- })
71
- .describe("An OpenAPI document used to define HTTP tools.");
72
- /** Load an HTTP(S) URL or local file and return its OpenAPI operations as tool schemas. */
73
- export async function openApiTools(source, options = {}) {
74
- const sourceUrl = httpUrl(source);
75
- const document = OpenApiDocumentSchema.parse(sourceUrl
76
- ? await fetchDocument(sourceUrl, options)
77
- : await readDocument(source, options.signal));
78
- return Object.entries(document.paths).flatMap(([path, item]) => HTTP_METHODS.flatMap((method) => {
79
- const operation = item[method.toLowerCase()];
80
- return operation ? [operationTool(document, sourceUrl, path, method, operation)] : [];
81
- }));
82
- }
83
- function httpUrl(source) {
84
- const value = source.trim();
85
- if (!value.startsWith("http://") && !value.startsWith("https://"))
86
- return undefined;
87
- const url = urlBuilder(value);
88
- return url?.scheme === "http" || url?.scheme === "https" ? url : undefined;
89
- }
90
- async function fetchDocument(url, options) {
91
- const response = await fetch(url, {
92
- signal: options.signal,
93
- headers: options.headers,
94
- });
95
- if (!response.ok) {
96
- throw new Error(`OpenAPI request failed with HTTP ${response.status}: ${url}`);
97
- }
98
- return response.json();
99
- }
100
- async function readDocument(path, signal) {
101
- return JSON.parse(await readFile(path, { encoding: "utf8", signal }));
102
- }
103
- function operationTool(document, sourceUrl, path, method, operation) {
104
- const requestSchema = jsonSchema(operation.requestBody?.content) ?? { type: "object" };
105
- const responseSchema = successfulResponseSchema(operation);
106
- return {
107
- id: operation.operationId,
108
- description: operation.description?.trim() || operation.summary?.trim() || operation.operationId,
109
- url: operationUrl(document, sourceUrl, path),
110
- method,
111
- inputSchema: resolveReferences(document, requestSchema),
112
- outputSchema: resolveReferences(document, responseSchema),
113
- };
114
- }
115
- function successfulResponseSchema(operation) {
116
- for (const [status, response] of Object.entries(operation.responses)) {
117
- const schema = jsonSchema(response.content);
118
- if (/^2(?:\d{2}|XX)$/i.test(status) && schema)
119
- return schema;
120
- }
121
- return {};
122
- }
123
- function jsonSchema(content) {
124
- const mediaType = Object.entries(content ?? {}).find(([type]) => /^application\/(?:[^+;]+\+)?json(?:;|$)/i.test(type));
125
- return mediaType?.[1].schema;
126
- }
127
- function operationUrl(document, sourceUrl, path) {
128
- const server = document.servers?.[0]?.url;
129
- if (server) {
130
- const base = sourceUrl ? urlBuilder(new URL(server, sourceUrl)) : httpUrl(server);
131
- return base
132
- ? base.withPathAppend(path).toString()
133
- : `${server.replace(/\/$/, "")}/${path.replace(/^\//, "")}`;
134
- }
135
- return sourceUrl ? (urlBuilder(sourceUrl)?.withPathReplace(path).toString() ?? path) : path;
136
- }
137
- function resolveReferences(document, value, active = new Set()) {
138
- if (Array.isArray(value))
139
- return value.map((item) => resolveReferences(document, item, active));
140
- if (!object.isRecord(value))
141
- return value;
142
- const reference = typeof value.$ref === "string" ? value.$ref : undefined;
143
- if (reference) {
144
- if (active.has(reference))
145
- throw new Error(`Cyclic OpenAPI reference: ${reference}`);
146
- const target = referenceTarget(document, reference);
147
- const next = new Set(active).add(reference);
148
- const { $ref: _ignored, ...siblings } = value;
149
- return {
150
- ...resolveReferences(document, target, next),
151
- ...resolveReferences(document, siblings, next),
152
- };
153
- }
154
- return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, resolveReferences(document, child, active)]));
155
- }
156
- function referenceTarget(document, reference) {
157
- if (!reference.startsWith("#/"))
158
- throw new Error(`Unsupported OpenAPI reference: ${reference}`);
159
- let current = document;
160
- for (const rawSegment of reference.slice(2).split("/")) {
161
- const segment = rawSegment.replaceAll("~1", "/").replaceAll("~0", "~");
162
- if (!object.isRecord(current) || !(segment in current)) {
163
- throw new Error(`Unresolved OpenAPI reference: ${reference}`);
164
- }
165
- current = current[segment];
166
- }
167
- return current;
168
- }
169
- if (import.meta.main) {
170
- const source = process.argv[2];
171
- if (!source)
172
- throw new Error("Usage: bun openapi-tool.ts <openapi-url-or-file>");
173
- console.log(JSON.stringify(await openApiTools(source), null, 2));
174
- }
175
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BlbmFwaS10b29sLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL29wZW5hcGktdG9vbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7OztHQUlHO0FBQ0gsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQzVDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUNoRCxPQUFPLEVBQUUsVUFBVSxFQUFtQixNQUFNLDRCQUE0QixDQUFDO0FBRXpFLE9BQU8sRUFBRSxDQUFDLEVBQUUsTUFBTSxLQUFLLENBQUM7QUFFeEIsTUFBTSxZQUFZLEdBQUcsQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxNQUFNLEVBQUUsT0FBTyxDQUFVLENBQUM7QUFFcEcsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDO0tBQ3ZCLE1BQU0sQ0FBQyxDQUFDLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO0tBQy9CLFFBQVEsQ0FBQyxnREFBZ0QsQ0FBQyxDQUFDO0FBRTlELE1BQU0sc0JBQXNCLEdBQUcsQ0FBQztLQUM3QixNQUFNLENBQUM7SUFDTixNQUFNLEVBQUUsZ0JBQWdCLENBQUMsUUFBUSxDQUFDLHNDQUFzQyxDQUFDO0NBQzFFLENBQUM7S0FDRCxRQUFRLENBQUMseUJBQXlCLENBQUMsQ0FBQztBQUV2QyxNQUFNLHdCQUF3QixHQUFHLENBQUM7S0FDL0IsTUFBTSxDQUFDO0lBQ04sT0FBTyxFQUFFLENBQUM7U0FDUCxNQUFNLENBQUMsQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFLHNCQUFzQixDQUFDO1NBQzFDLFFBQVEsQ0FBQyw0Q0FBNEMsQ0FBQztDQUMxRCxDQUFDO0tBQ0QsUUFBUSxDQUFDLDBCQUEwQixDQUFDLENBQUM7QUFFeEMsTUFBTSxxQkFBcUIsR0FBRyxDQUFDO0tBQzVCLE1BQU0s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QUFTLEVBQUUsQ0FBQyxDQUFDO1FBQ2hFLENBQUM7UUFDRCxPQUFPLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQzdCLENBQUM7SUFDRCxPQUFPLE9BQU8sQ0FBQztBQUNqQixDQUFDO0FBRUQsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO0lBQ3JCLE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDL0IsSUFBSSxDQUFDLE1BQU07UUFBRSxNQUFNLElBQUksS0FBSyxDQUFDLGtEQUFrRCxDQUFDLENBQUM7SUFDakYsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ25FLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIE9wZW5BUEkgb3BlcmF0aW9uIGxvYWRpbmcgYW5kIEpTT04gU2NoZW1hIGV4dHJhY3Rpb24uXG4gKlxuICogQG1vZHVsZVxuICovXG5pbXBvcnQgeyByZWFkRmlsZSB9IGZyb20gXCJub2RlOmZzL3Byb21pc2VzXCI7XG5pbXBvcnQgeyBvYmplY3QgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHsgdXJsQnVpbGRlciwgdHlwZSBVcmxCdWlsZGVyIH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLWNvcmUvbmV0XCI7XG5pbXBvcnQgdHlwZSB7IEpTT05TY2hlbWE3IH0gZnJvbSBcImpzb24tc2NoZW1hXCI7XG5pbXBvcnQgeyB6IH0gZnJvbSBcInpvZFwiO1xuXG5jb25zdCBIVFRQX01FVEhPRFMgPSBbXCJHRVRcIiwgXCJQT1NUXCIsIFwiUFVUXCIsIFwiUEFUQ0hcIiwgXCJERUxFVEVcIiwgXCJPUFRJT05TXCIsIFwiSEVBRFwiLCBcIlRSQUNFXCJdIGFzIGNvbnN0O1xuXG5jb25zdCBKc29uT2JqZWN0U2NoZW1hID0gelxuICAucmVjb3JkKHouc3RyaW5nKCksIHoudW5rbm93bigpKVxuICAuZGVzY3JpYmUoXCJBIEpTT04gb2JqZWN0IGVtYmVkZGVkIGluIGFuIE9wZW5BUEkgZG9jdW1lbnQuXCIpO1xuXG5jb25zdCBPcGVuQXBpTWVkaWFUeXBlU2NoZW1hID0gelxuICAub2JqZWN0KHtcbiAgICBzY2hlbWE6IEpzb25PYmplY3RTY2hlbWEuZGVzY3JpYmUoXCJUaGUgSlNPTiBTY2hlbWEgZm9yIHRoaXMgbWVkaWEgdHlwZS5cIiksXG4gIH0pXG4gIC5kZXNjcmliZShcIk9uZSBPcGVuQVBJIG1lZGlhIHR5cGUuXCIpO1xuXG5jb25zdCBPcGVuQXBpUmVxdWVzdEJvZHlTY2hlbWEgPSB6XG4gIC5vYmplY3Qoe1xuICAgIGNvbnRlbnQ6IHpcbiAgICAgIC5yZWNvcmQoei5zdHJpbmcoKSwgT3BlbkFwaU1lZGlhVHlwZVNjaGVtYSlcbiAgICAgIC5kZXNjcmliZShcIlJlcXVlc3QgbWVkaWEgdHlwZXMga2V5ZWQgYnkgY29udGVudCB0eXBlLlwiKSxcbiAgfSlcbiAgLmRlc2NyaWJlKFwiQW4gT3BlbkFQSSByZXF1ZXN0IGJvZHkuXCIpO1xuXG5jb25zdCBPcGVuQXBpUmVzcG9uc2VTY2hlbWEgPSB6XG4gIC5vYmplY3Qoe1xuICAgIGNvbnRlbnQ6IHpcbiAgICAgIC5yZWNvcmQoei5zdHJpbmcoKSwgT3BlbkFwaU1lZGlhVHlwZVNjaGVtYSlcbiAgICAgIC5vcHRpb25hbCgpXG4gICAgICAuZGVzY3JpYmUoXCJSZXNwb25zZSBtZWRpYSB0eXBlcyBrZXllZCBieSBjb250ZW50IHR5cGUuXCIpLFxuICB9KVxuICAuZGVzY3JpYmUoXCJPbmUgT3BlbkFQSSByZXNwb25zZS5cIik7XG5cbmNvbnN0IE9wZW5BcGlPcGVyYXRpb25TY2hlbWEgPSB6XG4gIC5vYmplY3Qoe1xuICAgIG9wZXJhdGlvbklkOiB6LnN0cmluZygpLmRlc2NyaWJlKFwiVGhlIHN0YWJsZSBvcGVyYXRpb24gaWRlbnRpZmllci5cIiksXG4gICAgc3VtbWFyeTogei5zdHJpbmcoKS5vcHRpb25hbCgpLmRlc2NyaWJlKFwiVGhlIHNob3J0IG9wZXJhdGlvbiBzdW1tYXJ5LlwiKSxcbiAgICBkZXNjcmlwdGlvbjogei5zdHJpbmcoKS5vcHRpb25hbCgpLmRlc2NyaWJlKFwiVGhlIGZ1bGwgb3BlcmF0aW9uIGRlc2NyaXB0aW9uLlwiKSxcbiAgICByZXF1ZXN0Qm9keTogT3BlbkFwaVJlcXVlc3RCb2R5U2NoZW1hLm9wdGlvbmFsKCkuZGVzY3JpYmUoXG4gICAgICBcIlRoZSBvcHRpb25hbCBvcGVyYXRpb24gcmVxdWVzdCBib2R5LlwiLFxuICAgICksXG4gICAgcmVzcG9uc2VzOiB6XG4gICAgICAucmVjb3JkKHouc3RyaW5nKCksIE9wZW5BcGlSZXNwb25zZVNjaGVtYSlcbiAgICAgIC5kZXNjcmliZShcIk9wZXJhdGlvbiByZXNwb25zZXMga2V5ZWQgYnkgSFRUUCBzdGF0dXMuXCIpLFxuICB9KVxuICAuZGVzY3JpYmUoXCJUaGUgT3BlbkFQSSBmaWVsZHMgcmVxdWlyZWQgdG8gZGVmaW5lIGEgdG9vbC5cIik7XG5cbmNvbnN0IE9wZW5BcGlQYXRoU2NoZW1hID0gelxuICAub2JqZWN0KHtcbiAgICBnZXQ6IE9wZW5BcGlPcGVyYXRpb25TY2hlbWEub3B0aW9uYWwoKSxcbiAgICBwb3N0OiBPcGVuQXBpT3BlcmF0aW9uU2NoZW1hLm9wdGlvbmFsKCksXG4gICAgcHV0OiBPcGVuQXBpT3BlcmF0aW9uU2NoZW1hLm9wdGlvbmFsKCksXG4gICAgcGF0Y2g6IE9wZW5BcGlPcGVyYXRpb25TY2hlbWEub3B0aW9uYWwoKSxcbiAgICBkZWxldGU6IE9wZW5BcGlPcGVyYXRpb25TY2hlbWEub3B0aW9uYWwoKSxcbiAgICBvcHRpb25zOiBPcGVuQXBpT3BlcmF0aW9uU2NoZW1hLm9wdGlvbmFsKCksXG4gICAgaGVhZDogT3BlbkFwaU9wZXJhdGlvblNjaGVtYS5vcHRpb25hbCgpLFxuICAgIHRyYWNlOiBPcGVuQXBpT3BlcmF0aW9uU2NoZW1hLm9wdGlvbmFsKCksXG4gIH0pXG4gIC5kZXNjcmliZShcIk9wZXJhdGlvbnMgYXZhaWxhYmxlIGF0IG9uZSBPcGVuQVBJIHBhdGguXCIpO1xuXG5jb25zdCBPcGVuQXBpRG9jdW1lbnRTY2hlbWEgPSB6XG4gIC5vYmplY3Qoe1xuICAgIHBhdGhzOiB6XG4gICAgICAucmVjb3JkKHouc3RyaW5nKCksIE9wZW5BcGlQYXRoU2NoZW1hKVxuICAgICAgLnJlZmluZSgocGF0aHMpID0+IE9iamVjdC5rZXlzKHBhdGhzKS5sZW5ndGggPiAwLCBcIk9wZW5BUEkgcGF0aHMgbXVzdCBub3QgYmUgZW1wdHlcIilcbiAgICAgIC5kZXNjcmliZShcIk9wZW5BUEkgcGF0aCBpdGVtcyBrZXllZCBieSBVUkwgcGF0aC5cIiksXG4gICAgY29tcG9uZW50czogSnNvbk9iamVjdFNjaGVtYS5vcHRpb25hbCgpLmRlc2NyaWJlKFwiUmV1c2FibGUgT3BlbkFQSSBjb21wb25lbnRzLlwiKSxcbiAgICBzZXJ2ZXJzOiB6XG4gICAgICAuYXJyYXkoXG4gICAgICAgIHoub2JqZWN0KHtcbiAgICAgICAgICB1cmw6IHouc3RyaW5nKCkuZGVzY3JpYmUoXCJTZXJ2ZXIgYmFzZSBVUkwuXCIpLFxuICAgICAgICB9KSxcbiAgICAgIClcbiAgICAgIC5vcHRpb25hbCgpXG4gICAgICAuZGVzY3JpYmUoXCJPcGVuQVBJIHNlcnZlciBkZWZpbml0aW9ucy5cIiksXG4gIH0pXG4gIC5kZXNjcmliZShcIkFuIE9wZW5BUEkgZG9jdW1lbnQgdXNlZCB0byBkZWZpbmUgSFRUUCB0b29scy5cIik7XG5cbnR5cGUgT3BlbkFwaURvY3VtZW50ID0gei5pbmZlcjx0eXBlb2YgT3BlbkFwaURvY3VtZW50U2NoZW1hPjtcbnR5cGUgT3BlbkFwaU9wZXJhdGlvbiA9IHouaW5mZXI8dHlwZW9mIE9wZW5BcGlPcGVyYXRpb25TY2hlbWE+O1xuXG4vKiogSFRUUCBtZXRob2Qgc3VwcG9ydGVkIGJ5IGFuIHtAbGluayBPcGVuQXBpVG9vbH0uICovXG5leHBvcnQgdHlwZSBPcGVuQXBpSHR0cE1ldGhvZCA9ICh0eXBlb2YgSFRUUF9NRVRIT0RTKVtudW1iZXJdO1xuXG4vKiogT25lIGV4ZWN1dGFibGUgSFRUUCBvcGVyYXRpb24gZGVyaXZlZCBmcm9tIGFuIE9wZW5BUEkgZG9jdW1lbnQuICovXG5leHBvcnQgaW50ZXJmYWNlIE9wZW5BcGlUb29sIHtcbiAgLyoqIFN0YWJsZSBvcGVyYXRpb24gaWRlbnRpZmllci4gKi9cbiAgaWQ6IHN0cmluZztcbiAgLyoqIE1vZGVsLWZhY2luZyBvcGVyYXRpb24gZGVzY3JpcHRpb24uICovXG4gIGRlc2NyaXB0aW9uOiBzdHJpbmc7XG4gIC8qKiBSZXNvbHZlZCBvcGVyYXRpb24gVVJMLCBvciB0aGUgT3BlbkFQSSBwYXRoIGZvciBhIGxvY2FsIGRvY3VtZW50IHdpdGhvdXQgYSBzZXJ2ZXIgVVJMLiAqL1xuICB1cmw6IHN0cmluZztcbiAgLyoqIEhUVFAgbWV0aG9kIHVzZWQgYnkgdGhlIG9wZXJhdGlvbi4gKi9cbiAgbWV0aG9kOiBPcGVuQXBpSHR0cE1ldGhvZDtcbiAgLyoqIEpTT04gU2NoZW1hIGRlc2NyaWJpbmcgb3BlcmF0aW9uIGlucHV0LiAqL1xuICBpbnB1dFNjaGVtYTogSlNPTlNjaGVtYTc7XG4gIC8qKiBKU09OIFNjaGVtYSBkZXNjcmliaW5nIGEgc3VjY2Vzc2Z1bCBvcGVyYXRpb24gcmVzdWx0LiAqL1xuICBvdXRwdXRTY2hlbWE6IEpTT05TY2hlbWE3O1xufVxuXG4vKiogUmVxdWVzdCBvcHRpb25zIHVzZWQgd2hpbGUgbG9hZGluZyBhbiBPcGVuQVBJIGRvY3VtZW50LiAqL1xuZXhwb3J0IGludGVyZmFjZSBPcGVuQXBpVG9vbHNPcHRpb25zIHtcbiAgLyoqIENhbmNlbHMgYW4gSFRUUCByZXF1ZXN0IG9yIGZpbGUgcmVhZC4gKi9cbiAgc2lnbmFsPzogQWJvcnRTaWduYWw7XG4gIC8qKiBIZWFkZXJzIHNlbnQgd2hlbiBgc291cmNlYCBpcyBhbiBIVFRQKFMpIFVSTC4gKi9cbiAgaGVhZGVycz86IFJlcXVlc3RJbml0W1wiaGVhZGVyc1wiXTtcbn1cblxuLyoqIExvYWQgYW4gSFRUUChTKSBVUkwgb3IgbG9jYWwgZmlsZSBhbmQgcmV0dXJuIGl0cyBPcGVuQVBJIG9wZXJhdGlvbnMgYXMgdG9vbCBzY2hlbWFzLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIG9wZW5BcGlUb29scyhcbiAgc291cmNlOiBzdHJpbmcsXG4gIG9wdGlvbnM6IE9wZW5BcGlUb29sc09wdGlvbnMgPSB7fSxcbik6IFByb21pc2U8T3BlbkFwaVRvb2xbXT4ge1xuICBjb25zdCBzb3VyY2VVcmwgPSBodHRwVXJsKHNvdXJjZSk7XG4gIGNvbnN0IGRvY3VtZW50ID0gT3BlbkFwaURvY3VtZW50U2NoZW1hLnBhcnNlKFxuICAgIHNvdXJjZVVybFxuICAgICAgPyBhd2FpdCBmZXRjaERvY3VtZW50KHNvdXJjZVVybCwgb3B0aW9ucylcbiAgICAgIDogYXdhaXQgcmVhZERvY3VtZW50KHNvdXJjZSwgb3B0aW9ucy5zaWduYWwpLFxuICApO1xuICByZXR1cm4gT2JqZWN0LmVudHJpZXMoZG9jdW1lbnQucGF0aHMpLmZsYXRNYXAoKFtwYXRoLCBpdGVtXSkgPT5cbiAgICBIVFRQX01FVEhPRFMuZmxhdE1hcCgobWV0aG9kKSA9PiB7XG4gICAgICBjb25zdCBvcGVyYXRpb24gPSBpdGVtW21ldGhvZC50b0xvd2VyQ2FzZSgpIGFzIExvd2VyY2FzZTxPcGVuQXBpSHR0cE1ldGhvZD5dO1xuICAgICAgcmV0dXJuIG9wZXJhdGlvbiA/IFtvcGVyYXRpb25Ub29sKGRvY3VtZW50LCBzb3VyY2VVcmwsIHBhdGgsIG1ldGhvZCwgb3BlcmF0aW9uKV0gOiBbXTtcbiAgICB9KSxcbiAgKTtcbn1cblxuZnVuY3Rpb24gaHR0cFVybChzb3VyY2U6IHN0cmluZyk6IFVybEJ1aWxkZXIgfCB1bmRlZmluZWQge1xuICBjb25zdCB2YWx1ZSA9IHNvdXJjZS50cmltKCk7XG4gIGlmICghdmFsdWUuc3RhcnRzV2l0aChcImh0dHA6Ly9cIikgJiYgIXZhbHVlLnN0YXJ0c1dpdGgoXCJodHRwczovL1wiKSkgcmV0dXJuIHVuZGVmaW5lZDtcbiAgY29uc3QgdXJsID0gdXJsQnVpbGRlcih2YWx1ZSk7XG4gIHJldHVybiB1cmw/LnNjaGVtZSA9PT0gXCJodHRwXCIgfHwgdXJsPy5zY2hlbWUgPT09IFwiaHR0cHNcIiA/IHVybCA6IHVuZGVmaW5lZDtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZmV0Y2hEb2N1bWVudCh1cmw6IFVSTCwgb3B0aW9uczogT3BlbkFwaVRvb2xzT3B0aW9ucyk6IFByb21pc2U8dW5rbm93bj4ge1xuICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZldGNoKHVybCwge1xuICAgIHNpZ25hbDogb3B0aW9ucy5zaWduYWwsXG4gICAgaGVhZGVyczogb3B0aW9ucy5oZWFkZXJzLFxuICB9KTtcbiAgaWYgKCFyZXNwb25zZS5vaykge1xuICAgIHRocm93IG5ldyBFcnJvcihgT3BlbkFQSSByZXF1ZXN0IGZhaWxlZCB3aXRoIEhUVFAgJHtyZXNwb25zZS5zdGF0dXN9OiAke3VybH1gKTtcbiAgfVxuICByZXR1cm4gcmVzcG9uc2UuanNvbigpO1xufVxuXG5hc3luYyBmdW5jdGlvbiByZWFkRG9jdW1lbnQocGF0aDogc3RyaW5nLCBzaWduYWw/OiBBYm9ydFNpZ25hbCk6IFByb21pc2U8dW5rbm93bj4ge1xuICByZXR1cm4gSlNPTi5wYXJzZShhd2FpdCByZWFkRmlsZShwYXRoLCB7IGVuY29kaW5nOiBcInV0ZjhcIiwgc2lnbmFsIH0pKTtcbn1cblxuZnVuY3Rpb24gb3BlcmF0aW9uVG9vbChcbiAgZG9jdW1lbnQ6IE9wZW5BcGlEb2N1bWVudCxcbiAgc291cmNlVXJsOiBVUkwgfCB1bmRlZmluZWQsXG4gIHBhdGg6IHN0cmluZyxcbiAgbWV0aG9kOiBPcGVuQXBpSHR0cE1ldGhvZCxcbiAgb3BlcmF0aW9uOiBPcGVuQXBpT3BlcmF0aW9uLFxuKTogT3BlbkFwaVRvb2wge1xuICBjb25zdCByZXF1ZXN0U2NoZW1hID0ganNvblNjaGVtYShvcGVyYXRpb24ucmVxdWVzdEJvZHk/LmNvbnRlbnQpID8/IHsgdHlwZTogXCJvYmplY3RcIiB9O1xuICBjb25zdCByZXNwb25zZVNjaGVtYSA9IHN1Y2Nlc3NmdWxSZXNwb25zZVNjaGVtYShvcGVyYXRpb24pO1xuICByZXR1cm4ge1xuICAgIGlkOiBvcGVyYXRpb24ub3BlcmF0aW9uSWQsXG4gICAgZGVzY3JpcHRpb246XG4gICAgICBvcGVyYXRpb24uZGVzY3JpcHRpb24/LnRyaW0oKSB8fCBvcGVyYXRpb24uc3VtbWFyeT8udHJpbSgpIHx8IG9wZXJhdGlvbi5vcGVyYXRpb25JZCxcbiAgICB1cmw6IG9wZXJhdGlvblVybChkb2N1bWVudCwgc291cmNlVXJsLCBwYXRoKSxcbiAgICBtZXRob2QsXG4gICAgaW5wdXRTY2hlbWE6IHJlc29sdmVSZWZlcmVuY2VzKGRvY3VtZW50LCByZXF1ZXN0U2NoZW1hKSBhcyBKU09OU2NoZW1hNyxcbiAgICBvdXRwdXRTY2hlbWE6IHJlc29sdmVSZWZlcmVuY2VzKGRvY3VtZW50LCByZXNwb25zZVNjaGVtYSkgYXMgSlNPTlNjaGVtYTcsXG4gIH07XG59XG5cbmZ1bmN0aW9uIHN1Y2Nlc3NmdWxSZXNwb25zZVNjaGVtYShvcGVyYXRpb246IE9wZW5BcGlPcGVyYXRpb24pOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiB7XG4gIGZvciAoY29uc3QgW3N0YXR1cywgcmVzcG9uc2VdIG9mIE9iamVjdC5lbnRyaWVzKG9wZXJhdGlvbi5yZXNwb25zZXMpKSB7XG4gICAgY29uc3Qgc2NoZW1hID0ganNvblNjaGVtYShyZXNwb25zZS5jb250ZW50KTtcbiAgICBpZiAoL14yKD86XFxkezJ9fFhYKSQvaS50ZXN0KHN0YXR1cykgJiYgc2NoZW1hKSByZXR1cm4gc2NoZW1hO1xuICB9XG4gIHJldHVybiB7fTtcbn1cblxuZnVuY3Rpb24ganNvblNjaGVtYShcbiAgY29udGVudDogUmVjb3JkPHN0cmluZywgei5pbmZlcjx0eXBlb2YgT3BlbkFwaU1lZGlhVHlwZVNjaGVtYT4+IHwgdW5kZWZpbmVkLFxuKTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gfCB1bmRlZmluZWQge1xuICBjb25zdCBtZWRpYVR5cGUgPSBPYmplY3QuZW50cmllcyhjb250ZW50ID8/IHt9KS5maW5kKChbdHlwZV0pID0+XG4gICAgL15hcHBsaWNhdGlvblxcLyg/OlteKztdK1xcKyk/anNvbig/Ojt8JCkvaS50ZXN0KHR5cGUpLFxuICApO1xuICByZXR1cm4gbWVkaWFUeXBlPy5bMV0uc2NoZW1hO1xufVxuXG5mdW5jdGlvbiBvcGVyYXRpb25VcmwoZG9jdW1lbnQ6IE9wZW5BcGlEb2N1bWVudCwgc291cmNlVXJsOiBVUkwgfCB1bmRlZmluZWQsIHBhdGg6IHN0cmluZyk6IHN0cmluZyB7XG4gIGNvbnN0IHNlcnZlciA9IGRvY3VtZW50LnNlcnZlcnM/LlswXT8udXJsO1xuICBpZiAoc2VydmVyKSB7XG4gICAgY29uc3QgYmFzZSA9IHNvdXJjZVVybCA/IHVybEJ1aWxkZXIobmV3IFVSTChzZXJ2ZXIsIHNvdXJjZVVybCkpIDogaHR0cFVybChzZXJ2ZXIpO1xuICAgIHJldHVybiBiYXNlXG4gICAgICA/IGJhc2Uud2l0aFBhdGhBcHBlbmQocGF0aCkudG9TdHJpbmcoKVxuICAgICAgOiBgJHtzZXJ2ZXIucmVwbGFjZSgvXFwvJC8sIFwiXCIpfS8ke3BhdGgucmVwbGFjZSgvXlxcLy8sIFwiXCIpfWA7XG4gIH1cbiAgcmV0dXJuIHNvdXJjZVVybCA/ICh1cmxCdWlsZGVyKHNvdXJjZVVybCk/LndpdGhQYXRoUmVwbGFjZShwYXRoKS50b1N0cmluZygpID8/IHBhdGgpIDogcGF0aDtcbn1cblxuZnVuY3Rpb24gcmVzb2x2ZVJlZmVyZW5jZXMoXG4gIGRvY3VtZW50OiBPcGVuQXBpRG9jdW1lbnQsXG4gIHZhbHVlOiB1bmtub3duLFxuICBhY3RpdmU6IFJlYWRvbmx5U2V0PHN0cmluZz4gPSBuZXcgU2V0KCksXG4pOiB1bmtub3duIHtcbiAgaWYgKEFycmF5LmlzQXJyYXkodmFsdWUpKSByZXR1cm4gdmFsdWUubWFwKChpdGVtKSA9PiByZXNvbHZlUmVmZXJlbmNlcyhkb2N1bWVudCwgaXRlbSwgYWN0aXZlKSk7XG4gIGlmICghb2JqZWN0LmlzUmVjb3JkKHZhbHVlKSkgcmV0dXJuIHZhbHVlO1xuICBjb25zdCByZWZlcmVuY2UgPSB0eXBlb2YgdmFsdWUuJHJlZiA9PT0gXCJzdHJpbmdcIiA/IHZhbHVlLiRyZWYgOiB1bmRlZmluZWQ7XG4gIGlmIChyZWZlcmVuY2UpIHtcbiAgICBpZiAoYWN0aXZlLmhhcyhyZWZlcmVuY2UpKSB0aHJvdyBuZXcgRXJyb3IoYEN5Y2xpYyBPcGVuQVBJIHJlZmVyZW5jZTogJHtyZWZlcmVuY2V9YCk7XG4gICAgY29uc3QgdGFyZ2V0ID0gcmVmZXJlbmNlVGFyZ2V0KGRvY3VtZW50LCByZWZlcmVuY2UpO1xuICAgIGNvbnN0IG5leHQgPSBuZXcgU2V0KGFjdGl2ZSkuYWRkKHJlZmVyZW5jZSk7XG4gICAgY29uc3QgeyAkcmVmOiBfaWdub3JlZCwgLi4uc2libGluZ3MgfSA9IHZhbHVlO1xuICAgIHJldHVybiB7XG4gICAgICAuLi4ocmVzb2x2ZVJlZmVyZW5jZXMoZG9jdW1lbnQsIHRhcmdldCwgbmV4dCkgYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj4pLFxuICAgICAgLi4uKHJlc29sdmVSZWZlcmVuY2VzKGRvY3VtZW50LCBzaWJsaW5ncywgbmV4dCkgYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj4pLFxuICAgIH07XG4gIH1cbiAgcmV0dXJuIE9iamVjdC5mcm9tRW50cmllcyhcbiAgICBPYmplY3QuZW50cmllcyh2YWx1ZSkubWFwKChba2V5LCBjaGlsZF0pID0+IFtrZXksIHJlc29sdmVSZWZlcmVuY2VzKGRvY3VtZW50LCBjaGlsZCwgYWN0aXZlKV0pLFxuICApO1xufVxuXG5mdW5jdGlvbiByZWZlcmVuY2VUYXJnZXQoZG9jdW1lbnQ6IE9wZW5BcGlEb2N1bWVudCwgcmVmZXJlbmNlOiBzdHJpbmcpOiB1bmtub3duIHtcbiAgaWYgKCFyZWZlcmVuY2Uuc3RhcnRzV2l0aChcIiMvXCIpKSB0aHJvdyBuZXcgRXJyb3IoYFVuc3VwcG9ydGVkIE9wZW5BUEkgcmVmZXJlbmNlOiAke3JlZmVyZW5jZX1gKTtcbiAgbGV0IGN1cnJlbnQ6IHVua25vd24gPSBkb2N1bWVudDtcbiAgZm9yIChjb25zdCByYXdTZWdtZW50IG9mIHJlZmVyZW5jZS5zbGljZSgyKS5zcGxpdChcIi9cIikpIHtcbiAgICBjb25zdCBzZWdtZW50ID0gcmF3U2VnbWVudC5yZXBsYWNlQWxsKFwifjFcIiwgXCIvXCIpLnJlcGxhY2VBbGwoXCJ+MFwiLCBcIn5cIik7XG4gICAgaWYgKCFvYmplY3QuaXNSZWNvcmQoY3VycmVudCkgfHwgIShzZWdtZW50IGluIGN1cnJlbnQpKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYFVucmVzb2x2ZWQgT3BlbkFQSSByZWZlcmVuY2U6ICR7cmVmZXJlbmNlfWApO1xuICAgIH1cbiAgICBjdXJyZW50ID0gY3VycmVudFtzZWdtZW50XTtcbiAgfVxuICByZXR1cm4gY3VycmVudDtcbn1cblxuaWYgKGltcG9ydC5tZXRhLm1haW4pIHtcbiAgY29uc3Qgc291cmNlID0gcHJvY2Vzcy5hcmd2WzJdO1xuICBpZiAoIXNvdXJjZSkgdGhyb3cgbmV3IEVycm9yKFwiVXNhZ2U6IGJ1biBvcGVuYXBpLXRvb2wudHMgPG9wZW5hcGktdXJsLW9yLWZpbGU+XCIpO1xuICBjb25zb2xlLmxvZyhKU09OLnN0cmluZ2lmeShhd2FpdCBvcGVuQXBpVG9vbHMoc291cmNlKSwgbnVsbCwgMikpO1xufVxuIl19