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,419 @@
|
|
|
1
|
+
import { getCaptureContext } from '../capture/recorder.js'
|
|
2
|
+
import { rawDateNow } from './side-effects.js'
|
|
3
|
+
import { getHttpRunContext, getHttpFrozenEvent, getHttpPromptMock, getHttpUserPromptMock, getHttpAIMock, pushTelemetryEvent, tryAutoInitHttpContext, getObservabilityContext } from './telemetry-push.js'
|
|
4
|
+
import { resolveAIMock, resolvePromptMock, resolveUserPromptMock } from '../internals/mock-resolver.js'
|
|
5
|
+
import { consumeCapturedLLMRequest } from './ai-interceptor.js'
|
|
6
|
+
import { getEdReplayContext, replayCall } from '../ci/replay.js'
|
|
7
|
+
import type { WorkflowEvent } from '../capture/event.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Tracks whether we are currently inside a `wrapAI` execution.
|
|
11
|
+
* When active, `ai-interceptor` should skip recording to avoid duplicates.
|
|
12
|
+
* Uses a counter (not boolean) to handle nested wrapAI calls correctly.
|
|
13
|
+
*/
|
|
14
|
+
const AI_WRAPPER_KEY = '__elasticdash_ai_wrapper_depth__'
|
|
15
|
+
const g = globalThis as Record<string, unknown>
|
|
16
|
+
if (g[AI_WRAPPER_KEY] == null) g[AI_WRAPPER_KEY] = 0
|
|
17
|
+
|
|
18
|
+
function enterAIWrapper(): void {
|
|
19
|
+
g[AI_WRAPPER_KEY] = ((g[AI_WRAPPER_KEY] as number) ?? 0) + 1
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function leaveAIWrapper(): void {
|
|
23
|
+
g[AI_WRAPPER_KEY] = Math.max(0, ((g[AI_WRAPPER_KEY] as number) ?? 0) - 1)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type UsageInfo = { inputTokens?: number; outputTokens?: number; totalTokens?: number }
|
|
27
|
+
|
|
28
|
+
function extractUsage(output: unknown): UsageInfo | undefined {
|
|
29
|
+
if (!output || typeof output !== 'object') return undefined
|
|
30
|
+
const o = output as Record<string, unknown>
|
|
31
|
+
if (o.usage && typeof o.usage === 'object') {
|
|
32
|
+
const u = o.usage as Record<string, number>
|
|
33
|
+
// Anthropic SDK: input_tokens / output_tokens
|
|
34
|
+
if (u.input_tokens != null || u.output_tokens != null) {
|
|
35
|
+
return {
|
|
36
|
+
inputTokens: u.input_tokens,
|
|
37
|
+
outputTokens: u.output_tokens,
|
|
38
|
+
totalTokens: (u.input_tokens ?? 0) + (u.output_tokens ?? 0),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// OpenAI SDK: prompt_tokens / completion_tokens
|
|
42
|
+
if (u.prompt_tokens != null || u.completion_tokens != null) {
|
|
43
|
+
return {
|
|
44
|
+
inputTokens: u.prompt_tokens,
|
|
45
|
+
outputTokens: u.completion_tokens,
|
|
46
|
+
totalTokens: u.total_tokens,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Vercel AI SDK: { inputTokens, outputTokens }
|
|
50
|
+
if (u.inputTokens != null || u.outputTokens != null) {
|
|
51
|
+
return {
|
|
52
|
+
inputTokens: u.inputTokens,
|
|
53
|
+
outputTokens: u.outputTokens,
|
|
54
|
+
totalTokens: (u.inputTokens ?? 0) + (u.outputTokens ?? 0),
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Gemini SDK: usageMetadata
|
|
59
|
+
if (o.usageMetadata && typeof o.usageMetadata === 'object') {
|
|
60
|
+
const u = o.usageMetadata as Record<string, number>
|
|
61
|
+
return {
|
|
62
|
+
inputTokens: u.promptTokenCount,
|
|
63
|
+
outputTokens: u.candidatesTokenCount,
|
|
64
|
+
totalTokens: u.totalTokenCount,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Flat token fields (e.g. { tokens: N } or { outputTokens: N, inputTokens: N })
|
|
68
|
+
if (typeof o.tokens === 'number' || typeof o.outputTokens === 'number') {
|
|
69
|
+
return {
|
|
70
|
+
inputTokens: typeof o.inputTokens === 'number' ? o.inputTokens : undefined,
|
|
71
|
+
outputTokens: typeof o.outputTokens === 'number' ? o.outputTokens : (typeof o.tokens === 'number' ? o.tokens : undefined),
|
|
72
|
+
totalTokens: (typeof o.inputTokens === 'number' ? o.inputTokens : 0) + (typeof o.outputTokens === 'number' ? o.outputTokens : (typeof o.tokens === 'number' ? o.tokens : 0)),
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return undefined
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* After callFn returns, consume any captured LLM request from ai-interceptor
|
|
80
|
+
* and merge it into the event input so the recorded event contains the actual
|
|
81
|
+
* HTTP payload sent to the LLM (messages, model, parameters).
|
|
82
|
+
* Also returns captured usage when the app's output doesn't include it.
|
|
83
|
+
*/
|
|
84
|
+
function enrichFromLLMCapture(
|
|
85
|
+
input: unknown,
|
|
86
|
+
appUsage: UsageInfo | undefined,
|
|
87
|
+
fallbackModel?: string,
|
|
88
|
+
fallbackProvider?: string,
|
|
89
|
+
): { input: unknown; usage: UsageInfo | undefined } {
|
|
90
|
+
const captured = consumeCapturedLLMRequest()
|
|
91
|
+
if (captured) {
|
|
92
|
+
const enrichedInput = (input && typeof input === 'object')
|
|
93
|
+
? { ...(input as Record<string, unknown>), llmRequest: captured.body, promptSnippet: captured.promptSnippet }
|
|
94
|
+
: { originalInput: input, llmRequest: captured.body, promptSnippet: captured.promptSnippet }
|
|
95
|
+
const usage = appUsage ?? captured.usage
|
|
96
|
+
return { input: enrichedInput, usage }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Interceptor capture failed (e.g. OpenAI SDK uses native fetch not globalThis.fetch).
|
|
100
|
+
// Fall back to model/provider from wrapAI options or function arguments.
|
|
101
|
+
const resolvedModel = fallbackModel
|
|
102
|
+
|| (input && typeof input === 'object' ? (input as Record<string, unknown>).model as string | undefined : undefined)
|
|
103
|
+
const resolvedProvider = fallbackProvider
|
|
104
|
+
|| (input && typeof input === 'object' ? (input as Record<string, unknown>).provider as string | undefined : undefined)
|
|
105
|
+
|
|
106
|
+
if (resolvedModel || resolvedProvider) {
|
|
107
|
+
const enrichedInput = (input && typeof input === 'object')
|
|
108
|
+
? { ...(input as Record<string, unknown>), llmRequest: { model: resolvedModel, provider: resolvedProvider } }
|
|
109
|
+
: { originalInput: input, llmRequest: { model: resolvedModel, provider: resolvedProvider } }
|
|
110
|
+
return { input: enrichedInput, usage: appUsage }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { input, usage: appUsage }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isReadableStream(v: unknown): v is ReadableStream<Uint8Array> {
|
|
117
|
+
return (
|
|
118
|
+
typeof v === 'object' &&
|
|
119
|
+
v !== null &&
|
|
120
|
+
typeof (v as ReadableStream).getReader === 'function' &&
|
|
121
|
+
typeof (v as ReadableStream).tee === 'function'
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isAsyncIterable(v: unknown): v is AsyncIterable<unknown> {
|
|
126
|
+
return typeof v === 'object' && v !== null && Symbol.asyncIterator in (v as object)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function bufferReadableStream(stream: ReadableStream<Uint8Array>): Promise<string> {
|
|
130
|
+
const decoder = new TextDecoder()
|
|
131
|
+
const reader = stream.getReader()
|
|
132
|
+
let raw = ''
|
|
133
|
+
try {
|
|
134
|
+
for (;;) {
|
|
135
|
+
const { done, value } = await reader.read()
|
|
136
|
+
if (done) break
|
|
137
|
+
raw += decoder.decode(value, { stream: true })
|
|
138
|
+
}
|
|
139
|
+
} finally {
|
|
140
|
+
reader.releaseLock()
|
|
141
|
+
}
|
|
142
|
+
return raw
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function reconstructStream(raw: string): ReadableStream<Uint8Array> {
|
|
146
|
+
const encoder = new TextEncoder()
|
|
147
|
+
return new ReadableStream<Uint8Array>({
|
|
148
|
+
start(ctrl) {
|
|
149
|
+
ctrl.enqueue(encoder.encode(raw))
|
|
150
|
+
ctrl.close()
|
|
151
|
+
},
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Wraps an AsyncIterable so chunks are collected while the caller iterates */
|
|
156
|
+
function wrapAsyncIterable<T>(
|
|
157
|
+
source: AsyncIterable<T>,
|
|
158
|
+
onComplete: (chunks: T[]) => void,
|
|
159
|
+
): AsyncIterable<T> {
|
|
160
|
+
return {
|
|
161
|
+
[Symbol.asyncIterator]() {
|
|
162
|
+
const iter = source[Symbol.asyncIterator]()
|
|
163
|
+
const collected: T[] = []
|
|
164
|
+
return {
|
|
165
|
+
async next() {
|
|
166
|
+
const result = await iter.next()
|
|
167
|
+
if (!result.done) {
|
|
168
|
+
collected.push(result.value)
|
|
169
|
+
} else {
|
|
170
|
+
onComplete(collected)
|
|
171
|
+
}
|
|
172
|
+
return result
|
|
173
|
+
},
|
|
174
|
+
async return(value?: unknown) {
|
|
175
|
+
onComplete(collected)
|
|
176
|
+
return iter.return ? iter.return(value) : { done: true as const, value: undefined }
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function wrapAI<Args extends unknown[], R>(
|
|
184
|
+
modelName: string,
|
|
185
|
+
callFn: (...args: Args) => Promise<R>,
|
|
186
|
+
_options?: { model?: string; provider?: string },
|
|
187
|
+
): (...args: Args) => Promise<R> {
|
|
188
|
+
return async (...args: Args): Promise<R> => {
|
|
189
|
+
// Phase 3 fixture replay: short-circuit if ed-runner replay is active
|
|
190
|
+
const edReplay = getEdReplayContext()
|
|
191
|
+
if (edReplay) {
|
|
192
|
+
const input = args.length === 1 ? args[0] : args
|
|
193
|
+
const { output } = replayCall(edReplay, 'ai_call', modelName, input)
|
|
194
|
+
return output as R
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
await tryAutoInitHttpContext()
|
|
198
|
+
const ctx = getCaptureContext()
|
|
199
|
+
const httpCtx = getHttpRunContext()
|
|
200
|
+
const obsCtx = getObservabilityContext()
|
|
201
|
+
|
|
202
|
+
if (!ctx && !httpCtx && !obsCtx) return callFn(...args)
|
|
203
|
+
|
|
204
|
+
enterAIWrapper()
|
|
205
|
+
try {
|
|
206
|
+
const start = rawDateNow()
|
|
207
|
+
|
|
208
|
+
// Observability-only mode: record and push, no mocks/replay
|
|
209
|
+
if (!ctx && !httpCtx && obsCtx) {
|
|
210
|
+
const id = obsCtx.nextId()
|
|
211
|
+
const input = args.length === 1 ? args[0] : args
|
|
212
|
+
try {
|
|
213
|
+
const output = await callFn(...args)
|
|
214
|
+
const enriched = enrichFromLLMCapture(input, extractUsage(output), _options?.model, _options?.provider)
|
|
215
|
+
|
|
216
|
+
if (isReadableStream(output)) {
|
|
217
|
+
const [streamForCaller, streamForRecorder] = output.tee()
|
|
218
|
+
bufferReadableStream(streamForRecorder).then((rawText) => {
|
|
219
|
+
const durationMs = rawDateNow() - start
|
|
220
|
+
pushTelemetryEvent({ id, type: 'ai', name: modelName, input: enriched.input, output: null, streamed: true, streamRaw: rawText, timestamp: start, durationMs, ...(enriched.usage ? { usage: enriched.usage } : {}) })
|
|
221
|
+
}).catch(() => {
|
|
222
|
+
const durationMs = rawDateNow() - start
|
|
223
|
+
pushTelemetryEvent({ id, type: 'ai', name: modelName, input: enriched.input, output: null, streamed: true, streamRaw: '', timestamp: start, durationMs, ...(enriched.usage ? { usage: enriched.usage } : {}) })
|
|
224
|
+
})
|
|
225
|
+
return streamForCaller as unknown as R
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (isAsyncIterable(output)) {
|
|
229
|
+
return wrapAsyncIterable(output, (chunks) => {
|
|
230
|
+
const durationMs = rawDateNow() - start
|
|
231
|
+
const rawText = chunks.map((c) => (typeof c === 'string' ? c : JSON.stringify(c))).join('')
|
|
232
|
+
pushTelemetryEvent({ id, type: 'ai', name: modelName, input: enriched.input, output: null, streamed: true, streamRaw: rawText, timestamp: start, durationMs, ...(enriched.usage ? { usage: enriched.usage } : {}) })
|
|
233
|
+
}) as unknown as R
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const durationMs = rawDateNow() - start
|
|
237
|
+
const event: WorkflowEvent = {
|
|
238
|
+
id, type: 'ai', name: modelName, input: enriched.input, output,
|
|
239
|
+
timestamp: start, durationMs,
|
|
240
|
+
...(enriched.usage ? { usage: enriched.usage } : {}),
|
|
241
|
+
}
|
|
242
|
+
pushTelemetryEvent(event)
|
|
243
|
+
return output
|
|
244
|
+
} catch (e) {
|
|
245
|
+
const enriched = enrichFromLLMCapture(input, undefined, _options?.model, _options?.provider)
|
|
246
|
+
const durationMs = rawDateNow() - start
|
|
247
|
+
pushTelemetryEvent({
|
|
248
|
+
id, type: 'ai', name: modelName, input: enriched.input,
|
|
249
|
+
output: { error: String(e) }, timestamp: start, durationMs,
|
|
250
|
+
})
|
|
251
|
+
throw e
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (ctx) {
|
|
256
|
+
const { recorder, replay } = ctx
|
|
257
|
+
const id = recorder.nextId()
|
|
258
|
+
|
|
259
|
+
if (replay.shouldReplay(id)) {
|
|
260
|
+
const historical = replay.getRecordedEvent(id)
|
|
261
|
+
if (historical?.streamed === true) {
|
|
262
|
+
const raw = typeof historical.streamRaw === 'string' ? historical.streamRaw : ''
|
|
263
|
+
return reconstructStream(raw) as unknown as R
|
|
264
|
+
}
|
|
265
|
+
return replay.getRecordedResult(id) as R
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Check AI mock (output mock — skip real call, return recorded result)
|
|
269
|
+
const aiMock = resolveAIMock(modelName)
|
|
270
|
+
if (aiMock.mocked) {
|
|
271
|
+
const input = args.length === 1 ? args[0] : args
|
|
272
|
+
const event: WorkflowEvent = {
|
|
273
|
+
id, type: 'ai', name: modelName, input,
|
|
274
|
+
output: aiMock.result, timestamp: start, durationMs: 0,
|
|
275
|
+
}
|
|
276
|
+
recorder.record(event)
|
|
277
|
+
if (httpCtx) pushTelemetryEvent(event)
|
|
278
|
+
return aiMock.result as R
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Check prompt mocks (system + user prompt replacement — call real LLM with modified prompts)
|
|
282
|
+
const rawInput = args.length === 1 ? args[0] : args
|
|
283
|
+
const promptModifiedInput = resolvePromptMock(rawInput)
|
|
284
|
+
const userPromptModifiedInput = resolveUserPromptMock(promptModifiedInput !== undefined ? promptModifiedInput : rawInput)
|
|
285
|
+
const modifiedInput = userPromptModifiedInput !== undefined ? userPromptModifiedInput : promptModifiedInput
|
|
286
|
+
const effectiveArgs: Args = modifiedInput !== undefined ? [modifiedInput] as unknown as Args : args
|
|
287
|
+
const input = modifiedInput !== undefined ? modifiedInput : rawInput
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const output = await callFn(...effectiveArgs)
|
|
291
|
+
|
|
292
|
+
if (isReadableStream(output)) {
|
|
293
|
+
const [streamForCaller, streamForRecorder] = output.tee()
|
|
294
|
+
bufferReadableStream(streamForRecorder).then((rawText) => {
|
|
295
|
+
const durationMs = rawDateNow() - start
|
|
296
|
+
const event: WorkflowEvent = { id, type: 'ai', name: modelName, input, output: null, streamed: true, streamRaw: rawText, timestamp: start, durationMs }
|
|
297
|
+
recorder.record(event)
|
|
298
|
+
if (httpCtx) pushTelemetryEvent(event)
|
|
299
|
+
}).catch(() => {
|
|
300
|
+
const durationMs = rawDateNow() - start
|
|
301
|
+
const event: WorkflowEvent = { id, type: 'ai', name: modelName, input, output: null, streamed: true, streamRaw: '', timestamp: start, durationMs }
|
|
302
|
+
recorder.record(event)
|
|
303
|
+
if (httpCtx) pushTelemetryEvent(event)
|
|
304
|
+
})
|
|
305
|
+
return streamForCaller as unknown as R
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (isAsyncIterable(output)) {
|
|
309
|
+
return wrapAsyncIterable(output, (chunks) => {
|
|
310
|
+
const durationMs = rawDateNow() - start
|
|
311
|
+
const rawText = chunks.map((c) => (typeof c === 'string' ? c : JSON.stringify(c))).join('')
|
|
312
|
+
const event: WorkflowEvent = { id, type: 'ai', name: modelName, input, output: null, streamed: true, streamRaw: rawText, timestamp: start, durationMs }
|
|
313
|
+
recorder.record(event)
|
|
314
|
+
if (httpCtx) pushTelemetryEvent(event)
|
|
315
|
+
}) as unknown as R
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const durationMs = rawDateNow() - start
|
|
319
|
+
const usage = extractUsage(output)
|
|
320
|
+
const event: WorkflowEvent = {
|
|
321
|
+
id, type: 'ai', name: modelName, input, output,
|
|
322
|
+
timestamp: start, durationMs,
|
|
323
|
+
...(usage ? { usage } : {}),
|
|
324
|
+
}
|
|
325
|
+
recorder.record(event)
|
|
326
|
+
if (httpCtx) pushTelemetryEvent(event)
|
|
327
|
+
return output
|
|
328
|
+
} catch (e) {
|
|
329
|
+
const durationMs = rawDateNow() - start
|
|
330
|
+
const event: WorkflowEvent = {
|
|
331
|
+
id, type: 'ai', name: modelName, input,
|
|
332
|
+
output: { error: String(e) }, timestamp: start, durationMs,
|
|
333
|
+
}
|
|
334
|
+
recorder.record(event)
|
|
335
|
+
if (httpCtx) pushTelemetryEvent(event)
|
|
336
|
+
throw e
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// HTTP mode only (no capture context)
|
|
341
|
+
const id = httpCtx!.nextId()
|
|
342
|
+
|
|
343
|
+
// Replay frozen step: push historical event so dashboard trace stays complete
|
|
344
|
+
const frozen = getHttpFrozenEvent(id)
|
|
345
|
+
if (frozen) {
|
|
346
|
+
pushTelemetryEvent(frozen)
|
|
347
|
+
if (frozen.streamed === true) {
|
|
348
|
+
const raw = typeof frozen.streamRaw === 'string' ? frozen.streamRaw : ''
|
|
349
|
+
return reconstructStream(raw) as unknown as R
|
|
350
|
+
}
|
|
351
|
+
return frozen.output as R
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Check AI output mock (skip real call, return mocked result)
|
|
355
|
+
const aiMock = getHttpAIMock(modelName)
|
|
356
|
+
if (aiMock.mocked) {
|
|
357
|
+
const input = args.length === 1 ? args[0] : args
|
|
358
|
+
const event: WorkflowEvent = {
|
|
359
|
+
id, type: 'ai', name: modelName, input,
|
|
360
|
+
output: aiMock.result, timestamp: start, durationMs: 0,
|
|
361
|
+
}
|
|
362
|
+
pushTelemetryEvent(event)
|
|
363
|
+
return aiMock.result as R
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Check prompt mocks (system + user prompt replacement in HTTP mode)
|
|
367
|
+
const rawHttpInput = args.length === 1 ? args[0] : args
|
|
368
|
+
const httpSystemModified = getHttpPromptMock(rawHttpInput)
|
|
369
|
+
const httpUserModified = getHttpUserPromptMock(httpSystemModified !== undefined ? httpSystemModified : rawHttpInput)
|
|
370
|
+
const httpModifiedInput = httpUserModified !== undefined ? httpUserModified : httpSystemModified
|
|
371
|
+
const httpEffectiveArgs: Args = httpModifiedInput !== undefined ? [httpModifiedInput] as unknown as Args : args
|
|
372
|
+
const input = httpModifiedInput !== undefined ? httpModifiedInput : rawHttpInput
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
const output = await callFn(...httpEffectiveArgs)
|
|
376
|
+
const enriched = enrichFromLLMCapture(input, extractUsage(output), _options?.model, _options?.provider)
|
|
377
|
+
|
|
378
|
+
if (isReadableStream(output)) {
|
|
379
|
+
const [streamForCaller, streamForRecorder] = output.tee()
|
|
380
|
+
bufferReadableStream(streamForRecorder).then((rawText) => {
|
|
381
|
+
const durationMs = rawDateNow() - start
|
|
382
|
+
pushTelemetryEvent({ id, type: 'ai', name: modelName, input: enriched.input, output: null, streamed: true, streamRaw: rawText, timestamp: start, durationMs, ...(enriched.usage ? { usage: enriched.usage } : {}) })
|
|
383
|
+
}).catch(() => {
|
|
384
|
+
const durationMs = rawDateNow() - start
|
|
385
|
+
pushTelemetryEvent({ id, type: 'ai', name: modelName, input: enriched.input, output: null, streamed: true, streamRaw: '', timestamp: start, durationMs, ...(enriched.usage ? { usage: enriched.usage } : {}) })
|
|
386
|
+
})
|
|
387
|
+
return streamForCaller as unknown as R
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (isAsyncIterable(output)) {
|
|
391
|
+
return wrapAsyncIterable(output, (chunks) => {
|
|
392
|
+
const durationMs = rawDateNow() - start
|
|
393
|
+
const rawText = chunks.map((c) => (typeof c === 'string' ? c : JSON.stringify(c))).join('')
|
|
394
|
+
pushTelemetryEvent({ id, type: 'ai', name: modelName, input: enriched.input, output: null, streamed: true, streamRaw: rawText, timestamp: start, durationMs, ...(enriched.usage ? { usage: enriched.usage } : {}) })
|
|
395
|
+
}) as unknown as R
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const durationMs = rawDateNow() - start
|
|
399
|
+
const event: WorkflowEvent = {
|
|
400
|
+
id, type: 'ai', name: modelName, input: enriched.input, output,
|
|
401
|
+
timestamp: start, durationMs,
|
|
402
|
+
...(enriched.usage ? { usage: enriched.usage } : {}),
|
|
403
|
+
}
|
|
404
|
+
pushTelemetryEvent(event)
|
|
405
|
+
return output
|
|
406
|
+
} catch (e) {
|
|
407
|
+
const enriched = enrichFromLLMCapture(input, undefined, _options?.model, _options?.provider)
|
|
408
|
+
const durationMs = rawDateNow() - start
|
|
409
|
+
pushTelemetryEvent({
|
|
410
|
+
id, type: 'ai', name: modelName, input: enriched.input,
|
|
411
|
+
output: { error: String(e) }, timestamp: start, durationMs,
|
|
412
|
+
})
|
|
413
|
+
throw e
|
|
414
|
+
}
|
|
415
|
+
} finally {
|
|
416
|
+
leaveAIWrapper()
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conditional recorder utility for worker contexts
|
|
3
|
+
*
|
|
4
|
+
* This module provides a safe wrapper around recordToolCall that only executes
|
|
5
|
+
* in worker environments. In non-worker contexts, it's a no-op to avoid unnecessary
|
|
6
|
+
* imports and function calls.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Check if running in an Elasticdash worker context
|
|
10
|
+
const isWorkerContext = (): boolean => {
|
|
11
|
+
if (typeof globalThis === 'undefined') {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
return (globalThis as any).__ELASTICDASH_WORKER__ === true;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
let recordToolCallFn: ((name: string, input: any, output: any) => void) | null = null;
|
|
18
|
+
|
|
19
|
+
// Lazy load recordToolCall only if in worker context
|
|
20
|
+
const getRecordToolCall = async (): Promise<((name: string, input: any, output: any) => void) | null> => {
|
|
21
|
+
if (!isWorkerContext()) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (recordToolCallFn !== null) {
|
|
26
|
+
return recordToolCallFn;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const { recordToolCall } = await import('../tracing.js');
|
|
31
|
+
recordToolCallFn = recordToolCall;
|
|
32
|
+
return recordToolCallFn;
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.warn('Failed to load recordToolCall from tracing module:', err);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Safely record a tool call only if in worker context
|
|
41
|
+
*
|
|
42
|
+
* @param name - Name of the tool/function
|
|
43
|
+
* @param input - Input parameters to the tool
|
|
44
|
+
* @param output - Output/result from the tool
|
|
45
|
+
*/
|
|
46
|
+
export const safeRecordToolCall = async (
|
|
47
|
+
name: string,
|
|
48
|
+
input: any,
|
|
49
|
+
output: any
|
|
50
|
+
): Promise<void> => {
|
|
51
|
+
const recorder = await getRecordToolCall();
|
|
52
|
+
if (recorder) {
|
|
53
|
+
recorder(name, input, output);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Synchronous version - checks if we're in worker context without importing
|
|
59
|
+
* Use this if you want to avoid async/await in your tool functions
|
|
60
|
+
*/
|
|
61
|
+
export const isWorker = (): boolean => {
|
|
62
|
+
return isWorkerContext();
|
|
63
|
+
};
|