elasticdash-test 0.1.2 → 0.1.4

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,9 @@
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`
8
+ - AI-specific matchers: `toHaveLLMStep`, `toCallTool`, `toMatchSemanticOutput`, `toHaveCustomStep`, `toHavePromptWhere`
7
9
  - Sequential execution, no parallelism overhead
8
10
  - No Jest dependency
9
11
 
@@ -82,7 +84,9 @@ aiTest('my test', async (ctx) => {
82
84
 
83
85
  ### Recording trace data
84
86
 
85
- Your AI workflow code should call these on `ctx.trace` to make assertions possible:
87
+ **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.
88
+
89
+ **Manual recording:** Use this for providers not covered by the interceptor, when testing against stubs/mocks, or to capture RAG / code / fixed steps:
86
90
 
87
91
  ```ts
88
92
  ctx.trace.recordLLMStep({
@@ -95,20 +99,47 @@ ctx.trace.recordToolCall({
95
99
  name: 'chargeCard',
96
100
  args: { amount: 99.99 },
97
101
  })
102
+
103
+ // Record custom workflow steps (RAG fetches, code/fixed steps, etc.)
104
+ ctx.trace.recordCustomStep({
105
+ kind: 'rag', // 'rag' | 'code' | 'fixed' | 'custom'
106
+ name: 'pokemon-search',
107
+ tags: ['sort:asc', 'source:db'],
108
+ payload: { query: 'pikachu attack' },
109
+ result: { ids: [25] },
110
+ metadata: { latencyMs: 120 },
111
+ })
98
112
  ```
99
113
 
100
114
  ### Matchers
101
115
 
102
116
  #### `toHaveLLMStep(config?)`
103
117
 
104
- Assert the trace contains at least one LLM step matching the given config. All fields are optional.
118
+ Assert the trace contains at least one LLM step matching the given config. All fields are optional and combined with AND logic.
105
119
 
106
120
  ```ts
107
121
  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' })
122
+ expect(ctx.trace).toHaveLLMStep({ contains: 'order confirmed' }) // searches prompt + completion
123
+ expect(ctx.trace).toHaveLLMStep({ promptContains: 'order status' }) // searches prompt only
124
+ expect(ctx.trace).toHaveLLMStep({ outputContains: 'order confirmed' }) // searches completion only
125
+ expect(ctx.trace).toHaveLLMStep({ provider: 'openai' })
126
+ expect(ctx.trace).toHaveLLMStep({ provider: 'openai', promptContains: 'order status' })
127
+ expect(ctx.trace).toHaveLLMStep({ promptContains: 'retry', times: 3 }) // exactly 3 matching steps
128
+ expect(ctx.trace).toHaveLLMStep({ provider: 'openai', minTimes: 2 }) // at least 2 matching steps
129
+ expect(ctx.trace).toHaveLLMStep({ outputContains: 'error', maxTimes: 1 }) // at most 1 matching step
110
130
  ```
111
131
 
132
+ | Field | Description |
133
+ |---|---|
134
+ | `model` | Exact model name match (e.g. `'gpt-4o'`) |
135
+ | `contains` | Substring match across prompt + completion (case-insensitive) |
136
+ | `promptContains` | Substring match in prompt only (case-insensitive) |
137
+ | `outputContains` | Substring match in completion only (case-insensitive) |
138
+ | `provider` | Provider name: `'openai'`, `'gemini'`, or `'grok'` |
139
+ | `times` | Exact match count (fails unless exactly this many steps match) |
140
+ | `minTimes` | Minimum match count (steps matching must be ≥ this value) |
141
+ | `maxTimes` | Maximum match count (steps matching must be ≤ this value) |
142
+
112
143
  #### `toCallTool(toolName)`
113
144
 
114
145
  Assert the trace contains a tool call with the given name.
@@ -117,14 +148,131 @@ Assert the trace contains a tool call with the given name.
117
148
  expect(ctx.trace).toCallTool('chargeCard')
118
149
  ```
119
150
 
120
- #### `toMatchSemanticOutput(expected)`
151
+ #### `toMatchSemanticOutput(expected, options?)`
121
152
 
122
- Assert the combined LLM output contains the expected string (case-insensitive substring match designed for a future embedding-based similarity upgrade).
153
+ 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
154
 
124
155
  ```ts
156
+ expect(ctx.trace).toMatchSemanticOutput('attack stat', {
157
+ provider: 'claude', // 'openai' (default) | 'claude' | 'gemini' | 'grok'
158
+ model: 'claude-3-opus-20240229', // overrides default model for the provider
159
+ sdk: myClaudeClient, // optional SDK instance (uses its chat/messages API)
160
+ })
161
+
162
+ // Minimal, using default OpenAI model
125
163
  expect(ctx.trace).toMatchSemanticOutput('order confirmed')
126
164
  ```
127
165
 
166
+ Environment keys by provider: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY` (or `GOOGLE_API_KEY`), `GROK_API_KEY`.
167
+
168
+ #### `toHaveCustomStep(config?)`
169
+
170
+ Assert a recorded custom step (RAG/code/fixed/custom) matches filters.
171
+
172
+ ```ts
173
+ expect(ctx.trace).toHaveCustomStep({ kind: 'rag', name: 'pokemon-search' })
174
+ expect(ctx.trace).toHaveCustomStep({ tag: 'sort:asc' })
175
+ expect(ctx.trace).toHaveCustomStep({ contains: 'pikachu' })
176
+ expect(ctx.trace).toHaveCustomStep({ resultContains: '25' })
177
+ expect(ctx.trace).toHaveCustomStep({ kind: 'rag', minTimes: 1, maxTimes: 2 })
178
+ ```
179
+
180
+ #### `toHavePromptWhere(config)`
181
+
182
+ Filter prompts, then assert additional constraints. Example: “all prompts containing A must also contain B”.
183
+
184
+ ```ts
185
+ // Prompts that contain "order" must also contain "confirmed"
186
+ expect(ctx.trace).toHavePromptWhere({
187
+ filterContains: 'order',
188
+ requireContains: 'confirmed',
189
+ })
190
+
191
+ // Prompts containing "retry" must NOT contain "cancel"
192
+ expect(ctx.trace).toHavePromptWhere({
193
+ filterContains: 'retry',
194
+ requireNotContains: 'cancel',
195
+ })
196
+
197
+ // And control counts on the filtered subset
198
+ expect(ctx.trace).toHavePromptWhere({
199
+ filterContains: 'order',
200
+ requireContains: 'confirmed',
201
+ minTimes: 1,
202
+ maxTimes: 3,
203
+ })
204
+
205
+ // Check a specific prompt position (1-based nth or 0-based index)
206
+ expect(ctx.trace).toHavePromptWhere({
207
+ filterContains: 'order',
208
+ requireContains: 'confirmed',
209
+ nth: 3, // the 3rd prompt among those containing "order"
210
+ })
211
+ ```
212
+
213
+ ---
214
+
215
+ ## Automatic AI Interception
216
+
217
+ The runner patches `globalThis.fetch` before tests run and automatically records LLM steps for calls to the following endpoints:
218
+
219
+ | Provider | Endpoints intercepted |
220
+ |---|---|
221
+ | **OpenAI** | `api.openai.com/v1/chat/completions`, `/v1/completions` |
222
+ | **Gemini** | `generativelanguage.googleapis.com/.../models/...:generateContent` |
223
+ | **Grok** (xAI) | `api.x.ai/v1/chat/completions` |
224
+
225
+ Each intercepted call records `model`, `provider`, `prompt`, and `completion` into `ctx.trace` automatically. Your workflow code needs no changes.
226
+
227
+ ```ts
228
+ aiTest('user lookup flow', async (ctx) => {
229
+ // This makes a real OpenAI call — intercepted automatically
230
+ await myWorkflow.run('Find all active users')
231
+
232
+ // Works without any ctx.trace.recordLLMStep() in your workflow
233
+ expect(ctx.trace).toHaveLLMStep({ promptContains: 'Find all active users' })
234
+ expect(ctx.trace).toHaveLLMStep({ provider: 'openai' })
235
+ })
236
+ ```
237
+
238
+ **Streaming:** When `stream: true` is set on a request, the completion is recorded as `"(streamed)"` — the prompt and model are still captured.
239
+
240
+ **Libraries using `https.request` directly** (older versions of some SDKs) are not covered by fetch interception. Use manual `ctx.trace.recordLLMStep()` for those.
241
+
242
+ ### Recording flow steps without passing `ctx.trace` (AsyncLocalStorage)
243
+
244
+ 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.
245
+
246
+ ```ts
247
+ // In your test
248
+ import { setCurrentTrace } from 'elasticdash-test'
249
+
250
+ aiTest('flow test', async (ctx) => {
251
+ setCurrentTrace(ctx.trace) // bind the trace to the current async context
252
+ await runFlowWithoutTraceArg() // your existing code
253
+ // assertions
254
+ expect(ctx.trace).toHaveCustomStep({ kind: 'rag', name: 'pokemon-search' })
255
+ })
256
+
257
+ // In your app/flow code (called during the test)
258
+ import { getCurrentTrace } from 'elasticdash-test'
259
+
260
+ function runFlowWithoutTraceArg() {
261
+ const trace = getCurrentTrace()
262
+ trace?.recordCustomStep({
263
+ kind: 'rag',
264
+ name: 'pokemon-search',
265
+ payload: { query: 'pikachu attack' },
266
+ result: { ids: [25] },
267
+ tags: ['source:db', 'sort:asc'],
268
+ })
269
+ }
270
+ ```
271
+
272
+ Notes:
273
+ - Works per async context; if you spawn detached work (child processes/independent workers), pass `trace` explicitly there.
274
+ - Still compatible with manual DI: you can continue passing `ctx.trace` explicitly if you prefer.
275
+
128
276
  ---
129
277
 
130
278
  ## Configuration
@@ -182,6 +330,8 @@ src/
182
330
  context.ts TraceHandle, AITestContext, RunnerHooks scaffold
183
331
  matchers/
184
332
  index.ts Custom expect matchers
333
+ interceptors/
334
+ ai-interceptor.ts Automatic fetch interceptor for OpenAI / Gemini / Grok
185
335
  ```
186
336
 
187
337
  ---
@@ -189,11 +339,15 @@ src/
189
339
  ## Programmatic API
190
340
 
191
341
  ```ts
192
- import { runFiles, reportResults, registerMatchers } from 'elasticdash-test'
342
+ import { runFiles, reportResults, registerMatchers, installAIInterceptor, uninstallAIInterceptor } from 'elasticdash-test'
193
343
 
194
344
  registerMatchers()
345
+ installAIInterceptor() // patch globalThis.fetch for automatic LLM tracing
346
+
195
347
  const results = await runFiles(['./tests/flow.ai.test.ts'])
196
348
  reportResults(results)
349
+
350
+ uninstallAIInterceptor() // restore original fetch when done
197
351
  ```
198
352
 
199
353
  ---
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.2');
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,8 +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
+ export { expect } from './matchers/index.js';
4
5
  export { runFiles } from './runner.js';
5
6
  export { reportResults } from './reporter.js';
7
+ export { installAIInterceptor, uninstallAIInterceptor } from './interceptors/ai-interceptor.js';
6
8
  export type { TestResult, FileResult, RunnerOptions } from './runner.js';
7
- 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';
8
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,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,7 +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
+ export { expect } from './matchers/index.js';
5
6
  export { runFiles } from './runner.js';
6
7
  export { reportResults } from './reporter.js';
8
+ export { installAIInterceptor, uninstallAIInterceptor } from './interceptors/ai-interceptor.js';
7
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,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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"}