spectrum-ts 1.7.1 → 1.8.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.
@@ -3,7 +3,7 @@ import {
3
3
  readSchema,
4
4
  resolveContents,
5
5
  streamSchema
6
- } from "./chunk-JWWIFSI7.js";
6
+ } from "./chunk-XZSBR26X.js";
7
7
 
8
8
  // src/content/group.ts
9
9
  import z from "zod";
@@ -192,7 +192,7 @@ function broadcast(source) {
192
192
  }
193
193
 
194
194
  // src/utils/cloud.ts
195
- var SPECTRUM_CLOUD_URL = `https://${process.env.SPECTRUM_CLOUD_URL ?? "spectrum.photon.codes"}`;
195
+ var SPECTRUM_CLOUD_URL = process.env.SPECTRUM_CLOUD_URL ?? "https://spectrum.photon.codes";
196
196
  var SpectrumCloudError = class extends Error {
197
197
  status;
198
198
  code;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  asVoice
3
- } from "./chunk-VVXMZYDH.js";
3
+ } from "./chunk-TN54TDTQ.js";
4
4
  import {
5
5
  UnsupportedError,
6
6
  asAttachment,
@@ -10,7 +10,7 @@ import {
10
10
  fromVCard,
11
11
  reactionSchema,
12
12
  toVCard
13
- } from "./chunk-JWWIFSI7.js";
13
+ } from "./chunk-XZSBR26X.js";
14
14
 
15
15
  // src/providers/terminal/index.ts
16
16
  import { spawn } from "child_process";
@@ -2,14 +2,14 @@ import {
2
2
  asGroup,
3
3
  asRichlink,
4
4
  groupSchema
5
- } from "./chunk-VO43HJ5B.js";
5
+ } from "./chunk-5NHNMN4H.js";
6
6
  import {
7
7
  asPoll,
8
8
  asPollOption,
9
9
  cloud,
10
10
  mergeStreams,
11
11
  stream
12
- } from "./chunk-WFIUWFE4.js";
12
+ } from "./chunk-HWADNTQF.js";
13
13
  import {
14
14
  UnsupportedError,
15
15
  asAttachment,
@@ -24,7 +24,7 @@ import {
24
24
  text,
25
25
  textSchema,
26
26
  toVCard
27
- } from "./chunk-JWWIFSI7.js";
27
+ } from "./chunk-XZSBR26X.js";
28
28
 
29
29
  // src/providers/imessage/index.ts
30
30
  import { createClient as createClient2, MessageEffect as MessageEffect2 } from "@photon-ai/advanced-imessage";
@@ -3,7 +3,7 @@ import {
3
3
  cloud,
4
4
  mergeStreams,
5
5
  stream
6
- } from "./chunk-WFIUWFE4.js";
6
+ } from "./chunk-HWADNTQF.js";
7
7
  import {
8
8
  UnsupportedError,
9
9
  asAttachment,
@@ -12,7 +12,7 @@ import {
12
12
  asReaction,
13
13
  asText,
14
14
  definePlatform
15
- } from "./chunk-JWWIFSI7.js";
15
+ } from "./chunk-XZSBR26X.js";
16
16
 
17
17
  // src/providers/whatsapp-business/index.ts
18
18
  import { createClient as createClient2 } from "@photon-ai/whatsapp-business";
@@ -2,7 +2,7 @@ import {
2
2
  bufferToStream,
3
3
  readSchema,
4
4
  streamSchema
5
- } from "./chunk-JWWIFSI7.js";
5
+ } from "./chunk-XZSBR26X.js";
6
6
 
7
7
  // src/content/voice.ts
8
8
  import { createReadStream } from "fs";
@@ -710,7 +710,125 @@ var UnsupportedError = class _UnsupportedError extends Error {
710
710
  }
711
711
  };
712
712
 
713
+ // src/platform/define.ts
714
+ import { withSpan as withSpan2 } from "@photon-ai/otel";
715
+
716
+ // src/utils/identifier.ts
717
+ import { sanitizeEmail, sanitizePhone } from "@photon-ai/otel";
718
+ var PHONE_LIKE = /^\+?[\d\s()\-.]{7,}$/;
719
+ var EMAIL_LIKE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
720
+ function classifyIdentifier(s) {
721
+ if (EMAIL_LIKE.test(s)) {
722
+ return { kind: "email", identifier: sanitizeEmail(s) };
723
+ }
724
+ if (PHONE_LIKE.test(s) && s.replace(/\D/g, "").length >= 7) {
725
+ return { kind: "phone", identifier: sanitizePhone(s) };
726
+ }
727
+ return { kind: "unknown", identifier: s };
728
+ }
729
+
730
+ // src/platform/build.ts
731
+ import { createLogger, withSpan } from "@photon-ai/otel";
732
+
733
+ // src/utils/telemetry.ts
734
+ var targetId = (target) => {
735
+ const id = target?.id;
736
+ return typeof id === "string" ? id : void 0;
737
+ };
738
+ var targetType = (target) => {
739
+ const type = target?.content?.type;
740
+ return typeof type === "string" ? type : void 0;
741
+ };
742
+ var replyOrEditAttrs = (content) => {
743
+ const target = content.target;
744
+ const inner = content.content;
745
+ const innerType = inner?.type;
746
+ return {
747
+ "spectrum.message.content.target.id": targetId(target),
748
+ "spectrum.message.content.target.type": targetType(target),
749
+ "spectrum.message.content.inner.type": typeof innerType === "string" ? innerType : void 0
750
+ };
751
+ };
752
+ var reactionAttrs = (content) => {
753
+ const target = content.target;
754
+ const emoji = content.emoji;
755
+ return {
756
+ "spectrum.message.content.target.id": targetId(target),
757
+ "spectrum.message.content.reaction.emoji": typeof emoji === "string" ? emoji : void 0
758
+ };
759
+ };
760
+ var groupAttrs = (content) => {
761
+ const items = content.items;
762
+ if (!Array.isArray(items)) {
763
+ return {};
764
+ }
765
+ const types = items.map((item) => {
766
+ const itemType = item?.content?.type;
767
+ return typeof itemType === "string" ? itemType : void 0;
768
+ }).filter((t) => t !== void 0);
769
+ return {
770
+ "spectrum.message.content.items.count": items.length,
771
+ "spectrum.message.content.items.types": types.length > 0 ? types.join(",") : void 0
772
+ };
773
+ };
774
+ var typingAttrs = (content) => {
775
+ const state = content.state;
776
+ return {
777
+ "spectrum.message.content.typing.state": typeof state === "string" ? state : void 0
778
+ };
779
+ };
780
+ var attachmentAttrs = (content) => {
781
+ const mime = content.mimeType;
782
+ const size = content.size;
783
+ return {
784
+ "spectrum.message.content.attachment.mime": typeof mime === "string" ? mime : void 0,
785
+ "spectrum.message.content.attachment.size": typeof size === "number" ? size : void 0
786
+ };
787
+ };
788
+ var voiceAttrs = (content) => {
789
+ const mime = content.mimeType;
790
+ const duration = content.duration;
791
+ const size = content.size;
792
+ return {
793
+ "spectrum.message.content.voice.mime": typeof mime === "string" ? mime : void 0,
794
+ "spectrum.message.content.voice.duration": typeof duration === "number" ? duration : void 0,
795
+ "spectrum.message.content.voice.size": typeof size === "number" ? size : void 0
796
+ };
797
+ };
798
+ var CONTENT_ATTR_HANDLERS = {
799
+ reply: replyOrEditAttrs,
800
+ edit: replyOrEditAttrs,
801
+ reaction: reactionAttrs,
802
+ group: groupAttrs,
803
+ typing: typingAttrs,
804
+ attachment: attachmentAttrs,
805
+ voice: voiceAttrs
806
+ };
807
+ function contentAttrs(content) {
808
+ const type = content?.type;
809
+ if (!(content && type)) {
810
+ return { "spectrum.message.content.type": void 0 };
811
+ }
812
+ const handler = CONTENT_ATTR_HANDLERS[type];
813
+ return {
814
+ "spectrum.message.content.type": type,
815
+ ...handler ? handler(content) : {}
816
+ };
817
+ }
818
+ function senderAttrs(sender) {
819
+ const id = sender?.id;
820
+ if (typeof id !== "string" || id.length === 0) {
821
+ return {};
822
+ }
823
+ const { kind, identifier } = classifyIdentifier(id);
824
+ return {
825
+ "spectrum.message.sender.id": identifier,
826
+ "spectrum.message.sender.kind": kind
827
+ };
828
+ }
829
+
713
830
  // src/platform/build.ts
831
+ var platformLog = createLogger("spectrum.platform");
714
832
  var ANSI_YELLOW = "\x1B[33m";
715
833
  var ANSI_RESET = "\x1B[0m";
716
834
  var supportsAnsiColor = () => {
@@ -752,9 +870,14 @@ var warnUnsupported = (err, fallbackPlatform) => {
752
870
  const platform = err.platform ?? fallbackPlatform;
753
871
  const subject = err.kind === "content" ? `content type "${err.contentType ?? "unknown"}"` : `action "${err.action ?? "unknown"}"`;
754
872
  const detail = err.detail ? `: ${err.detail}` : "";
755
- const body = `[spectrum-ts] ${platform} does not support ${subject}${detail}; skipping.`;
756
- console.warn(
757
- supportsAnsiColor() ? `${ANSI_YELLOW}${body}${ANSI_RESET}` : body
873
+ platformLog.warn(
874
+ `${platform} does not support ${subject}${detail}; skipping.`,
875
+ {
876
+ "spectrum.provider": platform,
877
+ "spectrum.unsupported.kind": err.kind,
878
+ "spectrum.unsupported.content_type": err.contentType,
879
+ "spectrum.unsupported.action": err.action
880
+ }
758
881
  );
759
882
  };
760
883
  var contentPlatform = (content) => {
@@ -879,38 +1002,49 @@ function buildSpace(params) {
879
1002
  const { spaceRef, extras, actionCtx, definition, client, config, store } = params;
880
1003
  let space;
881
1004
  async function dispatchSend(item) {
882
- let raw;
883
- try {
884
- const platformError = unsupportedPlatformContentError(
885
- item,
886
- definition.name
887
- );
888
- if (platformError) {
889
- throw platformError;
890
- }
891
- raw = await definition.send({
892
- ...actionCtx,
893
- content: item
894
- });
895
- } catch (err) {
896
- if (err instanceof UnsupportedError) {
897
- warnUnsupported(err, definition.name);
898
- return;
899
- }
900
- throw err;
901
- }
902
- if (!raw?.id) {
903
- if (isFireAndForget(item)) {
904
- return;
1005
+ return withSpan(
1006
+ "spectrum.message.send",
1007
+ {
1008
+ "spectrum.provider": definition.name,
1009
+ "spectrum.space.id": spaceRef.id,
1010
+ "spectrum.message.fire_and_forget": isFireAndForget(item),
1011
+ ...contentAttrs(item)
1012
+ },
1013
+ async () => {
1014
+ let raw;
1015
+ try {
1016
+ const platformError = unsupportedPlatformContentError(
1017
+ item,
1018
+ definition.name
1019
+ );
1020
+ if (platformError) {
1021
+ throw platformError;
1022
+ }
1023
+ raw = await definition.send({
1024
+ ...actionCtx,
1025
+ content: item
1026
+ });
1027
+ } catch (err) {
1028
+ if (err instanceof UnsupportedError) {
1029
+ warnUnsupported(err, definition.name);
1030
+ return;
1031
+ }
1032
+ throw err;
1033
+ }
1034
+ if (!raw?.id) {
1035
+ if (isFireAndForget(item)) {
1036
+ return;
1037
+ }
1038
+ throw new Error(
1039
+ `Platform "${definition.name}" send did not return a message id`
1040
+ );
1041
+ }
1042
+ return wrapProviderMessage(
1043
+ raw,
1044
+ { client, config, definition, space, spaceRef, store },
1045
+ "outbound"
1046
+ );
905
1047
  }
906
- throw new Error(
907
- `Platform "${definition.name}" send did not return a message id`
908
- );
909
- }
910
- return wrapProviderMessage(
911
- raw,
912
- { client, config, definition, space, spaceRef, store },
913
- "outbound"
914
1048
  );
915
1049
  }
916
1050
  async function sendImpl(...content) {
@@ -936,29 +1070,39 @@ function buildSpace(params) {
936
1070
  );
937
1071
  return;
938
1072
  }
939
- let raw;
940
- try {
941
- raw = await getMessage({
942
- space: spaceRef,
943
- messageId: id,
944
- client,
945
- config,
946
- store
947
- });
948
- } catch (err) {
949
- if (err instanceof UnsupportedError) {
950
- warnUnsupported(err, definition.name);
951
- return;
1073
+ return withSpan(
1074
+ "spectrum.message.get",
1075
+ {
1076
+ "spectrum.provider": definition.name,
1077
+ "spectrum.space.id": spaceRef.id,
1078
+ "spectrum.message.id": id
1079
+ },
1080
+ async () => {
1081
+ let raw;
1082
+ try {
1083
+ raw = await getMessage({
1084
+ space: spaceRef,
1085
+ messageId: id,
1086
+ client,
1087
+ config,
1088
+ store
1089
+ });
1090
+ } catch (err) {
1091
+ if (err instanceof UnsupportedError) {
1092
+ warnUnsupported(err, definition.name);
1093
+ return;
1094
+ }
1095
+ throw err;
1096
+ }
1097
+ if (!raw) {
1098
+ return;
1099
+ }
1100
+ return wrapProviderMessage(
1101
+ raw,
1102
+ { client, config, definition, space, spaceRef, store },
1103
+ "inbound"
1104
+ );
952
1105
  }
953
- throw err;
954
- }
955
- if (!raw) {
956
- return;
957
- }
958
- return wrapProviderMessage(
959
- raw,
960
- { client, config, definition, space, spaceRef, store },
961
- "inbound"
962
1106
  );
963
1107
  }
964
1108
  const platformActions = {};
@@ -1057,6 +1201,21 @@ function buildMessage(params) {
1057
1201
  }
1058
1202
 
1059
1203
  // src/platform/define.ts
1204
+ function classifySpaceIdentifier(args) {
1205
+ const stringArgs = args.filter((a) => typeof a === "string");
1206
+ if (stringArgs.length > 1) {
1207
+ return { kind: "group" };
1208
+ }
1209
+ const s = stringArgs[0];
1210
+ if (!s) {
1211
+ return { kind: "unknown" };
1212
+ }
1213
+ const { kind, identifier } = classifyIdentifier(s);
1214
+ if (kind === "unknown") {
1215
+ return { kind: "unknown" };
1216
+ }
1217
+ return { kind, identifier };
1218
+ }
1060
1219
  function createPlatformInstance(def, runtime) {
1061
1220
  const isPlatformUser = (value) => typeof value === "object" && value !== null && "__platform" in value && value.__platform === def.name;
1062
1221
  const resolveUserID = async (userID) => {
@@ -1126,39 +1285,50 @@ function createPlatformInstance(def, runtime) {
1126
1285
  };
1127
1286
  },
1128
1287
  async space(...args) {
1129
- const convertedArgs = await resolveStringUsers(args);
1130
- const { users, params } = normalizeSpaceArgs(convertedArgs);
1131
- let parsedParams = params;
1132
- if (params !== void 0 && def.space.params) {
1133
- parsedParams = def.space.params.parse(params);
1134
- }
1135
- const resolved = await def.space.resolve({
1136
- input: { users, params: parsedParams },
1137
- client: runtime.client,
1138
- config: runtime.config,
1139
- store: runtime.store
1140
- });
1141
- const parsedSpace = def.space.schema ? def.space.schema.parse(resolved) : resolved;
1142
- const spaceRef = {
1143
- ...parsedSpace,
1144
- id: parsedSpace.id,
1145
- __platform: def.name
1146
- };
1147
- const actionCtx = {
1148
- space: spaceRef,
1149
- client: runtime.client,
1150
- config: runtime.config,
1151
- store: runtime.store
1152
- };
1153
- return buildSpace({
1154
- spaceRef,
1155
- extras: parsedSpace,
1156
- actionCtx,
1157
- definition: def,
1158
- client: runtime.client,
1159
- config: runtime.config,
1160
- store: runtime.store
1161
- });
1288
+ const { kind, identifier } = classifySpaceIdentifier(args);
1289
+ return withSpan2(
1290
+ "spectrum.space.resolve",
1291
+ {
1292
+ "spectrum.provider": def.name,
1293
+ "spectrum.space.identifier_kind": kind,
1294
+ "spectrum.space.identifier": identifier
1295
+ },
1296
+ async () => {
1297
+ const convertedArgs = await resolveStringUsers(args);
1298
+ const { users, params } = normalizeSpaceArgs(convertedArgs);
1299
+ let parsedParams = params;
1300
+ if (params !== void 0 && def.space.params) {
1301
+ parsedParams = def.space.params.parse(params);
1302
+ }
1303
+ const resolved = await def.space.resolve({
1304
+ input: { users, params: parsedParams },
1305
+ client: runtime.client,
1306
+ config: runtime.config,
1307
+ store: runtime.store
1308
+ });
1309
+ const parsedSpace = def.space.schema ? def.space.schema.parse(resolved) : resolved;
1310
+ const spaceRef = {
1311
+ ...parsedSpace,
1312
+ id: parsedSpace.id,
1313
+ __platform: def.name
1314
+ };
1315
+ const actionCtx = {
1316
+ space: spaceRef,
1317
+ client: runtime.client,
1318
+ config: runtime.config,
1319
+ store: runtime.store
1320
+ };
1321
+ return buildSpace({
1322
+ spaceRef,
1323
+ extras: parsedSpace,
1324
+ actionCtx,
1325
+ definition: def,
1326
+ client: runtime.client,
1327
+ config: runtime.config,
1328
+ store: runtime.store
1329
+ });
1330
+ }
1331
+ );
1162
1332
  }
1163
1333
  };
1164
1334
  const eventProperties = {};
@@ -1282,6 +1452,8 @@ export {
1282
1452
  reply,
1283
1453
  typing,
1284
1454
  UnsupportedError,
1455
+ contentAttrs,
1456
+ senderAttrs,
1285
1457
  wrapProviderMessage,
1286
1458
  buildSpace,
1287
1459
  definePlatform
package/dist/index.d.ts CHANGED
@@ -2739,11 +2739,13 @@ declare function Spectrum<const Providers extends PlatformProviderConfig[]>(opti
2739
2739
  projectSecret: string;
2740
2740
  providers: [...Providers];
2741
2741
  options?: SpectrumOptions;
2742
+ telemetry?: boolean;
2742
2743
  } | {
2743
2744
  projectId?: never;
2744
2745
  projectSecret?: never;
2745
2746
  providers: [...Providers];
2746
2747
  options?: SpectrumOptions;
2748
+ telemetry?: boolean;
2747
2749
  }): Promise<SpectrumInstance<Providers>>;
2748
2750
 
2749
2751
  type SubscriptionStatus = "active" | "canceled" | "past_due";
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  group,
3
3
  richlink
4
- } from "./chunk-VO43HJ5B.js";
4
+ } from "./chunk-5NHNMN4H.js";
5
5
  import {
6
6
  voice
7
- } from "./chunk-VVXMZYDH.js";
7
+ } from "./chunk-TN54TDTQ.js";
8
8
  import {
9
9
  SpectrumCloudError,
10
10
  broadcast,
@@ -13,12 +13,13 @@ import {
13
13
  option,
14
14
  poll,
15
15
  stream
16
- } from "./chunk-WFIUWFE4.js";
16
+ } from "./chunk-HWADNTQF.js";
17
17
  import {
18
18
  UnsupportedError,
19
19
  attachment,
20
20
  buildSpace,
21
21
  contact,
22
+ contentAttrs,
22
23
  custom,
23
24
  definePlatform,
24
25
  edit,
@@ -26,11 +27,12 @@ import {
26
27
  reaction,
27
28
  reply,
28
29
  resolveContents,
30
+ senderAttrs,
29
31
  text,
30
32
  toVCard,
31
33
  typing,
32
34
  wrapProviderMessage
33
- } from "./chunk-JWWIFSI7.js";
35
+ } from "./chunk-XZSBR26X.js";
34
36
 
35
37
  // src/emoji/generated.ts
36
38
  var GeneratedEmoji = {
@@ -1962,6 +1964,11 @@ var aliases = {
1962
1964
  var Emoji = { ...GeneratedEmoji, ...aliases };
1963
1965
 
1964
1966
  // src/spectrum.ts
1967
+ import {
1968
+ createLogger,
1969
+ setupOtel,
1970
+ withSpan
1971
+ } from "@photon-ai/otel";
1965
1972
  import z from "zod";
1966
1973
 
1967
1974
  // src/utils/store.ts
@@ -2019,7 +2026,12 @@ function createStore() {
2019
2026
  };
2020
2027
  }
2021
2028
 
2029
+ // src/version.ts
2030
+ var SPECTRUM_SDK_VERSION = "1.8.0";
2031
+
2022
2032
  // src/spectrum.ts
2033
+ var PHOTON_OTEL_ENDPOINT = "https://otlp.photon.codes";
2034
+ var lifecycleLog = createLogger("spectrum.lifecycle");
2023
2035
  var ignoreCleanupError = () => void 0;
2024
2036
  var spectrumOptionsSchema = z.object({
2025
2037
  flattenGroups: z.boolean().optional()
@@ -2029,24 +2041,46 @@ var spectrumConfigSchema = z.union([
2029
2041
  projectId: z.string().min(1),
2030
2042
  projectSecret: z.string().min(1),
2031
2043
  providers: z.array(z.custom()),
2032
- options: spectrumOptionsSchema
2044
+ options: spectrumOptionsSchema,
2045
+ telemetry: z.boolean().optional()
2033
2046
  }),
2034
2047
  z.object({
2035
2048
  projectId: z.undefined().optional(),
2036
2049
  projectSecret: z.undefined().optional(),
2037
2050
  providers: z.array(z.custom()),
2038
- options: spectrumOptionsSchema
2051
+ options: spectrumOptionsSchema,
2052
+ telemetry: z.boolean().optional()
2039
2053
  })
2040
2054
  ]);
2055
+ function bootstrapTelemetry(opts) {
2056
+ const headers = {};
2057
+ if (opts.projectId && opts.projectSecret) {
2058
+ const credential = `${opts.projectId}:${opts.projectSecret}`;
2059
+ headers.Authorization = `Basic ${btoa(credential)}`;
2060
+ }
2061
+ const resourceAttributes = {};
2062
+ if (opts.projectId) {
2063
+ resourceAttributes["spectrum.project_id"] = opts.projectId;
2064
+ }
2065
+ return setupOtel({
2066
+ serviceName: "spectrum-ts",
2067
+ serviceVersion: SPECTRUM_SDK_VERSION,
2068
+ endpoint: PHOTON_OTEL_ENDPOINT,
2069
+ headers,
2070
+ resourceAttributes
2071
+ });
2072
+ }
2041
2073
  async function Spectrum(options) {
2042
2074
  spectrumConfigSchema.parse(options);
2043
2075
  const {
2044
2076
  projectId,
2045
2077
  projectSecret,
2046
2078
  providers,
2047
- options: runtimeOptions
2079
+ options: runtimeOptions,
2080
+ telemetry
2048
2081
  } = options;
2049
2082
  const flattenGroups = runtimeOptions?.flattenGroups ?? false;
2083
+ const otelHandle = telemetry ? bootstrapTelemetry({ projectId, projectSecret }) : void 0;
2050
2084
  const platformStates = /* @__PURE__ */ new Map();
2051
2085
  const messageBroadcasters = /* @__PURE__ */ new Map();
2052
2086
  const customEventStreams = /* @__PURE__ */ new Map();
@@ -2079,32 +2113,46 @@ async function Spectrum(options) {
2079
2113
  });
2080
2114
  const bindSend = async function* () {
2081
2115
  for await (const msg of raw) {
2082
- const spaceRef = {
2083
- ...msg.space,
2084
- __platform: definition.name
2085
- };
2086
- const actionCtx = { space: spaceRef, client, config, store };
2087
- const space = buildSpace({
2088
- spaceRef,
2089
- extras: {},
2090
- actionCtx,
2091
- definition,
2092
- client,
2093
- config,
2094
- store
2095
- });
2096
- const normalizedMessage = wrapProviderMessage(
2097
- msg,
2116
+ const built = await withSpan(
2117
+ "spectrum.message.receive",
2098
2118
  {
2099
- client,
2100
- config,
2101
- definition,
2102
- space,
2103
- spaceRef,
2104
- store
2119
+ "spectrum.provider": definition.name,
2120
+ "spectrum.message.id": msg.id,
2121
+ "spectrum.space.id": msg.space?.id,
2122
+ ...contentAttrs(msg.content),
2123
+ ...senderAttrs(msg.sender)
2105
2124
  },
2106
- "inbound"
2125
+ () => {
2126
+ const spaceRef = {
2127
+ ...msg.space,
2128
+ __platform: definition.name
2129
+ };
2130
+ const actionCtx = { space: spaceRef, client, config, store };
2131
+ const space2 = buildSpace({
2132
+ spaceRef,
2133
+ extras: {},
2134
+ actionCtx,
2135
+ definition,
2136
+ client,
2137
+ config,
2138
+ store
2139
+ });
2140
+ const normalizedMessage2 = wrapProviderMessage(
2141
+ msg,
2142
+ {
2143
+ client,
2144
+ config,
2145
+ definition,
2146
+ space: space2,
2147
+ spaceRef,
2148
+ store
2149
+ },
2150
+ "inbound"
2151
+ );
2152
+ return { space: space2, normalizedMessage: normalizedMessage2 };
2153
+ }
2107
2154
  );
2155
+ const { space, normalizedMessage } = built;
2108
2156
  if (flattenGroups && normalizedMessage.content.type === "group") {
2109
2157
  for (const item of normalizedMessage.content.items) {
2110
2158
  yield [space, item];
@@ -2130,28 +2178,49 @@ async function Spectrum(options) {
2130
2178
  }
2131
2179
  return broadcaster;
2132
2180
  };
2133
- for (const provider of providers) {
2134
- const providerConfig = provider;
2135
- const def = providerConfig.__definition;
2136
- const userConfig = def.config.parse(providerConfig.config);
2137
- const store = createStore();
2138
- const client = await def.lifecycle.createClient({
2139
- config: userConfig,
2140
- projectId,
2141
- projectSecret,
2142
- store
2143
- });
2144
- const state = {
2145
- client,
2146
- config: userConfig,
2147
- definition: def,
2148
- store
2149
- };
2150
- platformStates.set(def.name, {
2151
- ...state,
2152
- subscribeMessages: () => getOrCreateMessageBroadcast(state).subscribe()
2153
- });
2154
- }
2181
+ await withSpan(
2182
+ "spectrum.init",
2183
+ {
2184
+ "spectrum.provider_count": providers.length,
2185
+ "spectrum.flatten_groups": flattenGroups
2186
+ },
2187
+ async () => {
2188
+ for (const provider of providers) {
2189
+ const providerConfig = provider;
2190
+ const def = providerConfig.__definition;
2191
+ const userConfig = def.config.parse(providerConfig.config);
2192
+ const store = createStore();
2193
+ const client = await withSpan(
2194
+ "spectrum.provider.create_client",
2195
+ {
2196
+ "spectrum.provider": def.name
2197
+ },
2198
+ () => def.lifecycle.createClient({
2199
+ config: userConfig,
2200
+ projectId,
2201
+ projectSecret,
2202
+ store
2203
+ })
2204
+ );
2205
+ const state = {
2206
+ client,
2207
+ config: userConfig,
2208
+ definition: def,
2209
+ store
2210
+ };
2211
+ platformStates.set(def.name, {
2212
+ ...state,
2213
+ subscribeMessages: () => getOrCreateMessageBroadcast(state).subscribe()
2214
+ });
2215
+ }
2216
+ }
2217
+ );
2218
+ const providerNames = providers.map((p) => p.__definition.name).join(",");
2219
+ lifecycleLog.info("Spectrum started", {
2220
+ providerCount: providers.length,
2221
+ providers: providerNames,
2222
+ telemetry: telemetry === true
2223
+ });
2155
2224
  const createMessagesStream = () => stream((emit, end) => {
2156
2225
  const merged = mergeStreams(
2157
2226
  Array.from(
@@ -2185,7 +2254,15 @@ async function Spectrum(options) {
2185
2254
  const providerEvents = producer({ client, config, store });
2186
2255
  const annotatePlatform = async function* () {
2187
2256
  for await (const value of providerEvents) {
2188
- yield { ...value, platform: definition.name };
2257
+ const annotated = await withSpan(
2258
+ "spectrum.event",
2259
+ {
2260
+ "spectrum.provider": definition.name,
2261
+ "spectrum.event.name": eventName
2262
+ },
2263
+ () => ({ ...value, platform: definition.name })
2264
+ );
2265
+ yield annotated;
2189
2266
  }
2190
2267
  };
2191
2268
  providerStreams.push(adaptIterable(annotatePlatform()));
@@ -2226,17 +2303,33 @@ async function Spectrum(options) {
2226
2303
  process.off("SIGINT", handleSignal);
2227
2304
  process.off("SIGTERM", handleSignal);
2228
2305
  await Promise.allSettled(streamShutdowns);
2229
- const clientShutdowns = Array.from(
2230
- platformStates.values(),
2231
- (state) => state.definition.lifecycle.destroyClient?.({
2232
- client: state.client,
2233
- store: state.store
2234
- })
2235
- ).filter((shutdown) => shutdown !== void 0);
2306
+ const clientShutdowns = [];
2307
+ for (const state of platformStates.values()) {
2308
+ const destroy = state.definition.lifecycle.destroyClient;
2309
+ if (!destroy) {
2310
+ continue;
2311
+ }
2312
+ clientShutdowns.push(
2313
+ withSpan(
2314
+ "spectrum.provider.destroy_client",
2315
+ {
2316
+ "spectrum.provider": state.definition.name
2317
+ },
2318
+ () => destroy({
2319
+ client: state.client,
2320
+ store: state.store
2321
+ })
2322
+ )
2323
+ );
2324
+ }
2236
2325
  await Promise.allSettled(clientShutdowns);
2237
2326
  customEventStreams.clear();
2238
2327
  messageBroadcasters.clear();
2239
2328
  platformStates.clear();
2329
+ lifecycleLog.info("Spectrum stopped", { providers: providerNames });
2330
+ if (otelHandle) {
2331
+ await otelHandle.shutdown();
2332
+ }
2240
2333
  };
2241
2334
  const handleSignal = () => {
2242
2335
  setTimeout(() => process.exit(1), 3e3).unref();
@@ -2,10 +2,10 @@ import {
2
2
  background,
3
3
  effect,
4
4
  imessage
5
- } from "../../chunk-H2QKKFQW.js";
6
- import "../../chunk-VO43HJ5B.js";
7
- import "../../chunk-WFIUWFE4.js";
8
- import "../../chunk-JWWIFSI7.js";
5
+ } from "../../chunk-OM5VSKWD.js";
6
+ import "../../chunk-5NHNMN4H.js";
7
+ import "../../chunk-HWADNTQF.js";
8
+ import "../../chunk-XZSBR26X.js";
9
9
  export {
10
10
  background,
11
11
  effect,
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  imessage
3
- } from "../chunk-H2QKKFQW.js";
4
- import "../chunk-VO43HJ5B.js";
3
+ } from "../chunk-OM5VSKWD.js";
4
+ import "../chunk-5NHNMN4H.js";
5
5
  import {
6
6
  terminal
7
- } from "../chunk-2SB6VN7J.js";
8
- import "../chunk-VVXMZYDH.js";
7
+ } from "../chunk-N3ZKMTSG.js";
8
+ import "../chunk-TN54TDTQ.js";
9
9
  import {
10
10
  whatsappBusiness
11
- } from "../chunk-4O7DPKLI.js";
12
- import "../chunk-WFIUWFE4.js";
13
- import "../chunk-JWWIFSI7.js";
11
+ } from "../chunk-OR3VGVML.js";
12
+ import "../chunk-HWADNTQF.js";
13
+ import "../chunk-XZSBR26X.js";
14
14
  export {
15
15
  imessage,
16
16
  terminal,
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  terminal
3
- } from "../../chunk-2SB6VN7J.js";
4
- import "../../chunk-VVXMZYDH.js";
5
- import "../../chunk-JWWIFSI7.js";
3
+ } from "../../chunk-N3ZKMTSG.js";
4
+ import "../../chunk-TN54TDTQ.js";
5
+ import "../../chunk-XZSBR26X.js";
6
6
  export {
7
7
  terminal
8
8
  };
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  whatsappBusiness
3
- } from "../../chunk-4O7DPKLI.js";
4
- import "../../chunk-WFIUWFE4.js";
5
- import "../../chunk-JWWIFSI7.js";
3
+ } from "../../chunk-OR3VGVML.js";
4
+ import "../../chunk-HWADNTQF.js";
5
+ import "../../chunk-XZSBR26X.js";
6
6
  export {
7
7
  whatsappBusiness
8
8
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spectrum-ts",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -25,6 +25,7 @@
25
25
  "dependencies": {
26
26
  "@photon-ai/advanced-imessage": "^0.7.0",
27
27
  "@photon-ai/imessage-kit": "^3.0.0",
28
+ "@photon-ai/otel": "^0.1.1",
28
29
  "@photon-ai/whatsapp-business": "^0.1.1",
29
30
  "@repeaterjs/repeater": "^3.0.6",
30
31
  "better-grpc": "^0.3.2",