@cimplify/sdk 0.89.0 → 0.90.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.
Files changed (59) hide show
  1. package/dist/advanced.d.mts +3 -3
  2. package/dist/advanced.d.ts +3 -3
  3. package/dist/{chunk-DENWOZEA.mjs → chunk-2UDEWILL.mjs} +46 -8
  4. package/dist/{chunk-T2CJ67QV.js → chunk-BN4V4ONE.js} +46 -8
  5. package/dist/{chunk-JHJTXAPS.js → chunk-JVWFIFQ7.js} +4 -4
  6. package/dist/{chunk-ROBTTS3Q.js → chunk-KJP4U6L3.js} +28 -1
  7. package/dist/{chunk-M4ALCELT.mjs → chunk-NGE37REW.mjs} +2 -2
  8. package/dist/{chunk-APBLFZ2I.mjs → chunk-NNGKC3MM.mjs} +28 -1
  9. package/dist/chunk-U4MW6PYU.mjs +23 -0
  10. package/dist/chunk-XREIZ56K.js +26 -0
  11. package/dist/{client-CqZgdUtw.d.ts → client-CR1f94wY.d.ts} +81 -6
  12. package/dist/{client-CUbwb6Om.d.ts → client-CqpOcbin.d.ts} +2 -2
  13. package/dist/{client-CAY2NbRG.d.mts → client-DMttx1xk.d.mts} +2 -2
  14. package/dist/{client-D8ZFs-sq.d.mts → client-YiSelM7x.d.mts} +81 -6
  15. package/dist/{index-Bc5qUxAt.d.ts → index-CxTVu0HZ.d.ts} +3 -3
  16. package/dist/{index-CmWOz9bi.d.mts → index-DD9z396M.d.mts} +3 -3
  17. package/dist/index.d.mts +6 -6
  18. package/dist/index.d.ts +6 -6
  19. package/dist/index.js +17 -10
  20. package/dist/index.mjs +8 -2
  21. package/dist/mock/cli.mjs +49 -1
  22. package/dist/mock/library.js +49 -1
  23. package/dist/mock/library.mjs +49 -1
  24. package/dist/mock/msw.js +49 -1
  25. package/dist/mock/msw.mjs +49 -1
  26. package/dist/{payment-Dcgb9n1n.d.mts → payment-5d8FTA1q.d.mts} +1 -1
  27. package/dist/{payment-Xq0WDZXh.d.ts → payment-BX7ccIKY.d.ts} +1 -1
  28. package/dist/{price-DAAjKgAp.d.ts → price-DtfP_Xfq.d.ts} +1 -1
  29. package/dist/{price--Ma_WO5_.d.mts → price-arl_4Rzn.d.mts} +1 -1
  30. package/dist/{product-BakHcsP2.d.mts → product-DZX4QuKS.d.mts} +57 -2
  31. package/dist/{product-BakHcsP2.d.ts → product-DZX4QuKS.d.ts} +57 -2
  32. package/dist/react.d.mts +405 -7
  33. package/dist/react.d.ts +405 -7
  34. package/dist/react.js +2325 -69
  35. package/dist/react.mjs +2303 -67
  36. package/dist/{server-Bk_hlefX.d.mts → server-BceZMxcK.d.mts} +8 -1
  37. package/dist/{server-Bei5lBrc.d.ts → server-DoJGSC7L.d.ts} +8 -1
  38. package/dist/server.d.mts +4 -4
  39. package/dist/server.d.ts +4 -4
  40. package/dist/server.js +2 -2
  41. package/dist/server.mjs +1 -1
  42. package/dist/styles.css +1 -1
  43. package/dist/testing/msw.d.mts +2 -2
  44. package/dist/testing/msw.d.ts +2 -2
  45. package/dist/testing/msw.js +3 -2
  46. package/dist/testing/msw.mjs +2 -1
  47. package/dist/testing/suite.d.mts +6 -6
  48. package/dist/testing/suite.d.ts +6 -6
  49. package/dist/testing/suite.js +23 -22
  50. package/dist/testing/suite.mjs +4 -3
  51. package/dist/testing.d.mts +5 -5
  52. package/dist/testing.d.ts +5 -5
  53. package/dist/testing.js +79 -78
  54. package/dist/testing.mjs +5 -4
  55. package/dist/utils.d.mts +4 -4
  56. package/dist/utils.d.ts +4 -4
  57. package/package.json +4 -1
  58. package/registry/chat-widget.json +1 -1
  59. package/registry/product-customizer.json +1 -1
@@ -1,6 +1,6 @@
1
- export { k as AuthService, C as BusinessService, O as CartOperations, P as CatalogueQueries, a8 as CheckoutOperations, a8 as CheckoutService, ae as CimplifyElement, af as CimplifyElements, aG as ELEMENT_TYPES, aH as EVENT_TYPES, aK as ElementEventType, aL as ElementOptions, aM as ElementType, aU as ElementsOptions, b0 as FetchQuoteInput, b9 as FxService, bd as GetProductsOptions, bg as InventoryService, bo as LinkService, bs as LiteService, bz as MESSAGE_TYPES, b$ as OrderQueries, cl as PriceQuote, cr as QuoteBundleSelectionInput, cs as QuoteCompositeSelectionInput, ct as QuoteDynamicBuckets, cu as QuoteStatus, cv as QuoteUiMessage, cx as RefreshQuoteInput, cy as RefreshQuoteResult, c_ as SchedulingService, dE as createElements } from './client-D8ZFs-sq.mjs';
2
- import './product-BakHcsP2.mjs';
3
- import './payment-Dcgb9n1n.mjs';
1
+ export { k as AuthService, C as BusinessService, O as CartOperations, P as CatalogueQueries, ab as CheckoutOperations, ab as CheckoutService, ah as CimplifyElement, ai as CimplifyElements, aJ as ELEMENT_TYPES, aK as EVENT_TYPES, aN as ElementEventType, aO as ElementOptions, aP as ElementType, aX as ElementsOptions, b3 as FetchQuoteInput, bc as FxService, bg as GetProductsOptions, bj as InventoryService, br as LinkService, bx as LiteService, bE as MESSAGE_TYPES, c4 as OrderQueries, cq as PriceQuote, cw as QuoteBundleSelectionInput, cx as QuoteCompositeSelectionInput, cy as QuoteDynamicBuckets, cz as QuoteStatus, cA as QuoteUiMessage, cC as RefreshQuoteInput, cD as RefreshQuoteResult, d3 as SchedulingService, dK as createElements } from './client-YiSelM7x.mjs';
2
+ import './product-DZX4QuKS.mjs';
3
+ import './payment-5d8FTA1q.mjs';
4
4
 
5
5
  declare function generateIdempotencyKey(prefix?: string): string;
6
6
 
@@ -1,6 +1,6 @@
1
- export { k as AuthService, C as BusinessService, O as CartOperations, P as CatalogueQueries, a8 as CheckoutOperations, a8 as CheckoutService, ae as CimplifyElement, af as CimplifyElements, aG as ELEMENT_TYPES, aH as EVENT_TYPES, aK as ElementEventType, aL as ElementOptions, aM as ElementType, aU as ElementsOptions, b0 as FetchQuoteInput, b9 as FxService, bd as GetProductsOptions, bg as InventoryService, bo as LinkService, bs as LiteService, bz as MESSAGE_TYPES, b$ as OrderQueries, cl as PriceQuote, cr as QuoteBundleSelectionInput, cs as QuoteCompositeSelectionInput, ct as QuoteDynamicBuckets, cu as QuoteStatus, cv as QuoteUiMessage, cx as RefreshQuoteInput, cy as RefreshQuoteResult, c_ as SchedulingService, dE as createElements } from './client-CqZgdUtw.js';
2
- import './product-BakHcsP2.js';
3
- import './payment-Xq0WDZXh.js';
1
+ export { k as AuthService, C as BusinessService, O as CartOperations, P as CatalogueQueries, ab as CheckoutOperations, ab as CheckoutService, ah as CimplifyElement, ai as CimplifyElements, aJ as ELEMENT_TYPES, aK as EVENT_TYPES, aN as ElementEventType, aO as ElementOptions, aP as ElementType, aX as ElementsOptions, b3 as FetchQuoteInput, bc as FxService, bg as GetProductsOptions, bj as InventoryService, br as LinkService, bx as LiteService, bE as MESSAGE_TYPES, c4 as OrderQueries, cq as PriceQuote, cw as QuoteBundleSelectionInput, cx as QuoteCompositeSelectionInput, cy as QuoteDynamicBuckets, cz as QuoteStatus, cA as QuoteUiMessage, cC as RefreshQuoteInput, cD as RefreshQuoteResult, d3 as SchedulingService, dK as createElements } from './client-CR1f94wY.js';
2
+ import './product-DZX4QuKS.js';
3
+ import './payment-BX7ccIKY.js';
4
4
 
5
5
  declare function generateIdempotencyKey(prefix?: string): string;
6
6
 
@@ -312,26 +312,57 @@ var SupportService = class {
312
312
  *
313
313
  * The idempotency key doubles as the message `client_id`: the backend
314
314
  * dedupes on it, so client retries of the same send never insert twice.
315
+ * `replyToMessageId` quotes another message by its `ChatMessage.id`; the
316
+ * server resolves it thread-scoped and echoes the quote as `reply_to`.
315
317
  */
316
318
  async sendMessage(content, opts) {
317
319
  const idempotencyKey = opts?.idempotencyKey ?? generateIdempotencyKey();
318
320
  return safe(
319
321
  this.client.post(
320
322
  "/api/v1/support/conversation/messages",
321
- { content, client_id: idempotencyKey },
323
+ {
324
+ content,
325
+ client_id: idempotencyKey,
326
+ reply_to_message_id: opts?.replyToMessageId,
327
+ upload_ids: opts?.attachments?.map((a) => a.upload_id)
328
+ },
322
329
  { idempotencyKey }
323
330
  )
324
331
  );
325
332
  }
326
- /** Poll for new messages since the given cursor. */
333
+ /**
334
+ * Poll for messages. `after` pages forward (live updates); `before` pages
335
+ * backward for scrollback into older history.
336
+ */
327
337
  async getMessages(options = {}) {
328
338
  const params = new URLSearchParams();
329
339
  if (options.after) params.set("after", options.after);
340
+ if (options.before) params.set("before", options.before);
330
341
  if (options.limit) params.set("limit", String(options.limit));
331
342
  const query = params.toString();
332
343
  const path = query ? `/api/v1/support/conversation/messages?${query}` : "/api/v1/support/conversation/messages";
333
344
  return safe(this.client.get(path));
334
345
  }
346
+ /** React to a message in the active conversation by its `ChatMessage.id`. */
347
+ async reactToMessage(messageId, emoji) {
348
+ return safe(
349
+ this.client.post(
350
+ `/api/v1/support/conversation/messages/${messageId}/reactions`,
351
+ { emoji }
352
+ )
353
+ );
354
+ }
355
+ /** Ask for a person: flags the thread for the merchant's inbox. */
356
+ async requestHuman(opts) {
357
+ const idempotencyKey = opts?.idempotencyKey ?? generateIdempotencyKey();
358
+ return safe(
359
+ this.client.post(
360
+ "/api/v1/support/conversation/handoff",
361
+ void 0,
362
+ { idempotencyKey }
363
+ )
364
+ );
365
+ }
335
366
  };
336
367
 
337
368
  // src/internal/events.ts
@@ -658,9 +689,16 @@ var CimplifyClient = class {
658
689
  async authedFetch(url, options) {
659
690
  return this.resilientFetch(url, options);
660
691
  }
692
+ // Storage guards: even the `window.localStorage` property access throws in
693
+ // some sandboxed iframes and strict private-browsing modes. These run in the
694
+ // constructor at module load, so a throw would white-screen the whole app —
695
+ // fall back to an in-memory session token instead.
661
696
  loadSessionToken() {
662
- if (typeof window !== "undefined" && window.localStorage) {
663
- return localStorage.getItem(SESSION_TOKEN_STORAGE_KEY);
697
+ try {
698
+ if (typeof window !== "undefined" && window.localStorage) {
699
+ return localStorage.getItem(SESSION_TOKEN_STORAGE_KEY);
700
+ }
701
+ } catch {
664
702
  }
665
703
  return null;
666
704
  }
@@ -674,7 +712,7 @@ var CimplifyClient = class {
674
712
  * every visitor to one session, so SSR keeps the lazy server-minted flow.
675
713
  */
676
714
  mintSessionToken() {
677
- if (typeof window === "undefined" || !window.localStorage) {
715
+ if (typeof window === "undefined") {
678
716
  return null;
679
717
  }
680
718
  const token = generateSessionToken();
@@ -682,11 +720,11 @@ var CimplifyClient = class {
682
720
  return token;
683
721
  }
684
722
  saveSessionToken(token) {
685
- if (typeof window !== "undefined" && window.localStorage) {
686
- try {
723
+ try {
724
+ if (typeof window !== "undefined" && window.localStorage) {
687
725
  localStorage.setItem(SESSION_TOKEN_STORAGE_KEY, token);
688
- } catch {
689
726
  }
727
+ } catch {
690
728
  }
691
729
  }
692
730
  captureSessionToken(response) {
@@ -314,26 +314,57 @@ var SupportService = class {
314
314
  *
315
315
  * The idempotency key doubles as the message `client_id`: the backend
316
316
  * dedupes on it, so client retries of the same send never insert twice.
317
+ * `replyToMessageId` quotes another message by its `ChatMessage.id`; the
318
+ * server resolves it thread-scoped and echoes the quote as `reply_to`.
317
319
  */
318
320
  async sendMessage(content, opts) {
319
321
  const idempotencyKey = opts?.idempotencyKey ?? chunkGHFDYM5K_js.generateIdempotencyKey();
320
322
  return chunkGHFDYM5K_js.safe(
321
323
  this.client.post(
322
324
  "/api/v1/support/conversation/messages",
323
- { content, client_id: idempotencyKey },
325
+ {
326
+ content,
327
+ client_id: idempotencyKey,
328
+ reply_to_message_id: opts?.replyToMessageId,
329
+ upload_ids: opts?.attachments?.map((a) => a.upload_id)
330
+ },
324
331
  { idempotencyKey }
325
332
  )
326
333
  );
327
334
  }
328
- /** Poll for new messages since the given cursor. */
335
+ /**
336
+ * Poll for messages. `after` pages forward (live updates); `before` pages
337
+ * backward for scrollback into older history.
338
+ */
329
339
  async getMessages(options = {}) {
330
340
  const params = new URLSearchParams();
331
341
  if (options.after) params.set("after", options.after);
342
+ if (options.before) params.set("before", options.before);
332
343
  if (options.limit) params.set("limit", String(options.limit));
333
344
  const query = params.toString();
334
345
  const path = query ? `/api/v1/support/conversation/messages?${query}` : "/api/v1/support/conversation/messages";
335
346
  return chunkGHFDYM5K_js.safe(this.client.get(path));
336
347
  }
348
+ /** React to a message in the active conversation by its `ChatMessage.id`. */
349
+ async reactToMessage(messageId, emoji) {
350
+ return chunkGHFDYM5K_js.safe(
351
+ this.client.post(
352
+ `/api/v1/support/conversation/messages/${messageId}/reactions`,
353
+ { emoji }
354
+ )
355
+ );
356
+ }
357
+ /** Ask for a person: flags the thread for the merchant's inbox. */
358
+ async requestHuman(opts) {
359
+ const idempotencyKey = opts?.idempotencyKey ?? chunkGHFDYM5K_js.generateIdempotencyKey();
360
+ return chunkGHFDYM5K_js.safe(
361
+ this.client.post(
362
+ "/api/v1/support/conversation/handoff",
363
+ void 0,
364
+ { idempotencyKey }
365
+ )
366
+ );
367
+ }
337
368
  };
338
369
 
339
370
  // src/internal/events.ts
@@ -660,9 +691,16 @@ var CimplifyClient = class {
660
691
  async authedFetch(url, options) {
661
692
  return this.resilientFetch(url, options);
662
693
  }
694
+ // Storage guards: even the `window.localStorage` property access throws in
695
+ // some sandboxed iframes and strict private-browsing modes. These run in the
696
+ // constructor at module load, so a throw would white-screen the whole app —
697
+ // fall back to an in-memory session token instead.
663
698
  loadSessionToken() {
664
- if (typeof window !== "undefined" && window.localStorage) {
665
- return localStorage.getItem(SESSION_TOKEN_STORAGE_KEY);
699
+ try {
700
+ if (typeof window !== "undefined" && window.localStorage) {
701
+ return localStorage.getItem(SESSION_TOKEN_STORAGE_KEY);
702
+ }
703
+ } catch {
666
704
  }
667
705
  return null;
668
706
  }
@@ -676,7 +714,7 @@ var CimplifyClient = class {
676
714
  * every visitor to one session, so SSR keeps the lazy server-minted flow.
677
715
  */
678
716
  mintSessionToken() {
679
- if (typeof window === "undefined" || !window.localStorage) {
717
+ if (typeof window === "undefined") {
680
718
  return null;
681
719
  }
682
720
  const token = generateSessionToken();
@@ -684,11 +722,11 @@ var CimplifyClient = class {
684
722
  return token;
685
723
  }
686
724
  saveSessionToken(token) {
687
- if (typeof window !== "undefined" && window.localStorage) {
688
- try {
725
+ try {
726
+ if (typeof window !== "undefined" && window.localStorage) {
689
727
  localStorage.setItem(SESSION_TOKEN_STORAGE_KEY, token);
690
- } catch {
691
728
  }
729
+ } catch {
692
730
  }
693
731
  }
694
732
  captureSessionToken(response) {
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkROBTTS3Q_js = require('./chunk-ROBTTS3Q.js');
4
- var chunkT2CJ67QV_js = require('./chunk-T2CJ67QV.js');
3
+ var chunkKJP4U6L3_js = require('./chunk-KJP4U6L3.js');
4
+ var chunkBN4V4ONE_js = require('./chunk-BN4V4ONE.js');
5
5
  var zod = require('zod');
6
6
 
7
7
  var cimplifyRegistry = zod.z.registry();
@@ -600,7 +600,7 @@ async function toForwardedRequest(input, init) {
600
600
  };
601
601
  }
602
602
  function createTestClient(options = {}) {
603
- const mock = chunkROBTTS3Q_js.createMockApp(options);
603
+ const mock = chunkKJP4U6L3_js.createMockApp(options);
604
604
  let sessionToken;
605
605
  const fetchImpl = async (input, init) => {
606
606
  const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
@@ -619,7 +619,7 @@ function createTestClient(options = {}) {
619
619
  if (echoed) sessionToken = echoed;
620
620
  return res;
621
621
  };
622
- const client = chunkT2CJ67QV_js.createCimplifyClient({
622
+ const client = chunkBN4V4ONE_js.createCimplifyClient({
623
623
  baseUrl: TEST_BASE_URL,
624
624
  // Link mock lives at /v1/link on the same Hono app, so point the SDK's
625
625
  // separate Link transport at the same TEST_BASE_URL.
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ var chunkXREIZ56K_js = require('./chunk-XREIZ56K.js');
3
4
  var chunkAETVRBKI_js = require('./chunk-AETVRBKI.js');
4
5
  var chunkOWW5GUSB_js = require('./chunk-OWW5GUSB.js');
5
6
 
@@ -2485,13 +2486,18 @@ function buildV1(deps) {
2485
2486
  const body = await c.req.json().catch(() => ({}));
2486
2487
  return tryRoute(
2487
2488
  c,
2488
- () => deps.support.sendMessage(body.conversation_id ?? "", { content: body.content ?? "", content_type: body.content_type, sender_type: body.sender_type })
2489
+ () => deps.support.sendMessage(body.conversation_id ?? "", { content: body.content ?? "", content_type: body.content_type, sender_type: body.sender_type, reply_to_message_id: body.reply_to_message_id })
2489
2490
  );
2490
2491
  });
2491
2492
  app.get(
2492
2493
  "/support/conversation/messages",
2493
2494
  (c) => tryRoute(c, () => deps.support.listMessages(c.req.query("conversation_id") ?? "", { limit: getQueryNum(c, "limit") }))
2494
2495
  );
2496
+ app.post("/support/conversation/messages/:messageId/reactions", async (c) => {
2497
+ const body = await c.req.json().catch(() => ({}));
2498
+ return tryRoute(c, () => deps.support.react(c.req.param("messageId"), body.emoji ?? ""));
2499
+ });
2500
+ app.post("/support/conversation/handoff", (c) => c.json({ data: { requested: true } }));
2495
2501
  app.get(
2496
2502
  "/support/conversation/ws",
2497
2503
  (c) => c.json({ note: "WebSocket connection \u2014 connect via ws:// at the same path", supported: false }, 426)
@@ -5465,12 +5471,14 @@ function createSupportService(opts) {
5465
5471
  sendMessage(conversationId, body) {
5466
5472
  const conv = registry.conversations.get(conversationId);
5467
5473
  if (!conv) throw notFound(`Conversation ${conversationId} not found`);
5474
+ const target = body.reply_to_message_id ? conv.messages.find((m) => m.id === body.reply_to_message_id) : void 0;
5468
5475
  const msg = {
5469
5476
  id: ids.message(),
5470
5477
  conversation_id: conversationId,
5471
5478
  sender_type: body.sender_type ?? "customer",
5472
5479
  content_type: body.content_type ?? "text",
5473
5480
  content: body.content,
5481
+ reply_to: target ? { id: target.id, sender_type: target.sender_type, preview: chunkXREIZ56K_js.messagePreview(target.content) } : void 0,
5474
5482
  created_at: clock.iso()
5475
5483
  };
5476
5484
  conv.messages.push(msg);
@@ -5492,6 +5500,25 @@ function createSupportService(opts) {
5492
5500
  }
5493
5501
  return msg;
5494
5502
  },
5503
+ react(messageId, emoji) {
5504
+ for (const conv of registry.conversations.values()) {
5505
+ const target = conv.messages.find((m) => m.id === messageId);
5506
+ if (!target) continue;
5507
+ const reaction = {
5508
+ id: ids.message(),
5509
+ conversation_id: conv.id,
5510
+ sender_type: "customer",
5511
+ content_type: "text",
5512
+ content: emoji,
5513
+ reply_to: { id: target.id, sender_type: target.sender_type, preview: chunkXREIZ56K_js.messagePreview(target.content) },
5514
+ created_at: clock.iso()
5515
+ };
5516
+ conv.messages.push(reaction);
5517
+ registry.conversations.put(conv.id, conv);
5518
+ return reaction;
5519
+ }
5520
+ throw notFound(`Message ${messageId} not found`);
5521
+ },
5495
5522
  listMessages(conversationId, opts2) {
5496
5523
  const conv = registry.conversations.get(conversationId);
5497
5524
  if (!conv) throw notFound(`Conversation ${conversationId} not found`);
@@ -1,5 +1,5 @@
1
- import { createMockApp } from './chunk-APBLFZ2I.mjs';
2
- import { createCimplifyClient } from './chunk-DENWOZEA.mjs';
1
+ import { createMockApp } from './chunk-NNGKC3MM.mjs';
2
+ import { createCimplifyClient } from './chunk-2UDEWILL.mjs';
3
3
  import { z } from 'zod';
4
4
 
5
5
  var cimplifyRegistry = z.registry();
@@ -1,3 +1,4 @@
1
+ import { messagePreview } from './chunk-U4MW6PYU.mjs';
1
2
  import { ErrorCode } from './chunk-K5EN2W2Q.mjs';
2
3
  import { __publicField, __privateAdd, __privateSet, __privateGet, __privateMethod, __privateWrapper } from './chunk-3G6RQLXK.mjs';
3
4
 
@@ -2483,13 +2484,18 @@ function buildV1(deps) {
2483
2484
  const body = await c.req.json().catch(() => ({}));
2484
2485
  return tryRoute(
2485
2486
  c,
2486
- () => deps.support.sendMessage(body.conversation_id ?? "", { content: body.content ?? "", content_type: body.content_type, sender_type: body.sender_type })
2487
+ () => deps.support.sendMessage(body.conversation_id ?? "", { content: body.content ?? "", content_type: body.content_type, sender_type: body.sender_type, reply_to_message_id: body.reply_to_message_id })
2487
2488
  );
2488
2489
  });
2489
2490
  app.get(
2490
2491
  "/support/conversation/messages",
2491
2492
  (c) => tryRoute(c, () => deps.support.listMessages(c.req.query("conversation_id") ?? "", { limit: getQueryNum(c, "limit") }))
2492
2493
  );
2494
+ app.post("/support/conversation/messages/:messageId/reactions", async (c) => {
2495
+ const body = await c.req.json().catch(() => ({}));
2496
+ return tryRoute(c, () => deps.support.react(c.req.param("messageId"), body.emoji ?? ""));
2497
+ });
2498
+ app.post("/support/conversation/handoff", (c) => c.json({ data: { requested: true } }));
2493
2499
  app.get(
2494
2500
  "/support/conversation/ws",
2495
2501
  (c) => c.json({ note: "WebSocket connection \u2014 connect via ws:// at the same path", supported: false }, 426)
@@ -5463,12 +5469,14 @@ function createSupportService(opts) {
5463
5469
  sendMessage(conversationId, body) {
5464
5470
  const conv = registry.conversations.get(conversationId);
5465
5471
  if (!conv) throw notFound(`Conversation ${conversationId} not found`);
5472
+ const target = body.reply_to_message_id ? conv.messages.find((m) => m.id === body.reply_to_message_id) : void 0;
5466
5473
  const msg = {
5467
5474
  id: ids.message(),
5468
5475
  conversation_id: conversationId,
5469
5476
  sender_type: body.sender_type ?? "customer",
5470
5477
  content_type: body.content_type ?? "text",
5471
5478
  content: body.content,
5479
+ reply_to: target ? { id: target.id, sender_type: target.sender_type, preview: messagePreview(target.content) } : void 0,
5472
5480
  created_at: clock.iso()
5473
5481
  };
5474
5482
  conv.messages.push(msg);
@@ -5490,6 +5498,25 @@ function createSupportService(opts) {
5490
5498
  }
5491
5499
  return msg;
5492
5500
  },
5501
+ react(messageId, emoji) {
5502
+ for (const conv of registry.conversations.values()) {
5503
+ const target = conv.messages.find((m) => m.id === messageId);
5504
+ if (!target) continue;
5505
+ const reaction = {
5506
+ id: ids.message(),
5507
+ conversation_id: conv.id,
5508
+ sender_type: "customer",
5509
+ content_type: "text",
5510
+ content: emoji,
5511
+ reply_to: { id: target.id, sender_type: target.sender_type, preview: messagePreview(target.content) },
5512
+ created_at: clock.iso()
5513
+ };
5514
+ conv.messages.push(reaction);
5515
+ registry.conversations.put(conv.id, conv);
5516
+ return reaction;
5517
+ }
5518
+ throw notFound(`Message ${messageId} not found`);
5519
+ },
5493
5520
  listMessages(conversationId, opts2) {
5494
5521
  const conv = registry.conversations.get(conversationId);
5495
5522
  if (!conv) throw notFound(`Conversation ${conversationId} not found`);
@@ -0,0 +1,23 @@
1
+ // src/genui/segments.ts
2
+ var FENCE_PATTERN = /```genui\s*\n([\s\S]*?)(?:```|$)/g;
3
+ function splitGenUIContent(content) {
4
+ const segments = [];
5
+ let cursor = 0;
6
+ for (const match of content.matchAll(FENCE_PATTERN)) {
7
+ const prose = content.slice(cursor, match.index).trim();
8
+ if (prose) segments.push({ kind: "prose", text: prose });
9
+ const source = match[1].trim();
10
+ if (source) segments.push({ kind: "program", source });
11
+ cursor = match.index + match[0].length;
12
+ }
13
+ const tail = content.slice(cursor).trim();
14
+ if (tail) segments.push({ kind: "prose", text: tail });
15
+ return segments;
16
+ }
17
+ function messagePreview(content, max = 90) {
18
+ const prose = splitGenUIContent(content).filter((segment) => segment.kind === "prose").map((segment) => segment.text).join(" ").replace(/\s+/g, " ").trim();
19
+ if (!prose) return "Structured answer";
20
+ return prose.length > max ? `${prose.slice(0, max)}\u2026` : prose;
21
+ }
22
+
23
+ export { messagePreview, splitGenUIContent };
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ // src/genui/segments.ts
4
+ var FENCE_PATTERN = /```genui\s*\n([\s\S]*?)(?:```|$)/g;
5
+ function splitGenUIContent(content) {
6
+ const segments = [];
7
+ let cursor = 0;
8
+ for (const match of content.matchAll(FENCE_PATTERN)) {
9
+ const prose = content.slice(cursor, match.index).trim();
10
+ if (prose) segments.push({ kind: "prose", text: prose });
11
+ const source = match[1].trim();
12
+ if (source) segments.push({ kind: "program", source });
13
+ cursor = match.index + match[0].length;
14
+ }
15
+ const tail = content.slice(cursor).trim();
16
+ if (tail) segments.push({ kind: "prose", text: tail });
17
+ return segments;
18
+ }
19
+ function messagePreview(content, max = 90) {
20
+ const prose = splitGenUIContent(content).filter((segment) => segment.kind === "prose").map((segment) => segment.text).join(" ").replace(/\s+/g, " ").trim();
21
+ if (!prose) return "Structured answer";
22
+ return prose.length > max ? `${prose.slice(0, max)}\u2026` : prose;
23
+ }
24
+
25
+ exports.messagePreview = messagePreview;
26
+ exports.splitGenUIContent = splitGenUIContent;