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,621 @@
|
|
|
1
|
+
# Quick Start Guide
|
|
2
|
+
|
|
3
|
+
Get ElasticDash running in 10 minutes and start debugging your AI workflows.
|
|
4
|
+
|
|
5
|
+
## Implementation Checklist
|
|
6
|
+
|
|
7
|
+
Use this checklist to ensure your ElasticDash setup and workflow instrumentation are complete. Each item links to the relevant section below:
|
|
8
|
+
|
|
9
|
+
- [ ] [Install the SDK](#install-the-sdk)
|
|
10
|
+
- [ ] [Configure Environment Variables](#configure-environment-variables)
|
|
11
|
+
- [ ] [Create `ed_workflows.ts`](#create-ed_workflowsts)
|
|
12
|
+
- [ ] [Handle Streaming Workflows (if needed)](#streaming-workflows)
|
|
13
|
+
- [ ] [Create `ed_tools.ts`](#create-ed_toolsts)
|
|
14
|
+
- [ ] [Update Workflow Tool Calls](#update-workflow-tool-calls)
|
|
15
|
+
- [ ] [Add Dashboard Script to `package.json`](#add-dashboard-script-to-packagejson)
|
|
16
|
+
- [ ] [Check ESM/CJS Module System](#how-to-check-if-youre-using-esm-or-cjs)
|
|
17
|
+
- [ ] [Write and Run Example Test](#end-to-end-example)
|
|
18
|
+
- [ ] [Open the Dashboard](#open-the-dashboard)
|
|
19
|
+
- [ ] [(Optional) Get Trace Data from Langfuse](#get-trace-data-from-langfuse)
|
|
20
|
+
- [ ] [(Optional) Debug with the Dashboard](#debug-with-the-dashboard)
|
|
21
|
+
- [ ] [(Optional) Capture Streaming Flows](#capture-streaming-flows)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Table of Contents
|
|
26
|
+
|
|
27
|
+
- [Quick Start Guide](#quick-start-guide)
|
|
28
|
+
- [Implementation Checklist](#implementation-checklist)
|
|
29
|
+
- [Table of Contents](#table-of-contents)
|
|
30
|
+
- [Section 1: Installation \& Configuration](#section-1-installation--configuration)
|
|
31
|
+
- [Install the SDK](#install-the-sdk)
|
|
32
|
+
- [Configure Environment Variables](#configure-environment-variables)
|
|
33
|
+
- [Create `ed_workflows.ts`](#create-ed_workflowsts)
|
|
34
|
+
- [Streaming Workflows](#streaming-workflows)
|
|
35
|
+
- [Create `ed_tools.ts`](#create-ed_toolsts)
|
|
36
|
+
- [Update Workflow Tool Calls](#update-workflow-tool-calls)
|
|
37
|
+
- [Add Dashboard Script to `package.json`](#add-dashboard-script-to-packagejson)
|
|
38
|
+
- [Section 2: Usage \& Example](#section-2-usage--example)
|
|
39
|
+
- [End-to-End Example](#end-to-end-example)
|
|
40
|
+
- [Open the Dashboard](#open-the-dashboard)
|
|
41
|
+
- [Get Trace Data from Langfuse](#get-trace-data-from-langfuse)
|
|
42
|
+
- [Debug with the Dashboard](#debug-with-the-dashboard)
|
|
43
|
+
- [Capture Streaming Flows](#capture-streaming-flows)
|
|
44
|
+
- [Next Steps](#next-steps)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Section 1: Installation & Configuration
|
|
49
|
+
|
|
50
|
+
### Install the SDK
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install elasticdash-sdk
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Add `.temp/` to `.gitignore` — ElasticDash writes runtime artifacts there:
|
|
57
|
+
|
|
58
|
+
```gitignore
|
|
59
|
+
.temp/
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Configure Environment Variables
|
|
63
|
+
|
|
64
|
+
Set API keys for the LLM providers used in your workflows. Only set keys for providers you actually use.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# OpenAI
|
|
68
|
+
export OPENAI_API_KEY="sk-..."
|
|
69
|
+
|
|
70
|
+
# Claude (Anthropic)
|
|
71
|
+
export ANTHROPIC_API_KEY="sk-ant-..."
|
|
72
|
+
|
|
73
|
+
# Gemini (Google)
|
|
74
|
+
export GEMINI_API_KEY="AIzaSy..."
|
|
75
|
+
# or use GOOGLE_API_KEY instead
|
|
76
|
+
|
|
77
|
+
# Grok (xAI)
|
|
78
|
+
export GROK_API_KEY="xai-..."
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Supported provider env var names are documented in `docs/matchers.md`.
|
|
82
|
+
|
|
83
|
+
### Create `ed_workflows.ts`
|
|
84
|
+
|
|
85
|
+
Create `ed_workflows.ts` (or `ed_workflows.js`) in your project root. Export all workflow functions you want to debug:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
// ed_workflows.ts
|
|
89
|
+
export { checkoutFlow, refundFlow } from './src/workflows'
|
|
90
|
+
export { processOrderFlow } from './src/flows/orders'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Requirements:** Each exported workflow must be a directly callable async function with JSON-serializable input/output.
|
|
94
|
+
|
|
95
|
+
Valid examples:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
export async function checkoutFlow(input: { orderId: string }) {
|
|
99
|
+
return { ok: true, orderId: input.orderId }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function batchFlow(input: Array<{ id: string }>) {
|
|
103
|
+
return input.map((item) => ({ id: item.id, processed: true }))
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Not compatible (Next.js route handlers are not directly callable):
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
// ❌ Route handler — wrap in a plain function instead
|
|
111
|
+
export async function POST(req: NextRequest): Promise<NextResponse> {
|
|
112
|
+
return NextResponse.json({ ok: true })
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
If you use Next.js route handlers, export a separate plain function for workflow replay and call it from your route handler.
|
|
117
|
+
|
|
118
|
+
#### Streaming Workflows
|
|
119
|
+
|
|
120
|
+
If your workflow returns a streaming response (e.g., a Vercel AI SDK data-stream from a Next.js route), you need a wrapper that:
|
|
121
|
+
|
|
122
|
+
1. Calls the route handler directly (no HTTP server required)
|
|
123
|
+
2. Parses the stream into a structured result using `readVercelAIStream`
|
|
124
|
+
3. Records the result with `recordToolCall`
|
|
125
|
+
|
|
126
|
+
Create a separate handler file (e.g., `app/api/chat-stream/chatStreamHandler.ts`) that is **only imported by `ed_workflows.ts`**, never by `route.ts` or any Next.js-bundled file:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
// app/api/chat-stream/chatStreamHandler.ts
|
|
130
|
+
import { NextRequest } from 'next/server'
|
|
131
|
+
import { readVercelAIStream, recordToolCall } from 'elasticdash-sdk'
|
|
132
|
+
import type { VercelAIStreamResult } from 'elasticdash-sdk'
|
|
133
|
+
import { POST } from './route'
|
|
134
|
+
|
|
135
|
+
export interface ChatStreamInput {
|
|
136
|
+
messages: Array<{ role: string; content: string }>
|
|
137
|
+
sessionId?: string
|
|
138
|
+
userToken?: string
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export type ChatStreamResult = VercelAIStreamResult
|
|
142
|
+
|
|
143
|
+
export async function chatStreamHandler(args: ChatStreamInput): Promise<ChatStreamResult> {
|
|
144
|
+
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
|
|
145
|
+
if (args.userToken) headers['Authorization'] = `Bearer ${args.userToken}`
|
|
146
|
+
|
|
147
|
+
const req = new NextRequest('http://localhost/api/chat-stream', {
|
|
148
|
+
method: 'POST',
|
|
149
|
+
headers,
|
|
150
|
+
body: JSON.stringify({ messages: args.messages, ...(args.sessionId ? { sessionId: args.sessionId } : {}) }),
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
const response = await POST(req)
|
|
154
|
+
|
|
155
|
+
// If the response is not a data-stream, surface the error
|
|
156
|
+
if (response.headers.get('x-vercel-ai-data-stream') !== 'v1') {
|
|
157
|
+
let errorMessage = `HTTP ${response.status}`
|
|
158
|
+
try {
|
|
159
|
+
const json = await response.clone().json() as Record<string, unknown>
|
|
160
|
+
errorMessage = typeof json.error === 'string' ? json.error : JSON.stringify(json)
|
|
161
|
+
} catch {
|
|
162
|
+
errorMessage = await response.text().catch(() => errorMessage)
|
|
163
|
+
}
|
|
164
|
+
return { message: errorMessage, type: 'error', error: errorMessage }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const result = await readVercelAIStream(response)
|
|
168
|
+
recordToolCall('chatStream', args, result)
|
|
169
|
+
return result
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Then export it from `ed_workflows.ts`:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
// ed_workflows.ts
|
|
177
|
+
export { chatStreamHandler } from './app/api/chat-stream/chatStreamHandler'
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Key points:**
|
|
181
|
+
|
|
182
|
+
- `readVercelAIStream` parses the Vercel AI SDK `text/plain` + `x-vercel-ai-data-stream: v1` wire protocol into a structured `VercelAIStreamResult`
|
|
183
|
+
- `recordToolCall` is called manually (via the `ed_tools` file) so that inner tool recordings from the pipeline are not suppressed
|
|
184
|
+
- The handler file imports `elasticdash-sdk` directly — keep it isolated from Next.js bundling by only importing it from `ed_workflows.ts`
|
|
185
|
+
|
|
186
|
+
### Create `ed_tools.ts`
|
|
187
|
+
|
|
188
|
+
Create `ed_tools.ts` (or `ed_tools.js`) in your project root. Each tool wrapper needs two helpers and a standard pattern:
|
|
189
|
+
|
|
190
|
+
1. **`resolveMock()`** — checks whether the dashboard has injected mock data for this tool call (supports `mock-all`, `mock-specific`, and `live` modes). Zero-cost no-op outside the worker subprocess.
|
|
191
|
+
2. **`safeRecordToolCall()`** — records the tool call via `elasticdash-sdk`, but only when running inside the worker subprocess. The dynamic import is guarded so it never blocks your production service.
|
|
192
|
+
|
|
193
|
+
Paste these helpers at the top of `ed_tools.ts`, then wrap each tool using the pattern shown below:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
// ed_tools.ts
|
|
197
|
+
import { chargeCard as chargeCardImpl } from './src/services/payments'
|
|
198
|
+
import { getOrderDetails as getOrderDetailsImpl } from './src/services/orders'
|
|
199
|
+
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// Mock resolution
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Checks whether the current call to `toolName` should be short-circuited
|
|
206
|
+
* with mock data. Reads globals written by the elasticdash worker subprocess
|
|
207
|
+
* before the workflow starts.
|
|
208
|
+
*
|
|
209
|
+
* Zero-cost no-op outside the worker: returns { mocked: false } immediately
|
|
210
|
+
* when the globals are absent.
|
|
211
|
+
*/
|
|
212
|
+
function resolveMock(toolName: string): { mocked: true; result: unknown } | { mocked: false } {
|
|
213
|
+
const g = globalThis as any
|
|
214
|
+
const mocks = g.__ELASTICDASH_TOOL_MOCKS__
|
|
215
|
+
if (!mocks) return { mocked: false }
|
|
216
|
+
|
|
217
|
+
const entry = mocks[toolName]
|
|
218
|
+
if (!entry || entry.mode === 'live') return { mocked: false }
|
|
219
|
+
|
|
220
|
+
if (!g.__ELASTICDASH_TOOL_CALL_COUNTERS__) g.__ELASTICDASH_TOOL_CALL_COUNTERS__ = {}
|
|
221
|
+
const counters = g.__ELASTICDASH_TOOL_CALL_COUNTERS__
|
|
222
|
+
counters[toolName] = (counters[toolName] ?? 0) + 1
|
|
223
|
+
const callNumber = counters[toolName]
|
|
224
|
+
|
|
225
|
+
if (entry.mode === 'mock-all') {
|
|
226
|
+
const data = entry.mockData ?? {}
|
|
227
|
+
const result = data[callNumber] !== undefined ? data[callNumber] : data[0]
|
|
228
|
+
return { mocked: true, result }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (entry.mode === 'mock-specific') {
|
|
232
|
+
const indices = entry.callIndices ?? []
|
|
233
|
+
if (indices.includes(callNumber)) {
|
|
234
|
+
return { mocked: true, result: (entry.mockData ?? {})[callNumber] }
|
|
235
|
+
}
|
|
236
|
+
return { mocked: false }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return { mocked: false }
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
// Recording
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Records a tool call via elasticdash-sdk when running inside the worker
|
|
248
|
+
* subprocess. Silently skips in all other environments.
|
|
249
|
+
*/
|
|
250
|
+
async function safeRecordToolCall(tool: string, input: any, result: any) {
|
|
251
|
+
if (!(globalThis as any).__ELASTICDASH_WORKER__) return
|
|
252
|
+
try {
|
|
253
|
+
const { recordToolCall } = await import('elasticdash-sdk')
|
|
254
|
+
recordToolCall(tool, input, result)
|
|
255
|
+
} catch (err: any) {
|
|
256
|
+
if (err?.code !== 'MODULE_NOT_FOUND') {
|
|
257
|
+
console.error('Logging Error in Tool:', err)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
// Tools
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
export const chargeCard = async (input: any) => {
|
|
267
|
+
const mock = resolveMock('chargeCard')
|
|
268
|
+
if (mock.mocked) {
|
|
269
|
+
await safeRecordToolCall('chargeCard', input, mock.result)
|
|
270
|
+
return mock.result
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return await chargeCardImpl(input)
|
|
274
|
+
.then(async (res: any) => {
|
|
275
|
+
await safeRecordToolCall('chargeCard', input, res)
|
|
276
|
+
return res
|
|
277
|
+
})
|
|
278
|
+
.catch(async (err: any) => {
|
|
279
|
+
await safeRecordToolCall('chargeCard', input, err)
|
|
280
|
+
throw err
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export const getOrderDetails = async (input: any) => {
|
|
285
|
+
const mock = resolveMock('getOrderDetails')
|
|
286
|
+
if (mock.mocked) {
|
|
287
|
+
await safeRecordToolCall('getOrderDetails', input, mock.result)
|
|
288
|
+
return mock.result
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return await getOrderDetailsImpl(input)
|
|
292
|
+
.then(async (res: any) => {
|
|
293
|
+
await safeRecordToolCall('getOrderDetails', input, res)
|
|
294
|
+
return res
|
|
295
|
+
})
|
|
296
|
+
.catch(async (err: any) => {
|
|
297
|
+
await safeRecordToolCall('getOrderDetails', input, err)
|
|
298
|
+
throw err
|
|
299
|
+
})
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Pattern for each tool:**
|
|
304
|
+
|
|
305
|
+
1. Call `resolveMock('toolName')` — if mocked, record and return the mock result immediately
|
|
306
|
+
2. Otherwise run the real implementation with `.then()` / `.catch()`, recording in both paths
|
|
307
|
+
3. Always use `safeRecordToolCall` (never import `elasticdash-sdk` directly) to avoid blocking production
|
|
308
|
+
|
|
309
|
+
**Important:** The name string passed to `resolveMock()` and `safeRecordToolCall()` must match the exported function name exactly (e.g., `resolveMock('chargeCard')` for `export const chargeCard`). The dashboard uses this name to identify tools for mocking and trace display.
|
|
310
|
+
|
|
311
|
+
**Next.js projects:** Add `elasticdash-sdk` to `serverExternalPackages` in your `next.config.ts` (or `next.config.js`):
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
// next.config.ts
|
|
315
|
+
const nextConfig = {
|
|
316
|
+
serverExternalPackages: ['elasticdash-sdk'],
|
|
317
|
+
}
|
|
318
|
+
export default nextConfig
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
This tells Next.js to skip bundling the package into its server-side webpack build and instead load it via Node.js `require()` at runtime. Without this:
|
|
322
|
+
|
|
323
|
+
- The `dynamic import()` in `safeRecordToolCall` would cause webpack to try resolving `elasticdash-sdk` at build time — even though it's an optional dev/test dependency that may not be installed in production
|
|
324
|
+
- Node.js-specific APIs used by the test framework (filesystem, child processes, etc.) are incompatible with webpack bundling
|
|
325
|
+
- By keeping it external, the `catch` block in `safeRecordToolCall` handles `MODULE_NOT_FOUND` silently at runtime instead of failing the build
|
|
326
|
+
|
|
327
|
+
### Update Workflow Tool Calls
|
|
328
|
+
|
|
329
|
+
Change your workflows to import tools from `ed_tools.ts` instead of the original source files:
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
// ❌ Before
|
|
333
|
+
import { chargeCard } from './services/payments'
|
|
334
|
+
import { getOrderDetails } from './services/orders'
|
|
335
|
+
|
|
336
|
+
export const checkoutFlow = async (orderId: string) => {
|
|
337
|
+
const order = await getOrderDetails({ orderId })
|
|
338
|
+
const payment = await chargeCard({ amount: order.total })
|
|
339
|
+
return { orderId, paymentId: payment.id }
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ✅ After
|
|
343
|
+
import { chargeCard, getOrderDetails } from './ed_tools'
|
|
344
|
+
|
|
345
|
+
export const checkoutFlow = async (orderId: string) => {
|
|
346
|
+
const order = await getOrderDetails({ orderId })
|
|
347
|
+
const payment = await chargeCard({ amount: order.total })
|
|
348
|
+
return { orderId, paymentId: payment.id }
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Add Dashboard Script to `package.json`
|
|
353
|
+
|
|
354
|
+
**Standard setup (most projects):**
|
|
355
|
+
|
|
356
|
+
```json
|
|
357
|
+
{
|
|
358
|
+
"scripts": {
|
|
359
|
+
"dashboard:ai": "elasticdash dashboard"
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**Advanced setup (Next.js, TS path aliases, mixed ESM/CJS):**
|
|
365
|
+
|
|
366
|
+
If your project uses TypeScript path aliases (e.g., `@/lib/...`) or loads TypeScript at runtime with module complexity:
|
|
367
|
+
|
|
368
|
+
```json
|
|
369
|
+
{
|
|
370
|
+
"scripts": {
|
|
371
|
+
"dashboard:ai": "NODE_OPTIONS='--import tsx/esm --require tsx/cjs --require tsconfig-paths/register' elasticdash dashboard"
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
<details>
|
|
377
|
+
<summary>When do I need the advanced script?</summary>
|
|
378
|
+
|
|
379
|
+
**Why this works:**
|
|
380
|
+
|
|
381
|
+
- `tsx/esm` and `tsx/cjs` handle mixed ESM/CJS module loading at runtime
|
|
382
|
+
- `tsconfig-paths/register` resolves path aliases from your `tsconfig.json` (e.g., `@/lib` → `./src/lib`)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
**How to check if you're using ESM or CJS:**
|
|
386
|
+
|
|
387
|
+
There are two main indicators for your module system:
|
|
388
|
+
|
|
389
|
+
1. **`package.json`**
|
|
390
|
+
- `"type": "module"` → ESM (Node.js treats `.js` as ESM, uses `import`/`export`)
|
|
391
|
+
- `"type": "commonjs"` or no `type` field → CJS (Node.js treats `.js` as CommonJS, uses `require`/`module.exports`)
|
|
392
|
+
|
|
393
|
+
2. **`tsconfig.json`** (for TypeScript projects)
|
|
394
|
+
- `"module": "commonjs"` → TypeScript emits CommonJS (`require`/`module.exports`)
|
|
395
|
+
- `"module": "esnext"`, `"es2020"`, etc. → TypeScript emits ESM (`import`/`export`)
|
|
396
|
+
- `"module": "nodenext"` or `"node16"` → Ambiguous; TypeScript output matches the `package.json` `type` field per file
|
|
397
|
+
|
|
398
|
+
**Quick reference table:**
|
|
399
|
+
|
|
400
|
+
| `package.json` `type` | `tsconfig.json` `module` | Resulting Module System |
|
|
401
|
+
|----------------------------|--------------------------|------------------------|
|
|
402
|
+
| `"module"` | `esnext`/`es2020` | ESM |
|
|
403
|
+
| `"module"` | `nodenext`/`node16` | ESM |
|
|
404
|
+
| `"commonjs"` or missing | `commonjs` | CJS |
|
|
405
|
+
| `"commonjs"` or missing | `nodenext`/`node16` | CJS |
|
|
406
|
+
|
|
407
|
+
> **Tip:** If you see `"module": "commonjs"` in your `tsconfig.json`, your project is almost certainly CJS, even if `package.json` has no `type` field.
|
|
408
|
+
|
|
409
|
+
**You need the advanced script when:**
|
|
410
|
+
|
|
411
|
+
- **Any file in your project uses path aliases** like `@/services/payment` instead of relative imports — **this is the main reason**
|
|
412
|
+
- Your project mixes ESM and CJS modules in complex ways
|
|
413
|
+
- You see `Cannot find module '@/...'` or `ERR_UNKNOWN_FILE_EXTENSION` errors
|
|
414
|
+
|
|
415
|
+
**Important:** Path alias resolution is transitive. Even if `ed_workflows.ts` doesn't directly use `@/`, the advanced script is still needed if it imports files that do.
|
|
416
|
+
|
|
417
|
+
**You DON'T need it when:**
|
|
418
|
+
|
|
419
|
+
- Plain JavaScript projects
|
|
420
|
+
- Basic TypeScript projects with only relative imports (`./` or `../`)
|
|
421
|
+
- Pure ESM or pure CJS projects without path aliases
|
|
422
|
+
- Pre-compiled projects where dashboard loads `.js` files
|
|
423
|
+
|
|
424
|
+
</details>
|
|
425
|
+
|
|
426
|
+
That's it for setup. Your project should now have these files:
|
|
427
|
+
|
|
428
|
+
```
|
|
429
|
+
your-project/
|
|
430
|
+
ed_workflows.ts # workflow exports
|
|
431
|
+
ed_tools.ts # instrumented tool wrappers
|
|
432
|
+
package.json # dashboard script added
|
|
433
|
+
.gitignore # .temp/ added
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## Section 2: Usage & Example
|
|
439
|
+
|
|
440
|
+
### End-to-End Example
|
|
441
|
+
|
|
442
|
+
Here is a complete example showing a workflow, its tools, and how they wire together.
|
|
443
|
+
|
|
444
|
+
**1. Tool implementations** (`src/services/orders.ts`):
|
|
445
|
+
|
|
446
|
+
```ts
|
|
447
|
+
export async function getOrderDetails(input: { orderId: string }) {
|
|
448
|
+
// Real DB/API call
|
|
449
|
+
return { orderId: input.orderId, total: 49.99, items: ['widget-a'] }
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export async function chargeCard(input: { amount: number }) {
|
|
453
|
+
// Real payment call
|
|
454
|
+
return { id: 'pay_abc123', amount: input.amount, status: 'succeeded' }
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
**2. Instrumented tools** (`ed_tools.ts`):
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
import { getOrderDetails as getOrderDetailsImpl } from './src/services/orders'
|
|
462
|
+
import { chargeCard as chargeCardImpl } from './src/services/orders'
|
|
463
|
+
|
|
464
|
+
// resolveMock() and safeRecordToolCall() helpers go here
|
|
465
|
+
// (see full definitions in Section 1 above)
|
|
466
|
+
|
|
467
|
+
export const getOrderDetails = async (input: any) => {
|
|
468
|
+
const mock = resolveMock('getOrderDetails')
|
|
469
|
+
if (mock.mocked) {
|
|
470
|
+
await safeRecordToolCall('getOrderDetails', input, mock.result)
|
|
471
|
+
return mock.result
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return await getOrderDetailsImpl(input)
|
|
475
|
+
.then(async (res: any) => {
|
|
476
|
+
await safeRecordToolCall('getOrderDetails', input, res)
|
|
477
|
+
return res
|
|
478
|
+
})
|
|
479
|
+
.catch(async (err: any) => {
|
|
480
|
+
await safeRecordToolCall('getOrderDetails', input, err)
|
|
481
|
+
throw err
|
|
482
|
+
})
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export const chargeCard = async (input: any) => {
|
|
486
|
+
const mock = resolveMock('chargeCard')
|
|
487
|
+
if (mock.mocked) {
|
|
488
|
+
await safeRecordToolCall('chargeCard', input, mock.result)
|
|
489
|
+
return mock.result
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return await chargeCardImpl(input)
|
|
493
|
+
.then(async (res: any) => {
|
|
494
|
+
await safeRecordToolCall('chargeCard', input, res)
|
|
495
|
+
return res
|
|
496
|
+
})
|
|
497
|
+
.catch(async (err: any) => {
|
|
498
|
+
await safeRecordToolCall('chargeCard', input, err)
|
|
499
|
+
throw err
|
|
500
|
+
})
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
**3. Workflow** (`ed_workflows.ts`):
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
import { getOrderDetails, chargeCard } from './ed_tools'
|
|
508
|
+
|
|
509
|
+
export async function checkoutFlow(input: { orderId: string }) {
|
|
510
|
+
const order = await getOrderDetails({ orderId: input.orderId })
|
|
511
|
+
// LLM calls via fetch() are intercepted automatically — no wrapping needed
|
|
512
|
+
const payment = await chargeCard({ amount: order.total })
|
|
513
|
+
return { orderId: order.orderId, paymentId: payment.id, status: payment.status }
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
**4. Test file** (`examples/checkout.ai.test.ts`):
|
|
518
|
+
|
|
519
|
+
```ts
|
|
520
|
+
import 'elasticdash-sdk/test-setup'
|
|
521
|
+
import { expect } from 'expect'
|
|
522
|
+
|
|
523
|
+
aiTest('checkout flow charges the correct amount', async (ctx) => {
|
|
524
|
+
// Workflow runs; LLM calls and tool calls are recorded into ctx.trace automatically
|
|
525
|
+
const result = await checkoutFlow({ orderId: 'order-42' })
|
|
526
|
+
|
|
527
|
+
// Assert an LLM step occurred
|
|
528
|
+
expect(ctx.trace).toHaveLLMStep({ model: 'gpt-4' })
|
|
529
|
+
|
|
530
|
+
// Assert the chargeCard tool was called
|
|
531
|
+
expect(ctx.trace).toCallTool('chargeCard')
|
|
532
|
+
|
|
533
|
+
// Assert output makes sense semantically
|
|
534
|
+
expect(ctx.trace).toMatchSemanticOutput('payment succeeded')
|
|
535
|
+
})
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Open the Dashboard
|
|
539
|
+
|
|
540
|
+
```bash
|
|
541
|
+
npm run dashboard
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Get Trace Data from Langfuse
|
|
545
|
+
|
|
546
|
+
When a workflow fails in production, fetch the trace to replay it locally.
|
|
547
|
+
|
|
548
|
+
**Note:** When fetching recent observations via the API, you may receive an empty array even though the observations are visible on the Langfuse dashboard. This is expected - the API data lags behind the dashboard. Wait a few minutes and retry before contacting Langfuse support.
|
|
549
|
+
|
|
550
|
+
**Using curl:**
|
|
551
|
+
|
|
552
|
+
```bash
|
|
553
|
+
export LANGFUSE_PUBLIC_KEY="your_public_key"
|
|
554
|
+
export LANGFUSE_SECRET_KEY="your_secret_key"
|
|
555
|
+
TRACE_ID="your_trace_id"
|
|
556
|
+
|
|
557
|
+
curl "https://cloud.langfuse.com/api/public/v2/observations?traceId=${TRACE_ID}&fields=time,io,basic,model" \
|
|
558
|
+
-H "Authorization: Basic $(echo -n "${LANGFUSE_PUBLIC_KEY}:${LANGFUSE_SECRET_KEY}" | base64)" \
|
|
559
|
+
> trace.json
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
**Using Postman:**
|
|
563
|
+
|
|
564
|
+
1. `GET` `https://cloud.langfuse.com/api/public/v2/observations`
|
|
565
|
+
2. Params: `traceId=<your_trace_id>`, `fields=time,io,basic,model`
|
|
566
|
+
3. Auth tab: Basic Auth with public key + secret key
|
|
567
|
+
4. Save the response as `trace.json`
|
|
568
|
+
|
|
569
|
+
### Debug with the Dashboard
|
|
570
|
+
|
|
571
|
+
Once the dashboard is open:
|
|
572
|
+
|
|
573
|
+
1. **Select the workflow** — find and click your workflow in the list
|
|
574
|
+
2. **Upload the trace** — upload `trace.json` and wait for observations to load
|
|
575
|
+
3. **Identify problematic steps** — review AI/tool observations and mark the step(s) that need fixes
|
|
576
|
+
4. **Iterate on fixes** — re-run selected step(s) from the dashboard, update your code, re-run again until outputs match expectations
|
|
577
|
+
5. **Validate end-to-end** — run `Validate with Live Data` to confirm the full workflow produces correct output
|
|
578
|
+
|
|
579
|
+
### Capture Streaming Flows
|
|
580
|
+
|
|
581
|
+
ElasticDash can capture and replay non-AI HTTP streaming responses (e.g., SSE/NDJSON endpoints) automatically when your workflow uses normal `fetch`.
|
|
582
|
+
|
|
583
|
+
Checklist for streaming workflows:
|
|
584
|
+
|
|
585
|
+
1. Keep stream requests on standard `fetch` calls so the HTTP interceptor can observe them
|
|
586
|
+
2. Ensure the upstream response uses a streaming content type:
|
|
587
|
+
- `text/event-stream`
|
|
588
|
+
- `application/x-ndjson`
|
|
589
|
+
- `application/stream+json`
|
|
590
|
+
- `application/jsonl`
|
|
591
|
+
3. Consume `Response.body` as a stream in your app logic
|
|
592
|
+
4. Run the workflow once live to record stream payloads
|
|
593
|
+
5. Re-run with replay to validate deterministic stream-content behavior
|
|
594
|
+
|
|
595
|
+
Minimal consumer example:
|
|
596
|
+
|
|
597
|
+
```ts
|
|
598
|
+
const response = await fetch('https://example.com/stream')
|
|
599
|
+
if (!response.body) throw new Error('Missing stream body')
|
|
600
|
+
|
|
601
|
+
const reader = response.body.getReader()
|
|
602
|
+
const decoder = new TextDecoder()
|
|
603
|
+
let raw = ''
|
|
604
|
+
|
|
605
|
+
for (;;) {
|
|
606
|
+
const { done, value } = await reader.read()
|
|
607
|
+
if (done) break
|
|
608
|
+
raw += decoder.decode(value, { stream: true })
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
raw += decoder.decode()
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**Replay caveat:** ElasticDash preserves payload text and response metadata (status/statusText/headers) but does not preserve original chunk timing or chunk boundaries.
|
|
615
|
+
|
|
616
|
+
### Next Steps
|
|
617
|
+
|
|
618
|
+
- `docs/tools.md` — advanced tool instrumentation patterns
|
|
619
|
+
- `docs/dashboard.md` — detailed dashboard and Langfuse API usage
|
|
620
|
+
- `docs/matchers.md` — matcher reference and provider env vars
|
|
621
|
+
- `docs/agents.md` — agent-specific replay features
|