spectrum-ts 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/{attachment-B4nSrKVd.d.ts → attachment-WePAHfcH.d.ts} +1 -1
  2. package/dist/{authoring-BjE5BvlO.d.ts → authoring-DDh3muGT.d.ts} +61 -26
  3. package/dist/authoring.d.ts +3 -3
  4. package/dist/authoring.js +5 -5
  5. package/dist/{chunk-NNY6LMSC.js → chunk-77U6SH5A.js} +1 -1
  6. package/dist/{chunk-ATNAE7OR.js → chunk-AYCMTRVC.js} +549 -76
  7. package/dist/{chunk-6BI4PFTP.js → chunk-CHY5YLLV.js} +1 -1
  8. package/dist/{chunk-U3LXXT3W.js → chunk-EZ5SNNFS.js} +20 -8
  9. package/dist/{chunk-WXY5QP3M.js → chunk-FULEQIRQ.js} +27 -21
  10. package/dist/{chunk-2ILTJC35.js → chunk-LQMDV75O.js} +205 -11
  11. package/dist/{chunk-NGC4DJIX.js → chunk-LX437ZTY.js} +416 -135
  12. package/dist/{chunk-3B4QH4JG.js → chunk-MHGCPC2V.js} +1 -1
  13. package/dist/{chunk-U7AWXDH6.js → chunk-NZ5WCMTY.js} +1 -1
  14. package/dist/{chunk-5LT5J3NR.js → chunk-TXRWKSNH.js} +262 -30
  15. package/dist/{chunk-Q537JPTG.js → chunk-UXJ5OO6P.js} +10 -10
  16. package/dist/index.d.ts +107 -56
  17. package/dist/index.js +29 -182
  18. package/dist/providers/imessage/index.d.ts +6 -14
  19. package/dist/providers/imessage/index.js +6 -6
  20. package/dist/providers/index.d.ts +3 -3
  21. package/dist/providers/index.js +11 -11
  22. package/dist/providers/slack/index.d.ts +1 -2
  23. package/dist/providers/slack/index.js +3 -3
  24. package/dist/providers/telegram/index.d.ts +3 -5
  25. package/dist/providers/telegram/index.js +5 -5
  26. package/dist/providers/terminal/index.d.ts +2 -4
  27. package/dist/providers/terminal/index.js +5 -5
  28. package/dist/providers/whatsapp-business/index.d.ts +1 -1
  29. package/dist/providers/whatsapp-business/index.js +4 -4
  30. package/dist/{types-BD0-kKyv.d.ts → types-BujGKBin.d.ts} +1 -1
  31. package/dist/{types-Bje8aq1k.d.ts → types-YqCNUDIt.d.ts} +171 -23
  32. package/package.json +2 -1
@@ -1,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";
@@ -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
@@ -748,21 +1052,6 @@ function buildMessage(params) {
748
1052
 
749
1053
  // src/platform/define.ts
750
1054
  var platformLog2 = createLogger2("spectrum.platform");
751
- function classifySpaceIdentifier(args) {
752
- const stringArgs = args.filter((a) => typeof a === "string");
753
- if (stringArgs.length > 1) {
754
- return { kind: "group" };
755
- }
756
- const s = stringArgs[0];
757
- if (!s) {
758
- return { kind: "unknown" };
759
- }
760
- const { kind, identifier } = classifyIdentifier(s);
761
- if (kind === "unknown") {
762
- return { kind: "unknown" };
763
- }
764
- return { kind, identifier };
765
- }
766
1055
  function buildInstanceActions(platformName, declared, reservedKeys, buildCtx) {
767
1056
  const out = {};
768
1057
  for (const key of PLATFORM_WISE_ACTION_KEYS) {
@@ -794,7 +1083,6 @@ function buildInstanceActions(platformName, declared, reservedKeys, buildCtx) {
794
1083
  return out;
795
1084
  }
796
1085
  function createPlatformInstance(def, runtime) {
797
- const isPlatformUser = (value) => typeof value === "object" && value !== null && "__platform" in value && value.__platform === def.name;
798
1086
  const resolveUserID = async (userID) => {
799
1087
  const resolved = await def.user.resolve({
800
1088
  input: { userID },
@@ -807,105 +1095,93 @@ function createPlatformInstance(def, runtime) {
807
1095
  __platform: def.name
808
1096
  };
809
1097
  };
810
- const resolveStringUsers = async (args) => {
811
- const convertArg = async (arg) => {
812
- if (typeof arg === "string") {
813
- return await resolveUserID(arg);
814
- }
815
- if (Array.isArray(arg)) {
816
- return await Promise.all(arg.map(convertArg));
817
- }
818
- 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
819
1110
  };
820
- return await Promise.all(args.map(convertArg));
821
- };
822
- const normalizeSpaceArgs = (args) => {
823
- if (args.length === 0) {
824
- return { users: [], params: void 0 };
825
- }
826
- const [first, ...rest] = args;
827
- if (Array.isArray(first)) {
828
- return {
829
- users: first,
830
- params: rest[0]
831
- };
832
- }
833
- if (!isPlatformUser(first)) {
834
- return {
835
- users: [],
836
- params: first
837
- };
838
- }
839
- const last = args.at(-1);
840
- if (last !== void 0 && !isPlatformUser(last)) {
841
- return {
842
- users: args.slice(0, -1),
843
- params: last
844
- };
845
- }
846
- return {
847
- users: args,
848
- params: void 0
1111
+ const actionCtx = {
1112
+ space: spaceRef,
1113
+ ...providerCtx()
849
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
+ });
850
1124
  };
851
1125
  const base = {
852
1126
  async user(userID) {
853
- const resolved = await def.user.resolve({
854
- input: { userID },
855
- client: runtime.client,
856
- config: runtime.config,
857
- store: runtime.store
858
- });
859
- return {
860
- ...resolved,
861
- __platform: def.name
862
- };
1127
+ return await resolveUserID(userID);
863
1128
  },
864
- async space(...args) {
865
- const { kind, identifier } = classifySpaceIdentifier(args);
866
- return withSpan2(
867
- "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",
868
1159
  {
869
1160
  "spectrum.provider": def.name,
870
- "spectrum.space.identifier_kind": kind,
871
- "spectrum.space.identifier": identifier
1161
+ "spectrum.space.id": id
872
1162
  },
873
1163
  async () => {
874
- const convertedArgs = await resolveStringUsers(args);
875
- const { users, params } = normalizeSpaceArgs(convertedArgs);
876
- let parsedParams = params;
877
- if (params !== void 0 && def.space.params) {
878
- 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);
879
1171
  }
880
- const resolved = await def.space.resolve({
881
- input: { users, params: parsedParams },
882
- client: runtime.client,
883
- config: runtime.config,
884
- store: runtime.store
885
- });
886
- const parsedSpace = def.space.schema ? def.space.schema.parse(resolved) : resolved;
887
- const spaceRef = {
888
- ...parsedSpace,
889
- id: parsedSpace.id,
890
- __platform: def.name
891
- };
892
- const actionCtx = {
893
- space: spaceRef,
894
- client: runtime.client,
895
- config: runtime.config,
896
- store: runtime.store
897
- };
898
- return buildSpace({
899
- spaceRef,
900
- extras: parsedSpace,
901
- actionCtx,
902
- definition: def,
903
- client: runtime.client,
904
- config: runtime.config,
905
- store: runtime.store
906
- });
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
+ }
1181
+ }
1182
+ return finalizeSpace(candidate);
907
1183
  }
908
- );
1184
+ )
909
1185
  }
910
1186
  };
911
1187
  const eventProperties = {};
@@ -1039,10 +1315,15 @@ export {
1039
1315
  buildPhotoAction,
1040
1316
  avatar,
1041
1317
  edit,
1318
+ markdownSchema,
1319
+ asMarkdown,
1320
+ markdown,
1042
1321
  rename,
1043
1322
  reply,
1044
1323
  typing,
1324
+ unsend,
1045
1325
  UnsupportedError,
1326
+ renderInlineTokens,
1046
1327
  contentAttrs,
1047
1328
  senderAttrs,
1048
1329
  wrapProviderMessage,