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 +204 -8
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +25 -1
- package/dist/cli.js.map +1 -1
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/registry.js +19 -6
- package/dist/core/registry.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/interceptors/ai-interceptor.d.ts +10 -0
- package/dist/interceptors/ai-interceptor.d.ts.map +1 -0
- package/dist/interceptors/ai-interceptor.js +157 -0
- package/dist/interceptors/ai-interceptor.js.map +1 -0
- package/dist/matchers/index.d.ts +69 -2
- package/dist/matchers/index.d.ts.map +1 -1
- package/dist/matchers/index.js +522 -23
- package/dist/matchers/index.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +37 -2
- package/dist/runner.js.map +1 -1
- package/dist/trace-adapter/context.d.ts +18 -1
- package/dist/trace-adapter/context.d.ts.map +1 -1
- package/dist/trace-adapter/context.js +22 -0
- package/dist/trace-adapter/context.js.map +1 -1
- package/package.json +4 -3
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
|
-
-
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
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
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(
|
|
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":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,MAAM,WAAW,CAAA;AAC1B,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAQ7C,KAAK,UAAU,UAAU,CAAC,GAAW;IACnC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,uBAAuB,CAAC,CAAA;IAC1D,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,uBAAuB,CAAC,CAAA;IAE5D,KAAK,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,CAAC;QAC3C,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;YAClB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;YAC/C,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAsB,CAAA;QACjD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,CAAA;AACX,CAAC;AAED,yBAAyB;AACzB,KAAK,UAAU,iBAAiB,CAAC,QAAkB,EAAE,GAAW;IAC9D,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IACzD,OAAO,KAAK,CAAC,IAAI,EAAE,CAAA;AACrB,CAAC;AAED,oBAAoB;AACpB,KAAK,UAAU,SAAS;
|
|
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;
|
|
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"}
|
package/dist/core/registry.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
16
|
+
globalThis[REGISTRY_KEY] = createEmptyRegistry();
|
|
17
|
+
console.log('[elasticdash] clearRegistry called. Registry reset.');
|
|
11
18
|
}
|
|
12
19
|
export function getRegistry() {
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
+
const registry = getGlobalRegistry();
|
|
31
|
+
registry.beforeAllHooks.push(fn);
|
|
20
32
|
}
|
|
21
33
|
export function afterAll(fn) {
|
|
22
|
-
|
|
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,
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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;
|
|
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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"}
|