spectrum-ts 1.7.2 → 1.9.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";
@@ -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,7 +2,7 @@ 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,
@@ -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";
@@ -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,
@@ -19,6 +19,7 @@ import {
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,8 +1964,17 @@ 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
 
1974
+ // src/build-env.ts
1975
+ var SPECTRUM_SDK_VERSION = "local";
1976
+ var SPECTRUM_BUILD_ENV = "development";
1977
+
1967
1978
  // src/utils/store.ts
1968
1979
  var isRecordObject = (value) => {
1969
1980
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
@@ -2020,6 +2031,8 @@ function createStore() {
2020
2031
  }
2021
2032
 
2022
2033
  // src/spectrum.ts
2034
+ var PHOTON_OTEL_ENDPOINT = "https://otlp.photon.codes";
2035
+ var lifecycleLog = createLogger("spectrum.lifecycle");
2023
2036
  var ignoreCleanupError = () => void 0;
2024
2037
  var spectrumOptionsSchema = z.object({
2025
2038
  flattenGroups: z.boolean().optional()
@@ -2029,24 +2042,48 @@ var spectrumConfigSchema = z.union([
2029
2042
  projectId: z.string().min(1),
2030
2043
  projectSecret: z.string().min(1),
2031
2044
  providers: z.array(z.custom()),
2032
- options: spectrumOptionsSchema
2045
+ options: spectrumOptionsSchema,
2046
+ telemetry: z.boolean().optional()
2033
2047
  }),
2034
2048
  z.object({
2035
2049
  projectId: z.undefined().optional(),
2036
2050
  projectSecret: z.undefined().optional(),
2037
2051
  providers: z.array(z.custom()),
2038
- options: spectrumOptionsSchema
2052
+ options: spectrumOptionsSchema,
2053
+ telemetry: z.boolean().optional()
2039
2054
  })
2040
2055
  ]);
2056
+ function bootstrapTelemetry(opts) {
2057
+ const headers = {};
2058
+ if (opts.projectId && opts.projectSecret) {
2059
+ const credential = `${opts.projectId}:${opts.projectSecret}`;
2060
+ headers.Authorization = `Basic ${btoa(credential)}`;
2061
+ }
2062
+ const resourceAttributes = {
2063
+ "deployment.environment": process.env.DEPLOYMENT_ENV ?? SPECTRUM_BUILD_ENV
2064
+ };
2065
+ if (opts.projectId) {
2066
+ resourceAttributes["spectrum.project_id"] = opts.projectId;
2067
+ }
2068
+ return setupOtel({
2069
+ serviceName: "spectrum-ts",
2070
+ serviceVersion: SPECTRUM_SDK_VERSION,
2071
+ endpoint: PHOTON_OTEL_ENDPOINT,
2072
+ headers,
2073
+ resourceAttributes
2074
+ });
2075
+ }
2041
2076
  async function Spectrum(options) {
2042
2077
  spectrumConfigSchema.parse(options);
2043
2078
  const {
2044
2079
  projectId,
2045
2080
  projectSecret,
2046
2081
  providers,
2047
- options: runtimeOptions
2082
+ options: runtimeOptions,
2083
+ telemetry
2048
2084
  } = options;
2049
2085
  const flattenGroups = runtimeOptions?.flattenGroups ?? false;
2086
+ const otelHandle = telemetry ? bootstrapTelemetry({ projectId, projectSecret }) : void 0;
2050
2087
  const platformStates = /* @__PURE__ */ new Map();
2051
2088
  const messageBroadcasters = /* @__PURE__ */ new Map();
2052
2089
  const customEventStreams = /* @__PURE__ */ new Map();
@@ -2079,32 +2116,46 @@ async function Spectrum(options) {
2079
2116
  });
2080
2117
  const bindSend = async function* () {
2081
2118
  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,
2119
+ const built = await withSpan(
2120
+ "spectrum.message.receive",
2098
2121
  {
2099
- client,
2100
- config,
2101
- definition,
2102
- space,
2103
- spaceRef,
2104
- store
2122
+ "spectrum.provider": definition.name,
2123
+ "spectrum.message.id": msg.id,
2124
+ "spectrum.space.id": msg.space?.id,
2125
+ ...contentAttrs(msg.content),
2126
+ ...senderAttrs(msg.sender)
2105
2127
  },
2106
- "inbound"
2128
+ () => {
2129
+ const spaceRef = {
2130
+ ...msg.space,
2131
+ __platform: definition.name
2132
+ };
2133
+ const actionCtx = { space: spaceRef, client, config, store };
2134
+ const space2 = buildSpace({
2135
+ spaceRef,
2136
+ extras: {},
2137
+ actionCtx,
2138
+ definition,
2139
+ client,
2140
+ config,
2141
+ store
2142
+ });
2143
+ const normalizedMessage2 = wrapProviderMessage(
2144
+ msg,
2145
+ {
2146
+ client,
2147
+ config,
2148
+ definition,
2149
+ space: space2,
2150
+ spaceRef,
2151
+ store
2152
+ },
2153
+ "inbound"
2154
+ );
2155
+ return { space: space2, normalizedMessage: normalizedMessage2 };
2156
+ }
2107
2157
  );
2158
+ const { space, normalizedMessage } = built;
2108
2159
  if (flattenGroups && normalizedMessage.content.type === "group") {
2109
2160
  for (const item of normalizedMessage.content.items) {
2110
2161
  yield [space, item];
@@ -2130,28 +2181,49 @@ async function Spectrum(options) {
2130
2181
  }
2131
2182
  return broadcaster;
2132
2183
  };
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
- }
2184
+ await withSpan(
2185
+ "spectrum.init",
2186
+ {
2187
+ "spectrum.provider_count": providers.length,
2188
+ "spectrum.flatten_groups": flattenGroups
2189
+ },
2190
+ async () => {
2191
+ for (const provider of providers) {
2192
+ const providerConfig = provider;
2193
+ const def = providerConfig.__definition;
2194
+ const userConfig = def.config.parse(providerConfig.config);
2195
+ const store = createStore();
2196
+ const client = await withSpan(
2197
+ "spectrum.provider.create_client",
2198
+ {
2199
+ "spectrum.provider": def.name
2200
+ },
2201
+ () => def.lifecycle.createClient({
2202
+ config: userConfig,
2203
+ projectId,
2204
+ projectSecret,
2205
+ store
2206
+ })
2207
+ );
2208
+ const state = {
2209
+ client,
2210
+ config: userConfig,
2211
+ definition: def,
2212
+ store
2213
+ };
2214
+ platformStates.set(def.name, {
2215
+ ...state,
2216
+ subscribeMessages: () => getOrCreateMessageBroadcast(state).subscribe()
2217
+ });
2218
+ }
2219
+ }
2220
+ );
2221
+ const providerNames = providers.map((p) => p.__definition.name).join(",");
2222
+ lifecycleLog.info("Spectrum started", {
2223
+ providerCount: providers.length,
2224
+ providers: providerNames,
2225
+ telemetry: telemetry === true
2226
+ });
2155
2227
  const createMessagesStream = () => stream((emit, end) => {
2156
2228
  const merged = mergeStreams(
2157
2229
  Array.from(
@@ -2185,7 +2257,15 @@ async function Spectrum(options) {
2185
2257
  const providerEvents = producer({ client, config, store });
2186
2258
  const annotatePlatform = async function* () {
2187
2259
  for await (const value of providerEvents) {
2188
- yield { ...value, platform: definition.name };
2260
+ const annotated = await withSpan(
2261
+ "spectrum.event",
2262
+ {
2263
+ "spectrum.provider": definition.name,
2264
+ "spectrum.event.name": eventName
2265
+ },
2266
+ () => ({ ...value, platform: definition.name })
2267
+ );
2268
+ yield annotated;
2189
2269
  }
2190
2270
  };
2191
2271
  providerStreams.push(adaptIterable(annotatePlatform()));
@@ -2226,17 +2306,33 @@ async function Spectrum(options) {
2226
2306
  process.off("SIGINT", handleSignal);
2227
2307
  process.off("SIGTERM", handleSignal);
2228
2308
  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);
2309
+ const clientShutdowns = [];
2310
+ for (const state of platformStates.values()) {
2311
+ const destroy = state.definition.lifecycle.destroyClient;
2312
+ if (!destroy) {
2313
+ continue;
2314
+ }
2315
+ clientShutdowns.push(
2316
+ withSpan(
2317
+ "spectrum.provider.destroy_client",
2318
+ {
2319
+ "spectrum.provider": state.definition.name
2320
+ },
2321
+ () => destroy({
2322
+ client: state.client,
2323
+ store: state.store
2324
+ })
2325
+ )
2326
+ );
2327
+ }
2236
2328
  await Promise.allSettled(clientShutdowns);
2237
2329
  customEventStreams.clear();
2238
2330
  messageBroadcasters.clear();
2239
2331
  platformStates.clear();
2332
+ lifecycleLog.info("Spectrum stopped", { providers: providerNames });
2333
+ if (otelHandle) {
2334
+ await otelHandle.shutdown();
2335
+ }
2240
2336
  };
2241
2337
  const handleSignal = () => {
2242
2338
  setTimeout(() => process.exit(1), 3e3).unref();
@@ -2,10 +2,10 @@ import {
2
2
  background,
3
3
  effect,
4
4
  imessage
5
- } from "../../chunk-ZDNX3S3H.js";
6
- import "../../chunk-VO43HJ5B.js";
5
+ } from "../../chunk-OM5VSKWD.js";
6
+ import "../../chunk-5NHNMN4H.js";
7
7
  import "../../chunk-HWADNTQF.js";
8
- import "../../chunk-JWWIFSI7.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-ZDNX3S3H.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-ET42EGIA.js";
11
+ } from "../chunk-OR3VGVML.js";
12
12
  import "../chunk-HWADNTQF.js";
13
- import "../chunk-JWWIFSI7.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-ET42EGIA.js";
3
+ } from "../../chunk-OR3VGVML.js";
4
4
  import "../../chunk-HWADNTQF.js";
5
- import "../../chunk-JWWIFSI7.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.2",
3
+ "version": "1.9.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",