@contractspec/integration.providers-impls 2.10.0 → 3.1.1

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 (138) hide show
  1. package/README.md +66 -10
  2. package/dist/impls/async-event-queue.d.ts +8 -0
  3. package/dist/impls/async-event-queue.js +49 -0
  4. package/dist/impls/composio-fallback-resolver.d.ts +34 -0
  5. package/dist/impls/composio-fallback-resolver.js +580 -0
  6. package/dist/impls/composio-mcp.d.ts +22 -0
  7. package/dist/impls/composio-mcp.js +164 -0
  8. package/dist/impls/composio-proxies.d.ts +60 -0
  9. package/dist/impls/composio-proxies.js +311 -0
  10. package/dist/impls/composio-sdk.d.ts +25 -0
  11. package/dist/impls/composio-sdk.js +78 -0
  12. package/dist/impls/composio-types.d.ts +43 -0
  13. package/dist/impls/composio-types.js +54 -0
  14. package/dist/impls/elevenlabs-voice.js +2 -0
  15. package/dist/impls/fal-voice.js +2 -0
  16. package/dist/impls/fathom-meeting-recorder.js +2 -0
  17. package/dist/impls/fathom-meeting-recorder.mapper.js +2 -0
  18. package/dist/impls/fathom-meeting-recorder.utils.js +2 -0
  19. package/dist/impls/fathom-meeting-recorder.webhooks.js +2 -0
  20. package/dist/impls/fireflies-meeting-recorder.js +2 -0
  21. package/dist/impls/fireflies-meeting-recorder.queries.js +2 -0
  22. package/dist/impls/fireflies-meeting-recorder.utils.js +2 -0
  23. package/dist/impls/gcs-storage.js +2 -0
  24. package/dist/impls/gmail-inbound.js +2 -0
  25. package/dist/impls/gmail-outbound.js +2 -0
  26. package/dist/impls/google-calendar.js +2 -0
  27. package/dist/impls/gradium-voice.js +2 -0
  28. package/dist/impls/granola-meeting-recorder.js +2 -0
  29. package/dist/impls/granola-meeting-recorder.mcp.js +2 -0
  30. package/dist/impls/health/base-health-provider.d.ts +64 -13
  31. package/dist/impls/health/base-health-provider.js +508 -156
  32. package/dist/impls/health/hybrid-health-providers.d.ts +34 -0
  33. package/dist/impls/health/hybrid-health-providers.js +1090 -0
  34. package/dist/impls/health/official-health-providers.d.ts +78 -0
  35. package/dist/impls/health/official-health-providers.js +970 -0
  36. package/dist/impls/health/provider-normalizers.d.ts +28 -0
  37. package/dist/impls/health/provider-normalizers.js +289 -0
  38. package/dist/impls/health/providers.d.ts +2 -39
  39. package/dist/impls/health/providers.js +897 -184
  40. package/dist/impls/health-provider-factory.js +1011 -196
  41. package/dist/impls/index.d.ts +11 -0
  42. package/dist/impls/index.js +2588 -259
  43. package/dist/impls/jira.js +2 -0
  44. package/dist/impls/linear.js +2 -0
  45. package/dist/impls/messaging-github.d.ts +17 -0
  46. package/dist/impls/messaging-github.js +112 -0
  47. package/dist/impls/messaging-slack.d.ts +14 -0
  48. package/dist/impls/messaging-slack.js +82 -0
  49. package/dist/impls/messaging-whatsapp-meta.d.ts +13 -0
  50. package/dist/impls/messaging-whatsapp-meta.js +54 -0
  51. package/dist/impls/messaging-whatsapp-twilio.d.ts +13 -0
  52. package/dist/impls/messaging-whatsapp-twilio.js +84 -0
  53. package/dist/impls/mistral-conversational.d.ts +23 -0
  54. package/dist/impls/mistral-conversational.js +478 -0
  55. package/dist/impls/mistral-conversational.session.d.ts +32 -0
  56. package/dist/impls/mistral-conversational.session.js +208 -0
  57. package/dist/impls/mistral-embedding.js +2 -0
  58. package/dist/impls/mistral-llm.js +2 -0
  59. package/dist/impls/mistral-stt.d.ts +17 -0
  60. package/dist/impls/mistral-stt.js +169 -0
  61. package/dist/impls/notion.js +2 -0
  62. package/dist/impls/posthog-reader.js +2 -0
  63. package/dist/impls/posthog-utils.js +2 -0
  64. package/dist/impls/posthog.js +2 -0
  65. package/dist/impls/postmark-email.js +2 -0
  66. package/dist/impls/powens-client.js +2 -0
  67. package/dist/impls/powens-openbanking.js +2 -0
  68. package/dist/impls/provider-factory.d.ts +29 -1
  69. package/dist/impls/provider-factory.js +1985 -249
  70. package/dist/impls/qdrant-vector.js +2 -0
  71. package/dist/impls/stripe-payments.js +3 -1
  72. package/dist/impls/supabase-psql.js +2 -0
  73. package/dist/impls/supabase-vector.js +2 -0
  74. package/dist/impls/tldv-meeting-recorder.js +2 -0
  75. package/dist/impls/twilio-sms.js +2 -0
  76. package/dist/index.d.ts +1 -0
  77. package/dist/index.js +2615 -283
  78. package/dist/messaging.d.ts +1 -0
  79. package/dist/messaging.js +3 -0
  80. package/dist/node/impls/async-event-queue.js +49 -0
  81. package/dist/node/impls/composio-fallback-resolver.js +580 -0
  82. package/dist/node/impls/composio-mcp.js +164 -0
  83. package/dist/node/impls/composio-proxies.js +311 -0
  84. package/dist/node/impls/composio-sdk.js +78 -0
  85. package/dist/node/impls/composio-types.js +54 -0
  86. package/dist/node/impls/elevenlabs-voice.js +3 -0
  87. package/dist/node/impls/fal-voice.js +3 -0
  88. package/dist/node/impls/fathom-meeting-recorder.js +3 -0
  89. package/dist/node/impls/fathom-meeting-recorder.mapper.js +3 -0
  90. package/dist/node/impls/fathom-meeting-recorder.utils.js +3 -0
  91. package/dist/node/impls/fathom-meeting-recorder.webhooks.js +3 -0
  92. package/dist/node/impls/fireflies-meeting-recorder.js +3 -0
  93. package/dist/node/impls/fireflies-meeting-recorder.queries.js +3 -0
  94. package/dist/node/impls/fireflies-meeting-recorder.utils.js +3 -0
  95. package/dist/node/impls/gcs-storage.js +3 -0
  96. package/dist/node/impls/gmail-inbound.js +3 -0
  97. package/dist/node/impls/gmail-outbound.js +3 -0
  98. package/dist/node/impls/google-calendar.js +3 -0
  99. package/dist/node/impls/gradium-voice.js +3 -0
  100. package/dist/node/impls/granola-meeting-recorder.js +3 -0
  101. package/dist/node/impls/granola-meeting-recorder.mcp.js +3 -0
  102. package/dist/node/impls/health/base-health-provider.js +509 -156
  103. package/dist/node/impls/health/hybrid-health-providers.js +1090 -0
  104. package/dist/node/impls/health/official-health-providers.js +970 -0
  105. package/dist/node/impls/health/provider-normalizers.js +289 -0
  106. package/dist/node/impls/health/providers.js +898 -184
  107. package/dist/node/impls/health-provider-factory.js +1012 -196
  108. package/dist/node/impls/index.js +2589 -259
  109. package/dist/node/impls/jira.js +3 -0
  110. package/dist/node/impls/linear.js +3 -0
  111. package/dist/node/impls/messaging-github.js +112 -0
  112. package/dist/node/impls/messaging-slack.js +82 -0
  113. package/dist/node/impls/messaging-whatsapp-meta.js +54 -0
  114. package/dist/node/impls/messaging-whatsapp-twilio.js +84 -0
  115. package/dist/node/impls/mistral-conversational.js +478 -0
  116. package/dist/node/impls/mistral-conversational.session.js +208 -0
  117. package/dist/node/impls/mistral-embedding.js +3 -0
  118. package/dist/node/impls/mistral-llm.js +3 -0
  119. package/dist/node/impls/mistral-stt.js +169 -0
  120. package/dist/node/impls/notion.js +3 -0
  121. package/dist/node/impls/posthog-reader.js +3 -0
  122. package/dist/node/impls/posthog-utils.js +3 -0
  123. package/dist/node/impls/posthog.js +3 -0
  124. package/dist/node/impls/postmark-email.js +3 -0
  125. package/dist/node/impls/powens-client.js +3 -0
  126. package/dist/node/impls/powens-openbanking.js +3 -0
  127. package/dist/node/impls/provider-factory.js +1986 -249
  128. package/dist/node/impls/qdrant-vector.js +3 -0
  129. package/dist/node/impls/stripe-payments.js +4 -1
  130. package/dist/node/impls/supabase-psql.js +3 -0
  131. package/dist/node/impls/supabase-vector.js +3 -0
  132. package/dist/node/impls/tldv-meeting-recorder.js +3 -0
  133. package/dist/node/impls/twilio-sms.js +3 -0
  134. package/dist/node/index.js +2616 -283
  135. package/dist/node/messaging.js +2 -0
  136. package/dist/node/secrets/provider.js +3 -0
  137. package/dist/secrets/provider.js +2 -0
  138. package/package.json +219 -14
@@ -1,3 +1,6 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
1
4
  // src/analytics.ts
2
5
  export * from "@contractspec/lib.contracts-integrations";
3
6
 
@@ -16,6 +19,625 @@ export * from "@contractspec/lib.contracts-integrations";
16
19
  // src/health.ts
17
20
  export * from "@contractspec/lib.contracts-integrations";
18
21
 
22
+ // src/impls/async-event-queue.ts
23
+ class AsyncEventQueue {
24
+ values = [];
25
+ waiters = [];
26
+ done = false;
27
+ push(value) {
28
+ if (this.done) {
29
+ return;
30
+ }
31
+ const waiter = this.waiters.shift();
32
+ if (waiter) {
33
+ waiter({ value, done: false });
34
+ return;
35
+ }
36
+ this.values.push(value);
37
+ }
38
+ close() {
39
+ if (this.done) {
40
+ return;
41
+ }
42
+ this.done = true;
43
+ for (const waiter of this.waiters) {
44
+ waiter({ value: undefined, done: true });
45
+ }
46
+ this.waiters.length = 0;
47
+ }
48
+ [Symbol.asyncIterator]() {
49
+ return {
50
+ next: async () => {
51
+ const value = this.values.shift();
52
+ if (value != null) {
53
+ return { value, done: false };
54
+ }
55
+ if (this.done) {
56
+ return { value: undefined, done: true };
57
+ }
58
+ return new Promise((resolve) => {
59
+ this.waiters.push(resolve);
60
+ });
61
+ }
62
+ };
63
+ }
64
+ }
65
+
66
+ // src/impls/composio-types.ts
67
+ var INTEGRATION_KEY_TO_TOOLKIT = {
68
+ payments: "stripe",
69
+ email: "gmail",
70
+ calendar: "googlecalendar",
71
+ sms: "twilio",
72
+ "messaging.slack": "slack",
73
+ "messaging.github": "github",
74
+ "messaging.discord": "discord",
75
+ "messaging.teams": "microsoft_teams",
76
+ "analytics.posthog": "posthog",
77
+ "project-management.linear": "linear",
78
+ "project-management.jira": "jira",
79
+ "project-management.notion": "notion",
80
+ "project-management.asana": "asana",
81
+ "project-management.trello": "trello",
82
+ "project-management.monday": "monday",
83
+ "storage.s3": "aws",
84
+ "storage.gcs": "google_cloud",
85
+ "storage.gdrive": "googledrive",
86
+ "storage.dropbox": "dropbox",
87
+ "storage.onedrive": "onedrive",
88
+ "crm.salesforce": "salesforce",
89
+ "crm.hubspot": "hubspot",
90
+ "crm.pipedrive": "pipedrive",
91
+ "database.supabase": "supabase",
92
+ "vectordb.supabase": "supabase",
93
+ "ai-llm": "openai"
94
+ };
95
+ var SESSION_TTL_MS = 30 * 60 * 1000;
96
+ function isSessionExpired(session) {
97
+ return Date.now() - session.createdAt > SESSION_TTL_MS;
98
+ }
99
+ function resolveToolkit(integrationKey) {
100
+ if (INTEGRATION_KEY_TO_TOOLKIT[integrationKey]) {
101
+ return INTEGRATION_KEY_TO_TOOLKIT[integrationKey];
102
+ }
103
+ for (const [prefix, toolkit] of Object.entries(INTEGRATION_KEY_TO_TOOLKIT)) {
104
+ if (integrationKey.startsWith(prefix)) {
105
+ return toolkit;
106
+ }
107
+ }
108
+ const parts = integrationKey.split(".");
109
+ const last = parts[parts.length - 1];
110
+ return parts.length > 1 && last ? last : integrationKey;
111
+ }
112
+
113
+ // src/impls/composio-mcp.ts
114
+ class ComposioMcpProvider {
115
+ sessions = new Map;
116
+ config;
117
+ composioInstance;
118
+ constructor(config) {
119
+ this.config = config;
120
+ }
121
+ async executeTool(toolName, args) {
122
+ const userId = args._userId ?? "default";
123
+ const session = await this.getOrCreateSession(userId);
124
+ try {
125
+ const response = await fetch(session.mcpUrl, {
126
+ method: "POST",
127
+ headers: {
128
+ "Content-Type": "application/json",
129
+ ...session.mcpHeaders
130
+ },
131
+ body: JSON.stringify({
132
+ jsonrpc: "2.0",
133
+ id: crypto.randomUUID(),
134
+ method: "tools/call",
135
+ params: { name: toolName, arguments: args }
136
+ })
137
+ });
138
+ if (!response.ok) {
139
+ return {
140
+ success: false,
141
+ error: `Composio MCP call failed: ${response.status} ${response.statusText}`
142
+ };
143
+ }
144
+ const result = await response.json();
145
+ if (result.error) {
146
+ return {
147
+ success: false,
148
+ error: result.error.message ?? "Unknown MCP error"
149
+ };
150
+ }
151
+ return { success: true, data: result.result };
152
+ } catch (error) {
153
+ return {
154
+ success: false,
155
+ error: error instanceof Error ? error.message : String(error)
156
+ };
157
+ }
158
+ }
159
+ async searchTools(query) {
160
+ const session = await this.getOrCreateSession("default");
161
+ try {
162
+ const response = await fetch(session.mcpUrl, {
163
+ method: "POST",
164
+ headers: {
165
+ "Content-Type": "application/json",
166
+ ...session.mcpHeaders
167
+ },
168
+ body: JSON.stringify({
169
+ jsonrpc: "2.0",
170
+ id: crypto.randomUUID(),
171
+ method: "tools/list",
172
+ params: {}
173
+ })
174
+ });
175
+ if (!response.ok)
176
+ return [];
177
+ const result = await response.json();
178
+ const tools = result.result?.tools ?? [];
179
+ return tools.filter((t) => t.name.toLowerCase().includes(query.toLowerCase()) || (t.description ?? "").toLowerCase().includes(query.toLowerCase())).map((t) => ({
180
+ name: t.name,
181
+ description: t.description ?? "",
182
+ toolkit: resolveToolkit(t.name.split("_")[0]?.toLowerCase() ?? ""),
183
+ parameters: t.inputSchema ?? {}
184
+ }));
185
+ } catch {
186
+ return [];
187
+ }
188
+ }
189
+ getMcpConfig(userId) {
190
+ const session = this.sessions.get(userId);
191
+ if (!session || isSessionExpired(session))
192
+ return;
193
+ return { url: session.mcpUrl, headers: session.mcpHeaders };
194
+ }
195
+ async getOrCreateSession(userId) {
196
+ const existing = this.sessions.get(userId);
197
+ if (existing && !isSessionExpired(existing)) {
198
+ return existing;
199
+ }
200
+ const client = await this.getClient();
201
+ const entity = await client.getEntity(userId);
202
+ const mcpUrl = entity.getMcpUrl();
203
+ const mcpHeaders = entity.getMcpHeaders();
204
+ const session = {
205
+ userId,
206
+ mcpUrl,
207
+ mcpHeaders,
208
+ createdAt: Date.now()
209
+ };
210
+ this.sessions.set(userId, session);
211
+ return session;
212
+ }
213
+ async getClient() {
214
+ if (this.composioInstance)
215
+ return this.composioInstance;
216
+ const { Composio } = await import("@composio/core");
217
+ this.composioInstance = new Composio({
218
+ apiKey: this.config.apiKey,
219
+ ...this.config.baseUrl ? { baseUrl: this.config.baseUrl } : {}
220
+ });
221
+ return this.composioInstance;
222
+ }
223
+ }
224
+
225
+ // src/impls/composio-sdk.ts
226
+ class ComposioSdkProvider {
227
+ config;
228
+ client;
229
+ constructor(config) {
230
+ this.config = config;
231
+ }
232
+ async executeTool(toolName, args) {
233
+ const client = await this.getClient();
234
+ const userId = args._userId ?? "default";
235
+ try {
236
+ const entity = await client.getEntity(userId);
237
+ const result = await entity.execute(toolName, args);
238
+ return { success: true, data: result };
239
+ } catch (error) {
240
+ return {
241
+ success: false,
242
+ error: error instanceof Error ? error.message : String(error)
243
+ };
244
+ }
245
+ }
246
+ async searchTools(query) {
247
+ const client = await this.getClient();
248
+ try {
249
+ const tools = await client.actions.list({ query, limit: 20 });
250
+ return tools.map((t) => ({
251
+ name: t.name,
252
+ description: t.description ?? "",
253
+ toolkit: t.appName ?? "",
254
+ parameters: t.parameters ?? {}
255
+ }));
256
+ } catch {
257
+ return [];
258
+ }
259
+ }
260
+ async getConnectedAccounts(userId) {
261
+ const client = await this.getClient();
262
+ try {
263
+ const entity = await client.getEntity(userId);
264
+ const connections = await entity.getConnections();
265
+ return connections.map((c) => ({
266
+ id: c.id,
267
+ appName: c.appName,
268
+ status: c.status
269
+ }));
270
+ } catch {
271
+ return [];
272
+ }
273
+ }
274
+ async getMcpConfig(userId) {
275
+ const client = await this.getClient();
276
+ try {
277
+ const entity = await client.getEntity(userId);
278
+ return {
279
+ url: entity.getMcpUrl(),
280
+ headers: entity.getMcpHeaders()
281
+ };
282
+ } catch {
283
+ return;
284
+ }
285
+ }
286
+ async getClient() {
287
+ if (this.client)
288
+ return this.client;
289
+ const { Composio } = await import("@composio/core");
290
+ this.client = new Composio({
291
+ apiKey: this.config.apiKey,
292
+ ...this.config.baseUrl ? { baseUrl: this.config.baseUrl } : {}
293
+ });
294
+ return this.client;
295
+ }
296
+ }
297
+
298
+ // src/impls/composio-proxies.ts
299
+ function composioToolName(toolkit, action) {
300
+ return `${toolkit.toUpperCase()}_${action.toUpperCase()}`;
301
+ }
302
+ function unwrapResult(result, fallback) {
303
+ if (!result.success) {
304
+ throw new Error(`Composio tool execution failed: ${result.error}`);
305
+ }
306
+ return result.data ?? fallback;
307
+ }
308
+
309
+ class ComposioMessagingProxy {
310
+ proxy;
311
+ toolkit;
312
+ constructor(proxy, toolkit) {
313
+ this.proxy = proxy;
314
+ this.toolkit = toolkit;
315
+ }
316
+ async sendMessage(input) {
317
+ const result = await this.proxy.executeTool(composioToolName(this.toolkit, "SEND_MESSAGE"), {
318
+ channel: input.channelId,
319
+ thread_ts: input.threadId,
320
+ text: input.text,
321
+ recipient: input.recipientId
322
+ });
323
+ const data = unwrapResult(result, {});
324
+ return {
325
+ id: String(data.id ?? data.ts ?? crypto.randomUUID()),
326
+ providerMessageId: data.ts,
327
+ status: "sent",
328
+ sentAt: new Date
329
+ };
330
+ }
331
+ async updateMessage(messageId, input) {
332
+ const result = await this.proxy.executeTool(composioToolName(this.toolkit, "UPDATE_MESSAGE"), {
333
+ message_id: messageId,
334
+ channel: input.channelId,
335
+ text: input.text
336
+ });
337
+ const data = unwrapResult(result, {});
338
+ return {
339
+ id: String(data.id ?? messageId),
340
+ status: "sent",
341
+ sentAt: new Date
342
+ };
343
+ }
344
+ }
345
+
346
+ class ComposioEmailProxy {
347
+ proxy;
348
+ toolkit;
349
+ constructor(proxy, toolkit) {
350
+ this.proxy = proxy;
351
+ this.toolkit = toolkit;
352
+ }
353
+ async sendEmail(message) {
354
+ const result = await this.proxy.executeTool(composioToolName(this.toolkit, "SEND_EMAIL"), {
355
+ to: message.to.map((a) => a.email).join(","),
356
+ cc: message.cc?.map((a) => a.email).join(","),
357
+ bcc: message.bcc?.map((a) => a.email).join(","),
358
+ subject: message.subject,
359
+ body: message.htmlBody ?? message.textBody ?? "",
360
+ from: message.from.email
361
+ });
362
+ const data = unwrapResult(result, {});
363
+ return {
364
+ id: String(data.id ?? data.messageId ?? crypto.randomUUID()),
365
+ providerMessageId: data.messageId,
366
+ queuedAt: new Date
367
+ };
368
+ }
369
+ }
370
+
371
+ class ComposioPaymentsProxy {
372
+ proxy;
373
+ toolkit;
374
+ constructor(proxy, toolkit) {
375
+ this.proxy = proxy;
376
+ this.toolkit = toolkit;
377
+ }
378
+ async createCustomer(input) {
379
+ const result = await this.proxy.executeTool(composioToolName(this.toolkit, "CREATE_CUSTOMER"), { email: input.email, name: input.name, description: input.description });
380
+ const data = unwrapResult(result, {});
381
+ return {
382
+ id: String(data.id ?? crypto.randomUUID()),
383
+ email: data.email ?? input.email,
384
+ name: data.name ?? input.name
385
+ };
386
+ }
387
+ async getCustomer(customerId) {
388
+ const result = await this.proxy.executeTool(composioToolName(this.toolkit, "GET_CUSTOMER"), { customer_id: customerId });
389
+ if (!result.success)
390
+ return null;
391
+ const data = result.data;
392
+ if (!data)
393
+ return null;
394
+ return {
395
+ id: String(data.id ?? customerId),
396
+ email: data.email,
397
+ name: data.name
398
+ };
399
+ }
400
+ async createPaymentIntent(input) {
401
+ const result = await this.proxy.executeTool(composioToolName(this.toolkit, "CREATE_PAYMENT_INTENT"), {
402
+ amount: input.amount.amount,
403
+ currency: input.amount.currency,
404
+ customer_id: input.customerId,
405
+ description: input.description
406
+ });
407
+ const data = unwrapResult(result, {});
408
+ return {
409
+ id: String(data.id ?? crypto.randomUUID()),
410
+ amount: input.amount,
411
+ status: data.status ?? "requires_payment_method",
412
+ customerId: input.customerId,
413
+ clientSecret: data.client_secret
414
+ };
415
+ }
416
+ async capturePayment(paymentIntentId, input) {
417
+ const result = await this.proxy.executeTool(composioToolName(this.toolkit, "CAPTURE_PAYMENT"), { payment_intent_id: paymentIntentId, amount: input?.amount?.amount });
418
+ const data = unwrapResult(result, {});
419
+ return {
420
+ id: paymentIntentId,
421
+ amount: input?.amount ?? { amount: 0, currency: "usd" },
422
+ status: data.status ?? "succeeded"
423
+ };
424
+ }
425
+ async cancelPaymentIntent(paymentIntentId) {
426
+ const result = await this.proxy.executeTool(composioToolName(this.toolkit, "CANCEL_PAYMENT_INTENT"), { payment_intent_id: paymentIntentId });
427
+ const data = unwrapResult(result, {});
428
+ return {
429
+ id: paymentIntentId,
430
+ amount: { amount: 0, currency: "usd" },
431
+ status: data.status ?? "canceled"
432
+ };
433
+ }
434
+ async refundPayment(input) {
435
+ const result = await this.proxy.executeTool(composioToolName(this.toolkit, "REFUND_PAYMENT"), {
436
+ payment_intent_id: input.paymentIntentId,
437
+ amount: input.amount?.amount,
438
+ reason: input.reason
439
+ });
440
+ const data = unwrapResult(result, {});
441
+ return {
442
+ id: String(data.id ?? crypto.randomUUID()),
443
+ paymentIntentId: input.paymentIntentId,
444
+ amount: input.amount ?? { amount: 0, currency: "usd" },
445
+ status: "succeeded"
446
+ };
447
+ }
448
+ async listInvoices(_query) {
449
+ const result = await this.proxy.executeTool(composioToolName(this.toolkit, "LIST_INVOICES"), { customer_id: _query?.customerId, limit: _query?.limit });
450
+ if (!result.success)
451
+ return [];
452
+ const items = result.data ?? [];
453
+ return items.map((i) => ({
454
+ id: String(i.id),
455
+ status: i.status ?? "open",
456
+ amountDue: {
457
+ amount: Number(i.amount_due ?? 0),
458
+ currency: String(i.currency ?? "usd")
459
+ }
460
+ }));
461
+ }
462
+ async listTransactions(_query) {
463
+ const result = await this.proxy.executeTool(composioToolName(this.toolkit, "LIST_TRANSACTIONS"), { customer_id: _query?.customerId, limit: _query?.limit });
464
+ if (!result.success)
465
+ return [];
466
+ const items = result.data ?? [];
467
+ return items.map((i) => ({
468
+ id: String(i.id),
469
+ amount: {
470
+ amount: Number(i.amount ?? 0),
471
+ currency: String(i.currency ?? "usd")
472
+ },
473
+ type: "capture",
474
+ status: "succeeded",
475
+ createdAt: new Date
476
+ }));
477
+ }
478
+ }
479
+
480
+ class ComposioProjectManagementProxy {
481
+ proxy;
482
+ toolkit;
483
+ constructor(proxy, toolkit) {
484
+ this.proxy = proxy;
485
+ this.toolkit = toolkit;
486
+ }
487
+ async createWorkItem(input) {
488
+ const result = await this.proxy.executeTool(composioToolName(this.toolkit, "CREATE_ISSUE"), {
489
+ title: input.title,
490
+ description: input.description,
491
+ priority: input.priority,
492
+ assignee_id: input.assigneeId,
493
+ project_id: input.projectId,
494
+ labels: input.tags
495
+ });
496
+ const data = unwrapResult(result, {});
497
+ return {
498
+ id: String(data.id ?? data.key ?? crypto.randomUUID()),
499
+ title: input.title,
500
+ url: data.url,
501
+ status: data.status,
502
+ priority: input.priority,
503
+ tags: input.tags,
504
+ projectId: input.projectId
505
+ };
506
+ }
507
+ async createWorkItems(items) {
508
+ return Promise.all(items.map((item) => this.createWorkItem(item)));
509
+ }
510
+ }
511
+
512
+ class ComposioCalendarProxy {
513
+ proxy;
514
+ toolkit;
515
+ constructor(proxy, toolkit) {
516
+ this.proxy = proxy;
517
+ this.toolkit = toolkit;
518
+ }
519
+ async listEvents(query) {
520
+ const result = await this.proxy.executeTool(composioToolName(this.toolkit, "LIST_EVENTS"), {
521
+ calendar_id: query.calendarId,
522
+ time_min: query.timeMin?.toISOString(),
523
+ time_max: query.timeMax?.toISOString(),
524
+ max_results: query.maxResults
525
+ });
526
+ if (!result.success)
527
+ return { events: [] };
528
+ const items = result.data ?? [];
529
+ return {
530
+ events: items.map((e) => ({
531
+ id: String(e.id),
532
+ calendarId: query.calendarId,
533
+ title: String(e.summary ?? e.title ?? ""),
534
+ start: new Date(String(e.start ?? Date.now())),
535
+ end: new Date(String(e.end ?? Date.now()))
536
+ }))
537
+ };
538
+ }
539
+ async createEvent(input) {
540
+ const result = await this.proxy.executeTool(composioToolName(this.toolkit, "CREATE_EVENT"), {
541
+ calendar_id: input.calendarId,
542
+ summary: input.title,
543
+ description: input.description,
544
+ location: input.location,
545
+ start: input.start.toISOString(),
546
+ end: input.end.toISOString(),
547
+ attendees: input.attendees?.map((a) => a.email)
548
+ });
549
+ const data = unwrapResult(result, {});
550
+ return {
551
+ id: String(data.id ?? crypto.randomUUID()),
552
+ calendarId: input.calendarId,
553
+ title: input.title,
554
+ start: input.start,
555
+ end: input.end
556
+ };
557
+ }
558
+ async updateEvent(calendarId, eventId, input) {
559
+ const result = await this.proxy.executeTool(composioToolName(this.toolkit, "UPDATE_EVENT"), {
560
+ calendar_id: calendarId,
561
+ event_id: eventId,
562
+ summary: input.title,
563
+ description: input.description,
564
+ start: input.start?.toISOString(),
565
+ end: input.end?.toISOString()
566
+ });
567
+ const data = unwrapResult(result, {});
568
+ return {
569
+ id: eventId,
570
+ calendarId,
571
+ title: String(data.summary ?? input.title ?? ""),
572
+ start: input.start ?? new Date,
573
+ end: input.end ?? new Date
574
+ };
575
+ }
576
+ async deleteEvent(calendarId, eventId) {
577
+ await this.proxy.executeTool(composioToolName(this.toolkit, "DELETE_EVENT"), { calendar_id: calendarId, event_id: eventId });
578
+ }
579
+ }
580
+
581
+ class ComposioGenericProxy {
582
+ proxy;
583
+ toolkit;
584
+ constructor(proxy, toolkit) {
585
+ this.proxy = proxy;
586
+ this.toolkit = toolkit;
587
+ }
588
+ async executeTool(action, args) {
589
+ return this.proxy.executeTool(composioToolName(this.toolkit, action), args);
590
+ }
591
+ async searchTools(query) {
592
+ return this.proxy.searchTools(query);
593
+ }
594
+ getToolkit() {
595
+ return this.toolkit;
596
+ }
597
+ }
598
+
599
+ // src/impls/composio-fallback-resolver.ts
600
+ class ComposioFallbackResolver {
601
+ mcpProvider;
602
+ sdkProvider;
603
+ preferredTransport;
604
+ constructor(config) {
605
+ this.mcpProvider = new ComposioMcpProvider(config);
606
+ this.sdkProvider = new ComposioSdkProvider(config);
607
+ this.preferredTransport = config.preferredTransport;
608
+ }
609
+ canHandle(_integrationKey) {
610
+ return true;
611
+ }
612
+ createMessagingProxy(context) {
613
+ const toolkit = resolveToolkit(context.spec.meta.key);
614
+ return new ComposioMessagingProxy(this.getProxy(), toolkit);
615
+ }
616
+ createEmailProxy(context) {
617
+ const toolkit = resolveToolkit(context.spec.meta.key);
618
+ return new ComposioEmailProxy(this.getProxy(), toolkit);
619
+ }
620
+ createPaymentsProxy(context) {
621
+ const toolkit = resolveToolkit(context.spec.meta.key);
622
+ return new ComposioPaymentsProxy(this.getProxy(), toolkit);
623
+ }
624
+ createProjectManagementProxy(context) {
625
+ const toolkit = resolveToolkit(context.spec.meta.key);
626
+ return new ComposioProjectManagementProxy(this.getProxy(), toolkit);
627
+ }
628
+ createCalendarProxy(context) {
629
+ const toolkit = resolveToolkit(context.spec.meta.key);
630
+ return new ComposioCalendarProxy(this.getProxy(), toolkit);
631
+ }
632
+ createGenericProxy(context) {
633
+ const toolkit = resolveToolkit(context.spec.meta.key);
634
+ return new ComposioGenericProxy(this.getProxy(), toolkit);
635
+ }
636
+ getProxy() {
637
+ return this.preferredTransport === "sdk" ? this.sdkProvider : this.mcpProvider;
638
+ }
639
+ }
640
+
19
641
  // src/impls/elevenlabs-voice.ts
20
642
  import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
21
643
  var FORMAT_MAP = {
@@ -2033,154 +2655,478 @@ async function safeReadError3(response) {
2033
2655
  }
2034
2656
  }
2035
2657
 
2036
- // src/impls/health/base-health-provider.ts
2037
- class BaseHealthProvider {
2038
- providerKey;
2039
- transport;
2040
- apiBaseUrl;
2041
- mcpUrl;
2042
- apiKey;
2043
- accessToken;
2044
- mcpAccessToken;
2045
- webhookSecret;
2046
- fetchFn;
2047
- mcpRequestId = 0;
2048
- constructor(options) {
2049
- this.providerKey = options.providerKey;
2050
- this.transport = options.transport;
2051
- this.apiBaseUrl = options.apiBaseUrl ?? "https://api.example-health.local";
2052
- this.mcpUrl = options.mcpUrl;
2053
- this.apiKey = options.apiKey;
2054
- this.accessToken = options.accessToken;
2055
- this.mcpAccessToken = options.mcpAccessToken;
2056
- this.webhookSecret = options.webhookSecret;
2057
- this.fetchFn = options.fetchFn ?? fetch;
2058
- }
2059
- async listActivities(params) {
2060
- const result = await this.fetchList("activities", params);
2061
- return {
2062
- activities: result.items,
2063
- nextCursor: result.nextCursor,
2064
- hasMore: result.hasMore,
2065
- source: this.currentSource()
2066
- };
2658
+ // src/impls/health/provider-normalizers.ts
2659
+ var DEFAULT_LIST_KEYS = [
2660
+ "items",
2661
+ "data",
2662
+ "records",
2663
+ "activities",
2664
+ "workouts",
2665
+ "sleep",
2666
+ "biometrics",
2667
+ "nutrition"
2668
+ ];
2669
+ function asRecord(value) {
2670
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2671
+ return;
2067
2672
  }
2068
- async listWorkouts(params) {
2069
- const result = await this.fetchList("workouts", params);
2070
- return {
2071
- workouts: result.items,
2072
- nextCursor: result.nextCursor,
2073
- hasMore: result.hasMore,
2074
- source: this.currentSource()
2075
- };
2673
+ return value;
2674
+ }
2675
+ function asArray2(value) {
2676
+ return Array.isArray(value) ? value : undefined;
2677
+ }
2678
+ function readString2(record, keys) {
2679
+ if (!record)
2680
+ return;
2681
+ for (const key of keys) {
2682
+ const value = record[key];
2683
+ if (typeof value === "string" && value.trim().length > 0) {
2684
+ return value;
2685
+ }
2076
2686
  }
2077
- async listSleep(params) {
2078
- const result = await this.fetchList("sleep", params);
2079
- return {
2080
- sleep: result.items,
2081
- nextCursor: result.nextCursor,
2082
- hasMore: result.hasMore,
2083
- source: this.currentSource()
2084
- };
2687
+ return;
2688
+ }
2689
+ function readNumber(record, keys) {
2690
+ if (!record)
2691
+ return;
2692
+ for (const key of keys) {
2693
+ const value = record[key];
2694
+ if (typeof value === "number" && Number.isFinite(value)) {
2695
+ return value;
2696
+ }
2697
+ if (typeof value === "string" && value.trim().length > 0) {
2698
+ const parsed = Number(value);
2699
+ if (Number.isFinite(parsed)) {
2700
+ return parsed;
2701
+ }
2702
+ }
2085
2703
  }
2086
- async listBiometrics(params) {
2087
- const result = await this.fetchList("biometrics", params);
2088
- return {
2089
- biometrics: result.items,
2090
- nextCursor: result.nextCursor,
2091
- hasMore: result.hasMore,
2092
- source: this.currentSource()
2093
- };
2704
+ return;
2705
+ }
2706
+ function readBoolean2(record, keys) {
2707
+ if (!record)
2708
+ return;
2709
+ for (const key of keys) {
2710
+ const value = record[key];
2711
+ if (typeof value === "boolean") {
2712
+ return value;
2713
+ }
2094
2714
  }
2095
- async listNutrition(params) {
2096
- const result = await this.fetchList("nutrition", params);
2097
- return {
2098
- nutrition: result.items,
2099
- nextCursor: result.nextCursor,
2100
- hasMore: result.hasMore,
2101
- source: this.currentSource()
2102
- };
2715
+ return;
2716
+ }
2717
+ function extractList(payload, listKeys = DEFAULT_LIST_KEYS) {
2718
+ const root = asRecord(payload);
2719
+ if (!root) {
2720
+ return asArray2(payload)?.map((item) => asRecord(item)).filter((item) => Boolean(item)) ?? [];
2721
+ }
2722
+ for (const key of listKeys) {
2723
+ const arrayValue = asArray2(root[key]);
2724
+ if (!arrayValue)
2725
+ continue;
2726
+ return arrayValue.map((item) => asRecord(item)).filter((item) => Boolean(item));
2727
+ }
2728
+ return [];
2729
+ }
2730
+ function extractPagination(payload) {
2731
+ const root = asRecord(payload);
2732
+ const nestedPagination = asRecord(root?.pagination);
2733
+ const nextCursor = readString2(nestedPagination, ["nextCursor", "next_cursor"]) ?? readString2(root, [
2734
+ "nextCursor",
2735
+ "next_cursor",
2736
+ "cursor",
2737
+ "next_page_token"
2738
+ ]);
2739
+ const hasMore = readBoolean2(nestedPagination, ["hasMore", "has_more"]) ?? readBoolean2(root, ["hasMore", "has_more"]);
2740
+ return {
2741
+ nextCursor,
2742
+ hasMore: hasMore ?? Boolean(nextCursor)
2743
+ };
2744
+ }
2745
+ function toHealthActivity(item, context, fallbackType = "activity") {
2746
+ const externalId = readString2(item, ["external_id", "externalId", "uuid", "id"]) ?? `${context.providerKey}:${fallbackType}`;
2747
+ const id = readString2(item, ["id", "uuid", "workout_id"]) ?? `${context.providerKey}:activity:${externalId}`;
2748
+ return {
2749
+ id,
2750
+ externalId,
2751
+ tenantId: context.tenantId,
2752
+ connectionId: context.connectionId ?? "unknown",
2753
+ userId: readString2(item, ["user_id", "userId", "athlete_id"]),
2754
+ providerKey: context.providerKey,
2755
+ activityType: readString2(item, ["activity_type", "type", "sport_type", "sport"]) ?? fallbackType,
2756
+ startedAt: readIsoDate(item, [
2757
+ "started_at",
2758
+ "start_time",
2759
+ "start_date",
2760
+ "created_at"
2761
+ ]),
2762
+ endedAt: readIsoDate(item, ["ended_at", "end_time"]),
2763
+ durationSeconds: readNumber(item, [
2764
+ "duration_seconds",
2765
+ "duration",
2766
+ "elapsed_time"
2767
+ ]),
2768
+ distanceMeters: readNumber(item, ["distance_meters", "distance"]),
2769
+ caloriesKcal: readNumber(item, [
2770
+ "calories_kcal",
2771
+ "calories",
2772
+ "active_kilocalories"
2773
+ ]),
2774
+ steps: readNumber(item, ["steps"])?.valueOf(),
2775
+ metadata: item
2776
+ };
2777
+ }
2778
+ function toHealthWorkout(item, context, fallbackType = "workout") {
2779
+ const activity = toHealthActivity(item, context, fallbackType);
2780
+ return {
2781
+ id: activity.id,
2782
+ externalId: activity.externalId,
2783
+ tenantId: activity.tenantId,
2784
+ connectionId: activity.connectionId,
2785
+ userId: activity.userId,
2786
+ providerKey: activity.providerKey,
2787
+ workoutType: readString2(item, [
2788
+ "workout_type",
2789
+ "sport_type",
2790
+ "type",
2791
+ "activity_type"
2792
+ ]) ?? fallbackType,
2793
+ startedAt: activity.startedAt,
2794
+ endedAt: activity.endedAt,
2795
+ durationSeconds: activity.durationSeconds,
2796
+ distanceMeters: activity.distanceMeters,
2797
+ caloriesKcal: activity.caloriesKcal,
2798
+ averageHeartRateBpm: readNumber(item, [
2799
+ "average_heart_rate",
2800
+ "avg_hr",
2801
+ "average_heart_rate_bpm"
2802
+ ]),
2803
+ maxHeartRateBpm: readNumber(item, [
2804
+ "max_heart_rate",
2805
+ "max_hr",
2806
+ "max_heart_rate_bpm"
2807
+ ]),
2808
+ metadata: item
2809
+ };
2810
+ }
2811
+ function toHealthSleep(item, context) {
2812
+ const externalId = readString2(item, ["external_id", "externalId", "uuid", "id"]) ?? `${context.providerKey}:sleep`;
2813
+ const id = readString2(item, ["id", "uuid"]) ?? `${context.providerKey}:sleep:${externalId}`;
2814
+ const startedAt = readIsoDate(item, ["started_at", "start_time", "bedtime_start", "start"]) ?? new Date(0).toISOString();
2815
+ const endedAt = readIsoDate(item, ["ended_at", "end_time", "bedtime_end", "end"]) ?? startedAt;
2816
+ return {
2817
+ id,
2818
+ externalId,
2819
+ tenantId: context.tenantId,
2820
+ connectionId: context.connectionId ?? "unknown",
2821
+ userId: readString2(item, ["user_id", "userId"]),
2822
+ providerKey: context.providerKey,
2823
+ startedAt,
2824
+ endedAt,
2825
+ durationSeconds: readNumber(item, [
2826
+ "duration_seconds",
2827
+ "duration",
2828
+ "total_sleep_duration"
2829
+ ]),
2830
+ deepSleepSeconds: readNumber(item, [
2831
+ "deep_sleep_seconds",
2832
+ "deep_sleep_duration"
2833
+ ]),
2834
+ lightSleepSeconds: readNumber(item, [
2835
+ "light_sleep_seconds",
2836
+ "light_sleep_duration"
2837
+ ]),
2838
+ remSleepSeconds: readNumber(item, [
2839
+ "rem_sleep_seconds",
2840
+ "rem_sleep_duration"
2841
+ ]),
2842
+ awakeSeconds: readNumber(item, ["awake_seconds", "awake_time"]),
2843
+ sleepScore: readNumber(item, ["sleep_score", "score"]),
2844
+ metadata: item
2845
+ };
2846
+ }
2847
+ function toHealthBiometric(item, context, metricTypeFallback = "metric") {
2848
+ const externalId = readString2(item, ["external_id", "externalId", "uuid", "id"]) ?? `${context.providerKey}:biometric`;
2849
+ const id = readString2(item, ["id", "uuid"]) ?? `${context.providerKey}:biometric:${externalId}`;
2850
+ return {
2851
+ id,
2852
+ externalId,
2853
+ tenantId: context.tenantId,
2854
+ connectionId: context.connectionId ?? "unknown",
2855
+ userId: readString2(item, ["user_id", "userId"]),
2856
+ providerKey: context.providerKey,
2857
+ metricType: readString2(item, ["metric_type", "metric", "type", "name"]) ?? metricTypeFallback,
2858
+ value: readNumber(item, ["value", "score", "measurement"]) ?? 0,
2859
+ unit: readString2(item, ["unit"]),
2860
+ measuredAt: readIsoDate(item, ["measured_at", "timestamp", "created_at"]) ?? new Date().toISOString(),
2861
+ metadata: item
2862
+ };
2863
+ }
2864
+ function toHealthNutrition(item, context) {
2865
+ const externalId = readString2(item, ["external_id", "externalId", "uuid", "id"]) ?? `${context.providerKey}:nutrition`;
2866
+ const id = readString2(item, ["id", "uuid"]) ?? `${context.providerKey}:nutrition:${externalId}`;
2867
+ return {
2868
+ id,
2869
+ externalId,
2870
+ tenantId: context.tenantId,
2871
+ connectionId: context.connectionId ?? "unknown",
2872
+ userId: readString2(item, ["user_id", "userId"]),
2873
+ providerKey: context.providerKey,
2874
+ loggedAt: readIsoDate(item, ["logged_at", "created_at", "date", "timestamp"]) ?? new Date().toISOString(),
2875
+ caloriesKcal: readNumber(item, ["calories_kcal", "calories"]),
2876
+ proteinGrams: readNumber(item, ["protein_grams", "protein"]),
2877
+ carbsGrams: readNumber(item, ["carbs_grams", "carbs"]),
2878
+ fatGrams: readNumber(item, ["fat_grams", "fat"]),
2879
+ fiberGrams: readNumber(item, ["fiber_grams", "fiber"]),
2880
+ hydrationMl: readNumber(item, ["hydration_ml", "water_ml", "water"]),
2881
+ metadata: item
2882
+ };
2883
+ }
2884
+ function toHealthConnectionStatus(payload, params, source) {
2885
+ const record = asRecord(payload);
2886
+ const rawStatus = readString2(record, ["status", "connection_status", "health"]) ?? "healthy";
2887
+ return {
2888
+ tenantId: params.tenantId,
2889
+ connectionId: params.connectionId,
2890
+ status: rawStatus === "healthy" || rawStatus === "degraded" || rawStatus === "error" || rawStatus === "disconnected" ? rawStatus : "healthy",
2891
+ source,
2892
+ lastCheckedAt: readIsoDate(record, ["last_checked_at", "lastCheckedAt"]) ?? new Date().toISOString(),
2893
+ errorCode: readString2(record, ["error_code", "errorCode"]),
2894
+ errorMessage: readString2(record, ["error_message", "errorMessage"]),
2895
+ metadata: asRecord(record?.metadata)
2896
+ };
2897
+ }
2898
+ function toHealthWebhookEvent(payload, providerKey, verified) {
2899
+ const record = asRecord(payload);
2900
+ const entityType = readString2(record, ["entity_type", "entityType", "type"]);
2901
+ const normalizedEntityType = entityType === "activity" || entityType === "workout" || entityType === "sleep" || entityType === "biometric" || entityType === "nutrition" ? entityType : undefined;
2902
+ return {
2903
+ providerKey,
2904
+ eventType: readString2(record, ["event_type", "eventType", "event"]),
2905
+ externalEntityId: readString2(record, [
2906
+ "external_entity_id",
2907
+ "externalEntityId",
2908
+ "entity_id",
2909
+ "entityId",
2910
+ "id"
2911
+ ]),
2912
+ entityType: normalizedEntityType,
2913
+ receivedAt: new Date().toISOString(),
2914
+ verified,
2915
+ payload,
2916
+ metadata: asRecord(record?.metadata)
2917
+ };
2918
+ }
2919
+ function readIsoDate(record, keys) {
2920
+ const value = readString2(record, keys);
2921
+ if (!value)
2922
+ return;
2923
+ const parsed = new Date(value);
2924
+ if (Number.isNaN(parsed.getTime()))
2925
+ return;
2926
+ return parsed.toISOString();
2927
+ }
2928
+
2929
+ // src/impls/health/base-health-provider.ts
2930
+ class HealthProviderCapabilityError extends Error {
2931
+ code = "NOT_SUPPORTED";
2932
+ constructor(message) {
2933
+ super(message);
2934
+ this.name = "HealthProviderCapabilityError";
2935
+ }
2936
+ }
2937
+
2938
+ class BaseHealthProvider {
2939
+ providerKey;
2940
+ transport;
2941
+ apiBaseUrl;
2942
+ mcpUrl;
2943
+ apiKey;
2944
+ accessToken;
2945
+ refreshToken;
2946
+ mcpAccessToken;
2947
+ webhookSecret;
2948
+ webhookSignatureHeader;
2949
+ route;
2950
+ aggregatorKey;
2951
+ oauth;
2952
+ fetchFn;
2953
+ mcpRequestId = 0;
2954
+ constructor(options) {
2955
+ this.providerKey = options.providerKey;
2956
+ this.transport = options.transport;
2957
+ this.apiBaseUrl = options.apiBaseUrl;
2958
+ this.mcpUrl = options.mcpUrl;
2959
+ this.apiKey = options.apiKey;
2960
+ this.accessToken = options.accessToken;
2961
+ this.refreshToken = options.oauth?.refreshToken;
2962
+ this.mcpAccessToken = options.mcpAccessToken;
2963
+ this.webhookSecret = options.webhookSecret;
2964
+ this.webhookSignatureHeader = options.webhookSignatureHeader ?? "x-webhook-signature";
2965
+ this.route = options.route ?? "primary";
2966
+ this.aggregatorKey = options.aggregatorKey;
2967
+ this.oauth = options.oauth ?? {};
2968
+ this.fetchFn = options.fetchFn ?? fetch;
2969
+ }
2970
+ async listActivities(_params) {
2971
+ throw this.unsupported("activities");
2972
+ }
2973
+ async listWorkouts(_params) {
2974
+ throw this.unsupported("workouts");
2975
+ }
2976
+ async listSleep(_params) {
2977
+ throw this.unsupported("sleep");
2978
+ }
2979
+ async listBiometrics(_params) {
2980
+ throw this.unsupported("biometrics");
2981
+ }
2982
+ async listNutrition(_params) {
2983
+ throw this.unsupported("nutrition");
2103
2984
  }
2104
2985
  async getConnectionStatus(params) {
2105
- const payload = await this.fetchRecord("connection/status", params);
2106
- const status = readString2(payload, "status") ?? "healthy";
2107
- return {
2108
- tenantId: params.tenantId,
2109
- connectionId: params.connectionId,
2110
- status: status === "healthy" || status === "degraded" || status === "error" || status === "disconnected" ? status : "healthy",
2111
- source: this.currentSource(),
2112
- lastCheckedAt: readString2(payload, "lastCheckedAt") ?? new Date().toISOString(),
2113
- errorCode: readString2(payload, "errorCode"),
2114
- errorMessage: readString2(payload, "errorMessage"),
2115
- metadata: asRecord(payload.metadata)
2116
- };
2986
+ return this.fetchConnectionStatus(params, {
2987
+ mcpTool: `${this.providerSlug()}_connection_status`
2988
+ });
2117
2989
  }
2118
2990
  async syncActivities(params) {
2119
- return this.sync("activities", params);
2991
+ return this.syncFromList(() => this.listActivities(params));
2120
2992
  }
2121
2993
  async syncWorkouts(params) {
2122
- return this.sync("workouts", params);
2994
+ return this.syncFromList(() => this.listWorkouts(params));
2123
2995
  }
2124
2996
  async syncSleep(params) {
2125
- return this.sync("sleep", params);
2997
+ return this.syncFromList(() => this.listSleep(params));
2126
2998
  }
2127
2999
  async syncBiometrics(params) {
2128
- return this.sync("biometrics", params);
3000
+ return this.syncFromList(() => this.listBiometrics(params));
2129
3001
  }
2130
3002
  async syncNutrition(params) {
2131
- return this.sync("nutrition", params);
3003
+ return this.syncFromList(() => this.listNutrition(params));
2132
3004
  }
2133
3005
  async parseWebhook(request) {
2134
3006
  const payload = request.parsedBody ?? safeJsonParse(request.rawBody);
2135
- const body = asRecord(payload);
2136
- return {
2137
- providerKey: this.providerKey,
2138
- eventType: readString2(body, "eventType") ?? readString2(body, "event"),
2139
- externalEntityId: readString2(body, "externalEntityId") ?? readString2(body, "entityId"),
2140
- entityType: normalizeEntityType(readString2(body, "entityType") ?? readString2(body, "type")),
2141
- receivedAt: new Date().toISOString(),
2142
- verified: await this.verifyWebhook(request),
2143
- payload
2144
- };
3007
+ const verified = await this.verifyWebhook(request);
3008
+ return toHealthWebhookEvent(payload, this.providerKey, verified);
2145
3009
  }
2146
3010
  async verifyWebhook(request) {
2147
- if (!this.webhookSecret) {
3011
+ if (!this.webhookSecret)
2148
3012
  return true;
2149
- }
2150
- const signature = readHeader(request.headers, "x-webhook-signature");
3013
+ const signature = readHeader(request.headers, this.webhookSignatureHeader);
2151
3014
  return signature === this.webhookSecret;
2152
3015
  }
2153
- async fetchList(resource, params) {
2154
- const payload = await this.fetchRecord(resource, params);
2155
- const items = asArray2(payload.items) ?? asArray2(payload[resource]) ?? asArray2(payload.records) ?? [];
3016
+ async fetchActivities(params, config) {
3017
+ const response = await this.fetchList(params, config);
2156
3018
  return {
2157
- items,
2158
- nextCursor: readString2(payload, "nextCursor") ?? readString2(payload, "cursor"),
2159
- hasMore: readBoolean2(payload, "hasMore")
3019
+ activities: response.items,
3020
+ nextCursor: response.nextCursor,
3021
+ hasMore: response.hasMore,
3022
+ source: this.currentSource()
2160
3023
  };
2161
3024
  }
2162
- async sync(resource, params) {
2163
- const payload = await this.fetchRecord(`sync/${resource}`, params, "POST");
3025
+ async fetchWorkouts(params, config) {
3026
+ const response = await this.fetchList(params, config);
2164
3027
  return {
2165
- synced: readNumber(payload, "synced") ?? 0,
2166
- failed: readNumber(payload, "failed") ?? 0,
2167
- nextCursor: readString2(payload, "nextCursor"),
2168
- errors: asArray2(payload.errors)?.map((item) => String(item)),
3028
+ workouts: response.items,
3029
+ nextCursor: response.nextCursor,
3030
+ hasMore: response.hasMore,
2169
3031
  source: this.currentSource()
2170
3032
  };
2171
3033
  }
2172
- async fetchRecord(resource, params, method = "GET") {
2173
- if (this.transport.endsWith("mcp")) {
2174
- return this.callMcpTool(resource, params);
3034
+ async fetchSleep(params, config) {
3035
+ const response = await this.fetchList(params, config);
3036
+ return {
3037
+ sleep: response.items,
3038
+ nextCursor: response.nextCursor,
3039
+ hasMore: response.hasMore,
3040
+ source: this.currentSource()
3041
+ };
3042
+ }
3043
+ async fetchBiometrics(params, config) {
3044
+ const response = await this.fetchList(params, config);
3045
+ return {
3046
+ biometrics: response.items,
3047
+ nextCursor: response.nextCursor,
3048
+ hasMore: response.hasMore,
3049
+ source: this.currentSource()
3050
+ };
3051
+ }
3052
+ async fetchNutrition(params, config) {
3053
+ const response = await this.fetchList(params, config);
3054
+ return {
3055
+ nutrition: response.items,
3056
+ nextCursor: response.nextCursor,
3057
+ hasMore: response.hasMore,
3058
+ source: this.currentSource()
3059
+ };
3060
+ }
3061
+ async fetchConnectionStatus(params, config) {
3062
+ const payload = await this.fetchPayload(config, params);
3063
+ return toHealthConnectionStatus(payload, params, this.currentSource());
3064
+ }
3065
+ currentSource() {
3066
+ return {
3067
+ providerKey: this.providerKey,
3068
+ transport: this.transport,
3069
+ route: this.route,
3070
+ aggregatorKey: this.aggregatorKey
3071
+ };
3072
+ }
3073
+ providerSlug() {
3074
+ return this.providerKey.replace("health.", "").replace(/-/g, "_");
3075
+ }
3076
+ unsupported(capability) {
3077
+ return new HealthProviderCapabilityError(`${this.providerKey} does not support ${capability}`);
3078
+ }
3079
+ async syncFromList(executor) {
3080
+ const result = await executor();
3081
+ const records = countResultRecords(result);
3082
+ return {
3083
+ synced: records,
3084
+ failed: 0,
3085
+ nextCursor: undefined,
3086
+ source: result.source
3087
+ };
3088
+ }
3089
+ async fetchList(params, config) {
3090
+ const payload = await this.fetchPayload(config, params);
3091
+ const items = extractList(payload, config.listKeys).map((item) => config.mapItem(item, params)).filter((item) => Boolean(item));
3092
+ const pagination = extractPagination(payload);
3093
+ return {
3094
+ items,
3095
+ nextCursor: pagination.nextCursor,
3096
+ hasMore: pagination.hasMore
3097
+ };
3098
+ }
3099
+ async fetchPayload(config, params) {
3100
+ const method = config.method ?? "GET";
3101
+ const query = config.buildQuery?.(params);
3102
+ const body = config.buildBody?.(params);
3103
+ if (this.isMcpTransport()) {
3104
+ return this.callMcpTool(config.mcpTool, {
3105
+ ...query ?? {},
3106
+ ...body ?? {}
3107
+ });
2175
3108
  }
2176
- const url = new URL(`${this.apiBaseUrl.replace(/\/$/, "")}/${resource}`);
2177
- if (method === "GET") {
2178
- for (const [key, value] of Object.entries(params)) {
3109
+ if (!config.apiPath || !this.apiBaseUrl) {
3110
+ throw new Error(`${this.providerKey} transport is missing an API path.`);
3111
+ }
3112
+ if (method === "POST") {
3113
+ return this.requestApi(config.apiPath, "POST", undefined, body);
3114
+ }
3115
+ return this.requestApi(config.apiPath, "GET", query, undefined);
3116
+ }
3117
+ isMcpTransport() {
3118
+ return this.transport.endsWith("mcp") || this.transport === "unofficial";
3119
+ }
3120
+ async requestApi(path, method, query, body) {
3121
+ const url = new URL(path, ensureTrailingSlash(this.apiBaseUrl ?? ""));
3122
+ if (query) {
3123
+ for (const [key, value] of Object.entries(query)) {
2179
3124
  if (value == null)
2180
3125
  continue;
2181
3126
  if (Array.isArray(value)) {
2182
- value.forEach((item) => {
2183
- url.searchParams.append(key, String(item));
3127
+ value.forEach((entry) => {
3128
+ if (entry != null)
3129
+ url.searchParams.append(key, String(entry));
2184
3130
  });
2185
3131
  continue;
2186
3132
  }
@@ -2189,22 +3135,22 @@ class BaseHealthProvider {
2189
3135
  }
2190
3136
  const response = await this.fetchFn(url, {
2191
3137
  method,
2192
- headers: {
2193
- "Content-Type": "application/json",
2194
- ...this.accessToken || this.apiKey ? { Authorization: `Bearer ${this.accessToken ?? this.apiKey}` } : {}
2195
- },
2196
- body: method === "POST" ? JSON.stringify(params) : undefined
3138
+ headers: this.authorizationHeaders(),
3139
+ body: method === "POST" ? JSON.stringify(body ?? {}) : undefined
2197
3140
  });
2198
- if (!response.ok) {
2199
- const errorBody = await safeResponseText(response);
2200
- throw new Error(`${this.providerKey} ${resource} failed (${response.status}): ${errorBody}`);
3141
+ if (response.status === 401 && await this.refreshAccessToken()) {
3142
+ const retryResponse = await this.fetchFn(url, {
3143
+ method,
3144
+ headers: this.authorizationHeaders(),
3145
+ body: method === "POST" ? JSON.stringify(body ?? {}) : undefined
3146
+ });
3147
+ return this.readResponsePayload(retryResponse, path);
2201
3148
  }
2202
- const data = await response.json();
2203
- return asRecord(data) ?? {};
3149
+ return this.readResponsePayload(response, path);
2204
3150
  }
2205
- async callMcpTool(resource, params) {
3151
+ async callMcpTool(toolName, args) {
2206
3152
  if (!this.mcpUrl) {
2207
- return {};
3153
+ throw new Error(`${this.providerKey} MCP URL is not configured.`);
2208
3154
  }
2209
3155
  const response = await this.fetchFn(this.mcpUrl, {
2210
3156
  method: "POST",
@@ -2217,33 +3163,94 @@ class BaseHealthProvider {
2217
3163
  id: ++this.mcpRequestId,
2218
3164
  method: "tools/call",
2219
3165
  params: {
2220
- name: `${this.providerKey.replace("health.", "")}_${resource.replace(/\//g, "_")}`,
2221
- arguments: params
3166
+ name: toolName,
3167
+ arguments: args
2222
3168
  }
2223
3169
  })
2224
3170
  });
2225
- if (!response.ok) {
2226
- const errorBody = await safeResponseText(response);
2227
- throw new Error(`${this.providerKey} MCP ${resource} failed (${response.status}): ${errorBody}`);
2228
- }
2229
- const rpcPayload = await response.json();
2230
- const rpc = asRecord(rpcPayload);
2231
- const result = asRecord(rpc?.result) ?? {};
2232
- const structured = asRecord(result.structuredContent);
2233
- if (structured)
2234
- return structured;
2235
- const data = asRecord(result.data);
2236
- if (data)
2237
- return data;
2238
- return result;
2239
- }
2240
- currentSource() {
3171
+ const payload = await this.readResponsePayload(response, toolName);
3172
+ const rpcEnvelope = asRecord(payload);
3173
+ if (!rpcEnvelope)
3174
+ return payload;
3175
+ const rpcResult = asRecord(rpcEnvelope.result);
3176
+ if (rpcResult) {
3177
+ return rpcResult.structuredContent ?? rpcResult.data ?? rpcResult;
3178
+ }
3179
+ return rpcEnvelope.structuredContent ?? rpcEnvelope.data ?? rpcEnvelope;
3180
+ }
3181
+ authorizationHeaders() {
3182
+ const token = this.accessToken ?? this.apiKey;
2241
3183
  return {
2242
- providerKey: this.providerKey,
2243
- transport: this.transport,
2244
- route: "primary"
3184
+ "Content-Type": "application/json",
3185
+ ...token ? { Authorization: `Bearer ${token}` } : {}
2245
3186
  };
2246
3187
  }
3188
+ async refreshAccessToken() {
3189
+ if (!this.oauth.tokenUrl || !this.refreshToken) {
3190
+ return false;
3191
+ }
3192
+ const tokenUrl = new URL(this.oauth.tokenUrl);
3193
+ const body = new URLSearchParams({
3194
+ grant_type: "refresh_token",
3195
+ refresh_token: this.refreshToken,
3196
+ ...this.oauth.clientId ? { client_id: this.oauth.clientId } : {},
3197
+ ...this.oauth.clientSecret ? { client_secret: this.oauth.clientSecret } : {}
3198
+ });
3199
+ const response = await this.fetchFn(tokenUrl, {
3200
+ method: "POST",
3201
+ headers: {
3202
+ "Content-Type": "application/x-www-form-urlencoded"
3203
+ },
3204
+ body: body.toString()
3205
+ });
3206
+ if (!response.ok) {
3207
+ return false;
3208
+ }
3209
+ const payload = await response.json();
3210
+ this.accessToken = payload.access_token;
3211
+ this.refreshToken = payload.refresh_token ?? this.refreshToken;
3212
+ if (typeof payload.expires_in === "number") {
3213
+ this.oauth.tokenExpiresAt = new Date(Date.now() + payload.expires_in * 1000).toISOString();
3214
+ }
3215
+ return Boolean(this.accessToken);
3216
+ }
3217
+ async readResponsePayload(response, context) {
3218
+ if (!response.ok) {
3219
+ const message = await safeReadText2(response);
3220
+ throw new Error(`${this.providerKey} request ${context} failed (${response.status}): ${message}`);
3221
+ }
3222
+ if (response.status === 204) {
3223
+ return {};
3224
+ }
3225
+ return response.json();
3226
+ }
3227
+ }
3228
+ function readHeader(headers, key) {
3229
+ const target = key.toLowerCase();
3230
+ const entry = Object.entries(headers).find(([headerKey]) => headerKey.toLowerCase() === target);
3231
+ if (!entry)
3232
+ return;
3233
+ const value = entry[1];
3234
+ return Array.isArray(value) ? value[0] : value;
3235
+ }
3236
+ function countResultRecords(result) {
3237
+ const listKeys = [
3238
+ "activities",
3239
+ "workouts",
3240
+ "sleep",
3241
+ "biometrics",
3242
+ "nutrition"
3243
+ ];
3244
+ for (const key of listKeys) {
3245
+ const value = result[key];
3246
+ if (Array.isArray(value)) {
3247
+ return value.length;
3248
+ }
3249
+ }
3250
+ return 0;
3251
+ }
3252
+ function ensureTrailingSlash(value) {
3253
+ return value.endsWith("/") ? value : `${value}/`;
2247
3254
  }
2248
3255
  function safeJsonParse(raw) {
2249
3256
  try {
@@ -2252,177 +3259,538 @@ function safeJsonParse(raw) {
2252
3259
  return { rawBody: raw };
2253
3260
  }
2254
3261
  }
2255
- function readHeader(headers, key) {
2256
- const match = Object.entries(headers).find(([headerKey]) => headerKey.toLowerCase() === key.toLowerCase());
2257
- if (!match)
2258
- return;
2259
- const value = match[1];
2260
- return Array.isArray(value) ? value[0] : value;
2261
- }
2262
- function normalizeEntityType(value) {
2263
- if (!value)
2264
- return;
2265
- if (value === "activity" || value === "workout" || value === "sleep" || value === "biometric" || value === "nutrition") {
2266
- return value;
3262
+ async function safeReadText2(response) {
3263
+ try {
3264
+ return await response.text();
3265
+ } catch {
3266
+ return response.statusText;
2267
3267
  }
2268
- return;
2269
3268
  }
2270
- function asRecord(value) {
2271
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
2272
- return;
2273
- }
2274
- return value;
3269
+
3270
+ // src/impls/health/official-health-providers.ts
3271
+ function buildSharedQuery(params) {
3272
+ return {
3273
+ tenantId: params.tenantId,
3274
+ connectionId: params.connectionId,
3275
+ userId: params.userId,
3276
+ from: params.from,
3277
+ to: params.to,
3278
+ cursor: params.cursor,
3279
+ pageSize: params.pageSize
3280
+ };
2275
3281
  }
2276
- function asArray2(value) {
2277
- return Array.isArray(value) ? value : undefined;
3282
+ function withMetricTypes(params) {
3283
+ return {
3284
+ ...buildSharedQuery(params),
3285
+ metricTypes: params.metricTypes
3286
+ };
2278
3287
  }
2279
- function readString2(record, key) {
2280
- const value = record?.[key];
2281
- return typeof value === "string" ? value : undefined;
3288
+
3289
+ class OpenWearablesHealthProvider extends BaseHealthProvider {
3290
+ upstreamProvider;
3291
+ constructor(options) {
3292
+ super({
3293
+ providerKey: options.providerKey ?? "health.openwearables",
3294
+ apiBaseUrl: options.apiBaseUrl ?? "https://api.openwearables.io",
3295
+ webhookSignatureHeader: "x-openwearables-signature",
3296
+ ...options
3297
+ });
3298
+ this.upstreamProvider = options.upstreamProvider;
3299
+ }
3300
+ async listActivities(params) {
3301
+ return this.fetchActivities(params, {
3302
+ apiPath: "/v1/activities",
3303
+ mcpTool: "openwearables_list_activities",
3304
+ buildQuery: (input) => this.withUpstreamProvider(buildSharedQuery(input)),
3305
+ mapItem: (item, input) => toHealthActivity(item, this.context(input), "activity")
3306
+ });
3307
+ }
3308
+ async listWorkouts(params) {
3309
+ return this.fetchWorkouts(params, {
3310
+ apiPath: "/v1/workouts",
3311
+ mcpTool: "openwearables_list_workouts",
3312
+ buildQuery: (input) => this.withUpstreamProvider(buildSharedQuery(input)),
3313
+ mapItem: (item, input) => toHealthWorkout(item, this.context(input))
3314
+ });
3315
+ }
3316
+ async listSleep(params) {
3317
+ return this.fetchSleep(params, {
3318
+ apiPath: "/v1/sleep",
3319
+ mcpTool: "openwearables_list_sleep",
3320
+ buildQuery: (input) => this.withUpstreamProvider(buildSharedQuery(input)),
3321
+ mapItem: (item, input) => toHealthSleep(item, this.context(input))
3322
+ });
3323
+ }
3324
+ async listBiometrics(params) {
3325
+ return this.fetchBiometrics(params, {
3326
+ apiPath: "/v1/biometrics",
3327
+ mcpTool: "openwearables_list_biometrics",
3328
+ buildQuery: (input) => this.withUpstreamProvider(withMetricTypes(input)),
3329
+ mapItem: (item, input) => toHealthBiometric(item, this.context(input))
3330
+ });
3331
+ }
3332
+ async listNutrition(params) {
3333
+ return this.fetchNutrition(params, {
3334
+ apiPath: "/v1/nutrition",
3335
+ mcpTool: "openwearables_list_nutrition",
3336
+ buildQuery: (input) => this.withUpstreamProvider(buildSharedQuery(input)),
3337
+ mapItem: (item, input) => toHealthNutrition(item, this.context(input))
3338
+ });
3339
+ }
3340
+ async getConnectionStatus(params) {
3341
+ return this.fetchConnectionStatus(params, {
3342
+ apiPath: `/v1/connections/${encodeURIComponent(params.connectionId)}/status`,
3343
+ mcpTool: "openwearables_connection_status"
3344
+ });
3345
+ }
3346
+ withUpstreamProvider(query) {
3347
+ return {
3348
+ ...query,
3349
+ ...this.upstreamProvider ? { upstreamProvider: this.upstreamProvider } : {}
3350
+ };
3351
+ }
3352
+ context(params) {
3353
+ return {
3354
+ tenantId: params.tenantId,
3355
+ connectionId: params.connectionId,
3356
+ providerKey: this.providerKey
3357
+ };
3358
+ }
3359
+ }
3360
+
3361
+ class AppleHealthBridgeProvider extends OpenWearablesHealthProvider {
3362
+ constructor(options) {
3363
+ super({
3364
+ ...options,
3365
+ providerKey: "health.apple-health",
3366
+ upstreamProvider: "apple-health"
3367
+ });
3368
+ }
2282
3369
  }
2283
- function readBoolean2(record, key) {
2284
- const value = record?.[key];
2285
- return typeof value === "boolean" ? value : undefined;
3370
+
3371
+ class WhoopHealthProvider extends BaseHealthProvider {
3372
+ constructor(options) {
3373
+ super({
3374
+ providerKey: "health.whoop",
3375
+ apiBaseUrl: options.apiBaseUrl ?? "https://api.prod.whoop.com",
3376
+ webhookSignatureHeader: "x-whoop-signature",
3377
+ oauth: {
3378
+ tokenUrl: options.oauth?.tokenUrl ?? "https://api.prod.whoop.com/oauth/oauth2/token",
3379
+ ...options.oauth
3380
+ },
3381
+ ...options
3382
+ });
3383
+ }
3384
+ async listActivities(params) {
3385
+ return this.fetchActivities(params, {
3386
+ apiPath: "/v2/activity/workout",
3387
+ mcpTool: "whoop_list_activities",
3388
+ buildQuery: buildSharedQuery,
3389
+ mapItem: (item, input) => toHealthActivity(item, this.context(input), "workout")
3390
+ });
3391
+ }
3392
+ async listWorkouts(params) {
3393
+ return this.fetchWorkouts(params, {
3394
+ apiPath: "/v2/activity/workout",
3395
+ mcpTool: "whoop_list_workouts",
3396
+ buildQuery: buildSharedQuery,
3397
+ mapItem: (item, input) => toHealthWorkout(item, this.context(input))
3398
+ });
3399
+ }
3400
+ async listSleep(params) {
3401
+ return this.fetchSleep(params, {
3402
+ apiPath: "/v2/activity/sleep",
3403
+ mcpTool: "whoop_list_sleep",
3404
+ buildQuery: buildSharedQuery,
3405
+ mapItem: (item, input) => toHealthSleep(item, this.context(input))
3406
+ });
3407
+ }
3408
+ async listBiometrics(params) {
3409
+ return this.fetchBiometrics(params, {
3410
+ apiPath: "/v2/recovery",
3411
+ mcpTool: "whoop_list_biometrics",
3412
+ buildQuery: withMetricTypes,
3413
+ mapItem: (item, input) => toHealthBiometric(item, this.context(input), "recovery_score")
3414
+ });
3415
+ }
3416
+ async listNutrition(_params) {
3417
+ throw this.unsupported("nutrition");
3418
+ }
3419
+ async getConnectionStatus(params) {
3420
+ return this.fetchConnectionStatus(params, {
3421
+ apiPath: "/v2/user/profile/basic",
3422
+ mcpTool: "whoop_connection_status"
3423
+ });
3424
+ }
3425
+ context(params) {
3426
+ return {
3427
+ tenantId: params.tenantId,
3428
+ connectionId: params.connectionId,
3429
+ providerKey: this.providerKey
3430
+ };
3431
+ }
2286
3432
  }
2287
- function readNumber(record, key) {
2288
- const value = record?.[key];
2289
- return typeof value === "number" && Number.isFinite(value) ? value : undefined;
3433
+
3434
+ class OuraHealthProvider extends BaseHealthProvider {
3435
+ constructor(options) {
3436
+ super({
3437
+ providerKey: "health.oura",
3438
+ apiBaseUrl: options.apiBaseUrl ?? "https://api.ouraring.com",
3439
+ webhookSignatureHeader: "x-oura-signature",
3440
+ oauth: {
3441
+ tokenUrl: options.oauth?.tokenUrl ?? "https://api.ouraring.com/oauth/token",
3442
+ ...options.oauth
3443
+ },
3444
+ ...options
3445
+ });
3446
+ }
3447
+ async listActivities(params) {
3448
+ return this.fetchActivities(params, {
3449
+ apiPath: "/v2/usercollection/daily_activity",
3450
+ mcpTool: "oura_list_activities",
3451
+ buildQuery: buildSharedQuery,
3452
+ mapItem: (item, input) => toHealthActivity(item, this.context(input))
3453
+ });
3454
+ }
3455
+ async listWorkouts(params) {
3456
+ return this.fetchWorkouts(params, {
3457
+ apiPath: "/v2/usercollection/workout",
3458
+ mcpTool: "oura_list_workouts",
3459
+ buildQuery: buildSharedQuery,
3460
+ mapItem: (item, input) => toHealthWorkout(item, this.context(input))
3461
+ });
3462
+ }
3463
+ async listSleep(params) {
3464
+ return this.fetchSleep(params, {
3465
+ apiPath: "/v2/usercollection/sleep",
3466
+ mcpTool: "oura_list_sleep",
3467
+ buildQuery: buildSharedQuery,
3468
+ mapItem: (item, input) => toHealthSleep(item, this.context(input))
3469
+ });
3470
+ }
3471
+ async listBiometrics(params) {
3472
+ return this.fetchBiometrics(params, {
3473
+ apiPath: "/v2/usercollection/daily_readiness",
3474
+ mcpTool: "oura_list_biometrics",
3475
+ buildQuery: withMetricTypes,
3476
+ mapItem: (item, input) => toHealthBiometric(item, this.context(input), "readiness_score")
3477
+ });
3478
+ }
3479
+ async listNutrition(_params) {
3480
+ throw this.unsupported("nutrition");
3481
+ }
3482
+ async getConnectionStatus(params) {
3483
+ return this.fetchConnectionStatus(params, {
3484
+ apiPath: "/v2/usercollection/personal_info",
3485
+ mcpTool: "oura_connection_status"
3486
+ });
3487
+ }
3488
+ context(params) {
3489
+ return {
3490
+ tenantId: params.tenantId,
3491
+ connectionId: params.connectionId,
3492
+ providerKey: this.providerKey
3493
+ };
3494
+ }
2290
3495
  }
2291
- async function safeResponseText(response) {
2292
- try {
2293
- return await response.text();
2294
- } catch {
2295
- return response.statusText;
3496
+
3497
+ class StravaHealthProvider extends BaseHealthProvider {
3498
+ constructor(options) {
3499
+ super({
3500
+ providerKey: "health.strava",
3501
+ apiBaseUrl: options.apiBaseUrl ?? "https://www.strava.com",
3502
+ webhookSignatureHeader: "x-strava-signature",
3503
+ oauth: {
3504
+ tokenUrl: options.oauth?.tokenUrl ?? "https://www.strava.com/oauth/token",
3505
+ ...options.oauth
3506
+ },
3507
+ ...options
3508
+ });
3509
+ }
3510
+ async listActivities(params) {
3511
+ return this.fetchActivities(params, {
3512
+ apiPath: "/api/v3/athlete/activities",
3513
+ mcpTool: "strava_list_activities",
3514
+ buildQuery: buildSharedQuery,
3515
+ mapItem: (item, input) => toHealthActivity(item, this.context(input))
3516
+ });
3517
+ }
3518
+ async listWorkouts(params) {
3519
+ return this.fetchWorkouts(params, {
3520
+ apiPath: "/api/v3/athlete/activities",
3521
+ mcpTool: "strava_list_workouts",
3522
+ buildQuery: buildSharedQuery,
3523
+ mapItem: (item, input) => toHealthWorkout(item, this.context(input))
3524
+ });
3525
+ }
3526
+ async listSleep(_params) {
3527
+ throw this.unsupported("sleep");
3528
+ }
3529
+ async listBiometrics(_params) {
3530
+ throw this.unsupported("biometrics");
3531
+ }
3532
+ async listNutrition(_params) {
3533
+ throw this.unsupported("nutrition");
3534
+ }
3535
+ async getConnectionStatus(params) {
3536
+ return this.fetchConnectionStatus(params, {
3537
+ apiPath: "/api/v3/athlete",
3538
+ mcpTool: "strava_connection_status"
3539
+ });
3540
+ }
3541
+ context(params) {
3542
+ return {
3543
+ tenantId: params.tenantId,
3544
+ connectionId: params.connectionId,
3545
+ providerKey: this.providerKey
3546
+ };
2296
3547
  }
2297
3548
  }
2298
3549
 
2299
- // src/impls/health/providers.ts
2300
- function createProviderOptions(options, fallbackTransport) {
2301
- return {
2302
- ...options,
2303
- transport: options.transport ?? fallbackTransport
2304
- };
2305
- }
2306
-
2307
- class OpenWearablesHealthProvider extends BaseHealthProvider {
3550
+ class FitbitHealthProvider extends BaseHealthProvider {
2308
3551
  constructor(options) {
2309
3552
  super({
2310
- providerKey: "health.openwearables",
2311
- ...createProviderOptions(options, "aggregator-api")
3553
+ providerKey: "health.fitbit",
3554
+ apiBaseUrl: options.apiBaseUrl ?? "https://api.fitbit.com",
3555
+ webhookSignatureHeader: "x-fitbit-signature",
3556
+ oauth: {
3557
+ tokenUrl: options.oauth?.tokenUrl ?? "https://api.fitbit.com/oauth2/token",
3558
+ ...options.oauth
3559
+ },
3560
+ ...options
2312
3561
  });
2313
3562
  }
2314
- }
2315
-
2316
- class WhoopHealthProvider extends BaseHealthProvider {
2317
- constructor(options) {
2318
- super({
2319
- providerKey: "health.whoop",
2320
- ...createProviderOptions(options, "official-api")
3563
+ async listActivities(params) {
3564
+ return this.fetchActivities(params, {
3565
+ apiPath: "/1/user/-/activities/list.json",
3566
+ mcpTool: "fitbit_list_activities",
3567
+ buildQuery: buildSharedQuery,
3568
+ mapItem: (item, input) => toHealthActivity(item, this.context(input))
2321
3569
  });
2322
3570
  }
2323
- }
2324
-
2325
- class AppleHealthBridgeProvider extends BaseHealthProvider {
2326
- constructor(options) {
2327
- super({
2328
- providerKey: "health.apple-health",
2329
- ...createProviderOptions(options, "aggregator-api")
3571
+ async listWorkouts(params) {
3572
+ return this.fetchWorkouts(params, {
3573
+ apiPath: "/1/user/-/activities/list.json",
3574
+ mcpTool: "fitbit_list_workouts",
3575
+ buildQuery: buildSharedQuery,
3576
+ mapItem: (item, input) => toHealthWorkout(item, this.context(input))
2330
3577
  });
2331
3578
  }
2332
- }
2333
-
2334
- class OuraHealthProvider extends BaseHealthProvider {
2335
- constructor(options) {
2336
- super({
2337
- providerKey: "health.oura",
2338
- ...createProviderOptions(options, "official-api")
3579
+ async listSleep(params) {
3580
+ return this.fetchSleep(params, {
3581
+ apiPath: "/1.2/user/-/sleep/list.json",
3582
+ mcpTool: "fitbit_list_sleep",
3583
+ buildQuery: buildSharedQuery,
3584
+ mapItem: (item, input) => toHealthSleep(item, this.context(input))
2339
3585
  });
2340
3586
  }
2341
- }
2342
-
2343
- class StravaHealthProvider extends BaseHealthProvider {
2344
- constructor(options) {
2345
- super({
2346
- providerKey: "health.strava",
2347
- ...createProviderOptions(options, "official-api")
3587
+ async listBiometrics(params) {
3588
+ return this.fetchBiometrics(params, {
3589
+ apiPath: "/1/user/-/body/log/weight/date/today/1m.json",
3590
+ mcpTool: "fitbit_list_biometrics",
3591
+ buildQuery: withMetricTypes,
3592
+ mapItem: (item, input) => toHealthBiometric(item, this.context(input), "weight")
2348
3593
  });
2349
3594
  }
2350
- }
2351
-
2352
- class GarminHealthProvider extends BaseHealthProvider {
2353
- constructor(options) {
2354
- super({
2355
- providerKey: "health.garmin",
2356
- ...createProviderOptions(options, "official-api")
3595
+ async listNutrition(params) {
3596
+ return this.fetchNutrition(params, {
3597
+ apiPath: "/1/user/-/foods/log/date/today.json",
3598
+ mcpTool: "fitbit_list_nutrition",
3599
+ buildQuery: buildSharedQuery,
3600
+ mapItem: (item, input) => toHealthNutrition(item, this.context(input))
3601
+ });
3602
+ }
3603
+ async getConnectionStatus(params) {
3604
+ return this.fetchConnectionStatus(params, {
3605
+ apiPath: "/1/user/-/profile.json",
3606
+ mcpTool: "fitbit_connection_status"
2357
3607
  });
2358
3608
  }
3609
+ context(params) {
3610
+ return {
3611
+ tenantId: params.tenantId,
3612
+ connectionId: params.connectionId,
3613
+ providerKey: this.providerKey
3614
+ };
3615
+ }
2359
3616
  }
2360
3617
 
2361
- class FitbitHealthProvider extends BaseHealthProvider {
3618
+ // src/impls/health/hybrid-health-providers.ts
3619
+ var LIMITED_PROVIDER_SLUG = {
3620
+ "health.garmin": "garmin",
3621
+ "health.myfitnesspal": "myfitnesspal",
3622
+ "health.eightsleep": "eightsleep",
3623
+ "health.peloton": "peloton"
3624
+ };
3625
+ function buildSharedQuery2(params) {
3626
+ return {
3627
+ tenantId: params.tenantId,
3628
+ connectionId: params.connectionId,
3629
+ userId: params.userId,
3630
+ from: params.from,
3631
+ to: params.to,
3632
+ cursor: params.cursor,
3633
+ pageSize: params.pageSize
3634
+ };
3635
+ }
3636
+
3637
+ class GarminHealthProvider extends OpenWearablesHealthProvider {
2362
3638
  constructor(options) {
2363
3639
  super({
2364
- providerKey: "health.fitbit",
2365
- ...createProviderOptions(options, "official-api")
3640
+ ...options,
3641
+ providerKey: "health.garmin",
3642
+ upstreamProvider: "garmin"
2366
3643
  });
2367
3644
  }
2368
3645
  }
2369
3646
 
2370
- class MyFitnessPalHealthProvider extends BaseHealthProvider {
3647
+ class MyFitnessPalHealthProvider extends OpenWearablesHealthProvider {
2371
3648
  constructor(options) {
2372
3649
  super({
3650
+ ...options,
2373
3651
  providerKey: "health.myfitnesspal",
2374
- ...createProviderOptions(options, "official-api")
3652
+ upstreamProvider: "myfitnesspal"
2375
3653
  });
2376
3654
  }
2377
3655
  }
2378
3656
 
2379
- class EightSleepHealthProvider extends BaseHealthProvider {
3657
+ class EightSleepHealthProvider extends OpenWearablesHealthProvider {
2380
3658
  constructor(options) {
2381
3659
  super({
3660
+ ...options,
2382
3661
  providerKey: "health.eightsleep",
2383
- ...createProviderOptions(options, "official-api")
3662
+ upstreamProvider: "eightsleep"
2384
3663
  });
2385
3664
  }
2386
3665
  }
2387
3666
 
2388
- class PelotonHealthProvider extends BaseHealthProvider {
3667
+ class PelotonHealthProvider extends OpenWearablesHealthProvider {
2389
3668
  constructor(options) {
2390
3669
  super({
3670
+ ...options,
2391
3671
  providerKey: "health.peloton",
2392
- ...createProviderOptions(options, "official-api")
3672
+ upstreamProvider: "peloton"
2393
3673
  });
2394
3674
  }
2395
3675
  }
2396
3676
 
2397
3677
  class UnofficialHealthAutomationProvider extends BaseHealthProvider {
3678
+ providerSlugValue;
2398
3679
  constructor(options) {
2399
3680
  super({
2400
- ...createProviderOptions(options, "unofficial"),
2401
- providerKey: options.providerKey
3681
+ ...options,
3682
+ providerKey: options.providerKey,
3683
+ webhookSignatureHeader: "x-unofficial-signature"
3684
+ });
3685
+ this.providerSlugValue = LIMITED_PROVIDER_SLUG[options.providerKey];
3686
+ }
3687
+ async listActivities(params) {
3688
+ return this.fetchActivities(params, {
3689
+ mcpTool: `${this.providerSlugValue}_list_activities`,
3690
+ buildQuery: buildSharedQuery2,
3691
+ mapItem: (item, input) => toHealthActivity(item, this.context(input), "activity")
3692
+ });
3693
+ }
3694
+ async listWorkouts(params) {
3695
+ return this.fetchWorkouts(params, {
3696
+ mcpTool: `${this.providerSlugValue}_list_workouts`,
3697
+ buildQuery: buildSharedQuery2,
3698
+ mapItem: (item, input) => toHealthWorkout(item, this.context(input))
3699
+ });
3700
+ }
3701
+ async listSleep(params) {
3702
+ return this.fetchSleep(params, {
3703
+ mcpTool: `${this.providerSlugValue}_list_sleep`,
3704
+ buildQuery: buildSharedQuery2,
3705
+ mapItem: (item, input) => toHealthSleep(item, this.context(input))
3706
+ });
3707
+ }
3708
+ async listBiometrics(params) {
3709
+ return this.fetchBiometrics(params, {
3710
+ mcpTool: `${this.providerSlugValue}_list_biometrics`,
3711
+ buildQuery: (input) => ({
3712
+ ...buildSharedQuery2(input),
3713
+ metricTypes: input.metricTypes
3714
+ }),
3715
+ mapItem: (item, input) => toHealthBiometric(item, this.context(input))
3716
+ });
3717
+ }
3718
+ async listNutrition(params) {
3719
+ return this.fetchNutrition(params, {
3720
+ mcpTool: `${this.providerSlugValue}_list_nutrition`,
3721
+ buildQuery: buildSharedQuery2,
3722
+ mapItem: (item, input) => toHealthNutrition(item, this.context(input))
2402
3723
  });
2403
3724
  }
3725
+ async getConnectionStatus(params) {
3726
+ return this.fetchConnectionStatus(params, {
3727
+ mcpTool: `${this.providerSlugValue}_connection_status`
3728
+ });
3729
+ }
3730
+ context(params) {
3731
+ return {
3732
+ tenantId: params.tenantId,
3733
+ connectionId: params.connectionId,
3734
+ providerKey: this.providerKey
3735
+ };
3736
+ }
2404
3737
  }
2405
-
2406
3738
  // src/impls/health-provider-factory.ts
2407
3739
  import {
2408
3740
  isUnofficialHealthProviderAllowed,
2409
3741
  resolveHealthStrategyOrder
2410
3742
  } from "@contractspec/integration.runtime/runtime";
3743
+ var OFFICIAL_TRANSPORT_SUPPORTED_BY_PROVIDER = {
3744
+ "health.openwearables": false,
3745
+ "health.whoop": true,
3746
+ "health.apple-health": false,
3747
+ "health.oura": true,
3748
+ "health.strava": true,
3749
+ "health.garmin": false,
3750
+ "health.fitbit": true,
3751
+ "health.myfitnesspal": false,
3752
+ "health.eightsleep": false,
3753
+ "health.peloton": false
3754
+ };
3755
+ var UNOFFICIAL_SUPPORTED_BY_PROVIDER = {
3756
+ "health.openwearables": false,
3757
+ "health.whoop": false,
3758
+ "health.apple-health": false,
3759
+ "health.oura": false,
3760
+ "health.strava": false,
3761
+ "health.garmin": true,
3762
+ "health.fitbit": false,
3763
+ "health.myfitnesspal": true,
3764
+ "health.eightsleep": true,
3765
+ "health.peloton": true
3766
+ };
2411
3767
  function createHealthProviderFromContext(context, secrets) {
2412
3768
  const providerKey = context.spec.meta.key;
2413
3769
  const config = toFactoryConfig(context.config);
2414
3770
  const strategyOrder = buildStrategyOrder(config);
2415
- const errors = [];
2416
- for (const strategy of strategyOrder) {
2417
- const provider = createHealthProviderForStrategy(providerKey, strategy, config, secrets);
3771
+ const attemptLogs = [];
3772
+ for (let index = 0;index < strategyOrder.length; index += 1) {
3773
+ const strategy = strategyOrder[index];
3774
+ if (!strategy)
3775
+ continue;
3776
+ const route = index === 0 ? "primary" : "fallback";
3777
+ if (!supportsStrategy(providerKey, strategy)) {
3778
+ attemptLogs.push(`${strategy}: unsupported by ${providerKey}`);
3779
+ continue;
3780
+ }
3781
+ if (!hasCredentialsForStrategy(strategy, config, secrets)) {
3782
+ attemptLogs.push(`${strategy}: missing credentials`);
3783
+ continue;
3784
+ }
3785
+ const provider = createHealthProviderForStrategy(providerKey, strategy, route, config, secrets);
2418
3786
  if (provider) {
2419
3787
  return provider;
2420
3788
  }
2421
- errors.push(`${strategy}: not available`);
3789
+ attemptLogs.push(`${strategy}: not available`);
2422
3790
  }
2423
- throw new Error(`Unable to resolve health provider for ${providerKey}. Strategies attempted: ${errors.join(", ")}.`);
3791
+ throw new Error(`Unable to resolve health provider for ${providerKey}. Strategies attempted: ${attemptLogs.join(", ")}.`);
2424
3792
  }
2425
- function createHealthProviderForStrategy(providerKey, strategy, config, secrets) {
3793
+ function createHealthProviderForStrategy(providerKey, strategy, route, config, secrets) {
2426
3794
  const options = {
2427
3795
  transport: strategy,
2428
3796
  apiBaseUrl: config.apiBaseUrl,
@@ -2430,10 +3798,21 @@ function createHealthProviderForStrategy(providerKey, strategy, config, secrets)
2430
3798
  apiKey: getSecretString(secrets, "apiKey"),
2431
3799
  accessToken: getSecretString(secrets, "accessToken"),
2432
3800
  mcpAccessToken: getSecretString(secrets, "mcpAccessToken"),
2433
- webhookSecret: getSecretString(secrets, "webhookSecret")
3801
+ webhookSecret: getSecretString(secrets, "webhookSecret"),
3802
+ route,
3803
+ oauth: {
3804
+ tokenUrl: config.oauthTokenUrl,
3805
+ refreshToken: getSecretString(secrets, "refreshToken"),
3806
+ clientId: getSecretString(secrets, "clientId"),
3807
+ clientSecret: getSecretString(secrets, "clientSecret"),
3808
+ tokenExpiresAt: getSecretString(secrets, "tokenExpiresAt")
3809
+ }
2434
3810
  };
2435
3811
  if (strategy === "aggregator-api" || strategy === "aggregator-mcp") {
2436
- return new OpenWearablesHealthProvider(options);
3812
+ return createAggregatorProvider(providerKey, {
3813
+ ...options,
3814
+ aggregatorKey: "health.openwearables"
3815
+ });
2437
3816
  }
2438
3817
  if (strategy === "unofficial") {
2439
3818
  if (!isUnofficialHealthProviderAllowed(providerKey, config)) {
@@ -2455,6 +3834,31 @@ function createHealthProviderForStrategy(providerKey, strategy, config, secrets)
2455
3834
  }
2456
3835
  return createOfficialProvider(providerKey, options);
2457
3836
  }
3837
+ function createAggregatorProvider(providerKey, options) {
3838
+ if (providerKey === "health.apple-health") {
3839
+ return new AppleHealthBridgeProvider(options);
3840
+ }
3841
+ if (providerKey === "health.garmin") {
3842
+ return new GarminHealthProvider(options);
3843
+ }
3844
+ if (providerKey === "health.myfitnesspal") {
3845
+ return new MyFitnessPalHealthProvider(options);
3846
+ }
3847
+ if (providerKey === "health.eightsleep") {
3848
+ return new EightSleepHealthProvider(options);
3849
+ }
3850
+ if (providerKey === "health.peloton") {
3851
+ return new PelotonHealthProvider(options);
3852
+ }
3853
+ if (providerKey === "health.openwearables") {
3854
+ return new OpenWearablesHealthProvider(options);
3855
+ }
3856
+ return new OpenWearablesHealthProvider({
3857
+ ...options,
3858
+ providerKey,
3859
+ upstreamProvider: providerKey.replace("health.", "")
3860
+ });
3861
+ }
2458
3862
  function createOfficialProvider(providerKey, options) {
2459
3863
  switch (providerKey) {
2460
3864
  case "health.openwearables":
@@ -2472,11 +3876,20 @@ function createOfficialProvider(providerKey, options) {
2472
3876
  case "health.fitbit":
2473
3877
  return new FitbitHealthProvider(options);
2474
3878
  case "health.myfitnesspal":
2475
- return new MyFitnessPalHealthProvider(options);
3879
+ return new MyFitnessPalHealthProvider({
3880
+ ...options,
3881
+ transport: "aggregator-api"
3882
+ });
2476
3883
  case "health.eightsleep":
2477
- return new EightSleepHealthProvider(options);
3884
+ return new EightSleepHealthProvider({
3885
+ ...options,
3886
+ transport: "aggregator-api"
3887
+ });
2478
3888
  case "health.peloton":
2479
- return new PelotonHealthProvider(options);
3889
+ return new PelotonHealthProvider({
3890
+ ...options,
3891
+ transport: "aggregator-api"
3892
+ });
2480
3893
  default:
2481
3894
  throw new Error(`Unsupported health provider key: ${providerKey}`);
2482
3895
  }
@@ -2489,6 +3902,7 @@ function toFactoryConfig(config) {
2489
3902
  return {
2490
3903
  apiBaseUrl: asString(record.apiBaseUrl),
2491
3904
  mcpUrl: asString(record.mcpUrl),
3905
+ oauthTokenUrl: asString(record.oauthTokenUrl),
2492
3906
  defaultTransport: normalizeTransport(record.defaultTransport),
2493
3907
  strategyOrder: normalizeTransportArray(record.strategyOrder),
2494
3908
  allowUnofficial: typeof record.allowUnofficial === "boolean" ? record.allowUnofficial : false,
@@ -2517,6 +3931,27 @@ function normalizeTransportArray(value) {
2517
3931
  const transports = value.map((item) => normalizeTransport(item)).filter((item) => Boolean(item));
2518
3932
  return transports.length > 0 ? transports : undefined;
2519
3933
  }
3934
+ function supportsStrategy(providerKey, strategy) {
3935
+ if (strategy === "official-api" || strategy === "official-mcp") {
3936
+ return OFFICIAL_TRANSPORT_SUPPORTED_BY_PROVIDER[providerKey];
3937
+ }
3938
+ if (strategy === "unofficial") {
3939
+ return UNOFFICIAL_SUPPORTED_BY_PROVIDER[providerKey];
3940
+ }
3941
+ return true;
3942
+ }
3943
+ function hasCredentialsForStrategy(strategy, config, secrets) {
3944
+ const hasApiCredential = Boolean(getSecretString(secrets, "accessToken")) || Boolean(getSecretString(secrets, "apiKey"));
3945
+ const hasMcpCredential = Boolean(getSecretString(secrets, "mcpAccessToken")) || hasApiCredential;
3946
+ if (strategy === "official-api" || strategy === "aggregator-api") {
3947
+ return hasApiCredential;
3948
+ }
3949
+ if (strategy === "official-mcp" || strategy === "aggregator-mcp") {
3950
+ return Boolean(config.mcpUrl) && hasMcpCredential;
3951
+ }
3952
+ const hasAutomationCredential = hasMcpCredential || Boolean(getSecretString(secrets, "username")) && Boolean(getSecretString(secrets, "password"));
3953
+ return Boolean(config.mcpUrl) && hasAutomationCredential;
3954
+ }
2520
3955
  function getSecretString(secrets, key) {
2521
3956
  const value = secrets[key];
2522
3957
  return typeof value === "string" && value.trim().length > 0 ? value : undefined;
@@ -2792,45 +4227,474 @@ function mapFinishReason(reason) {
2792
4227
  }
2793
4228
  }
2794
4229
 
2795
- // src/impls/mistral-embedding.ts
2796
- import { Mistral as Mistral2 } from "@mistralai/mistralai";
4230
+ // src/impls/mistral-embedding.ts
4231
+ import { Mistral as Mistral2 } from "@mistralai/mistralai";
4232
+
4233
+ class MistralEmbeddingProvider {
4234
+ client;
4235
+ defaultModel;
4236
+ constructor(options) {
4237
+ if (!options.apiKey) {
4238
+ throw new Error("MistralEmbeddingProvider requires an apiKey");
4239
+ }
4240
+ this.client = options.client ?? new Mistral2({
4241
+ apiKey: options.apiKey,
4242
+ serverURL: options.serverURL
4243
+ });
4244
+ this.defaultModel = options.defaultModel ?? "mistral-embed";
4245
+ }
4246
+ async embedDocuments(documents, options) {
4247
+ if (documents.length === 0)
4248
+ return [];
4249
+ const model = options?.model ?? this.defaultModel;
4250
+ const response = await this.client.embeddings.create({
4251
+ model,
4252
+ inputs: documents.map((doc) => doc.text)
4253
+ });
4254
+ return response.data.map((item, index) => ({
4255
+ id: documents[index]?.id ?? (item.index != null ? `embedding-${item.index}` : `embedding-${index}`),
4256
+ vector: item.embedding ?? [],
4257
+ dimensions: item.embedding?.length ?? 0,
4258
+ model: response.model,
4259
+ metadata: documents[index]?.metadata ? Object.fromEntries(Object.entries(documents[index]?.metadata ?? {}).map(([key, value]) => [key, String(value)])) : undefined
4260
+ }));
4261
+ }
4262
+ async embedQuery(query, options) {
4263
+ const [result] = await this.embedDocuments([{ id: "query", text: query }], options);
4264
+ if (!result) {
4265
+ throw new Error("Failed to compute embedding for query");
4266
+ }
4267
+ return result;
4268
+ }
4269
+ }
4270
+
4271
+ // src/impls/mistral-stt.ts
4272
+ var DEFAULT_BASE_URL4 = "https://api.mistral.ai/v1";
4273
+ var DEFAULT_MODEL = "voxtral-mini-latest";
4274
+ var AUDIO_MIME_BY_FORMAT = {
4275
+ mp3: "audio/mpeg",
4276
+ wav: "audio/wav",
4277
+ ogg: "audio/ogg",
4278
+ pcm: "audio/pcm",
4279
+ opus: "audio/opus"
4280
+ };
4281
+
4282
+ class MistralSttProvider {
4283
+ apiKey;
4284
+ defaultModel;
4285
+ defaultLanguage;
4286
+ baseUrl;
4287
+ fetchImpl;
4288
+ constructor(options) {
4289
+ if (!options.apiKey) {
4290
+ throw new Error("MistralSttProvider requires an apiKey");
4291
+ }
4292
+ this.apiKey = options.apiKey;
4293
+ this.defaultModel = options.defaultModel ?? DEFAULT_MODEL;
4294
+ this.defaultLanguage = options.defaultLanguage;
4295
+ this.baseUrl = normalizeBaseUrl(options.serverURL ?? DEFAULT_BASE_URL4);
4296
+ this.fetchImpl = options.fetchImpl ?? fetch;
4297
+ }
4298
+ async transcribe(input) {
4299
+ const formData = new FormData;
4300
+ const model = input.model ?? this.defaultModel;
4301
+ const mimeType = AUDIO_MIME_BY_FORMAT[input.audio.format] ?? "audio/wav";
4302
+ const fileName = `audio.${input.audio.format}`;
4303
+ const audioBytes = new Uint8Array(input.audio.data);
4304
+ const blob = new Blob([audioBytes], { type: mimeType });
4305
+ formData.append("file", blob, fileName);
4306
+ formData.append("model", model);
4307
+ formData.append("response_format", "verbose_json");
4308
+ const language = input.language ?? this.defaultLanguage;
4309
+ if (language) {
4310
+ formData.append("language", language);
4311
+ }
4312
+ const response = await this.fetchImpl(`${this.baseUrl}/audio/transcriptions`, {
4313
+ method: "POST",
4314
+ headers: {
4315
+ Authorization: `Bearer ${this.apiKey}`
4316
+ },
4317
+ body: formData
4318
+ });
4319
+ if (!response.ok) {
4320
+ const body = await response.text();
4321
+ throw new Error(`Mistral transcription request failed (${response.status}): ${body}`);
4322
+ }
4323
+ const payload = await response.json();
4324
+ return toTranscriptionResult(payload, input);
4325
+ }
4326
+ }
4327
+ function toTranscriptionResult(payload, input) {
4328
+ const record = asRecord2(payload);
4329
+ const text = readString3(record, "text") ?? "";
4330
+ const language = readString3(record, "language") ?? input.language ?? "unknown";
4331
+ const segments = parseSegments(record);
4332
+ if (segments.length === 0 && text.length > 0) {
4333
+ segments.push({
4334
+ text,
4335
+ startMs: 0,
4336
+ endMs: input.audio.durationMs ?? 0
4337
+ });
4338
+ }
4339
+ const durationMs = input.audio.durationMs ?? segments.reduce((max, segment) => Math.max(max, segment.endMs), 0);
4340
+ const topLevelWords = parseWordTimings(record.words);
4341
+ const flattenedWords = segments.flatMap((segment) => segment.wordTimings ?? []);
4342
+ const wordTimings = topLevelWords.length > 0 ? topLevelWords : flattenedWords.length > 0 ? flattenedWords : undefined;
4343
+ const speakers = dedupeSpeakers(segments);
4344
+ return {
4345
+ text,
4346
+ segments,
4347
+ language,
4348
+ durationMs,
4349
+ speakers: speakers.length > 0 ? speakers : undefined,
4350
+ wordTimings
4351
+ };
4352
+ }
4353
+ function parseSegments(record) {
4354
+ if (!Array.isArray(record.segments)) {
4355
+ return [];
4356
+ }
4357
+ const parsed = [];
4358
+ for (const entry of record.segments) {
4359
+ const segmentRecord = asRecord2(entry);
4360
+ const text = readString3(segmentRecord, "text");
4361
+ if (!text) {
4362
+ continue;
4363
+ }
4364
+ const startSeconds = readNumber2(segmentRecord, "start") ?? 0;
4365
+ const endSeconds = readNumber2(segmentRecord, "end") ?? startSeconds;
4366
+ parsed.push({
4367
+ text,
4368
+ startMs: secondsToMs(startSeconds),
4369
+ endMs: secondsToMs(endSeconds),
4370
+ speakerId: readString3(segmentRecord, "speaker") ?? undefined,
4371
+ confidence: readNumber2(segmentRecord, "confidence"),
4372
+ wordTimings: parseWordTimings(segmentRecord.words)
4373
+ });
4374
+ }
4375
+ return parsed;
4376
+ }
4377
+ function parseWordTimings(value) {
4378
+ if (!Array.isArray(value)) {
4379
+ return [];
4380
+ }
4381
+ const words = [];
4382
+ for (const entry of value) {
4383
+ const wordRecord = asRecord2(entry);
4384
+ const word = readString3(wordRecord, "word");
4385
+ const startSeconds = readNumber2(wordRecord, "start");
4386
+ const endSeconds = readNumber2(wordRecord, "end");
4387
+ if (!word || startSeconds == null || endSeconds == null) {
4388
+ continue;
4389
+ }
4390
+ words.push({
4391
+ word,
4392
+ startMs: secondsToMs(startSeconds),
4393
+ endMs: secondsToMs(endSeconds),
4394
+ confidence: readNumber2(wordRecord, "confidence")
4395
+ });
4396
+ }
4397
+ return words;
4398
+ }
4399
+ function dedupeSpeakers(segments) {
4400
+ const seen = new Set;
4401
+ const speakers = [];
4402
+ for (const segment of segments) {
4403
+ if (!segment.speakerId || seen.has(segment.speakerId)) {
4404
+ continue;
4405
+ }
4406
+ seen.add(segment.speakerId);
4407
+ speakers.push({
4408
+ id: segment.speakerId,
4409
+ name: segment.speakerName
4410
+ });
4411
+ }
4412
+ return speakers;
4413
+ }
4414
+ function normalizeBaseUrl(url) {
4415
+ return url.endsWith("/") ? url.slice(0, -1) : url;
4416
+ }
4417
+ function asRecord2(value) {
4418
+ if (value && typeof value === "object") {
4419
+ return value;
4420
+ }
4421
+ return {};
4422
+ }
4423
+ function readString3(record, key) {
4424
+ const value = record[key];
4425
+ return typeof value === "string" ? value : undefined;
4426
+ }
4427
+ function readNumber2(record, key) {
4428
+ const value = record[key];
4429
+ return typeof value === "number" ? value : undefined;
4430
+ }
4431
+ function secondsToMs(value) {
4432
+ return Math.round(value * 1000);
4433
+ }
4434
+
4435
+ // src/impls/mistral-conversational.session.ts
4436
+ class MistralConversationSession {
4437
+ events;
4438
+ queue = new AsyncEventQueue;
4439
+ turns = [];
4440
+ history = [];
4441
+ sessionId = crypto.randomUUID();
4442
+ startedAt = Date.now();
4443
+ sessionConfig;
4444
+ defaultModel;
4445
+ complete;
4446
+ sttProvider;
4447
+ pending = Promise.resolve();
4448
+ closed = false;
4449
+ closedSummary;
4450
+ constructor(options) {
4451
+ this.sessionConfig = options.sessionConfig;
4452
+ this.defaultModel = options.defaultModel;
4453
+ this.complete = options.complete;
4454
+ this.sttProvider = options.sttProvider;
4455
+ this.events = this.queue;
4456
+ this.queue.push({
4457
+ type: "session_started",
4458
+ sessionId: this.sessionId
4459
+ });
4460
+ }
4461
+ sendAudio(chunk) {
4462
+ if (this.closed) {
4463
+ return;
4464
+ }
4465
+ this.pending = this.pending.then(async () => {
4466
+ const transcription = await this.sttProvider.transcribe({
4467
+ audio: {
4468
+ data: chunk,
4469
+ format: this.sessionConfig.inputFormat ?? "pcm",
4470
+ sampleRateHz: 16000
4471
+ },
4472
+ language: this.sessionConfig.language
4473
+ });
4474
+ const transcriptText = transcription.text.trim();
4475
+ if (transcriptText.length > 0) {
4476
+ await this.handleUserText(transcriptText);
4477
+ }
4478
+ }).catch((error) => {
4479
+ this.emitError(error);
4480
+ });
4481
+ }
4482
+ sendText(text) {
4483
+ if (this.closed) {
4484
+ return;
4485
+ }
4486
+ const normalized = text.trim();
4487
+ if (normalized.length === 0) {
4488
+ return;
4489
+ }
4490
+ this.pending = this.pending.then(() => this.handleUserText(normalized)).catch((error) => {
4491
+ this.emitError(error);
4492
+ });
4493
+ }
4494
+ interrupt() {
4495
+ if (this.closed) {
4496
+ return;
4497
+ }
4498
+ this.queue.push({
4499
+ type: "error",
4500
+ error: new Error("Interrupt is not supported for non-streaming sessions.")
4501
+ });
4502
+ }
4503
+ async close() {
4504
+ if (this.closedSummary) {
4505
+ return this.closedSummary;
4506
+ }
4507
+ this.closed = true;
4508
+ await this.pending;
4509
+ const durationMs = Date.now() - this.startedAt;
4510
+ const summary = {
4511
+ sessionId: this.sessionId,
4512
+ durationMs,
4513
+ turns: this.turns.map((turn) => ({
4514
+ role: turn.role === "assistant" ? "agent" : turn.role,
4515
+ text: turn.text,
4516
+ startMs: turn.startMs,
4517
+ endMs: turn.endMs
4518
+ })),
4519
+ transcript: this.turns.map((turn) => `${turn.role}: ${turn.text}`).join(`
4520
+ `)
4521
+ };
4522
+ this.closedSummary = summary;
4523
+ this.queue.push({
4524
+ type: "session_ended",
4525
+ reason: "closed_by_client",
4526
+ durationMs
4527
+ });
4528
+ this.queue.close();
4529
+ return summary;
4530
+ }
4531
+ async handleUserText(text) {
4532
+ if (this.closed) {
4533
+ return;
4534
+ }
4535
+ const userStart = Date.now();
4536
+ this.queue.push({ type: "user_speech_started" });
4537
+ this.queue.push({ type: "user_speech_ended", transcript: text });
4538
+ this.queue.push({
4539
+ type: "transcript",
4540
+ role: "user",
4541
+ text,
4542
+ timestamp: userStart
4543
+ });
4544
+ this.turns.push({
4545
+ role: "user",
4546
+ text,
4547
+ startMs: userStart,
4548
+ endMs: Date.now()
4549
+ });
4550
+ this.history.push({ role: "user", content: text });
4551
+ const assistantStart = Date.now();
4552
+ const assistantText = await this.complete(this.history, {
4553
+ ...this.sessionConfig,
4554
+ llmModel: this.sessionConfig.llmModel ?? this.defaultModel
4555
+ });
4556
+ if (this.closed) {
4557
+ return;
4558
+ }
4559
+ const normalizedAssistantText = assistantText.trim();
4560
+ const finalAssistantText = normalizedAssistantText.length > 0 ? normalizedAssistantText : "I was unable to produce a response.";
4561
+ this.queue.push({
4562
+ type: "agent_speech_started",
4563
+ text: finalAssistantText
4564
+ });
4565
+ this.queue.push({
4566
+ type: "transcript",
4567
+ role: "agent",
4568
+ text: finalAssistantText,
4569
+ timestamp: assistantStart
4570
+ });
4571
+ this.queue.push({ type: "agent_speech_ended" });
4572
+ this.turns.push({
4573
+ role: "assistant",
4574
+ text: finalAssistantText,
4575
+ startMs: assistantStart,
4576
+ endMs: Date.now()
4577
+ });
4578
+ this.history.push({ role: "assistant", content: finalAssistantText });
4579
+ }
4580
+ emitError(error) {
4581
+ if (this.closed) {
4582
+ return;
4583
+ }
4584
+ this.queue.push({ type: "error", error: toError(error) });
4585
+ }
4586
+ }
4587
+ function toError(error) {
4588
+ if (error instanceof Error) {
4589
+ return error;
4590
+ }
4591
+ return new Error(String(error));
4592
+ }
4593
+
4594
+ // src/impls/mistral-conversational.ts
4595
+ var DEFAULT_BASE_URL5 = "https://api.mistral.ai/v1";
4596
+ var DEFAULT_MODEL2 = "mistral-small-latest";
4597
+ var DEFAULT_VOICE = "default";
2797
4598
 
2798
- class MistralEmbeddingProvider {
2799
- client;
4599
+ class MistralConversationalProvider {
4600
+ apiKey;
2800
4601
  defaultModel;
4602
+ defaultVoiceId;
4603
+ baseUrl;
4604
+ fetchImpl;
4605
+ sttProvider;
2801
4606
  constructor(options) {
2802
4607
  if (!options.apiKey) {
2803
- throw new Error("MistralEmbeddingProvider requires an apiKey");
4608
+ throw new Error("MistralConversationalProvider requires an apiKey");
2804
4609
  }
2805
- this.client = options.client ?? new Mistral2({
4610
+ this.apiKey = options.apiKey;
4611
+ this.defaultModel = options.defaultModel ?? DEFAULT_MODEL2;
4612
+ this.defaultVoiceId = options.defaultVoiceId ?? DEFAULT_VOICE;
4613
+ this.baseUrl = normalizeBaseUrl2(options.serverURL ?? DEFAULT_BASE_URL5);
4614
+ this.fetchImpl = options.fetchImpl ?? fetch;
4615
+ this.sttProvider = options.sttProvider ?? new MistralSttProvider({
2806
4616
  apiKey: options.apiKey,
2807
- serverURL: options.serverURL
4617
+ defaultModel: options.sttOptions?.defaultModel,
4618
+ defaultLanguage: options.sttOptions?.defaultLanguage,
4619
+ serverURL: options.sttOptions?.serverURL ?? options.serverURL,
4620
+ fetchImpl: this.fetchImpl
2808
4621
  });
2809
- this.defaultModel = options.defaultModel ?? "mistral-embed";
2810
4622
  }
2811
- async embedDocuments(documents, options) {
2812
- if (documents.length === 0)
2813
- return [];
2814
- const model = options?.model ?? this.defaultModel;
2815
- const response = await this.client.embeddings.create({
2816
- model,
2817
- inputs: documents.map((doc) => doc.text)
4623
+ async startSession(config) {
4624
+ return new MistralConversationSession({
4625
+ sessionConfig: {
4626
+ ...config,
4627
+ voiceId: config.voiceId || this.defaultVoiceId
4628
+ },
4629
+ defaultModel: this.defaultModel,
4630
+ complete: (history, sessionConfig) => this.completeConversation(history, sessionConfig),
4631
+ sttProvider: this.sttProvider
2818
4632
  });
2819
- return response.data.map((item, index) => ({
2820
- id: documents[index]?.id ?? (item.index != null ? `embedding-${item.index}` : `embedding-${index}`),
2821
- vector: item.embedding ?? [],
2822
- dimensions: item.embedding?.length ?? 0,
2823
- model: response.model,
2824
- metadata: documents[index]?.metadata ? Object.fromEntries(Object.entries(documents[index]?.metadata ?? {}).map(([key, value]) => [key, String(value)])) : undefined
2825
- }));
2826
4633
  }
2827
- async embedQuery(query, options) {
2828
- const [result] = await this.embedDocuments([{ id: "query", text: query }], options);
2829
- if (!result) {
2830
- throw new Error("Failed to compute embedding for query");
4634
+ async listVoices() {
4635
+ return [
4636
+ {
4637
+ id: this.defaultVoiceId,
4638
+ name: "Mistral Default Voice",
4639
+ description: "Default conversational voice profile.",
4640
+ capabilities: ["conversational"]
4641
+ }
4642
+ ];
4643
+ }
4644
+ async completeConversation(history, sessionConfig) {
4645
+ const model = sessionConfig.llmModel ?? this.defaultModel;
4646
+ const messages = [];
4647
+ if (sessionConfig.systemPrompt) {
4648
+ messages.push({ role: "system", content: sessionConfig.systemPrompt });
2831
4649
  }
2832
- return result;
4650
+ for (const item of history) {
4651
+ messages.push({ role: item.role, content: item.content });
4652
+ }
4653
+ const response = await this.fetchImpl(`${this.baseUrl}/chat/completions`, {
4654
+ method: "POST",
4655
+ headers: {
4656
+ Authorization: `Bearer ${this.apiKey}`,
4657
+ "Content-Type": "application/json"
4658
+ },
4659
+ body: JSON.stringify({
4660
+ model,
4661
+ messages
4662
+ })
4663
+ });
4664
+ if (!response.ok) {
4665
+ const body = await response.text();
4666
+ throw new Error(`Mistral conversational request failed (${response.status}): ${body}`);
4667
+ }
4668
+ const payload = await response.json();
4669
+ return readAssistantText(payload);
4670
+ }
4671
+ }
4672
+ function normalizeBaseUrl2(url) {
4673
+ return url.endsWith("/") ? url.slice(0, -1) : url;
4674
+ }
4675
+ function readAssistantText(payload) {
4676
+ const record = asRecord3(payload);
4677
+ const choices = Array.isArray(record.choices) ? record.choices : [];
4678
+ const firstChoice = asRecord3(choices[0]);
4679
+ const message = asRecord3(firstChoice.message);
4680
+ if (typeof message.content === "string") {
4681
+ return message.content;
4682
+ }
4683
+ if (Array.isArray(message.content)) {
4684
+ const textParts = message.content.map((part) => {
4685
+ const entry = asRecord3(part);
4686
+ const text = entry.text;
4687
+ return typeof text === "string" ? text : "";
4688
+ }).filter((text) => text.length > 0);
4689
+ return textParts.join("");
4690
+ }
4691
+ return "";
4692
+ }
4693
+ function asRecord3(value) {
4694
+ if (value && typeof value === "object") {
4695
+ return value;
2833
4696
  }
4697
+ return {};
2834
4698
  }
2835
4699
 
2836
4700
  // src/impls/qdrant-vector.ts
@@ -3232,7 +5096,7 @@ function distanceToScore(distance, metric) {
3232
5096
 
3233
5097
  // src/impls/stripe-payments.ts
3234
5098
  import Stripe from "stripe";
3235
- var API_VERSION = "2026-01-28.clover";
5099
+ var API_VERSION = "2026-02-25.clover";
3236
5100
 
3237
5101
  class StripePaymentsProvider {
3238
5102
  stripe;
@@ -3897,6 +5761,318 @@ function mapStatus(status) {
3897
5761
  }
3898
5762
  }
3899
5763
 
5764
+ // src/impls/messaging-slack.ts
5765
+ class SlackMessagingProvider {
5766
+ botToken;
5767
+ defaultChannelId;
5768
+ apiBaseUrl;
5769
+ constructor(options) {
5770
+ this.botToken = options.botToken;
5771
+ this.defaultChannelId = options.defaultChannelId;
5772
+ this.apiBaseUrl = options.apiBaseUrl ?? "https://slack.com/api";
5773
+ }
5774
+ async sendMessage(input) {
5775
+ const channel = input.channelId ?? input.recipientId ?? this.defaultChannelId;
5776
+ if (!channel) {
5777
+ throw new Error("Slack sendMessage requires channelId, recipientId, or defaultChannelId.");
5778
+ }
5779
+ const payload = {
5780
+ channel,
5781
+ text: input.text,
5782
+ mrkdwn: input.markdown ?? true,
5783
+ thread_ts: input.threadId
5784
+ };
5785
+ const response = await fetch(`${this.apiBaseUrl}/chat.postMessage`, {
5786
+ method: "POST",
5787
+ headers: {
5788
+ authorization: `Bearer ${this.botToken}`,
5789
+ "content-type": "application/json"
5790
+ },
5791
+ body: JSON.stringify(payload)
5792
+ });
5793
+ const body = await response.json();
5794
+ if (!response.ok || !body.ok || !body.ts) {
5795
+ throw new Error(`Slack sendMessage failed: ${body.error ?? `HTTP_${response.status}`}`);
5796
+ }
5797
+ return {
5798
+ id: `slack:${body.channel ?? channel}:${body.ts}`,
5799
+ providerMessageId: body.ts,
5800
+ status: "sent",
5801
+ sentAt: new Date,
5802
+ metadata: {
5803
+ channelId: body.channel ?? channel
5804
+ }
5805
+ };
5806
+ }
5807
+ async updateMessage(messageId, input) {
5808
+ const channel = input.channelId ?? this.defaultChannelId;
5809
+ if (!channel) {
5810
+ throw new Error("Slack updateMessage requires channelId or defaultChannelId.");
5811
+ }
5812
+ const response = await fetch(`${this.apiBaseUrl}/chat.update`, {
5813
+ method: "POST",
5814
+ headers: {
5815
+ authorization: `Bearer ${this.botToken}`,
5816
+ "content-type": "application/json"
5817
+ },
5818
+ body: JSON.stringify({
5819
+ channel,
5820
+ ts: messageId,
5821
+ text: input.text,
5822
+ mrkdwn: input.markdown ?? true
5823
+ })
5824
+ });
5825
+ const body = await response.json();
5826
+ if (!response.ok || !body.ok || !body.ts) {
5827
+ throw new Error(`Slack updateMessage failed: ${body.error ?? `HTTP_${response.status}`}`);
5828
+ }
5829
+ return {
5830
+ id: `slack:${body.channel ?? channel}:${body.ts}`,
5831
+ providerMessageId: body.ts,
5832
+ status: "sent",
5833
+ sentAt: new Date,
5834
+ metadata: {
5835
+ channelId: body.channel ?? channel
5836
+ }
5837
+ };
5838
+ }
5839
+ }
5840
+
5841
+ // src/impls/messaging-github.ts
5842
+ class GithubMessagingProvider {
5843
+ token;
5844
+ defaultOwner;
5845
+ defaultRepo;
5846
+ apiBaseUrl;
5847
+ constructor(options) {
5848
+ this.token = options.token;
5849
+ this.defaultOwner = options.defaultOwner;
5850
+ this.defaultRepo = options.defaultRepo;
5851
+ this.apiBaseUrl = options.apiBaseUrl ?? "https://api.github.com";
5852
+ }
5853
+ async sendMessage(input) {
5854
+ const target = this.resolveTarget(input);
5855
+ const response = await fetch(`${this.apiBaseUrl}/repos/${target.owner}/${target.repo}/issues/${target.issueNumber}/comments`, {
5856
+ method: "POST",
5857
+ headers: {
5858
+ authorization: `Bearer ${this.token}`,
5859
+ accept: "application/vnd.github+json",
5860
+ "content-type": "application/json"
5861
+ },
5862
+ body: JSON.stringify({ body: input.text })
5863
+ });
5864
+ const body = await response.json();
5865
+ if (!response.ok || !body.id) {
5866
+ throw new Error(`GitHub sendMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
5867
+ }
5868
+ return {
5869
+ id: String(body.id),
5870
+ providerMessageId: body.node_id,
5871
+ status: "sent",
5872
+ sentAt: new Date,
5873
+ metadata: {
5874
+ url: body.html_url ?? "",
5875
+ owner: target.owner,
5876
+ repo: target.repo,
5877
+ issueNumber: String(target.issueNumber)
5878
+ }
5879
+ };
5880
+ }
5881
+ async updateMessage(messageId, input) {
5882
+ const owner = input.metadata?.owner ?? this.defaultOwner;
5883
+ const repo = input.metadata?.repo ?? this.defaultRepo;
5884
+ if (!owner || !repo) {
5885
+ throw new Error("GitHub updateMessage requires owner and repo metadata.");
5886
+ }
5887
+ const response = await fetch(`${this.apiBaseUrl}/repos/${owner}/${repo}/issues/comments/${messageId}`, {
5888
+ method: "PATCH",
5889
+ headers: {
5890
+ authorization: `Bearer ${this.token}`,
5891
+ accept: "application/vnd.github+json",
5892
+ "content-type": "application/json"
5893
+ },
5894
+ body: JSON.stringify({ body: input.text })
5895
+ });
5896
+ const body = await response.json();
5897
+ if (!response.ok || !body.id) {
5898
+ throw new Error(`GitHub updateMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
5899
+ }
5900
+ return {
5901
+ id: String(body.id),
5902
+ providerMessageId: body.node_id,
5903
+ status: "sent",
5904
+ sentAt: new Date,
5905
+ metadata: {
5906
+ url: body.html_url ?? "",
5907
+ owner,
5908
+ repo
5909
+ }
5910
+ };
5911
+ }
5912
+ resolveTarget(input) {
5913
+ const parsedRecipient = parseRecipient(input.recipientId);
5914
+ const owner = parsedRecipient?.owner ?? this.defaultOwner;
5915
+ const repo = parsedRecipient?.repo ?? this.defaultRepo;
5916
+ const issueNumber = parsedRecipient?.issueNumber ?? parseIssueNumber(input.threadId);
5917
+ if (!owner || !repo || issueNumber == null) {
5918
+ throw new Error("GitHub sendMessage requires owner/repo and issueNumber (use recipientId like owner/repo#123 or provide defaults + threadId).");
5919
+ }
5920
+ return {
5921
+ owner,
5922
+ repo,
5923
+ issueNumber
5924
+ };
5925
+ }
5926
+ }
5927
+ function parseRecipient(value) {
5928
+ if (!value)
5929
+ return null;
5930
+ const match = value.trim().match(/^([^/]+)\/([^#]+)#(\d+)$/);
5931
+ if (!match)
5932
+ return null;
5933
+ const owner = match[1];
5934
+ const repo = match[2];
5935
+ const issueNumber = Number(match[3]);
5936
+ if (!owner || !repo || !Number.isInteger(issueNumber)) {
5937
+ return null;
5938
+ }
5939
+ return { owner, repo, issueNumber };
5940
+ }
5941
+ function parseIssueNumber(value) {
5942
+ if (!value)
5943
+ return null;
5944
+ const numeric = Number(value);
5945
+ return Number.isInteger(numeric) ? numeric : null;
5946
+ }
5947
+
5948
+ // src/impls/messaging-whatsapp-meta.ts
5949
+ class MetaWhatsappMessagingProvider {
5950
+ accessToken;
5951
+ phoneNumberId;
5952
+ apiVersion;
5953
+ constructor(options) {
5954
+ this.accessToken = options.accessToken;
5955
+ this.phoneNumberId = options.phoneNumberId;
5956
+ this.apiVersion = options.apiVersion ?? "v22.0";
5957
+ }
5958
+ async sendMessage(input) {
5959
+ const to = input.recipientId;
5960
+ if (!to) {
5961
+ throw new Error("Meta WhatsApp sendMessage requires recipientId.");
5962
+ }
5963
+ const response = await fetch(`https://graph.facebook.com/${this.apiVersion}/${this.phoneNumberId}/messages`, {
5964
+ method: "POST",
5965
+ headers: {
5966
+ authorization: `Bearer ${this.accessToken}`,
5967
+ "content-type": "application/json"
5968
+ },
5969
+ body: JSON.stringify({
5970
+ messaging_product: "whatsapp",
5971
+ to,
5972
+ type: "text",
5973
+ text: {
5974
+ body: input.text,
5975
+ preview_url: false
5976
+ }
5977
+ })
5978
+ });
5979
+ const body = await response.json();
5980
+ const messageId = body.messages?.[0]?.id;
5981
+ if (!response.ok || !messageId) {
5982
+ const errorCode = body.error?.code != null ? String(body.error.code) : "";
5983
+ throw new Error(`Meta WhatsApp sendMessage failed: ${body.error?.message ?? `HTTP_${response.status}`}${errorCode ? ` (${errorCode})` : ""}`);
5984
+ }
5985
+ return {
5986
+ id: messageId,
5987
+ providerMessageId: messageId,
5988
+ status: "sent",
5989
+ sentAt: new Date,
5990
+ metadata: {
5991
+ phoneNumberId: this.phoneNumberId
5992
+ }
5993
+ };
5994
+ }
5995
+ }
5996
+
5997
+ // src/impls/messaging-whatsapp-twilio.ts
5998
+ import { Buffer as Buffer4 } from "node:buffer";
5999
+
6000
+ class TwilioWhatsappMessagingProvider {
6001
+ accountSid;
6002
+ authToken;
6003
+ fromNumber;
6004
+ constructor(options) {
6005
+ this.accountSid = options.accountSid;
6006
+ this.authToken = options.authToken;
6007
+ this.fromNumber = options.fromNumber;
6008
+ }
6009
+ async sendMessage(input) {
6010
+ const to = normalizeWhatsappAddress(input.recipientId);
6011
+ const from = normalizeWhatsappAddress(input.channelId ?? this.fromNumber);
6012
+ if (!to) {
6013
+ throw new Error("Twilio WhatsApp sendMessage requires recipientId.");
6014
+ }
6015
+ if (!from) {
6016
+ throw new Error("Twilio WhatsApp sendMessage requires channelId or configured fromNumber.");
6017
+ }
6018
+ const params = new URLSearchParams;
6019
+ params.set("To", to);
6020
+ params.set("From", from);
6021
+ params.set("Body", input.text);
6022
+ const auth = Buffer4.from(`${this.accountSid}:${this.authToken}`).toString("base64");
6023
+ const response = await fetch(`https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}/Messages.json`, {
6024
+ method: "POST",
6025
+ headers: {
6026
+ authorization: `Basic ${auth}`,
6027
+ "content-type": "application/x-www-form-urlencoded"
6028
+ },
6029
+ body: params.toString()
6030
+ });
6031
+ const body = await response.json();
6032
+ if (!response.ok || !body.sid) {
6033
+ throw new Error(`Twilio WhatsApp sendMessage failed: ${body.error_message ?? `HTTP_${response.status}`}`);
6034
+ }
6035
+ return {
6036
+ id: body.sid,
6037
+ providerMessageId: body.sid,
6038
+ status: mapTwilioStatus(body.status),
6039
+ sentAt: new Date,
6040
+ errorCode: body.error_code != null ? String(body.error_code) : undefined,
6041
+ errorMessage: body.error_message ?? undefined,
6042
+ metadata: {
6043
+ from,
6044
+ to
6045
+ }
6046
+ };
6047
+ }
6048
+ }
6049
+ function normalizeWhatsappAddress(value) {
6050
+ if (!value)
6051
+ return null;
6052
+ if (value.startsWith("whatsapp:"))
6053
+ return value;
6054
+ return `whatsapp:${value}`;
6055
+ }
6056
+ function mapTwilioStatus(status) {
6057
+ switch (status) {
6058
+ case "queued":
6059
+ case "accepted":
6060
+ case "scheduled":
6061
+ return "queued";
6062
+ case "sending":
6063
+ return "sending";
6064
+ case "delivered":
6065
+ return "delivered";
6066
+ case "failed":
6067
+ case "undelivered":
6068
+ case "canceled":
6069
+ return "failed";
6070
+ case "sent":
6071
+ default:
6072
+ return "sent";
6073
+ }
6074
+ }
6075
+
3900
6076
  // src/impls/powens-client.ts
3901
6077
  import { URL as URL2 } from "node:url";
3902
6078
  var POWENS_BASE_URL = {
@@ -4403,7 +6579,7 @@ function resolveLabelIds(defaults, tags) {
4403
6579
  }
4404
6580
 
4405
6581
  // src/impls/jira.ts
4406
- import { Buffer as Buffer4 } from "node:buffer";
6582
+ import { Buffer as Buffer5 } from "node:buffer";
4407
6583
 
4408
6584
  class JiraProjectManagementProvider {
4409
6585
  siteUrl;
@@ -4480,7 +6656,7 @@ function normalizeSiteUrl(siteUrl) {
4480
6656
  return siteUrl.replace(/\/$/, "");
4481
6657
  }
4482
6658
  function buildAuthHeader(email, apiToken) {
4483
- const token = Buffer4.from(`${email}:${apiToken}`).toString("base64");
6659
+ const token = Buffer5.from(`${email}:${apiToken}`).toString("base64");
4484
6660
  return `Basic ${token}`;
4485
6661
  }
4486
6662
  function resolveIssueType(type, defaults) {
@@ -4683,7 +6859,7 @@ function buildParagraphBlocks(text) {
4683
6859
  }
4684
6860
 
4685
6861
  // src/impls/tldv-meeting-recorder.ts
4686
- var DEFAULT_BASE_URL4 = "https://pasta.tldv.io/v1alpha1";
6862
+ var DEFAULT_BASE_URL6 = "https://pasta.tldv.io/v1alpha1";
4687
6863
 
4688
6864
  class TldvMeetingRecorderProvider {
4689
6865
  apiKey;
@@ -4691,7 +6867,7 @@ class TldvMeetingRecorderProvider {
4691
6867
  defaultPageSize;
4692
6868
  constructor(options) {
4693
6869
  this.apiKey = options.apiKey;
4694
- this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL4;
6870
+ this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL6;
4695
6871
  this.defaultPageSize = options.pageSize;
4696
6872
  }
4697
6873
  async listMeetings(params) {
@@ -4826,10 +7002,30 @@ async function safeReadError4(response) {
4826
7002
  }
4827
7003
 
4828
7004
  // src/impls/provider-factory.ts
4829
- import { Buffer as Buffer5 } from "node:buffer";
7005
+ import { Buffer as Buffer6 } from "node:buffer";
7006
+ import { resolveIntegrationRequestContext } from "@contractspec/lib.contracts-integrations/integrations/runtime";
7007
+ import { buildAuthHeaders } from "@contractspec/lib.contracts-integrations/integrations/auth-helpers";
7008
+ import { findAuthConfig } from "@contractspec/lib.contracts-integrations/integrations/auth";
4830
7009
  var SECRET_CACHE = new Map;
4831
7010
 
4832
7011
  class IntegrationProviderFactory {
7012
+ composioFallback;
7013
+ constructor(options) {
7014
+ this.composioFallback = options?.composioFallback;
7015
+ }
7016
+ async resolveProviderContext(context) {
7017
+ const secrets = await this.loadSecrets(context);
7018
+ const { transport, authMethod, apiVersion } = resolveIntegrationRequestContext(context.spec, context.connection);
7019
+ let authHeaders = {};
7020
+ if (authMethod && context.spec.supportedAuthMethods) {
7021
+ const authConfig = findAuthConfig(context.spec.supportedAuthMethods, authMethod);
7022
+ if (authConfig) {
7023
+ const stringSecrets = Object.fromEntries(Object.entries(secrets).filter(([, v]) => typeof v === "string").map(([k, v]) => [k, v]));
7024
+ authHeaders = buildAuthHeaders(authConfig, stringSecrets);
7025
+ }
7026
+ }
7027
+ return { transport, authMethod, apiVersion, authHeaders, secrets };
7028
+ }
4833
7029
  async createPaymentsProvider(context) {
4834
7030
  const secrets = await this.loadSecrets(context);
4835
7031
  switch (context.spec.meta.key) {
@@ -4838,6 +7034,9 @@ class IntegrationProviderFactory {
4838
7034
  apiKey: requireSecret(secrets, "apiKey", "Stripe API key is required")
4839
7035
  });
4840
7036
  default:
7037
+ if (this.composioFallback?.canHandle(context.spec.meta.key)) {
7038
+ return this.composioFallback.createPaymentsProxy(context);
7039
+ }
4841
7040
  throw new Error(`Unsupported payments integration: ${context.spec.meta.key}`);
4842
7041
  }
4843
7042
  }
@@ -4851,6 +7050,9 @@ class IntegrationProviderFactory {
4851
7050
  messageStream: context.config.messageStream
4852
7051
  });
4853
7052
  default:
7053
+ if (this.composioFallback?.canHandle(context.spec.meta.key)) {
7054
+ return this.composioFallback.createEmailProxy(context);
7055
+ }
4854
7056
  throw new Error(`Unsupported email integration: ${context.spec.meta.key}`);
4855
7057
  }
4856
7058
  }
@@ -4864,9 +7066,48 @@ class IntegrationProviderFactory {
4864
7066
  fromNumber: context.config.fromNumber
4865
7067
  });
4866
7068
  default:
7069
+ if (this.composioFallback?.canHandle(context.spec.meta.key)) {
7070
+ return this.composioFallback.createMessagingProxy(context);
7071
+ }
4867
7072
  throw new Error(`Unsupported SMS integration: ${context.spec.meta.key}`);
4868
7073
  }
4869
7074
  }
7075
+ async createMessagingProvider(context) {
7076
+ const secrets = await this.loadSecrets(context);
7077
+ const config = context.config;
7078
+ switch (context.spec.meta.key) {
7079
+ case "messaging.slack":
7080
+ return new SlackMessagingProvider({
7081
+ botToken: requireSecret(secrets, "botToken", "Slack bot token is required"),
7082
+ defaultChannelId: config?.defaultChannelId,
7083
+ apiBaseUrl: config?.apiBaseUrl
7084
+ });
7085
+ case "messaging.github":
7086
+ return new GithubMessagingProvider({
7087
+ token: requireSecret(secrets, "token", "GitHub token is required"),
7088
+ defaultOwner: config?.defaultOwner,
7089
+ defaultRepo: config?.defaultRepo,
7090
+ apiBaseUrl: config?.apiBaseUrl
7091
+ });
7092
+ case "messaging.whatsapp.meta":
7093
+ return new MetaWhatsappMessagingProvider({
7094
+ accessToken: requireSecret(secrets, "accessToken", "Meta WhatsApp access token is required"),
7095
+ phoneNumberId: requireConfig(context, "phoneNumberId", "Meta WhatsApp phoneNumberId is required"),
7096
+ apiVersion: config?.apiVersion
7097
+ });
7098
+ case "messaging.whatsapp.twilio":
7099
+ return new TwilioWhatsappMessagingProvider({
7100
+ accountSid: requireSecret(secrets, "accountSid", "Twilio account SID is required"),
7101
+ authToken: requireSecret(secrets, "authToken", "Twilio auth token is required"),
7102
+ fromNumber: config?.fromNumber
7103
+ });
7104
+ default:
7105
+ if (this.composioFallback?.canHandle(context.spec.meta.key)) {
7106
+ return this.composioFallback.createMessagingProxy(context);
7107
+ }
7108
+ throw new Error(`Unsupported messaging integration: ${context.spec.meta.key}`);
7109
+ }
7110
+ }
4870
7111
  async createVectorStoreProvider(context) {
4871
7112
  const secrets = await this.loadSecrets(context);
4872
7113
  const config = context.config;
@@ -4887,6 +7128,9 @@ class IntegrationProviderFactory {
4887
7128
  sslMode: config?.sslMode
4888
7129
  });
4889
7130
  default:
7131
+ if (this.composioFallback?.canHandle(context.spec.meta.key)) {
7132
+ return this.composioFallback.createGenericProxy(context);
7133
+ }
4890
7134
  throw new Error(`Unsupported vector store integration: ${context.spec.meta.key}`);
4891
7135
  }
4892
7136
  }
@@ -4903,6 +7147,9 @@ class IntegrationProviderFactory {
4903
7147
  personalApiKey: requireSecret(secrets, "personalApiKey", "PostHog personalApiKey is required")
4904
7148
  });
4905
7149
  default:
7150
+ if (this.composioFallback?.canHandle(context.spec.meta.key)) {
7151
+ return this.composioFallback.createGenericProxy(context);
7152
+ }
4906
7153
  throw new Error(`Unsupported analytics integration: ${context.spec.meta.key}`);
4907
7154
  }
4908
7155
  }
@@ -4917,6 +7164,9 @@ class IntegrationProviderFactory {
4917
7164
  sslMode: config?.sslMode
4918
7165
  });
4919
7166
  default:
7167
+ if (this.composioFallback?.canHandle(context.spec.meta.key)) {
7168
+ return this.composioFallback.createGenericProxy(context);
7169
+ }
4920
7170
  throw new Error(`Unsupported database integration: ${context.spec.meta.key}`);
4921
7171
  }
4922
7172
  }
@@ -4930,6 +7180,9 @@ class IntegrationProviderFactory {
4930
7180
  clientOptions: secrets.type === "service_account" ? { credentials: secrets } : undefined
4931
7181
  });
4932
7182
  default:
7183
+ if (this.composioFallback?.canHandle(context.spec.meta.key)) {
7184
+ return this.composioFallback.createGenericProxy(context);
7185
+ }
4933
7186
  throw new Error(`Unsupported storage integration: ${context.spec.meta.key}`);
4934
7187
  }
4935
7188
  }
@@ -4962,9 +7215,53 @@ class IntegrationProviderFactory {
4962
7215
  pollIntervalMs: config?.pollIntervalMs
4963
7216
  });
4964
7217
  default:
7218
+ if (this.composioFallback?.canHandle(context.spec.meta.key)) {
7219
+ return this.composioFallback.createGenericProxy(context);
7220
+ }
4965
7221
  throw new Error(`Unsupported voice integration: ${context.spec.meta.key}`);
4966
7222
  }
4967
7223
  }
7224
+ async createSttProvider(context) {
7225
+ const secrets = await this.loadSecrets(context);
7226
+ const config = context.config;
7227
+ switch (context.spec.meta.key) {
7228
+ case "ai-voice-stt.mistral":
7229
+ return new MistralSttProvider({
7230
+ apiKey: requireSecret(secrets, "apiKey", "Mistral API key is required"),
7231
+ defaultModel: config?.model,
7232
+ defaultLanguage: config?.language,
7233
+ serverURL: config?.serverURL
7234
+ });
7235
+ default:
7236
+ if (this.composioFallback?.canHandle(context.spec.meta.key)) {
7237
+ return this.composioFallback.createGenericProxy(context);
7238
+ }
7239
+ throw new Error(`Unsupported STT integration: ${context.spec.meta.key}`);
7240
+ }
7241
+ }
7242
+ async createConversationalProvider(context) {
7243
+ const secrets = await this.loadSecrets(context);
7244
+ const config = context.config;
7245
+ switch (context.spec.meta.key) {
7246
+ case "ai-voice-conv.mistral":
7247
+ return new MistralConversationalProvider({
7248
+ apiKey: requireSecret(secrets, "apiKey", "Mistral API key is required"),
7249
+ defaultModel: config?.model,
7250
+ defaultVoiceId: config?.defaultVoice,
7251
+ serverURL: config?.serverURL,
7252
+ sttOptions: {
7253
+ defaultModel: config?.model,
7254
+ defaultLanguage: config?.language,
7255
+ serverURL: config?.serverURL
7256
+ }
7257
+ });
7258
+ default:
7259
+ if (this.composioFallback?.canHandle(context.spec.meta.key)) {
7260
+ return this.composioFallback.createGenericProxy(context);
7261
+ }
7262
+ throw new Error(`Unsupported conversational integration: ${context.spec.meta.key}`);
7263
+ }
7264
+ }
4968
7265
  async createProjectManagementProvider(context) {
4969
7266
  const secrets = await this.loadSecrets(context);
4970
7267
  const config = context.config;
@@ -5002,6 +7299,9 @@ class IntegrationProviderFactory {
5002
7299
  descriptionProperty: config?.descriptionProperty
5003
7300
  });
5004
7301
  default:
7302
+ if (this.composioFallback?.canHandle(context.spec.meta.key)) {
7303
+ return this.composioFallback.createProjectManagementProxy(context);
7304
+ }
5005
7305
  throw new Error(`Unsupported project management integration: ${context.spec.meta.key}`);
5006
7306
  }
5007
7307
  }
@@ -5050,6 +7350,9 @@ class IntegrationProviderFactory {
5050
7350
  webhookSecret: secrets.webhookSecret
5051
7351
  });
5052
7352
  default:
7353
+ if (this.composioFallback?.canHandle(context.spec.meta.key)) {
7354
+ return this.composioFallback.createGenericProxy(context);
7355
+ }
5053
7356
  throw new Error(`Unsupported meeting recorder integration: ${context.spec.meta.key}`);
5054
7357
  }
5055
7358
  }
@@ -5062,6 +7365,9 @@ class IntegrationProviderFactory {
5062
7365
  defaultModel: context.config.model
5063
7366
  });
5064
7367
  default:
7368
+ if (this.composioFallback?.canHandle(context.spec.meta.key)) {
7369
+ return this.composioFallback.createGenericProxy(context);
7370
+ }
5065
7371
  throw new Error(`Unsupported LLM integration: ${context.spec.meta.key}`);
5066
7372
  }
5067
7373
  }
@@ -5074,6 +7380,9 @@ class IntegrationProviderFactory {
5074
7380
  defaultModel: context.config.embeddingModel
5075
7381
  });
5076
7382
  default:
7383
+ if (this.composioFallback?.canHandle(context.spec.meta.key)) {
7384
+ return this.composioFallback.createGenericProxy(context);
7385
+ }
5077
7386
  throw new Error(`Unsupported embeddings integration: ${context.spec.meta.key}`);
5078
7387
  }
5079
7388
  }
@@ -5095,6 +7404,9 @@ class IntegrationProviderFactory {
5095
7404
  });
5096
7405
  }
5097
7406
  default:
7407
+ if (this.composioFallback?.canHandle(context.spec.meta.key)) {
7408
+ return this.composioFallback.createGenericProxy(context);
7409
+ }
5098
7410
  throw new Error(`Unsupported open banking integration: ${context.spec.meta.key}`);
5099
7411
  }
5100
7412
  }
@@ -5115,7 +7427,7 @@ class IntegrationProviderFactory {
5115
7427
  }
5116
7428
  }
5117
7429
  function parseSecret(secret) {
5118
- const text = Buffer5.from(secret.data).toString("utf-8").trim();
7430
+ const text = Buffer6.from(secret.data).toString("utf-8").trim();
5119
7431
  if (!text)
5120
7432
  return {};
5121
7433
  try {
@@ -5161,6 +7473,9 @@ export * from "@contractspec/lib.contracts-integrations";
5161
7473
  // src/sms.ts
5162
7474
  export * from "@contractspec/lib.contracts-integrations";
5163
7475
 
7476
+ // src/messaging.ts
7477
+ export * from "@contractspec/lib.contracts-integrations";
7478
+
5164
7479
  // src/payments.ts
5165
7480
  export * from "@contractspec/lib.contracts-integrations";
5166
7481
 
@@ -5173,15 +7488,19 @@ export * from "@contractspec/lib.contracts-integrations";
5173
7488
  // src/meeting-recorder.ts
5174
7489
  export * from "@contractspec/lib.contracts-integrations";
5175
7490
  export {
7491
+ resolveToolkit,
7492
+ isSessionExpired,
5176
7493
  createHealthProviderFromContext,
5177
7494
  WhoopHealthProvider,
5178
7495
  UnofficialHealthAutomationProvider,
7496
+ TwilioWhatsappMessagingProvider,
5179
7497
  TwilioSmsProvider,
5180
7498
  TldvMeetingRecorderProvider,
5181
7499
  SupabaseVectorProvider,
5182
7500
  SupabasePostgresProvider,
5183
7501
  StripePaymentsProvider,
5184
7502
  StravaHealthProvider,
7503
+ SlackMessagingProvider,
5185
7504
  QdrantVectorProvider,
5186
7505
  PowensOpenBankingProvider,
5187
7506
  PowensClientError,
@@ -5194,17 +7513,22 @@ export {
5194
7513
  OpenWearablesHealthProvider,
5195
7514
  NotionProjectManagementProvider,
5196
7515
  MyFitnessPalHealthProvider,
7516
+ MistralSttProvider,
5197
7517
  MistralLLMProvider,
5198
7518
  MistralEmbeddingProvider,
7519
+ MistralConversationalProvider,
7520
+ MetaWhatsappMessagingProvider,
5199
7521
  LinearProjectManagementProvider,
5200
7522
  JiraProjectManagementProvider,
5201
7523
  IntegrationProviderFactory,
7524
+ INTEGRATION_KEY_TO_TOOLKIT,
5202
7525
  GranolaMeetingRecorderProvider,
5203
7526
  GradiumVoiceProvider,
5204
7527
  GoogleCloudStorageProvider,
5205
7528
  GoogleCalendarProvider,
5206
7529
  GmailOutboundProvider,
5207
7530
  GmailInboundProvider,
7531
+ GithubMessagingProvider,
5208
7532
  GarminHealthProvider,
5209
7533
  FitbitHealthProvider,
5210
7534
  FirefliesMeetingRecorderProvider,
@@ -5212,5 +7536,14 @@ export {
5212
7536
  FalVoiceProvider,
5213
7537
  ElevenLabsVoiceProvider,
5214
7538
  EightSleepHealthProvider,
7539
+ ComposioSdkProvider,
7540
+ ComposioProjectManagementProxy,
7541
+ ComposioPaymentsProxy,
7542
+ ComposioMessagingProxy,
7543
+ ComposioMcpProvider,
7544
+ ComposioGenericProxy,
7545
+ ComposioFallbackResolver,
7546
+ ComposioEmailProxy,
7547
+ ComposioCalendarProxy,
5215
7548
  AppleHealthBridgeProvider
5216
7549
  };