elasticdash-test 0.1.14 → 0.1.16
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/README.md +36 -5
- package/dist/dashboard-server.d.ts +9 -0
- package/dist/dashboard-server.d.ts.map +1 -1
- package/dist/dashboard-server.js +209 -22
- package/dist/dashboard-server.js.map +1 -1
- package/dist/html/dashboard.html +158 -8
- package/dist/index.cjs +828 -108
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/interceptors/telemetry-push.d.ts +47 -0
- package/dist/interceptors/telemetry-push.d.ts.map +1 -1
- package/dist/interceptors/telemetry-push.js +139 -6
- package/dist/interceptors/telemetry-push.js.map +1 -1
- package/dist/interceptors/tool.d.ts.map +1 -1
- package/dist/interceptors/tool.js +2 -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 +28 -4
- package/dist/interceptors/workflow-ai.js.map +1 -1
- package/dist/internals/mock-resolver.d.ts +42 -5
- package/dist/internals/mock-resolver.d.ts.map +1 -1
- package/dist/internals/mock-resolver.js +124 -5
- package/dist/internals/mock-resolver.js.map +1 -1
- package/dist/workflow-runner-worker.js +8 -2
- package/dist/workflow-runner-worker.js.map +1 -1
- package/package.json +3 -2
- package/src/dashboard-server.ts +86 -17
- package/src/html/dashboard.html +158 -8
- package/src/index.ts +3 -2
- package/src/interceptors/telemetry-push.ts +158 -7
- package/src/interceptors/tool.ts +2 -1
- package/src/interceptors/workflow-ai.ts +30 -4
- package/src/internals/mock-resolver.ts +131 -5
- package/src/workflow-runner-worker.ts +23 -2
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Runtime
|
|
2
|
+
* Runtime mock resolution for module-imported tools and AI calls.
|
|
3
3
|
*
|
|
4
|
-
* Tools that are statically imported (not accessed via globalThis)
|
|
5
|
-
* intercepted by the worker's proxy-based mocking. Instead, each
|
|
6
|
-
* calls `resolveMock` at its entry point.
|
|
7
|
-
*
|
|
4
|
+
* Tools/AI calls that are statically imported (not accessed via globalThis)
|
|
5
|
+
* cannot be intercepted by the worker's proxy-based mocking. Instead, each
|
|
6
|
+
* wrapped function calls `resolveMock` / `resolveAIMock` at its entry point.
|
|
7
|
+
* The worker writes the mock config to `__ELASTICDASH_TOOL_MOCKS__` /
|
|
8
|
+
* `__ELASTICDASH_AI_MOCKS__` before the workflow runs and clears it after.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
interface ToolMockEntry {
|
|
@@ -13,6 +14,17 @@ interface ToolMockEntry {
|
|
|
13
14
|
mockData?: Record<number, unknown>
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
/** Per-model AI mock configuration (mirrors ToolMockConfig) */
|
|
18
|
+
export interface AIMockEntry {
|
|
19
|
+
mode: 'live' | 'mock-all' | 'mock-specific'
|
|
20
|
+
callIndices?: number[]
|
|
21
|
+
mockData?: Record<number, unknown>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface AIMockConfig {
|
|
25
|
+
[modelName: string]: AIMockEntry
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
type MockResult =
|
|
17
29
|
| { mocked: true; result: unknown }
|
|
18
30
|
| { mocked: false }
|
|
@@ -90,3 +102,117 @@ export function resolveMock(toolName: string): MockResult {
|
|
|
90
102
|
|
|
91
103
|
return { mocked: false }
|
|
92
104
|
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Extracts the system prompt string from an LLM call input.
|
|
108
|
+
* Handles:
|
|
109
|
+
* - Anthropic style: `{ system: "...", messages: [...] }`
|
|
110
|
+
* - OpenAI style: `{ messages: [{ role: "system", content: "..." }, ...] }`
|
|
111
|
+
* - Plain message array: `[{ role: "system", content: "..." }, ...]`
|
|
112
|
+
* - Separate field: `{ systemPrompt: "...", messages: [...] }` (custom wrapAI callers)
|
|
113
|
+
*/
|
|
114
|
+
export function extractSystemPrompt(input: unknown): string | undefined {
|
|
115
|
+
if (!input || typeof input !== 'object') return undefined
|
|
116
|
+
const o = input as Record<string, unknown>
|
|
117
|
+
if (typeof o.system === 'string') return o.system
|
|
118
|
+
if (typeof o.systemPrompt === 'string' && o.systemPrompt.length > 0) return o.systemPrompt
|
|
119
|
+
const msgs = Array.isArray(o.messages) ? o.messages : (Array.isArray(input) ? input as unknown[] : null)
|
|
120
|
+
if (msgs) {
|
|
121
|
+
for (const m of msgs) {
|
|
122
|
+
if (m && typeof m === 'object') {
|
|
123
|
+
const msg = m as Record<string, unknown>
|
|
124
|
+
if (msg.role === 'system' && typeof msg.content === 'string') return msg.content
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return undefined
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Returns a shallow copy of `input` with the system prompt replaced by `newSystemPrompt`.
|
|
133
|
+
*/
|
|
134
|
+
export function replaceSystemPrompt(input: unknown, newSystemPrompt: string): unknown {
|
|
135
|
+
if (!input || typeof input !== 'object') return input
|
|
136
|
+
const o = input as Record<string, unknown>
|
|
137
|
+
if (typeof o.system === 'string') return { ...o, system: newSystemPrompt }
|
|
138
|
+
if (typeof o.systemPrompt === 'string') return { ...o, systemPrompt: newSystemPrompt }
|
|
139
|
+
if (Array.isArray(input)) {
|
|
140
|
+
return (input as unknown[]).map(m => {
|
|
141
|
+
if (m && typeof m === 'object') {
|
|
142
|
+
const msg = m as Record<string, unknown>
|
|
143
|
+
if (msg.role === 'system' && typeof msg.content === 'string') return { ...msg, content: newSystemPrompt }
|
|
144
|
+
}
|
|
145
|
+
return m
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
if (Array.isArray(o.messages)) {
|
|
149
|
+
return {
|
|
150
|
+
...o,
|
|
151
|
+
messages: (o.messages as unknown[]).map(m => {
|
|
152
|
+
if (m && typeof m === 'object') {
|
|
153
|
+
const msg = m as Record<string, unknown>
|
|
154
|
+
if (msg.role === 'system' && typeof msg.content === 'string') return { ...msg, content: newSystemPrompt }
|
|
155
|
+
}
|
|
156
|
+
return m
|
|
157
|
+
}),
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return input
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* If a prompt mock is configured for the system prompt found in `input`, returns
|
|
165
|
+
* a copy of `input` with the system prompt replaced. Otherwise returns `undefined`.
|
|
166
|
+
*
|
|
167
|
+
* `__ELASTICDASH_PROMPT_MOCKS__` is `Record<string, string>` keyed by the original
|
|
168
|
+
* system prompt text, set by the worker/dashboard before the workflow runs.
|
|
169
|
+
*/
|
|
170
|
+
export function resolvePromptMock(input: unknown): unknown | undefined {
|
|
171
|
+
const g = globalThis as Record<string, unknown>
|
|
172
|
+
const mocks = g['__ELASTICDASH_PROMPT_MOCKS__'] as Record<string, string> | undefined
|
|
173
|
+
if (!mocks || Object.keys(mocks).length === 0) return undefined
|
|
174
|
+
const systemPrompt = extractSystemPrompt(input)
|
|
175
|
+
if (systemPrompt === undefined) return undefined
|
|
176
|
+
const newSystemPrompt = mocks[systemPrompt]
|
|
177
|
+
if (newSystemPrompt === undefined) return undefined
|
|
178
|
+
return replaceSystemPrompt(input, newSystemPrompt)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Resolves whether the current call to `modelName` should be mocked.
|
|
183
|
+
* Mirrors `resolveMock` but reads `__ELASTICDASH_AI_MOCKS__` and
|
|
184
|
+
* `__ELASTICDASH_AI_CALL_COUNTERS__`.
|
|
185
|
+
*/
|
|
186
|
+
export function resolveAIMock(modelName: string): MockResult {
|
|
187
|
+
const g = globalThis as Record<string, unknown>
|
|
188
|
+
|
|
189
|
+
const mocks = g['__ELASTICDASH_AI_MOCKS__'] as Record<string, AIMockEntry> | undefined
|
|
190
|
+
if (!mocks) return { mocked: false }
|
|
191
|
+
|
|
192
|
+
const entry = mocks[modelName]
|
|
193
|
+
if (!entry || entry.mode === 'live') return { mocked: false }
|
|
194
|
+
|
|
195
|
+
if (!g['__ELASTICDASH_AI_CALL_COUNTERS__']) {
|
|
196
|
+
g['__ELASTICDASH_AI_CALL_COUNTERS__'] = {} as Record<string, number>
|
|
197
|
+
}
|
|
198
|
+
const counters = g['__ELASTICDASH_AI_CALL_COUNTERS__'] as Record<string, number>
|
|
199
|
+
counters[modelName] = (counters[modelName] ?? 0) + 1
|
|
200
|
+
const callNumber = counters[modelName]
|
|
201
|
+
|
|
202
|
+
if (entry.mode === 'mock-all') {
|
|
203
|
+
const data = entry.mockData ?? {}
|
|
204
|
+
const raw = data[callNumber] !== undefined ? data[callNumber] : data[0]
|
|
205
|
+
return { mocked: true, result: normaliseMockResult(raw) }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (entry.mode === 'mock-specific') {
|
|
209
|
+
const indices = entry.callIndices ?? []
|
|
210
|
+
if (indices.includes(callNumber)) {
|
|
211
|
+
const data = entry.mockData ?? {}
|
|
212
|
+
return { mocked: true, result: normaliseMockResult(data[callNumber]) }
|
|
213
|
+
}
|
|
214
|
+
return { mocked: false }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return { mocked: false }
|
|
218
|
+
}
|
|
@@ -79,6 +79,17 @@ interface ToolMockConfig {
|
|
|
79
79
|
[toolName: string]: ToolMockEntry
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/** Per-model AI mock configuration (mirrors ToolMockConfig) */
|
|
83
|
+
interface AIMockEntry {
|
|
84
|
+
mode: 'live' | 'mock-all' | 'mock-specific'
|
|
85
|
+
callIndices?: number[]
|
|
86
|
+
mockData?: Record<number, unknown>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface AIMockConfig {
|
|
90
|
+
[modelName: string]: AIMockEntry
|
|
91
|
+
}
|
|
92
|
+
|
|
82
93
|
/** Minimal inline tool-wrapping — records each tool call to the trace. */
|
|
83
94
|
async function loadAndWrapTools(
|
|
84
95
|
toolsModulePath: string,
|
|
@@ -210,6 +221,10 @@ async function main() {
|
|
|
210
221
|
agentState?: AgentState
|
|
211
222
|
/** Optional tool mock configuration from the dashboard UI */
|
|
212
223
|
toolMockConfig?: ToolMockConfig
|
|
224
|
+
/** Optional AI/LLM mock configuration from the dashboard UI */
|
|
225
|
+
aiMockConfig?: AIMockConfig
|
|
226
|
+
/** Optional prompt mock config: replacement system prompts keyed by original system prompt text */
|
|
227
|
+
promptMockConfig?: Record<string, string>
|
|
213
228
|
}
|
|
214
229
|
try {
|
|
215
230
|
payload = JSON.parse(raw)
|
|
@@ -219,7 +234,7 @@ async function main() {
|
|
|
219
234
|
return
|
|
220
235
|
}
|
|
221
236
|
|
|
222
|
-
const { workflowsModulePath, toolsModulePath, workflowName, args, input, replayMode = false, checkpoint = 0, history = [], agentState, toolMockConfig } = payload
|
|
237
|
+
const { workflowsModulePath, toolsModulePath, workflowName, args, input, replayMode = false, checkpoint = 0, history = [], agentState, toolMockConfig, aiMockConfig, promptMockConfig } = payload
|
|
223
238
|
|
|
224
239
|
const { context, finalise } = startTraceSession()
|
|
225
240
|
setCurrentTrace(context.trace)
|
|
@@ -258,9 +273,12 @@ async function main() {
|
|
|
258
273
|
let workflowError: Error | undefined
|
|
259
274
|
|
|
260
275
|
try {
|
|
261
|
-
// Write mock
|
|
276
|
+
// Write mock configs to globals so module-imported tools/AI can read them via resolveMock/resolveAIMock()
|
|
262
277
|
;(globalThis as any).__ELASTICDASH_TOOL_MOCKS__ = toolMockConfig ?? {}
|
|
263
278
|
;(globalThis as any).__ELASTICDASH_TOOL_CALL_COUNTERS__ = {}
|
|
279
|
+
;(globalThis as any).__ELASTICDASH_AI_MOCKS__ = aiMockConfig ?? {}
|
|
280
|
+
;(globalThis as any).__ELASTICDASH_AI_CALL_COUNTERS__ = {}
|
|
281
|
+
;(globalThis as any).__ELASTICDASH_PROMPT_MOCKS__ = promptMockConfig ?? {}
|
|
264
282
|
|
|
265
283
|
await installDBAutoInterceptor()
|
|
266
284
|
installAIInterceptor()
|
|
@@ -321,6 +339,9 @@ async function main() {
|
|
|
321
339
|
// Clear mock globals
|
|
322
340
|
delete (globalThis as any).__ELASTICDASH_TOOL_MOCKS__
|
|
323
341
|
delete (globalThis as any).__ELASTICDASH_TOOL_CALL_COUNTERS__
|
|
342
|
+
delete (globalThis as any).__ELASTICDASH_AI_MOCKS__
|
|
343
|
+
delete (globalThis as any).__ELASTICDASH_AI_CALL_COUNTERS__
|
|
344
|
+
delete (globalThis as any).__ELASTICDASH_PROMPT_MOCKS__
|
|
324
345
|
}
|
|
325
346
|
|
|
326
347
|
await recorder.flush()
|