@contractspec/integration.providers-impls 2.8.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/dist/health.d.ts +1 -0
- package/dist/health.js +3 -0
- package/dist/impls/health/base-health-provider.d.ts +47 -0
- package/dist/impls/health/base-health-provider.js +266 -0
- package/dist/impls/health/providers.d.ts +39 -0
- package/dist/impls/health/providers.js +383 -0
- package/dist/impls/health-provider-factory.d.ts +3 -0
- package/dist/impls/health-provider-factory.js +495 -0
- package/dist/impls/index.d.ts +2 -0
- package/dist/impls/index.js +512 -4
- package/dist/impls/provider-factory.d.ts +2 -0
- package/dist/impls/provider-factory.js +499 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +515 -4
- package/dist/node/health.js +2 -0
- package/dist/node/impls/health/base-health-provider.js +265 -0
- package/dist/node/impls/health/providers.js +382 -0
- package/dist/node/impls/health-provider-factory.js +494 -0
- package/dist/node/impls/index.js +512 -4
- package/dist/node/impls/provider-factory.js +499 -3
- package/dist/node/index.js +515 -4
- package/package.json +54 -6
package/dist/node/index.js
CHANGED
|
@@ -13,6 +13,9 @@ export * from "@contractspec/lib.contracts-integrations";
|
|
|
13
13
|
// src/embedding.ts
|
|
14
14
|
export * from "@contractspec/lib.contracts-integrations";
|
|
15
15
|
|
|
16
|
+
// src/health.ts
|
|
17
|
+
export * from "@contractspec/lib.contracts-integrations";
|
|
18
|
+
|
|
16
19
|
// src/impls/elevenlabs-voice.ts
|
|
17
20
|
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
|
|
18
21
|
var FORMAT_MAP = {
|
|
@@ -2030,6 +2033,498 @@ async function safeReadError3(response) {
|
|
|
2030
2033
|
}
|
|
2031
2034
|
}
|
|
2032
2035
|
|
|
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
|
+
};
|
|
2067
|
+
}
|
|
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
|
+
};
|
|
2076
|
+
}
|
|
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
|
+
};
|
|
2085
|
+
}
|
|
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
|
+
};
|
|
2094
|
+
}
|
|
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
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
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
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
async syncActivities(params) {
|
|
2119
|
+
return this.sync("activities", params);
|
|
2120
|
+
}
|
|
2121
|
+
async syncWorkouts(params) {
|
|
2122
|
+
return this.sync("workouts", params);
|
|
2123
|
+
}
|
|
2124
|
+
async syncSleep(params) {
|
|
2125
|
+
return this.sync("sleep", params);
|
|
2126
|
+
}
|
|
2127
|
+
async syncBiometrics(params) {
|
|
2128
|
+
return this.sync("biometrics", params);
|
|
2129
|
+
}
|
|
2130
|
+
async syncNutrition(params) {
|
|
2131
|
+
return this.sync("nutrition", params);
|
|
2132
|
+
}
|
|
2133
|
+
async parseWebhook(request) {
|
|
2134
|
+
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
|
+
};
|
|
2145
|
+
}
|
|
2146
|
+
async verifyWebhook(request) {
|
|
2147
|
+
if (!this.webhookSecret) {
|
|
2148
|
+
return true;
|
|
2149
|
+
}
|
|
2150
|
+
const signature = readHeader(request.headers, "x-webhook-signature");
|
|
2151
|
+
return signature === this.webhookSecret;
|
|
2152
|
+
}
|
|
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) ?? [];
|
|
2156
|
+
return {
|
|
2157
|
+
items,
|
|
2158
|
+
nextCursor: readString2(payload, "nextCursor") ?? readString2(payload, "cursor"),
|
|
2159
|
+
hasMore: readBoolean2(payload, "hasMore")
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
async sync(resource, params) {
|
|
2163
|
+
const payload = await this.fetchRecord(`sync/${resource}`, params, "POST");
|
|
2164
|
+
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)),
|
|
2169
|
+
source: this.currentSource()
|
|
2170
|
+
};
|
|
2171
|
+
}
|
|
2172
|
+
async fetchRecord(resource, params, method = "GET") {
|
|
2173
|
+
if (this.transport.endsWith("mcp")) {
|
|
2174
|
+
return this.callMcpTool(resource, params);
|
|
2175
|
+
}
|
|
2176
|
+
const url = new URL(`${this.apiBaseUrl.replace(/\/$/, "")}/${resource}`);
|
|
2177
|
+
if (method === "GET") {
|
|
2178
|
+
for (const [key, value] of Object.entries(params)) {
|
|
2179
|
+
if (value == null)
|
|
2180
|
+
continue;
|
|
2181
|
+
if (Array.isArray(value)) {
|
|
2182
|
+
value.forEach((item) => {
|
|
2183
|
+
url.searchParams.append(key, String(item));
|
|
2184
|
+
});
|
|
2185
|
+
continue;
|
|
2186
|
+
}
|
|
2187
|
+
url.searchParams.set(key, String(value));
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
const response = await this.fetchFn(url, {
|
|
2191
|
+
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
|
|
2197
|
+
});
|
|
2198
|
+
if (!response.ok) {
|
|
2199
|
+
const errorBody = await safeResponseText(response);
|
|
2200
|
+
throw new Error(`${this.providerKey} ${resource} failed (${response.status}): ${errorBody}`);
|
|
2201
|
+
}
|
|
2202
|
+
const data = await response.json();
|
|
2203
|
+
return asRecord(data) ?? {};
|
|
2204
|
+
}
|
|
2205
|
+
async callMcpTool(resource, params) {
|
|
2206
|
+
if (!this.mcpUrl) {
|
|
2207
|
+
return {};
|
|
2208
|
+
}
|
|
2209
|
+
const response = await this.fetchFn(this.mcpUrl, {
|
|
2210
|
+
method: "POST",
|
|
2211
|
+
headers: {
|
|
2212
|
+
"Content-Type": "application/json",
|
|
2213
|
+
...this.mcpAccessToken ? { Authorization: `Bearer ${this.mcpAccessToken}` } : {}
|
|
2214
|
+
},
|
|
2215
|
+
body: JSON.stringify({
|
|
2216
|
+
jsonrpc: "2.0",
|
|
2217
|
+
id: ++this.mcpRequestId,
|
|
2218
|
+
method: "tools/call",
|
|
2219
|
+
params: {
|
|
2220
|
+
name: `${this.providerKey.replace("health.", "")}_${resource.replace(/\//g, "_")}`,
|
|
2221
|
+
arguments: params
|
|
2222
|
+
}
|
|
2223
|
+
})
|
|
2224
|
+
});
|
|
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() {
|
|
2241
|
+
return {
|
|
2242
|
+
providerKey: this.providerKey,
|
|
2243
|
+
transport: this.transport,
|
|
2244
|
+
route: "primary"
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
function safeJsonParse(raw) {
|
|
2249
|
+
try {
|
|
2250
|
+
return JSON.parse(raw);
|
|
2251
|
+
} catch {
|
|
2252
|
+
return { rawBody: raw };
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
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;
|
|
2267
|
+
}
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
function asRecord(value) {
|
|
2271
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
return value;
|
|
2275
|
+
}
|
|
2276
|
+
function asArray2(value) {
|
|
2277
|
+
return Array.isArray(value) ? value : undefined;
|
|
2278
|
+
}
|
|
2279
|
+
function readString2(record, key) {
|
|
2280
|
+
const value = record?.[key];
|
|
2281
|
+
return typeof value === "string" ? value : undefined;
|
|
2282
|
+
}
|
|
2283
|
+
function readBoolean2(record, key) {
|
|
2284
|
+
const value = record?.[key];
|
|
2285
|
+
return typeof value === "boolean" ? value : undefined;
|
|
2286
|
+
}
|
|
2287
|
+
function readNumber(record, key) {
|
|
2288
|
+
const value = record?.[key];
|
|
2289
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
2290
|
+
}
|
|
2291
|
+
async function safeResponseText(response) {
|
|
2292
|
+
try {
|
|
2293
|
+
return await response.text();
|
|
2294
|
+
} catch {
|
|
2295
|
+
return response.statusText;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
|
|
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 {
|
|
2308
|
+
constructor(options) {
|
|
2309
|
+
super({
|
|
2310
|
+
providerKey: "health.openwearables",
|
|
2311
|
+
...createProviderOptions(options, "aggregator-api")
|
|
2312
|
+
});
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
class WhoopHealthProvider extends BaseHealthProvider {
|
|
2317
|
+
constructor(options) {
|
|
2318
|
+
super({
|
|
2319
|
+
providerKey: "health.whoop",
|
|
2320
|
+
...createProviderOptions(options, "official-api")
|
|
2321
|
+
});
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
class AppleHealthBridgeProvider extends BaseHealthProvider {
|
|
2326
|
+
constructor(options) {
|
|
2327
|
+
super({
|
|
2328
|
+
providerKey: "health.apple-health",
|
|
2329
|
+
...createProviderOptions(options, "aggregator-api")
|
|
2330
|
+
});
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
class OuraHealthProvider extends BaseHealthProvider {
|
|
2335
|
+
constructor(options) {
|
|
2336
|
+
super({
|
|
2337
|
+
providerKey: "health.oura",
|
|
2338
|
+
...createProviderOptions(options, "official-api")
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
class StravaHealthProvider extends BaseHealthProvider {
|
|
2344
|
+
constructor(options) {
|
|
2345
|
+
super({
|
|
2346
|
+
providerKey: "health.strava",
|
|
2347
|
+
...createProviderOptions(options, "official-api")
|
|
2348
|
+
});
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
class GarminHealthProvider extends BaseHealthProvider {
|
|
2353
|
+
constructor(options) {
|
|
2354
|
+
super({
|
|
2355
|
+
providerKey: "health.garmin",
|
|
2356
|
+
...createProviderOptions(options, "official-api")
|
|
2357
|
+
});
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
class FitbitHealthProvider extends BaseHealthProvider {
|
|
2362
|
+
constructor(options) {
|
|
2363
|
+
super({
|
|
2364
|
+
providerKey: "health.fitbit",
|
|
2365
|
+
...createProviderOptions(options, "official-api")
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
class MyFitnessPalHealthProvider extends BaseHealthProvider {
|
|
2371
|
+
constructor(options) {
|
|
2372
|
+
super({
|
|
2373
|
+
providerKey: "health.myfitnesspal",
|
|
2374
|
+
...createProviderOptions(options, "official-api")
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
class EightSleepHealthProvider extends BaseHealthProvider {
|
|
2380
|
+
constructor(options) {
|
|
2381
|
+
super({
|
|
2382
|
+
providerKey: "health.eightsleep",
|
|
2383
|
+
...createProviderOptions(options, "official-api")
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
class PelotonHealthProvider extends BaseHealthProvider {
|
|
2389
|
+
constructor(options) {
|
|
2390
|
+
super({
|
|
2391
|
+
providerKey: "health.peloton",
|
|
2392
|
+
...createProviderOptions(options, "official-api")
|
|
2393
|
+
});
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
class UnofficialHealthAutomationProvider extends BaseHealthProvider {
|
|
2398
|
+
constructor(options) {
|
|
2399
|
+
super({
|
|
2400
|
+
...createProviderOptions(options, "unofficial"),
|
|
2401
|
+
providerKey: options.providerKey
|
|
2402
|
+
});
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
// src/impls/health-provider-factory.ts
|
|
2407
|
+
import {
|
|
2408
|
+
isUnofficialHealthProviderAllowed,
|
|
2409
|
+
resolveHealthStrategyOrder
|
|
2410
|
+
} from "@contractspec/integration.runtime/runtime";
|
|
2411
|
+
function createHealthProviderFromContext(context, secrets) {
|
|
2412
|
+
const providerKey = context.spec.meta.key;
|
|
2413
|
+
const config = toFactoryConfig(context.config);
|
|
2414
|
+
const strategyOrder = buildStrategyOrder(config);
|
|
2415
|
+
const errors = [];
|
|
2416
|
+
for (const strategy of strategyOrder) {
|
|
2417
|
+
const provider = createHealthProviderForStrategy(providerKey, strategy, config, secrets);
|
|
2418
|
+
if (provider) {
|
|
2419
|
+
return provider;
|
|
2420
|
+
}
|
|
2421
|
+
errors.push(`${strategy}: not available`);
|
|
2422
|
+
}
|
|
2423
|
+
throw new Error(`Unable to resolve health provider for ${providerKey}. Strategies attempted: ${errors.join(", ")}.`);
|
|
2424
|
+
}
|
|
2425
|
+
function createHealthProviderForStrategy(providerKey, strategy, config, secrets) {
|
|
2426
|
+
const options = {
|
|
2427
|
+
transport: strategy,
|
|
2428
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
2429
|
+
mcpUrl: config.mcpUrl,
|
|
2430
|
+
apiKey: getSecretString(secrets, "apiKey"),
|
|
2431
|
+
accessToken: getSecretString(secrets, "accessToken"),
|
|
2432
|
+
mcpAccessToken: getSecretString(secrets, "mcpAccessToken"),
|
|
2433
|
+
webhookSecret: getSecretString(secrets, "webhookSecret")
|
|
2434
|
+
};
|
|
2435
|
+
if (strategy === "aggregator-api" || strategy === "aggregator-mcp") {
|
|
2436
|
+
return new OpenWearablesHealthProvider(options);
|
|
2437
|
+
}
|
|
2438
|
+
if (strategy === "unofficial") {
|
|
2439
|
+
if (!isUnofficialHealthProviderAllowed(providerKey, config)) {
|
|
2440
|
+
return;
|
|
2441
|
+
}
|
|
2442
|
+
if (providerKey !== "health.myfitnesspal" && providerKey !== "health.eightsleep" && providerKey !== "health.peloton" && providerKey !== "health.garmin") {
|
|
2443
|
+
return;
|
|
2444
|
+
}
|
|
2445
|
+
return new UnofficialHealthAutomationProvider({
|
|
2446
|
+
...options,
|
|
2447
|
+
providerKey
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
if (strategy === "official-mcp") {
|
|
2451
|
+
return createOfficialProvider(providerKey, {
|
|
2452
|
+
...options,
|
|
2453
|
+
transport: "official-mcp"
|
|
2454
|
+
});
|
|
2455
|
+
}
|
|
2456
|
+
return createOfficialProvider(providerKey, options);
|
|
2457
|
+
}
|
|
2458
|
+
function createOfficialProvider(providerKey, options) {
|
|
2459
|
+
switch (providerKey) {
|
|
2460
|
+
case "health.openwearables":
|
|
2461
|
+
return new OpenWearablesHealthProvider(options);
|
|
2462
|
+
case "health.whoop":
|
|
2463
|
+
return new WhoopHealthProvider(options);
|
|
2464
|
+
case "health.apple-health":
|
|
2465
|
+
return new AppleHealthBridgeProvider(options);
|
|
2466
|
+
case "health.oura":
|
|
2467
|
+
return new OuraHealthProvider(options);
|
|
2468
|
+
case "health.strava":
|
|
2469
|
+
return new StravaHealthProvider(options);
|
|
2470
|
+
case "health.garmin":
|
|
2471
|
+
return new GarminHealthProvider(options);
|
|
2472
|
+
case "health.fitbit":
|
|
2473
|
+
return new FitbitHealthProvider(options);
|
|
2474
|
+
case "health.myfitnesspal":
|
|
2475
|
+
return new MyFitnessPalHealthProvider(options);
|
|
2476
|
+
case "health.eightsleep":
|
|
2477
|
+
return new EightSleepHealthProvider(options);
|
|
2478
|
+
case "health.peloton":
|
|
2479
|
+
return new PelotonHealthProvider(options);
|
|
2480
|
+
default:
|
|
2481
|
+
throw new Error(`Unsupported health provider key: ${providerKey}`);
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
function toFactoryConfig(config) {
|
|
2485
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
2486
|
+
return {};
|
|
2487
|
+
}
|
|
2488
|
+
const record = config;
|
|
2489
|
+
return {
|
|
2490
|
+
apiBaseUrl: asString(record.apiBaseUrl),
|
|
2491
|
+
mcpUrl: asString(record.mcpUrl),
|
|
2492
|
+
defaultTransport: normalizeTransport(record.defaultTransport),
|
|
2493
|
+
strategyOrder: normalizeTransportArray(record.strategyOrder),
|
|
2494
|
+
allowUnofficial: typeof record.allowUnofficial === "boolean" ? record.allowUnofficial : false,
|
|
2495
|
+
unofficialAllowList: Array.isArray(record.unofficialAllowList) ? record.unofficialAllowList.map((item) => typeof item === "string" ? item : undefined).filter((item) => Boolean(item)) : undefined
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
function buildStrategyOrder(config) {
|
|
2499
|
+
const order = resolveHealthStrategyOrder(config);
|
|
2500
|
+
if (!config.defaultTransport) {
|
|
2501
|
+
return order;
|
|
2502
|
+
}
|
|
2503
|
+
const withoutDefault = order.filter((item) => item !== config.defaultTransport);
|
|
2504
|
+
return [config.defaultTransport, ...withoutDefault];
|
|
2505
|
+
}
|
|
2506
|
+
function normalizeTransport(value) {
|
|
2507
|
+
if (typeof value !== "string")
|
|
2508
|
+
return;
|
|
2509
|
+
if (value === "official-api" || value === "official-mcp" || value === "aggregator-api" || value === "aggregator-mcp" || value === "unofficial") {
|
|
2510
|
+
return value;
|
|
2511
|
+
}
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
function normalizeTransportArray(value) {
|
|
2515
|
+
if (!Array.isArray(value))
|
|
2516
|
+
return;
|
|
2517
|
+
const transports = value.map((item) => normalizeTransport(item)).filter((item) => Boolean(item));
|
|
2518
|
+
return transports.length > 0 ? transports : undefined;
|
|
2519
|
+
}
|
|
2520
|
+
function getSecretString(secrets, key) {
|
|
2521
|
+
const value = secrets[key];
|
|
2522
|
+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
2523
|
+
}
|
|
2524
|
+
function asString(value) {
|
|
2525
|
+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2033
2528
|
// src/impls/mistral-llm.ts
|
|
2034
2529
|
import { Mistral } from "@mistralai/mistralai";
|
|
2035
2530
|
|
|
@@ -3403,7 +3898,7 @@ function mapStatus(status) {
|
|
|
3403
3898
|
}
|
|
3404
3899
|
|
|
3405
3900
|
// src/impls/powens-client.ts
|
|
3406
|
-
import { URL } from "node:url";
|
|
3901
|
+
import { URL as URL2 } from "node:url";
|
|
3407
3902
|
var POWENS_BASE_URL = {
|
|
3408
3903
|
sandbox: "https://api-sandbox.powens.com/v2",
|
|
3409
3904
|
production: "https://api.powens.com/v2"
|
|
@@ -3489,7 +3984,7 @@ class PowensClient {
|
|
|
3489
3984
|
});
|
|
3490
3985
|
}
|
|
3491
3986
|
async request(options) {
|
|
3492
|
-
const url = new
|
|
3987
|
+
const url = new URL2(options.path, this.baseUrl);
|
|
3493
3988
|
if (options.searchParams) {
|
|
3494
3989
|
for (const [key, value] of Object.entries(options.searchParams)) {
|
|
3495
3990
|
if (value === undefined || value === null)
|
|
@@ -3559,7 +4054,7 @@ class PowensClient {
|
|
|
3559
4054
|
return this.token.accessToken;
|
|
3560
4055
|
}
|
|
3561
4056
|
async fetchAccessToken() {
|
|
3562
|
-
const url = new
|
|
4057
|
+
const url = new URL2("/oauth/token", this.baseUrl);
|
|
3563
4058
|
const basicAuth = Buffer.from(`${this.clientId}:${this.clientSecret}`, "utf-8").toString("base64");
|
|
3564
4059
|
const response = await this.fetchImpl(url, {
|
|
3565
4060
|
method: "POST",
|
|
@@ -4603,6 +5098,10 @@ class IntegrationProviderFactory {
|
|
|
4603
5098
|
throw new Error(`Unsupported open banking integration: ${context.spec.meta.key}`);
|
|
4604
5099
|
}
|
|
4605
5100
|
}
|
|
5101
|
+
async createHealthProvider(context) {
|
|
5102
|
+
const secrets = await this.loadSecrets(context);
|
|
5103
|
+
return createHealthProviderFromContext(context, secrets);
|
|
5104
|
+
}
|
|
4606
5105
|
async loadSecrets(context) {
|
|
4607
5106
|
const cacheKey = context.connection.meta.id;
|
|
4608
5107
|
if (SECRET_CACHE.has(cacheKey)) {
|
|
@@ -4674,11 +5173,15 @@ export * from "@contractspec/lib.contracts-integrations";
|
|
|
4674
5173
|
// src/meeting-recorder.ts
|
|
4675
5174
|
export * from "@contractspec/lib.contracts-integrations";
|
|
4676
5175
|
export {
|
|
5176
|
+
createHealthProviderFromContext,
|
|
5177
|
+
WhoopHealthProvider,
|
|
5178
|
+
UnofficialHealthAutomationProvider,
|
|
4677
5179
|
TwilioSmsProvider,
|
|
4678
5180
|
TldvMeetingRecorderProvider,
|
|
4679
5181
|
SupabaseVectorProvider,
|
|
4680
5182
|
SupabasePostgresProvider,
|
|
4681
5183
|
StripePaymentsProvider,
|
|
5184
|
+
StravaHealthProvider,
|
|
4682
5185
|
QdrantVectorProvider,
|
|
4683
5186
|
PowensOpenBankingProvider,
|
|
4684
5187
|
PowensClientError,
|
|
@@ -4686,7 +5189,11 @@ export {
|
|
|
4686
5189
|
PostmarkEmailProvider,
|
|
4687
5190
|
PosthogAnalyticsReader,
|
|
4688
5191
|
PosthogAnalyticsProvider,
|
|
5192
|
+
PelotonHealthProvider,
|
|
5193
|
+
OuraHealthProvider,
|
|
5194
|
+
OpenWearablesHealthProvider,
|
|
4689
5195
|
NotionProjectManagementProvider,
|
|
5196
|
+
MyFitnessPalHealthProvider,
|
|
4690
5197
|
MistralLLMProvider,
|
|
4691
5198
|
MistralEmbeddingProvider,
|
|
4692
5199
|
LinearProjectManagementProvider,
|
|
@@ -4698,8 +5205,12 @@ export {
|
|
|
4698
5205
|
GoogleCalendarProvider,
|
|
4699
5206
|
GmailOutboundProvider,
|
|
4700
5207
|
GmailInboundProvider,
|
|
5208
|
+
GarminHealthProvider,
|
|
5209
|
+
FitbitHealthProvider,
|
|
4701
5210
|
FirefliesMeetingRecorderProvider,
|
|
4702
5211
|
FathomMeetingRecorderProvider,
|
|
4703
5212
|
FalVoiceProvider,
|
|
4704
|
-
ElevenLabsVoiceProvider
|
|
5213
|
+
ElevenLabsVoiceProvider,
|
|
5214
|
+
EightSleepHealthProvider,
|
|
5215
|
+
AppleHealthBridgeProvider
|
|
4705
5216
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contractspec/integration.providers-impls",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "Integration provider implementations for email, payments, storage, and more",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"contractspec",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"typecheck": "tsc --noEmit"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@contractspec/lib.contracts-spec": "2.
|
|
36
|
-
"@contractspec/lib.contracts-integrations": "2.
|
|
37
|
-
"@contractspec/integration.runtime": "2.
|
|
35
|
+
"@contractspec/lib.contracts-spec": "2.10.0",
|
|
36
|
+
"@contractspec/lib.contracts-integrations": "2.10.0",
|
|
37
|
+
"@contractspec/integration.runtime": "2.10.0",
|
|
38
38
|
"@elevenlabs/elevenlabs-js": "^2.36.0",
|
|
39
39
|
"@fal-ai/client": "^1.9.3",
|
|
40
40
|
"@google-cloud/storage": "^7.19.0",
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/bun": "1.3.9",
|
|
58
|
-
"@contractspec/tool.typescript": "2.
|
|
58
|
+
"@contractspec/tool.typescript": "2.9.0",
|
|
59
59
|
"typescript": "^5.9.3",
|
|
60
|
-
"@contractspec/tool.bun": "2.
|
|
60
|
+
"@contractspec/tool.bun": "2.9.0"
|
|
61
61
|
},
|
|
62
62
|
"exports": {
|
|
63
63
|
".": {
|
|
@@ -96,6 +96,12 @@
|
|
|
96
96
|
"node": "./dist/node/embedding.js",
|
|
97
97
|
"default": "./dist/embedding.js"
|
|
98
98
|
},
|
|
99
|
+
"./health": {
|
|
100
|
+
"types": "./dist/health.d.ts",
|
|
101
|
+
"bun": "./dist/health.js",
|
|
102
|
+
"node": "./dist/node/health.js",
|
|
103
|
+
"default": "./dist/health.js"
|
|
104
|
+
},
|
|
99
105
|
"./impls": {
|
|
100
106
|
"types": "./dist/impls/index.d.ts",
|
|
101
107
|
"bun": "./dist/impls/index.js",
|
|
@@ -216,6 +222,24 @@
|
|
|
216
222
|
"node": "./dist/node/impls/granola-meeting-recorder.types.js",
|
|
217
223
|
"default": "./dist/impls/granola-meeting-recorder.types.js"
|
|
218
224
|
},
|
|
225
|
+
"./impls/health-provider-factory": {
|
|
226
|
+
"types": "./dist/impls/health-provider-factory.d.ts",
|
|
227
|
+
"bun": "./dist/impls/health-provider-factory.js",
|
|
228
|
+
"node": "./dist/node/impls/health-provider-factory.js",
|
|
229
|
+
"default": "./dist/impls/health-provider-factory.js"
|
|
230
|
+
},
|
|
231
|
+
"./impls/health/base-health-provider": {
|
|
232
|
+
"types": "./dist/impls/health/base-health-provider.d.ts",
|
|
233
|
+
"bun": "./dist/impls/health/base-health-provider.js",
|
|
234
|
+
"node": "./dist/node/impls/health/base-health-provider.js",
|
|
235
|
+
"default": "./dist/impls/health/base-health-provider.js"
|
|
236
|
+
},
|
|
237
|
+
"./impls/health/providers": {
|
|
238
|
+
"types": "./dist/impls/health/providers.d.ts",
|
|
239
|
+
"bun": "./dist/impls/health/providers.js",
|
|
240
|
+
"node": "./dist/node/impls/health/providers.js",
|
|
241
|
+
"default": "./dist/impls/health/providers.js"
|
|
242
|
+
},
|
|
219
243
|
"./impls/index": {
|
|
220
244
|
"types": "./dist/impls/index.d.ts",
|
|
221
245
|
"bun": "./dist/impls/index.js",
|
|
@@ -436,6 +460,12 @@
|
|
|
436
460
|
"node": "./dist/node/embedding.js",
|
|
437
461
|
"default": "./dist/embedding.js"
|
|
438
462
|
},
|
|
463
|
+
"./health": {
|
|
464
|
+
"types": "./dist/health.d.ts",
|
|
465
|
+
"bun": "./dist/health.js",
|
|
466
|
+
"node": "./dist/node/health.js",
|
|
467
|
+
"default": "./dist/health.js"
|
|
468
|
+
},
|
|
439
469
|
"./impls": {
|
|
440
470
|
"types": "./dist/impls/index.d.ts",
|
|
441
471
|
"bun": "./dist/impls/index.js",
|
|
@@ -556,6 +586,24 @@
|
|
|
556
586
|
"node": "./dist/node/impls/granola-meeting-recorder.types.js",
|
|
557
587
|
"default": "./dist/impls/granola-meeting-recorder.types.js"
|
|
558
588
|
},
|
|
589
|
+
"./impls/health-provider-factory": {
|
|
590
|
+
"types": "./dist/impls/health-provider-factory.d.ts",
|
|
591
|
+
"bun": "./dist/impls/health-provider-factory.js",
|
|
592
|
+
"node": "./dist/node/impls/health-provider-factory.js",
|
|
593
|
+
"default": "./dist/impls/health-provider-factory.js"
|
|
594
|
+
},
|
|
595
|
+
"./impls/health/base-health-provider": {
|
|
596
|
+
"types": "./dist/impls/health/base-health-provider.d.ts",
|
|
597
|
+
"bun": "./dist/impls/health/base-health-provider.js",
|
|
598
|
+
"node": "./dist/node/impls/health/base-health-provider.js",
|
|
599
|
+
"default": "./dist/impls/health/base-health-provider.js"
|
|
600
|
+
},
|
|
601
|
+
"./impls/health/providers": {
|
|
602
|
+
"types": "./dist/impls/health/providers.d.ts",
|
|
603
|
+
"bun": "./dist/impls/health/providers.js",
|
|
604
|
+
"node": "./dist/node/impls/health/providers.js",
|
|
605
|
+
"default": "./dist/impls/health/providers.js"
|
|
606
|
+
},
|
|
559
607
|
"./impls/index": {
|
|
560
608
|
"types": "./dist/impls/index.d.ts",
|
|
561
609
|
"bun": "./dist/impls/index.js",
|