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.
- package/dist/{attachment-B4nSrKVd.d.ts → attachment-CEpGtZLm.d.ts} +1 -1
- package/dist/{authoring-BjE5BvlO.d.ts → authoring-CP3vRza8.d.ts} +61 -26
- package/dist/authoring.d.ts +3 -3
- package/dist/authoring.js +5 -5
- package/dist/{chunk-NNY6LMSC.js → chunk-77U6SH5A.js} +1 -1
- package/dist/{chunk-WXY5QP3M.js → chunk-7ON5XHC2.js} +27 -21
- package/dist/{chunk-6BI4PFTP.js → chunk-CHY5YLLV.js} +1 -1
- package/dist/{chunk-Q537JPTG.js → chunk-FA7VA4XN.js} +10 -10
- package/dist/{chunk-NGC4DJIX.js → chunk-L3NUESOW.js} +425 -137
- package/dist/{chunk-2ILTJC35.js → chunk-LQMDV75O.js} +205 -11
- package/dist/{chunk-3B4QH4JG.js → chunk-MHGCPC2V.js} +1 -1
- package/dist/{chunk-U7AWXDH6.js → chunk-NZ5WCMTY.js} +1 -1
- package/dist/{chunk-5LT5J3NR.js → chunk-PSSWQBOH.js} +262 -30
- package/dist/{chunk-U3LXXT3W.js → chunk-Q44CIGG6.js} +20 -8
- package/dist/{chunk-ATNAE7OR.js → chunk-WMG36LHW.js} +676 -159
- package/dist/index.d.ts +107 -56
- package/dist/index.js +29 -182
- package/dist/providers/imessage/index.d.ts +7 -14
- package/dist/providers/imessage/index.js +6 -6
- package/dist/providers/index.d.ts +3 -3
- package/dist/providers/index.js +11 -11
- package/dist/providers/slack/index.d.ts +1 -2
- package/dist/providers/slack/index.js +3 -3
- package/dist/providers/telegram/index.d.ts +3 -5
- package/dist/providers/telegram/index.js +5 -5
- package/dist/providers/terminal/index.d.ts +2 -4
- package/dist/providers/terminal/index.js +5 -5
- package/dist/providers/whatsapp-business/index.d.ts +1 -1
- package/dist/providers/whatsapp-business/index.js +4 -4
- package/dist/{types-Bje8aq1k.d.ts → types-Be0T6E0e.d.ts} +172 -23
- package/dist/{types-BD0-kKyv.d.ts → types-CDYXH2R7.d.ts} +1 -1
- 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
|
|
200
|
-
type: z4.literal("
|
|
201
|
-
text
|
|
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
|
|
204
|
-
|
|
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 () =>
|
|
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
|
|
402
|
+
import z6 from "zod";
|
|
217
403
|
var isMessage = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
|
|
218
|
-
var reactionSchema =
|
|
219
|
-
type:
|
|
220
|
-
emoji:
|
|
221
|
-
target:
|
|
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-
|
|
4
|
+
} from "./chunk-LQMDV75O.js";
|
|
5
5
|
|
|
6
6
|
// src/content/group.ts
|
|
7
7
|
import z from "zod";
|
|
@@ -4,23 +4,25 @@ import {
|
|
|
4
4
|
} from "./chunk-34FQGGD7.js";
|
|
5
5
|
import {
|
|
6
6
|
asGroup
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-MHGCPC2V.js";
|
|
8
8
|
import {
|
|
9
9
|
asVoice
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-77U6SH5A.js";
|
|
11
11
|
import {
|
|
12
12
|
toVCard
|
|
13
13
|
} from "./chunk-6UZFVXQF.js";
|
|
14
14
|
import {
|
|
15
15
|
UnsupportedError,
|
|
16
|
-
|
|
17
|
-
|
|
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-
|
|
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, "&").replace(LT_PATTERN, "<").replace(GT_PATTERN, ">");
|
|
380
|
+
var escapeAttribute = (value) => escapeHtml(value).replace(QUOTE_PATTERN, """);
|
|
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/
|
|
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 *
|
|
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
|
-
|
|
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
|
-
|
|
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 "${
|
|
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
|
-
|
|
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
|
|
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
|
|
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: {
|
|
920
|
+
space: { create: createSpace },
|
|
689
921
|
messages: handleMessages,
|
|
690
922
|
send
|
|
691
923
|
});
|