elasticdash-test 0.1.16 → 0.1.17-alpha-2
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.
- package/dist/cloud-client.d.ts +34 -0
- package/dist/cloud-client.d.ts.map +1 -0
- package/dist/cloud-client.js +103 -0
- package/dist/cloud-client.js.map +1 -0
- package/dist/dashboard-server.d.ts.map +1 -1
- package/dist/dashboard-server.js +425 -90
- package/dist/dashboard-server.js.map +1 -1
- package/dist/evaluators/determinism.d.ts +3 -0
- package/dist/evaluators/determinism.d.ts.map +1 -0
- package/dist/evaluators/determinism.js +116 -0
- package/dist/evaluators/determinism.js.map +1 -0
- package/dist/evaluators/index.d.ts +4 -0
- package/dist/evaluators/index.d.ts.map +1 -0
- package/dist/evaluators/index.js +61 -0
- package/dist/evaluators/index.js.map +1 -0
- package/dist/evaluators/latency-budget.d.ts +3 -0
- package/dist/evaluators/latency-budget.d.ts.map +1 -0
- package/dist/evaluators/latency-budget.js +45 -0
- package/dist/evaluators/latency-budget.js.map +1 -0
- package/dist/evaluators/llm-judge.d.ts +3 -0
- package/dist/evaluators/llm-judge.d.ts.map +1 -0
- package/dist/evaluators/llm-judge.js +122 -0
- package/dist/evaluators/llm-judge.js.map +1 -0
- package/dist/evaluators/output-contains.d.ts +3 -0
- package/dist/evaluators/output-contains.d.ts.map +1 -0
- package/dist/evaluators/output-contains.js +52 -0
- package/dist/evaluators/output-contains.js.map +1 -0
- package/dist/evaluators/output-schema.d.ts +3 -0
- package/dist/evaluators/output-schema.d.ts.map +1 -0
- package/dist/evaluators/output-schema.js +58 -0
- package/dist/evaluators/output-schema.js.map +1 -0
- package/dist/evaluators/token-budget.d.ts +3 -0
- package/dist/evaluators/token-budget.d.ts.map +1 -0
- package/dist/evaluators/token-budget.js +45 -0
- package/dist/evaluators/token-budget.js.map +1 -0
- package/dist/evaluators/types.d.ts +103 -0
- package/dist/evaluators/types.d.ts.map +1 -0
- package/dist/evaluators/types.js +6 -0
- package/dist/evaluators/types.js.map +1 -0
- package/dist/html/dashboard.html +359 -73
- package/dist/index.cjs +253 -14
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interceptors/telemetry-push.d.ts +40 -0
- package/dist/interceptors/telemetry-push.d.ts.map +1 -1
- package/dist/interceptors/telemetry-push.js +129 -6
- package/dist/interceptors/telemetry-push.js.map +1 -1
- package/dist/interceptors/tool.d.ts.map +1 -1
- package/dist/interceptors/tool.js +8 -1
- package/dist/interceptors/tool.js.map +1 -1
- package/dist/interceptors/workflow-ai.d.ts.map +1 -1
- package/dist/interceptors/workflow-ai.js +21 -6
- package/dist/interceptors/workflow-ai.js.map +1 -1
- package/dist/internals/mock-resolver.d.ts +70 -2
- package/dist/internals/mock-resolver.d.ts.map +1 -1
- package/dist/internals/mock-resolver.js +157 -6
- package/dist/internals/mock-resolver.js.map +1 -1
- package/dist/test-group/cli.d.ts +8 -0
- package/dist/test-group/cli.d.ts.map +1 -0
- package/dist/test-group/cli.js +162 -0
- package/dist/test-group/cli.js.map +1 -0
- package/dist/test-group/git-context.d.ts +3 -0
- package/dist/test-group/git-context.d.ts.map +1 -0
- package/dist/test-group/git-context.js +59 -0
- package/dist/test-group/git-context.js.map +1 -0
- package/dist/test-group/reporter.d.ts +4 -0
- package/dist/test-group/reporter.d.ts.map +1 -0
- package/dist/test-group/reporter.js +54 -0
- package/dist/test-group/reporter.js.map +1 -0
- package/dist/test-group/runner.d.ts +18 -0
- package/dist/test-group/runner.d.ts.map +1 -0
- package/dist/test-group/runner.js +234 -0
- package/dist/test-group/runner.js.map +1 -0
- package/dist/workflow-runner-worker.js +7 -1
- package/dist/workflow-runner-worker.js.map +1 -1
- package/package.json +1 -1
- package/src/dashboard-server.ts +101 -28
- package/src/html/dashboard.html +359 -73
- package/src/index.ts +2 -2
- package/src/interceptors/telemetry-push.ts +159 -7
- package/src/interceptors/tool.ts +9 -1
- package/src/interceptors/workflow-ai.ts +22 -6
- package/src/internals/mock-resolver.ts +190 -7
- package/src/workflow-runner-worker.ts +11 -3
package/dist/dashboard-server.js
CHANGED
|
@@ -156,6 +156,28 @@ function buildToolArgs(input, tool) {
|
|
|
156
156
|
}
|
|
157
157
|
return [input];
|
|
158
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Flatten structured prompt mock config to simple Record<string, string> for HTTP mode.
|
|
161
|
+
* Accepts both the new structured format { mode, replacement } and legacy string values.
|
|
162
|
+
* Only entries with mode !== 'live' (or plain string entries) are included.
|
|
163
|
+
*/
|
|
164
|
+
function flattenPromptMockConfig(raw) {
|
|
165
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
166
|
+
return {};
|
|
167
|
+
const result = {};
|
|
168
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
169
|
+
if (typeof val === 'string') {
|
|
170
|
+
result[key] = val;
|
|
171
|
+
}
|
|
172
|
+
else if (val && typeof val === 'object' && 'replacement' in val) {
|
|
173
|
+
const entry = val;
|
|
174
|
+
if (entry.mode !== 'live' && typeof entry.replacement === 'string') {
|
|
175
|
+
result[key] = entry.replacement;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
159
181
|
function formatError(error) {
|
|
160
182
|
if (error instanceof Error)
|
|
161
183
|
return error.message;
|
|
@@ -313,6 +335,7 @@ function runWorkflowInSubprocess(workflowsModulePath, toolsModulePath, workflowN
|
|
|
313
335
|
...(options?.toolMockConfig !== undefined ? { toolMockConfig: options.toolMockConfig } : {}),
|
|
314
336
|
...(options?.aiMockConfig !== undefined ? { aiMockConfig: options.aiMockConfig } : {}),
|
|
315
337
|
...(options?.promptMockConfig !== undefined ? { promptMockConfig: options.promptMockConfig } : {}),
|
|
338
|
+
...(options?.userPromptMockConfig !== undefined ? { userPromptMockConfig: options.userPromptMockConfig } : {}),
|
|
316
339
|
});
|
|
317
340
|
child.stdin.write(payload);
|
|
318
341
|
child.stdin.end(); // Always close stdin to avoid subprocess hang
|
|
@@ -363,8 +386,9 @@ async function runGenerationObservation(observation) {
|
|
|
363
386
|
async function rerunObservation(cwd, observation, tools) {
|
|
364
387
|
const type = observation.type?.toUpperCase();
|
|
365
388
|
const name = observation.name ?? '(unknown)';
|
|
366
|
-
|
|
367
|
-
|
|
389
|
+
const isToolByName = name.startsWith('tool-') || name.startsWith('tool:');
|
|
390
|
+
if (type === 'TOOL' || isToolByName) {
|
|
391
|
+
observation.name = isToolByName ? name.slice(5) : name; // Support both explicit type and name prefix for tool observations
|
|
368
392
|
return runToolObservation(cwd, observation, tools);
|
|
369
393
|
}
|
|
370
394
|
if (type === 'GENERATION') {
|
|
@@ -643,6 +667,10 @@ async function validateWorkflowRuns(cwd, body) {
|
|
|
643
667
|
const promptMockConfig = body.promptMockConfig && typeof body.promptMockConfig === 'object' && !Array.isArray(body.promptMockConfig)
|
|
644
668
|
? body.promptMockConfig
|
|
645
669
|
: undefined;
|
|
670
|
+
// Parse user prompt mock config if provided
|
|
671
|
+
const userPromptMockConfig = body.userPromptMockConfig && typeof body.userPromptMockConfig === 'object' && !Array.isArray(body.userPromptMockConfig)
|
|
672
|
+
? body.userPromptMockConfig
|
|
673
|
+
: undefined;
|
|
646
674
|
const workflowsModulePath = resolveWorkflowModule(cwd);
|
|
647
675
|
if (!workflowsModulePath) {
|
|
648
676
|
return {
|
|
@@ -658,7 +686,7 @@ async function validateWorkflowRuns(cwd, body) {
|
|
|
658
686
|
console.log(`[elasticdash] Running workflow "${workflowName}" ${runCount} time(s) in ${mode} mode via subprocess`);
|
|
659
687
|
async function runOne(runNumber) {
|
|
660
688
|
console.log(`[elasticdash] === Run ${runNumber}: Starting workflow "${workflowName}" ===`);
|
|
661
|
-
const result = await runWorkflowInSubprocess(workflowsModulePath, toolsModulePath, workflowName, workflowArgs, workflowInput, (toolMockConfig || aiMockConfig || promptMockConfig) ? { ...(toolMockConfig ? { toolMockConfig } : {}), ...(aiMockConfig ? { aiMockConfig } : {}), ...(promptMockConfig ? { promptMockConfig } : {}) } : undefined)
|
|
689
|
+
const result = await runWorkflowInSubprocess(workflowsModulePath, toolsModulePath, workflowName, workflowArgs, workflowInput, (toolMockConfig || aiMockConfig || promptMockConfig || userPromptMockConfig) ? { ...(toolMockConfig ? { toolMockConfig } : {}), ...(aiMockConfig ? { aiMockConfig } : {}), ...(promptMockConfig ? { promptMockConfig } : {}), ...(userPromptMockConfig ? { userPromptMockConfig } : {}) } : undefined)
|
|
662
690
|
.catch(err => {
|
|
663
691
|
throw { ok: false, error: `Workflow subprocess failed: ${formatError(err)}` };
|
|
664
692
|
});
|
|
@@ -969,6 +997,30 @@ function getDashboardHtml() {
|
|
|
969
997
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
970
998
|
<title>ElasticDash Dashboard</title>
|
|
971
999
|
<style>
|
|
1000
|
+
/* Help icon tooltip */
|
|
1001
|
+
.help-icon-wrap { position: relative; display: inline-flex; align-items: center; }
|
|
1002
|
+
.help-icon {
|
|
1003
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
1004
|
+
width: 16px; height: 16px; border-radius: 50%;
|
|
1005
|
+
background: #e0e0e0; color: #666; font-size: 11px; font-weight: bold;
|
|
1006
|
+
cursor: help; vertical-align: middle; margin-left: 4px;
|
|
1007
|
+
user-select: none;
|
|
1008
|
+
}
|
|
1009
|
+
.help-tooltip {
|
|
1010
|
+
display: none; position: absolute; left: 50%; bottom: calc(100% + 8px);
|
|
1011
|
+
transform: translateX(-50%);
|
|
1012
|
+
background: #333; color: #fff; font-size: 12px; font-weight: 400;
|
|
1013
|
+
line-height: 1.4; padding: 8px 12px; border-radius: 6px;
|
|
1014
|
+
width: 280px; white-space: normal; z-index: 10000;
|
|
1015
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.18);
|
|
1016
|
+
pointer-events: auto;
|
|
1017
|
+
}
|
|
1018
|
+
.help-tooltip::after {
|
|
1019
|
+
content: ''; position: absolute; top: 100%; left: 50%;
|
|
1020
|
+
transform: translateX(-50%);
|
|
1021
|
+
border: 6px solid transparent; border-top-color: #333;
|
|
1022
|
+
}
|
|
1023
|
+
.help-icon-wrap:hover .help-tooltip { display: block; }
|
|
972
1024
|
/* Ensure first cell in observation-table never overflows parent */
|
|
973
1025
|
.observation-table td:first-child {
|
|
974
1026
|
max-width: 120px;
|
|
@@ -1278,7 +1330,7 @@ function getDashboardHtml() {
|
|
|
1278
1330
|
window._toolMockConfig = {}; // { toolName: { mode: 'live'|'mock-all'|'mock-specific', callIndices: [], mockData: {} } }
|
|
1279
1331
|
|
|
1280
1332
|
// ---- Prompt Mock Config State ----
|
|
1281
|
-
// { [originalSystemPrompt]:
|
|
1333
|
+
// { [originalSystemPrompt]: { mode, replacement, callIndices? } }
|
|
1282
1334
|
window._promptMockConfig = {};
|
|
1283
1335
|
|
|
1284
1336
|
function getToolsFromTrace() {
|
|
@@ -1286,7 +1338,7 @@ function getDashboardHtml() {
|
|
|
1286
1338
|
const toolCalls = {};
|
|
1287
1339
|
currentObservations.forEach(function(obs, i) {
|
|
1288
1340
|
const isToolByType = obs.type === 'TOOL';
|
|
1289
|
-
const isToolByName = typeof obs.name === 'string' && obs.name.startsWith('tool-');
|
|
1341
|
+
const isToolByName = typeof obs.name === 'string' && (obs.name.startsWith('tool-') || obs.name.startsWith('tool:'));
|
|
1290
1342
|
if (!isToolByType && !isToolByName) return;
|
|
1291
1343
|
const name = isToolByName && obs.type !== 'TOOL'
|
|
1292
1344
|
? obs.name.slice(5)
|
|
@@ -1372,14 +1424,15 @@ function getDashboardHtml() {
|
|
|
1372
1424
|
return '<div style="color:#999;font-size:13px;padding:6px 0;">No tools detected.</div>';
|
|
1373
1425
|
}
|
|
1374
1426
|
|
|
1375
|
-
let html = '<div style="
|
|
1376
|
-
html += '<table style="width:100%;border-collapse:collapse;font-size:13px;">';
|
|
1427
|
+
let html = '<div style="border:1px solid #e0e0e0;border-radius:6px;height:100%;display:flex;flex-direction:column;overflow:hidden;">';
|
|
1428
|
+
html += '<table style="width:100%;border-collapse:collapse;font-size:13px;table-layout:fixed;">';
|
|
1377
1429
|
html += '<thead><tr style="background:#f5f5f5;">';
|
|
1378
|
-
html += '<th style="padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">Tool</th>';
|
|
1379
|
-
html += '<th style="padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">Calls
|
|
1380
|
-
html += '<th style="padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">
|
|
1381
|
-
html += '<th style="padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">Details</th>';
|
|
1382
|
-
html += '</tr></thead
|
|
1430
|
+
html += '<th style="width:30%;padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">Tool</th>';
|
|
1431
|
+
html += '<th style="width:15%;padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">Calls</th>';
|
|
1432
|
+
html += '<th style="width:20%;padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">Mode</th>';
|
|
1433
|
+
html += '<th style="width:35%;padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">Details</th>';
|
|
1434
|
+
html += '</tr></thead></table>';
|
|
1435
|
+
html += '<div style="flex:1;overflow-y:auto;"><table style="width:100%;border-collapse:collapse;font-size:13px;table-layout:fixed;"><tbody>';
|
|
1383
1436
|
|
|
1384
1437
|
toolNames.forEach(function(name) {
|
|
1385
1438
|
const calls = traceTools[name] || [];
|
|
@@ -1388,10 +1441,10 @@ function getDashboardHtml() {
|
|
|
1388
1441
|
const nameStyle = inTrace ? '' : 'color:#999;';
|
|
1389
1442
|
|
|
1390
1443
|
html += '<tr class="tool-mock-row" data-tool-name="' + esc(name) + '" style="border-bottom:1px solid #f0f0f0;">';
|
|
1391
|
-
html += '<td style="padding:6px 10px;font-family:Monaco,monospace;' + nameStyle + '">' + esc(name) + (inTrace ? '' : ' <span style="font-size:10px;color:#aaa;">(not in trace)</span>') + '</td>';
|
|
1392
|
-
html += '<td style="padding:6px 10px;">' + calls.length + '</td>';
|
|
1393
|
-
html += '<td style="padding:6px 10px;">';
|
|
1394
|
-
html += '<select class="tool-mock-mode" style="font-size:12px;padding:2px 4px;" onchange="window.onToolMockModeChange(\\'' + esc(name) + '\\', this.value)">';
|
|
1444
|
+
html += '<td style="width:30%;padding:6px 10px;font-family:Monaco,monospace;overflow:hidden;text-overflow:ellipsis;' + nameStyle + '">' + esc(name) + (inTrace ? '' : ' <span style="font-size:10px;color:#aaa;">(not in trace)</span>') + '</td>';
|
|
1445
|
+
html += '<td style="width:15%;padding:6px 10px;">' + calls.length + '</td>';
|
|
1446
|
+
html += '<td style="width:20%;padding:6px 10px;">';
|
|
1447
|
+
html += '<select class="tool-mock-mode" style="font-size:12px;padding:2px 4px;width: 100%;" onchange="window.onToolMockModeChange(\\'' + esc(name) + '\\', this.value)">';
|
|
1395
1448
|
html += '<option value="live"' + (existing.mode === 'live' ? ' selected' : '') + '>Live</option>';
|
|
1396
1449
|
html += '<option value="mock-all"' + (existing.mode === 'mock-all' ? ' selected' : '') + '>Mock All Calls</option>';
|
|
1397
1450
|
if (calls.length > 0) {
|
|
@@ -1401,7 +1454,7 @@ function getDashboardHtml() {
|
|
|
1401
1454
|
html += '</td>';
|
|
1402
1455
|
|
|
1403
1456
|
// Details column: per-call checkboxes + mock data inputs
|
|
1404
|
-
html += '<td style="padding:6px 10px;">';
|
|
1457
|
+
html += '<td style="width:35%;padding:6px 10px;">';
|
|
1405
1458
|
if (existing.mode === 'mock-all') {
|
|
1406
1459
|
let defaultData = (existing.mockData && existing.mockData[0] !== undefined) ? JSON.stringify(existing.mockData[0]) : (calls.length > 0 ? JSON.stringify(calls[0].output) : '');
|
|
1407
1460
|
defaultData = convert(defaultData);
|
|
@@ -1432,7 +1485,7 @@ function getDashboardHtml() {
|
|
|
1432
1485
|
html += '</tr>';
|
|
1433
1486
|
});
|
|
1434
1487
|
|
|
1435
|
-
html += '</tbody></table></div>';
|
|
1488
|
+
html += '</tbody></table></div></div>';
|
|
1436
1489
|
return html;
|
|
1437
1490
|
}
|
|
1438
1491
|
|
|
@@ -1440,9 +1493,10 @@ function getDashboardHtml() {
|
|
|
1440
1493
|
|
|
1441
1494
|
/** Extract the system prompt string from an LLM call input object or JSON string. */
|
|
1442
1495
|
function extractSystemPromptFromInput(input) {
|
|
1443
|
-
// Input may arrive as a JSON-encoded string (e.g. from Langfuse traces)
|
|
1444
|
-
|
|
1445
|
-
|
|
1496
|
+
// Input may arrive as a JSON-encoded string (e.g. from Langfuse traces).
|
|
1497
|
+
// Unwrap until we get a non-string value.
|
|
1498
|
+
while (typeof input === 'string') {
|
|
1499
|
+
try { input = JSON.parse(input); } catch(e) { break; }
|
|
1446
1500
|
}
|
|
1447
1501
|
if (!input || typeof input !== 'object') return null;
|
|
1448
1502
|
// Anthropic style: { system: "...", messages: [...] }
|
|
@@ -1464,19 +1518,24 @@ function getDashboardHtml() {
|
|
|
1464
1518
|
|
|
1465
1519
|
/**
|
|
1466
1520
|
* Returns an array of unique system prompts observed across all GENERATION observations.
|
|
1467
|
-
* Each entry: { systemPrompt, modelName, count, rowIndex }
|
|
1521
|
+
* Each entry: { systemPrompt, modelName, calls: [{obsIndex, callNumber}], count, rowIndex }
|
|
1522
|
+
* callNumber is the 1-indexed occurrence of this prompt across GENERATION observations.
|
|
1468
1523
|
*/
|
|
1469
1524
|
function getSystemPromptsFromTrace() {
|
|
1470
|
-
var seen = []; // [{ systemPrompt, modelName, count }]
|
|
1525
|
+
var seen = []; // [{ systemPrompt, modelName, calls, count }]
|
|
1471
1526
|
var seenMap = {}; // systemPrompt -> index in seen
|
|
1472
|
-
|
|
1527
|
+
var callCounters = {}; // systemPrompt -> running count
|
|
1528
|
+
currentObservations.forEach(function(obs, obsIndex) {
|
|
1473
1529
|
if (obs.type !== 'GENERATION') return;
|
|
1474
1530
|
var sp = extractSystemPromptFromInput(obs.input);
|
|
1475
1531
|
if (!sp) return;
|
|
1532
|
+
callCounters[sp] = (callCounters[sp] || 0) + 1;
|
|
1533
|
+
var callNumber = callCounters[sp];
|
|
1476
1534
|
if (seenMap[sp] === undefined) {
|
|
1477
1535
|
seenMap[sp] = seen.length;
|
|
1478
|
-
seen.push({ systemPrompt: sp, modelName: obs.model || obs.name || '(unknown)', count: 0 });
|
|
1536
|
+
seen.push({ systemPrompt: sp, modelName: obs.model || obs.name || '(unknown)', calls: [], count: 0 });
|
|
1479
1537
|
}
|
|
1538
|
+
seen[seenMap[sp]].calls.push({ obsIndex: obsIndex, callNumber: callNumber });
|
|
1480
1539
|
seen[seenMap[sp]].count++;
|
|
1481
1540
|
});
|
|
1482
1541
|
return seen.map(function(e, i) { return Object.assign({}, e, { rowIndex: i }); });
|
|
@@ -1488,82 +1547,145 @@ function getDashboardHtml() {
|
|
|
1488
1547
|
return '<div style="color:#999;font-size:13px;padding:6px 0;">No system prompts detected in trace. Only AI calls with a system prompt can be mocked here.</div>';
|
|
1489
1548
|
}
|
|
1490
1549
|
|
|
1491
|
-
let html = '<div style="
|
|
1492
|
-
html += '<table style="width:100%;border-collapse:collapse;font-size:13px;">';
|
|
1550
|
+
let html = '<div style="border:1px solid #e0e0e0;border-radius:6px;height:100%;display:flex;flex-direction:column;overflow:hidden;">';
|
|
1551
|
+
html += '<table style="width:100%;border-collapse:collapse;font-size:13px;table-layout:fixed;">';
|
|
1493
1552
|
html += '<thead><tr style="background:#f5f5f5;">';
|
|
1494
|
-
html += '<th style="padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;
|
|
1495
|
-
html += '<th style="padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">
|
|
1496
|
-
html += '<th style="padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">
|
|
1497
|
-
html += '<th style="padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">
|
|
1498
|
-
html += '</tr></thead
|
|
1553
|
+
html += '<th style="width:25%;padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">System Prompt</th>';
|
|
1554
|
+
html += '<th style="width:10%;padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">Calls</th>';
|
|
1555
|
+
html += '<th style="width:20%;padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">Mode</th>';
|
|
1556
|
+
html += '<th style="width:45%;padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">Replacement</th>';
|
|
1557
|
+
html += '</tr></thead></table>';
|
|
1558
|
+
html += '<div style="flex:1;overflow-y:auto;"><table style="width:100%;border-collapse:collapse;font-size:13px;table-layout:fixed;"><tbody>';
|
|
1499
1559
|
|
|
1500
1560
|
prompts.forEach(function(row) {
|
|
1501
1561
|
const key = row.systemPrompt;
|
|
1502
|
-
const
|
|
1503
|
-
const
|
|
1504
|
-
const preview = key.length > 80 ? key.slice(0, 80) + '…' : key;
|
|
1562
|
+
const existing = window._promptMockConfig[key] || { mode: 'live', replacement: '' };
|
|
1563
|
+
const preview = key.length > 60 ? key.slice(0, 60) + '…' : key;
|
|
1505
1564
|
|
|
1506
1565
|
html += '<tr class="prompt-mock-row" data-row-index="' + row.rowIndex + '" style="border-bottom:1px solid #f0f0f0;vertical-align:top;">';
|
|
1507
1566
|
|
|
1508
|
-
//
|
|
1509
|
-
html += '<td style="padding:
|
|
1510
|
-
html += '<input type="checkbox" class="prompt-mock-enable" title="Override this system prompt"' + (isEnabled ? ' checked' : '') + ' onchange="window.onPromptMockToggle(' + row.rowIndex + ', this.checked)">';
|
|
1511
|
-
html += '</td>';
|
|
1567
|
+
// System prompt preview column
|
|
1568
|
+
html += '<td style="width:25%;padding:6px 10px;overflow:hidden;text-overflow:ellipsis;"><div style="font-size:11px;color:#555;font-family:Monaco,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="' + esc(key) + '">' + esc(preview) + '</div></td>';
|
|
1512
1569
|
|
|
1513
|
-
//
|
|
1514
|
-
html += '<td style="padding:
|
|
1570
|
+
// Calls count column
|
|
1571
|
+
html += '<td style="width:10%;padding:6px 10px;color:#555;white-space:nowrap;">' + row.count + 'x</td>';
|
|
1515
1572
|
|
|
1516
|
-
//
|
|
1517
|
-
html += '<td style="padding:
|
|
1573
|
+
// Mode column
|
|
1574
|
+
html += '<td style="width:20%;padding:6px 10px;white-space:nowrap;">';
|
|
1575
|
+
html += '<select class="prompt-mock-mode" style="font-size:12px;padding:2px 4px;width: 100%;" onchange="window.onSystemPromptMockModeChange(' + row.rowIndex + ', this.value)">';
|
|
1576
|
+
html += '<option value="live"' + (existing.mode === 'live' ? ' selected' : '') + '>Live</option>';
|
|
1577
|
+
html += '<option value="replace-all"' + (existing.mode === 'replace-all' ? ' selected' : '') + '>Replace All</option>';
|
|
1578
|
+
if (row.count > 1) {
|
|
1579
|
+
html += '<option value="replace-specific"' + (existing.mode === 'replace-specific' ? ' selected' : '') + '>Replace Specific</option>';
|
|
1580
|
+
}
|
|
1581
|
+
html += '</select>';
|
|
1582
|
+
html += '</td>';
|
|
1518
1583
|
|
|
1519
|
-
//
|
|
1520
|
-
html += '<td style="padding:
|
|
1521
|
-
if (
|
|
1522
|
-
html += '<
|
|
1584
|
+
// Replacement column
|
|
1585
|
+
html += '<td style="width:45%;padding:6px 10px;">';
|
|
1586
|
+
if (existing.mode === 'replace-all') {
|
|
1587
|
+
html += '<textarea class="prompt-mock-input" data-row-index="' + row.rowIndex + '" style="width:100%;box-sizing:border-box;font-size:11px;font-family:Monaco,monospace;padding:4px;border:1px solid #ddd;border-radius:4px;min-height:48px;resize:vertical;" oninput="window.onPromptMockInput(' + row.rowIndex + ', this.value)">' + esc(existing.replacement || key) + '</textarea>';
|
|
1588
|
+
} else if (existing.mode === 'replace-specific') {
|
|
1589
|
+
html += '<div style="font-size:11px;color:#555;margin-bottom:4px;">Select calls to replace:</div>';
|
|
1590
|
+
row.calls.forEach(function(call) {
|
|
1591
|
+
const isChecked = existing.callIndices && existing.callIndices.indexOf(call.callNumber) !== -1;
|
|
1592
|
+
html += '<div style="margin-bottom:4px;padding:4px;background:#fafafa;border-radius:4px;border:1px solid #eee;">';
|
|
1593
|
+
html += '<label style="display:flex;align-items:center;gap:6px;font-size:12px;cursor:pointer;">';
|
|
1594
|
+
html += '<input type="checkbox" class="prompt-call-checkbox" value="' + call.callNumber + '"' + (isChecked ? ' checked' : '') + ' onchange="window.onSystemPromptCallCheckChange(' + row.rowIndex + ',' + call.callNumber + ',this.checked)">';
|
|
1595
|
+
html += '<span>Call #' + call.callNumber + '</span>';
|
|
1596
|
+
html += '</label>';
|
|
1597
|
+
html += '</div>';
|
|
1598
|
+
});
|
|
1599
|
+
html += '<textarea class="prompt-mock-input" data-row-index="' + row.rowIndex + '" style="width:100%;box-sizing:border-box;font-size:11px;font-family:Monaco,monospace;padding:4px;border:1px solid #ddd;border-radius:4px;min-height:48px;resize:vertical;margin-top:4px;" oninput="window.onPromptMockInput(' + row.rowIndex + ', this.value)">' + esc(existing.replacement || key) + '</textarea>';
|
|
1523
1600
|
} else {
|
|
1524
|
-
html += '<
|
|
1601
|
+
html += '<span style="color:#aaa;font-size:11px;">—</span>';
|
|
1525
1602
|
}
|
|
1526
1603
|
html += '</td>';
|
|
1527
1604
|
|
|
1528
1605
|
html += '</tr>';
|
|
1529
1606
|
});
|
|
1530
1607
|
|
|
1531
|
-
html += '</tbody></table></div>';
|
|
1608
|
+
html += '</tbody></table></div></div>';
|
|
1532
1609
|
return html;
|
|
1533
1610
|
}
|
|
1534
1611
|
|
|
1535
1612
|
function buildPromptMockConfigFromUI() {
|
|
1536
|
-
// Return _promptMockConfig as-is (string key → string value), filtering out blank values
|
|
1537
1613
|
const config = {};
|
|
1538
1614
|
Object.keys(window._promptMockConfig).forEach(function(key) {
|
|
1539
|
-
const
|
|
1540
|
-
if (
|
|
1615
|
+
const entry = window._promptMockConfig[key];
|
|
1616
|
+
if (!entry || entry.mode === 'live') return;
|
|
1617
|
+
config[key] = { mode: entry.mode, replacement: entry.replacement || '' };
|
|
1618
|
+
if (entry.mode === 'replace-specific' && entry.callIndices) {
|
|
1619
|
+
config[key].callIndices = entry.callIndices;
|
|
1620
|
+
}
|
|
1541
1621
|
});
|
|
1542
1622
|
return config;
|
|
1543
1623
|
}
|
|
1544
1624
|
|
|
1545
|
-
window.
|
|
1625
|
+
window.onSystemPromptMockModeChange = function(rowIndex, mode) {
|
|
1546
1626
|
const prompts = getSystemPromptsFromTrace();
|
|
1547
1627
|
const row = prompts[rowIndex];
|
|
1548
1628
|
if (!row) return;
|
|
1549
1629
|
const key = row.systemPrompt;
|
|
1550
|
-
if (
|
|
1630
|
+
if (mode === 'live') {
|
|
1551
1631
|
delete window._promptMockConfig[key];
|
|
1552
1632
|
} else {
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1633
|
+
if (!window._promptMockConfig[key]) {
|
|
1634
|
+
window._promptMockConfig[key] = { mode: mode, replacement: row.systemPrompt };
|
|
1635
|
+
} else {
|
|
1636
|
+
window._promptMockConfig[key].mode = mode;
|
|
1637
|
+
}
|
|
1638
|
+
if (mode === 'replace-specific' && !window._promptMockConfig[key].callIndices) {
|
|
1639
|
+
window._promptMockConfig[key].callIndices = [];
|
|
1556
1640
|
}
|
|
1557
1641
|
}
|
|
1558
1642
|
const container = document.getElementById('promptMockContainer');
|
|
1559
1643
|
if (container) container.innerHTML = renderPromptMockSection();
|
|
1560
1644
|
};
|
|
1561
1645
|
|
|
1646
|
+
window.onSystemPromptCallCheckChange = function(rowIndex, callNumber, checked) {
|
|
1647
|
+
const prompts = getSystemPromptsFromTrace();
|
|
1648
|
+
const row = prompts[rowIndex];
|
|
1649
|
+
if (!row) return;
|
|
1650
|
+
const key = row.systemPrompt;
|
|
1651
|
+
if (!window._promptMockConfig[key]) return;
|
|
1652
|
+
var indices = window._promptMockConfig[key].callIndices || [];
|
|
1653
|
+
if (checked) {
|
|
1654
|
+
if (indices.indexOf(callNumber) === -1) indices.push(callNumber);
|
|
1655
|
+
} else {
|
|
1656
|
+
var pos = indices.indexOf(callNumber);
|
|
1657
|
+
if (pos >= 0) indices.splice(pos, 1);
|
|
1658
|
+
}
|
|
1659
|
+
window._promptMockConfig[key].callIndices = indices;
|
|
1660
|
+
};
|
|
1661
|
+
|
|
1662
|
+
window.switchMockTab = function(tabName) {
|
|
1663
|
+
const tabs = {
|
|
1664
|
+
tool: document.getElementById('mockTabTool'),
|
|
1665
|
+
prompt: document.getElementById('mockTabPrompt'),
|
|
1666
|
+
userPrompt: document.getElementById('mockTabUserPrompt'),
|
|
1667
|
+
};
|
|
1668
|
+
const contents = {
|
|
1669
|
+
tool: document.getElementById('mockTabToolContent'),
|
|
1670
|
+
prompt: document.getElementById('mockTabPromptContent'),
|
|
1671
|
+
userPrompt: document.getElementById('mockTabUserPromptContent'),
|
|
1672
|
+
};
|
|
1673
|
+
const toolExtra = document.getElementById('mockTabToolExtra');
|
|
1674
|
+
Object.keys(tabs).forEach(function(key) {
|
|
1675
|
+
const isActive = key === tabName;
|
|
1676
|
+
if (tabs[key]) { tabs[key].style.borderBottomColor = isActive ? '#4f46e5' : 'transparent'; tabs[key].style.color = isActive ? '#4f46e5' : '#888'; }
|
|
1677
|
+
if (contents[key]) contents[key].style.display = isActive ? '' : 'none';
|
|
1678
|
+
});
|
|
1679
|
+
if (toolExtra) toolExtra.style.display = tabName === 'tool' ? 'flex' : 'none';
|
|
1680
|
+
};
|
|
1681
|
+
|
|
1562
1682
|
window.onPromptMockInput = function(rowIndex, value) {
|
|
1563
1683
|
const prompts = getSystemPromptsFromTrace();
|
|
1564
1684
|
const row = prompts[rowIndex];
|
|
1565
1685
|
if (!row) return;
|
|
1566
|
-
window._promptMockConfig[row.systemPrompt]
|
|
1686
|
+
if (window._promptMockConfig[row.systemPrompt]) {
|
|
1687
|
+
window._promptMockConfig[row.systemPrompt].replacement = value;
|
|
1688
|
+
}
|
|
1567
1689
|
};
|
|
1568
1690
|
|
|
1569
1691
|
window.onToolMockModeChange = function(toolName, mode) {
|
|
@@ -1594,10 +1716,187 @@ function getDashboardHtml() {
|
|
|
1594
1716
|
if (container) container.innerHTML = renderToolMockSection(showAll && showAll.checked);
|
|
1595
1717
|
};
|
|
1596
1718
|
|
|
1719
|
+
// ---- User Prompt Mock Helpers ----
|
|
1720
|
+
|
|
1721
|
+
/** Extract all user-role message content strings from an LLM call input. */
|
|
1722
|
+
function extractUserPromptsFromInput(input) {
|
|
1723
|
+
// Unwrap JSON-encoded strings until we get a non-string value.
|
|
1724
|
+
while (typeof input === 'string') {
|
|
1725
|
+
try { input = JSON.parse(input); } catch(e) { break; }
|
|
1726
|
+
}
|
|
1727
|
+
if (!input || typeof input !== 'object') return [];
|
|
1728
|
+
var msgs = Array.isArray(input.messages) ? input.messages : (Array.isArray(input) ? input : null);
|
|
1729
|
+
if (!msgs) return [];
|
|
1730
|
+
var results = [];
|
|
1731
|
+
for (var i = 0; i < msgs.length; i++) {
|
|
1732
|
+
var m = msgs[i];
|
|
1733
|
+
if (m && typeof m === 'object' && m.role === 'user' && typeof m.content === 'string') {
|
|
1734
|
+
results.push(m.content);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
return results;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
/**
|
|
1741
|
+
* Returns an array of unique user prompt texts observed across all GENERATION observations.
|
|
1742
|
+
* Each entry: { userPrompt, modelName, calls: [{obsIndex, callNumber}], count, rowIndex }
|
|
1743
|
+
* callNumber is the 1-indexed occurrence of this text across GENERATION observations.
|
|
1744
|
+
*/
|
|
1745
|
+
function getUserPromptsFromTrace() {
|
|
1746
|
+
var seen = []; // [{ userPrompt, modelName, calls, count }]
|
|
1747
|
+
var seenMap = {}; // userPrompt -> index in seen
|
|
1748
|
+
var callCounters = {}; // userPrompt -> running count
|
|
1749
|
+
currentObservations.forEach(function(obs, obsIndex) {
|
|
1750
|
+
if (obs.type !== 'GENERATION') return;
|
|
1751
|
+
var prompts = extractUserPromptsFromInput(obs.input);
|
|
1752
|
+
var uniqueInCall = [];
|
|
1753
|
+
prompts.forEach(function(p) { if (uniqueInCall.indexOf(p) === -1) uniqueInCall.push(p); });
|
|
1754
|
+
uniqueInCall.forEach(function(p) {
|
|
1755
|
+
callCounters[p] = (callCounters[p] || 0) + 1;
|
|
1756
|
+
var callNumber = callCounters[p];
|
|
1757
|
+
if (seenMap[p] === undefined) {
|
|
1758
|
+
seenMap[p] = seen.length;
|
|
1759
|
+
seen.push({ userPrompt: p, modelName: obs.model || obs.name || '(unknown)', calls: [], count: 0 });
|
|
1760
|
+
}
|
|
1761
|
+
seen[seenMap[p]].calls.push({ obsIndex: obsIndex, callNumber: callNumber });
|
|
1762
|
+
seen[seenMap[p]].count++;
|
|
1763
|
+
});
|
|
1764
|
+
});
|
|
1765
|
+
return seen.map(function(e, i) { return Object.assign({}, e, { rowIndex: i }); });
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
function renderUserPromptMockSection() {
|
|
1769
|
+
const prompts = getUserPromptsFromTrace();
|
|
1770
|
+
if (prompts.length === 0) {
|
|
1771
|
+
return '<div style="color:#999;font-size:13px;padding:6px 0;">No user prompts detected in trace. Only AI calls with user-role messages can be mocked here.</div>';
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
let html = '<div style="border:1px solid #e0e0e0;border-radius:6px;height:100%;display:flex;flex-direction:column;overflow:hidden;">';
|
|
1775
|
+
html += '<table style="width:100%;border-collapse:collapse;font-size:13px;table-layout:fixed;">';
|
|
1776
|
+
html += '<thead><tr style="background:#f5f5f5;">';
|
|
1777
|
+
html += '<th style="width:30%;padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">User Prompt</th>';
|
|
1778
|
+
html += '<th style="width:10%;padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">Calls</th>';
|
|
1779
|
+
html += '<th style="width:20%;padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">Mode</th>';
|
|
1780
|
+
html += '<th style="width:40%;padding:6px 10px;text-align:left;border-bottom:1px solid #e0e0e0;">Replacement</th>';
|
|
1781
|
+
html += '</tr></thead></table>';
|
|
1782
|
+
html += '<div style="flex:1;overflow-y:auto;"><table style="width:100%;border-collapse:collapse;font-size:13px;table-layout:fixed;"><tbody>';
|
|
1783
|
+
|
|
1784
|
+
prompts.forEach(function(row) {
|
|
1785
|
+
const key = row.userPrompt;
|
|
1786
|
+
const existing = window._userPromptMockConfig[key] || { mode: 'live', replacement: '' };
|
|
1787
|
+
const preview = key.length > 60 ? key.slice(0, 60) + '…' : key;
|
|
1788
|
+
|
|
1789
|
+
html += '<tr class="user-prompt-mock-row" data-row-index="' + row.rowIndex + '" style="border-bottom:1px solid #f0f0f0;vertical-align:top;">';
|
|
1790
|
+
|
|
1791
|
+
// User prompt preview column
|
|
1792
|
+
html += '<td style="width:30%;padding:6px 10px;overflow:hidden;text-overflow:ellipsis;"><div style="font-size:11px;color:#555;font-family:Monaco,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="' + esc(key) + '">' + esc(preview) + '</div></td>';
|
|
1793
|
+
|
|
1794
|
+
// Calls count column
|
|
1795
|
+
html += '<td style="width:10%;padding:6px 10px;color:#555;white-space:nowrap;">' + row.count + 'x</td>';
|
|
1796
|
+
|
|
1797
|
+
// Mode column
|
|
1798
|
+
html += '<td style="width:20%;padding:6px 10px;white-space:nowrap;">';
|
|
1799
|
+
html += '<select class="user-prompt-mock-mode" style="font-size:12px;padding:2px 4px;width: 100%;" onchange="window.onUserPromptMockModeChange(' + row.rowIndex + ', this.value)">';
|
|
1800
|
+
html += '<option value="live"' + (existing.mode === 'live' ? ' selected' : '') + '>Live</option>';
|
|
1801
|
+
html += '<option value="replace-all"' + (existing.mode === 'replace-all' ? ' selected' : '') + '>Replace All</option>';
|
|
1802
|
+
if (row.count > 1) {
|
|
1803
|
+
html += '<option value="replace-specific"' + (existing.mode === 'replace-specific' ? ' selected' : '') + '>Replace Specific</option>';
|
|
1804
|
+
}
|
|
1805
|
+
html += '</select>';
|
|
1806
|
+
html += '</td>';
|
|
1807
|
+
|
|
1808
|
+
// Replacement column
|
|
1809
|
+
html += '<td style="width:40%;padding:6px 10px;">';
|
|
1810
|
+
if (existing.mode === 'replace-all') {
|
|
1811
|
+
html += '<textarea class="user-prompt-mock-input" data-row-index="' + row.rowIndex + '" style="width:100%;box-sizing:border-box;font-size:11px;font-family:Monaco,monospace;padding:4px;border:1px solid #ddd;border-radius:4px;min-height:48px;resize:vertical;" oninput="window.onUserPromptMockInput(' + row.rowIndex + ', this.value)">' + esc(existing.replacement || key) + '</textarea>';
|
|
1812
|
+
} else if (existing.mode === 'replace-specific') {
|
|
1813
|
+
html += '<div style="font-size:11px;color:#555;margin-bottom:4px;">Select calls to replace:</div>';
|
|
1814
|
+
row.calls.forEach(function(call) {
|
|
1815
|
+
const isChecked = existing.callIndices && existing.callIndices.indexOf(call.callNumber) !== -1;
|
|
1816
|
+
html += '<div style="margin-bottom:4px;padding:4px;background:#fafafa;border-radius:4px;border:1px solid #eee;">';
|
|
1817
|
+
html += '<label style="display:flex;align-items:center;gap:6px;font-size:12px;cursor:pointer;">';
|
|
1818
|
+
html += '<input type="checkbox" class="user-prompt-call-checkbox" value="' + call.callNumber + '"' + (isChecked ? ' checked' : '') + ' onchange="window.onUserPromptCallCheckChange(' + row.rowIndex + ',' + call.callNumber + ',this.checked)">';
|
|
1819
|
+
html += '<span>Call #' + call.callNumber + '</span>';
|
|
1820
|
+
html += '</label>';
|
|
1821
|
+
html += '</div>';
|
|
1822
|
+
});
|
|
1823
|
+
html += '<textarea class="user-prompt-mock-input" data-row-index="' + row.rowIndex + '" style="width:100%;box-sizing:border-box;font-size:11px;font-family:Monaco,monospace;padding:4px;border:1px solid #ddd;border-radius:4px;min-height:48px;resize:vertical;margin-top:4px;" oninput="window.onUserPromptMockInput(' + row.rowIndex + ', this.value)">' + esc(existing.replacement || key) + '</textarea>';
|
|
1824
|
+
} else {
|
|
1825
|
+
html += '<span style="color:#aaa;font-size:11px;">—</span>';
|
|
1826
|
+
}
|
|
1827
|
+
html += '</td>';
|
|
1828
|
+
|
|
1829
|
+
html += '</tr>';
|
|
1830
|
+
});
|
|
1831
|
+
|
|
1832
|
+
html += '</tbody></table></div></div>';
|
|
1833
|
+
return html;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
function buildUserPromptMockConfigFromUI() {
|
|
1837
|
+
const config = {};
|
|
1838
|
+
Object.keys(window._userPromptMockConfig).forEach(function(key) {
|
|
1839
|
+
const entry = window._userPromptMockConfig[key];
|
|
1840
|
+
if (!entry || entry.mode === 'live') return;
|
|
1841
|
+
config[key] = { mode: entry.mode, replacement: entry.replacement || '' };
|
|
1842
|
+
if (entry.mode === 'replace-specific' && entry.callIndices) {
|
|
1843
|
+
config[key].callIndices = entry.callIndices;
|
|
1844
|
+
}
|
|
1845
|
+
});
|
|
1846
|
+
return config;
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
window.onUserPromptMockModeChange = function(rowIndex, mode) {
|
|
1850
|
+
const prompts = getUserPromptsFromTrace();
|
|
1851
|
+
const row = prompts[rowIndex];
|
|
1852
|
+
if (!row) return;
|
|
1853
|
+
const key = row.userPrompt;
|
|
1854
|
+
if (mode === 'live') {
|
|
1855
|
+
delete window._userPromptMockConfig[key];
|
|
1856
|
+
} else {
|
|
1857
|
+
if (!window._userPromptMockConfig[key]) {
|
|
1858
|
+
window._userPromptMockConfig[key] = { mode: mode, replacement: row.userPrompt };
|
|
1859
|
+
} else {
|
|
1860
|
+
window._userPromptMockConfig[key].mode = mode;
|
|
1861
|
+
}
|
|
1862
|
+
if (mode === 'replace-specific' && !window._userPromptMockConfig[key].callIndices) {
|
|
1863
|
+
window._userPromptMockConfig[key].callIndices = [];
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
const container = document.getElementById('userPromptMockContainer');
|
|
1867
|
+
if (container) container.innerHTML = renderUserPromptMockSection();
|
|
1868
|
+
};
|
|
1869
|
+
|
|
1870
|
+
window.onUserPromptMockInput = function(rowIndex, value) {
|
|
1871
|
+
const prompts = getUserPromptsFromTrace();
|
|
1872
|
+
const row = prompts[rowIndex];
|
|
1873
|
+
if (!row) return;
|
|
1874
|
+
if (window._userPromptMockConfig[row.userPrompt]) {
|
|
1875
|
+
window._userPromptMockConfig[row.userPrompt].replacement = value;
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
|
|
1879
|
+
window.onUserPromptCallCheckChange = function(rowIndex, callNumber, checked) {
|
|
1880
|
+
const prompts = getUserPromptsFromTrace();
|
|
1881
|
+
const row = prompts[rowIndex];
|
|
1882
|
+
if (!row) return;
|
|
1883
|
+
const key = row.userPrompt;
|
|
1884
|
+
if (!window._userPromptMockConfig[key]) return;
|
|
1885
|
+
var indices = window._userPromptMockConfig[key].callIndices || [];
|
|
1886
|
+
if (checked) {
|
|
1887
|
+
if (indices.indexOf(callNumber) === -1) indices.push(callNumber);
|
|
1888
|
+
} else {
|
|
1889
|
+
var pos = indices.indexOf(callNumber);
|
|
1890
|
+
if (pos >= 0) indices.splice(pos, 1);
|
|
1891
|
+
}
|
|
1892
|
+
window._userPromptMockConfig[key].callIndices = indices;
|
|
1893
|
+
};
|
|
1894
|
+
|
|
1597
1895
|
window.openLiveValidationDialog = function() {
|
|
1598
1896
|
if (window.liveValidationDialog) return;
|
|
1599
1897
|
window._toolMockConfig = {}; // Reset mock configs each time dialog opens
|
|
1600
1898
|
window._promptMockConfig = {};
|
|
1899
|
+
window._userPromptMockConfig = {};
|
|
1601
1900
|
|
|
1602
1901
|
const hasTraceTools = currentObservations.some(function(o) { return o.type === 'TOOL'; });
|
|
1603
1902
|
const hasRegisteredTools = codeIndex.tools && codeIndex.tools.length > 0;
|
|
@@ -1606,7 +1905,7 @@ function getDashboardHtml() {
|
|
|
1606
1905
|
window.liveValidationDialog.id = 'liveValidationDialog';
|
|
1607
1906
|
window.liveValidationDialog.style = 'position:fixed;top:0;left:0;width:100vw;height:100vh;background:rgba(0,0,0,0.25);display:flex;align-items:center;justify-content:center;z-index:9999;';
|
|
1608
1907
|
window.liveValidationDialog.innerHTML = \`
|
|
1609
|
-
<div style="background:white;padding:32px 28px;border-radius:12px;box-shadow:0 2px 24px #0002;
|
|
1908
|
+
<div style="background:white;padding:32px 28px;border-radius:12px;box-shadow:0 2px 24px #0002;width:680px;max-width:90vw;max-height:90vh;overflow-y:auto;">
|
|
1610
1909
|
<h3 style="margin-top:0;margin-bottom:18px;font-size:20px;">Validate Updated Flow with Live Data</h3>
|
|
1611
1910
|
<label style="font-size:15px;display:block;margin-bottom:8px;">How many times do you want to run the flow with live data?</label>
|
|
1612
1911
|
<input id="liveValidationCount" type="number" min="1" value="1" style="width:100%;font-size:16px;padding:6px 10px;margin-bottom:18px;" />
|
|
@@ -1614,23 +1913,29 @@ function getDashboardHtml() {
|
|
|
1614
1913
|
<input id="liveValidationSequential" type="checkbox" />
|
|
1615
1914
|
Run in sequence instead of parallel
|
|
1616
1915
|
</label>
|
|
1617
|
-
\${(hasTraceTools || hasRegisteredTools) ? \`
|
|
1618
1916
|
<div style="border-top:1px solid #eee;padding-top:16px;margin-bottom:16px;">
|
|
1619
|
-
<div style="display:flex;
|
|
1620
|
-
|
|
1621
|
-
<
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1917
|
+
<div style="display:flex;align-items:center;gap:0;border-bottom:2px solid #e0e0e0;margin-bottom:0;">
|
|
1918
|
+
\${(hasTraceTools || hasRegisteredTools) ? \`<button id="mockTabTool" class="mock-tab-btn" onclick="window.switchMockTab('tool')" style="padding:8px 18px;font-size:14px;font-weight:600;border:none;background:none;cursor:pointer;border-bottom:2px solid #4f46e5;color:#4f46e5;margin-bottom:-2px;">Tool Mocking <span class="help-icon-wrap"><span class="help-icon">?</span><span class="help-tooltip">Control which tools use real calls vs mock data. ‘Live’ runs the real tool, ‘Mock All Calls’ returns mock data for every call, and ‘Mock Specific Calls’ lets you pick which call indices to mock.</span></span></button>\` : ''}
|
|
1919
|
+
<button id="mockTabPrompt" class="mock-tab-btn" onclick="window.switchMockTab('prompt')" style="padding:8px 18px;font-size:14px;font-weight:600;border:none;background:none;cursor:pointer;border-bottom:2px solid \${(hasTraceTools || hasRegisteredTools) ? 'transparent' : '#4f46e5'};color:\${(hasTraceTools || hasRegisteredTools) ? '#888' : '#4f46e5'};margin-bottom:-2px;">System Prompt <span class="help-icon-wrap"><span class="help-icon">?</span><span class="help-tooltip">Override the system prompt for LLM calls. Check a row to enable editing — the replacement applies to all LLM calls that use that exact system prompt text.</span></span></button>
|
|
1920
|
+
<button id="mockTabUserPrompt" class="mock-tab-btn" onclick="window.switchMockTab('userPrompt')" style="padding:8px 18px;font-size:14px;font-weight:600;border:none;background:none;cursor:pointer;border-bottom:2px solid transparent;color:#888;margin-bottom:-2px;">User Prompt <span class="help-icon-wrap"><span class="help-icon">?</span><span class="help-tooltip">Override user-role messages in LLM calls. Each unique user message text is listed. Choose ‘Replace All’ to replace it in every LLM call where it appears, or ‘Replace Specific’ to only replace it in selected call occurrences.</span></span></button>
|
|
1921
|
+
<div id="mockTabToolExtra" style="margin-left:auto;display:flex;align-items:center;">
|
|
1922
|
+
\${(hasTraceTools || hasRegisteredTools) ? \`<label style="display:flex;align-items:center;gap:6px;font-size:13px;cursor:pointer;">
|
|
1923
|
+
<input id="showAllToolsToggle" type="checkbox" onchange="document.getElementById('toolMockContainer').innerHTML = renderToolMockSection(this.checked);" />
|
|
1924
|
+
Show all tools
|
|
1925
|
+
</label>\` : ''}
|
|
1926
|
+
</div>
|
|
1625
1927
|
</div>
|
|
1626
|
-
<div
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
<div style="
|
|
1631
|
-
|
|
1928
|
+
<div style="height:300px;position:relative;">
|
|
1929
|
+
<div id="mockTabToolContent" style="height:100%;overflow:hidden;\${(hasTraceTools || hasRegisteredTools) ? '' : 'display:none;'}">
|
|
1930
|
+
<div id="toolMockContainer" style="height:100%;"></div>
|
|
1931
|
+
</div>
|
|
1932
|
+
<div id="mockTabPromptContent" style="height:100%;overflow:hidden;\${(hasTraceTools || hasRegisteredTools) ? 'display:none;' : ''}">
|
|
1933
|
+
<div id="promptMockContainer" style="height:100%;"></div>
|
|
1934
|
+
</div>
|
|
1935
|
+
<div id="mockTabUserPromptContent" style="height:100%;overflow:hidden;display:none;">
|
|
1936
|
+
<div id="userPromptMockContainer" style="height:100%;"></div>
|
|
1937
|
+
</div>
|
|
1632
1938
|
</div>
|
|
1633
|
-
<div id="promptMockContainer" style="max-height:160px;overflow-y:auto;"></div>
|
|
1634
1939
|
</div>
|
|
1635
1940
|
<div style="display:flex;gap:12px;justify-content:space-between;align-items:center;">
|
|
1636
1941
|
<span id="liveValidationProgress" style="font-size:14px;color:#555;"></span>
|
|
@@ -1651,6 +1956,10 @@ function getDashboardHtml() {
|
|
|
1651
1956
|
if (promptMockContainer) {
|
|
1652
1957
|
promptMockContainer.innerHTML = renderPromptMockSection();
|
|
1653
1958
|
}
|
|
1959
|
+
const userPromptMockContainer = document.getElementById('userPromptMockContainer');
|
|
1960
|
+
if (userPromptMockContainer) {
|
|
1961
|
+
userPromptMockContainer.innerHTML = renderUserPromptMockSection();
|
|
1962
|
+
}
|
|
1654
1963
|
document.getElementById('cancelLiveValidation').onclick = function() {
|
|
1655
1964
|
window.liveValidationDialog.remove();
|
|
1656
1965
|
window.liveValidationDialog = null;
|
|
@@ -1664,6 +1973,8 @@ function getDashboardHtml() {
|
|
|
1664
1973
|
window._toolMockConfig = toolMockConfig;
|
|
1665
1974
|
const promptMockConfig = buildPromptMockConfigFromUI();
|
|
1666
1975
|
window._promptMockConfig = promptMockConfig;
|
|
1976
|
+
const userPromptMockConfig = buildUserPromptMockConfigFromUI();
|
|
1977
|
+
window._userPromptMockConfig = userPromptMockConfig;
|
|
1667
1978
|
const submitBtn = document.getElementById('submitLiveValidation');
|
|
1668
1979
|
submitBtn.disabled = true;
|
|
1669
1980
|
submitBtn.textContent = 'Validating...';
|
|
@@ -1700,7 +2011,7 @@ function getDashboardHtml() {
|
|
|
1700
2011
|
const collectedTraces = [];
|
|
1701
2012
|
let fatalError = null;
|
|
1702
2013
|
for (let i = 0; i < count; i++) {
|
|
1703
|
-
const singlePayload = { workflowName: selectedWorkflow?.name, runCount: 1, sequential: false, observations: currentObservations, toolMockConfig, promptMockConfig };
|
|
2014
|
+
const singlePayload = { workflowName: selectedWorkflow?.name, runCount: 1, sequential: false, observations: currentObservations, toolMockConfig, promptMockConfig, userPromptMockConfig };
|
|
1704
2015
|
try {
|
|
1705
2016
|
const response = await fetch('/api/validate-workflow', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(singlePayload) });
|
|
1706
2017
|
const data = await response.json();
|
|
@@ -1719,7 +2030,7 @@ function getDashboardHtml() {
|
|
|
1719
2030
|
} else {
|
|
1720
2031
|
// Parallel mode: single bulk request
|
|
1721
2032
|
if (progressEl) progressEl.textContent = \`Running \${count} workflow run\${count !== 1 ? 's' : ''} in parallel…\`;
|
|
1722
|
-
const payload = { workflowName: selectedWorkflow?.name, runCount: count, sequential: false, observations: currentObservations, toolMockConfig, promptMockConfig };
|
|
2033
|
+
const payload = { workflowName: selectedWorkflow?.name, runCount: count, sequential: false, observations: currentObservations, toolMockConfig, promptMockConfig, userPromptMockConfig };
|
|
1723
2034
|
try {
|
|
1724
2035
|
const response = await fetch('/api/validate-workflow', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
|
|
1725
2036
|
const data = await response.json();
|
|
@@ -1911,6 +2222,7 @@ function getDashboardHtml() {
|
|
|
1911
2222
|
}
|
|
1912
2223
|
}
|
|
1913
2224
|
|
|
2225
|
+
|
|
1914
2226
|
function renderObservationTable() {
|
|
1915
2227
|
// Capture scroll positions for step 5 columns before rebuilding DOM
|
|
1916
2228
|
let _step5Col1Scroll = 0, _step5Col2Scroll = 0;
|
|
@@ -2424,6 +2736,7 @@ function getDashboardHtml() {
|
|
|
2424
2736
|
observations: currentObservations,
|
|
2425
2737
|
toolMockConfig: window._toolMockConfig || {},
|
|
2426
2738
|
promptMockConfig: window._promptMockConfig || {},
|
|
2739
|
+
userPromptMockConfig: window._userPromptMockConfig || {},
|
|
2427
2740
|
};
|
|
2428
2741
|
const response = await fetch('/api/run-from-breakpoint', {
|
|
2429
2742
|
method: 'POST',
|
|
@@ -2455,7 +2768,7 @@ function getDashboardHtml() {
|
|
|
2455
2768
|
step5RerunInFlight = true;
|
|
2456
2769
|
renderObservationTable();
|
|
2457
2770
|
try {
|
|
2458
|
-
const payload = { workflowName: selectedWorkflow?.name, runCount: 1, sequential: false, observations: currentObservations, toolMockConfig: window._toolMockConfig || {}, promptMockConfig: window._promptMockConfig || {} };
|
|
2771
|
+
const payload = { workflowName: selectedWorkflow?.name, runCount: 1, sequential: false, observations: currentObservations, toolMockConfig: window._toolMockConfig || {}, promptMockConfig: window._promptMockConfig || {}, userPromptMockConfig: window._userPromptMockConfig || {} };
|
|
2459
2772
|
const response = await fetch('/api/validate-workflow', {
|
|
2460
2773
|
method: 'POST',
|
|
2461
2774
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -2507,6 +2820,7 @@ function getDashboardHtml() {
|
|
|
2507
2820
|
snapshotId: liveTrace.snapshotId,
|
|
2508
2821
|
toolMockConfig: window._toolMockConfig || {},
|
|
2509
2822
|
promptMockConfig: window._promptMockConfig || {},
|
|
2823
|
+
userPromptMockConfig: window._userPromptMockConfig || {},
|
|
2510
2824
|
};
|
|
2511
2825
|
const response = await fetch('/api/resume-agent-from-task', {
|
|
2512
2826
|
method: 'POST',
|
|
@@ -3027,11 +3341,11 @@ function resolveTemplateValue(value, input) {
|
|
|
3027
3341
|
return value;
|
|
3028
3342
|
}
|
|
3029
3343
|
async function runHttpWorkflow(opts) {
|
|
3030
|
-
const { workflowName, workflowInput, frozenEvents = [], promptMocks = {}, pushedEvents, runConfigs, config, dashboardPort } = opts;
|
|
3344
|
+
const { workflowName, workflowInput, frozenEvents = [], promptMocks = {}, userPromptMocks, toolMockConfig, aiMockConfig, pushedEvents, runConfigs, config, dashboardPort } = opts;
|
|
3031
3345
|
const runId = randomUUID();
|
|
3032
|
-
// Register run config so the user's server can fetch frozen events and
|
|
3346
|
+
// Register run config so the user's server can fetch frozen events, prompt mocks, and output mocks
|
|
3033
3347
|
pushedEvents.set(runId, []);
|
|
3034
|
-
runConfigs.set(runId, { frozenEvents, promptMocks });
|
|
3348
|
+
runConfigs.set(runId, { frozenEvents, promptMocks, userPromptMocks, toolMockConfig, aiMockConfig });
|
|
3035
3349
|
try {
|
|
3036
3350
|
const parsedInput = parseObservationInput(workflowInput);
|
|
3037
3351
|
const inputObj = parsedInput && typeof parsedInput === 'object' && !Array.isArray(parsedInput) ? parsedInput : {};
|
|
@@ -3176,14 +3490,21 @@ export async function startDashboardServer(cwd, options = {}) {
|
|
|
3176
3490
|
const resolvedInput = resolveWorkflowArgsFromObservations(body, workflowName);
|
|
3177
3491
|
const workflowInput = resolvedInput.input ?? null;
|
|
3178
3492
|
const traces = [];
|
|
3179
|
-
const promptMocks =
|
|
3180
|
-
|
|
3181
|
-
|
|
3493
|
+
const promptMocks = flattenPromptMockConfig(body.promptMockConfig);
|
|
3494
|
+
const valUserPromptMocks = body.userPromptMockConfig && typeof body.userPromptMockConfig === 'object' && !Array.isArray(body.userPromptMockConfig)
|
|
3495
|
+
? body.userPromptMockConfig
|
|
3496
|
+
: undefined;
|
|
3497
|
+
const valToolMockConfig = body.toolMockConfig && typeof body.toolMockConfig === 'object' && !Array.isArray(body.toolMockConfig)
|
|
3498
|
+
? body.toolMockConfig
|
|
3499
|
+
: undefined;
|
|
3500
|
+
const valAiMockConfig = body.aiMockConfig && typeof body.aiMockConfig === 'object' && !Array.isArray(body.aiMockConfig)
|
|
3501
|
+
? body.aiMockConfig
|
|
3502
|
+
: undefined;
|
|
3182
3503
|
const runOne = async (runNumber) => {
|
|
3183
3504
|
const result = await runHttpWorkflow({
|
|
3184
3505
|
workflowName, workflowInput, pushedEvents, runConfigs,
|
|
3185
3506
|
config: httpConfig, dashboardPort: port,
|
|
3186
|
-
promptMocks,
|
|
3507
|
+
promptMocks, userPromptMocks: valUserPromptMocks, toolMockConfig: valToolMockConfig, aiMockConfig: valAiMockConfig,
|
|
3187
3508
|
});
|
|
3188
3509
|
const traceStub = { getSteps: () => [], getLLMSteps: () => [], getToolCalls: () => [], getCustomSteps: () => [], recordLLMStep: () => { }, recordToolCall: () => { }, recordCustomStep: () => { } };
|
|
3189
3510
|
return {
|
|
@@ -3251,14 +3572,22 @@ export async function startDashboardServer(cwd, options = {}) {
|
|
|
3251
3572
|
const httpConfig = elasticdashConfig.workflows?.[workflowName];
|
|
3252
3573
|
if (httpConfig?.mode === 'http') {
|
|
3253
3574
|
// HTTP workflow mode — call user's dev server with frozen events + prompt mocks for step replay
|
|
3254
|
-
const bpPromptMocks =
|
|
3255
|
-
|
|
3256
|
-
|
|
3575
|
+
const bpPromptMocks = flattenPromptMockConfig(body.promptMockConfig);
|
|
3576
|
+
const bpUserPromptMocks = body.userPromptMockConfig && typeof body.userPromptMockConfig === 'object' && !Array.isArray(body.userPromptMockConfig)
|
|
3577
|
+
? body.userPromptMockConfig
|
|
3578
|
+
: undefined;
|
|
3579
|
+
const bpToolMockConfig = body.toolMockConfig && typeof body.toolMockConfig === 'object' && !Array.isArray(body.toolMockConfig)
|
|
3580
|
+
? body.toolMockConfig
|
|
3581
|
+
: undefined;
|
|
3582
|
+
const bpAiMockConfig = body.aiMockConfig && typeof body.aiMockConfig === 'object' && !Array.isArray(body.aiMockConfig)
|
|
3583
|
+
? body.aiMockConfig
|
|
3584
|
+
: undefined;
|
|
3257
3585
|
console.log(`[elasticdash] Run from breakpoint (HTTP mode): workflow="${workflowName}" checkpoint=${checkpoint} frozen=${frozenEvents.length}`);
|
|
3258
3586
|
const result = await runHttpWorkflow({
|
|
3259
3587
|
workflowName, workflowInput, pushedEvents, runConfigs,
|
|
3260
3588
|
config: httpConfig, dashboardPort: port,
|
|
3261
|
-
frozenEvents, promptMocks: bpPromptMocks,
|
|
3589
|
+
frozenEvents, promptMocks: bpPromptMocks, userPromptMocks: bpUserPromptMocks,
|
|
3590
|
+
toolMockConfig: bpToolMockConfig, aiMockConfig: bpAiMockConfig,
|
|
3262
3591
|
});
|
|
3263
3592
|
const traceStub = { getSteps: () => [], getLLMSteps: () => [], getToolCalls: () => [], getCustomSteps: () => [], recordLLMStep: () => { }, recordToolCall: () => { }, recordCustomStep: () => { } };
|
|
3264
3593
|
const snapshotId = result.workflowTrace ? saveSnapshot(cwd, result.workflowTrace) : undefined;
|
|
@@ -3292,8 +3621,11 @@ export async function startDashboardServer(cwd, options = {}) {
|
|
|
3292
3621
|
const promptMockConfig = body.promptMockConfig && typeof body.promptMockConfig === 'object' && !Array.isArray(body.promptMockConfig)
|
|
3293
3622
|
? body.promptMockConfig
|
|
3294
3623
|
: undefined;
|
|
3624
|
+
const userPromptMockConfig = body.userPromptMockConfig && typeof body.userPromptMockConfig === 'object' && !Array.isArray(body.userPromptMockConfig)
|
|
3625
|
+
? body.userPromptMockConfig
|
|
3626
|
+
: undefined;
|
|
3295
3627
|
console.log(`[elasticdash] Run from breakpoint: workflow="${workflowName}" checkpoint=${checkpoint} historyLen=${history.length}`);
|
|
3296
|
-
const result = await runWorkflowInSubprocess(workflowsModulePath, toolsModulePath, workflowName, workflowArgs, workflowInput, { replayMode: true, checkpoint, history, ...(toolMockConfig ? { toolMockConfig } : {}), ...(aiMockConfig ? { aiMockConfig } : {}), ...(promptMockConfig ? { promptMockConfig } : {}) });
|
|
3628
|
+
const result = await runWorkflowInSubprocess(workflowsModulePath, toolsModulePath, workflowName, workflowArgs, workflowInput, { replayMode: true, checkpoint, history, ...(toolMockConfig ? { toolMockConfig } : {}), ...(aiMockConfig ? { aiMockConfig } : {}), ...(promptMockConfig ? { promptMockConfig } : {}), ...(userPromptMockConfig ? { userPromptMockConfig } : {}) });
|
|
3297
3629
|
const traceStub = {
|
|
3298
3630
|
getSteps: () => (result.steps ?? []),
|
|
3299
3631
|
getLLMSteps: () => (result.llmSteps ?? []),
|
|
@@ -3370,8 +3702,11 @@ export async function startDashboardServer(cwd, options = {}) {
|
|
|
3370
3702
|
const promptMockConfig = body.promptMockConfig && typeof body.promptMockConfig === 'object' && !Array.isArray(body.promptMockConfig)
|
|
3371
3703
|
? body.promptMockConfig
|
|
3372
3704
|
: undefined;
|
|
3705
|
+
const userPromptMockConfig = body.userPromptMockConfig && typeof body.userPromptMockConfig === 'object' && !Array.isArray(body.userPromptMockConfig)
|
|
3706
|
+
? body.userPromptMockConfig
|
|
3707
|
+
: undefined;
|
|
3373
3708
|
console.log(`[elasticdash] Resume agent from task: workflow="${workflowName}" taskIndex=${taskIndex}`);
|
|
3374
|
-
const result = await runWorkflowInSubprocess(workflowsModulePath, toolsModulePath, workflowName, [], null, { replayMode: history.length > 0, checkpoint: 0, history, agentState, ...(toolMockConfig ? { toolMockConfig } : {}), ...(aiMockConfig ? { aiMockConfig } : {}), ...(promptMockConfig ? { promptMockConfig } : {}) });
|
|
3709
|
+
const result = await runWorkflowInSubprocess(workflowsModulePath, toolsModulePath, workflowName, [], null, { replayMode: history.length > 0, checkpoint: 0, history, agentState, ...(toolMockConfig ? { toolMockConfig } : {}), ...(aiMockConfig ? { aiMockConfig } : {}), ...(promptMockConfig ? { promptMockConfig } : {}), ...(userPromptMockConfig ? { userPromptMockConfig } : {}) });
|
|
3375
3710
|
const traceStub = {
|
|
3376
3711
|
getSteps: () => (result.steps ?? []),
|
|
3377
3712
|
getLLMSteps: () => (result.llmSteps ?? []),
|
|
@@ -3422,7 +3757,7 @@ export async function startDashboardServer(cwd, options = {}) {
|
|
|
3422
3757
|
const runId = url.pathname.slice('/api/run-configs/'.length);
|
|
3423
3758
|
const cfg = runConfigs.get(runId);
|
|
3424
3759
|
res.writeHead(cfg ? 200 : 404, { 'Content-Type': 'application/json' });
|
|
3425
|
-
res.end(JSON.stringify({ frozenEvents: cfg?.frozenEvents ?? [], promptMocks: cfg?.promptMocks ?? {} }));
|
|
3760
|
+
res.end(JSON.stringify({ frozenEvents: cfg?.frozenEvents ?? [], promptMocks: cfg?.promptMocks ?? {}, ...(cfg?.userPromptMocks ? { userPromptMocks: cfg.userPromptMocks } : {}), ...(cfg?.toolMockConfig ? { toolMockConfig: cfg.toolMockConfig } : {}), ...(cfg?.aiMockConfig ? { aiMockConfig: cfg.aiMockConfig } : {}) }));
|
|
3426
3761
|
}
|
|
3427
3762
|
else if (url.pathname === '/api/trace-events' && req.method === 'POST') {
|
|
3428
3763
|
// Receive telemetry events pushed from wrapAI / wrapTool in HTTP workflow mode
|