spectrum-ts 1.18.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 (38) hide show
  1. package/README.md +11 -1
  2. package/dist/{attachment-DfWSZS5L.d.ts → attachment-WePAHfcH.d.ts} +1 -1
  3. package/dist/{authoring-C9uDdZ2F.d.ts → authoring-DDh3muGT.d.ts} +61 -26
  4. package/dist/authoring.d.ts +3 -3
  5. package/dist/authoring.js +8 -5
  6. package/dist/chunk-34FQGGD7.js +34 -0
  7. package/dist/chunk-3GEJYGZK.js +84 -0
  8. package/dist/{chunk-MC6ZKFSG.js → chunk-5XEFJBN2.js} +25 -103
  9. package/dist/{chunk-QGJFZMD5.js → chunk-6UZFVXQF.js} +17 -101
  10. package/dist/{chunk-NNY6LMSC.js → chunk-77U6SH5A.js} +1 -1
  11. package/dist/{chunk-YN6WOTBF.js → chunk-AYCMTRVC.js} +622 -79
  12. package/dist/{chunk-JQN6CRSC.js → chunk-CHY5YLLV.js} +11 -40
  13. package/dist/{chunk-5BKZJMZV.js → chunk-EZ5SNNFS.js} +79 -38
  14. package/dist/{chunk-3OTECDNH.js → chunk-FULEQIRQ.js} +31 -23
  15. package/dist/{chunk-2ILTJC35.js → chunk-LQMDV75O.js} +205 -11
  16. package/dist/{chunk-IPOFBAIM.js → chunk-LX437ZTY.js} +439 -154
  17. package/dist/chunk-MHGCPC2V.js +35 -0
  18. package/dist/chunk-NZ5WCMTY.js +91 -0
  19. package/dist/chunk-TXRWKSNH.js +927 -0
  20. package/dist/{chunk-5TIF3FIE.js → chunk-UXJ5OO6P.js} +16 -14
  21. package/dist/index.d.ts +125 -129
  22. package/dist/index.js +180 -73
  23. package/dist/manifest.json +6 -0
  24. package/dist/providers/imessage/index.d.ts +6 -14
  25. package/dist/providers/imessage/index.js +9 -6
  26. package/dist/providers/index.d.ts +5 -2
  27. package/dist/providers/index.js +18 -10
  28. package/dist/providers/slack/index.d.ts +1 -2
  29. package/dist/providers/slack/index.js +5 -4
  30. package/dist/providers/telegram/index.d.ts +45 -0
  31. package/dist/providers/telegram/index.js +13 -0
  32. package/dist/providers/terminal/index.d.ts +18 -422
  33. package/dist/providers/terminal/index.js +7 -5
  34. package/dist/providers/whatsapp-business/index.d.ts +1 -1
  35. package/dist/providers/whatsapp-business/index.js +7 -5
  36. package/dist/types-BujGKBin.d.ts +82 -0
  37. package/dist/{types-DcQ5a7PK.d.ts → types-YqCNUDIt.d.ts} +204 -26
  38. package/package.json +3 -1
@@ -3,37 +3,11 @@ import {
3
3
  bufferToStream,
4
4
  fetchUrlBytes,
5
5
  readSchema,
6
- resolveContents,
7
6
  streamSchema
8
- } from "./chunk-2ILTJC35.js";
9
-
10
- // src/content/group.ts
11
- import z from "zod";
12
- var isMessage = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
13
- var groupSchema = z.object({
14
- type: z.literal("group"),
15
- items: z.array(z.custom(isMessage)).min(2)
16
- });
17
- var asGroup = (input) => groupSchema.parse({ type: "group", items: input.items });
18
- var stubOutboundMessage = (content) => ({ id: "", content });
19
- function group(...items) {
20
- return {
21
- build: async () => {
22
- const resolved = await resolveContents(items);
23
- const members = [];
24
- for (const item of resolved) {
25
- if (item.type === "group" || item.type === "reaction") {
26
- throw new Error(`group() cannot contain "${item.type}" items`);
27
- }
28
- members.push(stubOutboundMessage(item));
29
- }
30
- return asGroup({ items: members });
31
- }
32
- };
33
- }
7
+ } from "./chunk-LQMDV75O.js";
34
8
 
35
9
  // src/content/richlink.ts
36
- import z2 from "zod";
10
+ import z from "zod";
37
11
 
38
12
  // src/utils/link-metadata.ts
39
13
  import ogs from "open-graph-scraper";
@@ -90,22 +64,22 @@ var fetchImage = (url) => fetchUrlBytes(new URL(url), {
90
64
  });
91
65
 
92
66
  // src/content/richlink.ts
93
- var richlinkCoverSchema = z2.object({
94
- mimeType: z2.string().min(1).optional(),
67
+ var richlinkCoverSchema = z.object({
68
+ mimeType: z.string().min(1).optional(),
95
69
  read: readSchema,
96
70
  stream: streamSchema
97
71
  });
98
- var optionalStringAccessor = z2.function({
72
+ var optionalStringAccessor = z.function({
99
73
  input: [],
100
- output: z2.promise(z2.string().min(1).optional())
74
+ output: z.promise(z.string().min(1).optional())
101
75
  });
102
- var coverAccessor = z2.function({
76
+ var coverAccessor = z.function({
103
77
  input: [],
104
- output: z2.promise(richlinkCoverSchema.optional())
78
+ output: z.promise(richlinkCoverSchema.optional())
105
79
  });
106
- var richlinkSchema = z2.object({
107
- type: z2.literal("richlink"),
108
- url: z2.url(),
80
+ var richlinkSchema = z.object({
81
+ type: z.literal("richlink"),
82
+ url: z.url(),
109
83
  title: optionalStringAccessor,
110
84
  summary: optionalStringAccessor,
111
85
  cover: coverAccessor
@@ -150,9 +124,6 @@ function richlink(url) {
150
124
  }
151
125
 
152
126
  export {
153
- groupSchema,
154
- asGroup,
155
- group,
156
127
  asRichlink,
157
128
  richlink
158
129
  };
@@ -1,21 +1,26 @@
1
1
  import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
2
2
  import {
3
3
  asVoice
4
- } from "./chunk-NNY6LMSC.js";
4
+ } from "./chunk-77U6SH5A.js";
5
+ import {
6
+ asContact
7
+ } from "./chunk-NZ5WCMTY.js";
8
+ import {
9
+ stream
10
+ } from "./chunk-5XEFJBN2.js";
5
11
  import {
6
- asContact,
7
12
  fromVCard,
8
13
  toVCard
9
- } from "./chunk-QGJFZMD5.js";
14
+ } from "./chunk-6UZFVXQF.js";
10
15
  import {
11
16
  UnsupportedError,
12
17
  definePlatform
13
- } from "./chunk-IPOFBAIM.js";
18
+ } from "./chunk-LX437ZTY.js";
14
19
  import {
15
20
  asAttachment,
16
21
  asCustom,
17
22
  reactionSchema
18
- } from "./chunk-2ILTJC35.js";
23
+ } from "./chunk-LQMDV75O.js";
19
24
 
20
25
  // src/providers/terminal/index.ts
21
26
  import { spawn } from "child_process";
@@ -694,7 +699,7 @@ function protocolToSpectrum(p) {
694
699
  }
695
700
  return cached;
696
701
  };
697
- const stream = async () => {
702
+ const stream2 = async () => {
698
703
  if (path) {
699
704
  const [{ createReadStream }, { Readable }] = await Promise.all([
700
705
  import("fs"),
@@ -718,7 +723,7 @@ function protocolToSpectrum(p) {
718
723
  mimeType: p.mimeType,
719
724
  size: p.size,
720
725
  read: readBytes,
721
- stream
726
+ stream: stream2
722
727
  });
723
728
  }
724
729
  return asVoice({
@@ -726,7 +731,7 @@ function protocolToSpectrum(p) {
726
731
  mimeType: p.mimeType,
727
732
  size: p.size,
728
733
  read: readBytes,
729
- stream
734
+ stream: stream2
730
735
  });
731
736
  }
732
737
  if (p.type === "contact") {
@@ -776,41 +781,71 @@ var terminal = definePlatform("Terminal", {
776
781
  })
777
782
  },
778
783
  space: {
779
- params: z.object({ id: z.string().optional() }),
780
- resolve: async ({ client, input }) => {
781
- const id = input.params?.id ?? generateChatId(client);
784
+ create: async ({ client }) => {
785
+ const id = generateChatId(client);
782
786
  client.knownChats.add(id);
783
787
  await client.session.request("ensureSpace", { id });
784
788
  return { id };
789
+ },
790
+ // Explicit (not the framework default) so targeting a known id still
791
+ // materializes the chat in the TUI via `ensureSpace`.
792
+ get: async ({ client, input }) => {
793
+ client.knownChats.add(input.id);
794
+ await client.session.request("ensureSpace", { id: input.id });
795
+ return { id: input.id };
785
796
  }
786
797
  },
787
- async *messages({ client }) {
788
- for await (const evt of client.events) {
789
- if (evt.kind === "message") {
790
- const msg = evt.value;
791
- client.knownChats.add(msg.spaceId);
792
- yield {
793
- id: msg.id,
794
- content: protocolToSpectrum(msg.content),
795
- sender: { id: msg.senderId },
796
- space: { id: msg.spaceId },
797
- timestamp: parseTimestamp(msg.timestamp),
798
- // replyTo is a terminal-specific extra — agents inspect via a
799
- // cast until Spectrum's message model grows first-class support.
800
- ...msg.replyTo ? { replyTo: msg.replyTo } : {}
801
- };
802
- continue;
803
- }
804
- const r = evt.value;
805
- client.knownChats.add(r.spaceId);
806
- yield {
807
- id: `reaction:${r.messageId}:${r.reaction}:${r.timestamp}`,
808
- content: reactionContentFromProtocol(r),
809
- sender: { id: r.senderId },
810
- space: { id: r.spaceId },
811
- timestamp: parseTimestamp(r.timestamp)
798
+ // Return a ManagedStream (not a native async generator): a native generator
799
+ // parked on an in-flight `client.events.next()` cannot be force-cancelled —
800
+ // a `.return()` queues behind the pending `next()` and never reaches the
801
+ // event queue, which would deadlock `Spectrum.stop()`. Driving the queue with
802
+ // an explicit pump lets cleanup call the queue iterator's `return()` directly
803
+ // (synchronous close + drain), so the stream tears down promptly on stop()
804
+ // without waiting for destroyClient.
805
+ messages({ client }) {
806
+ return stream((emit, end) => {
807
+ const iterator = client.events[Symbol.asyncIterator]();
808
+ const pump = (async () => {
809
+ try {
810
+ let result = await iterator.next();
811
+ while (!result.done) {
812
+ const evt = result.value;
813
+ if (evt.kind === "message") {
814
+ const msg = evt.value;
815
+ client.knownChats.add(msg.spaceId);
816
+ await emit({
817
+ id: msg.id,
818
+ content: protocolToSpectrum(msg.content),
819
+ sender: { id: msg.senderId },
820
+ space: { id: msg.spaceId },
821
+ timestamp: parseTimestamp(msg.timestamp),
822
+ // replyTo is a terminal-specific extra — agents inspect via a
823
+ // cast until Spectrum's message model grows first-class support.
824
+ ...msg.replyTo ? { replyTo: msg.replyTo } : {}
825
+ });
826
+ } else {
827
+ const r = evt.value;
828
+ client.knownChats.add(r.spaceId);
829
+ await emit({
830
+ id: `reaction:${r.messageId}:${r.reaction}:${r.timestamp}`,
831
+ content: reactionContentFromProtocol(r),
832
+ sender: { id: r.senderId },
833
+ space: { id: r.spaceId },
834
+ timestamp: parseTimestamp(r.timestamp)
835
+ });
836
+ }
837
+ result = await iterator.next();
838
+ }
839
+ end();
840
+ } catch (error) {
841
+ end(error);
842
+ }
843
+ })();
844
+ return async () => {
845
+ await iterator.return?.();
846
+ await pump.catch(() => void 0);
812
847
  };
813
- }
848
+ });
814
849
  },
815
850
  send: async ({ client, content, space }) => {
816
851
  if (content.type === "reply") {
@@ -828,7 +863,13 @@ var terminal = definePlatform("Terminal", {
828
863
  messageId: content.target.id,
829
864
  reaction: content.emoji
830
865
  });
831
- return;
866
+ const timestamp = /* @__PURE__ */ new Date();
867
+ return {
868
+ id: `reaction:${content.target.id}:${content.emoji}:${timestamp.toISOString()}`,
869
+ content,
870
+ space: { id: space.id },
871
+ timestamp
872
+ };
832
873
  }
833
874
  if (content.type === "typing") {
834
875
  const method = content.state === "start" ? "startTyping" : "stopTyping";
@@ -1,19 +1,21 @@
1
1
  import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
2
2
  import {
3
- cloud,
3
+ cloud
4
+ } from "./chunk-3GEJYGZK.js";
5
+ import {
4
6
  mergeStreams,
5
7
  stream
6
- } from "./chunk-MC6ZKFSG.js";
8
+ } from "./chunk-5XEFJBN2.js";
7
9
  import {
8
10
  UnsupportedError,
9
11
  definePlatform
10
- } from "./chunk-IPOFBAIM.js";
12
+ } from "./chunk-LX437ZTY.js";
11
13
  import {
12
14
  asAttachment,
13
15
  asCustom,
14
16
  asReaction,
15
17
  asText
16
- } from "./chunk-2ILTJC35.js";
18
+ } from "./chunk-LQMDV75O.js";
17
19
 
18
20
  // src/providers/slack/index.ts
19
21
  import { createClient as createClient2, staticTokens } from "@photon-ai/slack";
@@ -338,13 +340,12 @@ var send = async (client, space, content) => {
338
340
  );
339
341
  }
340
342
  if (content.type === "reaction") {
341
- await reactToMessage(
343
+ return await reactToMessage(
342
344
  client,
343
345
  space,
344
346
  content.target.ts ?? content.target.id,
345
- content.emoji
347
+ content
346
348
  );
347
- return;
348
349
  }
349
350
  if (content.type === "typing") {
350
351
  return;
@@ -386,15 +387,23 @@ var sendContent = async (client, space, content, threadTs) => {
386
387
  throw UnsupportedError.content(content.type);
387
388
  }
388
389
  };
389
- var reactToMessage = async (client, space, targetTs, emoji) => {
390
+ var reactToMessage = async (client, space, targetTs, content) => {
390
391
  await client.team(space.teamId).messages.send({
391
392
  channel: space.id,
392
393
  reaction: {
393
- emoji,
394
+ emoji: content.emoji,
394
395
  itemChannel: space.id,
395
396
  itemTs: targetTs
396
397
  }
397
398
  });
399
+ return {
400
+ id: `${targetTs}:reaction:self:${content.emoji}`,
401
+ content,
402
+ space: { id: space.id, teamId: space.teamId },
403
+ timestamp: /* @__PURE__ */ new Date(),
404
+ ts: targetTs,
405
+ isFromMe: true
406
+ };
398
407
  };
399
408
  var replyToMessage = async (client, space, targetTs, content) => await sendContent(client, space, content, targetTs);
400
409
 
@@ -422,7 +431,6 @@ var spaceSchema = z.object({
422
431
  teamId: z.string()
423
432
  });
424
433
  var spaceParamsSchema = z.object({
425
- channel: z.string().optional(),
426
434
  teamId: z.string()
427
435
  });
428
436
  var messageSchema = z.object({
@@ -473,27 +481,18 @@ var slack = definePlatform("Slack", {
473
481
  space: {
474
482
  schema: spaceSchema,
475
483
  params: spaceParamsSchema,
476
- resolve: async ({ input }) => {
484
+ create: async ({ input }) => {
477
485
  const teamId = input.params?.teamId;
478
486
  if (!teamId) {
479
487
  throw new Error(
480
- "Slack space creation requires a teamId param. Pass it via slack.space({ channel, teamId }) or slack.space([user], { teamId })."
481
- );
482
- }
483
- const channel = input.params?.channel;
484
- if (channel) {
485
- return { id: channel, teamId };
486
- }
487
- if (input.users.length === 0) {
488
- throw new Error(
489
- "Slack space creation requires either a channel param or at least one user"
488
+ "Slack space creation requires a teamId param. Pass it via space.create(user, { teamId })."
490
489
  );
491
490
  }
492
491
  if (input.users.length > 1) {
493
492
  throw UnsupportedError.action(
494
- "createSpace",
493
+ "space.create",
495
494
  "Slack",
496
- "group DMs require an explicit channel id (Slack's conversations.open is not exposed); pass `channel` in params"
495
+ "group DMs require an explicit channel id (Slack's conversations.open is not exposed); use space.get(channelId, { teamId })"
497
496
  );
498
497
  }
499
498
  const user = input.users[0];
@@ -501,6 +500,15 @@ var slack = definePlatform("Slack", {
501
500
  throw new Error("Slack space creation requires a user");
502
501
  }
503
502
  return { id: user.id, teamId };
503
+ },
504
+ get: async ({ input }) => {
505
+ const teamId = input.params?.teamId;
506
+ if (!teamId) {
507
+ throw new Error(
508
+ "Slack spaces require a teamId param. Pass it via space.get(channelId, { teamId })."
509
+ );
510
+ }
511
+ return { id: input.id, teamId };
504
512
  }
505
513
  },
506
514
  message: {
@@ -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,