@c8y/ngx-components 1024.20.1 → 1024.21.3

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 (142) hide show
  1. package/fesm2022/c8y-ngx-components-ai-agent-chat.mjs +527 -12
  2. package/fesm2022/c8y-ngx-components-ai-agent-chat.mjs.map +1 -1
  3. package/fesm2022/c8y-ngx-components-ai-ai-chat.mjs +31 -9
  4. package/fesm2022/c8y-ngx-components-ai-ai-chat.mjs.map +1 -1
  5. package/fesm2022/c8y-ngx-components-ai.mjs +503 -62
  6. package/fesm2022/c8y-ngx-components-ai.mjs.map +1 -1
  7. package/fesm2022/c8y-ngx-components-alarm-event-selector.mjs +2 -2
  8. package/fesm2022/c8y-ngx-components-alarm-event-selector.mjs.map +1 -1
  9. package/fesm2022/c8y-ngx-components-alarms.mjs +3 -3
  10. package/fesm2022/c8y-ngx-components-alarms.mjs.map +1 -1
  11. package/fesm2022/c8y-ngx-components-application-access-list.mjs +1 -1
  12. package/fesm2022/c8y-ngx-components-application-access-list.mjs.map +1 -1
  13. package/fesm2022/c8y-ngx-components-application-select.mjs +1 -1
  14. package/fesm2022/c8y-ngx-components-application-select.mjs.map +1 -1
  15. package/fesm2022/c8y-ngx-components-asset-properties.mjs +3 -2
  16. package/fesm2022/c8y-ngx-components-asset-properties.mjs.map +1 -1
  17. package/fesm2022/c8y-ngx-components-auth-configuration.mjs +1 -1
  18. package/fesm2022/c8y-ngx-components-auth-configuration.mjs.map +1 -1
  19. package/fesm2022/c8y-ngx-components-bookmarks.mjs +1 -1
  20. package/fesm2022/c8y-ngx-components-bookmarks.mjs.map +1 -1
  21. package/fesm2022/c8y-ngx-components-branding-shared-lazy.mjs +1 -1
  22. package/fesm2022/c8y-ngx-components-branding-shared-lazy.mjs.map +1 -1
  23. package/fesm2022/c8y-ngx-components-cockpit-config.mjs +5 -5
  24. package/fesm2022/c8y-ngx-components-cockpit-config.mjs.map +1 -1
  25. package/fesm2022/{c8y-ngx-components-context-dashboard-dashboard-version-history.component-Jyjkv4JT.mjs → c8y-ngx-components-context-dashboard-dashboard-version-history.component-CP1afwVe.mjs} +2 -2
  26. package/fesm2022/{c8y-ngx-components-context-dashboard-dashboard-version-history.component-Jyjkv4JT.mjs.map → c8y-ngx-components-context-dashboard-dashboard-version-history.component-CP1afwVe.mjs.map} +1 -1
  27. package/fesm2022/c8y-ngx-components-context-dashboard.mjs +81 -20
  28. package/fesm2022/c8y-ngx-components-context-dashboard.mjs.map +1 -1
  29. package/fesm2022/{c8y-ngx-components-dashboard-details-advanced-tab-dashboard-details-advanced-tab.component-UEUYsXBv.mjs → c8y-ngx-components-dashboard-details-advanced-tab-dashboard-details-advanced-tab.component-CRvgIgri.mjs} +2 -2
  30. package/fesm2022/{c8y-ngx-components-dashboard-details-advanced-tab-dashboard-details-advanced-tab.component-UEUYsXBv.mjs.map → c8y-ngx-components-dashboard-details-advanced-tab-dashboard-details-advanced-tab.component-CRvgIgri.mjs.map} +1 -1
  31. package/fesm2022/c8y-ngx-components-dashboard-details-advanced-tab.mjs +2 -2
  32. package/fesm2022/c8y-ngx-components-dashboard-manager.mjs +1 -1
  33. package/fesm2022/c8y-ngx-components-dashboard-manager.mjs.map +1 -1
  34. package/fesm2022/c8y-ngx-components-datapoint-explorer-view.mjs +3 -3
  35. package/fesm2022/c8y-ngx-components-datapoint-explorer-view.mjs.map +1 -1
  36. package/fesm2022/c8y-ngx-components-datapoint-selector.mjs +2 -2
  37. package/fesm2022/c8y-ngx-components-datapoint-selector.mjs.map +1 -1
  38. package/fesm2022/c8y-ngx-components-device-profile.mjs +2 -2
  39. package/fesm2022/c8y-ngx-components-device-profile.mjs.map +1 -1
  40. package/fesm2022/c8y-ngx-components-diagnostics.mjs +1 -1
  41. package/fesm2022/c8y-ngx-components-diagnostics.mjs.map +1 -1
  42. package/fesm2022/c8y-ngx-components-echart.mjs +146 -126
  43. package/fesm2022/c8y-ngx-components-echart.mjs.map +1 -1
  44. package/fesm2022/c8y-ngx-components-ecosystem-application-plugins.mjs +2 -2
  45. package/fesm2022/c8y-ngx-components-ecosystem-application-plugins.mjs.map +1 -1
  46. package/fesm2022/c8y-ngx-components-ecosystem-shared.mjs +1 -1
  47. package/fesm2022/c8y-ngx-components-ecosystem-shared.mjs.map +1 -1
  48. package/fesm2022/c8y-ngx-components-ecosystem.mjs +4 -4
  49. package/fesm2022/c8y-ngx-components-ecosystem.mjs.map +1 -1
  50. package/fesm2022/c8y-ngx-components-events-events-timeline.mjs +1 -1
  51. package/fesm2022/c8y-ngx-components-events-events-timeline.mjs.map +1 -1
  52. package/fesm2022/c8y-ngx-components-events.mjs +2 -2
  53. package/fesm2022/c8y-ngx-components-events.mjs.map +1 -1
  54. package/fesm2022/c8y-ngx-components-icon-selector.mjs +4 -4
  55. package/fesm2022/c8y-ngx-components-icon-selector.mjs.map +1 -1
  56. package/fesm2022/c8y-ngx-components-operations-bulk-operation-list-item.mjs +1 -1
  57. package/fesm2022/c8y-ngx-components-operations-bulk-operation-list-item.mjs.map +1 -1
  58. package/fesm2022/c8y-ngx-components-operations-operations-list.mjs +1 -1
  59. package/fesm2022/c8y-ngx-components-operations-operations-list.mjs.map +1 -1
  60. package/fesm2022/c8y-ngx-components-operations-operations-timeline.mjs +1 -1
  61. package/fesm2022/c8y-ngx-components-operations-operations-timeline.mjs.map +1 -1
  62. package/fesm2022/c8y-ngx-components-operations-stepper-bulk-type-configuration.mjs +1 -1
  63. package/fesm2022/c8y-ngx-components-operations-stepper-bulk-type-configuration.mjs.map +1 -1
  64. package/fesm2022/c8y-ngx-components-operations-stepper-bulk-type-device-profile.mjs +2 -2
  65. package/fesm2022/c8y-ngx-components-operations-stepper-bulk-type-device-profile.mjs.map +1 -1
  66. package/fesm2022/c8y-ngx-components-operations-stepper-bulk-type-firmware.mjs +2 -2
  67. package/fesm2022/c8y-ngx-components-operations-stepper-bulk-type-firmware.mjs.map +1 -1
  68. package/fesm2022/c8y-ngx-components-operations-stepper-bulk-type-software.mjs +2 -2
  69. package/fesm2022/c8y-ngx-components-operations-stepper-bulk-type-software.mjs.map +1 -1
  70. package/fesm2022/c8y-ngx-components-protocol-lpwan.mjs +5 -5
  71. package/fesm2022/c8y-ngx-components-protocol-lpwan.mjs.map +1 -1
  72. package/fesm2022/c8y-ngx-components-protocol-opcua.mjs +1 -1
  73. package/fesm2022/c8y-ngx-components-protocol-opcua.mjs.map +1 -1
  74. package/fesm2022/c8y-ngx-components-register-device.mjs +3 -3
  75. package/fesm2022/c8y-ngx-components-register-device.mjs.map +1 -1
  76. package/fesm2022/c8y-ngx-components-remote-access-configurations.mjs +1 -1
  77. package/fesm2022/c8y-ngx-components-remote-access-configurations.mjs.map +1 -1
  78. package/fesm2022/c8y-ngx-components-replace-device-replace-device-wizard.mjs +1 -1
  79. package/fesm2022/c8y-ngx-components-replace-device-replace-device-wizard.mjs.map +1 -1
  80. package/fesm2022/c8y-ngx-components-report-dashboard.mjs +1 -1
  81. package/fesm2022/c8y-ngx-components-report-dashboard.mjs.map +1 -1
  82. package/fesm2022/c8y-ngx-components-repository-configuration.mjs +4 -4
  83. package/fesm2022/c8y-ngx-components-repository-configuration.mjs.map +1 -1
  84. package/fesm2022/c8y-ngx-components-repository-firmware.mjs +4 -4
  85. package/fesm2022/c8y-ngx-components-repository-firmware.mjs.map +1 -1
  86. package/fesm2022/c8y-ngx-components-repository-shared.mjs +2 -2
  87. package/fesm2022/c8y-ngx-components-repository-shared.mjs.map +1 -1
  88. package/fesm2022/c8y-ngx-components-repository-software.mjs +3 -3
  89. package/fesm2022/c8y-ngx-components-repository-software.mjs.map +1 -1
  90. package/fesm2022/c8y-ngx-components-static-assets-modal.mjs +1 -1
  91. package/fesm2022/c8y-ngx-components-static-assets-modal.mjs.map +1 -1
  92. package/fesm2022/c8y-ngx-components-tracking.mjs +1 -1
  93. package/fesm2022/c8y-ngx-components-tracking.mjs.map +1 -1
  94. package/fesm2022/c8y-ngx-components-trusted-certificates.mjs +1 -1
  95. package/fesm2022/c8y-ngx-components-trusted-certificates.mjs.map +1 -1
  96. package/fesm2022/c8y-ngx-components-widgets-definitions-html-widget.mjs +17 -1
  97. package/fesm2022/c8y-ngx-components-widgets-definitions-html-widget.mjs.map +1 -1
  98. package/fesm2022/c8y-ngx-components-widgets-definitions-scada.mjs +21 -1
  99. package/fesm2022/c8y-ngx-components-widgets-definitions-scada.mjs.map +1 -1
  100. package/fesm2022/c8y-ngx-components-widgets-implementations-asset-table.mjs +60 -31
  101. package/fesm2022/c8y-ngx-components-widgets-implementations-asset-table.mjs.map +1 -1
  102. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-graph.mjs +2 -2
  103. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-graph.mjs.map +1 -1
  104. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-list.mjs +2 -2
  105. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-list.mjs.map +1 -1
  106. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-table.mjs +1 -1
  107. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-table.mjs.map +1 -1
  108. package/fesm2022/c8y-ngx-components-widgets-implementations-image.mjs +1 -1
  109. package/fesm2022/c8y-ngx-components-widgets-implementations-image.mjs.map +1 -1
  110. package/fesm2022/c8y-ngx-components-widgets-implementations-quick-links.mjs +1 -1
  111. package/fesm2022/c8y-ngx-components-widgets-implementations-quick-links.mjs.map +1 -1
  112. package/fesm2022/c8y-ngx-components-widgets-implementations-scada.mjs +1 -1
  113. package/fesm2022/c8y-ngx-components-widgets-implementations-scada.mjs.map +1 -1
  114. package/fesm2022/c8y-ngx-components.mjs +500 -38
  115. package/fesm2022/c8y-ngx-components.mjs.map +1 -1
  116. package/locales/de.po +72 -0
  117. package/locales/es.po +72 -0
  118. package/locales/fr.po +72 -0
  119. package/locales/ja_JP.po +72 -0
  120. package/locales/ko.po +72 -0
  121. package/locales/locales.pot +72 -3
  122. package/locales/nl.po +72 -0
  123. package/locales/pl.po +73 -1
  124. package/locales/pt_BR.po +74 -2
  125. package/locales/zh_CN.po +72 -0
  126. package/locales/zh_TW.po +72 -0
  127. package/package.json +1 -1
  128. package/types/c8y-ngx-components-ai-agent-chat.d.ts +201 -3
  129. package/types/c8y-ngx-components-ai-agent-chat.d.ts.map +1 -1
  130. package/types/c8y-ngx-components-ai-ai-chat.d.ts +14 -4
  131. package/types/c8y-ngx-components-ai-ai-chat.d.ts.map +1 -1
  132. package/types/c8y-ngx-components-ai.d.ts +157 -2
  133. package/types/c8y-ngx-components-ai.d.ts.map +1 -1
  134. package/types/c8y-ngx-components-asset-properties.d.ts.map +1 -1
  135. package/types/c8y-ngx-components-context-dashboard.d.ts +37 -3
  136. package/types/c8y-ngx-components-context-dashboard.d.ts.map +1 -1
  137. package/types/c8y-ngx-components-echart.d.ts +6 -0
  138. package/types/c8y-ngx-components-echart.d.ts.map +1 -1
  139. package/types/c8y-ngx-components-widgets-implementations-asset-table.d.ts +5 -0
  140. package/types/c8y-ngx-components-widgets-implementations-asset-table.d.ts.map +1 -1
  141. package/types/c8y-ngx-components.d.ts +310 -12
  142. package/types/c8y-ngx-components.d.ts.map +1 -1
@@ -5,6 +5,143 @@ import { FetchClient } from '@c8y/client';
5
5
  import { Observable } from 'rxjs';
6
6
  import { gettext } from '@c8y/ngx-components/gettext';
7
7
 
8
+ function convertToolOutputToAgentFormat(tool) {
9
+ const { output, outputType, error } = tool;
10
+ // Content arrays need outputType="content" for roundtripping (set by convertStreamingToolOutputToToolCallPart)
11
+ if (outputType === 'content' && Array.isArray(output)) {
12
+ return { type: 'content', value: output };
13
+ }
14
+ if (error) {
15
+ return typeof output === 'string'
16
+ ? { type: 'error-text', value: output }
17
+ : { type: 'error-json', value: output ?? null };
18
+ }
19
+ if (typeof output === 'string') {
20
+ return { type: 'text', value: output };
21
+ }
22
+ if (output == null) {
23
+ return { type: 'text', value: '' };
24
+ }
25
+ return { type: 'json', value: output };
26
+ }
27
+ /** Builds the `tool-call` content part for a tool call or a tool call awaiting/holding an approval decision. */
28
+ function toToolCallContent(part) {
29
+ return {
30
+ type: 'tool-call',
31
+ toolCallId: part.toolCallId,
32
+ toolName: part.toolName,
33
+ input: part.input ?? {} // must be a dict
34
+ };
35
+ }
36
+ /**
37
+ * Resolves the outcome to record beside a decided tool-approval exchange: the refusal itself when
38
+ * the user said no, or the result of the call it authorised. Returns `undefined` when the call was
39
+ * approved but has not produced a result yet.
40
+ *
41
+ * @param part The approval part; `part.approval.approved` must already be defined.
42
+ * @param resultsByToolCallId Completed tool results indexed by call id. The result of an approved
43
+ * call usually streams in with a later message, but may also live on `part` itself already, when
44
+ * the conversation being converted was itself restored from storage.
45
+ * @param convertOutput Converts a completed tool call's recorded output into the wire format.
46
+ */
47
+ function resolveApprovalOutcome(part, resultsByToolCallId, convertOutput) {
48
+ if (!part.approval?.approved) {
49
+ return {
50
+ type: 'tool-result',
51
+ toolCallId: part.toolCallId,
52
+ toolName: part.toolName,
53
+ output: {
54
+ type: 'execution-denied',
55
+ ...(part.approval?.reason ? { reason: part.approval.reason } : {})
56
+ }
57
+ };
58
+ }
59
+ const result = resultsByToolCallId.get(part.toolCallId) ?? (part.output !== undefined ? part : undefined);
60
+ // No result anywhere means the call was approved but has not run yet.
61
+ return result
62
+ ? {
63
+ type: 'tool-result',
64
+ toolCallId: part.toolCallId,
65
+ toolName: part.toolName,
66
+ output: convertOutput(result)
67
+ }
68
+ : undefined;
69
+ }
70
+ /**
71
+ * Splits an assistant message's content into per-step groups at `step-start` boundaries.
72
+ * Parts before the first `step-start` are placed in an implicit first step, and empty groups
73
+ * (from leading, consecutive or trailing `step-start` parts) are dropped.
74
+ *
75
+ * Each step has to become its own message in the wire format, which has no `step-start` part:
76
+ * this ensures text from a later step is never placed after tool calls from an earlier step,
77
+ * which some AI providers forbid.
78
+ */
79
+ function splitIntoSteps(content) {
80
+ const steps = [[]];
81
+ for (const part of content) {
82
+ if (part.type === 'step-start') {
83
+ if (steps[steps.length - 1].length > 0)
84
+ steps.push([]);
85
+ }
86
+ else {
87
+ steps[steps.length - 1].push(part);
88
+ }
89
+ }
90
+ if (steps[steps.length - 1].length === 0)
91
+ steps.pop();
92
+ return steps;
93
+ }
94
+ /**
95
+ * Converts from our UI-friendly message format back to the format the API requires when
96
+ * sending message history back to the agent manager service.
97
+ *
98
+ * Currently the format is Vercel v6 ModelMessage (so this is a little similar to Vercel's convertToModelMessages).
99
+ *
100
+ * We do not bother with details such as reasoning (not worth the tokens) or in-progress tool calls
101
+ * (since this method is only used on completed responses).
102
+ */
103
+ function convertToAgentMessageFormat(messages) {
104
+ return messages.flatMap(message => {
105
+ if (message.role === 'user' || message.role === 'system') {
106
+ return [{ role: message.role, content: message.content }];
107
+ }
108
+ // Each step maps to its own assistant message + optional tool message.
109
+ const steps = splitIntoSteps(message.content);
110
+ const stepMessages = [];
111
+ for (const stepParts of steps) {
112
+ const stepAssistantContent = [];
113
+ const stepToolResultContent = [];
114
+ for (const part of stepParts) {
115
+ if (part.type === 'text' && part.text) {
116
+ stepAssistantContent.push({ type: 'text', text: part.text });
117
+ }
118
+ else if (part.type === 'tool-result') {
119
+ stepAssistantContent.push({
120
+ type: 'tool-call',
121
+ toolCallId: part.toolCallId,
122
+ toolName: part.toolName,
123
+ input: !part.input ? {} : part.input // must be a dict
124
+ });
125
+ stepToolResultContent.push({
126
+ type: 'tool-result',
127
+ toolCallId: part.toolCallId,
128
+ toolName: part.toolName,
129
+ output: convertToolOutputToAgentFormat(part)
130
+ });
131
+ }
132
+ // Reasoning, tool-input-streaming and tool-executing (in-progress calls) are intentionally skipped
133
+ }
134
+ if (stepAssistantContent.length > 0) {
135
+ stepMessages.push({ role: 'assistant', content: stepAssistantContent });
136
+ }
137
+ if (stepToolResultContent.length > 0) {
138
+ stepMessages.push({ role: 'tool', content: stepToolResultContent });
139
+ }
140
+ }
141
+ return stepMessages;
142
+ });
143
+ }
144
+
8
145
  // Internal implementation detail; maybe we don't even need this enum since
9
146
  var DataStreamType;
10
147
  (function (DataStreamType) {
@@ -305,7 +442,10 @@ class AIService {
305
442
  if (m.role !== 'assistant')
306
443
  continue;
307
444
  for (const part of m.content) {
308
- if (part.type === 'tool-result') {
445
+ // A conversation restored from storage carries the result on the approval part itself,
446
+ // rather than in the following message where it originally streamed in.
447
+ if (part.type === 'tool-result' ||
448
+ (part.type === 'tool-approval-responded' && part.output !== undefined)) {
309
449
  resultsByToolCallId.set(part.toolCallId, part);
310
450
  }
311
451
  }
@@ -373,12 +513,7 @@ class AIService {
373
513
  case 'tool-approval-responded':
374
514
  if (!part.approval)
375
515
  break;
376
- assistantContent.push({
377
- type: 'tool-call',
378
- toolCallId: part.toolCallId,
379
- toolName: part.toolName,
380
- input: part.input ?? {}
381
- });
516
+ assistantContent.push(toToolCallContent(part));
382
517
  assistantContent.push({
383
518
  type: 'tool-approval-request',
384
519
  approvalId: part.approval.approvalId,
@@ -392,32 +527,13 @@ class AIService {
392
527
  ...(part.approval.reason ? { reason: part.approval.reason } : {})
393
528
  });
394
529
  if (!serializedToolCallIds.has(part.toolCallId)) {
395
- if (!part.approval.approved) {
396
- toolContent.push({
397
- type: 'tool-result',
398
- toolCallId: part.toolCallId,
399
- toolName: part.toolName,
400
- output: {
401
- type: 'execution-denied',
402
- ...(part.approval.reason ? { reason: part.approval.reason } : {})
403
- }
404
- });
530
+ // No outcome means the call was approved but has not run yet: this approval is the
531
+ // one triggering the resume, and the agent executes the tool itself.
532
+ const outcome = resolveApprovalOutcome(part, resultsByToolCallId, result => this.convertToolResultOutputForAgent(result));
533
+ if (outcome) {
534
+ toolContent.push(outcome);
405
535
  serializedToolCallIds.add(part.toolCallId);
406
536
  }
407
- else {
408
- const result = resultsByToolCallId.get(part.toolCallId);
409
- if (result) {
410
- toolContent.push({
411
- type: 'tool-result',
412
- toolCallId: part.toolCallId,
413
- toolName: part.toolName,
414
- output: this.convertToolResultOutputForAgent(result)
415
- });
416
- serializedToolCallIds.add(part.toolCallId);
417
- }
418
- // No result yet: this approval is the one triggering the resume,
419
- // and the agent executes the tool itself.
420
- }
421
537
  }
422
538
  }
423
539
  break;
@@ -425,12 +541,7 @@ class AIService {
425
541
  // Skip results already serialized next to their approval exchange in an earlier message
426
542
  if (serializedToolCallIds.has(part.toolCallId))
427
543
  break;
428
- assistantContent.push({
429
- type: 'tool-call',
430
- toolCallId: part.toolCallId,
431
- toolName: part.toolName,
432
- input: part.input ?? {}
433
- });
544
+ assistantContent.push(toToolCallContent(part));
434
545
  toolContent.push({
435
546
  type: 'tool-result',
436
547
  toolCallId: part.toolCallId,
@@ -666,22 +777,10 @@ function defaultPruneMessagesForAgent(messages) {
666
777
  // Tool calls that went through an approval exchange must keep their results as well:
667
778
  // the wire format pairs each resolved approval with its result, so pruning the result
668
779
  // would leave the approved tool call dangling in the prompt sent to the model.
669
- const approvalToolCallIds = new Set();
670
- for (const m of messages) {
671
- if (m.role !== 'assistant')
672
- continue;
673
- for (const part of m.content) {
674
- if (part.type === 'tool-approval-requested' || part.type === 'tool-approval-responded') {
675
- approvalToolCallIds.add(part.toolCallId);
676
- }
677
- }
678
- }
780
+ const approvalToolCallIds = collectApprovalToolCallIds(messages);
679
781
  return messages.map(m => {
680
782
  if (m.role !== 'assistant') {
681
- return {
682
- role: m.role,
683
- content: m.content
684
- };
783
+ return { role: m.role, content: m.content };
685
784
  }
686
785
  const textParts = m.content.filter((part) => part.type === 'text');
687
786
  const objectParts = m.content.filter((part) => part.type === 'object');
@@ -691,22 +790,364 @@ function defaultPruneMessagesForAgent(messages) {
691
790
  part.type === 'tool-approval-responded' ||
692
791
  (part.type === 'tool-result' && approvalToolCallIds.has(part.toolCallId)));
693
792
  const content = [];
694
- // Only add text part if there are text parts
695
793
  if (textParts.length > 0) {
696
- content.push({
697
- type: 'text',
698
- text: textParts.map(part => part.text).join('\n\n')
699
- });
794
+ content.push({ type: 'text', text: textParts.map(part => part.text).join('\n\n') });
700
795
  }
701
- // Preserve object parts for object agents
702
796
  content.push(...objectParts);
703
797
  content.push(...approvalParts);
704
- return {
705
- role: m.role,
706
- content
707
- };
798
+ return { role: m.role, content };
799
+ });
800
+ }
801
+ /**
802
+ * The default implementation for preparing messages for conversation persistence.
803
+ *
804
+ * Unlike `defaultPruneMessagesForAgent` (which strips tool calls for token efficiency),
805
+ * this preserves tool call history so stored conversations can be fully restored in the UI.
806
+ *
807
+ * Strips only transient streaming states that should never be persisted:
808
+ * reasoning blocks, in-progress tool-input-streaming, and tool-executing parts.
809
+ *
810
+ * Tool outputs longer than 10,000 characters are truncated, as the storage service rejects
811
+ * conversations of about 1 MB and above. `defaultPruneMessagesForAgent` never sends these outputs
812
+ * back to the agent, so the truncation only shows in the UI once the conversation is reopened.
813
+ * The results of tool calls that went through an approval are the exception: the agent does get
814
+ * them back, so they are kept whole for a reopened conversation to send it the same history.
815
+ *
816
+ * Step boundaries are kept because `toConversationMessages`, which converts the result into the
817
+ * format the storage service accepts, needs them to split a multi-step turn into one message per step.
818
+ *
819
+ * Implements `pruneMessagesFunction`.
820
+ */
821
+ function defaultPruneMessagesForPersistence(messages) {
822
+ const approvalToolCallIds = collectApprovalToolCallIds(messages);
823
+ return messages.map(m => {
824
+ if (m.role !== 'assistant') {
825
+ return m;
826
+ }
827
+ const content = m.content
828
+ .filter(part => part.type === 'step-start' ||
829
+ part.type === 'text' ||
830
+ part.type === 'tool-result' ||
831
+ part.type === 'object' ||
832
+ // Approval parts carry the user's decision on a tool call, including a refusal, which is
833
+ // the only record that the call was attempted at all.
834
+ part.type === 'tool-approval-requested' ||
835
+ part.type === 'tool-approval-responded')
836
+ .map(part => part.type === 'tool-result' && !approvalToolCallIds.has(part.toolCallId)
837
+ ? truncateToolOutput(part)
838
+ : part);
839
+ return { ...m, content };
708
840
  });
709
841
  }
842
+ const MAX_PERSISTED_TOOL_OUTPUT_LENGTH = 10_000;
843
+ /** Matches the note `truncateToolOutput` appends to the outputs it cuts. */
844
+ const TRUNCATED_OUTPUT_PATTERN = /… \[truncated to \d+ of \d+ characters\]$/;
845
+ /**
846
+ * Cuts a tool output longer than `MAX_PERSISTED_TOOL_OUTPUT_LENGTH` characters (of its JSON form,
847
+ * for a structured output) down to that length. The cut no longer parses, so it is kept as text.
848
+ */
849
+ function truncateToolOutput(part) {
850
+ const serialized = typeof part.output === 'string' ? part.output : JSON.stringify(part.output);
851
+ if (!serialized || serialized.length <= MAX_PERSISTED_TOOL_OUTPUT_LENGTH) {
852
+ return part;
853
+ }
854
+ // Cut when the conversation was saved before, so it already notes the original size, which
855
+ // cutting it again would replace with the size of the cut.
856
+ if (typeof part.output === 'string' && TRUNCATED_OUTPUT_PATTERN.test(part.output)) {
857
+ return part;
858
+ }
859
+ return {
860
+ ...part,
861
+ output: `${serialized.slice(0, MAX_PERSISTED_TOOL_OUTPUT_LENGTH)}… [truncated to ${MAX_PERSISTED_TOOL_OUTPUT_LENGTH} of ${serialized.length} characters]`,
862
+ outputType: undefined
863
+ };
864
+ }
865
+ /** Collects the ids of the tool calls that went through an approval exchange. */
866
+ function collectApprovalToolCallIds(messages) {
867
+ const ids = new Set();
868
+ for (const m of messages) {
869
+ if (m.role !== 'assistant')
870
+ continue;
871
+ for (const part of m.content) {
872
+ if (part.type === 'tool-approval-requested' || part.type === 'tool-approval-responded') {
873
+ ids.add(part.toolCallId);
874
+ }
875
+ }
876
+ }
877
+ return ids;
878
+ }
879
+
880
+ /**
881
+ * Converts chat messages into the format accepted by the conversation storage API.
882
+ *
883
+ * Unlike the history sent to the agent, tool calls are preserved so a stored conversation can be
884
+ * restored with its full transcript by `fromConversationMessages`. Note that:
885
+ * - each step of a multi-step assistant turn becomes its own message, since the wire format has no
886
+ * `step-start` part — `fromConversationMessages` rebuilds the boundaries from the message split;
887
+ * - a tool call that required approval keeps its full exchange: the `tool-approval-request` beside
888
+ * the call, and the user's `tool-approval-response` beside the outcome, which for a refused call
889
+ * is an `execution-denied` result;
890
+ * - the agent that produced an assistant turn is recorded on every assistant message of that turn,
891
+ * so it survives pruning of individual steps;
892
+ * - `object` parts are stored as text, as the wire format has no structured-object equivalent;
893
+ * - `system` messages are dropped, as the service rejects them: a conversation's system prompt
894
+ * comes from the agent's server-side configuration, never from stored history;
895
+ * - reasoning and in-progress tool calls are dropped, as they are not worth persisting.
896
+ *
897
+ * @param messages The chat messages to store, typically already pruned for persistence.
898
+ */
899
+ function toConversationMessages(messages) {
900
+ // The result of an approved tool call streams in with a later assistant message, but on the wire
901
+ // it has to sit beside its approval exchange, so index every result up front.
902
+ const resultsByToolCallId = new Map();
903
+ for (const message of messages) {
904
+ if (message.role !== 'assistant')
905
+ continue;
906
+ for (const part of message.content) {
907
+ if (part.type === 'tool-result')
908
+ resultsByToolCallId.set(part.toolCallId, part);
909
+ }
910
+ }
911
+ // Tracks calls already written beside their approval, so the copy carried by a later message is
912
+ // not written a second time.
913
+ const serializedToolCallIds = new Set();
914
+ return messages.flatMap(message => {
915
+ if (message.role === 'system') {
916
+ return [];
917
+ }
918
+ if (message.role === 'user') {
919
+ return [withDisplayMetadata({ role: 'user', content: message.content }, message)];
920
+ }
921
+ const stored = [];
922
+ for (const stepParts of splitIntoSteps(message.content)) {
923
+ stored.push(...buildStepMessages(stepParts, resultsByToolCallId, serializedToolCallIds));
924
+ }
925
+ if (stored.length > 0) {
926
+ stored[0] = withDisplayMetadata(stored[0], message);
927
+ }
928
+ return message.agentName
929
+ ? stored.map(m => (m.role === 'assistant' ? { ...m, agentName: message.agentName } : m))
930
+ : stored;
931
+ });
932
+ }
933
+ /**
934
+ * Converts one step's parts into the `assistant` message it maps to, paired with a `tool` message
935
+ * when the step carries tool results or approval decisions.
936
+ *
937
+ * @param resultsByToolCallId Completed tool results indexed by call id, so an approved call's result
938
+ * (which streams in with a later message) can be matched to the approval it belongs beside.
939
+ * @param serializedToolCallIds Tracks calls already written beside their approval, so the copy
940
+ * carried by a later message is not written a second time.
941
+ */
942
+ function buildStepMessages(stepParts, resultsByToolCallId, serializedToolCallIds) {
943
+ const assistantContent = [];
944
+ const toolContent = [];
945
+ for (const part of stepParts) {
946
+ if (part.type === 'text' && part.text) {
947
+ assistantContent.push({ type: 'text', text: part.text });
948
+ }
949
+ else if (part.type === 'object') {
950
+ assistantContent.push({ type: 'text', text: part.jsonContent });
951
+ }
952
+ else if (part.type === 'tool-approval-requested' || part.type === 'tool-approval-responded') {
953
+ if (!part.approval)
954
+ continue;
955
+ assistantContent.push(toToolCallContent(part));
956
+ assistantContent.push({
957
+ type: 'tool-approval-request',
958
+ approvalId: part.approval.approvalId,
959
+ toolCallId: part.toolCallId
960
+ });
961
+ // An undecided approval is stored as the pending request alone.
962
+ if (part.approval.approved === undefined)
963
+ continue;
964
+ toolContent.push({
965
+ type: 'tool-approval-response',
966
+ approvalId: part.approval.approvalId,
967
+ approved: part.approval.approved,
968
+ ...(part.approval.reason ? { reason: part.approval.reason } : {})
969
+ });
970
+ if (serializedToolCallIds.has(part.toolCallId))
971
+ continue;
972
+ const outcome = resolveApprovalOutcome(part, resultsByToolCallId, convertToolOutputToAgentFormat);
973
+ if (outcome) {
974
+ toolContent.push(outcome);
975
+ serializedToolCallIds.add(part.toolCallId);
976
+ }
977
+ }
978
+ else if (part.type === 'tool-result') {
979
+ if (serializedToolCallIds.has(part.toolCallId))
980
+ continue;
981
+ assistantContent.push(toToolCallContent(part));
982
+ toolContent.push({
983
+ type: 'tool-result',
984
+ toolCallId: part.toolCallId,
985
+ toolName: part.toolName,
986
+ output: convertToolOutputToAgentFormat(part)
987
+ });
988
+ }
989
+ }
990
+ const stepMessages = [];
991
+ if (assistantContent.length > 0) {
992
+ stepMessages.push({ role: 'assistant', content: assistantContent });
993
+ }
994
+ if (toolContent.length > 0) {
995
+ stepMessages.push({ role: 'tool', content: toolContent });
996
+ }
997
+ return stepMessages;
998
+ }
999
+ /**
1000
+ * Restores stored messages into the format the chat renders, inverting `toConversationMessages`.
1001
+ *
1002
+ * The consecutive assistant and tool messages that one assistant turn was split into are merged
1003
+ * back into a single message, with a `step-start` part marking each boundary, and each tool call
1004
+ * is reunited with its result and, where it needed one, with the user's approval decision.
1005
+ *
1006
+ * A restored approved call carries its result on the approval part itself, rather than in the
1007
+ * following message where it originally streamed in. The chat renders that identically, and a
1008
+ * restored decision is never re-actionable: the approval buttons only appear on the latest message.
1009
+ *
1010
+ * Note that the wire format marks where a step ended but not where a turn did, so assistant
1011
+ * messages that followed one another without the user speaking in between — an approval and the
1012
+ * response resumed from it, for example — come back merged into a single turn. Every part and its
1013
+ * order is preserved; only the split into separate messages is not. The merged turn carries the
1014
+ * sum of their token usage and the timestamp of the last one. Messages recorded as coming from
1015
+ * different agents are never merged.
1016
+ *
1017
+ * @param messages The messages of a stored conversation.
1018
+ */
1019
+ function fromConversationMessages(messages) {
1020
+ const restored = [];
1021
+ // The assistant turn currently being rebuilt, or undefined when the last message was not one.
1022
+ let turn;
1023
+ for (const message of messages) {
1024
+ if (message.role === 'tool') {
1025
+ for (const part of message.content) {
1026
+ if (part.type === 'tool-approval-response') {
1027
+ applyApprovalResponse(turn, part);
1028
+ }
1029
+ else {
1030
+ applyToolResult(turn, part);
1031
+ }
1032
+ }
1033
+ }
1034
+ else if (message.role === 'assistant') {
1035
+ // A different agent answering starts a new turn, even without the user speaking in between.
1036
+ const agentChanged = !!turn?.agentName && !!message.agentName && turn.agentName !== message.agentName;
1037
+ if (!turn || agentChanged) {
1038
+ turn = {
1039
+ role: 'assistant',
1040
+ content: [],
1041
+ ...(message.timestamp ? { timestamp: message.timestamp } : {}),
1042
+ ...(message.usage ? { usage: message.usage } : {}),
1043
+ ...(message.agentName ? { agentName: message.agentName } : {})
1044
+ };
1045
+ restored.push(turn);
1046
+ }
1047
+ else {
1048
+ if (!turn.agentName && message.agentName)
1049
+ turn.agentName = message.agentName;
1050
+ // Only the first stored message of a chat message carries its metadata, so a later one with
1051
+ // it was a separate chat message, such as the response resumed after a tool approval.
1052
+ if (message.usage)
1053
+ turn.usage = addUsage(turn.usage, message.usage);
1054
+ if (message.timestamp)
1055
+ turn.timestamp = message.timestamp;
1056
+ turn.content.push({ type: 'step-start' });
1057
+ }
1058
+ // Another client may store an assistant message as plain text, which the wire format allows.
1059
+ const content = typeof message.content === 'string'
1060
+ ? [{ type: 'text', text: message.content }]
1061
+ : message.content;
1062
+ for (const part of content) {
1063
+ if (part.type === 'text') {
1064
+ turn.content.push({ type: 'text', text: part.text });
1065
+ }
1066
+ else if (part.type === 'tool-call') {
1067
+ // Stored as a completed call: its output is filled in from the tool message that follows.
1068
+ turn.content.push({
1069
+ type: 'tool-result',
1070
+ toolName: part.toolName,
1071
+ toolCallId: part.toolCallId,
1072
+ input: part.input
1073
+ });
1074
+ }
1075
+ else if (part.type === 'tool-approval-request') {
1076
+ const call = findToolCall(turn, part.toolCallId);
1077
+ if (call) {
1078
+ call.type = 'tool-approval-requested';
1079
+ call.approval = { approvalId: part.approvalId };
1080
+ }
1081
+ }
1082
+ }
1083
+ }
1084
+ else {
1085
+ turn = undefined;
1086
+ restored.push({
1087
+ role: message.role,
1088
+ content: message.content,
1089
+ ...(message.timestamp ? { timestamp: message.timestamp } : {})
1090
+ });
1091
+ }
1092
+ }
1093
+ return restored;
1094
+ }
1095
+ function withDisplayMetadata(stored, source) {
1096
+ return {
1097
+ ...stored,
1098
+ ...(source.timestamp ? { timestamp: source.timestamp } : {}),
1099
+ ...(source.role === 'assistant' && source.usage ? { usage: source.usage } : {})
1100
+ };
1101
+ }
1102
+ function addUsage(total, usage) {
1103
+ if (!total)
1104
+ return usage;
1105
+ return {
1106
+ inputTokens: (total.inputTokens ?? 0) + (usage.inputTokens ?? 0),
1107
+ outputTokens: (total.outputTokens ?? 0) + (usage.outputTokens ?? 0),
1108
+ totalTokens: (total.totalTokens ?? 0) + (usage.totalTokens ?? 0)
1109
+ };
1110
+ }
1111
+ function findToolCall(turn, toolCallId) {
1112
+ return turn?.content.find((candidate) => 'toolCallId' in candidate && candidate.toolCallId === toolCallId);
1113
+ }
1114
+ function applyApprovalResponse(turn, part) {
1115
+ const call = turn?.content.find((candidate) => 'approval' in candidate && candidate.approval?.approvalId === part.approvalId);
1116
+ if (!call) {
1117
+ return;
1118
+ }
1119
+ call.type = 'tool-approval-responded';
1120
+ call.approval = {
1121
+ approvalId: part.approvalId,
1122
+ approved: part.approved,
1123
+ ...(part.reason ? { reason: part.reason } : {})
1124
+ };
1125
+ }
1126
+ function applyToolResult(turn, part) {
1127
+ const call = findToolCall(turn, part.toolCallId);
1128
+ if (!call) {
1129
+ return;
1130
+ }
1131
+ const output = part.output;
1132
+ switch (part.output.type) {
1133
+ case 'execution-denied':
1134
+ // A refused call never ran, so it has no output — the decision itself is on the approval part.
1135
+ break;
1136
+ case 'content':
1137
+ call.output = output.value;
1138
+ // Marks the output as a multi-part content array so it can be sent back to the agent as one.
1139
+ call.outputType = 'content';
1140
+ break;
1141
+ case 'error-text':
1142
+ case 'error-json':
1143
+ call.output = output.value;
1144
+ call.error = true;
1145
+ break;
1146
+ default:
1147
+ // text and json outputs both restore to the plain value the UI renders.
1148
+ call.output = output.value;
1149
+ }
1150
+ }
710
1151
 
711
1152
  /**
712
1153
  * Remembers the user's "always approve" decisions for tool calls that require human approval.
@@ -852,5 +1293,5 @@ function summarizeQuery(query, translateService) {
852
1293
  * Generated bundle index. Do not edit.
853
1294
  */
854
1295
 
855
- export { AIService, AiToolApprovalMemoryService, DataStreamType, cumulocityApiRequestLabelProvider, defaultPruneMessagesForAgent };
1296
+ export { AIService, AiToolApprovalMemoryService, DataStreamType, cumulocityApiRequestLabelProvider, defaultPruneMessagesForAgent, defaultPruneMessagesForPersistence, fromConversationMessages, toConversationMessages };
856
1297
  //# sourceMappingURL=c8y-ngx-components-ai.mjs.map