spectrum-ts 1.13.1 → 1.15.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.
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  bufferToStream,
3
+ fetchUrlBytes,
3
4
  readSchema,
4
5
  streamSchema
5
- } from "./chunk-YDHES53X.js";
6
+ } from "./chunk-YFCDKZ6W.js";
6
7
 
7
8
  // src/content/voice.ts
8
9
  import { createReadStream } from "fs";
@@ -26,11 +27,23 @@ var resolveVoiceName = (input, name) => {
26
27
  if (name) {
27
28
  return name;
28
29
  }
30
+ if (input instanceof URL) {
31
+ return basename(input.pathname) || void 0;
32
+ }
29
33
  if (typeof input === "string") {
30
34
  return basename(input);
31
35
  }
32
36
  return;
33
37
  };
38
+ var resolveVoiceMimeHint = (input, name) => {
39
+ if (input instanceof URL) {
40
+ return basename(input.pathname) || void 0;
41
+ }
42
+ if (typeof input === "string") {
43
+ return basename(input);
44
+ }
45
+ return name;
46
+ };
34
47
  var resolveVoiceMimeType = (name, mimeType) => {
35
48
  if (mimeType) {
36
49
  if (!AUDIO_MIME_PATTERN.test(mimeType)) {
@@ -79,8 +92,16 @@ function voice(input, options) {
79
92
  return {
80
93
  build: async () => {
81
94
  const name = resolveVoiceName(input, options?.name);
82
- const mimeHint = typeof input === "string" ? basename(input) : name;
95
+ const mimeHint = resolveVoiceMimeHint(input, name);
83
96
  const mimeType = resolveVoiceMimeType(mimeHint, options?.mimeType);
97
+ if (input instanceof URL) {
98
+ return asVoice({
99
+ name,
100
+ mimeType,
101
+ duration: options?.duration,
102
+ read: async () => (await fetchUrlBytes(input)).data
103
+ });
104
+ }
84
105
  if (typeof input === "string") {
85
106
  const stats = await stat(input);
86
107
  if (!stats.isFile()) {
@@ -2,7 +2,7 @@ import {
2
2
  asGroup,
3
3
  asRichlink,
4
4
  groupSchema
5
- } from "./chunk-UFJZIZDO.js";
5
+ } from "./chunk-JBMQ5GEW.js";
6
6
  import {
7
7
  asPoll,
8
8
  asPollOption
@@ -16,7 +16,7 @@ import {
16
16
  asContact,
17
17
  fromVCard,
18
18
  toVCard
19
- } from "./chunk-L3VXHUVY.js";
19
+ } from "./chunk-J2ANGYYW.js";
20
20
  import {
21
21
  UnsupportedError,
22
22
  asAttachment,
@@ -29,11 +29,12 @@ import {
29
29
  reactionSchema,
30
30
  text,
31
31
  textSchema
32
- } from "./chunk-YDHES53X.js";
32
+ } from "./chunk-YFCDKZ6W.js";
33
33
 
34
34
  // src/providers/imessage/index.ts
35
35
  import { createClient as createClient2, MessageEffect as MessageEffect2 } from "@photon-ai/advanced-imessage";
36
36
  import { IMessageSDK as IMessageSDK2 } from "@photon-ai/imessage-kit";
37
+ import { withSpan } from "@photon-ai/otel";
37
38
 
38
39
  // src/providers/imessage/content/background.ts
39
40
  import z from "zod";
@@ -322,6 +323,7 @@ var readLocalAttachment = async (att) => {
322
323
  var toAttachmentContent = (att) => {
323
324
  const { localPath } = att;
324
325
  return asAttachment({
326
+ id: att.id,
325
327
  name: att.fileName ?? DEFAULT_ATTACHMENT_NAME,
326
328
  mimeType: att.mimeType,
327
329
  size: att.sizeBytes,
@@ -546,7 +548,7 @@ var setBackground = async (remote, spaceId, content) => {
546
548
 
547
549
  // src/providers/imessage/remote/inbound.ts
548
550
  import {
549
- NotFoundError
551
+ NotFoundError as NotFoundError2
550
552
  } from "@photon-ai/advanced-imessage";
551
553
 
552
554
  // src/providers/imessage/cache.ts
@@ -620,45 +622,10 @@ var getPollCache = (owner) => {
620
622
  return cache;
621
623
  };
622
624
 
623
- // src/providers/imessage/remote/inbound.ts
624
- var URL_BALLOON_BUNDLE_ID = "com.apple.messages.URLBalloonProvider";
625
- var getBalloonBundleId = (message) => message.content.balloonBundleId;
626
- var messageAttachments = (message) => message.content.attachments;
627
- var resolveChatGuid = (message, hint) => {
628
- if (hint) {
629
- return hint;
630
- }
631
- const first = message.chatGuids?.[0];
632
- return first ?? "";
633
- };
634
- var resolveSenderId = (message) => message.sender?.address ?? "";
635
- var isIMessageMessage = (value) => {
636
- if (typeof value !== "object" || value === null) {
637
- return false;
638
- }
639
- const record = value;
640
- return typeof record.id === "string" && record.id.length > 0 && typeof record.content === "object" && record.content !== null && typeof record.sender === "object" && record.sender !== null && typeof record.space === "object" && record.space !== null;
641
- };
642
- var asProviderGroup = (items) => groupSchema.parse({ type: "group", items });
643
- var buildMessageBase = (message, chatGuidHint, timestamp, phone) => {
644
- const chat = resolveChatGuid(message, chatGuidHint);
645
- return {
646
- sender: { id: resolveSenderId(message) },
647
- space: {
648
- id: chat,
649
- type: chat.includes(";+;") ? "group" : "dm",
650
- phone
651
- },
652
- timestamp
653
- };
654
- };
655
- var toAttachmentContent2 = (client, info) => asAttachment({
656
- name: info.fileName,
657
- mimeType: info.mimeType,
658
- size: info.totalBytes,
659
- read: async () => await downloadPrimaryAttachment(client, info.guid),
660
- stream: async () => downloadPrimaryAttachmentStream(client, info.guid)
661
- });
625
+ // src/providers/imessage/remote/attachments.ts
626
+ import {
627
+ NotFoundError
628
+ } from "@photon-ai/advanced-imessage";
662
629
  var downloadPrimaryAttachmentStream = (client, attachmentGuid) => {
663
630
  const frames = client.attachments.downloadStream(attachmentGuid);
664
631
  const iterator = frames[Symbol.asyncIterator]();
@@ -713,6 +680,66 @@ var downloadPrimaryAttachment = async (client, attachmentGuid) => {
713
680
  }
714
681
  return Buffer.concat(chunks);
715
682
  };
683
+ var getRemoteAttachment = async (client, guid) => {
684
+ let info;
685
+ try {
686
+ info = await client.attachments.get(guid);
687
+ } catch (err) {
688
+ if (err instanceof NotFoundError) {
689
+ return;
690
+ }
691
+ throw err;
692
+ }
693
+ return asAttachment({
694
+ id: info.guid,
695
+ name: info.fileName,
696
+ mimeType: info.mimeType,
697
+ size: info.totalBytes,
698
+ read: () => downloadPrimaryAttachment(client, info.guid),
699
+ stream: async () => downloadPrimaryAttachmentStream(client, info.guid)
700
+ });
701
+ };
702
+
703
+ // src/providers/imessage/remote/inbound.ts
704
+ var URL_BALLOON_BUNDLE_ID = "com.apple.messages.URLBalloonProvider";
705
+ var getBalloonBundleId = (message) => message.content.balloonBundleId;
706
+ var messageAttachments = (message) => message.content.attachments;
707
+ var resolveChatGuid = (message, hint) => {
708
+ if (hint) {
709
+ return hint;
710
+ }
711
+ const first = message.chatGuids?.[0];
712
+ return first ?? "";
713
+ };
714
+ var resolveSenderId = (message) => message.sender?.address ?? "";
715
+ var isIMessageMessage = (value) => {
716
+ if (typeof value !== "object" || value === null) {
717
+ return false;
718
+ }
719
+ const record = value;
720
+ return typeof record.id === "string" && record.id.length > 0 && typeof record.content === "object" && record.content !== null && typeof record.sender === "object" && record.sender !== null && typeof record.space === "object" && record.space !== null;
721
+ };
722
+ var asProviderGroup = (items) => groupSchema.parse({ type: "group", items });
723
+ var buildMessageBase = (message, chatGuidHint, timestamp, phone) => {
724
+ const chat = resolveChatGuid(message, chatGuidHint);
725
+ return {
726
+ sender: { id: resolveSenderId(message) },
727
+ space: {
728
+ id: chat,
729
+ type: chat.includes(";+;") ? "group" : "dm",
730
+ phone
731
+ },
732
+ timestamp
733
+ };
734
+ };
735
+ var toAttachmentContent2 = (client, info) => asAttachment({
736
+ id: info.guid,
737
+ name: info.fileName,
738
+ mimeType: info.mimeType,
739
+ size: info.totalBytes,
740
+ read: async () => await downloadPrimaryAttachment(client, info.guid),
741
+ stream: async () => downloadPrimaryAttachmentStream(client, info.guid)
742
+ });
716
743
  var toVCardContent2 = async (client, info) => {
717
744
  try {
718
745
  const buf = await downloadPrimaryAttachment(client, info.guid);
@@ -896,7 +923,7 @@ var getMessage3 = async (remote, spaceId, msgId, phone) => {
896
923
  const item = parent.content.items[childRef.partIndex];
897
924
  return isIMessageMessage(item) ? item : void 0;
898
925
  } catch (err) {
899
- if (err instanceof NotFoundError) {
926
+ if (err instanceof NotFoundError2) {
900
927
  return;
901
928
  }
902
929
  throw err;
@@ -916,7 +943,7 @@ var getMessage3 = async (remote, spaceId, msgId, phone) => {
916
943
  cacheMessage(cache, rebuilt);
917
944
  return rebuilt;
918
945
  } catch (err) {
919
- if (err instanceof NotFoundError) {
946
+ if (err instanceof NotFoundError2) {
920
947
  return;
921
948
  }
922
949
  throw err;
@@ -1375,7 +1402,7 @@ var editMessage = async (remote, spaceId, msgId, content) => {
1375
1402
  import {
1376
1403
  AuthenticationError,
1377
1404
  IMessageError,
1378
- NotFoundError as NotFoundError2,
1405
+ NotFoundError as NotFoundError3,
1379
1406
  ValidationError
1380
1407
  } from "@photon-ai/advanced-imessage";
1381
1408
 
@@ -1751,7 +1778,7 @@ var toPollDeltaMessages = async (client, pollCache, event, phone) => {
1751
1778
 
1752
1779
  // src/providers/imessage/remote/stream.ts
1753
1780
  var isRetryableIMessageStreamError = (error) => {
1754
- if (error instanceof AuthenticationError || error instanceof NotFoundError2 || error instanceof ValidationError) {
1781
+ if (error instanceof AuthenticationError || error instanceof NotFoundError3 || error instanceof ValidationError) {
1755
1782
  return false;
1756
1783
  }
1757
1784
  if (error instanceof IMessageError) {
@@ -2202,12 +2229,53 @@ var imessage = definePlatform("iMessage", {
2202
2229
  return await send4(remote, space.id, content);
2203
2230
  },
2204
2231
  actions: {
2205
- getMessage: async ({ space, messageId, client }) => {
2232
+ getMessage: async ({ client }, space, messageId) => {
2206
2233
  if (isLocal(client)) {
2207
2234
  return getMessage2(client, messageId);
2208
2235
  }
2209
2236
  const remote = clientForPhone(client, space.phone);
2210
2237
  return getMessage4(remote, space.id, messageId, space.phone);
2238
+ },
2239
+ // Fetch an attachment by GUID. Returns a spectrum `Attachment` whose
2240
+ // `.read()` / `.stream()` lazily download the bytes — calling both
2241
+ // issues two independent gRPC downloads, so cache `.read()` if you
2242
+ // need the bytes more than once. Returns `undefined` for unknown
2243
+ // GUIDs. Local-mode iMessage is not supported.
2244
+ getAttachment: async ({ client }, guid, phone) => {
2245
+ if (isLocal(client)) {
2246
+ throw UnsupportedError.action(
2247
+ "getAttachment",
2248
+ "iMessage (local mode)",
2249
+ "fetching attachments by GUID requires remote iMessage"
2250
+ );
2251
+ }
2252
+ if (client.length === 0) {
2253
+ throw new Error("No iMessage clients configured");
2254
+ }
2255
+ const routedPhone = (() => {
2256
+ if (isSharedMode(client)) {
2257
+ return SHARED_PHONE;
2258
+ }
2259
+ if (phone) {
2260
+ return phone;
2261
+ }
2262
+ if (client.length === 1) {
2263
+ return client[0].phone;
2264
+ }
2265
+ throw new Error(
2266
+ `imessage.getAttachment requires a phone in multi-phone mode. Available: ${availablePhones(client).join(", ")}`
2267
+ );
2268
+ })();
2269
+ const remote = clientForPhone(client, routedPhone);
2270
+ return withSpan(
2271
+ "spectrum.imessage.getAttachment",
2272
+ {
2273
+ "spectrum.provider": "iMessage",
2274
+ "spectrum.imessage.attachment.guid": guid,
2275
+ "spectrum.imessage.phone": routedPhone
2276
+ },
2277
+ () => getRemoteAttachment(remote, guid)
2278
+ );
2211
2279
  }
2212
2280
  }
2213
2281
  });
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readSchema
3
- } from "./chunk-YDHES53X.js";
3
+ } from "./chunk-YFCDKZ6W.js";
4
4
 
5
5
  // src/utils/vcard.ts
6
6
  import vCard from "vcf";
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  bufferToStream,
3
+ fetchUrlBytes,
3
4
  readSchema,
4
5
  resolveContents,
5
6
  streamSchema
6
- } from "./chunk-YDHES53X.js";
7
+ } from "./chunk-YFCDKZ6W.js";
7
8
 
8
9
  // src/content/group.ts
9
10
  import z from "zod";
@@ -82,24 +83,10 @@ var fetchLinkMetadata = async (url) => {
82
83
  return {};
83
84
  }
84
85
  };
85
- var fetchImage = async (url) => {
86
- const controller = new AbortController();
87
- const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
88
- try {
89
- const res = await fetch(url, {
90
- signal: controller.signal,
91
- headers: { "User-Agent": USER_AGENT }
92
- });
93
- if (!res.ok) {
94
- throw new Error(`image fetch ${url} returned ${res.status}`);
95
- }
96
- const data = Buffer.from(await res.arrayBuffer());
97
- const mimeType = res.headers.get("content-type") ?? void 0;
98
- return { data, mimeType };
99
- } finally {
100
- clearTimeout(timer);
101
- }
102
- };
86
+ var fetchImage = (url) => fetchUrlBytes(new URL(url), {
87
+ timeoutMs: DEFAULT_TIMEOUT_MS,
88
+ headers: { "User-Agent": USER_AGENT }
89
+ });
103
90
 
104
91
  // src/content/richlink.ts
105
92
  var richlinkCoverSchema = z2.object({
@@ -1,18 +1,18 @@
1
1
  import {
2
2
  asVoice
3
- } from "./chunk-4TXLNBGE.js";
3
+ } from "./chunk-2B76JFHX.js";
4
4
  import {
5
5
  asContact,
6
6
  fromVCard,
7
7
  toVCard
8
- } from "./chunk-L3VXHUVY.js";
8
+ } from "./chunk-J2ANGYYW.js";
9
9
  import {
10
10
  UnsupportedError,
11
11
  asAttachment,
12
12
  asCustom,
13
13
  definePlatform,
14
14
  reactionSchema
15
- } from "./chunk-YDHES53X.js";
15
+ } from "./chunk-YFCDKZ6W.js";
16
16
 
17
17
  // src/providers/terminal/index.ts
18
18
  import { spawn } from "child_process";
@@ -10,7 +10,7 @@ import {
10
10
  asReaction,
11
11
  asText,
12
12
  definePlatform
13
- } from "./chunk-YDHES53X.js";
13
+ } from "./chunk-YFCDKZ6W.js";
14
14
 
15
15
  // src/providers/slack/index.ts
16
16
  import { createClient as createClient2, staticTokens } from "@photon-ai/slack";
@@ -169,6 +169,7 @@ var tsToDate = (ts) => {
169
169
  return new Date(seconds * 1e3);
170
170
  };
171
171
  var lazySlackFile = (client, teamId, file) => asAttachment({
172
+ id: file.id,
172
173
  name: file.name,
173
174
  mimeType: file.mimeType,
174
175
  size: file.size,
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-YKWKZ2PZ.js";
9
9
  import {
10
10
  asContact
11
- } from "./chunk-L3VXHUVY.js";
11
+ } from "./chunk-J2ANGYYW.js";
12
12
  import {
13
13
  UnsupportedError,
14
14
  asAttachment,
@@ -16,7 +16,7 @@ import {
16
16
  asReaction,
17
17
  asText,
18
18
  definePlatform
19
- } from "./chunk-YDHES53X.js";
19
+ } from "./chunk-YFCDKZ6W.js";
20
20
 
21
21
  // src/providers/whatsapp-business/index.ts
22
22
  import { createClient as createClient2 } from "@photon-ai/whatsapp-business";
@@ -508,6 +508,7 @@ var fetchMedia = async (client, mediaId) => {
508
508
  return response;
509
509
  };
510
510
  var lazyMedia = (client, media) => asAttachment({
511
+ id: media.id,
511
512
  name: media.filename ?? `media-${media.id}`,
512
513
  mimeType: media.mimeType,
513
514
  read: async () => Buffer.from(await (await fetchMedia(client, media.id)).arrayBuffer()),
@@ -1,4 +1,5 @@
1
1
  // src/content/attachment.ts
2
+ import { randomUUID } from "crypto";
2
3
  import { createReadStream } from "fs";
3
4
  import { readFile, stat } from "fs/promises";
4
5
  import { basename } from "path";
@@ -22,18 +23,52 @@ var bufferToStream = (buf) => new ReadableStream({
22
23
  controller.close();
23
24
  }
24
25
  });
26
+ var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
27
+ var fetchUrlBytes = async (url, options) => {
28
+ const controller = new AbortController();
29
+ const timer = setTimeout(
30
+ () => controller.abort(),
31
+ options?.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS
32
+ );
33
+ try {
34
+ const res = await fetch(url, {
35
+ signal: controller.signal,
36
+ headers: options?.headers
37
+ });
38
+ if (!res.ok) {
39
+ throw new Error(`URL fetch ${url.toString()} returned ${res.status}`);
40
+ }
41
+ const data = Buffer.from(await res.arrayBuffer());
42
+ const mimeType = res.headers.get("content-type") ?? void 0;
43
+ return { data, mimeType };
44
+ } finally {
45
+ clearTimeout(timer);
46
+ }
47
+ };
25
48
 
26
49
  // src/content/attachment.ts
27
50
  var DEFAULT_ATTACHMENT_NAME = "attachment";
28
51
  var attachmentSchema = z2.object({
29
52
  type: z2.literal("attachment"),
53
+ id: z2.string().nonempty(),
30
54
  name: z2.string().nonempty(),
31
55
  mimeType: z2.string().nonempty(),
32
56
  size: z2.number().int().nonnegative().optional(),
33
57
  read: readSchema,
34
58
  stream: streamSchema
35
59
  });
36
- var resolveAttachmentName = (input, name) => name || (typeof input === "string" ? basename(input) : DEFAULT_ATTACHMENT_NAME);
60
+ var resolveAttachmentName = (input, name) => {
61
+ if (name) {
62
+ return name;
63
+ }
64
+ if (input instanceof URL) {
65
+ return basename(input.pathname) || DEFAULT_ATTACHMENT_NAME;
66
+ }
67
+ if (typeof input === "string") {
68
+ return basename(input);
69
+ }
70
+ return DEFAULT_ATTACHMENT_NAME;
71
+ };
37
72
  var resolveAttachmentMimeType = (name, mimeType) => {
38
73
  if (mimeType) {
39
74
  return mimeType;
@@ -58,6 +93,7 @@ var asAttachment = (input) => {
58
93
  const stream = input.stream ?? (async () => bufferToStream(await read()));
59
94
  return attachmentSchema.parse({
60
95
  type: "attachment",
96
+ id: input.id ?? randomUUID(),
61
97
  name: input.name,
62
98
  mimeType: input.mimeType,
63
99
  size: input.size,
@@ -68,11 +104,21 @@ var asAttachment = (input) => {
68
104
  function attachment(input, options) {
69
105
  return {
70
106
  build: async () => {
107
+ const id = options?.id;
71
108
  const name = resolveAttachmentName(input, options?.name);
72
109
  const mimeType = resolveAttachmentMimeType(name, options?.mimeType);
110
+ if (input instanceof URL) {
111
+ return asAttachment({
112
+ id,
113
+ name,
114
+ mimeType,
115
+ read: async () => (await fetchUrlBytes(input)).data
116
+ });
117
+ }
73
118
  if (typeof input === "string") {
74
119
  const stats = await stat(input);
75
120
  return asAttachment({
121
+ id,
76
122
  name,
77
123
  mimeType,
78
124
  size: stats.size,
@@ -83,6 +129,7 @@ function attachment(input, options) {
83
129
  });
84
130
  }
85
131
  return asAttachment({
132
+ id,
86
133
  name,
87
134
  mimeType,
88
135
  size: input.byteLength,
@@ -114,7 +161,12 @@ var resolveMimeType = (input, mimeType, contentLabel) => {
114
161
  if (mimeType) {
115
162
  return mimeType;
116
163
  }
117
- if (typeof input === "string") {
164
+ if (input instanceof URL) {
165
+ const resolved = lookupMimeType2(basename2(input.pathname));
166
+ if (resolved) {
167
+ return resolved;
168
+ }
169
+ } else if (typeof input === "string") {
118
170
  const resolved = lookupMimeType2(basename2(input));
119
171
  if (resolved) {
120
172
  return resolved;
@@ -140,7 +192,9 @@ var buildPhotoAction = (input, options, contentLabel) => {
140
192
  }
141
193
  const mimeType = resolveMimeType(input, options?.mimeType, contentLabel);
142
194
  let read;
143
- if (typeof input === "string") {
195
+ if (input instanceof URL) {
196
+ read = cachedRead(async () => (await fetchUrlBytes(input)).data);
197
+ } else if (typeof input === "string") {
144
198
  read = cachedRead(() => readFile2(input));
145
199
  } else {
146
200
  const snapshot = Buffer.from(input);
@@ -502,6 +556,7 @@ var RESERVED_SPACE_KEYS = /* @__PURE__ */ new Set([
502
556
  "stopTyping",
503
557
  "responding"
504
558
  ]);
559
+ var PLATFORM_WISE_ACTION_KEYS = /* @__PURE__ */ new Set(["getMessage"]);
505
560
  var RESERVED_MESSAGE_KEYS = /* @__PURE__ */ new Set([
506
561
  "content",
507
562
  "direction",
@@ -514,8 +569,17 @@ var RESERVED_MESSAGE_KEYS = /* @__PURE__ */ new Set([
514
569
  "space",
515
570
  "timestamp"
516
571
  ]);
572
+ var scopeLabel = (scope) => {
573
+ if (scope === "space") {
574
+ return "Space";
575
+ }
576
+ if (scope === "message") {
577
+ return "Message";
578
+ }
579
+ return "PlatformInstance";
580
+ };
517
581
  var warnReservedAction = (scope, name, platform) => {
518
- const body = `[spectrum-ts] ${platform} declared ${scope} action "${name}" which collides with a reserved ${scope === "space" ? "Space" : "Message"} key; skipping.`;
582
+ const body = `[spectrum-ts] ${platform} declared ${scope} action "${name}" which collides with a reserved ${scopeLabel(scope)} key; skipping.`;
519
583
  console.warn(
520
584
  supportsAnsiColor() ? `${ANSI_YELLOW}${body}${ANSI_RESET}` : body
521
585
  );
@@ -718,11 +782,7 @@ function buildSpace(params) {
718
782
  async function getMessageImpl(id) {
719
783
  const getMessage = definition.actions?.getMessage;
720
784
  if (!getMessage) {
721
- warnUnsupported(
722
- UnsupportedError.action("getMessage", definition.name),
723
- definition.name
724
- );
725
- return;
785
+ throw UnsupportedError.action("getMessage", definition.name);
726
786
  }
727
787
  return withSpan(
728
788
  "spectrum.message.get",
@@ -732,22 +792,11 @@ function buildSpace(params) {
732
792
  "spectrum.message.id": id
733
793
  },
734
794
  async () => {
735
- let raw;
736
- try {
737
- raw = await getMessage({
738
- space: spaceRef,
739
- messageId: id,
740
- client,
741
- config,
742
- store
743
- });
744
- } catch (err) {
745
- if (err instanceof UnsupportedError) {
746
- warnUnsupported(err, definition.name);
747
- return;
748
- }
749
- throw err;
750
- }
795
+ const raw = await getMessage(
796
+ { client, config, store },
797
+ spaceRef,
798
+ id
799
+ );
751
800
  if (!raw) {
752
801
  return;
753
802
  }
@@ -785,7 +834,7 @@ function buildSpace(params) {
785
834
  await space.send(rename(displayName));
786
835
  },
787
836
  avatar: (async (input, options) => {
788
- if (typeof input === "string") {
837
+ if (typeof input === "string" || input instanceof URL) {
789
838
  await space.send(avatar(input, options));
790
839
  return;
791
840
  }
@@ -905,6 +954,36 @@ function classifySpaceIdentifier(args) {
905
954
  }
906
955
  return { kind, identifier };
907
956
  }
957
+ function buildInstanceActions(platformName, declared, reservedKeys, buildCtx) {
958
+ const out = {};
959
+ for (const key of PLATFORM_WISE_ACTION_KEYS) {
960
+ const override = declared?.[key];
961
+ if (override && typeof override === "function") {
962
+ out[key] = (...args) => override(buildCtx(), ...args);
963
+ } else {
964
+ out[key] = () => {
965
+ throw UnsupportedError.action(key, platformName);
966
+ };
967
+ }
968
+ }
969
+ if (!declared) {
970
+ return out;
971
+ }
972
+ for (const [name, factory] of Object.entries(declared)) {
973
+ if (PLATFORM_WISE_ACTION_KEYS.has(name)) {
974
+ continue;
975
+ }
976
+ if (reservedKeys.has(name)) {
977
+ warnReservedAction("instance", name, platformName);
978
+ continue;
979
+ }
980
+ if (typeof factory !== "function") {
981
+ continue;
982
+ }
983
+ out[name] = (...args) => factory(buildCtx(), ...args);
984
+ }
985
+ return out;
986
+ }
908
987
  function createPlatformInstance(def, runtime) {
909
988
  const isPlatformUser = (value) => typeof value === "object" && value !== null && "__platform" in value && value.__platform === def.name;
910
989
  const resolveUserID = async (userID) => {
@@ -1040,7 +1119,26 @@ function createPlatformInstance(def, runtime) {
1040
1119
  return messagesIterable;
1041
1120
  }
1042
1121
  });
1043
- return Object.assign(base, eventProperties);
1122
+ const instanceActions = buildInstanceActions(
1123
+ def.name,
1124
+ def.actions,
1125
+ /* @__PURE__ */ new Set([
1126
+ "user",
1127
+ "space",
1128
+ "messages",
1129
+ ...Object.keys(customEvents)
1130
+ ]),
1131
+ () => ({
1132
+ client: runtime.client,
1133
+ config: runtime.config,
1134
+ store: runtime.store
1135
+ })
1136
+ );
1137
+ return Object.assign(
1138
+ base,
1139
+ instanceActions,
1140
+ eventProperties
1141
+ );
1044
1142
  }
1045
1143
  function definePlatform(name, def) {
1046
1144
  const fullDef = { name, ...def };
@@ -1121,6 +1219,7 @@ export {
1121
1219
  readSchema,
1122
1220
  streamSchema,
1123
1221
  bufferToStream,
1222
+ fetchUrlBytes,
1124
1223
  attachmentSchema,
1125
1224
  asAttachment,
1126
1225
  attachment,