@ikatec/digisac-api-sdk 4.5.1 → 4.6.0

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.
Files changed (117) hide show
  1. package/README.md +5 -3
  2. package/dist/apis/answers/AnswersApi.cjs +38 -0
  3. package/dist/apis/answers/AnswersApi.cjs.map +1 -1
  4. package/dist/apis/answers/AnswersApi.d.ts +33 -2
  5. package/dist/apis/answers/AnswersApi.d.ts.map +1 -1
  6. package/dist/apis/answers/AnswersApi.mjs +38 -0
  7. package/dist/apis/answers/AnswersApi.mjs.map +1 -1
  8. package/dist/apis/answers/types.d.ts +64 -1
  9. package/dist/apis/answers/types.d.ts.map +1 -1
  10. package/dist/apis/botSimulators/BotSimulatorsApi.cjs +42 -0
  11. package/dist/apis/botSimulators/BotSimulatorsApi.cjs.map +1 -0
  12. package/dist/apis/botSimulators/BotSimulatorsApi.d.ts +31 -0
  13. package/dist/apis/botSimulators/BotSimulatorsApi.d.ts.map +1 -0
  14. package/dist/apis/botSimulators/BotSimulatorsApi.mjs +41 -0
  15. package/dist/apis/botSimulators/BotSimulatorsApi.mjs.map +1 -0
  16. package/dist/apis/botSimulators/index.cjs +4 -0
  17. package/dist/apis/botSimulators/index.d.ts +3 -0
  18. package/dist/apis/botSimulators/index.d.ts.map +1 -0
  19. package/dist/apis/botSimulators/index.mjs +3 -0
  20. package/dist/apis/botSimulators/types.cjs +0 -0
  21. package/dist/apis/botSimulators/types.d.ts +73 -0
  22. package/dist/apis/botSimulators/types.d.ts.map +1 -0
  23. package/dist/apis/botSimulators/types.mjs +0 -0
  24. package/dist/apis/botVersions/BotVersionsApi.cjs +25 -0
  25. package/dist/apis/botVersions/BotVersionsApi.cjs.map +1 -1
  26. package/dist/apis/botVersions/BotVersionsApi.d.ts +18 -0
  27. package/dist/apis/botVersions/BotVersionsApi.d.ts.map +1 -1
  28. package/dist/apis/botVersions/BotVersionsApi.mjs +25 -0
  29. package/dist/apis/botVersions/BotVersionsApi.mjs.map +1 -1
  30. package/dist/apis/botVersions/types.d.ts +4 -0
  31. package/dist/apis/botVersions/types.d.ts.map +1 -1
  32. package/dist/apis/bots/BotsApi.cjs +99 -4
  33. package/dist/apis/bots/BotsApi.cjs.map +1 -1
  34. package/dist/apis/bots/BotsApi.d.ts +78 -5
  35. package/dist/apis/bots/BotsApi.d.ts.map +1 -1
  36. package/dist/apis/bots/BotsApi.mjs +99 -4
  37. package/dist/apis/bots/BotsApi.mjs.map +1 -1
  38. package/dist/apis/bots/types.d.ts +266 -16
  39. package/dist/apis/bots/types.d.ts.map +1 -1
  40. package/dist/apis/index.cjs +3 -0
  41. package/dist/apis/index.d.ts +1 -0
  42. package/dist/apis/index.d.ts.map +1 -1
  43. package/dist/apis/index.mjs +3 -1
  44. package/dist/apis/questions/QuestionsApi.cjs +15 -0
  45. package/dist/apis/questions/QuestionsApi.cjs.map +1 -1
  46. package/dist/apis/questions/QuestionsApi.d.ts +15 -1
  47. package/dist/apis/questions/QuestionsApi.d.ts.map +1 -1
  48. package/dist/apis/questions/QuestionsApi.mjs +15 -0
  49. package/dist/apis/questions/QuestionsApi.mjs.map +1 -1
  50. package/dist/apis/questions/types.d.ts +25 -5
  51. package/dist/apis/questions/types.d.ts.map +1 -1
  52. package/dist/apis/quickReplies/QuickRepliesApi.cjs +16 -0
  53. package/dist/apis/quickReplies/QuickRepliesApi.cjs.map +1 -1
  54. package/dist/apis/quickReplies/QuickRepliesApi.d.ts +15 -2
  55. package/dist/apis/quickReplies/QuickRepliesApi.d.ts.map +1 -1
  56. package/dist/apis/quickReplies/QuickRepliesApi.mjs +16 -0
  57. package/dist/apis/quickReplies/QuickRepliesApi.mjs.map +1 -1
  58. package/dist/apis/quickReplies/types.d.ts +30 -2
  59. package/dist/apis/quickReplies/types.d.ts.map +1 -1
  60. package/dist/apis/schedule/ScheduleApi.cjs +37 -2
  61. package/dist/apis/schedule/ScheduleApi.cjs.map +1 -1
  62. package/dist/apis/schedule/ScheduleApi.d.ts +30 -3
  63. package/dist/apis/schedule/ScheduleApi.d.ts.map +1 -1
  64. package/dist/apis/schedule/ScheduleApi.mjs +37 -2
  65. package/dist/apis/schedule/ScheduleApi.mjs.map +1 -1
  66. package/dist/apis/schedule/types.d.ts +58 -11
  67. package/dist/apis/schedule/types.d.ts.map +1 -1
  68. package/dist/apis/ticketTopics/TicketTopicsApi.cjs +25 -2
  69. package/dist/apis/ticketTopics/TicketTopicsApi.cjs.map +1 -1
  70. package/dist/apis/ticketTopics/TicketTopicsApi.d.ts +18 -4
  71. package/dist/apis/ticketTopics/TicketTopicsApi.d.ts.map +1 -1
  72. package/dist/apis/ticketTopics/TicketTopicsApi.mjs +25 -2
  73. package/dist/apis/ticketTopics/TicketTopicsApi.mjs.map +1 -1
  74. package/dist/apis/ticketTopics/types.d.ts +22 -1
  75. package/dist/apis/ticketTopics/types.d.ts.map +1 -1
  76. package/dist/core/BaseCrudApi.cjs +5 -2
  77. package/dist/core/BaseCrudApi.cjs.map +1 -1
  78. package/dist/core/BaseCrudApi.d.ts +7 -4
  79. package/dist/core/BaseCrudApi.d.ts.map +1 -1
  80. package/dist/core/BaseCrudApi.mjs +5 -2
  81. package/dist/core/BaseCrudApi.mjs.map +1 -1
  82. package/dist/core/types.d.ts +1 -1
  83. package/dist/core/types.d.ts.map +1 -1
  84. package/dist/incommingWebhooks/index.d.ts +7 -7
  85. package/dist/incommingWebhooks/index.d.ts.map +1 -1
  86. package/package.json +1 -1
  87. package/src/apis/answers/AnswersApi.test.ts +67 -0
  88. package/src/apis/answers/AnswersApi.ts +66 -2
  89. package/src/apis/answers/types.ts +64 -2
  90. package/src/apis/botSimulators/BotSimulatorsApi.test.ts +58 -0
  91. package/src/apis/botSimulators/BotSimulatorsApi.ts +57 -0
  92. package/src/apis/botSimulators/index.ts +2 -0
  93. package/src/apis/botSimulators/types.ts +72 -0
  94. package/src/apis/botVersions/BotVersionsApi.test.ts +51 -0
  95. package/src/apis/botVersions/BotVersionsApi.ts +41 -0
  96. package/src/apis/botVersions/types.ts +4 -0
  97. package/src/apis/bots/BotsApi.test.ts +177 -0
  98. package/src/apis/bots/BotsApi.ts +162 -6
  99. package/src/apis/bots/types.ts +267 -15
  100. package/src/apis/index.ts +1 -0
  101. package/src/apis/payloads.test-d.ts +148 -0
  102. package/src/apis/questions/QuestionsApi.test.ts +49 -0
  103. package/src/apis/questions/QuestionsApi.ts +20 -1
  104. package/src/apis/questions/types.ts +25 -6
  105. package/src/apis/quickReplies/QuickRepliesApi.test.ts +69 -0
  106. package/src/apis/quickReplies/QuickRepliesApi.ts +35 -2
  107. package/src/apis/quickReplies/types.ts +34 -2
  108. package/src/apis/schedule/ScheduleApi.test.ts +80 -0
  109. package/src/apis/schedule/ScheduleApi.ts +59 -6
  110. package/src/apis/schedule/types.ts +66 -11
  111. package/src/apis/ticketTopics/TicketTopicsApi.test.ts +40 -0
  112. package/src/apis/ticketTopics/TicketTopicsApi.ts +35 -4
  113. package/src/apis/ticketTopics/types.ts +26 -1
  114. package/src/core/BaseCrudApi.ts +9 -5
  115. package/src/core/types.ts +2 -1
  116. package/src/incommingWebhooks/index.test-d.ts +9 -0
  117. package/src/incommingWebhooks/index.ts +7 -7
@@ -1,4 +1,5 @@
1
1
  import type { BotVersion } from '../botVersions/types'
2
+ import type { Contact } from '../contacts/types'
2
3
  import type { Service } from '../services/types'
3
4
 
4
5
  // ─── Settings ─────────────────────────────────────────────────────────────────
@@ -35,6 +36,10 @@ export type BotEvent =
35
36
  | 'CONTACT_RECONNECTED'
36
37
  | 'API_SIGNAL'
37
38
  | 'ANONYMOUS_USER_CREATED_WEBCHAT'
39
+ /** Fired by the worker in `@EVERY` when the contact stops answering the bot. */
40
+ | 'BOT_INACTIVE'
41
+ /** Fired by the worker when an asynchronous `HTTP_REQUEST` action gets its callback. */
42
+ | 'HTTP_REQUEST_CALLBACK'
38
43
 
39
44
  export type BotFlowTriggerNode = {
40
45
  id: string
@@ -69,8 +74,19 @@ export type BotFlowEdge = {
69
74
  sourceHandle: string
70
75
  }
71
76
 
72
- /** Visual flow graph used by the flow builder UI. Gets compiled into `contexts` on save. */
77
+ /** Visual flow graph used by the flow builder UI, as the API answers it. */
73
78
  export type BotFlowJson = {
79
+ nodes: BotFlowNode[]
80
+ /** Absent in a bot saved without a flow (`{ nodes: [] }`). */
81
+ edges?: BotFlowEdge[]
82
+ }
83
+
84
+ /**
85
+ * The flow graph to save. `edges` is required: a bot saved without it (or without `flowJson`)
86
+ * never sends a message — the worker fails reading `flowJson.edges` (SD-2323). Send at least
87
+ * `{ nodes: [], edges: [] }`.
88
+ */
89
+ export type BotFlowJsonInput = {
74
90
  nodes: BotFlowNode[]
75
91
  edges: BotFlowEdge[]
76
92
  }
@@ -108,7 +124,7 @@ export type BotActionDataMap = {
108
124
  actions?: { title: string; value: string }[]
109
125
  }
110
126
  SEND_TERM: {
111
- /** Acceptance term UUID. */
127
+ /** Acceptance term UUID (as stored); see `BotActionInput` for how to send it. */
112
128
  acceptanceTerms?: string
113
129
  }
114
130
  CONFIRM_TERM: {
@@ -170,14 +186,21 @@ export type BotActionDataMap = {
170
186
  maxAttempts?: number
171
187
  /** Actions to take based on AI agent outcomes. */
172
188
  actions?: { context?: string; action: { id: string; type: string } }[]
189
+ /** Knowledge base subject UUIDs the agent answers from. */
190
+ knowledgeBase?: string[]
191
+ /** Waits `waitSeconds` to group the contact's messages before answering. */
192
+ messageGrouping?: { enabled?: boolean; waitSeconds?: number }
173
193
  }
174
194
  HTTP_REQUEST: {
175
195
  requestName?: string
176
196
  endpointUrl?: string
177
197
  httpMethod?: 'get' | 'post' | 'put' | 'patch' | 'delete'
178
198
  isAsyncRequest?: boolean
199
+ /** Sends the Digisac payload (contact, ticket, message) along with the request. */
200
+ sendDigisacPayload?: boolean
179
201
  bodyType?: 'json' | 'xml' | 'formData' | 'empty'
180
- headers?: { key: string; value: string }[]
202
+ /** A `secret` header comes back from the API with `value: null`. */
203
+ headers?: { key: string; value: string | null; secret?: boolean }[]
181
204
  requestBodyFormData?: { key: string; value: string }[]
182
205
  requestBodyJson?: string
183
206
  requestBodyXml?: string
@@ -248,40 +271,269 @@ export type BotContexts = {
248
271
  [contextId: string]: BotContext
249
272
  }
250
273
 
274
+ // ─── Contexts as the /bots routes take and return them ───────────────────────
275
+ //
276
+ // `BotContexts` above is the stored form (a map by context id), served as is by `/bot-versions`
277
+ // and `currentBotVersion`. The `/bots` routes take the contexts as a LIST instead and answer the
278
+ // list again, with each trigger, variable, operator and action described by its option object.
279
+
280
+ /** A variable, operator or action option as the bot builder describes it. */
281
+ export type BotOption<TValue extends string = string> = { value: TValue; name?: string }
282
+
283
+ export type BotConditionTarget =
284
+ | string
285
+ | number
286
+ | string[]
287
+ | { id: string }
288
+ | { id: string }[]
289
+ | { value: string }
290
+ | { value: string }[]
291
+ | { variableName: string; variableValue: string }
292
+
293
+ /** A condition is dropped in silence when `variable`, `operator` or a truthy `target` is missing. */
294
+ export type BotConditionInput = {
295
+ variable: BotOption
296
+ operator: BotOption
297
+ target: BotConditionTarget
298
+ }
299
+
300
+ export type BotConditionGroupInput = {
301
+ conditionType: 'and' | 'or'
302
+ conditions: BotConditionInput[]
303
+ }
304
+
305
+ /** Actions the API saves. `SET_VAR` runs in the worker, but saving it answers 500. */
306
+ export type BotSavableActionType = Exclude<BotActionType, 'SET_VAR'>
307
+
308
+ export type BotActionInput = {
309
+ [K in BotSavableActionType]: {
310
+ type: BotOption<K>
311
+ /** Related records go as their id, except the ones below. */
312
+ data: K extends 'SEND_MESSAGE'
313
+ ? Omit<BotActionDataMap['SEND_MESSAGE'], 'file'> & {
314
+ /**
315
+ * The file goes as an object: a bare id is dropped. `{ fileName, mimetype, base64Url }`
316
+ * uploads a new one.
317
+ */
318
+ file?: { id: string } | { fileName: string; mimetype: string; base64Url: string }
319
+ }
320
+ : K extends 'SEND_TERM'
321
+ ? {
322
+ /**
323
+ * Send the term as `{ id }`: the bot keeps it as `data.currentDefaultTerm`, and both
324
+ * the "term in use" check of `DELETE /acceptance-terms/:id` and the reset of the
325
+ * contacts' acceptance read its `id`. A bare id is saved, but those checks miss it.
326
+ */
327
+ acceptanceTerms?: { id: string } | string
328
+ }
329
+ : BotActionDataMap[K]
330
+ }
331
+ }[BotSavableActionType]
332
+
333
+ /**
334
+ * A `SEND_MESSAGE` without text, interactive or file is dropped, and so is a rule left with no
335
+ * action (except a `BOT_INACTIVE` rule with `data`).
336
+ */
337
+ export type BotRuleInput = {
338
+ title?: string
339
+ actions: BotActionInput[]
340
+ fallbackActions?: BotActionInput[]
341
+ /** Required: a missing `conditions` answers 500. */
342
+ conditions: BotConditionInput[] | BotConditionGroupInput
343
+ data?: Record<string, unknown>
344
+ }
345
+
346
+ export type BotTriggerInput = {
347
+ trigger: { type: BotEvent }
348
+ rules: BotRuleInput[]
349
+ }
350
+
351
+ /** `@INIT`, `@EVERY` and `@FALLBACK` are the built-in contexts; other ids are user-defined. */
352
+ export type BotContextInput = {
353
+ id: string
354
+ name?: string
355
+ triggers: BotTriggerInput[]
356
+ }
357
+
358
+ export type BotParsedCondition =
359
+ | {
360
+ /** `undefined` for a variable the builder does not list. */
361
+ variable?: BotOption & { type?: string; subType?: string }
362
+ operator?: BotOption
363
+ /** Departments and tags come back as their records. */
364
+ target: unknown
365
+ }
366
+ | { $and: BotParsedCondition[] }
367
+ | { $or: BotParsedCondition[] }
368
+
369
+ export type BotParsedAction = {
370
+ type: { value: BotSavableActionType; name: string }
371
+ /** The ids of questions, terms, tags, departments, users etc. come back as their records. */
372
+ data: Record<string, unknown>
373
+ }
374
+
375
+ export type BotParsedRule = {
376
+ title: string
377
+ actions: BotParsedAction[]
378
+ fallbackActions: BotParsedAction[]
379
+ conditions: BotParsedCondition[]
380
+ }
381
+
382
+ export type BotParsedTrigger = {
383
+ /** `undefined` for the worker events the builder does not list (`LEAVE_CONTEXT`, `BOT_INACTIVE`, `HTTP_REQUEST_CALLBACK`). */
384
+ trigger?: { type: BotEvent; name: string }
385
+ rules: BotParsedRule[]
386
+ }
387
+
388
+ export type BotParsedContext = {
389
+ id: string
390
+ name: string
391
+ triggers: BotParsedTrigger[]
392
+ }
393
+
251
394
  // ─── Bot ──────────────────────────────────────────────────────────────────────
252
395
 
396
+ /** Bots are hard-deleted (no `deletedAt`). */
253
397
  export type Bot = {
254
398
  id: string
255
399
  name: string
256
- data: Record<string, unknown>
257
- contexts: BotContexts
400
+ /** Rewritten on every save: the `SEND_TERM` acceptance term of the contexts, as it was sent. */
401
+ data: { currentDefaultTerm?: { id: string } | string | null } & Record<string, unknown>
402
+ /**
403
+ * The list form (see `BotParsedContext`), not the stored map. Saving it back as is loses the
404
+ * `data` of the rules and the `{{@variable}}` conditions (the list does not carry them).
405
+ */
406
+ contexts: BotParsedContext[]
258
407
  flowJson: BotFlowJson | null
259
408
  settings: BotSettings
260
409
  currentBotVersionId: string | null
261
410
  accountId: string
262
411
  createdAt: string
263
412
  updatedAt: string
264
- deletedAt: string | null
413
+ /**
414
+ * The stored map of `contexts`: a JSON string in `getById`, an object in `updateById`; absent
415
+ * in `getMany` and `create`.
416
+ */
417
+ defaultContexts?: string | BotContexts
265
418
  // relationships
266
419
  currentBotVersion?: BotVersion | null
267
420
  services?: Service[]
421
+ /** In `getById`, the first version comes with its contexts in the list form. */
422
+ botVersions?: BotVersion[]
268
423
  }
269
424
 
425
+ export type BotRelationships = 'currentBotVersion' | 'services' | 'botVersions'
426
+
427
+ /** The bot as stored (contexts in the map form), as the `bot.*` webhooks deliver it. */
428
+ export type BotRecord = Omit<
429
+ Bot,
430
+ 'contexts' | 'defaultContexts' | 'flowJson' | BotRelationships
431
+ > & {
432
+ contexts: BotContexts
433
+ /**
434
+ * Raw, unlike the API answers: `{}` for a bot saved without a flow, and the bare list of nodes
435
+ * for a bot created with `isDraft`.
436
+ */
437
+ flowJson: Partial<BotFlowJson> | BotFlowNode[] | null
438
+ }
439
+
440
+ /** What the `bot.api-signal` webhook delivers (the `triggerSignal` call), not the bot. */
441
+ export type BotApiSignalData = {
442
+ botId: string
443
+ contactId: string
444
+ flag?: string
445
+ }
446
+
447
+ /** A duplicate name answers 409, unless the account allows duplicate names. */
270
448
  export type CreateBotPayload = {
271
449
  name: string
272
- data?: Record<string, unknown>
273
- contexts?: BotContexts
274
- flowJson?: BotFlowJson
450
+ /** Required: a missing `contexts` answers 500. */
451
+ contexts: BotContextInput[]
452
+ /** Omitted, the bot never sends a message (SD-2323): send at least `{ nodes: [], edges: [] }`. */
453
+ flowJson?: BotFlowJsonInput
275
454
  settings?: BotSettings
455
+ /**
456
+ * Saves the version as a draft instead of publishing it. The bot itself still takes the draft
457
+ * contexts on create, and its `flowJson` is saved as the bare list of nodes.
458
+ */
459
+ isDraft?: boolean
460
+ /** The id of a bot of the account to duplicate: its secret HTTP headers are kept. */
461
+ id?: string
462
+ /** @deprecated Ignored: the backend rewrites `data` on every save. */
463
+ data?: Record<string, unknown>
276
464
  }
277
465
 
278
- export type BotRelationships = 'currentBotVersion' | 'services'
466
+ /** Renames the bot without touching its contexts or versions. */
467
+ export type RenameBotPayload = {
468
+ name: string
469
+ onlyUpdateBotName: true
470
+ }
279
471
 
280
- export type UpdateBotPayload = {
281
- name?: string
282
- data?: Record<string, unknown>
283
- contexts?: BotContexts
284
- flowJson?: BotFlowJson
472
+ export type SaveBotPayload = {
473
+ /** Required on update too. */
474
+ name: string
475
+ /**
476
+ * The whole flow: it replaces the published one (or the draft, with `isDraft`). An empty list
477
+ * publishes a bot with no contexts.
478
+ */
479
+ contexts: BotContextInput[]
480
+ /** Omitted or without `edges`, the bot never sends a message (SD-2323). */
481
+ flowJson?: BotFlowJsonInput
285
482
  settings?: BotSettings
483
+ /** Saves into the latest draft (or a new one) instead of publishing. */
484
+ isDraft?: boolean
485
+ /** @deprecated Ignored: the backend rewrites `data` on every save. */
486
+ data?: Record<string, unknown>
487
+ /** @deprecated Ignored: every publish points the bot to the version it saves. */
286
488
  currentBotVersionId?: string | null
287
489
  }
490
+
491
+ export type UpdateBotPayload = RenameBotPayload | SaveBotPayload
492
+
493
+ // ─── Bot errors and extra routes ─────────────────────────────────────────────
494
+
495
+ export type BotHistoryStatus = 'PENDING' | 'SUCCESS' | 'FAILED'
496
+
497
+ /** A step of a bot run, as `getErrors` lists it (always `status: 'FAILED'`). */
498
+ export type BotHistory = {
499
+ id: string
500
+ action: string
501
+ context: string
502
+ payload: Record<string, unknown>
503
+ status: BotHistoryStatus
504
+ errorCode: string | null
505
+ errorMessage: string | null
506
+ createdAt: string
507
+ updatedAt: string
508
+ botId: string
509
+ botSessionId: string
510
+ accountId: string
511
+ contactId: string
512
+ contact?: Contact & { service?: Service }
513
+ }
514
+
515
+ export type BotErrorsQuery = {
516
+ page?: number
517
+ /** Defaults to 15, with no upper limit. */
518
+ perPage?: number
519
+ /** A column of the history, or `serviceName` for the connection of the contact. Defaults to `createdAt`. */
520
+ sort?: string
521
+ order?: 'ASC' | 'DESC'
522
+ errorCode?: string
523
+ serviceId?: string
524
+ /** Matches part of the action name. */
525
+ search?: string
526
+ /** ISO date. */
527
+ startDate?: string
528
+ /** ISO date. */
529
+ endDate?: string
530
+ }
531
+
532
+ export type SuggestAgentPromptPayload = {
533
+ voiceTone: 'formal' | 'neutral' | 'informal'
534
+ languageType: 'simple' | 'neutral' | 'technical'
535
+ function: 'triage' | 'sales' | 'answer-questions' | 'support'
536
+ companySegment: string
537
+ companySubject: string
538
+ companyServices: string
539
+ }
package/src/apis/index.ts CHANGED
@@ -4,6 +4,7 @@ export * from './accounts'
4
4
  export * from './activityLog'
5
5
  export * from './answers'
6
6
  export * from './authHistory'
7
+ export * from './botSimulators'
7
8
  export * from './botVersions'
8
9
  export * from './bots'
9
10
  export * from './campaigns'
@@ -1,13 +1,24 @@
1
1
  import { expectAssignable, expectError, expectNotAssignable, expectType } from 'tsd'
2
2
  import type { ApiClient } from '../core/ApiClient'
3
+ import type { Paginated } from '../core/types'
4
+ import { AnswersApi } from './answers/AnswersApi'
5
+ import type { AnswersOverview } from './answers/types'
6
+ import { BotSimulatorsApi } from './botSimulators/BotSimulatorsApi'
7
+ import { BotsApi } from './bots/BotsApi'
8
+ import type { Bot, BotContextInput, BotParsedContext } from './bots/types'
3
9
  import { CardsApi } from './cards/CardsApi'
4
10
  import { ContactsApi } from './contacts/ContactsApi'
5
11
  import { KanbanColumnsApi } from './kanbanColumns/KanbanColumnsApi'
6
12
  import { PipelineApi } from './pipeline/PipelineApi'
7
13
  import type { PermissionName } from './permissions/types'
8
14
  import { PersonalAccessTokensApi } from './personalAccessTokens/PersonalAccessTokensApi'
15
+ import { QuestionsApi } from './questions/QuestionsApi'
16
+ import { QuickRepliesApi } from './quickReplies/QuickRepliesApi'
9
17
  import { RolesApi } from './roles/RolesApi'
18
+ import { ScheduleApi } from './schedule/ScheduleApi'
19
+ import type { ScheduleRecord } from './schedule/types'
10
20
  import { TagsApi } from './tags/TagsApi'
21
+ import { TicketTopicsApi } from './ticketTopics/TicketTopicsApi'
11
22
  import { TicketsApi } from './tickets/TicketsApi'
12
23
  import type { Ticket, TicketTransfer } from './tickets/types'
13
24
  import { TimetableApi } from './timetable/TimetableApi'
@@ -115,3 +126,140 @@ expectType<TicketTransfer[] | undefined>(ticket.ticketTransfers)
115
126
  declare const transfer: TicketTransfer
116
127
  expectType<string | null>(transfer.byUserId)
117
128
  expectType<'opened' | 'transferred' | 'closed' | null>(transfer.action)
129
+
130
+ // ── bots: the /bots routes take and return the contexts as a list ───
131
+
132
+ const bots = new BotsApi(client)
133
+ const botContexts: BotContextInput[] = [
134
+ {
135
+ id: '@INIT',
136
+ triggers: [
137
+ {
138
+ trigger: { type: 'MESSAGE_RECEIVED' },
139
+ rules: [
140
+ { conditions: [], actions: [{ type: { value: 'SEND_MESSAGE' }, data: { text: 'Oi' } }] },
141
+ ],
142
+ },
143
+ ],
144
+ },
145
+ ]
146
+ bots.create({ name: 'Bot', contexts: botContexts, flowJson: { nodes: [], edges: [] } })
147
+ expectError(bots.create({ name: 'Bot', contexts: botContexts, flowJson: { nodes: [] } }))
148
+ expectError(bots.create({ name: 'Bot' }))
149
+ expectError(bots.create({ name: 'Bot', contexts: { '@INIT': { name: 'Início', triggers: {} } } }))
150
+ expectError(
151
+ bots.create({
152
+ name: 'Bot',
153
+ contexts: [
154
+ {
155
+ id: '@INIT',
156
+ triggers: [
157
+ {
158
+ trigger: { type: 'MESSAGE_RECEIVED' },
159
+ rules: [
160
+ {
161
+ conditions: [],
162
+ actions: [{ type: { value: 'SET_VAR' }, data: { key: 'k', value: 'v' } }],
163
+ },
164
+ ],
165
+ },
166
+ ],
167
+ },
168
+ ],
169
+ }),
170
+ )
171
+ bots.updateById('b-1', { name: 'Bot', contexts: botContexts, isDraft: true })
172
+ bots.updateById('b-1', { name: 'Bot', onlyUpdateBotName: true })
173
+ bots.create({
174
+ name: 'Bot',
175
+ contexts: [
176
+ {
177
+ id: '@INIT',
178
+ triggers: [
179
+ {
180
+ trigger: { type: 'TICKET_OPENED' },
181
+ rules: [
182
+ {
183
+ conditions: [],
184
+ actions: [
185
+ { type: { value: 'SEND_TERM' }, data: { acceptanceTerms: { id: 'term-1' } } },
186
+ ],
187
+ },
188
+ ],
189
+ },
190
+ ],
191
+ },
192
+ ],
193
+ })
194
+ expectError(bots.updateById('b-1', { contexts: botContexts }))
195
+ expectError(bots.updateById('b-1', { name: 'Bot' }))
196
+ expectType<Promise<Paginated<Bot>>>(bots.getMany({ paginate: false }))
197
+ expectType<Promise<'OK' | false>>(bots.deleteById('b-1'))
198
+ declare const bot: Bot
199
+ expectType<BotParsedContext[]>(bot.contexts)
200
+
201
+ // ── bot simulator: the trigger pick needs the id of the selection message ──
202
+
203
+ const simulator = new BotSimulatorsApi(client)
204
+ simulator.sendEvent('b-1', { type: 'text_contact', text: 'oi' })
205
+ simulator.sendEvent('b-1', { type: 'select_trigger', id: 'm-1', selectedValue: 'TICKET_OPENED' })
206
+ expectError(simulator.sendEvent('b-1', { type: 'select_trigger', selectedValue: 'TICKET_OPENED' }))
207
+ expectError(
208
+ simulator.sendEvent('b-1', { type: 'select_condition', id: 'm-1', selectedValue: 'yes' }),
209
+ )
210
+
211
+ // ── questions: duration goes as a string; update is not partial ─────
212
+
213
+ const questions = new QuestionsApi(client)
214
+ const question = {
215
+ name: 'NPS',
216
+ type: 'nps',
217
+ questionMessage: 'De 0 a 10?',
218
+ duration: '60',
219
+ tries: 3,
220
+ successMessage: 'Obrigado!',
221
+ invalidMessage: 'De 0 a 10, por favor',
222
+ } as const
223
+ questions.create(question)
224
+ expectType<Promise<'OK'>>(questions.deleteById('q-1'))
225
+ expectError(questions.create({ ...question, duration: 60 }))
226
+ questions.updateById('q-1', { type: 'nps', questionMessage: 'x', duration: 60, tries: 3 })
227
+ expectError(questions.updateById('q-1', { name: 'NPS' }))
228
+
229
+ // ── answers: the overview NPS is positional ─────────────────────────
230
+
231
+ const answers = new AnswersApi(client)
232
+ expectType<Promise<AnswersOverview>>(answers.overview({ type: 'nps', periodType: 'closeDate' }))
233
+ expectError(answers.overview({ type: 'custom' }))
234
+ expectType<Promise<'OK'>>(answers.deleteById('a-1'))
235
+
236
+ // ── schedule: create answers no body; update is not partial ─────────
237
+
238
+ const schedule = new ScheduleApi(client)
239
+ const scheduled = {
240
+ scheduledAt: '2026-10-02T12:00:00Z',
241
+ contactId: 'c-1',
242
+ departmentId: 'd-1',
243
+ notes: '',
244
+ }
245
+ expectType<Promise<void>>(schedule.create({ ...scheduled, message: 'Oi' }))
246
+ expectType<Promise<ScheduleRecord>>(schedule.updateById('s-1', scheduled))
247
+ expectError(schedule.updateById('s-1', { message: 'Oi' }))
248
+ expectError(schedule.create({ ...scheduled, files: [{ name: 'a.png' }] }))
249
+ expectError(schedule.create({ ...scheduled, files: [{ id: 'f-1' }] }))
250
+ schedule.updateById('s-1', { ...scheduled, files: [{ id: 'f-1' }] })
251
+
252
+ // ── ticket topics: create needs the departments ─────────────────────
253
+
254
+ const topics = new TicketTopicsApi(client)
255
+ topics.create({ name: 'Dúvida', departments: [] })
256
+ expectError(topics.create({ name: 'Dúvida' }))
257
+ expectError(topics.updateById('t-1', { departments: [] }))
258
+ expectType<Promise<'OK'>>(topics.deleteById('t-1'))
259
+
260
+ // ── quick replies: update fails without the files ───────────────────
261
+
262
+ const quickReplies = new QuickRepliesApi(client)
263
+ quickReplies.updateById('r-1', { title: 'Oi', text: 'Olá', files: [] })
264
+ expectError(quickReplies.updateById('r-1', { title: 'Oi', text: 'Olá' }))
265
+ expectType<Promise<'OK'>>(quickReplies.deleteById('r-1'))
@@ -0,0 +1,49 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import type { ApiClient } from '../../core/ApiClient'
3
+ import { QuestionsApi } from './QuestionsApi'
4
+
5
+ // ── Mock client factory ──────────────────────────────────────────────
6
+
7
+ function createMockClient(): ApiClient {
8
+ return {
9
+ setAccessToken: vi.fn().mockReturnThis(),
10
+ request: vi.fn(),
11
+ get: vi.fn(),
12
+ post: vi.fn(),
13
+ put: vi.fn(),
14
+ patch: vi.fn(),
15
+ delete: vi.fn(),
16
+ }
17
+ }
18
+
19
+ // ── Tests ────────────────────────────────────────────────────────────
20
+
21
+ describe('QuestionsApi', () => {
22
+ let client: ReturnType<typeof createMockClient>
23
+ let api: QuestionsApi
24
+
25
+ beforeEach(() => {
26
+ client = createMockClient()
27
+ api = new QuestionsApi(client)
28
+ })
29
+
30
+ it('create sends duration as a string', async () => {
31
+ const body = {
32
+ name: 'NPS',
33
+ type: 'nps',
34
+ questionMessage: 'De 0 a 10?',
35
+ duration: '60',
36
+ tries: 3,
37
+ successMessage: 'Obrigado!',
38
+ invalidMessage: 'De 0 a 10',
39
+ } as const
40
+ await api.create(body)
41
+ expect(client.post).toHaveBeenCalledWith('/questions', body, undefined)
42
+ })
43
+
44
+ it('deleteById resolves to the OK of the route', async () => {
45
+ vi.mocked(client.delete).mockResolvedValue('OK')
46
+ expect(await api.deleteById('q1')).toBe('OK')
47
+ expect(client.delete).toHaveBeenCalledWith('/questions/q1', undefined)
48
+ })
49
+ })
@@ -1,13 +1,32 @@
1
1
  import type { ApiClient } from '../../core/ApiClient'
2
2
  import { BaseCrudApi } from '../../core/BaseCrudApi'
3
+ import type { ListQuery } from '../../core/types'
3
4
  import type { Question, CreateQuestionPayload, UpdateQuestionPayload } from './types'
4
5
 
6
+ /**
7
+ * Evaluation questions (NPS/CSAT), sent to the contact by the `SEND_QUESTION` bot action. The
8
+ * backend also serves the same routes under the singular `/question` (kept for compatibility).
9
+ *
10
+ * Permissions: `evaluation.view` to read; `evaluation.view` OR `evaluation.create`/`update`/
11
+ * `destroy` to write.
12
+ */
5
13
  export class QuestionsApi extends BaseCrudApi<
6
14
  Question,
7
15
  CreateQuestionPayload,
8
- UpdateQuestionPayload
16
+ UpdateQuestionPayload,
17
+ ListQuery<Question>,
18
+ 'OK'
9
19
  > {
10
20
  constructor(client: ApiClient) {
11
21
  super(client, '/questions')
12
22
  }
23
+
24
+ /**
25
+ * Deletes a question (soft delete). Resolves to `'OK'`. A question used in a bot answers 403;
26
+ * the answers of a deleted question leave the overview and the exports.
27
+ * @permissions evaluation.view OR evaluation.destroy
28
+ */
29
+ override deleteById(id: string, headers?: Record<string, string>): Promise<'OK'> {
30
+ return super.deleteById(id, headers)
31
+ }
13
32
  }
@@ -1,5 +1,6 @@
1
1
  import type { Answer } from '../answers/types'
2
2
 
3
+ /** `custom` exists in the database, but it never takes an answer (the UI hides it). */
3
4
  export type QuestionType = 'nps' | 'csat' | 'custom'
4
5
 
5
6
  export type Question = {
@@ -7,39 +8,57 @@ export type Question = {
7
8
  name: string
8
9
  type: QuestionType
9
10
  questionMessage: string
11
+ /** Minutes the contact has to answer. Comes back as a number, although it is sent as a string. */
10
12
  duration: number
11
13
  tries: number
12
14
  successMessage: string
13
15
  invalidMessage: string
14
16
  reasonMessage: string | null
17
+ /** Sends `closureMessage` after the evaluation ends. */
18
+ sendClosureMessage: boolean
19
+ closureMessage: string | null
15
20
  accountId: string
16
21
  createdAt: string
17
22
  updatedAt: string
18
23
  deletedAt: string | null
19
- // relationships
24
+ /** @deprecated Never comes: the route drops every `include` (empty whitelist). */
20
25
  answers?: Answer[]
21
26
  }
22
27
 
28
+ /** @deprecated The route drops every `include` (empty whitelist). */
23
29
  export type QuestionRelationships = 'answers'
24
30
 
25
31
  export type CreateQuestionPayload = {
32
+ /** Unique in the account (409, unless the account allows duplicate names). */
26
33
  name: string
34
+ /** An unknown type answers 500 (no validation before the database). */
27
35
  type: QuestionType
28
36
  questionMessage: string
29
- duration: number
37
+ /** Minutes, AS A STRING: a number answers 400 (`Should be of type string.`). */
38
+ duration: string
39
+ /** Greater than 0. */
30
40
  tries: number
41
+ /** Not validated by the route: missing answers 500. */
31
42
  successMessage: string
43
+ /** Not validated by the route: missing answers 500. */
32
44
  invalidMessage: string
33
45
  reasonMessage?: string
46
+ sendClosureMessage?: boolean
47
+ /** Required when `sendClosureMessage` is true (400); up to 500 characters. */
48
+ closureMessage?: string
34
49
  }
35
50
 
51
+ /** Not partial: `type`, `questionMessage`, `duration` and `tries` are required on update. */
36
52
  export type UpdateQuestionPayload = {
53
+ type: QuestionType
54
+ questionMessage: string
55
+ /** Minutes; on update a number is also taken. */
56
+ duration: string | number
57
+ tries: number
37
58
  name?: string
38
- type?: QuestionType
39
- questionMessage?: string
40
- duration?: number
41
- tries?: number
42
59
  successMessage?: string
43
60
  invalidMessage?: string
44
61
  reasonMessage?: string | null
62
+ sendClosureMessage?: boolean
63
+ closureMessage?: string | null
45
64
  }