@elizaos/plugin-health 2.0.0-beta.1 → 2.0.3-beta.3

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 (164) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +33 -27
  3. package/assets/hero.svg +67 -0
  4. package/package.json +69 -4
  5. package/dist/actions/index.d.ts +0 -20
  6. package/dist/actions/index.d.ts.map +0 -1
  7. package/dist/actions/index.js +0 -5
  8. package/dist/actions/index.js.map +0 -1
  9. package/dist/anchors/index.d.ts +0 -19
  10. package/dist/anchors/index.d.ts.map +0 -1
  11. package/dist/anchors/index.js +0 -9
  12. package/dist/anchors/index.js.map +0 -1
  13. package/dist/connectors/contract-stubs.d.ts +0 -112
  14. package/dist/connectors/contract-stubs.d.ts.map +0 -1
  15. package/dist/connectors/contract-stubs.js +0 -1
  16. package/dist/connectors/contract-stubs.js.map +0 -1
  17. package/dist/connectors/index.d.ts +0 -28
  18. package/dist/connectors/index.d.ts.map +0 -1
  19. package/dist/connectors/index.js +0 -202
  20. package/dist/connectors/index.js.map +0 -1
  21. package/dist/contracts/circadian-default.d.ts +0 -15
  22. package/dist/contracts/circadian-default.d.ts.map +0 -1
  23. package/dist/contracts/circadian-default.js +0 -30
  24. package/dist/contracts/circadian-default.js.map +0 -1
  25. package/dist/contracts/circadian.d.ts +0 -92
  26. package/dist/contracts/circadian.d.ts.map +0 -1
  27. package/dist/contracts/circadian.js +0 -14
  28. package/dist/contracts/circadian.js.map +0 -1
  29. package/dist/contracts/health.d.ts +0 -9
  30. package/dist/contracts/health.d.ts.map +0 -1
  31. package/dist/contracts/health.js +0 -21
  32. package/dist/contracts/health.js.map +0 -1
  33. package/dist/contracts/lifeops-connector-degradation.d.ts +0 -9
  34. package/dist/contracts/lifeops-connector-degradation.d.ts.map +0 -1
  35. package/dist/contracts/lifeops-connector-degradation.js +0 -17
  36. package/dist/contracts/lifeops-connector-degradation.js.map +0 -1
  37. package/dist/contracts/lifeops.d.ts +0 -3123
  38. package/dist/contracts/lifeops.d.ts.map +0 -1
  39. package/dist/contracts/lifeops.js +0 -635
  40. package/dist/contracts/lifeops.js.map +0 -1
  41. package/dist/contracts/permissions.d.ts +0 -39
  42. package/dist/contracts/permissions.d.ts.map +0 -1
  43. package/dist/contracts/permissions.js +0 -1
  44. package/dist/contracts/permissions.js.map +0 -1
  45. package/dist/default-packs/bedtime.d.ts +0 -14
  46. package/dist/default-packs/bedtime.d.ts.map +0 -1
  47. package/dist/default-packs/bedtime.js +0 -48
  48. package/dist/default-packs/bedtime.js.map +0 -1
  49. package/dist/default-packs/contract-stubs.d.ts +0 -161
  50. package/dist/default-packs/contract-stubs.d.ts.map +0 -1
  51. package/dist/default-packs/contract-stubs.js +0 -1
  52. package/dist/default-packs/contract-stubs.js.map +0 -1
  53. package/dist/default-packs/index.d.ts +0 -18
  54. package/dist/default-packs/index.d.ts.map +0 -1
  55. package/dist/default-packs/index.js +0 -39
  56. package/dist/default-packs/index.js.map +0 -1
  57. package/dist/default-packs/sleep-recap.d.ts +0 -14
  58. package/dist/default-packs/sleep-recap.d.ts.map +0 -1
  59. package/dist/default-packs/sleep-recap.js +0 -51
  60. package/dist/default-packs/sleep-recap.js.map +0 -1
  61. package/dist/default-packs/wake-up.d.ts +0 -14
  62. package/dist/default-packs/wake-up.d.ts.map +0 -1
  63. package/dist/default-packs/wake-up.js +0 -61
  64. package/dist/default-packs/wake-up.js.map +0 -1
  65. package/dist/health-bridge/health-bridge.d.ts +0 -57
  66. package/dist/health-bridge/health-bridge.d.ts.map +0 -1
  67. package/dist/health-bridge/health-bridge.js +0 -558
  68. package/dist/health-bridge/health-bridge.js.map +0 -1
  69. package/dist/health-bridge/health-connectors.d.ts +0 -23
  70. package/dist/health-bridge/health-connectors.d.ts.map +0 -1
  71. package/dist/health-bridge/health-connectors.js +0 -1018
  72. package/dist/health-bridge/health-connectors.js.map +0 -1
  73. package/dist/health-bridge/health-oauth.d.ts +0 -62
  74. package/dist/health-bridge/health-oauth.d.ts.map +0 -1
  75. package/dist/health-bridge/health-oauth.js +0 -432
  76. package/dist/health-bridge/health-oauth.js.map +0 -1
  77. package/dist/health-bridge/health-provider-registry.d.ts +0 -89
  78. package/dist/health-bridge/health-provider-registry.d.ts.map +0 -1
  79. package/dist/health-bridge/health-provider-registry.js +0 -141
  80. package/dist/health-bridge/health-provider-registry.js.map +0 -1
  81. package/dist/health-bridge/health-records.d.ts +0 -14
  82. package/dist/health-bridge/health-records.d.ts.map +0 -1
  83. package/dist/health-bridge/health-records.js +0 -45
  84. package/dist/health-bridge/health-records.js.map +0 -1
  85. package/dist/health-bridge/index.d.ts +0 -22
  86. package/dist/health-bridge/index.d.ts.map +0 -1
  87. package/dist/health-bridge/index.js +0 -7
  88. package/dist/health-bridge/index.js.map +0 -1
  89. package/dist/health-bridge/service-normalize-health.d.ts +0 -3
  90. package/dist/health-bridge/service-normalize-health.d.ts.map +0 -1
  91. package/dist/health-bridge/service-normalize-health.js +0 -96
  92. package/dist/health-bridge/service-normalize-health.js.map +0 -1
  93. package/dist/index.d.ts +0 -41
  94. package/dist/index.d.ts.map +0 -1
  95. package/dist/index.js +0 -62
  96. package/dist/index.js.map +0 -1
  97. package/dist/screen-time/index.d.ts +0 -23
  98. package/dist/screen-time/index.d.ts.map +0 -1
  99. package/dist/screen-time/index.js +0 -1
  100. package/dist/screen-time/index.js.map +0 -1
  101. package/dist/sleep/awake-probability.d.ts +0 -11
  102. package/dist/sleep/awake-probability.d.ts.map +0 -1
  103. package/dist/sleep/awake-probability.js +0 -163
  104. package/dist/sleep/awake-probability.js.map +0 -1
  105. package/dist/sleep/circadian-rules.d.ts +0 -45
  106. package/dist/sleep/circadian-rules.d.ts.map +0 -1
  107. package/dist/sleep/circadian-rules.js +0 -258
  108. package/dist/sleep/circadian-rules.js.map +0 -1
  109. package/dist/sleep/index.d.ts +0 -21
  110. package/dist/sleep/index.d.ts.map +0 -1
  111. package/dist/sleep/index.js +0 -11
  112. package/dist/sleep/index.js.map +0 -1
  113. package/dist/sleep/sleep-cycle-dispatch.d.ts +0 -75
  114. package/dist/sleep/sleep-cycle-dispatch.d.ts.map +0 -1
  115. package/dist/sleep/sleep-cycle-dispatch.js +0 -102
  116. package/dist/sleep/sleep-cycle-dispatch.js.map +0 -1
  117. package/dist/sleep/sleep-cycle.d.ts +0 -38
  118. package/dist/sleep/sleep-cycle.d.ts.map +0 -1
  119. package/dist/sleep/sleep-cycle.js +0 -418
  120. package/dist/sleep/sleep-cycle.js.map +0 -1
  121. package/dist/sleep/sleep-episode-store.d.ts +0 -25
  122. package/dist/sleep/sleep-episode-store.d.ts.map +0 -1
  123. package/dist/sleep/sleep-episode-store.js +0 -69
  124. package/dist/sleep/sleep-episode-store.js.map +0 -1
  125. package/dist/sleep/sleep-episode-types.d.ts +0 -38
  126. package/dist/sleep/sleep-episode-types.d.ts.map +0 -1
  127. package/dist/sleep/sleep-episode-types.js +0 -14
  128. package/dist/sleep/sleep-episode-types.js.map +0 -1
  129. package/dist/sleep/sleep-recap.d.ts +0 -19
  130. package/dist/sleep/sleep-recap.d.ts.map +0 -1
  131. package/dist/sleep/sleep-recap.js +0 -1
  132. package/dist/sleep/sleep-recap.js.map +0 -1
  133. package/dist/sleep/sleep-regularity.d.ts +0 -19
  134. package/dist/sleep/sleep-regularity.d.ts.map +0 -1
  135. package/dist/sleep/sleep-regularity.js +0 -242
  136. package/dist/sleep/sleep-regularity.js.map +0 -1
  137. package/dist/sleep/sleep-wake-events.d.ts +0 -58
  138. package/dist/sleep/sleep-wake-events.d.ts.map +0 -1
  139. package/dist/sleep/sleep-wake-events.js +0 -135
  140. package/dist/sleep/sleep-wake-events.js.map +0 -1
  141. package/dist/sleep/source-reliability.d.ts +0 -38
  142. package/dist/sleep/source-reliability.d.ts.map +0 -1
  143. package/dist/sleep/source-reliability.js +0 -62
  144. package/dist/sleep/source-reliability.js.map +0 -1
  145. package/dist/util/index.d.ts +0 -10
  146. package/dist/util/index.d.ts.map +0 -1
  147. package/dist/util/index.js +0 -3
  148. package/dist/util/index.js.map +0 -1
  149. package/dist/util/normalize.d.ts +0 -22
  150. package/dist/util/normalize.d.ts.map +0 -1
  151. package/dist/util/normalize.js +0 -62
  152. package/dist/util/normalize.js.map +0 -1
  153. package/dist/util/time-util.d.ts +0 -10
  154. package/dist/util/time-util.d.ts.map +0 -1
  155. package/dist/util/time-util.js +0 -14
  156. package/dist/util/time-util.js.map +0 -1
  157. package/dist/util/time.d.ts +0 -17
  158. package/dist/util/time.d.ts.map +0 -1
  159. package/dist/util/time.js +0 -152
  160. package/dist/util/time.js.map +0 -1
  161. package/dist/util/token-encryption.d.ts +0 -42
  162. package/dist/util/token-encryption.d.ts.map +0 -1
  163. package/dist/util/token-encryption.js +0 -96
  164. package/dist/util/token-encryption.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/health-bridge/health-connectors.ts"],"sourcesContent":["import { logger } from \"@elizaos/core\";\nimport type {\n LifeOpsHealthMetric,\n LifeOpsHealthMetricSample,\n LifeOpsHealthSleepEpisode,\n LifeOpsHealthSleepStage,\n LifeOpsHealthWorkout,\n} from \"../contracts/health.js\";\nimport type { StoredHealthConnectorToken } from \"./health-oauth.js\";\nimport { requireHealthProviderSpec } from \"./health-provider-registry.js\";\nimport {\n createLifeOpsHealthMetricSample,\n createLifeOpsHealthSleepEpisode,\n createLifeOpsHealthWorkout,\n} from \"./health-records.js\";\n\nconst HEALTH_CONNECTOR_TIMEOUT_MS = 15_000;\nconst MAX_PAGINATION_PAGES = 5;\n\nexport class HealthConnectorApiError extends Error {\n constructor(\n public readonly status: number,\n public readonly provider: StoredHealthConnectorToken[\"provider\"],\n message: string,\n ) {\n super(message);\n this.name = \"HealthConnectorApiError\";\n }\n}\n\nexport interface HealthConnectorSyncPayload {\n samples: LifeOpsHealthMetricSample[];\n workouts: LifeOpsHealthWorkout[];\n sleepEpisodes: LifeOpsHealthSleepEpisode[];\n identity: Record<string, unknown> | null;\n cursor: string | null;\n}\n\ninterface SyncArgs {\n token: StoredHealthConnectorToken;\n grantId: string;\n startDate: string;\n endDate: string;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return null;\n }\n return value as Record<string, unknown>;\n}\n\nfunction asRecordArray(value: unknown): Record<string, unknown>[] {\n if (!Array.isArray(value)) {\n return [];\n }\n return value\n .map(asRecord)\n .filter((record): record is Record<string, unknown> => record !== null);\n}\n\nfunction getRecord(\n source: Record<string, unknown>,\n key: string,\n): Record<string, unknown> | null {\n return asRecord(source[key]);\n}\n\nfunction getArray(\n source: Record<string, unknown>,\n key: string,\n): Record<string, unknown>[] {\n return asRecordArray(source[key]);\n}\n\nfunction getText(source: Record<string, unknown>, key: string): string | null {\n const value = source[key];\n if (typeof value === \"string\" && value.trim().length > 0) {\n return value.trim();\n }\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return String(value);\n }\n return null;\n}\n\nfunction getNumber(\n source: Record<string, unknown>,\n key: string,\n): number | null {\n const value = source[key];\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === \"string\" && value.trim().length > 0) {\n const parsed = Number(value);\n if (Number.isFinite(parsed)) {\n return parsed;\n }\n }\n return null;\n}\n\nfunction getBoolean(\n source: Record<string, unknown>,\n key: string,\n): boolean | null {\n const value = source[key];\n return typeof value === \"boolean\" ? value : null;\n}\n\nfunction isoFromUnixSeconds(value: number | null): string | null {\n if (value === null) {\n return null;\n }\n return new Date(value * 1_000).toISOString();\n}\n\nfunction normalizeIso(value: string | null): string | null {\n if (!value) {\n return null;\n }\n const parsed = Date.parse(value);\n return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;\n}\n\nfunction localDateFromIso(value: string): string {\n return value.slice(0, 10);\n}\n\nfunction addDays(date: string, days: number): string {\n const parsed = Date.parse(`${date}T00:00:00.000Z`);\n return new Date(parsed + days * 86_400_000).toISOString().slice(0, 10);\n}\n\nfunction dateRange(startDate: string, endDate: string, maxDays = 31): string[] {\n const dates: string[] = [];\n let current = startDate;\n while (current <= endDate && dates.length < maxDays) {\n dates.push(current);\n current = addDays(current, 1);\n }\n return dates;\n}\n\nfunction ymdCompact(date: string): string {\n return date.replace(/-/g, \"\");\n}\n\nfunction authHeader(token: StoredHealthConnectorToken): string {\n const type = token.tokenType.trim().length > 0 ? token.tokenType : \"Bearer\";\n return `${type} ${token.accessToken}`;\n}\n\nfunction providerMockBase(\n provider: StoredHealthConnectorToken[\"provider\"],\n): string | null {\n const key = `ELIZA_MOCK_${provider.toUpperCase()}_BASE`;\n const value = process.env[key] ?? process.env.ELIZA_MOCK_HEALTH_BASE;\n if (!value) {\n return null;\n }\n const url = new URL(value);\n if (![\"localhost\", \"127.0.0.1\", \"::1\", \"[::1]\"].includes(url.hostname)) {\n throw new HealthConnectorApiError(\n 409,\n provider,\n \"Health connector mock base must point to loopback.\",\n );\n }\n return url.toString().replace(/\\/+$/, \"\");\n}\n\nfunction providerBaseUrl(\n provider: StoredHealthConnectorToken[\"provider\"],\n): string {\n const mock = providerMockBase(provider);\n if (mock) {\n return mock;\n }\n // Base URL provided by the connector contribution; the dispatcher does not\n // hardcode. The registry's `apiBaseUrl` is the single source of truth for\n // the per-provider REST endpoint.\n return requireHealthProviderSpec(provider).apiBaseUrl;\n}\n\nasync function readJsonResponse(\n response: Response,\n provider: StoredHealthConnectorToken[\"provider\"],\n): Promise<unknown> {\n const text = await response.text();\n if (!response.ok) {\n throw new HealthConnectorApiError(\n response.status,\n provider,\n text || `${provider} API request failed with HTTP ${response.status}.`,\n );\n }\n if (text.trim().length === 0) {\n return {};\n }\n return JSON.parse(text) as unknown;\n}\n\nasync function fetchHealthJson(args: {\n token: StoredHealthConnectorToken;\n path: string;\n query?: Record<string, string | number | null | undefined>;\n method?: \"GET\" | \"POST\";\n form?: URLSearchParams;\n}): Promise<Record<string, unknown>> {\n const json = await fetchHealthValue(args);\n const record = asRecord(json);\n if (!record) {\n throw new HealthConnectorApiError(\n 502,\n args.token.provider,\n `${args.token.provider} API returned a non-object response.`,\n );\n }\n const status = getNumber(record, \"status\");\n if (status !== null && status !== 0) {\n throw new HealthConnectorApiError(\n 502,\n args.token.provider,\n `${args.token.provider} API returned status ${status}.`,\n );\n }\n return record;\n}\n\nasync function fetchHealthValue(args: {\n token: StoredHealthConnectorToken;\n path: string;\n query?: Record<string, string | number | null | undefined>;\n method?: \"GET\" | \"POST\";\n form?: URLSearchParams;\n}): Promise<unknown> {\n const url = new URL(`${providerBaseUrl(args.token.provider)}${args.path}`);\n for (const [key, value] of Object.entries(args.query ?? {})) {\n if (value !== null && value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n const response = await fetch(url, {\n method: args.method ?? \"GET\",\n headers: {\n Accept: \"application/json\",\n Authorization: authHeader(args.token),\n ...(args.form\n ? { \"Content-Type\": \"application/x-www-form-urlencoded\" }\n : {}),\n },\n body: args.form,\n signal: AbortSignal.timeout(HEALTH_CONNECTOR_TIMEOUT_MS),\n });\n return readJsonResponse(response, args.token.provider);\n}\n\nfunction sample(args: {\n token: StoredHealthConnectorToken;\n grantId: string;\n metric: LifeOpsHealthMetric;\n value: number | null;\n unit: string;\n startAt: string | null;\n endAt?: string | null;\n sourceExternalId: string;\n metadata?: Record<string, unknown>;\n}): LifeOpsHealthMetricSample | null {\n if (args.value === null || !Number.isFinite(args.value) || !args.startAt) {\n return null;\n }\n return createLifeOpsHealthMetricSample({\n agentId: args.token.agentId,\n provider: args.token.provider,\n grantId: args.grantId,\n metric: args.metric,\n value: args.value,\n unit: args.unit,\n startAt: args.startAt,\n endAt: args.endAt ?? args.startAt,\n localDate: localDateFromIso(args.startAt),\n sourceExternalId: args.sourceExternalId,\n metadata: args.metadata ?? {},\n });\n}\n\nfunction compactSamples(\n samples: Array<LifeOpsHealthMetricSample | null>,\n): LifeOpsHealthMetricSample[] {\n return samples.filter(\n (entry): entry is LifeOpsHealthMetricSample => entry !== null,\n );\n}\n\nasync function syncStrava(args: SyncArgs): Promise<HealthConnectorSyncPayload> {\n const after = Math.floor(\n Date.parse(`${args.startDate}T00:00:00.000Z`) / 1_000,\n );\n const before = Math.floor(\n Date.parse(`${args.endDate}T23:59:59.999Z`) / 1_000,\n );\n const [athlete, activitiesJson] = await Promise.all([\n fetchHealthJson({ token: args.token, path: \"/athlete\" }),\n fetchHealthValue({\n token: args.token,\n path: \"/athlete/activities\",\n query: { after, before, per_page: 200 },\n }),\n ]);\n const activities = asRecordArray(activitiesJson);\n const workouts: LifeOpsHealthWorkout[] = [];\n const samples: LifeOpsHealthMetricSample[] = [];\n for (const activity of activities) {\n const id = getText(activity, \"id\");\n const startAt = normalizeIso(getText(activity, \"start_date\"));\n if (!id || !startAt) {\n continue;\n }\n const elapsedSeconds = getNumber(activity, \"elapsed_time\");\n const movingSeconds = getNumber(activity, \"moving_time\");\n const durationSeconds = Math.trunc(movingSeconds ?? elapsedSeconds ?? 0);\n const endAt =\n durationSeconds > 0\n ? new Date(Date.parse(startAt) + durationSeconds * 1_000).toISOString()\n : null;\n const calories = getNumber(activity, \"calories\");\n const distance = getNumber(activity, \"distance\");\n const averageHeartRate = getNumber(activity, \"average_heartrate\");\n const maxHeartRate = getNumber(activity, \"max_heartrate\");\n workouts.push(\n createLifeOpsHealthWorkout({\n agentId: args.token.agentId,\n provider: \"strava\",\n grantId: args.grantId,\n sourceExternalId: id,\n workoutType:\n getText(activity, \"sport_type\") ?? getText(activity, \"type\") ?? \"run\",\n title: getText(activity, \"name\") ?? \"\",\n startAt,\n endAt,\n durationSeconds,\n distanceMeters: distance,\n calories,\n averageHeartRate,\n maxHeartRate,\n metadata: {\n elapsedSeconds,\n movingSeconds,\n elevationGainMeters: getNumber(activity, \"total_elevation_gain\"),\n averageSpeedMetersPerSecond: getNumber(activity, \"average_speed\"),\n maxSpeedMetersPerSecond: getNumber(activity, \"max_speed\"),\n averageWatts: getNumber(activity, \"average_watts\"),\n averageCadence: getNumber(activity, \"average_cadence\"),\n },\n }),\n );\n samples.push(\n ...compactSamples([\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"distance_meters\",\n value: distance,\n unit: \"m\",\n startAt,\n endAt,\n sourceExternalId: `${id}:distance_meters`,\n }),\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"active_minutes\",\n value: durationSeconds / 60,\n unit: \"min\",\n startAt,\n endAt,\n sourceExternalId: `${id}:active_minutes`,\n }),\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"calories\",\n value: calories,\n unit: \"kcal\",\n startAt,\n endAt,\n sourceExternalId: `${id}:calories`,\n }),\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"heart_rate\",\n value: averageHeartRate,\n unit: \"bpm\",\n startAt,\n endAt,\n sourceExternalId: `${id}:heart_rate`,\n }),\n ]),\n );\n }\n return {\n samples,\n workouts,\n sleepEpisodes: [],\n identity: athlete,\n cursor: null,\n };\n}\n\nasync function syncFitbit(args: SyncArgs): Promise<HealthConnectorSyncPayload> {\n const dates = dateRange(args.startDate, args.endDate);\n const identityJson = await fetchHealthJson({\n token: args.token,\n path: \"/1/user/-/profile.json\",\n });\n const samples: LifeOpsHealthMetricSample[] = [];\n const sleepEpisodes: LifeOpsHealthSleepEpisode[] = [];\n const workouts: LifeOpsHealthWorkout[] = [];\n for (const date of dates) {\n const [activity, sleep, heart, weight] = await Promise.all([\n fetchHealthJson({\n token: args.token,\n path: `/1/user/-/activities/date/${date}.json`,\n }),\n fetchHealthJson({\n token: args.token,\n path: `/1.2/user/-/sleep/date/${date}.json`,\n }),\n fetchHealthJson({\n token: args.token,\n path: `/1/user/-/activities/heart/date/${date}/1d.json`,\n }),\n fetchHealthJson({\n token: args.token,\n path: `/1/user/-/body/log/weight/date/${date}.json`,\n }),\n ]);\n const dayAt = `${date}T12:00:00.000Z`;\n const summary = getRecord(activity, \"summary\") ?? {};\n const distances = getArray(summary, \"distances\");\n const totalDistance = distances.reduce((sum, entry) => {\n const distance = getNumber(entry, \"distance\");\n return sum + (distance ?? 0);\n }, 0);\n samples.push(\n ...compactSamples([\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"steps\",\n value: getNumber(summary, \"steps\"),\n unit: \"count\",\n startAt: dayAt,\n sourceExternalId: `${date}:fitbit:steps`,\n }),\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"active_minutes\",\n value:\n (getNumber(summary, \"fairlyActiveMinutes\") ?? 0) +\n (getNumber(summary, \"veryActiveMinutes\") ?? 0),\n unit: \"min\",\n startAt: dayAt,\n sourceExternalId: `${date}:fitbit:active_minutes`,\n }),\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"calories\",\n value: getNumber(summary, \"caloriesOut\"),\n unit: \"kcal\",\n startAt: dayAt,\n sourceExternalId: `${date}:fitbit:calories`,\n }),\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"distance_meters\",\n value: totalDistance > 0 ? totalDistance * 1_000 : null,\n unit: \"m\",\n startAt: dayAt,\n sourceExternalId: `${date}:fitbit:distance_meters`,\n }),\n ]),\n );\n\n const heartEntries = getArray(heart, \"activities-heart\");\n const heartValue = heartEntries\n .map((entry) => getRecord(entry, \"value\"))\n .find((entry): entry is Record<string, unknown> => entry !== null);\n samples.push(\n ...compactSamples([\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"resting_heart_rate\",\n value: heartValue ? getNumber(heartValue, \"restingHeartRate\") : null,\n unit: \"bpm\",\n startAt: dayAt,\n sourceExternalId: `${date}:fitbit:resting_heart_rate`,\n }),\n ]),\n );\n\n const sleepSummary = getRecord(sleep, \"summary\") ?? {};\n samples.push(\n ...compactSamples([\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"sleep_hours\",\n value:\n getNumber(sleepSummary, \"totalMinutesAsleep\") !== null\n ? (getNumber(sleepSummary, \"totalMinutesAsleep\") ?? 0) / 60\n : null,\n unit: \"h\",\n startAt: dayAt,\n sourceExternalId: `${date}:fitbit:sleep_hours`,\n }),\n ]),\n );\n for (const sleepLog of getArray(sleep, \"sleep\")) {\n const logId =\n getText(sleepLog, \"logId\") ??\n `${date}:${getText(sleepLog, \"startTime\") ?? \"sleep\"}`;\n const startAt = normalizeIso(getText(sleepLog, \"startTime\"));\n const endAt = normalizeIso(getText(sleepLog, \"endTime\"));\n if (!startAt || !endAt) {\n continue;\n }\n sleepEpisodes.push(\n createLifeOpsHealthSleepEpisode({\n agentId: args.token.agentId,\n provider: \"fitbit\",\n grantId: args.grantId,\n sourceExternalId: logId,\n localDate: date,\n timezone: null,\n startAt,\n endAt,\n isMainSleep: getBoolean(sleepLog, \"isMainSleep\") ?? false,\n sleepType: getText(sleepLog, \"type\"),\n durationSeconds: Math.trunc(\n (getNumber(sleepLog, \"duration\") ?? 0) / 1_000,\n ),\n timeInBedSeconds:\n getNumber(sleepLog, \"timeInBed\") !== null\n ? Math.trunc((getNumber(sleepLog, \"timeInBed\") ?? 0) * 60)\n : null,\n efficiency: getNumber(sleepLog, \"efficiency\"),\n latencySeconds:\n getNumber(sleepLog, \"minutesToFallAsleep\") !== null\n ? Math.trunc(\n (getNumber(sleepLog, \"minutesToFallAsleep\") ?? 0) * 60,\n )\n : null,\n awakeSeconds:\n getNumber(sleepLog, \"minutesAwake\") !== null\n ? Math.trunc((getNumber(sleepLog, \"minutesAwake\") ?? 0) * 60)\n : null,\n lightSleepSeconds: null,\n deepSleepSeconds: null,\n remSleepSeconds: null,\n sleepScore: getNumber(sleepLog, \"efficiency\"),\n readinessScore: null,\n averageHeartRate: null,\n lowestHeartRate: null,\n averageHrvMs: null,\n respiratoryRate: null,\n bloodOxygenPercent: null,\n stageSamples: fitbitStageSamples(sleepLog),\n metadata: { rawDateOfSleep: getText(sleepLog, \"dateOfSleep\") },\n }),\n );\n }\n\n for (const log of getArray(weight, \"weight\")) {\n const loggedAt = normalizeIso(\n `${getText(log, \"date\") ?? date}T${getText(log, \"time\") ?? \"12:00:00\"}`,\n );\n samples.push(\n ...compactSamples([\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"weight_kg\",\n value: getNumber(log, \"weight\"),\n unit: \"kg\",\n startAt: loggedAt,\n sourceExternalId:\n getText(log, \"logId\") ?? `${date}:fitbit:weight_kg`,\n metadata: { providerUnit: getText(log, \"weightUnit\") },\n }),\n ]),\n );\n }\n }\n return {\n samples,\n workouts,\n sleepEpisodes,\n identity: getRecord(identityJson, \"user\") ?? identityJson,\n cursor: null,\n };\n}\n\nfunction fitbitStageSamples(\n sleepLog: Record<string, unknown>,\n): LifeOpsHealthSleepEpisode[\"stageSamples\"] {\n const levels = getRecord(sleepLog, \"levels\");\n const data = levels ? getArray(levels, \"data\") : [];\n const samples: LifeOpsHealthSleepEpisode[\"stageSamples\"] = [];\n for (const entry of data) {\n const startAt = normalizeIso(getText(entry, \"dateTime\"));\n const seconds = getNumber(entry, \"seconds\");\n if (!startAt || seconds === null) {\n continue;\n }\n samples.push({\n stage: fitbitStage(getText(entry, \"level\")),\n startAt,\n endAt: new Date(Date.parse(startAt) + seconds * 1_000).toISOString(),\n confidence: null,\n providerCode: getText(entry, \"level\"),\n });\n }\n return samples;\n}\n\nfunction fitbitStage(value: string | null): LifeOpsHealthSleepStage {\n if (value === \"wake\" || value === \"awake\") return \"awake\";\n if (value === \"light\") return \"light\";\n if (value === \"deep\") return \"deep\";\n if (value === \"rem\") return \"rem\";\n if (value === \"restless\") return \"restless\";\n return \"unknown\";\n}\n\nasync function fetchOuraCollection(args: {\n token: StoredHealthConnectorToken;\n path: string;\n query: Record<string, string>;\n}): Promise<Record<string, unknown>[]> {\n const items: Record<string, unknown>[] = [];\n let nextToken: string | null = null;\n for (let page = 0; page < MAX_PAGINATION_PAGES; page += 1) {\n const json = await fetchHealthJson({\n token: args.token,\n path: args.path,\n query: { ...args.query, next_token: nextToken },\n });\n items.push(...getArray(json, \"data\"));\n nextToken = getText(json, \"next_token\");\n if (!nextToken) {\n break;\n }\n }\n return items;\n}\n\nasync function syncOura(args: SyncArgs): Promise<HealthConnectorSyncPayload> {\n const startDatetime = `${args.startDate}T00:00:00Z`;\n const endDatetime = `${args.endDate}T23:59:59Z`;\n const [\n personal,\n dailyActivity,\n dailyReadiness,\n sleep,\n heartRate,\n workoutsRaw,\n ] = await Promise.all([\n fetchHealthJson({\n token: args.token,\n path: \"/v2/usercollection/personal_info\",\n }),\n fetchOuraCollection({\n token: args.token,\n path: \"/v2/usercollection/daily_activity\",\n query: { start_date: args.startDate, end_date: args.endDate },\n }),\n fetchOuraCollection({\n token: args.token,\n path: \"/v2/usercollection/daily_readiness\",\n query: { start_date: args.startDate, end_date: args.endDate },\n }),\n fetchOuraCollection({\n token: args.token,\n path: \"/v2/usercollection/sleep\",\n query: { start_date: args.startDate, end_date: args.endDate },\n }),\n fetchOuraCollection({\n token: args.token,\n path: \"/v2/usercollection/heartrate\",\n query: { start_datetime: startDatetime, end_datetime: endDatetime },\n }),\n fetchOuraCollection({\n token: args.token,\n path: \"/v2/usercollection/workout\",\n query: { start_date: args.startDate, end_date: args.endDate },\n }),\n ]);\n const samples: LifeOpsHealthMetricSample[] = [];\n const sleepEpisodes: LifeOpsHealthSleepEpisode[] = [];\n const workouts: LifeOpsHealthWorkout[] = [];\n for (const day of dailyActivity) {\n const date = getText(day, \"day\");\n const startAt = date ? `${date}T12:00:00.000Z` : null;\n const id = getText(day, \"id\") ?? date ?? \"daily_activity\";\n samples.push(\n ...compactSamples([\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"steps\",\n value: getNumber(day, \"steps\"),\n unit: \"count\",\n startAt,\n sourceExternalId: `${id}:steps`,\n }),\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"calories\",\n value:\n getNumber(day, \"total_calories\") ??\n getNumber(day, \"active_calories\"),\n unit: \"kcal\",\n startAt,\n sourceExternalId: `${id}:calories`,\n }),\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"distance_meters\",\n value: getNumber(day, \"equivalent_walking_distance\"),\n unit: \"m\",\n startAt,\n sourceExternalId: `${id}:distance_meters`,\n }),\n ]),\n );\n }\n for (const readiness of dailyReadiness) {\n const date = getText(readiness, \"day\");\n const startAt = date ? `${date}T12:00:00.000Z` : null;\n samples.push(\n ...compactSamples([\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"readiness_score\",\n value: getNumber(readiness, \"score\"),\n unit: \"score\",\n startAt,\n sourceExternalId: `${getText(readiness, \"id\") ?? date}:readiness_score`,\n }),\n ]),\n );\n }\n for (const entry of sleep) {\n const id = getText(entry, \"id\");\n const startAt = normalizeIso(getText(entry, \"bedtime_start\"));\n const endAt = normalizeIso(getText(entry, \"bedtime_end\"));\n if (!id || !startAt || !endAt) {\n continue;\n }\n const date = getText(entry, \"day\") ?? localDateFromIso(startAt);\n const sleepScore = getNumber(entry, \"score\");\n samples.push(\n ...compactSamples([\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"sleep_hours\",\n value:\n getNumber(entry, \"total_sleep_duration\") !== null\n ? (getNumber(entry, \"total_sleep_duration\") ?? 0) / 3600\n : null,\n unit: \"h\",\n startAt,\n endAt,\n sourceExternalId: `${id}:sleep_hours`,\n }),\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"sleep_score\",\n value: sleepScore,\n unit: \"score\",\n startAt,\n endAt,\n sourceExternalId: `${id}:sleep_score`,\n }),\n ]),\n );\n sleepEpisodes.push(\n createLifeOpsHealthSleepEpisode({\n agentId: args.token.agentId,\n provider: \"oura\",\n grantId: args.grantId,\n sourceExternalId: id,\n localDate: date,\n timezone: getText(entry, \"timezone\"),\n startAt,\n endAt,\n isMainSleep: getText(entry, \"type\") === \"long_sleep\",\n sleepType: getText(entry, \"type\"),\n durationSeconds: Math.trunc(\n getNumber(entry, \"total_sleep_duration\") ?? 0,\n ),\n timeInBedSeconds:\n Math.trunc(getNumber(entry, \"time_in_bed\") ?? 0) || null,\n efficiency: getNumber(entry, \"efficiency\"),\n latencySeconds: Math.trunc(getNumber(entry, \"latency\") ?? 0) || null,\n awakeSeconds: Math.trunc(getNumber(entry, \"awake_time\") ?? 0) || null,\n lightSleepSeconds:\n Math.trunc(getNumber(entry, \"light_sleep_duration\") ?? 0) || null,\n deepSleepSeconds:\n Math.trunc(getNumber(entry, \"deep_sleep_duration\") ?? 0) || null,\n remSleepSeconds:\n Math.trunc(getNumber(entry, \"rem_sleep_duration\") ?? 0) || null,\n sleepScore,\n readinessScore: null,\n averageHeartRate: getNumber(entry, \"average_heart_rate\"),\n lowestHeartRate: getNumber(entry, \"lowest_heart_rate\"),\n averageHrvMs: getNumber(entry, \"average_hrv\"),\n respiratoryRate: getNumber(entry, \"average_breath\"),\n bloodOxygenPercent: null,\n stageSamples: [],\n metadata: { source: \"oura_sleep\" },\n }),\n );\n }\n for (const entry of heartRate) {\n const timestamp = normalizeIso(getText(entry, \"timestamp\"));\n const id = getText(entry, \"id\") ?? getText(entry, \"timestamp\") ?? \"heart\";\n samples.push(\n ...compactSamples([\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"heart_rate\",\n value: getNumber(entry, \"bpm\"),\n unit: \"bpm\",\n startAt: timestamp,\n sourceExternalId: `${id}:heart_rate`,\n metadata: { source: getText(entry, \"source\") },\n }),\n ]),\n );\n }\n for (const workout of workoutsRaw) {\n const id = getText(workout, \"id\");\n const startAt = normalizeIso(getText(workout, \"start_datetime\"));\n const endAt = normalizeIso(getText(workout, \"end_datetime\"));\n if (!id || !startAt) {\n continue;\n }\n workouts.push(\n createLifeOpsHealthWorkout({\n agentId: args.token.agentId,\n provider: \"oura\",\n grantId: args.grantId,\n sourceExternalId: id,\n workoutType: getText(workout, \"activity\") ?? \"workout\",\n title: getText(workout, \"activity\") ?? \"\",\n startAt,\n endAt,\n durationSeconds:\n endAt !== null\n ? Math.max(\n 0,\n Math.trunc((Date.parse(endAt) - Date.parse(startAt)) / 1_000),\n )\n : 0,\n distanceMeters: getNumber(workout, \"distance\"),\n calories: getNumber(workout, \"calories\"),\n averageHeartRate: null,\n maxHeartRate: null,\n metadata: { source: \"oura_workout\" },\n }),\n );\n }\n return {\n samples,\n workouts,\n sleepEpisodes,\n identity: getRecord(personal, \"data\") ?? personal,\n cursor: null,\n };\n}\n\nasync function withingsPost(\n token: StoredHealthConnectorToken,\n path: string,\n formValues: Record<string, string | number | null | undefined>,\n): Promise<Record<string, unknown>> {\n const form = new URLSearchParams();\n for (const [key, value] of Object.entries(formValues)) {\n if (value !== null && value !== undefined) {\n form.set(key, String(value));\n }\n }\n return fetchHealthJson({ token, path, method: \"POST\", form });\n}\n\nasync function syncWithings(\n args: SyncArgs,\n): Promise<HealthConnectorSyncPayload> {\n const startUnix = Math.floor(\n Date.parse(`${args.startDate}T00:00:00.000Z`) / 1_000,\n );\n const endUnix = Math.floor(\n Date.parse(`${args.endDate}T23:59:59.999Z`) / 1_000,\n );\n const [activityJson, sleepJson, measuresJson] = await Promise.all([\n withingsPost(args.token, \"/v2/measure\", {\n action: \"getactivity\",\n startdateymd: ymdCompact(args.startDate),\n enddateymd: ymdCompact(args.endDate),\n }),\n withingsPost(args.token, \"/v2/sleep\", {\n action: \"getsummary\",\n startdateymd: ymdCompact(args.startDate),\n enddateymd: ymdCompact(args.endDate),\n }),\n withingsPost(args.token, \"/measure\", {\n action: \"getmeas\",\n startdate: startUnix,\n enddate: endUnix,\n meastype: \"1,9,10,11,54,71,73\",\n }),\n ]);\n const samples: LifeOpsHealthMetricSample[] = [];\n const workouts: LifeOpsHealthWorkout[] = [];\n const sleepEpisodes: LifeOpsHealthSleepEpisode[] = [];\n const activityBody = getRecord(activityJson, \"body\") ?? {};\n for (const entry of getArray(activityBody, \"activities\")) {\n const date = getText(entry, \"date\");\n const startAt = date ? `${date}T12:00:00.000Z` : null;\n const id = date ?? \"activity\";\n samples.push(\n ...compactSamples([\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"steps\",\n value: getNumber(entry, \"steps\"),\n unit: \"count\",\n startAt,\n sourceExternalId: `${id}:withings:steps`,\n }),\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"active_minutes\",\n value: getNumber(entry, \"active\"),\n unit: \"min\",\n startAt,\n sourceExternalId: `${id}:withings:active_minutes`,\n }),\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"calories\",\n value:\n getNumber(entry, \"totalcalories\") ?? getNumber(entry, \"calories\"),\n unit: \"kcal\",\n startAt,\n sourceExternalId: `${id}:withings:calories`,\n }),\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"distance_meters\",\n value: getNumber(entry, \"distance\"),\n unit: \"m\",\n startAt,\n sourceExternalId: `${id}:withings:distance_meters`,\n }),\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"heart_rate\",\n value: getNumber(entry, \"hr_average\"),\n unit: \"bpm\",\n startAt,\n sourceExternalId: `${id}:withings:heart_rate`,\n }),\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"resting_heart_rate\",\n value: getNumber(entry, \"hr_resting\"),\n unit: \"bpm\",\n startAt,\n sourceExternalId: `${id}:withings:resting_heart_rate`,\n }),\n ]),\n );\n }\n const sleepBody = getRecord(sleepJson, \"body\") ?? {};\n for (const entry of getArray(sleepBody, \"series\")) {\n const startAt = isoFromUnixSeconds(getNumber(entry, \"startdate\"));\n const endAt = isoFromUnixSeconds(getNumber(entry, \"enddate\"));\n if (!startAt || !endAt) {\n continue;\n }\n const data = getRecord(entry, \"data\") ?? {};\n const externalId =\n getText(entry, \"id\") ?? `${getText(entry, \"startdate\") ?? startAt}:sleep`;\n const date = getText(entry, \"date\") ?? localDateFromIso(startAt);\n const durationSeconds =\n getNumber(data, \"total_sleep_time\") ??\n Math.max(\n 0,\n Math.trunc((Date.parse(endAt) - Date.parse(startAt)) / 1_000),\n );\n samples.push(\n ...compactSamples([\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: \"sleep_hours\",\n value: durationSeconds / 3600,\n unit: \"h\",\n startAt,\n endAt,\n sourceExternalId: `${externalId}:sleep_hours`,\n }),\n ]),\n );\n sleepEpisodes.push(\n createLifeOpsHealthSleepEpisode({\n agentId: args.token.agentId,\n provider: \"withings\",\n grantId: args.grantId,\n sourceExternalId: externalId,\n localDate: date,\n timezone: getText(entry, \"timezone\"),\n startAt,\n endAt,\n isMainSleep: true,\n sleepType: \"summary\",\n durationSeconds,\n timeInBedSeconds:\n Math.trunc(getNumber(data, \"wakeupduration\") ?? 0) + durationSeconds,\n efficiency: null,\n latencySeconds: getNumber(data, \"durationtosleep\"),\n awakeSeconds: getNumber(data, \"wakeupduration\"),\n lightSleepSeconds: getNumber(data, \"lightduration\"),\n deepSleepSeconds: getNumber(data, \"deepduration\"),\n remSleepSeconds: getNumber(data, \"remduration\"),\n sleepScore: null,\n readinessScore: null,\n averageHeartRate: getNumber(data, \"hr_average\"),\n lowestHeartRate: getNumber(data, \"hr_min\"),\n averageHrvMs: null,\n respiratoryRate: getNumber(data, \"rr_average\"),\n bloodOxygenPercent: null,\n stageSamples: [],\n metadata: { source: \"withings_sleep_summary\" },\n }),\n );\n }\n const measuresBody = getRecord(measuresJson, \"body\") ?? {};\n for (const group of getArray(measuresBody, \"measuregrps\")) {\n const measuredAt = isoFromUnixSeconds(getNumber(group, \"date\"));\n const groupId =\n getText(group, \"grpid\") ?? getText(group, \"date\") ?? \"measure\";\n for (const measure of getArray(group, \"measures\")) {\n const mapped = mapWithingsMeasure(measure);\n if (!mapped) {\n continue;\n }\n samples.push(\n ...compactSamples([\n sample({\n token: args.token,\n grantId: args.grantId,\n metric: mapped.metric,\n value: mapped.value,\n unit: mapped.unit,\n startAt: measuredAt,\n sourceExternalId: `${groupId}:withings:${mapped.metric}`,\n metadata: { withingsType: getNumber(measure, \"type\") },\n }),\n ]),\n );\n }\n }\n return {\n samples,\n workouts,\n sleepEpisodes,\n identity: args.token.identity,\n cursor: null,\n };\n}\n\nfunction mapWithingsMeasure(\n measure: Record<string, unknown>,\n): { metric: LifeOpsHealthMetric; value: number; unit: string } | null {\n const type = getNumber(measure, \"type\");\n const value = getNumber(measure, \"value\");\n const unit = getNumber(measure, \"unit\");\n if (type === null || value === null || unit === null) {\n return null;\n }\n const normalizedValue = value * 10 ** unit;\n switch (type) {\n case 1:\n return { metric: \"weight_kg\", value: normalizedValue, unit: \"kg\" };\n case 9:\n return {\n metric: \"blood_pressure_diastolic\",\n value: normalizedValue,\n unit: \"mmHg\",\n };\n case 10:\n return {\n metric: \"blood_pressure_systolic\",\n value: normalizedValue,\n unit: \"mmHg\",\n };\n case 11:\n return { metric: \"heart_rate\", value: normalizedValue, unit: \"bpm\" };\n case 54:\n return {\n metric: \"blood_oxygen_percent\",\n value: normalizedValue,\n unit: \"%\",\n };\n case 71:\n return {\n metric: \"body_temperature_celsius\",\n value: normalizedValue,\n unit: \"C\",\n };\n case 73:\n return {\n metric: \"heart_rate_variability\",\n value: normalizedValue,\n unit: \"ms\",\n };\n default:\n return null;\n }\n}\n\nexport async function syncHealthConnectorData(\n args: SyncArgs,\n): Promise<HealthConnectorSyncPayload> {\n logger.debug(\n {\n boundary: \"lifeops\",\n operation: \"health_connector_sync\",\n provider: args.token.provider,\n agentId: args.token.agentId,\n startDate: args.startDate,\n endDate: args.endDate,\n },\n \"[lifeops] Syncing health connector data\",\n );\n switch (args.token.provider) {\n case \"strava\":\n return syncStrava(args);\n case \"fitbit\":\n return syncFitbit(args);\n case \"withings\":\n return syncWithings(args);\n case \"oura\":\n return syncOura(args);\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,62 +0,0 @@
1
- import type { LifeOpsConnectorMode, LifeOpsConnectorSide, LifeOpsHealthConnectorCapability, LifeOpsHealthConnectorProvider, StartLifeOpsHealthConnectorResponse } from "../contracts/health.js";
2
- export declare class HealthOAuthError extends Error {
3
- readonly status: number;
4
- constructor(status: number, message: string);
5
- }
6
- export interface StoredHealthConnectorToken {
7
- provider: LifeOpsHealthConnectorProvider;
8
- agentId: string;
9
- side: LifeOpsConnectorSide;
10
- mode: LifeOpsConnectorMode;
11
- clientId: string;
12
- clientSecret: string | null;
13
- redirectUri: string;
14
- accessToken: string;
15
- refreshToken: string | null;
16
- tokenType: string;
17
- grantedScopes: string[];
18
- expiresAt: number | null;
19
- identity: Record<string, unknown>;
20
- createdAt: string;
21
- updatedAt: string;
22
- }
23
- export interface HealthConnectorCallbackResult {
24
- agentId: string;
25
- provider: LifeOpsHealthConnectorProvider;
26
- side: LifeOpsConnectorSide;
27
- mode: LifeOpsConnectorMode;
28
- tokenRef: string;
29
- identity: Record<string, unknown>;
30
- grantedCapabilities: LifeOpsHealthConnectorCapability[];
31
- grantedScopes: string[];
32
- expiresAt: string | null;
33
- hasRefreshToken: boolean;
34
- }
35
- export interface ResolvedHealthOAuthConfig {
36
- provider: LifeOpsHealthConnectorProvider;
37
- mode: LifeOpsConnectorMode;
38
- defaultMode: LifeOpsConnectorMode;
39
- availableModes: LifeOpsConnectorMode[];
40
- configured: boolean;
41
- clientId: string | null;
42
- clientSecret: string | null;
43
- redirectUri: string;
44
- }
45
- export declare function healthConnectorCapabilities(provider: LifeOpsHealthConnectorProvider): LifeOpsHealthConnectorCapability[];
46
- export declare function healthConnectorScopes(provider: LifeOpsHealthConnectorProvider): string[];
47
- export declare function healthScopesToCapabilities(provider: LifeOpsHealthConnectorProvider, scopes: readonly string[]): LifeOpsHealthConnectorCapability[];
48
- export declare function resolveHealthOAuthConfig(provider: LifeOpsHealthConnectorProvider, requestUrl: URL, requestedMode?: LifeOpsConnectorMode, env?: NodeJS.ProcessEnv): ResolvedHealthOAuthConfig;
49
- export declare function readStoredHealthToken(tokenRef: string | null | undefined, env?: NodeJS.ProcessEnv): StoredHealthConnectorToken | null;
50
- export declare function deleteStoredHealthToken(tokenRef: string | null | undefined, env?: NodeJS.ProcessEnv): void;
51
- export declare function refreshStoredHealthToken(tokenRef: string | null | undefined): Promise<StoredHealthConnectorToken | null>;
52
- export declare function startHealthConnectorOAuth(args: {
53
- provider: LifeOpsHealthConnectorProvider;
54
- agentId: string;
55
- side: LifeOpsConnectorSide;
56
- mode?: LifeOpsConnectorMode;
57
- requestUrl: URL;
58
- redirectUrl?: string;
59
- capabilities?: LifeOpsHealthConnectorCapability[];
60
- }): StartLifeOpsHealthConnectorResponse;
61
- export declare function completeHealthConnectorOAuth(callbackUrl: URL): Promise<HealthConnectorCallbackResult>;
62
- //# sourceMappingURL=health-oauth.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"health-oauth.d.ts","sourceRoot":"","sources":["../../src/health-bridge/health-oauth.ts"],"names":[],"mappings":"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"}
@@ -1,432 +0,0 @@
1
- import crypto from "node:crypto";
2
- import fs from "node:fs";
3
- import path from "node:path";
4
- import { resolveOAuthDir } from "@elizaos/core";
5
- import {
6
- decryptTokenEnvelope,
7
- encryptTokenPayload,
8
- isEncryptedTokenEnvelope,
9
- resolveTokenEncryptionKey
10
- } from "../util/token-encryption.js";
11
- import {
12
- requireHealthProviderSpec
13
- } from "./health-provider-registry.js";
14
- const HEALTH_OAUTH_SESSION_TTL_MS = 10 * 60 * 1e3;
15
- const ACCESS_TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
16
- const pendingHealthOAuthSessions = /* @__PURE__ */ new Map();
17
- class HealthOAuthError extends Error {
18
- constructor(status, message) {
19
- super(message);
20
- this.status = status;
21
- this.name = "HealthOAuthError";
22
- }
23
- status;
24
- }
25
- function isWrappedHealthTokenResponse(value) {
26
- return "status" in value || "body" in value || "error" in value;
27
- }
28
- function unwrapHealthTokenResponse(value, provider) {
29
- if (isWrappedHealthTokenResponse(value)) {
30
- if (value.status !== void 0 && value.status !== 0) {
31
- throw new HealthOAuthError(502, `Token exchange failed for ${provider}`);
32
- }
33
- if (value.body) {
34
- return value.body;
35
- }
36
- }
37
- return value;
38
- }
39
- function providerSpec(provider) {
40
- return requireHealthProviderSpec(provider);
41
- }
42
- function isLoopbackHostname(hostname) {
43
- const normalized = hostname.trim().toLowerCase();
44
- return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]";
45
- }
46
- function readEnv(env, provider, suffix) {
47
- const spec = providerSpec(provider);
48
- const key = `ELIZA_${spec.envPrefix}_${suffix}`;
49
- const value = env[key]?.trim();
50
- return value ?? null;
51
- }
52
- function normalizeBaseUrl(value) {
53
- return value.endsWith("/") ? value.slice(0, -1) : value;
54
- }
55
- function healthConnectorCapabilities(provider) {
56
- return [...providerSpec(provider).capabilities];
57
- }
58
- function healthConnectorScopes(provider) {
59
- return [...providerSpec(provider).oauth.defaultScopes];
60
- }
61
- function healthScopesToCapabilities(provider, scopes) {
62
- const set = new Set(scopes);
63
- if (provider === "strava") {
64
- return set.has("activity:read") || set.has("activity:read_all") ? ["health.activity.read", "health.workouts.read"] : [];
65
- }
66
- if (provider === "oura") {
67
- const capabilities2 = [];
68
- if (set.has("daily")) {
69
- capabilities2.push(
70
- "health.activity.read",
71
- "health.sleep.read",
72
- "health.readiness.read"
73
- );
74
- }
75
- if (set.has("workout")) capabilities2.push("health.workouts.read");
76
- if (set.has("heartrate") || set.has("spo2"))
77
- capabilities2.push("health.vitals.read");
78
- if (set.has("personal")) capabilities2.push("health.body.read");
79
- return [...new Set(capabilities2)];
80
- }
81
- if (provider === "fitbit") {
82
- const capabilities2 = [];
83
- if (set.has("activity")) {
84
- capabilities2.push("health.activity.read", "health.workouts.read");
85
- }
86
- if (set.has("sleep")) capabilities2.push("health.sleep.read");
87
- if (set.has("heartrate")) capabilities2.push("health.vitals.read");
88
- if (set.has("weight")) capabilities2.push("health.body.read");
89
- return [...new Set(capabilities2)];
90
- }
91
- const capabilities = [];
92
- if (set.has("user.activity")) {
93
- capabilities.push("health.activity.read", "health.sleep.read");
94
- }
95
- if (set.has("user.sleepevents")) capabilities.push("health.sleep.read");
96
- if (set.has("user.metrics")) {
97
- capabilities.push("health.body.read", "health.vitals.read");
98
- }
99
- return [...new Set(capabilities)];
100
- }
101
- function resolveHealthOAuthConfig(provider, requestUrl, requestedMode, env = process.env) {
102
- const localClientId = readEnv(env, provider, "CLIENT_ID");
103
- const localClientSecret = readEnv(env, provider, "CLIENT_SECRET");
104
- const publicBaseUrl = readEnv(env, provider, "PUBLIC_BASE_URL");
105
- const availableModes = [];
106
- if (localClientId && localClientSecret) {
107
- availableModes.push("local");
108
- }
109
- if (localClientId && localClientSecret && publicBaseUrl) {
110
- availableModes.push("remote");
111
- }
112
- const defaultMode = isLoopbackHostname(requestUrl.hostname) && availableModes.includes("local") ? "local" : availableModes[0] ?? (isLoopbackHostname(requestUrl.hostname) ? "local" : "remote");
113
- const mode = requestedMode ?? defaultMode;
114
- const port = requestUrl.port || (requestUrl.protocol === "https:" ? "443" : "80");
115
- const redirectUri = mode === "remote" && publicBaseUrl ? `${normalizeBaseUrl(publicBaseUrl)}/api/lifeops/connectors/health/${provider}/callback` : `http://127.0.0.1:${port}/api/lifeops/connectors/health/${provider}/callback`;
116
- return {
117
- provider,
118
- mode,
119
- defaultMode,
120
- availableModes,
121
- configured: Boolean(localClientId && localClientSecret),
122
- clientId: localClientId,
123
- clientSecret: localClientSecret,
124
- redirectUri
125
- };
126
- }
127
- function requireHealthOAuthConfig(config, requestUrl) {
128
- if (config.mode === "local" && !isLoopbackHostname(requestUrl.hostname)) {
129
- throw new HealthOAuthError(
130
- 400,
131
- "Local health OAuth requires the API to be addressed over a loopback host."
132
- );
133
- }
134
- if (!config.configured || !config.clientId || !config.clientSecret) {
135
- throw new HealthOAuthError(
136
- 503,
137
- `${config.provider} OAuth ${config.mode} mode is not configured.`
138
- );
139
- }
140
- }
141
- function base64Url(buffer) {
142
- return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
143
- }
144
- function sha256Base64Url(value) {
145
- return base64Url(crypto.createHash("sha256").update(value).digest());
146
- }
147
- function parseGrantedScopes(provider, value) {
148
- if (typeof value !== "string") {
149
- return healthConnectorScopes(provider);
150
- }
151
- return value.split(/[,\s]+/).map((scope) => scope.trim()).filter(Boolean);
152
- }
153
- function tokenExpiresAt(response) {
154
- if (typeof response.expires_at === "number") {
155
- return response.expires_at * 1e3;
156
- }
157
- if (typeof response.expires_in === "number") {
158
- return Date.now() + response.expires_in * 1e3;
159
- }
160
- return null;
161
- }
162
- function tokenRefFor(args) {
163
- return path.join(args.agentId, args.side, args.mode, `${args.provider}.json`);
164
- }
165
- function tokenStorageRoot(env = process.env) {
166
- return path.join(resolveOAuthDir(env), "lifeops", "health");
167
- }
168
- function tokenPath(tokenRef, env) {
169
- return path.join(tokenStorageRoot(env), tokenRef);
170
- }
171
- function writeStoredHealthToken(tokenRef, token, env = process.env) {
172
- const filePath = tokenPath(tokenRef, env);
173
- fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 448 });
174
- const key = resolveTokenEncryptionKey(tokenStorageRoot(env), env);
175
- const payload = JSON.stringify(token, null, 2);
176
- const encoded = JSON.stringify(encryptTokenPayload(payload, key), null, 2);
177
- fs.writeFileSync(filePath, encoded, { mode: 384 });
178
- }
179
- function readStoredHealthToken(tokenRef, env = process.env) {
180
- if (!tokenRef) {
181
- return null;
182
- }
183
- const filePath = tokenPath(tokenRef, env);
184
- if (!fs.existsSync(filePath)) {
185
- return null;
186
- }
187
- const raw = fs.readFileSync(filePath, "utf8");
188
- const parsed = JSON.parse(raw);
189
- if (!isEncryptedTokenEnvelope(parsed)) {
190
- throw new Error(
191
- "Stored health token is not encrypted. Re-link the account."
192
- );
193
- }
194
- const text = decryptTokenEnvelope(
195
- parsed,
196
- resolveTokenEncryptionKey(tokenStorageRoot(env), env)
197
- );
198
- return JSON.parse(text);
199
- }
200
- function deleteStoredHealthToken(tokenRef, env = process.env) {
201
- if (!tokenRef) {
202
- return;
203
- }
204
- fs.rmSync(tokenPath(tokenRef, env), { force: true });
205
- }
206
- async function exchangeToken(session, code) {
207
- const oauth = providerSpec(session.provider).oauth;
208
- const body = new URLSearchParams({
209
- grant_type: "authorization_code",
210
- code,
211
- redirect_uri: session.redirectUri
212
- });
213
- if (oauth.tokenRequestStyle === "withings") {
214
- body.set("action", "requesttoken");
215
- }
216
- if (oauth.tokenRequestStyle !== "basic") {
217
- body.set("client_id", session.clientId);
218
- if (session.clientSecret) body.set("client_secret", session.clientSecret);
219
- }
220
- if (session.codeVerifier) {
221
- body.set("code_verifier", session.codeVerifier);
222
- }
223
- const response = await fetch(oauth.tokenUrl, {
224
- method: "POST",
225
- headers: {
226
- Accept: "application/json",
227
- "Content-Type": "application/x-www-form-urlencoded",
228
- ...oauth.tokenRequestStyle === "basic" && session.clientSecret ? {
229
- Authorization: `Basic ${Buffer.from(
230
- `${session.clientId}:${session.clientSecret}`
231
- ).toString("base64")}`
232
- } : {}
233
- },
234
- body,
235
- signal: AbortSignal.timeout(15e3)
236
- });
237
- const json = await response.json();
238
- if (!response.ok) {
239
- throw new HealthOAuthError(
240
- response.status,
241
- `Token exchange failed for ${session.provider}`
242
- );
243
- }
244
- return unwrapHealthTokenResponse(json, session.provider);
245
- }
246
- async function refreshStoredHealthToken(tokenRef) {
247
- const token = readStoredHealthToken(tokenRef);
248
- if (!token?.refreshToken) {
249
- return token;
250
- }
251
- if (token.expiresAt !== null && token.expiresAt - ACCESS_TOKEN_REFRESH_BUFFER_MS > Date.now()) {
252
- return token;
253
- }
254
- const oauth = providerSpec(token.provider).oauth;
255
- const body = new URLSearchParams({
256
- grant_type: "refresh_token",
257
- refresh_token: token.refreshToken
258
- });
259
- if (oauth.tokenRequestStyle === "withings") {
260
- body.set("action", "requesttoken");
261
- body.set("client_id", token.clientId);
262
- if (token.clientSecret) body.set("client_secret", token.clientSecret);
263
- } else if (oauth.tokenRequestStyle !== "basic") {
264
- body.set("client_id", token.clientId);
265
- if (token.clientSecret) body.set("client_secret", token.clientSecret);
266
- }
267
- const response = await fetch(oauth.tokenUrl, {
268
- method: "POST",
269
- headers: {
270
- Accept: "application/json",
271
- "Content-Type": "application/x-www-form-urlencoded",
272
- ...oauth.tokenRequestStyle === "basic" && token.clientSecret ? {
273
- Authorization: `Basic ${Buffer.from(
274
- `${token.clientId}:${token.clientSecret}`
275
- ).toString("base64")}`
276
- } : {}
277
- },
278
- body,
279
- signal: AbortSignal.timeout(15e3)
280
- });
281
- if (!response.ok) {
282
- throw new HealthOAuthError(
283
- response.status,
284
- `${token.provider} token refresh failed`
285
- );
286
- }
287
- const json = await response.json();
288
- const payload = unwrapHealthTokenResponse(json, token.provider);
289
- if (!payload.access_token) {
290
- throw new HealthOAuthError(502, `${token.provider} token refresh failed`);
291
- }
292
- const next = {
293
- ...token,
294
- accessToken: payload.access_token,
295
- refreshToken: payload.refresh_token ?? token.refreshToken,
296
- tokenType: payload.token_type ?? token.tokenType,
297
- grantedScopes: parseGrantedScopes(token.provider, payload.scope),
298
- expiresAt: tokenExpiresAt(payload),
299
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
300
- };
301
- writeStoredHealthToken(tokenRef ?? tokenRefFor(token), next);
302
- return next;
303
- }
304
- function startHealthConnectorOAuth(args) {
305
- const config = resolveHealthOAuthConfig(
306
- args.provider,
307
- args.requestUrl,
308
- args.mode
309
- );
310
- requireHealthOAuthConfig(config, args.requestUrl);
311
- const oauth = providerSpec(args.provider).oauth;
312
- const state = crypto.randomBytes(24).toString("hex");
313
- const codeVerifier = oauth.usePkce ? base64Url(crypto.randomBytes(32)) : null;
314
- const scopes = healthConnectorScopes(args.provider);
315
- pendingHealthOAuthSessions.set(state, {
316
- state,
317
- provider: args.provider,
318
- agentId: args.agentId,
319
- side: args.side,
320
- mode: config.mode,
321
- clientId: config.clientId,
322
- clientSecret: config.clientSecret,
323
- redirectUri: config.redirectUri,
324
- codeVerifier,
325
- requestedCapabilities: args.capabilities && args.capabilities.length > 0 ? [...new Set(args.capabilities)] : healthConnectorCapabilities(args.provider),
326
- createdAt: Date.now()
327
- });
328
- const authUrl = new URL(oauth.authorizeUrl);
329
- authUrl.searchParams.set("client_id", config.clientId);
330
- authUrl.searchParams.set("response_type", "code");
331
- authUrl.searchParams.set("redirect_uri", config.redirectUri);
332
- authUrl.searchParams.set(
333
- "scope",
334
- scopes.join(oauth.scopeSeparator === "comma" ? "," : " ")
335
- );
336
- authUrl.searchParams.set("state", state);
337
- if (oauth.extraAuthorizeParams) {
338
- for (const [key, value] of Object.entries(oauth.extraAuthorizeParams)) {
339
- authUrl.searchParams.set(key, value);
340
- }
341
- }
342
- if (codeVerifier) {
343
- authUrl.searchParams.set("code_challenge", sha256Base64Url(codeVerifier));
344
- authUrl.searchParams.set("code_challenge_method", "S256");
345
- }
346
- return {
347
- provider: args.provider,
348
- side: args.side,
349
- mode: config.mode,
350
- requestedCapabilities: args.capabilities && args.capabilities.length > 0 ? [...new Set(args.capabilities)] : healthConnectorCapabilities(args.provider),
351
- redirectUri: config.redirectUri,
352
- authUrl: authUrl.toString()
353
- };
354
- }
355
- async function completeHealthConnectorOAuth(callbackUrl) {
356
- const state = callbackUrl.searchParams.get("state") ?? "";
357
- const session = pendingHealthOAuthSessions.get(state);
358
- if (!session) {
359
- throw new HealthOAuthError(400, "Unknown or expired health OAuth session.");
360
- }
361
- pendingHealthOAuthSessions.delete(state);
362
- if (Date.now() - session.createdAt > HEALTH_OAUTH_SESSION_TTL_MS) {
363
- throw new HealthOAuthError(400, "Health OAuth session expired.");
364
- }
365
- const error = callbackUrl.searchParams.get("error");
366
- if (error) {
367
- throw new HealthOAuthError(
368
- 400,
369
- `${session.provider} authorization failed: ${error}`
370
- );
371
- }
372
- const code = callbackUrl.searchParams.get("code");
373
- if (!code) {
374
- throw new HealthOAuthError(400, "Missing health OAuth authorization code.");
375
- }
376
- const payload = await exchangeToken(session, code);
377
- if (!payload.access_token) {
378
- throw new HealthOAuthError(
379
- 502,
380
- `${session.provider} token response missing access token.`
381
- );
382
- }
383
- const scopes = parseGrantedScopes(session.provider, payload.scope);
384
- const identity = session.provider === "strava" && payload.athlete ? payload.athlete : {
385
- userId: payload.user_id ?? payload.userid ?? callbackUrl.searchParams.get("userid") ?? null
386
- };
387
- const nowIso = (/* @__PURE__ */ new Date()).toISOString();
388
- const tokenRef = tokenRefFor(session);
389
- const token = {
390
- provider: session.provider,
391
- agentId: session.agentId,
392
- side: session.side,
393
- mode: session.mode,
394
- clientId: session.clientId,
395
- clientSecret: session.clientSecret,
396
- redirectUri: session.redirectUri,
397
- accessToken: payload.access_token,
398
- refreshToken: payload.refresh_token ?? null,
399
- tokenType: payload.token_type ?? "Bearer",
400
- grantedScopes: scopes,
401
- expiresAt: tokenExpiresAt(payload),
402
- identity,
403
- createdAt: nowIso,
404
- updatedAt: nowIso
405
- };
406
- writeStoredHealthToken(tokenRef, token);
407
- return {
408
- agentId: session.agentId,
409
- provider: session.provider,
410
- side: session.side,
411
- mode: session.mode,
412
- tokenRef,
413
- identity,
414
- grantedCapabilities: healthScopesToCapabilities(session.provider, scopes),
415
- grantedScopes: scopes,
416
- expiresAt: token.expiresAt ? new Date(token.expiresAt).toISOString() : null,
417
- hasRefreshToken: Boolean(token.refreshToken)
418
- };
419
- }
420
- export {
421
- HealthOAuthError,
422
- completeHealthConnectorOAuth,
423
- deleteStoredHealthToken,
424
- healthConnectorCapabilities,
425
- healthConnectorScopes,
426
- healthScopesToCapabilities,
427
- readStoredHealthToken,
428
- refreshStoredHealthToken,
429
- resolveHealthOAuthConfig,
430
- startHealthConnectorOAuth
431
- };
432
- //# sourceMappingURL=health-oauth.js.map