@webless/agent 0.10.4 → 0.12.0

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/dist/embed.d.cts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Root } from 'react-dom/client';
2
- import { u as AgentTagManifest, N as NormalizedAgentTagManifest } from './panel-controller-rxR75tRa.cjs';
3
- export { e as AgentIndexVersion, j as AgentMountStrategy, k as AgentPanelSubmitOptions, l as AgentPlacementConfig, s as AgentTabSide, t as AgentTabVariant, J as DEFAULT_AGENT_PLACEMENT, a1 as normalizeAgentPlacement, a2 as normalizeAgentTagManifest, a4 as resetAgentPanel, a5 as submitAgentPanel } from './panel-controller-rxR75tRa.cjs';
2
+ import { w as AgentTagManifest, O as NormalizedAgentTagManifest } from './panel-controller-CYoivbYB.cjs';
3
+ export { f as AgentIndexVersion, k as AgentMountStrategy, l as AgentPanelSubmitOptions, m as AgentPlacementConfig, u as AgentTabSide, v as AgentTabVariant, L as DEFAULT_AGENT_PLACEMENT, a3 as normalizeAgentPlacement, a4 as normalizeAgentTagManifest, a6 as resetAgentPanel, a7 as submitAgentPanel } from './panel-controller-CYoivbYB.cjs';
4
+ import 'zod';
4
5
  import 'react';
5
6
 
6
7
  type AgentRuntimeHandle = {
package/dist/embed.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Root } from 'react-dom/client';
2
- import { u as AgentTagManifest, N as NormalizedAgentTagManifest } from './panel-controller-rxR75tRa.js';
3
- export { e as AgentIndexVersion, j as AgentMountStrategy, k as AgentPanelSubmitOptions, l as AgentPlacementConfig, s as AgentTabSide, t as AgentTabVariant, J as DEFAULT_AGENT_PLACEMENT, a1 as normalizeAgentPlacement, a2 as normalizeAgentTagManifest, a4 as resetAgentPanel, a5 as submitAgentPanel } from './panel-controller-rxR75tRa.js';
2
+ import { w as AgentTagManifest, O as NormalizedAgentTagManifest } from './panel-controller-CYoivbYB.js';
3
+ export { f as AgentIndexVersion, k as AgentMountStrategy, l as AgentPanelSubmitOptions, m as AgentPlacementConfig, u as AgentTabSide, v as AgentTabVariant, L as DEFAULT_AGENT_PLACEMENT, a3 as normalizeAgentPlacement, a4 as normalizeAgentTagManifest, a6 as resetAgentPanel, a7 as submitAgentPanel } from './panel-controller-CYoivbYB.js';
4
+ import 'zod';
4
5
  import 'react';
5
6
 
6
7
  type AgentRuntimeHandle = {
package/dist/embed.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resetAgentPanel,
10
10
  resolveAgentRuntimeConfig,
11
11
  submitAgentPanel
12
- } from "./chunk-74CSWSC5.js";
12
+ } from "./chunk-EVWZF34F.js";
13
13
 
14
14
  // src/embed/mount.tsx
15
15
  import { createRoot } from "react-dom/client";
package/dist/index.cjs CHANGED
@@ -182,16 +182,16 @@ function parseStep(value) {
182
182
  if (!hasOnlyKeys(value, ["description", "fieldPaths", "id", "label"])) {
183
183
  return null;
184
184
  }
185
- const id = boundedString(value.id, 80);
185
+ const id2 = boundedString(value.id, 80);
186
186
  const label = boundedString(value.label, 160);
187
187
  const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
188
- if (!id || !/^[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(id) || !label || value.description !== void 0 && !description || !Array.isArray(value.fieldPaths) || value.fieldPaths.length < 1 || value.fieldPaths.length > 32 || value.fieldPaths.some(
188
+ if (!id2 || !/^[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(id2) || !label || value.description !== void 0 && !description || !Array.isArray(value.fieldPaths) || value.fieldPaths.length < 1 || value.fieldPaths.length > 32 || value.fieldPaths.some(
189
189
  (path) => typeof path !== "string" || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || path.length > 160
190
190
  )) {
191
191
  return null;
192
192
  }
193
193
  return {
194
- id,
194
+ id: id2,
195
195
  label,
196
196
  fieldPaths: value.fieldPaths,
197
197
  ...description ? { description } : {}
@@ -228,14 +228,14 @@ function parseAgentToolUiSurface(value) {
228
228
  ])) {
229
229
  return null;
230
230
  }
231
- const id = boundedString(value.id, 200);
231
+ const id2 = boundedString(value.id, 200);
232
232
  const title = boundedString(value.title, 200);
233
233
  const toolSlug = boundedString(value.toolSlug, 200);
234
234
  const description = value.description === void 0 ? void 0 : boundedString(value.description, 800);
235
235
  const operationId = value.operationId === void 0 ? void 0 : boundedString(value.operationId, 200);
236
236
  const requestId = value.requestId === void 0 ? void 0 : boundedString(value.requestId, 200);
237
237
  const submitLabel = value.submitLabel === void 0 ? void 0 : boundedString(value.submitLabel, 80);
238
- if (!id || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
238
+ if (!id2 || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
239
239
  return null;
240
240
  }
241
241
  const fields = value.fields.map(parseField);
@@ -255,7 +255,7 @@ function parseAgentToolUiSurface(value) {
255
255
  }
256
256
  return {
257
257
  schemaVersion: AGENT_TOOL_UI_SCHEMA_VERSION,
258
- id,
258
+ id: id2,
259
259
  title,
260
260
  toolSlug,
261
261
  fields,
@@ -366,8 +366,203 @@ function completeConnectedToolWork(item, result) {
366
366
  }
367
367
 
368
368
  // src/runtime/client.ts
369
+ var import_client3 = require("eve/client");
370
+
371
+ // src/runtime/handoff.ts
369
372
  var import_client2 = require("eve/client");
370
373
 
374
+ // src/runtime/generated/handoff-contract.ts
375
+ var import_zod = require("zod");
376
+ var id = import_zod.z.string().min(1).max(200);
377
+ var sequence = import_zod.z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
378
+ var text = import_zod.z.string().trim().min(1).max(16e3);
379
+ var agentRuntimeHandoffVersion = "webless.ai/agent-runtime-handoff/v1";
380
+ var agentRuntimeHandoffStatusSchema = import_zod.z.enum([
381
+ "ai",
382
+ "requesting",
383
+ "queued",
384
+ "human",
385
+ "resolved",
386
+ "failed"
387
+ ]);
388
+ var agentRuntimeHandoffActorSchema = import_zod.z.strictObject({
389
+ id,
390
+ name: import_zod.z.string().trim().min(1).max(200)
391
+ });
392
+ var eventBase = {
393
+ id,
394
+ sequence: sequence.refine((value) => value > 0),
395
+ handoffId: id,
396
+ epoch: sequence.refine((value) => value > 0),
397
+ createdAt: import_zod.z.iso.datetime()
398
+ };
399
+ var agentRuntimeHandoffEventSchema = import_zod.z.discriminatedUnion("type", [
400
+ import_zod.z.strictObject({
401
+ ...eventBase,
402
+ type: import_zod.z.literal("status"),
403
+ status: agentRuntimeHandoffStatusSchema,
404
+ actor: agentRuntimeHandoffActorSchema.nullable()
405
+ }),
406
+ import_zod.z.strictObject({
407
+ ...eventBase,
408
+ type: import_zod.z.literal("visitor.message"),
409
+ message: text,
410
+ operationId: id
411
+ }),
412
+ import_zod.z.strictObject({
413
+ ...eventBase,
414
+ type: import_zod.z.literal("human.message"),
415
+ message: text,
416
+ actor: agentRuntimeHandoffActorSchema
417
+ })
418
+ ]);
419
+ var agentRuntimeHandoffReadRequestSchema = import_zod.z.strictObject({
420
+ after: sequence.default(0)
421
+ });
422
+ var agentRuntimeHandoffPageSchema = import_zod.z.strictObject({
423
+ apiVersion: import_zod.z.literal(agentRuntimeHandoffVersion),
424
+ sessionId: id,
425
+ enabled: import_zod.z.boolean(),
426
+ status: agentRuntimeHandoffStatusSchema,
427
+ handoffId: id.nullable(),
428
+ epoch: sequence,
429
+ after: sequence,
430
+ cursor: sequence,
431
+ hasMore: import_zod.z.boolean(),
432
+ events: import_zod.z.array(agentRuntimeHandoffEventSchema).max(100)
433
+ }).superRefine((page, ctx) => {
434
+ let cursor = page.after;
435
+ let previousEpoch = 0;
436
+ const statuses = /* @__PURE__ */ new Map();
437
+ const ids = /* @__PURE__ */ new Set();
438
+ const handoffs = /* @__PURE__ */ new Map();
439
+ const terminalEpochs = /* @__PURE__ */ new Set();
440
+ let currentStatus;
441
+ for (const event of page.events) {
442
+ if (event.epoch < previousEpoch)
443
+ ctx.addIssue({
444
+ code: "custom",
445
+ message: "Handoff epochs cannot decrease."
446
+ });
447
+ previousEpoch = event.epoch;
448
+ if (ids.has(event.id))
449
+ ctx.addIssue({
450
+ code: "custom",
451
+ message: "Duplicate handoff event ID."
452
+ });
453
+ ids.add(event.id);
454
+ const handoffId = handoffs.get(event.epoch);
455
+ if (handoffId !== void 0 && handoffId !== event.handoffId || event.epoch === page.epoch && event.handoffId !== page.handoffId)
456
+ ctx.addIssue({
457
+ code: "custom",
458
+ message: "Inconsistent handoff epoch identity."
459
+ });
460
+ handoffs.set(event.epoch, event.handoffId);
461
+ if (terminalEpochs.has(event.epoch) && (event.type !== "status" || event.status !== "ai"))
462
+ ctx.addIssue({
463
+ code: "custom",
464
+ message: "Handoff event follows resolution."
465
+ });
466
+ if (event.type === "status") {
467
+ if (event.status === "human" && event.actor === null)
468
+ ctx.addIssue({
469
+ code: "custom",
470
+ message: "Human ownership requires a representative."
471
+ });
472
+ const previous = statuses.get(event.epoch);
473
+ const transitions = {
474
+ ai: [],
475
+ requesting: ["queued", "resolved", "failed"],
476
+ queued: ["human", "resolved", "failed"],
477
+ human: ["resolved", "failed"],
478
+ resolved: ["ai"],
479
+ failed: []
480
+ };
481
+ if (previous !== void 0 && !transitions[previous].includes(event.status))
482
+ ctx.addIssue({
483
+ code: "custom",
484
+ message: "Invalid handoff ownership transition."
485
+ });
486
+ statuses.set(event.epoch, event.status);
487
+ if (["resolved", "failed", "ai"].includes(event.status))
488
+ terminalEpochs.add(event.epoch);
489
+ if (event.epoch === page.epoch) currentStatus = event.status;
490
+ }
491
+ if (event.sequence !== cursor + 1) {
492
+ ctx.addIssue({
493
+ code: "custom",
494
+ message: "Handoff event gap or replay."
495
+ });
496
+ }
497
+ cursor = event.sequence;
498
+ if (event.epoch > page.epoch) {
499
+ ctx.addIssue({
500
+ code: "custom",
501
+ message: "Event exceeds current epoch."
502
+ });
503
+ }
504
+ }
505
+ if (!page.hasMore && currentStatus !== void 0 && currentStatus !== page.status)
506
+ ctx.addIssue({
507
+ code: "custom",
508
+ message: "Handoff ownership contradicts its final status event."
509
+ });
510
+ if (page.cursor !== cursor || page.hasMore && page.events.length === 0) {
511
+ ctx.addIssue({ code: "custom", message: "Invalid handoff page cursor." });
512
+ }
513
+ if (page.epoch === 0 !== (page.handoffId === null)) {
514
+ ctx.addIssue({ code: "custom", message: "Invalid handoff identity." });
515
+ }
516
+ if (page.handoffId === null && page.status !== "ai") {
517
+ ctx.addIssue({
518
+ code: "custom",
519
+ message: "Handoff identity is required."
520
+ });
521
+ }
522
+ });
523
+ var agentRuntimeHandoffStartSchema = import_zod.z.strictObject({
524
+ operationId: id
525
+ });
526
+ var agentRuntimeHandoffCommandSchema = import_zod.z.strictObject({
527
+ operationId: id,
528
+ handoffId: id,
529
+ epoch: sequence.refine((value) => value > 0)
530
+ });
531
+ var agentRuntimeHandoffMessageSchema = agentRuntimeHandoffCommandSchema.extend({ message: text });
532
+ var agentRuntimeHandoffBindingSchema = import_zod.z.strictObject({
533
+ tenantId: id,
534
+ indexId: id,
535
+ visitorSubject: id,
536
+ sessionId: id,
537
+ handoffId: id,
538
+ epoch: sequence.refine((value) => value > 0)
539
+ });
540
+ var providerEventBase = {
541
+ apiVersion: import_zod.z.literal(agentRuntimeHandoffVersion),
542
+ binding: agentRuntimeHandoffBindingSchema,
543
+ eventId: id,
544
+ sequence: sequence.refine((value) => value > 0)
545
+ };
546
+ var agentRuntimeHandoffProviderEventSchema = import_zod.z.discriminatedUnion(
547
+ "type",
548
+ [
549
+ import_zod.z.strictObject({ ...providerEventBase, type: import_zod.z.literal("queued") }),
550
+ import_zod.z.strictObject({
551
+ ...providerEventBase,
552
+ type: import_zod.z.literal("assigned"),
553
+ actor: agentRuntimeHandoffActorSchema
554
+ }),
555
+ import_zod.z.strictObject({
556
+ ...providerEventBase,
557
+ type: import_zod.z.literal("human.message"),
558
+ actor: agentRuntimeHandoffActorSchema,
559
+ message: text
560
+ }),
561
+ import_zod.z.strictObject({ ...providerEventBase, type: import_zod.z.literal("resolved") }),
562
+ import_zod.z.strictObject({ ...providerEventBase, type: import_zod.z.literal("failed") })
563
+ ]
564
+ );
565
+
371
566
  // src/runtime/capability.ts
372
567
  var import_client = require("eve/client");
373
568
  var MAX_REFRESH_SKEW_MS = 3e4;
@@ -437,18 +632,26 @@ function createAgentRuntimeCapability(options) {
437
632
  );
438
633
  }
439
634
  }
440
- const bootstrapBody = JSON.stringify({
635
+ const bootstrapBody = {
441
636
  clientSessionId: options.visitorSessionId,
442
637
  indexId: options.indexId,
443
638
  ...previewBuildId ? { previewBuildId } : {},
444
639
  ...previewGrant ? { previewGrant } : {},
445
640
  version: options.version
446
- });
447
- const postBootstrap = (origin) => fetchImplementation(`${origin}/webless/v1/bootstrap`, {
448
- body: bootstrapBody,
449
- headers: { "content-type": "application/json" },
450
- method: "POST"
451
- });
641
+ };
642
+ const postBootstrap = async (origin) => {
643
+ const send = (advertiseLocation) => fetchImplementation(`${origin}/webless/v1/bootstrap`, {
644
+ body: JSON.stringify({ ...bootstrapBody, ...advertiseLocation ? { locationConsent: "v1" } : {} }),
645
+ headers: { "content-type": "application/json" },
646
+ method: "POST"
647
+ });
648
+ const response2 = await send(Boolean(options.locationConsent));
649
+ if (options.locationConsent && response2.status === 400) {
650
+ const error = await response2.clone().json().catch(() => null);
651
+ if (isRecord3(error) && error.code === "invalid_request") return send(false);
652
+ }
653
+ return response2;
654
+ };
452
655
  let response;
453
656
  let lastError;
454
657
  for (const origin of localBootstrapOrigins(options.runtimeOrigin)) {
@@ -514,6 +717,75 @@ async function withCapabilityRefresh(capability, request) {
514
717
  }
515
718
  }
516
719
 
720
+ // src/runtime/handoff.ts
721
+ function createHandoffClient(options) {
722
+ const target = () => {
723
+ const sessionId = options.getSessionId();
724
+ if (!sessionId)
725
+ throw new Error(
726
+ "Start a conversation before requesting a representative."
727
+ );
728
+ return {
729
+ sessionId,
730
+ path: `/webless/v1/session/${encodeURIComponent(sessionId)}/handoff`
731
+ };
732
+ };
733
+ const request = (path, init) => withCapabilityRefresh(options.capability, async () => {
734
+ const response = await options.getClient().fetch(path, {
735
+ ...init,
736
+ cache: "no-store",
737
+ redirect: "error"
738
+ });
739
+ if (!response.ok) {
740
+ throw new import_client2.ClientError(
741
+ response.status,
742
+ await response.text(),
743
+ response.headers
744
+ );
745
+ }
746
+ return response;
747
+ });
748
+ const command = async (suffix, body, signal) => {
749
+ const { path } = target();
750
+ await request(path + suffix, {
751
+ method: "POST",
752
+ headers: { "content-type": "application/json" },
753
+ body: JSON.stringify(body),
754
+ signal
755
+ });
756
+ };
757
+ return {
758
+ async read(input = {}) {
759
+ const { after } = agentRuntimeHandoffReadRequestSchema.parse({
760
+ after: input.after
761
+ });
762
+ const { sessionId, path } = target();
763
+ const response = await request(`${path}?after=${after}`, {
764
+ signal: input.signal
765
+ });
766
+ const value = await response.json();
767
+ const page = agentRuntimeHandoffPageSchema.parse(value);
768
+ if (page.sessionId !== sessionId || page.after !== after) {
769
+ throw new Error(
770
+ "The handoff response does not belong to this conversation or cursor."
771
+ );
772
+ }
773
+ return page;
774
+ },
775
+ start: (operationId, signal) => command(
776
+ "",
777
+ agentRuntimeHandoffStartSchema.parse({ operationId }),
778
+ signal
779
+ ),
780
+ send: (input, signal) => command(
781
+ "/messages",
782
+ agentRuntimeHandoffMessageSchema.parse(input),
783
+ signal
784
+ ),
785
+ returnToAI: (input, signal) => command("/return", agentRuntimeHandoffCommandSchema.parse(input), signal)
786
+ };
787
+ }
788
+
517
789
  // src/runtime/config.ts
518
790
  var DEFAULT_RUNTIME_ORIGIN = "https://runtime.staging.webless.ai";
519
791
  var trimTrailingSlash = (value) => value.replace(/\/+$/, "");
@@ -992,6 +1264,7 @@ function specialistNameFromInput(input) {
992
1264
  return match?.[1]?.trim() || void 0;
993
1265
  }
994
1266
  function requestedWorkItem(action) {
1267
+ if (action.kind === "tool-call" && action.toolName === "request_location") return null;
995
1268
  if (action.kind === "tool-call" && action.toolName === "search_discovery") {
996
1269
  return {
997
1270
  id: action.callId,
@@ -1097,13 +1370,14 @@ function applyWorkEvent(event, handlers, workItems) {
1097
1370
  );
1098
1371
  }
1099
1372
  var AgentSession = class {
1100
- constructor(indexId, version, runtimeOrigin, visitorSessionId, storeOptions, previewBuildId, getUnpublishedPreviewGrant) {
1373
+ constructor(indexId, version, runtimeOrigin, visitorSessionId, storeOptions, previewBuildId, getUnpublishedPreviewGrant, locationConsent) {
1101
1374
  this.indexId = indexId;
1102
1375
  this.version = version;
1103
1376
  this.runtimeOrigin = runtimeOrigin;
1104
1377
  this.visitorSessionId = visitorSessionId;
1105
1378
  this.storeOptions = storeOptions;
1106
1379
  this.capability = createAgentRuntimeCapability({
1380
+ locationConsent,
1107
1381
  getUnpublishedPreviewGrant,
1108
1382
  indexId,
1109
1383
  previewBuildId,
@@ -1111,6 +1385,11 @@ var AgentSession = class {
1111
1385
  version,
1112
1386
  visitorSessionId
1113
1387
  });
1388
+ this.handoff = createHandoffClient({
1389
+ getClient: () => this.ensureClient(),
1390
+ getSessionId: () => this.getActiveSessionId(),
1391
+ capability: this.capability
1392
+ });
1114
1393
  }
1115
1394
  indexId;
1116
1395
  version;
@@ -1123,6 +1402,7 @@ var AgentSession = class {
1123
1402
  activeResponse;
1124
1403
  childStreams;
1125
1404
  capability;
1405
+ handoff;
1126
1406
  getActiveSessionId() {
1127
1407
  return this.session?.state.sessionId ?? loadPersistedAgentSession(this.visitorSessionId, this.storeOptions)?.sessionId;
1128
1408
  }
@@ -1197,7 +1477,7 @@ var AgentSession = class {
1197
1477
  }
1198
1478
  this.activeResponse = void 0;
1199
1479
  this.session = void 0;
1200
- this.client = new import_client2.Client({
1480
+ this.client = new import_client3.Client({
1201
1481
  auth: { bearer: () => this.capability.getAccessToken() },
1202
1482
  host: config.host,
1203
1483
  redirect: "error"
@@ -1233,7 +1513,7 @@ var AgentSession = class {
1233
1513
  () => activeSession.send(message, { signal })
1234
1514
  );
1235
1515
  } catch (error) {
1236
- if (error instanceof import_client2.ClientError && error.status === 409 && error.code === "session_not_active") {
1516
+ if (error instanceof import_client3.ClientError && error.status === 409 && error.code === "session_not_active") {
1237
1517
  clearPersistedAgentSession(this.visitorSessionId, this.storeOptions);
1238
1518
  this.session = void 0;
1239
1519
  session = void 0;
@@ -1407,10 +1687,10 @@ var AgentSession = class {
1407
1687
  }
1408
1688
  this.session = session;
1409
1689
  const inputResponses = responses.map(
1410
- ({ requestId, optionId, text }) => ({
1690
+ ({ requestId, optionId, text: text2 }) => ({
1411
1691
  requestId,
1412
1692
  ...optionId ? { optionId } : {},
1413
- ...text ? { text } : {}
1693
+ ...text2 ? { text: text2 } : {}
1414
1694
  })
1415
1695
  );
1416
1696
  const response = await withCapabilityRefresh(
@@ -1495,10 +1775,12 @@ function createAgentClient(options) {
1495
1775
  visitorSessionId,
1496
1776
  storeOptions,
1497
1777
  options.previewBuildId,
1498
- options.getUnpublishedPreviewGrant
1778
+ options.getUnpublishedPreviewGrant,
1779
+ options.locationConsent
1499
1780
  );
1500
1781
  return {
1501
1782
  indexId,
1783
+ handoff: session.handoff,
1502
1784
  version,
1503
1785
  runtimeOrigin,
1504
1786
  visitorSessionId,
@@ -1530,7 +1812,7 @@ function createAgentClient(options) {
1530
1812
  }
1531
1813
 
1532
1814
  // src/runtime/errors.ts
1533
- var import_client3 = require("eve/client");
1815
+ var import_client4 = require("eve/client");
1534
1816
  var TRANSIENT_AGENT_ERROR_MESSAGE = "The agent run stopped before the action finished. Please try again.";
1535
1817
  var PREVIEW_AUTHORIZATION_ERROR_MESSAGE = "This preview could not be authorized. Open the latest preview from Webless.";
1536
1818
  function isTransientRuntimeMessage(message) {
@@ -1542,7 +1824,7 @@ function isPreviewAuthorizationMessage(message) {
1542
1824
  return normalized.includes("unpublished preview authorization") || normalized.includes("unpublished preview grant") || normalized.includes("agent studio preview grant") || normalized.includes("preview authorization");
1543
1825
  }
1544
1826
  function formatAgentError(error) {
1545
- if (error instanceof import_client3.ClientError) {
1827
+ if (error instanceof import_client4.ClientError) {
1546
1828
  if (error.status === 401 && error.code === "index_required") {
1547
1829
  return "Missing indexId \u2014 pass a published index id to createAgentClient().";
1548
1830
  }