@contractspec/lib.knowledge 2.3.0 → 2.5.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 (44) hide show
  1. package/dist/access/guard.d.ts +3 -0
  2. package/dist/access/guard.js +290 -5
  3. package/dist/access/index.js +290 -5
  4. package/dist/i18n/catalogs/en.d.ts +8 -0
  5. package/dist/i18n/catalogs/en.js +107 -0
  6. package/dist/i18n/catalogs/es.d.ts +6 -0
  7. package/dist/i18n/catalogs/es.js +80 -0
  8. package/dist/i18n/catalogs/fr.d.ts +6 -0
  9. package/dist/i18n/catalogs/fr.js +80 -0
  10. package/dist/i18n/catalogs/index.d.ts +8 -0
  11. package/dist/i18n/catalogs/index.js +263 -0
  12. package/dist/i18n/i18n.test.d.ts +1 -0
  13. package/dist/i18n/index.d.ts +27 -0
  14. package/dist/i18n/index.js +321 -0
  15. package/dist/i18n/keys.d.ts +74 -0
  16. package/dist/i18n/keys.js +33 -0
  17. package/dist/i18n/locale.d.ts +8 -0
  18. package/dist/i18n/locale.js +14 -0
  19. package/dist/i18n/messages.d.ts +14 -0
  20. package/dist/i18n/messages.js +275 -0
  21. package/dist/index.js +316 -23
  22. package/dist/ingestion/gmail-adapter.d.ts +2 -1
  23. package/dist/ingestion/gmail-adapter.js +284 -8
  24. package/dist/ingestion/index.js +284 -8
  25. package/dist/node/access/guard.js +290 -5
  26. package/dist/node/access/index.js +290 -5
  27. package/dist/node/i18n/catalogs/en.js +106 -0
  28. package/dist/node/i18n/catalogs/es.js +79 -0
  29. package/dist/node/i18n/catalogs/fr.js +79 -0
  30. package/dist/node/i18n/catalogs/index.js +262 -0
  31. package/dist/node/i18n/index.js +320 -0
  32. package/dist/node/i18n/keys.js +32 -0
  33. package/dist/node/i18n/locale.js +13 -0
  34. package/dist/node/i18n/messages.js +274 -0
  35. package/dist/node/index.js +316 -23
  36. package/dist/node/ingestion/gmail-adapter.js +284 -8
  37. package/dist/node/ingestion/index.js +284 -8
  38. package/dist/node/query/index.js +282 -10
  39. package/dist/node/query/service.js +282 -10
  40. package/dist/query/index.js +282 -10
  41. package/dist/query/service.d.ts +3 -0
  42. package/dist/query/service.js +282 -10
  43. package/dist/types.d.ts +2 -0
  44. package/package.json +125 -5
@@ -1,3 +1,273 @@
1
+ // src/i18n/catalogs/en.ts
2
+ import { defineTranslation } from "@contractspec/lib.contracts-spec/translations";
3
+ var enMessages = defineTranslation({
4
+ meta: {
5
+ key: "knowledge.messages",
6
+ version: "1.0.0",
7
+ domain: "knowledge",
8
+ description: "All user-facing and LLM-facing strings for the knowledge package",
9
+ owners: ["platform"],
10
+ stability: "experimental"
11
+ },
12
+ locale: "en",
13
+ fallback: "en",
14
+ messages: {
15
+ "access.notBound": {
16
+ value: 'Knowledge space "{spaceKey}" is not bound in the resolved app config.',
17
+ description: "Denial reason when a knowledge space is not bound",
18
+ placeholders: [{ name: "spaceKey", type: "string" }]
19
+ },
20
+ "access.readOnly": {
21
+ value: 'Knowledge space "{spaceKey}" is category "{category}" and is read-only.',
22
+ description: "Denial reason when write is attempted on a read-only space",
23
+ placeholders: [
24
+ { name: "spaceKey", type: "string" },
25
+ { name: "category", type: "string" }
26
+ ]
27
+ },
28
+ "access.workflowUnauthorized": {
29
+ value: 'Workflow "{workflowName}" is not authorized to access knowledge space "{spaceKey}".',
30
+ description: "Denial reason when a workflow lacks space access",
31
+ placeholders: [
32
+ { name: "workflowName", type: "string" },
33
+ { name: "spaceKey", type: "string" }
34
+ ]
35
+ },
36
+ "access.agentUnauthorized": {
37
+ value: 'Agent "{agentName}" is not authorized to access knowledge space "{spaceKey}".',
38
+ description: "Denial reason when an agent lacks space access",
39
+ placeholders: [
40
+ { name: "agentName", type: "string" },
41
+ { name: "spaceKey", type: "string" }
42
+ ]
43
+ },
44
+ "access.ephemeralWarning": {
45
+ value: 'Knowledge space "{spaceKey}" is ephemeral; results may be transient.',
46
+ description: "Warning for ephemeral knowledge spaces",
47
+ placeholders: [{ name: "spaceKey", type: "string" }]
48
+ },
49
+ "query.systemPrompt": {
50
+ value: "You are a knowledge assistant that answers questions using the provided context. Cite relevant sources if possible.",
51
+ description: "Default LLM system prompt for knowledge queries"
52
+ },
53
+ "query.userMessage": {
54
+ value: `Question:
55
+ {question}
56
+
57
+ Context:
58
+ {context}`,
59
+ description: "User message template combining question and context",
60
+ placeholders: [
61
+ { name: "question", type: "string" },
62
+ { name: "context", type: "string" }
63
+ ]
64
+ },
65
+ "query.noResults": {
66
+ value: "No relevant documents found.",
67
+ description: "Displayed when vector search returns zero results"
68
+ },
69
+ "query.sourceLabel": {
70
+ value: "Source {index} (score: {score}):",
71
+ description: "Label prefix for each source in the context block",
72
+ placeholders: [
73
+ { name: "index", type: "number" },
74
+ { name: "score", type: "string" }
75
+ ]
76
+ },
77
+ "ingestion.gmail.subject": {
78
+ value: "Subject: {subject}",
79
+ description: "Gmail thread subject label",
80
+ placeholders: [{ name: "subject", type: "string" }]
81
+ },
82
+ "ingestion.gmail.snippet": {
83
+ value: "Snippet: {snippet}",
84
+ description: "Gmail thread snippet label",
85
+ placeholders: [{ name: "snippet", type: "string" }]
86
+ },
87
+ "ingestion.gmail.from": {
88
+ value: "From: {from}",
89
+ description: "Gmail message sender label",
90
+ placeholders: [{ name: "from", type: "string" }]
91
+ },
92
+ "ingestion.gmail.to": {
93
+ value: "To: {to}",
94
+ description: "Gmail message recipients label",
95
+ placeholders: [{ name: "to", type: "string" }]
96
+ },
97
+ "ingestion.gmail.date": {
98
+ value: "Date: {date}",
99
+ description: "Gmail message date label",
100
+ placeholders: [{ name: "date", type: "string" }]
101
+ }
102
+ }
103
+ });
104
+
105
+ // src/i18n/catalogs/fr.ts
106
+ import { defineTranslation as defineTranslation2 } from "@contractspec/lib.contracts-spec/translations";
107
+ var frMessages = defineTranslation2({
108
+ meta: {
109
+ key: "knowledge.messages",
110
+ version: "1.0.0",
111
+ domain: "knowledge",
112
+ description: "All user-facing and LLM-facing strings for the knowledge package (French)",
113
+ owners: ["platform"],
114
+ stability: "experimental"
115
+ },
116
+ locale: "fr",
117
+ fallback: "en",
118
+ messages: {
119
+ "access.notBound": {
120
+ value: `L'espace de connaissances "{spaceKey}" n'est pas lié dans la configuration de l'application.`,
121
+ description: "Denial reason when a knowledge space is not bound"
122
+ },
123
+ "access.readOnly": {
124
+ value: `L'espace de connaissances "{spaceKey}" est de catégorie "{category}" et est en lecture seule.`,
125
+ description: "Denial reason when write is attempted on a read-only space"
126
+ },
127
+ "access.workflowUnauthorized": {
128
+ value: `Le workflow "{workflowName}" n'est pas autorisé à accéder à l'espace de connaissances "{spaceKey}".`,
129
+ description: "Denial reason when a workflow lacks space access"
130
+ },
131
+ "access.agentUnauthorized": {
132
+ value: `L'agent "{agentName}" n'est pas autorisé à accéder à l'espace de connaissances "{spaceKey}".`,
133
+ description: "Denial reason when an agent lacks space access"
134
+ },
135
+ "access.ephemeralWarning": {
136
+ value: `L'espace de connaissances "{spaceKey}" est éphémère ; les résultats peuvent être transitoires.`,
137
+ description: "Warning for ephemeral knowledge spaces"
138
+ },
139
+ "query.systemPrompt": {
140
+ value: "Vous êtes un assistant de connaissances qui répond aux questions en utilisant le contexte fourni. Citez les sources pertinentes si possible.",
141
+ description: "Default LLM system prompt for knowledge queries"
142
+ },
143
+ "query.userMessage": {
144
+ value: `Question :
145
+ {question}
146
+
147
+ Contexte :
148
+ {context}`,
149
+ description: "User message template combining question and context"
150
+ },
151
+ "query.noResults": {
152
+ value: "Aucun document pertinent trouvé.",
153
+ description: "Displayed when vector search returns zero results"
154
+ },
155
+ "query.sourceLabel": {
156
+ value: "Source {index} (score : {score}) :",
157
+ description: "Label prefix for each source in the context block"
158
+ },
159
+ "ingestion.gmail.subject": {
160
+ value: "Objet : {subject}",
161
+ description: "Gmail thread subject label"
162
+ },
163
+ "ingestion.gmail.snippet": {
164
+ value: "Extrait : {snippet}",
165
+ description: "Gmail thread snippet label"
166
+ },
167
+ "ingestion.gmail.from": {
168
+ value: "De : {from}",
169
+ description: "Gmail message sender label"
170
+ },
171
+ "ingestion.gmail.to": {
172
+ value: "À : {to}",
173
+ description: "Gmail message recipients label"
174
+ },
175
+ "ingestion.gmail.date": {
176
+ value: "Date : {date}",
177
+ description: "Gmail message date label"
178
+ }
179
+ }
180
+ });
181
+
182
+ // src/i18n/catalogs/es.ts
183
+ import { defineTranslation as defineTranslation3 } from "@contractspec/lib.contracts-spec/translations";
184
+ var esMessages = defineTranslation3({
185
+ meta: {
186
+ key: "knowledge.messages",
187
+ version: "1.0.0",
188
+ domain: "knowledge",
189
+ description: "All user-facing and LLM-facing strings for the knowledge package (Spanish)",
190
+ owners: ["platform"],
191
+ stability: "experimental"
192
+ },
193
+ locale: "es",
194
+ fallback: "en",
195
+ messages: {
196
+ "access.notBound": {
197
+ value: 'El espacio de conocimiento "{spaceKey}" no está vinculado en la configuración de la aplicación.',
198
+ description: "Denial reason when a knowledge space is not bound"
199
+ },
200
+ "access.readOnly": {
201
+ value: 'El espacio de conocimiento "{spaceKey}" es de categoría "{category}" y es de solo lectura.',
202
+ description: "Denial reason when write is attempted on a read-only space"
203
+ },
204
+ "access.workflowUnauthorized": {
205
+ value: 'El flujo de trabajo "{workflowName}" no está autorizado para acceder al espacio de conocimiento "{spaceKey}".',
206
+ description: "Denial reason when a workflow lacks space access"
207
+ },
208
+ "access.agentUnauthorized": {
209
+ value: 'El agente "{agentName}" no está autorizado para acceder al espacio de conocimiento "{spaceKey}".',
210
+ description: "Denial reason when an agent lacks space access"
211
+ },
212
+ "access.ephemeralWarning": {
213
+ value: 'El espacio de conocimiento "{spaceKey}" es efímero; los resultados pueden ser transitorios.',
214
+ description: "Warning for ephemeral knowledge spaces"
215
+ },
216
+ "query.systemPrompt": {
217
+ value: "Eres un asistente de conocimiento que responde preguntas utilizando el contexto proporcionado. Cita las fuentes relevantes si es posible.",
218
+ description: "Default LLM system prompt for knowledge queries"
219
+ },
220
+ "query.userMessage": {
221
+ value: `Pregunta:
222
+ {question}
223
+
224
+ Contexto:
225
+ {context}`,
226
+ description: "User message template combining question and context"
227
+ },
228
+ "query.noResults": {
229
+ value: "No se encontraron documentos relevantes.",
230
+ description: "Displayed when vector search returns zero results"
231
+ },
232
+ "query.sourceLabel": {
233
+ value: "Fuente {index} (puntuación: {score}):",
234
+ description: "Label prefix for each source in the context block"
235
+ },
236
+ "ingestion.gmail.subject": {
237
+ value: "Asunto: {subject}",
238
+ description: "Gmail thread subject label"
239
+ },
240
+ "ingestion.gmail.snippet": {
241
+ value: "Extracto: {snippet}",
242
+ description: "Gmail thread snippet label"
243
+ },
244
+ "ingestion.gmail.from": {
245
+ value: "De: {from}",
246
+ description: "Gmail message sender label"
247
+ },
248
+ "ingestion.gmail.to": {
249
+ value: "Para: {to}",
250
+ description: "Gmail message recipients label"
251
+ },
252
+ "ingestion.gmail.date": {
253
+ value: "Fecha: {date}",
254
+ description: "Gmail message date label"
255
+ }
256
+ }
257
+ });
258
+
259
+ // src/i18n/messages.ts
260
+ import {
261
+ createI18nFactory
262
+ } from "@contractspec/lib.contracts-spec/translations";
263
+ var factory = createI18nFactory({
264
+ specKey: "knowledge.messages",
265
+ catalogs: [enMessages, frMessages, esMessages]
266
+ });
267
+ var createKnowledgeI18n = factory.create;
268
+ var getDefaultI18n = factory.getDefault;
269
+ var resetI18nRegistry = factory.resetRegistry;
270
+
1
271
  // src/access/guard.ts
2
272
  var DEFAULT_DISALLOWED_WRITE = ["external", "ephemeral"];
3
273
 
@@ -5,23 +275,30 @@ class KnowledgeAccessGuard {
5
275
  disallowedWrite;
6
276
  requireWorkflowBinding;
7
277
  requireAgentBinding;
278
+ i18n;
8
279
  constructor(options = {}) {
9
280
  this.disallowedWrite = new Set(options.disallowWriteCategories ?? DEFAULT_DISALLOWED_WRITE);
10
281
  this.requireWorkflowBinding = options.requireWorkflowBinding ?? true;
11
282
  this.requireAgentBinding = options.requireAgentBinding ?? false;
283
+ this.i18n = options.locale ? createKnowledgeI18n(options.locale) : getDefaultI18n();
12
284
  }
13
285
  checkAccess(spaceBinding, context, appConfig) {
14
286
  const { binding, space } = spaceBinding;
15
287
  if (binding.required !== false && !this.isSpaceBound(spaceBinding, appConfig)) {
16
288
  return {
17
289
  allowed: false,
18
- reason: `Knowledge space "${space.meta.key}" is not bound in the resolved app config.`
290
+ reason: this.i18n.t("access.notBound", {
291
+ spaceKey: space.meta.key
292
+ })
19
293
  };
20
294
  }
21
295
  if (context.operation === "write" && this.disallowedWrite.has(space.meta.category)) {
22
296
  return {
23
297
  allowed: false,
24
- reason: `Knowledge space "${space.meta.key}" is category "${space.meta.category}" and is read-only.`
298
+ reason: this.i18n.t("access.readOnly", {
299
+ spaceKey: space.meta.key,
300
+ category: space.meta.category
301
+ })
25
302
  };
26
303
  }
27
304
  if (this.requireWorkflowBinding && context.workflowName) {
@@ -29,7 +306,10 @@ class KnowledgeAccessGuard {
29
306
  if (allowedWorkflows && !allowedWorkflows.includes(context.workflowName)) {
30
307
  return {
31
308
  allowed: false,
32
- reason: `Workflow "${context.workflowName}" is not authorized to access knowledge space "${space.meta.key}".`
309
+ reason: this.i18n.t("access.workflowUnauthorized", {
310
+ workflowName: context.workflowName,
311
+ spaceKey: space.meta.key
312
+ })
33
313
  };
34
314
  }
35
315
  }
@@ -38,7 +318,10 @@ class KnowledgeAccessGuard {
38
318
  if (allowedAgents && !allowedAgents.includes(context.agentName)) {
39
319
  return {
40
320
  allowed: false,
41
- reason: `Agent "${context.agentName}" is not authorized to access knowledge space "${space.meta.key}".`
321
+ reason: this.i18n.t("access.agentUnauthorized", {
322
+ agentName: context.agentName,
323
+ spaceKey: space.meta.key
324
+ })
42
325
  };
43
326
  }
44
327
  }
@@ -46,7 +329,9 @@ class KnowledgeAccessGuard {
46
329
  return {
47
330
  allowed: true,
48
331
  severity: "warning",
49
- reason: `Knowledge space "${space.meta.key}" is ephemeral; results may be transient.`
332
+ reason: this.i18n.t("access.ephemeralWarning", {
333
+ spaceKey: space.meta.key
334
+ })
50
335
  };
51
336
  }
52
337
  return { allowed: true };
@@ -155,11 +440,13 @@ class KnowledgeQueryService {
155
440
  vectorStore;
156
441
  llm;
157
442
  config;
443
+ i18n;
158
444
  constructor(embeddings, vectorStore, llm, config) {
159
445
  this.embeddings = embeddings;
160
446
  this.vectorStore = vectorStore;
161
447
  this.llm = llm;
162
448
  this.config = config;
449
+ this.i18n = config.locale ? createKnowledgeI18n(config.locale) : getDefaultI18n();
163
450
  }
164
451
  async query(question) {
165
452
  const embedding = await this.embeddings.embedQuery(question);
@@ -170,7 +457,7 @@ class KnowledgeQueryService {
170
457
  namespace: this.config.namespace,
171
458
  filter: undefined
172
459
  });
173
- const context = buildContext(results);
460
+ const context = buildContext(results, this.i18n);
174
461
  const messages = this.buildMessages(question, context);
175
462
  const response = await this.llm.chat(messages);
176
463
  return {
@@ -183,7 +470,7 @@ class KnowledgeQueryService {
183
470
  };
184
471
  }
185
472
  buildMessages(question, context) {
186
- const systemPrompt = this.config.systemPrompt ?? "You are a knowledge assistant that answers questions using the provided context. Cite relevant sources if possible.";
473
+ const systemPrompt = this.config.systemPrompt ?? this.i18n.t("query.systemPrompt");
187
474
  return [
188
475
  {
189
476
  role: "system",
@@ -194,24 +481,24 @@ class KnowledgeQueryService {
194
481
  content: [
195
482
  {
196
483
  type: "text",
197
- text: `Question:
198
- ${question}
199
-
200
- Context:
201
- ${context}`
484
+ text: this.i18n.t("query.userMessage", { question, context })
202
485
  }
203
486
  ]
204
487
  }
205
488
  ];
206
489
  }
207
490
  }
208
- function buildContext(results) {
491
+ function buildContext(results, i18n) {
209
492
  if (results.length === 0) {
210
- return "No relevant documents found.";
493
+ return i18n.t("query.noResults");
211
494
  }
212
495
  return results.map((result, index) => {
213
496
  const text = extractText(result);
214
- return `Source ${index + 1} (score: ${result.score.toFixed(3)}):
497
+ const label = i18n.t("query.sourceLabel", {
498
+ index: index + 1,
499
+ score: result.score.toFixed(3)
500
+ });
501
+ return `${label}
215
502
  ${text}`;
216
503
  }).join(`
217
504
 
@@ -347,11 +634,13 @@ class GmailIngestionAdapter {
347
634
  processor;
348
635
  embeddings;
349
636
  indexer;
350
- constructor(gmail, processor, embeddings, indexer) {
637
+ i18n;
638
+ constructor(gmail, processor, embeddings, indexer, locale) {
351
639
  this.gmail = gmail;
352
640
  this.processor = processor;
353
641
  this.embeddings = embeddings;
354
642
  this.indexer = indexer;
643
+ this.i18n = locale ? createKnowledgeI18n(locale) : getDefaultI18n();
355
644
  }
356
645
  async syncThreads(query) {
357
646
  const threads = await this.gmail.listThreads(query);
@@ -366,7 +655,7 @@ class GmailIngestionAdapter {
366
655
  await this.indexer.upsert(fragments, embeddings);
367
656
  }
368
657
  toRawDocument(thread) {
369
- const content = composeThreadText(thread);
658
+ const content = composeThreadText(thread, this.i18n);
370
659
  return {
371
660
  id: thread.id,
372
661
  mimeType: "text/plain",
@@ -379,18 +668,22 @@ class GmailIngestionAdapter {
379
668
  };
380
669
  }
381
670
  }
382
- function composeThreadText(thread) {
671
+ function composeThreadText(thread, i18n) {
383
672
  const header = [
384
- `Subject: ${thread.subject ?? ""}`,
385
- `Snippet: ${thread.snippet ?? ""}`
673
+ i18n.t("ingestion.gmail.subject", { subject: thread.subject ?? "" }),
674
+ i18n.t("ingestion.gmail.snippet", { snippet: thread.snippet ?? "" })
386
675
  ];
387
676
  const messageTexts = thread.messages.map((message) => {
388
677
  const parts = [
389
- `From: ${formatAddress(message.from)}`,
390
- `To: ${message.to.map(formatAddress).join(", ")}`
678
+ i18n.t("ingestion.gmail.from", { from: formatAddress(message.from) }),
679
+ i18n.t("ingestion.gmail.to", {
680
+ to: message.to.map(formatAddress).join(", ")
681
+ })
391
682
  ];
392
683
  if (message.sentAt) {
393
- parts.push(`Date: ${message.sentAt.toISOString()}`);
684
+ parts.push(i18n.t("ingestion.gmail.date", {
685
+ date: message.sentAt.toISOString()
686
+ }));
394
687
  }
395
688
  const body = message.textBody ?? stripHtml(message.htmlBody ?? "");
396
689
  return `${parts.join(`