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,393 @@
|
|
|
1
|
+
import { getCaptureContext } from '../capture/recorder.js'
|
|
2
|
+
import { getCurrentTrace } from '../trace-adapter/context.js'
|
|
3
|
+
import { getHttpRunContext, getHttpFrozenEvent, pushTelemetryEvent, tryAutoInitHttpContext, getObservabilityContext } from './telemetry-push.js'
|
|
4
|
+
import { rawDateNow } from './side-effects.js'
|
|
5
|
+
|
|
6
|
+
// AI provider URLs are already captured by ai-interceptor.ts as "llm" steps.
|
|
7
|
+
// Skip them here to avoid duplicate observations.
|
|
8
|
+
const AI_URL_PATTERNS = [
|
|
9
|
+
/https?:\/\/api\.openai\.com\/v1\/((chat\/)?completions|embeddings)/,
|
|
10
|
+
/https?:\/\/api\.anthropic\.com\/v1\/messages/,
|
|
11
|
+
/https?:\/\/generativelanguage\.googleapis\.com\/.*\/models\/[^/:]+:(generateContent|streamGenerateContent)/,
|
|
12
|
+
/https?:\/\/api\.x\.ai\/v1\/(chat\/)?completions/,
|
|
13
|
+
/https?:\/\/api\.moonshot\.ai\/v1\/(chat\/)?completions/,
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
function isAIProviderUrl(url: string): boolean {
|
|
17
|
+
return AI_URL_PATTERNS.some(p => p.test(url))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
function parseQuery(url: string): Record<string, string> | undefined {
|
|
22
|
+
try {
|
|
23
|
+
const { searchParams } = new URL(url)
|
|
24
|
+
if (searchParams.size === 0) return undefined
|
|
25
|
+
return Object.fromEntries(searchParams.entries())
|
|
26
|
+
} catch {
|
|
27
|
+
// Relative URL — extract manually
|
|
28
|
+
const qIdx = url.indexOf('?')
|
|
29
|
+
if (qIdx === -1) return undefined
|
|
30
|
+
try {
|
|
31
|
+
const params = new URLSearchParams(url.slice(qIdx + 1))
|
|
32
|
+
if (![...params].length) return undefined
|
|
33
|
+
return Object.fromEntries(params.entries())
|
|
34
|
+
} catch {
|
|
35
|
+
return undefined
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseBody(body?: RequestInit['body'] | null): unknown {
|
|
41
|
+
if (body == null) return undefined
|
|
42
|
+
if (typeof body === 'string') {
|
|
43
|
+
try { return JSON.parse(body) } catch { return body }
|
|
44
|
+
}
|
|
45
|
+
if (body instanceof URLSearchParams) {
|
|
46
|
+
return Object.fromEntries(body.entries())
|
|
47
|
+
}
|
|
48
|
+
return '[binary]'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizeHeaders(headers?: RequestInit['headers']): Record<string, string> | undefined {
|
|
52
|
+
if (!headers) return undefined
|
|
53
|
+
if (headers instanceof Headers) {
|
|
54
|
+
const obj: Record<string, string> = {}
|
|
55
|
+
headers.forEach((v, k) => { obj[k] = v })
|
|
56
|
+
return obj
|
|
57
|
+
}
|
|
58
|
+
if (Array.isArray(headers)) return Object.fromEntries(headers)
|
|
59
|
+
return headers as Record<string, string>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function pickReplayResponseHeaders(headers?: Record<string, unknown>): Record<string, string> {
|
|
63
|
+
if (!headers) return { 'Content-Type': 'application/json' }
|
|
64
|
+
const out: Record<string, string> = {}
|
|
65
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
66
|
+
if (typeof value === 'string') out[key] = value
|
|
67
|
+
}
|
|
68
|
+
if (!out['Content-Type']) out['Content-Type'] = 'application/json'
|
|
69
|
+
return out
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isStreamingContentType(headers: Headers): boolean {
|
|
73
|
+
const ct = headers.get('content-type') ?? ''
|
|
74
|
+
return (
|
|
75
|
+
ct.includes('text/event-stream') ||
|
|
76
|
+
ct.includes('application/x-ndjson') ||
|
|
77
|
+
ct.includes('application/stream+json') ||
|
|
78
|
+
ct.includes('application/jsonl') ||
|
|
79
|
+
headers.get('x-vercel-ai-data-stream') === 'v1'
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isVercelAIDataStream(headers: Headers): boolean {
|
|
84
|
+
return headers.get('x-vercel-ai-data-stream') === 'v1'
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface VercelAIStreamResult {
|
|
88
|
+
type: 'text' | 'result' | 'plan' | 'error'
|
|
89
|
+
message: string
|
|
90
|
+
refinedQuery?: string
|
|
91
|
+
sessionId?: string
|
|
92
|
+
awaitingApproval?: boolean
|
|
93
|
+
executionPlan?: unknown[]
|
|
94
|
+
error?: string
|
|
95
|
+
planRejected?: boolean
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseVercelAIDataStream(raw: string): VercelAIStreamResult {
|
|
99
|
+
let accumulatedText = ''
|
|
100
|
+
let resultData: Record<string, unknown> = {}
|
|
101
|
+
let errorMessage = ''
|
|
102
|
+
let hasError = false
|
|
103
|
+
|
|
104
|
+
for (const line of raw.split('\n')) {
|
|
105
|
+
if (!line) continue
|
|
106
|
+
const colonIdx = line.indexOf(':')
|
|
107
|
+
if (colonIdx === -1) continue
|
|
108
|
+
const prefix = line.slice(0, colonIdx)
|
|
109
|
+
const payload = line.slice(colonIdx + 1)
|
|
110
|
+
try {
|
|
111
|
+
if (prefix === '0') {
|
|
112
|
+
accumulatedText += JSON.parse(payload) as string
|
|
113
|
+
} else if (prefix === '2') {
|
|
114
|
+
const events = JSON.parse(payload) as Array<Record<string, unknown>>
|
|
115
|
+
for (const event of events) {
|
|
116
|
+
if (event.type === 'result' || event.type === 'plan') {
|
|
117
|
+
resultData = { ...resultData, ...event }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} else if (prefix === '3') {
|
|
121
|
+
hasError = true
|
|
122
|
+
errorMessage = JSON.parse(payload) as string
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
// ignore malformed frames
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (hasError) {
|
|
130
|
+
return { message: errorMessage, type: 'error', error: errorMessage }
|
|
131
|
+
}
|
|
132
|
+
if (accumulatedText) {
|
|
133
|
+
return { message: accumulatedText, type: 'text', refinedQuery: resultData.refinedQuery as string | undefined }
|
|
134
|
+
}
|
|
135
|
+
if (resultData.type === 'plan') {
|
|
136
|
+
return {
|
|
137
|
+
message: (resultData.message as string) ?? '',
|
|
138
|
+
type: 'plan',
|
|
139
|
+
sessionId: resultData.sessionId as string | undefined,
|
|
140
|
+
awaitingApproval: true,
|
|
141
|
+
executionPlan: resultData.executionPlan as unknown[] | undefined,
|
|
142
|
+
refinedQuery: resultData.refinedQuery as string | undefined,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
message: (resultData.message as string) ?? '',
|
|
147
|
+
type: 'result',
|
|
148
|
+
refinedQuery: resultData.refinedQuery as string | undefined,
|
|
149
|
+
error: resultData.error as string | undefined,
|
|
150
|
+
planRejected: resultData.planRejected as boolean | undefined,
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export type { VercelAIStreamResult }
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Reads a Vercel AI SDK data-stream response to completion and returns a
|
|
158
|
+
* structured result. Use this inside workflow functions that call the streaming
|
|
159
|
+
* endpoint via fetch so the framework can intercept and replay the call.
|
|
160
|
+
*
|
|
161
|
+
* @param response - The fetch Response whose body carries the
|
|
162
|
+
* `x-vercel-ai-data-stream: v1` wire protocol.
|
|
163
|
+
*/
|
|
164
|
+
export async function readVercelAIStream(response: Response): Promise<VercelAIStreamResult> {
|
|
165
|
+
if (!response.body) {
|
|
166
|
+
return { message: 'No response body', type: 'error', error: 'No response body' }
|
|
167
|
+
}
|
|
168
|
+
const reader = response.body.getReader()
|
|
169
|
+
const decoder = new TextDecoder()
|
|
170
|
+
let raw = ''
|
|
171
|
+
try {
|
|
172
|
+
for (;;) {
|
|
173
|
+
const { done, value } = await reader.read()
|
|
174
|
+
if (done) break
|
|
175
|
+
raw += decoder.decode(value, { stream: true })
|
|
176
|
+
}
|
|
177
|
+
} finally {
|
|
178
|
+
reader.releaseLock()
|
|
179
|
+
}
|
|
180
|
+
return parseVercelAIDataStream(raw)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function reconstructStream(raw: string): ReadableStream<Uint8Array> {
|
|
184
|
+
const encoder = new TextEncoder()
|
|
185
|
+
return new ReadableStream<Uint8Array>({
|
|
186
|
+
start(ctrl) {
|
|
187
|
+
ctrl.enqueue(encoder.encode(raw))
|
|
188
|
+
ctrl.close()
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let originalFetch: typeof globalThis.fetch | undefined
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Returns the original unpatched fetch. SDK-internal calls (telemetry,
|
|
197
|
+
* catalog, triggers) must use this to avoid being recorded as trace events.
|
|
198
|
+
*/
|
|
199
|
+
export function getOriginalFetch(): typeof globalThis.fetch {
|
|
200
|
+
return originalFetch ?? globalThis.fetch
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Synthesize a Response from a frozen HTTP event */
|
|
204
|
+
function synthesizeFrozenResponse(frozen: import('../capture/event.js').WorkflowEvent): Response {
|
|
205
|
+
const frozenInput = frozen.input as Record<string, unknown> | undefined
|
|
206
|
+
const responseMeta = (frozenInput?.__elasticdashResponse ?? {}) as Record<string, unknown>
|
|
207
|
+
const status = typeof responseMeta.status === 'number' ? responseMeta.status : 200
|
|
208
|
+
const statusText = typeof responseMeta.statusText === 'string' ? responseMeta.statusText : ''
|
|
209
|
+
const headers = pickReplayResponseHeaders(
|
|
210
|
+
responseMeta.headers && typeof responseMeta.headers === 'object'
|
|
211
|
+
? (responseMeta.headers as Record<string, unknown>)
|
|
212
|
+
: undefined,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
if (frozen.streamed === true) {
|
|
216
|
+
const raw = typeof frozen.streamRaw === 'string' ? frozen.streamRaw : ''
|
|
217
|
+
return new Response(reconstructStream(raw), { status, statusText, headers })
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const body = frozen.output != null ? JSON.stringify(frozen.output) : null
|
|
221
|
+
return new Response(body, { status, statusText, headers })
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Execute a live fetch and record the event, returning both the Response and the recorded event */
|
|
225
|
+
async function executeLiveAndRecord(
|
|
226
|
+
originalFetchFn: typeof globalThis.fetch,
|
|
227
|
+
input: string | URL | Request,
|
|
228
|
+
init: RequestInit | undefined,
|
|
229
|
+
id: number,
|
|
230
|
+
url: string,
|
|
231
|
+
method: string,
|
|
232
|
+
rawHeaders: RequestInit['headers'] | undefined,
|
|
233
|
+
rawBody: RequestInit['body'] | null | undefined,
|
|
234
|
+
): Promise<{ response: Response; event: import('../capture/event.js').WorkflowEvent }> {
|
|
235
|
+
const query = parseQuery(url)
|
|
236
|
+
const body = parseBody(rawBody as RequestInit['body'] | null | undefined)
|
|
237
|
+
const headers = normalizeHeaders(rawHeaders)
|
|
238
|
+
|
|
239
|
+
const start = rawDateNow()
|
|
240
|
+
const res = await originalFetchFn(input, init)
|
|
241
|
+
|
|
242
|
+
const responseHeadersObj: Record<string, string> = {}
|
|
243
|
+
res.headers.forEach((v, k) => { responseHeadersObj[k] = v })
|
|
244
|
+
|
|
245
|
+
const elasticdashResponse = { status: res.status, statusText: res.statusText, headers: responseHeadersObj, url: res.url }
|
|
246
|
+
const baseInput = {
|
|
247
|
+
url, method,
|
|
248
|
+
...(query ? { query } : {}),
|
|
249
|
+
...(body !== undefined ? { body } : {}),
|
|
250
|
+
...(headers && Object.keys(headers).length > 0 ? { headers } : {}),
|
|
251
|
+
__elasticdashResponse: elasticdashResponse,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (isStreamingContentType(res.headers) && res.body) {
|
|
255
|
+
// Streaming responses are not captured — the individual tool/AI calls
|
|
256
|
+
// inside the handler are already recorded separately. Pass through
|
|
257
|
+
// without buffering to avoid holding large streams in memory.
|
|
258
|
+
const event: import('../capture/event.js').WorkflowEvent = {
|
|
259
|
+
id, type: 'http', name: 'fetch', input: baseInput,
|
|
260
|
+
output: null, streamed: true, timestamp: start, durationMs: rawDateNow() - start,
|
|
261
|
+
}
|
|
262
|
+
return { response: res, event }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
let output: unknown = null
|
|
266
|
+
try { output = await res.clone().json() } catch { /* not JSON */ }
|
|
267
|
+
|
|
268
|
+
const event: import('../capture/event.js').WorkflowEvent = {
|
|
269
|
+
id, type: 'http', name: 'fetch', input: baseInput, output, timestamp: start, durationMs: rawDateNow() - start,
|
|
270
|
+
}
|
|
271
|
+
return { response: res, event }
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function interceptFetch(): void {
|
|
275
|
+
if (originalFetch) return // already installed
|
|
276
|
+
originalFetch = globalThis.fetch
|
|
277
|
+
|
|
278
|
+
globalThis.fetch = async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {
|
|
279
|
+
await tryAutoInitHttpContext()
|
|
280
|
+
const ctx = getCaptureContext()
|
|
281
|
+
const httpCtx = getHttpRunContext()
|
|
282
|
+
const obsCtx = getObservabilityContext()
|
|
283
|
+
|
|
284
|
+
if (!ctx && !httpCtx && !obsCtx) return originalFetch!(input, init)
|
|
285
|
+
|
|
286
|
+
const url =
|
|
287
|
+
typeof input === 'string'
|
|
288
|
+
? input
|
|
289
|
+
: input instanceof URL
|
|
290
|
+
? input.href
|
|
291
|
+
: (input as Request).url
|
|
292
|
+
const method = (init?.method ?? (input instanceof Request ? input.method : 'GET')).toUpperCase()
|
|
293
|
+
const rawHeaders = init?.headers ?? (input instanceof Request ? input.headers : undefined)
|
|
294
|
+
const rawBody = init?.body ?? (input instanceof Request ? input.body : undefined)
|
|
295
|
+
|
|
296
|
+
// Let ai-interceptor handle AI provider URLs — it assigns its own event IDs
|
|
297
|
+
if (isAIProviderUrl(url)) {
|
|
298
|
+
return originalFetch!(input, init)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
// --- Observability-only mode: record and push, no mocks/replay ---
|
|
303
|
+
if (!ctx && !httpCtx && obsCtx) {
|
|
304
|
+
const id = obsCtx.nextId()
|
|
305
|
+
const { response, event } = await executeLiveAndRecord(originalFetch!, input, init, id, url, method, rawHeaders, rawBody as RequestInit['body'] | null | undefined)
|
|
306
|
+
const eventPromise = (response as unknown as Record<string, unknown>).__eventPromise as Promise<import('../capture/event.js').WorkflowEvent> | undefined
|
|
307
|
+
if (eventPromise) {
|
|
308
|
+
eventPromise.then((ev) => pushTelemetryEvent(ev)).catch(() => {})
|
|
309
|
+
} else if (event) {
|
|
310
|
+
pushTelemetryEvent(event)
|
|
311
|
+
}
|
|
312
|
+
return response
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// --- HTTP mode (no capture context) — replay frozen events or execute live ---
|
|
316
|
+
if (!ctx && httpCtx) {
|
|
317
|
+
const id = httpCtx.nextId()
|
|
318
|
+
|
|
319
|
+
// Replay frozen step
|
|
320
|
+
const frozen = getHttpFrozenEvent(id)
|
|
321
|
+
if (frozen && frozen.type === 'http') {
|
|
322
|
+
pushTelemetryEvent(frozen)
|
|
323
|
+
return synthesizeFrozenResponse(frozen)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Not frozen → execute live, push telemetry
|
|
327
|
+
const { response, event } = await executeLiveAndRecord(originalFetch!, input, init, id, url, method, rawHeaders, rawBody as RequestInit['body'] | null | undefined)
|
|
328
|
+
const eventPromise = (response as unknown as Record<string, unknown>).__eventPromise as Promise<import('../capture/event.js').WorkflowEvent> | undefined
|
|
329
|
+
if (eventPromise) {
|
|
330
|
+
eventPromise.then((ev) => pushTelemetryEvent(ev)).catch(() => {})
|
|
331
|
+
} else if (event) {
|
|
332
|
+
pushTelemetryEvent(event)
|
|
333
|
+
}
|
|
334
|
+
return response
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// --- Capture mode (existing behaviour, enhanced with telemetry + TraceHandle) ---
|
|
338
|
+
const trace = getCurrentTrace()
|
|
339
|
+
const { recorder, replay } = ctx!
|
|
340
|
+
const id = recorder.nextId()
|
|
341
|
+
|
|
342
|
+
if (replay.shouldReplay(id)) {
|
|
343
|
+
const historicalEvent = replay.getRecordedEvent(id)
|
|
344
|
+
const historicalInput = historicalEvent?.input as Record<string, unknown> | undefined
|
|
345
|
+
const historicalMethod = typeof historicalInput?.method === 'string' ? historicalInput.method.toUpperCase() : 'GET'
|
|
346
|
+
const historicalUrl = typeof historicalInput?.url === 'string' ? historicalInput.url : undefined
|
|
347
|
+
const isReplayMatch = !!historicalEvent
|
|
348
|
+
&& historicalEvent.type === 'http'
|
|
349
|
+
&& historicalEvent.name === 'fetch'
|
|
350
|
+
&& historicalMethod === method
|
|
351
|
+
&& historicalUrl === url
|
|
352
|
+
|
|
353
|
+
if (isReplayMatch && historicalEvent) {
|
|
354
|
+
recorder.record(historicalEvent)
|
|
355
|
+
if (httpCtx) pushTelemetryEvent(historicalEvent)
|
|
356
|
+
if (trace && typeof trace.recordToolCall === 'function') {
|
|
357
|
+
trace.recordToolCall({ name: 'fetch', args: { url, method }, result: historicalEvent.output, workflowEventId: id })
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return synthesizeFrozenResponse(historicalEvent)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const { response, event } = await executeLiveAndRecord(originalFetch!, input, init, id, url, method, rawHeaders, rawBody as RequestInit['body'] | null | undefined)
|
|
365
|
+
const eventPromise = (response as unknown as Record<string, unknown>).__eventPromise as Promise<import('../capture/event.js').WorkflowEvent> | undefined
|
|
366
|
+
if (eventPromise) {
|
|
367
|
+
recorder.trackAsync(
|
|
368
|
+
eventPromise.then((ev) => {
|
|
369
|
+
recorder.record(ev)
|
|
370
|
+
if (httpCtx) pushTelemetryEvent(ev)
|
|
371
|
+
if (trace && typeof trace.recordToolCall === 'function') {
|
|
372
|
+
trace.recordToolCall({ name: 'fetch', args: { url, method }, result: ev.output, workflowEventId: id })
|
|
373
|
+
}
|
|
374
|
+
}).catch(() => {})
|
|
375
|
+
)
|
|
376
|
+
} else if (event) {
|
|
377
|
+
recorder.record(event)
|
|
378
|
+
if (httpCtx) pushTelemetryEvent(event)
|
|
379
|
+
if (trace && typeof trace.recordToolCall === 'function') {
|
|
380
|
+
trace.recordToolCall({ name: 'fetch', args: { url, method }, result: event.output, workflowEventId: id })
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return response
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function restoreFetch(): void {
|
|
389
|
+
if (originalFetch) {
|
|
390
|
+
globalThis.fetch = originalFetch
|
|
391
|
+
originalFetch = undefined
|
|
392
|
+
}
|
|
393
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { getCaptureContext } from '../capture/recorder.js'
|
|
2
|
+
|
|
3
|
+
let originalRandom: (() => number) | undefined
|
|
4
|
+
let originalDateNow: (() => number) | undefined
|
|
5
|
+
|
|
6
|
+
/** Call the real Date.now(), bypassing any interception. Safe to call from inside interceptors. */
|
|
7
|
+
export function rawDateNow(): number {
|
|
8
|
+
return originalDateNow ? originalDateNow() : Date.now()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function interceptRandom(): void {
|
|
12
|
+
if (originalRandom) return // already installed
|
|
13
|
+
originalRandom = Math.random
|
|
14
|
+
|
|
15
|
+
Math.random = (): number => {
|
|
16
|
+
const ctx = getCaptureContext()
|
|
17
|
+
if (!ctx) return originalRandom!()
|
|
18
|
+
|
|
19
|
+
const { recorder, replay } = ctx
|
|
20
|
+
const n = recorder.nextSideEffectId()
|
|
21
|
+
|
|
22
|
+
if (replay.shouldReplaySideEffectOfType(n, 'Math.random')) {
|
|
23
|
+
return replay.getSideEffectResultOfType(n, 'Math.random') as number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const value = originalRandom!()
|
|
27
|
+
recorder.record({
|
|
28
|
+
id: n,
|
|
29
|
+
type: 'side_effect',
|
|
30
|
+
name: 'Math.random',
|
|
31
|
+
input: null,
|
|
32
|
+
output: value,
|
|
33
|
+
timestamp: rawDateNow(),
|
|
34
|
+
durationMs: 0,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
return value
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function restoreRandom(): void {
|
|
42
|
+
if (originalRandom) {
|
|
43
|
+
Math.random = originalRandom
|
|
44
|
+
originalRandom = undefined
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function interceptDateNow(): void {
|
|
49
|
+
if (originalDateNow) return // already installed
|
|
50
|
+
originalDateNow = Date.now
|
|
51
|
+
|
|
52
|
+
Date.now = (): number => {
|
|
53
|
+
const ctx = getCaptureContext()
|
|
54
|
+
if (!ctx) return originalDateNow!()
|
|
55
|
+
|
|
56
|
+
const { recorder, replay } = ctx
|
|
57
|
+
const n = recorder.nextSideEffectId()
|
|
58
|
+
|
|
59
|
+
if (replay.shouldReplaySideEffectOfType(n, 'Date.now')) {
|
|
60
|
+
return replay.getSideEffectResultOfType(n, 'Date.now') as number
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const value = originalDateNow!()
|
|
64
|
+
recorder.record({
|
|
65
|
+
id: n,
|
|
66
|
+
type: 'side_effect',
|
|
67
|
+
name: 'Date.now',
|
|
68
|
+
input: null,
|
|
69
|
+
output: value,
|
|
70
|
+
timestamp: value,
|
|
71
|
+
durationMs: 0,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
return value
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function restoreDateNow(): void {
|
|
79
|
+
if (originalDateNow) {
|
|
80
|
+
Date.now = originalDateNow
|
|
81
|
+
originalDateNow = undefined
|
|
82
|
+
}
|
|
83
|
+
}
|