@epam/ai-dial-mcp-apps 1.3.0-dev.246 → 1.3.0-dev.248

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.
@@ -1 +1 @@
1
- {"version":3,"file":"McpAppInlinePreview.d.ts","sourceRoot":"","sources":["../../../src/components/McpAppInlinePreview/McpAppInlinePreview.tsx"],"names":[],"mappings":"AAqBA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EACnB,MAAM,uBAAuB,CAAC;AAG/B,qDAAqD;AACrD,MAAM,WAAW,wBAAwB;IACvC,uCAAuC;IACvC,KAAK,EAAE,aAAa,CAAC;IACrB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,uIAAuI;IACvI,KAAK,EAAE,mBAAmB,CAAC;IAC3B,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,6GAA6G;IAC7G,WAAW,EAAE,iBAAiB,CAAC;IAC/B,kEAAkE;IAClE,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,wDAAwD;IACxD,eAAe,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,eAAe,EAAE,MAAM,CAAC;IACxB,uFAAuF;IACvF,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,oFAAoF;IACpF,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,4EAA4E;IAC5E,mBAAmB,EAAE,MAAM,CAAC;IAC5B,8EAA8E;IAC9E,MAAM,CAAC,EAAE,yBAAyB,CAAC;CACpC;AAuLD,iHAAiH;AACjH,eAAO,MAAM,mBAAmB,gEAAgC,CAAC"}
1
+ {"version":3,"file":"McpAppInlinePreview.d.ts","sourceRoot":"","sources":["../../../src/components/McpAppInlinePreview/McpAppInlinePreview.tsx"],"names":[],"mappings":"AAuBA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EACnB,MAAM,uBAAuB,CAAC;AAG/B,qDAAqD;AACrD,MAAM,WAAW,wBAAwB;IACvC,uCAAuC;IACvC,KAAK,EAAE,aAAa,CAAC;IACrB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,uIAAuI;IACvI,KAAK,EAAE,mBAAmB,CAAC;IAC3B,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,6GAA6G;IAC7G,WAAW,EAAE,iBAAiB,CAAC;IAC/B,kEAAkE;IAClE,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,wDAAwD;IACxD,eAAe,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,eAAe,EAAE,MAAM,CAAC;IACxB,uFAAuF;IACvF,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,oFAAoF;IACpF,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,4EAA4E;IAC5E,mBAAmB,EAAE,MAAM,CAAC;IAC5B,8EAA8E;IAC9E,MAAM,CAAC,EAAE,yBAAyB,CAAC;CACpC;AA0LD,iHAAiH;AACjH,eAAO,MAAM,mBAAmB,gEAAgC,CAAC"}
package/index.js CHANGED
@@ -1,23 +1,23 @@
1
1
  import { AttachmentContentType as e, McpAppCanvasRenderer as t } from "@epam/ai-dial-attachment-canvas";
2
2
  import { MessageRole as n, buildCssVars as r } from "@epam/ai-dial-chat-shared";
3
- import { DIAL_ICON_SIZE as i, DIAL_KIT_ICON_STROKE as a, ElementSize as o, EllipsisTooltip as s, GhostIconButton as c, Spinner as l, mergeClasses as u } from "@epam/ai-dial-ui-kit";
4
- import { IconAlertTriangle as d, IconArrowsMaximize as f, IconRefresh as p } from "@tabler/icons-react";
5
- import { memo as m, useCallback as h, useEffect as g, useMemo as _, useRef as v, useState as y } from "react";
6
- import { Fragment as b, jsx as x, jsxs as S } from "react/jsx-runtime";
3
+ import { DIAL_ICON_SIZE as i, DIAL_KIT_ICON_STROKE as a, ElementSize as o, EllipsisTooltip as s, GhostIconButton as c, LiveRegion as l, LiveRegionElement as u, Spinner as d, mergeClasses as f } from "@epam/ai-dial-ui-kit";
4
+ import { IconAlertTriangle as p, IconArrowsMaximize as m, IconRefresh as h } from "@tabler/icons-react";
5
+ import { memo as g, useCallback as _, useEffect as v, useMemo as y, useRef as b, useState as x } from "react";
6
+ import { Fragment as S, jsx as C, jsxs as w } from "react/jsx-runtime";
7
7
  //#region src/constants/public-class-names.ts
8
- var C = {
8
+ var T = {
9
9
  preview: "dial-mcp-apps-preview",
10
10
  previewHeader: "dial-mcp-apps-preview-header"
11
- }, w = /* @__PURE__ */ function(e) {
11
+ }, E = /* @__PURE__ */ function(e) {
12
12
  return e.Loading = "loading", e.Ready = "ready", e.Error = "error", e.Unavailable = "unavailable", e;
13
- }({}), T = (e) => {
13
+ }({}), D = (e) => {
14
14
  try {
15
15
  let t = JSON.parse(e);
16
16
  return typeof t == "object" && t ? t : {};
17
17
  } catch {
18
18
  return {};
19
19
  }
20
- }, E = (e) => {
20
+ }, O = (e) => {
21
21
  let t = /* @__PURE__ */ new Map();
22
22
  for (let n of e?.tool_messages ?? []) {
23
23
  for (let e of n.tool_calls ?? []) t.set(e.id, {
@@ -32,7 +32,7 @@ var C = {
32
32
  for (let n of e?.tool_execution_history ?? []) {
33
33
  for (let e of n.tool_calls ?? []) t.set(e.id, {
34
34
  name: e.function.name,
35
- args: T(e.function.arguments)
35
+ args: D(e.function.arguments)
36
36
  });
37
37
  if (n.role === "tool" && n.tool_call_id) {
38
38
  let e = t.get(n.tool_call_id);
@@ -40,21 +40,21 @@ var C = {
40
40
  }
41
41
  }
42
42
  return t;
43
- }, D = /[^a-zA-Z0-9_-]+/g, O = 64, k = (e) => {
44
- let t = e.replace(D, "_");
45
- return t !== "" && !/^[a-zA-Z_]/.test(t) && (t = `_${t}`), t.slice(0, O);
46
- }, A = (e) => {
43
+ }, k = /[^a-zA-Z0-9_-]+/g, A = 64, j = (e) => {
44
+ let t = e.replace(k, "_");
45
+ return t !== "" && !/^[a-zA-Z_]/.test(t) && (t = `_${t}`), t.slice(0, A);
46
+ }, M = (e) => {
47
47
  let t = /* @__PURE__ */ new Set();
48
- for (let n of e) for (let e of E(n.custom_content?.state).values()) t.add(e.name);
48
+ for (let n of e) for (let e of O(n.custom_content?.state).values()) t.add(e.name);
49
49
  return t;
50
- }, j = (e, t, r) => {
50
+ }, N = (e, t, r) => {
51
51
  if (t.length === 0 || e.role !== n.Assistant) return;
52
- let i = new Set([...E(e.custom_content?.state).values()].map((e) => e.name));
52
+ let i = new Set([...O(e.custom_content?.state).values()].map((e) => e.name));
53
53
  return t.find((e) => i.has(e.toolName)) || t.find((e) => e.discovery === "direct") || (r ? t[0] : void 0);
54
- }, M = (e) => `${e}:mcp-app`, N = (e) => e == null ? void 0 : JSON.stringify(e.toolInput ?? null), P = (e, t) => {
54
+ }, P = (e) => `${e}:mcp-app`, F = (e) => e == null ? void 0 : JSON.stringify(e.toolInput ?? null), I = (e, t) => {
55
55
  let n = e.custom_content?.state;
56
56
  if (!n) return;
57
- let r = [...E(n).values()].reverse().find((e) => e.name === t);
57
+ let r = [...O(n).values()].reverse().find((e) => e.name === t);
58
58
  if (r) return {
59
59
  toolInput: r.args,
60
60
  toolResult: r.result == null ? void 0 : { content: [{
@@ -62,29 +62,29 @@ var C = {
62
62
  text: r.result
63
63
  }] }
64
64
  };
65
- }, F = async (e, t, n) => {
65
+ }, L = async (e, t, n) => {
66
66
  if (e.discovery !== "direct" && t?.toolInput == null) return t?.toolResult;
67
67
  try {
68
68
  return await n(e.toolsetId, e.mcpToolName, t?.toolInput ?? {}, e.kind);
69
69
  } catch {
70
70
  return t?.toolResult;
71
71
  }
72
- }, I = (t, n, r, i, a, o) => {
73
- let { hostContext: s, hostInfo: c, sandboxUrl: l, fetchResourceHtml: u, callTool: d } = a, [f, p] = y(), [m, _] = y(), [v, b] = y(() => t == null || l == null ? w.Unavailable : w.Loading), [x, S] = y(0), C = h(() => {
72
+ }, R = (t, n, r, i, a, o) => {
73
+ let { hostContext: s, hostInfo: c, sandboxUrl: l, fetchResourceHtml: u, callTool: d } = a, [f, p] = x(), [m, h] = x(), [g, y] = x(() => t == null || l == null ? E.Unavailable : E.Loading), [b, S] = x(0), C = _(() => {
74
74
  r.invalidate(i), S((e) => e + 1);
75
- }, [r, i]), T = h(async (e) => {
75
+ }, [r, i]), w = _(async (e) => {
76
76
  if (t != null && l != null) try {
77
- let a = N(n), { html: o, toolResult: s } = await r.getOrFetch(i, a, async () => {
78
- let [e, r] = await Promise.all([u(t.toolsetId, t.resourceUri), F(t, n, d)]);
77
+ let a = F(n), { html: o, toolResult: s } = await r.getOrFetch(i, a, async () => {
78
+ let [e, r] = await Promise.all([u(t.toolsetId, t.resourceUri), L(t, n, d)]);
79
79
  return {
80
80
  html: e,
81
81
  toolResult: r
82
82
  };
83
83
  });
84
84
  if (e()) return;
85
- p(o), _(s), b(w.Ready);
85
+ p(o), h(s), y(E.Ready);
86
86
  } catch {
87
- e() || b(w.Error);
87
+ e() || y(E.Error);
88
88
  }
89
89
  }, [
90
90
  t,
@@ -95,33 +95,33 @@ var C = {
95
95
  u,
96
96
  d
97
97
  ]);
98
- g(() => {
98
+ v(() => {
99
99
  if (t == null || l == null) {
100
- b(w.Unavailable);
100
+ y(E.Unavailable);
101
101
  return;
102
102
  }
103
103
  let e = !1;
104
- return b(w.Loading), T(() => e), () => {
104
+ return y(E.Loading), w(() => e), () => {
105
105
  e = !0;
106
106
  };
107
107
  }, [
108
108
  t,
109
109
  l,
110
- T,
111
- x
110
+ w,
111
+ b
112
112
  ]);
113
- let E = h(async (e, n) => {
113
+ let T = _(async (e, n) => {
114
114
  if (t == null) throw Error(`Tool call "${e}" failed: no matching MCP App`);
115
115
  return d(t.toolsetId, e, n, t.kind);
116
116
  }, [t, d]);
117
- return v !== w.Ready || t == null || l == null || f == null ? {
118
- status: v,
117
+ return g !== E.Ready || t == null || l == null || f == null ? {
118
+ status: g,
119
119
  reload: C,
120
- attemptId: x
120
+ attemptId: b
121
121
  } : {
122
- status: v,
122
+ status: g,
123
123
  reload: C,
124
- attemptId: x,
124
+ attemptId: b,
125
125
  content: {
126
126
  type: e.McpApp,
127
127
  html: f,
@@ -130,123 +130,126 @@ var C = {
130
130
  toolResult: m,
131
131
  hostContext: s,
132
132
  hostInfo: c,
133
- onToolCall: E,
133
+ onToolCall: T,
134
134
  onRequestDisplayMode: o
135
135
  }
136
136
  };
137
- }, L = {
137
+ }, z = {
138
138
  preview: "_preview_19as4_11",
139
139
  previewHeader: "_previewHeader_19as4_23"
140
- }, R = m(({ match: e, toolCall: n, cache: m, cacheKey: _, hostAdapter: v, onExpand: T, expandAriaLabel: E, reloadAriaLabel: D, actionsGroupAriaLabel: O = "MCP app actions", loadErrorLabel: k, isOpenedInCanvas: A = !1, openedInCanvasLabel: j, colors: M }) => {
141
- let [N, P] = y(void 0);
142
- g(() => {
143
- P(void 0);
144
- }, [_]);
145
- let F = h((e) => e === "fullscreen" ? (T(), "fullscreen") : "inline", [T]), R = r({
146
- "--mcpapp-preview-bg": M?.previewBackground,
147
- "--mcpapp-preview-border": M?.previewBorder,
148
- "--mcpapp-preview-header-border": M?.previewHeaderBorder
149
- }), { status: z, content: B, reload: V, attemptId: H } = I(e, n, m, _, v, F);
150
- return z === w.Unavailable ? null : /* @__PURE__ */ S("div", {
151
- style: R,
152
- className: u(L.preview, "flex w-full min-w-0 flex-col overflow-hidden rounded-xl border", C.preview),
153
- children: [/* @__PURE__ */ S("div", {
154
- className: u(L.previewHeader, "flex min-h-10 items-center justify-between gap-2 border-b px-4 py-2", C.previewHeader),
155
- children: [/* @__PURE__ */ x(s, {
156
- text: N == null ? /* @__PURE__ */ x("span", {
140
+ }, B = g(({ match: e, toolCall: n, cache: g, cacheKey: y, hostAdapter: b, onExpand: D, expandAriaLabel: O, reloadAriaLabel: k, actionsGroupAriaLabel: A = "MCP app actions", loadErrorLabel: j, isOpenedInCanvas: M = !1, openedInCanvasLabel: N, colors: P }) => {
141
+ let [F, I] = x(void 0);
142
+ v(() => {
143
+ I(void 0);
144
+ }, [y]);
145
+ let L = _((e) => e === "fullscreen" ? (D(), "fullscreen") : "inline", [D]), B = r({
146
+ "--mcpapp-preview-bg": P?.previewBackground,
147
+ "--mcpapp-preview-border": P?.previewBorder,
148
+ "--mcpapp-preview-header-border": P?.previewHeaderBorder
149
+ }), { status: V, content: H, reload: U, attemptId: W } = R(e, n, g, y, b, L);
150
+ return V === E.Unavailable ? null : /* @__PURE__ */ w("div", {
151
+ style: B,
152
+ className: f(z.preview, "flex w-full min-w-0 flex-col overflow-hidden rounded-xl border", T.preview),
153
+ children: [/* @__PURE__ */ w("div", {
154
+ className: f(z.previewHeader, "flex min-h-10 items-center justify-between gap-2 border-b px-4 py-2", T.previewHeader),
155
+ children: [/* @__PURE__ */ C(s, {
156
+ text: F == null ? /* @__PURE__ */ C("span", {
157
157
  className: "dial-tiny-lead-semi-text",
158
158
  children: e.mcpToolName
159
- }) : /* @__PURE__ */ S(b, { children: [
160
- /* @__PURE__ */ x("span", {
159
+ }) : /* @__PURE__ */ w(S, { children: [
160
+ /* @__PURE__ */ C("span", {
161
161
  className: "dial-tiny-lead-semi-text",
162
162
  children: e.mcpToolName
163
163
  }),
164
- /* @__PURE__ */ x("span", {
164
+ /* @__PURE__ */ C("span", {
165
165
  "aria-hidden": !0,
166
166
  className: "mx-1.5 inline-block h-3 w-0 border-s border-current align-middle"
167
167
  }),
168
- /* @__PURE__ */ x("span", {
168
+ /* @__PURE__ */ C("span", {
169
169
  className: "dial-tiny-lead-semi-text",
170
- children: N.name
170
+ children: F.name
171
171
  }),
172
- N.version && /* @__PURE__ */ x("span", {
172
+ F.version && /* @__PURE__ */ C("span", {
173
173
  className: "dial-caption-text ms-2",
174
- children: N.version
174
+ children: F.version
175
175
  })
176
176
  ] }),
177
177
  className: "text-secondary"
178
- }), /* @__PURE__ */ S("div", {
178
+ }), /* @__PURE__ */ w("div", {
179
179
  role: "toolbar",
180
- "aria-label": O,
180
+ "aria-label": A,
181
181
  className: "flex shrink-0 items-center gap-1",
182
- children: [/* @__PURE__ */ x(c, {
183
- icon: /* @__PURE__ */ x(p, {
182
+ children: [/* @__PURE__ */ C(c, {
183
+ icon: /* @__PURE__ */ C(h, {
184
184
  size: i.SM,
185
185
  stroke: a,
186
186
  "aria-hidden": !0
187
187
  }),
188
- "aria-label": D,
188
+ "aria-label": k,
189
189
  size: o.Small,
190
- onClick: V
191
- }), /* @__PURE__ */ x(c, {
192
- icon: /* @__PURE__ */ x(f, {
190
+ onClick: U
191
+ }), /* @__PURE__ */ C(c, {
192
+ icon: /* @__PURE__ */ C(m, {
193
193
  size: i.SM,
194
194
  stroke: a,
195
195
  "aria-hidden": !0
196
196
  }),
197
- "aria-label": E,
197
+ "aria-label": O,
198
198
  size: o.Small,
199
- onClick: T
199
+ onClick: D
200
200
  })]
201
201
  })]
202
- }), /* @__PURE__ */ x("div", {
202
+ }), /* @__PURE__ */ w("div", {
203
203
  className: "relative min-h-[100px] w-full",
204
- children: A ? /* @__PURE__ */ x("div", {
205
- role: "status",
206
- "aria-live": "polite",
204
+ children: [/* @__PURE__ */ C(l, {
205
+ visuallyHidden: !0,
206
+ as: u.Span,
207
+ children: M ? N : ""
208
+ }), M ? /* @__PURE__ */ C("div", {
209
+ "aria-hidden": !0,
207
210
  className: "absolute inset-0 flex items-center justify-center p-4",
208
- children: /* @__PURE__ */ x("span", {
211
+ children: /* @__PURE__ */ C("span", {
209
212
  className: "dial-body-text text-center text-secondary",
210
- children: j
213
+ children: N
211
214
  })
212
- }) : /* @__PURE__ */ S(b, { children: [
213
- z === w.Loading && /* @__PURE__ */ x("div", {
215
+ }) : /* @__PURE__ */ w(S, { children: [
216
+ V === E.Loading && /* @__PURE__ */ C("div", {
214
217
  className: "absolute inset-0 flex items-center justify-center",
215
- children: /* @__PURE__ */ x(l, {})
218
+ children: /* @__PURE__ */ C(d, {})
216
219
  }),
217
- z === w.Error && /* @__PURE__ */ S("div", {
220
+ V === E.Error && /* @__PURE__ */ w("div", {
218
221
  className: "absolute inset-0 flex flex-col items-center justify-center gap-2 p-4",
219
- children: [/* @__PURE__ */ x(d, {
222
+ children: [/* @__PURE__ */ C(p, {
220
223
  size: 40,
221
224
  stroke: a,
222
225
  "aria-hidden": !0,
223
226
  className: "text-error"
224
- }), /* @__PURE__ */ x("p", {
225
- role: "alert",
227
+ }), /* @__PURE__ */ C("p", {
228
+ role: "status",
226
229
  className: "dial-body-text text-center text-primary",
227
- children: k
230
+ children: j
228
231
  })]
229
232
  }),
230
- z === w.Ready && B && /* @__PURE__ */ x(t, {
231
- content: B,
232
- errorLabel: k,
233
- onAppInfo: P
234
- }, H)
235
- ] })
233
+ V === E.Ready && H && /* @__PURE__ */ C(t, {
234
+ content: H,
235
+ errorLabel: j,
236
+ onAppInfo: I
237
+ }, W)
238
+ ] })]
236
239
  })]
237
240
  });
238
- }), z = 9e5, B = (e) => {
239
- let t = v(/* @__PURE__ */ new Map()), n = v(/* @__PURE__ */ new Map());
240
- return _(() => {
241
+ }), V = 9e5, H = (e) => {
242
+ let t = b(/* @__PURE__ */ new Map()), n = b(/* @__PURE__ */ new Map());
243
+ return y(() => {
241
244
  let r = (t) => `${e}:${t}`, i = (e, t) => `${r(e)}::${t ?? ""}`, a = (e, n) => {
242
245
  let i = t.current.get(r(e));
243
- if (i != null && i.seedKey === n && !(Date.now() - i.cachedAt > z)) return {
246
+ if (i != null && i.seedKey === n && !(Date.now() - i.cachedAt > V)) return {
244
247
  html: i.html,
245
248
  toolResult: i.toolResult
246
249
  };
247
250
  }, o = (e, n, i) => {
248
251
  let a = Date.now();
249
- for (let [e, n] of t.current) a - n.cachedAt > z && t.current.delete(e);
252
+ for (let [e, n] of t.current) a - n.cachedAt > V && t.current.delete(e);
250
253
  t.current.set(r(e), {
251
254
  ...n,
252
255
  seedKey: i,
@@ -273,4 +276,4 @@ var C = {
273
276
  }, [e]);
274
277
  };
275
278
  //#endregion
276
- export { C as MCP_APPS_CLASS, R as McpAppInlinePreview, w as McpAppInlinePreviewStatus, A as collectToolCallNames, N as computeMcpAppSeedKey, j as findMcpAppForMessage, M as mcpAppCanvasKey, P as resolveMcpAppToolCallSeed, F as resolveMcpAppToolResult, k as sanitizeToolName, I as useMcpAppInlinePreview, B as useMcpAppResponseCache };
279
+ export { T as MCP_APPS_CLASS, B as McpAppInlinePreview, E as McpAppInlinePreviewStatus, M as collectToolCallNames, F as computeMcpAppSeedKey, N as findMcpAppForMessage, P as mcpAppCanvasKey, I as resolveMcpAppToolCallSeed, L as resolveMcpAppToolResult, j as sanitizeToolName, R as useMcpAppInlinePreview, H as useMcpAppResponseCache };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@epam/ai-dial-mcp-apps",
3
3
  "description": "Host-agnostic MCP Apps building blocks — tool-call/message matching, the response cache, and the inline preview hook/component shared by the full-width canvas and inline surfaces",
4
- "version": "1.3.0-dev.246",
4
+ "version": "1.3.0-dev.248",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "main": "./index.js",
@@ -19,11 +19,11 @@
19
19
  "dependencies": {
20
20
  "@tabler/icons-react": "^3.44.0",
21
21
  "@modelcontextprotocol/sdk": "^1.32.1",
22
- "@epam/ai-dial-attachment-canvas": "1.3.0-dev.246"
22
+ "@epam/ai-dial-attachment-canvas": "1.3.0-dev.248"
23
23
  },
24
24
  "peerDependencies": {
25
- "@epam/ai-dial-chat-shared": "1.3.0-dev.246",
26
- "@epam/ai-dial-ui-kit": "^0.16.0-dev.45",
25
+ "@epam/ai-dial-chat-shared": "1.3.0-dev.248",
26
+ "@epam/ai-dial-ui-kit": "^0.16.0-dev.56",
27
27
  "react": "^19.2.8"
28
28
  },
29
29
  "repository": {