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