@contractspec/integration.providers-impls 1.57.0 → 1.58.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.
Files changed (256) hide show
  1. package/dist/analytics.d.ts +1 -8
  2. package/dist/analytics.d.ts.map +1 -1
  3. package/dist/analytics.js +3 -3
  4. package/dist/calendar.d.ts +1 -8
  5. package/dist/calendar.d.ts.map +1 -1
  6. package/dist/calendar.js +3 -3
  7. package/dist/database.d.ts +1 -8
  8. package/dist/database.d.ts.map +1 -1
  9. package/dist/database.js +3 -3
  10. package/dist/email.d.ts +1 -8
  11. package/dist/email.d.ts.map +1 -1
  12. package/dist/email.js +3 -3
  13. package/dist/embedding.d.ts +1 -8
  14. package/dist/embedding.d.ts.map +1 -1
  15. package/dist/embedding.js +3 -3
  16. package/dist/impls/elevenlabs-voice.d.ts +14 -18
  17. package/dist/impls/elevenlabs-voice.d.ts.map +1 -1
  18. package/dist/impls/elevenlabs-voice.js +98 -88
  19. package/dist/impls/fal-voice.d.ts +22 -26
  20. package/dist/impls/fal-voice.d.ts.map +1 -1
  21. package/dist/impls/fal-voice.js +103 -78
  22. package/dist/impls/fathom-meeting-recorder.d.ts +35 -39
  23. package/dist/impls/fathom-meeting-recorder.d.ts.map +1 -1
  24. package/dist/impls/fathom-meeting-recorder.js +285 -142
  25. package/dist/impls/fathom-meeting-recorder.mapper.d.ts +4 -8
  26. package/dist/impls/fathom-meeting-recorder.mapper.d.ts.map +1 -1
  27. package/dist/impls/fathom-meeting-recorder.mapper.js +102 -38
  28. package/dist/impls/fathom-meeting-recorder.types.d.ts +16 -20
  29. package/dist/impls/fathom-meeting-recorder.types.d.ts.map +1 -1
  30. package/dist/impls/fathom-meeting-recorder.types.js +1 -0
  31. package/dist/impls/fathom-meeting-recorder.utils.d.ts +10 -14
  32. package/dist/impls/fathom-meeting-recorder.utils.d.ts.map +1 -1
  33. package/dist/impls/fathom-meeting-recorder.utils.js +58 -41
  34. package/dist/impls/fathom-meeting-recorder.webhooks.d.ts +3 -7
  35. package/dist/impls/fathom-meeting-recorder.webhooks.d.ts.map +1 -1
  36. package/dist/impls/fathom-meeting-recorder.webhooks.js +25 -20
  37. package/dist/impls/fireflies-meeting-recorder.d.ts +21 -25
  38. package/dist/impls/fireflies-meeting-recorder.d.ts.map +1 -1
  39. package/dist/impls/fireflies-meeting-recorder.js +272 -149
  40. package/dist/impls/fireflies-meeting-recorder.queries.d.ts +3 -6
  41. package/dist/impls/fireflies-meeting-recorder.queries.d.ts.map +1 -1
  42. package/dist/impls/fireflies-meeting-recorder.queries.js +10 -8
  43. package/dist/impls/fireflies-meeting-recorder.types.d.ts +26 -29
  44. package/dist/impls/fireflies-meeting-recorder.types.d.ts.map +1 -1
  45. package/dist/impls/fireflies-meeting-recorder.types.js +1 -0
  46. package/dist/impls/fireflies-meeting-recorder.utils.d.ts +4 -7
  47. package/dist/impls/fireflies-meeting-recorder.utils.d.ts.map +1 -1
  48. package/dist/impls/fireflies-meeting-recorder.utils.js +34 -27
  49. package/dist/impls/gcs-storage.d.ts +18 -22
  50. package/dist/impls/gcs-storage.d.ts.map +1 -1
  51. package/dist/impls/gcs-storage.js +92 -84
  52. package/dist/impls/gmail-inbound.d.ts +20 -24
  53. package/dist/impls/gmail-inbound.d.ts.map +1 -1
  54. package/dist/impls/gmail-inbound.js +212 -185
  55. package/dist/impls/gmail-outbound.d.ts +12 -16
  56. package/dist/impls/gmail-outbound.d.ts.map +1 -1
  57. package/dist/impls/gmail-outbound.js +126 -92
  58. package/dist/impls/google-calendar.d.ts +17 -21
  59. package/dist/impls/google-calendar.d.ts.map +1 -1
  60. package/dist/impls/google-calendar.js +182 -145
  61. package/dist/impls/gradium-voice.d.ts +20 -22
  62. package/dist/impls/gradium-voice.d.ts.map +1 -1
  63. package/dist/impls/gradium-voice.js +85 -74
  64. package/dist/impls/granola-meeting-recorder.d.ts +31 -24
  65. package/dist/impls/granola-meeting-recorder.d.ts.map +1 -1
  66. package/dist/impls/granola-meeting-recorder.js +511 -143
  67. package/dist/impls/granola-meeting-recorder.mcp.d.ts +25 -0
  68. package/dist/impls/granola-meeting-recorder.mcp.d.ts.map +1 -0
  69. package/dist/impls/granola-meeting-recorder.mcp.js +279 -0
  70. package/dist/impls/granola-meeting-recorder.types.d.ts +60 -49
  71. package/dist/impls/granola-meeting-recorder.types.d.ts.map +1 -1
  72. package/dist/impls/granola-meeting-recorder.types.js +1 -0
  73. package/dist/impls/index.d.ts +28 -28
  74. package/dist/impls/index.d.ts.map +1 -0
  75. package/dist/impls/index.js +4659 -29
  76. package/dist/impls/jira.d.ts +18 -22
  77. package/dist/impls/jira.d.ts.map +1 -1
  78. package/dist/impls/jira.js +112 -101
  79. package/dist/impls/linear.d.ts +17 -21
  80. package/dist/impls/linear.d.ts.map +1 -1
  81. package/dist/impls/linear.js +78 -69
  82. package/dist/impls/mistral-embedding.d.ts +17 -21
  83. package/dist/impls/mistral-embedding.d.ts.map +1 -1
  84. package/dist/impls/mistral-embedding.js +41 -39
  85. package/dist/impls/mistral-llm.d.ts +25 -29
  86. package/dist/impls/mistral-llm.d.ts.map +1 -1
  87. package/dist/impls/mistral-llm.js +266 -244
  88. package/dist/impls/notion.d.ts +20 -24
  89. package/dist/impls/notion.d.ts.map +1 -1
  90. package/dist/impls/notion.js +145 -110
  91. package/dist/impls/posthog-reader.d.ts +18 -22
  92. package/dist/impls/posthog-reader.d.ts.map +1 -1
  93. package/dist/impls/posthog-reader.js +148 -129
  94. package/dist/impls/posthog-utils.d.ts +4 -7
  95. package/dist/impls/posthog-utils.d.ts.map +1 -1
  96. package/dist/impls/posthog-utils.js +31 -22
  97. package/dist/impls/posthog.d.ts +33 -37
  98. package/dist/impls/posthog.d.ts.map +1 -1
  99. package/dist/impls/posthog.js +320 -119
  100. package/dist/impls/postmark-email.d.ts +13 -17
  101. package/dist/impls/postmark-email.d.ts.map +1 -1
  102. package/dist/impls/postmark-email.js +55 -50
  103. package/dist/impls/powens-client.d.ts +111 -114
  104. package/dist/impls/powens-client.d.ts.map +1 -1
  105. package/dist/impls/powens-client.js +194 -170
  106. package/dist/impls/powens-openbanking.d.ts +22 -26
  107. package/dist/impls/powens-openbanking.d.ts.map +1 -1
  108. package/dist/impls/powens-openbanking.js +425 -217
  109. package/dist/impls/provider-factory.d.ts +29 -33
  110. package/dist/impls/provider-factory.d.ts.map +1 -1
  111. package/dist/impls/provider-factory.js +4072 -275
  112. package/dist/impls/qdrant-vector.d.ts +18 -22
  113. package/dist/impls/qdrant-vector.d.ts.map +1 -1
  114. package/dist/impls/qdrant-vector.js +76 -69
  115. package/dist/impls/stripe-payments.d.ts +22 -26
  116. package/dist/impls/stripe-payments.d.ts.map +1 -1
  117. package/dist/impls/stripe-payments.js +219 -193
  118. package/dist/impls/supabase-psql.d.ts +21 -25
  119. package/dist/impls/supabase-psql.d.ts.map +1 -1
  120. package/dist/impls/supabase-psql.js +138 -98
  121. package/dist/impls/supabase-vector.d.ts +29 -33
  122. package/dist/impls/supabase-vector.d.ts.map +1 -1
  123. package/dist/impls/supabase-vector.js +278 -103
  124. package/dist/impls/tldv-meeting-recorder.d.ts +18 -22
  125. package/dist/impls/tldv-meeting-recorder.d.ts.map +1 -1
  126. package/dist/impls/tldv-meeting-recorder.js +142 -127
  127. package/dist/impls/twilio-sms.d.ts +14 -17
  128. package/dist/impls/twilio-sms.d.ts.map +1 -1
  129. package/dist/impls/twilio-sms.js +62 -55
  130. package/dist/index.d.ts +15 -64
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +4700 -107
  133. package/dist/llm.d.ts +1 -8
  134. package/dist/llm.d.ts.map +1 -1
  135. package/dist/llm.js +3 -3
  136. package/dist/meeting-recorder.d.ts +1 -8
  137. package/dist/meeting-recorder.d.ts.map +1 -1
  138. package/dist/meeting-recorder.js +3 -3
  139. package/dist/node/analytics.js +2 -0
  140. package/dist/node/calendar.js +2 -0
  141. package/dist/node/database.js +2 -0
  142. package/dist/node/email.js +2 -0
  143. package/dist/node/embedding.js +2 -0
  144. package/dist/node/impls/elevenlabs-voice.js +102 -0
  145. package/dist/node/impls/fal-voice.js +112 -0
  146. package/dist/node/impls/fathom-meeting-recorder.js +287 -0
  147. package/dist/node/impls/fathom-meeting-recorder.mapper.js +105 -0
  148. package/dist/node/impls/fathom-meeting-recorder.types.js +0 -0
  149. package/dist/node/impls/fathom-meeting-recorder.utils.js +72 -0
  150. package/dist/node/impls/fathom-meeting-recorder.webhooks.js +29 -0
  151. package/dist/node/impls/fireflies-meeting-recorder.js +274 -0
  152. package/dist/node/impls/fireflies-meeting-recorder.queries.js +85 -0
  153. package/dist/node/impls/fireflies-meeting-recorder.types.js +0 -0
  154. package/dist/node/impls/fireflies-meeting-recorder.utils.js +42 -0
  155. package/dist/node/impls/gcs-storage.js +97 -0
  156. package/dist/node/impls/gmail-inbound.js +227 -0
  157. package/dist/node/impls/gmail-outbound.js +139 -0
  158. package/dist/node/impls/google-calendar.js +191 -0
  159. package/dist/node/impls/gradium-voice.js +90 -0
  160. package/dist/node/impls/granola-meeting-recorder.js +512 -0
  161. package/dist/node/impls/granola-meeting-recorder.mcp.js +278 -0
  162. package/dist/node/impls/granola-meeting-recorder.types.js +0 -0
  163. package/dist/node/impls/index.js +4658 -0
  164. package/dist/node/impls/jira.js +124 -0
  165. package/dist/node/impls/linear.js +83 -0
  166. package/dist/node/impls/mistral-embedding.js +43 -0
  167. package/dist/node/impls/mistral-llm.js +269 -0
  168. package/dist/node/impls/notion.js +160 -0
  169. package/dist/node/impls/posthog-reader.js +159 -0
  170. package/dist/node/impls/posthog-utils.js +38 -0
  171. package/dist/node/impls/posthog.js +322 -0
  172. package/dist/node/impls/postmark-email.js +60 -0
  173. package/dist/node/impls/powens-client.js +195 -0
  174. package/dist/node/impls/powens-openbanking.js +426 -0
  175. package/dist/node/impls/provider-factory.js +4080 -0
  176. package/dist/node/impls/qdrant-vector.js +78 -0
  177. package/dist/node/impls/stripe-payments.js +228 -0
  178. package/dist/node/impls/supabase-psql.js +150 -0
  179. package/dist/node/impls/supabase-vector.js +323 -0
  180. package/dist/node/impls/tldv-meeting-recorder.js +145 -0
  181. package/dist/node/impls/twilio-sms.js +65 -0
  182. package/dist/node/index.js +4699 -0
  183. package/dist/node/llm.js +2 -0
  184. package/dist/node/meeting-recorder.js +2 -0
  185. package/dist/node/openbanking.js +2 -0
  186. package/dist/node/payments.js +2 -0
  187. package/dist/node/project-management.js +2 -0
  188. package/dist/node/runtime.js +0 -0
  189. package/dist/node/secrets/provider.js +11 -0
  190. package/dist/node/sms.js +2 -0
  191. package/dist/node/storage.js +2 -0
  192. package/dist/node/vector-store.js +2 -0
  193. package/dist/node/voice.js +2 -0
  194. package/dist/openbanking.d.ts +1 -8
  195. package/dist/openbanking.d.ts.map +1 -1
  196. package/dist/openbanking.js +3 -3
  197. package/dist/payments.d.ts +1 -8
  198. package/dist/payments.d.ts.map +1 -1
  199. package/dist/payments.js +3 -3
  200. package/dist/project-management.d.ts +1 -8
  201. package/dist/project-management.d.ts.map +1 -1
  202. package/dist/project-management.js +3 -3
  203. package/dist/runtime.d.ts +2 -2
  204. package/dist/runtime.d.ts.map +1 -0
  205. package/dist/runtime.js +1 -0
  206. package/dist/secrets/provider.d.ts +3 -2
  207. package/dist/secrets/provider.d.ts.map +1 -0
  208. package/dist/secrets/provider.js +12 -3
  209. package/dist/sms.d.ts +1 -8
  210. package/dist/sms.d.ts.map +1 -1
  211. package/dist/sms.js +3 -3
  212. package/dist/storage.d.ts +1 -8
  213. package/dist/storage.d.ts.map +1 -1
  214. package/dist/storage.js +3 -3
  215. package/dist/vector-store.d.ts +1 -8
  216. package/dist/vector-store.d.ts.map +1 -1
  217. package/dist/vector-store.js +3 -3
  218. package/dist/voice.d.ts +1 -8
  219. package/dist/voice.d.ts.map +1 -1
  220. package/dist/voice.js +3 -3
  221. package/package.json +405 -114
  222. package/dist/_virtual/_rolldown/runtime.js +0 -36
  223. package/dist/impls/elevenlabs-voice.js.map +0 -1
  224. package/dist/impls/fal-voice.js.map +0 -1
  225. package/dist/impls/fathom-meeting-recorder.js.map +0 -1
  226. package/dist/impls/fathom-meeting-recorder.mapper.js.map +0 -1
  227. package/dist/impls/fathom-meeting-recorder.utils.js.map +0 -1
  228. package/dist/impls/fathom-meeting-recorder.webhooks.js.map +0 -1
  229. package/dist/impls/fireflies-meeting-recorder.js.map +0 -1
  230. package/dist/impls/fireflies-meeting-recorder.queries.js.map +0 -1
  231. package/dist/impls/fireflies-meeting-recorder.utils.js.map +0 -1
  232. package/dist/impls/gcs-storage.js.map +0 -1
  233. package/dist/impls/gmail-inbound.js.map +0 -1
  234. package/dist/impls/gmail-outbound.js.map +0 -1
  235. package/dist/impls/google-calendar.js.map +0 -1
  236. package/dist/impls/gradium-voice.js.map +0 -1
  237. package/dist/impls/granola-meeting-recorder.js.map +0 -1
  238. package/dist/impls/jira.js.map +0 -1
  239. package/dist/impls/linear.js.map +0 -1
  240. package/dist/impls/mistral-embedding.js.map +0 -1
  241. package/dist/impls/mistral-llm.js.map +0 -1
  242. package/dist/impls/notion.js.map +0 -1
  243. package/dist/impls/posthog-reader.js.map +0 -1
  244. package/dist/impls/posthog-utils.js.map +0 -1
  245. package/dist/impls/posthog.js.map +0 -1
  246. package/dist/impls/postmark-email.js.map +0 -1
  247. package/dist/impls/powens-client.js.map +0 -1
  248. package/dist/impls/powens-openbanking.js.map +0 -1
  249. package/dist/impls/provider-factory.js.map +0 -1
  250. package/dist/impls/qdrant-vector.js.map +0 -1
  251. package/dist/impls/stripe-payments.js.map +0 -1
  252. package/dist/impls/supabase-psql.js.map +0 -1
  253. package/dist/impls/supabase-vector.js.map +0 -1
  254. package/dist/impls/tldv-meeting-recorder.js.map +0 -1
  255. package/dist/impls/twilio-sms.js.map +0 -1
  256. package/dist/index.js.map +0 -1
@@ -0,0 +1,4658 @@
1
+ // src/impls/elevenlabs-voice.ts
2
+ import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
3
+ var FORMAT_MAP = {
4
+ mp3: "mp3_44100_128",
5
+ wav: "pcm_44100",
6
+ ogg: "mp3_44100_128",
7
+ pcm: "pcm_16000"
8
+ };
9
+ var SAMPLE_RATE = {
10
+ mp3_22050_32: 22050,
11
+ mp3_44100_32: 44100,
12
+ mp3_44100_64: 44100,
13
+ mp3_44100_96: 44100,
14
+ mp3_44100_128: 44100,
15
+ mp3_44100_192: 44100,
16
+ pcm_16000: 16000,
17
+ pcm_22050: 22050,
18
+ pcm_24000: 24000,
19
+ pcm_44100: 44100,
20
+ ulaw_8000: 8000
21
+ };
22
+
23
+ class ElevenLabsVoiceProvider {
24
+ client;
25
+ defaultVoiceId;
26
+ modelId;
27
+ constructor(options) {
28
+ this.client = options.client ?? new ElevenLabsClient({
29
+ apiKey: options.apiKey
30
+ });
31
+ this.defaultVoiceId = options.defaultVoiceId;
32
+ this.modelId = options.modelId;
33
+ }
34
+ async listVoices() {
35
+ const response = await this.client.voices.getAll();
36
+ return (response.voices ?? []).map((voice) => ({
37
+ id: voice.voiceId ?? "",
38
+ name: voice.name ?? "",
39
+ description: voice.description ?? undefined,
40
+ language: voice.labels?.language ?? undefined,
41
+ metadata: {
42
+ category: voice.category ?? "",
43
+ ...voice.previewUrl ? { previewUrl: voice.previewUrl } : {},
44
+ ...(() => {
45
+ const { language, ...rest } = voice.labels ?? {};
46
+ return rest;
47
+ })()
48
+ }
49
+ }));
50
+ }
51
+ async synthesize(input) {
52
+ const voiceId = input.voiceId ?? this.defaultVoiceId;
53
+ if (!voiceId) {
54
+ throw new Error("Voice ID is required for ElevenLabs synthesis.");
55
+ }
56
+ const formatKey = input.format ?? "mp3";
57
+ const outputFormat = FORMAT_MAP[formatKey] ?? FORMAT_MAP.mp3;
58
+ const sampleRate = input.sampleRateHz ?? SAMPLE_RATE[outputFormat] ?? SAMPLE_RATE.mp3_44100_128 ?? 44100;
59
+ const voiceSettings = input.stability != null || input.similarityBoost != null || input.style != null ? {
60
+ ...input.stability != null ? { stability: input.stability } : {},
61
+ ...input.similarityBoost != null ? { similarityBoost: input.similarityBoost } : {},
62
+ ...input.style != null ? { style: input.style } : {}
63
+ } : undefined;
64
+ const stream = await this.client.textToSpeech.convert(voiceId, {
65
+ text: input.text,
66
+ modelId: this.modelId,
67
+ outputFormat,
68
+ voiceSettings
69
+ });
70
+ const audio = await readWebStream(stream);
71
+ return {
72
+ audio,
73
+ format: formatKey,
74
+ sampleRateHz: sampleRate,
75
+ durationSeconds: undefined,
76
+ url: undefined
77
+ };
78
+ }
79
+ }
80
+ async function readWebStream(stream) {
81
+ const reader = stream.getReader();
82
+ const chunks = [];
83
+ while (true) {
84
+ const { done, value } = await reader.read();
85
+ if (done)
86
+ break;
87
+ if (value) {
88
+ chunks.push(value);
89
+ }
90
+ }
91
+ const length = chunks.reduce((total, chunk) => total + chunk.length, 0);
92
+ const result = new Uint8Array(length);
93
+ let offset = 0;
94
+ for (const chunk of chunks) {
95
+ result.set(chunk, offset);
96
+ offset += chunk.length;
97
+ }
98
+ return result;
99
+ }
100
+
101
+ // src/impls/fal-voice.ts
102
+ import { createFalClient } from "@fal-ai/client";
103
+ var DEFAULT_MODEL_ID = "fal-ai/chatterbox/text-to-speech";
104
+
105
+ class FalVoiceProvider {
106
+ client;
107
+ modelId;
108
+ defaultVoiceUrl;
109
+ defaultExaggeration;
110
+ defaultTemperature;
111
+ defaultCfg;
112
+ pollIntervalMs;
113
+ constructor(options) {
114
+ this.client = options.client ?? createFalClient({
115
+ credentials: options.apiKey
116
+ });
117
+ this.modelId = options.modelId ?? DEFAULT_MODEL_ID;
118
+ this.defaultVoiceUrl = options.defaultVoiceUrl;
119
+ this.defaultExaggeration = options.defaultExaggeration;
120
+ this.defaultTemperature = options.defaultTemperature;
121
+ this.defaultCfg = options.defaultCfg;
122
+ this.pollIntervalMs = options.pollIntervalMs;
123
+ }
124
+ async listVoices() {
125
+ const voices = [
126
+ {
127
+ id: "default",
128
+ name: "Default Chatterbox Voice",
129
+ description: "Uses the default model voice (or configured default reference audio URL).",
130
+ metadata: {
131
+ modelId: this.modelId
132
+ }
133
+ }
134
+ ];
135
+ if (this.defaultVoiceUrl) {
136
+ voices.push({
137
+ id: this.defaultVoiceUrl,
138
+ name: "Configured Reference Voice",
139
+ description: "Reference voice configured at provider setup and used when voiceId is default.",
140
+ previewUrl: this.defaultVoiceUrl,
141
+ metadata: {
142
+ modelId: this.modelId,
143
+ source: "config.defaultVoiceUrl"
144
+ }
145
+ });
146
+ }
147
+ return voices;
148
+ }
149
+ async synthesize(input) {
150
+ const referenceVoiceUrl = resolveVoiceUrl(input.voiceId, this.defaultVoiceUrl);
151
+ const result = await this.client.subscribe(this.modelId, {
152
+ input: {
153
+ text: input.text,
154
+ ...referenceVoiceUrl ? { audio_url: referenceVoiceUrl } : {},
155
+ ...this.defaultExaggeration != null ? { exaggeration: this.defaultExaggeration } : {},
156
+ ...this.defaultTemperature != null ? { temperature: this.defaultTemperature } : {},
157
+ ...this.defaultCfg != null ? { cfg: this.defaultCfg } : {}
158
+ },
159
+ pollInterval: this.pollIntervalMs
160
+ });
161
+ const audioUrl = extractAudioUrl(result.data);
162
+ if (!audioUrl) {
163
+ throw new Error("Fal synthesis completed without an audio URL in response.");
164
+ }
165
+ const response = await fetch(audioUrl);
166
+ if (!response.ok) {
167
+ throw new Error(`Fal audio download failed (${response.status}).`);
168
+ }
169
+ const audio = new Uint8Array(await response.arrayBuffer());
170
+ return {
171
+ audio,
172
+ format: input.format ?? inferFormatFromUrl(audioUrl) ?? "wav",
173
+ sampleRateHz: input.sampleRateHz ?? 24000,
174
+ durationSeconds: undefined,
175
+ url: audioUrl
176
+ };
177
+ }
178
+ }
179
+ function resolveVoiceUrl(voiceId, defaultVoiceUrl) {
180
+ if (!voiceId || voiceId === "default")
181
+ return defaultVoiceUrl;
182
+ if (isHttpUrl(voiceId))
183
+ return voiceId;
184
+ throw new Error('Fal voiceId must be "default" or a public reference audio URL.');
185
+ }
186
+ function extractAudioUrl(output) {
187
+ if (output.audio?.url)
188
+ return output.audio.url;
189
+ if (typeof output.audio_url === "string")
190
+ return output.audio_url;
191
+ if (typeof output.url === "string")
192
+ return output.url;
193
+ return;
194
+ }
195
+ function inferFormatFromUrl(url) {
196
+ const normalized = url.toLowerCase();
197
+ if (normalized.endsWith(".wav"))
198
+ return "wav";
199
+ if (normalized.endsWith(".mp3"))
200
+ return "mp3";
201
+ if (normalized.endsWith(".ogg") || normalized.endsWith(".opus"))
202
+ return "ogg";
203
+ if (normalized.endsWith(".pcm"))
204
+ return "pcm";
205
+ return;
206
+ }
207
+ function isHttpUrl(value) {
208
+ return value.startsWith("https://") || value.startsWith("http://");
209
+ }
210
+
211
+ // src/impls/fathom-meeting-recorder.utils.ts
212
+ function extractItems(page) {
213
+ if (Array.isArray(page.items))
214
+ return page.items;
215
+ if (Array.isArray(page.data)) {
216
+ return page.data;
217
+ }
218
+ return [];
219
+ }
220
+ function extractNextCursor(page) {
221
+ return page.nextCursor ?? page.next_cursor ?? undefined;
222
+ }
223
+ function mapInvitee(invitee) {
224
+ const email = invitee.email;
225
+ const name = invitee.name;
226
+ if (!email && !name)
227
+ return;
228
+ return {
229
+ email,
230
+ name,
231
+ role: "attendee",
232
+ isExternal: invitee.is_external
233
+ };
234
+ }
235
+ function matchRecordingId(meeting, targetId) {
236
+ return meeting.recordingId === targetId;
237
+ }
238
+ function durationSeconds(start, end) {
239
+ if (!start || !end)
240
+ return;
241
+ const startDate = start instanceof Date ? start : new Date(start);
242
+ const endDate = end instanceof Date ? end : new Date(end);
243
+ if (Number.isNaN(startDate.valueOf()) || Number.isNaN(endDate.valueOf())) {
244
+ return;
245
+ }
246
+ return Math.max(0, (endDate.valueOf() - startDate.valueOf()) / 1000);
247
+ }
248
+ function mapTranscriptSegment(segment, index) {
249
+ return {
250
+ index,
251
+ speakerName: segment.speaker?.display_name ?? undefined,
252
+ speakerEmail: segment.speaker?.matched_calendar_invitee_email ?? undefined,
253
+ text: segment.text,
254
+ startTimeMs: parseTimestamp(segment.timestamp)
255
+ };
256
+ }
257
+ function parseTimestamp(value) {
258
+ const parts = value.split(":").map((part) => Number(part));
259
+ if (parts.length !== 3 || parts.some((part) => Number.isNaN(part))) {
260
+ return;
261
+ }
262
+ const [hours = 0, minutes = 0, seconds = 0] = parts;
263
+ return (hours * 3600 + minutes * 60 + seconds) * 1000;
264
+ }
265
+ async function safeReadError(response) {
266
+ try {
267
+ const data = await response.json();
268
+ return data?.message ?? response.statusText;
269
+ } catch {
270
+ return response.statusText;
271
+ }
272
+ }
273
+
274
+ // src/impls/fathom-meeting-recorder.mapper.ts
275
+ var mapFathomMeetingInvites = (invitees) => {
276
+ return invitees.map((invitee) => ({
277
+ ...invitee,
278
+ name: invitee.name ?? undefined,
279
+ email: invitee.email ?? undefined
280
+ }));
281
+ };
282
+ function mapFathomMeeting(meeting, params) {
283
+ const connectionId = params.connectionId ?? "unknown";
284
+ return {
285
+ id: meeting.recordingId.toString(),
286
+ tenantId: params.tenantId,
287
+ connectionId,
288
+ externalId: meeting.recordingId.toString(),
289
+ title: meeting.title ?? meeting.meetingTitle,
290
+ organizer: meeting.recordedBy ? {
291
+ name: meeting.recordedBy.name ?? undefined,
292
+ email: meeting.recordedBy.email ?? undefined,
293
+ role: "organizer"
294
+ } : undefined,
295
+ invitees: meeting.calendarInvitees.length ? mapFathomMeetingInvites(meeting.calendarInvitees) : undefined,
296
+ participants: meeting.calendarInvitees.length ? mapFathomMeetingInvites(meeting.calendarInvitees) : undefined,
297
+ scheduledStartAt: meeting.scheduledStartTime?.toISOString(),
298
+ scheduledEndAt: meeting.scheduledEndTime?.toISOString(),
299
+ recordingStartAt: meeting.recordingStartTime?.toISOString(),
300
+ recordingEndAt: meeting.recordingEndTime?.toISOString(),
301
+ durationSeconds: durationSeconds(meeting.recordingStartTime, meeting.recordingEndTime),
302
+ meetingUrl: meeting.url ?? undefined,
303
+ shareUrl: meeting.shareUrl ?? undefined,
304
+ transcriptAvailable: Array.isArray(meeting.transcript),
305
+ sourcePlatform: "fathom",
306
+ language: meeting.transcriptLanguage,
307
+ metadata: {
308
+ calendarInviteesDomainsType: meeting.calendarInviteesDomainsType
309
+ }
310
+ };
311
+ }
312
+
313
+ // src/impls/fathom-meeting-recorder.webhooks.ts
314
+ import { TriggeredFor } from "fathom-typescript/sdk/models/operations";
315
+ function normalizeWebhookHeaders(headers) {
316
+ const normalized = {};
317
+ for (const [key, value] of Object.entries(headers)) {
318
+ if (value == null)
319
+ continue;
320
+ const normalizedKey = key.toLowerCase();
321
+ if (Array.isArray(value)) {
322
+ if (value.length === 0)
323
+ continue;
324
+ normalized[normalizedKey] = value.join(", ");
325
+ } else {
326
+ normalized[normalizedKey] = value;
327
+ }
328
+ }
329
+ return normalized;
330
+ }
331
+ function normalizeTriggeredFor(values) {
332
+ if (!values)
333
+ return;
334
+ const allowed = new Set(Object.values(TriggeredFor));
335
+ const normalized = values.map((value) => value.trim()).filter((value) => allowed.has(value));
336
+ return normalized.length ? normalized : undefined;
337
+ }
338
+
339
+ // src/impls/fathom-meeting-recorder.ts
340
+ import { Fathom } from "fathom-typescript";
341
+ import { TriggeredFor as TriggeredFor2 } from "fathom-typescript/sdk/models/operations";
342
+ var DEFAULT_BASE_URL = "https://api.fathom.ai/external/v1";
343
+
344
+ class FathomMeetingRecorderProvider {
345
+ client;
346
+ apiKey;
347
+ baseUrl;
348
+ includeTranscript;
349
+ includeSummary;
350
+ includeActionItems;
351
+ includeCrmMatches;
352
+ triggeredFor;
353
+ webhookSecret;
354
+ maxPages;
355
+ constructor(options) {
356
+ this.apiKey = options.apiKey;
357
+ this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
358
+ this.includeTranscript = options.includeTranscript ?? false;
359
+ this.includeSummary = options.includeSummary ?? false;
360
+ this.includeActionItems = options.includeActionItems ?? false;
361
+ this.includeCrmMatches = options.includeCrmMatches ?? false;
362
+ this.triggeredFor = options.triggeredFor;
363
+ this.webhookSecret = options.webhookSecret;
364
+ this.maxPages = options.maxPages ?? 5;
365
+ this.client = options.client ?? new Fathom({
366
+ serverURL: this.baseUrl,
367
+ security: {
368
+ apiKeyAuth: this.apiKey
369
+ }
370
+ });
371
+ }
372
+ async listMeetings(params) {
373
+ const request = {
374
+ cursor: params.cursor,
375
+ createdAfter: params.from,
376
+ createdBefore: params.to,
377
+ includeTranscript: params.includeTranscript ?? this.includeTranscript,
378
+ includeSummary: params.includeSummary ?? this.includeSummary,
379
+ includeActionItems: this.includeActionItems,
380
+ includeCrmMatches: this.includeCrmMatches
381
+ };
382
+ const result = await this.client.listMeetings(request);
383
+ let firstPage;
384
+ for await (const page of result) {
385
+ firstPage = page;
386
+ break;
387
+ }
388
+ if (!firstPage) {
389
+ return { meetings: [] };
390
+ }
391
+ const rawItems = extractItems(firstPage);
392
+ const meetings = rawItems.map((meeting) => mapFathomMeeting(meeting, params));
393
+ return {
394
+ meetings,
395
+ nextCursor: extractNextCursor(firstPage) ?? undefined,
396
+ hasMore: Boolean(extractNextCursor(firstPage))
397
+ };
398
+ }
399
+ async getMeeting(params) {
400
+ const result = await this.client.listMeetings({
401
+ includeTranscript: params.includeTranscript ?? this.includeTranscript,
402
+ includeSummary: params.includeSummary ?? this.includeSummary,
403
+ includeActionItems: this.includeActionItems,
404
+ includeCrmMatches: this.includeCrmMatches
405
+ });
406
+ let pageCount = 0;
407
+ const targetId = Number.parseInt(params.meetingId, 10);
408
+ for await (const page of result) {
409
+ pageCount += 1;
410
+ const match = extractItems(page).find((meeting) => matchRecordingId(meeting, targetId));
411
+ if (match) {
412
+ return mapFathomMeeting(match, params);
413
+ }
414
+ if (pageCount >= this.maxPages) {
415
+ break;
416
+ }
417
+ }
418
+ throw new Error(`Fathom meeting "${targetId}" not found.`);
419
+ }
420
+ async getTranscript(params) {
421
+ const response = await this.request(`/recordings/${encodeURIComponent(params.meetingId)}/transcript`);
422
+ if (!Array.isArray(response.transcript)) {
423
+ throw new Error("Fathom transcript response did not include transcript.");
424
+ }
425
+ const segments = response.transcript.map((segment, index) => mapTranscriptSegment(segment, index));
426
+ return {
427
+ id: params.meetingId,
428
+ meetingId: params.meetingId,
429
+ tenantId: params.tenantId,
430
+ connectionId: params.connectionId ?? "unknown",
431
+ externalId: params.meetingId,
432
+ format: "segments",
433
+ text: segments.map((segment) => segment.text).join(`
434
+ `),
435
+ segments,
436
+ metadata: {
437
+ provider: "fathom"
438
+ },
439
+ raw: response.transcript
440
+ };
441
+ }
442
+ async parseWebhook(request) {
443
+ const payload = request.parsedBody ?? JSON.parse(request.rawBody);
444
+ const body = payload;
445
+ const recordingId = body.recording_id ?? body.recordingId ?? body.meeting_id ?? body.meetingId;
446
+ const verified = this.webhookSecret ? await this.verifyWebhook(request) : undefined;
447
+ return {
448
+ providerKey: "meeting-recorder.fathom",
449
+ eventType: body.event_type ?? body.eventType,
450
+ meetingId: recordingId,
451
+ recordingId,
452
+ verified,
453
+ payload
454
+ };
455
+ }
456
+ async verifyWebhook(request) {
457
+ if (!this.webhookSecret)
458
+ return true;
459
+ const headers = normalizeWebhookHeaders(request.headers);
460
+ try {
461
+ return Boolean(Fathom.verifyWebhook(this.webhookSecret, headers, request.rawBody));
462
+ } catch {
463
+ return false;
464
+ }
465
+ }
466
+ async registerWebhook(registration) {
467
+ const triggeredFor = normalizeTriggeredFor(registration.triggeredFor) ?? normalizeTriggeredFor(this.triggeredFor) ?? [TriggeredFor2.MyRecordings];
468
+ const webhook = await this.client.createWebhook({
469
+ destinationUrl: registration.url,
470
+ includeTranscript: registration.includeTranscript ?? true,
471
+ includeSummary: registration.includeSummary ?? false,
472
+ includeActionItems: registration.includeActionItems ?? false,
473
+ includeCrmMatches: registration.includeCrmMatches ?? false,
474
+ triggeredFor
475
+ });
476
+ return {
477
+ id: webhook.id,
478
+ secret: webhook.secret
479
+ };
480
+ }
481
+ async request(path) {
482
+ const response = await fetch(`${this.baseUrl}${path}`, {
483
+ headers: {
484
+ "Content-Type": "application/json",
485
+ "X-Api-Key": this.apiKey
486
+ }
487
+ });
488
+ if (!response.ok) {
489
+ const message = await safeReadError(response);
490
+ throw new Error(`Fathom API error (${response.status}): ${message}`);
491
+ }
492
+ return await response.json();
493
+ }
494
+ }
495
+
496
+ // src/impls/fireflies-meeting-recorder.queries.ts
497
+ var TRANSCRIPTS_QUERY = `
498
+ query Transcripts(
499
+ $limit: Int
500
+ $skip: Int
501
+ $fromDate: DateTime
502
+ $toDate: DateTime
503
+ $keyword: String
504
+ $scope: TranscriptsQueryScope
505
+ ) {
506
+ transcripts(
507
+ limit: $limit
508
+ skip: $skip
509
+ fromDate: $fromDate
510
+ toDate: $toDate
511
+ keyword: $keyword
512
+ scope: $scope
513
+ ) {
514
+ id
515
+ title
516
+ organizer_email
517
+ participants
518
+ meeting_attendees {
519
+ name
520
+ email
521
+ displayName
522
+ }
523
+ dateString
524
+ duration
525
+ meeting_link
526
+ transcript_url
527
+ }
528
+ }
529
+ `;
530
+ var TRANSCRIPT_QUERY = `
531
+ query Transcript($transcriptId: String!) {
532
+ transcript(id: $transcriptId) {
533
+ id
534
+ title
535
+ organizer_email
536
+ participants
537
+ meeting_attendees {
538
+ name
539
+ email
540
+ displayName
541
+ }
542
+ dateString
543
+ duration
544
+ meeting_link
545
+ transcript_url
546
+ }
547
+ }
548
+ `;
549
+ var TRANSCRIPT_WITH_SEGMENTS_QUERY = `
550
+ query Transcript($transcriptId: String!) {
551
+ transcript(id: $transcriptId) {
552
+ id
553
+ title
554
+ organizer_email
555
+ participants
556
+ meeting_attendees {
557
+ name
558
+ email
559
+ displayName
560
+ }
561
+ dateString
562
+ duration
563
+ meeting_link
564
+ transcript_url
565
+ sentences {
566
+ index
567
+ speaker_name
568
+ speaker_id
569
+ text
570
+ start_time
571
+ end_time
572
+ }
573
+ }
574
+ }
575
+ `;
576
+
577
+ // src/impls/fireflies-meeting-recorder.utils.ts
578
+ import { Buffer as Buffer2 } from "node:buffer";
579
+ import { timingSafeEqual } from "crypto";
580
+ function parseSeconds(value) {
581
+ if (value == null)
582
+ return;
583
+ const num = typeof value === "number" ? value : Number(value);
584
+ if (!Number.isFinite(num))
585
+ return;
586
+ return num * 1000;
587
+ }
588
+ function normalizeHeader(headers, key) {
589
+ const header = headers[key] ?? headers[key.toLowerCase()] ?? headers[key.toUpperCase()];
590
+ if (Array.isArray(header))
591
+ return header[0];
592
+ return header;
593
+ }
594
+ function safeCompareHex(a, b) {
595
+ try {
596
+ const aBuffer = Buffer2.from(a, "hex");
597
+ const bBuffer = Buffer2.from(b, "hex");
598
+ if (aBuffer.length !== bBuffer.length)
599
+ return false;
600
+ return timingSafeEqual(aBuffer, bBuffer);
601
+ } catch {
602
+ return false;
603
+ }
604
+ }
605
+ async function safeReadError2(response) {
606
+ try {
607
+ const data = await response.json();
608
+ return data?.message ?? response.statusText;
609
+ } catch {
610
+ return response.statusText;
611
+ }
612
+ }
613
+
614
+ // src/impls/fireflies-meeting-recorder.ts
615
+ import { createHmac } from "crypto";
616
+ var DEFAULT_BASE_URL2 = "https://api.fireflies.ai/graphql";
617
+
618
+ class FirefliesMeetingRecorderProvider {
619
+ apiKey;
620
+ baseUrl;
621
+ defaultPageSize;
622
+ webhookSecret;
623
+ constructor(options) {
624
+ this.apiKey = options.apiKey;
625
+ this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL2;
626
+ this.defaultPageSize = options.pageSize;
627
+ this.webhookSecret = options.webhookSecret;
628
+ }
629
+ async listMeetings(params) {
630
+ const limit = params.pageSize ?? this.defaultPageSize ?? 25;
631
+ const skip = params.cursor ? Number(params.cursor) : 0;
632
+ const data = await this.query(TRANSCRIPTS_QUERY, {
633
+ limit,
634
+ skip: Number.isFinite(skip) ? skip : 0,
635
+ fromDate: params.from,
636
+ toDate: params.to,
637
+ keyword: params.query,
638
+ scope: params.query ? "all" : undefined
639
+ });
640
+ const meetings = data.transcripts.map((transcript) => this.mapTranscriptToMeeting(transcript, params));
641
+ const nextCursor = meetings.length === limit ? String(skip + limit) : undefined;
642
+ return {
643
+ meetings,
644
+ nextCursor,
645
+ hasMore: Boolean(nextCursor)
646
+ };
647
+ }
648
+ async getMeeting(params) {
649
+ const data = await this.query(TRANSCRIPT_QUERY, {
650
+ transcriptId: params.meetingId
651
+ });
652
+ return this.mapTranscriptToMeeting(data.transcript, params);
653
+ }
654
+ async getTranscript(params) {
655
+ const data = await this.query(TRANSCRIPT_WITH_SEGMENTS_QUERY, { transcriptId: params.meetingId });
656
+ const transcript = data.transcript;
657
+ const segments = (transcript.sentences ?? []).map((segment) => this.mapSentence(segment));
658
+ return {
659
+ id: transcript.id,
660
+ meetingId: transcript.id,
661
+ tenantId: params.tenantId,
662
+ connectionId: params.connectionId ?? "unknown",
663
+ externalId: transcript.id,
664
+ format: "segments",
665
+ text: segments.map((segment) => segment.text).join(`
666
+ `),
667
+ segments,
668
+ generatedAt: transcript.dateString ?? undefined,
669
+ sourceUrl: transcript.transcript_url ?? undefined,
670
+ metadata: {
671
+ meetingLink: transcript.meeting_link,
672
+ durationMinutes: transcript.duration
673
+ },
674
+ raw: transcript
675
+ };
676
+ }
677
+ async parseWebhook(request) {
678
+ const payload = request.parsedBody ?? JSON.parse(request.rawBody);
679
+ const body = payload;
680
+ const verified = this.webhookSecret ? await this.verifyWebhook(request) : undefined;
681
+ return {
682
+ providerKey: "meeting-recorder.fireflies",
683
+ eventType: body.eventType,
684
+ meetingId: body.meetingId,
685
+ transcriptId: body.meetingId,
686
+ verified,
687
+ payload,
688
+ metadata: {
689
+ clientReferenceId: body.clientReferenceId
690
+ }
691
+ };
692
+ }
693
+ async verifyWebhook(request) {
694
+ if (!this.webhookSecret)
695
+ return true;
696
+ const signatureHeader = normalizeHeader(request.headers, "x-hub-signature");
697
+ if (!signatureHeader)
698
+ return false;
699
+ const signature = signatureHeader.replace(/^sha256=/, "");
700
+ const digest = createHmac("sha256", this.webhookSecret).update(request.rawBody).digest("hex");
701
+ return safeCompareHex(digest, signature);
702
+ }
703
+ mapTranscriptToMeeting(transcript, params) {
704
+ const connectionId = params.connectionId ?? "unknown";
705
+ const organizer = transcript.organizer_email ? { email: transcript.organizer_email, role: "organizer" } : undefined;
706
+ const attendees = transcript.meeting_attendees?.length ? transcript.meeting_attendees.map((attendee) => this.mapAttendee(attendee)) : transcript.participants?.map((email) => ({ email, role: "attendee" }));
707
+ return {
708
+ id: transcript.id,
709
+ tenantId: params.tenantId,
710
+ connectionId,
711
+ externalId: transcript.id,
712
+ title: transcript.title ?? undefined,
713
+ organizer,
714
+ invitees: attendees,
715
+ participants: attendees,
716
+ scheduledStartAt: transcript.dateString ?? undefined,
717
+ recordingStartAt: transcript.dateString ?? undefined,
718
+ durationSeconds: transcript.duration ? transcript.duration * 60 : undefined,
719
+ meetingUrl: transcript.meeting_link ?? transcript.transcript_url ?? undefined,
720
+ transcriptAvailable: Boolean(transcript.transcript_url),
721
+ sourcePlatform: "fireflies",
722
+ metadata: {
723
+ transcriptUrl: transcript.transcript_url
724
+ }
725
+ };
726
+ }
727
+ mapAttendee(attendee) {
728
+ return {
729
+ name: attendee.name ?? attendee.displayName ?? undefined,
730
+ email: attendee.email ?? undefined,
731
+ role: "attendee"
732
+ };
733
+ }
734
+ mapSentence(segment) {
735
+ return {
736
+ index: segment.index ?? undefined,
737
+ speakerId: segment.speaker_id ?? undefined,
738
+ speakerName: segment.speaker_name ?? undefined,
739
+ text: segment.text,
740
+ startTimeMs: parseSeconds(segment.start_time),
741
+ endTimeMs: parseSeconds(segment.end_time)
742
+ };
743
+ }
744
+ async query(query, variables) {
745
+ const response = await fetch(this.baseUrl, {
746
+ method: "POST",
747
+ headers: {
748
+ "Content-Type": "application/json",
749
+ Authorization: `Bearer ${this.apiKey}`
750
+ },
751
+ body: JSON.stringify({ query, variables })
752
+ });
753
+ if (!response.ok) {
754
+ const message = await safeReadError2(response);
755
+ throw new Error(`Fireflies API error (${response.status}): ${message}`);
756
+ }
757
+ const result = await response.json();
758
+ if (result.errors?.length) {
759
+ throw new Error(result.errors.map((error) => error.message).join("; "));
760
+ }
761
+ if (!result.data) {
762
+ throw new Error("Fireflies API returned empty data payload.");
763
+ }
764
+ return result.data;
765
+ }
766
+ }
767
+
768
+ // src/impls/gcs-storage.ts
769
+ import { Storage } from "@google-cloud/storage";
770
+
771
+ class GoogleCloudStorageProvider {
772
+ storage;
773
+ bucketName;
774
+ constructor(options) {
775
+ this.storage = options.storage ?? new Storage(options.clientOptions ?? undefined);
776
+ this.bucketName = options.bucket;
777
+ }
778
+ async putObject(input) {
779
+ const bucketName = input.bucket ?? this.bucketName;
780
+ const bucket = this.storage.bucket(bucketName);
781
+ const file = bucket.file(input.key);
782
+ const buffer = toBuffer(input.data);
783
+ await file.save(buffer, {
784
+ resumable: false,
785
+ contentType: input.contentType,
786
+ metadata: input.metadata
787
+ });
788
+ if (input.makePublic) {
789
+ await file.makePublic();
790
+ }
791
+ const [metadata] = await file.getMetadata();
792
+ return toMetadata(metadata);
793
+ }
794
+ async getObject(input) {
795
+ const bucketName = input.bucket ?? this.bucketName;
796
+ const bucket = this.storage.bucket(bucketName);
797
+ const file = bucket.file(input.key);
798
+ const [exists] = await file.exists();
799
+ if (!exists)
800
+ return null;
801
+ const [contents] = await file.download();
802
+ const [metadata] = await file.getMetadata();
803
+ return {
804
+ ...toMetadata(metadata),
805
+ data: new Uint8Array(contents)
806
+ };
807
+ }
808
+ async deleteObject(input) {
809
+ const bucketName = input.bucket ?? this.bucketName;
810
+ const bucket = this.storage.bucket(bucketName);
811
+ const file = bucket.file(input.key);
812
+ await file.delete({ ignoreNotFound: true });
813
+ }
814
+ async generateSignedUrl(options) {
815
+ const bucketName = options.bucket ?? this.bucketName;
816
+ const bucket = this.storage.bucket(bucketName);
817
+ const file = bucket.file(options.key);
818
+ const action = options.method === "PUT" ? "write" : "read";
819
+ const expires = Date.now() + options.expiresInSeconds * 1000;
820
+ const [url] = await file.getSignedUrl({
821
+ action,
822
+ expires,
823
+ contentType: options.contentType
824
+ });
825
+ return { url, expiresAt: new Date(expires) };
826
+ }
827
+ async listObjects(query) {
828
+ const bucketName = query.bucket ?? this.bucketName;
829
+ const bucket = this.storage.bucket(bucketName);
830
+ const [files, nextQuery, response] = await bucket.getFiles({
831
+ prefix: query.prefix,
832
+ maxResults: query.maxResults,
833
+ pageToken: query.pageToken
834
+ });
835
+ const nextTokenFromQuery = typeof nextQuery === "object" && nextQuery !== null && "pageToken" in nextQuery ? nextQuery.pageToken : undefined;
836
+ const nextTokenFromResponse = response && typeof response === "object" && "nextPageToken" in response ? response.nextPageToken : undefined;
837
+ return {
838
+ objects: files.map((file) => toMetadata(file.metadata)),
839
+ nextPageToken: nextTokenFromQuery ?? nextTokenFromResponse ?? undefined
840
+ };
841
+ }
842
+ }
843
+ function toBuffer(data) {
844
+ if (data instanceof Uint8Array) {
845
+ return Buffer.from(data);
846
+ }
847
+ return Buffer.from(data);
848
+ }
849
+ function toMetadata(metadata) {
850
+ const meta = metadata;
851
+ return {
852
+ bucket: String(meta.bucket ?? ""),
853
+ key: String(meta.name ?? ""),
854
+ sizeBytes: meta.size ? Number(meta.size) : undefined,
855
+ contentType: meta.contentType ? String(meta.contentType) : undefined,
856
+ etag: meta.etag ? String(meta.etag) : undefined,
857
+ checksum: meta.md5Hash ? String(meta.md5Hash) : undefined,
858
+ lastModified: meta.updated ? new Date(String(meta.updated)) : undefined,
859
+ metadata: meta.metadata
860
+ };
861
+ }
862
+
863
+ // src/impls/gmail-inbound.ts
864
+ import { google } from "googleapis";
865
+
866
+ class GmailInboundProvider {
867
+ gmail;
868
+ userId;
869
+ includeSpamTrash;
870
+ auth;
871
+ constructor(options) {
872
+ this.auth = options.auth;
873
+ this.gmail = options.gmail ?? google.gmail({
874
+ version: "v1",
875
+ auth: options.auth
876
+ });
877
+ this.userId = options.userId ?? "me";
878
+ this.includeSpamTrash = options.includeSpamTrash ?? false;
879
+ }
880
+ async listThreads(query) {
881
+ const response = await this.gmail.users.threads.list({
882
+ userId: this.userId,
883
+ maxResults: query?.pageSize,
884
+ pageToken: query?.pageToken,
885
+ q: query?.query,
886
+ labelIds: query?.label ? [query.label] : undefined,
887
+ includeSpamTrash: this.includeSpamTrash,
888
+ auth: this.auth
889
+ });
890
+ const threads = await Promise.all((response.data.threads ?? []).map(async (thread) => {
891
+ if (!thread.id)
892
+ return null;
893
+ return this.getThread(thread.id);
894
+ }));
895
+ return threads.filter((thread) => thread !== null);
896
+ }
897
+ async getThread(threadId) {
898
+ const response = await this.gmail.users.threads.get({
899
+ id: threadId,
900
+ userId: this.userId,
901
+ format: "full",
902
+ auth: this.auth
903
+ });
904
+ const thread = response.data;
905
+ if (!thread)
906
+ return null;
907
+ const messages = thread.messages?.map((message) => this.transformMessage(message)) ?? [];
908
+ const participants = dedupeAddresses(messages.flatMap((message) => [
909
+ message.from,
910
+ ...message.to,
911
+ ...message.cc ?? []
912
+ ]));
913
+ const firstMessage = messages[0];
914
+ const lastMessage = messages[messages.length - 1];
915
+ const updatedAt = lastMessage?.receivedAt ?? lastMessage?.sentAt ?? firstMessage?.receivedAt ?? firstMessage?.sentAt ?? new Date;
916
+ const labels = Array.from(new Set(messages.flatMap((message) => {
917
+ const labelField = message.metadata?.labelIds;
918
+ if (!labelField)
919
+ return [];
920
+ return labelField.split(",").map((label) => label.trim());
921
+ }).filter((label) => Boolean(label))));
922
+ return {
923
+ id: thread.id ?? threadId,
924
+ subject: messages[0]?.subject,
925
+ snippet: thread.snippet ?? "",
926
+ participants,
927
+ messages,
928
+ updatedAt,
929
+ labels,
930
+ metadata: thread.historyId ? { historyId: thread.historyId } : undefined
931
+ };
932
+ }
933
+ async listMessagesSince(query) {
934
+ const after = query.since ? Math.floor(query.since.getTime() / 1000) : undefined;
935
+ const q = [];
936
+ if (after) {
937
+ q.push(`after:${after}`);
938
+ }
939
+ const response = await this.gmail.users.messages.list({
940
+ userId: this.userId,
941
+ maxResults: query.pageSize,
942
+ pageToken: query.pageToken,
943
+ labelIds: query.label ? [query.label] : undefined,
944
+ q: q.join(" "),
945
+ includeSpamTrash: this.includeSpamTrash,
946
+ auth: this.auth
947
+ });
948
+ const messages = await Promise.all((response.data.messages ?? []).map(async (item) => {
949
+ if (!item.id)
950
+ return null;
951
+ const full = await this.gmail.users.messages.get({
952
+ userId: this.userId,
953
+ id: item.id,
954
+ format: "full",
955
+ auth: this.auth
956
+ });
957
+ if (!full.data)
958
+ return null;
959
+ return this.transformMessage(full.data);
960
+ }));
961
+ return {
962
+ messages: messages.filter((message) => message !== null),
963
+ nextPageToken: response.data.nextPageToken ?? undefined
964
+ };
965
+ }
966
+ transformMessage(message) {
967
+ const headers = message.payload?.headers ?? [];
968
+ const subject = headerValue(headers, "Subject") ?? "";
969
+ const from = parseAddress(headerValue(headers, "From")) ?? inferFallbackAddress("from", message.id);
970
+ const to = parseAddressList(headerValue(headers, "To"));
971
+ const cc = parseAddressList(headerValue(headers, "Cc"));
972
+ const bcc = parseAddressList(headerValue(headers, "Bcc"));
973
+ const replyTo = parseAddress(headerValue(headers, "Reply-To"));
974
+ const { text, html, attachments } = extractContent(message.payload);
975
+ const timestamp = message.internalDate ? new Date(Number(message.internalDate)) : new Date;
976
+ const metadata = {
977
+ ...message.labelIds?.length ? { labelIds: message.labelIds.join(",") } : {},
978
+ ...message.historyId ? { historyId: message.historyId } : {}
979
+ };
980
+ return {
981
+ id: message.id ?? "",
982
+ threadId: message.threadId ?? "",
983
+ subject,
984
+ from,
985
+ to,
986
+ cc,
987
+ bcc,
988
+ replyTo: replyTo ?? undefined,
989
+ sentAt: timestamp,
990
+ receivedAt: timestamp,
991
+ textBody: text ?? undefined,
992
+ htmlBody: html ?? undefined,
993
+ attachments,
994
+ headers: Object.fromEntries(headers.map((header) => [header.name ?? "", header.value ?? ""])),
995
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined
996
+ };
997
+ }
998
+ }
999
+ function headerValue(headers, name) {
1000
+ const header = headers.find((candidate) => candidate.name?.toLowerCase() === name.toLowerCase());
1001
+ const value = header?.value;
1002
+ return typeof value === "string" ? value : undefined;
1003
+ }
1004
+ function parseAddress(header) {
1005
+ const addresses = parseAddressList(header);
1006
+ if (addresses.length === 0) {
1007
+ return null;
1008
+ }
1009
+ const firstAddress = addresses[0];
1010
+ return firstAddress || null;
1011
+ }
1012
+ function inferFallbackAddress(field, messageId) {
1013
+ const suffix = messageId ? messageId.replace(/[^\w]/g, "").slice(-8) || "unknown" : "unknown";
1014
+ return {
1015
+ email: `${field}-${suffix}@mail.local`
1016
+ };
1017
+ }
1018
+ function parseAddressList(header) {
1019
+ if (!header)
1020
+ return [];
1021
+ return header.split(",").map((part) => part.trim()).filter(Boolean).map((value) => {
1022
+ const match = value.match(/^(?:"?([^"]*)"?\s)?<?([^<>]+)>?$/);
1023
+ if (!match) {
1024
+ return { email: value };
1025
+ }
1026
+ const name = match[1]?.trim();
1027
+ const email = match[2]?.trim();
1028
+ if (!email) {
1029
+ return { email: value };
1030
+ }
1031
+ return name ? { email, name } : { email };
1032
+ });
1033
+ }
1034
+ function dedupeAddresses(addresses) {
1035
+ const map = new Map;
1036
+ for (const address of addresses) {
1037
+ if (!address)
1038
+ continue;
1039
+ map.set(address.email.toLowerCase(), address);
1040
+ }
1041
+ return Array.from(map.values());
1042
+ }
1043
+ function extractContent(payload) {
1044
+ if (!payload) {
1045
+ return { attachments: [] };
1046
+ }
1047
+ const attachments = [];
1048
+ const visit = (part) => {
1049
+ if (!part)
1050
+ return {};
1051
+ if (part.filename && part.body?.attachmentId) {
1052
+ attachments.push({
1053
+ id: part.body.attachmentId,
1054
+ filename: part.filename,
1055
+ contentType: part.mimeType ?? "application/octet-stream",
1056
+ sizeBytes: part.body.size ?? undefined
1057
+ });
1058
+ }
1059
+ const mimeType = part.mimeType ?? "";
1060
+ const data = part.body?.data;
1061
+ if (mimeType === "text/plain" && data) {
1062
+ return { text: decodeBase64Url(data) };
1063
+ }
1064
+ if (mimeType === "text/html" && data) {
1065
+ return { html: decodeBase64Url(data) };
1066
+ }
1067
+ if (part.parts?.length) {
1068
+ return part.parts.reduce((acc, nested) => {
1069
+ const value = visit(nested);
1070
+ return {
1071
+ text: value.text ?? acc.text,
1072
+ html: value.html ?? acc.html
1073
+ };
1074
+ }, {});
1075
+ }
1076
+ return {};
1077
+ };
1078
+ const { text, html } = visit(payload);
1079
+ return { text, html, attachments };
1080
+ }
1081
+ function decodeBase64Url(data) {
1082
+ const normalized = data.replace(/-/g, "+").replace(/_/g, "/");
1083
+ const padding = normalized.length % 4;
1084
+ const padded = padding === 0 ? normalized : normalized + "=".repeat(4 - padding);
1085
+ return Buffer.from(padded, "base64").toString("utf-8");
1086
+ }
1087
+
1088
+ // src/impls/gmail-outbound.ts
1089
+ import { google as google2 } from "googleapis";
1090
+
1091
+ class GmailOutboundProvider {
1092
+ gmail;
1093
+ userId;
1094
+ auth;
1095
+ constructor(options) {
1096
+ this.auth = options.auth;
1097
+ this.gmail = options.gmail ?? google2.gmail({
1098
+ version: "v1",
1099
+ auth: options.auth
1100
+ });
1101
+ this.userId = options.userId ?? "me";
1102
+ }
1103
+ async sendEmail(message) {
1104
+ const raw = encodeMessage(message);
1105
+ const response = await this.gmail.users.messages.send({
1106
+ userId: this.userId,
1107
+ requestBody: {
1108
+ raw
1109
+ },
1110
+ auth: this.auth
1111
+ });
1112
+ const id = response.data.id ?? "";
1113
+ return {
1114
+ id,
1115
+ providerMessageId: response.data.id ?? undefined,
1116
+ queuedAt: new Date
1117
+ };
1118
+ }
1119
+ }
1120
+ function encodeMessage(message) {
1121
+ const headers = [
1122
+ `From: ${formatAddress(message.from)}`,
1123
+ `To: ${message.to.map(formatAddress).join(", ")}`,
1124
+ `Subject: ${message.subject}`,
1125
+ "MIME-Version: 1.0"
1126
+ ];
1127
+ if (message.cc?.length) {
1128
+ headers.push(`Cc: ${message.cc.map(formatAddress).join(", ")}`);
1129
+ }
1130
+ if (message.replyTo) {
1131
+ headers.push(`Reply-To: ${formatAddress(message.replyTo)}`);
1132
+ }
1133
+ Object.entries(message.headers ?? {}).forEach(([key, value]) => {
1134
+ headers.push(`${key}: ${value}`);
1135
+ });
1136
+ const attachments = message.attachments ?? [];
1137
+ const hasHtml = Boolean(message.htmlBody);
1138
+ const hasText = Boolean(message.textBody);
1139
+ const boundaryMain = `mixed_${Date.now()}`;
1140
+ const boundaryAlt = `alt_${Date.now()}`;
1141
+ let body = "";
1142
+ if (attachments.length > 0) {
1143
+ headers.push(`Content-Type: multipart/mixed; boundary="${boundaryMain}"`);
1144
+ body += `\r
1145
+ --${boundaryMain}\r
1146
+ `;
1147
+ body += buildAlternativePart(hasText, hasHtml, boundaryAlt, message);
1148
+ attachments.forEach((attachment) => {
1149
+ body += buildAttachmentPart(boundaryMain, attachment);
1150
+ });
1151
+ body += `\r
1152
+ --${boundaryMain}--`;
1153
+ } else if (hasText && hasHtml) {
1154
+ headers.push(`Content-Type: multipart/alternative; boundary="${boundaryAlt}"`);
1155
+ body += `\r
1156
+ --${boundaryAlt}\r
1157
+ `;
1158
+ body += buildTextPart('text/plain; charset="utf-8"', message.textBody || "");
1159
+ body += `\r
1160
+ --${boundaryAlt}\r
1161
+ `;
1162
+ body += buildTextPart('text/html; charset="utf-8"', message.htmlBody || "");
1163
+ body += `\r
1164
+ --${boundaryAlt}--`;
1165
+ } else if (hasHtml) {
1166
+ headers.push('Content-Type: text/html; charset="utf-8"');
1167
+ body += `\r
1168
+ \r
1169
+ ${message.htmlBody}`;
1170
+ } else {
1171
+ headers.push('Content-Type: text/plain; charset="utf-8"');
1172
+ body += `\r
1173
+ \r
1174
+ ${message.textBody ?? ""}`;
1175
+ }
1176
+ const mime = `${headers.join(`\r
1177
+ `)}${body}`;
1178
+ return Buffer.from(mime).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1179
+ }
1180
+ function buildAlternativePart(hasText, hasHtml, boundary, message) {
1181
+ let content = "";
1182
+ content += `Content-Type: multipart/alternative; boundary="${boundary}"\r
1183
+ `;
1184
+ content += `\r
1185
+ `;
1186
+ if (hasText) {
1187
+ content += `--${boundary}\r
1188
+ `;
1189
+ content += buildTextPart('text/plain; charset="utf-8"', message.textBody || "");
1190
+ }
1191
+ if (hasHtml) {
1192
+ content += `\r
1193
+ --${boundary}\r
1194
+ `;
1195
+ content += buildTextPart('text/html; charset="utf-8"', message.htmlBody || "");
1196
+ }
1197
+ content += `\r
1198
+ --${boundary}--`;
1199
+ return content;
1200
+ }
1201
+ function buildTextPart(contentType, content) {
1202
+ return `Content-Type: ${contentType}\r
1203
+ ` + `Content-Transfer-Encoding: 7bit\r
1204
+ \r
1205
+ ` + content;
1206
+ }
1207
+ function buildAttachmentPart(boundary, attachment) {
1208
+ const data = attachment.data ?? new Uint8Array;
1209
+ const encoded = data.byteLength > 0 ? Buffer.from(data).toString("base64") : "";
1210
+ return `\r
1211
+ --${boundary}\r
1212
+ ` + `Content-Type: ${attachment.contentType}; name="${attachment.filename}"\r
1213
+ ` + `Content-Transfer-Encoding: base64\r
1214
+ ` + `Content-Disposition: attachment; filename="${attachment.filename}"\r
1215
+ \r
1216
+ ` + encoded;
1217
+ }
1218
+ function formatAddress(address) {
1219
+ if (address.name) {
1220
+ return `"${address.name}" <${address.email}>`;
1221
+ }
1222
+ return address.email;
1223
+ }
1224
+
1225
+ // src/impls/google-calendar.ts
1226
+ import { google as google3 } from "googleapis";
1227
+
1228
+ class GoogleCalendarProvider {
1229
+ calendar;
1230
+ defaultCalendarId;
1231
+ auth;
1232
+ constructor(options) {
1233
+ this.auth = options.auth;
1234
+ this.calendar = options.calendar ?? google3.calendar({
1235
+ version: "v3",
1236
+ auth: options.auth
1237
+ });
1238
+ this.defaultCalendarId = options.calendarId ?? "primary";
1239
+ }
1240
+ async listEvents(query) {
1241
+ const response = await this.calendar.events.list({
1242
+ calendarId: query.calendarId ?? this.defaultCalendarId,
1243
+ timeMin: query.timeMin?.toISOString(),
1244
+ timeMax: query.timeMax?.toISOString(),
1245
+ maxResults: query.maxResults,
1246
+ pageToken: query.pageToken,
1247
+ singleEvents: true,
1248
+ orderBy: "startTime",
1249
+ auth: this.auth
1250
+ });
1251
+ const events = response.data.items?.map((item) => this.fromGoogleEvent(query.calendarId ?? this.defaultCalendarId, item)) ?? [];
1252
+ return {
1253
+ events,
1254
+ nextPageToken: response.data.nextPageToken ?? undefined
1255
+ };
1256
+ }
1257
+ async createEvent(input) {
1258
+ const calendarId = input.calendarId ?? this.defaultCalendarId;
1259
+ const response = await this.calendar.events.insert({
1260
+ calendarId,
1261
+ requestBody: this.toGoogleEvent(input),
1262
+ conferenceDataVersion: input.conference?.create ? 1 : undefined,
1263
+ auth: this.auth
1264
+ });
1265
+ return this.fromGoogleEvent(calendarId, response.data);
1266
+ }
1267
+ async updateEvent(calendarId, eventId, input) {
1268
+ const response = await this.calendar.events.patch({
1269
+ calendarId: calendarId ?? this.defaultCalendarId,
1270
+ eventId,
1271
+ requestBody: this.toGoogleEvent(input),
1272
+ conferenceDataVersion: input.conference?.create ? 1 : undefined,
1273
+ auth: this.auth
1274
+ });
1275
+ return this.fromGoogleEvent(calendarId, response.data);
1276
+ }
1277
+ async deleteEvent(calendarId, eventId) {
1278
+ await this.calendar.events.delete({
1279
+ calendarId: calendarId ?? this.defaultCalendarId,
1280
+ eventId,
1281
+ auth: this.auth
1282
+ });
1283
+ }
1284
+ fromGoogleEvent(calendarId, event) {
1285
+ const start = parseDateTime(event.start);
1286
+ const end = parseDateTime(event.end);
1287
+ const attendees = event.attendees?.map((attendee) => ({
1288
+ email: attendee.email ?? "",
1289
+ name: attendee.displayName ?? undefined,
1290
+ optional: attendee.optional ?? undefined,
1291
+ responseStatus: normalizeResponseStatus(attendee.responseStatus)
1292
+ })) ?? [];
1293
+ const reminders = event.reminders?.overrides?.map((reminder) => ({
1294
+ method: reminder.method ?? "popup",
1295
+ minutesBeforeStart: reminder.minutes ?? 0
1296
+ })) ?? [];
1297
+ const metadata = buildMetadata(event);
1298
+ return {
1299
+ id: event.id ?? "",
1300
+ calendarId,
1301
+ title: event.summary ?? "",
1302
+ description: event.description ?? undefined,
1303
+ location: event.location ?? undefined,
1304
+ start,
1305
+ end,
1306
+ allDay: event.start?.date ? true : undefined,
1307
+ attendees,
1308
+ reminders,
1309
+ conferenceLink: event.hangoutLink ?? event.conferenceData?.entryPoints?.find((entry) => entry.uri)?.uri ?? undefined,
1310
+ metadata,
1311
+ createdAt: event.created ? new Date(event.created) : undefined,
1312
+ updatedAt: event.updated ? new Date(event.updated) : undefined
1313
+ };
1314
+ }
1315
+ toGoogleEvent(input) {
1316
+ const event = {};
1317
+ if ("title" in input && input.title)
1318
+ event.summary = input.title;
1319
+ if (input.description !== undefined)
1320
+ event.description = input.description;
1321
+ if (input.location !== undefined)
1322
+ event.location = input.location;
1323
+ if (input.start) {
1324
+ event.start = formatDateTime(input.start, input.allDay);
1325
+ }
1326
+ if (input.end) {
1327
+ event.end = formatDateTime(input.end, input.allDay);
1328
+ }
1329
+ if (input.attendees) {
1330
+ event.attendees = input.attendees.map((attendee) => ({
1331
+ email: attendee.email,
1332
+ displayName: attendee.name,
1333
+ optional: attendee.optional,
1334
+ responseStatus: attendee.responseStatus
1335
+ }));
1336
+ }
1337
+ if (input.reminders) {
1338
+ event.reminders = {
1339
+ useDefault: false,
1340
+ overrides: input.reminders.map((reminder) => ({
1341
+ method: reminder.method,
1342
+ minutes: reminder.minutesBeforeStart
1343
+ }))
1344
+ };
1345
+ }
1346
+ if (input.conference?.create) {
1347
+ event.conferenceData = {
1348
+ createRequest: {
1349
+ requestId: `conf-${Date.now()}`
1350
+ }
1351
+ };
1352
+ }
1353
+ if (input.metadata) {
1354
+ event.extendedProperties = {
1355
+ ...event.extendedProperties ?? {},
1356
+ private: {
1357
+ ...event.extendedProperties?.private ?? {},
1358
+ ...input.metadata
1359
+ }
1360
+ };
1361
+ }
1362
+ return event;
1363
+ }
1364
+ }
1365
+ function parseDateTime(time) {
1366
+ if (!time)
1367
+ return new Date;
1368
+ if (time.dateTime)
1369
+ return new Date(time.dateTime);
1370
+ if (time.date)
1371
+ return new Date(`${time.date}T00:00:00`);
1372
+ return new Date;
1373
+ }
1374
+ function formatDateTime(date, allDay) {
1375
+ if (allDay) {
1376
+ return { date: date.toISOString().slice(0, 10) };
1377
+ }
1378
+ return { dateTime: date.toISOString() };
1379
+ }
1380
+ function normalizeResponseStatus(status) {
1381
+ if (!status)
1382
+ return;
1383
+ const allowed = [
1384
+ "needsAction",
1385
+ "declined",
1386
+ "tentative",
1387
+ "accepted"
1388
+ ];
1389
+ return allowed.includes(status) ? status : undefined;
1390
+ }
1391
+ function buildMetadata(event) {
1392
+ const metadata = {};
1393
+ if (event.status)
1394
+ metadata.status = event.status;
1395
+ if (event.htmlLink)
1396
+ metadata.htmlLink = event.htmlLink;
1397
+ if (event.iCalUID)
1398
+ metadata.iCalUID = event.iCalUID;
1399
+ if (event.etag)
1400
+ metadata.etag = event.etag;
1401
+ if (event.conferenceData?.conferenceSolution?.name) {
1402
+ metadata.conferenceSolution = event.conferenceData.conferenceSolution.name;
1403
+ }
1404
+ if (event.extendedProperties?.private) {
1405
+ Object.entries(event.extendedProperties.private).forEach(([key, value]) => {
1406
+ if (typeof value === "string") {
1407
+ metadata[`extended.${key}`] = value;
1408
+ }
1409
+ });
1410
+ }
1411
+ return Object.keys(metadata).length > 0 ? metadata : undefined;
1412
+ }
1413
+
1414
+ // src/impls/gradium-voice.ts
1415
+ import { Gradium } from "@confiture-ai/gradium-sdk-js";
1416
+ var FORMAT_MAP2 = {
1417
+ mp3: "wav",
1418
+ wav: "wav",
1419
+ ogg: "opus",
1420
+ pcm: "pcm"
1421
+ };
1422
+
1423
+ class GradiumVoiceProvider {
1424
+ client;
1425
+ defaultVoiceId;
1426
+ defaultOutputFormat;
1427
+ constructor(options) {
1428
+ this.client = options.client ?? new Gradium({
1429
+ apiKey: options.apiKey,
1430
+ region: options.region,
1431
+ baseURL: options.baseUrl,
1432
+ timeout: options.timeoutMs
1433
+ });
1434
+ this.defaultVoiceId = options.defaultVoiceId;
1435
+ this.defaultOutputFormat = options.outputFormat;
1436
+ }
1437
+ async listVoices() {
1438
+ const voices = await this.client.voices.list({ include_catalog: true });
1439
+ return voices.map((voice) => this.fromGradiumVoice(voice));
1440
+ }
1441
+ async synthesize(input) {
1442
+ const voiceId = input.voiceId ?? this.defaultVoiceId;
1443
+ if (!voiceId) {
1444
+ throw new Error("Voice ID is required for Gradium synthesis.");
1445
+ }
1446
+ const outputFormat = (input.format ? FORMAT_MAP2[input.format] : undefined) ?? this.defaultOutputFormat ?? "wav";
1447
+ const response = await this.client.tts.create({
1448
+ voice_id: voiceId,
1449
+ output_format: outputFormat,
1450
+ text: input.text
1451
+ });
1452
+ return {
1453
+ audio: response.raw_data,
1454
+ format: input.format ?? toContractFormat(outputFormat),
1455
+ sampleRateHz: input.sampleRateHz ?? response.sample_rate ?? inferSampleRate(outputFormat),
1456
+ durationSeconds: undefined,
1457
+ url: undefined
1458
+ };
1459
+ }
1460
+ fromGradiumVoice(voice) {
1461
+ return {
1462
+ id: voice.uid,
1463
+ name: voice.name,
1464
+ description: voice.description ?? undefined,
1465
+ language: voice.language ?? undefined,
1466
+ metadata: {
1467
+ startSeconds: String(voice.start_s),
1468
+ ...voice.stop_s != null ? { stopSeconds: String(voice.stop_s) } : {},
1469
+ filename: voice.filename
1470
+ }
1471
+ };
1472
+ }
1473
+ }
1474
+ function toContractFormat(format) {
1475
+ switch (format) {
1476
+ case "opus":
1477
+ return "ogg";
1478
+ case "wav":
1479
+ return "wav";
1480
+ case "pcm":
1481
+ case "pcm_16000":
1482
+ case "pcm_24000":
1483
+ return "pcm";
1484
+ default:
1485
+ return format;
1486
+ }
1487
+ }
1488
+ function inferSampleRate(format) {
1489
+ switch (format) {
1490
+ case "ulaw_8000":
1491
+ case "alaw_8000":
1492
+ return 8000;
1493
+ case "pcm_16000":
1494
+ return 16000;
1495
+ case "pcm_24000":
1496
+ return 24000;
1497
+ default:
1498
+ return 48000;
1499
+ }
1500
+ }
1501
+
1502
+ // src/impls/granola-meeting-recorder.mcp.ts
1503
+ var UNKNOWN_EMAIL = "unknown@granola.local";
1504
+ var EPOCH = "1970-01-01T00:00:00.000Z";
1505
+
1506
+ class GranolaMcpClient {
1507
+ requestId = 0;
1508
+ mcpUrl;
1509
+ mcpAccessToken;
1510
+ mcpHeaders;
1511
+ fetchFn;
1512
+ constructor(options) {
1513
+ this.mcpUrl = options.mcpUrl;
1514
+ this.mcpAccessToken = options.mcpAccessToken;
1515
+ this.mcpHeaders = options.mcpHeaders;
1516
+ this.fetchFn = options.fetchFn ?? fetch;
1517
+ }
1518
+ async callTool(name, args) {
1519
+ const headers = {
1520
+ "Content-Type": "application/json",
1521
+ ...this.mcpHeaders ?? {}
1522
+ };
1523
+ if (this.mcpAccessToken) {
1524
+ headers.Authorization = `Bearer ${this.mcpAccessToken}`;
1525
+ }
1526
+ const response = await this.fetchFn(this.mcpUrl, {
1527
+ method: "POST",
1528
+ headers,
1529
+ body: JSON.stringify({
1530
+ jsonrpc: "2.0",
1531
+ id: ++this.requestId,
1532
+ method: "tools/call",
1533
+ params: {
1534
+ name,
1535
+ arguments: args
1536
+ }
1537
+ })
1538
+ });
1539
+ if (!response.ok) {
1540
+ const message = await safeReadText(response);
1541
+ throw new Error(`Granola MCP error (${response.status}): ${message}`);
1542
+ }
1543
+ const rpc = await response.json();
1544
+ if (rpc.error) {
1545
+ throw new Error(rpc.error.message ?? "Granola MCP returned an error.");
1546
+ }
1547
+ return extractRpcResult(rpc);
1548
+ }
1549
+ }
1550
+ function normalizeMcpListResult(payload) {
1551
+ const root = asObject(payload);
1552
+ const list = asArray(payload) ?? asArray(root?.notes) ?? asArray(root?.meetings) ?? asArray(root?.items) ?? asArray(root?.results) ?? asArray(root?.data) ?? [];
1553
+ const notes = list.map((item) => mapSummaryItem(item)).filter((item) => Boolean(item));
1554
+ return {
1555
+ notes,
1556
+ nextCursor: readString(root, ["nextCursor", "next_cursor", "cursor"]),
1557
+ hasMore: readBoolean(root, ["hasMore", "has_more"])
1558
+ };
1559
+ }
1560
+ function normalizeMcpMeeting(payload, targetMeetingId) {
1561
+ const root = asObject(payload);
1562
+ const candidates = asArray(payload) ?? asArray(root?.meetings) ?? asArray(root?.notes) ?? asArray(root?.items) ?? asArray(root?.results) ?? asArray(root?.data);
1563
+ if (candidates?.length) {
1564
+ const selected = candidates.find((item) => readId(item) === targetMeetingId) ?? candidates.find((item) => String(readId(item) ?? "").includes(targetMeetingId)) ?? candidates[0];
1565
+ return selected ? mapMeetingItem(selected) : undefined;
1566
+ }
1567
+ const direct = mapMeetingItem(payload);
1568
+ if (direct && direct.id === targetMeetingId) {
1569
+ return direct;
1570
+ }
1571
+ return direct;
1572
+ }
1573
+ function normalizeMcpTranscript(payload) {
1574
+ const root = asObject(payload);
1575
+ const list = asArray(payload) ?? asArray(root?.transcript) ?? asArray(root?.segments) ?? asArray(root?.items) ?? asArray(root?.data) ?? [];
1576
+ if (list.length === 0 && typeof payload === "string") {
1577
+ return [
1578
+ {
1579
+ text: payload,
1580
+ start_time: "00:00:00",
1581
+ end_time: "00:00:00"
1582
+ }
1583
+ ];
1584
+ }
1585
+ return list.map((item) => mapTranscriptSegment2(item)).filter((item) => Boolean(item));
1586
+ }
1587
+ function extractRpcResult(rpc) {
1588
+ const result = rpc.result;
1589
+ if (!result)
1590
+ return null;
1591
+ if (result.structuredContent !== undefined)
1592
+ return result.structuredContent;
1593
+ if (result.data !== undefined)
1594
+ return result.data;
1595
+ const textPayload = result.content?.find((entry) => entry?.type === "text")?.text;
1596
+ if (!textPayload)
1597
+ return result;
1598
+ try {
1599
+ return JSON.parse(textPayload);
1600
+ } catch {
1601
+ return textPayload;
1602
+ }
1603
+ }
1604
+ function mapSummaryItem(value) {
1605
+ const object = asObject(value);
1606
+ if (!object)
1607
+ return;
1608
+ const id = readId(object);
1609
+ if (!id)
1610
+ return;
1611
+ const owner = mapOwner(object);
1612
+ return {
1613
+ id,
1614
+ title: readString(object, ["title", "name", "meeting_title"]) ?? null,
1615
+ owner,
1616
+ created_at: readString(object, ["created_at", "createdAt", "date", "meeting_date"]) ?? EPOCH
1617
+ };
1618
+ }
1619
+ function mapMeetingItem(value) {
1620
+ const summary = mapSummaryItem(value);
1621
+ if (!summary)
1622
+ return;
1623
+ const object = asObject(value) ?? {};
1624
+ const attendees = asArray(object.attendees) ?? asArray(object.participants) ?? asArray(object.invitees) ?? [];
1625
+ const mappedAttendees = attendees.map((entry) => mapUser(entry)).filter((entry) => Boolean(entry));
1626
+ const folders = asArray(object.folder_membership) ?? asArray(object.folders) ?? [];
1627
+ const folderMembership = folders.map((entry, index) => mapFolder(entry, index)).filter((entry) => Boolean(entry));
1628
+ const calendarEvent = asObject(object.calendar_event) ? {
1629
+ event_title: readString(asObject(object.calendar_event), [
1630
+ "event_title",
1631
+ "title"
1632
+ ]) ?? null,
1633
+ invitees: mappedAttendees.map((attendee) => ({
1634
+ email: attendee.email
1635
+ })),
1636
+ organiser: readString(asObject(object.calendar_event), [
1637
+ "organiser",
1638
+ "organizer"
1639
+ ]) ?? summary.owner.email,
1640
+ calendar_event_id: readString(asObject(object.calendar_event), [
1641
+ "calendar_event_id",
1642
+ "id"
1643
+ ]) ?? null,
1644
+ scheduled_start_time: readString(asObject(object.calendar_event), [
1645
+ "scheduled_start_time",
1646
+ "start_time",
1647
+ "start"
1648
+ ]) ?? null,
1649
+ scheduled_end_time: readString(asObject(object.calendar_event), [
1650
+ "scheduled_end_time",
1651
+ "end_time",
1652
+ "end"
1653
+ ]) ?? null
1654
+ } : null;
1655
+ const transcript = normalizeMcpTranscript(object.transcript ?? object.segments);
1656
+ return {
1657
+ ...summary,
1658
+ calendar_event: calendarEvent,
1659
+ attendees: mappedAttendees,
1660
+ folder_membership: folderMembership,
1661
+ summary_text: readString(object, ["summary_text", "summary", "enhanced_notes"]) ?? "",
1662
+ transcript: transcript.length ? transcript : null
1663
+ };
1664
+ }
1665
+ function mapTranscriptSegment2(value) {
1666
+ const object = asObject(value);
1667
+ if (!object) {
1668
+ if (typeof value !== "string")
1669
+ return;
1670
+ return {
1671
+ text: value,
1672
+ start_time: "00:00:00",
1673
+ end_time: "00:00:00"
1674
+ };
1675
+ }
1676
+ const text = readString(object, ["text", "content", "utterance"]);
1677
+ if (!text)
1678
+ return;
1679
+ const speakerSource = readString(asObject(object.speaker), ["source", "name"]) ?? readString(object, ["speaker", "speaker_name"]);
1680
+ return {
1681
+ speaker: speakerSource ? { source: speakerSource } : undefined,
1682
+ text,
1683
+ start_time: readString(object, ["start_time", "startTime", "timestamp", "time"]) ?? "00:00:00",
1684
+ end_time: readString(object, ["end_time", "endTime"]) ?? "00:00:00"
1685
+ };
1686
+ }
1687
+ function mapOwner(object) {
1688
+ const ownerObject = asObject(object.owner) ?? asObject(object.organizer) ?? asObject(object.organiser);
1689
+ const attendee = asArray(object.attendees)?.[0] ?? asArray(object.participants)?.[0] ?? asArray(object.invitees)?.[0];
1690
+ const ownerCandidate = ownerObject ?? asObject(attendee) ?? {};
1691
+ return {
1692
+ name: readString(ownerCandidate, ["name", "displayName"]) ?? null,
1693
+ email: readString(ownerCandidate, ["email"]) ?? UNKNOWN_EMAIL
1694
+ };
1695
+ }
1696
+ function mapUser(value) {
1697
+ if (typeof value === "string") {
1698
+ return {
1699
+ name: null,
1700
+ email: value
1701
+ };
1702
+ }
1703
+ const object = asObject(value);
1704
+ if (!object)
1705
+ return;
1706
+ const email = readString(object, ["email"]);
1707
+ if (!email)
1708
+ return;
1709
+ return {
1710
+ name: readString(object, ["name", "displayName"]) ?? null,
1711
+ email
1712
+ };
1713
+ }
1714
+ function mapFolder(value, index) {
1715
+ const object = asObject(value);
1716
+ if (!object)
1717
+ return;
1718
+ const id = readString(object, ["id"]) ?? `folder-${index}`;
1719
+ const name = readString(object, ["name"]) ?? "Folder";
1720
+ return {
1721
+ id,
1722
+ object: "folder",
1723
+ name
1724
+ };
1725
+ }
1726
+ function readId(value) {
1727
+ const object = asObject(value);
1728
+ if (!object)
1729
+ return;
1730
+ return readString(object, [
1731
+ "id",
1732
+ "meeting_id",
1733
+ "meetingId",
1734
+ "note_id",
1735
+ "noteId"
1736
+ ]);
1737
+ }
1738
+ function readString(object, keys) {
1739
+ if (!object)
1740
+ return;
1741
+ for (const key of keys) {
1742
+ const value = object[key];
1743
+ if (typeof value === "string" && value.trim().length > 0) {
1744
+ return value;
1745
+ }
1746
+ }
1747
+ return;
1748
+ }
1749
+ function readBoolean(object, keys) {
1750
+ if (!object)
1751
+ return;
1752
+ for (const key of keys) {
1753
+ const value = object[key];
1754
+ if (typeof value === "boolean")
1755
+ return value;
1756
+ }
1757
+ return;
1758
+ }
1759
+ function asObject(value) {
1760
+ if (!value || typeof value !== "object" || Array.isArray(value))
1761
+ return;
1762
+ return value;
1763
+ }
1764
+ function asArray(value) {
1765
+ return Array.isArray(value) ? value : undefined;
1766
+ }
1767
+ async function safeReadText(response) {
1768
+ try {
1769
+ return await response.text();
1770
+ } catch {
1771
+ return response.statusText;
1772
+ }
1773
+ }
1774
+
1775
+ // src/impls/granola-meeting-recorder.ts
1776
+ var DEFAULT_BASE_URL3 = "https://public-api.granola.ai";
1777
+ var DEFAULT_MCP_URL = "https://mcp.granola.ai/mcp";
1778
+ var MAX_PAGE_SIZE = 30;
1779
+
1780
+ class GranolaMeetingRecorderProvider {
1781
+ apiKey;
1782
+ baseUrl;
1783
+ defaultPageSize;
1784
+ transport;
1785
+ mcpClient;
1786
+ constructor(options) {
1787
+ this.apiKey = options.apiKey;
1788
+ this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL3;
1789
+ this.defaultPageSize = options.pageSize;
1790
+ this.transport = options.transport ?? "api";
1791
+ this.mcpClient = new GranolaMcpClient({
1792
+ mcpUrl: options.mcpUrl ?? DEFAULT_MCP_URL,
1793
+ mcpAccessToken: options.mcpAccessToken,
1794
+ mcpHeaders: options.mcpHeaders,
1795
+ fetchFn: options.fetchFn
1796
+ });
1797
+ }
1798
+ async listMeetings(params) {
1799
+ if (this.transport === "mcp") {
1800
+ return this.listMeetingsViaMcp(params);
1801
+ }
1802
+ const query = new URLSearchParams;
1803
+ if (params.from)
1804
+ query.set("created_after", params.from);
1805
+ if (params.to)
1806
+ query.set("created_before", params.to);
1807
+ if (params.cursor)
1808
+ query.set("cursor", params.cursor);
1809
+ const pageSize = params.pageSize ?? this.defaultPageSize;
1810
+ if (pageSize) {
1811
+ query.set("page_size", String(Math.min(pageSize, MAX_PAGE_SIZE)));
1812
+ }
1813
+ const data = await this.request(`/v1/notes?${query.toString()}`);
1814
+ return {
1815
+ meetings: data.notes.map((note) => this.mapNoteSummary(note, params)),
1816
+ nextCursor: data.cursor ?? undefined,
1817
+ hasMore: data.hasMore
1818
+ };
1819
+ }
1820
+ async getMeeting(params) {
1821
+ if (this.transport === "mcp") {
1822
+ return this.getMeetingViaMcp(params);
1823
+ }
1824
+ const includeTranscript = params.includeTranscript ?? false;
1825
+ const note = await this.getNote(params.meetingId, includeTranscript);
1826
+ return this.mapNoteDetail(note, params);
1827
+ }
1828
+ async getTranscript(params) {
1829
+ if (this.transport === "mcp") {
1830
+ return this.getTranscriptViaMcp(params);
1831
+ }
1832
+ const note = await this.getNote(params.meetingId, true);
1833
+ const segments = this.mapTranscriptSegments(note.transcript);
1834
+ return {
1835
+ id: note.id,
1836
+ meetingId: note.id,
1837
+ tenantId: params.tenantId,
1838
+ connectionId: params.connectionId ?? "unknown",
1839
+ externalId: note.id,
1840
+ format: "segments",
1841
+ text: segments.map((segment) => segment.text).join(`
1842
+ `),
1843
+ segments,
1844
+ generatedAt: note.created_at,
1845
+ metadata: {
1846
+ summaryText: note.summary_text
1847
+ },
1848
+ raw: note.transcript ?? undefined
1849
+ };
1850
+ }
1851
+ async listMeetingsViaMcp(params) {
1852
+ const payload = await this.mcpClient.callTool("list_meetings", {
1853
+ cursor: params.cursor,
1854
+ limit: params.pageSize ?? this.defaultPageSize,
1855
+ query: params.query,
1856
+ from: params.from,
1857
+ to: params.to,
1858
+ organizerEmail: params.organizerEmail,
1859
+ participantEmail: params.participantEmail
1860
+ });
1861
+ const normalized = normalizeMcpListResult(payload);
1862
+ return {
1863
+ meetings: normalized.notes.map((note) => this.mapNoteSummary(note, params)),
1864
+ nextCursor: normalized.nextCursor,
1865
+ hasMore: normalized.hasMore ?? Boolean(normalized.nextCursor && normalized.notes.length > 0)
1866
+ };
1867
+ }
1868
+ async getMeetingViaMcp(params) {
1869
+ const primaryPayload = await this.mcpClient.callTool("list_meetings", {
1870
+ query: params.meetingId,
1871
+ limit: 50
1872
+ });
1873
+ let note = normalizeMcpMeeting(primaryPayload, params.meetingId);
1874
+ if (!note) {
1875
+ const fallbackPayload = await this.mcpClient.callTool("get_meetings", {
1876
+ query: params.meetingId
1877
+ });
1878
+ note = normalizeMcpMeeting(fallbackPayload, params.meetingId);
1879
+ }
1880
+ if (!note) {
1881
+ throw new Error(`Granola meeting "${params.meetingId}" not found via MCP.`);
1882
+ }
1883
+ return this.mapNoteDetail(note, params);
1884
+ }
1885
+ async getTranscriptViaMcp(params) {
1886
+ const payload = await this.mcpClient.callTool("get_meeting_transcript", {
1887
+ meeting_id: params.meetingId,
1888
+ meetingId: params.meetingId,
1889
+ id: params.meetingId
1890
+ });
1891
+ const transcript = normalizeMcpTranscript(payload);
1892
+ const segments = this.mapTranscriptSegments(transcript);
1893
+ return {
1894
+ id: params.meetingId,
1895
+ meetingId: params.meetingId,
1896
+ tenantId: params.tenantId,
1897
+ connectionId: params.connectionId ?? "unknown",
1898
+ externalId: params.meetingId,
1899
+ format: "segments",
1900
+ text: segments.map((segment) => segment.text).join(`
1901
+ `),
1902
+ segments,
1903
+ metadata: {
1904
+ provider: "granola",
1905
+ transport: "mcp"
1906
+ },
1907
+ raw: payload
1908
+ };
1909
+ }
1910
+ async getNote(noteId, includeTranscript) {
1911
+ const query = includeTranscript ? "?include=transcript" : "";
1912
+ return this.request(`/v1/notes/${noteId}${query}`);
1913
+ }
1914
+ mapNoteSummary(note, params) {
1915
+ const connectionId = params.connectionId ?? "unknown";
1916
+ return {
1917
+ id: note.id,
1918
+ tenantId: params.tenantId,
1919
+ connectionId,
1920
+ externalId: note.id,
1921
+ title: note.title ?? undefined,
1922
+ organizer: this.mapUser(note.owner),
1923
+ scheduledStartAt: note.created_at,
1924
+ recordingStartAt: note.created_at,
1925
+ transcriptAvailable: false,
1926
+ createdAt: note.created_at,
1927
+ updatedAt: note.created_at,
1928
+ sourcePlatform: "granola"
1929
+ };
1930
+ }
1931
+ mapNoteDetail(note, params) {
1932
+ const connectionId = params.connectionId ?? "unknown";
1933
+ const calendarEvent = note.calendar_event ?? undefined;
1934
+ const invitees = calendarEvent?.invitees ? calendarEvent.invitees.map((invitee) => this.mapInvitee(invitee)) : note.attendees?.map((attendee) => this.mapUser(attendee)).filter(Boolean);
1935
+ const participants = note.attendees?.map((attendee) => this.mapUser(attendee)).filter(Boolean);
1936
+ return {
1937
+ id: note.id,
1938
+ tenantId: params.tenantId,
1939
+ connectionId,
1940
+ externalId: note.id,
1941
+ title: note.title ?? calendarEvent?.event_title ?? undefined,
1942
+ summary: note.summary_text ?? undefined,
1943
+ organizer: this.mapUser(note.owner),
1944
+ invitees: invitees?.length ? invitees : undefined,
1945
+ participants: participants?.length ? participants : undefined,
1946
+ scheduledStartAt: calendarEvent?.scheduled_start_time ?? undefined,
1947
+ scheduledEndAt: calendarEvent?.scheduled_end_time ?? undefined,
1948
+ recordingStartAt: calendarEvent?.scheduled_start_time ?? note.created_at,
1949
+ recordingEndAt: calendarEvent?.scheduled_end_time ?? undefined,
1950
+ transcriptAvailable: Array.isArray(note.transcript),
1951
+ createdAt: note.created_at,
1952
+ updatedAt: note.created_at,
1953
+ sourcePlatform: "granola",
1954
+ metadata: {
1955
+ calendarEvent,
1956
+ folderMembership: note.folder_membership
1957
+ }
1958
+ };
1959
+ }
1960
+ mapTranscriptSegments(transcript) {
1961
+ if (!transcript)
1962
+ return [];
1963
+ return transcript.map((segment, index) => ({
1964
+ index,
1965
+ speakerName: segment.speaker?.source ?? undefined,
1966
+ text: segment.text,
1967
+ startTime: segment.start_time,
1968
+ endTime: segment.end_time
1969
+ }));
1970
+ }
1971
+ mapUser(user) {
1972
+ if (!user)
1973
+ return;
1974
+ return {
1975
+ name: user.name ?? undefined,
1976
+ email: user.email ?? undefined,
1977
+ role: "organizer"
1978
+ };
1979
+ }
1980
+ mapInvitee(invitee) {
1981
+ return {
1982
+ email: invitee.email,
1983
+ role: "attendee"
1984
+ };
1985
+ }
1986
+ async request(path) {
1987
+ if (!this.apiKey) {
1988
+ throw new Error('Granola apiKey is required when transport is "api".');
1989
+ }
1990
+ const response = await fetch(`${this.baseUrl}${path}`, {
1991
+ headers: {
1992
+ Authorization: `Bearer ${this.apiKey}`,
1993
+ "Content-Type": "application/json"
1994
+ }
1995
+ });
1996
+ if (!response.ok) {
1997
+ const message = await safeReadError3(response);
1998
+ throw new Error(`Granola API error (${response.status}): ${message}`);
1999
+ }
2000
+ return await response.json();
2001
+ }
2002
+ }
2003
+ async function safeReadError3(response) {
2004
+ try {
2005
+ const data = await response.json();
2006
+ return data?.message ?? response.statusText;
2007
+ } catch {
2008
+ return response.statusText;
2009
+ }
2010
+ }
2011
+
2012
+ // src/impls/mistral-llm.ts
2013
+ import { Mistral } from "@mistralai/mistralai";
2014
+
2015
+ class MistralLLMProvider {
2016
+ client;
2017
+ defaultModel;
2018
+ constructor(options) {
2019
+ if (!options.apiKey) {
2020
+ throw new Error("MistralLLMProvider requires an apiKey");
2021
+ }
2022
+ this.client = options.client ?? new Mistral({
2023
+ apiKey: options.apiKey,
2024
+ serverURL: options.serverURL,
2025
+ userAgent: options.userAgentSuffix ? `${options.userAgentSuffix}` : undefined
2026
+ });
2027
+ this.defaultModel = options.defaultModel ?? "mistral-large-latest";
2028
+ }
2029
+ async chat(messages, options = {}) {
2030
+ const request = this.buildChatRequest(messages, options);
2031
+ const response = await this.client.chat.complete(request);
2032
+ return this.buildLLMResponse(response);
2033
+ }
2034
+ async* stream(messages, options = {}) {
2035
+ const request = this.buildChatRequest(messages, options);
2036
+ request.stream = true;
2037
+ const stream = await this.client.chat.stream(request);
2038
+ const aggregatedParts = [];
2039
+ const aggregatedToolCalls = [];
2040
+ let usage;
2041
+ let finishReason;
2042
+ for await (const event of stream) {
2043
+ for (const choice of event.data.choices) {
2044
+ const delta = choice.delta;
2045
+ if (typeof delta.content === "string") {
2046
+ if (delta.content.length > 0) {
2047
+ aggregatedParts.push({ type: "text", text: delta.content });
2048
+ yield {
2049
+ type: "message_delta",
2050
+ delta: { type: "text", text: delta.content },
2051
+ index: choice.index
2052
+ };
2053
+ }
2054
+ } else if (Array.isArray(delta.content)) {
2055
+ for (const chunk of delta.content) {
2056
+ if (chunk.type === "text" && "text" in chunk) {
2057
+ aggregatedParts.push({ type: "text", text: chunk.text });
2058
+ yield {
2059
+ type: "message_delta",
2060
+ delta: { type: "text", text: chunk.text },
2061
+ index: choice.index
2062
+ };
2063
+ }
2064
+ }
2065
+ }
2066
+ if (delta.toolCalls) {
2067
+ let localIndex = 0;
2068
+ for (const call of delta.toolCalls) {
2069
+ const toolCall = this.fromMistralToolCall(call, localIndex);
2070
+ aggregatedToolCalls.push(toolCall);
2071
+ yield {
2072
+ type: "tool_call",
2073
+ call: toolCall,
2074
+ index: choice.index
2075
+ };
2076
+ localIndex += 1;
2077
+ }
2078
+ }
2079
+ if (choice.finishReason && choice.finishReason !== "null") {
2080
+ finishReason = choice.finishReason;
2081
+ }
2082
+ }
2083
+ if (event.data.usage) {
2084
+ const usageEntry = this.fromUsage(event.data.usage);
2085
+ if (usageEntry) {
2086
+ usage = usageEntry;
2087
+ yield { type: "usage", usage: usageEntry };
2088
+ }
2089
+ }
2090
+ }
2091
+ const message = {
2092
+ role: "assistant",
2093
+ content: aggregatedParts.length ? aggregatedParts : [{ type: "text", text: "" }]
2094
+ };
2095
+ if (aggregatedToolCalls.length > 0) {
2096
+ message.content = [
2097
+ ...aggregatedToolCalls,
2098
+ ...aggregatedParts.length ? aggregatedParts : []
2099
+ ];
2100
+ }
2101
+ yield {
2102
+ type: "end",
2103
+ response: {
2104
+ message,
2105
+ usage,
2106
+ finishReason: mapFinishReason(finishReason)
2107
+ }
2108
+ };
2109
+ }
2110
+ async countTokens(_messages) {
2111
+ throw new Error("Mistral API does not currently support token counting");
2112
+ }
2113
+ buildChatRequest(messages, options) {
2114
+ const model = options.model ?? this.defaultModel;
2115
+ const mappedMessages = messages.map((message) => this.toMistralMessage(message));
2116
+ const request = {
2117
+ model,
2118
+ messages: mappedMessages
2119
+ };
2120
+ if (options.temperature != null) {
2121
+ request.temperature = options.temperature;
2122
+ }
2123
+ if (options.topP != null) {
2124
+ request.topP = options.topP;
2125
+ }
2126
+ if (options.maxOutputTokens != null) {
2127
+ request.maxTokens = options.maxOutputTokens;
2128
+ }
2129
+ if (options.stopSequences?.length) {
2130
+ request.stop = options.stopSequences.length === 1 ? options.stopSequences[0] : options.stopSequences;
2131
+ }
2132
+ if (options.tools?.length) {
2133
+ request.tools = options.tools.map((tool) => ({
2134
+ type: "function",
2135
+ function: {
2136
+ name: tool.name,
2137
+ description: tool.description,
2138
+ parameters: typeof tool.inputSchema === "object" && tool.inputSchema !== null ? tool.inputSchema : {}
2139
+ }
2140
+ }));
2141
+ }
2142
+ if (options.responseFormat === "json") {
2143
+ request.responseFormat = { type: "json_object" };
2144
+ }
2145
+ return request;
2146
+ }
2147
+ buildLLMResponse(response) {
2148
+ const firstChoice = response.choices[0];
2149
+ if (!firstChoice) {
2150
+ return {
2151
+ message: {
2152
+ role: "assistant",
2153
+ content: [{ type: "text", text: "" }]
2154
+ },
2155
+ usage: this.fromUsage(response.usage),
2156
+ raw: response
2157
+ };
2158
+ }
2159
+ const message = this.fromAssistantMessage(firstChoice.message);
2160
+ return {
2161
+ message,
2162
+ usage: this.fromUsage(response.usage),
2163
+ finishReason: mapFinishReason(firstChoice.finishReason),
2164
+ raw: response
2165
+ };
2166
+ }
2167
+ fromUsage(usage) {
2168
+ if (!usage)
2169
+ return;
2170
+ return {
2171
+ promptTokens: usage.promptTokens ?? 0,
2172
+ completionTokens: usage.completionTokens ?? 0,
2173
+ totalTokens: usage.totalTokens ?? 0
2174
+ };
2175
+ }
2176
+ fromAssistantMessage(message) {
2177
+ const parts = [];
2178
+ if (typeof message.content === "string") {
2179
+ parts.push({ type: "text", text: message.content });
2180
+ } else if (Array.isArray(message.content)) {
2181
+ message.content.forEach((chunk) => {
2182
+ if (chunk.type === "text" && "text" in chunk) {
2183
+ parts.push({ type: "text", text: chunk.text });
2184
+ }
2185
+ });
2186
+ }
2187
+ const toolCalls = message.toolCalls?.map((call, index) => this.fromMistralToolCall(call, index)) ?? [];
2188
+ if (toolCalls.length > 0) {
2189
+ parts.splice(0, 0, ...toolCalls);
2190
+ }
2191
+ if (parts.length === 0) {
2192
+ parts.push({ type: "text", text: "" });
2193
+ }
2194
+ return {
2195
+ role: "assistant",
2196
+ content: parts
2197
+ };
2198
+ }
2199
+ fromMistralToolCall(call, index) {
2200
+ const args = typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments);
2201
+ return {
2202
+ type: "tool-call",
2203
+ id: call.id ?? `tool-call-${index}`,
2204
+ name: call.function.name,
2205
+ arguments: args
2206
+ };
2207
+ }
2208
+ toMistralMessage(message) {
2209
+ const textContent = this.extractText(message.content);
2210
+ const toolCalls = this.extractToolCalls(message);
2211
+ switch (message.role) {
2212
+ case "system":
2213
+ return {
2214
+ role: "system",
2215
+ content: textContent ?? ""
2216
+ };
2217
+ case "user":
2218
+ return {
2219
+ role: "user",
2220
+ content: textContent ?? ""
2221
+ };
2222
+ case "assistant":
2223
+ return {
2224
+ role: "assistant",
2225
+ content: toolCalls.length > 0 ? null : textContent ?? "",
2226
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined
2227
+ };
2228
+ case "tool":
2229
+ return {
2230
+ role: "tool",
2231
+ content: textContent ?? "",
2232
+ toolCallId: message.toolCallId ?? toolCalls[0]?.id
2233
+ };
2234
+ default:
2235
+ return {
2236
+ role: "user",
2237
+ content: textContent ?? ""
2238
+ };
2239
+ }
2240
+ }
2241
+ extractText(parts) {
2242
+ const textParts = parts.filter((part) => part.type === "text").map((part) => part.text);
2243
+ if (textParts.length === 0)
2244
+ return null;
2245
+ return textParts.join("");
2246
+ }
2247
+ extractToolCalls(message) {
2248
+ const toolCallParts = message.content.filter((part) => part.type === "tool-call");
2249
+ return toolCallParts.map((call, index) => ({
2250
+ id: call.id ?? `call_${index}`,
2251
+ type: "function",
2252
+ index,
2253
+ function: {
2254
+ name: call.name,
2255
+ arguments: call.arguments
2256
+ }
2257
+ }));
2258
+ }
2259
+ }
2260
+ function mapFinishReason(reason) {
2261
+ if (!reason)
2262
+ return;
2263
+ const normalized = reason.toLowerCase();
2264
+ switch (normalized) {
2265
+ case "stop":
2266
+ return "stop";
2267
+ case "length":
2268
+ return "length";
2269
+ case "tool_call":
2270
+ case "tool_calls":
2271
+ return "tool_call";
2272
+ case "content_filter":
2273
+ return "content_filter";
2274
+ default:
2275
+ return;
2276
+ }
2277
+ }
2278
+
2279
+ // src/impls/mistral-embedding.ts
2280
+ import { Mistral as Mistral2 } from "@mistralai/mistralai";
2281
+
2282
+ class MistralEmbeddingProvider {
2283
+ client;
2284
+ defaultModel;
2285
+ constructor(options) {
2286
+ if (!options.apiKey) {
2287
+ throw new Error("MistralEmbeddingProvider requires an apiKey");
2288
+ }
2289
+ this.client = options.client ?? new Mistral2({
2290
+ apiKey: options.apiKey,
2291
+ serverURL: options.serverURL
2292
+ });
2293
+ this.defaultModel = options.defaultModel ?? "mistral-embed";
2294
+ }
2295
+ async embedDocuments(documents, options) {
2296
+ if (documents.length === 0)
2297
+ return [];
2298
+ const model = options?.model ?? this.defaultModel;
2299
+ const response = await this.client.embeddings.create({
2300
+ model,
2301
+ inputs: documents.map((doc) => doc.text)
2302
+ });
2303
+ return response.data.map((item, index) => ({
2304
+ id: documents[index]?.id ?? (item.index != null ? `embedding-${item.index}` : `embedding-${index}`),
2305
+ vector: item.embedding ?? [],
2306
+ dimensions: item.embedding?.length ?? 0,
2307
+ model: response.model,
2308
+ metadata: documents[index]?.metadata ? Object.fromEntries(Object.entries(documents[index]?.metadata ?? {}).map(([key, value]) => [key, String(value)])) : undefined
2309
+ }));
2310
+ }
2311
+ async embedQuery(query, options) {
2312
+ const [result] = await this.embedDocuments([{ id: "query", text: query }], options);
2313
+ if (!result) {
2314
+ throw new Error("Failed to compute embedding for query");
2315
+ }
2316
+ return result;
2317
+ }
2318
+ }
2319
+
2320
+ // src/impls/qdrant-vector.ts
2321
+ import { QdrantClient } from "@qdrant/js-client-rest";
2322
+
2323
+ class QdrantVectorProvider {
2324
+ client;
2325
+ createCollectionIfMissing;
2326
+ distance;
2327
+ constructor(options) {
2328
+ this.client = options.client ?? new QdrantClient({
2329
+ url: options.url,
2330
+ apiKey: options.apiKey,
2331
+ ...options.clientParams
2332
+ });
2333
+ this.createCollectionIfMissing = options.createCollectionIfMissing ?? true;
2334
+ this.distance = options.distance ?? "Cosine";
2335
+ }
2336
+ async upsert(request) {
2337
+ if (request.documents.length === 0)
2338
+ return;
2339
+ const firstDocument = request.documents[0];
2340
+ if (!firstDocument)
2341
+ return;
2342
+ const vectorSize = firstDocument.vector.length;
2343
+ if (this.createCollectionIfMissing) {
2344
+ await this.ensureCollection(request.collection, vectorSize);
2345
+ }
2346
+ const points = request.documents.map((document) => ({
2347
+ id: document.id,
2348
+ vector: document.vector,
2349
+ payload: {
2350
+ ...document.payload,
2351
+ ...document.namespace ? { namespace: document.namespace } : {},
2352
+ ...document.expiresAt ? { expiresAt: document.expiresAt.toISOString() } : {}
2353
+ }
2354
+ }));
2355
+ await this.client.upsert(request.collection, {
2356
+ wait: true,
2357
+ points
2358
+ });
2359
+ }
2360
+ async search(query) {
2361
+ const results = await this.client.search(query.collection, {
2362
+ vector: query.vector,
2363
+ limit: query.topK,
2364
+ filter: query.filter,
2365
+ score_threshold: query.scoreThreshold,
2366
+ with_payload: true,
2367
+ with_vector: false
2368
+ });
2369
+ return results.map((item) => ({
2370
+ id: String(item.id),
2371
+ score: item.score,
2372
+ payload: item.payload ?? undefined,
2373
+ namespace: typeof item.payload === "object" && item.payload !== null ? item.payload.namespace : undefined
2374
+ }));
2375
+ }
2376
+ async delete(request) {
2377
+ await this.client.delete(request.collection, {
2378
+ wait: true,
2379
+ points: request.ids
2380
+ });
2381
+ }
2382
+ async ensureCollection(collectionName, vectorSize) {
2383
+ try {
2384
+ await this.client.getCollection(collectionName);
2385
+ } catch (_error) {
2386
+ await this.client.createCollection(collectionName, {
2387
+ vectors: {
2388
+ size: vectorSize,
2389
+ distance: this.distance
2390
+ }
2391
+ });
2392
+ }
2393
+ }
2394
+ }
2395
+
2396
+ // src/impls/supabase-psql.ts
2397
+ import { Buffer as Buffer3 } from "node:buffer";
2398
+ import { sql as drizzleSql } from "drizzle-orm";
2399
+ import { drizzle } from "drizzle-orm/postgres-js";
2400
+ import postgres from "postgres";
2401
+
2402
+ class SupabasePostgresProvider {
2403
+ client;
2404
+ db;
2405
+ ownsClient;
2406
+ createDrizzle;
2407
+ constructor(options = {}) {
2408
+ this.createDrizzle = options.createDrizzle ?? ((client) => drizzle(client));
2409
+ if (options.db) {
2410
+ if (!options.client) {
2411
+ throw new Error("SupabasePostgresProvider requires a postgres client when db is provided.");
2412
+ }
2413
+ this.client = options.client;
2414
+ this.db = options.db;
2415
+ this.ownsClient = false;
2416
+ return;
2417
+ }
2418
+ if (options.client) {
2419
+ this.client = options.client;
2420
+ this.ownsClient = false;
2421
+ } else {
2422
+ if (!options.connectionString) {
2423
+ throw new Error("SupabasePostgresProvider requires either a connectionString or a client.");
2424
+ }
2425
+ this.client = postgres(options.connectionString, {
2426
+ max: options.maxConnections,
2427
+ prepare: false,
2428
+ ssl: resolveSslMode(options.sslMode)
2429
+ });
2430
+ this.ownsClient = true;
2431
+ }
2432
+ this.db = this.createDrizzle(this.client);
2433
+ }
2434
+ async query(statement, params = []) {
2435
+ const query = buildParameterizedSql(statement, params);
2436
+ const result = await this.db.execute(query);
2437
+ const rows = asRows(result);
2438
+ return {
2439
+ rows,
2440
+ rowCount: rows.length
2441
+ };
2442
+ }
2443
+ async execute(statement, params = []) {
2444
+ const query = buildParameterizedSql(statement, params);
2445
+ await this.db.execute(query);
2446
+ }
2447
+ async transaction(run) {
2448
+ const transactionResult = this.client.begin(async (transactionClient) => {
2449
+ const transactionalProvider = new SupabasePostgresProvider({
2450
+ client: transactionClient,
2451
+ db: this.createDrizzle(transactionClient),
2452
+ createDrizzle: this.createDrizzle
2453
+ });
2454
+ return run(transactionalProvider);
2455
+ });
2456
+ return transactionResult;
2457
+ }
2458
+ async close() {
2459
+ if (this.ownsClient) {
2460
+ await this.client.end({ timeout: 5 });
2461
+ }
2462
+ }
2463
+ }
2464
+ function buildParameterizedSql(statement, params) {
2465
+ const segments = [];
2466
+ const pattern = /\$(\d+)/g;
2467
+ let cursor = 0;
2468
+ for (const match of statement.matchAll(pattern)) {
2469
+ const token = match[0];
2470
+ const indexPart = match[1];
2471
+ const start = match.index;
2472
+ if (indexPart == null || start == null)
2473
+ continue;
2474
+ const parameterIndex = Number(indexPart) - 1;
2475
+ if (!Number.isInteger(parameterIndex) || parameterIndex < 0 || parameterIndex >= params.length) {
2476
+ throw new Error(`SQL placeholder ${token} is out of bounds for ${params.length} parameter(s).`);
2477
+ }
2478
+ const staticSegment = statement.slice(cursor, start);
2479
+ if (staticSegment.length > 0) {
2480
+ segments.push(drizzleSql.raw(staticSegment));
2481
+ }
2482
+ const parameterValue = params[parameterIndex];
2483
+ if (parameterValue === undefined) {
2484
+ throw new Error(`SQL placeholder ${token} is missing a parameter value.`);
2485
+ }
2486
+ const normalizedValue = normalizeParam(parameterValue);
2487
+ segments.push(drizzleSql`${normalizedValue}`);
2488
+ cursor = start + token.length;
2489
+ }
2490
+ const tailSegment = statement.slice(cursor);
2491
+ if (tailSegment.length > 0) {
2492
+ segments.push(drizzleSql.raw(tailSegment));
2493
+ }
2494
+ if (segments.length === 0) {
2495
+ return drizzleSql.raw("");
2496
+ }
2497
+ return drizzleSql.join(segments);
2498
+ }
2499
+ function normalizeParam(value) {
2500
+ if (typeof value === "bigint") {
2501
+ return value.toString();
2502
+ }
2503
+ if (value instanceof Uint8Array) {
2504
+ return Buffer3.from(value);
2505
+ }
2506
+ if (isPlainObject(value)) {
2507
+ return JSON.stringify(value);
2508
+ }
2509
+ return value;
2510
+ }
2511
+ function asRows(result) {
2512
+ if (!Array.isArray(result)) {
2513
+ return [];
2514
+ }
2515
+ return result;
2516
+ }
2517
+ function isPlainObject(value) {
2518
+ if (value == null || typeof value !== "object") {
2519
+ return false;
2520
+ }
2521
+ if (Array.isArray(value)) {
2522
+ return false;
2523
+ }
2524
+ if (value instanceof Date) {
2525
+ return false;
2526
+ }
2527
+ if (value instanceof Uint8Array) {
2528
+ return false;
2529
+ }
2530
+ return true;
2531
+ }
2532
+ function resolveSslMode(mode) {
2533
+ switch (mode) {
2534
+ case "allow":
2535
+ return false;
2536
+ case "prefer":
2537
+ return "prefer";
2538
+ case "require":
2539
+ default:
2540
+ return "require";
2541
+ }
2542
+ }
2543
+
2544
+ // src/impls/supabase-vector.ts
2545
+ class SupabaseVectorProvider {
2546
+ database;
2547
+ createTableIfMissing;
2548
+ distanceMetric;
2549
+ quotedSchema;
2550
+ qualifiedTable;
2551
+ collectionIndex;
2552
+ namespaceIndex;
2553
+ ensureTablePromise;
2554
+ constructor(options) {
2555
+ this.database = options.database ?? new SupabasePostgresProvider({
2556
+ connectionString: options.connectionString,
2557
+ maxConnections: options.maxConnections,
2558
+ sslMode: options.sslMode
2559
+ });
2560
+ this.createTableIfMissing = options.createTableIfMissing ?? true;
2561
+ this.distanceMetric = options.distanceMetric ?? "cosine";
2562
+ const schema = sanitizeIdentifier(options.schema ?? "public", "schema");
2563
+ const table = sanitizeIdentifier(options.table ?? "contractspec_vectors", "table");
2564
+ this.quotedSchema = quoteIdentifier(schema);
2565
+ this.qualifiedTable = `${this.quotedSchema}.${quoteIdentifier(table)}`;
2566
+ this.collectionIndex = quoteIdentifier(`${table}_collection_idx`);
2567
+ this.namespaceIndex = quoteIdentifier(`${table}_namespace_idx`);
2568
+ }
2569
+ async upsert(request) {
2570
+ if (request.documents.length === 0) {
2571
+ return;
2572
+ }
2573
+ if (this.createTableIfMissing) {
2574
+ await this.ensureTable();
2575
+ }
2576
+ for (const document of request.documents) {
2577
+ await this.database.execute(`INSERT INTO ${this.qualifiedTable}
2578
+ (collection, id, embedding, payload, namespace, expires_at, updated_at)
2579
+ VALUES ($1, $2, $3::vector, $4::jsonb, $5, $6, now())
2580
+ ON CONFLICT (collection, id)
2581
+ DO UPDATE SET
2582
+ embedding = EXCLUDED.embedding,
2583
+ payload = EXCLUDED.payload,
2584
+ namespace = EXCLUDED.namespace,
2585
+ expires_at = EXCLUDED.expires_at,
2586
+ updated_at = now();`, [
2587
+ request.collection,
2588
+ document.id,
2589
+ toVectorLiteral(document.vector),
2590
+ document.payload ? JSON.stringify(document.payload) : null,
2591
+ document.namespace ?? null,
2592
+ document.expiresAt ?? null
2593
+ ]);
2594
+ }
2595
+ }
2596
+ async search(query) {
2597
+ const operator = this.distanceOperator;
2598
+ const results = await this.database.query(`SELECT
2599
+ id,
2600
+ payload,
2601
+ namespace,
2602
+ (embedding ${operator} $3::vector) AS distance
2603
+ FROM ${this.qualifiedTable}
2604
+ WHERE collection = $1
2605
+ AND ($2::text IS NULL OR namespace = $2)
2606
+ AND (expires_at IS NULL OR expires_at > now())
2607
+ AND ($4::jsonb IS NULL OR payload @> $4::jsonb)
2608
+ ORDER BY embedding ${operator} $3::vector
2609
+ LIMIT $5;`, [
2610
+ query.collection,
2611
+ query.namespace ?? null,
2612
+ toVectorLiteral(query.vector),
2613
+ query.filter ? JSON.stringify(query.filter) : null,
2614
+ query.topK
2615
+ ]);
2616
+ const mapped = results.rows.map((row) => {
2617
+ const distance = Number(row.distance);
2618
+ return {
2619
+ id: row.id,
2620
+ score: distanceToScore(distance, this.distanceMetric),
2621
+ payload: isRecord(row.payload) ? row.payload : undefined,
2622
+ namespace: row.namespace ?? undefined
2623
+ };
2624
+ });
2625
+ const scoreThreshold = query.scoreThreshold;
2626
+ if (scoreThreshold == null) {
2627
+ return mapped;
2628
+ }
2629
+ return mapped.filter((result) => result.score >= scoreThreshold);
2630
+ }
2631
+ async delete(request) {
2632
+ if (request.ids.length === 0) {
2633
+ return;
2634
+ }
2635
+ const params = [
2636
+ request.collection,
2637
+ request.ids,
2638
+ request.namespace ?? null
2639
+ ];
2640
+ await this.database.execute(`DELETE FROM ${this.qualifiedTable}
2641
+ WHERE collection = $1
2642
+ AND id = ANY($2::text[])
2643
+ AND ($3::text IS NULL OR namespace = $3);`, params);
2644
+ }
2645
+ async ensureTable() {
2646
+ if (!this.ensureTablePromise) {
2647
+ this.ensureTablePromise = this.createTable();
2648
+ }
2649
+ await this.ensureTablePromise;
2650
+ }
2651
+ async createTable() {
2652
+ await this.database.execute("CREATE EXTENSION IF NOT EXISTS vector;");
2653
+ await this.database.execute(`CREATE SCHEMA IF NOT EXISTS ${this.quotedSchema};`);
2654
+ await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.qualifiedTable} (
2655
+ collection text NOT NULL,
2656
+ id text NOT NULL,
2657
+ embedding vector NOT NULL,
2658
+ payload jsonb,
2659
+ namespace text,
2660
+ expires_at timestamptz,
2661
+ created_at timestamptz NOT NULL DEFAULT now(),
2662
+ updated_at timestamptz NOT NULL DEFAULT now(),
2663
+ PRIMARY KEY (collection, id)
2664
+ );`);
2665
+ await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.collectionIndex}
2666
+ ON ${this.qualifiedTable} (collection);`);
2667
+ await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.namespaceIndex}
2668
+ ON ${this.qualifiedTable} (namespace);`);
2669
+ }
2670
+ get distanceOperator() {
2671
+ switch (this.distanceMetric) {
2672
+ case "l2":
2673
+ return "<->";
2674
+ case "inner_product":
2675
+ return "<#>";
2676
+ case "cosine":
2677
+ default:
2678
+ return "<=>";
2679
+ }
2680
+ }
2681
+ }
2682
+ function sanitizeIdentifier(value, label) {
2683
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
2684
+ throw new Error(`SupabaseVectorProvider ${label} "${value}" is invalid.`);
2685
+ }
2686
+ return value;
2687
+ }
2688
+ function quoteIdentifier(value) {
2689
+ return `"${value.replaceAll('"', '""')}"`;
2690
+ }
2691
+ function toVectorLiteral(vector) {
2692
+ if (vector.length === 0) {
2693
+ throw new Error("Supabase vectors must contain at least one dimension.");
2694
+ }
2695
+ for (const value of vector) {
2696
+ if (!Number.isFinite(value)) {
2697
+ throw new Error(`Supabase vectors must be finite numbers. Found "${value}".`);
2698
+ }
2699
+ }
2700
+ return `[${vector.join(",")}]`;
2701
+ }
2702
+ function isRecord(value) {
2703
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2704
+ }
2705
+ function distanceToScore(distance, metric) {
2706
+ switch (metric) {
2707
+ case "inner_product":
2708
+ return -distance;
2709
+ case "l2":
2710
+ return 1 / (1 + distance);
2711
+ case "cosine":
2712
+ default:
2713
+ return 1 - distance;
2714
+ }
2715
+ }
2716
+
2717
+ // src/impls/stripe-payments.ts
2718
+ import Stripe from "stripe";
2719
+ var API_VERSION = "2026-01-28.clover";
2720
+
2721
+ class StripePaymentsProvider {
2722
+ stripe;
2723
+ constructor(options) {
2724
+ this.stripe = options.stripe ?? new Stripe(options.apiKey, {
2725
+ apiVersion: API_VERSION
2726
+ });
2727
+ }
2728
+ async createCustomer(input) {
2729
+ const customer = await this.stripe.customers.create({
2730
+ email: input.email,
2731
+ name: input.name,
2732
+ description: input.description,
2733
+ metadata: input.metadata
2734
+ });
2735
+ return this.toCustomer(customer);
2736
+ }
2737
+ async getCustomer(customerId) {
2738
+ const customer = await this.stripe.customers.retrieve(customerId);
2739
+ if (customer.deleted)
2740
+ return null;
2741
+ return this.toCustomer(customer);
2742
+ }
2743
+ async createPaymentIntent(input) {
2744
+ const intent = await this.stripe.paymentIntents.create({
2745
+ amount: input.amount.amount,
2746
+ currency: input.amount.currency,
2747
+ customer: input.customerId,
2748
+ description: input.description,
2749
+ capture_method: input.captureMethod ?? "automatic",
2750
+ confirmation_method: input.confirmationMethod ?? "automatic",
2751
+ automatic_payment_methods: { enabled: true },
2752
+ metadata: input.metadata,
2753
+ return_url: input.returnUrl,
2754
+ statement_descriptor: input.statementDescriptor
2755
+ });
2756
+ return this.toPaymentIntent(intent);
2757
+ }
2758
+ async capturePayment(paymentIntentId, input) {
2759
+ const intent = await this.stripe.paymentIntents.capture(paymentIntentId, input?.amount ? { amount_to_capture: input.amount.amount } : undefined);
2760
+ return this.toPaymentIntent(intent);
2761
+ }
2762
+ async cancelPaymentIntent(paymentIntentId) {
2763
+ const intent = await this.stripe.paymentIntents.cancel(paymentIntentId);
2764
+ return this.toPaymentIntent(intent);
2765
+ }
2766
+ async refundPayment(input) {
2767
+ const refund = await this.stripe.refunds.create({
2768
+ payment_intent: input.paymentIntentId,
2769
+ amount: input.amount?.amount,
2770
+ reason: mapRefundReason(input.reason),
2771
+ metadata: input.metadata
2772
+ });
2773
+ const paymentIntentId = typeof refund.payment_intent === "string" ? refund.payment_intent : refund.payment_intent?.id ?? "";
2774
+ return {
2775
+ id: refund.id,
2776
+ paymentIntentId,
2777
+ amount: {
2778
+ amount: refund.amount ?? 0,
2779
+ currency: refund.currency?.toUpperCase() ?? "USD"
2780
+ },
2781
+ status: mapRefundStatus(refund.status),
2782
+ reason: refund.reason ?? undefined,
2783
+ metadata: this.toMetadata(refund.metadata),
2784
+ createdAt: refund.created ? new Date(refund.created * 1000) : undefined
2785
+ };
2786
+ }
2787
+ async listInvoices(query) {
2788
+ const requestedStatus = query?.status?.[0];
2789
+ const stripeStatus = requestedStatus && requestedStatus !== "deleted" ? requestedStatus : undefined;
2790
+ const response = await this.stripe.invoices.list({
2791
+ customer: query?.customerId,
2792
+ status: stripeStatus,
2793
+ limit: query?.limit,
2794
+ starting_after: query?.startingAfter
2795
+ });
2796
+ return response.data.map((invoice) => this.toInvoice(invoice));
2797
+ }
2798
+ async listTransactions(query) {
2799
+ const response = await this.stripe.charges.list({
2800
+ customer: query?.customerId,
2801
+ payment_intent: query?.paymentIntentId,
2802
+ limit: query?.limit,
2803
+ starting_after: query?.startingAfter
2804
+ });
2805
+ return response.data.map((charge) => ({
2806
+ id: charge.id,
2807
+ paymentIntentId: typeof charge.payment_intent === "string" ? charge.payment_intent : charge.payment_intent?.id,
2808
+ amount: {
2809
+ amount: charge.amount,
2810
+ currency: charge.currency?.toUpperCase() ?? "USD"
2811
+ },
2812
+ type: "capture",
2813
+ status: mapChargeStatus(charge.status),
2814
+ description: charge.description ?? undefined,
2815
+ createdAt: new Date(charge.created * 1000),
2816
+ metadata: this.mergeMetadata(this.toMetadata(charge.metadata), {
2817
+ balanceTransaction: typeof charge.balance_transaction === "string" ? charge.balance_transaction : undefined
2818
+ })
2819
+ }));
2820
+ }
2821
+ toCustomer(customer) {
2822
+ const metadata = this.toMetadata(customer.metadata);
2823
+ const updatedAtValue = metadata?.updatedAt;
2824
+ return {
2825
+ id: customer.id,
2826
+ email: customer.email ?? undefined,
2827
+ name: customer.name ?? undefined,
2828
+ metadata,
2829
+ createdAt: customer.created ? new Date(customer.created * 1000) : undefined,
2830
+ updatedAt: updatedAtValue ? new Date(updatedAtValue) : undefined
2831
+ };
2832
+ }
2833
+ toPaymentIntent(intent) {
2834
+ const metadata = this.toMetadata(intent.metadata);
2835
+ return {
2836
+ id: intent.id,
2837
+ amount: this.toMoney(intent.amount_received ?? intent.amount ?? 0, intent.currency),
2838
+ status: mapPaymentIntentStatus(intent.status),
2839
+ customerId: typeof intent.customer === "string" ? intent.customer : intent.customer?.id,
2840
+ description: intent.description ?? undefined,
2841
+ clientSecret: intent.client_secret ?? undefined,
2842
+ metadata,
2843
+ createdAt: new Date(intent.created * 1000),
2844
+ updatedAt: intent.canceled_at != null ? new Date(intent.canceled_at * 1000) : new Date(intent.created * 1000)
2845
+ };
2846
+ }
2847
+ toInvoice(invoice) {
2848
+ const metadata = this.toMetadata(invoice.metadata);
2849
+ return {
2850
+ id: invoice.id,
2851
+ number: invoice.number ?? undefined,
2852
+ status: invoice.status ?? "draft",
2853
+ amountDue: this.toMoney(invoice.amount_due ?? 0, invoice.currency),
2854
+ amountPaid: this.toMoney(invoice.amount_paid ?? 0, invoice.currency),
2855
+ customerId: typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id,
2856
+ dueDate: invoice.due_date ? new Date(invoice.due_date * 1000) : undefined,
2857
+ hostedInvoiceUrl: invoice.hosted_invoice_url ?? undefined,
2858
+ metadata,
2859
+ createdAt: invoice.created ? new Date(invoice.created * 1000) : undefined,
2860
+ updatedAt: invoice.status_transitions?.finalized_at ? new Date(invoice.status_transitions.finalized_at * 1000) : undefined
2861
+ };
2862
+ }
2863
+ toMoney(amount, currency) {
2864
+ return {
2865
+ amount,
2866
+ currency: currency?.toUpperCase() ?? "USD"
2867
+ };
2868
+ }
2869
+ toMetadata(metadata) {
2870
+ if (!metadata)
2871
+ return;
2872
+ const entries = Object.entries(metadata).filter((entry) => typeof entry[1] === "string");
2873
+ if (entries.length === 0)
2874
+ return;
2875
+ return Object.fromEntries(entries);
2876
+ }
2877
+ mergeMetadata(base, extras) {
2878
+ const filteredExtras = Object.entries(extras).filter((entry) => typeof entry[1] === "string");
2879
+ if (!base && filteredExtras.length === 0) {
2880
+ return;
2881
+ }
2882
+ return {
2883
+ ...base ?? {},
2884
+ ...Object.fromEntries(filteredExtras)
2885
+ };
2886
+ }
2887
+ }
2888
+ function mapRefundReason(reason) {
2889
+ if (!reason)
2890
+ return;
2891
+ const allowed = [
2892
+ "duplicate",
2893
+ "fraudulent",
2894
+ "requested_by_customer"
2895
+ ];
2896
+ return allowed.includes(reason) ? reason : undefined;
2897
+ }
2898
+ function mapPaymentIntentStatus(status) {
2899
+ switch (status) {
2900
+ case "requires_payment_method":
2901
+ return "requires_payment_method";
2902
+ case "requires_confirmation":
2903
+ return "requires_confirmation";
2904
+ case "requires_action":
2905
+ case "requires_capture":
2906
+ return "requires_action";
2907
+ case "processing":
2908
+ return "processing";
2909
+ case "succeeded":
2910
+ return "succeeded";
2911
+ case "canceled":
2912
+ return "canceled";
2913
+ default:
2914
+ return "requires_payment_method";
2915
+ }
2916
+ }
2917
+ function mapRefundStatus(status) {
2918
+ switch (status) {
2919
+ case "pending":
2920
+ case "succeeded":
2921
+ case "failed":
2922
+ case "canceled":
2923
+ return status;
2924
+ default:
2925
+ return "pending";
2926
+ }
2927
+ }
2928
+ function mapChargeStatus(status) {
2929
+ switch (status) {
2930
+ case "pending":
2931
+ case "processing":
2932
+ return "pending";
2933
+ case "succeeded":
2934
+ return "succeeded";
2935
+ case "failed":
2936
+ case "canceled":
2937
+ return "failed";
2938
+ default:
2939
+ return "pending";
2940
+ }
2941
+ }
2942
+
2943
+ // src/impls/postmark-email.ts
2944
+ import { ServerClient } from "postmark";
2945
+
2946
+ class PostmarkEmailProvider {
2947
+ client;
2948
+ defaultFromEmail;
2949
+ messageStream;
2950
+ constructor(options) {
2951
+ this.client = options.client ?? new ServerClient(options.serverToken, {
2952
+ useHttps: true
2953
+ });
2954
+ this.defaultFromEmail = options.defaultFromEmail;
2955
+ this.messageStream = options.messageStream;
2956
+ }
2957
+ async sendEmail(message) {
2958
+ const request = {
2959
+ From: formatAddress2(message.from) ?? this.defaultFromEmail,
2960
+ To: message.to.map((addr) => formatAddress2(addr)).join(", "),
2961
+ Cc: message.cc?.map((addr) => formatAddress2(addr)).join(", ") || undefined,
2962
+ Bcc: message.bcc?.map((addr) => formatAddress2(addr)).join(", ") || undefined,
2963
+ ReplyTo: message.replyTo ? formatAddress2(message.replyTo) : undefined,
2964
+ Subject: message.subject,
2965
+ TextBody: message.textBody,
2966
+ HtmlBody: message.htmlBody,
2967
+ Headers: message.headers ? Object.entries(message.headers).map(([name, value]) => ({
2968
+ Name: name,
2969
+ Value: value
2970
+ })) : undefined,
2971
+ MessageStream: this.messageStream,
2972
+ Attachments: buildAttachments(message)
2973
+ };
2974
+ const response = await this.client.sendEmail(request);
2975
+ return {
2976
+ id: response.MessageID,
2977
+ providerMessageId: response.MessageID,
2978
+ queuedAt: new Date(response.SubmittedAt ?? new Date().toISOString())
2979
+ };
2980
+ }
2981
+ }
2982
+ function formatAddress2(address) {
2983
+ if (address.name) {
2984
+ return `"${address.name}" <${address.email}>`;
2985
+ }
2986
+ return address.email;
2987
+ }
2988
+ function buildAttachments(message) {
2989
+ if (!message.attachments?.length)
2990
+ return;
2991
+ return message.attachments.filter((attachment) => attachment.data).map((attachment) => ({
2992
+ Name: attachment.filename,
2993
+ Content: Buffer.from(attachment.data ?? new Uint8Array).toString("base64"),
2994
+ ContentType: attachment.contentType,
2995
+ ContentID: null,
2996
+ ContentLength: attachment.sizeBytes,
2997
+ Disposition: "attachment"
2998
+ }));
2999
+ }
3000
+
3001
+ // src/impls/posthog-reader.ts
3002
+ class PosthogAnalyticsReader {
3003
+ projectId;
3004
+ client;
3005
+ constructor(options) {
3006
+ this.projectId = options.projectId;
3007
+ this.client = options.client;
3008
+ }
3009
+ async queryHogQL(input) {
3010
+ const projectId = resolveProjectId(undefined, this.projectId);
3011
+ const response = await this.client.request({
3012
+ method: "POST",
3013
+ path: `/api/projects/${projectId}/query`,
3014
+ body: {
3015
+ query: {
3016
+ kind: "HogQLQuery",
3017
+ query: input.query,
3018
+ values: input.values
3019
+ }
3020
+ }
3021
+ });
3022
+ return response.data;
3023
+ }
3024
+ async getEvents(input) {
3025
+ const projectId = resolveProjectId(input.projectId, this.projectId);
3026
+ const response = await this.client.request({
3027
+ method: "GET",
3028
+ path: `/api/projects/${projectId}/events/`,
3029
+ query: {
3030
+ event: input.event ?? resolveSingleEvent(input.events),
3031
+ events: resolveEventList(input.events),
3032
+ distinct_id: input.distinctId,
3033
+ order_by: input.orderBy,
3034
+ limit: input.limit,
3035
+ offset: input.offset,
3036
+ properties: input.properties ? JSON.stringify(input.properties) : undefined,
3037
+ ...buildEventDateQuery(input.dateRange)
3038
+ }
3039
+ });
3040
+ return response.data;
3041
+ }
3042
+ async getPersons(input) {
3043
+ const projectId = resolveProjectId(input.projectId, this.projectId);
3044
+ const response = await this.client.request({
3045
+ method: "GET",
3046
+ path: `/api/projects/${projectId}/persons/`,
3047
+ query: {
3048
+ cohort_id: input.cohortId,
3049
+ search: input.search,
3050
+ limit: input.limit,
3051
+ offset: input.offset,
3052
+ properties: input.properties ? JSON.stringify(input.properties) : undefined
3053
+ }
3054
+ });
3055
+ return response.data;
3056
+ }
3057
+ async getInsights(input) {
3058
+ const projectId = resolveProjectId(input.projectId, this.projectId);
3059
+ const response = await this.client.request({
3060
+ method: "GET",
3061
+ path: `/api/projects/${projectId}/insights/`,
3062
+ query: {
3063
+ insight: input.insightType,
3064
+ limit: input.limit,
3065
+ offset: input.offset
3066
+ }
3067
+ });
3068
+ return response.data;
3069
+ }
3070
+ async getInsightResult(input) {
3071
+ const projectId = resolveProjectId(input.projectId, this.projectId);
3072
+ const response = await this.client.request({
3073
+ method: "GET",
3074
+ path: `/api/projects/${projectId}/insights/${input.insightId}/`
3075
+ });
3076
+ return response.data;
3077
+ }
3078
+ async getCohorts(input) {
3079
+ const projectId = resolveProjectId(input.projectId, this.projectId);
3080
+ const response = await this.client.request({
3081
+ method: "GET",
3082
+ path: `/api/projects/${projectId}/cohorts/`,
3083
+ query: {
3084
+ limit: input.limit,
3085
+ offset: input.offset
3086
+ }
3087
+ });
3088
+ return response.data;
3089
+ }
3090
+ async getFeatureFlags(input) {
3091
+ const projectId = resolveProjectId(input.projectId, this.projectId);
3092
+ const response = await this.client.request({
3093
+ method: "GET",
3094
+ path: `/api/projects/${projectId}/feature_flags/`,
3095
+ query: {
3096
+ active: input.active,
3097
+ limit: input.limit,
3098
+ offset: input.offset
3099
+ }
3100
+ });
3101
+ return response.data;
3102
+ }
3103
+ async getAnnotations(input) {
3104
+ const projectId = resolveProjectId(input.projectId, this.projectId);
3105
+ const response = await this.client.request({
3106
+ method: "GET",
3107
+ path: `/api/projects/${projectId}/annotations/`,
3108
+ query: {
3109
+ limit: input.limit,
3110
+ offset: input.offset,
3111
+ ...buildAnnotationDateQuery(input.dateRange)
3112
+ }
3113
+ });
3114
+ return response.data;
3115
+ }
3116
+ }
3117
+ function resolveProjectId(inputProjectId, defaultProjectId) {
3118
+ const projectId = inputProjectId ?? defaultProjectId;
3119
+ if (!projectId) {
3120
+ throw new Error("PostHog projectId is required for API reads.");
3121
+ }
3122
+ return projectId;
3123
+ }
3124
+ function resolveSingleEvent(events) {
3125
+ if (!events || events.length !== 1)
3126
+ return;
3127
+ return events[0];
3128
+ }
3129
+ function resolveEventList(events) {
3130
+ if (!events || events.length <= 1)
3131
+ return;
3132
+ return events.join(",");
3133
+ }
3134
+ function buildEventDateQuery(range) {
3135
+ const after = formatDate(range?.from);
3136
+ const before = formatDate(range?.to);
3137
+ return {
3138
+ after,
3139
+ before,
3140
+ timezone: range?.timezone
3141
+ };
3142
+ }
3143
+ function buildAnnotationDateQuery(range) {
3144
+ const dateFrom = formatDate(range?.from);
3145
+ const dateTo = formatDate(range?.to);
3146
+ return {
3147
+ date_from: dateFrom,
3148
+ date_to: dateTo,
3149
+ timezone: range?.timezone
3150
+ };
3151
+ }
3152
+ function formatDate(value) {
3153
+ if (!value)
3154
+ return;
3155
+ return typeof value === "string" ? value : value.toISOString();
3156
+ }
3157
+
3158
+ // src/impls/posthog-utils.ts
3159
+ function normalizeHost(host) {
3160
+ return host.replace(/\/$/, "");
3161
+ }
3162
+ function buildUrl(host, path, query) {
3163
+ if (/^https?:\/\//.test(path)) {
3164
+ return appendQuery(path, query);
3165
+ }
3166
+ const normalizedPath = path.replace(/^\/+/, "");
3167
+ return appendQuery(`${host}/${normalizedPath}`, query);
3168
+ }
3169
+ function appendQuery(url, query) {
3170
+ if (!query)
3171
+ return url;
3172
+ const params = new URLSearchParams;
3173
+ Object.entries(query).forEach(([key, value]) => {
3174
+ if (value === undefined)
3175
+ return;
3176
+ params.set(key, String(value));
3177
+ });
3178
+ const suffix = params.toString();
3179
+ return suffix ? `${url}?${suffix}` : url;
3180
+ }
3181
+ function parseJson(value) {
3182
+ if (!value)
3183
+ return {};
3184
+ try {
3185
+ return JSON.parse(value);
3186
+ } catch {
3187
+ return value;
3188
+ }
3189
+ }
3190
+
3191
+ // src/impls/posthog.ts
3192
+ import { PostHog } from "posthog-node";
3193
+ var DEFAULT_POSTHOG_HOST = "https://app.posthog.com";
3194
+
3195
+ class PosthogAnalyticsProvider {
3196
+ host;
3197
+ projectId;
3198
+ projectApiKey;
3199
+ personalApiKey;
3200
+ mcpUrl;
3201
+ fetchFn;
3202
+ client;
3203
+ reader;
3204
+ constructor(options) {
3205
+ this.host = normalizeHost(options.host ?? DEFAULT_POSTHOG_HOST);
3206
+ this.projectId = options.projectId;
3207
+ this.projectApiKey = options.projectApiKey;
3208
+ this.personalApiKey = options.personalApiKey;
3209
+ this.mcpUrl = options.mcpUrl;
3210
+ this.fetchFn = options.fetch ?? fetch;
3211
+ this.client = options.client ?? (options.projectApiKey ? new PostHog(options.projectApiKey, {
3212
+ host: this.host,
3213
+ requestTimeout: options.requestTimeoutMs ?? 1e4
3214
+ }) : undefined);
3215
+ this.reader = new PosthogAnalyticsReader({
3216
+ projectId: this.projectId,
3217
+ client: { request: this.request.bind(this) }
3218
+ });
3219
+ }
3220
+ async capture(event) {
3221
+ if (!this.client) {
3222
+ throw new Error("PostHog projectApiKey is required for capture.");
3223
+ }
3224
+ await this.client.capture({
3225
+ distinctId: event.distinctId,
3226
+ event: event.event,
3227
+ properties: event.properties,
3228
+ timestamp: event.timestamp,
3229
+ groups: event.groups
3230
+ });
3231
+ }
3232
+ async identify(input) {
3233
+ if (!this.client) {
3234
+ throw new Error("PostHog projectApiKey is required for identify.");
3235
+ }
3236
+ await this.client.identify({
3237
+ distinctId: input.distinctId,
3238
+ properties: {
3239
+ ...input.properties ? { $set: input.properties } : {},
3240
+ ...input.setOnce ? { $set_once: input.setOnce } : {}
3241
+ }
3242
+ });
3243
+ }
3244
+ async queryHogQL(input) {
3245
+ return this.reader.queryHogQL(input);
3246
+ }
3247
+ async getEvents(input) {
3248
+ return this.reader.getEvents(input);
3249
+ }
3250
+ async getPersons(input) {
3251
+ return this.reader.getPersons(input);
3252
+ }
3253
+ async getInsights(input) {
3254
+ return this.reader.getInsights(input);
3255
+ }
3256
+ async getInsightResult(input) {
3257
+ return this.reader.getInsightResult(input);
3258
+ }
3259
+ async getCohorts(input) {
3260
+ return this.reader.getCohorts(input);
3261
+ }
3262
+ async getFeatureFlags(input) {
3263
+ return this.reader.getFeatureFlags(input);
3264
+ }
3265
+ async getAnnotations(input) {
3266
+ return this.reader.getAnnotations(input);
3267
+ }
3268
+ async request(request) {
3269
+ if (!this.personalApiKey) {
3270
+ throw new Error("PostHog personalApiKey is required for API requests.");
3271
+ }
3272
+ const url = buildUrl(this.host, request.path, request.query);
3273
+ const response = await this.fetchFn(url, {
3274
+ method: request.method,
3275
+ headers: {
3276
+ Authorization: `Bearer ${this.personalApiKey}`,
3277
+ "Content-Type": "application/json",
3278
+ ...request.headers ?? {}
3279
+ },
3280
+ body: request.body ? JSON.stringify(request.body) : undefined
3281
+ });
3282
+ const text = await response.text();
3283
+ const data = parseJson(text);
3284
+ return {
3285
+ status: response.status,
3286
+ data,
3287
+ headers: Object.fromEntries(response.headers.entries())
3288
+ };
3289
+ }
3290
+ async callMcpTool(call) {
3291
+ if (!this.mcpUrl) {
3292
+ throw new Error("PostHog MCP URL is not configured.");
3293
+ }
3294
+ const response = await this.fetchFn(this.mcpUrl, {
3295
+ method: "POST",
3296
+ headers: {
3297
+ "Content-Type": "application/json"
3298
+ },
3299
+ body: JSON.stringify({
3300
+ jsonrpc: "2.0",
3301
+ id: 1,
3302
+ method: "tools/call",
3303
+ params: {
3304
+ name: call.name,
3305
+ arguments: call.arguments ?? {}
3306
+ }
3307
+ })
3308
+ });
3309
+ if (!response.ok) {
3310
+ const body = await response.text();
3311
+ throw new Error(`PostHog MCP error (${response.status}): ${body}`);
3312
+ }
3313
+ const result = await response.json();
3314
+ if (result.error) {
3315
+ throw new Error(result.error.message ?? "PostHog MCP error");
3316
+ }
3317
+ return result.result ?? null;
3318
+ }
3319
+ }
3320
+
3321
+ // src/impls/twilio-sms.ts
3322
+ import Twilio from "twilio";
3323
+
3324
+ class TwilioSmsProvider {
3325
+ client;
3326
+ fromNumber;
3327
+ constructor(options) {
3328
+ this.client = options.client ?? Twilio(options.accountSid, options.authToken);
3329
+ this.fromNumber = options.fromNumber;
3330
+ }
3331
+ async sendSms(input) {
3332
+ const message = await this.client.messages.create({
3333
+ to: input.to,
3334
+ from: input.from ?? this.fromNumber,
3335
+ body: input.body
3336
+ });
3337
+ return {
3338
+ id: message.sid,
3339
+ to: message.to ?? input.to,
3340
+ from: message.from ?? input.from ?? this.fromNumber ?? "",
3341
+ body: message.body ?? input.body,
3342
+ status: mapStatus(message.status),
3343
+ sentAt: message.dateCreated ? new Date(message.dateCreated) : undefined,
3344
+ deliveredAt: message.status === "delivered" && message.dateUpdated ? new Date(message.dateUpdated) : undefined,
3345
+ price: message.price ? Number(message.price) : undefined,
3346
+ priceCurrency: message.priceUnit ?? undefined,
3347
+ errorCode: message.errorCode ? String(message.errorCode) : undefined,
3348
+ errorMessage: message.errorMessage ?? undefined
3349
+ };
3350
+ }
3351
+ async getDeliveryStatus(messageId) {
3352
+ const message = await this.client.messages(messageId).fetch();
3353
+ return {
3354
+ status: mapStatus(message.status),
3355
+ errorCode: message.errorCode ? String(message.errorCode) : undefined,
3356
+ errorMessage: message.errorMessage ?? undefined,
3357
+ updatedAt: message.dateUpdated ? new Date(message.dateUpdated) : new Date
3358
+ };
3359
+ }
3360
+ }
3361
+ function mapStatus(status) {
3362
+ switch (status) {
3363
+ case "queued":
3364
+ case "accepted":
3365
+ case "scheduled":
3366
+ return "queued";
3367
+ case "sending":
3368
+ case "processing":
3369
+ return "sending";
3370
+ case "sent":
3371
+ return "sent";
3372
+ case "delivered":
3373
+ return "delivered";
3374
+ case "undelivered":
3375
+ return "undelivered";
3376
+ case "failed":
3377
+ case "canceled":
3378
+ return "failed";
3379
+ default:
3380
+ return "queued";
3381
+ }
3382
+ }
3383
+
3384
+ // src/impls/powens-client.ts
3385
+ import { URL } from "node:url";
3386
+ var POWENS_BASE_URL = {
3387
+ sandbox: "https://api-sandbox.powens.com/v2",
3388
+ production: "https://api.powens.com/v2"
3389
+ };
3390
+
3391
+ class PowensClientError extends Error {
3392
+ status;
3393
+ code;
3394
+ requestId;
3395
+ response;
3396
+ constructor(message, status, code, requestId, response) {
3397
+ super(message);
3398
+ this.name = "PowensClientError";
3399
+ this.status = status;
3400
+ this.code = code;
3401
+ this.requestId = requestId;
3402
+ this.response = response;
3403
+ }
3404
+ }
3405
+
3406
+ class PowensClient {
3407
+ clientId;
3408
+ clientSecret;
3409
+ apiKey;
3410
+ fetchImpl;
3411
+ logger;
3412
+ defaultTimeoutMs;
3413
+ token;
3414
+ baseUrl;
3415
+ constructor(options) {
3416
+ this.clientId = options.clientId;
3417
+ this.clientSecret = options.clientSecret;
3418
+ this.apiKey = options.apiKey;
3419
+ this.fetchImpl = options.fetchImpl ?? fetch;
3420
+ this.logger = options.logger;
3421
+ this.defaultTimeoutMs = options.defaultTimeoutMs ?? 15000;
3422
+ this.baseUrl = options.baseUrl ?? POWENS_BASE_URL[options.environment] ?? POWENS_BASE_URL.production;
3423
+ }
3424
+ async listAccounts(params) {
3425
+ const searchParams = {
3426
+ cursor: params.cursor,
3427
+ limit: params.limit,
3428
+ include_balances: params.includeBalances,
3429
+ institution_uuid: params.institutionUuid
3430
+ };
3431
+ const response = await this.request({
3432
+ method: "GET",
3433
+ path: `/users/${encodeURIComponent(params.userUuid)}/accounts`,
3434
+ searchParams
3435
+ });
3436
+ return response;
3437
+ }
3438
+ async getAccount(accountUuid) {
3439
+ return this.request({
3440
+ method: "GET",
3441
+ path: `/accounts/${encodeURIComponent(accountUuid)}`
3442
+ });
3443
+ }
3444
+ async listTransactions(params) {
3445
+ const searchParams = {
3446
+ cursor: params.cursor,
3447
+ limit: params.limit,
3448
+ from: params.from,
3449
+ to: params.to,
3450
+ include_pending: params.includePending
3451
+ };
3452
+ return this.request({
3453
+ method: "GET",
3454
+ path: `/accounts/${encodeURIComponent(params.accountUuid)}/transactions`,
3455
+ searchParams
3456
+ });
3457
+ }
3458
+ async getBalances(accountUuid) {
3459
+ return this.request({
3460
+ method: "GET",
3461
+ path: `/accounts/${encodeURIComponent(accountUuid)}/balances`
3462
+ });
3463
+ }
3464
+ async getConnectionStatus(connectionUuid) {
3465
+ return this.request({
3466
+ method: "GET",
3467
+ path: `/connections/${encodeURIComponent(connectionUuid)}`
3468
+ });
3469
+ }
3470
+ async request(options) {
3471
+ const url = new URL(options.path, this.baseUrl);
3472
+ if (options.searchParams) {
3473
+ for (const [key, value] of Object.entries(options.searchParams)) {
3474
+ if (value === undefined || value === null)
3475
+ continue;
3476
+ url.searchParams.set(key, String(value));
3477
+ }
3478
+ }
3479
+ const headers = {
3480
+ Accept: "application/json",
3481
+ "Content-Type": "application/json",
3482
+ ...options.headers
3483
+ };
3484
+ if (this.apiKey) {
3485
+ headers["x-api-key"] = this.apiKey;
3486
+ }
3487
+ if (!options.skipAuth) {
3488
+ const token = await this.ensureAccessToken();
3489
+ headers.Authorization = `Bearer ${token}`;
3490
+ }
3491
+ const controller = new AbortController;
3492
+ const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? this.defaultTimeoutMs);
3493
+ try {
3494
+ const response = await this.fetchImpl(url, {
3495
+ method: options.method,
3496
+ headers,
3497
+ body: options.body ? JSON.stringify(options.body) : undefined,
3498
+ signal: controller.signal
3499
+ });
3500
+ const requestId = response.headers.get("x-request-id") ?? undefined;
3501
+ if (!response.ok) {
3502
+ let errorBody;
3503
+ try {
3504
+ errorBody = await response.json();
3505
+ } catch {}
3506
+ const errorObject = typeof errorBody === "object" && errorBody !== null ? errorBody : undefined;
3507
+ const message = typeof errorObject?.message === "string" ? errorObject.message : `Powens API request failed with status ${response.status}`;
3508
+ const code = typeof errorObject?.code === "string" ? errorObject.code : undefined;
3509
+ throw new PowensClientError(message, response.status, code, requestId, errorBody);
3510
+ }
3511
+ if (response.status === 204) {
3512
+ return;
3513
+ }
3514
+ try {
3515
+ return await response.json();
3516
+ } catch (error) {
3517
+ this.logger?.error?.("[PowensClient] Failed to parse JSON response", error);
3518
+ throw new PowensClientError("Failed to parse Powens response payload as JSON", response.status, undefined, requestId);
3519
+ }
3520
+ } catch (error) {
3521
+ if (error instanceof PowensClientError) {
3522
+ throw error;
3523
+ }
3524
+ if (error.name === "AbortError") {
3525
+ throw new PowensClientError(`Powens API request timed out after ${options.timeoutMs ?? this.defaultTimeoutMs}ms`, 408);
3526
+ }
3527
+ this.logger?.error?.("[PowensClient] Request failed", error);
3528
+ throw new PowensClientError(error.message ?? "Powens API request failed", 500);
3529
+ } finally {
3530
+ clearTimeout(timeout);
3531
+ }
3532
+ }
3533
+ async ensureAccessToken() {
3534
+ if (this.token && Date.now() < this.token.expiresAt - 5000) {
3535
+ return this.token.accessToken;
3536
+ }
3537
+ this.token = await this.fetchAccessToken();
3538
+ return this.token.accessToken;
3539
+ }
3540
+ async fetchAccessToken() {
3541
+ const url = new URL("/oauth/token", this.baseUrl);
3542
+ const basicAuth = Buffer.from(`${this.clientId}:${this.clientSecret}`, "utf-8").toString("base64");
3543
+ const response = await this.fetchImpl(url, {
3544
+ method: "POST",
3545
+ headers: {
3546
+ Authorization: `Basic ${basicAuth}`,
3547
+ "Content-Type": "application/x-www-form-urlencoded",
3548
+ Accept: "application/json"
3549
+ },
3550
+ body: new URLSearchParams({
3551
+ grant_type: "client_credentials"
3552
+ }).toString()
3553
+ });
3554
+ if (!response.ok) {
3555
+ let errorBody;
3556
+ try {
3557
+ errorBody = await response.json();
3558
+ } catch {}
3559
+ const errorObject = typeof errorBody === "object" && errorBody !== null ? errorBody : undefined;
3560
+ const message = typeof errorObject?.error_description === "string" ? errorObject.error_description : "Failed to obtain Powens access token";
3561
+ throw new PowensClientError(message, response.status, undefined, undefined, errorBody);
3562
+ }
3563
+ const payload = await response.json();
3564
+ const expiresAt = Date.now() + payload.expires_in * 1000;
3565
+ this.logger?.debug?.("[PowensClient] Received access token", {
3566
+ expiresIn: payload.expires_in
3567
+ });
3568
+ return {
3569
+ accessToken: payload.access_token,
3570
+ expiresAt,
3571
+ scope: payload.scope
3572
+ };
3573
+ }
3574
+ }
3575
+
3576
+ // src/impls/powens-openbanking.ts
3577
+ class PowensOpenBankingProvider {
3578
+ client;
3579
+ logger;
3580
+ constructor(options) {
3581
+ this.client = new PowensClient(options);
3582
+ this.logger = options.logger;
3583
+ }
3584
+ async listAccounts(params) {
3585
+ if (!params.userId) {
3586
+ throw new PowensClientError("Powens account listing requires the upstream userId mapped to Powens user UUID.", 400);
3587
+ }
3588
+ const context = this.toContext(params.tenantId, params.connectionId);
3589
+ try {
3590
+ const response = await this.client.listAccounts({
3591
+ userUuid: params.userId,
3592
+ cursor: params.cursor,
3593
+ limit: params.pageSize,
3594
+ includeBalances: params.includeBalances,
3595
+ institutionUuid: params.institutionId
3596
+ });
3597
+ return {
3598
+ accounts: response.accounts.map((account) => this.mapAccount(account, context)),
3599
+ nextCursor: response.pagination?.nextCursor,
3600
+ hasMore: response.pagination?.hasMore
3601
+ };
3602
+ } catch (error) {
3603
+ this.handleError("listAccounts", error);
3604
+ }
3605
+ }
3606
+ async getAccountDetails(params) {
3607
+ const context = this.toContext(params.tenantId, params.connectionId);
3608
+ try {
3609
+ const account = await this.client.getAccount(params.accountId);
3610
+ return this.mapAccountDetails(account, context);
3611
+ } catch (error) {
3612
+ this.handleError("getAccountDetails", error);
3613
+ }
3614
+ }
3615
+ async listTransactions(params) {
3616
+ const context = this.toContext(params.tenantId, params.connectionId);
3617
+ try {
3618
+ const response = await this.client.listTransactions({
3619
+ accountUuid: params.accountId,
3620
+ cursor: params.cursor,
3621
+ limit: params.pageSize,
3622
+ from: params.from,
3623
+ to: params.to,
3624
+ includePending: params.includePending
3625
+ });
3626
+ return {
3627
+ transactions: response.transactions.map((transaction) => this.mapTransaction(transaction, context)),
3628
+ nextCursor: response.pagination?.nextCursor,
3629
+ hasMore: response.pagination?.hasMore
3630
+ };
3631
+ } catch (error) {
3632
+ this.handleError("listTransactions", error);
3633
+ }
3634
+ }
3635
+ async getBalances(params) {
3636
+ const context = this.toContext(params.tenantId, params.connectionId);
3637
+ try {
3638
+ const balances = await this.client.getBalances(params.accountId);
3639
+ return balances.filter((balance) => params.balanceTypes?.length ? params.balanceTypes.includes(String(balance.type)) : true).map((balance) => this.mapBalance(balance, context));
3640
+ } catch (error) {
3641
+ this.handleError("getBalances", error);
3642
+ }
3643
+ }
3644
+ async getConnectionStatus(params) {
3645
+ try {
3646
+ const status = await this.client.getConnectionStatus(params.connectionId);
3647
+ return {
3648
+ connectionId: params.connectionId,
3649
+ tenantId: params.tenantId,
3650
+ status: this.mapConnectionStatus(status.status),
3651
+ lastCheckedAt: status.lastAttemptAt,
3652
+ errorCode: status.errorCode,
3653
+ errorMessage: status.errorMessage,
3654
+ details: status.metadata
3655
+ };
3656
+ } catch (error) {
3657
+ this.handleError("getConnectionStatus", error);
3658
+ }
3659
+ }
3660
+ mapAccount(account, context) {
3661
+ return {
3662
+ id: account.uuid,
3663
+ externalId: account.reference ?? account.uuid,
3664
+ tenantId: context.tenantId,
3665
+ connectionId: context.connectionId,
3666
+ userId: account.userUuid,
3667
+ displayName: account.name,
3668
+ institutionId: account.institution.id,
3669
+ institutionName: account.institution.name,
3670
+ institutionLogoUrl: account.institution.logoUrl,
3671
+ accountType: account.type ?? "unknown",
3672
+ iban: account.iban,
3673
+ bic: account.bic,
3674
+ currency: account.currency ?? "EUR",
3675
+ accountNumberMasked: account.metadata?.account_number_masked,
3676
+ ownership: this.mapOwnership(account.metadata?.ownership),
3677
+ status: this.mapAccountStatus(account.status),
3678
+ lastSyncedAt: account.metadata?.last_sync_at,
3679
+ metadata: account.metadata
3680
+ };
3681
+ }
3682
+ mapAccountDetails(account, context) {
3683
+ return {
3684
+ ...this.mapAccount(account, context),
3685
+ productCode: account.metadata?.product_code,
3686
+ openedAt: account.metadata?.opened_at,
3687
+ closedAt: account.metadata?.closed_at,
3688
+ availableBalance: account.availableBalance ?? undefined,
3689
+ currentBalance: account.balance ?? undefined,
3690
+ creditLimit: account.metadata?.credit_limit,
3691
+ customFields: account.metadata
3692
+ };
3693
+ }
3694
+ mapTransaction(transaction, context) {
3695
+ return {
3696
+ id: transaction.uuid,
3697
+ externalId: transaction.uuid,
3698
+ tenantId: context.tenantId,
3699
+ accountId: transaction.accountUuid,
3700
+ connectionId: context.connectionId,
3701
+ amount: transaction.amount,
3702
+ currency: transaction.currency,
3703
+ direction: transaction.direction === "credit" ? "credit" : "debit",
3704
+ description: transaction.description ?? transaction.rawLabel,
3705
+ bookingDate: transaction.bookingDate,
3706
+ valueDate: transaction.valueDate,
3707
+ postedAt: transaction.bookingDate,
3708
+ category: transaction.category,
3709
+ rawCategory: transaction.rawLabel,
3710
+ merchantName: transaction.merchantName,
3711
+ merchantCategoryCode: transaction.merchantCategoryCode,
3712
+ counterpartyName: transaction.counterpartyName,
3713
+ counterpartyAccount: transaction.counterpartyAccount,
3714
+ reference: transaction.metadata?.reference,
3715
+ status: this.mapTransactionStatus(transaction.status),
3716
+ metadata: transaction.metadata
3717
+ };
3718
+ }
3719
+ mapBalance(balance, context) {
3720
+ return {
3721
+ accountId: balance.accountUuid,
3722
+ connectionId: context.connectionId,
3723
+ tenantId: context.tenantId,
3724
+ type: balance.type ?? "current",
3725
+ currency: balance.currency ?? "EUR",
3726
+ amount: balance.amount,
3727
+ lastUpdatedAt: balance.updatedAt,
3728
+ metadata: balance.metadata
3729
+ };
3730
+ }
3731
+ toContext(tenantId, connectionId) {
3732
+ return { tenantId, connectionId };
3733
+ }
3734
+ mapOwnership(value) {
3735
+ switch (value?.toLowerCase()) {
3736
+ case "individual":
3737
+ case "personal":
3738
+ return "individual";
3739
+ case "joint":
3740
+ return "joint";
3741
+ case "business":
3742
+ case "corporate":
3743
+ return "business";
3744
+ default:
3745
+ return "unknown";
3746
+ }
3747
+ }
3748
+ mapAccountStatus(status) {
3749
+ switch (status?.toLowerCase()) {
3750
+ case "active":
3751
+ case "enabled":
3752
+ return "active";
3753
+ case "disabled":
3754
+ case "inactive":
3755
+ return "inactive";
3756
+ case "closed":
3757
+ return "closed";
3758
+ case "suspended":
3759
+ return "suspended";
3760
+ default:
3761
+ return "active";
3762
+ }
3763
+ }
3764
+ mapTransactionStatus(status) {
3765
+ switch (status?.toLowerCase()) {
3766
+ case "pending":
3767
+ case "authorised":
3768
+ return "pending";
3769
+ case "booked":
3770
+ case "posted":
3771
+ return "booked";
3772
+ case "cancelled":
3773
+ case "rejected":
3774
+ return "cancelled";
3775
+ default:
3776
+ return "booked";
3777
+ }
3778
+ }
3779
+ mapConnectionStatus(status) {
3780
+ switch (status) {
3781
+ case "healthy":
3782
+ return "healthy";
3783
+ case "pending":
3784
+ return "degraded";
3785
+ case "error":
3786
+ return "error";
3787
+ case "revoked":
3788
+ return "disconnected";
3789
+ default:
3790
+ return "degraded";
3791
+ }
3792
+ }
3793
+ handleError(operation, error) {
3794
+ if (error instanceof PowensClientError) {
3795
+ this.logger?.error?.(`[PowensOpenBankingProvider] ${operation} failed`, {
3796
+ status: error.status,
3797
+ code: error.code,
3798
+ requestId: error.requestId,
3799
+ message: error.message
3800
+ });
3801
+ throw error;
3802
+ }
3803
+ this.logger?.error?.(`[PowensOpenBankingProvider] ${operation} failed with unexpected error`, error);
3804
+ throw error instanceof Error ? error : new Error(`Powens operation "${operation}" failed`);
3805
+ }
3806
+ }
3807
+
3808
+ // src/impls/linear.ts
3809
+ import { LinearClient } from "@linear/sdk";
3810
+ var PRIORITY_MAP = {
3811
+ urgent: 1,
3812
+ high: 2,
3813
+ medium: 3,
3814
+ low: 4,
3815
+ none: 0
3816
+ };
3817
+
3818
+ class LinearProjectManagementProvider {
3819
+ client;
3820
+ defaults;
3821
+ constructor(options) {
3822
+ this.client = options.client ?? new LinearClient({ apiKey: options.apiKey });
3823
+ this.defaults = {
3824
+ teamId: options.teamId,
3825
+ projectId: options.projectId,
3826
+ assigneeId: options.assigneeId,
3827
+ stateId: options.stateId,
3828
+ labelIds: options.labelIds,
3829
+ tagLabelMap: options.tagLabelMap
3830
+ };
3831
+ }
3832
+ async createWorkItem(input) {
3833
+ const teamId = this.defaults.teamId;
3834
+ if (!teamId) {
3835
+ throw new Error("Linear teamId is required to create work items.");
3836
+ }
3837
+ const payload = await this.client.createIssue({
3838
+ teamId,
3839
+ title: input.title,
3840
+ description: input.description,
3841
+ priority: mapPriority(input.priority),
3842
+ estimate: input.estimate,
3843
+ assigneeId: input.assigneeId ?? this.defaults.assigneeId,
3844
+ projectId: input.projectId ?? this.defaults.projectId,
3845
+ stateId: this.defaults.stateId,
3846
+ labelIds: resolveLabelIds(this.defaults, input.tags)
3847
+ });
3848
+ const issue = await payload.issue;
3849
+ const state = issue ? await issue.state : undefined;
3850
+ return {
3851
+ id: issue?.id ?? "",
3852
+ title: issue?.title ?? input.title,
3853
+ url: issue?.url ?? undefined,
3854
+ status: state?.name ?? undefined,
3855
+ priority: input.priority,
3856
+ tags: input.tags,
3857
+ projectId: input.projectId ?? this.defaults.projectId,
3858
+ externalId: input.externalId,
3859
+ metadata: input.metadata
3860
+ };
3861
+ }
3862
+ async createWorkItems(items) {
3863
+ const created = [];
3864
+ for (const item of items) {
3865
+ created.push(await this.createWorkItem(item));
3866
+ }
3867
+ return created;
3868
+ }
3869
+ }
3870
+ function mapPriority(priority) {
3871
+ if (!priority)
3872
+ return;
3873
+ return PRIORITY_MAP[priority] ?? undefined;
3874
+ }
3875
+ function resolveLabelIds(defaults, tags) {
3876
+ const labelIds = new Set;
3877
+ (defaults.labelIds ?? []).forEach((id) => labelIds.add(id));
3878
+ if (tags && defaults.tagLabelMap) {
3879
+ tags.forEach((tag) => {
3880
+ const mapped = defaults.tagLabelMap?.[tag];
3881
+ if (mapped)
3882
+ labelIds.add(mapped);
3883
+ });
3884
+ }
3885
+ const merged = [...labelIds];
3886
+ return merged.length > 0 ? merged : undefined;
3887
+ }
3888
+
3889
+ // src/impls/jira.ts
3890
+ import { Buffer as Buffer4 } from "node:buffer";
3891
+
3892
+ class JiraProjectManagementProvider {
3893
+ siteUrl;
3894
+ authHeader;
3895
+ defaults;
3896
+ fetchFn;
3897
+ constructor(options) {
3898
+ this.siteUrl = normalizeSiteUrl(options.siteUrl);
3899
+ this.authHeader = buildAuthHeader(options.email, options.apiToken);
3900
+ this.defaults = {
3901
+ projectKey: options.projectKey,
3902
+ issueType: options.issueType,
3903
+ defaultLabels: options.defaultLabels,
3904
+ issueTypeMap: options.issueTypeMap
3905
+ };
3906
+ this.fetchFn = options.fetch ?? fetch;
3907
+ }
3908
+ async createWorkItem(input) {
3909
+ const projectKey = input.projectId ?? this.defaults.projectKey;
3910
+ if (!projectKey) {
3911
+ throw new Error("Jira projectKey is required to create work items.");
3912
+ }
3913
+ const issueType = resolveIssueType(input.type, this.defaults);
3914
+ const description = buildJiraDescription(input.description);
3915
+ const labels = mergeLabels(this.defaults.defaultLabels, input.tags);
3916
+ const priority = mapPriority2(input.priority);
3917
+ const payload = {
3918
+ fields: {
3919
+ project: { key: projectKey },
3920
+ summary: input.title,
3921
+ description,
3922
+ issuetype: { name: issueType },
3923
+ labels,
3924
+ priority: priority ? { name: priority } : undefined,
3925
+ assignee: input.assigneeId ? { accountId: input.assigneeId } : undefined,
3926
+ duedate: input.dueDate ? input.dueDate.toISOString().slice(0, 10) : undefined
3927
+ }
3928
+ };
3929
+ const response = await this.fetchFn(`${this.siteUrl}/rest/api/3/issue`, {
3930
+ method: "POST",
3931
+ headers: {
3932
+ Authorization: this.authHeader,
3933
+ "Content-Type": "application/json",
3934
+ Accept: "application/json"
3935
+ },
3936
+ body: JSON.stringify(payload)
3937
+ });
3938
+ if (!response.ok) {
3939
+ const body = await response.text();
3940
+ throw new Error(`Jira API error (${response.status}): ${body || response.statusText}`);
3941
+ }
3942
+ const data = await response.json();
3943
+ return {
3944
+ id: data.id ?? data.key ?? "",
3945
+ title: input.title,
3946
+ url: data.key ? `${this.siteUrl}/browse/${data.key}` : undefined,
3947
+ status: input.status,
3948
+ priority: input.priority,
3949
+ tags: input.tags,
3950
+ projectId: projectKey,
3951
+ externalId: input.externalId,
3952
+ metadata: input.metadata
3953
+ };
3954
+ }
3955
+ async createWorkItems(items) {
3956
+ const created = [];
3957
+ for (const item of items) {
3958
+ created.push(await this.createWorkItem(item));
3959
+ }
3960
+ return created;
3961
+ }
3962
+ }
3963
+ function normalizeSiteUrl(siteUrl) {
3964
+ return siteUrl.replace(/\/$/, "");
3965
+ }
3966
+ function buildAuthHeader(email, apiToken) {
3967
+ const token = Buffer4.from(`${email}:${apiToken}`).toString("base64");
3968
+ return `Basic ${token}`;
3969
+ }
3970
+ function resolveIssueType(type, defaults) {
3971
+ if (type && defaults.issueTypeMap?.[type]) {
3972
+ return defaults.issueTypeMap[type] ?? defaults.issueType ?? "Task";
3973
+ }
3974
+ return defaults.issueType ?? "Task";
3975
+ }
3976
+ function mapPriority2(priority) {
3977
+ switch (priority) {
3978
+ case "urgent":
3979
+ return "Highest";
3980
+ case "high":
3981
+ return "High";
3982
+ case "medium":
3983
+ return "Medium";
3984
+ case "low":
3985
+ return "Low";
3986
+ case "none":
3987
+ default:
3988
+ return;
3989
+ }
3990
+ }
3991
+ function mergeLabels(defaults, tags) {
3992
+ const merged = new Set;
3993
+ (defaults ?? []).forEach((label) => merged.add(label));
3994
+ (tags ?? []).forEach((tag) => merged.add(tag));
3995
+ const result = [...merged];
3996
+ return result.length > 0 ? result : undefined;
3997
+ }
3998
+ function buildJiraDescription(description) {
3999
+ if (!description)
4000
+ return;
4001
+ const lines = description.split(/\r?\n/).filter((line) => line.trim());
4002
+ const content = lines.map((line) => ({
4003
+ type: "paragraph",
4004
+ content: [{ type: "text", text: line }]
4005
+ }));
4006
+ if (content.length === 0)
4007
+ return;
4008
+ return { type: "doc", version: 1, content };
4009
+ }
4010
+
4011
+ // src/impls/notion.ts
4012
+ import { Client } from "@notionhq/client";
4013
+
4014
+ class NotionProjectManagementProvider {
4015
+ client;
4016
+ defaults;
4017
+ constructor(options) {
4018
+ this.client = options.client ?? new Client({ auth: options.apiKey });
4019
+ this.defaults = {
4020
+ databaseId: options.databaseId,
4021
+ summaryParentPageId: options.summaryParentPageId,
4022
+ titleProperty: options.titleProperty,
4023
+ statusProperty: options.statusProperty,
4024
+ priorityProperty: options.priorityProperty,
4025
+ tagsProperty: options.tagsProperty,
4026
+ dueDateProperty: options.dueDateProperty,
4027
+ descriptionProperty: options.descriptionProperty
4028
+ };
4029
+ }
4030
+ async createWorkItem(input) {
4031
+ if (input.type === "summary" && this.defaults.summaryParentPageId) {
4032
+ return this.createSummaryPage(input);
4033
+ }
4034
+ const databaseId = this.defaults.databaseId;
4035
+ if (!databaseId) {
4036
+ throw new Error("Notion databaseId is required to create work items.");
4037
+ }
4038
+ const titleProperty = this.defaults.titleProperty ?? "Name";
4039
+ const properties = {
4040
+ [titleProperty]: buildTitleProperty(input.title)
4041
+ };
4042
+ applySelect(properties, this.defaults.statusProperty, input.status);
4043
+ applySelect(properties, this.defaults.priorityProperty, input.priority);
4044
+ applyMultiSelect(properties, this.defaults.tagsProperty, input.tags);
4045
+ applyDate(properties, this.defaults.dueDateProperty, input.dueDate);
4046
+ applyRichText(properties, this.defaults.descriptionProperty, input.description);
4047
+ const page = await this.client.pages.create({
4048
+ parent: { type: "database_id", database_id: databaseId },
4049
+ properties
4050
+ });
4051
+ return {
4052
+ id: page.id,
4053
+ title: input.title,
4054
+ url: "url" in page ? page.url : undefined,
4055
+ status: input.status,
4056
+ priority: input.priority,
4057
+ tags: input.tags,
4058
+ projectId: databaseId,
4059
+ externalId: input.externalId,
4060
+ metadata: input.metadata
4061
+ };
4062
+ }
4063
+ async createWorkItems(items) {
4064
+ const created = [];
4065
+ for (const item of items) {
4066
+ created.push(await this.createWorkItem(item));
4067
+ }
4068
+ return created;
4069
+ }
4070
+ async createSummaryPage(input) {
4071
+ const parentId = this.defaults.summaryParentPageId;
4072
+ if (!parentId) {
4073
+ throw new Error("Notion summaryParentPageId is required for summaries.");
4074
+ }
4075
+ const summaryProperties = {
4076
+ title: buildTitleProperty(input.title)
4077
+ };
4078
+ const page = await this.client.pages.create({
4079
+ parent: { type: "page_id", page_id: parentId },
4080
+ properties: summaryProperties
4081
+ });
4082
+ if (input.description) {
4083
+ const children = buildParagraphBlocks(input.description);
4084
+ if (children.length > 0) {
4085
+ await this.client.blocks.children.append({
4086
+ block_id: page.id,
4087
+ children
4088
+ });
4089
+ }
4090
+ }
4091
+ return {
4092
+ id: page.id,
4093
+ title: input.title,
4094
+ url: "url" in page ? page.url : undefined,
4095
+ status: input.status,
4096
+ priority: input.priority,
4097
+ tags: input.tags,
4098
+ projectId: parentId,
4099
+ externalId: input.externalId,
4100
+ metadata: input.metadata
4101
+ };
4102
+ }
4103
+ }
4104
+ function buildTitleProperty(title) {
4105
+ return {
4106
+ title: [
4107
+ {
4108
+ type: "text",
4109
+ text: { content: title }
4110
+ }
4111
+ ]
4112
+ };
4113
+ }
4114
+ function buildRichText(text) {
4115
+ return {
4116
+ rich_text: [
4117
+ {
4118
+ type: "text",
4119
+ text: { content: text }
4120
+ }
4121
+ ]
4122
+ };
4123
+ }
4124
+ function applySelect(properties, property, value) {
4125
+ if (!property || !value)
4126
+ return;
4127
+ const next = {
4128
+ select: { name: value }
4129
+ };
4130
+ properties[property] = next;
4131
+ }
4132
+ function applyMultiSelect(properties, property, values) {
4133
+ if (!property || !values || values.length === 0)
4134
+ return;
4135
+ const next = {
4136
+ multi_select: values.map((value) => ({ name: value }))
4137
+ };
4138
+ properties[property] = next;
4139
+ }
4140
+ function applyDate(properties, property, value) {
4141
+ if (!property || !value)
4142
+ return;
4143
+ const next = {
4144
+ date: { start: value.toISOString() }
4145
+ };
4146
+ properties[property] = next;
4147
+ }
4148
+ function applyRichText(properties, property, value) {
4149
+ if (!property || !value)
4150
+ return;
4151
+ properties[property] = buildRichText(value);
4152
+ }
4153
+ function buildParagraphBlocks(text) {
4154
+ const lines = text.split(/\r?\n/).filter((line) => line.trim());
4155
+ return lines.map((line) => ({
4156
+ object: "block",
4157
+ type: "paragraph",
4158
+ paragraph: {
4159
+ rich_text: [
4160
+ {
4161
+ type: "text",
4162
+ text: { content: line }
4163
+ }
4164
+ ]
4165
+ }
4166
+ }));
4167
+ }
4168
+
4169
+ // src/impls/tldv-meeting-recorder.ts
4170
+ var DEFAULT_BASE_URL4 = "https://pasta.tldv.io/v1alpha1";
4171
+
4172
+ class TldvMeetingRecorderProvider {
4173
+ apiKey;
4174
+ baseUrl;
4175
+ defaultPageSize;
4176
+ constructor(options) {
4177
+ this.apiKey = options.apiKey;
4178
+ this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL4;
4179
+ this.defaultPageSize = options.pageSize;
4180
+ }
4181
+ async listMeetings(params) {
4182
+ const page = params.cursor ? Number(params.cursor) : 1;
4183
+ const limit = params.pageSize ?? this.defaultPageSize ?? 50;
4184
+ const query = new URLSearchParams;
4185
+ query.set("page", String(Number.isFinite(page) ? page : 1));
4186
+ query.set("limit", String(limit));
4187
+ if (params.query)
4188
+ query.set("query", params.query);
4189
+ if (params.from)
4190
+ query.set("from", params.from);
4191
+ if (params.to)
4192
+ query.set("to", params.to);
4193
+ if (params.organizerEmail)
4194
+ query.set("organizer", params.organizerEmail);
4195
+ if (params.participantEmail)
4196
+ query.set("participant", params.participantEmail);
4197
+ const data = await this.request(`/meetings?${query.toString()}`);
4198
+ const nextPage = data.page < data.pages ? data.page + 1 : undefined;
4199
+ return {
4200
+ meetings: data.results.map((meeting) => this.mapMeeting(meeting, params)),
4201
+ nextCursor: nextPage ? String(nextPage) : undefined,
4202
+ hasMore: Boolean(nextPage)
4203
+ };
4204
+ }
4205
+ async getMeeting(params) {
4206
+ const meeting = await this.request(`/meetings/${encodeURIComponent(params.meetingId)}`);
4207
+ return this.mapMeeting(meeting, params);
4208
+ }
4209
+ async getTranscript(params) {
4210
+ const response = await this.request(`/meetings/${encodeURIComponent(params.meetingId)}/transcript`);
4211
+ const segments = response.data.map((segment, index) => this.mapTranscriptSegment(segment, index));
4212
+ return {
4213
+ id: response.id,
4214
+ meetingId: response.meetingId,
4215
+ tenantId: params.tenantId,
4216
+ connectionId: params.connectionId ?? "unknown",
4217
+ externalId: response.id,
4218
+ format: "segments",
4219
+ text: segments.map((segment) => segment.text).join(`
4220
+ `),
4221
+ segments,
4222
+ metadata: {
4223
+ providerMeetingId: response.meetingId
4224
+ },
4225
+ raw: response.data
4226
+ };
4227
+ }
4228
+ async parseWebhook(request) {
4229
+ const payload = request.parsedBody ?? JSON.parse(request.rawBody);
4230
+ const body = payload;
4231
+ return {
4232
+ providerKey: "meeting-recorder.tldv",
4233
+ eventType: body.event,
4234
+ meetingId: body.data?.id ?? body.data?.meetingId,
4235
+ receivedAt: body.executedAt,
4236
+ payload
4237
+ };
4238
+ }
4239
+ mapMeeting(meeting, params) {
4240
+ const connectionId = params.connectionId ?? "unknown";
4241
+ const invitees = meeting.invitees?.map((invitee) => this.mapInvitee(invitee)).filter(Boolean);
4242
+ return {
4243
+ id: meeting.id,
4244
+ tenantId: params.tenantId,
4245
+ connectionId,
4246
+ externalId: meeting.id,
4247
+ title: meeting.name,
4248
+ organizer: this.mapInvitee(meeting.organizer, "organizer"),
4249
+ invitees: invitees?.length ? invitees : undefined,
4250
+ participants: invitees?.length ? invitees : undefined,
4251
+ scheduledStartAt: meeting.happenedAt,
4252
+ recordingStartAt: meeting.happenedAt,
4253
+ durationSeconds: meeting.duration,
4254
+ meetingUrl: meeting.url,
4255
+ transcriptAvailable: true,
4256
+ sourcePlatform: "tldv",
4257
+ metadata: {
4258
+ template: meeting.template,
4259
+ extraProperties: meeting.extraProperties
4260
+ }
4261
+ };
4262
+ }
4263
+ mapInvitee(invitee, role = "attendee") {
4264
+ if (!invitee)
4265
+ return;
4266
+ return {
4267
+ name: invitee.name ?? undefined,
4268
+ email: invitee.email ?? undefined,
4269
+ role
4270
+ };
4271
+ }
4272
+ mapTranscriptSegment(segment, index) {
4273
+ return {
4274
+ index,
4275
+ speakerName: segment.speaker ?? undefined,
4276
+ text: segment.text,
4277
+ startTimeMs: parseSeconds2(segment.startTime),
4278
+ endTimeMs: parseSeconds2(segment.endTime)
4279
+ };
4280
+ }
4281
+ async request(path) {
4282
+ const response = await fetch(`${this.baseUrl}${path}`, {
4283
+ headers: {
4284
+ "Content-Type": "application/json",
4285
+ "x-api-key": this.apiKey
4286
+ }
4287
+ });
4288
+ if (!response.ok) {
4289
+ const message = await safeReadError4(response);
4290
+ throw new Error(`tl;dv API error (${response.status}): ${message}`);
4291
+ }
4292
+ return await response.json();
4293
+ }
4294
+ }
4295
+ function parseSeconds2(value) {
4296
+ if (value == null)
4297
+ return;
4298
+ const num = typeof value === "number" ? value : Number(value);
4299
+ if (!Number.isFinite(num))
4300
+ return;
4301
+ return num * 1000;
4302
+ }
4303
+ async function safeReadError4(response) {
4304
+ try {
4305
+ const data = await response.json();
4306
+ return data?.message ?? response.statusText;
4307
+ } catch {
4308
+ return response.statusText;
4309
+ }
4310
+ }
4311
+
4312
+ // src/impls/provider-factory.ts
4313
+ import { Buffer as Buffer5 } from "node:buffer";
4314
+ var SECRET_CACHE = new Map;
4315
+
4316
+ class IntegrationProviderFactory {
4317
+ async createPaymentsProvider(context) {
4318
+ const secrets = await this.loadSecrets(context);
4319
+ switch (context.spec.meta.key) {
4320
+ case "payments.stripe":
4321
+ return new StripePaymentsProvider({
4322
+ apiKey: requireSecret(secrets, "apiKey", "Stripe API key is required")
4323
+ });
4324
+ default:
4325
+ throw new Error(`Unsupported payments integration: ${context.spec.meta.key}`);
4326
+ }
4327
+ }
4328
+ async createEmailOutboundProvider(context) {
4329
+ const secrets = await this.loadSecrets(context);
4330
+ switch (context.spec.meta.key) {
4331
+ case "email.postmark":
4332
+ return new PostmarkEmailProvider({
4333
+ serverToken: requireSecret(secrets, "serverToken", "Postmark server token is required"),
4334
+ defaultFromEmail: context.config.fromEmail,
4335
+ messageStream: context.config.messageStream
4336
+ });
4337
+ default:
4338
+ throw new Error(`Unsupported email integration: ${context.spec.meta.key}`);
4339
+ }
4340
+ }
4341
+ async createSmsProvider(context) {
4342
+ const secrets = await this.loadSecrets(context);
4343
+ switch (context.spec.meta.key) {
4344
+ case "sms.twilio":
4345
+ return new TwilioSmsProvider({
4346
+ accountSid: requireSecret(secrets, "accountSid", "Twilio account SID is required"),
4347
+ authToken: requireSecret(secrets, "authToken", "Twilio auth token is required"),
4348
+ fromNumber: context.config.fromNumber
4349
+ });
4350
+ default:
4351
+ throw new Error(`Unsupported SMS integration: ${context.spec.meta.key}`);
4352
+ }
4353
+ }
4354
+ async createVectorStoreProvider(context) {
4355
+ const secrets = await this.loadSecrets(context);
4356
+ const config = context.config;
4357
+ switch (context.spec.meta.key) {
4358
+ case "vectordb.qdrant":
4359
+ return new QdrantVectorProvider({
4360
+ url: requireConfig(context, "apiUrl", "Qdrant apiUrl config is required"),
4361
+ apiKey: secrets.apiKey
4362
+ });
4363
+ case "vectordb.supabase":
4364
+ return new SupabaseVectorProvider({
4365
+ connectionString: requireDatabaseUrl(secrets, "Supabase vector databaseUrl secret is required"),
4366
+ schema: config?.schema,
4367
+ table: config?.table,
4368
+ createTableIfMissing: config?.createTableIfMissing,
4369
+ distanceMetric: config?.distanceMetric,
4370
+ maxConnections: config?.maxConnections,
4371
+ sslMode: config?.sslMode
4372
+ });
4373
+ default:
4374
+ throw new Error(`Unsupported vector store integration: ${context.spec.meta.key}`);
4375
+ }
4376
+ }
4377
+ async createAnalyticsProvider(context) {
4378
+ const secrets = await this.loadSecrets(context);
4379
+ const config = context.config;
4380
+ switch (context.spec.meta.key) {
4381
+ case "analytics.posthog":
4382
+ return new PosthogAnalyticsProvider({
4383
+ host: config?.host,
4384
+ projectId: config?.projectId,
4385
+ mcpUrl: config?.mcpUrl,
4386
+ projectApiKey: secrets.projectApiKey,
4387
+ personalApiKey: requireSecret(secrets, "personalApiKey", "PostHog personalApiKey is required")
4388
+ });
4389
+ default:
4390
+ throw new Error(`Unsupported analytics integration: ${context.spec.meta.key}`);
4391
+ }
4392
+ }
4393
+ async createDatabaseProvider(context) {
4394
+ const secrets = await this.loadSecrets(context);
4395
+ const config = context.config;
4396
+ switch (context.spec.meta.key) {
4397
+ case "database.supabase":
4398
+ return new SupabasePostgresProvider({
4399
+ connectionString: requireDatabaseUrl(secrets, "Supabase database databaseUrl secret is required"),
4400
+ maxConnections: config?.maxConnections,
4401
+ sslMode: config?.sslMode
4402
+ });
4403
+ default:
4404
+ throw new Error(`Unsupported database integration: ${context.spec.meta.key}`);
4405
+ }
4406
+ }
4407
+ async createObjectStorageProvider(context) {
4408
+ const secrets = await this.loadSecrets(context);
4409
+ switch (context.spec.meta.key) {
4410
+ case "storage.s3":
4411
+ case "storage.gcs":
4412
+ return new GoogleCloudStorageProvider({
4413
+ bucket: requireConfig(context, "bucket", "Storage bucket is required"),
4414
+ clientOptions: secrets.type === "service_account" ? { credentials: secrets } : undefined
4415
+ });
4416
+ default:
4417
+ throw new Error(`Unsupported storage integration: ${context.spec.meta.key}`);
4418
+ }
4419
+ }
4420
+ async createVoiceProvider(context) {
4421
+ const secrets = await this.loadSecrets(context);
4422
+ const config = context.config;
4423
+ switch (context.spec.meta.key) {
4424
+ case "ai-voice.elevenlabs":
4425
+ return new ElevenLabsVoiceProvider({
4426
+ apiKey: requireSecret(secrets, "apiKey", "ElevenLabs API key is required"),
4427
+ defaultVoiceId: config?.defaultVoiceId
4428
+ });
4429
+ case "ai-voice.gradium":
4430
+ return new GradiumVoiceProvider({
4431
+ apiKey: requireSecret(secrets, "apiKey", "Gradium API key is required"),
4432
+ defaultVoiceId: config?.defaultVoiceId,
4433
+ region: config?.region,
4434
+ baseUrl: config?.baseUrl,
4435
+ timeoutMs: config?.timeoutMs,
4436
+ outputFormat: config?.outputFormat
4437
+ });
4438
+ case "ai-voice.fal":
4439
+ return new FalVoiceProvider({
4440
+ apiKey: requireSecret(secrets, "apiKey", "Fal API key is required"),
4441
+ modelId: config?.modelId,
4442
+ defaultVoiceUrl: config?.defaultVoiceUrl,
4443
+ defaultExaggeration: config?.defaultExaggeration,
4444
+ defaultTemperature: config?.defaultTemperature,
4445
+ defaultCfg: config?.defaultCfg,
4446
+ pollIntervalMs: config?.pollIntervalMs
4447
+ });
4448
+ default:
4449
+ throw new Error(`Unsupported voice integration: ${context.spec.meta.key}`);
4450
+ }
4451
+ }
4452
+ async createProjectManagementProvider(context) {
4453
+ const secrets = await this.loadSecrets(context);
4454
+ const config = context.config;
4455
+ switch (context.spec.meta.key) {
4456
+ case "project-management.linear":
4457
+ return new LinearProjectManagementProvider({
4458
+ apiKey: requireSecret(secrets, "apiKey", "Linear API key is required"),
4459
+ teamId: requireConfig(context, "teamId", "Linear teamId is required"),
4460
+ projectId: config?.projectId,
4461
+ assigneeId: config?.assigneeId,
4462
+ stateId: config?.stateId,
4463
+ labelIds: config?.labelIds,
4464
+ tagLabelMap: config?.tagLabelMap
4465
+ });
4466
+ case "project-management.jira":
4467
+ return new JiraProjectManagementProvider({
4468
+ siteUrl: requireConfig(context, "siteUrl", "Jira siteUrl is required"),
4469
+ email: requireSecret(secrets, "email", "Jira email is required"),
4470
+ apiToken: requireSecret(secrets, "apiToken", "Jira API token is required"),
4471
+ projectKey: config?.projectKey,
4472
+ issueType: config?.issueType,
4473
+ defaultLabels: config?.defaultLabels,
4474
+ issueTypeMap: config?.issueTypeMap
4475
+ });
4476
+ case "project-management.notion":
4477
+ return new NotionProjectManagementProvider({
4478
+ apiKey: requireSecret(secrets, "apiKey", "Notion API key is required"),
4479
+ databaseId: config?.databaseId,
4480
+ summaryParentPageId: config?.summaryParentPageId,
4481
+ titleProperty: config?.titleProperty,
4482
+ statusProperty: config?.statusProperty,
4483
+ priorityProperty: config?.priorityProperty,
4484
+ tagsProperty: config?.tagsProperty,
4485
+ dueDateProperty: config?.dueDateProperty,
4486
+ descriptionProperty: config?.descriptionProperty
4487
+ });
4488
+ default:
4489
+ throw new Error(`Unsupported project management integration: ${context.spec.meta.key}`);
4490
+ }
4491
+ }
4492
+ async createMeetingRecorderProvider(context) {
4493
+ const secrets = await this.loadSecrets(context);
4494
+ const config = context.config;
4495
+ switch (context.spec.meta.key) {
4496
+ case "meeting-recorder.granola":
4497
+ if (config?.transport === "mcp") {
4498
+ return new GranolaMeetingRecorderProvider({
4499
+ transport: "mcp",
4500
+ mcpUrl: config?.mcpUrl,
4501
+ mcpHeaders: config?.mcpHeaders,
4502
+ mcpAccessToken: secrets.mcpAccessToken ?? secrets.apiKey,
4503
+ pageSize: config?.pageSize
4504
+ });
4505
+ }
4506
+ return new GranolaMeetingRecorderProvider({
4507
+ apiKey: requireSecret(secrets, "apiKey", "Granola API key is required"),
4508
+ baseUrl: config?.baseUrl,
4509
+ pageSize: config?.pageSize
4510
+ });
4511
+ case "meeting-recorder.tldv":
4512
+ return new TldvMeetingRecorderProvider({
4513
+ apiKey: requireSecret(secrets, "apiKey", "tl;dv API key is required"),
4514
+ baseUrl: config?.baseUrl,
4515
+ pageSize: config?.pageSize
4516
+ });
4517
+ case "meeting-recorder.fireflies":
4518
+ return new FirefliesMeetingRecorderProvider({
4519
+ apiKey: requireSecret(secrets, "apiKey", "Fireflies API key is required"),
4520
+ baseUrl: config?.baseUrl,
4521
+ pageSize: config?.transcriptsPageSize ?? config?.pageSize,
4522
+ webhookSecret: secrets.webhookSecret
4523
+ });
4524
+ case "meeting-recorder.fathom":
4525
+ return new FathomMeetingRecorderProvider({
4526
+ apiKey: requireSecret(secrets, "apiKey", "Fathom API key is required"),
4527
+ baseUrl: config?.baseUrl,
4528
+ includeTranscript: config?.includeTranscript,
4529
+ includeSummary: config?.includeSummary,
4530
+ includeActionItems: config?.includeActionItems,
4531
+ includeCrmMatches: config?.includeCrmMatches,
4532
+ triggeredFor: config?.triggeredFor,
4533
+ maxPages: config?.maxPages,
4534
+ webhookSecret: secrets.webhookSecret
4535
+ });
4536
+ default:
4537
+ throw new Error(`Unsupported meeting recorder integration: ${context.spec.meta.key}`);
4538
+ }
4539
+ }
4540
+ async createLlmProvider(context) {
4541
+ const secrets = await this.loadSecrets(context);
4542
+ switch (context.spec.meta.key) {
4543
+ case "ai-llm.mistral":
4544
+ return new MistralLLMProvider({
4545
+ apiKey: requireSecret(secrets, "apiKey", "Mistral API key is required"),
4546
+ defaultModel: context.config.model
4547
+ });
4548
+ default:
4549
+ throw new Error(`Unsupported LLM integration: ${context.spec.meta.key}`);
4550
+ }
4551
+ }
4552
+ async createEmbeddingProvider(context) {
4553
+ const secrets = await this.loadSecrets(context);
4554
+ switch (context.spec.meta.key) {
4555
+ case "ai-llm.mistral":
4556
+ return new MistralEmbeddingProvider({
4557
+ apiKey: requireSecret(secrets, "apiKey", "Mistral API key is required"),
4558
+ defaultModel: context.config.embeddingModel
4559
+ });
4560
+ default:
4561
+ throw new Error(`Unsupported embeddings integration: ${context.spec.meta.key}`);
4562
+ }
4563
+ }
4564
+ async createOpenBankingProvider(context) {
4565
+ const secrets = await this.loadSecrets(context);
4566
+ const config = context.config;
4567
+ switch (context.spec.meta.key) {
4568
+ case "openbanking.powens": {
4569
+ const environmentValue = requireConfig(context, "environment", "Powens environment (sandbox | production) must be specified in integration config.");
4570
+ if (environmentValue !== "sandbox" && environmentValue !== "production") {
4571
+ throw new Error(`Powens environment "${environmentValue}" is invalid. Expected "sandbox" or "production".`);
4572
+ }
4573
+ return new PowensOpenBankingProvider({
4574
+ clientId: requireSecret(secrets, "clientId", "Powens clientId is required"),
4575
+ clientSecret: requireSecret(secrets, "clientSecret", "Powens clientSecret is required"),
4576
+ apiKey: secrets.apiKey,
4577
+ environment: environmentValue,
4578
+ baseUrl: config?.baseUrl
4579
+ });
4580
+ }
4581
+ default:
4582
+ throw new Error(`Unsupported open banking integration: ${context.spec.meta.key}`);
4583
+ }
4584
+ }
4585
+ async loadSecrets(context) {
4586
+ const cacheKey = context.connection.meta.id;
4587
+ if (SECRET_CACHE.has(cacheKey)) {
4588
+ const cached = SECRET_CACHE.get(cacheKey);
4589
+ return cached ?? {};
4590
+ }
4591
+ const secret = await context.secretProvider.getSecret(context.secretReference);
4592
+ const value = parseSecret(secret);
4593
+ SECRET_CACHE.set(cacheKey, value);
4594
+ return value;
4595
+ }
4596
+ }
4597
+ function parseSecret(secret) {
4598
+ const text = Buffer5.from(secret.data).toString("utf-8").trim();
4599
+ if (!text)
4600
+ return {};
4601
+ try {
4602
+ return JSON.parse(text);
4603
+ } catch {
4604
+ return { apiKey: text };
4605
+ }
4606
+ }
4607
+ function requireSecret(secrets, key, message) {
4608
+ const value = secrets[key];
4609
+ if (value == null || value === "") {
4610
+ throw new Error(message);
4611
+ }
4612
+ return value;
4613
+ }
4614
+ function requireDatabaseUrl(secrets, message) {
4615
+ const value = secrets.databaseUrl ?? secrets.connectionString ?? secrets.postgresUrl ?? secrets.apiKey;
4616
+ if (typeof value !== "string" || value.trim().length === 0) {
4617
+ throw new Error(message);
4618
+ }
4619
+ return value;
4620
+ }
4621
+ function requireConfig(context, key, message) {
4622
+ const config = context.config;
4623
+ const value = config?.[key];
4624
+ if (value == null) {
4625
+ throw new Error(message);
4626
+ }
4627
+ return value;
4628
+ }
4629
+ export {
4630
+ TwilioSmsProvider,
4631
+ TldvMeetingRecorderProvider,
4632
+ SupabaseVectorProvider,
4633
+ SupabasePostgresProvider,
4634
+ StripePaymentsProvider,
4635
+ QdrantVectorProvider,
4636
+ PowensOpenBankingProvider,
4637
+ PowensClientError,
4638
+ PowensClient,
4639
+ PostmarkEmailProvider,
4640
+ PosthogAnalyticsReader,
4641
+ PosthogAnalyticsProvider,
4642
+ NotionProjectManagementProvider,
4643
+ MistralLLMProvider,
4644
+ MistralEmbeddingProvider,
4645
+ LinearProjectManagementProvider,
4646
+ JiraProjectManagementProvider,
4647
+ IntegrationProviderFactory,
4648
+ GranolaMeetingRecorderProvider,
4649
+ GradiumVoiceProvider,
4650
+ GoogleCloudStorageProvider,
4651
+ GoogleCalendarProvider,
4652
+ GmailOutboundProvider,
4653
+ GmailInboundProvider,
4654
+ FirefliesMeetingRecorderProvider,
4655
+ FathomMeetingRecorderProvider,
4656
+ FalVoiceProvider,
4657
+ ElevenLabsVoiceProvider
4658
+ };