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.
Files changed (349) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +775 -0
  3. package/dist/browser-ui.d.ts +43 -0
  4. package/dist/browser-ui.d.ts.map +1 -0
  5. package/dist/browser-ui.js +246 -0
  6. package/dist/browser-ui.js.map +1 -0
  7. package/dist/capture/event.d.ts +33 -0
  8. package/dist/capture/event.d.ts.map +1 -0
  9. package/dist/capture/event.js +2 -0
  10. package/dist/capture/event.js.map +1 -0
  11. package/dist/capture/index.d.ts +4 -0
  12. package/dist/capture/index.d.ts.map +1 -0
  13. package/dist/capture/index.js +4 -0
  14. package/dist/capture/index.js.map +1 -0
  15. package/dist/capture/recorder.d.ts +24 -0
  16. package/dist/capture/recorder.d.ts.map +1 -0
  17. package/dist/capture/recorder.js +46 -0
  18. package/dist/capture/recorder.js.map +1 -0
  19. package/dist/capture/replay.d.ts +20 -0
  20. package/dist/capture/replay.d.ts.map +1 -0
  21. package/dist/capture/replay.js +47 -0
  22. package/dist/capture/replay.js.map +1 -0
  23. package/dist/ci/api-client.d.ts +38 -0
  24. package/dist/ci/api-client.d.ts.map +1 -0
  25. package/dist/ci/api-client.js +96 -0
  26. package/dist/ci/api-client.js.map +1 -0
  27. package/dist/ci/benchmark.d.ts +33 -0
  28. package/dist/ci/benchmark.d.ts.map +1 -0
  29. package/dist/ci/benchmark.js +213 -0
  30. package/dist/ci/benchmark.js.map +1 -0
  31. package/dist/ci/ed-runner.d.ts +48 -0
  32. package/dist/ci/ed-runner.d.ts.map +1 -0
  33. package/dist/ci/ed-runner.js +260 -0
  34. package/dist/ci/ed-runner.js.map +1 -0
  35. package/dist/ci/executor.d.ts +13 -0
  36. package/dist/ci/executor.d.ts.map +1 -0
  37. package/dist/ci/executor.js +542 -0
  38. package/dist/ci/executor.js.map +1 -0
  39. package/dist/ci/git-info.d.ts +17 -0
  40. package/dist/ci/git-info.d.ts.map +1 -0
  41. package/dist/ci/git-info.js +102 -0
  42. package/dist/ci/git-info.js.map +1 -0
  43. package/dist/ci/index.d.ts +6 -0
  44. package/dist/ci/index.d.ts.map +1 -0
  45. package/dist/ci/index.js +4 -0
  46. package/dist/ci/index.js.map +1 -0
  47. package/dist/ci/measurement.d.ts +9 -0
  48. package/dist/ci/measurement.d.ts.map +1 -0
  49. package/dist/ci/measurement.js +15 -0
  50. package/dist/ci/measurement.js.map +1 -0
  51. package/dist/ci/replay.d.ts +31 -0
  52. package/dist/ci/replay.d.ts.map +1 -0
  53. package/dist/ci/replay.js +96 -0
  54. package/dist/ci/replay.js.map +1 -0
  55. package/dist/ci/reporters/default.d.ts +8 -0
  56. package/dist/ci/reporters/default.d.ts.map +1 -0
  57. package/dist/ci/reporters/default.js +46 -0
  58. package/dist/ci/reporters/default.js.map +1 -0
  59. package/dist/ci/reporters/index.d.ts +8 -0
  60. package/dist/ci/reporters/index.d.ts.map +1 -0
  61. package/dist/ci/reporters/index.js +14 -0
  62. package/dist/ci/reporters/index.js.map +1 -0
  63. package/dist/ci/reporters/json.d.ts +8 -0
  64. package/dist/ci/reporters/json.d.ts.map +1 -0
  65. package/dist/ci/reporters/json.js +14 -0
  66. package/dist/ci/reporters/json.js.map +1 -0
  67. package/dist/ci/reporters/junit.d.ts +8 -0
  68. package/dist/ci/reporters/junit.d.ts.map +1 -0
  69. package/dist/ci/reporters/junit.js +48 -0
  70. package/dist/ci/reporters/junit.js.map +1 -0
  71. package/dist/ci/runner.d.ts +3 -0
  72. package/dist/ci/runner.d.ts.map +1 -0
  73. package/dist/ci/runner.js +187 -0
  74. package/dist/ci/runner.js.map +1 -0
  75. package/dist/ci/test-discovery.d.ts +5 -0
  76. package/dist/ci/test-discovery.d.ts.map +1 -0
  77. package/dist/ci/test-discovery.js +11 -0
  78. package/dist/ci/test-discovery.js.map +1 -0
  79. package/dist/ci/test-loader.d.ts +19 -0
  80. package/dist/ci/test-loader.d.ts.map +1 -0
  81. package/dist/ci/test-loader.js +149 -0
  82. package/dist/ci/test-loader.js.map +1 -0
  83. package/dist/ci/test-registry.d.ts +42 -0
  84. package/dist/ci/test-registry.d.ts.map +1 -0
  85. package/dist/ci/test-registry.js +18 -0
  86. package/dist/ci/test-registry.js.map +1 -0
  87. package/dist/ci/trace-schema.d.ts +30 -0
  88. package/dist/ci/trace-schema.d.ts.map +1 -0
  89. package/dist/ci/trace-schema.js +66 -0
  90. package/dist/ci/trace-schema.js.map +1 -0
  91. package/dist/ci/trace-writer.d.ts +16 -0
  92. package/dist/ci/trace-writer.d.ts.map +1 -0
  93. package/dist/ci/trace-writer.js +108 -0
  94. package/dist/ci/trace-writer.js.map +1 -0
  95. package/dist/ci/types.d.ts +108 -0
  96. package/dist/ci/types.d.ts.map +1 -0
  97. package/dist/ci/types.js +3 -0
  98. package/dist/ci/types.js.map +1 -0
  99. package/dist/ci/upload-client.d.ts +74 -0
  100. package/dist/ci/upload-client.d.ts.map +1 -0
  101. package/dist/ci/upload-client.js +195 -0
  102. package/dist/ci/upload-client.js.map +1 -0
  103. package/dist/cli.d.ts +3 -0
  104. package/dist/cli.d.ts.map +1 -0
  105. package/dist/cli.js +716 -0
  106. package/dist/cli.js.map +1 -0
  107. package/dist/core/agent-state.d.ts +47 -0
  108. package/dist/core/agent-state.d.ts.map +1 -0
  109. package/dist/core/agent-state.js +137 -0
  110. package/dist/core/agent-state.js.map +1 -0
  111. package/dist/core/judge-utils.d.ts +22 -0
  112. package/dist/core/judge-utils.d.ts.map +1 -0
  113. package/dist/core/judge-utils.js +211 -0
  114. package/dist/core/judge-utils.js.map +1 -0
  115. package/dist/core/registry.d.ts +28 -0
  116. package/dist/core/registry.d.ts.map +1 -0
  117. package/dist/core/registry.js +52 -0
  118. package/dist/core/registry.js.map +1 -0
  119. package/dist/dashboard-server.d.ts +65 -0
  120. package/dist/dashboard-server.d.ts.map +1 -0
  121. package/dist/dashboard-server.js +3940 -0
  122. package/dist/dashboard-server.js.map +1 -0
  123. package/dist/execution/tool-runner.d.ts +26 -0
  124. package/dist/execution/tool-runner.d.ts.map +1 -0
  125. package/dist/execution/tool-runner.js +316 -0
  126. package/dist/execution/tool-runner.js.map +1 -0
  127. package/dist/html/dashboard.html +2218 -0
  128. package/dist/http.d.ts +14 -0
  129. package/dist/http.d.ts.map +1 -0
  130. package/dist/http.js +13 -0
  131. package/dist/http.js.map +1 -0
  132. package/dist/index.cjs +8102 -0
  133. package/dist/index.d.ts +61 -0
  134. package/dist/index.d.ts.map +1 -0
  135. package/dist/index.js +67 -0
  136. package/dist/index.js.map +1 -0
  137. package/dist/interceptors/ai-interceptor.d.ts +26 -0
  138. package/dist/interceptors/ai-interceptor.d.ts.map +1 -0
  139. package/dist/interceptors/ai-interceptor.js +756 -0
  140. package/dist/interceptors/ai-interceptor.js.map +1 -0
  141. package/dist/interceptors/db-auto.d.ts +8 -0
  142. package/dist/interceptors/db-auto.d.ts.map +1 -0
  143. package/dist/interceptors/db-auto.js +217 -0
  144. package/dist/interceptors/db-auto.js.map +1 -0
  145. package/dist/interceptors/db.d.ts +23 -0
  146. package/dist/interceptors/db.d.ts.map +1 -0
  147. package/dist/interceptors/db.js +137 -0
  148. package/dist/interceptors/db.js.map +1 -0
  149. package/dist/interceptors/http.d.ts +28 -0
  150. package/dist/interceptors/http.d.ts.map +1 -0
  151. package/dist/interceptors/http.js +356 -0
  152. package/dist/interceptors/http.js.map +1 -0
  153. package/dist/interceptors/side-effects.d.ts +7 -0
  154. package/dist/interceptors/side-effects.d.ts.map +1 -0
  155. package/dist/interceptors/side-effects.js +72 -0
  156. package/dist/interceptors/side-effects.js.map +1 -0
  157. package/dist/interceptors/telemetry-push.d.ts +142 -0
  158. package/dist/interceptors/telemetry-push.d.ts.map +1 -0
  159. package/dist/interceptors/telemetry-push.js +463 -0
  160. package/dist/interceptors/telemetry-push.js.map +1 -0
  161. package/dist/interceptors/tool.d.ts +2 -0
  162. package/dist/interceptors/tool.d.ts.map +1 -0
  163. package/dist/interceptors/tool.js +274 -0
  164. package/dist/interceptors/tool.js.map +1 -0
  165. package/dist/interceptors/workflow-ai.d.ts +5 -0
  166. package/dist/interceptors/workflow-ai.d.ts.map +1 -0
  167. package/dist/interceptors/workflow-ai.js +382 -0
  168. package/dist/interceptors/workflow-ai.js.map +1 -0
  169. package/dist/internals/conditional-recorder.d.ts +21 -0
  170. package/dist/internals/conditional-recorder.d.ts.map +1 -0
  171. package/dist/internals/conditional-recorder.js +54 -0
  172. package/dist/internals/conditional-recorder.js.map +1 -0
  173. package/dist/internals/mock-resolver.d.ts +146 -0
  174. package/dist/internals/mock-resolver.d.ts.map +1 -0
  175. package/dist/internals/mock-resolver.js +427 -0
  176. package/dist/internals/mock-resolver.js.map +1 -0
  177. package/dist/matchers/index.d.ts +96 -0
  178. package/dist/matchers/index.d.ts.map +1 -0
  179. package/dist/matchers/index.js +668 -0
  180. package/dist/matchers/index.js.map +1 -0
  181. package/dist/observability.d.ts +82 -0
  182. package/dist/observability.d.ts.map +1 -0
  183. package/dist/observability.js +471 -0
  184. package/dist/observability.js.map +1 -0
  185. package/dist/portal-executor.d.ts +30 -0
  186. package/dist/portal-executor.d.ts.map +1 -0
  187. package/dist/portal-executor.js +324 -0
  188. package/dist/portal-executor.js.map +1 -0
  189. package/dist/portal-server.d.ts +3 -0
  190. package/dist/portal-server.d.ts.map +1 -0
  191. package/dist/portal-server.js +279 -0
  192. package/dist/portal-server.js.map +1 -0
  193. package/dist/proxy/llm-capture.d.ts +14 -0
  194. package/dist/proxy/llm-capture.d.ts.map +1 -0
  195. package/dist/proxy/llm-capture.js +264 -0
  196. package/dist/proxy/llm-capture.js.map +1 -0
  197. package/dist/reporter.d.ts +3 -0
  198. package/dist/reporter.d.ts.map +1 -0
  199. package/dist/reporter.js +72 -0
  200. package/dist/reporter.js.map +1 -0
  201. package/dist/runWorkflowSubprocess.d.ts +14 -0
  202. package/dist/runWorkflowSubprocess.d.ts.map +1 -0
  203. package/dist/runWorkflowSubprocess.js +66 -0
  204. package/dist/runWorkflowSubprocess.js.map +1 -0
  205. package/dist/runner.d.ts +16 -0
  206. package/dist/runner.d.ts.map +1 -0
  207. package/dist/runner.js +138 -0
  208. package/dist/runner.js.map +1 -0
  209. package/dist/socket-connector.d.ts +22 -0
  210. package/dist/socket-connector.d.ts.map +1 -0
  211. package/dist/socket-connector.js +104 -0
  212. package/dist/socket-connector.js.map +1 -0
  213. package/dist/telemetry-batcher.d.ts +56 -0
  214. package/dist/telemetry-batcher.d.ts.map +1 -0
  215. package/dist/telemetry-batcher.js +143 -0
  216. package/dist/telemetry-batcher.js.map +1 -0
  217. package/dist/test-setup.d.ts +12 -0
  218. package/dist/test-setup.d.ts.map +1 -0
  219. package/dist/test-setup.js +13 -0
  220. package/dist/test-setup.js.map +1 -0
  221. package/dist/tool-registry.d.ts +31 -0
  222. package/dist/tool-registry.d.ts.map +1 -0
  223. package/dist/tool-registry.js +73 -0
  224. package/dist/tool-registry.js.map +1 -0
  225. package/dist/tool-runner-worker.d.ts +2 -0
  226. package/dist/tool-runner-worker.d.ts.map +1 -0
  227. package/dist/tool-runner-worker.js +215 -0
  228. package/dist/tool-runner-worker.js.map +1 -0
  229. package/dist/trace-adapter/context.d.ts +72 -0
  230. package/dist/trace-adapter/context.d.ts.map +1 -0
  231. package/dist/trace-adapter/context.js +80 -0
  232. package/dist/trace-adapter/context.js.map +1 -0
  233. package/dist/tracing.d.ts +2 -0
  234. package/dist/tracing.d.ts.map +1 -0
  235. package/dist/tracing.js +59 -0
  236. package/dist/tracing.js.map +1 -0
  237. package/dist/trigger-executor.d.ts +12 -0
  238. package/dist/trigger-executor.d.ts.map +1 -0
  239. package/dist/trigger-executor.js +130 -0
  240. package/dist/trigger-executor.js.map +1 -0
  241. package/dist/types/portal.d.ts +76 -0
  242. package/dist/types/portal.d.ts.map +1 -0
  243. package/dist/types/portal.js +2 -0
  244. package/dist/types/portal.js.map +1 -0
  245. package/dist/utils/debug.d.ts +3 -0
  246. package/dist/utils/debug.d.ts.map +1 -0
  247. package/dist/utils/debug.js +8 -0
  248. package/dist/utils/debug.js.map +1 -0
  249. package/dist/utils/license-error.d.ts +23 -0
  250. package/dist/utils/license-error.d.ts.map +1 -0
  251. package/dist/utils/license-error.js +42 -0
  252. package/dist/utils/license-error.js.map +1 -0
  253. package/dist/utils/redact.d.ts +7 -0
  254. package/dist/utils/redact.d.ts.map +1 -0
  255. package/dist/utils/redact.js +26 -0
  256. package/dist/utils/redact.js.map +1 -0
  257. package/dist/workflow-runner-worker.d.ts +2 -0
  258. package/dist/workflow-runner-worker.d.ts.map +1 -0
  259. package/dist/workflow-runner-worker.js +329 -0
  260. package/dist/workflow-runner-worker.js.map +1 -0
  261. package/dist/workflow-runner.d.ts +14 -0
  262. package/dist/workflow-runner.d.ts.map +1 -0
  263. package/dist/workflow-runner.js +34 -0
  264. package/dist/workflow-runner.js.map +1 -0
  265. package/docs/agent-coding-instructions.md +138 -0
  266. package/docs/agent-integration-guide.md +564 -0
  267. package/docs/agents.md +140 -0
  268. package/docs/dashboard.md +394 -0
  269. package/docs/deno.md +69 -0
  270. package/docs/instrumentation.md +424 -0
  271. package/docs/langfuse-trace-structure.md +145 -0
  272. package/docs/matchers.md +173 -0
  273. package/docs/observability_contract.md +192 -0
  274. package/docs/observability_mode.md +195 -0
  275. package/docs/quickstart.md +621 -0
  276. package/docs/security-compliance.md +566 -0
  277. package/docs/test-writing-guidelines.md +444 -0
  278. package/docs/tools.md +165 -0
  279. package/docs/workflow-modes.md +253 -0
  280. package/package.json +76 -0
  281. package/src/browser-ui.ts +281 -0
  282. package/src/capture/event.ts +30 -0
  283. package/src/capture/index.ts +3 -0
  284. package/src/capture/recorder.ts +62 -0
  285. package/src/capture/replay.ts +55 -0
  286. package/src/ci/api-client.ts +136 -0
  287. package/src/ci/benchmark.ts +257 -0
  288. package/src/ci/ed-runner.ts +351 -0
  289. package/src/ci/executor.ts +671 -0
  290. package/src/ci/git-info.ts +127 -0
  291. package/src/ci/index.ts +5 -0
  292. package/src/ci/measurement.ts +25 -0
  293. package/src/ci/replay.ts +127 -0
  294. package/src/ci/reporters/default.ts +50 -0
  295. package/src/ci/reporters/index.ts +21 -0
  296. package/src/ci/reporters/json.ts +18 -0
  297. package/src/ci/reporters/junit.ts +61 -0
  298. package/src/ci/runner.ts +208 -0
  299. package/src/ci/test-discovery.ts +16 -0
  300. package/src/ci/test-loader.ts +187 -0
  301. package/src/ci/test-registry.ts +62 -0
  302. package/src/ci/trace-schema.ts +96 -0
  303. package/src/ci/trace-writer.ts +107 -0
  304. package/src/ci/types.ts +115 -0
  305. package/src/ci/upload-client.ts +300 -0
  306. package/src/cli.ts +811 -0
  307. package/src/core/agent-state.ts +162 -0
  308. package/src/core/judge-utils.ts +232 -0
  309. package/src/core/registry.ts +92 -0
  310. package/src/dashboard-server.ts +2047 -0
  311. package/src/execution/tool-runner.ts +352 -0
  312. package/src/html/dashboard.html +2218 -0
  313. package/src/http.ts +13 -0
  314. package/src/index.ts +138 -0
  315. package/src/interceptors/ai-interceptor.ts +798 -0
  316. package/src/interceptors/db-auto.ts +243 -0
  317. package/src/interceptors/db.ts +156 -0
  318. package/src/interceptors/http.ts +393 -0
  319. package/src/interceptors/side-effects.ts +83 -0
  320. package/src/interceptors/telemetry-push.ts +537 -0
  321. package/src/interceptors/tool.ts +287 -0
  322. package/src/interceptors/workflow-ai.ts +419 -0
  323. package/src/internals/conditional-recorder.ts +63 -0
  324. package/src/internals/mock-resolver.ts +492 -0
  325. package/src/matchers/index.ts +824 -0
  326. package/src/observability.ts +501 -0
  327. package/src/portal-executor.ts +355 -0
  328. package/src/portal-server.ts +304 -0
  329. package/src/proxy/llm-capture.ts +301 -0
  330. package/src/reporter.ts +81 -0
  331. package/src/runWorkflowSubprocess.ts +74 -0
  332. package/src/runner.ts +178 -0
  333. package/src/socket-connector.ts +117 -0
  334. package/src/telemetry-batcher.ts +191 -0
  335. package/src/test-setup.ts +16 -0
  336. package/src/tool-registry.ts +94 -0
  337. package/src/tool-runner-worker.ts +244 -0
  338. package/src/trace-adapter/context.ts +156 -0
  339. package/src/tracing.ts +62 -0
  340. package/src/trigger-executor.ts +171 -0
  341. package/src/types/agent.d.ts +63 -0
  342. package/src/types/expect.d.ts +81 -0
  343. package/src/types/modules.d.ts +2 -0
  344. package/src/types/portal.ts +69 -0
  345. package/src/utils/debug.ts +8 -0
  346. package/src/utils/license-error.ts +43 -0
  347. package/src/utils/redact.ts +25 -0
  348. package/src/workflow-runner-worker.ts +386 -0
  349. 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
+ }