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.
- package/LICENSE +21 -0
- package/README.md +363 -0
- package/dist/Advisor/index.d.ts +78 -0
- package/dist/Advisor/index.d.ts.map +1 -0
- package/dist/Advisor/index.js +539 -0
- package/dist/Advisor/index.js.map +1 -0
- package/dist/Alerting/index.d.ts +35 -0
- package/dist/Alerting/index.d.ts.map +1 -0
- package/dist/Alerting/index.js +76 -0
- package/dist/Alerting/index.js.map +1 -0
- package/dist/Auth/index.d.ts +82 -0
- package/dist/Auth/index.d.ts.map +1 -0
- package/dist/Auth/index.js +242 -0
- package/dist/Auth/index.js.map +1 -0
- package/dist/Baseline/index.d.ts +43 -0
- package/dist/Baseline/index.d.ts.map +1 -0
- package/dist/Baseline/index.js +195 -0
- package/dist/Baseline/index.js.map +1 -0
- package/dist/Claims/index.d.ts +73 -0
- package/dist/Claims/index.d.ts.map +1 -0
- package/dist/Claims/index.js +1669 -0
- package/dist/Claims/index.js.map +1 -0
- package/dist/Client/index.d.ts +90 -0
- package/dist/Client/index.d.ts.map +1 -0
- package/dist/Client/index.js +186 -0
- package/dist/Client/index.js.map +1 -0
- package/dist/Config/index.d.ts +41 -0
- package/dist/Config/index.d.ts.map +1 -0
- package/dist/Config/index.js +129 -0
- package/dist/Config/index.js.map +1 -0
- package/dist/Coverage/index.d.ts +28 -0
- package/dist/Coverage/index.d.ts.map +1 -0
- package/dist/Coverage/index.js +134 -0
- package/dist/Coverage/index.js.map +1 -0
- package/dist/Demo/index.d.ts +16 -0
- package/dist/Demo/index.d.ts.map +1 -0
- package/dist/Demo/index.js +189 -0
- package/dist/Demo/index.js.map +1 -0
- package/dist/Gate/index.d.ts +39 -0
- package/dist/Gate/index.d.ts.map +1 -0
- package/dist/Gate/index.js +207 -0
- package/dist/Gate/index.js.map +1 -0
- package/dist/Grounding/index.d.ts +40 -0
- package/dist/Grounding/index.d.ts.map +1 -0
- package/dist/Grounding/index.js +1433 -0
- package/dist/Grounding/index.js.map +1 -0
- package/dist/L2/index.d.ts +93 -0
- package/dist/L2/index.d.ts.map +1 -0
- package/dist/L2/index.js +1773 -0
- package/dist/L2/index.js.map +1 -0
- package/dist/MCP/index.d.ts +139 -0
- package/dist/MCP/index.d.ts.map +1 -0
- package/dist/MCP/index.js +1250 -0
- package/dist/MCP/index.js.map +1 -0
- package/dist/Matchers/index.d.ts +101 -0
- package/dist/Matchers/index.d.ts.map +1 -0
- package/dist/Matchers/index.js +690 -0
- package/dist/Matchers/index.js.map +1 -0
- package/dist/Middleware/index.d.ts +146 -0
- package/dist/Middleware/index.d.ts.map +1 -0
- package/dist/Middleware/index.js +239 -0
- package/dist/Middleware/index.js.map +1 -0
- package/dist/Mode/index.d.ts +87 -0
- package/dist/Mode/index.d.ts.map +1 -0
- package/dist/Mode/index.js +117 -0
- package/dist/Mode/index.js.map +1 -0
- package/dist/Policy/index.d.ts +89 -0
- package/dist/Policy/index.d.ts.map +1 -0
- package/dist/Policy/index.js +143 -0
- package/dist/Policy/index.js.map +1 -0
- package/dist/Proxy/SessionStore.d.ts +94 -0
- package/dist/Proxy/SessionStore.d.ts.map +1 -0
- package/dist/Proxy/SessionStore.js +225 -0
- package/dist/Proxy/SessionStore.js.map +1 -0
- package/dist/Proxy/index.d.ts +166 -0
- package/dist/Proxy/index.d.ts.map +1 -0
- package/dist/Proxy/index.js +531 -0
- package/dist/Proxy/index.js.map +1 -0
- package/dist/Registry/index.d.ts +93 -0
- package/dist/Registry/index.d.ts.map +1 -0
- package/dist/Registry/index.js +818 -0
- package/dist/Registry/index.js.map +1 -0
- package/dist/Reports/index.d.ts +38 -0
- package/dist/Reports/index.d.ts.map +1 -0
- package/dist/Reports/index.js +149 -0
- package/dist/Reports/index.js.map +1 -0
- package/dist/Rules/index.d.ts +587 -0
- package/dist/Rules/index.d.ts.map +1 -0
- package/dist/Rules/index.js +6236 -0
- package/dist/Rules/index.js.map +1 -0
- package/dist/Rules/intents.d.ts +22 -0
- package/dist/Rules/intents.d.ts.map +1 -0
- package/dist/Rules/intents.js +242 -0
- package/dist/Rules/intents.js.map +1 -0
- package/dist/Runner/index.d.ts +39 -0
- package/dist/Runner/index.d.ts.map +1 -0
- package/dist/Runner/index.js +185 -0
- package/dist/Runner/index.js.map +1 -0
- package/dist/SDK/anthropic.d.ts +102 -0
- package/dist/SDK/anthropic.d.ts.map +1 -0
- package/dist/SDK/anthropic.js +425 -0
- package/dist/SDK/anthropic.js.map +1 -0
- package/dist/SDK/openai.d.ts +164 -0
- package/dist/SDK/openai.d.ts.map +1 -0
- package/dist/SDK/openai.js +557 -0
- package/dist/SDK/openai.js.map +1 -0
- package/dist/Store/index.d.ts +72 -0
- package/dist/Store/index.d.ts.map +1 -0
- package/dist/Store/index.js +136 -0
- package/dist/Store/index.js.map +1 -0
- package/dist/Telemetry/index.d.ts +84 -0
- package/dist/Telemetry/index.d.ts.map +1 -0
- package/dist/Telemetry/index.js +239 -0
- package/dist/Telemetry/index.js.map +1 -0
- package/dist/Trace/index.d.ts +219 -0
- package/dist/Trace/index.d.ts.map +1 -0
- package/dist/Trace/index.js +763 -0
- package/dist/Trace/index.js.map +1 -0
- package/dist/TraceReadiness/index.d.ts +42 -0
- package/dist/TraceReadiness/index.d.ts.map +1 -0
- package/dist/TraceReadiness/index.js +169 -0
- package/dist/TraceReadiness/index.js.map +1 -0
- package/dist/cli/index.d.ts +15 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +807 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/i18n/index.d.ts +44 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +124 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +218 -0
- package/dist/index.js.map +1 -0
- package/dist/thin.d.ts +39 -0
- package/dist/thin.d.ts.map +1 -0
- package/dist/thin.js +120 -0
- package/dist/thin.js.map +1 -0
- package/dist/types/index.d.ts +498 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +17 -0
- package/dist/types/index.js.map +1 -0
- package/dist-npm/Alerting/index.d.ts +35 -0
- package/dist-npm/Alerting/index.d.ts.map +1 -0
- package/dist-npm/Alerting/index.js +76 -0
- package/dist-npm/Alerting/index.js.map +1 -0
- package/dist-npm/Auth/index.d.ts +82 -0
- package/dist-npm/Auth/index.d.ts.map +1 -0
- package/dist-npm/Auth/index.js +242 -0
- package/dist-npm/Auth/index.js.map +1 -0
- package/dist-npm/Client/index.d.ts +90 -0
- package/dist-npm/Client/index.d.ts.map +1 -0
- package/dist-npm/Client/index.js +186 -0
- package/dist-npm/Client/index.js.map +1 -0
- package/dist-npm/Demo/index.d.ts +16 -0
- package/dist-npm/Demo/index.d.ts.map +1 -0
- package/dist-npm/Demo/index.js +189 -0
- package/dist-npm/Demo/index.js.map +1 -0
- package/dist-npm/Middleware/index.d.ts +146 -0
- package/dist-npm/Middleware/index.d.ts.map +1 -0
- package/dist-npm/Middleware/index.js +239 -0
- package/dist-npm/Middleware/index.js.map +1 -0
- package/dist-npm/Proxy/SessionStore.d.ts +94 -0
- package/dist-npm/Proxy/SessionStore.d.ts.map +1 -0
- package/dist-npm/Proxy/SessionStore.js +225 -0
- package/dist-npm/Proxy/SessionStore.js.map +1 -0
- package/dist-npm/Proxy/index.d.ts +166 -0
- package/dist-npm/Proxy/index.d.ts.map +1 -0
- package/dist-npm/Proxy/index.js +531 -0
- package/dist-npm/Proxy/index.js.map +1 -0
- package/dist-npm/SDK/anthropic.d.ts +102 -0
- package/dist-npm/SDK/anthropic.d.ts.map +1 -0
- package/dist-npm/SDK/anthropic.js +425 -0
- package/dist-npm/SDK/anthropic.js.map +1 -0
- package/dist-npm/SDK/openai.d.ts +164 -0
- package/dist-npm/SDK/openai.d.ts.map +1 -0
- package/dist-npm/SDK/openai.js +557 -0
- package/dist-npm/SDK/openai.js.map +1 -0
- package/dist-npm/Store/index.d.ts +72 -0
- package/dist-npm/Store/index.d.ts.map +1 -0
- package/dist-npm/Store/index.js +136 -0
- package/dist-npm/Store/index.js.map +1 -0
- package/dist-npm/Telemetry/index.d.ts +84 -0
- package/dist-npm/Telemetry/index.d.ts.map +1 -0
- package/dist-npm/Telemetry/index.js +239 -0
- package/dist-npm/Telemetry/index.js.map +1 -0
- package/dist-npm/Trace/index.d.ts +219 -0
- package/dist-npm/Trace/index.d.ts.map +1 -0
- package/dist-npm/Trace/index.js +763 -0
- package/dist-npm/Trace/index.js.map +1 -0
- package/dist-npm/thin.d.ts +39 -0
- package/dist-npm/thin.d.ts.map +1 -0
- package/dist-npm/thin.js +120 -0
- package/dist-npm/thin.js.map +1 -0
- package/dist-npm/types/index.d.ts +498 -0
- package/dist-npm/types/index.d.ts.map +1 -0
- package/dist-npm/types/index.js +17 -0
- package/dist-npm/types/index.js.map +1 -0
- 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
|