@personize/sdk 0.6.2 → 0.6.5

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/dist/client.js CHANGED
@@ -26,7 +26,11 @@ class Personize {
26
26
  if (options?.excludeTags)
27
27
  params.excludeTags = Array.isArray(options.excludeTags) ? options.excludeTags.join(',') : options.excludeTags;
28
28
  const response = await this.client.get('/api/v1/guidelines', { params });
29
- return response.data;
29
+ const responseData = response.data;
30
+ if (responseData?.data) {
31
+ responseData.data = this.normalizeGuidelinesListResponse(responseData.data);
32
+ }
33
+ return responseData;
30
34
  },
31
35
  /**
32
36
  * GET /api/v1/guidelines/:id/structure — Get guideline headings
@@ -100,13 +104,18 @@ class Personize {
100
104
  if (options?.excludeTags)
101
105
  params.excludeTags = Array.isArray(options.excludeTags) ? options.excludeTags.join(',') : options.excludeTags;
102
106
  const response = await this.client.get('/api/v1/collections', { params });
103
- return response.data;
107
+ const responseData = response.data;
108
+ if (responseData?.data) {
109
+ responseData.data = this.normalizeCollectionsListResponse(responseData.data);
110
+ }
111
+ return responseData;
104
112
  },
105
113
  /**
106
114
  * POST /api/v1/collections — Create a new property collection
107
115
  */
108
116
  create: async (payload) => {
109
- const response = await this.client.post('/api/v1/collections', payload);
117
+ const normalizedPayload = await this.normalizeCollectionCreatePayload(payload);
118
+ const response = await this.client.post('/api/v1/collections', normalizedPayload);
110
119
  return response.data;
111
120
  },
112
121
  /**
@@ -156,9 +165,38 @@ class Personize {
156
165
  * Use `memorize` to auto-save outputs and tool results to memory.
157
166
  */
158
167
  prompt: async (options) => {
159
- const response = await this.client.post('/api/v1/prompt', options);
168
+ const normalizedOptions = this.normalizePromptOptions(options);
169
+ const response = await this.client.post('/api/v1/prompt', normalizedOptions);
160
170
  return response.data;
161
171
  },
172
+ /**
173
+ * POST /api/v1/prompt (stream: true) — Stream prompt execution as Server-Sent Events.
174
+ *
175
+ * Returns an async generator that yields SSE events as they arrive:
176
+ * - `text`: plain text chunks from the LLM
177
+ * - `output`: a named output extracted when its </output> marker closes
178
+ * - `step_complete`: fired after each instruction step (multi-step mode)
179
+ * - `done`: final event with all outputs, evaluation, and metadata
180
+ * - `error`: non-fatal error during streaming
181
+ *
182
+ * @example
183
+ * ```ts
184
+ * for await (const event of personize.ai.promptStream({
185
+ * prompt: 'Write a hero section',
186
+ * outputs: [{ name: 'headline' }, { name: 'subtitle' }],
187
+ * })) {
188
+ * if (event.type === 'output') console.log(event.name, event.data);
189
+ * if (event.type === 'done') console.log('Finished:', event.metadata);
190
+ * }
191
+ * ```
192
+ */
193
+ promptStream: (options) => {
194
+ const normalizedOptions = this.normalizePromptOptions({ ...options, stream: true });
195
+ const baseURL = this.client.defaults.baseURL || 'https://agent.personize.ai';
196
+ const authHeader = this.client.defaults.headers?.['Authorization'];
197
+ const streamTimeout = options.streamTimeout ?? 120000;
198
+ return streamPromptSSE(baseURL, authHeader, normalizedOptions, streamTimeout, options.signal);
199
+ },
162
200
  };
163
201
  this.agents = {
164
202
  /**
@@ -201,7 +239,35 @@ class Personize {
201
239
  * Performs structured property extraction and free-form memory creation.
202
240
  */
203
241
  memorize: async (data) => {
204
- const response = await this.client.post('/api/v1/memorize', data);
242
+ if (data.properties && Object.keys(data.properties).length > 0) {
243
+ return this.memorizeBatchFromRecords({
244
+ source: 'SDK memorize compatibility',
245
+ enhanced: data.enhanced,
246
+ records: [{
247
+ content: data.content,
248
+ email: data.email,
249
+ website_url: data.website_url,
250
+ record_id: data.record_id,
251
+ type: data.type,
252
+ customKeyName: data.customKeyName,
253
+ customKeyValue: data.customKeyValue,
254
+ collectionId: data.collectionIds?.[0],
255
+ collectionName: data.collectionName,
256
+ properties: data.properties,
257
+ tags: data.tags,
258
+ enhanced: data.enhanced,
259
+ timestamp: data.timestamp,
260
+ speaker: data.speaker,
261
+ }],
262
+ mapping: {
263
+ entityType: data.type || 'Record',
264
+ properties: {},
265
+ },
266
+ rows: [],
267
+ });
268
+ }
269
+ const normalizedData = await this.normalizeMemorizeOptions(data);
270
+ const response = await this.client.post('/api/v1/memorize', normalizedData);
205
271
  return response.data;
206
272
  },
207
273
  /**
@@ -209,23 +275,63 @@ class Personize {
209
275
  * Supports reflection loops for improved coverage, answer generation, and entity scoping.
210
276
  */
211
277
  smartRecall: async (data) => {
212
- const response = await this.client.post('/api/v1/smart-recall', data);
213
- return response.data;
278
+ const normalizedData = this.normalizeSmartRecallOptions(data);
279
+ const response = await this.client.post('/api/v1/smart-recall', normalizedData);
280
+ const responseData = response.data;
281
+ if (responseData?.data) {
282
+ responseData.data = this.normalizeRecallResponse(responseData.data);
283
+ }
284
+ return responseData;
214
285
  },
215
286
  /**
216
287
  * POST /api/v1/recall — Direct memory lookup (no reflection).
217
288
  */
218
289
  recall: async (data) => {
219
- const response = await this.client.post('/api/v1/recall', data);
220
- return response.data;
290
+ if (this.isLegacyRecallRequest(data)) {
291
+ const compatPayload = this.normalizeLegacyRecallToSmartRecall(data);
292
+ const response = await this.client.post('/api/v1/smart-recall', compatPayload);
293
+ const responseData = response.data;
294
+ if (responseData?.data) {
295
+ responseData.data = this.normalizeRecallResponse(responseData.data);
296
+ }
297
+ return responseData;
298
+ }
299
+ const normalizedData = this.normalizeRecallOptions(data);
300
+ const response = await this.client.post('/api/v1/recall', normalizedData);
301
+ const responseData = response.data;
302
+ if (responseData?.data) {
303
+ responseData.data = this.normalizeRecallResponse(responseData.data);
304
+ }
305
+ return responseData;
221
306
  },
222
307
  /**
223
308
  * POST /api/v1/search — Filter and search records by property conditions.
224
309
  * Returns matching record IDs with optional property values and memories.
225
310
  */
226
311
  search: async (data) => {
227
- const response = await this.client.post('/api/v1/search', data);
228
- return response.data;
312
+ const normalizedRequest = await this.normalizeSearchRequest(data);
313
+ if (normalizedRequest.mode === 'smartRecall') {
314
+ const response = await this.client.post('/api/v1/smart-recall', normalizedRequest.payload);
315
+ const responseData = response.data;
316
+ if (responseData?.data) {
317
+ const recallData = this.normalizeRecallResponse(responseData.data);
318
+ responseData.data = this.attachArrayMetadata([...(recallData || [])], {
319
+ recordIds: [],
320
+ totalMatched: recallData?.length || 0,
321
+ page: 1,
322
+ pageSize: recallData?.length || 0,
323
+ totalPages: 1,
324
+ results: recallData ? [...recallData] : [],
325
+ });
326
+ }
327
+ return responseData;
328
+ }
329
+ const response = await this.client.post('/api/v1/search', normalizedRequest.payload);
330
+ const responseData = response.data;
331
+ if (responseData?.data) {
332
+ responseData.data = this.normalizeSearchResponse(responseData.data);
333
+ }
334
+ return responseData;
229
335
  },
230
336
  /**
231
337
  * POST /api/v1/batch-memorize — Unified batch sync with per-property extractMemories flag.
@@ -233,6 +339,9 @@ class Personize {
233
339
  * Properties without it (or false) are stored as structured data only.
234
340
  */
235
341
  memorizeBatch: async (data) => {
342
+ if (data.records?.length) {
343
+ return this.memorizeBatchFromRecords(data);
344
+ }
236
345
  const { organizationId, userId } = await this.resolveIdentity();
237
346
  const response = await this.client.post('/api/v1/batch-memorize', {
238
347
  ...data,
@@ -249,6 +358,98 @@ class Personize {
249
358
  const response = await this.client.post('/api/v1/smart-memory-digest', data);
250
359
  return response.data;
251
360
  },
361
+ /**
362
+ * POST /api/v1/memory/update — Update a single property or freeform memory.
363
+ *
364
+ * For property updates: pass `propertyName` + `propertyValue` (or array operations).
365
+ * For freeform edits: pass `memoryId` + `text`.
366
+ * Use `expectedVersion` for optimistic concurrency (409 on mismatch).
367
+ */
368
+ update: async (data) => {
369
+ const response = await this.client.post('/api/v1/memory/update', data);
370
+ return response.data;
371
+ },
372
+ /**
373
+ * POST /api/v1/memory/bulk-update — Update multiple properties on a record in one request.
374
+ * Use `expectedVersion` for optimistic concurrency (checked before any writes).
375
+ */
376
+ bulkUpdate: async (data) => {
377
+ const response = await this.client.post('/api/v1/memory/bulk-update', data);
378
+ return response.data;
379
+ },
380
+ /**
381
+ * POST /api/v1/memory/delete — Soft-delete memories (30-day recovery window).
382
+ * Deleted items are excluded from all reads. Use `cancelDeletion` to restore.
383
+ */
384
+ delete: async (data) => {
385
+ const response = await this.client.post('/api/v1/memory/delete', data);
386
+ return response.data;
387
+ },
388
+ /**
389
+ * POST /api/v1/memory/delete-record — Soft-delete all memories for a record (30-day recovery).
390
+ */
391
+ deleteRecord: async (data) => {
392
+ const response = await this.client.post('/api/v1/memory/delete-record', data);
393
+ return response.data;
394
+ },
395
+ /**
396
+ * POST /api/v1/memory/cancel-deletion — Cancel a pending soft-delete within the 30-day window.
397
+ * Returns 409 DELETION_FINALIZED if the window has expired.
398
+ */
399
+ cancelDeletion: async (data) => {
400
+ const response = await this.client.post('/api/v1/memory/cancel-deletion', data);
401
+ return response.data;
402
+ },
403
+ /**
404
+ * POST /api/v1/memory/property-history — Query property change history for a record.
405
+ * Supports filtering by property name, time range, and cursor-based pagination.
406
+ */
407
+ propertyHistory: async (data) => {
408
+ const response = await this.client.post('/api/v1/memory/property-history', data);
409
+ return response.data;
410
+ },
411
+ /**
412
+ * POST /api/v1/memory/query-properties — LLM-powered search across property values.
413
+ * Finds records where a property value matches a natural-language query.
414
+ */
415
+ queryProperties: async (data) => {
416
+ const response = await this.client.post('/api/v1/memory/query-properties', data);
417
+ return response.data;
418
+ },
419
+ /**
420
+ * POST /api/v1/memory/filter-by-property — Deterministic property filter (no LLM, no token cost).
421
+ * Finds records where property values match structured conditions.
422
+ *
423
+ * @example
424
+ * ```ts
425
+ * const result = await client.memory.filterByProperty({
426
+ * conditions: [{ propertyName: 'deal_stage', operator: 'equals', value: 'closed_won' }],
427
+ * type: 'Company',
428
+ * limit: 100,
429
+ * });
430
+ * ```
431
+ */
432
+ filterByProperty: async (data) => {
433
+ const response = await this.client.post('/api/v1/memory/filter-by-property', data);
434
+ return response.data;
435
+ },
436
+ /**
437
+ * POST /api/v1/properties — Get record properties with schema descriptions.
438
+ */
439
+ properties: async (data) => {
440
+ const response = await this.client.post('/api/v1/properties', {
441
+ email: data.email,
442
+ websiteUrl: data.websiteUrl || data.website_url,
443
+ recordId: data.recordId || data.record_id,
444
+ type: data.type,
445
+ customKeyName: data.customKeyName,
446
+ customKeyValue: data.customKeyValue,
447
+ propertyNames: data.propertyNames,
448
+ includeDescriptions: data.includeDescriptions,
449
+ nonEmpty: data.nonEmpty,
450
+ });
451
+ return response.data;
452
+ },
252
453
  };
253
454
  this.evaluate = {
254
455
  /**
@@ -312,6 +513,387 @@ class Personize {
312
513
  const { organizationId } = await this.resolveIdentity();
313
514
  return organizationId;
314
515
  }
516
+ normalizePropertyOptions(options) {
517
+ if (!options)
518
+ return undefined;
519
+ return Array.isArray(options) ? options.join(',') : options;
520
+ }
521
+ inferEntityTypeFromKeys(input) {
522
+ if (input.primaryKeyField === 'email' || input.email)
523
+ return 'Contact';
524
+ if (input.primaryKeyField === 'website' || input.primaryKeyField === 'website_url' || input.website_url || input.websiteUrl) {
525
+ return 'Company';
526
+ }
527
+ return undefined;
528
+ }
529
+ async listAllCollections() {
530
+ const collections = [];
531
+ let nextToken;
532
+ do {
533
+ const response = await this.client.get('/api/v1/collections', {
534
+ params: {
535
+ limit: 100,
536
+ nextToken,
537
+ summary: 'true',
538
+ },
539
+ });
540
+ const data = response.data?.data;
541
+ for (const action of data?.actions ?? []) {
542
+ const payload = action?.payload ?? {};
543
+ if (payload.collectionId && payload.collectionName) {
544
+ collections.push({
545
+ collectionId: String(payload.collectionId),
546
+ collectionName: String(payload.collectionName),
547
+ });
548
+ }
549
+ }
550
+ nextToken = data?.nextToken;
551
+ } while (nextToken);
552
+ return collections;
553
+ }
554
+ async resolveCollectionIdsByName(names) {
555
+ if (!names?.length)
556
+ return undefined;
557
+ const normalizedNames = names.map((name) => name.trim().toLowerCase()).filter(Boolean);
558
+ if (!normalizedNames.length)
559
+ return undefined;
560
+ const collections = await this.listAllCollections();
561
+ const ids = normalizedNames.map((target) => {
562
+ const match = collections.find((collection) => collection.collectionName.toLowerCase() === target);
563
+ return match?.collectionId;
564
+ }).filter((value) => Boolean(value));
565
+ return ids.length ? Array.from(new Set(ids)) : undefined;
566
+ }
567
+ async normalizeCollectionCreatePayload(payload) {
568
+ const normalizedProperties = payload.properties?.map((property) => ({
569
+ ...property,
570
+ options: this.normalizePropertyOptions(property.options),
571
+ update: property.update ?? (property.updateSemantics ? property.updateSemantics !== 'append' : undefined),
572
+ }));
573
+ return {
574
+ collectionName: payload.collectionName || payload.name || '',
575
+ collectionId: payload.collectionId || payload.slug,
576
+ definition: payload.definition || payload.description,
577
+ entityType: payload.entityType || this.inferEntityTypeFromKeys({ primaryKeyField: payload.primaryKeyField }),
578
+ properties: normalizedProperties,
579
+ status: payload.status,
580
+ };
581
+ }
582
+ attachArrayMetadata(items, metadata) {
583
+ return Object.assign(items, metadata);
584
+ }
585
+ normalizeGuidelinesListResponse(data) {
586
+ if (!data?.actions)
587
+ return data;
588
+ const items = data.actions.map((action) => ({
589
+ id: action.id,
590
+ type: action.type,
591
+ slug: typeof action.payload.name === 'string' ? action.payload.name : undefined,
592
+ ...action.payload,
593
+ }));
594
+ return this.attachArrayMetadata(items, data);
595
+ }
596
+ normalizeCollectionsListResponse(data) {
597
+ if (!data?.actions)
598
+ return data;
599
+ const items = data.actions.map((action) => ({
600
+ id: action.id,
601
+ type: action.type,
602
+ name: action.payload.collectionName,
603
+ slug: action.payload.collectionId,
604
+ ...action.payload,
605
+ }));
606
+ return this.attachArrayMetadata(items, data);
607
+ }
608
+ normalizePromptOptions(options) {
609
+ if (!options.evaluationCriteria || options.evaluate) {
610
+ const { evaluationCriteria: _ec, ...rest } = options;
611
+ return rest;
612
+ }
613
+ const { evaluationCriteria, ...rest } = options;
614
+ return {
615
+ ...rest,
616
+ evaluate: {
617
+ criteria: evaluationCriteria,
618
+ serverSide: true,
619
+ },
620
+ };
621
+ }
622
+ async normalizeMemorizeOptions(data) {
623
+ const { collectionName, collectionNames, properties, ...rest } = data;
624
+ const requestedCollectionNames = [
625
+ ...(collectionNames || []),
626
+ ...(collectionName ? [collectionName] : []),
627
+ ];
628
+ const collectionIds = rest.collectionIds?.length
629
+ ? rest.collectionIds
630
+ : await this.resolveCollectionIdsByName(requestedCollectionNames);
631
+ return {
632
+ ...rest,
633
+ collectionIds,
634
+ properties,
635
+ };
636
+ }
637
+ normalizeSmartRecallOptions(data) {
638
+ const { message, collectionName, ...rest } = data;
639
+ return {
640
+ ...rest,
641
+ query: data.query || message || '',
642
+ collectionNames: data.collectionNames || (collectionName ? [collectionName] : undefined),
643
+ };
644
+ }
645
+ isLegacyRecallRequest(data) {
646
+ return Boolean(data.message ||
647
+ data.limit != null ||
648
+ data.collectionName ||
649
+ data.collectionNames?.length ||
650
+ !data.type);
651
+ }
652
+ normalizeLegacyRecallToSmartRecall(data) {
653
+ return {
654
+ query: data.query || data.message || '',
655
+ limit: data.limit,
656
+ email: data.email,
657
+ website_url: data.website_url,
658
+ websiteUrl: data.websiteUrl,
659
+ record_id: data.record_id,
660
+ recordId: data.recordId,
661
+ type: data.type || this.inferEntityTypeFromKeys({
662
+ email: data.email,
663
+ website_url: data.website_url,
664
+ websiteUrl: data.websiteUrl,
665
+ }),
666
+ customKeyName: data.customKeyName,
667
+ customKeyValue: data.customKeyValue,
668
+ filters: data.filters,
669
+ collectionNames: data.collectionNames || (data.collectionName ? [data.collectionName] : undefined),
670
+ };
671
+ }
672
+ normalizeRecallOptions(data) {
673
+ const { message, limit: _limit, collectionName: _collectionName, collectionNames: _collectionNames, ...rest } = data;
674
+ return {
675
+ ...rest,
676
+ query: data.query || message || '',
677
+ type: data.type || this.inferEntityTypeFromKeys({
678
+ email: rest.email,
679
+ website_url: rest.website_url,
680
+ websiteUrl: rest.websiteUrl,
681
+ }),
682
+ };
683
+ }
684
+ buildSearchGroupsFromFilters(filters) {
685
+ if (!filters)
686
+ return undefined;
687
+ const conditions = Object.entries(filters)
688
+ .filter(([, value]) => value !== undefined)
689
+ .flatMap(([property, value]) => {
690
+ if (Array.isArray(value)) {
691
+ return value.map((item) => ({
692
+ property,
693
+ operator: 'contains',
694
+ value: item,
695
+ }));
696
+ }
697
+ return [{
698
+ property,
699
+ operator: 'equals',
700
+ value: value,
701
+ }];
702
+ });
703
+ return conditions.length ? [{ conditions }] : undefined;
704
+ }
705
+ async normalizeSearchRequest(data) {
706
+ const collectionNames = data.collectionNames || (data.collectionName ? [data.collectionName] : undefined);
707
+ if (data.query && !data.groups?.length) {
708
+ return {
709
+ mode: 'smartRecall',
710
+ payload: {
711
+ query: data.query,
712
+ limit: data.limit || data.pageSize,
713
+ type: data.type,
714
+ email: data.email,
715
+ websiteUrl: data.websiteUrl,
716
+ recordId: data.recordId,
717
+ customKeyName: data.customKeyName,
718
+ customKeyValue: data.customKeyValue,
719
+ collectionNames,
720
+ filters: data.filters,
721
+ },
722
+ };
723
+ }
724
+ const collectionIds = data.collectionIds?.length
725
+ ? data.collectionIds
726
+ : await this.resolveCollectionIdsByName(collectionNames);
727
+ return {
728
+ mode: 'search',
729
+ payload: {
730
+ type: data.type,
731
+ email: data.email,
732
+ websiteUrl: data.websiteUrl,
733
+ recordId: data.recordId,
734
+ customKeyName: data.customKeyName,
735
+ customKeyValue: data.customKeyValue,
736
+ collectionIds,
737
+ groups: data.groups || this.buildSearchGroupsFromFilters(data.filters),
738
+ pageSize: data.pageSize || data.limit,
739
+ page: data.page,
740
+ countOnly: data.countOnly,
741
+ returnRecords: data.returnRecords,
742
+ includeMemories: data.includeMemories,
743
+ dataSource: data.dataSource,
744
+ },
745
+ };
746
+ }
747
+ normalizeSearchResponse(data) {
748
+ if (!data)
749
+ return data;
750
+ const results = data.recordIds.map((recordId) => ({
751
+ recordId,
752
+ mainProperties: data.mainProperties?.[recordId],
753
+ record: data.records?.[recordId],
754
+ memories: data.memories?.[recordId],
755
+ }));
756
+ return this.attachArrayMetadata(results, {
757
+ ...data,
758
+ results: [...results],
759
+ });
760
+ }
761
+ normalizeRecallResponse(data) {
762
+ if (!data)
763
+ return undefined;
764
+ const source = Array.isArray(data) ? { results: data } : data;
765
+ const rawResults = Array.isArray(source.results)
766
+ ? source.results
767
+ : Array.isArray(source.memories)
768
+ ? source.memories
769
+ : [];
770
+ const results = rawResults.map((entry) => {
771
+ if (typeof entry !== 'object' || entry === null) {
772
+ return { content: String(entry), memory: String(entry) };
773
+ }
774
+ const record = entry;
775
+ const content = typeof record.content === 'string'
776
+ ? record.content
777
+ : typeof record.memory === 'string'
778
+ ? record.memory
779
+ : typeof record.text === 'string'
780
+ ? record.text
781
+ : undefined;
782
+ return {
783
+ ...record,
784
+ content,
785
+ memory: typeof record.memory === 'string' ? record.memory : content,
786
+ };
787
+ });
788
+ return this.attachArrayMetadata(results, {
789
+ ...(Array.isArray(data) ? {} : source),
790
+ results: [...results],
791
+ });
792
+ }
793
+ isBatchRecordProperty(value) {
794
+ return typeof value === 'object' && value !== null && 'value' in value;
795
+ }
796
+ async memorizeBatchFromRecords(data) {
797
+ const records = data.records || [];
798
+ if (!records.length) {
799
+ return { success: true, data: { memorized: 0 } };
800
+ }
801
+ const contentRecords = records.filter((record) => record.content);
802
+ for (const record of contentRecords) {
803
+ const normalizedMemorize = await this.normalizeMemorizeOptions({
804
+ content: String(record.content),
805
+ email: record.email,
806
+ website_url: record.website_url || record.websiteUrl,
807
+ record_id: record.record_id || record.recordId,
808
+ type: record.type,
809
+ customKeyName: record.customKeyName,
810
+ customKeyValue: record.customKeyValue,
811
+ tags: record.tags,
812
+ enhanced: record.enhanced ?? data.enhanced,
813
+ timestamp: record.timestamp,
814
+ speaker: record.speaker,
815
+ collectionNames: record.collectionName ? [record.collectionName] : undefined,
816
+ collectionIds: record.collectionId ? [record.collectionId] : undefined,
817
+ });
818
+ await this.memory.memorize(normalizedMemorize);
819
+ }
820
+ const structuredRecords = records.filter((record) => record.properties && Object.keys(record.properties).length > 0);
821
+ if (!structuredRecords.length) {
822
+ return { success: true, data: { memorized: contentRecords.length } };
823
+ }
824
+ const first = structuredRecords[0];
825
+ const entityType = first.type || this.inferEntityTypeFromKeys({
826
+ email: first.email,
827
+ website_url: first.website_url,
828
+ websiteUrl: first.websiteUrl,
829
+ }) || 'Record';
830
+ const mappingProperties = {};
831
+ const rows = structuredRecords.map((record) => {
832
+ const row = {};
833
+ if (record.email)
834
+ row.email = record.email;
835
+ if (record.website_url || record.websiteUrl)
836
+ row.website = record.website_url || record.websiteUrl;
837
+ if (record.record_id || record.recordId)
838
+ row.record_id = record.record_id || record.recordId;
839
+ if (record.customKeyValue)
840
+ row.custom_key = record.customKeyValue;
841
+ for (const [propertyName, rawProperty] of Object.entries(record.properties || {})) {
842
+ const property = this.isBatchRecordProperty(rawProperty)
843
+ ? rawProperty
844
+ : { value: rawProperty };
845
+ row[propertyName] = property.value;
846
+ if (!mappingProperties[propertyName]) {
847
+ mappingProperties[propertyName] = {
848
+ sourceField: propertyName,
849
+ collectionId: property.collectionId || record.collectionId || '',
850
+ collectionName: property.collectionName || record.collectionName || '',
851
+ extractMemories: property.extractMemories,
852
+ };
853
+ }
854
+ }
855
+ return row;
856
+ });
857
+ const unresolvedNames = Array.from(new Set(Object.values(mappingProperties)
858
+ .filter((property) => !property.collectionId && property.collectionName)
859
+ .map((property) => property.collectionName)));
860
+ const resolvedMap = new Map();
861
+ if (unresolvedNames.length) {
862
+ const collections = await this.listAllCollections();
863
+ for (const collection of collections) {
864
+ if (unresolvedNames.some((name) => name.toLowerCase() === collection.collectionName.toLowerCase())) {
865
+ resolvedMap.set(collection.collectionName.toLowerCase(), collection.collectionId);
866
+ }
867
+ }
868
+ }
869
+ for (const property of Object.values(mappingProperties)) {
870
+ if (!property.collectionId && property.collectionName) {
871
+ property.collectionId = resolvedMap.get(property.collectionName.toLowerCase()) || '';
872
+ }
873
+ if (!property.collectionId || !property.collectionName) {
874
+ throw new Error('records shorthand requires collectionId/collectionName for each structured property');
875
+ }
876
+ }
877
+ const { organizationId, userId } = await this.resolveIdentity();
878
+ const response = await this.client.post('/api/v1/batch-memorize', {
879
+ source: data.source || 'SDK records shorthand',
880
+ tier: data.tier,
881
+ dryRun: data.dryRun,
882
+ chunkSize: data.chunkSize,
883
+ mapping: {
884
+ entityType,
885
+ email: rows.some((row) => row.email) ? 'email' : undefined,
886
+ website: rows.some((row) => row.website) ? 'website' : undefined,
887
+ customKeyName: first.customKeyName,
888
+ customKey: rows.some((row) => row.custom_key) ? 'custom_key' : undefined,
889
+ properties: mappingProperties,
890
+ },
891
+ rows,
892
+ orgId: organizationId,
893
+ userId,
894
+ });
895
+ return response.data;
896
+ }
315
897
  /**
316
898
  * GET /api/v1/test — Verify API key is valid. Returns request metadata and resolved identity.
317
899
  */
@@ -328,3 +910,124 @@ class Personize {
328
910
  }
329
911
  }
330
912
  exports.Personize = Personize;
913
+ // ────────────────────────────── SSE Stream Parser ──────────────────────────────
914
+ /**
915
+ * Initiates a streaming prompt request and yields SSE events.
916
+ * Extracted as a standalone function to avoid `this` binding issues with async generators.
917
+ */
918
+ async function* streamPromptSSE(baseURL, authHeader, options, streamTimeout, signal) {
919
+ const controller = new AbortController();
920
+ const timeoutId = setTimeout(() => controller.abort(), streamTimeout);
921
+ // Link external signal if provided
922
+ if (signal) {
923
+ if (signal.aborted) {
924
+ clearTimeout(timeoutId);
925
+ controller.abort();
926
+ }
927
+ else {
928
+ signal.addEventListener('abort', () => controller.abort(), { once: true });
929
+ }
930
+ }
931
+ let response;
932
+ try {
933
+ response = await fetch(`${baseURL}/api/v1/prompt`, {
934
+ method: 'POST',
935
+ headers: {
936
+ 'Authorization': authHeader,
937
+ 'Content-Type': 'application/json',
938
+ },
939
+ body: JSON.stringify(options),
940
+ signal: controller.signal,
941
+ });
942
+ }
943
+ catch (err) {
944
+ clearTimeout(timeoutId);
945
+ throw new Error(`Prompt stream connection failed: ${err instanceof Error ? err.message : err}`);
946
+ }
947
+ if (!response.ok) {
948
+ clearTimeout(timeoutId);
949
+ const errorBody = await response.text().catch(() => '');
950
+ throw new Error(`Prompt stream failed (${response.status}): ${errorBody}`);
951
+ }
952
+ if (!response.body) {
953
+ clearTimeout(timeoutId);
954
+ throw new Error('Prompt stream: response body is null');
955
+ }
956
+ try {
957
+ yield* parseSSEStream(response.body);
958
+ }
959
+ finally {
960
+ clearTimeout(timeoutId);
961
+ }
962
+ }
963
+ /**
964
+ * Parses a ReadableStream of SSE bytes into typed PromptSSEEvent objects.
965
+ *
966
+ * Handles:
967
+ * - Partial chunks split across reads
968
+ * - Multi-line `data:` fields (accumulated per event)
969
+ * - Empty lines as event separators
970
+ * - Graceful end-of-stream
971
+ */
972
+ async function* parseSSEStream(body) {
973
+ const reader = body.getReader();
974
+ const decoder = new TextDecoder();
975
+ let buffer = '';
976
+ try {
977
+ while (true) {
978
+ const { done, value } = await reader.read();
979
+ if (done)
980
+ break;
981
+ buffer += decoder.decode(value, { stream: true });
982
+ // Process complete events (separated by double newline)
983
+ const parts = buffer.split('\n\n');
984
+ // Last part may be incomplete — keep it in buffer
985
+ buffer = parts.pop() || '';
986
+ for (const part of parts) {
987
+ const dataLines = [];
988
+ for (const line of part.split('\n')) {
989
+ if (line.startsWith('data: ')) {
990
+ dataLines.push(line.slice(6));
991
+ }
992
+ else if (line.startsWith('data:')) {
993
+ dataLines.push(line.slice(5));
994
+ }
995
+ // Ignore lines starting with ':' (comments/keepalive)
996
+ // Ignore 'event:', 'id:', 'retry:' — we only use unnamed data events
997
+ }
998
+ if (dataLines.length === 0)
999
+ continue;
1000
+ const jsonStr = dataLines.join('\n');
1001
+ try {
1002
+ const event = JSON.parse(jsonStr);
1003
+ yield event;
1004
+ }
1005
+ catch {
1006
+ // Non-JSON data line — skip (could be a keepalive or malformed chunk)
1007
+ }
1008
+ }
1009
+ }
1010
+ // Flush remaining buffer
1011
+ if (buffer.trim()) {
1012
+ const dataLines = [];
1013
+ for (const line of buffer.split('\n')) {
1014
+ if (line.startsWith('data: '))
1015
+ dataLines.push(line.slice(6));
1016
+ else if (line.startsWith('data:'))
1017
+ dataLines.push(line.slice(5));
1018
+ }
1019
+ if (dataLines.length > 0) {
1020
+ try {
1021
+ const event = JSON.parse(dataLines.join('\n'));
1022
+ yield event;
1023
+ }
1024
+ catch {
1025
+ // Ignore incomplete final chunk
1026
+ }
1027
+ }
1028
+ }
1029
+ }
1030
+ finally {
1031
+ reader.releaseLock();
1032
+ }
1033
+ }