@whitewall/blip-sdk 0.0.164 → 0.0.166

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.
@@ -48,6 +48,7 @@ export class AccountNamespace extends Namespace {
48
48
  )
49
49
  }
50
50
 
51
+ /** If you are initializing a chatbot, it usually is status 'available' with routingRule 'identity' */
51
52
  public async setPresence(presence: Omit<Presence, 'lastSeen' | 'instances'>, opts?: ConsumeOptions): Promise<void> {
52
53
  return await this.sendCommand(
53
54
  {
@@ -221,6 +222,7 @@ export class AccountNamespace extends Namespace {
221
222
  }
222
223
 
223
224
  public async *streamContacts(filter?: ODataFilter<Contact>, opts?: ConsumeOptions): AsyncIterable<Contact> {
225
+ const take = opts?.take ?? 100
224
226
  const items = this.sendCommand<'get', Array<Contact>>(
225
227
  {
226
228
  method: 'get',
@@ -230,13 +232,22 @@ export class AccountNamespace extends Namespace {
230
232
  collection: true,
231
233
  stream: true,
232
234
  ...opts,
235
+ take,
233
236
  },
234
237
  )
235
238
 
236
- const seenIdentities = new Set<string | number>()
239
+ const seenIdentities = new Map<string, true>()
240
+ // best-effort to deduplicate without growing memory indefinitely
241
+ const maxIdentityCacheSize = take * 5
242
+
237
243
  for await (const contact of items) {
238
244
  if (!seenIdentities.has(contact.identity)) {
239
- seenIdentities.add(contact.identity)
245
+ seenIdentities.set(contact.identity, true)
246
+
247
+ if (seenIdentities.size > maxIdentityCacheSize) {
248
+ seenIdentities.delete(seenIdentities.keys().next().value!)
249
+ }
250
+
240
251
  yield contact
241
252
  }
242
253
  }
@@ -252,7 +263,10 @@ export class AccountNamespace extends Namespace {
252
263
  const end = new Date(endDate)
253
264
  if (start > end) return
254
265
 
255
- const seenNames = new Set<string>()
266
+ const seenNames = new Map<string, true>()
267
+ // best-effort to deduplicate without growing memory indefinitely
268
+ const maxNameCacheSize = take * 5
269
+ let yieldedCount = 0
256
270
  let cursorTime = end.getTime()
257
271
  const startTime = start.getTime()
258
272
 
@@ -282,17 +296,23 @@ export class AccountNamespace extends Namespace {
282
296
 
283
297
  const name = Node.from(contact.identity).name
284
298
  if (!seenNames.has(name)) {
285
- seenNames.add(name)
299
+ seenNames.set(name, true)
300
+
301
+ if (seenNames.size > maxNameCacheSize) {
302
+ seenNames.delete(seenNames.keys().next().value!)
303
+ }
304
+
286
305
  yield contact
306
+ yieldedCount++
287
307
 
288
- if (opts.max && seenNames.size >= opts.max) {
308
+ if (opts.max && yieldedCount >= opts.max) {
289
309
  return
290
310
  }
291
311
  }
292
312
  }
293
313
  }
294
314
 
295
- const reachedMax = opts.max && seenNames.size >= opts.max
315
+ const reachedMax = opts.max && yieldedCount >= opts.max
296
316
  const hasMore = !reachedMax && count === take && cursorTime !== oldestTime - 1
297
317
  if (!hasMore || !opts.fetchall) break
298
318
 
@@ -560,7 +580,7 @@ export class AccountNamespace extends Namespace {
560
580
  },
561
581
  opts?: Omit<ConsumeOptions, 'skip'>,
562
582
  ): Promise<Array<ThreadItem>> {
563
- let thread: Array<ThreadItem> = []
583
+ const thread: Array<ThreadItem> = []
564
584
  let lastMessage = query?.messageId
565
585
  let lastStorageDate = query?.referenceDate ? new Date(query.referenceDate).toISOString() : undefined
566
586
  let hasMore = true
@@ -593,14 +613,14 @@ export class AccountNamespace extends Namespace {
593
613
  fetchall: false,
594
614
  },
595
615
  )
596
- thread = [...thread, ...threadItems]
616
+ thread.push(...threadItems)
597
617
 
598
618
  hasMore = threadItems.length === take && lastMessage !== threadItems.at(-1)?.id
599
619
  lastMessage = threadItems.at(-1)?.id
600
620
  lastStorageDate = threadItems.at(-1)?.date
601
621
  } while (hasMore && opts?.fetchall)
602
622
 
603
- return thread.toSorted((a, b) => b.date.localeCompare(a.date))
623
+ return thread.sort((a, b) => b.date.localeCompare(a.date))
604
624
  }
605
625
 
606
626
  public async getNotifications(
@@ -127,7 +127,7 @@ export class Namespace {
127
127
  for (const item of items) {
128
128
  yield item
129
129
 
130
- if (options.max && yielded++ >= options.max) {
130
+ if (options.max !== undefined && ++yielded >= options.max) {
131
131
  return
132
132
  }
133
133
  }
@@ -1,7 +1,7 @@
1
1
  import type { BlipClient } from '../client.ts'
2
2
  import type { BlipLanguage } from '../types/account.ts'
3
3
  import type { Identity } from '../types/node.ts'
4
- import type { DetailedPlugin, Plugin, PluginSubscription } from '../types/plugins.ts'
4
+ import type { DetailedPlugin, Plugin, PluginBilling, PluginSubscription } from '../types/plugins.ts'
5
5
  import type { Application } from '../types/portal.ts'
6
6
  import { uri } from '../utils/uri.ts'
7
7
  import { type ConsumeOptions, Namespace, type SendCommandOptions } from './namespace.ts'
@@ -221,11 +221,156 @@ export class PluginsNamespace extends Namespace {
221
221
  )
222
222
  }
223
223
 
224
+ /** This route can only be called by an admin user of blip.ai */
225
+ public deletePluginMedia(mediaId: string, opts?: ConsumeOptions) {
226
+ return this.sendCommand(
227
+ {
228
+ method: 'delete',
229
+ uri: uri`/images/${mediaId}`,
230
+ },
231
+ opts,
232
+ )
233
+ }
234
+
235
+ /** This route can only be called by an admin user of blip.ai */
236
+ public addPluginMedia(pluginId: string, type: 'image' | 'video', url: string, opts?: ConsumeOptions) {
237
+ return this.sendCommand(
238
+ {
239
+ method: 'set',
240
+ type: 'application/vnd.iris.plugins.image-create+json',
241
+ uri: uri`/images`,
242
+ resource: {
243
+ uri: url,
244
+ pluginId,
245
+ imageType: type === 'image' ? 3 : 1,
246
+ },
247
+ },
248
+ opts,
249
+ )
250
+ }
251
+
252
+ /** This route can only be called by an admin user of blip.ai */
253
+ public addPlugin(
254
+ plugin: {
255
+ documentation: DetailedPlugin['documentation']
256
+ url: string
257
+ icon: DetailedPlugin['icon']
258
+ price?: DetailedPlugin['price']
259
+ authorId: number
260
+ media: Array<{ uri: string; type: 'image' | 'video' }>
261
+ tags: Array<number>
262
+ languages: {
263
+ pt: {
264
+ name: string
265
+ overview: string
266
+ description: string
267
+ }
268
+ en: {
269
+ name: string
270
+ overview: string
271
+ description: string
272
+ }
273
+ es: {
274
+ name: string
275
+ overview: string
276
+ description: string
277
+ }
278
+ }
279
+ prices?: Array<
280
+ Omit<PluginBilling, 'billingId' | 'currency' | 'language' | 'isActive' | 'featuresIncluded'> & {
281
+ featuresIncluded: Array<{
282
+ icon: string
283
+ description: string
284
+ }>
285
+ }
286
+ >
287
+ },
288
+ opts?: ConsumeOptions,
289
+ ) {
290
+ const price = plugin.prices?.[0]?.price ?? plugin.price
291
+ return this.sendCommand(
292
+ {
293
+ method: 'set',
294
+ type: 'application/vnd.lime.collection+json',
295
+ uri: uri`/plugins`,
296
+ resource: {
297
+ itemType: 'application/vnd.iris.plugin.resource+json',
298
+ items: [
299
+ {
300
+ name: plugin.languages.pt.name,
301
+ website: plugin.documentation,
302
+ url: plugin.url,
303
+ overview: plugin.languages.pt.overview,
304
+ description: plugin.languages.pt.description,
305
+ version: '1.0.0',
306
+ icon: plugin.icon,
307
+ isPaid: price !== undefined && price > 0,
308
+ price,
309
+ authorId: plugin.authorId,
310
+ installationType: 0,
311
+ configurationLink: '/application/detail/{botId}/plugin/{pluginId}',
312
+ pluginType: 'Frontend',
313
+ images: plugin.media.map((m) => ({
314
+ language: 'pt',
315
+ uri: m.uri,
316
+ imageType: m.type === 'image' ? 3 : 1,
317
+ })),
318
+ chargeType: plugin.prices && plugin.prices.length > 1 ? 3 : 1,
319
+ tags: plugin.tags.map((t) => ({ tagValue: t })),
320
+ languages: [
321
+ {
322
+ language: 0,
323
+ },
324
+ {
325
+ language: 1,
326
+ },
327
+ {
328
+ language: 2,
329
+ },
330
+ ],
331
+ translations: [
332
+ {
333
+ name: plugin.languages.en.name,
334
+ overview: plugin.languages.en.overview,
335
+ description: plugin.languages.en.description,
336
+ language: 'en',
337
+ },
338
+ {
339
+ name: plugin.languages.es.name,
340
+ overview: plugin.languages.es.overview,
341
+ description: plugin.languages.es.description,
342
+ language: 'es',
343
+ },
344
+ ],
345
+ billings: plugin.prices?.map((p) => ({
346
+ name: p.name,
347
+ description: p.description,
348
+ price: p.price,
349
+ // TODO: I don't know exactly what it means yet
350
+ language: 'pt',
351
+ // TODO: This one too
352
+ currency: 1,
353
+ chargeType: 1,
354
+ isRecommended: p.isRecommended,
355
+ featuresIncluded: JSON.stringify(p.featuresIncluded),
356
+ additionalDescription: p.additionalDescription,
357
+ isActive: true,
358
+ })),
359
+ },
360
+ ],
361
+ },
362
+ },
363
+ opts,
364
+ )
365
+ }
366
+
224
367
  /** This route can only be called by an admin user of blip.ai */
225
368
  public updatePlugin(
226
369
  pluginId: string,
227
370
  plugin: Partial<
228
- Pick<DetailedPlugin, 'name' | 'overview' | 'description' | 'icon' | 'documentation'> & { url: string }
371
+ Pick<DetailedPlugin, 'name' | 'overview' | 'description' | 'icon' | 'documentation' | 'price'> & {
372
+ url: string
373
+ }
229
374
  >,
230
375
  opts?: ConsumeOptions,
231
376
  ): Promise<void> {
@@ -241,34 +386,44 @@ export class PluginsNamespace extends Namespace {
241
386
  icon: plugin.icon,
242
387
  website: plugin.documentation,
243
388
  url: plugin.url,
389
+ price: plugin.price,
390
+ isPaid: plugin.price !== undefined && plugin.price > 0,
244
391
  },
245
392
  },
246
393
  opts,
247
394
  )
248
395
  }
249
396
 
250
- /** This route can only be called by an admin user of blip.ai */
251
- public deletePluginMedia(imageId: string, opts?: ConsumeOptions) {
252
- return this.sendCommand(
253
- {
254
- method: 'delete',
255
- uri: uri`/images/${imageId}`,
256
- },
257
- opts,
258
- )
259
- }
260
-
261
- /** This route can only be called by an admin user of blip.ai */
262
- public addPluginMedia(pluginId: string, type: 'image' | 'video', url: string, opts?: ConsumeOptions) {
397
+ public setPluginBilling(
398
+ pluginId: string,
399
+ billingId: string,
400
+ billing: Omit<PluginBilling, 'billingId' | 'currency' | 'language' | 'featuresIncluded'> & {
401
+ featuresIncluded: Array<{
402
+ icon: string
403
+ description: string
404
+ }>
405
+ },
406
+ opts?: ConsumeOptions,
407
+ ) {
263
408
  return this.sendCommand(
264
409
  {
265
410
  method: 'set',
266
- type: 'application/vnd.iris.plugins.image-create+json',
267
- uri: uri`/images`,
411
+ type: 'application/vnd.iris.plugins.billing-update+json',
412
+ uri: uri`/billings/${billingId}`,
268
413
  resource: {
269
- uri: url,
270
- pluginId,
271
- imageType: type === 'image' ? 3 : 1,
414
+ blipPluginId: pluginId,
415
+ name: billing.name,
416
+ description: billing.description,
417
+ featuresIncluded: billing.featuresIncluded ? JSON.stringify(billing.featuresIncluded) : undefined,
418
+ isRecommended: billing.isRecommended,
419
+ price: billing.price,
420
+ additionalDescription: billing.additionalDescription,
421
+ // TODO: add params to this
422
+ language: 'pt',
423
+ // TODO: This one too
424
+ currency: 1,
425
+ chargeType: 3,
426
+ isActive: true,
272
427
  },
273
428
  },
274
429
  opts,
@@ -1,3 +1,4 @@
1
+ import type { ThreadItem } from './account.ts'
1
2
  import type { JsonObject } from './commons.ts'
2
3
  import type { TicketStatus } from './desk.ts'
3
4
  import type { Envelope } from './envelope.ts'
@@ -115,3 +116,26 @@ export const messageToDocument = <Type extends MessageTypes>(
115
116
  type: message.type,
116
117
  value: message.content,
117
118
  })
119
+
120
+ /** @description Best effort to figure out the date the message was created */
121
+ export const messageDate = (message: UnknownMessage): Date | undefined => {
122
+ return message.metadata?.['#envelope.storageDate']
123
+ ? new Date(message.metadata['#envelope.storageDate'])
124
+ : message.metadata?.date_created
125
+ ? new Date(Number(message.metadata.date_created))
126
+ : message.metadata?.['#date_processed']
127
+ ? new Date(Number(message.metadata['#date_processed']))
128
+ : undefined
129
+ }
130
+
131
+ /** @returns Best effort to figure out the actual unique ID, since `message.id` can have collisions */
132
+ export const messageUniqueId = (message: UnknownMessage | ThreadItem) => {
133
+ // Blip doesn't have a standard unique identifier, we need to try multiple sources that may not be present
134
+ return (
135
+ message.metadata?.['#uniqueId'] ??
136
+ message.metadata?.$internalId ??
137
+ message.metadata?.['#messageId'] ??
138
+ message.metadata?.['#envelope.storageDate'] ??
139
+ message.id
140
+ )
141
+ }
@@ -33,10 +33,11 @@ export type PluginBilling = {
33
33
  currency: string
34
34
  description: string
35
35
  featuresIncluded: string
36
- isRecommended: boolean
36
+ isRecommended?: boolean
37
37
  language: string
38
38
  name: string
39
39
  price: number
40
+ additionalDescription?: string
40
41
  }
41
42
 
42
43
  export type PluginSubscription = DetailedPlugin & {