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
@@ -1,10 +1,14 @@
1
1
  import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
2
2
  import {
3
+ StreamConsumedError,
4
+ asText,
5
+ drainStreamText,
3
6
  fetchUrlBytes,
4
7
  reaction,
5
8
  readSchema,
6
- resolveContents
7
- } from "./chunk-2ILTJC35.js";
9
+ resolveContents,
10
+ streamTextBuilder
11
+ } from "./chunk-LQMDV75O.js";
8
12
 
9
13
  // src/content/avatar.ts
10
14
  import z2 from "zod";
@@ -98,6 +102,11 @@ var asEdit = (input) => editSchema.parse({ type: "edit", ...input });
98
102
  function edit(content, target) {
99
103
  return {
100
104
  build: async () => {
105
+ if (!target) {
106
+ throw new Error(
107
+ "edit() target is undefined \u2014 the targeted message was never sent (space.send resolves undefined when a platform skips unsupported content)"
108
+ );
109
+ }
101
110
  if (target.direction !== "outbound") {
102
111
  throw new Error(
103
112
  `edit() target must be an outbound message (got direction "${target.direction}", message id "${target.id}")`
@@ -107,7 +116,7 @@ function edit(content, target) {
107
116
  if (!resolved) {
108
117
  throw new Error("edit() requires content");
109
118
  }
110
- if (resolved.type === "edit" || resolved.type === "reply" || resolved.type === "reaction" || resolved.type === "group" || resolved.type === "typing" || resolved.type === "rename" || resolved.type === "avatar") {
119
+ if (resolved.type === "edit" || resolved.type === "reply" || resolved.type === "reaction" || resolved.type === "group" || resolved.type === "typing" || resolved.type === "rename" || resolved.type === "avatar" || resolved.type === "unsend") {
111
120
  throw new Error(`edit() cannot wrap "${resolved.type}" content`);
112
121
  }
113
122
  return asEdit({ content: resolved, target });
@@ -115,11 +124,25 @@ function edit(content, target) {
115
124
  };
116
125
  }
117
126
 
118
- // src/content/rename.ts
127
+ // src/content/markdown.ts
119
128
  import z4 from "zod";
120
- var renameSchema = z4.object({
121
- type: z4.literal("rename"),
122
- displayName: z4.string().min(1, "rename() displayName must be non-empty")
129
+ var markdownSchema = z4.object({
130
+ type: z4.literal("markdown"),
131
+ markdown: z4.string().nonempty()
132
+ });
133
+ var asMarkdown = (markdown2) => markdownSchema.parse({ type: "markdown", markdown: markdown2 });
134
+ function markdown(source, options) {
135
+ if (typeof source === "string") {
136
+ return { build: async () => asMarkdown(source) };
137
+ }
138
+ return streamTextBuilder("markdown", source, options);
139
+ }
140
+
141
+ // src/content/rename.ts
142
+ import z5 from "zod";
143
+ var renameSchema = z5.object({
144
+ type: z5.literal("rename"),
145
+ displayName: z5.string().min(1, "rename() displayName must be non-empty")
123
146
  });
124
147
  function rename(displayName) {
125
148
  return {
@@ -128,15 +151,15 @@ function rename(displayName) {
128
151
  }
129
152
 
130
153
  // src/content/reply.ts
131
- import z5 from "zod";
154
+ import z6 from "zod";
132
155
  var isMessage2 = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
133
156
  var isContent2 = (v) => typeof v === "object" && v !== null && "type" in v && typeof v.type === "string";
134
- var replySchema = z5.object({
135
- type: z5.literal("reply"),
136
- content: z5.custom(isContent2, {
157
+ var replySchema = z6.object({
158
+ type: z6.literal("reply"),
159
+ content: z6.custom(isContent2, {
137
160
  message: "reply content must be a Content value"
138
161
  }),
139
- target: z5.custom(isMessage2, {
162
+ target: z6.custom(isMessage2, {
140
163
  message: "reply target must be a Message"
141
164
  })
142
165
  });
@@ -144,11 +167,16 @@ var asReply = (input) => replySchema.parse({ type: "reply", ...input });
144
167
  function reply(content, target) {
145
168
  return {
146
169
  build: async () => {
170
+ if (!target) {
171
+ throw new Error(
172
+ "reply() target is undefined \u2014 the targeted message was never sent (space.send resolves undefined when a platform skips unsupported content)"
173
+ );
174
+ }
147
175
  const [resolved] = await resolveContents([content]);
148
176
  if (!resolved) {
149
177
  throw new Error("reply() requires content");
150
178
  }
151
- if (resolved.type === "reply" || resolved.type === "edit" || resolved.type === "reaction" || resolved.type === "group" || resolved.type === "typing" || resolved.type === "rename" || resolved.type === "avatar") {
179
+ if (resolved.type === "reply" || resolved.type === "edit" || resolved.type === "reaction" || resolved.type === "group" || resolved.type === "typing" || resolved.type === "rename" || resolved.type === "avatar" || resolved.type === "unsend") {
152
180
  throw new Error(`reply() cannot wrap "${resolved.type}" content`);
153
181
  }
154
182
  return asReply({ content: resolved, target });
@@ -157,10 +185,10 @@ function reply(content, target) {
157
185
  }
158
186
 
159
187
  // src/content/typing.ts
160
- import z6 from "zod";
161
- var typingSchema = z6.object({
162
- type: z6.literal("typing"),
163
- state: z6.enum(["start", "stop"])
188
+ import z7 from "zod";
189
+ var typingSchema = z7.object({
190
+ type: z7.literal("typing"),
191
+ state: z7.enum(["start", "stop"])
164
192
  });
165
193
  function typing(state = "start") {
166
194
  return {
@@ -168,6 +196,34 @@ function typing(state = "start") {
168
196
  };
169
197
  }
170
198
 
199
+ // src/content/unsend.ts
200
+ import z8 from "zod";
201
+ var isMessage3 = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
202
+ var unsendSchema = z8.object({
203
+ type: z8.literal("unsend"),
204
+ target: z8.custom(isMessage3, {
205
+ message: "unsend target must be a Message"
206
+ })
207
+ });
208
+ var asUnsend = (input) => unsendSchema.parse({ type: "unsend", ...input });
209
+ function unsend(target) {
210
+ return {
211
+ build: async () => {
212
+ if (!target) {
213
+ throw new Error(
214
+ "unsend() target is undefined \u2014 the targeted message was never sent (space.send resolves undefined when a platform skips unsupported content)"
215
+ );
216
+ }
217
+ if (target.direction !== "outbound") {
218
+ throw new Error(
219
+ `unsend() target must be an outbound message (got direction "${target.direction}", message id "${target.id}")`
220
+ );
221
+ }
222
+ return asUnsend({ target });
223
+ }
224
+ };
225
+ }
226
+
171
227
  // src/utils/errors.ts
172
228
  var composeMessage = (opts) => {
173
229
  const platform = opts.platform ?? "platform";
@@ -216,7 +272,7 @@ var UnsupportedError = class _UnsupportedError extends Error {
216
272
  };
217
273
 
218
274
  // src/platform/define.ts
219
- import { withSpan as withSpan2 } from "@photon-ai/otel";
275
+ import { createLogger as createLogger2, withSpan as withSpan2 } from "@photon-ai/otel";
220
276
 
221
277
  // src/utils/identifier.ts
222
278
  import { sanitizeEmail, sanitizePhone } from "@photon-ai/otel";
@@ -235,6 +291,133 @@ function classifyIdentifier(s) {
235
291
  // src/platform/build.ts
236
292
  import { createLogger, withSpan } from "@photon-ai/otel";
237
293
 
294
+ // src/utils/markdown.ts
295
+ import { Marked } from "marked";
296
+ var markdownLexer = new Marked();
297
+ var BULLET = "\u2022 ";
298
+ var HR_LINE = "\u2014\u2014\u2014";
299
+ var NESTED_LIST_INDENT = " ";
300
+ var BLOCK_SEPARATOR = "\n\n";
301
+ var TABLE_CELL_SEPARATOR = " | ";
302
+ var DEFAULT_LIST_START = 1;
303
+ var asMarkedToken = (token) => token;
304
+ var checkboxPrefix = (item) => {
305
+ if (!item.task) {
306
+ return "";
307
+ }
308
+ return item.checked ? "[x] " : "[ ] ";
309
+ };
310
+ var listMarker = (list, index) => {
311
+ if (!list.ordered) {
312
+ return BULLET;
313
+ }
314
+ const start = list.start === "" ? DEFAULT_LIST_START : list.start;
315
+ return `${start + index}. `;
316
+ };
317
+ var renderLink = (token) => {
318
+ if (token.text === token.href) {
319
+ return token.href;
320
+ }
321
+ return `${renderInlineTokens(token.tokens)} (${token.href})`;
322
+ };
323
+ var renderImage = (token) => token.text ? `${token.text} (${token.href})` : token.href;
324
+ var renderInlineToken = (token) => {
325
+ switch (token.type) {
326
+ case "strong":
327
+ case "em":
328
+ case "del":
329
+ return renderInlineTokens(token.tokens);
330
+ case "codespan":
331
+ return token.text;
332
+ case "br":
333
+ return "\n";
334
+ case "link":
335
+ return renderLink(token);
336
+ case "image":
337
+ return renderImage(token);
338
+ case "escape":
339
+ return token.text;
340
+ case "text":
341
+ return token.tokens ? renderInlineTokens(token.tokens) : token.text;
342
+ // Raw HTML in markdown source stays literal — plain text has no markup.
343
+ case "html":
344
+ return token.text;
345
+ // Task-item checkboxes are rendered from `ListItem.task`/`checked`.
346
+ case "checkbox":
347
+ return "";
348
+ default:
349
+ return "raw" in token ? String(token.raw) : "";
350
+ }
351
+ };
352
+ var renderInlineTokens = (tokens) => {
353
+ let out = "";
354
+ for (const token of tokens) {
355
+ out += renderInlineToken(asMarkedToken(token));
356
+ }
357
+ return out;
358
+ };
359
+ var renderBlockquote = (quote) => renderBlockTokens(quote.tokens).split("\n").map((line) => line ? `> ${line}` : ">").join("\n");
360
+ var renderList = (list) => {
361
+ const lines = [];
362
+ for (const [index, item] of list.items.entries()) {
363
+ const prefix = `${listMarker(list, index)}${checkboxPrefix(item)}`;
364
+ const blocks = [];
365
+ for (const token of item.tokens) {
366
+ const rendered = renderBlockToken(asMarkedToken(token));
367
+ if (rendered) {
368
+ blocks.push(rendered);
369
+ }
370
+ }
371
+ const [first = "", ...rest] = blocks.join("\n").split("\n");
372
+ lines.push(`${prefix}${first}`);
373
+ for (const line of rest) {
374
+ lines.push(`${NESTED_LIST_INDENT}${line}`);
375
+ }
376
+ }
377
+ return lines.join("\n");
378
+ };
379
+ var renderTable = (table) => {
380
+ const renderRow = (cells) => cells.map((cell) => renderInlineTokens(cell.tokens)).join(TABLE_CELL_SEPARATOR);
381
+ const lines = [renderRow(table.header)];
382
+ for (const row of table.rows) {
383
+ lines.push(renderRow(row));
384
+ }
385
+ return lines.join("\n");
386
+ };
387
+ var renderBlockToken = (token) => {
388
+ switch (token.type) {
389
+ case "heading":
390
+ case "paragraph":
391
+ return renderInlineTokens(token.tokens);
392
+ case "code":
393
+ return token.text;
394
+ case "blockquote":
395
+ return renderBlockquote(token);
396
+ case "list":
397
+ return renderList(token);
398
+ case "table":
399
+ return renderTable(token);
400
+ case "hr":
401
+ return HR_LINE;
402
+ case "space":
403
+ case "def":
404
+ return "";
405
+ default:
406
+ return renderInlineToken(token);
407
+ }
408
+ };
409
+ var renderBlockTokens = (tokens) => {
410
+ const blocks = [];
411
+ for (const token of tokens) {
412
+ const rendered = renderBlockToken(asMarkedToken(token));
413
+ if (rendered) {
414
+ blocks.push(rendered);
415
+ }
416
+ }
417
+ return blocks.join(BLOCK_SEPARATOR);
418
+ };
419
+ var markdownToPlainText = (markdown2) => renderBlockTokens(markdownLexer.lexer(markdown2)).trim();
420
+
238
421
  // src/utils/telemetry.ts
239
422
  var targetId = (target) => {
240
423
  const id = target?.id;
@@ -254,6 +437,13 @@ var replyOrEditAttrs = (content) => {
254
437
  "spectrum.message.content.inner.type": typeof innerType === "string" ? innerType : void 0
255
438
  };
256
439
  };
440
+ var unsendAttrs = (content) => {
441
+ const target = content.target;
442
+ return {
443
+ "spectrum.message.content.target.id": targetId(target),
444
+ "spectrum.message.content.target.type": targetType(target)
445
+ };
446
+ };
257
447
  var reactionAttrs = (content) => {
258
448
  const target = content.target;
259
449
  const emoji = content.emoji;
@@ -303,6 +493,7 @@ var voiceAttrs = (content) => {
303
493
  var CONTENT_ATTR_HANDLERS = {
304
494
  reply: replyOrEditAttrs,
305
495
  edit: replyOrEditAttrs,
496
+ unsend: unsendAttrs,
306
497
  reaction: reactionAttrs,
307
498
  group: groupAttrs,
308
499
  typing: typingAttrs,
@@ -350,11 +541,11 @@ var supportsAnsiColor = () => {
350
541
  return Boolean(process.stderr?.isTTY);
351
542
  };
352
543
  var FIRE_AND_FORGET_TYPES = /* @__PURE__ */ new Set([
353
- "reaction",
354
544
  "typing",
355
545
  "edit",
356
546
  "rename",
357
- "avatar"
547
+ "avatar",
548
+ "unsend"
358
549
  ]);
359
550
  var isFireAndForget = (item) => FIRE_AND_FORGET_TYPES.has(item.type) || item.__fireAndForget === true;
360
551
  var RESERVED_SPACE_KEYS = /* @__PURE__ */ new Set([
@@ -362,6 +553,7 @@ var RESERVED_SPACE_KEYS = /* @__PURE__ */ new Set([
362
553
  "id",
363
554
  "send",
364
555
  "edit",
556
+ "unsend",
365
557
  "getMessage",
366
558
  "rename",
367
559
  "avatar",
@@ -380,7 +572,8 @@ var RESERVED_MESSAGE_KEYS = /* @__PURE__ */ new Set([
380
572
  "reply",
381
573
  "sender",
382
574
  "space",
383
- "timestamp"
575
+ "timestamp",
576
+ "unsend"
384
577
  ]);
385
578
  var scopeLabel = (scope) => {
386
579
  if (scope === "space") {
@@ -451,6 +644,102 @@ var unsupportedPlatformContentError = (content, platform) => {
451
644
  `requires ${requiredPlatform}`
452
645
  );
453
646
  };
647
+ var findStreamText = (item) => {
648
+ if (item.type === "streamText") {
649
+ return item;
650
+ }
651
+ if ((item.type === "reply" || item.type === "edit") && item.content.type === "streamText") {
652
+ return item.content;
653
+ }
654
+ return;
655
+ };
656
+ var replaceStreamText = (item, source, full) => {
657
+ const inner = source.format === "markdown" ? asMarkdown(full) : asText(full);
658
+ if (item.type === "reply" || item.type === "edit") {
659
+ return { ...item, content: inner };
660
+ }
661
+ return inner;
662
+ };
663
+ var downgradeMarkdown = (md) => {
664
+ const plain = markdownToPlainText(md.markdown);
665
+ return plain ? asText(plain) : void 0;
666
+ };
667
+ var replaceMarkdown = (item) => {
668
+ if (item.type === "markdown") {
669
+ return downgradeMarkdown(item) ?? item;
670
+ }
671
+ if ((item.type === "reply" || item.type === "edit") && item.content.type === "markdown") {
672
+ const downgraded = downgradeMarkdown(item.content);
673
+ return downgraded ? { ...item, content: downgraded } : item;
674
+ }
675
+ if (item.type === "group") {
676
+ let changed = false;
677
+ const items = item.items.map((member) => {
678
+ if (member.content.type !== "markdown") {
679
+ return member;
680
+ }
681
+ const downgraded = downgradeMarkdown(member.content);
682
+ if (!downgraded) {
683
+ return member;
684
+ }
685
+ changed = true;
686
+ return { ...member, content: downgraded };
687
+ });
688
+ return changed ? { ...item, items } : item;
689
+ }
690
+ return item;
691
+ };
692
+ async function resendDrainedStream(send, item, source, platform, unsupported) {
693
+ platformLog.info(
694
+ `${platform} does not support streaming text; waiting for the stream to finish to send the full text as one message.`,
695
+ {
696
+ "spectrum.provider": platform,
697
+ "spectrum.stream_text.fallback": true
698
+ }
699
+ );
700
+ let full;
701
+ try {
702
+ full = await drainStreamText(source);
703
+ } catch (drainErr) {
704
+ if (drainErr instanceof StreamConsumedError) {
705
+ throw unsupported;
706
+ }
707
+ throw drainErr;
708
+ }
709
+ if (!full) {
710
+ throw unsupported;
711
+ }
712
+ return await sendWithFallbacks(
713
+ send,
714
+ replaceStreamText(item, source, full),
715
+ platform
716
+ );
717
+ }
718
+ async function sendWithFallbacks(send, item, platform) {
719
+ try {
720
+ return await send(item);
721
+ } catch (err) {
722
+ if (!(err instanceof UnsupportedError)) {
723
+ throw err;
724
+ }
725
+ const source = findStreamText(item);
726
+ if (source) {
727
+ return await resendDrainedStream(send, item, source, platform, err);
728
+ }
729
+ const downgraded = replaceMarkdown(item);
730
+ if (downgraded === item) {
731
+ throw err;
732
+ }
733
+ platformLog.info(
734
+ `${platform} does not support markdown; sending the content as plain text instead.`,
735
+ {
736
+ "spectrum.provider": platform,
737
+ "spectrum.markdown.fallback": true
738
+ }
739
+ );
740
+ return await send(downgraded);
741
+ }
742
+ }
454
743
  var providerMessageCoreKeys = /* @__PURE__ */ new Set([
455
744
  "content",
456
745
  "id",
@@ -537,19 +826,21 @@ function buildSpace(params) {
537
826
  ...contentAttrs(item)
538
827
  },
539
828
  async () => {
829
+ const platformError = unsupportedPlatformContentError(
830
+ item,
831
+ definition.name
832
+ );
833
+ if (platformError) {
834
+ warnUnsupported(platformError, definition.name);
835
+ return;
836
+ }
837
+ const providerSend = async (content) => await definition.send({
838
+ ...actionCtx,
839
+ content
840
+ });
540
841
  let raw;
541
842
  try {
542
- const platformError = unsupportedPlatformContentError(
543
- item,
544
- definition.name
545
- );
546
- if (platformError) {
547
- throw platformError;
548
- }
549
- raw = await definition.send({
550
- ...actionCtx,
551
- content: item
552
- });
843
+ raw = await sendWithFallbacks(providerSend, item, definition.name);
553
844
  } catch (err) {
554
845
  if (err instanceof UnsupportedError) {
555
846
  warnUnsupported(err, definition.name);
@@ -637,6 +928,9 @@ function buildSpace(params) {
637
928
  edit: async (message, newContent) => {
638
929
  await space.send(edit(newContent, message));
639
930
  },
931
+ unsend: async (message) => {
932
+ await space.send(unsend(message));
933
+ },
640
934
  getMessage: getMessageImpl,
641
935
  rename: async (displayName) => {
642
936
  await space.send(rename(displayName));
@@ -684,7 +978,7 @@ function buildMessage(params) {
684
978
  };
685
979
  const react = async (emoji) => {
686
980
  const target = requireBuiltMessage("react");
687
- await space.send(reaction(emoji, target));
981
+ return await space.send(reaction(emoji, target));
688
982
  };
689
983
  async function reply2(...content) {
690
984
  const target = requireBuiltMessage("reply");
@@ -700,6 +994,15 @@ function buildMessage(params) {
700
994
  }
701
995
  await space.send(edit(newContent, target));
702
996
  };
997
+ const unsend2 = async () => {
998
+ const target = requireBuiltMessage("unsend");
999
+ if (target.direction !== "outbound") {
1000
+ throw new Error(
1001
+ `cannot unsend message ${target.id}: only outbound messages can be unsent (direction: "${target.direction}")`
1002
+ );
1003
+ }
1004
+ await space.send(unsend(target));
1005
+ };
703
1006
  const buildSenderWithPlatform = () => {
704
1007
  if (params.sender === void 0) {
705
1008
  return;
@@ -738,6 +1041,7 @@ function buildMessage(params) {
738
1041
  react,
739
1042
  reply: reply2,
740
1043
  edit: edit2,
1044
+ unsend: unsend2,
741
1045
  sender: senderWithPlatform,
742
1046
  space,
743
1047
  timestamp: params.timestamp
@@ -747,21 +1051,7 @@ function buildMessage(params) {
747
1051
  }
748
1052
 
749
1053
  // src/platform/define.ts
750
- function classifySpaceIdentifier(args) {
751
- const stringArgs = args.filter((a) => typeof a === "string");
752
- if (stringArgs.length > 1) {
753
- return { kind: "group" };
754
- }
755
- const s = stringArgs[0];
756
- if (!s) {
757
- return { kind: "unknown" };
758
- }
759
- const { kind, identifier } = classifyIdentifier(s);
760
- if (kind === "unknown") {
761
- return { kind: "unknown" };
762
- }
763
- return { kind, identifier };
764
- }
1054
+ var platformLog2 = createLogger2("spectrum.platform");
765
1055
  function buildInstanceActions(platformName, declared, reservedKeys, buildCtx) {
766
1056
  const out = {};
767
1057
  for (const key of PLATFORM_WISE_ACTION_KEYS) {
@@ -793,7 +1083,6 @@ function buildInstanceActions(platformName, declared, reservedKeys, buildCtx) {
793
1083
  return out;
794
1084
  }
795
1085
  function createPlatformInstance(def, runtime) {
796
- const isPlatformUser = (value) => typeof value === "object" && value !== null && "__platform" in value && value.__platform === def.name;
797
1086
  const resolveUserID = async (userID) => {
798
1087
  const resolved = await def.user.resolve({
799
1088
  input: { userID },
@@ -806,117 +1095,112 @@ function createPlatformInstance(def, runtime) {
806
1095
  __platform: def.name
807
1096
  };
808
1097
  };
809
- const resolveStringUsers = async (args) => {
810
- const convertArg = async (arg) => {
811
- if (typeof arg === "string") {
812
- return await resolveUserID(arg);
813
- }
814
- if (Array.isArray(arg)) {
815
- return await Promise.all(arg.map(convertArg));
816
- }
817
- return arg;
1098
+ const providerCtx = () => ({
1099
+ client: runtime.client,
1100
+ config: runtime.config,
1101
+ store: runtime.store
1102
+ });
1103
+ const parseSpaceParams = (params) => params !== void 0 && def.space.params ? def.space.params.parse(params) : params;
1104
+ const finalizeSpace = (resolved) => {
1105
+ const parsedSpace = def.space.schema ? def.space.schema.parse(resolved) : resolved;
1106
+ const spaceRef = {
1107
+ ...parsedSpace,
1108
+ id: parsedSpace.id,
1109
+ __platform: def.name
818
1110
  };
819
- return await Promise.all(args.map(convertArg));
820
- };
821
- const normalizeSpaceArgs = (args) => {
822
- if (args.length === 0) {
823
- return { users: [], params: void 0 };
824
- }
825
- const [first, ...rest] = args;
826
- if (Array.isArray(first)) {
827
- return {
828
- users: first,
829
- params: rest[0]
830
- };
831
- }
832
- if (!isPlatformUser(first)) {
833
- return {
834
- users: [],
835
- params: first
836
- };
837
- }
838
- const last = args.at(-1);
839
- if (last !== void 0 && !isPlatformUser(last)) {
840
- return {
841
- users: args.slice(0, -1),
842
- params: last
843
- };
844
- }
845
- return {
846
- users: args,
847
- params: void 0
1111
+ const actionCtx = {
1112
+ space: spaceRef,
1113
+ ...providerCtx()
848
1114
  };
1115
+ return buildSpace({
1116
+ spaceRef,
1117
+ extras: parsedSpace,
1118
+ actionCtx,
1119
+ definition: def,
1120
+ client: runtime.client,
1121
+ config: runtime.config,
1122
+ store: runtime.store
1123
+ });
849
1124
  };
850
1125
  const base = {
851
1126
  async user(userID) {
852
- const resolved = await def.user.resolve({
853
- input: { userID },
854
- client: runtime.client,
855
- config: runtime.config,
856
- store: runtime.store
857
- });
858
- return {
859
- ...resolved,
860
- __platform: def.name
861
- };
1127
+ return await resolveUserID(userID);
862
1128
  },
863
- async space(...args) {
864
- const { kind, identifier } = classifySpaceIdentifier(args);
865
- return withSpan2(
866
- "spectrum.space.resolve",
1129
+ space: {
1130
+ create: async (users, params) => {
1131
+ const userList = Array.isArray(users) ? users : [users];
1132
+ const first = userList.length === 1 ? userList[0] : void 0;
1133
+ const single = typeof first === "string" ? classifyIdentifier(first) : void 0;
1134
+ const kind = userList.length > 1 ? "group" : single?.kind ?? "unknown";
1135
+ return await withSpan2(
1136
+ "spectrum.space.create",
1137
+ {
1138
+ "spectrum.provider": def.name,
1139
+ "spectrum.space.user_count": userList.length,
1140
+ "spectrum.space.identifier_kind": kind,
1141
+ "spectrum.space.identifier": kind === "unknown" ? void 0 : single?.identifier
1142
+ },
1143
+ async () => {
1144
+ const resolvedUsers = await Promise.all(
1145
+ userList.map(
1146
+ (u) => typeof u === "string" ? resolveUserID(u) : u
1147
+ )
1148
+ );
1149
+ const resolved = await def.space.create({
1150
+ input: { users: resolvedUsers, params: parseSpaceParams(params) },
1151
+ ...providerCtx()
1152
+ });
1153
+ return finalizeSpace(resolved);
1154
+ }
1155
+ );
1156
+ },
1157
+ get: async (id, params) => await withSpan2(
1158
+ "spectrum.space.get",
867
1159
  {
868
1160
  "spectrum.provider": def.name,
869
- "spectrum.space.identifier_kind": kind,
870
- "spectrum.space.identifier": identifier
1161
+ "spectrum.space.id": id
871
1162
  },
872
1163
  async () => {
873
- const convertedArgs = await resolveStringUsers(args);
874
- const { users, params } = normalizeSpaceArgs(convertedArgs);
875
- let parsedParams = params;
876
- if (params !== void 0 && def.space.params) {
877
- parsedParams = def.space.params.parse(params);
1164
+ const parsedParams = parseSpaceParams(params);
1165
+ if (def.space.get) {
1166
+ const resolved = await def.space.get({
1167
+ input: { id, params: parsedParams },
1168
+ ...providerCtx()
1169
+ });
1170
+ return finalizeSpace(resolved);
1171
+ }
1172
+ const candidate = { id };
1173
+ if (def.space.schema) {
1174
+ const parsed = def.space.schema.safeParse(candidate);
1175
+ if (!parsed.success) {
1176
+ throw new Error(
1177
+ `Platform "${def.name}" cannot construct a space from an id alone \u2014 its space schema requires more fields. Implement \`space.get\` in the "${def.name}" provider definition.`,
1178
+ { cause: parsed.error }
1179
+ );
1180
+ }
878
1181
  }
879
- const resolved = await def.space.resolve({
880
- input: { users, params: parsedParams },
881
- client: runtime.client,
882
- config: runtime.config,
883
- store: runtime.store
884
- });
885
- const parsedSpace = def.space.schema ? def.space.schema.parse(resolved) : resolved;
886
- const spaceRef = {
887
- ...parsedSpace,
888
- id: parsedSpace.id,
889
- __platform: def.name
890
- };
891
- const actionCtx = {
892
- space: spaceRef,
893
- client: runtime.client,
894
- config: runtime.config,
895
- store: runtime.store
896
- };
897
- return buildSpace({
898
- spaceRef,
899
- extras: parsedSpace,
900
- actionCtx,
901
- definition: def,
902
- client: runtime.client,
903
- config: runtime.config,
904
- store: runtime.store
905
- });
1182
+ return finalizeSpace(candidate);
906
1183
  }
907
- );
1184
+ )
908
1185
  }
909
1186
  };
910
1187
  const eventProperties = {};
911
1188
  const customEvents = def.events ?? {};
912
1189
  for (const eventName of Object.keys(customEvents)) {
913
- const producer = customEvents[eventName];
914
- if (producer) {
1190
+ const declared = customEvents[eventName];
1191
+ if (typeof declared === "function") {
1192
+ const producer = declared;
915
1193
  eventProperties[eventName] = producer({
916
1194
  client: runtime.client,
917
1195
  config: runtime.config,
1196
+ projectConfig: runtime.projectConfig,
918
1197
  store: runtime.store
919
1198
  });
1199
+ continue;
1200
+ }
1201
+ const fusorEvents = runtime.subscribeEvent?.(eventName);
1202
+ if (fusorEvents) {
1203
+ eventProperties[eventName] = fusorEvents;
920
1204
  }
921
1205
  }
922
1206
  let messagesIterable;
@@ -948,8 +1232,9 @@ function createPlatformInstance(def, runtime) {
948
1232
  eventProperties
949
1233
  );
950
1234
  }
951
- function definePlatform(name, def) {
952
- const fullDef = { name, ...def };
1235
+ function definePlatform(name, rawDef) {
1236
+ const def = rawDef;
1237
+ const fullDef = { ...def, name };
953
1238
  const platformCache = /* @__PURE__ */ new WeakMap();
954
1239
  const narrowSpectrum = (spectrum) => {
955
1240
  const cached = platformCache.get(spectrum);
@@ -969,17 +1254,19 @@ function definePlatform(name, def) {
969
1254
  };
970
1255
  const narrowSpace = (input) => {
971
1256
  if (input.__platform !== name) {
972
- throw new Error(
973
- `Expected space from "${name}", got "${input.__platform}"`
974
- );
1257
+ platformLog2.warn("space platform mismatch; narrowing skipped", {
1258
+ expected: name,
1259
+ actual: input.__platform
1260
+ });
975
1261
  }
976
1262
  return input;
977
1263
  };
978
1264
  const narrowMessage = (input) => {
979
1265
  if (input.platform !== name) {
980
- throw new Error(
981
- `Expected message from "${name}", got "${input.platform}"`
982
- );
1266
+ platformLog2.warn("message platform mismatch; narrowing skipped", {
1267
+ expected: name,
1268
+ actual: input.platform
1269
+ });
983
1270
  }
984
1271
  return input;
985
1272
  };
@@ -1022,26 +1309,24 @@ function definePlatform(name, def) {
1022
1309
  }
1023
1310
  return narrower;
1024
1311
  }
1025
- function defineFusorPlatform(name, def) {
1026
- return definePlatform(
1027
- name,
1028
- def
1029
- );
1030
- }
1031
1312
 
1032
1313
  export {
1033
1314
  photoActionSchema,
1034
1315
  buildPhotoAction,
1035
1316
  avatar,
1036
1317
  edit,
1318
+ markdownSchema,
1319
+ asMarkdown,
1320
+ markdown,
1037
1321
  rename,
1038
1322
  reply,
1039
1323
  typing,
1324
+ unsend,
1040
1325
  UnsupportedError,
1326
+ renderInlineTokens,
1041
1327
  contentAttrs,
1042
1328
  senderAttrs,
1043
1329
  wrapProviderMessage,
1044
1330
  buildSpace,
1045
- definePlatform,
1046
- defineFusorPlatform
1331
+ definePlatform
1047
1332
  };