truthguard-ai 0.1.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.

Potentially problematic release.


This version of truthguard-ai might be problematic. Click here for more details.

Files changed (199) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +363 -0
  3. package/dist/Advisor/index.d.ts +78 -0
  4. package/dist/Advisor/index.d.ts.map +1 -0
  5. package/dist/Advisor/index.js +539 -0
  6. package/dist/Advisor/index.js.map +1 -0
  7. package/dist/Alerting/index.d.ts +35 -0
  8. package/dist/Alerting/index.d.ts.map +1 -0
  9. package/dist/Alerting/index.js +76 -0
  10. package/dist/Alerting/index.js.map +1 -0
  11. package/dist/Auth/index.d.ts +82 -0
  12. package/dist/Auth/index.d.ts.map +1 -0
  13. package/dist/Auth/index.js +242 -0
  14. package/dist/Auth/index.js.map +1 -0
  15. package/dist/Baseline/index.d.ts +43 -0
  16. package/dist/Baseline/index.d.ts.map +1 -0
  17. package/dist/Baseline/index.js +195 -0
  18. package/dist/Baseline/index.js.map +1 -0
  19. package/dist/Claims/index.d.ts +73 -0
  20. package/dist/Claims/index.d.ts.map +1 -0
  21. package/dist/Claims/index.js +1669 -0
  22. package/dist/Claims/index.js.map +1 -0
  23. package/dist/Client/index.d.ts +90 -0
  24. package/dist/Client/index.d.ts.map +1 -0
  25. package/dist/Client/index.js +186 -0
  26. package/dist/Client/index.js.map +1 -0
  27. package/dist/Config/index.d.ts +41 -0
  28. package/dist/Config/index.d.ts.map +1 -0
  29. package/dist/Config/index.js +129 -0
  30. package/dist/Config/index.js.map +1 -0
  31. package/dist/Coverage/index.d.ts +28 -0
  32. package/dist/Coverage/index.d.ts.map +1 -0
  33. package/dist/Coverage/index.js +134 -0
  34. package/dist/Coverage/index.js.map +1 -0
  35. package/dist/Demo/index.d.ts +16 -0
  36. package/dist/Demo/index.d.ts.map +1 -0
  37. package/dist/Demo/index.js +189 -0
  38. package/dist/Demo/index.js.map +1 -0
  39. package/dist/Gate/index.d.ts +39 -0
  40. package/dist/Gate/index.d.ts.map +1 -0
  41. package/dist/Gate/index.js +207 -0
  42. package/dist/Gate/index.js.map +1 -0
  43. package/dist/Grounding/index.d.ts +40 -0
  44. package/dist/Grounding/index.d.ts.map +1 -0
  45. package/dist/Grounding/index.js +1433 -0
  46. package/dist/Grounding/index.js.map +1 -0
  47. package/dist/L2/index.d.ts +93 -0
  48. package/dist/L2/index.d.ts.map +1 -0
  49. package/dist/L2/index.js +1773 -0
  50. package/dist/L2/index.js.map +1 -0
  51. package/dist/MCP/index.d.ts +139 -0
  52. package/dist/MCP/index.d.ts.map +1 -0
  53. package/dist/MCP/index.js +1250 -0
  54. package/dist/MCP/index.js.map +1 -0
  55. package/dist/Matchers/index.d.ts +101 -0
  56. package/dist/Matchers/index.d.ts.map +1 -0
  57. package/dist/Matchers/index.js +690 -0
  58. package/dist/Matchers/index.js.map +1 -0
  59. package/dist/Middleware/index.d.ts +146 -0
  60. package/dist/Middleware/index.d.ts.map +1 -0
  61. package/dist/Middleware/index.js +239 -0
  62. package/dist/Middleware/index.js.map +1 -0
  63. package/dist/Mode/index.d.ts +87 -0
  64. package/dist/Mode/index.d.ts.map +1 -0
  65. package/dist/Mode/index.js +117 -0
  66. package/dist/Mode/index.js.map +1 -0
  67. package/dist/Policy/index.d.ts +89 -0
  68. package/dist/Policy/index.d.ts.map +1 -0
  69. package/dist/Policy/index.js +143 -0
  70. package/dist/Policy/index.js.map +1 -0
  71. package/dist/Proxy/SessionStore.d.ts +94 -0
  72. package/dist/Proxy/SessionStore.d.ts.map +1 -0
  73. package/dist/Proxy/SessionStore.js +225 -0
  74. package/dist/Proxy/SessionStore.js.map +1 -0
  75. package/dist/Proxy/index.d.ts +166 -0
  76. package/dist/Proxy/index.d.ts.map +1 -0
  77. package/dist/Proxy/index.js +531 -0
  78. package/dist/Proxy/index.js.map +1 -0
  79. package/dist/Registry/index.d.ts +93 -0
  80. package/dist/Registry/index.d.ts.map +1 -0
  81. package/dist/Registry/index.js +818 -0
  82. package/dist/Registry/index.js.map +1 -0
  83. package/dist/Reports/index.d.ts +38 -0
  84. package/dist/Reports/index.d.ts.map +1 -0
  85. package/dist/Reports/index.js +149 -0
  86. package/dist/Reports/index.js.map +1 -0
  87. package/dist/Rules/index.d.ts +587 -0
  88. package/dist/Rules/index.d.ts.map +1 -0
  89. package/dist/Rules/index.js +6236 -0
  90. package/dist/Rules/index.js.map +1 -0
  91. package/dist/Rules/intents.d.ts +22 -0
  92. package/dist/Rules/intents.d.ts.map +1 -0
  93. package/dist/Rules/intents.js +242 -0
  94. package/dist/Rules/intents.js.map +1 -0
  95. package/dist/Runner/index.d.ts +39 -0
  96. package/dist/Runner/index.d.ts.map +1 -0
  97. package/dist/Runner/index.js +185 -0
  98. package/dist/Runner/index.js.map +1 -0
  99. package/dist/SDK/anthropic.d.ts +102 -0
  100. package/dist/SDK/anthropic.d.ts.map +1 -0
  101. package/dist/SDK/anthropic.js +425 -0
  102. package/dist/SDK/anthropic.js.map +1 -0
  103. package/dist/SDK/openai.d.ts +164 -0
  104. package/dist/SDK/openai.d.ts.map +1 -0
  105. package/dist/SDK/openai.js +557 -0
  106. package/dist/SDK/openai.js.map +1 -0
  107. package/dist/Store/index.d.ts +72 -0
  108. package/dist/Store/index.d.ts.map +1 -0
  109. package/dist/Store/index.js +136 -0
  110. package/dist/Store/index.js.map +1 -0
  111. package/dist/Telemetry/index.d.ts +84 -0
  112. package/dist/Telemetry/index.d.ts.map +1 -0
  113. package/dist/Telemetry/index.js +239 -0
  114. package/dist/Telemetry/index.js.map +1 -0
  115. package/dist/Trace/index.d.ts +219 -0
  116. package/dist/Trace/index.d.ts.map +1 -0
  117. package/dist/Trace/index.js +763 -0
  118. package/dist/Trace/index.js.map +1 -0
  119. package/dist/TraceReadiness/index.d.ts +42 -0
  120. package/dist/TraceReadiness/index.d.ts.map +1 -0
  121. package/dist/TraceReadiness/index.js +169 -0
  122. package/dist/TraceReadiness/index.js.map +1 -0
  123. package/dist/cli/index.d.ts +15 -0
  124. package/dist/cli/index.d.ts.map +1 -0
  125. package/dist/cli/index.js +807 -0
  126. package/dist/cli/index.js.map +1 -0
  127. package/dist/i18n/index.d.ts +44 -0
  128. package/dist/i18n/index.d.ts.map +1 -0
  129. package/dist/i18n/index.js +124 -0
  130. package/dist/i18n/index.js.map +1 -0
  131. package/dist/index.d.ts +55 -0
  132. package/dist/index.d.ts.map +1 -0
  133. package/dist/index.js +218 -0
  134. package/dist/index.js.map +1 -0
  135. package/dist/thin.d.ts +39 -0
  136. package/dist/thin.d.ts.map +1 -0
  137. package/dist/thin.js +120 -0
  138. package/dist/thin.js.map +1 -0
  139. package/dist/types/index.d.ts +498 -0
  140. package/dist/types/index.d.ts.map +1 -0
  141. package/dist/types/index.js +17 -0
  142. package/dist/types/index.js.map +1 -0
  143. package/dist-npm/Alerting/index.d.ts +35 -0
  144. package/dist-npm/Alerting/index.d.ts.map +1 -0
  145. package/dist-npm/Alerting/index.js +76 -0
  146. package/dist-npm/Alerting/index.js.map +1 -0
  147. package/dist-npm/Auth/index.d.ts +82 -0
  148. package/dist-npm/Auth/index.d.ts.map +1 -0
  149. package/dist-npm/Auth/index.js +242 -0
  150. package/dist-npm/Auth/index.js.map +1 -0
  151. package/dist-npm/Client/index.d.ts +90 -0
  152. package/dist-npm/Client/index.d.ts.map +1 -0
  153. package/dist-npm/Client/index.js +186 -0
  154. package/dist-npm/Client/index.js.map +1 -0
  155. package/dist-npm/Demo/index.d.ts +16 -0
  156. package/dist-npm/Demo/index.d.ts.map +1 -0
  157. package/dist-npm/Demo/index.js +189 -0
  158. package/dist-npm/Demo/index.js.map +1 -0
  159. package/dist-npm/Middleware/index.d.ts +146 -0
  160. package/dist-npm/Middleware/index.d.ts.map +1 -0
  161. package/dist-npm/Middleware/index.js +239 -0
  162. package/dist-npm/Middleware/index.js.map +1 -0
  163. package/dist-npm/Proxy/SessionStore.d.ts +94 -0
  164. package/dist-npm/Proxy/SessionStore.d.ts.map +1 -0
  165. package/dist-npm/Proxy/SessionStore.js +225 -0
  166. package/dist-npm/Proxy/SessionStore.js.map +1 -0
  167. package/dist-npm/Proxy/index.d.ts +166 -0
  168. package/dist-npm/Proxy/index.d.ts.map +1 -0
  169. package/dist-npm/Proxy/index.js +531 -0
  170. package/dist-npm/Proxy/index.js.map +1 -0
  171. package/dist-npm/SDK/anthropic.d.ts +102 -0
  172. package/dist-npm/SDK/anthropic.d.ts.map +1 -0
  173. package/dist-npm/SDK/anthropic.js +425 -0
  174. package/dist-npm/SDK/anthropic.js.map +1 -0
  175. package/dist-npm/SDK/openai.d.ts +164 -0
  176. package/dist-npm/SDK/openai.d.ts.map +1 -0
  177. package/dist-npm/SDK/openai.js +557 -0
  178. package/dist-npm/SDK/openai.js.map +1 -0
  179. package/dist-npm/Store/index.d.ts +72 -0
  180. package/dist-npm/Store/index.d.ts.map +1 -0
  181. package/dist-npm/Store/index.js +136 -0
  182. package/dist-npm/Store/index.js.map +1 -0
  183. package/dist-npm/Telemetry/index.d.ts +84 -0
  184. package/dist-npm/Telemetry/index.d.ts.map +1 -0
  185. package/dist-npm/Telemetry/index.js +239 -0
  186. package/dist-npm/Telemetry/index.js.map +1 -0
  187. package/dist-npm/Trace/index.d.ts +219 -0
  188. package/dist-npm/Trace/index.d.ts.map +1 -0
  189. package/dist-npm/Trace/index.js +763 -0
  190. package/dist-npm/Trace/index.js.map +1 -0
  191. package/dist-npm/thin.d.ts +39 -0
  192. package/dist-npm/thin.d.ts.map +1 -0
  193. package/dist-npm/thin.js +120 -0
  194. package/dist-npm/thin.js.map +1 -0
  195. package/dist-npm/types/index.d.ts +498 -0
  196. package/dist-npm/types/index.d.ts.map +1 -0
  197. package/dist-npm/types/index.js +17 -0
  198. package/dist-npm/types/index.js.map +1 -0
  199. package/package.json +114 -0
@@ -0,0 +1,763 @@
1
+ "use strict";
2
+ /**
3
+ * Trace SDK
4
+ *
5
+ * Provides a fluent builder for constructing agent execution traces and
6
+ * utility helpers for querying them.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.TraceUtils = exports.TraceBuilder = void 0;
10
+ const crypto_1 = require("crypto");
11
+ /**
12
+ * Fluent builder for constructing a {@link Trace}.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const trace = new TraceBuilder({ metadata: { sessionId: 'abc' } })
17
+ * .addUserInput('How many employees are on leave today?')
18
+ * .addToolCall('getLeaveRecords', { date: '2024-03-15' })
19
+ * .addToolOutput('getLeaveRecords', [{ employeeId: 'E01', name: 'Ana' }, { employeeId: 'E02', name: 'Ivo' }])
20
+ * .addFinalResponse('There are 2 employees on leave today.')
21
+ * .build();
22
+ * ```
23
+ */
24
+ class TraceBuilder {
25
+ constructor(options = {}) {
26
+ this.steps = [];
27
+ this.traceId = options.traceId ?? (0, crypto_1.randomUUID)();
28
+ this.metadata = options.metadata ?? {};
29
+ this.startedAt = new Date().toISOString();
30
+ }
31
+ /** Add a system prompt step (static context/instructions). */
32
+ addSystemPrompt(content, meta) {
33
+ return this.addStep('system_prompt', content, meta);
34
+ }
35
+ /** Add a user input step. */
36
+ addUserInput(content, meta) {
37
+ return this.addStep('user_input', content, meta);
38
+ }
39
+ /** Add a tool call step. */
40
+ addToolCall(toolName, parameters, meta) {
41
+ const toolCall = {
42
+ toolName,
43
+ parameters,
44
+ calledAt: new Date().toISOString(),
45
+ };
46
+ return this.addStep('tool_call', `Tool call: ${toolName}`, {
47
+ ...meta,
48
+ toolCalls: [...(meta?.toolCalls ?? []), toolCall],
49
+ });
50
+ }
51
+ /** Add a tool output step. */
52
+ addToolOutput(toolName, output, meta) {
53
+ const toolOutput = {
54
+ toolName,
55
+ output,
56
+ receivedAt: new Date().toISOString(),
57
+ };
58
+ return this.addStep('tool_output', `Tool output: ${toolName}`, {
59
+ ...meta,
60
+ toolOutputs: [...(meta?.toolOutputs ?? []), toolOutput],
61
+ });
62
+ }
63
+ /** Add an LLM call step (e.g., intermediate reasoning). */
64
+ addLlmCall(content, meta) {
65
+ return this.addStep('llm_call', content, meta);
66
+ }
67
+ /** Add the final response step. */
68
+ addFinalResponse(content, meta) {
69
+ return this.addStep('final_response', content, meta);
70
+ }
71
+ /** Low-level step addition. */
72
+ addStep(role, content, meta) {
73
+ const step = {
74
+ stepId: (0, crypto_1.randomUUID)(),
75
+ role,
76
+ content,
77
+ startedAt: new Date().toISOString(),
78
+ ...meta,
79
+ };
80
+ this.steps.push(step);
81
+ return this;
82
+ }
83
+ /** Build and return the completed {@link Trace}. */
84
+ build() {
85
+ const endedAt = new Date().toISOString();
86
+ const startMs = new Date(this.startedAt).getTime();
87
+ const endMs = new Date(endedAt).getTime();
88
+ return {
89
+ traceId: this.traceId,
90
+ steps: this.steps,
91
+ startedAt: this.startedAt,
92
+ endedAt,
93
+ totalLatencyMs: endMs - startMs,
94
+ metadata: this.metadata,
95
+ };
96
+ }
97
+ }
98
+ exports.TraceBuilder = TraceBuilder;
99
+ // ---------------------------------------------------------------------------
100
+ // Period inference helpers
101
+ // ---------------------------------------------------------------------------
102
+ const MONTH_NAMES = {
103
+ january: '01', jan: '01', februar: '02', february: '02', feb: '02',
104
+ mart: '03', march: '03', mar: '03', april: '04', apr: '04',
105
+ maj: '05', may: '05', jun: '06', june: '06', jul: '07', july: '07',
106
+ avgust: '08', august: '08', aug: '08', septembar: '09', september: '09', sep: '09',
107
+ oktobar: '10', october: '10', oct: '10', novembar: '11', november: '11', nov: '11',
108
+ decembar: '12', december: '12', dec: '12',
109
+ /* Serbian cyrillic-latin additional forms */
110
+ januar: '01',
111
+ };
112
+ /** Find a param value by trying multiple key names (case-insensitive). */
113
+ function findParam(params, keys) {
114
+ for (const k of keys) {
115
+ const kLower = k.toLowerCase();
116
+ for (const [pk, pv] of Object.entries(params)) {
117
+ if (pk.toLowerCase() === kLower)
118
+ return pv;
119
+ }
120
+ }
121
+ return undefined;
122
+ }
123
+ /** Find a date-like param and normalise to ISO date string. */
124
+ function findDateParam(params, keys) {
125
+ const val = findParam(params, keys);
126
+ if (val === undefined || val === null)
127
+ return undefined;
128
+ const s = String(val).trim();
129
+ // Already ISO: "2024-03-15" or "2024-03"
130
+ if (/^\d{4}-\d{2}(-\d{2})?$/.test(s))
131
+ return s;
132
+ // European: "15.03.2024" → "2024-03-15"
133
+ const eu = s.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})\.?$/);
134
+ if (eu)
135
+ return `${eu[3]}-${eu[2].padStart(2, '0')}-${eu[1].padStart(2, '0')}`;
136
+ return s; // pass-through as-is
137
+ }
138
+ /** Normalise a month value like "2024-01", "January 2024", "januar 2024" → "2024-MM". */
139
+ function normaliseMonth(raw) {
140
+ const trimmed = raw.trim();
141
+ // Already ISO month: "2024-01"
142
+ if (/^\d{4}-\d{2}$/.test(trimmed))
143
+ return trimmed;
144
+ // "January 2024" or "januar 2024"
145
+ const parts = trimmed.split(/\s+/);
146
+ if (parts.length === 2) {
147
+ const monthNum = MONTH_NAMES[parts[0].toLowerCase()];
148
+ const year = parts[1];
149
+ if (monthNum && /^\d{4}$/.test(year))
150
+ return `${year}-${monthNum}`;
151
+ // Reversed: "2024 January"
152
+ const monthNum2 = MONTH_NAMES[parts[1].toLowerCase()];
153
+ if (monthNum2 && /^\d{4}$/.test(parts[0]))
154
+ return `${parts[0]}-${monthNum2}`;
155
+ }
156
+ // "February 2024" with lowercase already handled
157
+ return undefined;
158
+ }
159
+ // ---------------------------------------------------------------------------
160
+ // Trace normalizer helpers — detect and convert external formats
161
+ // ---------------------------------------------------------------------------
162
+ /** Safely attempt to parse a string as JSON; returns the original string on failure. */
163
+ function tryParseJSON(s) {
164
+ if (!s || typeof s !== 'string')
165
+ return s;
166
+ try {
167
+ return JSON.parse(s);
168
+ }
169
+ catch {
170
+ return s;
171
+ }
172
+ }
173
+ /** OpenAI format: assistant message may carry a `tool_calls` array. */
174
+ function hasToolCallsPayload(raw) {
175
+ const tc = raw['tool_calls'] ?? raw['toolCalls'];
176
+ return Array.isArray(tc) && tc.length > 0;
177
+ }
178
+ /** Extract ToolCall[] from an OpenAI-format assistant step. */
179
+ function extractOpenAIToolCalls(raw) {
180
+ const arr = (raw['tool_calls'] ?? raw['toolCalls']);
181
+ if (!Array.isArray(arr))
182
+ return [];
183
+ return arr.map((tc) => {
184
+ const fn = (tc['function'] ?? tc);
185
+ let params = {};
186
+ const args = fn['arguments'] ?? fn['parameters'] ?? tc['parameters'];
187
+ if (typeof args === 'string') {
188
+ try {
189
+ params = JSON.parse(args);
190
+ }
191
+ catch {
192
+ params = { _raw: args };
193
+ }
194
+ }
195
+ else if (args && typeof args === 'object') {
196
+ params = args;
197
+ }
198
+ return {
199
+ toolName: (fn['name'] ?? tc['name'] ?? tc['toolName'] ?? 'unknown'),
200
+ parameters: params,
201
+ };
202
+ });
203
+ }
204
+ /** Anthropic format: assistant message has content blocks with type='tool_use'. */
205
+ function hasAnthropicToolUse(raw) {
206
+ const content = raw['content'];
207
+ return Array.isArray(content) && content.some((b) => b?.type === 'tool_use');
208
+ }
209
+ /** Extract ToolCall[] from an Anthropic-format assistant step. */
210
+ function extractAnthropicToolCalls(raw) {
211
+ const content = raw['content'];
212
+ if (!Array.isArray(content))
213
+ return [];
214
+ return content
215
+ .filter((b) => b?.type === 'tool_use')
216
+ .map((b) => ({
217
+ toolName: (b['name'] ?? 'unknown'),
218
+ parameters: (b['input'] ?? {}),
219
+ }));
220
+ }
221
+ /** Anthropic format: user message has content blocks with type='tool_result'. */
222
+ function hasAnthropicToolResult(raw) {
223
+ const content = raw['content'];
224
+ return Array.isArray(content) && content.some((b) => b?.type === 'tool_result');
225
+ }
226
+ /** Extract ToolOutput[] from an Anthropic-format tool_result step. */
227
+ function extractAnthropicToolResults(raw) {
228
+ const content = raw['content'];
229
+ if (!Array.isArray(content))
230
+ return [];
231
+ return content
232
+ .filter((b) => b?.type === 'tool_result')
233
+ .map((b) => {
234
+ let output = b['content'];
235
+ if (typeof output === 'string')
236
+ output = tryParseJSON(output);
237
+ return {
238
+ toolName: (b['tool_use_id'] ?? b['name'] ?? 'unknown'),
239
+ output,
240
+ };
241
+ });
242
+ }
243
+ // ---------------------------------------------------------------------------
244
+ // TraceUtils — query helpers
245
+ // ---------------------------------------------------------------------------
246
+ /** Utility helpers for querying a {@link Trace}. */
247
+ exports.TraceUtils = {
248
+ /**
249
+ * Return all steps matching a given role.
250
+ */
251
+ getStepsByRole(trace, role) {
252
+ return (trace?.steps ?? []).filter((s) => s.role === role);
253
+ },
254
+ /**
255
+ * Return the final response step, or undefined if not present.
256
+ */
257
+ getFinalResponse(trace) {
258
+ const steps = exports.TraceUtils.getStepsByRole(trace, 'final_response');
259
+ return steps[steps.length - 1];
260
+ },
261
+ /**
262
+ * Return all tool outputs flattened from all tool_output steps.
263
+ */
264
+ getAllToolOutputs(trace) {
265
+ return (trace?.steps ?? [])
266
+ .filter((s) => s.role === 'tool_output' && s.toolOutputs)
267
+ .flatMap((s) => s.toolOutputs ?? []);
268
+ },
269
+ /**
270
+ * Return all tool calls flattened from all tool_call steps.
271
+ */
272
+ getAllToolCalls(trace) {
273
+ return (trace?.steps ?? [])
274
+ .filter((s) => s.role === 'tool_call' && s.toolCalls)
275
+ .flatMap((s) => s.toolCalls ?? []);
276
+ },
277
+ /**
278
+ * Return all tool output steps (with their stepId preserved).
279
+ */
280
+ getToolOutputSteps(trace) {
281
+ return (trace?.steps ?? []).filter((s) => s.role === 'tool_output');
282
+ },
283
+ /**
284
+ * Check whether any tool was called in this trace.
285
+ *
286
+ * Returns true if ANY of these conditions hold:
287
+ * 1. A step has role='tool_call', OR
288
+ * 2. A step has role='tool_output' (some proxies emit only outputs).
289
+ */
290
+ hasToolCalls(trace) {
291
+ return (trace?.steps ?? []).some((s) => s.role === 'tool_call' || s.role === 'tool_output');
292
+ },
293
+ /**
294
+ * Return the system prompt step, or undefined if not present.
295
+ */
296
+ getSystemPrompt(trace) {
297
+ const steps = exports.TraceUtils.getStepsByRole(trace, 'system_prompt');
298
+ return steps[0];
299
+ },
300
+ /**
301
+ * Serialise a trace to a JSON string.
302
+ */
303
+ toJSON(trace) {
304
+ return JSON.stringify(trace ?? {}, null, 2);
305
+ },
306
+ /**
307
+ * Deserialise a trace from a JSON string.
308
+ */
309
+ fromJSON(json) {
310
+ return JSON.parse(json);
311
+ },
312
+ // -----------------------------------------------------------------------
313
+ // Multi-turn conversation helpers
314
+ // -----------------------------------------------------------------------
315
+ /**
316
+ * Parse trace steps into an ordered list of conversation turns.
317
+ *
318
+ * Algorithm (generic, works for any agent framework):
319
+ * 1. Skip `system_prompt` / `llm_call` steps — they are not turn boundaries.
320
+ * 2. A new turn starts at a `user_input` that either:
321
+ * - is the first `user_input` in the trace, or
322
+ * - follows a `final_response` (i.e. a previous turn already ended).
323
+ * 3. Consecutive `user_input` steps with identical content are de-duped
324
+ * (a common pattern when agents re-send the message).
325
+ * 4. Tool call / tool output steps are assigned to the current turn.
326
+ * 5. A `final_response` closes the current turn.
327
+ * 6. The last turn is marked `isActive`.
328
+ */
329
+ getConversationTurns(trace) {
330
+ const steps = trace?.steps ?? [];
331
+ const turns = [];
332
+ let current = null;
333
+ let lastRole = null;
334
+ for (const step of steps) {
335
+ if (step.role === 'system_prompt' || step.role === 'llm_call') {
336
+ continue;
337
+ }
338
+ if (step.role === 'user_input') {
339
+ // Dedup: skip consecutive user_input with identical content
340
+ if (current && lastRole === 'user_input' && current.userInput === step.content) {
341
+ continue;
342
+ }
343
+ // New turn boundary: first input, or after a final_response
344
+ if (!current || lastRole === 'final_response') {
345
+ current = {
346
+ userInput: step.content,
347
+ toolCalls: [],
348
+ toolOutputs: [],
349
+ response: '',
350
+ isActive: false,
351
+ };
352
+ turns.push(current);
353
+ }
354
+ else if (current && lastRole === 'user_input') {
355
+ // Different user_input following another user_input without a
356
+ // final_response in between → replace (overwrite) the current
357
+ // turn's question (the agent re-framed / the user re-phrased).
358
+ current.userInput = step.content;
359
+ }
360
+ lastRole = 'user_input';
361
+ continue;
362
+ }
363
+ if (!current)
364
+ continue; // steps before first user_input — skip
365
+ if (step.role === 'tool_call') {
366
+ current.toolCalls.push(step);
367
+ }
368
+ else if (step.role === 'tool_output') {
369
+ current.toolOutputs.push(step);
370
+ }
371
+ else if (step.role === 'final_response') {
372
+ current.response = step.content;
373
+ lastRole = 'final_response';
374
+ continue;
375
+ }
376
+ lastRole = step.role;
377
+ }
378
+ // Mark the last turn as active
379
+ if (turns.length > 0) {
380
+ turns[turns.length - 1].isActive = true;
381
+ }
382
+ return turns;
383
+ },
384
+ /**
385
+ * Return the active (last) conversation turn, or `undefined` for
386
+ * single-step traces with no `user_input`.
387
+ */
388
+ getActiveTurn(trace) {
389
+ const turns = exports.TraceUtils.getConversationTurns(trace);
390
+ return turns.length > 0 ? turns[turns.length - 1] : undefined;
391
+ },
392
+ /**
393
+ * Return the user question from the active (last) turn only.
394
+ *
395
+ * For single-turn traces this returns the same as joining all
396
+ * `user_input` contents. For multi-turn traces it returns only
397
+ * the last question — the one being evaluated.
398
+ *
399
+ * When the trace was built by `buildActiveTurnTrace`, the original
400
+ * active question is stored in `metadata.activeUserInput` (the
401
+ * user_input step contains the full contextual history).
402
+ */
403
+ getActiveUserInput(trace) {
404
+ const fromMeta = trace.metadata?.activeUserInput;
405
+ if (typeof fromMeta === 'string' && fromMeta.length > 0)
406
+ return fromMeta;
407
+ return exports.TraceUtils.getActiveTurn(trace)?.userInput ?? '';
408
+ },
409
+ /**
410
+ * Return a context-enriched version of the active user input.
411
+ *
412
+ * If the trace has multiple turns, the earlier Q&A pairs are
413
+ * prepended so that rules can understand follow-up references
414
+ * like "what about the same for March?" or "po sektorima".
415
+ *
416
+ * Returns the plain active input for single-turn traces.
417
+ */
418
+ getContextualUserInput(trace) {
419
+ const turns = exports.TraceUtils.getConversationTurns(trace);
420
+ if (turns.length <= 1) {
421
+ return turns[0]?.userInput ?? '';
422
+ }
423
+ const historyParts = [];
424
+ for (let i = 0; i < turns.length - 1; i++) {
425
+ historyParts.push(`Q: ${turns[i].userInput}`);
426
+ if (turns[i].response) {
427
+ // Include only a short prefix of the response for context
428
+ const respPreview = turns[i].response.length > 200
429
+ ? turns[i].response.slice(0, 200) + '…'
430
+ : turns[i].response;
431
+ historyParts.push(`A: ${respPreview}`);
432
+ }
433
+ }
434
+ const active = turns[turns.length - 1];
435
+ return [...historyParts, `Q: ${active.userInput}`].join('\n');
436
+ },
437
+ /**
438
+ * Return tool outputs from only the active (last) conversation turn.
439
+ *
440
+ * For single-turn traces this is equivalent to `getAllToolOutputs()`.
441
+ * For multi-turn traces it excludes tool outputs from earlier turns
442
+ * so that rules evaluate only the data relevant to the current answer.
443
+ */
444
+ getActiveToolOutputs(trace) {
445
+ const activeTurn = exports.TraceUtils.getActiveTurn(trace);
446
+ if (!activeTurn)
447
+ return [];
448
+ return activeTurn.toolOutputs.flatMap((s) => s.toolOutputs ?? []);
449
+ },
450
+ /**
451
+ * Return whether the trace contains multiple conversation turns.
452
+ */
453
+ isMultiTurn(trace) {
454
+ return exports.TraceUtils.getConversationTurns(trace).length > 1;
455
+ },
456
+ /**
457
+ * Build a virtual single-turn trace from the active (last) conversation turn.
458
+ *
459
+ * For multi-turn traces, the grounding engine should evaluate ONLY the
460
+ * active turn's response against the active turn's tool data. This method
461
+ * creates a new Trace that contains:
462
+ *
463
+ * 1. The original system_prompt (if present)
464
+ * 2. The active turn's user_input
465
+ * 3. The active turn's tool_call steps
466
+ * 4. The active turn's tool_output steps
467
+ * 5. The active turn's final_response
468
+ *
469
+ * The contextual user input (with history prepended) is used so that
470
+ * rules can understand follow-up references like "what about March?".
471
+ *
472
+ * Returns the original trace unmodified if it has only 1 turn.
473
+ */
474
+ buildActiveTurnTrace(trace) {
475
+ const turns = exports.TraceUtils.getConversationTurns(trace);
476
+ if (turns.length <= 1)
477
+ return trace;
478
+ const active = turns[turns.length - 1];
479
+ const systemPrompt = exports.TraceUtils.getSystemPrompt(trace);
480
+ const contextualInput = exports.TraceUtils.getContextualUserInput(trace);
481
+ const steps = [];
482
+ // 1. System prompt (shared across turns)
483
+ if (systemPrompt) {
484
+ steps.push(systemPrompt);
485
+ }
486
+ // 2. User input with conversational context
487
+ steps.push({
488
+ stepId: `active_user_input`,
489
+ role: 'user_input',
490
+ content: contextualInput,
491
+ });
492
+ // 3. Active turn's tool calls
493
+ for (const step of active.toolCalls) {
494
+ steps.push(step);
495
+ }
496
+ // 4. Active turn's tool outputs
497
+ for (const step of active.toolOutputs) {
498
+ steps.push(step);
499
+ }
500
+ // 5. Active turn's final response
501
+ if (active.response) {
502
+ steps.push({
503
+ stepId: `active_final_response`,
504
+ role: 'final_response',
505
+ content: active.response,
506
+ });
507
+ }
508
+ return {
509
+ traceId: trace.traceId,
510
+ steps,
511
+ startedAt: trace.startedAt,
512
+ endedAt: trace.endedAt,
513
+ totalLatencyMs: trace.totalLatencyMs,
514
+ metadata: {
515
+ ...trace.metadata,
516
+ multiTurn: true,
517
+ turnCount: turns.length,
518
+ activeTurnIndex: turns.length - 1,
519
+ activeUserInput: active.userInput,
520
+ },
521
+ };
522
+ },
523
+ // -----------------------------------------------------------------------
524
+ // Trace normalizer — converts external formats to TG internal format
525
+ // -----------------------------------------------------------------------
526
+ /**
527
+ * Normalise a trace in-place so that external formats (OpenAI chat,
528
+ * Anthropic Messages, custom integrations) are mapped to TG's canonical
529
+ * step roles.
530
+ *
531
+ * This is **idempotent**: calling it on an already-normalised trace is safe.
532
+ *
533
+ * Recognised external patterns:
534
+ * - OpenAI: `assistant` with `tool_calls[]` → `tool_call`
535
+ * - OpenAI: `role: 'tool'` or `role: 'function'` → `tool_output`
536
+ * - Anthropic: `assistant` with `content[].type === 'tool_use'` → `tool_call`
537
+ * - Anthropic: `user` with `content[].type === 'tool_result'` → `tool_output`
538
+ * - Custom: `function_call` → `tool_call`; `function_output`/`tool_response` → `tool_output`
539
+ * - Role aliases: `system`/`instructions` → `system_prompt`; `human`/`user` → `user_input`
540
+ * - Content-only steps: parses JSON from content into toolCalls/toolOutputs when missing
541
+ */
542
+ normalizeTrace(trace) {
543
+ if (!trace?.steps?.length)
544
+ return trace;
545
+ const normalized = [];
546
+ for (const step of trace.steps) {
547
+ const role = (step.role ?? '');
548
+ const raw = step;
549
+ // --- OpenAI: assistant with tool_calls → tool_call step ---
550
+ if (role === 'assistant' && hasToolCallsPayload(raw)) {
551
+ const tcArr = extractOpenAIToolCalls(raw);
552
+ if (tcArr.length > 0) {
553
+ normalized.push({
554
+ ...step,
555
+ role: 'tool_call',
556
+ toolCalls: tcArr,
557
+ });
558
+ continue;
559
+ }
560
+ }
561
+ // --- OpenAI: role 'tool' or 'function' → tool_output ---
562
+ if (role === 'tool' || role === 'function') {
563
+ const output = tryParseJSON(step.content);
564
+ const toolName = (raw['name'] ??
565
+ raw['toolName'] ?? 'unknown');
566
+ normalized.push({
567
+ ...step,
568
+ role: 'tool_output',
569
+ toolOutputs: step.toolOutputs ?? [{ toolName, output }],
570
+ });
571
+ continue;
572
+ }
573
+ // --- Custom: function_call → tool_call ---
574
+ if (role === 'function_call') {
575
+ normalized.push({ ...step, role: 'tool_call' });
576
+ continue;
577
+ }
578
+ // --- Custom: function_output / tool_response → tool_output ---
579
+ if (role === 'function_output' || role === 'tool_response') {
580
+ const output = tryParseJSON(step.content);
581
+ normalized.push({
582
+ ...step,
583
+ role: 'tool_output',
584
+ toolOutputs: step.toolOutputs ?? [{
585
+ toolName: (raw['name'] ??
586
+ raw['toolName'] ?? 'unknown'),
587
+ output,
588
+ }],
589
+ });
590
+ continue;
591
+ }
592
+ // --- Anthropic: assistant with tool_use content blocks ---
593
+ if (role === 'assistant' && hasAnthropicToolUse(raw)) {
594
+ const tcArr = extractAnthropicToolCalls(raw);
595
+ if (tcArr.length > 0) {
596
+ normalized.push({
597
+ ...step,
598
+ role: 'tool_call',
599
+ toolCalls: tcArr,
600
+ });
601
+ continue;
602
+ }
603
+ }
604
+ // --- Anthropic: user with tool_result content blocks ---
605
+ if (role === 'user' && hasAnthropicToolResult(raw)) {
606
+ const outputs = extractAnthropicToolResults(raw);
607
+ if (outputs.length > 0) {
608
+ normalized.push({
609
+ ...step,
610
+ role: 'tool_output',
611
+ toolOutputs: outputs,
612
+ });
613
+ continue;
614
+ }
615
+ }
616
+ // --- tool_output step with content but no toolOutputs → parse content ---
617
+ if (role === 'tool_output' && !step.toolOutputs?.length && step.content) {
618
+ const output = tryParseJSON(step.content);
619
+ // Detect "tool-name-only" outputs: when content is just a function
620
+ // name echoed back (e.g. "get_payroll_summary") instead of actual
621
+ // data. These carry zero grounding information and should be treated
622
+ // as empty so that downstream verdicts become UNVERIFIABLE instead of
623
+ // a misleading UNGROUNDED / hallucinated_entity.
624
+ const contentStr = step.content.trim();
625
+ const isToolNameOnly = typeof output === 'string' &&
626
+ /^[a-zA-Z_][a-zA-Z0-9_.:-]*$/.test(contentStr);
627
+ // Also check if the content matches the preceding tool_call's toolName
628
+ const prevStep = normalized[normalized.length - 1];
629
+ const prevToolName = prevStep?.role === 'tool_call'
630
+ ? (prevStep.toolCalls?.[0]?.toolName ?? '')
631
+ : '';
632
+ const matchesPrevToolName = prevToolName !== '' &&
633
+ contentStr.toLowerCase() === prevToolName.toLowerCase();
634
+ const isNameOnly = isToolNameOnly || matchesPrevToolName;
635
+ const effectiveOutput = isNameOnly ? null : output;
636
+ const toolName = (matchesPrevToolName ? prevToolName : undefined) ??
637
+ (raw['toolName'] ?? raw['name'] ?? 'unknown');
638
+ normalized.push({
639
+ ...step,
640
+ toolOutputs: [{
641
+ toolName,
642
+ output: effectiveOutput,
643
+ }],
644
+ // Preserve info about tool-name-only detection so downstream
645
+ // modules (TraceReadiness, Rules, Advisor) can give targeted feedback.
646
+ ...(isNameOnly ? { metadata: { ...step.metadata, _toolNameOnly: contentStr } } : {}),
647
+ });
648
+ continue;
649
+ }
650
+ // --- tool_call step with content but no toolCalls → try to parse ---
651
+ if (role === 'tool_call' && !step.toolCalls?.length && step.content) {
652
+ const parsed = tryParseJSON(step.content);
653
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
654
+ const obj = parsed;
655
+ const toolName = (obj['name'] ?? obj['toolName'] ??
656
+ raw['toolName'] ?? 'unknown');
657
+ const params = (obj['parameters'] ?? obj['arguments'] ?? obj['input'] ?? {});
658
+ normalized.push({
659
+ ...step,
660
+ toolCalls: [{ toolName, parameters: params }],
661
+ });
662
+ continue;
663
+ }
664
+ }
665
+ // --- Map common role aliases ---
666
+ if (role === 'system' || role === 'instructions') {
667
+ normalized.push({ ...step, role: 'system_prompt' });
668
+ continue;
669
+ }
670
+ if (role === 'human' || role === 'user') {
671
+ normalized.push({ ...step, role: 'user_input' });
672
+ continue;
673
+ }
674
+ if (role === 'assistant' && !step.toolCalls?.length) {
675
+ // Plain assistant message → final_response (only if it has content)
676
+ if (step.content?.trim()) {
677
+ normalized.push({ ...step, role: 'final_response' });
678
+ continue;
679
+ }
680
+ }
681
+ // Pass through already-canonical roles unchanged
682
+ normalized.push(step);
683
+ }
684
+ trace.steps = normalized;
685
+ return trace;
686
+ },
687
+ /**
688
+ * Infer the temporal period from tool call parameters and tool output metadata.
689
+ *
690
+ * Checks (in priority order):
691
+ * 1. Explicit date range params (startDate/endDate, from/to)
692
+ * 2. Month param (month: "2024-01" or "January 2024")
693
+ * 3. Year param (year: 2024)
694
+ * 4. Period range in tool output (period.from / period.to)
695
+ * 5. Relative params (days: 7, period: "this_month")
696
+ *
697
+ * Returns a normalised ISO-ish string or undefined.
698
+ */
699
+ inferPeriod(trace) {
700
+ // --- Source 1: tool call parameters ---
701
+ const toolCalls = exports.TraceUtils.getAllToolCalls(trace);
702
+ for (const tc of toolCalls) {
703
+ const p = tc.parameters;
704
+ if (!p || typeof p !== 'object')
705
+ continue;
706
+ // 1a. Explicit date range: startDate+endDate or from+to
707
+ const rangeStart = findDateParam(p, ['startDate', 'start_date', 'from', 'dateFrom', 'date_from']);
708
+ const rangeEnd = findDateParam(p, ['endDate', 'end_date', 'to', 'dateTo', 'date_to']);
709
+ if (rangeStart && rangeEnd)
710
+ return `${rangeStart}/${rangeEnd}`;
711
+ if (rangeStart)
712
+ return rangeStart;
713
+ // 1b. Single date param
714
+ const singleDate = findDateParam(p, ['date']);
715
+ if (singleDate)
716
+ return singleDate;
717
+ // 1c. Month param: "2024-01", "2024-12", "January 2024", "januar 2024"
718
+ const monthVal = findParam(p, ['month', 'mesec']);
719
+ if (monthVal !== undefined) {
720
+ const norm = normaliseMonth(String(monthVal));
721
+ if (norm)
722
+ return norm;
723
+ }
724
+ // 1d. Year + quarter
725
+ const quarterVal = findParam(p, ['quarter', 'kvartal']);
726
+ const yearVal = findParam(p, ['year', 'godina']);
727
+ if (quarterVal !== undefined && yearVal !== undefined) {
728
+ const q = String(quarterVal).replace(/^q/i, '').trim();
729
+ return `${yearVal}-Q${q}`;
730
+ }
731
+ // 1e. Year only
732
+ if (yearVal !== undefined)
733
+ return String(yearVal);
734
+ // 1f. Relative days
735
+ const daysVal = findParam(p, ['days', 'dana']);
736
+ if (typeof daysVal === 'number' && daysVal > 0)
737
+ return `last_${daysVal}_days`;
738
+ // 1g. Named period
739
+ const periodVal = findParam(p, ['period', 'period_type']);
740
+ if (typeof periodVal === 'string' && periodVal.length > 0)
741
+ return periodVal;
742
+ }
743
+ // --- Source 2: tool output period metadata ---
744
+ const toolOutputs = exports.TraceUtils.getAllToolOutputs(trace);
745
+ for (const to of toolOutputs) {
746
+ const output = to.output;
747
+ if (output && typeof output === 'object' && !Array.isArray(output)) {
748
+ const obj = output;
749
+ const periodObj = obj['period'];
750
+ if (periodObj && typeof periodObj === 'object' && !Array.isArray(periodObj)) {
751
+ const po = periodObj;
752
+ const from = po['from'] ?? po['start'];
753
+ const to2 = po['to'] ?? po['end'];
754
+ if (typeof from === 'string' && typeof to2 === 'string') {
755
+ return `${from}/${to2}`;
756
+ }
757
+ }
758
+ }
759
+ }
760
+ return undefined;
761
+ },
762
+ };
763
+ //# sourceMappingURL=index.js.map