@remnic/connector-omi 9.3.751 → 9.3.753

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 CHANGED
@@ -16,17 +16,11 @@ Remnic discovers it at runtime. No further registration is needed.
16
16
 
17
17
  ## Setup
18
18
 
19
- The connector uses Omi's Integrations API, which is scoped to an app
20
- you create:
21
-
22
- 1. In the Omi app: **Apps → Create App External Integration**, and
23
- grant the **read conversations** capability (plus **read memories**
24
- if you want native-memory import). Install/enable the app for your
25
- account.
26
- 2. On the app's management page, create an **API key** (`sk_...`).
27
- 3. Note your **app id** and your **uid** (Omi passes `?uid=` to your
28
- app's links; it identifies the account to read).
29
- 4. Configure:
19
+ The connector uses Omi's Developer API by default:
20
+
21
+ 1. In the Omi app, open **Settings → Developer → Create Key**.
22
+ 2. Create a Developer API key (`omi_dev_...`).
23
+ 3. Configure:
30
24
 
31
25
  ```jsonc
32
26
  {
@@ -35,8 +29,6 @@ you create:
35
29
  "sources": {
36
30
  "omi": {
37
31
  "enabled": true,
38
- "appId": "your-omi-app-id",
39
- "userId": "your-omi-uid",
40
32
  "memoryMode": "smart", // smart (default) | off | review | auto
41
33
  "importNativeMemories": "smart" // Omi memories through the same trust pipeline
42
34
  }
@@ -46,7 +38,12 @@ you create:
46
38
  ```
47
39
 
48
40
  Provide the key via `OMI_API_KEY` (or `REMNIC_OMI_API_KEY`, or `apiKey`
49
- in config).
41
+ in config). No app id or uid is needed for the current Developer API.
42
+
43
+ Legacy External Integration app installs remain supported. If you are
44
+ still using that older flow, configure both `appId` and `userId`; when
45
+ both are present the connector uses Omi's app-scoped integration
46
+ endpoints.
50
47
 
51
48
  ## Usage
52
49
 
@@ -58,9 +55,10 @@ remnic wearables transcript --date 2026-06-10 --source omi
58
55
 
59
56
  ## Speaker labels
60
57
 
61
- Omi marks the wearer (`is_user`) automatically. Other voices arrive as
62
- `SPEAKER_NN` diarization labels or stable person ids once you tag
63
- people in Omi. Map either form to a display name once:
58
+ Omi Developer API segments carry `speaker_name` and `speaker_id`.
59
+ Legacy integration segments may mark the wearer (`is_user`) and use
60
+ `SPEAKER_NN` diarization labels or stable person ids once you tag
61
+ people in Omi. Map any speaker key to a display name once:
64
62
 
65
63
  ```bash
66
64
  remnic wearables speakers set omi SPEAKER_01 "Jane Doe"
@@ -68,9 +66,10 @@ remnic wearables speakers set omi SPEAKER_01 "Jane Doe"
68
66
 
69
67
  ## Notes
70
68
 
71
- - Transcripts are fetched **unabridged**: the connector passes
72
- `max_transcript_segments=-1` because the API's default silently
73
- truncates conversations to their first 100 segments.
69
+ - Transcripts are fetched with `include_transcript=true`. In legacy
70
+ integration mode, the connector also passes `max_transcript_segments=-1`
71
+ because that API's default silently truncates conversations to their
72
+ first 100 segments.
74
73
  - Day windows are timezone-correct: the connector computes local-day
75
74
  ISO bounds (DST-aware) for the API's `start_date`/`end_date` filters,
76
75
  and only `completed`, non-discarded conversations sync.
package/dist/index.d.ts CHANGED
@@ -1,26 +1,22 @@
1
1
  import { WearableConversation, WearableNativeMemory, WearableConnectorFactoryOptions, WearableSourceConnector, WearableConnectorRegistration } from '@remnic/core';
2
2
 
3
3
  /**
4
- * Minimal Omi Integrations API client (raw fetch, no SDK).
4
+ * Minimal Omi Developer API client (raw fetch, no SDK).
5
5
  *
6
- * Contract verified against docs.omi.me and the open-source backend
7
- * (`backend/routers/integration.py`, `models/integrations.py`) in
8
- * 2026-06:
6
+ * Current contract verified against docs.omi.me in 2026-07:
9
7
  *
10
- * - base `https://api.omi.me`, auth `Authorization: Bearer sk_...`
11
- * (an app API key created in the Omi app; the app needs the
12
- * External Integration `read_conversations` / `read_memories`
13
- * capabilities and must be enabled for the target user)
14
- * - `uid` rides as a query parameter on every endpoint
15
- * - `GET /v2/integrations/{app_id}/conversations` with
16
- * `limit`/`offset` pagination, `start_date`/`end_date` (ISO 8601),
17
- * repeated `statuses` params, and `max_transcript_segments=-1`
18
- * (the API default silently truncates to the first 100 segments)
19
- * - `GET /v2/integrations/{app_id}/memories` with `limit`/`offset`
20
- * - responses serialize with exclude_none — every optional field may
21
- * be entirely absent
8
+ * - base `https://api.omi.me`, auth `Authorization: Bearer omi_dev_...`
9
+ * (Developer API key from Settings Developer Create Key)
10
+ * - `GET /v1/dev/user/conversations` with `limit`/`offset`
11
+ * pagination, `start_date`/`end_date` (ISO 8601), and
12
+ * `include_transcript=true`
13
+ * - `GET /v1/dev/user/memories` with `limit`/`offset`
14
+ * - responses are arrays; every optional field may be absent
22
15
  * - errors are FastAPI-shaped `{"detail": "..."}`
23
16
  *
17
+ * The older app-scoped Integrations API remains supported when both
18
+ * `appId` and `userId` are configured.
19
+ *
24
20
  * The API key is never logged and never appears in thrown error
25
21
  * messages.
26
22
  */
@@ -28,6 +24,8 @@ declare const OMI_DEFAULT_BASE_URL = "https://api.omi.me";
28
24
  interface OmiTranscriptSegment {
29
25
  text?: string;
30
26
  speaker?: string;
27
+ speaker_id?: number | string | null;
28
+ speaker_name?: string | null;
31
29
  is_user?: boolean;
32
30
  person_id?: string | null;
33
31
  start?: number;
@@ -71,8 +69,8 @@ interface OmiMemoriesPage {
71
69
  }
72
70
  interface OmiClientOptions {
73
71
  apiKey: string;
74
- appId: string;
75
- userId: string;
72
+ appId?: string;
73
+ userId?: string;
76
74
  baseUrl?: string;
77
75
  fetchImpl?: typeof fetch;
78
76
  timeoutMs?: number;
@@ -88,8 +86,9 @@ declare class OmiApiError extends Error {
88
86
  }
89
87
  declare class OmiClient {
90
88
  private readonly apiKey;
91
- private readonly appId;
92
- private readonly userId;
89
+ private readonly appId?;
90
+ private readonly userId?;
91
+ private readonly mode;
93
92
  private readonly baseUrl;
94
93
  private readonly fetchImpl;
95
94
  private readonly timeoutMs;
@@ -119,9 +118,11 @@ declare class OmiClient {
119
118
  * `WearableConversation` shape, plus the timezone-aware day-window
120
119
  * helpers the Omi API needs (its date filters are ISO datetimes).
121
120
  *
122
- * Omi segments carry `is_user` for the wearer, opaque `SPEAKER_NN`
123
- * diarization labels, optional `person_id`s (user-defined people), and
124
- * start/end offsets in seconds relative to the conversation start.
121
+ * Legacy Omi integration segments carry `is_user` for the wearer,
122
+ * opaque `SPEAKER_NN` diarization labels, optional `person_id`s
123
+ * (user-defined people), and start/end offsets in seconds relative to
124
+ * the conversation start. The current Developer API returns
125
+ * `speaker_name`/`speaker_id` instead; normalize both shapes.
125
126
  */
126
127
 
127
128
  declare const OMI_SOURCE_ID = "omi";
@@ -144,12 +145,13 @@ declare function memoryToNativeMemory(memory: OmiMemory): WearableNativeMemory |
144
145
  * À-la-carte optional companion of @remnic/core (computed-specifier
145
146
  * discovery; importing this module self-registers idempotently).
146
147
  *
147
- * Requires an Omi integration app with the External Integration
148
- * `read_conversations` (and, for native-memory import,
149
- * `read_memories`) capabilities:
150
- * - `wearables.sources.omi.appId` — the app id
151
- * - `wearables.sources.omi.userId` — the target uid
148
+ * Requires an Omi Developer API key from Settings → Developer →
149
+ * Create Key:
152
150
  * - key via `REMNIC_OMI_API_KEY` / `OMI_API_KEY` env (or `apiKey`)
151
+ *
152
+ * Legacy External Integration app installs remain supported when both
153
+ * `wearables.sources.omi.appId` and `wearables.sources.omi.userId`
154
+ * are configured.
153
155
  */
154
156
 
155
157
  declare function resolveOmiApiKey(configuredKey: string | undefined, env?: NodeJS.ProcessEnv): string | undefined;
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ var OmiClient = class {
26
26
  apiKey;
27
27
  appId;
28
28
  userId;
29
+ mode;
29
30
  baseUrl;
30
31
  fetchImpl;
31
32
  timeoutMs;
@@ -33,22 +34,20 @@ var OmiClient = class {
33
34
  constructor(options) {
34
35
  if (typeof options.apiKey !== "string" || options.apiKey.trim().length === 0) {
35
36
  throw new OmiApiError(
36
- "Omi API key is missing. Set wearables.sources.omi.apiKey or the OMI_API_KEY environment variable (create a key under your app's API Keys in the Omi app)."
37
+ "Omi API key is missing. Set wearables.sources.omi.apiKey or the OMI_API_KEY environment variable (create a Developer API key in Settings \u2192 Developer \u2192 Create Key in the Omi app)."
37
38
  );
38
39
  }
39
- if (typeof options.appId !== "string" || options.appId.trim().length === 0) {
40
+ const appId = typeof options.appId === "string" && options.appId.trim().length > 0 ? options.appId.trim() : void 0;
41
+ const userId = typeof options.userId === "string" && options.userId.trim().length > 0 ? options.userId.trim() : void 0;
42
+ if (appId === void 0 !== (userId === void 0)) {
40
43
  throw new OmiApiError(
41
- "Omi app id is missing. Set wearables.sources.omi.appId to your integration app's id."
42
- );
43
- }
44
- if (typeof options.userId !== "string" || options.userId.trim().length === 0) {
45
- throw new OmiApiError(
46
- "Omi user id is missing. Set wearables.sources.omi.userId to the target uid (shown when the user installs/opens your Omi app)."
44
+ "Omi legacy integration mode requires both wearables.sources.omi.appId and wearables.sources.omi.userId. Omit both to use the Developer API key-only process."
47
45
  );
48
46
  }
49
47
  this.apiKey = options.apiKey.trim();
50
- this.appId = options.appId.trim();
51
- this.userId = options.userId.trim();
48
+ this.appId = appId;
49
+ this.userId = userId;
50
+ this.mode = appId !== void 0 && userId !== void 0 ? "integration" : "developer";
52
51
  this.baseUrl = stripTrailingSlashes(options.baseUrl ?? OMI_DEFAULT_BASE_URL);
53
52
  this.fetchImpl = options.fetchImpl ?? fetch;
54
53
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
@@ -58,29 +57,38 @@ var OmiClient = class {
58
57
  async listConversations(params) {
59
58
  const offset = params.offset ?? 0;
60
59
  const search = new URLSearchParams({
61
- uid: this.userId,
62
60
  limit: String(PAGE_SIZE),
63
61
  offset: String(offset),
64
62
  start_date: params.startIso,
65
- end_date: params.endIso,
66
- include_discarded: "false",
67
- // -1 = unlimited; the API default silently truncates transcripts
68
- // to their first 100 segments.
69
- max_transcript_segments: "-1"
63
+ end_date: params.endIso
70
64
  });
71
- search.append("statuses", "completed");
72
- const payload = await this.requestJson(
73
- `/v2/integrations/${encodeURIComponent(this.appId)}/conversations?${search.toString()}`,
74
- params.signal
75
- );
76
- const conversations = payload.conversations;
65
+ let payload;
66
+ if (this.mode === "developer") {
67
+ search.set("include_transcript", "true");
68
+ payload = await this.requestJson(
69
+ `/v1/dev/user/conversations?${search.toString()}`,
70
+ params.signal
71
+ );
72
+ } else {
73
+ search.set("uid", this.userId ?? "");
74
+ search.set("include_discarded", "false");
75
+ search.set("max_transcript_segments", "-1");
76
+ search.append("statuses", "completed");
77
+ payload = await this.requestJson(
78
+ `/v2/integrations/${encodeURIComponent(this.appId ?? "")}/conversations?${search.toString()}`,
79
+ params.signal
80
+ );
81
+ }
82
+ const conversations = this.mode === "developer" ? payload : payload.conversations;
77
83
  if (!Array.isArray(conversations)) {
78
84
  throw new OmiApiError(
79
- "Omi API returned an unexpected conversations shape (missing conversations array)"
85
+ this.mode === "developer" ? "Omi API returned an unexpected conversations shape (expected array)" : "Omi API returned an unexpected conversations shape (missing conversations array)"
80
86
  );
81
87
  }
82
88
  const valid = conversations.filter(
83
89
  (entry) => entry !== null && typeof entry === "object" && typeof entry.id === "string"
90
+ ).filter(isCompletedOrStatuslessConversation).filter(
91
+ (conversation) => startsInsideHalfOpenWindow(conversation, params.startIso, params.endIso)
84
92
  );
85
93
  return {
86
94
  conversations: valid,
@@ -91,18 +99,26 @@ var OmiClient = class {
91
99
  async listMemories(params = {}) {
92
100
  const offset = params.offset ?? 0;
93
101
  const search = new URLSearchParams({
94
- uid: this.userId,
95
102
  limit: String(PAGE_SIZE),
96
103
  offset: String(offset)
97
104
  });
98
- const payload = await this.requestJson(
99
- `/v2/integrations/${encodeURIComponent(this.appId)}/memories?${search.toString()}`,
100
- params.signal
101
- );
102
- const memories = payload.memories;
105
+ let payload;
106
+ if (this.mode === "developer") {
107
+ payload = await this.requestJson(
108
+ `/v1/dev/user/memories?${search.toString()}`,
109
+ params.signal
110
+ );
111
+ } else {
112
+ search.set("uid", this.userId ?? "");
113
+ payload = await this.requestJson(
114
+ `/v2/integrations/${encodeURIComponent(this.appId ?? "")}/memories?${search.toString()}`,
115
+ params.signal
116
+ );
117
+ }
118
+ const memories = this.mode === "developer" ? payload : payload.memories;
103
119
  if (!Array.isArray(memories)) {
104
120
  throw new OmiApiError(
105
- "Omi API returned an unexpected memories shape (missing memories array)"
121
+ this.mode === "developer" ? "Omi API returned an unexpected memories shape (expected array)" : "Omi API returned an unexpected memories shape (missing memories array)"
106
122
  );
107
123
  }
108
124
  const valid = memories.filter(
@@ -116,19 +132,27 @@ var OmiClient = class {
116
132
  async verifyAuth(signal) {
117
133
  try {
118
134
  const search = new URLSearchParams({
119
- uid: this.userId,
120
135
  limit: "1",
121
- offset: "0",
122
- max_transcript_segments: "1"
136
+ offset: "0"
123
137
  });
124
- await this.requestJson(
125
- `/v2/integrations/${encodeURIComponent(this.appId)}/conversations?${search.toString()}`,
126
- signal
127
- );
138
+ if (this.mode === "developer") {
139
+ search.set("include_transcript", "false");
140
+ await this.requestJson(
141
+ `/v1/dev/user/conversations?${search.toString()}`,
142
+ signal
143
+ );
144
+ } else {
145
+ search.set("uid", this.userId ?? "");
146
+ search.set("max_transcript_segments", "1");
147
+ await this.requestJson(
148
+ `/v2/integrations/${encodeURIComponent(this.appId ?? "")}/conversations?${search.toString()}`,
149
+ signal
150
+ );
151
+ }
128
152
  return { ok: true };
129
153
  } catch (err) {
130
154
  if (err instanceof OmiApiError && err.status !== void 0) {
131
- const hint = err.status === 401 ? "missing/malformed Authorization header" : err.status === 403 ? "key rejected, app not enabled for this uid, or missing read_conversations capability" : err.status === 404 ? "app not found \u2014 check wearables.sources.omi.appId" : void 0;
155
+ const hint = err.status === 401 ? "missing/malformed Authorization header" : err.status === 403 ? this.mode === "developer" ? "Developer API key rejected or missing conversation access" : "key rejected, app not enabled for this uid, or missing read_conversations capability" : err.status === 404 ? this.mode === "developer" ? "Developer API endpoint not found \u2014 check the configured baseUrl" : "app not found \u2014 check wearables.sources.omi.appId" : void 0;
132
156
  return {
133
157
  ok: false,
134
158
  detail: [err.detail, hint].filter(Boolean).join(" \u2014 ") || err.message
@@ -205,6 +229,24 @@ function stripTrailingSlashes(value) {
205
229
  while (end > 0 && value.charCodeAt(end - 1) === 47) end--;
206
230
  return value.slice(0, end);
207
231
  }
232
+ function startsInsideHalfOpenWindow(conversation, startIso, endIso) {
233
+ const startMs = Date.parse(startIso);
234
+ const endMs = Date.parse(endIso);
235
+ if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) return true;
236
+ const conversationIso = typeof conversation.started_at === "string" ? conversation.started_at : conversation.created_at;
237
+ if (typeof conversationIso !== "string" || conversationIso.length === 0) {
238
+ return true;
239
+ }
240
+ const conversationMs = Date.parse(conversationIso);
241
+ if (!Number.isFinite(conversationMs)) return true;
242
+ return conversationMs >= startMs && conversationMs < endMs;
243
+ }
244
+ function isCompletedOrStatuslessConversation(conversation) {
245
+ if (typeof conversation.status !== "string" || conversation.status.length === 0) {
246
+ return true;
247
+ }
248
+ return conversation.status === "completed";
249
+ }
208
250
  async function readDetail(response) {
209
251
  try {
210
252
  const body = await response.clone().json();
@@ -270,15 +312,17 @@ function conversationToWearable(conversation) {
270
312
  if (text.length === 0) continue;
271
313
  const isWearer = segment.is_user === true;
272
314
  const personId = typeof segment.person_id === "string" && segment.person_id.length > 0 ? segment.person_id : void 0;
273
- const label = typeof segment.speaker === "string" && segment.speaker.trim().length > 0 ? segment.speaker.trim() : void 0;
315
+ const speakerId = typeof segment.speaker_id === "string" || typeof segment.speaker_id === "number" ? String(segment.speaker_id) : void 0;
316
+ const label = typeof segment.speaker_name === "string" && segment.speaker_name.trim().length > 0 ? segment.speaker_name.trim() : typeof segment.speaker === "string" && segment.speaker.trim().length > 0 ? segment.speaker.trim() : void 0;
274
317
  const startIso = !Number.isNaN(startedAtMs) && typeof segment.start === "number" ? new Date(startedAtMs + segment.start * 1e3).toISOString() : void 0;
275
318
  const endIso = !Number.isNaN(startedAtMs) && typeof segment.end === "number" ? new Date(startedAtMs + segment.end * 1e3).toISOString() : void 0;
276
319
  segments.push({
277
320
  text,
278
321
  // person_id is the most stable key when the user has tagged the
279
- // speaker as a known person in Omi; the diarization label
280
- // otherwise.
281
- speakerKey: isWearer ? "user" : personId ?? label ?? "unknown",
322
+ // speaker as a known person in Omi. The Developer API's
323
+ // speaker_id is the next best stable key, then the diarization
324
+ // label.
325
+ speakerKey: isWearer ? "user" : personId ?? speakerId ?? label ?? "unknown",
282
326
  ...label !== void 0 ? { speakerName: label } : {},
283
327
  ...isWearer ? { isWearer: true } : {},
284
328
  ...startIso !== void 0 ? { startIso } : {},
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/normalize.ts"],"sourcesContent":["/**\n * @remnic/connector-omi — Omi AI wearable connector.\n *\n * À-la-carte optional companion of @remnic/core (computed-specifier\n * discovery; importing this module self-registers idempotently).\n *\n * Requires an Omi integration app with the External Integration\n * `read_conversations` (and, for native-memory import,\n * `read_memories`) capabilities:\n * - `wearables.sources.omi.appId` — the app id\n * - `wearables.sources.omi.userId` — the target uid\n * - key via `REMNIC_OMI_API_KEY` / `OMI_API_KEY` env (or `apiKey`)\n */\n\nimport {\n registerWearableConnector,\n getWearableConnector,\n type WearableConnectorFactoryOptions,\n type WearableConnectorRegistration,\n type WearableFetchOptions,\n type WearableFetchPage,\n type WearableNativeMemoryPage,\n type WearableSourceConnector,\n} from \"@remnic/core\";\n\nimport { OmiClient } from \"./client.js\";\nimport {\n conversationToWearable,\n memoryToNativeMemory,\n OMI_SOURCE_ID,\n zonedDayBounds,\n} from \"./normalize.js\";\n\nexport { OmiApiError, OmiClient, OMI_DEFAULT_BASE_URL } from \"./client.js\";\nexport type {\n OmiClientOptions,\n OmiConversation,\n OmiConversationsPage,\n OmiMemoriesPage,\n OmiMemory,\n OmiTranscriptSegment,\n} from \"./client.js\";\nexport {\n conversationToWearable,\n memoryToNativeMemory,\n nextIsoDate,\n OMI_SOURCE_ID,\n timezoneOffsetIso,\n zonedDayBounds,\n zonedDayStartIso,\n} from \"./normalize.js\";\n\nexport function resolveOmiApiKey(\n configuredKey: string | undefined,\n env: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n if (typeof configuredKey === \"string\" && configuredKey.trim().length > 0) {\n return configuredKey.trim();\n }\n for (const name of [\"REMNIC_OMI_API_KEY\", \"OMI_API_KEY\"]) {\n const value = env[name];\n if (typeof value === \"string\" && value.trim().length > 0) {\n return value.trim();\n }\n }\n return undefined;\n}\n\nfunction parseOffsetCursor(cursor: string | null | undefined): number {\n if (typeof cursor !== \"string\" || cursor.length === 0) return 0;\n const parsed = Number(cursor);\n return Number.isInteger(parsed) && parsed > 0 ? parsed : 0;\n}\n\nexport function createOmiConnector(\n options: WearableConnectorFactoryOptions,\n): WearableSourceConnector {\n // Construction is lazy so `wearables status` works without\n // credentials; the client constructor throws the actionable\n // missing-credential messages at call time.\n let client: OmiClient | null = null;\n const getClient = (): OmiClient => {\n if (!client) {\n client = new OmiClient({\n apiKey: resolveOmiApiKey(options.settings.apiKey) ?? \"\",\n appId: options.settings.appId ?? \"\",\n userId: options.settings.userId ?? \"\",\n baseUrl: options.settings.baseUrl,\n });\n }\n return client;\n };\n\n return {\n id: OMI_SOURCE_ID,\n displayName: \"Omi\",\n async verifyAuth(signal?: AbortSignal) {\n return getClient().verifyAuth(signal);\n },\n async fetchConversations(opts: WearableFetchOptions): Promise<WearableFetchPage> {\n const bounds = zonedDayBounds(opts.date, opts.timezone);\n const page = await getClient().listConversations({\n startIso: bounds.startIso,\n endIso: bounds.endIso,\n offset: parseOffsetCursor(opts.cursor),\n signal: opts.signal,\n });\n return {\n conversations: page.conversations\n .filter((conversation) => conversation.discarded !== true)\n .map(conversationToWearable),\n nextCursor: page.nextOffset !== null ? String(page.nextOffset) : null,\n };\n },\n async fetchNativeMemories(opts: {\n cursor?: string | null;\n signal?: AbortSignal;\n }): Promise<WearableNativeMemoryPage> {\n const page = await getClient().listMemories({\n offset: parseOffsetCursor(opts.cursor),\n signal: opts.signal,\n });\n const memories = [];\n for (const memory of page.memories) {\n const mapped = memoryToNativeMemory(memory);\n if (mapped !== null) memories.push(mapped);\n }\n return {\n memories,\n nextCursor: page.nextOffset !== null ? String(page.nextOffset) : null,\n };\n },\n };\n}\n\nexport const wearableConnectorRegistration: WearableConnectorRegistration = {\n id: OMI_SOURCE_ID,\n displayName: \"Omi\",\n factory: createOmiConnector,\n};\n\n/**\n * Idempotently register the connector with the core registry. Importing\n * this module registers it as a side effect; calling this again is safe\n * (returns false when already registered).\n */\nexport function ensureOmiConnectorRegistered(): boolean {\n if (getWearableConnector(OMI_SOURCE_ID) !== undefined) {\n return false;\n }\n registerWearableConnector(wearableConnectorRegistration);\n return true;\n}\n\nensureOmiConnectorRegistered();\n","/**\n * Minimal Omi Integrations API client (raw fetch, no SDK).\n *\n * Contract verified against docs.omi.me and the open-source backend\n * (`backend/routers/integration.py`, `models/integrations.py`) in\n * 2026-06:\n *\n * - base `https://api.omi.me`, auth `Authorization: Bearer sk_...`\n * (an app API key created in the Omi app; the app needs the\n * External Integration `read_conversations` / `read_memories`\n * capabilities and must be enabled for the target user)\n * - `uid` rides as a query parameter on every endpoint\n * - `GET /v2/integrations/{app_id}/conversations` with\n * `limit`/`offset` pagination, `start_date`/`end_date` (ISO 8601),\n * repeated `statuses` params, and `max_transcript_segments=-1`\n * (the API default silently truncates to the first 100 segments)\n * - `GET /v2/integrations/{app_id}/memories` with `limit`/`offset`\n * - responses serialize with exclude_none — every optional field may\n * be entirely absent\n * - errors are FastAPI-shaped `{\"detail\": \"...\"}`\n *\n * The API key is never logged and never appears in thrown error\n * messages.\n */\n\nexport const OMI_DEFAULT_BASE_URL = \"https://api.omi.me\";\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst MAX_RETRIES = 3;\nconst MAX_RETRY_DELAY_MS = 30_000;\n/** Page size for both conversations and memories (API max is 1000). */\nconst PAGE_SIZE = 100;\n\nexport interface OmiTranscriptSegment {\n text?: string;\n speaker?: string;\n is_user?: boolean;\n person_id?: string | null;\n start?: number;\n end?: number;\n}\n\nexport interface OmiConversation {\n id: string;\n created_at?: string;\n started_at?: string;\n finished_at?: string;\n structured?: {\n title?: string;\n overview?: string;\n category?: string;\n action_items?: Array<{ description?: string; completed?: boolean }>;\n };\n transcript_segments?: OmiTranscriptSegment[];\n geolocation?: { address?: string | null } | null;\n status?: string;\n discarded?: boolean;\n}\n\nexport interface OmiMemory {\n id: string;\n content?: string;\n category?: string;\n tags?: string[];\n created_at?: string;\n}\n\nexport interface OmiConversationsPage {\n conversations: OmiConversation[];\n nextOffset: number | null;\n}\n\nexport interface OmiMemoriesPage {\n memories: OmiMemory[];\n nextOffset: number | null;\n}\n\nexport interface OmiClientOptions {\n apiKey: string;\n appId: string;\n userId: string;\n baseUrl?: string;\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n sleep?: (ms: number) => Promise<void>;\n}\n\nexport class OmiApiError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n /** FastAPI `detail` string when the body carried one. */\n readonly detail?: string,\n ) {\n super(message);\n this.name = \"OmiApiError\";\n }\n}\n\nexport class OmiClient {\n private readonly apiKey: string;\n private readonly appId: string;\n private readonly userId: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly sleep: (ms: number) => Promise<void>;\n\n constructor(options: OmiClientOptions) {\n if (typeof options.apiKey !== \"string\" || options.apiKey.trim().length === 0) {\n throw new OmiApiError(\n \"Omi API key is missing. Set wearables.sources.omi.apiKey or the \" +\n \"OMI_API_KEY environment variable (create a key under your app's \" +\n \"API Keys in the Omi app).\",\n );\n }\n if (typeof options.appId !== \"string\" || options.appId.trim().length === 0) {\n throw new OmiApiError(\n \"Omi app id is missing. Set wearables.sources.omi.appId to your \" +\n \"integration app's id.\",\n );\n }\n if (typeof options.userId !== \"string\" || options.userId.trim().length === 0) {\n throw new OmiApiError(\n \"Omi user id is missing. Set wearables.sources.omi.userId to the \" +\n \"target uid (shown when the user installs/opens your Omi app).\",\n );\n }\n this.apiKey = options.apiKey.trim();\n this.appId = options.appId.trim();\n this.userId = options.userId.trim();\n this.baseUrl = stripTrailingSlashes(options.baseUrl ?? OMI_DEFAULT_BASE_URL);\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.sleep =\n options.sleep ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));\n }\n\n /** One page of completed conversations inside [startIso, endIso). */\n async listConversations(params: {\n startIso: string;\n endIso: string;\n offset?: number;\n signal?: AbortSignal;\n }): Promise<OmiConversationsPage> {\n const offset = params.offset ?? 0;\n const search = new URLSearchParams({\n uid: this.userId,\n limit: String(PAGE_SIZE),\n offset: String(offset),\n start_date: params.startIso,\n end_date: params.endIso,\n include_discarded: \"false\",\n // -1 = unlimited; the API default silently truncates transcripts\n // to their first 100 segments.\n max_transcript_segments: \"-1\",\n });\n // Repeated param (FastAPI List[str]) — comma-joining does NOT work.\n search.append(\"statuses\", \"completed\");\n const payload = await this.requestJson(\n `/v2/integrations/${encodeURIComponent(this.appId)}/conversations?${search.toString()}`,\n params.signal,\n );\n const conversations = (payload as { conversations?: unknown }).conversations;\n if (!Array.isArray(conversations)) {\n throw new OmiApiError(\n \"Omi API returned an unexpected conversations shape (missing conversations array)\",\n );\n }\n const valid = conversations.filter(\n (entry): entry is OmiConversation =>\n entry !== null &&\n typeof entry === \"object\" &&\n typeof (entry as { id?: unknown }).id === \"string\",\n );\n return {\n conversations: valid,\n nextOffset: conversations.length === PAGE_SIZE ? offset + PAGE_SIZE : null,\n };\n }\n\n /** One page of Omi memories (provider-extracted facts). */\n async listMemories(params: {\n offset?: number;\n signal?: AbortSignal;\n } = {}): Promise<OmiMemoriesPage> {\n const offset = params.offset ?? 0;\n const search = new URLSearchParams({\n uid: this.userId,\n limit: String(PAGE_SIZE),\n offset: String(offset),\n });\n const payload = await this.requestJson(\n `/v2/integrations/${encodeURIComponent(this.appId)}/memories?${search.toString()}`,\n params.signal,\n );\n const memories = (payload as { memories?: unknown }).memories;\n if (!Array.isArray(memories)) {\n throw new OmiApiError(\n \"Omi API returned an unexpected memories shape (missing memories array)\",\n );\n }\n const valid = memories.filter(\n (entry): entry is OmiMemory =>\n entry !== null &&\n typeof entry === \"object\" &&\n typeof (entry as { id?: unknown }).id === \"string\",\n );\n return {\n memories: valid,\n nextOffset: memories.length === PAGE_SIZE ? offset + PAGE_SIZE : null,\n };\n }\n\n async verifyAuth(signal?: AbortSignal): Promise<{ ok: boolean; detail?: string }> {\n try {\n const search = new URLSearchParams({\n uid: this.userId,\n limit: \"1\",\n offset: \"0\",\n max_transcript_segments: \"1\",\n });\n await this.requestJson(\n `/v2/integrations/${encodeURIComponent(this.appId)}/conversations?${search.toString()}`,\n signal,\n );\n return { ok: true };\n } catch (err) {\n if (err instanceof OmiApiError && err.status !== undefined) {\n // The backend's authorization chain yields distinct, actionable\n // detail strings: bad key (403), app not enabled for the user\n // (403), missing capability (403), app not found (404).\n const hint =\n err.status === 401\n ? \"missing/malformed Authorization header\"\n : err.status === 403\n ? \"key rejected, app not enabled for this uid, or missing read_conversations capability\"\n : err.status === 404\n ? \"app not found — check wearables.sources.omi.appId\"\n : undefined;\n return {\n ok: false,\n detail: [err.detail, hint].filter(Boolean).join(\" — \") || err.message,\n };\n }\n // OmiApiError messages are our own constructed strings (already\n // scrubbed — network text is reduced to name + code inside\n // requestJson); foreign errors reduce to name + errno code so raw\n // Node text (paths, loader stacks) never reaches operator\n // surfaces.\n return {\n ok: false,\n detail: err instanceof OmiApiError ? err.message : describeNetworkError(err),\n };\n }\n }\n\n private async requestJson(pathAndQuery: string, signal?: AbortSignal): Promise<unknown> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n signal?.throwIfAborted();\n const timeoutSignal = AbortSignal.timeout(this.timeoutMs);\n const combined = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.baseUrl}${pathAndQuery}`, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n Accept: \"application/json\",\n },\n signal: combined,\n });\n } catch (err) {\n if (signal?.aborted) throw err;\n lastError = err;\n if (attempt < MAX_RETRIES) {\n await this.sleep(backoffMs(attempt));\n continue;\n }\n throw new OmiApiError(\n `Omi API request failed after ${MAX_RETRIES + 1} attempts: ${describeNetworkError(err)}`,\n );\n }\n\n if (response.status === 429 || response.status >= 500) {\n lastError = new OmiApiError(\n `Omi API responded ${response.status}`,\n response.status,\n await readDetail(response),\n );\n if (attempt < MAX_RETRIES) {\n await this.sleep(retryDelayMs(response, attempt));\n continue;\n }\n throw lastError;\n }\n if (!response.ok) {\n throw new OmiApiError(\n `Omi API responded ${response.status} for ${pathAndQuery.split(\"?\")[0]}`,\n response.status,\n await readDetail(response),\n );\n }\n try {\n return await response.json();\n } catch {\n throw new OmiApiError(\"Omi API returned a non-JSON body\");\n }\n }\n throw lastError instanceof Error ? lastError : new OmiApiError(\"Omi API request failed\");\n }\n}\n\n/**\n * Network/timeout failures wrap Node error text that can carry loader\n * paths or stack fragments; sync errors reach MCP clients verbatim, so\n * only the error name + code survive.\n */\nfunction describeNetworkError(err: unknown): string {\n if (!(err instanceof Error)) return \"unexpected non-Error failure\";\n const code = (err as NodeJS.ErrnoException).code;\n return typeof code === \"string\" && code.length > 0 ? `${err.name} (${code})` : err.name;\n}\n\n/** Loop instead of `/\\/+$/` — CodeQL js/polynomial-redos on user-set URLs. */\nfunction stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value.charCodeAt(end - 1) === 0x2f) end--;\n return value.slice(0, end);\n}\n\nasync function readDetail(response: Response): Promise<string | undefined> {\n try {\n const body = (await response.clone().json()) as { detail?: unknown };\n return typeof body?.detail === \"string\" ? body.detail : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction backoffMs(attempt: number): number {\n return Math.min(MAX_RETRY_DELAY_MS, 1_000 * 2 ** attempt);\n}\n\nfunction retryDelayMs(response: Response, attempt: number): number {\n const headerValue = response.headers.get(\"retry-after\");\n if (headerValue !== null) {\n const parsed = Number(headerValue);\n if (Number.isFinite(parsed) && parsed > 0) {\n return Math.min(MAX_RETRY_DELAY_MS, Math.ceil(parsed * 1_000));\n }\n }\n return backoffMs(attempt);\n}\n","/**\n * Normalize Omi conversations into Remnic's provider-agnostic\n * `WearableConversation` shape, plus the timezone-aware day-window\n * helpers the Omi API needs (its date filters are ISO datetimes).\n *\n * Omi segments carry `is_user` for the wearer, opaque `SPEAKER_NN`\n * diarization labels, optional `person_id`s (user-defined people), and\n * start/end offsets in seconds relative to the conversation start.\n */\n\nimport type {\n WearableConversation,\n WearableNativeMemory,\n WearableTranscriptSegment,\n} from \"@remnic/core\";\n\nimport type { OmiConversation, OmiMemory } from \"./client.js\";\n\nexport const OMI_SOURCE_ID = \"omi\";\n\n/** \"GMT+05:30\" → \"+05:30\"; plain \"GMT\" → \"+00:00\". */\nexport function timezoneOffsetIso(instant: Date, timezone: string): string {\n try {\n const parts = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: timezone,\n timeZoneName: \"longOffset\",\n }).formatToParts(instant);\n const name = parts.find((part) => part.type === \"timeZoneName\")?.value ?? \"GMT\";\n const match = name.match(/GMT([+-]\\d{2}:\\d{2})?/);\n return match?.[1] ?? \"+00:00\";\n } catch {\n return \"+00:00\";\n }\n}\n\n/** ISO instant for local midnight of `date` in `timezone`. */\nexport function zonedDayStartIso(date: string, timezone: string): string {\n // Two-pass offset resolution: guess from midday (stable away from DST\n // transitions), then re-derive at the candidate midnight itself.\n let offset = timezoneOffsetIso(new Date(`${date}T12:00:00Z`), timezone);\n const candidate = new Date(`${date}T00:00:00${offset}`);\n const refined = timezoneOffsetIso(candidate, timezone);\n if (refined !== offset) {\n offset = refined;\n }\n return `${date}T00:00:00${offset}`;\n}\n\nexport function nextIsoDate(date: string): string {\n const parsed = new Date(`${date}T00:00:00Z`);\n parsed.setUTCDate(parsed.getUTCDate() + 1);\n return parsed.toISOString().slice(0, 10);\n}\n\n/** Half-open [start, end) ISO bounds of a local day. */\nexport function zonedDayBounds(\n date: string,\n timezone: string,\n): { startIso: string; endIso: string } {\n return {\n startIso: zonedDayStartIso(date, timezone),\n endIso: zonedDayStartIso(nextIsoDate(date), timezone),\n };\n}\n\nexport function conversationToWearable(\n conversation: OmiConversation,\n): WearableConversation {\n const startedAtMs = conversation.started_at\n ? Date.parse(conversation.started_at)\n : Number.NaN;\n const segments: WearableTranscriptSegment[] = [];\n for (const segment of conversation.transcript_segments ?? []) {\n const text = typeof segment.text === \"string\" ? segment.text.trim() : \"\";\n if (text.length === 0) continue;\n const isWearer = segment.is_user === true;\n const personId =\n typeof segment.person_id === \"string\" && segment.person_id.length > 0\n ? segment.person_id\n : undefined;\n const label =\n typeof segment.speaker === \"string\" && segment.speaker.trim().length > 0\n ? segment.speaker.trim()\n : undefined;\n const startIso =\n !Number.isNaN(startedAtMs) && typeof segment.start === \"number\"\n ? new Date(startedAtMs + segment.start * 1_000).toISOString()\n : undefined;\n const endIso =\n !Number.isNaN(startedAtMs) && typeof segment.end === \"number\"\n ? new Date(startedAtMs + segment.end * 1_000).toISOString()\n : undefined;\n segments.push({\n text,\n // person_id is the most stable key when the user has tagged the\n // speaker as a known person in Omi; the diarization label\n // otherwise.\n speakerKey: isWearer ? \"user\" : (personId ?? label ?? \"unknown\"),\n ...(label !== undefined ? { speakerName: label } : {}),\n ...(isWearer ? { isWearer: true } : {}),\n ...(startIso !== undefined ? { startIso } : {}),\n ...(endIso !== undefined ? { endIso } : {}),\n });\n }\n\n const title = conversation.structured?.title?.trim();\n const overview = conversation.structured?.overview?.trim();\n const address = conversation.geolocation?.address;\n\n return {\n id: conversation.id,\n source: OMI_SOURCE_ID,\n ...(title && title.length > 0 ? { title } : {}),\n ...(overview && overview.length > 0 ? { summary: overview } : {}),\n startIso: conversation.started_at ?? conversation.created_at ?? \"\",\n ...(conversation.finished_at !== undefined\n ? { endIso: conversation.finished_at }\n : {}),\n ...(typeof address === \"string\" && address.length > 0\n ? { location: address }\n : {}),\n segments,\n };\n}\n\nexport function memoryToNativeMemory(memory: OmiMemory): WearableNativeMemory | null {\n const content = typeof memory.content === \"string\" ? memory.content.trim() : \"\";\n if (content.length === 0) return null;\n return {\n id: memory.id,\n content,\n ...(memory.created_at !== undefined ? { createdIso: memory.created_at } : {}),\n ...(Array.isArray(memory.tags) && memory.tags.length > 0\n ? { tags: memory.tags }\n : {}),\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/normalize.ts"],"sourcesContent":["/**\n * @remnic/connector-omi — Omi AI wearable connector.\n *\n * À-la-carte optional companion of @remnic/core (computed-specifier\n * discovery; importing this module self-registers idempotently).\n *\n * Requires an Omi Developer API key from Settings → Developer →\n * Create Key:\n * - key via `REMNIC_OMI_API_KEY` / `OMI_API_KEY` env (or `apiKey`)\n *\n * Legacy External Integration app installs remain supported when both\n * `wearables.sources.omi.appId` and `wearables.sources.omi.userId`\n * are configured.\n */\n\nimport {\n registerWearableConnector,\n getWearableConnector,\n type WearableConnectorFactoryOptions,\n type WearableConnectorRegistration,\n type WearableFetchOptions,\n type WearableFetchPage,\n type WearableNativeMemoryPage,\n type WearableSourceConnector,\n} from \"@remnic/core\";\n\nimport { OmiClient } from \"./client.js\";\nimport {\n conversationToWearable,\n memoryToNativeMemory,\n OMI_SOURCE_ID,\n zonedDayBounds,\n} from \"./normalize.js\";\n\nexport { OmiApiError, OmiClient, OMI_DEFAULT_BASE_URL } from \"./client.js\";\nexport type {\n OmiClientOptions,\n OmiConversation,\n OmiConversationsPage,\n OmiMemoriesPage,\n OmiMemory,\n OmiTranscriptSegment,\n} from \"./client.js\";\nexport {\n conversationToWearable,\n memoryToNativeMemory,\n nextIsoDate,\n OMI_SOURCE_ID,\n timezoneOffsetIso,\n zonedDayBounds,\n zonedDayStartIso,\n} from \"./normalize.js\";\n\nexport function resolveOmiApiKey(\n configuredKey: string | undefined,\n env: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n if (typeof configuredKey === \"string\" && configuredKey.trim().length > 0) {\n return configuredKey.trim();\n }\n for (const name of [\"REMNIC_OMI_API_KEY\", \"OMI_API_KEY\"]) {\n const value = env[name];\n if (typeof value === \"string\" && value.trim().length > 0) {\n return value.trim();\n }\n }\n return undefined;\n}\n\nfunction parseOffsetCursor(cursor: string | null | undefined): number {\n if (typeof cursor !== \"string\" || cursor.length === 0) return 0;\n const parsed = Number(cursor);\n return Number.isInteger(parsed) && parsed > 0 ? parsed : 0;\n}\n\nexport function createOmiConnector(\n options: WearableConnectorFactoryOptions,\n): WearableSourceConnector {\n // Construction is lazy so `wearables status` works without\n // credentials; the client constructor throws the actionable\n // missing-credential messages at call time.\n let client: OmiClient | null = null;\n const getClient = (): OmiClient => {\n if (!client) {\n client = new OmiClient({\n apiKey: resolveOmiApiKey(options.settings.apiKey) ?? \"\",\n appId: options.settings.appId ?? \"\",\n userId: options.settings.userId ?? \"\",\n baseUrl: options.settings.baseUrl,\n });\n }\n return client;\n };\n\n return {\n id: OMI_SOURCE_ID,\n displayName: \"Omi\",\n async verifyAuth(signal?: AbortSignal) {\n return getClient().verifyAuth(signal);\n },\n async fetchConversations(opts: WearableFetchOptions): Promise<WearableFetchPage> {\n const bounds = zonedDayBounds(opts.date, opts.timezone);\n const page = await getClient().listConversations({\n startIso: bounds.startIso,\n endIso: bounds.endIso,\n offset: parseOffsetCursor(opts.cursor),\n signal: opts.signal,\n });\n return {\n conversations: page.conversations\n .filter((conversation) => conversation.discarded !== true)\n .map(conversationToWearable),\n nextCursor: page.nextOffset !== null ? String(page.nextOffset) : null,\n };\n },\n async fetchNativeMemories(opts: {\n cursor?: string | null;\n signal?: AbortSignal;\n }): Promise<WearableNativeMemoryPage> {\n const page = await getClient().listMemories({\n offset: parseOffsetCursor(opts.cursor),\n signal: opts.signal,\n });\n const memories = [];\n for (const memory of page.memories) {\n const mapped = memoryToNativeMemory(memory);\n if (mapped !== null) memories.push(mapped);\n }\n return {\n memories,\n nextCursor: page.nextOffset !== null ? String(page.nextOffset) : null,\n };\n },\n };\n}\n\nexport const wearableConnectorRegistration: WearableConnectorRegistration = {\n id: OMI_SOURCE_ID,\n displayName: \"Omi\",\n factory: createOmiConnector,\n};\n\n/**\n * Idempotently register the connector with the core registry. Importing\n * this module registers it as a side effect; calling this again is safe\n * (returns false when already registered).\n */\nexport function ensureOmiConnectorRegistered(): boolean {\n if (getWearableConnector(OMI_SOURCE_ID) !== undefined) {\n return false;\n }\n registerWearableConnector(wearableConnectorRegistration);\n return true;\n}\n\nensureOmiConnectorRegistered();\n","/**\n * Minimal Omi Developer API client (raw fetch, no SDK).\n *\n * Current contract verified against docs.omi.me in 2026-07:\n *\n * - base `https://api.omi.me`, auth `Authorization: Bearer omi_dev_...`\n * (Developer API key from Settings → Developer → Create Key)\n * - `GET /v1/dev/user/conversations` with `limit`/`offset`\n * pagination, `start_date`/`end_date` (ISO 8601), and\n * `include_transcript=true`\n * - `GET /v1/dev/user/memories` with `limit`/`offset`\n * - responses are arrays; every optional field may be absent\n * - errors are FastAPI-shaped `{\"detail\": \"...\"}`\n *\n * The older app-scoped Integrations API remains supported when both\n * `appId` and `userId` are configured.\n *\n * The API key is never logged and never appears in thrown error\n * messages.\n */\n\nexport const OMI_DEFAULT_BASE_URL = \"https://api.omi.me\";\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst MAX_RETRIES = 3;\nconst MAX_RETRY_DELAY_MS = 30_000;\n/** Page size for both conversations and memories (API max is 1000). */\nconst PAGE_SIZE = 100;\n\nexport interface OmiTranscriptSegment {\n text?: string;\n speaker?: string;\n speaker_id?: number | string | null;\n speaker_name?: string | null;\n is_user?: boolean;\n person_id?: string | null;\n start?: number;\n end?: number;\n}\n\nexport interface OmiConversation {\n id: string;\n created_at?: string;\n started_at?: string;\n finished_at?: string;\n structured?: {\n title?: string;\n overview?: string;\n category?: string;\n action_items?: Array<{ description?: string; completed?: boolean }>;\n };\n transcript_segments?: OmiTranscriptSegment[];\n geolocation?: { address?: string | null } | null;\n status?: string;\n discarded?: boolean;\n}\n\nexport interface OmiMemory {\n id: string;\n content?: string;\n category?: string;\n tags?: string[];\n created_at?: string;\n}\n\nexport interface OmiConversationsPage {\n conversations: OmiConversation[];\n nextOffset: number | null;\n}\n\nexport interface OmiMemoriesPage {\n memories: OmiMemory[];\n nextOffset: number | null;\n}\n\nexport interface OmiClientOptions {\n apiKey: string;\n appId?: string;\n userId?: string;\n baseUrl?: string;\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n sleep?: (ms: number) => Promise<void>;\n}\n\ntype OmiApiMode = \"developer\" | \"integration\";\n\nexport class OmiApiError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n /** FastAPI `detail` string when the body carried one. */\n readonly detail?: string,\n ) {\n super(message);\n this.name = \"OmiApiError\";\n }\n}\n\nexport class OmiClient {\n private readonly apiKey: string;\n private readonly appId?: string;\n private readonly userId?: string;\n private readonly mode: OmiApiMode;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly sleep: (ms: number) => Promise<void>;\n\n constructor(options: OmiClientOptions) {\n if (typeof options.apiKey !== \"string\" || options.apiKey.trim().length === 0) {\n throw new OmiApiError(\n \"Omi API key is missing. Set wearables.sources.omi.apiKey or the \" +\n \"OMI_API_KEY environment variable (create a Developer API key in \" +\n \"Settings → Developer → Create Key in the Omi app).\",\n );\n }\n\n const appId =\n typeof options.appId === \"string\" && options.appId.trim().length > 0\n ? options.appId.trim()\n : undefined;\n const userId =\n typeof options.userId === \"string\" && options.userId.trim().length > 0\n ? options.userId.trim()\n : undefined;\n if ((appId === undefined) !== (userId === undefined)) {\n throw new OmiApiError(\n \"Omi legacy integration mode requires both wearables.sources.omi.appId \" +\n \"and wearables.sources.omi.userId. Omit both to use the Developer \" +\n \"API key-only process.\",\n );\n }\n\n this.apiKey = options.apiKey.trim();\n this.appId = appId;\n this.userId = userId;\n this.mode = appId !== undefined && userId !== undefined ? \"integration\" : \"developer\";\n this.baseUrl = stripTrailingSlashes(options.baseUrl ?? OMI_DEFAULT_BASE_URL);\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.sleep =\n options.sleep ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));\n }\n\n /** One page of completed conversations inside [startIso, endIso). */\n async listConversations(params: {\n startIso: string;\n endIso: string;\n offset?: number;\n signal?: AbortSignal;\n }): Promise<OmiConversationsPage> {\n const offset = params.offset ?? 0;\n const search = new URLSearchParams({\n limit: String(PAGE_SIZE),\n offset: String(offset),\n start_date: params.startIso,\n end_date: params.endIso,\n });\n let payload: unknown;\n if (this.mode === \"developer\") {\n search.set(\"include_transcript\", \"true\");\n payload = await this.requestJson(\n `/v1/dev/user/conversations?${search.toString()}`,\n params.signal,\n );\n } else {\n search.set(\"uid\", this.userId ?? \"\");\n search.set(\"include_discarded\", \"false\");\n // -1 = unlimited; the legacy API default silently truncates\n // transcripts to their first 100 segments.\n search.set(\"max_transcript_segments\", \"-1\");\n // Repeated param (FastAPI List[str]) — comma-joining does NOT work.\n search.append(\"statuses\", \"completed\");\n payload = await this.requestJson(\n `/v2/integrations/${encodeURIComponent(this.appId ?? \"\")}/conversations?${search.toString()}`,\n params.signal,\n );\n }\n const conversations =\n this.mode === \"developer\"\n ? payload\n : (payload as { conversations?: unknown }).conversations;\n if (!Array.isArray(conversations)) {\n throw new OmiApiError(\n this.mode === \"developer\"\n ? \"Omi API returned an unexpected conversations shape (expected array)\"\n : \"Omi API returned an unexpected conversations shape (missing conversations array)\",\n );\n }\n const valid = conversations.filter(\n (entry): entry is OmiConversation =>\n entry !== null &&\n typeof entry === \"object\" &&\n typeof (entry as { id?: unknown }).id === \"string\",\n )\n .filter(isCompletedOrStatuslessConversation)\n .filter((conversation) =>\n startsInsideHalfOpenWindow(conversation, params.startIso, params.endIso),\n );\n return {\n conversations: valid,\n nextOffset: conversations.length === PAGE_SIZE ? offset + PAGE_SIZE : null,\n };\n }\n\n /** One page of Omi memories (provider-extracted facts). */\n async listMemories(params: {\n offset?: number;\n signal?: AbortSignal;\n } = {}): Promise<OmiMemoriesPage> {\n const offset = params.offset ?? 0;\n const search = new URLSearchParams({\n limit: String(PAGE_SIZE),\n offset: String(offset),\n });\n let payload: unknown;\n if (this.mode === \"developer\") {\n payload = await this.requestJson(\n `/v1/dev/user/memories?${search.toString()}`,\n params.signal,\n );\n } else {\n search.set(\"uid\", this.userId ?? \"\");\n payload = await this.requestJson(\n `/v2/integrations/${encodeURIComponent(this.appId ?? \"\")}/memories?${search.toString()}`,\n params.signal,\n );\n }\n const memories =\n this.mode === \"developer\" ? payload : (payload as { memories?: unknown }).memories;\n if (!Array.isArray(memories)) {\n throw new OmiApiError(\n this.mode === \"developer\"\n ? \"Omi API returned an unexpected memories shape (expected array)\"\n : \"Omi API returned an unexpected memories shape (missing memories array)\",\n );\n }\n const valid = memories.filter(\n (entry): entry is OmiMemory =>\n entry !== null &&\n typeof entry === \"object\" &&\n typeof (entry as { id?: unknown }).id === \"string\",\n );\n return {\n memories: valid,\n nextOffset: memories.length === PAGE_SIZE ? offset + PAGE_SIZE : null,\n };\n }\n\n async verifyAuth(signal?: AbortSignal): Promise<{ ok: boolean; detail?: string }> {\n try {\n const search = new URLSearchParams({\n limit: \"1\",\n offset: \"0\",\n });\n if (this.mode === \"developer\") {\n search.set(\"include_transcript\", \"false\");\n await this.requestJson(\n `/v1/dev/user/conversations?${search.toString()}`,\n signal,\n );\n } else {\n search.set(\"uid\", this.userId ?? \"\");\n search.set(\"max_transcript_segments\", \"1\");\n await this.requestJson(\n `/v2/integrations/${encodeURIComponent(this.appId ?? \"\")}/conversations?${search.toString()}`,\n signal,\n );\n }\n return { ok: true };\n } catch (err) {\n if (err instanceof OmiApiError && err.status !== undefined) {\n // The auth chain yields distinct, actionable detail strings.\n // Legacy integration mode can also surface app/user capability\n // failures, so keep those hints mode-specific.\n const hint =\n err.status === 401\n ? \"missing/malformed Authorization header\"\n : err.status === 403\n ? this.mode === \"developer\"\n ? \"Developer API key rejected or missing conversation access\"\n : \"key rejected, app not enabled for this uid, or missing read_conversations capability\"\n : err.status === 404\n ? this.mode === \"developer\"\n ? \"Developer API endpoint not found — check the configured baseUrl\"\n : \"app not found — check wearables.sources.omi.appId\"\n : undefined;\n return {\n ok: false,\n detail: [err.detail, hint].filter(Boolean).join(\" — \") || err.message,\n };\n }\n // OmiApiError messages are our own constructed strings (already\n // scrubbed — network text is reduced to name + code inside\n // requestJson); foreign errors reduce to name + errno code so raw\n // Node text (paths, loader stacks) never reaches operator\n // surfaces.\n return {\n ok: false,\n detail: err instanceof OmiApiError ? err.message : describeNetworkError(err),\n };\n }\n }\n\n private async requestJson(pathAndQuery: string, signal?: AbortSignal): Promise<unknown> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n signal?.throwIfAborted();\n const timeoutSignal = AbortSignal.timeout(this.timeoutMs);\n const combined = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.baseUrl}${pathAndQuery}`, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n Accept: \"application/json\",\n },\n signal: combined,\n });\n } catch (err) {\n if (signal?.aborted) throw err;\n lastError = err;\n if (attempt < MAX_RETRIES) {\n await this.sleep(backoffMs(attempt));\n continue;\n }\n throw new OmiApiError(\n `Omi API request failed after ${MAX_RETRIES + 1} attempts: ${describeNetworkError(err)}`,\n );\n }\n\n if (response.status === 429 || response.status >= 500) {\n lastError = new OmiApiError(\n `Omi API responded ${response.status}`,\n response.status,\n await readDetail(response),\n );\n if (attempt < MAX_RETRIES) {\n await this.sleep(retryDelayMs(response, attempt));\n continue;\n }\n throw lastError;\n }\n if (!response.ok) {\n throw new OmiApiError(\n `Omi API responded ${response.status} for ${pathAndQuery.split(\"?\")[0]}`,\n response.status,\n await readDetail(response),\n );\n }\n try {\n return await response.json();\n } catch {\n throw new OmiApiError(\"Omi API returned a non-JSON body\");\n }\n }\n throw lastError instanceof Error ? lastError : new OmiApiError(\"Omi API request failed\");\n }\n}\n\n/**\n * Network/timeout failures wrap Node error text that can carry loader\n * paths or stack fragments; sync errors reach MCP clients verbatim, so\n * only the error name + code survive.\n */\nfunction describeNetworkError(err: unknown): string {\n if (!(err instanceof Error)) return \"unexpected non-Error failure\";\n const code = (err as NodeJS.ErrnoException).code;\n return typeof code === \"string\" && code.length > 0 ? `${err.name} (${code})` : err.name;\n}\n\n/** Loop instead of `/\\/+$/` — CodeQL js/polynomial-redos on user-set URLs. */\nfunction stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value.charCodeAt(end - 1) === 0x2f) end--;\n return value.slice(0, end);\n}\n\nfunction startsInsideHalfOpenWindow(\n conversation: OmiConversation,\n startIso: string,\n endIso: string,\n): boolean {\n const startMs = Date.parse(startIso);\n const endMs = Date.parse(endIso);\n if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) return true;\n\n const conversationIso =\n typeof conversation.started_at === \"string\"\n ? conversation.started_at\n : conversation.created_at;\n if (typeof conversationIso !== \"string\" || conversationIso.length === 0) {\n return true;\n }\n const conversationMs = Date.parse(conversationIso);\n if (!Number.isFinite(conversationMs)) return true;\n return conversationMs >= startMs && conversationMs < endMs;\n}\n\nfunction isCompletedOrStatuslessConversation(conversation: OmiConversation): boolean {\n if (typeof conversation.status !== \"string\" || conversation.status.length === 0) {\n return true;\n }\n return conversation.status === \"completed\";\n}\n\nasync function readDetail(response: Response): Promise<string | undefined> {\n try {\n const body = (await response.clone().json()) as { detail?: unknown };\n return typeof body?.detail === \"string\" ? body.detail : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction backoffMs(attempt: number): number {\n return Math.min(MAX_RETRY_DELAY_MS, 1_000 * 2 ** attempt);\n}\n\nfunction retryDelayMs(response: Response, attempt: number): number {\n const headerValue = response.headers.get(\"retry-after\");\n if (headerValue !== null) {\n const parsed = Number(headerValue);\n if (Number.isFinite(parsed) && parsed > 0) {\n return Math.min(MAX_RETRY_DELAY_MS, Math.ceil(parsed * 1_000));\n }\n }\n return backoffMs(attempt);\n}\n","/**\n * Normalize Omi conversations into Remnic's provider-agnostic\n * `WearableConversation` shape, plus the timezone-aware day-window\n * helpers the Omi API needs (its date filters are ISO datetimes).\n *\n * Legacy Omi integration segments carry `is_user` for the wearer,\n * opaque `SPEAKER_NN` diarization labels, optional `person_id`s\n * (user-defined people), and start/end offsets in seconds relative to\n * the conversation start. The current Developer API returns\n * `speaker_name`/`speaker_id` instead; normalize both shapes.\n */\n\nimport type {\n WearableConversation,\n WearableNativeMemory,\n WearableTranscriptSegment,\n} from \"@remnic/core\";\n\nimport type { OmiConversation, OmiMemory } from \"./client.js\";\n\nexport const OMI_SOURCE_ID = \"omi\";\n\n/** \"GMT+05:30\" → \"+05:30\"; plain \"GMT\" → \"+00:00\". */\nexport function timezoneOffsetIso(instant: Date, timezone: string): string {\n try {\n const parts = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: timezone,\n timeZoneName: \"longOffset\",\n }).formatToParts(instant);\n const name = parts.find((part) => part.type === \"timeZoneName\")?.value ?? \"GMT\";\n const match = name.match(/GMT([+-]\\d{2}:\\d{2})?/);\n return match?.[1] ?? \"+00:00\";\n } catch {\n return \"+00:00\";\n }\n}\n\n/** ISO instant for local midnight of `date` in `timezone`. */\nexport function zonedDayStartIso(date: string, timezone: string): string {\n // Two-pass offset resolution: guess from midday (stable away from DST\n // transitions), then re-derive at the candidate midnight itself.\n let offset = timezoneOffsetIso(new Date(`${date}T12:00:00Z`), timezone);\n const candidate = new Date(`${date}T00:00:00${offset}`);\n const refined = timezoneOffsetIso(candidate, timezone);\n if (refined !== offset) {\n offset = refined;\n }\n return `${date}T00:00:00${offset}`;\n}\n\nexport function nextIsoDate(date: string): string {\n const parsed = new Date(`${date}T00:00:00Z`);\n parsed.setUTCDate(parsed.getUTCDate() + 1);\n return parsed.toISOString().slice(0, 10);\n}\n\n/** Half-open [start, end) ISO bounds of a local day. */\nexport function zonedDayBounds(\n date: string,\n timezone: string,\n): { startIso: string; endIso: string } {\n return {\n startIso: zonedDayStartIso(date, timezone),\n endIso: zonedDayStartIso(nextIsoDate(date), timezone),\n };\n}\n\nexport function conversationToWearable(\n conversation: OmiConversation,\n): WearableConversation {\n const startedAtMs = conversation.started_at\n ? Date.parse(conversation.started_at)\n : Number.NaN;\n const segments: WearableTranscriptSegment[] = [];\n for (const segment of conversation.transcript_segments ?? []) {\n const text = typeof segment.text === \"string\" ? segment.text.trim() : \"\";\n if (text.length === 0) continue;\n const isWearer = segment.is_user === true;\n const personId =\n typeof segment.person_id === \"string\" && segment.person_id.length > 0\n ? segment.person_id\n : undefined;\n const speakerId =\n typeof segment.speaker_id === \"string\" || typeof segment.speaker_id === \"number\"\n ? String(segment.speaker_id)\n : undefined;\n const label =\n typeof segment.speaker_name === \"string\" && segment.speaker_name.trim().length > 0\n ? segment.speaker_name.trim()\n : typeof segment.speaker === \"string\" && segment.speaker.trim().length > 0\n ? segment.speaker.trim()\n : undefined;\n const startIso =\n !Number.isNaN(startedAtMs) && typeof segment.start === \"number\"\n ? new Date(startedAtMs + segment.start * 1_000).toISOString()\n : undefined;\n const endIso =\n !Number.isNaN(startedAtMs) && typeof segment.end === \"number\"\n ? new Date(startedAtMs + segment.end * 1_000).toISOString()\n : undefined;\n segments.push({\n text,\n // person_id is the most stable key when the user has tagged the\n // speaker as a known person in Omi. The Developer API's\n // speaker_id is the next best stable key, then the diarization\n // label.\n speakerKey: isWearer ? \"user\" : (personId ?? speakerId ?? label ?? \"unknown\"),\n ...(label !== undefined ? { speakerName: label } : {}),\n ...(isWearer ? { isWearer: true } : {}),\n ...(startIso !== undefined ? { startIso } : {}),\n ...(endIso !== undefined ? { endIso } : {}),\n });\n }\n\n const title = conversation.structured?.title?.trim();\n const overview = conversation.structured?.overview?.trim();\n const address = conversation.geolocation?.address;\n\n return {\n id: conversation.id,\n source: OMI_SOURCE_ID,\n ...(title && title.length > 0 ? { title } : {}),\n ...(overview && overview.length > 0 ? { summary: overview } : {}),\n startIso: conversation.started_at ?? conversation.created_at ?? \"\",\n ...(conversation.finished_at !== undefined\n ? { endIso: conversation.finished_at }\n : {}),\n ...(typeof address === \"string\" && address.length > 0\n ? { location: address }\n : {}),\n segments,\n };\n}\n\nexport function memoryToNativeMemory(memory: OmiMemory): WearableNativeMemory | null {\n const content = typeof memory.content === \"string\" ? memory.content.trim() : \"\";\n if (content.length === 0) return null;\n return {\n id: memory.id,\n content,\n ...(memory.created_at !== undefined ? { createdIso: memory.created_at } : {}),\n ...(Array.isArray(memory.tags) && memory.tags.length > 0\n ? { tags: memory.tags }\n : {}),\n };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@remnic/connector-omi",
3
- "version": "9.3.751",
4
- "description": "Omi AI wearable connector for Remnic — pull, clean, and remember necklace transcripts via the Omi Integrations API",
3
+ "version": "9.3.753",
4
+ "description": "Omi AI wearable connector for Remnic — pull, clean, and remember necklace transcripts via the Omi Developer API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -19,13 +19,13 @@
19
19
  "provenance": true
20
20
  },
21
21
  "peerDependencies": {
22
- "@remnic/core": "^9.3.751"
22
+ "@remnic/core": "^9.3.753"
23
23
  },
24
24
  "devDependencies": {
25
25
  "tsup": "^8.0.0",
26
26
  "typescript": "^5.7.0",
27
27
  "tsx": "^4.0.0",
28
- "@remnic/core": "9.3.751"
28
+ "@remnic/core": "9.3.753"
29
29
  },
30
30
  "license": "MIT",
31
31
  "repository": {