@spectrum-ts/core 8.2.2 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { $t as asGroup, An as photoActionSchema, Ft as asRead, H as ProviderMessageRecord, Ht as asPollOption, K as ManagedStream, Kt as asMarkdown, On as PhotoInput, St as asText, Tt as asRichlink, Vt as asPoll, cn as asReaction, dn as asCustom, gt as asVoice, jt as replySchema, kn as buildPhotoAction, kt as asReply, r as asAttachment, tn as groupSchema, un as reactionSchema, xn as asContact } from "./attachment-DqrtDRaN.js";
1
+ import { At as asReply, Cn as asCustom, Ct as asText, Et as asRichlink, Ft as renameSchema, H as ProviderMessageRecord, Hn as PhotoInput, Jt as asMarkdown, Lt as asRead, Mt as replySchema, Nn as asContact, Sn as reactionSchema, U as ProviderUserRecord, Un as buildPhotoAction, Ut as asPoll, Vn as avatarSchema, Wn as photoActionSchema, Wt as asPollOption, _n as removeMemberSchema, _t as asVoice, bn as asReaction, hn as leaveSpaceSchema, pn as addMemberSchema, q as ManagedStream, r as asAttachment, rn as groupSchema, tn as asGroup } from "./attachment-B4H1Q8xO.js";
2
2
  import z from "zod";
3
3
  import { FetchSpanOptions, LogAttrs, LogAttrs as LogAttrs$1, LogLevel, PhotonLogger, SanitizeUrlOptions, createLogger, sanitizeEmail, sanitizeErrorMessage, sanitizePhone, sanitizeUrl, setLogLevel } from "@photon-ai/otel";
4
4
  import { Token } from "marked";
@@ -123,4 +123,4 @@ declare const resumableOrderedStream: <TLive, TMissed, TOutput>(options: Resumab
123
123
  */
124
124
  declare function errorAttrs(error: unknown, prefix?: string): LogAttrs$1;
125
125
  //#endregion
126
- export { type CloseableAsyncIterable, type LogAttrs, type LogLevel, type PhotoInput, type PhotonLogger, type ProviderMessageRecord, type ResumableStreamItem, type SanitizeUrlOptions, asAttachment, asContact, asCustom, asGroup, asMarkdown, asPoll, asPollOption, asReaction, asRead, asReply, asRichlink, asText, asVoice, buildPhotoAction, createLogger, ensureM4a, errorAttrs, groupSchema, messageEffectSchema, photoActionSchema, reactionSchema, renderInlineTokens, replySchema, resumableOrderedStream, sanitizeEmail, sanitizeErrorMessage, sanitizePhone, sanitizeUrl, setLogLevel, tracedFetch };
126
+ export { type CloseableAsyncIterable, type LogAttrs, type LogLevel, type PhotoInput, type PhotonLogger, type ProviderMessageRecord, type ProviderUserRecord, type ResumableStreamItem, type SanitizeUrlOptions, addMemberSchema, asAttachment, asContact, asCustom, asGroup, asMarkdown, asPoll, asPollOption, asReaction, asRead, asReply, asRichlink, asText, asVoice, avatarSchema, buildPhotoAction, createLogger, ensureM4a, errorAttrs, groupSchema, leaveSpaceSchema, messageEffectSchema, photoActionSchema, reactionSchema, removeMemberSchema, renameSchema, renderInlineTokens, replySchema, resumableOrderedStream, sanitizeEmail, sanitizeErrorMessage, sanitizePhone, sanitizeUrl, setLogLevel, tracedFetch };
package/dist/authoring.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as asGroup, C as asPoll, D as asMarkdown, G as buildPhotoAction, I as textSchema, K as photoActionSchema, M as groupSchema, P as asText, S as reactionSchema, V as asContact, Y as attachmentSchema, Z as tracedFetch, _ as replySchema, b as asReaction, d as asVoice, h as asReply, i as stream, k as markdownSchema, l as renderInlineTokens, o as errorAttrs, p as asRichlink, q as asAttachment, v as asRead, w as asPollOption, z as asCustom } from "./stream-DAbdViJ9.js";
1
+ import { A as addMemberSchema, B as groupSchema, E as asPollOption, F as asMarkdown, H as asText, L as markdownSchema, M as leaveSpaceSchema, P as removeMemberSchema, R as asGroup, S as asReaction, T as asPoll, W as textSchema, Y as asContact, _ as replySchema, at as attachmentSchema, b as asRead, d as asVoice, et as avatarSchema, h as asReply, i as stream, l as renderInlineTokens, nt as photoActionSchema, o as errorAttrs, p as asRichlink, q as asCustom, rt as asAttachment, st as tracedFetch, tt as buildPhotoAction, w as reactionSchema, y as renameSchema } from "./stream-DxI5ZiE2.js";
2
2
  import z from "zod";
3
3
  import { createLogger, createLogger as createLogger$1, sanitizeEmail, sanitizeErrorMessage, sanitizePhone, sanitizeUrl, setLogLevel } from "@photon-ai/otel";
4
4
  import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
@@ -422,4 +422,4 @@ const resumableOrderedStream = (options) => stream((emit, end) => {
422
422
  };
423
423
  });
424
424
  //#endregion
425
- export { asAttachment, asContact, asCustom, asGroup, asMarkdown, asPoll, asPollOption, asReaction, asRead, asReply, asRichlink, asText, asVoice, buildPhotoAction, createLogger, ensureM4a, errorAttrs, groupSchema, messageEffectSchema, photoActionSchema, reactionSchema, renderInlineTokens, replySchema, resumableOrderedStream, sanitizeEmail, sanitizeErrorMessage, sanitizePhone, sanitizeUrl, setLogLevel, tracedFetch };
425
+ export { addMemberSchema, asAttachment, asContact, asCustom, asGroup, asMarkdown, asPoll, asPollOption, asReaction, asRead, asReply, asRichlink, asText, asVoice, avatarSchema, buildPhotoAction, createLogger, ensureM4a, errorAttrs, groupSchema, leaveSpaceSchema, messageEffectSchema, photoActionSchema, reactionSchema, removeMemberSchema, renameSchema, renderInlineTokens, replySchema, resumableOrderedStream, sanitizeEmail, sanitizeErrorMessage, sanitizePhone, sanitizeUrl, setLogLevel, tracedFetch };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as FusorTokenData, A as isFusorEvent, At as reply, B as PlatformUser, Bt as PollOption, C as FusorVerify, Cn as AgentSender, Ct as text, D as WebhookRawResult, Dn as avatar, Dt as resolveContents, E as WebhookRawRequest, En as AvatarInput, Et as richlink, F as PlatformInstance, Fn as appLayoutSchema, G as Broadcaster, Gt as Markdown, I as PlatformMessage, It as read, J as mergeStreams, Jt as DeltaExtractor, K as ManagedStream, L as PlatformProviderConfig, Lt as Poll, M as EventProducer, Mn as AppLayout, Mt as Rename, N as Platform, Nn as AppUrl, Nt as rename, O as FusorEvent, Ot as Reply, P as PlatformDef, Pn as app, Pt as Read, Q as DedicatedTokenData, Qt as Group, R as PlatformRuntime, Rt as PollChoice, S as FusorRespond, Sn as contact, T as WebhookHandler, Tn as Avatar, U as SchemaMessage, Ut as option, V as ProviderMessage, W as SpaceNamespace, Wt as poll, X as Store, Xt as StreamTextSource, Y as stream, Yt as StreamText, Z as CloudPlatform, Zt as TextStreamOptions, _ as FusorClient, _n as ContactInput, _t as voice, a as Content, an as Space, at as SharedTokenData, b as FusorMessagesReturn, bn as ContactPhone, bt as Typing, c as fromVCard, ct as SpectrumCloudError, d as UnsupportedKind, dt as TokenData, en as group, et as ImessageInfoData, f as Spectrum, fn as custom, ft as cloud, g as isFusorClient, gn as ContactEmail, h as fusor, hn as ContactDetails, ht as Voice, i as attachment, in as Message, it as ProjectProfile, j as AnyPlatformDef, jn as App, k as fusorEvent, l as toVCard, ln as reaction, lt as SubscriptionData, m as definePlatform, mn as ContactAddress, mt as EmojiKey, n as AttachmentInput, nn as Edit, nt as PlatformsData, o as ContentBuilder, on as Reaction, ot as SlackTeamMeta, p as SpectrumInstance, pn as Contact, pt as Emoji, q as broadcast, qt as markdown, rn as edit, rt as ProjectData, s as ContentInput, sn as ReactionBuilder, st as SlackTokenData, t as Attachment, tt as PlatformStatus, u as UnsupportedError, ut as SubscriptionStatus, v as FusorMessages, vn as ContactName, vt as Unsend, w as FusorVerifyRequest, wn as User, wt as Richlink, x as FusorReply, xt as typing, y as FusorMessagesCtx, yn as ContactOrg, yt as unsend, z as PlatformSpace, zt as PollChoiceInput } from "./attachment-DqrtDRaN.js";
2
- export { type AgentSender, type AnyPlatformDef, type App, type AppLayout, type AppUrl, type Attachment, type AttachmentInput, type Avatar, type AvatarInput, type Broadcaster, type CloudPlatform, type Contact, type ContactAddress, type ContactDetails, type ContactEmail, type ContactInput, type ContactName, type ContactOrg, type ContactPhone, type Content, type ContentBuilder, type ContentInput, type DedicatedTokenData, type DeltaExtractor, type Edit, Emoji, type EmojiKey, type EventProducer, type FusorClient, type FusorEvent, type FusorMessages, type FusorMessagesCtx, type FusorMessagesReturn, type FusorReply, type FusorRespond, type FusorTokenData, type FusorVerify, type FusorVerifyRequest, type Group, type ImessageInfoData, type ManagedStream, type Markdown, type Message, type Platform, type PlatformDef, type PlatformInstance, type PlatformMessage, type PlatformProviderConfig, type PlatformRuntime, type PlatformSpace, type PlatformStatus, type PlatformUser, type PlatformsData, type Poll, type PollChoice, type PollChoiceInput, type PollOption, type ProjectData, type ProjectProfile, type ProviderMessage, type Reaction, type ReactionBuilder, type Read, type Rename, type Reply, type Richlink, type SchemaMessage, type SharedTokenData, type SlackTeamMeta, type SlackTokenData, type Space, type SpaceNamespace, Spectrum, SpectrumCloudError, type SpectrumInstance, type Store, type StreamText, type StreamTextSource, type SubscriptionData, type SubscriptionStatus, type TextStreamOptions, type TokenData, type Typing, type Unsend, UnsupportedError, type UnsupportedKind, type User, type Voice, type WebhookHandler, type WebhookRawRequest, type WebhookRawResult, app, appLayoutSchema, attachment, avatar, broadcast, cloud, contact, custom, definePlatform, edit, fromVCard, fusor, fusorEvent, group, isFusorClient, isFusorEvent, markdown, mergeStreams, option, poll, reaction, read, rename, reply, resolveContents, richlink, stream, text, toVCard, typing, unsend, voice };
1
+ import { $ as DedicatedTokenData, $t as TextStreamOptions, A as isFusorEvent, An as ContactName, B as PlatformUser, Bn as avatar, Bt as PollChoice, C as FusorVerify, D as WebhookRawResult, Dn as ContactDetails, Dt as richlink, E as WebhookRawRequest, En as ContactAddress, F as PlatformInstance, Fn as AgentSender, G as SpaceNamespace, Gn as App, Gt as option, Ht as PollOption, I as PlatformMessage, In as User, It as Read, J as broadcast, Jn as app, K as Broadcaster, Kn as AppLayout, Kt as poll, L as PlatformProviderConfig, Ln as Avatar, M as EventProducer, Mn as ContactPhone, N as Platform, Nt as Rename, O as FusorEvent, On as ContactEmail, Ot as resolveContents, P as PlatformDef, Pn as contact, Pt as rename, Q as CloudPlatform, Qt as StreamTextSource, R as PlatformRuntime, Rn as AvatarData, Rt as read, S as FusorRespond, St as typing, T as WebhookHandler, Tn as Contact, Tt as Richlink, V as ProviderMessage, Vt as PollChoiceInput, W as SchemaMessage, X as stream, Xt as DeltaExtractor, Y as mergeStreams, Yn as appLayoutSchema, Yt as markdown, Z as Store, Zt as StreamText, _ as FusorClient, a as Content, an as edit, at as ProjectProfile, b as FusorMessagesReturn, bt as unsend, c as fromVCard, cn as AddMember, ct as SlackTokenData, d as UnsupportedKind, dn as RemoveMember, dt as SubscriptionStatus, en as Group, et as FusorTokenData, f as Spectrum, fn as addMember, ft as TokenData, g as isFusorClient, gn as removeMember, gt as Voice, h as fusor, ht as EmojiKey, i as attachment, in as Edit, it as ProjectData, j as AnyPlatformDef, jn as ContactOrg, jt as reply, k as fusorEvent, kn as ContactInput, kt as Reply, l as toVCard, ln as LeaveSpace, lt as SpectrumCloudError, m as definePlatform, mn as leaveSpace, mt as Emoji, n as AttachmentInput, nn as group, nt as PlatformStatus, o as ContentBuilder, on as Message, ot as SharedTokenData, p as SpectrumInstance, pt as cloud, q as ManagedStream, qn as AppUrl, qt as Markdown, rt as PlatformsData, s as ContentInput, sn as Space, st as SlackTeamMeta, t as Attachment, tt as ImessageInfoData, u as UnsupportedError, un as MemberInput, ut as SubscriptionData, v as FusorMessages, vn as Reaction, vt as voice, w as FusorVerifyRequest, wn as custom, wt as text, x as FusorReply, xn as reaction, xt as Typing, y as FusorMessagesCtx, yn as ReactionBuilder, yt as Unsend, z as PlatformSpace, zn as AvatarInput, zt as Poll } from "./attachment-B4H1Q8xO.js";
2
+ export { type AddMember, type AgentSender, type AnyPlatformDef, type App, type AppLayout, type AppUrl, type Attachment, type AttachmentInput, type Avatar, type AvatarData, type AvatarInput, type Broadcaster, type CloudPlatform, type Contact, type ContactAddress, type ContactDetails, type ContactEmail, type ContactInput, type ContactName, type ContactOrg, type ContactPhone, type Content, type ContentBuilder, type ContentInput, type DedicatedTokenData, type DeltaExtractor, type Edit, Emoji, type EmojiKey, type EventProducer, type FusorClient, type FusorEvent, type FusorMessages, type FusorMessagesCtx, type FusorMessagesReturn, type FusorReply, type FusorRespond, type FusorTokenData, type FusorVerify, type FusorVerifyRequest, type Group, type ImessageInfoData, type LeaveSpace, type ManagedStream, type Markdown, type MemberInput, type Message, type Platform, type PlatformDef, type PlatformInstance, type PlatformMessage, type PlatformProviderConfig, type PlatformRuntime, type PlatformSpace, type PlatformStatus, type PlatformUser, type PlatformsData, type Poll, type PollChoice, type PollChoiceInput, type PollOption, type ProjectData, type ProjectProfile, type ProviderMessage, type Reaction, type ReactionBuilder, type Read, type RemoveMember, type Rename, type Reply, type Richlink, type SchemaMessage, type SharedTokenData, type SlackTeamMeta, type SlackTokenData, type Space, type SpaceNamespace, Spectrum, SpectrumCloudError, type SpectrumInstance, type Store, type StreamText, type StreamTextSource, type SubscriptionData, type SubscriptionStatus, type TextStreamOptions, type TokenData, type Typing, type Unsend, UnsupportedError, type UnsupportedKind, type User, type Voice, type WebhookHandler, type WebhookRawRequest, type WebhookRawResult, addMember, app, appLayoutSchema, attachment, avatar, broadcast, cloud, contact, custom, definePlatform, edit, fromVCard, fusor, fusorEvent, group, isFusorClient, isFusorEvent, leaveSpace, markdown, mergeStreams, option, poll, reaction, read, removeMember, rename, reply, resolveContents, richlink, stream, text, toVCard, typing, unsend, voice };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { B as custom, D as asMarkdown, E as poll, F as text, G as buildPhotoAction, H as contact, J as attachment, K as photoActionSchema, L as StreamConsumedError, N as resolveContents, O as markdown, P as asText, R as drainStreamText, T as option, U as fromVCard, V as asContact, W as toVCard, X as fetchUrlBytes, Z as tracedFetch, a as contentAttrs, c as markdownToPlainText, f as voice, g as reply, i as stream, j as group, m as richlink, n as createAsyncQueue, o as errorAttrs, q as asAttachment, r as mergeStreams, s as senderAttrs, t as broadcast, u as classifyIdentifier, x as reaction, y as read, z as asCustom } from "./stream-DAbdViJ9.js";
1
+ import { $ as avatar, A as addMemberSchema, C as reaction, D as option, F as asMarkdown, G as StreamConsumedError, H as asText, I as markdown, J as custom, K as drainStreamText, M as leaveSpaceSchema, N as removeMember, O as poll, P as removeMemberSchema, Q as toVCard, U as text, V as resolveContents, X as contact, Y as asContact, Z as fromVCard, a as contentAttrs, c as markdownToPlainText, et as avatarSchema, f as voice, g as reply, i as stream, it as attachment, j as leaveSpace, k as addMember, m as richlink, n as createAsyncQueue, o as errorAttrs, ot as fetchUrlBytes, q as asCustom, r as mergeStreams, rt as asAttachment, s as senderAttrs, st as tracedFetch, t as broadcast, u as classifyIdentifier, v as rename, x as read, y as renameSchema, z as group } from "./stream-DxI5ZiE2.js";
2
2
  import z from "zod";
3
3
  import ogs from "open-graph-scraper";
4
4
  import { createLogger, setLogLevel, setupOtel, withSpan } from "@photon-ai/otel";
@@ -161,30 +161,6 @@ function app(url) {
161
161
  return { build: async () => asApp(url) };
162
162
  }
163
163
  //#endregion
164
- //#region src/content/avatar.ts
165
- /**
166
- * Set or clear the chat avatar (group icon). Universal content — providers
167
- * dispatch by `content.type === "avatar"` in their `send` action and decide
168
- * their own support story (e.g. iMessage only supports it for remote group
169
- * chats).
170
- *
171
- * `space.send(avatar(...))` is the canonical form; `space.avatar(...)` is
172
- * universal sugar that delegates here. Per-platform constraints (e.g.
173
- * group-only, remote-only) surface as `UnsupportedError` from the provider's
174
- * `send` action so the canonical and sugar forms share one error path.
175
- */
176
- const avatarSchema = z.object({
177
- type: z.literal("avatar"),
178
- action: photoActionSchema
179
- });
180
- function avatar(input, options) {
181
- const action = buildPhotoAction(input, options, "avatar");
182
- return { build: async () => avatarSchema.parse({
183
- type: "avatar",
184
- action
185
- }) };
186
- }
187
- //#endregion
188
164
  //#region src/content/edit.ts
189
165
  const isMessage$1 = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
190
166
  const isContent = (v) => typeof v === "object" && v !== null && "type" in v && typeof v.type === "string";
@@ -198,7 +174,8 @@ const isContent = (v) => typeof v === "object" && v !== null && "type" in v && t
198
174
  * (no new message id is produced; the existing message mutates in place).
199
175
  *
200
176
  * Edit cannot wrap `edit`, `reply`, `reaction`, `group`, `typing`, `rename`,
201
- * `avatar`, `unsend`, or `read` content.
177
+ * `avatar`, `addMember`, `removeMember`, `leaveSpace`, `unsend`, or `read`
178
+ * content.
202
179
  */
203
180
  const editSchema = z.object({
204
181
  type: z.literal("edit"),
@@ -222,7 +199,7 @@ function edit(content, target) {
222
199
  if (target.direction !== "outbound") throw new Error(`edit() target must be an outbound message (got direction "${target.direction}", message id "${target.id}")`);
223
200
  const [resolved] = await resolveContents([content]);
224
201
  if (!resolved) throw new Error("edit() requires content");
225
- 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" || resolved.type === "read") throw new Error(`edit() cannot wrap "${resolved.type}" content`);
202
+ if (resolved.type === "edit" || resolved.type === "reply" || resolved.type === "reaction" || resolved.type === "group" || resolved.type === "typing" || resolved.type === "rename" || resolved.type === "avatar" || resolved.type === "addMember" || resolved.type === "removeMember" || resolved.type === "leaveSpace" || resolved.type === "unsend" || resolved.type === "read") throw new Error(`edit() cannot wrap "${resolved.type}" content`);
226
203
  return asEdit({
227
204
  content: resolved,
228
205
  target
@@ -230,31 +207,6 @@ function edit(content, target) {
230
207
  } };
231
208
  }
232
209
  //#endregion
233
- //#region src/content/rename.ts
234
- /**
235
- * Rename the current chat. Universal content — providers dispatch by
236
- * `content.type === "rename"` in their `send` action and decide their own
237
- * support story (e.g. iMessage only supports it for remote group chats).
238
- *
239
- * `space.send(rename("New Name"))` is the canonical form; `space.rename(...)`
240
- * is universal sugar that delegates here.
241
- *
242
- * Throws at build time if `displayName` is empty. Per-platform constraints
243
- * (e.g. group-only, remote-only) surface as `UnsupportedError` from the
244
- * provider's `send` action so the canonical and sugar forms share one
245
- * error path.
246
- */
247
- const renameSchema = z.object({
248
- type: z.literal("rename"),
249
- displayName: z.string().min(1, "rename() displayName must be non-empty")
250
- });
251
- function rename(displayName) {
252
- return { build: async () => renameSchema.parse({
253
- type: "rename",
254
- displayName
255
- }) };
256
- }
257
- //#endregion
258
210
  //#region src/content/typing.ts
259
211
  /**
260
212
  * A `typing` content value carries a typing-indicator signal — either
@@ -2344,6 +2296,9 @@ const FIRE_AND_FORGET_TYPES = new Set([
2344
2296
  "edit",
2345
2297
  "rename",
2346
2298
  "avatar",
2299
+ "addMember",
2300
+ "removeMember",
2301
+ "leaveSpace",
2347
2302
  "unsend",
2348
2303
  "read"
2349
2304
  ]);
@@ -2356,13 +2311,24 @@ const RESERVED_SPACE_KEYS = new Set([
2356
2311
  "unsend",
2357
2312
  "read",
2358
2313
  "getMessage",
2314
+ "getMembers",
2315
+ "getAvatar",
2316
+ "getDisplayName",
2359
2317
  "rename",
2360
2318
  "avatar",
2319
+ "add",
2320
+ "remove",
2321
+ "leave",
2361
2322
  "startTyping",
2362
2323
  "stopTyping",
2363
2324
  "responding"
2364
2325
  ]);
2365
- const PLATFORM_WISE_ACTION_KEYS = new Set(["getMessage"]);
2326
+ const PLATFORM_WISE_ACTION_KEYS = new Set([
2327
+ "getMessage",
2328
+ "getMembers",
2329
+ "getAvatar",
2330
+ "getDisplayName"
2331
+ ]);
2366
2332
  const RESERVED_MESSAGE_KEYS = new Set([
2367
2333
  "content",
2368
2334
  "direction",
@@ -2691,6 +2657,50 @@ function buildSpace(params) {
2691
2657
  }, "inbound");
2692
2658
  });
2693
2659
  }
2660
+ async function getMembersImpl() {
2661
+ const getMembers = definition.actions?.getMembers;
2662
+ if (!getMembers) throw UnsupportedError.action("getMembers", definition.name);
2663
+ return withSpan("spectrum.space.members.get", {
2664
+ "spectrum.provider": definition.name,
2665
+ "spectrum.space.id": spaceRef.id
2666
+ }, async () => {
2667
+ const raw = await getMembers({
2668
+ client,
2669
+ config,
2670
+ store
2671
+ }, spaceRef);
2672
+ if (!Array.isArray(raw)) throw new Error(`${definition.name} getMembers() must resolve an array of member records (got ${raw === null ? "null" : typeof raw})`);
2673
+ return raw.map((member) => ({
2674
+ ...member,
2675
+ id: member.id,
2676
+ __platform: definition.name
2677
+ }));
2678
+ });
2679
+ }
2680
+ async function getAvatarImpl() {
2681
+ const getAvatar = definition.actions?.getAvatar;
2682
+ if (!getAvatar) throw UnsupportedError.action("getAvatar", definition.name);
2683
+ return withSpan("spectrum.space.avatar.get", {
2684
+ "spectrum.provider": definition.name,
2685
+ "spectrum.space.id": spaceRef.id
2686
+ }, async () => await getAvatar({
2687
+ client,
2688
+ config,
2689
+ store
2690
+ }, spaceRef));
2691
+ }
2692
+ async function getDisplayNameImpl() {
2693
+ const getDisplayName = definition.actions?.getDisplayName;
2694
+ if (!getDisplayName) throw UnsupportedError.action("getDisplayName", definition.name);
2695
+ return withSpan("spectrum.space.displayName.get", {
2696
+ "spectrum.provider": definition.name,
2697
+ "spectrum.space.id": spaceRef.id
2698
+ }, async () => await getDisplayName({
2699
+ client,
2700
+ config,
2701
+ store
2702
+ }, spaceRef));
2703
+ }
2694
2704
  const platformActions = {};
2695
2705
  const declaredActions = definition.space.actions;
2696
2706
  if (declaredActions) for (const [name, factory] of Object.entries(declaredActions)) {
@@ -2717,6 +2727,9 @@ function buildSpace(params) {
2717
2727
  await space.send(read(message));
2718
2728
  },
2719
2729
  getMessage: getMessageImpl,
2730
+ getMembers: getMembersImpl,
2731
+ getAvatar: getAvatarImpl,
2732
+ getDisplayName: getDisplayNameImpl,
2720
2733
  rename: async (displayName) => {
2721
2734
  await space.send(rename(displayName));
2722
2735
  },
@@ -2728,6 +2741,15 @@ function buildSpace(params) {
2728
2741
  if (!options?.mimeType) throw new Error("space.avatar(Buffer) requires options.mimeType — pass { mimeType: '...' }");
2729
2742
  await space.send(avatar(input, { mimeType: options.mimeType }));
2730
2743
  }),
2744
+ add: async (users) => {
2745
+ await space.send(addMember(users));
2746
+ },
2747
+ remove: async (users) => {
2748
+ await space.send(removeMember(users));
2749
+ },
2750
+ leave: async () => {
2751
+ await space.send(leaveSpace());
2752
+ },
2731
2753
  startTyping: async () => {
2732
2754
  await space.send(typing("start"));
2733
2755
  },
@@ -3020,7 +3042,7 @@ function definePlatform(name, rawDef) {
3020
3042
  }
3021
3043
  //#endregion
3022
3044
  //#region src/build-env.ts
3023
- const SPECTRUM_SDK_VERSION = "8.2.2";
3045
+ const SPECTRUM_SDK_VERSION = "9.1.0";
3024
3046
  //#endregion
3025
3047
  //#region src/utils/provider-packages.ts
3026
3048
  const OFFICIAL_PROVIDER_PACKAGES = {
@@ -3780,9 +3802,41 @@ const mapContent = (content, platform, spaceRef, ctx) => {
3780
3802
  case "reply": return deserializeReply(raw, platform, spaceRef, ctx);
3781
3803
  case "group": return deserializeGroup(raw, platform, spaceRef, ctx);
3782
3804
  case "attachment": return deserializeAttachment(raw, platform, spaceRef, ctx);
3805
+ case "addMember": return addMemberSchema.parse({
3806
+ type: "addMember",
3807
+ members: memberStrings(raw.members)
3808
+ });
3809
+ case "removeMember": return removeMemberSchema.parse({
3810
+ type: "removeMember",
3811
+ members: memberStrings(raw.members)
3812
+ });
3813
+ case "leaveSpace": return leaveSpaceSchema.parse({ type: "leaveSpace" });
3814
+ case "rename": return renameSchema.parse({
3815
+ type: "rename",
3816
+ displayName: asString(raw.displayName)
3817
+ });
3818
+ case "avatar": return deserializeAvatar(raw, platform);
3783
3819
  default: return asCustom(content);
3784
3820
  }
3785
3821
  };
3822
+ const memberStrings = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
3823
+ const deserializeAvatar = (raw, platform) => {
3824
+ const action = isRecord(raw.action) ? raw.action : {};
3825
+ if (action.kind === "clear") return avatarSchema.parse({
3826
+ type: "avatar",
3827
+ action: { kind: "clear" }
3828
+ });
3829
+ if (action.kind !== "set") throw new Error(`unknown avatar action kind: ${String(action.kind)}`);
3830
+ const unavailable = () => Promise.reject(UnsupportedError.action("getAvatar", platform, "avatar bytes are not delivered over the Spectrum webhook — fetch the current icon via space.getAvatar()"));
3831
+ return avatarSchema.parse({
3832
+ type: "avatar",
3833
+ action: {
3834
+ kind: "set",
3835
+ mimeType: asString(action.mimeType) || DEFAULT_MIME_TYPE,
3836
+ read: unavailable
3837
+ }
3838
+ });
3839
+ };
3786
3840
  const deserializeAttachment = (raw, platform, spaceRef, ctx) => {
3787
3841
  const id = asString(raw.id);
3788
3842
  const bytes = ctx.resolveAttachment?.(platform, spaceRef, id);
@@ -4564,4 +4618,4 @@ async function Spectrum(options) {
4564
4618
  } });
4565
4619
  }
4566
4620
  //#endregion
4567
- export { Emoji, Spectrum, SpectrumCloudError, UnsupportedError, app, appLayoutSchema, attachment, avatar, broadcast, cloud, contact, custom, definePlatform, edit, fromVCard, fusor, fusorEvent, group, isFusorClient, isFusorEvent, markdown, mergeStreams, option, poll, reaction, read, rename, reply, resolveContents, richlink, stream, text, toVCard, typing, unsend, voice };
4621
+ export { Emoji, Spectrum, SpectrumCloudError, UnsupportedError, addMember, app, appLayoutSchema, attachment, avatar, broadcast, cloud, contact, custom, definePlatform, edit, fromVCard, fusor, fusorEvent, group, isFusorClient, isFusorEvent, leaveSpace, markdown, mergeStreams, option, poll, reaction, read, removeMember, rename, reply, resolveContents, richlink, stream, text, toVCard, typing, unsend, voice };
@@ -202,6 +202,36 @@ const buildPhotoAction = (input, options, contentLabel) => {
202
202
  };
203
203
  };
204
204
  //#endregion
205
+ //#region src/content/avatar.ts
206
+ /**
207
+ * Set or clear the chat avatar (group icon). Universal content — providers
208
+ * dispatch by `content.type === "avatar"` in their `send` action and decide
209
+ * their own support story (e.g. iMessage only supports it for remote group
210
+ * chats).
211
+ *
212
+ * `space.send(avatar(...))` is the canonical form; `space.avatar(...)` is
213
+ * universal sugar that delegates here. Per-platform constraints (e.g.
214
+ * group-only, remote-only) surface as `UnsupportedError` from the provider's
215
+ * `send` action so the canonical and sugar forms share one error path.
216
+ *
217
+ * Bidirectional: providers also surface platform icon changes as inbound
218
+ * `Message`s carrying this content — `action.kind === "set"` arrives with
219
+ * the fetched icon behind `read()`, `"clear"` mirrors icon removal.
220
+ * `message.sender` is the user who changed it (may be `undefined` when the
221
+ * platform recorded no actor).
222
+ */
223
+ const avatarSchema = z.object({
224
+ type: z.literal("avatar"),
225
+ action: photoActionSchema
226
+ });
227
+ function avatar(input, options) {
228
+ const action = buildPhotoAction(input, options, "avatar");
229
+ return { build: async () => avatarSchema.parse({
230
+ type: "avatar",
231
+ action
232
+ }) };
233
+ }
234
+ //#endregion
205
235
  //#region src/utils/vcard.ts
206
236
  const asPropertyArray = (prop) => {
207
237
  if (!prop) return [];
@@ -758,6 +788,68 @@ function markdown(source, options) {
758
788
  return streamTextBuilder("markdown", source, options);
759
789
  }
760
790
  //#endregion
791
+ //#region src/content/membership.ts
792
+ /**
793
+ * Group-membership management content. Universal content — providers
794
+ * dispatch by `content.type` in their `send` action and decide their own
795
+ * support story (e.g. iMessage only supports it for remote group chats;
796
+ * on a DM it surfaces an `UnsupportedError` telling the caller to create
797
+ * a group via `space.create` instead).
798
+ *
799
+ * `space.send(addMember(users))` is the canonical form; `space.add(...)`,
800
+ * `space.remove(...)`, and `space.leave()` are universal sugar that
801
+ * delegate here. All three are fire-and-forget — no `Message` is produced.
802
+ *
803
+ * Bidirectional: providers also surface platform membership events as
804
+ * inbound `Message`s carrying this content. `message.sender` is the acting
805
+ * user — who added/removed the members — and may be `undefined` when the
806
+ * platform recorded no actor. For `leaveSpace` the sender is the leaver
807
+ * (the content carries no `members`). The agent's own actions are
808
+ * suppressed: `space.add(...)` does not echo back as an inbound event.
809
+ *
810
+ * Members are id strings (or `User`s, normalized to their `id`) in the same
811
+ * platform handle format `space.create` accepts. No async resolution happens
812
+ * in the builder — ids reach the provider verbatim.
813
+ */
814
+ const addMemberSchema = z.object({
815
+ type: z.literal("addMember"),
816
+ members: z.array(z.string()).min(1, "addMember() requires at least one member")
817
+ });
818
+ const removeMemberSchema = z.object({
819
+ type: z.literal("removeMember"),
820
+ members: z.array(z.string()).min(1, "removeMember() requires at least one member")
821
+ });
822
+ const leaveSpaceSchema = z.object({ type: z.literal("leaveSpace") });
823
+ const toMemberIds = (users) => (Array.isArray(users) ? users : [users]).map((u) => typeof u === "string" ? u : u.id);
824
+ /**
825
+ * Build an `AddMember` content value inviting `users` into the current
826
+ * group chat. Accepts a single `User` or id string, or an array of either
827
+ * — batches land in one provider call.
828
+ */
829
+ function addMember(users) {
830
+ return { build: async () => addMemberSchema.parse({
831
+ type: "addMember",
832
+ members: toMemberIds(users)
833
+ }) };
834
+ }
835
+ /**
836
+ * Build a `RemoveMember` content value removing `users` from the current
837
+ * group chat. Accepts the same input shapes as `addMember`.
838
+ */
839
+ function removeMember(users) {
840
+ return { build: async () => removeMemberSchema.parse({
841
+ type: "removeMember",
842
+ members: toMemberIds(users)
843
+ }) };
844
+ }
845
+ /**
846
+ * Build a `LeaveSpace` content value making the agent's own account leave
847
+ * the current group chat.
848
+ */
849
+ function leaveSpace() {
850
+ return { build: async () => leaveSpaceSchema.parse({ type: "leaveSpace" }) };
851
+ }
852
+ //#endregion
761
853
  //#region src/content/poll.ts
762
854
  const pollChoiceSchema = z.object({ title: z.string().nonempty() });
763
855
  const pollSchema = z.object({
@@ -891,6 +983,35 @@ function read(target) {
891
983
  } };
892
984
  }
893
985
  //#endregion
986
+ //#region src/content/rename.ts
987
+ /**
988
+ * Rename the current chat. Universal content — providers dispatch by
989
+ * `content.type === "rename"` in their `send` action and decide their own
990
+ * support story (e.g. iMessage only supports it for remote group chats).
991
+ *
992
+ * `space.send(rename("New Name"))` is the canonical form; `space.rename(...)`
993
+ * is universal sugar that delegates here.
994
+ *
995
+ * Bidirectional: providers also surface platform rename events as inbound
996
+ * `Message`s carrying this content; `message.sender` is the user who renamed
997
+ * the chat (may be `undefined` when the platform recorded no actor).
998
+ *
999
+ * Throws at build time if `displayName` is empty. Per-platform constraints
1000
+ * (e.g. group-only, remote-only) surface as `UnsupportedError` from the
1001
+ * provider's `send` action so the canonical and sugar forms share one
1002
+ * error path.
1003
+ */
1004
+ const renameSchema = z.object({
1005
+ type: z.literal("rename"),
1006
+ displayName: z.string().min(1, "rename() displayName must be non-empty")
1007
+ });
1008
+ function rename(displayName) {
1009
+ return { build: async () => renameSchema.parse({
1010
+ type: "rename",
1011
+ displayName
1012
+ }) };
1013
+ }
1014
+ //#endregion
894
1015
  //#region src/content/reply.ts
895
1016
  const isMessage = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
896
1017
  const isContent = (v) => typeof v === "object" && v !== null && "type" in v && typeof v.type === "string";
@@ -902,7 +1023,8 @@ const isContent = (v) => typeof v === "object" && v !== null && "type" in v && t
902
1023
  * `reply` like any other content type and route to a threaded send.
903
1024
  *
904
1025
  * Reply cannot wrap `reply`, `edit`, `reaction`, `group`, `typing`,
905
- * `rename`, `avatar`, `unsend`, or `read` content.
1026
+ * `rename`, `avatar`, `addMember`, `removeMember`, `leaveSpace`, `unsend`,
1027
+ * or `read` content.
906
1028
  */
907
1029
  const replySchema = z.object({
908
1030
  type: z.literal("reply"),
@@ -918,7 +1040,7 @@ function reply(content, target) {
918
1040
  if (!target) throw new Error("reply() target is undefined — the targeted message was never sent (space.send resolves undefined when a platform skips unsupported content)");
919
1041
  const [resolved] = await resolveContents([content]);
920
1042
  if (!resolved) throw new Error("reply() requires content");
921
- 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" || resolved.type === "read") throw new Error(`reply() cannot wrap "${resolved.type}" content`);
1043
+ if (resolved.type === "reply" || resolved.type === "edit" || resolved.type === "reaction" || resolved.type === "group" || resolved.type === "typing" || resolved.type === "rename" || resolved.type === "avatar" || resolved.type === "addMember" || resolved.type === "removeMember" || resolved.type === "leaveSpace" || resolved.type === "unsend" || resolved.type === "read") throw new Error(`reply() cannot wrap "${resolved.type}" content`);
922
1044
  return asReply({
923
1045
  content: resolved,
924
1046
  target
@@ -1454,4 +1576,4 @@ function broadcast(source) {
1454
1576
  };
1455
1577
  }
1456
1578
  //#endregion
1457
- export { asGroup as A, custom as B, asPoll as C, asMarkdown as D, poll as E, text as F, buildPhotoAction as G, contact as H, textSchema as I, attachment as J, photoActionSchema as K, StreamConsumedError as L, groupSchema as M, resolveContents as N, markdown as O, asText as P, drainStreamText as R, reactionSchema as S, option as T, fromVCard as U, asContact as V, toVCard as W, fetchUrlBytes as X, attachmentSchema as Y, tracedFetch as Z, replySchema as _, contentAttrs as a, asReaction as b, markdownToPlainText as c, asVoice as d, voice as f, reply as g, asReply as h, stream as i, group as j, markdownSchema as k, renderInlineTokens as l, richlink as m, createAsyncQueue as n, errorAttrs as o, asRichlink as p, asAttachment as q, mergeStreams as r, senderAttrs as s, broadcast as t, classifyIdentifier as u, asRead as v, asPollOption as w, reaction as x, read as y, asCustom as z };
1579
+ export { avatar as $, addMemberSchema as A, groupSchema as B, reaction as C, option as D, asPollOption as E, asMarkdown as F, StreamConsumedError as G, asText as H, markdown as I, custom as J, drainStreamText as K, markdownSchema as L, leaveSpaceSchema as M, removeMember as N, poll as O, removeMemberSchema as P, toVCard as Q, asGroup as R, asReaction as S, asPoll as T, text as U, resolveContents as V, textSchema as W, contact as X, asContact as Y, fromVCard as Z, replySchema as _, contentAttrs as a, attachmentSchema as at, asRead as b, markdownToPlainText as c, asVoice as d, avatarSchema as et, voice as f, reply as g, asReply as h, stream as i, attachment as it, leaveSpace as j, addMember as k, renderInlineTokens as l, richlink as m, createAsyncQueue as n, photoActionSchema as nt, errorAttrs as o, fetchUrlBytes as ot, asRichlink as p, asCustom as q, mergeStreams as r, asAttachment as rt, senderAttrs as s, tracedFetch as st, broadcast as t, buildPhotoAction as tt, classifyIdentifier as u, rename as v, reactionSchema as w, read as x, renameSchema as y, group as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spectrum-ts/core",
3
- "version": "8.2.2",
3
+ "version": "9.1.0",
4
4
  "description": "The spectrum-ts runtime — Spectrum, content builders, fusor, and the provider authoring API.",
5
5
  "repository": {
6
6
  "type": "git",