@decocms/mesh-sdk 1.2.1 → 1.2.2

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.
@@ -21,6 +21,12 @@ export interface CreateMcpClientOptions {
21
21
 
22
22
  export type UseMcpClientOptions = CreateMcpClientOptions;
23
23
 
24
+ export interface UseMcpClientOptionalOptions
25
+ extends Omit<CreateMcpClientOptions, "connectionId"> {
26
+ /** Connection ID - string for connection MCP, null for default/self, undefined to skip (returns null) */
27
+ connectionId: string | null | undefined;
28
+ }
29
+
24
30
  /**
25
31
  * Build the MCP URL from connectionId and optional meshUrl
26
32
  * Uses /mcp/:connectionId for all servers
@@ -89,6 +95,7 @@ export async function createMCPClient({
89
95
  token ?? "",
90
96
  meshUrl ?? "",
91
97
  );
98
+
92
99
  (client as Client & { toJSON: () => string }).toJSON = () =>
93
100
  `mcp-client:${queryKey.join(":")}`;
94
101
 
@@ -110,7 +117,7 @@ export function useMCPClient({
110
117
  }: UseMcpClientOptions): Client {
111
118
  const queryKey = KEYS.mcpClient(
112
119
  orgId,
113
- connectionId ?? "",
120
+ connectionId ?? "self",
114
121
  token ?? "",
115
122
  meshUrl ?? "",
116
123
  );
@@ -122,6 +129,52 @@ export function useMCPClient({
122
129
  gcTime: 0, // Clean up immediately when query is inactive
123
130
  });
124
131
 
125
- // useSuspenseQuery guarantees data is available (suspends until ready)
126
132
  return client!;
127
133
  }
134
+
135
+ /**
136
+ * Optional MCP client - returns null when connectionId is undefined (skip creating client).
137
+ * Use when the connection may not be selected yet (e.g. model picker with no connections).
138
+ *
139
+ * - connectionId: string → connection-specific MCP
140
+ * - connectionId: null → default/self MCP
141
+ * - connectionId: undefined → skip (returns null, no MCP call)
142
+ *
143
+ * @param options - Configuration for the MCP client
144
+ * @returns The MCP client instance, or null when connectionId is undefined
145
+ */
146
+ export function useMCPClientOptional({
147
+ connectionId,
148
+ orgId,
149
+ token,
150
+ meshUrl,
151
+ }: UseMcpClientOptionalOptions): Client | null {
152
+ const queryKey =
153
+ connectionId !== undefined
154
+ ? KEYS.mcpClient(
155
+ orgId,
156
+ connectionId ?? "self",
157
+ token ?? "",
158
+ meshUrl ?? "",
159
+ )
160
+ : (["mcp", "client", "skip", orgId] as const);
161
+
162
+ const { data: client } = useSuspenseQuery({
163
+ queryKey,
164
+ queryFn: async () => {
165
+ if (connectionId === undefined) {
166
+ return null;
167
+ }
168
+ return createMCPClient({
169
+ connectionId: connectionId as string | null,
170
+ orgId,
171
+ token,
172
+ meshUrl,
173
+ });
174
+ },
175
+ staleTime: Infinity,
176
+ gcTime: 0,
177
+ });
178
+
179
+ return client ?? null;
180
+ }
@@ -1,4 +1,11 @@
1
1
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import {
3
+ ErrorCode,
4
+ McpError,
5
+ type GetPromptRequest,
6
+ type GetPromptResult,
7
+ type ListPromptsResult,
8
+ } from "@modelcontextprotocol/sdk/types.js";
2
9
  import {
3
10
  useQuery,
4
11
  UseQueryResult,
@@ -7,11 +14,6 @@ import {
7
14
  type UseQueryOptions,
8
15
  type UseSuspenseQueryOptions,
9
16
  } from "@tanstack/react-query";
10
- import type {
11
- GetPromptRequest,
12
- GetPromptResult,
13
- ListPromptsResult,
14
- } from "@modelcontextprotocol/sdk/types.js";
15
17
  import { KEYS } from "../lib/query-keys";
16
18
 
17
19
  /**
@@ -23,7 +25,15 @@ export async function listPrompts(client: Client): Promise<ListPromptsResult> {
23
25
  if (!capabilities?.prompts) {
24
26
  return { prompts: [] };
25
27
  }
26
- return await client.listPrompts();
28
+
29
+ try {
30
+ return await client.listPrompts();
31
+ } catch (error) {
32
+ if (error instanceof McpError && error.code === ErrorCode.MethodNotFound) {
33
+ return { prompts: [] };
34
+ }
35
+ throw error;
36
+ }
27
37
  }
28
38
 
29
39
  /**
@@ -1,4 +1,10 @@
1
1
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import {
3
+ ErrorCode,
4
+ McpError,
5
+ type ListResourcesResult,
6
+ type ReadResourceResult,
7
+ } from "@modelcontextprotocol/sdk/types.js";
2
8
  import {
3
9
  useQuery,
4
10
  useSuspenseQuery,
@@ -7,10 +13,6 @@ import {
7
13
  type UseSuspenseQueryOptions,
8
14
  type UseSuspenseQueryResult,
9
15
  } from "@tanstack/react-query";
10
- import type {
11
- ListResourcesResult,
12
- ReadResourceResult,
13
- } from "@modelcontextprotocol/sdk/types.js";
14
16
  import { KEYS } from "../lib/query-keys";
15
17
 
16
18
  /**
@@ -24,7 +26,15 @@ export async function listResources(
24
26
  if (!capabilities?.resources) {
25
27
  return { resources: [] };
26
28
  }
27
- return await client.listResources();
29
+
30
+ try {
31
+ return await client.listResources();
32
+ } catch (error) {
33
+ if (error instanceof McpError && error.code === ErrorCode.MethodNotFound) {
34
+ return { resources: [] };
35
+ }
36
+ throw error;
37
+ }
28
38
  }
29
39
 
30
40
  /**
package/src/index.ts CHANGED
@@ -2,11 +2,16 @@
2
2
  export {
3
3
  ProjectContextProvider,
4
4
  useProjectContext,
5
+ useOrg,
6
+ useCurrentProject,
7
+ useIsOrgAdmin,
5
8
  Locator,
6
- ORG_ADMIN_PROJECT_SLUG,
7
9
  type ProjectContextProviderProps,
8
10
  type ProjectLocator,
9
11
  type LocatorStructured,
12
+ type OrganizationData,
13
+ type ProjectData,
14
+ type ProjectUI,
10
15
  } from "./context";
11
16
 
12
17
  // Hooks
@@ -15,9 +20,14 @@ export {
15
20
  useCollectionItem,
16
21
  useCollectionList,
17
22
  useCollectionActions,
23
+ buildWhereExpression,
24
+ buildOrderByExpression,
25
+ buildCollectionQueryKey,
26
+ EMPTY_COLLECTION_LIST_RESULT,
18
27
  type CollectionEntity,
19
28
  type CollectionFilter,
20
29
  type UseCollectionListOptions,
30
+ type CollectionQueryKey,
21
31
  // Connection hooks
22
32
  useConnections,
23
33
  useConnection,
@@ -27,8 +37,10 @@ export {
27
37
  // MCP client hook and factory
28
38
  createMCPClient,
29
39
  useMCPClient,
40
+ useMCPClientOptional,
30
41
  type CreateMcpClientOptions,
31
42
  type UseMcpClientOptions,
43
+ type UseMcpClientOptionalOptions,
32
44
  // MCP tools hooks
33
45
  useMCPToolsList,
34
46
  useMCPToolsListQuery,
@@ -68,6 +80,17 @@ export {
68
80
 
69
81
  // Types
70
82
  export {
83
+ // AI Provider types
84
+ PROVIDER_IDS,
85
+ MODEL_CAPABILITIES,
86
+ type ProviderId,
87
+ type ModelCapability,
88
+ type AiProviderModel,
89
+ type AiProviderModelLimits,
90
+ type AiProviderModelCosts,
91
+ type AiProviderKey,
92
+ type AiProviderInfo,
93
+ // Connection types
71
94
  ConnectionEntitySchema,
72
95
  ConnectionCreateDataSchema,
73
96
  ConnectionUpdateDataSchema,
@@ -90,20 +113,58 @@ export {
90
113
  type VirtualMCPCreateData,
91
114
  type VirtualMCPUpdateData,
92
115
  type VirtualMCPConnection,
93
- type ToolSelectionMode,
116
+ // Decopilot event types
117
+ THREAD_STATUSES,
118
+ THREAD_DISPLAY_STATUSES,
119
+ DECOPILOT_EVENTS,
120
+ ALL_DECOPILOT_EVENT_TYPES,
121
+ createDecopilotStepEvent,
122
+ createDecopilotFinishEvent,
123
+ createDecopilotThreadStatusEvent,
124
+ type ThreadStatus,
125
+ type ThreadDisplayStatus,
126
+ type DecopilotEventType,
127
+ type DecopilotStepEvent,
128
+ type DecopilotFinishEvent,
129
+ type DecopilotThreadStatusEvent,
130
+ type DecopilotSSEEvent,
131
+ type DecopilotEventMap,
94
132
  } from "./types";
95
133
 
96
134
  // Streamable HTTP transport
97
135
  export { StreamableHTTPClientTransport } from "./lib/streamable-http-client-transport";
98
136
 
137
+ // Bridge transport
138
+ export {
139
+ createBridgeTransportPair,
140
+ BridgeClientTransport,
141
+ BridgeServerTransport,
142
+ type BridgeTransportPair,
143
+ } from "./lib/bridge-transport";
144
+
145
+ // Server-client bridge
146
+ export {
147
+ createServerFromClient,
148
+ type ServerFromClientOptions,
149
+ } from "./lib/server-client-bridge";
150
+
99
151
  // Query keys
100
152
  export { KEYS } from "./lib/query-keys";
101
153
 
154
+ // Default model selection
155
+ export {
156
+ DEFAULT_MODEL_PREFERENCES,
157
+ FAST_MODEL_PREFERENCES,
158
+ selectDefaultModel,
159
+ getFastModel,
160
+ } from "./lib/default-model";
161
+
102
162
  // MCP OAuth utilities
103
163
  export {
104
164
  authenticateMcp,
105
165
  handleOAuthCallback,
106
166
  isConnectionAuthenticated,
167
+ setOAuthRedirectOrigin,
107
168
  type McpOAuthProviderOptions,
108
169
  type OAuthTokenInfo,
109
170
  type AuthenticateMcpResult,
@@ -111,18 +172,36 @@ export {
111
172
  type OAuthWindowMode,
112
173
  } from "./lib/mcp-oauth";
113
174
 
175
+ // Usage utilities
176
+ export {
177
+ getCostFromUsage,
178
+ emptyUsageStats,
179
+ addUsage,
180
+ calculateUsageStats,
181
+ sanitizeProviderMetadata,
182
+ type UsageData,
183
+ type UsageStats,
184
+ } from "./lib/usage";
185
+
114
186
  // Constants and well-known MCP definitions
115
187
  export {
116
188
  // Frontend self MCP ID
117
189
  SELF_MCP_ALIAS_ID,
190
+ // Frontend dev-assets MCP ID
191
+ DEV_ASSETS_MCP_ALIAS_ID,
118
192
  // Org-scoped MCP ID generators
119
193
  WellKnownOrgMCPId,
120
194
  // Connection factory functions
121
195
  getWellKnownRegistryConnection,
122
196
  getWellKnownCommunityRegistryConnection,
123
197
  getWellKnownSelfConnection,
198
+ getWellKnownDevAssetsConnection,
124
199
  getWellKnownOpenRouterConnection,
125
200
  getWellKnownMcpStudioConnection,
126
201
  // Virtual MCP factory functions
127
- getWellKnownDecopilotAgent,
202
+ getWellKnownDecopilotVirtualMCP,
203
+ getWellKnownDecopilotConnection,
204
+ // Decopilot utilities
205
+ isDecopilot,
206
+ getDecopilotId,
128
207
  } from "./lib/constants";
@@ -0,0 +1,368 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
5
+ import {
6
+ createBridgeTransportPair,
7
+ BridgeClientTransport,
8
+ BridgeServerTransport,
9
+ } from "./bridge-transport";
10
+
11
+ /** Poll until `condition` returns true, checking every 5ms up to `timeoutMs`. */
12
+ async function waitFor(
13
+ condition: () => boolean,
14
+ timeoutMs = 200,
15
+ ): Promise<void> {
16
+ const start = Date.now();
17
+ while (!condition()) {
18
+ if (Date.now() - start > timeoutMs) return;
19
+ await new Promise((resolve) => setTimeout(resolve, 5));
20
+ }
21
+ }
22
+
23
+ describe("BridgeTransport", () => {
24
+ describe("createBridgeTransportPair", () => {
25
+ it("should create a pair of transports", () => {
26
+ const { client, server, channel } = createBridgeTransportPair();
27
+
28
+ expect(client).toBeInstanceOf(BridgeClientTransport);
29
+ expect(server).toBeInstanceOf(BridgeServerTransport);
30
+ expect(channel).toBeDefined();
31
+ });
32
+
33
+ it("should create transports with microtask scheduling", () => {
34
+ const { client, server } = createBridgeTransportPair();
35
+ expect(client).toBeDefined();
36
+ expect(server).toBeDefined();
37
+ });
38
+ });
39
+
40
+ describe("message delivery", () => {
41
+ it("should deliver messages in order client->server", async () => {
42
+ const { client, server } = createBridgeTransportPair();
43
+
44
+ const receivedMessages: JSONRPCMessage[] = [];
45
+
46
+ await server.start();
47
+ server.onmessage = (message) => {
48
+ receivedMessages.push(message);
49
+ };
50
+
51
+ await client.start();
52
+
53
+ const msg1: JSONRPCMessage = {
54
+ jsonrpc: "2.0",
55
+ id: 1,
56
+ method: "test",
57
+ params: { foo: "bar" },
58
+ };
59
+ const msg2: JSONRPCMessage = {
60
+ jsonrpc: "2.0",
61
+ id: 2,
62
+ method: "test2",
63
+ params: { baz: "qux" },
64
+ };
65
+
66
+ await client.send(msg1);
67
+ await client.send(msg2);
68
+
69
+ // Wait for microtask to process (or sync mode processes immediately)
70
+ await new Promise((resolve) => queueMicrotask(resolve));
71
+
72
+ expect(receivedMessages).toHaveLength(2);
73
+ expect(receivedMessages[0]).toEqual(msg1);
74
+ expect(receivedMessages[1]).toEqual(msg2);
75
+ });
76
+
77
+ it("should deliver messages in order server->client", async () => {
78
+ const { client, server } = createBridgeTransportPair();
79
+
80
+ const receivedMessages: JSONRPCMessage[] = [];
81
+
82
+ await client.start();
83
+ client.onmessage = (message) => {
84
+ receivedMessages.push(message);
85
+ };
86
+
87
+ await server.start();
88
+
89
+ const msg1: JSONRPCMessage = {
90
+ jsonrpc: "2.0",
91
+ id: 1,
92
+ method: "test",
93
+ params: { foo: "bar" },
94
+ };
95
+ const msg2: JSONRPCMessage = {
96
+ jsonrpc: "2.0",
97
+ id: 2,
98
+ method: "test2",
99
+ params: { baz: "qux" },
100
+ };
101
+
102
+ await server.send(msg1);
103
+ await server.send(msg2);
104
+
105
+ // Wait for microtask to process (or sync mode processes immediately)
106
+ await new Promise((resolve) => queueMicrotask(resolve));
107
+
108
+ expect(receivedMessages).toHaveLength(2);
109
+ expect(receivedMessages[0]).toEqual(msg1);
110
+ expect(receivedMessages[1]).toEqual(msg2);
111
+ });
112
+
113
+ it("should batch multiple messages in a single microtask", async () => {
114
+ const { client, server } = createBridgeTransportPair();
115
+
116
+ const receivedMessages: JSONRPCMessage[] = [];
117
+ let flushCount = 0;
118
+
119
+ await server.start();
120
+ server.onmessage = (message) => {
121
+ receivedMessages.push(message);
122
+ flushCount++;
123
+ };
124
+
125
+ await client.start();
126
+
127
+ // Send multiple messages synchronously
128
+ await client.send({ jsonrpc: "2.0", id: 1, method: "test1" });
129
+ await client.send({ jsonrpc: "2.0", id: 2, method: "test2" });
130
+ await client.send({ jsonrpc: "2.0", id: 3, method: "test3" });
131
+
132
+ // Wait for microtask to process
133
+ await new Promise((resolve) => setTimeout(resolve, 10));
134
+
135
+ expect(receivedMessages).toHaveLength(3);
136
+ // All messages should be delivered in a single flush
137
+ expect(flushCount).toBeGreaterThanOrEqual(1);
138
+ });
139
+ });
140
+
141
+ describe("start()", () => {
142
+ it("should allow starting client transport", async () => {
143
+ const { client } = createBridgeTransportPair();
144
+ await expect(client.start()).resolves.toBeUndefined();
145
+ });
146
+
147
+ it("should allow starting server transport", async () => {
148
+ const { server } = createBridgeTransportPair();
149
+ await expect(server.start()).resolves.toBeUndefined();
150
+ });
151
+
152
+ it("should throw if started twice", async () => {
153
+ const { client } = createBridgeTransportPair();
154
+ await client.start();
155
+ await expect(client.start()).rejects.toThrow("already started");
156
+ });
157
+ });
158
+
159
+ describe("close()", () => {
160
+ it("should close client transport and notify server", async () => {
161
+ const { client, server } = createBridgeTransportPair();
162
+
163
+ await client.start();
164
+ await server.start();
165
+
166
+ let serverClosed = false;
167
+ server.onclose = () => {
168
+ serverClosed = true;
169
+ };
170
+
171
+ await client.close();
172
+
173
+ expect(serverClosed).toBe(true);
174
+ });
175
+
176
+ it("should close server transport and notify client", async () => {
177
+ const { client, server } = createBridgeTransportPair();
178
+
179
+ await client.start();
180
+ await server.start();
181
+
182
+ let clientClosed = false;
183
+ client.onclose = () => {
184
+ clientClosed = true;
185
+ };
186
+
187
+ await server.close();
188
+
189
+ expect(clientClosed).toBe(true);
190
+ });
191
+
192
+ it("should prevent sending messages after close", async () => {
193
+ const { client, server } = createBridgeTransportPair();
194
+
195
+ await client.start();
196
+ await server.start();
197
+
198
+ const receivedMessages: JSONRPCMessage[] = [];
199
+ server.onmessage = (msg) => receivedMessages.push(msg);
200
+
201
+ // Send first message and wait for delivery
202
+ await client.send({ jsonrpc: "2.0", id: 1, method: "test" });
203
+ await waitFor(() => receivedMessages.length >= 1);
204
+
205
+ expect(receivedMessages.length).toBeGreaterThanOrEqual(1);
206
+ const initialCount = receivedMessages.length;
207
+
208
+ // Close client
209
+ await client.close();
210
+
211
+ // Try to send after close (should be silent no-op)
212
+ await client.send({ jsonrpc: "2.0", id: 2, method: "test2" });
213
+
214
+ // Wait a bit to ensure no delivery
215
+ await new Promise((resolve) => setTimeout(resolve, 50));
216
+
217
+ // Should not receive new messages after close
218
+ expect(receivedMessages.length).toBe(initialCount);
219
+ });
220
+
221
+ it("should fire onclose exactly once", async () => {
222
+ const { client } = createBridgeTransportPair();
223
+
224
+ await client.start();
225
+
226
+ let closeCount = 0;
227
+ client.onclose = () => {
228
+ closeCount++;
229
+ };
230
+
231
+ await client.close();
232
+ await client.close(); // Try closing again
233
+
234
+ expect(closeCount).toBe(1);
235
+ });
236
+ });
237
+
238
+ describe("error handling", () => {
239
+ it("should catch and forward errors from onmessage handler", async () => {
240
+ const { client, server } = createBridgeTransportPair();
241
+
242
+ await server.start();
243
+ await client.start();
244
+
245
+ const errors: Error[] = [];
246
+ server.onerror = (error) => {
247
+ errors.push(error);
248
+ };
249
+
250
+ server.onmessage = () => {
251
+ throw new Error("Test error");
252
+ };
253
+
254
+ await client.send({ jsonrpc: "2.0", id: 1, method: "test" });
255
+
256
+ // Wait for microtask to process and error to be forwarded
257
+ await waitFor(() => errors.length >= 1);
258
+
259
+ expect(errors).toHaveLength(1);
260
+ expect(errors[0].message).toBe("Test error");
261
+ });
262
+
263
+ it("should continue processing messages after error", async () => {
264
+ const { client, server } = createBridgeTransportPair();
265
+
266
+ await server.start();
267
+ await client.start();
268
+
269
+ const receivedMessages: JSONRPCMessage[] = [];
270
+ const errors: Error[] = [];
271
+
272
+ let callCount = 0;
273
+ server.onmessage = (msg) => {
274
+ callCount++;
275
+ if (callCount === 1) {
276
+ throw new Error("First message error");
277
+ }
278
+ receivedMessages.push(msg);
279
+ };
280
+
281
+ server.onerror = (error) => {
282
+ errors.push(error);
283
+ };
284
+
285
+ await client.send({ jsonrpc: "2.0", id: 1, method: "test1" });
286
+ await client.send({ jsonrpc: "2.0", id: 2, method: "test2" });
287
+
288
+ // Wait for microtask to process
289
+ await new Promise((resolve) => setTimeout(resolve, 10));
290
+
291
+ expect(errors).toHaveLength(1);
292
+ expect(receivedMessages).toHaveLength(1);
293
+ expect(receivedMessages[0].id).toBe(2);
294
+ });
295
+ });
296
+
297
+ describe("integration with MCP SDK", () => {
298
+ it("should work with MCP Client and Server", async () => {
299
+ const { client: clientTransport, server: serverTransport } =
300
+ createBridgeTransportPair();
301
+
302
+ const client = new Client({ name: "test-client", version: "1.0.0" });
303
+ const server = new Server({ name: "test-server", version: "1.0.0" });
304
+
305
+ // Connect server first
306
+ await server.connect(serverTransport);
307
+
308
+ // Connect client
309
+ await client.connect(clientTransport);
310
+
311
+ // Verify connection is established
312
+ expect(clientTransport.started).toBe(true);
313
+ expect(serverTransport.started).toBe(true);
314
+
315
+ // Clean up
316
+ await client.close();
317
+ await server.close();
318
+ });
319
+
320
+ it("should handle initialize handshake", async () => {
321
+ const { client: clientTransport, server: serverTransport } =
322
+ createBridgeTransportPair();
323
+
324
+ const client = new Client({ name: "test-client", version: "1.0.0" });
325
+ const server = new Server({ name: "test-server", version: "1.0.0" });
326
+
327
+ await server.connect(serverTransport);
328
+ await client.connect(clientTransport);
329
+
330
+ // Wait for initialization
331
+ await new Promise((resolve) => setTimeout(resolve, 100));
332
+
333
+ // Verify connection is established (client should have received initialize response)
334
+ expect(clientTransport.started).toBe(true);
335
+ expect(serverTransport.started).toBe(true);
336
+
337
+ await client.close();
338
+ await server.close();
339
+ });
340
+ });
341
+
342
+ describe("send() before start()", () => {
343
+ it("should allow sending messages before start (messages may be queued)", async () => {
344
+ const { client, server } = createBridgeTransportPair();
345
+
346
+ // Send message before starting - should not throw
347
+ await expect(
348
+ client.send({ jsonrpc: "2.0", id: 1, method: "test1" }),
349
+ ).resolves.toBeUndefined();
350
+
351
+ // Set handler and start
352
+ const receivedMessages: JSONRPCMessage[] = [];
353
+ server.onmessage = (msg) => receivedMessages.push(msg);
354
+ await server.start();
355
+ await client.start();
356
+
357
+ // Send another message after start to verify normal operation
358
+ await client.send({ jsonrpc: "2.0", id: 2, method: "test2" });
359
+
360
+ // Wait for delivery
361
+ await new Promise((resolve) => setTimeout(resolve, 20));
362
+
363
+ // At least the message sent after start should be delivered
364
+ // (messages sent before start may or may not be delivered depending on timing)
365
+ expect(receivedMessages.length).toBeGreaterThanOrEqual(1);
366
+ });
367
+ });
368
+ });