spectrum-ts 2.0.0 → 3.1.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-CEpGtZLm.d.ts} +1 -1
  2. package/dist/{authoring-BjE5BvlO.d.ts → authoring-CP3vRza8.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-WXY5QP3M.js → chunk-7ON5XHC2.js} +27 -21
  7. package/dist/{chunk-6BI4PFTP.js → chunk-CHY5YLLV.js} +1 -1
  8. package/dist/{chunk-Q537JPTG.js → chunk-FA7VA4XN.js} +10 -10
  9. package/dist/{chunk-NGC4DJIX.js → chunk-L3NUESOW.js} +425 -137
  10. package/dist/{chunk-2ILTJC35.js → chunk-LQMDV75O.js} +205 -11
  11. package/dist/{chunk-3B4QH4JG.js → chunk-MHGCPC2V.js} +1 -1
  12. package/dist/{chunk-U7AWXDH6.js → chunk-NZ5WCMTY.js} +1 -1
  13. package/dist/{chunk-5LT5J3NR.js → chunk-PSSWQBOH.js} +262 -30
  14. package/dist/{chunk-U3LXXT3W.js → chunk-Q44CIGG6.js} +20 -8
  15. package/dist/{chunk-ATNAE7OR.js → chunk-WMG36LHW.js} +676 -159
  16. package/dist/index.d.ts +107 -56
  17. package/dist/index.js +29 -182
  18. package/dist/providers/imessage/index.d.ts +7 -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-Bje8aq1k.d.ts → types-Be0T6E0e.d.ts} +172 -23
  31. package/dist/{types-BD0-kKyv.d.ts → types-CDYXH2R7.d.ts} +1 -1
  32. package/package.json +2 -1
@@ -195,16 +195,202 @@ function custom(raw) {
195
195
  }
196
196
 
197
197
  // src/content/text.ts
198
+ import z5 from "zod";
199
+
200
+ // src/content/stream-text.ts
198
201
  import z4 from "zod";
199
- var textSchema = z4.object({
200
- type: z4.literal("text"),
201
- text: z4.string().nonempty()
202
+ var streamTextSchema = z4.object({
203
+ type: z4.literal("streamText"),
204
+ // A single-consumption producer of normalized text deltas. The builder
205
+ // closes over the normalized source; the platform driver calls it once.
206
+ // Kept opaque to Zod via `z.custom` (same approach as `attachment.read`).
207
+ stream: z4.custom(
208
+ (v) => typeof v === "function",
209
+ {
210
+ message: "streamText.stream must be a function returning AsyncIterable<string>"
211
+ }
212
+ ),
213
+ // How platforms should interpret the accumulated text; absent = plain.
214
+ format: z4.enum(["plain", "markdown"]).optional()
202
215
  });
203
- var asText = (text2) => textSchema.parse({ type: "text", text: text2 });
204
- function text(text2) {
216
+ var asRecord = (value) => typeof value === "object" && value !== null ? value : void 0;
217
+ var SKIP_EVENT_TYPES = /* @__PURE__ */ new Set([
218
+ "message_start",
219
+ "message_delta",
220
+ "message_stop",
221
+ "content_block_start",
222
+ "content_block_stop",
223
+ "ping"
224
+ ]);
225
+ var fromOpenAIResponses = (obj) => {
226
+ const type = obj.type;
227
+ if (typeof type !== "string" || !type.startsWith("response.")) {
228
+ return;
229
+ }
230
+ if (type === "response.output_text.delta" && typeof obj.delta === "string") {
231
+ return obj.delta;
232
+ }
233
+ return null;
234
+ };
235
+ var fromAnthropicDelta = (obj) => {
236
+ if (obj.type !== "content_block_delta") {
237
+ return;
238
+ }
239
+ const delta = asRecord(obj.delta);
240
+ if (delta?.type === "text_delta" && typeof delta.text === "string") {
241
+ return delta.text;
242
+ }
243
+ return null;
244
+ };
245
+ var fromAiSdkPart = (obj) => {
246
+ if (obj.type !== "text-delta") {
247
+ return;
248
+ }
249
+ if (typeof obj.textDelta === "string") {
250
+ return obj.textDelta;
251
+ }
252
+ return typeof obj.text === "string" ? obj.text : null;
253
+ };
254
+ var fromOpenAIChat = (obj) => {
255
+ if (!Array.isArray(obj.choices)) {
256
+ return;
257
+ }
258
+ const delta = asRecord(asRecord(obj.choices[0])?.delta);
259
+ const content = delta?.content;
260
+ return typeof content === "string" ? content : null;
261
+ };
262
+ var fromControlEvent = (obj) => typeof obj.type === "string" && SKIP_EVENT_TYPES.has(obj.type) ? null : void 0;
263
+ var OBJECT_EXTRACTORS = [
264
+ fromOpenAIResponses,
265
+ fromAnthropicDelta,
266
+ fromAiSdkPart,
267
+ fromOpenAIChat,
268
+ fromControlEvent
269
+ ];
270
+ var defaultExtract = (chunk) => {
271
+ if (typeof chunk === "string") {
272
+ return chunk;
273
+ }
274
+ const record = asRecord(chunk);
275
+ if (!record) {
276
+ throw new Error(
277
+ `text stream: cannot extract a text delta from a ${typeof chunk} chunk. Pass { extract } to map your stream's chunks to text.`
278
+ );
279
+ }
280
+ for (const extractor of OBJECT_EXTRACTORS) {
281
+ const result = extractor(record);
282
+ if (result !== void 0) {
283
+ return result;
284
+ }
285
+ }
286
+ throw new Error(
287
+ `text stream: unrecognized chunk shape (type=${String(record.type)}). Pass an { extract } function to map your provider's chunk to a text delta.`
288
+ );
289
+ };
290
+ var isReadableStream = (value) => typeof value?.getReader === "function";
291
+ var isAsyncIterable = (value) => typeof value?.[Symbol.asyncIterator] === "function";
292
+ async function* readableToAsync(source) {
293
+ if (isAsyncIterable(source)) {
294
+ yield* source;
295
+ return;
296
+ }
297
+ const reader = source.getReader();
298
+ try {
299
+ while (true) {
300
+ const { done, value } = await reader.read();
301
+ if (done) {
302
+ return;
303
+ }
304
+ yield value;
305
+ }
306
+ } finally {
307
+ reader.releaseLock();
308
+ }
309
+ }
310
+ var resolveChunkIterable = (source) => {
311
+ const textStream = source.textStream;
312
+ if (textStream != null) {
313
+ if (isReadableStream(textStream)) {
314
+ return readableToAsync(textStream);
315
+ }
316
+ if (isAsyncIterable(textStream)) {
317
+ return textStream;
318
+ }
319
+ throw new Error(
320
+ "text stream: `.textStream` must be an AsyncIterable or a ReadableStream."
321
+ );
322
+ }
323
+ if (isReadableStream(source)) {
324
+ return readableToAsync(source);
325
+ }
326
+ if (isAsyncIterable(source)) {
327
+ return source;
328
+ }
329
+ throw new Error(
330
+ "text stream: source must be an AsyncIterable, a ReadableStream, or an object with a `.textStream` (e.g. the AI SDK streamText() result)."
331
+ );
332
+ };
333
+ var StreamConsumedError = class extends Error {
334
+ constructor() {
335
+ super(
336
+ "text stream: this source has already been consumed \u2014 a stream can only be sent once."
337
+ );
338
+ this.name = "StreamConsumedError";
339
+ }
340
+ };
341
+ var normalize = (source, options) => {
342
+ const extract = options?.extract ? options.extract : defaultExtract;
343
+ let consumed = false;
344
+ return async function* normalized() {
345
+ if (consumed) {
346
+ throw new StreamConsumedError();
347
+ }
348
+ consumed = true;
349
+ for await (const chunk of resolveChunkIterable(source)) {
350
+ const delta = extract(chunk);
351
+ if (delta) {
352
+ yield delta;
353
+ }
354
+ }
355
+ };
356
+ };
357
+ var asStreamText = (input) => streamTextSchema.parse({
358
+ type: "streamText",
359
+ stream: input.stream,
360
+ ...input.format ? { format: input.format } : {}
361
+ });
362
+ var drainStreamText = async (content) => {
363
+ let full = "";
364
+ for await (const delta of content.stream()) {
365
+ full += delta;
366
+ }
367
+ return full;
368
+ };
369
+ var streamTextBuilder = (kind, source, options) => {
370
+ if (typeof source.build === "function") {
371
+ throw new Error(
372
+ `${kind}(): pass the stream source itself (an AsyncIterable, a ReadableStream, or an SDK result with .textStream), not another content builder.`
373
+ );
374
+ }
205
375
  return {
206
- build: async () => asText(text2)
376
+ build: async () => asStreamText({
377
+ stream: normalize(source, options),
378
+ format: kind === "markdown" ? "markdown" : void 0
379
+ })
207
380
  };
381
+ };
382
+
383
+ // src/content/text.ts
384
+ var textSchema = z5.object({
385
+ type: z5.literal("text"),
386
+ text: z5.string().nonempty()
387
+ });
388
+ var asText = (text2) => textSchema.parse({ type: "text", text: text2 });
389
+ function text(source, options) {
390
+ if (typeof source === "string") {
391
+ return { build: async () => asText(source) };
392
+ }
393
+ return streamTextBuilder("text", source, options);
208
394
  }
209
395
 
210
396
  // src/content/resolve.ts
@@ -213,12 +399,12 @@ var resolveContents = (items) => Promise.all(
213
399
  );
214
400
 
215
401
  // src/content/reaction.ts
216
- import z5 from "zod";
402
+ import z6 from "zod";
217
403
  var isMessage = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
218
- var reactionSchema = z5.object({
219
- type: z5.literal("reaction"),
220
- emoji: z5.string().min(1),
221
- target: z5.custom(isMessage, {
404
+ var reactionSchema = z6.object({
405
+ type: z6.literal("reaction"),
406
+ emoji: z6.string().min(1),
407
+ target: z6.custom(isMessage, {
222
408
  message: "reaction target must be a Message"
223
409
  })
224
410
  });
@@ -226,6 +412,11 @@ var asReaction = (input) => reactionSchema.parse({ type: "reaction", ...input })
226
412
  function reaction(emoji, target) {
227
413
  return {
228
414
  build: async () => {
415
+ if (!target) {
416
+ throw new Error(
417
+ "reaction() target is undefined \u2014 the targeted message was never sent (space.send resolves undefined when a platform skips unsupported content)"
418
+ );
419
+ }
229
420
  if (target.content.type === "reaction") {
230
421
  throw new Error('reaction() cannot target "reaction" content');
231
422
  }
@@ -250,6 +441,9 @@ export {
250
441
  attachment,
251
442
  asCustom,
252
443
  custom,
444
+ StreamConsumedError,
445
+ drainStreamText,
446
+ streamTextBuilder,
253
447
  textSchema,
254
448
  asText,
255
449
  text,
@@ -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-L3NUESOW.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
  });