spectrum-ts 2.0.0 → 3.0.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 (32) hide show
  1. package/dist/{attachment-B4nSrKVd.d.ts → attachment-WePAHfcH.d.ts} +1 -1
  2. package/dist/{authoring-BjE5BvlO.d.ts → authoring-DDh3muGT.d.ts} +61 -26
  3. package/dist/authoring.d.ts +3 -3
  4. package/dist/authoring.js +5 -5
  5. package/dist/{chunk-NNY6LMSC.js → chunk-77U6SH5A.js} +1 -1
  6. package/dist/{chunk-ATNAE7OR.js → chunk-AYCMTRVC.js} +549 -76
  7. package/dist/{chunk-6BI4PFTP.js → chunk-CHY5YLLV.js} +1 -1
  8. package/dist/{chunk-U3LXXT3W.js → chunk-EZ5SNNFS.js} +20 -8
  9. package/dist/{chunk-WXY5QP3M.js → chunk-FULEQIRQ.js} +27 -21
  10. package/dist/{chunk-2ILTJC35.js → chunk-LQMDV75O.js} +205 -11
  11. package/dist/{chunk-NGC4DJIX.js → chunk-LX437ZTY.js} +416 -135
  12. package/dist/{chunk-3B4QH4JG.js → chunk-MHGCPC2V.js} +1 -1
  13. package/dist/{chunk-U7AWXDH6.js → chunk-NZ5WCMTY.js} +1 -1
  14. package/dist/{chunk-5LT5J3NR.js → chunk-TXRWKSNH.js} +262 -30
  15. package/dist/{chunk-Q537JPTG.js → chunk-UXJ5OO6P.js} +10 -10
  16. package/dist/index.d.ts +107 -56
  17. package/dist/index.js +29 -182
  18. package/dist/providers/imessage/index.d.ts +6 -14
  19. package/dist/providers/imessage/index.js +6 -6
  20. package/dist/providers/index.d.ts +3 -3
  21. package/dist/providers/index.js +11 -11
  22. package/dist/providers/slack/index.d.ts +1 -2
  23. package/dist/providers/slack/index.js +3 -3
  24. package/dist/providers/telegram/index.d.ts +3 -5
  25. package/dist/providers/telegram/index.js +5 -5
  26. package/dist/providers/terminal/index.d.ts +2 -4
  27. package/dist/providers/terminal/index.js +5 -5
  28. package/dist/providers/whatsapp-business/index.d.ts +1 -1
  29. package/dist/providers/whatsapp-business/index.js +4 -4
  30. package/dist/{types-BD0-kKyv.d.ts → types-BujGKBin.d.ts} +1 -1
  31. package/dist/{types-Bje8aq1k.d.ts → types-YqCNUDIt.d.ts} +171 -23
  32. package/package.json +2 -1
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
2
2
  import {
3
3
  resolveContents
4
- } from "./chunk-2ILTJC35.js";
4
+ } from "./chunk-LQMDV75O.js";
5
5
 
6
6
  // src/content/group.ts
7
7
  import z from "zod";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-6UZFVXQF.js";
5
5
  import {
6
6
  readSchema
7
- } from "./chunk-2ILTJC35.js";
7
+ } from "./chunk-LQMDV75O.js";
8
8
 
9
9
  // src/content/contact.ts
10
10
  import vCard from "vcf";
@@ -4,23 +4,25 @@ import {
4
4
  } from "./chunk-34FQGGD7.js";
5
5
  import {
6
6
  asGroup
7
- } from "./chunk-3B4QH4JG.js";
7
+ } from "./chunk-MHGCPC2V.js";
8
8
  import {
9
9
  asVoice
10
- } from "./chunk-NNY6LMSC.js";
10
+ } from "./chunk-77U6SH5A.js";
11
11
  import {
12
12
  toVCard
13
13
  } from "./chunk-6UZFVXQF.js";
14
14
  import {
15
15
  UnsupportedError,
16
- definePlatform
17
- } from "./chunk-NGC4DJIX.js";
16
+ asMarkdown,
17
+ definePlatform,
18
+ renderInlineTokens
19
+ } from "./chunk-LX437ZTY.js";
18
20
  import {
19
21
  asAttachment,
20
22
  asCustom,
21
23
  asReaction,
22
24
  asText
23
- } from "./chunk-2ILTJC35.js";
25
+ } from "./chunk-LQMDV75O.js";
24
26
 
25
27
  // src/providers/telegram/config.ts
26
28
  import z from "zod";
@@ -361,6 +363,167 @@ var stripVariationSelector = (emoji) => emoji.replace(VARIATION_SELECTOR_16, "")
361
363
  var isAllowedReactionEmoji = (emoji) => ALLOWED_REACTION_EMOJI.has(stripVariationSelector(emoji));
362
364
  var normalizeReactionEmoji = (emoji) => isAllowedReactionEmoji(emoji) ? stripVariationSelector(emoji) : emoji;
363
365
 
366
+ // src/providers/telegram/outbound/markdown.ts
367
+ import { Marked } from "marked";
368
+ var markdownLexer = new Marked();
369
+ var BULLET = "\u2022 ";
370
+ var HR_LINE = "\u2014\u2014\u2014";
371
+ var NESTED_LIST_INDENT = " ";
372
+ var BLOCK_SEPARATOR = "\n\n";
373
+ var TABLE_CELL_SEPARATOR = " | ";
374
+ var DEFAULT_LIST_START = 1;
375
+ var AMP_PATTERN = /&/g;
376
+ var LT_PATTERN = /</g;
377
+ var GT_PATTERN = />/g;
378
+ var QUOTE_PATTERN = /"/g;
379
+ var escapeHtml = (value) => value.replace(AMP_PATTERN, "&amp;").replace(LT_PATTERN, "&lt;").replace(GT_PATTERN, "&gt;");
380
+ var escapeAttribute = (value) => escapeHtml(value).replace(QUOTE_PATTERN, "&quot;");
381
+ var asMarkedToken = (token) => token;
382
+ var checkboxPrefix = (item) => {
383
+ if (!item.task) {
384
+ return "";
385
+ }
386
+ return item.checked ? "[x] " : "[ ] ";
387
+ };
388
+ var listMarker = (list, index) => {
389
+ if (!list.ordered) {
390
+ return BULLET;
391
+ }
392
+ const start = list.start === "" ? DEFAULT_LIST_START : list.start;
393
+ return `${start + index}. `;
394
+ };
395
+ var renderLink = (token) => {
396
+ if (token.text === token.href) {
397
+ return escapeHtml(token.href);
398
+ }
399
+ return `<a href="${escapeAttribute(token.href)}">${renderInlineTokens2(token.tokens)}</a>`;
400
+ };
401
+ var renderImage = (token) => `<a href="${escapeAttribute(token.href)}">${escapeHtml(token.text || token.href)}</a>`;
402
+ var renderText = (token) => {
403
+ if (token.tokens) {
404
+ return renderInlineTokens2(token.tokens);
405
+ }
406
+ return token.escaped ? token.text : escapeHtml(token.text);
407
+ };
408
+ var renderInlineToken = (token) => {
409
+ switch (token.type) {
410
+ case "strong":
411
+ return `<b>${renderInlineTokens2(token.tokens)}</b>`;
412
+ case "em":
413
+ return `<i>${renderInlineTokens2(token.tokens)}</i>`;
414
+ case "del":
415
+ return `<s>${renderInlineTokens2(token.tokens)}</s>`;
416
+ case "codespan":
417
+ return `<code>${escapeHtml(token.text)}</code>`;
418
+ case "br":
419
+ return "\n";
420
+ case "link":
421
+ return renderLink(token);
422
+ case "image":
423
+ return renderImage(token);
424
+ case "escape":
425
+ return escapeHtml(token.text);
426
+ case "text":
427
+ return renderText(token);
428
+ // Raw HTML in markdown source renders literally, never passes through:
429
+ // a tag outside Telegram's whitelist would 400 the whole Bot API call
430
+ // (a TelegramApiError, not UnsupportedError — the plain-text fallback
431
+ // would not catch it). Escaping makes invalid output impossible.
432
+ case "html":
433
+ return escapeHtml(token.text);
434
+ // Task-item checkboxes are rendered from `ListItem.task`/`checked`.
435
+ case "checkbox":
436
+ return "";
437
+ default:
438
+ return "raw" in token ? escapeHtml(String(token.raw)) : "";
439
+ }
440
+ };
441
+ var renderInlineTokens2 = (tokens) => {
442
+ let out = "";
443
+ for (const token of tokens) {
444
+ out += renderInlineToken(asMarkedToken(token));
445
+ }
446
+ return out;
447
+ };
448
+ var renderCode = (token) => {
449
+ if (token.lang) {
450
+ return `<pre><code class="language-${escapeAttribute(token.lang)}">${escapeHtml(token.text)}</code></pre>`;
451
+ }
452
+ return `<pre>${escapeHtml(token.text)}</pre>`;
453
+ };
454
+ var renderQuoteBody = (tokens) => {
455
+ const blocks = [];
456
+ for (const token of tokens) {
457
+ const marked = asMarkedToken(token);
458
+ const rendered = marked.type === "blockquote" ? renderQuoteBody(marked.tokens) : renderBlockToken(marked);
459
+ if (rendered) {
460
+ blocks.push(rendered);
461
+ }
462
+ }
463
+ return blocks.join("\n");
464
+ };
465
+ var renderList = (list) => {
466
+ const lines = [];
467
+ for (const [index, item] of list.items.entries()) {
468
+ const prefix = `${listMarker(list, index)}${checkboxPrefix(item)}`;
469
+ const blocks = [];
470
+ for (const token of item.tokens) {
471
+ const rendered = renderBlockToken(asMarkedToken(token));
472
+ if (rendered) {
473
+ blocks.push(rendered);
474
+ }
475
+ }
476
+ const [first = "", ...rest] = blocks.join("\n").split("\n");
477
+ lines.push(`${prefix}${first}`);
478
+ for (const line of rest) {
479
+ lines.push(`${NESTED_LIST_INDENT}${line}`);
480
+ }
481
+ }
482
+ return lines.join("\n");
483
+ };
484
+ var renderTable = (table) => {
485
+ const renderRow = (cells) => cells.map((cell) => renderInlineTokens(cell.tokens)).join(TABLE_CELL_SEPARATOR);
486
+ const lines = [renderRow(table.header)];
487
+ for (const row of table.rows) {
488
+ lines.push(renderRow(row));
489
+ }
490
+ return `<pre>${escapeHtml(lines.join("\n"))}</pre>`;
491
+ };
492
+ var renderBlockToken = (token) => {
493
+ switch (token.type) {
494
+ // Telegram has no headings; bold is the conventional stand-in.
495
+ case "heading":
496
+ return `<b>${renderInlineTokens2(token.tokens)}</b>`;
497
+ case "paragraph":
498
+ return renderInlineTokens2(token.tokens);
499
+ case "code":
500
+ return renderCode(token);
501
+ case "blockquote":
502
+ return `<blockquote>${renderQuoteBody(token.tokens)}</blockquote>`;
503
+ case "list":
504
+ return renderList(token);
505
+ case "table":
506
+ return renderTable(token);
507
+ case "hr":
508
+ return HR_LINE;
509
+ case "space":
510
+ case "def":
511
+ return "";
512
+ default:
513
+ return renderInlineToken(token);
514
+ }
515
+ };
516
+ var markdownToTelegramHtml = (markdown) => {
517
+ const blocks = [];
518
+ for (const token of markdownLexer.lexer(markdown)) {
519
+ const rendered = renderBlockToken(asMarkedToken(token));
520
+ if (rendered) {
521
+ blocks.push(rendered);
522
+ }
523
+ }
524
+ return blocks.join(BLOCK_SEPARATOR).trim();
525
+ };
526
+
364
527
  // src/providers/telegram/outbound/message.ts
365
528
  var VCARD_FILENAME = "contact.vcf";
366
529
  var VCARD_MIME = "text/vcard";
@@ -420,6 +583,14 @@ var buildSend = async (content) => {
420
583
  switch (content.type) {
421
584
  case "text":
422
585
  return { method: "sendMessage", params: { text: content.text } };
586
+ case "markdown":
587
+ return {
588
+ method: "sendMessage",
589
+ params: {
590
+ text: markdownToTelegramHtml(content.markdown),
591
+ parse_mode: "HTML"
592
+ }
593
+ };
423
594
  case "richlink":
424
595
  return { method: "sendMessage", params: { text: content.url } };
425
596
  case "attachment":
@@ -467,8 +638,70 @@ var buildSend = async (content) => {
467
638
  }
468
639
  };
469
640
 
470
- // src/providers/telegram/outbound/send.ts
641
+ // src/providers/telegram/outbound/stream-text.ts
642
+ import { sendMessageDraft } from "@photon-ai/telegram-ts";
471
643
  var MILLIS_PER_SECOND2 = 1e3;
644
+ var DRAFT_THROTTLE_MS = 500;
645
+ var nextDraftId = 1;
646
+ var sendStreamText = async (client, space, content) => {
647
+ const chatId = Number(space.id);
648
+ if (!(Number.isInteger(chatId) && chatId > 0)) {
649
+ throw UnsupportedError.content(
650
+ "streamText",
651
+ TELEGRAM_PLATFORM,
652
+ `message drafts work only in private chats (got chat id "${space.id}").`
653
+ );
654
+ }
655
+ const draftId = nextDraftId;
656
+ nextDraftId += 1;
657
+ const renderBody = (text) => content.format === "markdown" ? { text: markdownToTelegramHtml(text), parse_mode: "HTML" } : { text };
658
+ let lastDraftText;
659
+ let lastDraftAt = 0;
660
+ let draftsAvailable = true;
661
+ const updateDraft = async (text) => {
662
+ if (!draftsAvailable || text === lastDraftText) {
663
+ return;
664
+ }
665
+ try {
666
+ await sendMessageDraft({
667
+ body: { chat_id: chatId, draft_id: draftId, ...renderBody(text) },
668
+ client
669
+ });
670
+ lastDraftText = text;
671
+ lastDraftAt = Date.now();
672
+ } catch {
673
+ draftsAvailable = false;
674
+ }
675
+ };
676
+ await updateDraft("");
677
+ let full = "";
678
+ for await (const delta of content.stream()) {
679
+ full += delta;
680
+ if (Date.now() - lastDraftAt >= DRAFT_THROTTLE_MS) {
681
+ await updateDraft(full);
682
+ }
683
+ }
684
+ if (!full) {
685
+ throw UnsupportedError.content(
686
+ "streamText",
687
+ TELEGRAM_PLATFORM,
688
+ "stream produced no text \u2014 nothing to send."
689
+ );
690
+ }
691
+ const sent = await executeSpec(client, {
692
+ method: "sendMessage",
693
+ params: { chat_id: space.id, ...renderBody(full) }
694
+ });
695
+ return {
696
+ id: String(sent.message_id),
697
+ content: content.format === "markdown" ? asMarkdown(full) : asText(full),
698
+ space: { id: space.id },
699
+ timestamp: new Date(sent.date * MILLIS_PER_SECOND2)
700
+ };
701
+ };
702
+
703
+ // src/providers/telegram/outbound/send.ts
704
+ var MILLIS_PER_SECOND3 = 1e3;
472
705
  var sendContent = async (client, space, content) => {
473
706
  const spec = await buildSend(content);
474
707
  const sent = await executeSpec(client, {
@@ -479,7 +712,7 @@ var sendContent = async (client, space, content) => {
479
712
  id: String(sent.message_id),
480
713
  content,
481
714
  space: { id: space.id },
482
- timestamp: new Date(sent.date * MILLIS_PER_SECOND2)
715
+ timestamp: new Date(sent.date * MILLIS_PER_SECOND3)
483
716
  };
484
717
  };
485
718
  var sendGroup = async (client, space, items) => {
@@ -508,7 +741,14 @@ var sendReaction = async (client, space, content) => {
508
741
  },
509
742
  client
510
743
  });
511
- return;
744
+ const timestamp = /* @__PURE__ */ new Date();
745
+ const unixSeconds = Math.floor(timestamp.getTime() / MILLIS_PER_SECOND3);
746
+ return {
747
+ id: `reaction:${space.id}:${messageId}:${unixSeconds}:bot:${emoji}`,
748
+ content,
749
+ space: { id: space.id },
750
+ timestamp
751
+ };
512
752
  };
513
753
  var sendTyping = async (client, space, state) => {
514
754
  if (state === "start") {
@@ -520,18 +760,23 @@ var sendTyping = async (client, space, state) => {
520
760
  return;
521
761
  };
522
762
  var sendEdit = async (client, space, content) => {
523
- if (content.content.type !== "text") {
763
+ const inner = content.content;
764
+ if (inner.type !== "text" && inner.type !== "markdown") {
524
765
  throw UnsupportedError.content(
525
766
  "edit",
526
767
  TELEGRAM_PLATFORM,
527
- `only text content can be edited (got "${content.content.type}").`
768
+ `only text and markdown content can be edited (got "${inner.type}").`
528
769
  );
529
770
  }
771
+ const body = inner.type === "markdown" ? {
772
+ text: markdownToTelegramHtml(inner.markdown),
773
+ parse_mode: "HTML"
774
+ } : { text: inner.text };
530
775
  await editMessageText({
531
776
  body: {
532
777
  chat_id: space.id,
533
778
  message_id: parseMessageId(content.target.id),
534
- text: content.content.text
779
+ ...body
535
780
  },
536
781
  client
537
782
  });
@@ -552,6 +797,8 @@ var send = async ({
552
797
  return await sendEdit(client, space, content);
553
798
  case "group":
554
799
  return await sendGroup(client, space, content.items);
800
+ case "streamText":
801
+ return await sendStreamText(client, space, content);
555
802
  case "poll":
556
803
  case "poll_option":
557
804
  case "effect":
@@ -564,36 +811,21 @@ var send = async ({
564
811
  };
565
812
 
566
813
  // src/providers/telegram/space.ts
567
- import z2 from "zod";
568
- var spaceParamsSchema = z2.object({
569
- /**
570
- * Target a chat directly by id. Telegram chat ids are numbers in the wire
571
- * format (negative for groups/supergroups); accept either form and store as
572
- * a string. For a private chat the id equals the user's id.
573
- */
574
- chatId: z2.union([z2.string().min(1), z2.number()]).optional()
575
- });
576
814
  var resolveUser = ({
577
815
  input
578
816
  }) => Promise.resolve({ id: input.userID });
579
- var resolveSpace = ({
817
+ var createSpace = ({
580
818
  input
581
819
  }) => {
582
- const chatId = input.params?.chatId;
583
- if (chatId !== void 0) {
584
- return Promise.resolve({ id: String(chatId) });
585
- }
586
820
  const [first, ...rest] = input.users;
587
821
  if (first && rest.length === 0) {
588
822
  return Promise.resolve({ id: first.id });
589
823
  }
590
824
  if (!first) {
591
- throw new Error(
592
- "Telegram space creation requires params.chatId or a single recipient user."
593
- );
825
+ throw new Error("Telegram space creation requires a recipient user.");
594
826
  }
595
827
  throw new Error(
596
- "Telegram bots cannot create group chats \u2014 pass params.chatId for an existing chat, or resolve a single user (their private chat)."
828
+ "Telegram bots cannot create group chats \u2014 use space.get(chatId) for an existing chat, or create a space with a single user (their private chat)."
597
829
  );
598
830
  };
599
831
 
@@ -685,7 +917,7 @@ var telegram = definePlatform(TELEGRAM_PLATFORM, {
685
917
  }
686
918
  },
687
919
  user: { resolve: resolveUser },
688
- space: { params: spaceParamsSchema, resolve: resolveSpace },
920
+ space: { create: createSpace },
689
921
  messages: handleMessages,
690
922
  send
691
923
  });
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-3GEJYGZK.js";
8
8
  import {
9
9
  asContact
10
- } from "./chunk-U7AWXDH6.js";
10
+ } from "./chunk-NZ5WCMTY.js";
11
11
  import {
12
12
  mergeStreams,
13
13
  stream
@@ -15,13 +15,13 @@ import {
15
15
  import {
16
16
  UnsupportedError,
17
17
  definePlatform
18
- } from "./chunk-NGC4DJIX.js";
18
+ } from "./chunk-LX437ZTY.js";
19
19
  import {
20
20
  asAttachment,
21
21
  asCustom,
22
22
  asReaction,
23
23
  asText
24
- } from "./chunk-2ILTJC35.js";
24
+ } from "./chunk-LQMDV75O.js";
25
25
 
26
26
  // src/providers/whatsapp-business/index.ts
27
27
  import { createClient as createClient2 } from "@photon-ai/whatsapp-business";
@@ -639,8 +639,7 @@ var send = async (clients, spaceId, content) => {
639
639
  );
640
640
  }
641
641
  if (content.type === "reaction") {
642
- await reactToMessage(clients, spaceId, content.target.id, content.emoji);
643
- return;
642
+ return await reactToMessage(clients, spaceId, content);
644
643
  }
645
644
  if (content.type === "typing") {
646
645
  return;
@@ -706,11 +705,12 @@ var send = async (clients, spaceId, content) => {
706
705
  throw UnsupportedError.content(content.type);
707
706
  }
708
707
  };
709
- var reactToMessage = async (clients, spaceId, messageId, reaction) => {
710
- await primary(clients).messages.send({
708
+ var reactToMessage = async (clients, spaceId, content) => {
709
+ const result = await primary(clients).messages.send({
711
710
  to: spaceId,
712
- reaction: { messageId, emoji: reaction }
711
+ reaction: { messageId: content.target.id, emoji: content.emoji }
713
712
  });
713
+ return toRecord(result, spaceId, content);
714
714
  };
715
715
  var replyToMessage = async (clients, spaceId, messageId, content) => {
716
716
  const client = primary(clients);
@@ -833,13 +833,13 @@ var whatsappBusiness = definePlatform("WhatsApp Business", {
833
833
  },
834
834
  space: {
835
835
  schema: spaceSchema,
836
- resolve: async ({ input }) => {
836
+ create: async ({ input }) => {
837
837
  if (input.users.length === 0) {
838
838
  throw new Error("WhatsApp space creation requires at least one user");
839
839
  }
840
840
  if (input.users.length > 1) {
841
841
  throw UnsupportedError.action(
842
- "createSpace",
842
+ "space.create",
843
843
  "WhatsApp Business",
844
844
  "only 1:1 conversations are supported"
845
845
  );