@attrove/sdk 0.1.12 → 0.1.14

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
@@ -41,6 +41,9 @@ Ask questions about the user's unified context with AI-generated answers.
41
41
  // Simple query
42
42
  const response = await attrove.query('What did Sarah say about the Q4 budget?');
43
43
  console.log(response.answer);
44
+ console.log(response.used_message_ids); // msg_xxx IDs
45
+ console.log(response.used_meeting_ids); // mtg_xxx IDs
46
+ console.log(response.used_event_ids); // evt_xxx IDs
44
47
 
45
48
  // Multi-turn conversation - pass history from previous response
46
49
  let history = response.history;
@@ -50,14 +53,14 @@ history = followUp.history;
50
53
 
51
54
  // With filters
52
55
  const filtered = await attrove.query('Latest updates', {
53
- integrationIds: ['integration-uuid'], // Only search specific integration
56
+ integrationIds: ['int_xxx'], // Only search specific integration
54
57
  includeSources: true // Include source snippets
55
58
  });
56
59
  ```
57
60
 
58
61
  ### `search(query, options?)`
59
62
 
60
- Semantic search that returns raw messages without AI summarization.
63
+ Semantic search that returns raw matches across messages, meetings, and events without AI summarization.
61
64
 
62
65
  ```typescript
63
66
  const results = await attrove.search('product launch', {
package/cjs/constants.js CHANGED
@@ -13,7 +13,7 @@ exports.getWsCloseReason = exports.WS_CLOSE_CODES = exports.RETRYABLE_STATUS_SET
13
13
  * Automatically synchronized with package.json during the release process.
14
14
  * For programmatic access, use `getVersion()` from './version'.
15
15
  */
16
- exports.SDK_VERSION = "0.1.12"; // auto-synced from package.json during release
16
+ exports.SDK_VERSION = "0.1.14"; // auto-synced from package.json during release
17
17
  /**
18
18
  * Default API base URL for Attrove services.
19
19
  */
@@ -65,6 +65,9 @@ class MessagesResource {
65
65
  if (options.offset !== undefined) {
66
66
  params.offset = String(options.offset);
67
67
  }
68
+ if (options.excludeBots !== undefined) {
69
+ params.exclude_bots = String(options.excludeBots);
70
+ }
68
71
  if (options.expand?.length) {
69
72
  params.expand = options.expand.join(",");
70
73
  }
@@ -6,6 +6,146 @@
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.QueryResource = void 0;
9
+ function normalizeOptionalId(value) {
10
+ return value == null ? null : String(value);
11
+ }
12
+ function normalizeRequiredId(value) {
13
+ return String(value);
14
+ }
15
+ function normalizeKeyMessage(message) {
16
+ return {
17
+ message_id: normalizeRequiredId(message.message_id),
18
+ thread_id: normalizeOptionalId(message.thread_id),
19
+ conversation_id: normalizeOptionalId(message.conversation_id),
20
+ };
21
+ }
22
+ function normalizeThreadMessage(message) {
23
+ return {
24
+ message_id: normalizeRequiredId(message.message_id),
25
+ received_at: message.received_at,
26
+ integration_type: normalizeIntegrationType(message.integration_type),
27
+ integration_type_generic: message.integration_type_generic,
28
+ sender_name: message.sender_name,
29
+ recipient_names: message.recipient_names,
30
+ body_text: message.body_text,
31
+ thread_id: normalizeOptionalId(message.thread_id),
32
+ thread_message_count: message.thread_message_count,
33
+ thread_position: message.thread_position,
34
+ parent_message_id: normalizeOptionalId(message.parent_message_id),
35
+ conversation_type: message.conversation_type,
36
+ conversation_id: normalizeOptionalId(message.conversation_id),
37
+ conversation_participants: message.conversation_participants,
38
+ };
39
+ }
40
+ const VALID_MEETING_PROVIDERS = new Set([
41
+ 'google_meet',
42
+ 'zoom',
43
+ 'teams',
44
+ 'unknown',
45
+ ]);
46
+ function normalizeMeetingProvider(provider) {
47
+ if (VALID_MEETING_PROVIDERS.has(provider)) {
48
+ return provider;
49
+ }
50
+ // prettier-ignore
51
+ console.warn(`[Attrove SDK] Unknown meeting provider "${provider}" normalized to "unknown". SDK may need updating.`);
52
+ return 'unknown';
53
+ }
54
+ const VALID_INTEGRATION_TYPES = new Set([
55
+ 'slack',
56
+ 'gmail',
57
+ 'outlook',
58
+ 'google_calendar',
59
+ 'outlook_calendar',
60
+ 'unknown',
61
+ ]);
62
+ function normalizeIntegrationType(type) {
63
+ if (VALID_INTEGRATION_TYPES.has(type)) {
64
+ return type;
65
+ }
66
+ // prettier-ignore
67
+ console.warn(`[Attrove SDK] Unknown integration type "${type}" normalized to "unknown". SDK may need updating.`);
68
+ return 'unknown';
69
+ }
70
+ function normalizeSearchMeeting(meeting) {
71
+ return {
72
+ id: meeting.id,
73
+ title: meeting.title,
74
+ meeting_code: meeting.meeting_code,
75
+ start_time: meeting.start_time,
76
+ end_time: meeting.end_time,
77
+ summary: meeting.summary,
78
+ short_summary: meeting.short_summary,
79
+ action_items: meeting.action_items,
80
+ attendees: meeting.attendees,
81
+ meeting_link: meeting.meeting_link,
82
+ has_transcript: meeting.has_transcript,
83
+ provider: normalizeMeetingProvider(meeting.provider),
84
+ event_id: meeting.event_id,
85
+ created_at: meeting.created_at,
86
+ updated_at: meeting.updated_at,
87
+ };
88
+ }
89
+ function normalizeSearchEvent(event) {
90
+ let attendees;
91
+ if (Array.isArray(event.attendees)) {
92
+ attendees = event.attendees;
93
+ }
94
+ else if (event.attendees !== undefined && event.attendees !== null) {
95
+ // prettier-ignore
96
+ console.warn(`[Attrove SDK] Event ${event.id} has non-array attendees (${typeof event.attendees}), ignoring.`);
97
+ }
98
+ return {
99
+ id: event.id,
100
+ calendar_id: event.calendar_id,
101
+ title: event.title,
102
+ description: event.description,
103
+ start_time: event.start_time,
104
+ end_time: event.end_time,
105
+ location: event.location,
106
+ status: event.status,
107
+ all_day: event.all_day,
108
+ organizer_entity_id: event.organizer_entity_id,
109
+ attendee_entity_ids: event.attendee_entity_ids,
110
+ attendees,
111
+ html_link: event.html_link,
112
+ event_link: event.event_link,
113
+ created_at: event.created_at,
114
+ updated_at: event.updated_at,
115
+ };
116
+ }
117
+ function normalizeSearchResponse(response) {
118
+ const conversations = response.conversations || {};
119
+ if (!response.conversations) {
120
+ // prettier-ignore
121
+ console.warn('[Attrove SDK] Search response missing conversations field, defaulting to empty object.');
122
+ }
123
+ const normalizedConversations = {};
124
+ for (const [conversationId, conversation] of Object.entries(conversations)) {
125
+ const normalizedThreads = {};
126
+ for (const [threadId, messages] of Object.entries(conversation.threads || {})) {
127
+ normalizedThreads[threadId] = (messages || []).map(normalizeThreadMessage);
128
+ }
129
+ normalizedConversations[conversationId] = {
130
+ conversation_name: conversation.conversation_name,
131
+ threads: normalizedThreads,
132
+ };
133
+ }
134
+ if (!response.key_messages) {
135
+ // prettier-ignore
136
+ console.warn('[Attrove SDK] Search response missing key_messages field, defaulting to empty array.');
137
+ }
138
+ const result = {
139
+ key_messages: (response.key_messages || []).map(normalizeKeyMessage),
140
+ conversations: normalizedConversations,
141
+ key_meetings: (response.key_meetings || []).map(normalizeSearchMeeting),
142
+ key_events: (response.key_events || []).map(normalizeSearchEvent),
143
+ };
144
+ if (response.warnings && response.warnings.length > 0) {
145
+ result.warnings = response.warnings;
146
+ }
147
+ return result;
148
+ }
9
149
  /**
10
150
  * Query resource for RAG operations.
11
151
  *
@@ -48,7 +188,7 @@ class QueryResource {
48
188
  *
49
189
  * // With filters
50
190
  * const response = await attrove.query('Latest updates', {
51
- * integrationIds: ['550e8400-e29b-41d4-a716-446655440000'],
191
+ * integrationIds: ['int_xxx'],
52
192
  * includeSources: true
53
193
  * });
54
194
  * ```
@@ -71,25 +211,31 @@ class QueryResource {
71
211
  body.allow_bot_messages = options.allowBotMessages;
72
212
  }
73
213
  if (options.includeSources) {
74
- body.expand = "sources";
214
+ body.expand = 'sources';
75
215
  }
76
- const response = await this.http.post(`/v1/users/${this.userId}/query`, body);
216
+ const config = options.idempotencyKey
217
+ ? { headers: { "Idempotency-Key": options.idempotencyKey } }
218
+ : undefined;
219
+ const response = await this.http.post(`/v1/users/${this.userId}/query`, body, config);
77
220
  return {
78
221
  answer: response.answer,
79
222
  history: response.history,
80
223
  used_message_ids: response.used_message_ids,
224
+ used_meeting_ids: response.used_meeting_ids ?? [],
225
+ used_event_ids: response.used_event_ids ?? [],
81
226
  sources: response.sources,
82
227
  };
83
228
  }
84
229
  /**
85
230
  * Semantic search across the user's context.
86
231
  *
87
- * Returns raw matching messages grouped by conversation, without AI summarization.
88
- * Use this when you need full control over how results are displayed.
232
+ * Returns raw matching messages (grouped by conversation), plus matched meetings
233
+ * and calendar events, without AI summarization.
234
+ * Use this when you need full control over how results are displayed and processed.
89
235
  *
90
236
  * @param query - The search query (semantic, not keyword-based)
91
237
  * @param options - Search options including filters, date range, etc.
92
- * @returns Matching messages grouped by conversation
238
+ * @returns Matching messages grouped by conversation, plus matched meetings and calendar events
93
239
  *
94
240
  * @throws {AuthenticationError} If the API key is invalid or expired
95
241
  * @throws {ValidationError} If the search parameters are invalid
@@ -108,10 +254,16 @@ class QueryResource {
108
254
  * senderDomains: ['acme.com'],
109
255
  * includeBodyText: true
110
256
  * });
257
+ *
258
+ * // Expand meeting and event detail fields
259
+ * const richResults = await attrove.search('standup action items', {
260
+ * expand: ['summary', 'action_items', 'description']
261
+ * });
111
262
  * ```
112
263
  */
113
264
  async search(query, options = {}) {
114
265
  const body = { query };
266
+ const expandFields = new Set(options.expand || []);
115
267
  if (options.integrationIds?.length) {
116
268
  body.integration_ids = options.integrationIds;
117
269
  }
@@ -134,9 +286,16 @@ class QueryResource {
134
286
  body.entity_ids = options.entityIds;
135
287
  }
136
288
  if (options.includeBodyText) {
137
- body.expand = "body_text";
289
+ expandFields.add('body_text');
290
+ }
291
+ if (expandFields.size > 0) {
292
+ body.expand = Array.from(expandFields).join(',');
138
293
  }
139
- return this.http.post(`/v1/users/${this.userId}/search`, body);
294
+ const config = options.idempotencyKey
295
+ ? { headers: { "Idempotency-Key": options.idempotencyKey } }
296
+ : undefined;
297
+ const response = await this.http.post(`/v1/users/${this.userId}/search`, body, config);
298
+ return normalizeSearchResponse(response);
140
299
  }
141
300
  }
142
301
  exports.QueryResource = QueryResource;
@@ -27,6 +27,27 @@ class AdminSettingsResource {
27
27
  this.http = http;
28
28
  this.clientId = clientId;
29
29
  }
30
+ /**
31
+ * Get current organization sync settings.
32
+ *
33
+ * @returns Current settings
34
+ *
35
+ * @throws {AuthenticationError} If partner credentials are invalid
36
+ * @throws {NetworkError} If unable to reach the API
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * const admin = Attrove.admin({ clientId, clientSecret });
41
+ * const settings = await admin.settings.get();
42
+ * console.log(settings.initialSyncDays); // 30 or null
43
+ * ```
44
+ */
45
+ async get() {
46
+ const raw = await this.http.get('/v1/users/settings');
47
+ return {
48
+ initialSyncDays: raw.initial_sync_days,
49
+ };
50
+ }
30
51
  /**
31
52
  * Update organization sync settings.
32
53
  *
@@ -37,32 +58,27 @@ class AdminSettingsResource {
37
58
  * @param options - Settings to update
38
59
  * @returns Current settings after the update
39
60
  *
40
- * @throws {ValidationError} If the options are invalid (e.g., initialSyncDays out of range)
61
+ * @throws {ValidationError} If initialSyncDays is out of range (7-90) or not an integer
41
62
  * @throws {AuthenticationError} If partner credentials are invalid
42
63
  * @throws {NetworkError} If unable to reach the API
43
64
  *
44
65
  * @example
45
66
  * ```ts
46
67
  * const admin = Attrove.admin({ clientId, clientSecret });
47
- *
48
- * // Set initial sync depth to 14 days
49
68
  * const settings = await admin.settings.update({ initialSyncDays: 14 });
50
69
  * console.log(settings.initialSyncDays); // 14
51
70
  * ```
52
71
  */
53
72
  async update(options) {
54
- if (options.initialSyncDays !== undefined) {
55
- if (!Number.isInteger(options.initialSyncDays) ||
56
- options.initialSyncDays < MIN_SYNC_DAYS ||
57
- options.initialSyncDays > MAX_SYNC_DAYS) {
58
- throw new index_js_1.ValidationError(`initialSyncDays must be an integer between ${MIN_SYNC_DAYS} and ${MAX_SYNC_DAYS}, got ${options.initialSyncDays}`);
59
- }
73
+ if (!Number.isInteger(options.initialSyncDays) ||
74
+ options.initialSyncDays < MIN_SYNC_DAYS ||
75
+ options.initialSyncDays > MAX_SYNC_DAYS) {
76
+ throw new index_js_1.ValidationError(`initialSyncDays must be an integer between ${MIN_SYNC_DAYS} and ${MAX_SYNC_DAYS}, got ${options.initialSyncDays}`);
60
77
  }
61
- const body = { client_id: this.clientId };
62
- if (options.initialSyncDays !== undefined) {
63
- body.initial_sync_days = options.initialSyncDays;
64
- }
65
- const raw = await this.http.patch("/v1/users/settings", body);
78
+ const body = {
79
+ initial_sync_days: options.initialSyncDays,
80
+ };
81
+ const raw = await this.http.patch('/v1/users/settings', body);
66
82
  return {
67
83
  initialSyncDays: raw.initial_sync_days,
68
84
  };
@@ -79,11 +79,11 @@ exports.isValidUUID = isValidUUID;
79
79
  * ```
80
80
  */
81
81
  function createUserId(value) {
82
- if (!value || typeof value !== "string") {
83
- return { success: false, error: "UserId must be a non-empty string" };
82
+ if (!value || typeof value !== 'string') {
83
+ return { success: false, error: 'UserId must be a non-empty string' };
84
84
  }
85
85
  if (!UUID_REGEX.test(value)) {
86
- return { success: false, error: "UserId must be a valid UUID format" };
86
+ return { success: false, error: 'UserId must be a valid UUID format' };
87
87
  }
88
88
  return { success: true, value: value };
89
89
  }
@@ -92,16 +92,16 @@ exports.createUserId = createUserId;
92
92
  * Validate and create an IntegrationId from a string.
93
93
  */
94
94
  function createIntegrationId(value) {
95
- if (!value || typeof value !== "string") {
95
+ if (!value || typeof value !== 'string') {
96
96
  return {
97
97
  success: false,
98
- error: "IntegrationId must be a non-empty string",
98
+ error: 'IntegrationId must be a non-empty string',
99
99
  };
100
100
  }
101
101
  if (!UUID_REGEX.test(value)) {
102
102
  return {
103
103
  success: false,
104
- error: "IntegrationId must be a valid UUID format",
104
+ error: 'IntegrationId must be a valid UUID format',
105
105
  };
106
106
  }
107
107
  return { success: true, value: value };
@@ -111,11 +111,11 @@ exports.createIntegrationId = createIntegrationId;
111
111
  * Validate and create a MessageId from a string.
112
112
  */
113
113
  function createMessageId(value) {
114
- if (!value || typeof value !== "string") {
115
- return { success: false, error: "MessageId must be a non-empty string" };
114
+ if (!value || typeof value !== 'string') {
115
+ return { success: false, error: 'MessageId must be a non-empty string' };
116
116
  }
117
117
  if (!UUID_REGEX.test(value)) {
118
- return { success: false, error: "MessageId must be a valid UUID format" };
118
+ return { success: false, error: 'MessageId must be a valid UUID format' };
119
119
  }
120
120
  return { success: true, value: value };
121
121
  }
@@ -124,16 +124,16 @@ exports.createMessageId = createMessageId;
124
124
  * Validate and create a ConversationId from a string.
125
125
  */
126
126
  function createConversationId(value) {
127
- if (!value || typeof value !== "string") {
127
+ if (!value || typeof value !== 'string') {
128
128
  return {
129
129
  success: false,
130
- error: "ConversationId must be a non-empty string",
130
+ error: 'ConversationId must be a non-empty string',
131
131
  };
132
132
  }
133
133
  if (!UUID_REGEX.test(value)) {
134
134
  return {
135
135
  success: false,
136
- error: "ConversationId must be a valid UUID format",
136
+ error: 'ConversationId must be a valid UUID format',
137
137
  };
138
138
  }
139
139
  return { success: true, value: value };
@@ -151,8 +151,8 @@ exports.createConversationId = createConversationId;
151
151
  * ```
152
152
  */
153
153
  function createApiKey(value) {
154
- if (!value || typeof value !== "string") {
155
- return { success: false, error: "ApiKey must be a non-empty string" };
154
+ if (!value || typeof value !== 'string') {
155
+ return { success: false, error: 'ApiKey must be a non-empty string' };
156
156
  }
157
157
  if (!API_KEY_REGEX.test(value)) {
158
158
  return {
@@ -175,22 +175,22 @@ exports.createApiKey = createApiKey;
175
175
  * ```
176
176
  */
177
177
  function createISODate(value) {
178
- if (!value || typeof value !== "string") {
178
+ if (!value || typeof value !== 'string') {
179
179
  return {
180
180
  success: false,
181
- error: "ISODateString must be a non-empty string",
181
+ error: 'ISODateString must be a non-empty string',
182
182
  };
183
183
  }
184
184
  if (!ISO_DATE_REGEX.test(value)) {
185
185
  return {
186
186
  success: false,
187
- error: "ISODateString must be a valid ISO 8601 date format",
187
+ error: 'ISODateString must be a valid ISO 8601 date format',
188
188
  };
189
189
  }
190
190
  // Additional validation: ensure it parses to a valid date
191
191
  const parsed = Date.parse(value);
192
192
  if (Number.isNaN(parsed)) {
193
- return { success: false, error: "ISODateString must be a valid date" };
193
+ return { success: false, error: 'ISODateString must be a valid date' };
194
194
  }
195
195
  return { success: true, value: value };
196
196
  }
@@ -199,11 +199,11 @@ exports.createISODate = createISODate;
199
199
  * Validate and create a UUID from a string.
200
200
  */
201
201
  function createUUID(value) {
202
- if (!value || typeof value !== "string") {
203
- return { success: false, error: "UUID must be a non-empty string" };
202
+ if (!value || typeof value !== 'string') {
203
+ return { success: false, error: 'UUID must be a non-empty string' };
204
204
  }
205
205
  if (!UUID_REGEX.test(value)) {
206
- return { success: false, error: "UUID must be a valid UUID format" };
206
+ return { success: false, error: 'UUID must be a valid UUID format' };
207
207
  }
208
208
  return { success: true, value: value };
209
209
  }
@@ -217,7 +217,7 @@ exports.createUUID = createUUID;
217
217
  * @see {@link isApiKey} for a type guard that narrows to ApiKeyFormat
218
218
  */
219
219
  function isValidApiKey(value) {
220
- return typeof value === "string" && API_KEY_REGEX.test(value);
220
+ return typeof value === 'string' && API_KEY_REGEX.test(value);
221
221
  }
222
222
  exports.isValidApiKey = isValidApiKey;
223
223
  /**
@@ -244,7 +244,7 @@ exports.isValidApiKey = isValidApiKey;
244
244
  * @see {@link isValidApiKey} for a simple boolean check without type narrowing
245
245
  */
246
246
  function isApiKey(value) {
247
- return typeof value === "string" && API_KEY_REGEX.test(value);
247
+ return typeof value === 'string' && API_KEY_REGEX.test(value);
248
248
  }
249
249
  exports.isApiKey = isApiKey;
250
250
  /**
@@ -262,44 +262,44 @@ exports.isValidISODate = isValidISODate;
262
262
  */
263
263
  exports.ErrorCodes = {
264
264
  // Authentication errors
265
- AUTH_MISSING_TOKEN: "AUTH_MISSING_TOKEN",
266
- AUTH_INVALID_TOKEN: "AUTH_INVALID_TOKEN",
267
- AUTH_EXPIRED_TOKEN: "AUTH_EXPIRED_TOKEN",
268
- AUTH_USER_MISMATCH: "AUTH_USER_MISMATCH",
269
- AUTH_INSUFFICIENT_PERMISSIONS: "AUTH_INSUFFICIENT_PERMISSIONS",
265
+ AUTH_MISSING_TOKEN: 'AUTH_MISSING_TOKEN',
266
+ AUTH_INVALID_TOKEN: 'AUTH_INVALID_TOKEN',
267
+ AUTH_EXPIRED_TOKEN: 'AUTH_EXPIRED_TOKEN',
268
+ AUTH_USER_MISMATCH: 'AUTH_USER_MISMATCH',
269
+ AUTH_INSUFFICIENT_PERMISSIONS: 'AUTH_INSUFFICIENT_PERMISSIONS',
270
270
  // Resource errors
271
- RESOURCE_NOT_FOUND: "RESOURCE_NOT_FOUND",
272
- RESOURCE_ACCESS_DENIED: "RESOURCE_ACCESS_DENIED",
273
- RESOURCE_ALREADY_EXISTS: "RESOURCE_ALREADY_EXISTS",
274
- RESOURCE_DELETED: "RESOURCE_DELETED",
271
+ RESOURCE_NOT_FOUND: 'RESOURCE_NOT_FOUND',
272
+ RESOURCE_ACCESS_DENIED: 'RESOURCE_ACCESS_DENIED',
273
+ RESOURCE_ALREADY_EXISTS: 'RESOURCE_ALREADY_EXISTS',
274
+ RESOURCE_DELETED: 'RESOURCE_DELETED',
275
275
  // Validation errors
276
- VALIDATION_INVALID_ID: "VALIDATION_INVALID_ID",
277
- VALIDATION_REQUIRED_FIELD: "VALIDATION_REQUIRED_FIELD",
278
- VALIDATION_INVALID_FORMAT: "VALIDATION_INVALID_FORMAT",
279
- VALIDATION_OUT_OF_RANGE: "VALIDATION_OUT_OF_RANGE",
276
+ VALIDATION_INVALID_ID: 'VALIDATION_INVALID_ID',
277
+ VALIDATION_REQUIRED_FIELD: 'VALIDATION_REQUIRED_FIELD',
278
+ VALIDATION_INVALID_FORMAT: 'VALIDATION_INVALID_FORMAT',
279
+ VALIDATION_OUT_OF_RANGE: 'VALIDATION_OUT_OF_RANGE',
280
280
  // Integration errors
281
- INTEGRATION_OAUTH_FAILED: "INTEGRATION_OAUTH_FAILED",
282
- INTEGRATION_EMAIL_EXISTS: "INTEGRATION_EMAIL_EXISTS",
283
- INTEGRATION_TOKEN_EXPIRED: "INTEGRATION_TOKEN_EXPIRED",
284
- INTEGRATION_SYNC_FAILED: "INTEGRATION_SYNC_FAILED",
285
- INTEGRATION_NOT_CONNECTED: "INTEGRATION_NOT_CONNECTED",
281
+ INTEGRATION_OAUTH_FAILED: 'INTEGRATION_OAUTH_FAILED',
282
+ INTEGRATION_EMAIL_EXISTS: 'INTEGRATION_EMAIL_EXISTS',
283
+ INTEGRATION_TOKEN_EXPIRED: 'INTEGRATION_TOKEN_EXPIRED',
284
+ INTEGRATION_SYNC_FAILED: 'INTEGRATION_SYNC_FAILED',
285
+ INTEGRATION_NOT_CONNECTED: 'INTEGRATION_NOT_CONNECTED',
286
286
  // Rate limiting
287
- RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED",
287
+ RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',
288
288
  // Server errors
289
- INTERNAL_ERROR: "INTERNAL_ERROR",
290
- SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
291
- REQUEST_TIMEOUT: "REQUEST_TIMEOUT",
289
+ INTERNAL_ERROR: 'INTERNAL_ERROR',
290
+ SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',
291
+ REQUEST_TIMEOUT: 'REQUEST_TIMEOUT',
292
292
  };
293
293
  /**
294
294
  * Valid stream frame types.
295
295
  */
296
296
  const VALID_FRAME_TYPES = new Set([
297
- "chunk",
298
- "end",
299
- "error",
300
- "state",
301
- "message_ids",
302
- "stream_start",
297
+ 'chunk',
298
+ 'end',
299
+ 'error',
300
+ 'state',
301
+ 'message_ids',
302
+ 'stream_start',
303
303
  ]);
304
304
  /**
305
305
  * Runtime validator for StreamFrame objects.
@@ -318,35 +318,46 @@ const VALID_FRAME_TYPES = new Set([
318
318
  * ```
319
319
  */
320
320
  function isValidStreamFrame(data) {
321
- if (!data || typeof data !== "object") {
321
+ if (!data || typeof data !== 'object') {
322
322
  return false;
323
323
  }
324
324
  const frame = data;
325
325
  // All frames must have type and message_id
326
- if (typeof frame.type !== "string" || !VALID_FRAME_TYPES.has(frame.type)) {
326
+ if (typeof frame.type !== 'string' || !VALID_FRAME_TYPES.has(frame.type)) {
327
327
  return false;
328
328
  }
329
- if (typeof frame.message_id !== "string") {
329
+ if (typeof frame.message_id !== 'string') {
330
330
  return false;
331
331
  }
332
332
  // Type-specific validation
333
333
  switch (frame.type) {
334
- case "chunk":
335
- return typeof frame.content === "string";
336
- case "end":
337
- return (typeof frame.reason === "string" &&
338
- ["completed", "cancelled", "error"].includes(frame.reason));
339
- case "error":
340
- return typeof frame.error === "string";
341
- case "state":
342
- return typeof frame.state === "string";
343
- case "message_ids":
334
+ case 'chunk':
335
+ return typeof frame.content === 'string';
336
+ case 'end':
337
+ return (typeof frame.reason === 'string' &&
338
+ ['completed', 'cancelled', 'error'].includes(frame.reason) &&
339
+ isOptionalStringArray(frame.used_message_ids) &&
340
+ isOptionalStringArray(frame.used_meeting_ids) &&
341
+ isOptionalStringArray(frame.used_event_ids));
342
+ case 'error':
343
+ return typeof frame.error === 'string';
344
+ case 'state':
345
+ return typeof frame.state === 'string';
346
+ case 'message_ids':
344
347
  return (Array.isArray(frame.used_message_ids) &&
345
- frame.used_message_ids.every((id) => typeof id === "string"));
346
- case "stream_start":
348
+ frame.used_message_ids.every((id) => typeof id === 'string') &&
349
+ isOptionalStringArray(frame.used_meeting_ids) &&
350
+ isOptionalStringArray(frame.used_event_ids));
351
+ case 'stream_start':
347
352
  return true;
348
353
  default:
349
354
  return false;
350
355
  }
351
356
  }
352
357
  exports.isValidStreamFrame = isValidStreamFrame;
358
+ function isOptionalStringArray(value) {
359
+ if (value === undefined)
360
+ return true;
361
+ return (Array.isArray(value) &&
362
+ value.every((item) => typeof item === 'string'));
363
+ }
@@ -108,6 +108,8 @@ class StreamingClient {
108
108
  this.onWarning = onWarning;
109
109
  let answer = "";
110
110
  let usedMessageIds = [];
111
+ let usedMeetingIds = [];
112
+ let usedEventIds = [];
111
113
  let cancelled = false;
112
114
  let completed = false;
113
115
  // Connect to WebSocket
@@ -225,6 +227,12 @@ class StreamingClient {
225
227
  break;
226
228
  case "message_ids":
227
229
  usedMessageIds = frame.used_message_ids;
230
+ if (frame.used_meeting_ids !== undefined) {
231
+ usedMeetingIds = frame.used_meeting_ids;
232
+ }
233
+ if (frame.used_event_ids !== undefined) {
234
+ usedEventIds = frame.used_event_ids;
235
+ }
228
236
  break;
229
237
  case "error": {
230
238
  const error = new index_js_2.AttroveError(frame.error, index_js_3.ErrorCodes.INTERNAL_ERROR);
@@ -238,6 +246,12 @@ class StreamingClient {
238
246
  if (frame.used_message_ids) {
239
247
  usedMessageIds = frame.used_message_ids;
240
248
  }
249
+ if (frame.used_meeting_ids !== undefined) {
250
+ usedMeetingIds = frame.used_meeting_ids;
251
+ }
252
+ if (frame.used_event_ids !== undefined) {
253
+ usedEventIds = frame.used_event_ids;
254
+ }
241
255
  onEnd?.(frame.reason);
242
256
  // Build updated history
243
257
  const updatedHistory = [
@@ -249,6 +263,8 @@ class StreamingClient {
249
263
  answer,
250
264
  history: updatedHistory,
251
265
  usedMessageIds,
266
+ usedMeetingIds,
267
+ usedEventIds,
252
268
  cancelled,
253
269
  });
254
270
  break;