@spectrum-ts/core 8.1.0 → 8.2.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 groupSchema, Bt as asPollOption, Dn as buildPhotoAction, En as PhotoInput, H as ProviderMessageRecord, K as ManagedStream, Nt as asRead, On as photoActionSchema, St as asText, Tt as asRichlink, Wt as asMarkdown, Zt as asGroup, cn as reactionSchema, gt as asVoice, ln as asCustom, on as asReaction, r as asAttachment, yn as asContact, zt as asPoll } from "./attachment-DSVZbNyn.js";
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-LmpkSsPn.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, asRichlink, asText, asVoice, buildPhotoAction, createLogger, ensureM4a, errorAttrs, groupSchema, messageEffectSchema, photoActionSchema, reactionSchema, renderInlineTokens, resumableOrderedStream, sanitizeEmail, sanitizeErrorMessage, sanitizePhone, sanitizeUrl, setLogLevel, tracedFetch };
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 };
package/dist/authoring.js CHANGED
@@ -1,4 +1,4 @@
1
- import { D as asGroup, E as markdownSchema, H as buildPhotoAction, I as asCustom, J as tracedFetch, K as attachmentSchema, N as textSchema, R as asContact, U as photoActionSchema, W as asAttachment, _ as asReaction, b as asPoll, d as asVoice, h as asRead, i as stream, j as asText, k as groupSchema, l as renderInlineTokens, o as errorAttrs, p as asRichlink, w as asMarkdown, x as asPollOption, y as reactionSchema } from "./stream-Cs0NMbNv.js";
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";
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, asRichlink, asText, asVoice, buildPhotoAction, createLogger, ensureM4a, errorAttrs, groupSchema, messageEffectSchema, photoActionSchema, reactionSchema, renderInlineTokens, resumableOrderedStream, sanitizeEmail, sanitizeErrorMessage, sanitizePhone, sanitizeUrl, setLogLevel, tracedFetch };
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 };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as FusorTokenData, A as isFusorEvent, An as AppLayout, At as Rename, B as PlatformUser, C as FusorVerify, Cn as Avatar, Ct as text, D as WebhookRawResult, Dt as resolveContents, E as WebhookRawRequest, Et as richlink, F as PlatformInstance, Ft as Poll, G as Broadcaster, Gt as markdown, Ht as poll, I as PlatformMessage, It as PollChoice, J as mergeStreams, Jt as StreamTextSource, K as ManagedStream, Kt as DeltaExtractor, L as PlatformProviderConfig, Lt as PollChoiceInput, M as EventProducer, Mn as app, Mt as Read, N as Platform, Nn as appLayoutSchema, O as FusorEvent, Ot as Reply, P as PlatformDef, Pt as read, Q as DedicatedTokenData, Qt as group, R as PlatformRuntime, Rt as PollOption, S as FusorRespond, Sn as User, T as WebhookHandler, Tn as avatar, U as SchemaMessage, Ut as Markdown, V as ProviderMessage, Vt as option, W as SpaceNamespace, X as Store, Xt as Group, Y as stream, Yt as TextStreamOptions, Z as CloudPlatform, _ as FusorClient, _n as ContactOrg, _t as voice, a as Content, an as ReactionBuilder, at as SharedTokenData, b as FusorMessagesReturn, bn as contact, bt as Typing, c as fromVCard, ct as SpectrumCloudError, d as UnsupportedKind, dn as Contact, dt as TokenData, en as Edit, et as ImessageInfoData, f as Spectrum, fn as ContactAddress, ft as cloud, g as isFusorClient, gn as ContactName, h as fusor, hn as ContactInput, ht as Voice, i as attachment, in as Reaction, it as ProjectProfile, j as AnyPlatformDef, jn as AppUrl, jt as rename, k as fusorEvent, kn as App, kt as reply, l as toVCard, lt as SubscriptionData, m as definePlatform, mn as ContactEmail, mt as EmojiKey, n as AttachmentInput, nn as Message, nt as PlatformsData, o as ContentBuilder, ot as SlackTeamMeta, p as SpectrumInstance, pn as ContactDetails, pt as Emoji, q as broadcast, qt as StreamText, rn as Space, rt as ProjectData, s as ContentInput, sn as reaction, st as SlackTokenData, t as Attachment, tn as edit, tt as PlatformStatus, u as UnsupportedError, un as custom, ut as SubscriptionStatus, v as FusorMessages, vn as ContactPhone, vt as Unsend, w as FusorVerifyRequest, wn as AvatarInput, wt as Richlink, x as FusorReply, xn as AgentSender, xt as typing, y as FusorMessagesCtx, yt as unsend, z as PlatformSpace } from "./attachment-DSVZbNyn.js";
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-LmpkSsPn.js";
2
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 };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as resolveContents, B as fromVCard, C as poll, F as drainStreamText, G as attachment, H as buildPhotoAction, I as asCustom, J as tracedFetch, L as custom, M as text, O as group, P as StreamConsumedError, R as asContact, S as option, T as markdown, U as photoActionSchema, V as toVCard, W as asAttachment, a as contentAttrs, c as markdownToPlainText, f as voice, g as read, i as stream, j as asText, m as richlink, n as createAsyncQueue, o as errorAttrs, q as fetchUrlBytes, r as mergeStreams, s as senderAttrs, t as broadcast, u as classifyIdentifier, v as reaction, w as asMarkdown, z as contact } from "./stream-Cs0NMbNv.js";
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";
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";
@@ -186,8 +186,8 @@ function avatar(input, options) {
186
186
  }
187
187
  //#endregion
188
188
  //#region src/content/edit.ts
189
- const isMessage$2 = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
190
- const isContent$1 = (v) => typeof v === "object" && v !== null && "type" in v && typeof v.type === "string";
189
+ const isMessage$1 = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
190
+ const isContent = (v) => typeof v === "object" && v !== null && "type" in v && typeof v.type === "string";
191
191
  /**
192
192
  * An `edit` rewrites the content of a previously-sent outbound message.
193
193
  *
@@ -202,8 +202,8 @@ const isContent$1 = (v) => typeof v === "object" && v !== null && "type" in v &&
202
202
  */
203
203
  const editSchema = z.object({
204
204
  type: z.literal("edit"),
205
- content: z.custom(isContent$1, { message: "edit content must be a Content value" }),
206
- target: z.custom(isMessage$2, { message: "edit target must be a Message" })
205
+ content: z.custom(isContent, { message: "edit content must be a Content value" }),
206
+ target: z.custom(isMessage$1, { message: "edit target must be a Message" })
207
207
  });
208
208
  const asEdit = (input) => editSchema.parse({
209
209
  type: "edit",
@@ -255,41 +255,6 @@ function rename(displayName) {
255
255
  }) };
256
256
  }
257
257
  //#endregion
258
- //#region src/content/reply.ts
259
- const isMessage$1 = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
260
- const isContent = (v) => typeof v === "object" && v !== null && "type" in v && typeof v.type === "string";
261
- /**
262
- * A `reply` wraps inner content with the message it replies to.
263
- *
264
- * `space.send(reply(content, message))` is the canonical outbound API;
265
- * `message.reply(content)` is sugar that delegates here. Providers see
266
- * `reply` like any other content type and route to a threaded send.
267
- *
268
- * Reply cannot wrap `reply`, `edit`, `reaction`, `group`, `typing`,
269
- * `rename`, `avatar`, `unsend`, or `read` content.
270
- */
271
- const replySchema = z.object({
272
- type: z.literal("reply"),
273
- content: z.custom(isContent, { message: "reply content must be a Content value" }),
274
- target: z.custom(isMessage$1, { message: "reply target must be a Message" })
275
- });
276
- const asReply = (input) => replySchema.parse({
277
- type: "reply",
278
- ...input
279
- });
280
- function reply(content, target) {
281
- return { build: async () => {
282
- 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)");
283
- const [resolved] = await resolveContents([content]);
284
- if (!resolved) throw new Error("reply() requires content");
285
- 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`);
286
- return asReply({
287
- content: resolved,
288
- target
289
- });
290
- } };
291
- }
292
- //#endregion
293
258
  //#region src/content/typing.ts
294
259
  /**
295
260
  * A `typing` content value carries a typing-indicator signal — either
@@ -2572,8 +2537,8 @@ const rawDirection = (raw) => raw.direction === "inbound" || raw.direction === "
2572
2537
  * record can carry its own `direction` when the provider knows better than the
2573
2538
  * wrapping context, which matters for inbound reactions targeting outbound
2574
2539
  * messages.
2575
- * Recursion through `wrapNestedContent` handles reaction targets and group
2576
- * items, which providers return as nested raw records.
2540
+ * Recursion through `wrapNestedContent` handles reaction/reply targets and
2541
+ * group items, which providers return as nested raw records.
2577
2542
  */
2578
2543
  function wrapProviderMessage(raw, ctx, direction) {
2579
2544
  const effectiveDirection = rawDirection(raw) ?? direction;
@@ -2610,6 +2575,19 @@ const wrapNestedContent = (content, ctx, direction) => {
2610
2575
  };
2611
2576
  return content;
2612
2577
  }
2578
+ if (content.type === "reply") {
2579
+ const wrappedInner = wrapNestedContent(content.content, ctx, direction);
2580
+ const target = content.target;
2581
+ if (isRawProviderRecord(target)) return {
2582
+ ...content,
2583
+ content: wrappedInner,
2584
+ target: wrapProviderMessage(target, ctx, "inbound")
2585
+ };
2586
+ return {
2587
+ ...content,
2588
+ content: wrappedInner
2589
+ };
2590
+ }
2613
2591
  if (content.type === "edit") {
2614
2592
  const target = content.target;
2615
2593
  if (isRawProviderRecord(target)) return {
@@ -3042,7 +3020,7 @@ function definePlatform(name, rawDef) {
3042
3020
  }
3043
3021
  //#endregion
3044
3022
  //#region src/build-env.ts
3045
- const SPECTRUM_SDK_VERSION = "8.1.0";
3023
+ const SPECTRUM_SDK_VERSION = "8.2.0";
3046
3024
  //#endregion
3047
3025
  //#region src/utils/provider-packages.ts
3048
3026
  const OFFICIAL_PROVIDER_PACKAGES = {
@@ -3756,9 +3734,9 @@ const asOptionalDate = (value) => typeof value === "string" ? new Date(value) :
3756
3734
  * message with no resolvable platform) — the caller acknowledges it (200)
3757
3735
  * rather than failing, since neither is fixed by a retry.
3758
3736
  *
3759
- * Reaction targets and group items are emitted as **raw nested records**; the
3760
- * `wrapProviderMessage`/`wrapNestedContent` pipeline turns them into fully-built
3761
- * Messages, exactly as a provider's own `messages` handler would.
3737
+ * Reaction/reply targets and group items are emitted as **raw nested records**;
3738
+ * the `wrapProviderMessage`/`wrapNestedContent` pipeline turns them into
3739
+ * fully-built Messages, exactly as a provider's own `messages` handler would.
3762
3740
  */
3763
3741
  function deserializeSpectrumMessage(envelope, ctx) {
3764
3742
  if (envelope.event !== MESSAGES_EVENT) return null;
@@ -3799,6 +3777,7 @@ const mapContent = (content, platform, spaceRef, ctx) => {
3799
3777
  };
3800
3778
  case "contact": return deserializeContact(raw);
3801
3779
  case "reaction": return deserializeReaction(raw, spaceRef);
3780
+ case "reply": return deserializeReply(raw, platform, spaceRef, ctx);
3802
3781
  case "group": return deserializeGroup(raw, platform, spaceRef, ctx);
3803
3782
  case "attachment": return deserializeAttachment(raw, platform, spaceRef, ctx);
3804
3783
  default: return asCustom(content);
@@ -3822,6 +3801,11 @@ const deserializeReaction = (raw, spaceRef) => ({
3822
3801
  emoji: asString(raw.emoji),
3823
3802
  target: buildTargetRecord(raw.target, spaceRef)
3824
3803
  });
3804
+ const deserializeReply = (raw, platform, spaceRef, ctx) => ({
3805
+ type: "reply",
3806
+ content: isRecord(raw.content) ? deserializeContent(raw.content, platform, spaceRef, ctx) : asCustom(raw.content),
3807
+ target: buildTargetRecord(raw.target, spaceRef)
3808
+ });
3825
3809
  const buildTargetRecord = (target, spaceRef) => {
3826
3810
  const ref = isRecord(target) ? target : {};
3827
3811
  return {
@@ -701,7 +701,7 @@ function text(source, options) {
701
701
  const resolveContents = (items) => Promise.all(items.map((c) => typeof c === "string" ? text(c).build() : c.build()));
702
702
  //#endregion
703
703
  //#region src/content/group.ts
704
- const isMessage$2 = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
704
+ const isMessage$3 = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
705
705
  /**
706
706
  * A `group` bundles multiple messages into one logical unit (e.g. an album
707
707
  * of images sent together). Each item is a full `Message` — addressable by
@@ -713,7 +713,7 @@ const isMessage$2 = (v) => typeof v === "object" && v !== null && "id" in v && "
713
713
  */
714
714
  const groupSchema = z.object({
715
715
  type: z.literal("group"),
716
- items: z.array(z.custom(isMessage$2)).min(2)
716
+ items: z.array(z.custom(isMessage$3)).min(2)
717
717
  });
718
718
  const asGroup = (input) => groupSchema.parse({
719
719
  type: "group",
@@ -807,11 +807,11 @@ function poll(title, ...rest) {
807
807
  }
808
808
  //#endregion
809
809
  //#region src/content/reaction.ts
810
- const isMessage$1 = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
810
+ const isMessage$2 = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
811
811
  const reactionSchema = z.object({
812
812
  type: z.literal("reaction"),
813
813
  emoji: z.string().min(1),
814
- target: z.custom(isMessage$1, { message: "reaction target must be a Message" })
814
+ target: z.custom(isMessage$2, { message: "reaction target must be a Message" })
815
815
  });
816
816
  const asReaction = (input) => reactionSchema.parse({
817
817
  type: "reaction",
@@ -845,7 +845,7 @@ function reaction(emoji, target) {
845
845
  }
846
846
  //#endregion
847
847
  //#region src/content/read.ts
848
- const isMessage = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
848
+ const isMessage$1 = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
849
849
  /**
850
850
  * A `read` marks the conversation as read **up to** `target`, surfacing a
851
851
  * read receipt to the sender where the platform supports one.
@@ -868,7 +868,7 @@ const isMessage = (v) => typeof v === "object" && v !== null && "id" in v && "co
868
868
  */
869
869
  const readSchema = z.object({
870
870
  type: z.literal("read"),
871
- target: z.custom(isMessage, { message: "read target must be a Message" })
871
+ target: z.custom(isMessage$1, { message: "read target must be a Message" })
872
872
  });
873
873
  const asRead = (input) => readSchema.parse({
874
874
  type: "read",
@@ -891,6 +891,41 @@ function read(target) {
891
891
  } };
892
892
  }
893
893
  //#endregion
894
+ //#region src/content/reply.ts
895
+ const isMessage = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
896
+ const isContent = (v) => typeof v === "object" && v !== null && "type" in v && typeof v.type === "string";
897
+ /**
898
+ * A `reply` wraps inner content with the message it replies to.
899
+ *
900
+ * `space.send(reply(content, message))` is the canonical outbound API;
901
+ * `message.reply(content)` is sugar that delegates here. Providers see
902
+ * `reply` like any other content type and route to a threaded send.
903
+ *
904
+ * Reply cannot wrap `reply`, `edit`, `reaction`, `group`, `typing`,
905
+ * `rename`, `avatar`, `unsend`, or `read` content.
906
+ */
907
+ const replySchema = z.object({
908
+ type: z.literal("reply"),
909
+ content: z.custom(isContent, { message: "reply content must be a Content value" }),
910
+ target: z.custom(isMessage, { message: "reply target must be a Message" })
911
+ });
912
+ const asReply = (input) => replySchema.parse({
913
+ type: "reply",
914
+ ...input
915
+ });
916
+ function reply(content, target) {
917
+ return { build: async () => {
918
+ 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
+ const [resolved] = await resolveContents([content]);
920
+ 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`);
922
+ return asReply({
923
+ content: resolved,
924
+ target
925
+ });
926
+ } };
927
+ }
928
+ //#endregion
894
929
  //#region src/content/richlink.ts
895
930
  /**
896
931
  * A URL the platform should render as a rich link preview.
@@ -1419,4 +1454,4 @@ function broadcast(source) {
1419
1454
  };
1420
1455
  }
1421
1456
  //#endregion
1422
- export { resolveContents as A, fromVCard as B, poll as C, asGroup as D, markdownSchema as E, drainStreamText as F, attachment as G, buildPhotoAction as H, asCustom as I, tracedFetch as J, attachmentSchema as K, custom as L, text as M, textSchema as N, group as O, StreamConsumedError as P, asContact as R, option as S, markdown as T, photoActionSchema as U, toVCard as V, asAttachment as W, asReaction as _, contentAttrs as a, asPoll as b, markdownToPlainText as c, asVoice as d, voice as f, read as g, asRead as h, stream as i, asText as j, groupSchema as k, renderInlineTokens as l, richlink as m, createAsyncQueue as n, errorAttrs as o, asRichlink as p, fetchUrlBytes as q, mergeStreams as r, senderAttrs as s, broadcast as t, classifyIdentifier as u, reaction as v, asMarkdown as w, asPollOption as x, reactionSchema as y, contact as z };
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spectrum-ts/core",
3
- "version": "8.1.0",
3
+ "version": "8.2.0",
4
4
  "description": "The spectrum-ts runtime — Spectrum, content builders, fusor, and the provider authoring API.",
5
5
  "repository": {
6
6
  "type": "git",