@cap-js/agents 0.9.6 → 0.9.7

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 (57) hide show
  1. package/README.md +2 -131
  2. package/_i18n/messages_ar.properties +48 -0
  3. package/_i18n/messages_bg.properties +48 -0
  4. package/_i18n/messages_cs.properties +48 -0
  5. package/_i18n/messages_da.properties +48 -0
  6. package/_i18n/messages_de.properties +48 -0
  7. package/_i18n/messages_el.properties +48 -0
  8. package/_i18n/messages_en.properties +48 -0
  9. package/_i18n/messages_es.properties +48 -0
  10. package/_i18n/messages_es_MX.properties +48 -0
  11. package/_i18n/messages_fi.properties +48 -0
  12. package/_i18n/messages_fr.properties +48 -0
  13. package/_i18n/messages_he.properties +48 -0
  14. package/_i18n/messages_hr.properties +48 -0
  15. package/_i18n/messages_hu.properties +48 -0
  16. package/_i18n/messages_it.properties +48 -0
  17. package/_i18n/messages_ja.properties +48 -0
  18. package/_i18n/messages_kk.properties +48 -0
  19. package/_i18n/messages_ko.properties +48 -0
  20. package/_i18n/messages_ms.properties +48 -0
  21. package/_i18n/messages_nl.properties +48 -0
  22. package/_i18n/messages_no.properties +48 -0
  23. package/_i18n/messages_pl.properties +48 -0
  24. package/_i18n/messages_pt.properties +48 -0
  25. package/_i18n/messages_ro.properties +48 -0
  26. package/_i18n/messages_ru.properties +48 -0
  27. package/_i18n/messages_sh.properties +48 -0
  28. package/_i18n/messages_sk.properties +48 -0
  29. package/_i18n/messages_sl.properties +48 -0
  30. package/_i18n/messages_sv.properties +48 -0
  31. package/_i18n/messages_th.properties +48 -0
  32. package/_i18n/messages_tr.properties +48 -0
  33. package/_i18n/messages_uk.properties +48 -0
  34. package/_i18n/messages_vi.properties +48 -0
  35. package/_i18n/messages_zh_CN.properties +48 -0
  36. package/_i18n/messages_zh_TW.properties +48 -0
  37. package/cds-plugin.js +2 -0
  38. package/lib/agents/middleware/index.js +2 -0
  39. package/lib/agents/middleware/masking.js +124 -0
  40. package/lib/agents/middleware/remote-mcp.js +5 -3
  41. package/lib/masking/index.js +90 -0
  42. package/lib/masking/store.js +80 -0
  43. package/lib/masking/structured/findElements.js +459 -0
  44. package/lib/masking/structured/index.js +170 -0
  45. package/lib/masking/unstructured/dpi.js +84 -0
  46. package/lib/masking/unstructured/hana.js +64 -0
  47. package/lib/masking/unstructured/index.js +52 -0
  48. package/lib/preview/chat.html +66 -30
  49. package/lib/protocol/persistence/cleanup.js +13 -4
  50. package/lib/telemetry/chat-tracing.js +7 -2
  51. package/lib/telemetry/mlflow/tracing.js +3 -3
  52. package/lib/telemetry/span-masking.js +165 -0
  53. package/lib/telemetry/tool-tracing.js +4 -4
  54. package/lib/utils/markdown.js +2 -2
  55. package/package.json +3 -1
  56. package/srv/handlers/graph-executor.js +52 -17
  57. package/srv/handlers/system-prompt.js +7 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/agents",
3
- "version": "0.9.6",
3
+ "version": "0.9.7",
4
4
  "description": "CDS plugin for building agents",
5
5
  "author": "SAP SE (https://www.sap.com)",
6
6
  "license": "Apache-2.0",
@@ -50,6 +50,7 @@
50
50
  "@sap-ai-sdk/langchain": "^2.13.0",
51
51
  "@sap-ai-sdk/orchestration": "^2.11.0",
52
52
  "@sap-cloud-sdk/connectivity": "^4.7.0",
53
+ "@sap-cloud-sdk/http-client": "^4.9.1",
53
54
  "deepagents": "^1.10.0",
54
55
  "langchain": "^1.5.0",
55
56
  "marked": "^18",
@@ -91,6 +92,7 @@
91
92
  "mlflow": false,
92
93
  "connect": "auto",
93
94
  "retention": "30d",
95
+ "masking": false,
94
96
  "[development]": {
95
97
  "pushNotifications": {
96
98
  "allowedDomains": [
@@ -6,6 +6,8 @@ import { mlflowAttrs, mlflowTraceAttrs, setSpanAttrs } from "../../lib/telemetry
6
6
  import { CdsFileStore } from "../../lib/protocol/persistence/file-store.js"
7
7
  import { formatFileSize, sanitizeFilename } from "./tools.js"
8
8
  import { convertUsageData } from "../../lib/telemetry/chat-tracing.js"
9
+ import { resolvePseudonyms } from "../../lib/masking/index.js"
10
+ import { pseudonymizeUserMessage } from "../../lib/masking/unstructured/index.js"
9
11
  import { triggerCleanup } from "../../lib/protocol/persistence/cleanup.js"
10
12
  import { COLLECT_RESULT } from "./chat.js"
11
13
  import { linkTraceToPrompt } from "../../lib/telemetry/mlflow/tracing.js"
@@ -197,6 +199,9 @@ class GraphExecutor {
197
199
  // final visual (collapse to the last turn's bubble at task completion).
198
200
  let currentMsgId = null
199
201
  let thinkingCount = 0
202
+ // Holds a trailing fragment of the previous chunk that is a prefix of a known
203
+ // pseudonym hash. Prepended to the next chunk so split hashes are resolved correctly.
204
+ let pendingPrefix = ""
200
205
 
201
206
  try {
202
207
  if (typeof graph.stream !== "function" || cds.env.agents?.streaming === false) {
@@ -238,8 +243,23 @@ class GraphExecutor {
238
243
  const lastChunk =
239
244
  !!msgChunk.additional_kwargs?.intermediate_results?.llm?.choices[0].finish_reason
240
245
 
241
- const text = messageText(msgChunk?.content)
242
- if (!text) continue
246
+ const raw = pendingPrefix + (messageText(msgChunk?.content) ?? "")
247
+ pendingPrefix = ""
248
+ if (!raw) continue
249
+
250
+ // Hashes look like name-8hexchars and never contain spaces.
251
+ // On non-last chunks, slice last token and append to next chunk
252
+ // to avoid unresolved boundaries
253
+ let toEmit = raw
254
+ if (!lastChunk) {
255
+ const lastSpace = raw.lastIndexOf(" ")
256
+ if (lastSpace !== -1 && lastSpace < raw.length - 1) {
257
+ pendingPrefix = raw.slice(lastSpace + 1)
258
+ toEmit = raw.slice(0, lastSpace + 1)
259
+ }
260
+ }
261
+
262
+ const text = resolvePseudonyms(toEmit)
243
263
  // A2A TaskArtifactUpdateEvent: `append` and `lastChunk` are event-level
244
264
  // fields (siblings of `artifact`), NOT properties of `artifact`. The SDK's
245
265
  // ResultManager reads event.append; nesting them leaves it undefined and
@@ -354,16 +374,18 @@ class GraphExecutor {
354
374
  */
355
375
  async _summarizePartialWork(taskId, contextId, serviceName, reason) {
356
376
  const { summarizePartialWork } = await import("../../lib/agents/summarize-on-timeout.js")
357
- return summarizePartialWork({
358
- taskId,
359
- contextId,
360
- serviceName,
361
- reason,
362
- checkpointer: this._graph?.checkpointer,
363
- getModel: () => this._srv.send("buildModel"),
364
- // Summary runs after graph abort, so no execution-time grace is needed.
365
- timeout: 10_000,
366
- })
377
+ return resolvePseudonyms(
378
+ await summarizePartialWork({
379
+ taskId,
380
+ contextId,
381
+ serviceName,
382
+ reason,
383
+ checkpointer: this._graph?.checkpointer,
384
+ getModel: () => this._srv.send("buildModel"),
385
+ // Summary runs after graph abort, so no execution-time grace is needed.
386
+ timeout: 10_000,
387
+ }),
388
+ )
367
389
  }
368
390
 
369
391
  async execute(requestContext, eventBus) {
@@ -385,6 +407,17 @@ class GraphExecutor {
385
407
  cds.context["agent.service"] = serviceName
386
408
  cds.context["agent.eventBus"] = eventBus
387
409
 
410
+ // REVISIT: Resolve graph early for pseudonymizeUserMessage. Mid-term move into beforeAgent together with Audit & Telemetry which rely on it
411
+ const graph = await this._resolveGraph()
412
+ if (cds.env.agents.masking) {
413
+ await pseudonymizeUserMessage(
414
+ this._srv,
415
+ requestContext,
416
+ graph.checkpointer,
417
+ `${serviceName}:${contextId}`,
418
+ )
419
+ }
420
+
388
421
  metrics.concurrentExecutions.add(1, mAttrs)
389
422
 
390
423
  if (!isResume) {
@@ -533,8 +566,6 @@ class GraphExecutor {
533
566
  let usageData
534
567
  let result
535
568
  try {
536
- const graph = await this._resolveGraph()
537
-
538
569
  const extraConfig = this._configMapper ? await this._configMapper(requestContext) : {}
539
570
  if (extraConfig !== null && extraConfig !== undefined && typeof extraConfig !== "object") {
540
571
  throw new TypeError(`configMapper must return a plain object, got ${typeof extraConfig}`)
@@ -611,7 +642,7 @@ class GraphExecutor {
611
642
  }
612
643
 
613
644
  const outputMapper = this._outputMapper || defaultOutputMapper
614
- const output = outputMapper(result) || "I could not generate a response."
645
+ const output = resolvePseudonyms(outputMapper(result)) || "I could not generate a response."
615
646
 
616
647
  LOG.info(serviceName, "completed", { conversation: short(contextId), duration })
617
648
 
@@ -620,7 +651,9 @@ class GraphExecutor {
620
651
  setSpanAttrs(
621
652
  wfSpan,
622
653
  mlflowAttrs("AGENT", {
623
- outputs: { choices: [{ message: { role: "assistant", content: output } }] },
654
+ outputs: {
655
+ choices: [{ message: { role: "assistant", content: output } }],
656
+ },
624
657
  functionName: serviceName,
625
658
  }),
626
659
  )
@@ -630,7 +663,9 @@ class GraphExecutor {
630
663
  setSpanAttrs(
631
664
  rootSpan,
632
665
  mlflowAttrs("CHAIN", {
633
- outputs: { choices: [{ message: { role: "assistant", content: output } }] },
666
+ outputs: {
667
+ choices: [{ message: { role: "assistant", content: output } }],
668
+ },
634
669
  }),
635
670
  )
636
671
  }
@@ -21,5 +21,12 @@ export function buildSystemPrompt(srv) {
21
21
  ]
22
22
  : []),
23
23
  "Be concise and helpful.",
24
+ ...(cds.env.agents?.masking
25
+ ? [
26
+ `## How to handle pseudonymized values`,
27
+ `Never comment on pseudonymization and that you had to work with pseudonymized values but use the pseudonymized values in responses like it would be regular values!`,
28
+ `The user sees the actual values and thus should not be made aware of any pseudonymization that has taken place internally.`,
29
+ ]
30
+ : []),
24
31
  ].join("\n")
25
32
  }