@contractspec/lib.support-bot 2.4.0 → 2.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 (61) hide show
  1. package/dist/bot/auto-responder.d.ts +2 -0
  2. package/dist/bot/auto-responder.js +678 -21
  3. package/dist/bot/feedback-loop.js +652 -2
  4. package/dist/bot/index.js +681 -23
  5. package/dist/browser/bot/auto-responder.js +678 -21
  6. package/dist/browser/bot/feedback-loop.js +652 -2
  7. package/dist/browser/bot/index.js +681 -23
  8. package/dist/browser/i18n/catalogs/en.js +191 -0
  9. package/dist/browser/i18n/catalogs/es.js +191 -0
  10. package/dist/browser/i18n/catalogs/fr.js +191 -0
  11. package/dist/browser/i18n/catalogs/index.js +571 -0
  12. package/dist/browser/i18n/index.js +670 -0
  13. package/dist/browser/i18n/keys.js +71 -0
  14. package/dist/browser/i18n/locale.js +13 -0
  15. package/dist/browser/i18n/messages.js +585 -0
  16. package/dist/browser/index.js +884 -58
  17. package/dist/browser/rag/index.js +662 -5
  18. package/dist/browser/rag/ticket-resolver.js +662 -5
  19. package/dist/browser/tickets/classifier.js +839 -30
  20. package/dist/browser/tickets/index.js +839 -30
  21. package/dist/i18n/catalogs/en.d.ts +8 -0
  22. package/dist/i18n/catalogs/en.js +192 -0
  23. package/dist/i18n/catalogs/es.d.ts +6 -0
  24. package/dist/i18n/catalogs/es.js +192 -0
  25. package/dist/i18n/catalogs/fr.d.ts +6 -0
  26. package/dist/i18n/catalogs/fr.js +192 -0
  27. package/dist/i18n/catalogs/index.d.ts +8 -0
  28. package/dist/i18n/catalogs/index.js +572 -0
  29. package/dist/i18n/i18n.test.d.ts +1 -0
  30. package/dist/i18n/index.d.ts +30 -0
  31. package/dist/i18n/index.js +671 -0
  32. package/dist/i18n/keys.d.ts +174 -0
  33. package/dist/i18n/keys.js +72 -0
  34. package/dist/i18n/locale.d.ts +8 -0
  35. package/dist/i18n/locale.js +14 -0
  36. package/dist/i18n/messages.d.ts +15 -0
  37. package/dist/i18n/messages.js +586 -0
  38. package/dist/index.js +884 -58
  39. package/dist/node/bot/auto-responder.js +678 -21
  40. package/dist/node/bot/feedback-loop.js +652 -2
  41. package/dist/node/bot/index.js +681 -23
  42. package/dist/node/i18n/catalogs/en.js +191 -0
  43. package/dist/node/i18n/catalogs/es.js +191 -0
  44. package/dist/node/i18n/catalogs/fr.js +191 -0
  45. package/dist/node/i18n/catalogs/index.js +571 -0
  46. package/dist/node/i18n/index.js +670 -0
  47. package/dist/node/i18n/keys.js +71 -0
  48. package/dist/node/i18n/locale.js +13 -0
  49. package/dist/node/i18n/messages.js +585 -0
  50. package/dist/node/index.js +884 -58
  51. package/dist/node/rag/index.js +662 -5
  52. package/dist/node/rag/ticket-resolver.js +662 -5
  53. package/dist/node/tickets/classifier.js +839 -30
  54. package/dist/node/tickets/index.js +839 -30
  55. package/dist/rag/index.js +662 -5
  56. package/dist/rag/ticket-resolver.d.ts +2 -0
  57. package/dist/rag/ticket-resolver.js +662 -5
  58. package/dist/tickets/classifier.d.ts +6 -0
  59. package/dist/tickets/classifier.js +839 -30
  60. package/dist/tickets/index.js +839 -30
  61. package/package.json +148 -8
package/dist/index.js CHANGED
@@ -1,15 +1,667 @@
1
1
  // @bun
2
+ // src/i18n/catalogs/en.ts
3
+ import { defineTranslation } from "@contractspec/lib.contracts-spec/translations";
4
+ var enMessages = defineTranslation({
5
+ meta: {
6
+ key: "support-bot.messages",
7
+ version: "1.0.0",
8
+ domain: "support-bot",
9
+ description: "All user-facing, LLM-facing, and developer-facing strings for the support-bot package",
10
+ owners: ["platform"],
11
+ stability: "experimental"
12
+ },
13
+ locale: "en",
14
+ fallback: "en",
15
+ messages: {
16
+ "prompt.classifier.system": {
17
+ value: "Classify the support ticket.",
18
+ description: "Classifier LLM system prompt"
19
+ },
20
+ "prompt.autoResponder.system": {
21
+ value: "Write empathetic, accurate support replies that cite sources when relevant.",
22
+ description: "Auto-responder LLM system prompt"
23
+ },
24
+ "prompt.autoResponder.user": {
25
+ value: `Ticket #{ticketId} ({category}, {priority}, {sentiment})
26
+ Subject: {subject}
27
+
28
+ {body}
29
+
30
+ Knowledge:
31
+ {knowledge}
32
+
33
+ Respond to the customer.`,
34
+ description: "Auto-responder user prompt template",
35
+ placeholders: [
36
+ { name: "ticketId", type: "string" },
37
+ { name: "category", type: "string" },
38
+ { name: "priority", type: "string" },
39
+ { name: "sentiment", type: "string" },
40
+ { name: "subject", type: "string" },
41
+ { name: "body", type: "string" },
42
+ { name: "knowledge", type: "string" }
43
+ ]
44
+ },
45
+ "responder.closing.friendly": {
46
+ value: "We remain available if you need anything else.",
47
+ description: "Friendly closing line for support responses"
48
+ },
49
+ "responder.closing.formal": {
50
+ value: "Please let us know if you require additional assistance.",
51
+ description: "Formal closing line for support responses"
52
+ },
53
+ "responder.greeting.named": {
54
+ value: "Hi {name},",
55
+ description: "Greeting with customer name",
56
+ placeholders: [{ name: "name", type: "string" }]
57
+ },
58
+ "responder.greeting.anonymous": {
59
+ value: "Hi there,",
60
+ description: "Greeting without customer name"
61
+ },
62
+ "responder.intro.thanks": {
63
+ value: 'Thanks for contacting us about "{subject}".',
64
+ description: "Thank-you intro referencing the ticket subject",
65
+ placeholders: [{ name: "subject", type: "string" }]
66
+ },
67
+ "responder.signature": {
68
+ value: "\u2014 ContractSpec Support",
69
+ description: "Email / response signature"
70
+ },
71
+ "responder.references.header": {
72
+ value: "References:",
73
+ description: "Header for the references section"
74
+ },
75
+ "responder.references.sourceLabel": {
76
+ value: "Source {index}",
77
+ description: "Label for a numbered source reference",
78
+ placeholders: [{ name: "index", type: "number" }]
79
+ },
80
+ "responder.category.billing": {
81
+ value: "I understand billing issues can be stressful, so let me clarify the situation.",
82
+ description: "Category intro for billing tickets"
83
+ },
84
+ "responder.category.technical": {
85
+ value: "I see you encountered a technical issue. Here is what happened and how to fix it.",
86
+ description: "Category intro for technical tickets"
87
+ },
88
+ "responder.category.product": {
89
+ value: "Thanks for sharing feedback about the product. Here are the next steps.",
90
+ description: "Category intro for product tickets"
91
+ },
92
+ "responder.category.account": {
93
+ value: "Account access is critical, so let me walk you through the resolution.",
94
+ description: "Category intro for account tickets"
95
+ },
96
+ "responder.category.compliance": {
97
+ value: "Compliance questions require precision. See the policy-aligned answer below.",
98
+ description: "Category intro for compliance tickets"
99
+ },
100
+ "responder.category.other": {
101
+ value: "Here is what we found after reviewing your request.",
102
+ description: "Category intro for uncategorized tickets"
103
+ },
104
+ "responder.subject.replyPrefix": {
105
+ value: "Re: {subject}",
106
+ description: "Reply subject line prefix",
107
+ placeholders: [{ name: "subject", type: "string" }]
108
+ },
109
+ "resolver.question.subjectLabel": {
110
+ value: "Subject: {subject}",
111
+ description: "Subject label in resolver question context",
112
+ placeholders: [{ name: "subject", type: "string" }]
113
+ },
114
+ "resolver.question.channelLabel": {
115
+ value: "Channel: {channel}",
116
+ description: "Channel label in resolver question context",
117
+ placeholders: [{ name: "channel", type: "string" }]
118
+ },
119
+ "resolver.question.customerLabel": {
120
+ value: "Customer: {name}",
121
+ description: "Customer label in resolver question context",
122
+ placeholders: [{ name: "name", type: "string" }]
123
+ },
124
+ "resolver.action.escalate": {
125
+ value: "Escalate for human review",
126
+ description: "Action label for escalation"
127
+ },
128
+ "resolver.action.respond": {
129
+ value: "Send automated response",
130
+ description: "Action label for automated response"
131
+ },
132
+ "resolver.escalation.insufficientConfidence": {
133
+ value: "Insufficient confidence or missing knowledge references",
134
+ description: "Escalation reason when confidence is too low"
135
+ },
136
+ "tool.classify.title": {
137
+ value: "support_classify_ticket",
138
+ description: "MCP tool title for ticket classification"
139
+ },
140
+ "tool.classify.description": {
141
+ value: "Classify a ticket for priority, sentiment, and category",
142
+ description: "MCP tool description for ticket classification"
143
+ },
144
+ "tool.resolve.title": {
145
+ value: "support_resolve_ticket",
146
+ description: "MCP tool title for ticket resolution"
147
+ },
148
+ "tool.resolve.description": {
149
+ value: "Generate a knowledge-grounded resolution for a ticket",
150
+ description: "MCP tool description for ticket resolution"
151
+ },
152
+ "tool.draft.title": {
153
+ value: "support_draft_response",
154
+ description: "MCP tool title for response drafting"
155
+ },
156
+ "tool.draft.description": {
157
+ value: "Draft a user-facing reply based on resolution + classification",
158
+ description: "MCP tool description for response drafting"
159
+ },
160
+ "error.inputMustIncludeTicket": {
161
+ value: "Input must include ticket",
162
+ description: "Error when input payload is missing the ticket field"
163
+ },
164
+ "error.ticketMissingId": {
165
+ value: "Ticket is missing id",
166
+ description: "Error when ticket object lacks an id"
167
+ },
168
+ "error.resolutionClassificationRequired": {
169
+ value: "resolution and classification are required",
170
+ description: "Error when draft endpoint is called without resolution and classification"
171
+ },
172
+ "feedback.noRecords": {
173
+ value: "No feedback recorded yet.",
174
+ description: "Placeholder when no feedback entries exist"
175
+ },
176
+ "feedback.status.escalated": {
177
+ value: "Escalated",
178
+ description: "Status label for escalated tickets"
179
+ },
180
+ "feedback.status.autoResolved": {
181
+ value: "Auto-resolved",
182
+ description: "Status label for auto-resolved tickets"
183
+ },
184
+ "spec.instructionsAppendix": {
185
+ value: "Always cite support knowledge sources and flag compliance/billing issues for human review when unsure.",
186
+ description: "Instructions appendix appended to agent spec"
187
+ }
188
+ }
189
+ });
190
+
191
+ // src/i18n/catalogs/fr.ts
192
+ import { defineTranslation as defineTranslation2 } from "@contractspec/lib.contracts-spec/translations";
193
+ var frMessages = defineTranslation2({
194
+ meta: {
195
+ key: "support-bot.messages",
196
+ version: "1.0.0",
197
+ domain: "support-bot",
198
+ description: "French translations for the support-bot package",
199
+ owners: ["platform"],
200
+ stability: "experimental"
201
+ },
202
+ locale: "fr",
203
+ fallback: "en",
204
+ messages: {
205
+ "prompt.classifier.system": {
206
+ value: "Classifiez le ticket de support.",
207
+ description: "Classifier LLM system prompt"
208
+ },
209
+ "prompt.autoResponder.system": {
210
+ value: "R\xE9digez des r\xE9ponses de support empathiques et pr\xE9cises en citant les sources lorsque c\u2019est pertinent.",
211
+ description: "Auto-responder LLM system prompt"
212
+ },
213
+ "prompt.autoResponder.user": {
214
+ value: `Ticket #{ticketId} ({category}, {priority}, {sentiment})
215
+ Sujet\xA0: {subject}
216
+
217
+ {body}
218
+
219
+ Connaissances\xA0:
220
+ {knowledge}
221
+
222
+ R\xE9pondez au client.`,
223
+ description: "Auto-responder user prompt template",
224
+ placeholders: [
225
+ { name: "ticketId", type: "string" },
226
+ { name: "category", type: "string" },
227
+ { name: "priority", type: "string" },
228
+ { name: "sentiment", type: "string" },
229
+ { name: "subject", type: "string" },
230
+ { name: "body", type: "string" },
231
+ { name: "knowledge", type: "string" }
232
+ ]
233
+ },
234
+ "responder.closing.friendly": {
235
+ value: "Nous restons \xE0 votre disposition si vous avez besoin de quoi que ce soit.",
236
+ description: "Friendly closing line for support responses"
237
+ },
238
+ "responder.closing.formal": {
239
+ value: "Veuillez nous contacter si vous avez besoin d\u2019une assistance suppl\xE9mentaire.",
240
+ description: "Formal closing line for support responses"
241
+ },
242
+ "responder.greeting.named": {
243
+ value: "Bonjour {name},",
244
+ description: "Greeting with customer name",
245
+ placeholders: [{ name: "name", type: "string" }]
246
+ },
247
+ "responder.greeting.anonymous": {
248
+ value: "Bonjour,",
249
+ description: "Greeting without customer name"
250
+ },
251
+ "responder.intro.thanks": {
252
+ value: "Merci de nous avoir contact\xE9s au sujet de \xAB\xA0{subject}\xA0\xBB.",
253
+ description: "Thank-you intro referencing the ticket subject",
254
+ placeholders: [{ name: "subject", type: "string" }]
255
+ },
256
+ "responder.signature": {
257
+ value: "\u2014 Support ContractSpec",
258
+ description: "Email / response signature"
259
+ },
260
+ "responder.references.header": {
261
+ value: "R\xE9f\xE9rences\xA0:",
262
+ description: "Header for the references section"
263
+ },
264
+ "responder.references.sourceLabel": {
265
+ value: "Source {index}",
266
+ description: "Label for a numbered source reference",
267
+ placeholders: [{ name: "index", type: "number" }]
268
+ },
269
+ "responder.category.billing": {
270
+ value: "Je comprends que les probl\xE8mes de facturation peuvent \xEAtre stressants, laissez-moi clarifier la situation.",
271
+ description: "Category intro for billing tickets"
272
+ },
273
+ "responder.category.technical": {
274
+ value: "Je vois que vous avez rencontr\xE9 un probl\xE8me technique. Voici ce qui s\u2019est pass\xE9 et comment le r\xE9soudre.",
275
+ description: "Category intro for technical tickets"
276
+ },
277
+ "responder.category.product": {
278
+ value: "Merci d\u2019avoir partag\xE9 vos retours sur le produit. Voici les prochaines \xE9tapes.",
279
+ description: "Category intro for product tickets"
280
+ },
281
+ "responder.category.account": {
282
+ value: "L\u2019acc\xE8s au compte est essentiel, laissez-moi vous guider vers la r\xE9solution.",
283
+ description: "Category intro for account tickets"
284
+ },
285
+ "responder.category.compliance": {
286
+ value: "Les questions de conformit\xE9 exigent de la pr\xE9cision. Consultez la r\xE9ponse conforme aux politiques ci-dessous.",
287
+ description: "Category intro for compliance tickets"
288
+ },
289
+ "responder.category.other": {
290
+ value: "Voici ce que nous avons trouv\xE9 apr\xE8s examen de votre demande.",
291
+ description: "Category intro for uncategorized tickets"
292
+ },
293
+ "responder.subject.replyPrefix": {
294
+ value: "Re\xA0: {subject}",
295
+ description: "Reply subject line prefix",
296
+ placeholders: [{ name: "subject", type: "string" }]
297
+ },
298
+ "resolver.question.subjectLabel": {
299
+ value: "Sujet\xA0: {subject}",
300
+ description: "Subject label in resolver question context",
301
+ placeholders: [{ name: "subject", type: "string" }]
302
+ },
303
+ "resolver.question.channelLabel": {
304
+ value: "Canal\xA0: {channel}",
305
+ description: "Channel label in resolver question context",
306
+ placeholders: [{ name: "channel", type: "string" }]
307
+ },
308
+ "resolver.question.customerLabel": {
309
+ value: "Client\xA0: {name}",
310
+ description: "Customer label in resolver question context",
311
+ placeholders: [{ name: "name", type: "string" }]
312
+ },
313
+ "resolver.action.escalate": {
314
+ value: "Escalader pour r\xE9vision humaine",
315
+ description: "Action label for escalation"
316
+ },
317
+ "resolver.action.respond": {
318
+ value: "Envoyer une r\xE9ponse automatique",
319
+ description: "Action label for automated response"
320
+ },
321
+ "resolver.escalation.insufficientConfidence": {
322
+ value: "Confiance insuffisante ou r\xE9f\xE9rences de connaissances manquantes",
323
+ description: "Escalation reason when confidence is too low"
324
+ },
325
+ "tool.classify.title": {
326
+ value: "support_classify_ticket",
327
+ description: "MCP tool title for ticket classification"
328
+ },
329
+ "tool.classify.description": {
330
+ value: "Classifier un ticket par priorit\xE9, sentiment et cat\xE9gorie",
331
+ description: "MCP tool description for ticket classification"
332
+ },
333
+ "tool.resolve.title": {
334
+ value: "support_resolve_ticket",
335
+ description: "MCP tool title for ticket resolution"
336
+ },
337
+ "tool.resolve.description": {
338
+ value: "G\xE9n\xE9rer une r\xE9solution fond\xE9e sur la base de connaissances pour un ticket",
339
+ description: "MCP tool description for ticket resolution"
340
+ },
341
+ "tool.draft.title": {
342
+ value: "support_draft_response",
343
+ description: "MCP tool title for response drafting"
344
+ },
345
+ "tool.draft.description": {
346
+ value: "R\xE9diger une r\xE9ponse client bas\xE9e sur la r\xE9solution et la classification",
347
+ description: "MCP tool description for response drafting"
348
+ },
349
+ "error.inputMustIncludeTicket": {
350
+ value: "L\u2019entr\xE9e doit inclure un ticket",
351
+ description: "Error when input payload is missing the ticket field"
352
+ },
353
+ "error.ticketMissingId": {
354
+ value: "Le ticket n\u2019a pas d\u2019identifiant",
355
+ description: "Error when ticket object lacks an id"
356
+ },
357
+ "error.resolutionClassificationRequired": {
358
+ value: "la r\xE9solution et la classification sont requises",
359
+ description: "Error when draft endpoint is called without resolution and classification"
360
+ },
361
+ "feedback.noRecords": {
362
+ value: "Aucun retour enregistr\xE9 pour le moment.",
363
+ description: "Placeholder when no feedback entries exist"
364
+ },
365
+ "feedback.status.escalated": {
366
+ value: "Escalad\xE9",
367
+ description: "Status label for escalated tickets"
368
+ },
369
+ "feedback.status.autoResolved": {
370
+ value: "R\xE9solu automatiquement",
371
+ description: "Status label for auto-resolved tickets"
372
+ },
373
+ "spec.instructionsAppendix": {
374
+ value: "Citez toujours les sources de connaissances du support et signalez les probl\xE8mes de conformit\xE9/facturation pour r\xE9vision humaine en cas de doute.",
375
+ description: "Instructions appendix appended to agent spec"
376
+ }
377
+ }
378
+ });
379
+
380
+ // src/i18n/catalogs/es.ts
381
+ import { defineTranslation as defineTranslation3 } from "@contractspec/lib.contracts-spec/translations";
382
+ var esMessages = defineTranslation3({
383
+ meta: {
384
+ key: "support-bot.messages",
385
+ version: "1.0.0",
386
+ domain: "support-bot",
387
+ description: "Spanish translations for the support-bot package",
388
+ owners: ["platform"],
389
+ stability: "experimental"
390
+ },
391
+ locale: "es",
392
+ fallback: "en",
393
+ messages: {
394
+ "prompt.classifier.system": {
395
+ value: "Clasifica el ticket de soporte.",
396
+ description: "Classifier LLM system prompt"
397
+ },
398
+ "prompt.autoResponder.system": {
399
+ value: "Redacta respuestas de soporte emp\xE1ticas y precisas citando fuentes cuando sea relevante.",
400
+ description: "Auto-responder LLM system prompt"
401
+ },
402
+ "prompt.autoResponder.user": {
403
+ value: `Ticket #{ticketId} ({category}, {priority}, {sentiment})
404
+ Asunto: {subject}
405
+
406
+ {body}
407
+
408
+ Conocimientos:
409
+ {knowledge}
410
+
411
+ Responde al cliente.`,
412
+ description: "Auto-responder user prompt template",
413
+ placeholders: [
414
+ { name: "ticketId", type: "string" },
415
+ { name: "category", type: "string" },
416
+ { name: "priority", type: "string" },
417
+ { name: "sentiment", type: "string" },
418
+ { name: "subject", type: "string" },
419
+ { name: "body", type: "string" },
420
+ { name: "knowledge", type: "string" }
421
+ ]
422
+ },
423
+ "responder.closing.friendly": {
424
+ value: "Quedamos a su disposici\xF3n si necesita cualquier otra cosa.",
425
+ description: "Friendly closing line for support responses"
426
+ },
427
+ "responder.closing.formal": {
428
+ value: "No dude en contactarnos si requiere asistencia adicional.",
429
+ description: "Formal closing line for support responses"
430
+ },
431
+ "responder.greeting.named": {
432
+ value: "Hola {name},",
433
+ description: "Greeting with customer name",
434
+ placeholders: [{ name: "name", type: "string" }]
435
+ },
436
+ "responder.greeting.anonymous": {
437
+ value: "Hola,",
438
+ description: "Greeting without customer name"
439
+ },
440
+ "responder.intro.thanks": {
441
+ value: "Gracias por contactarnos sobre \xAB{subject}\xBB.",
442
+ description: "Thank-you intro referencing the ticket subject",
443
+ placeholders: [{ name: "subject", type: "string" }]
444
+ },
445
+ "responder.signature": {
446
+ value: "\u2014 Soporte ContractSpec",
447
+ description: "Email / response signature"
448
+ },
449
+ "responder.references.header": {
450
+ value: "Referencias:",
451
+ description: "Header for the references section"
452
+ },
453
+ "responder.references.sourceLabel": {
454
+ value: "Fuente {index}",
455
+ description: "Label for a numbered source reference",
456
+ placeholders: [{ name: "index", type: "number" }]
457
+ },
458
+ "responder.category.billing": {
459
+ value: "Entiendo que los problemas de facturaci\xF3n pueden ser estresantes, perm\xEDtame aclarar la situaci\xF3n.",
460
+ description: "Category intro for billing tickets"
461
+ },
462
+ "responder.category.technical": {
463
+ value: "Veo que encontr\xF3 un problema t\xE9cnico. Esto es lo que sucedi\xF3 y c\xF3mo solucionarlo.",
464
+ description: "Category intro for technical tickets"
465
+ },
466
+ "responder.category.product": {
467
+ value: "Gracias por compartir sus comentarios sobre el producto. Estos son los pr\xF3ximos pasos.",
468
+ description: "Category intro for product tickets"
469
+ },
470
+ "responder.category.account": {
471
+ value: "El acceso a la cuenta es fundamental, perm\xEDtame guiarle hacia la resoluci\xF3n.",
472
+ description: "Category intro for account tickets"
473
+ },
474
+ "responder.category.compliance": {
475
+ value: "Las preguntas de cumplimiento requieren precisi\xF3n. Consulte la respuesta alineada con las pol\xEDticas a continuaci\xF3n.",
476
+ description: "Category intro for compliance tickets"
477
+ },
478
+ "responder.category.other": {
479
+ value: "Esto es lo que encontramos tras revisar su solicitud.",
480
+ description: "Category intro for uncategorized tickets"
481
+ },
482
+ "responder.subject.replyPrefix": {
483
+ value: "Re: {subject}",
484
+ description: "Reply subject line prefix",
485
+ placeholders: [{ name: "subject", type: "string" }]
486
+ },
487
+ "resolver.question.subjectLabel": {
488
+ value: "Asunto: {subject}",
489
+ description: "Subject label in resolver question context",
490
+ placeholders: [{ name: "subject", type: "string" }]
491
+ },
492
+ "resolver.question.channelLabel": {
493
+ value: "Canal: {channel}",
494
+ description: "Channel label in resolver question context",
495
+ placeholders: [{ name: "channel", type: "string" }]
496
+ },
497
+ "resolver.question.customerLabel": {
498
+ value: "Cliente: {name}",
499
+ description: "Customer label in resolver question context",
500
+ placeholders: [{ name: "name", type: "string" }]
501
+ },
502
+ "resolver.action.escalate": {
503
+ value: "Escalar para revisi\xF3n humana",
504
+ description: "Action label for escalation"
505
+ },
506
+ "resolver.action.respond": {
507
+ value: "Enviar respuesta autom\xE1tica",
508
+ description: "Action label for automated response"
509
+ },
510
+ "resolver.escalation.insufficientConfidence": {
511
+ value: "Confianza insuficiente o referencias de conocimiento faltantes",
512
+ description: "Escalation reason when confidence is too low"
513
+ },
514
+ "tool.classify.title": {
515
+ value: "support_classify_ticket",
516
+ description: "MCP tool title for ticket classification"
517
+ },
518
+ "tool.classify.description": {
519
+ value: "Clasificar un ticket por prioridad, sentimiento y categor\xEDa",
520
+ description: "MCP tool description for ticket classification"
521
+ },
522
+ "tool.resolve.title": {
523
+ value: "support_resolve_ticket",
524
+ description: "MCP tool title for ticket resolution"
525
+ },
526
+ "tool.resolve.description": {
527
+ value: "Generar una resoluci\xF3n basada en conocimientos para un ticket",
528
+ description: "MCP tool description for ticket resolution"
529
+ },
530
+ "tool.draft.title": {
531
+ value: "support_draft_response",
532
+ description: "MCP tool title for response drafting"
533
+ },
534
+ "tool.draft.description": {
535
+ value: "Redactar una respuesta al cliente basada en la resoluci\xF3n y la clasificaci\xF3n",
536
+ description: "MCP tool description for response drafting"
537
+ },
538
+ "error.inputMustIncludeTicket": {
539
+ value: "La entrada debe incluir un ticket",
540
+ description: "Error when input payload is missing the ticket field"
541
+ },
542
+ "error.ticketMissingId": {
543
+ value: "El ticket no tiene identificador",
544
+ description: "Error when ticket object lacks an id"
545
+ },
546
+ "error.resolutionClassificationRequired": {
547
+ value: "la resoluci\xF3n y la clasificaci\xF3n son obligatorias",
548
+ description: "Error when draft endpoint is called without resolution and classification"
549
+ },
550
+ "feedback.noRecords": {
551
+ value: "No hay comentarios registrados a\xFAn.",
552
+ description: "Placeholder when no feedback entries exist"
553
+ },
554
+ "feedback.status.escalated": {
555
+ value: "Escalado",
556
+ description: "Status label for escalated tickets"
557
+ },
558
+ "feedback.status.autoResolved": {
559
+ value: "Resuelto autom\xE1ticamente",
560
+ description: "Status label for auto-resolved tickets"
561
+ },
562
+ "spec.instructionsAppendix": {
563
+ value: "Cite siempre las fuentes de conocimiento de soporte y se\xF1ale los problemas de cumplimiento/facturaci\xF3n para revisi\xF3n humana en caso de duda.",
564
+ description: "Instructions appendix appended to agent spec"
565
+ }
566
+ }
567
+ });
568
+
569
+ // src/i18n/messages.ts
570
+ import {
571
+ createI18nFactory
572
+ } from "@contractspec/lib.contracts-spec/translations";
573
+ import { interpolate } from "@contractspec/lib.contracts-spec/translations";
574
+ var factory = createI18nFactory({
575
+ specKey: "support-bot.messages",
576
+ catalogs: [enMessages, frMessages, esMessages]
577
+ });
578
+ var createSupportBotI18n = factory.create;
579
+ var getDefaultI18n = factory.getDefault;
580
+ var resetI18nRegistry = factory.resetRegistry;
581
+
582
+ // src/i18n/locale.ts
583
+ import {
584
+ DEFAULT_LOCALE,
585
+ SUPPORTED_LOCALES,
586
+ resolveLocale,
587
+ isSupportedLocale
588
+ } from "@contractspec/lib.contracts-spec/translations";
589
+
590
+ // src/i18n/keys.ts
591
+ var PROMPT_KEYS = {
592
+ "prompt.classifier.system": "prompt.classifier.system",
593
+ "prompt.autoResponder.system": "prompt.autoResponder.system",
594
+ "prompt.autoResponder.user": "prompt.autoResponder.user"
595
+ };
596
+ var RESPONDER_KEYS = {
597
+ "responder.closing.friendly": "responder.closing.friendly",
598
+ "responder.closing.formal": "responder.closing.formal",
599
+ "responder.greeting.named": "responder.greeting.named",
600
+ "responder.greeting.anonymous": "responder.greeting.anonymous",
601
+ "responder.intro.thanks": "responder.intro.thanks",
602
+ "responder.signature": "responder.signature",
603
+ "responder.references.header": "responder.references.header",
604
+ "responder.references.sourceLabel": "responder.references.sourceLabel",
605
+ "responder.category.billing": "responder.category.billing",
606
+ "responder.category.technical": "responder.category.technical",
607
+ "responder.category.product": "responder.category.product",
608
+ "responder.category.account": "responder.category.account",
609
+ "responder.category.compliance": "responder.category.compliance",
610
+ "responder.category.other": "responder.category.other",
611
+ "responder.subject.replyPrefix": "responder.subject.replyPrefix"
612
+ };
613
+ var RESOLVER_KEYS = {
614
+ "resolver.question.subjectLabel": "resolver.question.subjectLabel",
615
+ "resolver.question.channelLabel": "resolver.question.channelLabel",
616
+ "resolver.question.customerLabel": "resolver.question.customerLabel",
617
+ "resolver.action.escalate": "resolver.action.escalate",
618
+ "resolver.action.respond": "resolver.action.respond",
619
+ "resolver.escalation.insufficientConfidence": "resolver.escalation.insufficientConfidence"
620
+ };
621
+ var TOOL_KEYS = {
622
+ "tool.classify.title": "tool.classify.title",
623
+ "tool.classify.description": "tool.classify.description",
624
+ "tool.resolve.title": "tool.resolve.title",
625
+ "tool.resolve.description": "tool.resolve.description",
626
+ "tool.draft.title": "tool.draft.title",
627
+ "tool.draft.description": "tool.draft.description"
628
+ };
629
+ var ERROR_KEYS = {
630
+ "error.inputMustIncludeTicket": "error.inputMustIncludeTicket",
631
+ "error.ticketMissingId": "error.ticketMissingId",
632
+ "error.resolutionClassificationRequired": "error.resolutionClassificationRequired"
633
+ };
634
+ var FEEDBACK_KEYS = {
635
+ "feedback.noRecords": "feedback.noRecords",
636
+ "feedback.status.escalated": "feedback.status.escalated",
637
+ "feedback.status.autoResolved": "feedback.status.autoResolved"
638
+ };
639
+ var SPEC_KEYS = {
640
+ "spec.instructionsAppendix": "spec.instructionsAppendix"
641
+ };
642
+ var I18N_KEYS = {
643
+ ...PROMPT_KEYS,
644
+ ...RESPONDER_KEYS,
645
+ ...RESOLVER_KEYS,
646
+ ...TOOL_KEYS,
647
+ ...ERROR_KEYS,
648
+ ...FEEDBACK_KEYS,
649
+ ...SPEC_KEYS
650
+ };
2
651
  // src/bot/auto-responder.ts
3
652
  class AutoResponder {
4
653
  llm;
5
654
  model;
6
655
  tone;
7
656
  closing;
657
+ i18n;
8
658
  constructor(options) {
9
659
  this.llm = options?.llm;
10
660
  this.model = options?.model;
11
661
  this.tone = options?.tone ?? "friendly";
12
- this.closing = options?.closing ?? (this.tone === "friendly" ? "We remain available if you need anything else." : "Please let us know if you require additional assistance.");
662
+ this.i18n = createSupportBotI18n(options?.locale);
663
+ const { t } = this.i18n;
664
+ this.closing = options?.closing ?? (this.tone === "friendly" ? t("responder.closing.friendly") : t("responder.closing.formal"));
13
665
  }
14
666
  async draft(ticket, resolution, classification) {
15
667
  if (this.llm) {
@@ -18,21 +670,23 @@ class AutoResponder {
18
670
  return this.generateTemplate(ticket, resolution, classification);
19
671
  }
20
672
  async generateWithLLM(ticket, resolution, classification) {
21
- const prompt = `You are a ${this.tone} support agent. Draft an email response.
22
- Ticket Subject: ${ticket.subject}
23
- Ticket Body: ${ticket.body}
24
- Detected Category: ${classification.category}
25
- Detected Priority: ${classification.priority}
26
- Resolution:
27
- ${resolution.answer}
28
- Citations: ${resolution.citations.map((c) => c.label).join(", ")}`;
673
+ const { t } = this.i18n;
674
+ const prompt = t("prompt.autoResponder.user", {
675
+ tone: this.tone,
676
+ subject: ticket.subject,
677
+ body: ticket.body,
678
+ category: classification.category,
679
+ priority: classification.priority,
680
+ answer: resolution.answer,
681
+ citations: resolution.citations.map((c) => c.label).join(", ")
682
+ });
29
683
  const response = await this.llm.chat([
30
684
  {
31
685
  role: "system",
32
686
  content: [
33
687
  {
34
688
  type: "text",
35
- text: "Write empathetic, accurate support replies that cite sources when relevant."
689
+ text: t("prompt.autoResponder.system")
36
690
  }
37
691
  ]
38
692
  },
@@ -45,17 +699,18 @@ Citations: ${resolution.citations.map((c) => c.label).join(", ")}`;
45
699
  return this.buildDraft(ticket, resolution, classification, body);
46
700
  }
47
701
  generateTemplate(ticket, resolution, classification) {
48
- const greeting = ticket.customerName ? `Hi ${ticket.customerName},` : "Hi there,";
702
+ const { t } = this.i18n;
703
+ const greeting = ticket.customerName ? t("responder.greeting.named", { name: ticket.customerName }) : t("responder.greeting.anonymous");
49
704
  const body = `${greeting}
50
705
 
51
- Thanks for contacting us about "${ticket.subject}". ${this.renderCategoryIntro(classification)}
706
+ ${t("responder.intro.thanks", { subject: ticket.subject })} ${this.renderCategoryIntro(classification)}
52
707
 
53
708
  ${resolution.answer}
54
709
 
55
710
  ${this.renderCitations(resolution)}
56
711
  ${this.closing}
57
712
 
58
- \u2014 ContractSpec Support`;
713
+ ${t("responder.signature")}`;
59
714
  return this.buildDraft(ticket, resolution, classification, body);
60
715
  }
61
716
  buildDraft(ticket, resolution, classification, body) {
@@ -69,30 +724,32 @@ ${this.closing}
69
724
  };
70
725
  }
71
726
  renderCategoryIntro(classification) {
727
+ const { t } = this.i18n;
72
728
  switch (classification.category) {
73
729
  case "billing":
74
- return "I understand billing issues can be stressful, so let me clarify the situation.";
730
+ return t("responder.category.billing");
75
731
  case "technical":
76
- return "I see you encountered a technical issue. Here is what happened and how to fix it.";
732
+ return t("responder.category.technical");
77
733
  case "product":
78
- return "Thanks for sharing feedback about the product. Here are the next steps.";
734
+ return t("responder.category.product");
79
735
  case "account":
80
- return "Account access is critical, so let me walk you through the resolution.";
736
+ return t("responder.category.account");
81
737
  case "compliance":
82
- return "Compliance questions require precision. See the policy-aligned answer below.";
738
+ return t("responder.category.compliance");
83
739
  default:
84
- return "Here is what we found after reviewing your request.";
740
+ return t("responder.category.other");
85
741
  }
86
742
  }
87
743
  renderCitations(resolution) {
744
+ const { t } = this.i18n;
88
745
  if (!resolution.citations.length)
89
746
  return "";
90
747
  const lines = resolution.citations.map((citation, index) => {
91
- const label = citation.label || `Source ${index + 1}`;
748
+ const label = citation.label || t("responder.references.sourceLabel", { index: index + 1 });
92
749
  const link = citation.url ? ` (${citation.url})` : "";
93
750
  return `- ${label}${link}`;
94
751
  });
95
- return `References:
752
+ return `${t("responder.references.header")}
96
753
  ${lines.join(`
97
754
  `)}`;
98
755
  }
@@ -123,11 +780,12 @@ class SupportFeedbackLoop {
123
780
  };
124
781
  }
125
782
  feedbackSummary(limit = 5) {
783
+ const { t } = getDefaultI18n();
126
784
  const recent = this.history.slice(-limit);
127
785
  if (!recent.length)
128
- return "No feedback recorded yet.";
786
+ return t("feedback.noRecords");
129
787
  return recent.map((entry) => {
130
- const status = entry.resolution.actions.some((action) => action.type === "escalate") ? "Escalated" : "Auto-resolved";
788
+ const status = entry.resolution.actions.some((action) => action.type === "escalate") ? t("feedback.status.escalated") : t("feedback.status.autoResolved");
131
789
  return `${entry.ticket.subject} \u2013 ${status} (confidence: ${entry.resolution.confidence})`;
132
790
  }).join(`
133
791
  `);
@@ -298,10 +956,12 @@ class TicketResolver {
298
956
  knowledge;
299
957
  minConfidence;
300
958
  prependPrompt;
959
+ i18n;
301
960
  constructor(options) {
302
961
  this.knowledge = options.knowledge;
303
962
  this.minConfidence = options.minConfidence ?? 0.65;
304
963
  this.prependPrompt = options.prependPrompt;
964
+ this.i18n = createSupportBotI18n(options.locale);
305
965
  }
306
966
  async resolve(ticket) {
307
967
  const question = this.buildQuestion(ticket);
@@ -309,9 +969,14 @@ class TicketResolver {
309
969
  return this.toResolution(ticket, answer);
310
970
  }
311
971
  buildQuestion(ticket) {
312
- const header = [`Subject: ${ticket.subject}`, `Channel: ${ticket.channel}`];
313
- if (ticket.customerName)
314
- header.push(`Customer: ${ticket.customerName}`);
972
+ const { t } = this.i18n;
973
+ const header = [
974
+ t("resolver.question.subjectLabel", { subject: ticket.subject }),
975
+ t("resolver.question.channelLabel", { channel: ticket.channel })
976
+ ];
977
+ if (ticket.customerName) {
978
+ header.push(t("resolver.question.customerLabel", { name: ticket.customerName }));
979
+ }
315
980
  const sections = [
316
981
  this.prependPrompt,
317
982
  header.join(`
@@ -332,6 +997,7 @@ class TicketResolver {
332
997
  score: ref.score
333
998
  };
334
999
  });
1000
+ const { t } = this.i18n;
335
1001
  const confidence = this.deriveConfidence(answer);
336
1002
  const escalate = confidence < this.minConfidence || citations.length === 0;
337
1003
  return {
@@ -340,9 +1006,9 @@ class TicketResolver {
340
1006
  confidence,
341
1007
  citations,
342
1008
  actions: [
343
- escalate ? { type: "escalate", label: "Escalate for human review" } : { type: "respond", label: "Send automated response" }
1009
+ escalate ? { type: "escalate", label: t("resolver.action.escalate") } : { type: "respond", label: t("resolver.action.respond") }
344
1010
  ],
345
- escalationReason: escalate ? "Insufficient confidence or missing knowledge references" : undefined,
1011
+ escalationReason: escalate ? t("resolver.escalation.insufficientConfidence") : undefined,
346
1012
  knowledgeUpdates: escalate ? [ticket.body.slice(0, 200)] : undefined
347
1013
  };
348
1014
  }
@@ -357,35 +1023,193 @@ class TicketResolver {
357
1023
  }
358
1024
  // src/tickets/classifier.ts
359
1025
  var CATEGORY_KEYWORDS = {
360
- billing: ["invoice", "payout", "refund", "charge", "billing", "payment"],
361
- technical: ["bug", "error", "crash", "issue", "failed", "timeout"],
362
- product: ["feature", "roadmap", "idea", "request", "feedback"],
363
- account: ["login", "password", "2fa", "account", "profile", "email change"],
364
- compliance: ["kyc", "aml", "compliance", "regulation", "gdpr"],
365
- other: []
1026
+ en: {
1027
+ billing: ["invoice", "payout", "refund", "charge", "billing", "payment"],
1028
+ technical: ["bug", "error", "crash", "issue", "failed", "timeout"],
1029
+ product: ["feature", "roadmap", "idea", "request", "feedback"],
1030
+ account: ["login", "password", "2fa", "account", "profile", "email change"],
1031
+ compliance: ["kyc", "aml", "compliance", "regulation", "gdpr"],
1032
+ other: []
1033
+ },
1034
+ fr: {
1035
+ billing: [
1036
+ "facture",
1037
+ "versement",
1038
+ "remboursement",
1039
+ "frais",
1040
+ "facturation",
1041
+ "paiement"
1042
+ ],
1043
+ technical: [
1044
+ "bogue",
1045
+ "erreur",
1046
+ "plantage",
1047
+ "probl\xE8me",
1048
+ "\xE9chou\xE9",
1049
+ "d\xE9lai d\xE9pass\xE9"
1050
+ ],
1051
+ product: [
1052
+ "fonctionnalit\xE9",
1053
+ "feuille de route",
1054
+ "id\xE9e",
1055
+ "demande",
1056
+ "retour"
1057
+ ],
1058
+ account: [
1059
+ "connexion",
1060
+ "mot de passe",
1061
+ "2fa",
1062
+ "compte",
1063
+ "profil",
1064
+ "changement email"
1065
+ ],
1066
+ compliance: ["kyc", "aml", "conformit\xE9", "r\xE9glementation", "rgpd"],
1067
+ other: []
1068
+ },
1069
+ es: {
1070
+ billing: [
1071
+ "factura",
1072
+ "desembolso",
1073
+ "reembolso",
1074
+ "cargo",
1075
+ "facturaci\xF3n",
1076
+ "pago"
1077
+ ],
1078
+ technical: [
1079
+ "error",
1080
+ "fallo",
1081
+ "ca\xEDda",
1082
+ "problema",
1083
+ "fallido",
1084
+ "tiempo agotado"
1085
+ ],
1086
+ product: [
1087
+ "funcionalidad",
1088
+ "hoja de ruta",
1089
+ "idea",
1090
+ "solicitud",
1091
+ "comentario"
1092
+ ],
1093
+ account: [
1094
+ "inicio de sesi\xF3n",
1095
+ "contrase\xF1a",
1096
+ "2fa",
1097
+ "cuenta",
1098
+ "perfil",
1099
+ "cambio de email"
1100
+ ],
1101
+ compliance: ["kyc", "aml", "cumplimiento", "regulaci\xF3n", "rgpd"],
1102
+ other: []
1103
+ }
366
1104
  };
367
1105
  var PRIORITY_HINTS = {
368
- urgent: ["urgent", "asap", "immediately", "today", "right away"],
369
- high: ["high priority", "blocking", "major", "critical"],
370
- medium: ["soon", "next few days"],
371
- low: ["nice to have", "when possible", "later"]
1106
+ en: {
1107
+ urgent: ["urgent", "asap", "immediately", "today", "right away"],
1108
+ high: ["high priority", "blocking", "major", "critical"],
1109
+ medium: ["soon", "next few days"],
1110
+ low: ["nice to have", "when possible", "later"]
1111
+ },
1112
+ fr: {
1113
+ urgent: [
1114
+ "urgent",
1115
+ "d\xE8s que possible",
1116
+ "imm\xE9diatement",
1117
+ "aujourd'hui",
1118
+ "tout de suite"
1119
+ ],
1120
+ high: ["haute priorit\xE9", "bloquant", "majeur", "critique"],
1121
+ medium: ["bient\xF4t", "prochains jours"],
1122
+ low: ["ce serait bien", "quand possible", "plus tard"]
1123
+ },
1124
+ es: {
1125
+ urgent: [
1126
+ "urgente",
1127
+ "lo antes posible",
1128
+ "inmediatamente",
1129
+ "hoy",
1130
+ "ahora mismo"
1131
+ ],
1132
+ high: ["alta prioridad", "bloqueante", "mayor", "cr\xEDtico"],
1133
+ medium: ["pronto", "pr\xF3ximos d\xEDas"],
1134
+ low: ["ser\xEDa bueno", "cuando sea posible", "m\xE1s tarde"]
1135
+ }
372
1136
  };
373
1137
  var SENTIMENT_HINTS = {
374
- positive: ["love", "great", "awesome", "thank you"],
375
- neutral: ["question", "wonder", "curious"],
376
- negative: ["unhappy", "bad", "terrible", "awful", "angry"],
377
- frustrated: ["furious", "frustrated", "fed up", "ridiculous"]
1138
+ en: {
1139
+ positive: ["love", "great", "awesome", "thank you"],
1140
+ neutral: ["question", "wonder", "curious"],
1141
+ negative: ["unhappy", "bad", "terrible", "awful", "angry"],
1142
+ frustrated: ["furious", "frustrated", "fed up", "ridiculous"]
1143
+ },
1144
+ fr: {
1145
+ positive: ["adore", "super", "formidable", "merci"],
1146
+ neutral: ["question", "demande", "curieux"],
1147
+ negative: ["m\xE9content", "mauvais", "terrible", "affreux", "en col\xE8re"],
1148
+ frustrated: ["furieux", "frustr\xE9", "ras le bol", "ridicule"]
1149
+ },
1150
+ es: {
1151
+ positive: ["encanta", "genial", "incre\xEDble", "gracias"],
1152
+ neutral: ["pregunta", "duda", "curioso"],
1153
+ negative: ["descontento", "malo", "terrible", "horrible", "enojado"],
1154
+ frustrated: ["furioso", "frustrado", "harto", "rid\xEDculo"]
1155
+ }
1156
+ };
1157
+ var INTENT_KEYWORDS = {
1158
+ en: [
1159
+ { keyword: "refund", intent: "refund" },
1160
+ { keyword: "chargeback", intent: "refund" },
1161
+ { keyword: "payout", intent: "payout" },
1162
+ { keyword: "login", intent: "login-help" },
1163
+ { keyword: "feature", intent: "feature-request" },
1164
+ { keyword: "bug", intent: "bug-report" },
1165
+ { keyword: "error", intent: "bug-report" }
1166
+ ],
1167
+ fr: [
1168
+ { keyword: "remboursement", intent: "refund" },
1169
+ { keyword: "r\xE9trofacturation", intent: "refund" },
1170
+ { keyword: "versement", intent: "payout" },
1171
+ { keyword: "connexion", intent: "login-help" },
1172
+ { keyword: "fonctionnalit\xE9", intent: "feature-request" },
1173
+ { keyword: "bogue", intent: "bug-report" },
1174
+ { keyword: "erreur", intent: "bug-report" }
1175
+ ],
1176
+ es: [
1177
+ { keyword: "reembolso", intent: "refund" },
1178
+ { keyword: "contracargo", intent: "refund" },
1179
+ { keyword: "desembolso", intent: "payout" },
1180
+ { keyword: "inicio de sesi\xF3n", intent: "login-help" },
1181
+ { keyword: "funcionalidad", intent: "feature-request" },
1182
+ { keyword: "error", intent: "bug-report" },
1183
+ { keyword: "fallo", intent: "bug-report" }
1184
+ ]
378
1185
  };
1186
+ function resolveBaseLocale(locale) {
1187
+ if (!locale)
1188
+ return "en";
1189
+ const base = locale.split("-")[0]?.toLowerCase() ?? "en";
1190
+ if (base === "en" || base === "fr" || base === "es") {
1191
+ return base;
1192
+ }
1193
+ return "en";
1194
+ }
379
1195
 
380
1196
  class TicketClassifier {
381
1197
  keywords;
1198
+ priorityHints;
1199
+ sentimentHints;
1200
+ intentKeywords;
382
1201
  llm;
383
1202
  llmModel;
1203
+ locale;
384
1204
  constructor(options) {
1205
+ this.locale = resolveBaseLocale(options?.locale);
385
1206
  this.keywords = {
386
- ...CATEGORY_KEYWORDS,
1207
+ ...CATEGORY_KEYWORDS[this.locale],
387
1208
  ...options?.keywords ?? {}
388
1209
  };
1210
+ this.priorityHints = PRIORITY_HINTS[this.locale];
1211
+ this.sentimentHints = SENTIMENT_HINTS[this.locale];
1212
+ this.intentKeywords = INTENT_KEYWORDS[this.locale];
389
1213
  this.llm = options?.llm;
390
1214
  this.llmModel = options?.llmModel;
391
1215
  }
@@ -397,7 +1221,12 @@ class TicketClassifier {
397
1221
  const llmResult = await this.llm.chat([
398
1222
  {
399
1223
  role: "system",
400
- content: [{ type: "text", text: "Classify the support ticket." }]
1224
+ content: [
1225
+ {
1226
+ type: "text",
1227
+ text: createSupportBotI18n(this.locale).t("prompt.classifier.system")
1228
+ }
1229
+ ]
401
1230
  },
402
1231
  {
403
1232
  role: "user",
@@ -464,7 +1293,8 @@ ${ticket.body}`.toLowerCase();
464
1293
  "medium",
465
1294
  "low"
466
1295
  ]) {
467
- if (PRIORITY_HINTS[priority].some((word) => text.includes(word))) {
1296
+ const hints = this.priorityHints[priority];
1297
+ if (hints?.some((word) => text.includes(word))) {
468
1298
  return priority;
469
1299
  }
470
1300
  }
@@ -477,25 +1307,21 @@ ${ticket.body}`.toLowerCase();
477
1307
  "neutral",
478
1308
  "positive"
479
1309
  ]) {
480
- if (SENTIMENT_HINTS[sentiment].some((word) => text.includes(word))) {
1310
+ const hints = this.sentimentHints[sentiment];
1311
+ if (hints?.some((word) => text.includes(word))) {
481
1312
  return sentiment;
482
1313
  }
483
1314
  }
484
1315
  return "neutral";
485
1316
  }
486
1317
  extractIntents(text) {
487
- const intents = [];
488
- if (text.includes("refund") || text.includes("chargeback"))
489
- intents.push("refund");
490
- if (text.includes("payout"))
491
- intents.push("payout");
492
- if (text.includes("login"))
493
- intents.push("login-help");
494
- if (text.includes("feature"))
495
- intents.push("feature-request");
496
- if (text.includes("bug") || text.includes("error"))
497
- intents.push("bug-report");
498
- return intents.length ? intents : ["general"];
1318
+ const intents = new Set;
1319
+ for (const { keyword, intent } of this.intentKeywords) {
1320
+ if (text.includes(keyword)) {
1321
+ intents.add(intent);
1322
+ }
1323
+ }
1324
+ return intents.size > 0 ? [...intents] : ["general"];
499
1325
  }
500
1326
  estimateConfidence(category, priority, sentiment) {
501
1327
  let base = 0.6;