elasticdash-sdk 0.2.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.
- package/LICENSE +21 -0
- package/README.md +775 -0
- package/dist/browser-ui.d.ts +43 -0
- package/dist/browser-ui.d.ts.map +1 -0
- package/dist/browser-ui.js +246 -0
- package/dist/browser-ui.js.map +1 -0
- package/dist/capture/event.d.ts +33 -0
- package/dist/capture/event.d.ts.map +1 -0
- package/dist/capture/event.js +2 -0
- package/dist/capture/event.js.map +1 -0
- package/dist/capture/index.d.ts +4 -0
- package/dist/capture/index.d.ts.map +1 -0
- package/dist/capture/index.js +4 -0
- package/dist/capture/index.js.map +1 -0
- package/dist/capture/recorder.d.ts +24 -0
- package/dist/capture/recorder.d.ts.map +1 -0
- package/dist/capture/recorder.js +46 -0
- package/dist/capture/recorder.js.map +1 -0
- package/dist/capture/replay.d.ts +20 -0
- package/dist/capture/replay.d.ts.map +1 -0
- package/dist/capture/replay.js +47 -0
- package/dist/capture/replay.js.map +1 -0
- package/dist/ci/api-client.d.ts +38 -0
- package/dist/ci/api-client.d.ts.map +1 -0
- package/dist/ci/api-client.js +96 -0
- package/dist/ci/api-client.js.map +1 -0
- package/dist/ci/benchmark.d.ts +33 -0
- package/dist/ci/benchmark.d.ts.map +1 -0
- package/dist/ci/benchmark.js +213 -0
- package/dist/ci/benchmark.js.map +1 -0
- package/dist/ci/ed-runner.d.ts +48 -0
- package/dist/ci/ed-runner.d.ts.map +1 -0
- package/dist/ci/ed-runner.js +260 -0
- package/dist/ci/ed-runner.js.map +1 -0
- package/dist/ci/executor.d.ts +13 -0
- package/dist/ci/executor.d.ts.map +1 -0
- package/dist/ci/executor.js +542 -0
- package/dist/ci/executor.js.map +1 -0
- package/dist/ci/git-info.d.ts +17 -0
- package/dist/ci/git-info.d.ts.map +1 -0
- package/dist/ci/git-info.js +102 -0
- package/dist/ci/git-info.js.map +1 -0
- package/dist/ci/index.d.ts +6 -0
- package/dist/ci/index.d.ts.map +1 -0
- package/dist/ci/index.js +4 -0
- package/dist/ci/index.js.map +1 -0
- package/dist/ci/measurement.d.ts +9 -0
- package/dist/ci/measurement.d.ts.map +1 -0
- package/dist/ci/measurement.js +15 -0
- package/dist/ci/measurement.js.map +1 -0
- package/dist/ci/replay.d.ts +31 -0
- package/dist/ci/replay.d.ts.map +1 -0
- package/dist/ci/replay.js +96 -0
- package/dist/ci/replay.js.map +1 -0
- package/dist/ci/reporters/default.d.ts +8 -0
- package/dist/ci/reporters/default.d.ts.map +1 -0
- package/dist/ci/reporters/default.js +46 -0
- package/dist/ci/reporters/default.js.map +1 -0
- package/dist/ci/reporters/index.d.ts +8 -0
- package/dist/ci/reporters/index.d.ts.map +1 -0
- package/dist/ci/reporters/index.js +14 -0
- package/dist/ci/reporters/index.js.map +1 -0
- package/dist/ci/reporters/json.d.ts +8 -0
- package/dist/ci/reporters/json.d.ts.map +1 -0
- package/dist/ci/reporters/json.js +14 -0
- package/dist/ci/reporters/json.js.map +1 -0
- package/dist/ci/reporters/junit.d.ts +8 -0
- package/dist/ci/reporters/junit.d.ts.map +1 -0
- package/dist/ci/reporters/junit.js +48 -0
- package/dist/ci/reporters/junit.js.map +1 -0
- package/dist/ci/runner.d.ts +3 -0
- package/dist/ci/runner.d.ts.map +1 -0
- package/dist/ci/runner.js +187 -0
- package/dist/ci/runner.js.map +1 -0
- package/dist/ci/test-discovery.d.ts +5 -0
- package/dist/ci/test-discovery.d.ts.map +1 -0
- package/dist/ci/test-discovery.js +11 -0
- package/dist/ci/test-discovery.js.map +1 -0
- package/dist/ci/test-loader.d.ts +19 -0
- package/dist/ci/test-loader.d.ts.map +1 -0
- package/dist/ci/test-loader.js +149 -0
- package/dist/ci/test-loader.js.map +1 -0
- package/dist/ci/test-registry.d.ts +42 -0
- package/dist/ci/test-registry.d.ts.map +1 -0
- package/dist/ci/test-registry.js +18 -0
- package/dist/ci/test-registry.js.map +1 -0
- package/dist/ci/trace-schema.d.ts +30 -0
- package/dist/ci/trace-schema.d.ts.map +1 -0
- package/dist/ci/trace-schema.js +66 -0
- package/dist/ci/trace-schema.js.map +1 -0
- package/dist/ci/trace-writer.d.ts +16 -0
- package/dist/ci/trace-writer.d.ts.map +1 -0
- package/dist/ci/trace-writer.js +108 -0
- package/dist/ci/trace-writer.js.map +1 -0
- package/dist/ci/types.d.ts +108 -0
- package/dist/ci/types.d.ts.map +1 -0
- package/dist/ci/types.js +3 -0
- package/dist/ci/types.js.map +1 -0
- package/dist/ci/upload-client.d.ts +74 -0
- package/dist/ci/upload-client.d.ts.map +1 -0
- package/dist/ci/upload-client.js +195 -0
- package/dist/ci/upload-client.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +716 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/agent-state.d.ts +47 -0
- package/dist/core/agent-state.d.ts.map +1 -0
- package/dist/core/agent-state.js +137 -0
- package/dist/core/agent-state.js.map +1 -0
- package/dist/core/judge-utils.d.ts +22 -0
- package/dist/core/judge-utils.d.ts.map +1 -0
- package/dist/core/judge-utils.js +211 -0
- package/dist/core/judge-utils.js.map +1 -0
- package/dist/core/registry.d.ts +28 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +52 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/dashboard-server.d.ts +65 -0
- package/dist/dashboard-server.d.ts.map +1 -0
- package/dist/dashboard-server.js +3940 -0
- package/dist/dashboard-server.js.map +1 -0
- package/dist/execution/tool-runner.d.ts +26 -0
- package/dist/execution/tool-runner.d.ts.map +1 -0
- package/dist/execution/tool-runner.js +316 -0
- package/dist/execution/tool-runner.js.map +1 -0
- package/dist/html/dashboard.html +2218 -0
- package/dist/http.d.ts +14 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +13 -0
- package/dist/http.js.map +1 -0
- package/dist/index.cjs +8102 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +67 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptors/ai-interceptor.d.ts +26 -0
- package/dist/interceptors/ai-interceptor.d.ts.map +1 -0
- package/dist/interceptors/ai-interceptor.js +756 -0
- package/dist/interceptors/ai-interceptor.js.map +1 -0
- package/dist/interceptors/db-auto.d.ts +8 -0
- package/dist/interceptors/db-auto.d.ts.map +1 -0
- package/dist/interceptors/db-auto.js +217 -0
- package/dist/interceptors/db-auto.js.map +1 -0
- package/dist/interceptors/db.d.ts +23 -0
- package/dist/interceptors/db.d.ts.map +1 -0
- package/dist/interceptors/db.js +137 -0
- package/dist/interceptors/db.js.map +1 -0
- package/dist/interceptors/http.d.ts +28 -0
- package/dist/interceptors/http.d.ts.map +1 -0
- package/dist/interceptors/http.js +356 -0
- package/dist/interceptors/http.js.map +1 -0
- package/dist/interceptors/side-effects.d.ts +7 -0
- package/dist/interceptors/side-effects.d.ts.map +1 -0
- package/dist/interceptors/side-effects.js +72 -0
- package/dist/interceptors/side-effects.js.map +1 -0
- package/dist/interceptors/telemetry-push.d.ts +142 -0
- package/dist/interceptors/telemetry-push.d.ts.map +1 -0
- package/dist/interceptors/telemetry-push.js +463 -0
- package/dist/interceptors/telemetry-push.js.map +1 -0
- package/dist/interceptors/tool.d.ts +2 -0
- package/dist/interceptors/tool.d.ts.map +1 -0
- package/dist/interceptors/tool.js +274 -0
- package/dist/interceptors/tool.js.map +1 -0
- package/dist/interceptors/workflow-ai.d.ts +5 -0
- package/dist/interceptors/workflow-ai.d.ts.map +1 -0
- package/dist/interceptors/workflow-ai.js +382 -0
- package/dist/interceptors/workflow-ai.js.map +1 -0
- package/dist/internals/conditional-recorder.d.ts +21 -0
- package/dist/internals/conditional-recorder.d.ts.map +1 -0
- package/dist/internals/conditional-recorder.js +54 -0
- package/dist/internals/conditional-recorder.js.map +1 -0
- package/dist/internals/mock-resolver.d.ts +146 -0
- package/dist/internals/mock-resolver.d.ts.map +1 -0
- package/dist/internals/mock-resolver.js +427 -0
- package/dist/internals/mock-resolver.js.map +1 -0
- package/dist/matchers/index.d.ts +96 -0
- package/dist/matchers/index.d.ts.map +1 -0
- package/dist/matchers/index.js +668 -0
- package/dist/matchers/index.js.map +1 -0
- package/dist/observability.d.ts +82 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +471 -0
- package/dist/observability.js.map +1 -0
- package/dist/portal-executor.d.ts +30 -0
- package/dist/portal-executor.d.ts.map +1 -0
- package/dist/portal-executor.js +324 -0
- package/dist/portal-executor.js.map +1 -0
- package/dist/portal-server.d.ts +3 -0
- package/dist/portal-server.d.ts.map +1 -0
- package/dist/portal-server.js +279 -0
- package/dist/portal-server.js.map +1 -0
- package/dist/proxy/llm-capture.d.ts +14 -0
- package/dist/proxy/llm-capture.d.ts.map +1 -0
- package/dist/proxy/llm-capture.js +264 -0
- package/dist/proxy/llm-capture.js.map +1 -0
- package/dist/reporter.d.ts +3 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +72 -0
- package/dist/reporter.js.map +1 -0
- package/dist/runWorkflowSubprocess.d.ts +14 -0
- package/dist/runWorkflowSubprocess.d.ts.map +1 -0
- package/dist/runWorkflowSubprocess.js +66 -0
- package/dist/runWorkflowSubprocess.js.map +1 -0
- package/dist/runner.d.ts +16 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +138 -0
- package/dist/runner.js.map +1 -0
- package/dist/socket-connector.d.ts +22 -0
- package/dist/socket-connector.d.ts.map +1 -0
- package/dist/socket-connector.js +104 -0
- package/dist/socket-connector.js.map +1 -0
- package/dist/telemetry-batcher.d.ts +56 -0
- package/dist/telemetry-batcher.d.ts.map +1 -0
- package/dist/telemetry-batcher.js +143 -0
- package/dist/telemetry-batcher.js.map +1 -0
- package/dist/test-setup.d.ts +12 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/test-setup.js +13 -0
- package/dist/test-setup.js.map +1 -0
- package/dist/tool-registry.d.ts +31 -0
- package/dist/tool-registry.d.ts.map +1 -0
- package/dist/tool-registry.js +73 -0
- package/dist/tool-registry.js.map +1 -0
- package/dist/tool-runner-worker.d.ts +2 -0
- package/dist/tool-runner-worker.d.ts.map +1 -0
- package/dist/tool-runner-worker.js +215 -0
- package/dist/tool-runner-worker.js.map +1 -0
- package/dist/trace-adapter/context.d.ts +72 -0
- package/dist/trace-adapter/context.d.ts.map +1 -0
- package/dist/trace-adapter/context.js +80 -0
- package/dist/trace-adapter/context.js.map +1 -0
- package/dist/tracing.d.ts +2 -0
- package/dist/tracing.d.ts.map +1 -0
- package/dist/tracing.js +59 -0
- package/dist/tracing.js.map +1 -0
- package/dist/trigger-executor.d.ts +12 -0
- package/dist/trigger-executor.d.ts.map +1 -0
- package/dist/trigger-executor.js +130 -0
- package/dist/trigger-executor.js.map +1 -0
- package/dist/types/portal.d.ts +76 -0
- package/dist/types/portal.d.ts.map +1 -0
- package/dist/types/portal.js +2 -0
- package/dist/types/portal.js.map +1 -0
- package/dist/utils/debug.d.ts +3 -0
- package/dist/utils/debug.d.ts.map +1 -0
- package/dist/utils/debug.js +8 -0
- package/dist/utils/debug.js.map +1 -0
- package/dist/utils/license-error.d.ts +23 -0
- package/dist/utils/license-error.d.ts.map +1 -0
- package/dist/utils/license-error.js +42 -0
- package/dist/utils/license-error.js.map +1 -0
- package/dist/utils/redact.d.ts +7 -0
- package/dist/utils/redact.d.ts.map +1 -0
- package/dist/utils/redact.js +26 -0
- package/dist/utils/redact.js.map +1 -0
- package/dist/workflow-runner-worker.d.ts +2 -0
- package/dist/workflow-runner-worker.d.ts.map +1 -0
- package/dist/workflow-runner-worker.js +329 -0
- package/dist/workflow-runner-worker.js.map +1 -0
- package/dist/workflow-runner.d.ts +14 -0
- package/dist/workflow-runner.d.ts.map +1 -0
- package/dist/workflow-runner.js +34 -0
- package/dist/workflow-runner.js.map +1 -0
- package/docs/agent-coding-instructions.md +138 -0
- package/docs/agent-integration-guide.md +564 -0
- package/docs/agents.md +140 -0
- package/docs/dashboard.md +394 -0
- package/docs/deno.md +69 -0
- package/docs/instrumentation.md +424 -0
- package/docs/langfuse-trace-structure.md +145 -0
- package/docs/matchers.md +173 -0
- package/docs/observability_contract.md +192 -0
- package/docs/observability_mode.md +195 -0
- package/docs/quickstart.md +621 -0
- package/docs/security-compliance.md +566 -0
- package/docs/test-writing-guidelines.md +444 -0
- package/docs/tools.md +165 -0
- package/docs/workflow-modes.md +253 -0
- package/package.json +76 -0
- package/src/browser-ui.ts +281 -0
- package/src/capture/event.ts +30 -0
- package/src/capture/index.ts +3 -0
- package/src/capture/recorder.ts +62 -0
- package/src/capture/replay.ts +55 -0
- package/src/ci/api-client.ts +136 -0
- package/src/ci/benchmark.ts +257 -0
- package/src/ci/ed-runner.ts +351 -0
- package/src/ci/executor.ts +671 -0
- package/src/ci/git-info.ts +127 -0
- package/src/ci/index.ts +5 -0
- package/src/ci/measurement.ts +25 -0
- package/src/ci/replay.ts +127 -0
- package/src/ci/reporters/default.ts +50 -0
- package/src/ci/reporters/index.ts +21 -0
- package/src/ci/reporters/json.ts +18 -0
- package/src/ci/reporters/junit.ts +61 -0
- package/src/ci/runner.ts +208 -0
- package/src/ci/test-discovery.ts +16 -0
- package/src/ci/test-loader.ts +187 -0
- package/src/ci/test-registry.ts +62 -0
- package/src/ci/trace-schema.ts +96 -0
- package/src/ci/trace-writer.ts +107 -0
- package/src/ci/types.ts +115 -0
- package/src/ci/upload-client.ts +300 -0
- package/src/cli.ts +811 -0
- package/src/core/agent-state.ts +162 -0
- package/src/core/judge-utils.ts +232 -0
- package/src/core/registry.ts +92 -0
- package/src/dashboard-server.ts +2047 -0
- package/src/execution/tool-runner.ts +352 -0
- package/src/html/dashboard.html +2218 -0
- package/src/http.ts +13 -0
- package/src/index.ts +138 -0
- package/src/interceptors/ai-interceptor.ts +798 -0
- package/src/interceptors/db-auto.ts +243 -0
- package/src/interceptors/db.ts +156 -0
- package/src/interceptors/http.ts +393 -0
- package/src/interceptors/side-effects.ts +83 -0
- package/src/interceptors/telemetry-push.ts +537 -0
- package/src/interceptors/tool.ts +287 -0
- package/src/interceptors/workflow-ai.ts +419 -0
- package/src/internals/conditional-recorder.ts +63 -0
- package/src/internals/mock-resolver.ts +492 -0
- package/src/matchers/index.ts +824 -0
- package/src/observability.ts +501 -0
- package/src/portal-executor.ts +355 -0
- package/src/portal-server.ts +304 -0
- package/src/proxy/llm-capture.ts +301 -0
- package/src/reporter.ts +81 -0
- package/src/runWorkflowSubprocess.ts +74 -0
- package/src/runner.ts +178 -0
- package/src/socket-connector.ts +117 -0
- package/src/telemetry-batcher.ts +191 -0
- package/src/test-setup.ts +16 -0
- package/src/tool-registry.ts +94 -0
- package/src/tool-runner-worker.ts +244 -0
- package/src/trace-adapter/context.ts +156 -0
- package/src/tracing.ts +62 -0
- package/src/trigger-executor.ts +171 -0
- package/src/types/agent.d.ts +63 -0
- package/src/types/expect.d.ts +81 -0
- package/src/types/modules.d.ts +2 -0
- package/src/types/portal.ts +69 -0
- package/src/utils/debug.ts +8 -0
- package/src/utils/license-error.ts +43 -0
- package/src/utils/redact.ts +25 -0
- package/src/workflow-runner-worker.ts +386 -0
- package/src/workflow-runner.ts +58 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime mock resolution for module-imported tools and AI calls.
|
|
3
|
+
*
|
|
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.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
interface ToolMockEntry {
|
|
12
|
+
mode: 'live' | 'mock-all' | 'mock-specific'
|
|
13
|
+
callIndices?: number[]
|
|
14
|
+
mockData?: Record<number, unknown>
|
|
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
|
+
|
|
28
|
+
type MockResult =
|
|
29
|
+
| { mocked: true; result: unknown }
|
|
30
|
+
| { mocked: false }
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Recursively normalises a mock result fetched from the trace.
|
|
34
|
+
*
|
|
35
|
+
* The trace recorder stores tool outputs as-is. When a tool returns a JSON
|
|
36
|
+
* string (rather than a parsed object), the stored value is that string, and
|
|
37
|
+
* inner string values may themselves be JSON-quoted. This function unwraps
|
|
38
|
+
* every layer so the calling code receives the same shape as the real output.
|
|
39
|
+
*
|
|
40
|
+
* - string → try JSON.parse, then recurse on the result
|
|
41
|
+
* - array → recurse on every element
|
|
42
|
+
* - object → recurse on every value
|
|
43
|
+
* - other → return unchanged
|
|
44
|
+
*/
|
|
45
|
+
export function normaliseMockResult(value: unknown): unknown {
|
|
46
|
+
if (typeof value === 'string') {
|
|
47
|
+
try { return normaliseMockResult(JSON.parse(value)) } catch { return value }
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(value)) {
|
|
50
|
+
return value.map(normaliseMockResult)
|
|
51
|
+
}
|
|
52
|
+
if (value !== null && typeof value === 'object') {
|
|
53
|
+
const out: Record<string, unknown> = {}
|
|
54
|
+
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
|
|
55
|
+
out[k] = normaliseMockResult(v)
|
|
56
|
+
}
|
|
57
|
+
return out
|
|
58
|
+
}
|
|
59
|
+
return value
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Resolves whether the current call to `toolName` should be mocked.
|
|
64
|
+
*
|
|
65
|
+
* - Returns `{ mocked: false }` when no mock config is active or mode is `'live'`.
|
|
66
|
+
* - Increments the per-tool call counter on every non-live invocation so that
|
|
67
|
+
* `mock-specific` indices remain accurate even when some calls run live.
|
|
68
|
+
* - Safe to call in production: no-op when the mock globals are absent.
|
|
69
|
+
*/
|
|
70
|
+
export function resolveMock(toolName: string): MockResult {
|
|
71
|
+
const g = globalThis as Record<string, unknown>
|
|
72
|
+
|
|
73
|
+
const mocks = g['__ELASTICDASH_TOOL_MOCKS__'] as Record<string, ToolMockEntry> | undefined
|
|
74
|
+
if (!mocks) return { mocked: false }
|
|
75
|
+
|
|
76
|
+
const entry = mocks[toolName]
|
|
77
|
+
if (!entry || entry.mode === 'live') return { mocked: false }
|
|
78
|
+
|
|
79
|
+
// Initialise counters map if not yet present
|
|
80
|
+
if (!g['__ELASTICDASH_TOOL_CALL_COUNTERS__']) {
|
|
81
|
+
g['__ELASTICDASH_TOOL_CALL_COUNTERS__'] = {} as Record<string, number>
|
|
82
|
+
}
|
|
83
|
+
const counters = g['__ELASTICDASH_TOOL_CALL_COUNTERS__'] as Record<string, number>
|
|
84
|
+
counters[toolName] = (counters[toolName] ?? 0) + 1
|
|
85
|
+
const callNumber = counters[toolName]
|
|
86
|
+
|
|
87
|
+
if (entry.mode === 'mock-all') {
|
|
88
|
+
const data = entry.mockData ?? {}
|
|
89
|
+
const raw = data[callNumber] !== undefined ? data[callNumber] : data[0]
|
|
90
|
+
return { mocked: true, result: normaliseMockResult(raw) }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (entry.mode === 'mock-specific') {
|
|
94
|
+
const indices = entry.callIndices ?? []
|
|
95
|
+
if (indices.includes(callNumber)) {
|
|
96
|
+
const data = entry.mockData ?? {}
|
|
97
|
+
return { mocked: true, result: normaliseMockResult(data[callNumber]) }
|
|
98
|
+
}
|
|
99
|
+
// Counter already incremented; this specific call runs live
|
|
100
|
+
return { mocked: false }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { mocked: false }
|
|
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
|
+
* System prompt mock entry — mirrors user prompt mock entry.
|
|
165
|
+
*/
|
|
166
|
+
export interface SystemPromptMockEntry {
|
|
167
|
+
mode: 'live' | 'replace-all' | 'replace-specific'
|
|
168
|
+
replacement: string
|
|
169
|
+
callIndices?: number[]
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface SystemPromptMockConfig {
|
|
173
|
+
[originalText: string]: SystemPromptMockEntry
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* If a prompt mock is configured for the system prompt found in `input`, returns
|
|
178
|
+
* a copy of `input` with the system prompt replaced. Otherwise returns `undefined`.
|
|
179
|
+
*
|
|
180
|
+
* `__ELASTICDASH_PROMPT_MOCKS__` is `Record<string, SystemPromptMockEntry>` keyed by
|
|
181
|
+
* the original system prompt text. Supports replace-all and replace-specific modes
|
|
182
|
+
* with per-text call counting.
|
|
183
|
+
*
|
|
184
|
+
* For backward compatibility, also accepts the legacy `Record<string, string>` format
|
|
185
|
+
* (treated as replace-all).
|
|
186
|
+
*/
|
|
187
|
+
export function resolvePromptMock(input: unknown): unknown | undefined {
|
|
188
|
+
const g = globalThis as Record<string, unknown>
|
|
189
|
+
const mocks = g['__ELASTICDASH_PROMPT_MOCKS__'] as Record<string, string | SystemPromptMockEntry> | undefined
|
|
190
|
+
if (!mocks || Object.keys(mocks).length === 0) return undefined
|
|
191
|
+
const systemPrompt = extractSystemPrompt(input)
|
|
192
|
+
if (systemPrompt === undefined) return undefined
|
|
193
|
+
|
|
194
|
+
const match = lookupMockEntry(mocks, systemPrompt)
|
|
195
|
+
if (!match) return undefined
|
|
196
|
+
const entry = match.entry
|
|
197
|
+
|
|
198
|
+
// Legacy format: plain string replacement (treat as replace-all)
|
|
199
|
+
if (typeof entry === 'string') {
|
|
200
|
+
return replaceSystemPrompt(input, entry)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (entry.mode === 'live') return undefined
|
|
204
|
+
|
|
205
|
+
// Initialise per-text call counters
|
|
206
|
+
if (!g['__ELASTICDASH_PROMPT_CALL_COUNTERS__']) {
|
|
207
|
+
g['__ELASTICDASH_PROMPT_CALL_COUNTERS__'] = {} as Record<string, number>
|
|
208
|
+
}
|
|
209
|
+
const counters = g['__ELASTICDASH_PROMPT_CALL_COUNTERS__'] as Record<string, number>
|
|
210
|
+
counters[match.key] = (counters[match.key] ?? 0) + 1
|
|
211
|
+
const callNumber = counters[match.key]
|
|
212
|
+
|
|
213
|
+
if (entry.mode === 'replace-all') {
|
|
214
|
+
return replaceSystemPrompt(input, entry.replacement)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (entry.mode === 'replace-specific') {
|
|
218
|
+
const indices = entry.callIndices ?? []
|
|
219
|
+
if (indices.includes(callNumber)) {
|
|
220
|
+
return replaceSystemPrompt(input, entry.replacement)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return undefined
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Strips one layer of JSON string quoting if present.
|
|
229
|
+
* `'"hello"'` → `'hello'`, `'hello'` → `'hello'`
|
|
230
|
+
*/
|
|
231
|
+
function stripJsonQuotes(s: string): string {
|
|
232
|
+
if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) {
|
|
233
|
+
try {
|
|
234
|
+
const parsed = JSON.parse(s)
|
|
235
|
+
if (typeof parsed === 'string') return parsed
|
|
236
|
+
} catch { /* not valid JSON, return as-is */ }
|
|
237
|
+
}
|
|
238
|
+
return s
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Look up a mock entry by key, falling back to stripped-quotes variants
|
|
243
|
+
* to handle mismatches between trace-recorded text and runtime text.
|
|
244
|
+
*/
|
|
245
|
+
export function lookupMockEntry<T>(mocks: Record<string, T>, text: string): { key: string; entry: T } | undefined {
|
|
246
|
+
if (mocks[text]) return { key: text, entry: mocks[text] }
|
|
247
|
+
// Try stripping quotes from the runtime text
|
|
248
|
+
const stripped = stripJsonQuotes(text)
|
|
249
|
+
if (stripped !== text && mocks[stripped]) return { key: stripped, entry: mocks[stripped] }
|
|
250
|
+
// Try adding quotes to the runtime text (mock key might have quotes)
|
|
251
|
+
const quoted = JSON.stringify(text)
|
|
252
|
+
if (quoted !== text && mocks[quoted]) return { key: quoted, entry: mocks[quoted] }
|
|
253
|
+
return undefined
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Extracts all user-role message content strings from an LLM call input.
|
|
258
|
+
* Handles OpenAI-style `{ messages: [...] }` and plain message arrays.
|
|
259
|
+
*/
|
|
260
|
+
export function extractUserPrompts(input: unknown): string[] {
|
|
261
|
+
if (!input || typeof input !== 'object') return []
|
|
262
|
+
const o = input as Record<string, unknown>
|
|
263
|
+
const msgs = Array.isArray(o.messages) ? o.messages : (Array.isArray(input) ? input as unknown[] : null)
|
|
264
|
+
if (!msgs) return []
|
|
265
|
+
const results: string[] = []
|
|
266
|
+
for (const m of msgs) {
|
|
267
|
+
if (m && typeof m === 'object') {
|
|
268
|
+
const msg = m as Record<string, unknown>
|
|
269
|
+
if (msg.role === 'user' && typeof msg.content === 'string') results.push(msg.content)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return results
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Returns a shallow copy of `input` with all user-role messages whose content
|
|
277
|
+
* matches `originalText` replaced with `newText`.
|
|
278
|
+
*/
|
|
279
|
+
export function replaceUserPrompt(input: unknown, originalText: string, newText: string): unknown {
|
|
280
|
+
if (!input || typeof input !== 'object') return input
|
|
281
|
+
const o = input as Record<string, unknown>
|
|
282
|
+
|
|
283
|
+
const replaceInMsgs = (msgs: unknown[]): unknown[] =>
|
|
284
|
+
msgs.map(m => {
|
|
285
|
+
if (m && typeof m === 'object') {
|
|
286
|
+
const msg = m as Record<string, unknown>
|
|
287
|
+
if (msg.role === 'user' && typeof msg.content === 'string' && msg.content === originalText) {
|
|
288
|
+
return { ...msg, content: newText }
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return m
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
if (Array.isArray(input)) return replaceInMsgs(input as unknown[])
|
|
295
|
+
if (Array.isArray(o.messages)) return { ...o, messages: replaceInMsgs(o.messages as unknown[]) }
|
|
296
|
+
return input
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* User prompt mock entry — mirrors tool mock but for user-role messages.
|
|
301
|
+
*/
|
|
302
|
+
export interface UserPromptMockEntry {
|
|
303
|
+
mode: 'live' | 'replace-all' | 'replace-specific'
|
|
304
|
+
replacement: string
|
|
305
|
+
callIndices?: number[]
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export interface UserPromptMockConfig {
|
|
309
|
+
[originalText: string]: UserPromptMockEntry
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* If user prompt mocks are configured, applies all matching replacements to `input`.
|
|
314
|
+
* Returns modified input or `undefined` if no replacements applied.
|
|
315
|
+
*
|
|
316
|
+
* `__ELASTICDASH_USER_PROMPT_MOCKS__` is `Record<string, UserPromptMockEntry>` keyed
|
|
317
|
+
* by original user message text.
|
|
318
|
+
*
|
|
319
|
+
* Call counting is per unique text — "the x-th LLM call where this text appears".
|
|
320
|
+
*/
|
|
321
|
+
export function resolveUserPromptMock(input: unknown): unknown | undefined {
|
|
322
|
+
const g = globalThis as Record<string, unknown>
|
|
323
|
+
const mocks = g['__ELASTICDASH_USER_PROMPT_MOCKS__'] as UserPromptMockConfig | undefined
|
|
324
|
+
if (!mocks || Object.keys(mocks).length === 0) return undefined
|
|
325
|
+
|
|
326
|
+
const userPrompts = extractUserPrompts(input)
|
|
327
|
+
if (userPrompts.length === 0) return undefined
|
|
328
|
+
|
|
329
|
+
// Initialise per-text call counters
|
|
330
|
+
if (!g['__ELASTICDASH_USER_PROMPT_CALL_COUNTERS__']) {
|
|
331
|
+
g['__ELASTICDASH_USER_PROMPT_CALL_COUNTERS__'] = {} as Record<string, number>
|
|
332
|
+
}
|
|
333
|
+
const counters = g['__ELASTICDASH_USER_PROMPT_CALL_COUNTERS__'] as Record<string, number>
|
|
334
|
+
|
|
335
|
+
// Determine which texts in this call have active mock entries
|
|
336
|
+
const uniqueTexts = Array.from(new Set(userPrompts))
|
|
337
|
+
let modified = false
|
|
338
|
+
let result = input
|
|
339
|
+
|
|
340
|
+
for (const text of uniqueTexts) {
|
|
341
|
+
const match = lookupMockEntry(mocks, text)
|
|
342
|
+
if (!match || match.entry.mode === 'live') continue
|
|
343
|
+
const entry = match.entry
|
|
344
|
+
|
|
345
|
+
// Increment counter for this text (this is the x-th call where this text appears)
|
|
346
|
+
counters[match.key] = (counters[match.key] ?? 0) + 1
|
|
347
|
+
const callNumber = counters[match.key]
|
|
348
|
+
|
|
349
|
+
if (entry.mode === 'replace-all') {
|
|
350
|
+
result = replaceUserPrompt(result, text, entry.replacement)
|
|
351
|
+
modified = true
|
|
352
|
+
} else if (entry.mode === 'replace-specific') {
|
|
353
|
+
const indices = entry.callIndices ?? []
|
|
354
|
+
if (indices.includes(callNumber)) {
|
|
355
|
+
result = replaceUserPrompt(result, text, entry.replacement)
|
|
356
|
+
modified = true
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return modified ? result : undefined
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Resolves whether the current call to `modelName` should be mocked.
|
|
366
|
+
* Mirrors `resolveMock` but reads `__ELASTICDASH_AI_MOCKS__` and
|
|
367
|
+
* `__ELASTICDASH_AI_CALL_COUNTERS__`.
|
|
368
|
+
*/
|
|
369
|
+
/**
|
|
370
|
+
* Standalone utility: applies all matching user prompt mock replacements to
|
|
371
|
+
* a messages array (or LLM input object). Works without ALS — takes the mock
|
|
372
|
+
* config and call counters as explicit parameters.
|
|
373
|
+
*
|
|
374
|
+
* Returns the modified input, or the original input unchanged if no mocks matched.
|
|
375
|
+
*/
|
|
376
|
+
export function applyUserPromptMocks(
|
|
377
|
+
input: unknown,
|
|
378
|
+
userPromptMocks: Record<string, UserPromptMockEntry>,
|
|
379
|
+
callCounters?: Record<string, number>,
|
|
380
|
+
): unknown {
|
|
381
|
+
if (!userPromptMocks || Object.keys(userPromptMocks).length === 0) return input
|
|
382
|
+
|
|
383
|
+
const userPrompts = extractUserPrompts(input)
|
|
384
|
+
if (userPrompts.length === 0) return input
|
|
385
|
+
|
|
386
|
+
const counters = callCounters ?? {}
|
|
387
|
+
const uniqueTexts = Array.from(new Set(userPrompts))
|
|
388
|
+
let modified = false
|
|
389
|
+
let result = input
|
|
390
|
+
|
|
391
|
+
for (const text of uniqueTexts) {
|
|
392
|
+
const match = lookupMockEntry(userPromptMocks, text)
|
|
393
|
+
if (!match || match.entry.mode === 'live') continue
|
|
394
|
+
const entry = match.entry
|
|
395
|
+
|
|
396
|
+
counters[match.key] = (counters[match.key] ?? 0) + 1
|
|
397
|
+
const callNumber = counters[match.key]
|
|
398
|
+
|
|
399
|
+
if (entry.mode === 'replace-all') {
|
|
400
|
+
result = replaceUserPrompt(result, text, entry.replacement)
|
|
401
|
+
modified = true
|
|
402
|
+
} else if (entry.mode === 'replace-specific') {
|
|
403
|
+
const indices = entry.callIndices ?? []
|
|
404
|
+
if (indices.includes(callNumber)) {
|
|
405
|
+
result = replaceUserPrompt(result, text, entry.replacement)
|
|
406
|
+
modified = true
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return modified ? result : input
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Standalone utility: applies system prompt mock replacement to an LLM input
|
|
416
|
+
* object. Works without ALS — takes the mock config and call counters as
|
|
417
|
+
* explicit parameters.
|
|
418
|
+
*
|
|
419
|
+
* Returns the modified input, or the original input unchanged if no mock matched.
|
|
420
|
+
*/
|
|
421
|
+
export function applySystemPromptMocks(
|
|
422
|
+
input: unknown,
|
|
423
|
+
promptMocks: Record<string, string | SystemPromptMockEntry>,
|
|
424
|
+
callCounters?: Record<string, number>,
|
|
425
|
+
): unknown {
|
|
426
|
+
if (!promptMocks || Object.keys(promptMocks).length === 0) return input
|
|
427
|
+
|
|
428
|
+
const systemPrompt = extractSystemPrompt(input)
|
|
429
|
+
if (systemPrompt === undefined) return input
|
|
430
|
+
|
|
431
|
+
const match = lookupMockEntry(promptMocks, systemPrompt)
|
|
432
|
+
if (!match) return input
|
|
433
|
+
const entry = match.entry
|
|
434
|
+
|
|
435
|
+
// Legacy format: plain string replacement (treat as replace-all)
|
|
436
|
+
if (typeof entry === 'string') {
|
|
437
|
+
return replaceSystemPrompt(input, entry)
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (entry.mode === 'live') return input
|
|
441
|
+
|
|
442
|
+
const counters = callCounters ?? {}
|
|
443
|
+
counters[match.key] = (counters[match.key] ?? 0) + 1
|
|
444
|
+
const callNumber = counters[match.key]
|
|
445
|
+
|
|
446
|
+
if (entry.mode === 'replace-all') {
|
|
447
|
+
return replaceSystemPrompt(input, entry.replacement)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (entry.mode === 'replace-specific') {
|
|
451
|
+
const indices = entry.callIndices ?? []
|
|
452
|
+
if (indices.includes(callNumber)) {
|
|
453
|
+
return replaceSystemPrompt(input, entry.replacement)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return input
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export function resolveAIMock(modelName: string): MockResult {
|
|
461
|
+
const g = globalThis as Record<string, unknown>
|
|
462
|
+
|
|
463
|
+
const mocks = g['__ELASTICDASH_AI_MOCKS__'] as Record<string, AIMockEntry> | undefined
|
|
464
|
+
if (!mocks) return { mocked: false }
|
|
465
|
+
|
|
466
|
+
const entry = mocks[modelName]
|
|
467
|
+
if (!entry || entry.mode === 'live') return { mocked: false }
|
|
468
|
+
|
|
469
|
+
if (!g['__ELASTICDASH_AI_CALL_COUNTERS__']) {
|
|
470
|
+
g['__ELASTICDASH_AI_CALL_COUNTERS__'] = {} as Record<string, number>
|
|
471
|
+
}
|
|
472
|
+
const counters = g['__ELASTICDASH_AI_CALL_COUNTERS__'] as Record<string, number>
|
|
473
|
+
counters[modelName] = (counters[modelName] ?? 0) + 1
|
|
474
|
+
const callNumber = counters[modelName]
|
|
475
|
+
|
|
476
|
+
if (entry.mode === 'mock-all') {
|
|
477
|
+
const data = entry.mockData ?? {}
|
|
478
|
+
const raw = data[callNumber] !== undefined ? data[callNumber] : data[0]
|
|
479
|
+
return { mocked: true, result: normaliseMockResult(raw) }
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (entry.mode === 'mock-specific') {
|
|
483
|
+
const indices = entry.callIndices ?? []
|
|
484
|
+
if (indices.includes(callNumber)) {
|
|
485
|
+
const data = entry.mockData ?? {}
|
|
486
|
+
return { mocked: true, result: normaliseMockResult(data[callNumber]) }
|
|
487
|
+
}
|
|
488
|
+
return { mocked: false }
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return { mocked: false }
|
|
492
|
+
}
|