elasticdash-test 0.1.3 → 0.1.5

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/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
  An AI-native test runner for ElasticDash workflow testing. Built for async AI pipelines — not a general-purpose test runner.
4
4
 
5
5
  - Trace-first: every test receives a `trace` context to record and assert on LLM calls and tool invocations
6
- - AI-specific matchers: `toHaveLLMStep`, `toCallTool`, `toMatchSemanticOutput`
6
+ - Automatic fetch interception for OpenAI, Gemini, and Grok — no manual instrumentation required
7
+ - AI-specific matchers: `toHaveLLMStep`, `toCallTool`, `toMatchSemanticOutput`, `toHaveCustomStep`, `toHavePromptWhere`, `toEvaluateOutputMetric`
7
8
  - Sequential execution, no parallelism overhead
8
9
  - No Jest dependency
9
10
 
@@ -82,7 +83,9 @@ aiTest('my test', async (ctx) => {
82
83
 
83
84
  ### Recording trace data
84
85
 
85
- Your AI workflow code should call these on `ctx.trace` to make assertions possible:
86
+ **Automatic interception (recommended):** When your workflow code makes real API calls to OpenAI, Gemini, or Grok, the runner intercepts them automatically and records the LLM step — no changes to your workflow code needed. See [Automatic AI Interception](#automatic-ai-interception) below.
87
+
88
+ **Manual recording:** Use this for providers not covered by the interceptor, when testing against stubs/mocks, or to capture RAG / code / fixed steps:
86
89
 
87
90
  ```ts
88
91
  ctx.trace.recordLLMStep({
@@ -95,20 +98,47 @@ ctx.trace.recordToolCall({
95
98
  name: 'chargeCard',
96
99
  args: { amount: 99.99 },
97
100
  })
101
+
102
+ // Record custom workflow steps (RAG fetches, code/fixed steps, etc.)
103
+ ctx.trace.recordCustomStep({
104
+ kind: 'rag', // 'rag' | 'code' | 'fixed' | 'custom'
105
+ name: 'pokemon-search',
106
+ tags: ['sort:asc', 'source:db'],
107
+ payload: { query: 'pikachu attack' },
108
+ result: { ids: [25] },
109
+ metadata: { latencyMs: 120 },
110
+ })
98
111
  ```
99
112
 
100
113
  ### Matchers
101
114
 
102
115
  #### `toHaveLLMStep(config?)`
103
116
 
104
- Assert the trace contains at least one LLM step matching the given config. All fields are optional.
117
+ Assert the trace contains at least one LLM step matching the given config. All fields are optional and combined with AND logic.
105
118
 
106
119
  ```ts
107
120
  expect(ctx.trace).toHaveLLMStep({ model: 'gpt-4' })
108
- expect(ctx.trace).toHaveLLMStep({ contains: 'order confirmed' })
109
- expect(ctx.trace).toHaveLLMStep({ model: 'gpt-4', contains: 'order confirmed' })
121
+ expect(ctx.trace).toHaveLLMStep({ contains: 'order confirmed' }) // searches prompt + completion
122
+ expect(ctx.trace).toHaveLLMStep({ promptContains: 'order status' }) // searches prompt only
123
+ expect(ctx.trace).toHaveLLMStep({ outputContains: 'order confirmed' }) // searches completion only
124
+ expect(ctx.trace).toHaveLLMStep({ provider: 'openai' })
125
+ expect(ctx.trace).toHaveLLMStep({ provider: 'openai', promptContains: 'order status' })
126
+ expect(ctx.trace).toHaveLLMStep({ promptContains: 'retry', times: 3 }) // exactly 3 matching steps
127
+ expect(ctx.trace).toHaveLLMStep({ provider: 'openai', minTimes: 2 }) // at least 2 matching steps
128
+ expect(ctx.trace).toHaveLLMStep({ outputContains: 'error', maxTimes: 1 }) // at most 1 matching step
110
129
  ```
111
130
 
131
+ | Field | Description |
132
+ |---|---|
133
+ | `model` | Exact model name match (e.g. `'gpt-4o'`) |
134
+ | `contains` | Substring match across prompt + completion (case-insensitive) |
135
+ | `promptContains` | Substring match in prompt only (case-insensitive) |
136
+ | `outputContains` | Substring match in completion only (case-insensitive) |
137
+ | `provider` | Provider name: `'openai'`, `'gemini'`, or `'grok'` |
138
+ | `times` | Exact match count (fails unless exactly this many steps match) |
139
+ | `minTimes` | Minimum match count (steps matching must be ≥ this value) |
140
+ | `maxTimes` | Maximum match count (steps matching must be ≤ this value) |
141
+
112
142
  #### `toCallTool(toolName)`
113
143
 
114
144
  Assert the trace contains a tool call with the given name.
@@ -117,16 +147,176 @@ Assert the trace contains a tool call with the given name.
117
147
  expect(ctx.trace).toCallTool('chargeCard')
118
148
  ```
119
149
 
120
- #### `toMatchSemanticOutput(expected)`
150
+ #### `toMatchSemanticOutput(expected, options?)`
121
151
 
122
- Assert the combined LLM output contains the expected string (case-insensitive substring match designed for a future embedding-based similarity upgrade).
152
+ LLM-judged semantic match of combined LLM output vs. the expected string. Defaults to OpenAI GPT-4.1 with `OPENAI_API_KEY`. Optional options:
123
153
 
124
154
  ```ts
155
+ expect(ctx.trace).toMatchSemanticOutput('attack stat', {
156
+ provider: 'claude', // 'openai' (default) | 'claude' | 'gemini' | 'grok'
157
+ model: 'claude-3-opus-20240229', // overrides default model for the provider
158
+ sdk: myClaudeClient, // optional SDK instance (uses its chat/messages API)
159
+ })
160
+
161
+ // Minimal, using default OpenAI model
125
162
  expect(ctx.trace).toMatchSemanticOutput('order confirmed')
163
+
164
+ // OpenAI-compatible endpoint (e.g., Moonshot/Kimi) via baseURL + apiKey
165
+ expect(ctx.trace).toMatchSemanticOutput('order confirmed', {
166
+ provider: 'openai',
167
+ model: 'kimi-k2-turbo-preview',
168
+ apiKey: process.env.KIMI_API_KEY,
169
+ baseURL: 'https://api.moonshot.ai/v1',
170
+ })
171
+ ```
172
+
173
+ Environment keys by provider: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY` (or `GOOGLE_API_KEY`), `GROK_API_KEY`.
174
+ For OpenAI-compatible endpoints, pass `apiKey`/`baseURL` in options or set an appropriate env var used by your SDK.
175
+
176
+ #### `toEvaluateOutputMetric(config)`
177
+
178
+ Evaluate one LLM step’s prompt or result using an LLM and assert a numeric metric condition in the range 0.0–1.0. Defaults: target=`result`, condition=`atLeast 0.7`, provider=`openai`, model=`gpt-4.1`.
179
+
180
+ ```ts
181
+ // Evaluate the last LLM result with your own prompt; default condition atLeast 0.7
182
+ expect(ctx.trace).toEvaluateOutputMetric({
183
+ evaluationPrompt: 'Rate how well this answers the user question.',
184
+ })
185
+
186
+ // Check a specific step (3rd LLM prompt), target the prompt text, require >= 0.8 via Claude
187
+ expect(ctx.trace).toEvaluateOutputMetric({
188
+ evaluationPrompt: 'Score coherence of this prompt between 0 and 1.',
189
+ target: 'prompt',
190
+ nth: 3,
191
+ condition: { atLeast: 0.8 },
192
+ provider: 'claude',
193
+ model: 'claude-3-opus-20240229',
194
+ })
195
+
196
+ // Custom comparator: score must be < 0.3
197
+ expect(ctx.trace).toEvaluateOutputMetric({
198
+ evaluationPrompt: 'Rate hallucination risk (0=none, 1=high).',
199
+ condition: { lessThan: 0.3 },
200
+ })
201
+ ```
202
+
203
+ Options:
204
+ - `evaluationPrompt` (required): your scoring instructions; model is asked to return only a number between 0 and 1.
205
+ - `target`: `'result'` (default) or `'prompt'`. Mutually exclusive; evaluates that text only.
206
+ - `index` / `nth`: pick which LLM step to score (0-based or 1-based). Defaults to the last LLM step.
207
+ - `condition`: one of `greaterThan`, `lessThan`, `atLeast`, `atMost`, `equals`; default is `{ atLeast: 0.7 }`. Fails if the score is outside 0.0–1.0 or cannot be parsed.
208
+ - `provider` / `model` / `sdk` / `apiKey` / `baseURL`: same shape as `toMatchSemanticOutput` (supports OpenAI, Claude, Gemini, Grok, and OpenAI-compatible via `baseURL`). Requires corresponding API key if no SDK is supplied.
209
+
210
+ #### `toHaveCustomStep(config?)`
211
+
212
+ Assert a recorded custom step (RAG/code/fixed/custom) matches filters.
213
+
214
+ ```ts
215
+ expect(ctx.trace).toHaveCustomStep({ kind: 'rag', name: 'pokemon-search' })
216
+ expect(ctx.trace).toHaveCustomStep({ tag: 'sort:asc' })
217
+ expect(ctx.trace).toHaveCustomStep({ contains: 'pikachu' })
218
+ expect(ctx.trace).toHaveCustomStep({ resultContains: '25' })
219
+ expect(ctx.trace).toHaveCustomStep({ kind: 'rag', minTimes: 1, maxTimes: 2 })
220
+ ```
221
+
222
+ #### `toHavePromptWhere(config)`
223
+
224
+ Filter prompts, then assert additional constraints. Example: “all prompts containing A must also contain B”.
225
+
226
+ ```ts
227
+ // Prompts that contain "order" must also contain "confirmed"
228
+ expect(ctx.trace).toHavePromptWhere({
229
+ filterContains: 'order',
230
+ requireContains: 'confirmed',
231
+ })
232
+
233
+ // Prompts containing "retry" must NOT contain "cancel"
234
+ expect(ctx.trace).toHavePromptWhere({
235
+ filterContains: 'retry',
236
+ requireNotContains: 'cancel',
237
+ })
238
+
239
+ // And control counts on the filtered subset
240
+ expect(ctx.trace).toHavePromptWhere({
241
+ filterContains: 'order',
242
+ requireContains: 'confirmed',
243
+ minTimes: 1,
244
+ maxTimes: 3,
245
+ })
246
+
247
+ // Check a specific prompt position (1-based nth or 0-based index)
248
+ expect(ctx.trace).toHavePromptWhere({
249
+ filterContains: 'order',
250
+ requireContains: 'confirmed',
251
+ nth: 3, // the 3rd prompt among those containing "order"
252
+ })
126
253
  ```
127
254
 
128
255
  ---
129
256
 
257
+ ## Automatic AI Interception
258
+
259
+ The runner patches `globalThis.fetch` before tests run and automatically records LLM steps for calls to the following endpoints:
260
+
261
+ | Provider | Endpoints intercepted |
262
+ |---|---|
263
+ | **OpenAI** | `api.openai.com/v1/chat/completions`, `/v1/completions` |
264
+ | **Gemini** | `generativelanguage.googleapis.com/.../models/...:generateContent` |
265
+ | **Grok** (xAI) | `api.x.ai/v1/chat/completions` |
266
+
267
+ Each intercepted call records `model`, `provider`, `prompt`, and `completion` into `ctx.trace` automatically. Your workflow code needs no changes.
268
+
269
+ ```ts
270
+ aiTest('user lookup flow', async (ctx) => {
271
+ // This makes a real OpenAI call — intercepted automatically
272
+ await myWorkflow.run('Find all active users')
273
+
274
+ // Works without any ctx.trace.recordLLMStep() in your workflow
275
+ expect(ctx.trace).toHaveLLMStep({ promptContains: 'Find all active users' })
276
+ expect(ctx.trace).toHaveLLMStep({ provider: 'openai' })
277
+ })
278
+ ```
279
+
280
+ **Streaming:** When `stream: true` is set on a request, the completion is recorded as `"(streamed)"` — the prompt and model are still captured.
281
+
282
+ **Libraries using `https.request` directly** (older versions of some SDKs) are not covered by fetch interception. Use manual `ctx.trace.recordLLMStep()` for those.
283
+
284
+ ### Recording flow steps without passing `ctx.trace` (AsyncLocalStorage)
285
+
286
+ The runner now sets a per-test `currentTrace` using Node’s `AsyncLocalStorage`, so your app code can record steps without threading `ctx.trace` through every function. This remains safe under parallel execution.
287
+
288
+ ```ts
289
+ // In your test
290
+ import { setCurrentTrace } from 'elasticdash-test'
291
+
292
+ aiTest('flow test', async (ctx) => {
293
+ setCurrentTrace(ctx.trace) // bind the trace to the current async context
294
+ await runFlowWithoutTraceArg() // your existing code
295
+ // assertions
296
+ expect(ctx.trace).toHaveCustomStep({ kind: 'rag', name: 'pokemon-search' })
297
+ })
298
+
299
+ // In your app/flow code (called during the test)
300
+ import { getCurrentTrace } from 'elasticdash-test'
301
+
302
+ function runFlowWithoutTraceArg() {
303
+ const trace = getCurrentTrace()
304
+ trace?.recordCustomStep({
305
+ kind: 'rag',
306
+ name: 'pokemon-search',
307
+ payload: { query: 'pikachu attack' },
308
+ result: { ids: [25] },
309
+ tags: ['source:db', 'sort:asc'],
310
+ })
311
+ }
312
+ ```
313
+
314
+ Notes:
315
+ - Works per async context; if you spawn detached work (child processes/independent workers), pass `trace` explicitly there.
316
+ - Still compatible with manual DI: you can continue passing `ctx.trace` explicitly if you prefer.
317
+
318
+ ---
319
+
130
320
  ## Configuration
131
321
 
132
322
  Create an optional `elasticdash.config.ts` at the project root:
@@ -182,6 +372,8 @@ src/
182
372
  context.ts TraceHandle, AITestContext, RunnerHooks scaffold
183
373
  matchers/
184
374
  index.ts Custom expect matchers
375
+ interceptors/
376
+ ai-interceptor.ts Automatic fetch interceptor for OpenAI / Gemini / Grok
185
377
  ```
186
378
 
187
379
  ---
@@ -189,11 +381,15 @@ src/
189
381
  ## Programmatic API
190
382
 
191
383
  ```ts
192
- import { runFiles, reportResults, registerMatchers } from 'elasticdash-test'
384
+ import { runFiles, reportResults, registerMatchers, installAIInterceptor, uninstallAIInterceptor } from 'elasticdash-test'
193
385
 
194
386
  registerMatchers()
387
+ installAIInterceptor() // patch globalThis.fetch for automatic LLM tracing
388
+
195
389
  const results = await runFiles(['./tests/flow.ai.test.ts'])
196
390
  reportResults(results)
391
+
392
+ uninstallAIInterceptor() // restore original fetch when done
197
393
  ```
198
394
 
199
395
  ---
package/dist/cli.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ import 'dotenv/config';
3
3
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAA"}
package/dist/cli.js CHANGED
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import 'dotenv/config';
2
3
  import { Command } from 'commander';
3
4
  import fg from 'fast-glob';
4
5
  import path from 'node:path';
5
6
  import { pathToFileURL } from 'node:url';
6
7
  import { existsSync } from 'node:fs';
7
8
  import { registerMatchers } from './matchers/index.js';
9
+ import { installAIInterceptor } from './interceptors/ai-interceptor.js';
8
10
  import { runFiles } from './runner.js';
9
11
  import { reportResults } from './reporter.js';
10
12
  async function loadConfig(cwd) {
@@ -26,26 +28,48 @@ async function discoverTestFiles(patterns, cwd) {
26
28
  // --- Bootstrap ---
27
29
  async function bootstrap() {
28
30
  registerMatchers();
31
+ installAIInterceptor();
29
32
  const cwd = process.cwd();
30
33
  const config = await loadConfig(cwd);
31
34
  const defaultPattern = config.testMatch ?? ['**/*.ai.test.ts', '**/*.ai.test.js'];
35
+ // Read version from package.json
36
+ // Use require for CJS compatibility, fallback to import if needed
37
+ // This path is relative to the compiled dist directory
38
+ let version = 'unknown';
39
+ try {
40
+ // @ts-ignore
41
+ version = (await import(pathToFileURL(path.join(cwd, 'package.json')).href, { with: { type: 'json' } })).default.version;
42
+ }
43
+ catch (e) {
44
+ try {
45
+ version = require(path.join(cwd, 'package.json')).version;
46
+ }
47
+ catch { }
48
+ }
32
49
  const program = new Command();
33
50
  program
34
51
  .name('elasticdash')
35
52
  .description('AI-native test runner for ElasticDash workflow testing')
36
- .version('0.1.3');
53
+ .version(version);
37
54
  // elasticdash test [dir]
38
55
  program
39
56
  .command('test [dir]')
40
57
  .description('Discover and run all AI test files')
41
58
  .action(async (dir) => {
42
59
  const searchBase = dir ? path.resolve(cwd, dir) : cwd;
60
+ console.log('[elasticdash] Test discovery pattern:', defaultPattern);
61
+ console.log('[elasticdash] Test search base:', searchBase);
43
62
  const files = await discoverTestFiles(defaultPattern, searchBase);
63
+ console.log('[elasticdash] Discovered test files:', files);
44
64
  if (files.length === 0) {
45
65
  console.error(`No test files found matching: ${defaultPattern.join(', ')}`);
46
66
  process.exit(1);
47
67
  }
48
68
  const results = await runFiles(files);
69
+ // Log registered tests
70
+ const { getRegistry } = await import('./core/registry.js');
71
+ const registry = getRegistry();
72
+ console.log('[elasticdash] Tests registered:', registry.tests.map(t => t.name));
49
73
  reportResults(results);
50
74
  const anyFailed = results.some((fr) => fr.results.some((r) => !r.passed));
51
75
  process.exit(anyFailed ? 1 : 0);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/core/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAEhE,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;AAEvE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,YAAY,CAAA;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,cAAc,EAAE,KAAK,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;IACjD,aAAa,EAAE,KAAK,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;CACjD;AAYD,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED,wBAAgB,WAAW,IAAI,QAAQ,CAEtC;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,GAAG,IAAI,CAE3D;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAE9D;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAE7D;AAGD,OAAO,CAAC,MAAM,CAAC;IAEb,IAAI,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,KAAK,IAAI,CAAA;IAEpD,IAAI,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IAEvD,IAAI,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;CACvD"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/core/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAEhE,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;AAEvE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,YAAY,CAAA;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,cAAc,EAAE,KAAK,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;IACjD,aAAa,EAAE,KAAK,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;CACjD;AAmBD,wBAAgB,aAAa,IAAI,IAAI,CAGpC;AAED,wBAAgB,WAAW,IAAI,QAAQ,CAItC;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,GAAG,IAAI,CAI3D;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAG9D;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAG7D;AAGD,OAAO,CAAC,MAAM,CAAC;IAEb,IAAI,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,KAAK,IAAI,CAAA;IAEpD,IAAI,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IAEvD,IAAI,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;CACvD"}
@@ -1,4 +1,10 @@
1
- let _registry = createEmptyRegistry();
1
+ const REGISTRY_KEY = '__elasticdash_registry__';
2
+ function getGlobalRegistry() {
3
+ if (!globalThis[REGISTRY_KEY]) {
4
+ globalThis[REGISTRY_KEY] = createEmptyRegistry();
5
+ }
6
+ return globalThis[REGISTRY_KEY];
7
+ }
2
8
  function createEmptyRegistry() {
3
9
  return {
4
10
  tests: [],
@@ -7,19 +13,26 @@ function createEmptyRegistry() {
7
13
  };
8
14
  }
9
15
  export function clearRegistry() {
10
- _registry = createEmptyRegistry();
16
+ globalThis[REGISTRY_KEY] = createEmptyRegistry();
17
+ console.log('[elasticdash] clearRegistry called. Registry reset.');
11
18
  }
12
19
  export function getRegistry() {
13
- return _registry;
20
+ const registry = getGlobalRegistry();
21
+ console.log('[elasticdash] getRegistry called. Current tests:', registry.tests.map(t => t.name));
22
+ return registry;
14
23
  }
15
24
  export function aiTest(name, fn) {
16
- _registry.tests.push({ name, fn });
25
+ const registry = getGlobalRegistry();
26
+ registry.tests.push({ name, fn });
27
+ console.log(`[elasticdash] Registered test: ${name}`);
17
28
  }
18
29
  export function beforeAll(fn) {
19
- _registry.beforeAllHooks.push(fn);
30
+ const registry = getGlobalRegistry();
31
+ registry.beforeAllHooks.push(fn);
20
32
  }
21
33
  export function afterAll(fn) {
22
- _registry.afterAllHooks.push(fn);
34
+ const registry = getGlobalRegistry();
35
+ registry.afterAllHooks.push(fn);
23
36
  }
24
37
  globalThis.aiTest = aiTest;
25
38
  globalThis.beforeAll = beforeAll;
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/core/registry.ts"],"names":[],"mappings":"AAeA,IAAI,SAAS,GAAa,mBAAmB,EAAE,CAAA;AAE/C,SAAS,mBAAmB;IAC1B,OAAO;QACL,KAAK,EAAE,EAAE;QACT,cAAc,EAAE,EAAE;QAClB,aAAa,EAAE,EAAE;KAClB,CAAA;AACH,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,SAAS,GAAG,mBAAmB,EAAE,CAAA;AACnC,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,IAAY,EAAE,EAAgB;IACnD,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;AACpC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,EAA8B;IACtD,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACnC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,EAA8B;IACrD,SAAS,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AAClC,CAAC;AAYD,UAAU,CAAC,MAAM,GAAG,MAAM,CAAA;AAC1B,UAAU,CAAC,SAAS,GAAG,SAAS,CAAA;AAChC,UAAU,CAAC,QAAQ,GAAG,QAAQ,CAAA"}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/core/registry.ts"],"names":[],"mappings":"AAeA,MAAM,YAAY,GAAG,0BAA0B,CAAA;AAE/C,SAAS,iBAAiB;IACxB,IAAI,CAAE,UAAkB,CAAC,YAAY,CAAC,EAAE,CAAC;QACtC,UAAkB,CAAC,YAAY,CAAC,GAAG,mBAAmB,EAAE,CAAA;IAC3D,CAAC;IACD,OAAQ,UAAkB,CAAC,YAAY,CAAa,CAAA;AACtD,CAAC;AAED,SAAS,mBAAmB;IAC1B,OAAO;QACL,KAAK,EAAE,EAAE;QACT,cAAc,EAAE,EAAE;QAClB,aAAa,EAAE,EAAE;KAClB,CAAA;AACH,CAAC;AAED,MAAM,UAAU,aAAa;IAC1B,UAAkB,CAAC,YAAY,CAAC,GAAG,mBAAmB,EAAE,CAAA;IACzD,OAAO,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAA;AACpE,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAA;IACpC,OAAO,CAAC,GAAG,CAAC,kDAAkD,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IAChG,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,IAAY,EAAE,EAAgB;IACnD,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAA;IACpC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;IACjC,OAAO,CAAC,GAAG,CAAC,kCAAkC,IAAI,EAAE,CAAC,CAAA;AACvD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,EAA8B;IACtD,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAA;IACpC,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AAClC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,EAA8B;IACrD,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAA;IACpC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACjC,CAAC;AAYD,UAAU,CAAC,MAAM,GAAG,MAAM,CAAA;AAC1B,UAAU,CAAC,SAAS,GAAG,SAAS,CAAA;AAChC,UAAU,CAAC,QAAQ,GAAG,QAAQ,CAAA"}
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  export { aiTest, beforeAll, afterAll, getRegistry, clearRegistry } from './core/registry.js';
2
- export { createTraceHandle, startTraceSession } from './trace-adapter/context.js';
2
+ export { createTraceHandle, startTraceSession, setCurrentTrace, getCurrentTrace } from './trace-adapter/context.js';
3
3
  export { registerMatchers } from './matchers/index.js';
4
4
  export { expect } from './matchers/index.js';
5
5
  export { runFiles } from './runner.js';
6
6
  export { reportResults } from './reporter.js';
7
+ export { installAIInterceptor, uninstallAIInterceptor } from './interceptors/ai-interceptor.js';
7
8
  export type { TestResult, FileResult, RunnerOptions } from './runner.js';
8
- export type { AITestContext, TraceHandle, LLMStep, ToolCall, TraceStep, RunnerHooks } from './trace-adapter/context.js';
9
+ export type { AITestContext, TraceHandle, LLMStep, ToolCall, CustomStep, TraceStep, RunnerHooks } from './trace-adapter/context.js';
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAC5F,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACxE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAC5F,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACnH,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AAC/F,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACxE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA"}
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  // Public API surface for programmatic use
2
2
  export { aiTest, beforeAll, afterAll, getRegistry, clearRegistry } from './core/registry.js';
3
- export { createTraceHandle, startTraceSession } from './trace-adapter/context.js';
3
+ export { createTraceHandle, startTraceSession, setCurrentTrace, getCurrentTrace } from './trace-adapter/context.js';
4
4
  export { registerMatchers } from './matchers/index.js';
5
5
  export { expect } from './matchers/index.js';
6
6
  export { runFiles } from './runner.js';
7
7
  export { reportResults } from './reporter.js';
8
+ export { installAIInterceptor, uninstallAIInterceptor } from './interceptors/ai-interceptor.js';
8
9
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAC5F,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAC5F,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACnH,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAA"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Install the AI fetch interceptor. Wraps globalThis.fetch to automatically
3
+ * record LLM steps into the active trace for OpenAI, Gemini, and Grok calls.
4
+ */
5
+ export declare function installAIInterceptor(): void;
6
+ /**
7
+ * Uninstall the AI fetch interceptor, restoring globalThis.fetch to its original value.
8
+ */
9
+ export declare function uninstallAIInterceptor(): void;
10
+ //# sourceMappingURL=ai-interceptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-interceptor.d.ts","sourceRoot":"","sources":["../../src/interceptors/ai-interceptor.ts"],"names":[],"mappings":"AAuGA;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAwD3C;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAK7C"}
@@ -0,0 +1,157 @@
1
+ import { getCurrentTrace } from '../trace-adapter/context.js';
2
+ /** URL patterns for known AI providers */
3
+ const AI_PATTERNS = {
4
+ openai: /https?:\/\/api\.openai\.com\/v1\/(chat\/)?completions/,
5
+ gemini: /https?:\/\/generativelanguage\.googleapis\.com\/.*\/models\/[^/:]+:(generateContent|streamGenerateContent)/,
6
+ grok: /https?:\/\/api\.x\.ai\/v1\/(chat\/)?completions/,
7
+ };
8
+ /** Detect which provider (if any) a URL belongs to */
9
+ function detectProvider(url) {
10
+ for (const [provider, pattern] of Object.entries(AI_PATTERNS)) {
11
+ if (pattern.test(url))
12
+ return provider;
13
+ }
14
+ return null;
15
+ }
16
+ /** Extract model name from request body or URL (for Gemini) */
17
+ function extractModel(provider, body, url) {
18
+ if (provider === 'gemini') {
19
+ // URL shape: .../models/gemini-1.5-pro:generateContent
20
+ const match = /\/models\/([^/:]+):/.exec(url);
21
+ return match ? match[1] : 'unknown';
22
+ }
23
+ return typeof body.model === 'string' ? body.model : 'unknown';
24
+ }
25
+ /** Extract prompt text from request body */
26
+ function extractPrompt(provider, body) {
27
+ if (provider === 'openai' || provider === 'grok') {
28
+ const messages = body.messages;
29
+ if (Array.isArray(messages)) {
30
+ return messages
31
+ .map((m) => {
32
+ if (m && typeof m === 'object') {
33
+ const msg = m;
34
+ return `${msg.role}: ${msg.content}`;
35
+ }
36
+ return String(m);
37
+ })
38
+ .join('\n');
39
+ }
40
+ // Legacy completions API
41
+ return typeof body.prompt === 'string' ? body.prompt : '';
42
+ }
43
+ if (provider === 'gemini') {
44
+ const contents = body.contents;
45
+ if (Array.isArray(contents)) {
46
+ return contents
47
+ .flatMap((c) => {
48
+ if (c && typeof c === 'object') {
49
+ const parts = c.parts;
50
+ if (Array.isArray(parts)) {
51
+ return parts.map((p) => {
52
+ if (p && typeof p === 'object') {
53
+ return String(p.text ?? '');
54
+ }
55
+ return '';
56
+ });
57
+ }
58
+ }
59
+ return [];
60
+ })
61
+ .join('\n');
62
+ }
63
+ }
64
+ return '';
65
+ }
66
+ /** Extract completion text from response body */
67
+ function extractCompletion(provider, responseBody) {
68
+ if (provider === 'openai' || provider === 'grok') {
69
+ const choices = responseBody.choices;
70
+ if (Array.isArray(choices) && choices.length > 0) {
71
+ const first = choices[0];
72
+ if (first.message && typeof first.message === 'object') {
73
+ return String(first.message.content ?? '');
74
+ }
75
+ if (typeof first.text === 'string')
76
+ return first.text;
77
+ }
78
+ }
79
+ if (provider === 'gemini') {
80
+ const candidates = responseBody.candidates;
81
+ if (Array.isArray(candidates) && candidates.length > 0) {
82
+ const first = candidates[0];
83
+ if (first.content && typeof first.content === 'object') {
84
+ const parts = first.content.parts;
85
+ if (Array.isArray(parts) && parts.length > 0) {
86
+ return String(parts[0].text ?? '');
87
+ }
88
+ }
89
+ }
90
+ }
91
+ return '';
92
+ }
93
+ // Keep a reference to the original fetch so we can restore it
94
+ let originalFetch = null;
95
+ /**
96
+ * Install the AI fetch interceptor. Wraps globalThis.fetch to automatically
97
+ * record LLM steps into the active trace for OpenAI, Gemini, and Grok calls.
98
+ */
99
+ export function installAIInterceptor() {
100
+ if (originalFetch)
101
+ return; // already installed
102
+ originalFetch = globalThis.fetch;
103
+ globalThis.fetch = async function patchedFetch(input, init) {
104
+ const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
105
+ const provider = detectProvider(url);
106
+ const traceAtCall = getCurrentTrace();
107
+ // No match or no active trace: pass through unchanged
108
+ if (!provider || !traceAtCall) {
109
+ return originalFetch(input, init);
110
+ }
111
+ // Parse request body to extract model and prompt
112
+ let model = 'unknown';
113
+ let prompt = '';
114
+ let isStreaming = false;
115
+ try {
116
+ const rawBody = init?.body;
117
+ if (rawBody && typeof rawBody === 'string') {
118
+ const body = JSON.parse(rawBody);
119
+ model = extractModel(provider, body, url);
120
+ prompt = extractPrompt(provider, body);
121
+ isStreaming = body.stream === true;
122
+ }
123
+ }
124
+ catch {
125
+ // Ignore parse errors — still pass through
126
+ }
127
+ // Make the actual request
128
+ const response = await originalFetch(input, init);
129
+ if (traceAtCall) {
130
+ if (isStreaming) {
131
+ traceAtCall.recordLLMStep({ model, provider, prompt, completion: '(streamed)' });
132
+ }
133
+ else {
134
+ try {
135
+ const cloned = response.clone();
136
+ const responseBody = await cloned.json();
137
+ const completion = extractCompletion(provider, responseBody);
138
+ traceAtCall.recordLLMStep({ model, provider, prompt, completion });
139
+ }
140
+ catch {
141
+ traceAtCall.recordLLMStep({ model, provider, prompt, completion: '' });
142
+ }
143
+ }
144
+ }
145
+ return response;
146
+ };
147
+ }
148
+ /**
149
+ * Uninstall the AI fetch interceptor, restoring globalThis.fetch to its original value.
150
+ */
151
+ export function uninstallAIInterceptor() {
152
+ if (originalFetch) {
153
+ globalThis.fetch = originalFetch;
154
+ originalFetch = null;
155
+ }
156
+ }
157
+ //# sourceMappingURL=ai-interceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-interceptor.js","sourceRoot":"","sources":["../../src/interceptors/ai-interceptor.ts"],"names":[],"mappings":"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"}