@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/README.md +220 -12
- package/dist/client.d.ts +100 -3
- package/dist/client.js +714 -11
- package/dist/types.d.ts +509 -12
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
213
|
-
|
|
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
|
-
|
|
220
|
-
|
|
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
|
|
228
|
-
|
|
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
|
+
}
|