@copilotkit/aimock 1.22.1 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +22 -0
- package/README.md +10 -10
- package/dist/agui-types.d.cts.map +1 -1
- package/dist/cli.cjs +7 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +7 -1
- package/dist/cli.js.map +1 -1
- package/dist/fixture-loader.cjs +2 -1
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +2 -1
- package/dist/fixture-loader.js.map +1 -1
- package/dist/helpers.cjs +1 -0
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +1 -0
- package/dist/helpers.js.map +1 -1
- package/dist/model-utils.cjs +11 -0
- package/dist/model-utils.cjs.map +1 -0
- package/dist/model-utils.js +10 -0
- package/dist/model-utils.js.map +1 -0
- package/dist/recorder.cjs +19 -4
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +19 -4
- package/dist/recorder.js.map +1 -1
- package/dist/router.cjs +7 -3
- package/dist/router.cjs.map +1 -1
- package/dist/router.js +7 -3
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +1 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/types.d.cts +17 -2
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +17 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/ws-gemini-live.d.cts +2 -2
- package/dist/ws-realtime.cjs +577 -215
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts +2 -2
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +577 -215
- package/dist/ws-realtime.js.map +1 -1
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"source": {
|
|
10
10
|
"source": "npm",
|
|
11
11
|
"package": "@copilotkit/aimock",
|
|
12
|
-
"version": "^1.
|
|
12
|
+
"version": "^1.23.0"
|
|
13
13
|
},
|
|
14
14
|
"description": "Fixture authoring skill for @copilotkit/aimock — LLM, multimedia (image/TTS/transcription/video), MCP, A2A, AG-UI, vector, embeddings, structured output, sequential responses, streaming physics, record/replay, agent loop patterns, and debugging"
|
|
15
15
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.23.0] - 2026-05-13
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Model-aware fixture recording** — recorded fixtures now include the model name in match criteria, preventing collisions when an app makes multiple LLM calls with the same user message but different models. Model names are normalized by stripping date/version suffixes (e.g., `claude-opus-4-20250514` → `claude-opus-4`) so fixtures survive version bumps. Disable with `recordFullModelVersion: true`. ([#185](https://github.com/CopilotKit/aimock/issues/185))
|
|
10
|
+
- **Drift detection metadata** — recorded fixtures include `systemHash` and `toolsHash` in a `metadata` block for detecting system prompt or tool definition changes since recording.
|
|
11
|
+
- **Prefix model matching** — fixture router uses `startsWith` for string model matching, so `model: "claude-opus-4"` matches any `claude-opus-4-*` version.
|
|
12
|
+
- **GA Realtime protocol migration with Beta compatibility shim** — handler emits GA event names natively; `sendEvent()` wrapper translates back for Beta clients detected via `OpenAI-Beta` header. Default model changed to `gpt-realtime-2`.
|
|
13
|
+
- **GA Realtime models** — `gpt-realtime`, `gpt-realtime-2`, `gpt-realtime-1.5`, `gpt-realtime-mini` (and dated snapshots). Transcription/translation sessions use `gpt-4o-transcribe`, `gpt-4o-mini-transcribe`, or `whisper-1`.
|
|
14
|
+
- **Transcription and translation session types** — dedicated session configurations for translation and transcription workloads on the Realtime API.
|
|
15
|
+
- **Image input support** — Realtime sessions accept image content parts alongside text and audio.
|
|
16
|
+
- **Commentary phase** — Realtime handler supports the GA commentary phase for model-generated annotations.
|
|
17
|
+
- **`conversation.item.done` and `response.cancel` events** — new GA Realtime event types for item completion tracking and response cancellation.
|
|
18
|
+
- **Endpoint type routing for Realtime** — router distinguishes GA vs Beta Realtime endpoints for fixture matching.
|
|
19
|
+
- **Drift detection for GA Realtime** — drift test suite extended with GA protocol shapes, Beta conformance shapes, and three-way triangulation.
|
|
20
|
+
|
|
21
|
+
### Tests
|
|
22
|
+
|
|
23
|
+
- **73 GA Realtime integration tests** — comprehensive test coverage for all GA event types, Beta compatibility, session management, model routing, image input, translate/whisper, commentary, and cancellation.
|
|
24
|
+
- **GA and Beta Realtime conformance suites** — API conformance tests validating event shapes against both GA and Beta protocol specs.
|
|
25
|
+
- **GA Realtime drift detection** — SDK shape tests and provider triangulation for the GA Realtime protocol.
|
|
26
|
+
|
|
5
27
|
## [1.22.1] - 2026-05-12
|
|
6
28
|
|
|
7
29
|
### Fixed
|
package/README.md
CHANGED
|
@@ -34,14 +34,14 @@ await mock.stop();
|
|
|
34
34
|
|
|
35
35
|
aimock mocks everything your AI app talks to:
|
|
36
36
|
|
|
37
|
-
| Tool | What it mocks
|
|
38
|
-
| -------------- |
|
|
39
|
-
| **LLMock** | OpenAI (Chat/Responses/Realtime), Claude, Gemini (REST/Live/Interactions), Bedrock, Azure, Vertex AI, Ollama, Cohere | [Providers](https://aimock.copilotkit.dev/docs) |
|
|
40
|
-
| **MCPMock** | MCP tools, resources, prompts with session management
|
|
41
|
-
| **A2AMock** | Agent-to-agent protocol with SSE streaming
|
|
42
|
-
| **AGUIMock** | AG-UI agent-to-UI event streams for frontend testing
|
|
43
|
-
| **VectorMock** | Pinecone, Qdrant, ChromaDB compatible endpoints
|
|
44
|
-
| **Services** | Tavily search, Cohere rerank, OpenAI moderation
|
|
37
|
+
| Tool | What it mocks | Docs |
|
|
38
|
+
| -------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
|
|
39
|
+
| **LLMock** | OpenAI (Chat/Responses/Realtime GA+Beta), Claude, Gemini (REST/Live/Interactions), Bedrock, Azure, Vertex AI, Ollama, Cohere | [Providers](https://aimock.copilotkit.dev/docs) |
|
|
40
|
+
| **MCPMock** | MCP tools, resources, prompts with session management | [MCP](https://aimock.copilotkit.dev/mcp-mock) |
|
|
41
|
+
| **A2AMock** | Agent-to-agent protocol with SSE streaming | [A2A](https://aimock.copilotkit.dev/a2a-mock) |
|
|
42
|
+
| **AGUIMock** | AG-UI agent-to-UI event streams for frontend testing | [AG-UI](https://aimock.copilotkit.dev/agui-mock) |
|
|
43
|
+
| **VectorMock** | Pinecone, Qdrant, ChromaDB compatible endpoints | [Vector](https://aimock.copilotkit.dev/vector-mock) |
|
|
44
|
+
| **Services** | Tavily search, Cohere rerank, OpenAI moderation | [Services](https://aimock.copilotkit.dev/services) |
|
|
45
45
|
|
|
46
46
|
Run them all on one port with `npx @copilotkit/aimock --config aimock.json`, or use the programmatic API to compose exactly what you need.
|
|
47
47
|
|
|
@@ -49,14 +49,14 @@ Run them all on one port with `npx @copilotkit/aimock --config aimock.json`, or
|
|
|
49
49
|
|
|
50
50
|
- **[Record & Replay](https://aimock.copilotkit.dev/record-replay)** — Proxy real APIs, save as fixtures, replay deterministically forever
|
|
51
51
|
- **[Multi-turn Conversations](https://aimock.copilotkit.dev/multi-turn)** — Record and replay multi-turn traces with tool rounds; match distinct turns via `turnIndex`, `hasToolResult`, `toolCallId`, `sequenceIndex`, `systemMessage` (gate on host-supplied agent context), or custom predicates
|
|
52
|
-
- **[12 LLM Providers](https://aimock.copilotkit.dev/docs)** — OpenAI Chat, OpenAI Responses, OpenAI Realtime, Claude, Gemini, Gemini Live, Gemini Interactions, Azure, Bedrock, Vertex AI, Ollama, Cohere — full streaming support
|
|
52
|
+
- **[12 LLM Providers](https://aimock.copilotkit.dev/docs)** — OpenAI Chat, OpenAI Responses, OpenAI Realtime (GA + Beta shim), Claude, Gemini, Gemini Live, Gemini Interactions, Azure, Bedrock, Vertex AI, Ollama, Cohere — full streaming support
|
|
53
53
|
- **Multimedia APIs** — [image generation](https://aimock.copilotkit.dev/images) (DALL-E, Imagen), [text-to-speech](https://aimock.copilotkit.dev/speech), [audio transcription](https://aimock.copilotkit.dev/transcription), [video generation](https://aimock.copilotkit.dev/video)
|
|
54
54
|
- **[MCP](https://aimock.copilotkit.dev/mcp-mock) / [A2A](https://aimock.copilotkit.dev/a2a-mock) / [AG-UI](https://aimock.copilotkit.dev/agui-mock) / [Vector](https://aimock.copilotkit.dev/vector-mock)** — Mock every protocol your AI agents use
|
|
55
55
|
- **[Chaos Testing](https://aimock.copilotkit.dev/chaos-testing)** — 500 errors, malformed JSON, mid-stream disconnects at any probability
|
|
56
56
|
- **Per-Request Strict Mode** — `X-AIMock-Strict` header overrides the server-level `--strict` flag per request (`true`/`1` = strict, `false`/`0` = lenient)
|
|
57
57
|
- **[Drift Detection](https://aimock.copilotkit.dev/drift-detection)** — Daily CI validation against real APIs
|
|
58
58
|
- **[Streaming Physics](https://aimock.copilotkit.dev/streaming-physics)** — Configurable `ttft`, `tps`, and `jitter`
|
|
59
|
-
- **[WebSocket APIs](https://aimock.copilotkit.dev/websocket)** — OpenAI Realtime, Responses WS, Gemini Live
|
|
59
|
+
- **[WebSocket APIs](https://aimock.copilotkit.dev/websocket)** — OpenAI Realtime (GA protocol with models: gpt-realtime, gpt-realtime-2, gpt-realtime-1.5, gpt-realtime-mini; transcription/translation via gpt-4o-transcribe, gpt-4o-mini-transcribe, whisper-1; image input; commentary phase), Responses WS, Gemini Live
|
|
60
60
|
- **[Prometheus Metrics](https://aimock.copilotkit.dev/metrics)** — Request counts, latencies, fixture match rates
|
|
61
61
|
- **[Docker + Helm](https://aimock.copilotkit.dev/docker)** — Container image and Helm chart for CI/CD
|
|
62
62
|
- **[Vitest & Jest Plugins](https://aimock.copilotkit.dev/test-plugins)** — Zero-config `useAimock()` with auto lifecycle and env patching
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agui-types.d.cts","names":[],"sources":["../src/agui-types.ts"],"sourcesContent":[],"mappings":";KAOY,aAAA;AAAA,UA6CK,aAAA,CA7CQ;EA6CR,IAAA,EACT,aADsB;EAUb,SAAA,CAAA,EAAA,MAAA;EAAoB,QAAA,CAAA,EAAA,OAAA;;AAAQ,UAA5B,mBAAA,SAA4B,aAAA,CAAA;EAAa,IAAA,EAAA,aAAA;EAQzC,QAAA,EAAA,MAAA;EAAqB,KAAA,EAAA,MAAA;aAK1B,CAAA,EAAA,MAAA;OALkC,CAAA,EAHpC,iBAGoC;;AAQ7B,UARA,oBAAA,SAA6B,aAQU,CAAA;EAMvC,IAAA,EAAA,cAAA;EAKA,QAAA,EAAA,MAAA;EAOL,KAAA,EAAA,MAAA;EAEA,MAAA,CAAA,EAAA,OAAA;EASK,OAAA,CAAA,EAhCL,sBAgC+B;;AAGnC,UAhCS,iBAAA,SAA0B,aAgCnC,CAAA;MAH2C,EAAA,WAAA;EAAa,OAAA,EAAA,MAAA;EAO/C,IAAA,CAAA,EAAA,MAAA;AAMjB;AAKiB,UAzCA,oBAAA,SAA6B,aAyCH,CAAA;EAAA,IAAA,EAAA,cAAA;UAGlC,EAAA,MAAA;;AAHuD,UApC/C,qBAAA,SAA8B,aAoCiB,CAAA;EAU/C,IAAA,EAAA,eAAA;EAOA,QAAA,EAAA,MAAA;AAMjB;AAKiB,KAzDL,mBAAA,GAyD4B,WAAQ,GAAA,QAAa,GAAA,WAAA,GAAA,MAAA;AAQ5C,KA/DL,eAAA,GA+D6B,
|
|
1
|
+
{"version":3,"file":"agui-types.d.cts","names":[],"sources":["../src/agui-types.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/cli.cjs
CHANGED
|
@@ -27,6 +27,7 @@ Options:
|
|
|
27
27
|
--validate-on-load Validate fixture schemas at startup
|
|
28
28
|
--metrics Enable Prometheus metrics at GET /metrics
|
|
29
29
|
--record Record mode: proxy unmatched requests and save fixtures
|
|
30
|
+
--record-full-model-version Record exact model version without date stripping (default: false)
|
|
30
31
|
--proxy-only Proxy mode: forward unmatched requests without saving
|
|
31
32
|
--strict Strict mode: fail on unmatched requests (overridable per-request via X-AIMock-Strict header)
|
|
32
33
|
--journal-max <n> Max request entries retained in memory (default: 1000, 0 = unbounded)
|
|
@@ -95,6 +96,10 @@ const { values } = (0, node_util.parseArgs)({
|
|
|
95
96
|
type: "boolean",
|
|
96
97
|
default: false
|
|
97
98
|
},
|
|
99
|
+
"record-full-model-version": {
|
|
100
|
+
type: "boolean",
|
|
101
|
+
default: false
|
|
102
|
+
},
|
|
98
103
|
"proxy-only": {
|
|
99
104
|
type: "boolean",
|
|
100
105
|
default: false
|
|
@@ -241,7 +246,8 @@ if (values.record || values["proxy-only"]) {
|
|
|
241
246
|
record = {
|
|
242
247
|
providers,
|
|
243
248
|
fixturePath: recordBaseIsUrl ? void 0 : (0, node_path.resolve)(recordBase, "recorded"),
|
|
244
|
-
proxyOnly: values["proxy-only"]
|
|
249
|
+
proxyOnly: values["proxy-only"],
|
|
250
|
+
recordFullModelVersion: values["record-full-model-version"]
|
|
245
251
|
};
|
|
246
252
|
}
|
|
247
253
|
let aguiMount;
|
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.cjs","names":["Logger","AGUIMock","resolveFixturesValue","loadFixturesFromDir","loadFixtureFile","validateFixtures","createServer","watchFixtures"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from \"node:util\";\nimport { statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { createServer } from \"./server.js\";\nimport { loadFixtureFile, loadFixturesFromDir, validateFixtures } from \"./fixture-loader.js\";\nimport { Logger, type LogLevel } from \"./logger.js\";\nimport { watchFixtures } from \"./watcher.js\";\nimport { AGUIMock } from \"./agui-mock.js\";\nimport { resolveFixturesValue } from \"./fixtures-remote.js\";\nimport type { Fixture, ChaosConfig, RecordConfig } from \"./types.js\";\n\nconst HELP = `\nUsage: aimock [options]\n\nOptions:\n -p, --port <number> Port to listen on (default: 4010)\n -h, --host <string> Host to bind to (default: 127.0.0.1)\n -f, --fixtures <value> Fixture source (repeatable). Accepts:\n - filesystem path to a directory or .json file (default: ./fixtures)\n - https:// or http:// URL to a .json fixture file\n -l, --latency <ms> Latency in ms between SSE chunks (default: 0)\n -c, --chunk-size <chars> Chunk size in characters (default: 20)\n -w, --watch Watch fixture path for changes and reload\n --log-level <level> Log verbosity: silent, warn, info, debug (default: info)\n --validate-on-load Validate fixture schemas at startup\n --metrics Enable Prometheus metrics at GET /metrics\n --record Record mode: proxy unmatched requests and save fixtures\n --proxy-only Proxy mode: forward unmatched requests without saving\n --strict Strict mode: fail on unmatched requests (overridable per-request via X-AIMock-Strict header)\n --journal-max <n> Max request entries retained in memory (default: 1000, 0 = unbounded)\n --fixture-counts-max <n> Max unique testIds retained in fixture match-count map (default: 500, 0 = unbounded)\n --provider-openai <url> Upstream URL for OpenAI (used with --record)\n --provider-anthropic <url> Upstream URL for Anthropic\n --provider-gemini <url> Upstream URL for Gemini\n --provider-vertexai <url> Upstream URL for Vertex AI\n --provider-bedrock <url> Upstream URL for Bedrock\n --provider-azure <url> Upstream URL for Azure OpenAI\n --provider-ollama <url> Upstream URL for Ollama\n --provider-cohere <url> Upstream URL for Cohere\n --agui-record Enable AG-UI recording (proxy unmatched AG-UI requests)\n --agui-upstream <url> Upstream AG-UI agent URL (used with --agui-record)\n --agui-proxy-only AG-UI proxy mode: forward without saving\n --chaos-drop <rate> Probability (0-1) of dropping requests with 500\n --chaos-malformed <rate> Probability (0-1) of returning malformed JSON\n --chaos-disconnect <rate> Probability (0-1) of destroying connection\n --help Show this help message\n`.trim();\n\nconst { values } = parseArgs({\n options: {\n port: { type: \"string\", short: \"p\", default: \"4010\" },\n host: { type: \"string\", short: \"h\", default: \"127.0.0.1\" },\n fixtures: { type: \"string\", short: \"f\", multiple: true },\n latency: { type: \"string\", short: \"l\", default: \"0\" },\n \"chunk-size\": { type: \"string\", short: \"c\", default: \"20\" },\n watch: { type: \"boolean\", short: \"w\", default: false },\n \"log-level\": { type: \"string\", default: \"info\" },\n \"validate-on-load\": { type: \"boolean\", default: false },\n metrics: { type: \"boolean\", default: false },\n record: { type: \"boolean\", default: false },\n \"proxy-only\": { type: \"boolean\", default: false },\n strict: { type: \"boolean\", default: false },\n \"provider-openai\": { type: \"string\" },\n \"provider-anthropic\": { type: \"string\" },\n \"provider-gemini\": { type: \"string\" },\n \"provider-vertexai\": { type: \"string\" },\n \"provider-bedrock\": { type: \"string\" },\n \"provider-azure\": { type: \"string\" },\n \"provider-ollama\": { type: \"string\" },\n \"provider-cohere\": { type: \"string\" },\n \"agui-record\": { type: \"boolean\", default: false },\n \"agui-upstream\": { type: \"string\" },\n \"agui-proxy-only\": { type: \"boolean\", default: false },\n \"chaos-drop\": { type: \"string\" },\n \"chaos-malformed\": { type: \"string\" },\n \"chaos-disconnect\": { type: \"string\" },\n \"journal-max\": { type: \"string\", default: \"1000\" },\n \"fixture-counts-max\": { type: \"string\", default: \"500\" },\n help: { type: \"boolean\", default: false },\n },\n strict: true,\n});\n\nif (values.help) {\n console.log(HELP);\n process.exit(0);\n}\n\nconst port = Number(values.port);\nconst host = values.host!;\nconst latency = Number(values.latency);\nconst chunkSize = Number(values[\"chunk-size\"]);\nconst fixtureValues: string[] =\n values.fixtures && values.fixtures.length > 0 ? values.fixtures : [\"./fixtures\"];\nconst watchMode = values.watch!;\nconst validateOnLoad = values[\"validate-on-load\"]!;\nconst logLevelStr = values[\"log-level\"]!;\n\nif (![\"silent\", \"warn\", \"info\", \"debug\"].includes(logLevelStr)) {\n console.error(`Invalid log-level: ${logLevelStr} (must be silent, warn, info, or debug)`);\n process.exit(1);\n}\nconst logLevel = logLevelStr as LogLevel;\n\nif (Number.isNaN(port) || port < 0 || port > 65535) {\n console.error(`Invalid port: ${values.port}`);\n process.exit(1);\n}\n\nif (Number.isNaN(latency) || latency < 0) {\n console.error(`Invalid latency: ${values.latency}`);\n process.exit(1);\n}\n\nif (Number.isNaN(chunkSize) || chunkSize < 1) {\n console.error(`Invalid chunk-size: ${values[\"chunk-size\"]}`);\n process.exit(1);\n}\n\nconst journalMax = Number(values[\"journal-max\"]);\nif (Number.isNaN(journalMax) || !Number.isInteger(journalMax) || journalMax < 0) {\n console.error(\n `Invalid journal-max: ${values[\"journal-max\"]} (must be a non-negative integer; 0 or omitted = unbounded)`,\n );\n process.exit(1);\n}\n\nconst fixtureCountsMaxStr = values[\"fixture-counts-max\"];\nconst fixtureCountsMax = Number(fixtureCountsMaxStr);\nif (Number.isNaN(fixtureCountsMax) || !Number.isInteger(fixtureCountsMax) || fixtureCountsMax < 0) {\n console.error(\n `Invalid fixture-counts-max: ${fixtureCountsMaxStr} (must be a non-negative integer; 0 = unbounded)`,\n );\n process.exit(1);\n}\n\nconst logger = new Logger(logLevel);\n\n// Parse chaos config from CLI flags\nlet chaos: ChaosConfig | undefined;\n{\n const dropStr = values[\"chaos-drop\"];\n const malformedStr = values[\"chaos-malformed\"];\n const disconnectStr = values[\"chaos-disconnect\"];\n\n if (dropStr !== undefined || malformedStr !== undefined || disconnectStr !== undefined) {\n chaos = {};\n if (dropStr !== undefined) {\n const val = parseFloat(dropStr);\n if (isNaN(val) || val < 0 || val > 1) {\n console.error(`Invalid chaos-drop: ${dropStr} (must be 0-1)`);\n process.exit(1);\n }\n chaos.dropRate = val;\n }\n if (malformedStr !== undefined) {\n const val = parseFloat(malformedStr);\n if (isNaN(val) || val < 0 || val > 1) {\n console.error(`Invalid chaos-malformed: ${malformedStr} (must be 0-1)`);\n process.exit(1);\n }\n chaos.malformedRate = val;\n }\n if (disconnectStr !== undefined) {\n const val = parseFloat(disconnectStr);\n if (isNaN(val) || val < 0 || val > 1) {\n console.error(`Invalid chaos-disconnect: ${disconnectStr} (must be 0-1)`);\n process.exit(1);\n }\n chaos.disconnectRate = val;\n }\n }\n}\n\n// Parse record/proxy config from CLI flags\nlet record: RecordConfig | undefined;\nif (values.record || values[\"proxy-only\"]) {\n const providers: RecordConfig[\"providers\"] = {};\n if (values[\"provider-openai\"]) providers.openai = values[\"provider-openai\"];\n if (values[\"provider-anthropic\"]) providers.anthropic = values[\"provider-anthropic\"];\n if (values[\"provider-gemini\"]) providers.gemini = values[\"provider-gemini\"];\n if (values[\"provider-vertexai\"]) providers.vertexai = values[\"provider-vertexai\"];\n if (values[\"provider-bedrock\"]) providers.bedrock = values[\"provider-bedrock\"];\n if (values[\"provider-azure\"]) providers.azure = values[\"provider-azure\"];\n if (values[\"provider-ollama\"]) providers.ollama = values[\"provider-ollama\"];\n if (values[\"provider-cohere\"]) providers.cohere = values[\"provider-cohere\"];\n\n if (Object.keys(providers).length === 0) {\n console.error(\n `Error: --${values[\"proxy-only\"] ? \"proxy-only\" : \"record\"} requires at least one --provider-* flag`,\n );\n process.exit(1);\n }\n\n // For --record, the first --fixtures value is the base path for the recording\n // destination and must be a local filesystem path — writing to a URL is not supported.\n // For --proxy-only, unmatched requests are forwarded without saving, so no writable\n // destination is required; URL-only --fixtures is valid in that mode.\n const recordBase = fixtureValues[0];\n const recordBaseIsUrl = /^https?:\\/\\//i.test(recordBase);\n if (values.record && recordBaseIsUrl) {\n console.error(\n `Error: --record requires a local --fixtures path for the recording destination; got URL ${recordBase}`,\n );\n process.exit(1);\n }\n record = {\n providers,\n // In proxy-only mode with only URL sources, fixturePath is never consumed\n // (recorder.ts skips disk writes when proxyOnly is set). Leave it undefined\n // rather than resolving a URL string as a filesystem path.\n fixturePath: recordBaseIsUrl ? undefined : resolve(recordBase, \"recorded\"),\n proxyOnly: values[\"proxy-only\"],\n };\n}\n\n// Parse AG-UI record/proxy config from CLI flags\nlet aguiMount: { path: string; handler: AGUIMock } | undefined;\nif (values[\"agui-record\"] || values[\"agui-proxy-only\"]) {\n if (!values[\"agui-upstream\"]) {\n console.error(\"Error: --agui-record/--agui-proxy-only requires --agui-upstream\");\n process.exit(1);\n }\n // --agui-record writes recorded AG-UI fixtures to disk, so a URL source is unsupported.\n // --agui-proxy-only forwards without saving, so URL-only --fixtures is valid.\n const aguiBase = fixtureValues[0];\n const aguiBaseIsUrl = /^https?:\\/\\//i.test(aguiBase);\n if (values[\"agui-record\"] && aguiBaseIsUrl) {\n console.error(\n `Error: --agui-record requires a local --fixtures path for the recording destination; got URL ${aguiBase}`,\n );\n process.exit(1);\n }\n const agui = new AGUIMock();\n agui.enableRecording({\n upstream: values[\"agui-upstream\"],\n // In proxy-only mode with a URL-only --fixtures, the AG-UI recorder never\n // writes to disk (see agui-recorder.ts). Leave fixturePath undefined rather\n // than resolving a URL as a filesystem path.\n fixturePath: aguiBaseIsUrl ? undefined : resolve(aguiBase, \"agui-recorded\"),\n proxyOnly: values[\"agui-proxy-only\"],\n });\n aguiMount = { path: \"/agui\", handler: agui };\n}\n\ninterface ResolvedFixtureSource {\n source: string;\n path: string;\n isDir: boolean;\n}\n\nasync function resolveAllFixtureSources(): Promise<ResolvedFixtureSource[]> {\n const resolved: ResolvedFixtureSource[] = [];\n for (const value of fixtureValues) {\n let local;\n try {\n local = await resolveFixturesValue(value, {\n validateOnLoad,\n logger,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to resolve --fixtures value \"${value}\": ${msg}`);\n process.exit(1);\n }\n if (!local.path) {\n // Remote fetch failed without validate-on-load and no cache — already warned; skip.\n continue;\n }\n try {\n const stat = statSync(local.path);\n resolved.push({ source: local.source, path: local.path, isDir: stat.isDirectory() });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n console.error(`Fixtures path not found: ${local.path}`);\n } else {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to load fixtures from ${local.path}: ${msg}`);\n }\n process.exit(1);\n }\n }\n return resolved;\n}\n\nfunction loadSource(source: ResolvedFixtureSource): Fixture[] {\n return source.isDir\n ? loadFixturesFromDir(source.path, logger)\n : loadFixtureFile(source.path, logger);\n}\n\nasync function main() {\n const sources = await resolveAllFixtureSources();\n\n const fixtures: Fixture[] = [];\n for (const src of sources) {\n fixtures.push(...loadSource(src));\n }\n\n if (fixtures.length === 0) {\n if (validateOnLoad || values.strict) {\n console.error(\"Error: No fixtures loaded and validation/strict mode is enabled — aborting.\");\n process.exit(1);\n }\n console.warn(\"Warning: No fixtures loaded. The server will return 404 for all requests.\");\n }\n\n const sourceLabel = sources.map((s) => s.source).join(\", \") || \"<none>\";\n logger.info(`Loaded ${fixtures.length} fixture(s) from ${sourceLabel}`);\n\n // Validate fixtures if requested\n if (validateOnLoad) {\n const results = validateFixtures(fixtures);\n const errors = results.filter((r) => r.severity === \"error\");\n const warnings = results.filter((r) => r.severity === \"warning\");\n\n for (const w of warnings) {\n logger.warn(`Fixture ${w.fixtureIndex}: ${w.message}`);\n }\n for (const e of errors) {\n logger.error(`Fixture ${e.fixtureIndex}: ${e.message}`);\n }\n\n if (errors.length > 0) {\n console.error(`Validation failed: ${errors.length} error(s), ${warnings.length} warning(s)`);\n process.exit(1);\n }\n }\n\n const mounts = aguiMount ? [aguiMount] : undefined;\n\n const instance = await createServer(\n fixtures,\n {\n port,\n host,\n latency,\n chunkSize,\n logLevel,\n chaos,\n metrics: values.metrics,\n record,\n strict: values.strict,\n journalMaxEntries: journalMax,\n fixtureCountsMaxTestIds: fixtureCountsMax,\n },\n mounts,\n );\n\n logger.info(`aimock server listening on ${instance.url}`);\n\n // Start file watcher if requested. Only the first local source is watched —\n // remote URL sources are fetched once at boot and are not monitored.\n let watcher: { close: () => void } | null = null;\n if (watchMode) {\n const primary = sources[0];\n if (!primary) {\n logger.warn(\"--watch requested but no resolvable fixture sources; skipping watcher\");\n } else {\n const loadFn = (): Fixture[] => loadSource(primary);\n watcher = watchFixtures(primary.path, fixtures, loadFn, {\n logger,\n validate: validateOnLoad,\n validateFn: validateFixtures,\n });\n logger.info(`Watching ${primary.path} for changes`);\n }\n }\n\n function shutdown() {\n logger.info(\"Shutting down...\");\n if (watcher) watcher.close();\n instance.server.close(() => {\n process.exit(0);\n });\n }\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n\nmain().catch((err) => {\n console.error(err);\n process.exit(1);\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.cjs","names":["Logger","AGUIMock","resolveFixturesValue","loadFixturesFromDir","loadFixtureFile","validateFixtures","createServer","watchFixtures"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from \"node:util\";\nimport { statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { createServer } from \"./server.js\";\nimport { loadFixtureFile, loadFixturesFromDir, validateFixtures } from \"./fixture-loader.js\";\nimport { Logger, type LogLevel } from \"./logger.js\";\nimport { watchFixtures } from \"./watcher.js\";\nimport { AGUIMock } from \"./agui-mock.js\";\nimport { resolveFixturesValue } from \"./fixtures-remote.js\";\nimport type { Fixture, ChaosConfig, RecordConfig } from \"./types.js\";\n\nconst HELP = `\nUsage: aimock [options]\n\nOptions:\n -p, --port <number> Port to listen on (default: 4010)\n -h, --host <string> Host to bind to (default: 127.0.0.1)\n -f, --fixtures <value> Fixture source (repeatable). Accepts:\n - filesystem path to a directory or .json file (default: ./fixtures)\n - https:// or http:// URL to a .json fixture file\n -l, --latency <ms> Latency in ms between SSE chunks (default: 0)\n -c, --chunk-size <chars> Chunk size in characters (default: 20)\n -w, --watch Watch fixture path for changes and reload\n --log-level <level> Log verbosity: silent, warn, info, debug (default: info)\n --validate-on-load Validate fixture schemas at startup\n --metrics Enable Prometheus metrics at GET /metrics\n --record Record mode: proxy unmatched requests and save fixtures\n --record-full-model-version Record exact model version without date stripping (default: false)\n --proxy-only Proxy mode: forward unmatched requests without saving\n --strict Strict mode: fail on unmatched requests (overridable per-request via X-AIMock-Strict header)\n --journal-max <n> Max request entries retained in memory (default: 1000, 0 = unbounded)\n --fixture-counts-max <n> Max unique testIds retained in fixture match-count map (default: 500, 0 = unbounded)\n --provider-openai <url> Upstream URL for OpenAI (used with --record)\n --provider-anthropic <url> Upstream URL for Anthropic\n --provider-gemini <url> Upstream URL for Gemini\n --provider-vertexai <url> Upstream URL for Vertex AI\n --provider-bedrock <url> Upstream URL for Bedrock\n --provider-azure <url> Upstream URL for Azure OpenAI\n --provider-ollama <url> Upstream URL for Ollama\n --provider-cohere <url> Upstream URL for Cohere\n --agui-record Enable AG-UI recording (proxy unmatched AG-UI requests)\n --agui-upstream <url> Upstream AG-UI agent URL (used with --agui-record)\n --agui-proxy-only AG-UI proxy mode: forward without saving\n --chaos-drop <rate> Probability (0-1) of dropping requests with 500\n --chaos-malformed <rate> Probability (0-1) of returning malformed JSON\n --chaos-disconnect <rate> Probability (0-1) of destroying connection\n --help Show this help message\n`.trim();\n\nconst { values } = parseArgs({\n options: {\n port: { type: \"string\", short: \"p\", default: \"4010\" },\n host: { type: \"string\", short: \"h\", default: \"127.0.0.1\" },\n fixtures: { type: \"string\", short: \"f\", multiple: true },\n latency: { type: \"string\", short: \"l\", default: \"0\" },\n \"chunk-size\": { type: \"string\", short: \"c\", default: \"20\" },\n watch: { type: \"boolean\", short: \"w\", default: false },\n \"log-level\": { type: \"string\", default: \"info\" },\n \"validate-on-load\": { type: \"boolean\", default: false },\n metrics: { type: \"boolean\", default: false },\n record: { type: \"boolean\", default: false },\n \"record-full-model-version\": { type: \"boolean\", default: false },\n \"proxy-only\": { type: \"boolean\", default: false },\n strict: { type: \"boolean\", default: false },\n \"provider-openai\": { type: \"string\" },\n \"provider-anthropic\": { type: \"string\" },\n \"provider-gemini\": { type: \"string\" },\n \"provider-vertexai\": { type: \"string\" },\n \"provider-bedrock\": { type: \"string\" },\n \"provider-azure\": { type: \"string\" },\n \"provider-ollama\": { type: \"string\" },\n \"provider-cohere\": { type: \"string\" },\n \"agui-record\": { type: \"boolean\", default: false },\n \"agui-upstream\": { type: \"string\" },\n \"agui-proxy-only\": { type: \"boolean\", default: false },\n \"chaos-drop\": { type: \"string\" },\n \"chaos-malformed\": { type: \"string\" },\n \"chaos-disconnect\": { type: \"string\" },\n \"journal-max\": { type: \"string\", default: \"1000\" },\n \"fixture-counts-max\": { type: \"string\", default: \"500\" },\n help: { type: \"boolean\", default: false },\n },\n strict: true,\n});\n\nif (values.help) {\n console.log(HELP);\n process.exit(0);\n}\n\nconst port = Number(values.port);\nconst host = values.host!;\nconst latency = Number(values.latency);\nconst chunkSize = Number(values[\"chunk-size\"]);\nconst fixtureValues: string[] =\n values.fixtures && values.fixtures.length > 0 ? values.fixtures : [\"./fixtures\"];\nconst watchMode = values.watch!;\nconst validateOnLoad = values[\"validate-on-load\"]!;\nconst logLevelStr = values[\"log-level\"]!;\n\nif (![\"silent\", \"warn\", \"info\", \"debug\"].includes(logLevelStr)) {\n console.error(`Invalid log-level: ${logLevelStr} (must be silent, warn, info, or debug)`);\n process.exit(1);\n}\nconst logLevel = logLevelStr as LogLevel;\n\nif (Number.isNaN(port) || port < 0 || port > 65535) {\n console.error(`Invalid port: ${values.port}`);\n process.exit(1);\n}\n\nif (Number.isNaN(latency) || latency < 0) {\n console.error(`Invalid latency: ${values.latency}`);\n process.exit(1);\n}\n\nif (Number.isNaN(chunkSize) || chunkSize < 1) {\n console.error(`Invalid chunk-size: ${values[\"chunk-size\"]}`);\n process.exit(1);\n}\n\nconst journalMax = Number(values[\"journal-max\"]);\nif (Number.isNaN(journalMax) || !Number.isInteger(journalMax) || journalMax < 0) {\n console.error(\n `Invalid journal-max: ${values[\"journal-max\"]} (must be a non-negative integer; 0 or omitted = unbounded)`,\n );\n process.exit(1);\n}\n\nconst fixtureCountsMaxStr = values[\"fixture-counts-max\"];\nconst fixtureCountsMax = Number(fixtureCountsMaxStr);\nif (Number.isNaN(fixtureCountsMax) || !Number.isInteger(fixtureCountsMax) || fixtureCountsMax < 0) {\n console.error(\n `Invalid fixture-counts-max: ${fixtureCountsMaxStr} (must be a non-negative integer; 0 = unbounded)`,\n );\n process.exit(1);\n}\n\nconst logger = new Logger(logLevel);\n\n// Parse chaos config from CLI flags\nlet chaos: ChaosConfig | undefined;\n{\n const dropStr = values[\"chaos-drop\"];\n const malformedStr = values[\"chaos-malformed\"];\n const disconnectStr = values[\"chaos-disconnect\"];\n\n if (dropStr !== undefined || malformedStr !== undefined || disconnectStr !== undefined) {\n chaos = {};\n if (dropStr !== undefined) {\n const val = parseFloat(dropStr);\n if (isNaN(val) || val < 0 || val > 1) {\n console.error(`Invalid chaos-drop: ${dropStr} (must be 0-1)`);\n process.exit(1);\n }\n chaos.dropRate = val;\n }\n if (malformedStr !== undefined) {\n const val = parseFloat(malformedStr);\n if (isNaN(val) || val < 0 || val > 1) {\n console.error(`Invalid chaos-malformed: ${malformedStr} (must be 0-1)`);\n process.exit(1);\n }\n chaos.malformedRate = val;\n }\n if (disconnectStr !== undefined) {\n const val = parseFloat(disconnectStr);\n if (isNaN(val) || val < 0 || val > 1) {\n console.error(`Invalid chaos-disconnect: ${disconnectStr} (must be 0-1)`);\n process.exit(1);\n }\n chaos.disconnectRate = val;\n }\n }\n}\n\n// Parse record/proxy config from CLI flags\nlet record: RecordConfig | undefined;\nif (values.record || values[\"proxy-only\"]) {\n const providers: RecordConfig[\"providers\"] = {};\n if (values[\"provider-openai\"]) providers.openai = values[\"provider-openai\"];\n if (values[\"provider-anthropic\"]) providers.anthropic = values[\"provider-anthropic\"];\n if (values[\"provider-gemini\"]) providers.gemini = values[\"provider-gemini\"];\n if (values[\"provider-vertexai\"]) providers.vertexai = values[\"provider-vertexai\"];\n if (values[\"provider-bedrock\"]) providers.bedrock = values[\"provider-bedrock\"];\n if (values[\"provider-azure\"]) providers.azure = values[\"provider-azure\"];\n if (values[\"provider-ollama\"]) providers.ollama = values[\"provider-ollama\"];\n if (values[\"provider-cohere\"]) providers.cohere = values[\"provider-cohere\"];\n\n if (Object.keys(providers).length === 0) {\n console.error(\n `Error: --${values[\"proxy-only\"] ? \"proxy-only\" : \"record\"} requires at least one --provider-* flag`,\n );\n process.exit(1);\n }\n\n // For --record, the first --fixtures value is the base path for the recording\n // destination and must be a local filesystem path — writing to a URL is not supported.\n // For --proxy-only, unmatched requests are forwarded without saving, so no writable\n // destination is required; URL-only --fixtures is valid in that mode.\n const recordBase = fixtureValues[0];\n const recordBaseIsUrl = /^https?:\\/\\//i.test(recordBase);\n if (values.record && recordBaseIsUrl) {\n console.error(\n `Error: --record requires a local --fixtures path for the recording destination; got URL ${recordBase}`,\n );\n process.exit(1);\n }\n record = {\n providers,\n // In proxy-only mode with only URL sources, fixturePath is never consumed\n // (recorder.ts skips disk writes when proxyOnly is set). Leave it undefined\n // rather than resolving a URL string as a filesystem path.\n fixturePath: recordBaseIsUrl ? undefined : resolve(recordBase, \"recorded\"),\n proxyOnly: values[\"proxy-only\"],\n recordFullModelVersion: values[\"record-full-model-version\"],\n };\n}\n\n// Parse AG-UI record/proxy config from CLI flags\nlet aguiMount: { path: string; handler: AGUIMock } | undefined;\nif (values[\"agui-record\"] || values[\"agui-proxy-only\"]) {\n if (!values[\"agui-upstream\"]) {\n console.error(\"Error: --agui-record/--agui-proxy-only requires --agui-upstream\");\n process.exit(1);\n }\n // --agui-record writes recorded AG-UI fixtures to disk, so a URL source is unsupported.\n // --agui-proxy-only forwards without saving, so URL-only --fixtures is valid.\n const aguiBase = fixtureValues[0];\n const aguiBaseIsUrl = /^https?:\\/\\//i.test(aguiBase);\n if (values[\"agui-record\"] && aguiBaseIsUrl) {\n console.error(\n `Error: --agui-record requires a local --fixtures path for the recording destination; got URL ${aguiBase}`,\n );\n process.exit(1);\n }\n const agui = new AGUIMock();\n agui.enableRecording({\n upstream: values[\"agui-upstream\"],\n // In proxy-only mode with a URL-only --fixtures, the AG-UI recorder never\n // writes to disk (see agui-recorder.ts). Leave fixturePath undefined rather\n // than resolving a URL as a filesystem path.\n fixturePath: aguiBaseIsUrl ? undefined : resolve(aguiBase, \"agui-recorded\"),\n proxyOnly: values[\"agui-proxy-only\"],\n });\n aguiMount = { path: \"/agui\", handler: agui };\n}\n\ninterface ResolvedFixtureSource {\n source: string;\n path: string;\n isDir: boolean;\n}\n\nasync function resolveAllFixtureSources(): Promise<ResolvedFixtureSource[]> {\n const resolved: ResolvedFixtureSource[] = [];\n for (const value of fixtureValues) {\n let local;\n try {\n local = await resolveFixturesValue(value, {\n validateOnLoad,\n logger,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to resolve --fixtures value \"${value}\": ${msg}`);\n process.exit(1);\n }\n if (!local.path) {\n // Remote fetch failed without validate-on-load and no cache — already warned; skip.\n continue;\n }\n try {\n const stat = statSync(local.path);\n resolved.push({ source: local.source, path: local.path, isDir: stat.isDirectory() });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n console.error(`Fixtures path not found: ${local.path}`);\n } else {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to load fixtures from ${local.path}: ${msg}`);\n }\n process.exit(1);\n }\n }\n return resolved;\n}\n\nfunction loadSource(source: ResolvedFixtureSource): Fixture[] {\n return source.isDir\n ? loadFixturesFromDir(source.path, logger)\n : loadFixtureFile(source.path, logger);\n}\n\nasync function main() {\n const sources = await resolveAllFixtureSources();\n\n const fixtures: Fixture[] = [];\n for (const src of sources) {\n fixtures.push(...loadSource(src));\n }\n\n if (fixtures.length === 0) {\n if (validateOnLoad || values.strict) {\n console.error(\"Error: No fixtures loaded and validation/strict mode is enabled — aborting.\");\n process.exit(1);\n }\n console.warn(\"Warning: No fixtures loaded. The server will return 404 for all requests.\");\n }\n\n const sourceLabel = sources.map((s) => s.source).join(\", \") || \"<none>\";\n logger.info(`Loaded ${fixtures.length} fixture(s) from ${sourceLabel}`);\n\n // Validate fixtures if requested\n if (validateOnLoad) {\n const results = validateFixtures(fixtures);\n const errors = results.filter((r) => r.severity === \"error\");\n const warnings = results.filter((r) => r.severity === \"warning\");\n\n for (const w of warnings) {\n logger.warn(`Fixture ${w.fixtureIndex}: ${w.message}`);\n }\n for (const e of errors) {\n logger.error(`Fixture ${e.fixtureIndex}: ${e.message}`);\n }\n\n if (errors.length > 0) {\n console.error(`Validation failed: ${errors.length} error(s), ${warnings.length} warning(s)`);\n process.exit(1);\n }\n }\n\n const mounts = aguiMount ? [aguiMount] : undefined;\n\n const instance = await createServer(\n fixtures,\n {\n port,\n host,\n latency,\n chunkSize,\n logLevel,\n chaos,\n metrics: values.metrics,\n record,\n strict: values.strict,\n journalMaxEntries: journalMax,\n fixtureCountsMaxTestIds: fixtureCountsMax,\n },\n mounts,\n );\n\n logger.info(`aimock server listening on ${instance.url}`);\n\n // Start file watcher if requested. Only the first local source is watched —\n // remote URL sources are fetched once at boot and are not monitored.\n let watcher: { close: () => void } | null = null;\n if (watchMode) {\n const primary = sources[0];\n if (!primary) {\n logger.warn(\"--watch requested but no resolvable fixture sources; skipping watcher\");\n } else {\n const loadFn = (): Fixture[] => loadSource(primary);\n watcher = watchFixtures(primary.path, fixtures, loadFn, {\n logger,\n validate: validateOnLoad,\n validateFn: validateFixtures,\n });\n logger.info(`Watching ${primary.path} for changes`);\n }\n }\n\n function shutdown() {\n logger.info(\"Shutting down...\");\n if (watcher) watcher.close();\n instance.server.close(() => {\n process.exit(0);\n });\n }\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n\nmain().catch((err) => {\n console.error(err);\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;;;;;AAYA,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoCX,MAAM;AAER,MAAM,EAAE,oCAAqB;CAC3B,SAAS;EACP,MAAM;GAAE,MAAM;GAAU,OAAO;GAAK,SAAS;GAAQ;EACrD,MAAM;GAAE,MAAM;GAAU,OAAO;GAAK,SAAS;GAAa;EAC1D,UAAU;GAAE,MAAM;GAAU,OAAO;GAAK,UAAU;GAAM;EACxD,SAAS;GAAE,MAAM;GAAU,OAAO;GAAK,SAAS;GAAK;EACrD,cAAc;GAAE,MAAM;GAAU,OAAO;GAAK,SAAS;GAAM;EAC3D,OAAO;GAAE,MAAM;GAAW,OAAO;GAAK,SAAS;GAAO;EACtD,aAAa;GAAE,MAAM;GAAU,SAAS;GAAQ;EAChD,oBAAoB;GAAE,MAAM;GAAW,SAAS;GAAO;EACvD,SAAS;GAAE,MAAM;GAAW,SAAS;GAAO;EAC5C,QAAQ;GAAE,MAAM;GAAW,SAAS;GAAO;EAC3C,6BAA6B;GAAE,MAAM;GAAW,SAAS;GAAO;EAChE,cAAc;GAAE,MAAM;GAAW,SAAS;GAAO;EACjD,QAAQ;GAAE,MAAM;GAAW,SAAS;GAAO;EAC3C,mBAAmB,EAAE,MAAM,UAAU;EACrC,sBAAsB,EAAE,MAAM,UAAU;EACxC,mBAAmB,EAAE,MAAM,UAAU;EACrC,qBAAqB,EAAE,MAAM,UAAU;EACvC,oBAAoB,EAAE,MAAM,UAAU;EACtC,kBAAkB,EAAE,MAAM,UAAU;EACpC,mBAAmB,EAAE,MAAM,UAAU;EACrC,mBAAmB,EAAE,MAAM,UAAU;EACrC,eAAe;GAAE,MAAM;GAAW,SAAS;GAAO;EAClD,iBAAiB,EAAE,MAAM,UAAU;EACnC,mBAAmB;GAAE,MAAM;GAAW,SAAS;GAAO;EACtD,cAAc,EAAE,MAAM,UAAU;EAChC,mBAAmB,EAAE,MAAM,UAAU;EACrC,oBAAoB,EAAE,MAAM,UAAU;EACtC,eAAe;GAAE,MAAM;GAAU,SAAS;GAAQ;EAClD,sBAAsB;GAAE,MAAM;GAAU,SAAS;GAAO;EACxD,MAAM;GAAE,MAAM;GAAW,SAAS;GAAO;EAC1C;CACD,QAAQ;CACT,CAAC;AAEF,IAAI,OAAO,MAAM;AACf,SAAQ,IAAI,KAAK;AACjB,SAAQ,KAAK,EAAE;;AAGjB,MAAM,OAAO,OAAO,OAAO,KAAK;AAChC,MAAM,OAAO,OAAO;AACpB,MAAM,UAAU,OAAO,OAAO,QAAQ;AACtC,MAAM,YAAY,OAAO,OAAO,cAAc;AAC9C,MAAM,gBACJ,OAAO,YAAY,OAAO,SAAS,SAAS,IAAI,OAAO,WAAW,CAAC,aAAa;AAClF,MAAM,YAAY,OAAO;AACzB,MAAM,iBAAiB,OAAO;AAC9B,MAAM,cAAc,OAAO;AAE3B,IAAI,CAAC;CAAC;CAAU;CAAQ;CAAQ;CAAQ,CAAC,SAAS,YAAY,EAAE;AAC9D,SAAQ,MAAM,sBAAsB,YAAY,yCAAyC;AACzF,SAAQ,KAAK,EAAE;;AAEjB,MAAM,WAAW;AAEjB,IAAI,OAAO,MAAM,KAAK,IAAI,OAAO,KAAK,OAAO,OAAO;AAClD,SAAQ,MAAM,iBAAiB,OAAO,OAAO;AAC7C,SAAQ,KAAK,EAAE;;AAGjB,IAAI,OAAO,MAAM,QAAQ,IAAI,UAAU,GAAG;AACxC,SAAQ,MAAM,oBAAoB,OAAO,UAAU;AACnD,SAAQ,KAAK,EAAE;;AAGjB,IAAI,OAAO,MAAM,UAAU,IAAI,YAAY,GAAG;AAC5C,SAAQ,MAAM,uBAAuB,OAAO,gBAAgB;AAC5D,SAAQ,KAAK,EAAE;;AAGjB,MAAM,aAAa,OAAO,OAAO,eAAe;AAChD,IAAI,OAAO,MAAM,WAAW,IAAI,CAAC,OAAO,UAAU,WAAW,IAAI,aAAa,GAAG;AAC/E,SAAQ,MACN,wBAAwB,OAAO,eAAe,6DAC/C;AACD,SAAQ,KAAK,EAAE;;AAGjB,MAAM,sBAAsB,OAAO;AACnC,MAAM,mBAAmB,OAAO,oBAAoB;AACpD,IAAI,OAAO,MAAM,iBAAiB,IAAI,CAAC,OAAO,UAAU,iBAAiB,IAAI,mBAAmB,GAAG;AACjG,SAAQ,MACN,+BAA+B,oBAAoB,kDACpD;AACD,SAAQ,KAAK,EAAE;;AAGjB,MAAM,SAAS,IAAIA,sBAAO,SAAS;AAGnC,IAAI;AACJ;CACE,MAAM,UAAU,OAAO;CACvB,MAAM,eAAe,OAAO;CAC5B,MAAM,gBAAgB,OAAO;AAE7B,KAAI,YAAY,UAAa,iBAAiB,UAAa,kBAAkB,QAAW;AACtF,UAAQ,EAAE;AACV,MAAI,YAAY,QAAW;GACzB,MAAM,MAAM,WAAW,QAAQ;AAC/B,OAAI,MAAM,IAAI,IAAI,MAAM,KAAK,MAAM,GAAG;AACpC,YAAQ,MAAM,uBAAuB,QAAQ,gBAAgB;AAC7D,YAAQ,KAAK,EAAE;;AAEjB,SAAM,WAAW;;AAEnB,MAAI,iBAAiB,QAAW;GAC9B,MAAM,MAAM,WAAW,aAAa;AACpC,OAAI,MAAM,IAAI,IAAI,MAAM,KAAK,MAAM,GAAG;AACpC,YAAQ,MAAM,4BAA4B,aAAa,gBAAgB;AACvE,YAAQ,KAAK,EAAE;;AAEjB,SAAM,gBAAgB;;AAExB,MAAI,kBAAkB,QAAW;GAC/B,MAAM,MAAM,WAAW,cAAc;AACrC,OAAI,MAAM,IAAI,IAAI,MAAM,KAAK,MAAM,GAAG;AACpC,YAAQ,MAAM,6BAA6B,cAAc,gBAAgB;AACzE,YAAQ,KAAK,EAAE;;AAEjB,SAAM,iBAAiB;;;;AAM7B,IAAI;AACJ,IAAI,OAAO,UAAU,OAAO,eAAe;CACzC,MAAM,YAAuC,EAAE;AAC/C,KAAI,OAAO,mBAAoB,WAAU,SAAS,OAAO;AACzD,KAAI,OAAO,sBAAuB,WAAU,YAAY,OAAO;AAC/D,KAAI,OAAO,mBAAoB,WAAU,SAAS,OAAO;AACzD,KAAI,OAAO,qBAAsB,WAAU,WAAW,OAAO;AAC7D,KAAI,OAAO,oBAAqB,WAAU,UAAU,OAAO;AAC3D,KAAI,OAAO,kBAAmB,WAAU,QAAQ,OAAO;AACvD,KAAI,OAAO,mBAAoB,WAAU,SAAS,OAAO;AACzD,KAAI,OAAO,mBAAoB,WAAU,SAAS,OAAO;AAEzD,KAAI,OAAO,KAAK,UAAU,CAAC,WAAW,GAAG;AACvC,UAAQ,MACN,YAAY,OAAO,gBAAgB,eAAe,SAAS,0CAC5D;AACD,UAAQ,KAAK,EAAE;;CAOjB,MAAM,aAAa,cAAc;CACjC,MAAM,kBAAkB,gBAAgB,KAAK,WAAW;AACxD,KAAI,OAAO,UAAU,iBAAiB;AACpC,UAAQ,MACN,2FAA2F,aAC5F;AACD,UAAQ,KAAK,EAAE;;AAEjB,UAAS;EACP;EAIA,aAAa,kBAAkB,gCAAoB,YAAY,WAAW;EAC1E,WAAW,OAAO;EAClB,wBAAwB,OAAO;EAChC;;AAIH,IAAI;AACJ,IAAI,OAAO,kBAAkB,OAAO,oBAAoB;AACtD,KAAI,CAAC,OAAO,kBAAkB;AAC5B,UAAQ,MAAM,kEAAkE;AAChF,UAAQ,KAAK,EAAE;;CAIjB,MAAM,WAAW,cAAc;CAC/B,MAAM,gBAAgB,gBAAgB,KAAK,SAAS;AACpD,KAAI,OAAO,kBAAkB,eAAe;AAC1C,UAAQ,MACN,gGAAgG,WACjG;AACD,UAAQ,KAAK,EAAE;;CAEjB,MAAM,OAAO,IAAIC,4BAAU;AAC3B,MAAK,gBAAgB;EACnB,UAAU,OAAO;EAIjB,aAAa,gBAAgB,gCAAoB,UAAU,gBAAgB;EAC3E,WAAW,OAAO;EACnB,CAAC;AACF,aAAY;EAAE,MAAM;EAAS,SAAS;EAAM;;AAS9C,eAAe,2BAA6D;CAC1E,MAAM,WAAoC,EAAE;AAC5C,MAAK,MAAM,SAAS,eAAe;EACjC,IAAI;AACJ,MAAI;AACF,WAAQ,MAAMC,6CAAqB,OAAO;IACxC;IACA;IACD,CAAC;WACK,KAAK;GACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,WAAQ,MAAM,uCAAuC,MAAM,KAAK,MAAM;AACtE,WAAQ,KAAK,EAAE;;AAEjB,MAAI,CAAC,MAAM,KAET;AAEF,MAAI;GACF,MAAM,6BAAgB,MAAM,KAAK;AACjC,YAAS,KAAK;IAAE,QAAQ,MAAM;IAAQ,MAAM,MAAM;IAAM,OAAO,KAAK,aAAa;IAAE,CAAC;WAC7E,KAAK;AACZ,OAAK,IAA8B,SAAS,SAC1C,SAAQ,MAAM,4BAA4B,MAAM,OAAO;QAClD;IACL,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,YAAQ,MAAM,gCAAgC,MAAM,KAAK,IAAI,MAAM;;AAErE,WAAQ,KAAK,EAAE;;;AAGnB,QAAO;;AAGT,SAAS,WAAW,QAA0C;AAC5D,QAAO,OAAO,QACVC,2CAAoB,OAAO,MAAM,OAAO,GACxCC,uCAAgB,OAAO,MAAM,OAAO;;AAG1C,eAAe,OAAO;CACpB,MAAM,UAAU,MAAM,0BAA0B;CAEhD,MAAM,WAAsB,EAAE;AAC9B,MAAK,MAAM,OAAO,QAChB,UAAS,KAAK,GAAG,WAAW,IAAI,CAAC;AAGnC,KAAI,SAAS,WAAW,GAAG;AACzB,MAAI,kBAAkB,OAAO,QAAQ;AACnC,WAAQ,MAAM,8EAA8E;AAC5F,WAAQ,KAAK,EAAE;;AAEjB,UAAQ,KAAK,4EAA4E;;CAG3F,MAAM,cAAc,QAAQ,KAAK,MAAM,EAAE,OAAO,CAAC,KAAK,KAAK,IAAI;AAC/D,QAAO,KAAK,UAAU,SAAS,OAAO,mBAAmB,cAAc;AAGvE,KAAI,gBAAgB;EAClB,MAAM,UAAUC,wCAAiB,SAAS;EAC1C,MAAM,SAAS,QAAQ,QAAQ,MAAM,EAAE,aAAa,QAAQ;EAC5D,MAAM,WAAW,QAAQ,QAAQ,MAAM,EAAE,aAAa,UAAU;AAEhE,OAAK,MAAM,KAAK,SACd,QAAO,KAAK,WAAW,EAAE,aAAa,IAAI,EAAE,UAAU;AAExD,OAAK,MAAM,KAAK,OACd,QAAO,MAAM,WAAW,EAAE,aAAa,IAAI,EAAE,UAAU;AAGzD,MAAI,OAAO,SAAS,GAAG;AACrB,WAAQ,MAAM,sBAAsB,OAAO,OAAO,aAAa,SAAS,OAAO,aAAa;AAC5F,WAAQ,KAAK,EAAE;;;CAInB,MAAM,SAAS,YAAY,CAAC,UAAU,GAAG;CAEzC,MAAM,WAAW,MAAMC,4BACrB,UACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA,SAAS,OAAO;EAChB;EACA,QAAQ,OAAO;EACf,mBAAmB;EACnB,yBAAyB;EAC1B,EACD,OACD;AAED,QAAO,KAAK,8BAA8B,SAAS,MAAM;CAIzD,IAAI,UAAwC;AAC5C,KAAI,WAAW;EACb,MAAM,UAAU,QAAQ;AACxB,MAAI,CAAC,QACH,QAAO,KAAK,wEAAwE;OAC/E;GACL,MAAM,eAA0B,WAAW,QAAQ;AACnD,aAAUC,8BAAc,QAAQ,MAAM,UAAU,QAAQ;IACtD;IACA,UAAU;IACV,YAAYF;IACb,CAAC;AACF,UAAO,KAAK,YAAY,QAAQ,KAAK,cAAc;;;CAIvD,SAAS,WAAW;AAClB,SAAO,KAAK,mBAAmB;AAC/B,MAAI,QAAS,SAAQ,OAAO;AAC5B,WAAS,OAAO,YAAY;AAC1B,WAAQ,KAAK,EAAE;IACf;;AAGJ,SAAQ,GAAG,UAAU,SAAS;AAC9B,SAAQ,GAAG,WAAW,SAAS;;AAGjC,MAAM,CAAC,OAAO,QAAQ;AACpB,SAAQ,MAAM,IAAI;AAClB,SAAQ,KAAK,EAAE;EACf"}
|
package/dist/cli.js
CHANGED
|
@@ -26,6 +26,7 @@ Options:
|
|
|
26
26
|
--validate-on-load Validate fixture schemas at startup
|
|
27
27
|
--metrics Enable Prometheus metrics at GET /metrics
|
|
28
28
|
--record Record mode: proxy unmatched requests and save fixtures
|
|
29
|
+
--record-full-model-version Record exact model version without date stripping (default: false)
|
|
29
30
|
--proxy-only Proxy mode: forward unmatched requests without saving
|
|
30
31
|
--strict Strict mode: fail on unmatched requests (overridable per-request via X-AIMock-Strict header)
|
|
31
32
|
--journal-max <n> Max request entries retained in memory (default: 1000, 0 = unbounded)
|
|
@@ -94,6 +95,10 @@ const { values } = parseArgs({
|
|
|
94
95
|
type: "boolean",
|
|
95
96
|
default: false
|
|
96
97
|
},
|
|
98
|
+
"record-full-model-version": {
|
|
99
|
+
type: "boolean",
|
|
100
|
+
default: false
|
|
101
|
+
},
|
|
97
102
|
"proxy-only": {
|
|
98
103
|
type: "boolean",
|
|
99
104
|
default: false
|
|
@@ -240,7 +245,8 @@ if (values.record || values["proxy-only"]) {
|
|
|
240
245
|
record = {
|
|
241
246
|
providers,
|
|
242
247
|
fixturePath: recordBaseIsUrl ? void 0 : resolve(recordBase, "recorded"),
|
|
243
|
-
proxyOnly: values["proxy-only"]
|
|
248
|
+
proxyOnly: values["proxy-only"],
|
|
249
|
+
recordFullModelVersion: values["record-full-model-version"]
|
|
244
250
|
};
|
|
245
251
|
}
|
|
246
252
|
let aguiMount;
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from \"node:util\";\nimport { statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { createServer } from \"./server.js\";\nimport { loadFixtureFile, loadFixturesFromDir, validateFixtures } from \"./fixture-loader.js\";\nimport { Logger, type LogLevel } from \"./logger.js\";\nimport { watchFixtures } from \"./watcher.js\";\nimport { AGUIMock } from \"./agui-mock.js\";\nimport { resolveFixturesValue } from \"./fixtures-remote.js\";\nimport type { Fixture, ChaosConfig, RecordConfig } from \"./types.js\";\n\nconst HELP = `\nUsage: aimock [options]\n\nOptions:\n -p, --port <number> Port to listen on (default: 4010)\n -h, --host <string> Host to bind to (default: 127.0.0.1)\n -f, --fixtures <value> Fixture source (repeatable). Accepts:\n - filesystem path to a directory or .json file (default: ./fixtures)\n - https:// or http:// URL to a .json fixture file\n -l, --latency <ms> Latency in ms between SSE chunks (default: 0)\n -c, --chunk-size <chars> Chunk size in characters (default: 20)\n -w, --watch Watch fixture path for changes and reload\n --log-level <level> Log verbosity: silent, warn, info, debug (default: info)\n --validate-on-load Validate fixture schemas at startup\n --metrics Enable Prometheus metrics at GET /metrics\n --record Record mode: proxy unmatched requests and save fixtures\n --proxy-only Proxy mode: forward unmatched requests without saving\n --strict Strict mode: fail on unmatched requests (overridable per-request via X-AIMock-Strict header)\n --journal-max <n> Max request entries retained in memory (default: 1000, 0 = unbounded)\n --fixture-counts-max <n> Max unique testIds retained in fixture match-count map (default: 500, 0 = unbounded)\n --provider-openai <url> Upstream URL for OpenAI (used with --record)\n --provider-anthropic <url> Upstream URL for Anthropic\n --provider-gemini <url> Upstream URL for Gemini\n --provider-vertexai <url> Upstream URL for Vertex AI\n --provider-bedrock <url> Upstream URL for Bedrock\n --provider-azure <url> Upstream URL for Azure OpenAI\n --provider-ollama <url> Upstream URL for Ollama\n --provider-cohere <url> Upstream URL for Cohere\n --agui-record Enable AG-UI recording (proxy unmatched AG-UI requests)\n --agui-upstream <url> Upstream AG-UI agent URL (used with --agui-record)\n --agui-proxy-only AG-UI proxy mode: forward without saving\n --chaos-drop <rate> Probability (0-1) of dropping requests with 500\n --chaos-malformed <rate> Probability (0-1) of returning malformed JSON\n --chaos-disconnect <rate> Probability (0-1) of destroying connection\n --help Show this help message\n`.trim();\n\nconst { values } = parseArgs({\n options: {\n port: { type: \"string\", short: \"p\", default: \"4010\" },\n host: { type: \"string\", short: \"h\", default: \"127.0.0.1\" },\n fixtures: { type: \"string\", short: \"f\", multiple: true },\n latency: { type: \"string\", short: \"l\", default: \"0\" },\n \"chunk-size\": { type: \"string\", short: \"c\", default: \"20\" },\n watch: { type: \"boolean\", short: \"w\", default: false },\n \"log-level\": { type: \"string\", default: \"info\" },\n \"validate-on-load\": { type: \"boolean\", default: false },\n metrics: { type: \"boolean\", default: false },\n record: { type: \"boolean\", default: false },\n \"proxy-only\": { type: \"boolean\", default: false },\n strict: { type: \"boolean\", default: false },\n \"provider-openai\": { type: \"string\" },\n \"provider-anthropic\": { type: \"string\" },\n \"provider-gemini\": { type: \"string\" },\n \"provider-vertexai\": { type: \"string\" },\n \"provider-bedrock\": { type: \"string\" },\n \"provider-azure\": { type: \"string\" },\n \"provider-ollama\": { type: \"string\" },\n \"provider-cohere\": { type: \"string\" },\n \"agui-record\": { type: \"boolean\", default: false },\n \"agui-upstream\": { type: \"string\" },\n \"agui-proxy-only\": { type: \"boolean\", default: false },\n \"chaos-drop\": { type: \"string\" },\n \"chaos-malformed\": { type: \"string\" },\n \"chaos-disconnect\": { type: \"string\" },\n \"journal-max\": { type: \"string\", default: \"1000\" },\n \"fixture-counts-max\": { type: \"string\", default: \"500\" },\n help: { type: \"boolean\", default: false },\n },\n strict: true,\n});\n\nif (values.help) {\n console.log(HELP);\n process.exit(0);\n}\n\nconst port = Number(values.port);\nconst host = values.host!;\nconst latency = Number(values.latency);\nconst chunkSize = Number(values[\"chunk-size\"]);\nconst fixtureValues: string[] =\n values.fixtures && values.fixtures.length > 0 ? values.fixtures : [\"./fixtures\"];\nconst watchMode = values.watch!;\nconst validateOnLoad = values[\"validate-on-load\"]!;\nconst logLevelStr = values[\"log-level\"]!;\n\nif (![\"silent\", \"warn\", \"info\", \"debug\"].includes(logLevelStr)) {\n console.error(`Invalid log-level: ${logLevelStr} (must be silent, warn, info, or debug)`);\n process.exit(1);\n}\nconst logLevel = logLevelStr as LogLevel;\n\nif (Number.isNaN(port) || port < 0 || port > 65535) {\n console.error(`Invalid port: ${values.port}`);\n process.exit(1);\n}\n\nif (Number.isNaN(latency) || latency < 0) {\n console.error(`Invalid latency: ${values.latency}`);\n process.exit(1);\n}\n\nif (Number.isNaN(chunkSize) || chunkSize < 1) {\n console.error(`Invalid chunk-size: ${values[\"chunk-size\"]}`);\n process.exit(1);\n}\n\nconst journalMax = Number(values[\"journal-max\"]);\nif (Number.isNaN(journalMax) || !Number.isInteger(journalMax) || journalMax < 0) {\n console.error(\n `Invalid journal-max: ${values[\"journal-max\"]} (must be a non-negative integer; 0 or omitted = unbounded)`,\n );\n process.exit(1);\n}\n\nconst fixtureCountsMaxStr = values[\"fixture-counts-max\"];\nconst fixtureCountsMax = Number(fixtureCountsMaxStr);\nif (Number.isNaN(fixtureCountsMax) || !Number.isInteger(fixtureCountsMax) || fixtureCountsMax < 0) {\n console.error(\n `Invalid fixture-counts-max: ${fixtureCountsMaxStr} (must be a non-negative integer; 0 = unbounded)`,\n );\n process.exit(1);\n}\n\nconst logger = new Logger(logLevel);\n\n// Parse chaos config from CLI flags\nlet chaos: ChaosConfig | undefined;\n{\n const dropStr = values[\"chaos-drop\"];\n const malformedStr = values[\"chaos-malformed\"];\n const disconnectStr = values[\"chaos-disconnect\"];\n\n if (dropStr !== undefined || malformedStr !== undefined || disconnectStr !== undefined) {\n chaos = {};\n if (dropStr !== undefined) {\n const val = parseFloat(dropStr);\n if (isNaN(val) || val < 0 || val > 1) {\n console.error(`Invalid chaos-drop: ${dropStr} (must be 0-1)`);\n process.exit(1);\n }\n chaos.dropRate = val;\n }\n if (malformedStr !== undefined) {\n const val = parseFloat(malformedStr);\n if (isNaN(val) || val < 0 || val > 1) {\n console.error(`Invalid chaos-malformed: ${malformedStr} (must be 0-1)`);\n process.exit(1);\n }\n chaos.malformedRate = val;\n }\n if (disconnectStr !== undefined) {\n const val = parseFloat(disconnectStr);\n if (isNaN(val) || val < 0 || val > 1) {\n console.error(`Invalid chaos-disconnect: ${disconnectStr} (must be 0-1)`);\n process.exit(1);\n }\n chaos.disconnectRate = val;\n }\n }\n}\n\n// Parse record/proxy config from CLI flags\nlet record: RecordConfig | undefined;\nif (values.record || values[\"proxy-only\"]) {\n const providers: RecordConfig[\"providers\"] = {};\n if (values[\"provider-openai\"]) providers.openai = values[\"provider-openai\"];\n if (values[\"provider-anthropic\"]) providers.anthropic = values[\"provider-anthropic\"];\n if (values[\"provider-gemini\"]) providers.gemini = values[\"provider-gemini\"];\n if (values[\"provider-vertexai\"]) providers.vertexai = values[\"provider-vertexai\"];\n if (values[\"provider-bedrock\"]) providers.bedrock = values[\"provider-bedrock\"];\n if (values[\"provider-azure\"]) providers.azure = values[\"provider-azure\"];\n if (values[\"provider-ollama\"]) providers.ollama = values[\"provider-ollama\"];\n if (values[\"provider-cohere\"]) providers.cohere = values[\"provider-cohere\"];\n\n if (Object.keys(providers).length === 0) {\n console.error(\n `Error: --${values[\"proxy-only\"] ? \"proxy-only\" : \"record\"} requires at least one --provider-* flag`,\n );\n process.exit(1);\n }\n\n // For --record, the first --fixtures value is the base path for the recording\n // destination and must be a local filesystem path — writing to a URL is not supported.\n // For --proxy-only, unmatched requests are forwarded without saving, so no writable\n // destination is required; URL-only --fixtures is valid in that mode.\n const recordBase = fixtureValues[0];\n const recordBaseIsUrl = /^https?:\\/\\//i.test(recordBase);\n if (values.record && recordBaseIsUrl) {\n console.error(\n `Error: --record requires a local --fixtures path for the recording destination; got URL ${recordBase}`,\n );\n process.exit(1);\n }\n record = {\n providers,\n // In proxy-only mode with only URL sources, fixturePath is never consumed\n // (recorder.ts skips disk writes when proxyOnly is set). Leave it undefined\n // rather than resolving a URL string as a filesystem path.\n fixturePath: recordBaseIsUrl ? undefined : resolve(recordBase, \"recorded\"),\n proxyOnly: values[\"proxy-only\"],\n };\n}\n\n// Parse AG-UI record/proxy config from CLI flags\nlet aguiMount: { path: string; handler: AGUIMock } | undefined;\nif (values[\"agui-record\"] || values[\"agui-proxy-only\"]) {\n if (!values[\"agui-upstream\"]) {\n console.error(\"Error: --agui-record/--agui-proxy-only requires --agui-upstream\");\n process.exit(1);\n }\n // --agui-record writes recorded AG-UI fixtures to disk, so a URL source is unsupported.\n // --agui-proxy-only forwards without saving, so URL-only --fixtures is valid.\n const aguiBase = fixtureValues[0];\n const aguiBaseIsUrl = /^https?:\\/\\//i.test(aguiBase);\n if (values[\"agui-record\"] && aguiBaseIsUrl) {\n console.error(\n `Error: --agui-record requires a local --fixtures path for the recording destination; got URL ${aguiBase}`,\n );\n process.exit(1);\n }\n const agui = new AGUIMock();\n agui.enableRecording({\n upstream: values[\"agui-upstream\"],\n // In proxy-only mode with a URL-only --fixtures, the AG-UI recorder never\n // writes to disk (see agui-recorder.ts). Leave fixturePath undefined rather\n // than resolving a URL as a filesystem path.\n fixturePath: aguiBaseIsUrl ? undefined : resolve(aguiBase, \"agui-recorded\"),\n proxyOnly: values[\"agui-proxy-only\"],\n });\n aguiMount = { path: \"/agui\", handler: agui };\n}\n\ninterface ResolvedFixtureSource {\n source: string;\n path: string;\n isDir: boolean;\n}\n\nasync function resolveAllFixtureSources(): Promise<ResolvedFixtureSource[]> {\n const resolved: ResolvedFixtureSource[] = [];\n for (const value of fixtureValues) {\n let local;\n try {\n local = await resolveFixturesValue(value, {\n validateOnLoad,\n logger,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to resolve --fixtures value \"${value}\": ${msg}`);\n process.exit(1);\n }\n if (!local.path) {\n // Remote fetch failed without validate-on-load and no cache — already warned; skip.\n continue;\n }\n try {\n const stat = statSync(local.path);\n resolved.push({ source: local.source, path: local.path, isDir: stat.isDirectory() });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n console.error(`Fixtures path not found: ${local.path}`);\n } else {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to load fixtures from ${local.path}: ${msg}`);\n }\n process.exit(1);\n }\n }\n return resolved;\n}\n\nfunction loadSource(source: ResolvedFixtureSource): Fixture[] {\n return source.isDir\n ? loadFixturesFromDir(source.path, logger)\n : loadFixtureFile(source.path, logger);\n}\n\nasync function main() {\n const sources = await resolveAllFixtureSources();\n\n const fixtures: Fixture[] = [];\n for (const src of sources) {\n fixtures.push(...loadSource(src));\n }\n\n if (fixtures.length === 0) {\n if (validateOnLoad || values.strict) {\n console.error(\"Error: No fixtures loaded and validation/strict mode is enabled — aborting.\");\n process.exit(1);\n }\n console.warn(\"Warning: No fixtures loaded. The server will return 404 for all requests.\");\n }\n\n const sourceLabel = sources.map((s) => s.source).join(\", \") || \"<none>\";\n logger.info(`Loaded ${fixtures.length} fixture(s) from ${sourceLabel}`);\n\n // Validate fixtures if requested\n if (validateOnLoad) {\n const results = validateFixtures(fixtures);\n const errors = results.filter((r) => r.severity === \"error\");\n const warnings = results.filter((r) => r.severity === \"warning\");\n\n for (const w of warnings) {\n logger.warn(`Fixture ${w.fixtureIndex}: ${w.message}`);\n }\n for (const e of errors) {\n logger.error(`Fixture ${e.fixtureIndex}: ${e.message}`);\n }\n\n if (errors.length > 0) {\n console.error(`Validation failed: ${errors.length} error(s), ${warnings.length} warning(s)`);\n process.exit(1);\n }\n }\n\n const mounts = aguiMount ? [aguiMount] : undefined;\n\n const instance = await createServer(\n fixtures,\n {\n port,\n host,\n latency,\n chunkSize,\n logLevel,\n chaos,\n metrics: values.metrics,\n record,\n strict: values.strict,\n journalMaxEntries: journalMax,\n fixtureCountsMaxTestIds: fixtureCountsMax,\n },\n mounts,\n );\n\n logger.info(`aimock server listening on ${instance.url}`);\n\n // Start file watcher if requested. Only the first local source is watched —\n // remote URL sources are fetched once at boot and are not monitored.\n let watcher: { close: () => void } | null = null;\n if (watchMode) {\n const primary = sources[0];\n if (!primary) {\n logger.warn(\"--watch requested but no resolvable fixture sources; skipping watcher\");\n } else {\n const loadFn = (): Fixture[] => loadSource(primary);\n watcher = watchFixtures(primary.path, fixtures, loadFn, {\n logger,\n validate: validateOnLoad,\n validateFn: validateFixtures,\n });\n logger.info(`Watching ${primary.path} for changes`);\n }\n }\n\n function shutdown() {\n logger.info(\"Shutting down...\");\n if (watcher) watcher.close();\n instance.server.close(() => {\n process.exit(0);\n });\n }\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n\nmain().catch((err) => {\n console.error(err);\n process.exit(1);\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from \"node:util\";\nimport { statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { createServer } from \"./server.js\";\nimport { loadFixtureFile, loadFixturesFromDir, validateFixtures } from \"./fixture-loader.js\";\nimport { Logger, type LogLevel } from \"./logger.js\";\nimport { watchFixtures } from \"./watcher.js\";\nimport { AGUIMock } from \"./agui-mock.js\";\nimport { resolveFixturesValue } from \"./fixtures-remote.js\";\nimport type { Fixture, ChaosConfig, RecordConfig } from \"./types.js\";\n\nconst HELP = `\nUsage: aimock [options]\n\nOptions:\n -p, --port <number> Port to listen on (default: 4010)\n -h, --host <string> Host to bind to (default: 127.0.0.1)\n -f, --fixtures <value> Fixture source (repeatable). Accepts:\n - filesystem path to a directory or .json file (default: ./fixtures)\n - https:// or http:// URL to a .json fixture file\n -l, --latency <ms> Latency in ms between SSE chunks (default: 0)\n -c, --chunk-size <chars> Chunk size in characters (default: 20)\n -w, --watch Watch fixture path for changes and reload\n --log-level <level> Log verbosity: silent, warn, info, debug (default: info)\n --validate-on-load Validate fixture schemas at startup\n --metrics Enable Prometheus metrics at GET /metrics\n --record Record mode: proxy unmatched requests and save fixtures\n --record-full-model-version Record exact model version without date stripping (default: false)\n --proxy-only Proxy mode: forward unmatched requests without saving\n --strict Strict mode: fail on unmatched requests (overridable per-request via X-AIMock-Strict header)\n --journal-max <n> Max request entries retained in memory (default: 1000, 0 = unbounded)\n --fixture-counts-max <n> Max unique testIds retained in fixture match-count map (default: 500, 0 = unbounded)\n --provider-openai <url> Upstream URL for OpenAI (used with --record)\n --provider-anthropic <url> Upstream URL for Anthropic\n --provider-gemini <url> Upstream URL for Gemini\n --provider-vertexai <url> Upstream URL for Vertex AI\n --provider-bedrock <url> Upstream URL for Bedrock\n --provider-azure <url> Upstream URL for Azure OpenAI\n --provider-ollama <url> Upstream URL for Ollama\n --provider-cohere <url> Upstream URL for Cohere\n --agui-record Enable AG-UI recording (proxy unmatched AG-UI requests)\n --agui-upstream <url> Upstream AG-UI agent URL (used with --agui-record)\n --agui-proxy-only AG-UI proxy mode: forward without saving\n --chaos-drop <rate> Probability (0-1) of dropping requests with 500\n --chaos-malformed <rate> Probability (0-1) of returning malformed JSON\n --chaos-disconnect <rate> Probability (0-1) of destroying connection\n --help Show this help message\n`.trim();\n\nconst { values } = parseArgs({\n options: {\n port: { type: \"string\", short: \"p\", default: \"4010\" },\n host: { type: \"string\", short: \"h\", default: \"127.0.0.1\" },\n fixtures: { type: \"string\", short: \"f\", multiple: true },\n latency: { type: \"string\", short: \"l\", default: \"0\" },\n \"chunk-size\": { type: \"string\", short: \"c\", default: \"20\" },\n watch: { type: \"boolean\", short: \"w\", default: false },\n \"log-level\": { type: \"string\", default: \"info\" },\n \"validate-on-load\": { type: \"boolean\", default: false },\n metrics: { type: \"boolean\", default: false },\n record: { type: \"boolean\", default: false },\n \"record-full-model-version\": { type: \"boolean\", default: false },\n \"proxy-only\": { type: \"boolean\", default: false },\n strict: { type: \"boolean\", default: false },\n \"provider-openai\": { type: \"string\" },\n \"provider-anthropic\": { type: \"string\" },\n \"provider-gemini\": { type: \"string\" },\n \"provider-vertexai\": { type: \"string\" },\n \"provider-bedrock\": { type: \"string\" },\n \"provider-azure\": { type: \"string\" },\n \"provider-ollama\": { type: \"string\" },\n \"provider-cohere\": { type: \"string\" },\n \"agui-record\": { type: \"boolean\", default: false },\n \"agui-upstream\": { type: \"string\" },\n \"agui-proxy-only\": { type: \"boolean\", default: false },\n \"chaos-drop\": { type: \"string\" },\n \"chaos-malformed\": { type: \"string\" },\n \"chaos-disconnect\": { type: \"string\" },\n \"journal-max\": { type: \"string\", default: \"1000\" },\n \"fixture-counts-max\": { type: \"string\", default: \"500\" },\n help: { type: \"boolean\", default: false },\n },\n strict: true,\n});\n\nif (values.help) {\n console.log(HELP);\n process.exit(0);\n}\n\nconst port = Number(values.port);\nconst host = values.host!;\nconst latency = Number(values.latency);\nconst chunkSize = Number(values[\"chunk-size\"]);\nconst fixtureValues: string[] =\n values.fixtures && values.fixtures.length > 0 ? values.fixtures : [\"./fixtures\"];\nconst watchMode = values.watch!;\nconst validateOnLoad = values[\"validate-on-load\"]!;\nconst logLevelStr = values[\"log-level\"]!;\n\nif (![\"silent\", \"warn\", \"info\", \"debug\"].includes(logLevelStr)) {\n console.error(`Invalid log-level: ${logLevelStr} (must be silent, warn, info, or debug)`);\n process.exit(1);\n}\nconst logLevel = logLevelStr as LogLevel;\n\nif (Number.isNaN(port) || port < 0 || port > 65535) {\n console.error(`Invalid port: ${values.port}`);\n process.exit(1);\n}\n\nif (Number.isNaN(latency) || latency < 0) {\n console.error(`Invalid latency: ${values.latency}`);\n process.exit(1);\n}\n\nif (Number.isNaN(chunkSize) || chunkSize < 1) {\n console.error(`Invalid chunk-size: ${values[\"chunk-size\"]}`);\n process.exit(1);\n}\n\nconst journalMax = Number(values[\"journal-max\"]);\nif (Number.isNaN(journalMax) || !Number.isInteger(journalMax) || journalMax < 0) {\n console.error(\n `Invalid journal-max: ${values[\"journal-max\"]} (must be a non-negative integer; 0 or omitted = unbounded)`,\n );\n process.exit(1);\n}\n\nconst fixtureCountsMaxStr = values[\"fixture-counts-max\"];\nconst fixtureCountsMax = Number(fixtureCountsMaxStr);\nif (Number.isNaN(fixtureCountsMax) || !Number.isInteger(fixtureCountsMax) || fixtureCountsMax < 0) {\n console.error(\n `Invalid fixture-counts-max: ${fixtureCountsMaxStr} (must be a non-negative integer; 0 = unbounded)`,\n );\n process.exit(1);\n}\n\nconst logger = new Logger(logLevel);\n\n// Parse chaos config from CLI flags\nlet chaos: ChaosConfig | undefined;\n{\n const dropStr = values[\"chaos-drop\"];\n const malformedStr = values[\"chaos-malformed\"];\n const disconnectStr = values[\"chaos-disconnect\"];\n\n if (dropStr !== undefined || malformedStr !== undefined || disconnectStr !== undefined) {\n chaos = {};\n if (dropStr !== undefined) {\n const val = parseFloat(dropStr);\n if (isNaN(val) || val < 0 || val > 1) {\n console.error(`Invalid chaos-drop: ${dropStr} (must be 0-1)`);\n process.exit(1);\n }\n chaos.dropRate = val;\n }\n if (malformedStr !== undefined) {\n const val = parseFloat(malformedStr);\n if (isNaN(val) || val < 0 || val > 1) {\n console.error(`Invalid chaos-malformed: ${malformedStr} (must be 0-1)`);\n process.exit(1);\n }\n chaos.malformedRate = val;\n }\n if (disconnectStr !== undefined) {\n const val = parseFloat(disconnectStr);\n if (isNaN(val) || val < 0 || val > 1) {\n console.error(`Invalid chaos-disconnect: ${disconnectStr} (must be 0-1)`);\n process.exit(1);\n }\n chaos.disconnectRate = val;\n }\n }\n}\n\n// Parse record/proxy config from CLI flags\nlet record: RecordConfig | undefined;\nif (values.record || values[\"proxy-only\"]) {\n const providers: RecordConfig[\"providers\"] = {};\n if (values[\"provider-openai\"]) providers.openai = values[\"provider-openai\"];\n if (values[\"provider-anthropic\"]) providers.anthropic = values[\"provider-anthropic\"];\n if (values[\"provider-gemini\"]) providers.gemini = values[\"provider-gemini\"];\n if (values[\"provider-vertexai\"]) providers.vertexai = values[\"provider-vertexai\"];\n if (values[\"provider-bedrock\"]) providers.bedrock = values[\"provider-bedrock\"];\n if (values[\"provider-azure\"]) providers.azure = values[\"provider-azure\"];\n if (values[\"provider-ollama\"]) providers.ollama = values[\"provider-ollama\"];\n if (values[\"provider-cohere\"]) providers.cohere = values[\"provider-cohere\"];\n\n if (Object.keys(providers).length === 0) {\n console.error(\n `Error: --${values[\"proxy-only\"] ? \"proxy-only\" : \"record\"} requires at least one --provider-* flag`,\n );\n process.exit(1);\n }\n\n // For --record, the first --fixtures value is the base path for the recording\n // destination and must be a local filesystem path — writing to a URL is not supported.\n // For --proxy-only, unmatched requests are forwarded without saving, so no writable\n // destination is required; URL-only --fixtures is valid in that mode.\n const recordBase = fixtureValues[0];\n const recordBaseIsUrl = /^https?:\\/\\//i.test(recordBase);\n if (values.record && recordBaseIsUrl) {\n console.error(\n `Error: --record requires a local --fixtures path for the recording destination; got URL ${recordBase}`,\n );\n process.exit(1);\n }\n record = {\n providers,\n // In proxy-only mode with only URL sources, fixturePath is never consumed\n // (recorder.ts skips disk writes when proxyOnly is set). Leave it undefined\n // rather than resolving a URL string as a filesystem path.\n fixturePath: recordBaseIsUrl ? undefined : resolve(recordBase, \"recorded\"),\n proxyOnly: values[\"proxy-only\"],\n recordFullModelVersion: values[\"record-full-model-version\"],\n };\n}\n\n// Parse AG-UI record/proxy config from CLI flags\nlet aguiMount: { path: string; handler: AGUIMock } | undefined;\nif (values[\"agui-record\"] || values[\"agui-proxy-only\"]) {\n if (!values[\"agui-upstream\"]) {\n console.error(\"Error: --agui-record/--agui-proxy-only requires --agui-upstream\");\n process.exit(1);\n }\n // --agui-record writes recorded AG-UI fixtures to disk, so a URL source is unsupported.\n // --agui-proxy-only forwards without saving, so URL-only --fixtures is valid.\n const aguiBase = fixtureValues[0];\n const aguiBaseIsUrl = /^https?:\\/\\//i.test(aguiBase);\n if (values[\"agui-record\"] && aguiBaseIsUrl) {\n console.error(\n `Error: --agui-record requires a local --fixtures path for the recording destination; got URL ${aguiBase}`,\n );\n process.exit(1);\n }\n const agui = new AGUIMock();\n agui.enableRecording({\n upstream: values[\"agui-upstream\"],\n // In proxy-only mode with a URL-only --fixtures, the AG-UI recorder never\n // writes to disk (see agui-recorder.ts). Leave fixturePath undefined rather\n // than resolving a URL as a filesystem path.\n fixturePath: aguiBaseIsUrl ? undefined : resolve(aguiBase, \"agui-recorded\"),\n proxyOnly: values[\"agui-proxy-only\"],\n });\n aguiMount = { path: \"/agui\", handler: agui };\n}\n\ninterface ResolvedFixtureSource {\n source: string;\n path: string;\n isDir: boolean;\n}\n\nasync function resolveAllFixtureSources(): Promise<ResolvedFixtureSource[]> {\n const resolved: ResolvedFixtureSource[] = [];\n for (const value of fixtureValues) {\n let local;\n try {\n local = await resolveFixturesValue(value, {\n validateOnLoad,\n logger,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to resolve --fixtures value \"${value}\": ${msg}`);\n process.exit(1);\n }\n if (!local.path) {\n // Remote fetch failed without validate-on-load and no cache — already warned; skip.\n continue;\n }\n try {\n const stat = statSync(local.path);\n resolved.push({ source: local.source, path: local.path, isDir: stat.isDirectory() });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n console.error(`Fixtures path not found: ${local.path}`);\n } else {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to load fixtures from ${local.path}: ${msg}`);\n }\n process.exit(1);\n }\n }\n return resolved;\n}\n\nfunction loadSource(source: ResolvedFixtureSource): Fixture[] {\n return source.isDir\n ? loadFixturesFromDir(source.path, logger)\n : loadFixtureFile(source.path, logger);\n}\n\nasync function main() {\n const sources = await resolveAllFixtureSources();\n\n const fixtures: Fixture[] = [];\n for (const src of sources) {\n fixtures.push(...loadSource(src));\n }\n\n if (fixtures.length === 0) {\n if (validateOnLoad || values.strict) {\n console.error(\"Error: No fixtures loaded and validation/strict mode is enabled — aborting.\");\n process.exit(1);\n }\n console.warn(\"Warning: No fixtures loaded. The server will return 404 for all requests.\");\n }\n\n const sourceLabel = sources.map((s) => s.source).join(\", \") || \"<none>\";\n logger.info(`Loaded ${fixtures.length} fixture(s) from ${sourceLabel}`);\n\n // Validate fixtures if requested\n if (validateOnLoad) {\n const results = validateFixtures(fixtures);\n const errors = results.filter((r) => r.severity === \"error\");\n const warnings = results.filter((r) => r.severity === \"warning\");\n\n for (const w of warnings) {\n logger.warn(`Fixture ${w.fixtureIndex}: ${w.message}`);\n }\n for (const e of errors) {\n logger.error(`Fixture ${e.fixtureIndex}: ${e.message}`);\n }\n\n if (errors.length > 0) {\n console.error(`Validation failed: ${errors.length} error(s), ${warnings.length} warning(s)`);\n process.exit(1);\n }\n }\n\n const mounts = aguiMount ? [aguiMount] : undefined;\n\n const instance = await createServer(\n fixtures,\n {\n port,\n host,\n latency,\n chunkSize,\n logLevel,\n chaos,\n metrics: values.metrics,\n record,\n strict: values.strict,\n journalMaxEntries: journalMax,\n fixtureCountsMaxTestIds: fixtureCountsMax,\n },\n mounts,\n );\n\n logger.info(`aimock server listening on ${instance.url}`);\n\n // Start file watcher if requested. Only the first local source is watched —\n // remote URL sources are fetched once at boot and are not monitored.\n let watcher: { close: () => void } | null = null;\n if (watchMode) {\n const primary = sources[0];\n if (!primary) {\n logger.warn(\"--watch requested but no resolvable fixture sources; skipping watcher\");\n } else {\n const loadFn = (): Fixture[] => loadSource(primary);\n watcher = watchFixtures(primary.path, fixtures, loadFn, {\n logger,\n validate: validateOnLoad,\n validateFn: validateFixtures,\n });\n logger.info(`Watching ${primary.path} for changes`);\n }\n }\n\n function shutdown() {\n logger.info(\"Shutting down...\");\n if (watcher) watcher.close();\n instance.server.close(() => {\n process.exit(0);\n });\n }\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n\nmain().catch((err) => {\n console.error(err);\n process.exit(1);\n});\n"],"mappings":"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"}
|
package/dist/fixture-loader.cjs
CHANGED
|
@@ -42,7 +42,8 @@ function entryToFixture(entry) {
|
|
|
42
42
|
...entry.truncateAfterChunks !== void 0 && { truncateAfterChunks: entry.truncateAfterChunks },
|
|
43
43
|
...entry.disconnectAfterMs !== void 0 && { disconnectAfterMs: entry.disconnectAfterMs },
|
|
44
44
|
...entry.streamingProfile !== void 0 && { streamingProfile: entry.streamingProfile },
|
|
45
|
-
...entry.chaos !== void 0 && { chaos: entry.chaos }
|
|
45
|
+
...entry.chaos !== void 0 && { chaos: entry.chaos },
|
|
46
|
+
...entry.metadata !== void 0 && { metadata: entry.metadata }
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
function warn(logger, msg, ...rest) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixture-loader.cjs","names":["isContentWithToolCallsResponse","isTextResponse","isToolCallResponse","isErrorResponse","isEmbeddingResponse","isImageResponse","isAudioResponse","isTranscriptionResponse","isVideoResponse","isJSONResponse"],"sources":["../src/fixture-loader.ts"],"sourcesContent":["import { readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type {\n Fixture,\n FixtureFile,\n FixtureFileEntry,\n FixtureFileResponse,\n FixtureResponse,\n ResponseOverrides,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n isEmbeddingResponse,\n isImageResponse,\n isAudioResponse,\n isTranscriptionResponse,\n isVideoResponse,\n isJSONResponse,\n} from \"./helpers.js\";\nimport type { Logger } from \"./logger.js\";\n\n/**\n * Auto-stringify object-valued `content` and `toolCalls[].arguments` fields.\n * This lets fixture authors write plain JSON objects instead of escaped strings.\n * All other fields (including ResponseOverrides) pass through unmodified.\n */\nexport function normalizeResponse(raw: FixtureFileResponse): FixtureResponse {\n // Shallow-clone so we don't mutate the parsed JSON input.\n const response = { ...raw } as Record<string, unknown>;\n\n // Auto-stringify object content (e.g. structured output)\n if (typeof response.content === \"object\" && response.content !== null) {\n response.content = JSON.stringify(response.content);\n }\n\n // Auto-stringify object arguments in toolCalls\n if (Array.isArray(response.toolCalls)) {\n response.toolCalls = (response.toolCalls as Array<Record<string, unknown>>).map((tc) => {\n if (typeof tc.arguments === \"object\" && tc.arguments !== null) {\n return { ...tc, arguments: JSON.stringify(tc.arguments) };\n }\n return tc;\n });\n }\n\n return response as unknown as FixtureResponse;\n}\n\nexport function entryToFixture(entry: FixtureFileEntry): Fixture {\n return {\n match: {\n userMessage: entry.match.userMessage,\n systemMessage: entry.match.systemMessage,\n inputText: entry.match.inputText,\n toolCallId: entry.match.toolCallId,\n toolName: entry.match.toolName,\n model: entry.match.model,\n responseFormat: entry.match.responseFormat,\n endpoint: entry.match.endpoint,\n ...(entry.match.sequenceIndex !== undefined && { sequenceIndex: entry.match.sequenceIndex }),\n ...(entry.match.turnIndex !== undefined && {\n turnIndex: entry.match.turnIndex,\n }),\n ...(entry.match.hasToolResult !== undefined && {\n hasToolResult: entry.match.hasToolResult,\n }),\n },\n response: normalizeResponse(entry.response),\n ...(entry.latency !== undefined && { latency: entry.latency }),\n ...(entry.chunkSize !== undefined && { chunkSize: entry.chunkSize }),\n ...(entry.truncateAfterChunks !== undefined && {\n truncateAfterChunks: entry.truncateAfterChunks,\n }),\n ...(entry.disconnectAfterMs !== undefined && { disconnectAfterMs: entry.disconnectAfterMs }),\n ...(entry.streamingProfile !== undefined && { streamingProfile: entry.streamingProfile }),\n ...(entry.chaos !== undefined && { chaos: entry.chaos }),\n };\n}\n\n// Logging helper — uses logger if provided, falls back to console.warn.\nfunction warn(logger: Logger | undefined, msg: string, ...rest: unknown[]): void {\n if (logger) {\n logger.warn(msg, ...rest);\n } else {\n console.warn(`[fixture-loader] ${msg}`, ...rest);\n }\n}\n\nexport function loadFixtureFile(filePath: string, logger?: Logger): Fixture[] {\n let raw: string;\n try {\n raw = readFileSync(filePath, \"utf-8\");\n } catch (err) {\n warn(logger, `Could not read file ${filePath}:`, err);\n return [];\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n warn(logger, `Invalid JSON in ${filePath}:`, err);\n return [];\n }\n\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n !Array.isArray((parsed as FixtureFile).fixtures)\n ) {\n warn(logger, `Missing or invalid \"fixtures\" array in ${filePath}`);\n return [];\n }\n\n return (parsed as FixtureFile).fixtures.map(entryToFixture);\n}\n\nexport function loadFixturesFromDir(dirPath: string, logger?: Logger): Fixture[] {\n let entries: string[];\n try {\n entries = readdirSync(dirPath);\n } catch (err) {\n warn(logger, `Could not read directory ${dirPath}:`, err);\n return [];\n }\n\n const jsonFiles: string[] = [];\n const subdirs: string[] = [];\n for (const name of entries) {\n const fullPath = join(dirPath, name);\n try {\n if (statSync(fullPath).isDirectory()) {\n subdirs.push(name);\n continue;\n }\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n warn(logger, `Could not stat ${fullPath}:`, err);\n }\n continue;\n }\n if (name.endsWith(\".json\")) {\n jsonFiles.push(name);\n }\n }\n jsonFiles.sort();\n\n const fixtures: Fixture[] = [];\n for (const name of jsonFiles) {\n const filePath = join(dirPath, name);\n fixtures.push(...loadFixtureFile(filePath, logger));\n }\n\n // Recurse one level into subdirectories to support snapshot-style layouts\n // where the recorder writes to <fixturePath>/<testId>/<provider>.json.\n subdirs.sort();\n for (const sub of subdirs) {\n const subPath = join(dirPath, sub);\n let subEntries: string[];\n try {\n subEntries = readdirSync(subPath);\n } catch (err) {\n warn(logger, `Could not read subdirectory ${subPath}:`, err);\n continue;\n }\n const subJsonFiles: string[] = [];\n for (const subName of subEntries) {\n const subFullPath = join(subPath, subName);\n try {\n if (statSync(subFullPath).isDirectory()) {\n // Only one level of recursion — skip deeper nesting\n continue;\n }\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n warn(logger, `Could not stat ${subFullPath}:`, err);\n }\n continue;\n }\n if (subName.endsWith(\".json\")) {\n subJsonFiles.push(subName);\n }\n }\n subJsonFiles.sort();\n for (const subName of subJsonFiles) {\n const filePath = join(subPath, subName);\n fixtures.push(...loadFixtureFile(filePath, logger));\n }\n }\n\n return fixtures;\n}\n\n// ---------------------------------------------------------------------------\n// Fixture validation\n// ---------------------------------------------------------------------------\n\nexport interface ValidationResult {\n severity: \"error\" | \"warning\";\n fixtureIndex: number;\n message: string;\n}\n\nfunction validateReasoning(\n response: { reasoning?: unknown },\n fixtureIndex: number,\n results: ValidationResult[],\n): void {\n if (response.reasoning !== undefined) {\n if (typeof response.reasoning !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex,\n message: \"reasoning must be a string\",\n });\n } else if (response.reasoning === \"\") {\n results.push({\n severity: \"warning\",\n fixtureIndex,\n message: \"reasoning is empty string — no reasoning events will be emitted\",\n });\n }\n }\n}\n\nfunction validateWebSearches(\n response: { webSearches?: unknown },\n fixtureIndex: number,\n results: ValidationResult[],\n): void {\n if (response.webSearches !== undefined) {\n if (!Array.isArray(response.webSearches)) {\n results.push({\n severity: \"error\",\n fixtureIndex,\n message: \"webSearches must be an array of strings\",\n });\n } else if (response.webSearches.length === 0) {\n results.push({\n severity: \"warning\",\n fixtureIndex,\n message: \"webSearches is empty array — no web search events will be emitted\",\n });\n } else {\n for (let j = 0; j < response.webSearches.length; j++) {\n if (typeof response.webSearches[j] !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex,\n message: `webSearches[${j}] is not a string`,\n });\n break;\n }\n if (response.webSearches[j] === \"\") {\n results.push({\n severity: \"warning\",\n fixtureIndex,\n message: `webSearches[${j}] is empty string`,\n });\n }\n }\n }\n }\n}\n\nexport function validateFixtures(fixtures: Fixture[]): ValidationResult[] {\n const results: ValidationResult[] = [];\n\n const seenUserMessages = new Map<string, number>();\n\n for (let i = 0; i < fixtures.length; i++) {\n const f = fixtures[i];\n const response = f.response;\n\n // Skip response-shape validation for function responses — they are\n // evaluated at runtime so we cannot statically inspect them.\n if (typeof response === \"function\") {\n // Still validate match fields and numeric options below.\n } else {\n // --- Error checks ---\n\n // Response type recognition\n // Note: isContentWithToolCallsResponse must be checked before isTextResponse\n // and isToolCallResponse since it is a structural superset of both.\n if (\n !isContentWithToolCallsResponse(response) &&\n !isTextResponse(response) &&\n !isToolCallResponse(response) &&\n !isErrorResponse(response) &&\n !isEmbeddingResponse(response) &&\n !isImageResponse(response) &&\n !isAudioResponse(response) &&\n !isTranscriptionResponse(response) &&\n !isVideoResponse(response) &&\n !isJSONResponse(response)\n ) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message:\n \"response is not a recognized type (must have content, toolCalls, error, embedding, image, audio, transcription, video, or json)\",\n });\n }\n\n // Text response checks\n if (isTextResponse(response)) {\n if (response.content === \"\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"content is empty string\",\n });\n }\n validateReasoning(response, i, results);\n validateWebSearches(response, i, results);\n }\n\n // ContentWithToolCalls response checks\n if (isContentWithToolCallsResponse(response)) {\n if (response.content === \"\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"content is empty string\",\n });\n }\n if (response.toolCalls.length === 0) {\n results.push({\n severity: \"warning\",\n fixtureIndex: i,\n message: \"toolCalls array is empty — fixture will never produce tool calls\",\n });\n }\n for (let j = 0; j < response.toolCalls.length; j++) {\n const tc = response.toolCalls[j];\n if (!tc.name) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `toolCalls[${j}].name is empty`,\n });\n }\n try {\n JSON.parse(tc.arguments);\n } catch {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}`,\n });\n }\n }\n validateReasoning(response, i, results);\n validateWebSearches(response, i, results);\n }\n\n // Tool call response checks\n if (isToolCallResponse(response)) {\n if (response.toolCalls.length === 0) {\n results.push({\n severity: \"warning\",\n fixtureIndex: i,\n message: \"toolCalls array is empty — fixture will never produce tool calls\",\n });\n }\n for (let j = 0; j < response.toolCalls.length; j++) {\n const tc = response.toolCalls[j];\n if (!tc.name) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `toolCalls[${j}].name is empty`,\n });\n }\n try {\n JSON.parse(tc.arguments);\n } catch {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}`,\n });\n }\n }\n validateWebSearches(response, i, results);\n }\n\n // Error response checks\n if (isErrorResponse(response)) {\n if (!response.error.message) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"error.message is empty\",\n });\n }\n if (response.status !== undefined && (response.status < 100 || response.status > 599)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `error status ${response.status} is not a valid HTTP status code`,\n });\n }\n }\n\n // Embedding response checks\n if (isEmbeddingResponse(response)) {\n if (response.embedding.length === 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"embedding array is empty\",\n });\n }\n for (let j = 0; j < response.embedding.length; j++) {\n if (typeof response.embedding[j] !== \"number\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `embedding[${j}] is not a number`,\n });\n break; // one error is enough\n }\n }\n }\n\n // Audio response checks — validate object-form audio\n if (isAudioResponse(response) && typeof response.audio === \"object\") {\n const audioObj = response.audio;\n if (typeof audioObj.b64Json !== \"string\" || audioObj.b64Json === \"\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"audio.b64Json must be a non-empty string\",\n });\n }\n if (audioObj.contentType !== undefined && typeof audioObj.contentType !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `audio.contentType must be a string, got ${typeof audioObj.contentType}`,\n });\n }\n }\n\n // Validate ResponseOverrides fields\n if (\n isTextResponse(response) ||\n isToolCallResponse(response) ||\n isContentWithToolCallsResponse(response)\n ) {\n const r = response as ResponseOverrides;\n if (r.id !== undefined && typeof r.id !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"id\" must be a string, got ${typeof r.id}`,\n });\n }\n if (r.created !== undefined && (typeof r.created !== \"number\" || r.created < 0)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"created\" must be a non-negative number`,\n });\n }\n if (r.model !== undefined && typeof r.model !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"model\" must be a string, got ${typeof r.model}`,\n });\n }\n if (r.finishReason !== undefined && typeof r.finishReason !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"finishReason\" must be a string, got ${typeof r.finishReason}`,\n });\n }\n if (r.role !== undefined && typeof r.role !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"role\" must be a string, got ${typeof r.role}`,\n });\n }\n if (r.systemFingerprint !== undefined && typeof r.systemFingerprint !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"systemFingerprint\" must be a string, got ${typeof r.systemFingerprint}`,\n });\n }\n if (r.usage !== undefined) {\n if (typeof r.usage !== \"object\" || r.usage === null || Array.isArray(r.usage)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"usage\" must be an object`,\n });\n } else {\n // Check all known usage fields are numbers if present\n for (const key of Object.keys(r.usage)) {\n const val = (r.usage as Record<string, unknown>)[key];\n if (val !== undefined && typeof val !== \"number\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"usage.${key}\" must be a number, got ${typeof val}`,\n });\n }\n }\n }\n }\n }\n } // end: skip response-shape validation for function responses\n\n // Numeric sanity checks\n if (f.latency !== undefined && f.latency < 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"latency must be >= 0\",\n });\n }\n if (f.chunkSize !== undefined && f.chunkSize < 1) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"chunkSize must be >= 1\",\n });\n }\n if (f.truncateAfterChunks !== undefined && f.truncateAfterChunks < 1) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"truncateAfterChunks must be >= 1\",\n });\n }\n if (f.disconnectAfterMs !== undefined && f.disconnectAfterMs < 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"disconnectAfterMs must be >= 0\",\n });\n }\n if (f.streamingProfile !== undefined) {\n const sp = f.streamingProfile;\n if (sp.ttft !== undefined && sp.ttft < 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"streamingProfile.ttft must be >= 0\",\n });\n }\n if (sp.tps !== undefined && sp.tps <= 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"streamingProfile.tps must be > 0\",\n });\n }\n if (sp.jitter !== undefined && (sp.jitter < 0 || sp.jitter > 1)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"streamingProfile.jitter must be between 0 and 1\",\n });\n }\n }\n if (f.chaos !== undefined) {\n const ch = f.chaos;\n if (ch.dropRate !== undefined && (ch.dropRate < 0 || ch.dropRate > 1)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"chaos.dropRate must be between 0 and 1\",\n });\n }\n if (ch.malformedRate !== undefined && (ch.malformedRate < 0 || ch.malformedRate > 1)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"chaos.malformedRate must be between 0 and 1\",\n });\n }\n if (ch.disconnectRate !== undefined && (ch.disconnectRate < 0 || ch.disconnectRate > 1)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"chaos.disconnectRate must be between 0 and 1\",\n });\n }\n }\n\n // Match field type checks\n if (f.match.turnIndex !== undefined) {\n if (\n typeof f.match.turnIndex !== \"number\" ||\n f.match.turnIndex < 0 ||\n !Number.isInteger(f.match.turnIndex)\n ) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"match.turnIndex must be a non-negative integer\",\n });\n }\n }\n if (f.match.sequenceIndex !== undefined) {\n if (\n typeof f.match.sequenceIndex !== \"number\" ||\n f.match.sequenceIndex < 0 ||\n !Number.isInteger(f.match.sequenceIndex)\n ) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"match.sequenceIndex must be a non-negative integer\",\n });\n }\n }\n if (f.match.hasToolResult !== undefined && typeof f.match.hasToolResult !== \"boolean\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `match.hasToolResult must be a boolean, got ${typeof f.match.hasToolResult}`,\n });\n }\n if (f.match.systemMessage !== undefined) {\n const sm = f.match.systemMessage;\n if (typeof sm === \"string\") {\n // ok\n } else if (Array.isArray(sm)) {\n if (sm.length === 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `match.systemMessage array must contain at least one substring`,\n });\n } else {\n for (let j = 0; j < sm.length; j++) {\n if (typeof sm[j] !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `match.systemMessage[${j}] must be a string, got ${typeof sm[j]}`,\n });\n }\n }\n }\n } else {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `match.systemMessage must be a string or string[], got ${typeof sm}`,\n });\n }\n }\n\n // --- Warning checks ---\n\n // Duplicate userMessage shadowing — include turnIndex, hasToolResult, and\n // sequenceIndex in the dedup key so that fixtures which share a userMessage\n // but differ on those fields are NOT considered duplicates.\n const um = f.match.userMessage;\n if (typeof um === \"string\" && um) {\n const dedupKey = `${um}|${f.match.turnIndex}|${f.match.hasToolResult}|${f.match.sequenceIndex}`;\n const prev = seenUserMessages.get(dedupKey);\n if (prev !== undefined) {\n results.push({\n severity: \"warning\",\n fixtureIndex: i,\n message: `duplicate userMessage '${um}' — shadows fixture ${prev}`,\n });\n } else {\n seenUserMessages.set(dedupKey, i);\n }\n }\n\n // Catch-all not in last position\n const match = f.match;\n const hasDiscriminator =\n match.endpoint !== undefined ||\n match.userMessage !== undefined ||\n match.systemMessage !== undefined ||\n match.inputText !== undefined ||\n match.responseFormat !== undefined ||\n match.toolCallId !== undefined ||\n match.toolName !== undefined ||\n match.model !== undefined ||\n match.predicate !== undefined ||\n match.turnIndex !== undefined ||\n match.sequenceIndex !== undefined ||\n match.hasToolResult !== undefined;\n\n if (!hasDiscriminator && i < fixtures.length - 1) {\n results.push({\n severity: \"warning\",\n fixtureIndex: i,\n message: `empty match acts as catch-all but is not the last fixture — shadows fixtures ${i + 1}+`,\n });\n }\n }\n\n return results;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fixture-loader.cjs","names":["isContentWithToolCallsResponse","isTextResponse","isToolCallResponse","isErrorResponse","isEmbeddingResponse","isImageResponse","isAudioResponse","isTranscriptionResponse","isVideoResponse","isJSONResponse"],"sources":["../src/fixture-loader.ts"],"sourcesContent":["import { readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type {\n Fixture,\n FixtureFile,\n FixtureFileEntry,\n FixtureFileResponse,\n FixtureResponse,\n ResponseOverrides,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n isEmbeddingResponse,\n isImageResponse,\n isAudioResponse,\n isTranscriptionResponse,\n isVideoResponse,\n isJSONResponse,\n} from \"./helpers.js\";\nimport type { Logger } from \"./logger.js\";\n\n/**\n * Auto-stringify object-valued `content` and `toolCalls[].arguments` fields.\n * This lets fixture authors write plain JSON objects instead of escaped strings.\n * All other fields (including ResponseOverrides) pass through unmodified.\n */\nexport function normalizeResponse(raw: FixtureFileResponse): FixtureResponse {\n // Shallow-clone so we don't mutate the parsed JSON input.\n const response = { ...raw } as Record<string, unknown>;\n\n // Auto-stringify object content (e.g. structured output)\n if (typeof response.content === \"object\" && response.content !== null) {\n response.content = JSON.stringify(response.content);\n }\n\n // Auto-stringify object arguments in toolCalls\n if (Array.isArray(response.toolCalls)) {\n response.toolCalls = (response.toolCalls as Array<Record<string, unknown>>).map((tc) => {\n if (typeof tc.arguments === \"object\" && tc.arguments !== null) {\n return { ...tc, arguments: JSON.stringify(tc.arguments) };\n }\n return tc;\n });\n }\n\n return response as unknown as FixtureResponse;\n}\n\nexport function entryToFixture(entry: FixtureFileEntry): Fixture {\n return {\n match: {\n userMessage: entry.match.userMessage,\n systemMessage: entry.match.systemMessage,\n inputText: entry.match.inputText,\n toolCallId: entry.match.toolCallId,\n toolName: entry.match.toolName,\n model: entry.match.model,\n responseFormat: entry.match.responseFormat,\n endpoint: entry.match.endpoint,\n ...(entry.match.sequenceIndex !== undefined && { sequenceIndex: entry.match.sequenceIndex }),\n ...(entry.match.turnIndex !== undefined && {\n turnIndex: entry.match.turnIndex,\n }),\n ...(entry.match.hasToolResult !== undefined && {\n hasToolResult: entry.match.hasToolResult,\n }),\n },\n response: normalizeResponse(entry.response),\n ...(entry.latency !== undefined && { latency: entry.latency }),\n ...(entry.chunkSize !== undefined && { chunkSize: entry.chunkSize }),\n ...(entry.truncateAfterChunks !== undefined && {\n truncateAfterChunks: entry.truncateAfterChunks,\n }),\n ...(entry.disconnectAfterMs !== undefined && { disconnectAfterMs: entry.disconnectAfterMs }),\n ...(entry.streamingProfile !== undefined && { streamingProfile: entry.streamingProfile }),\n ...(entry.chaos !== undefined && { chaos: entry.chaos }),\n ...(entry.metadata !== undefined && { metadata: entry.metadata }),\n };\n}\n\n// Logging helper — uses logger if provided, falls back to console.warn.\nfunction warn(logger: Logger | undefined, msg: string, ...rest: unknown[]): void {\n if (logger) {\n logger.warn(msg, ...rest);\n } else {\n console.warn(`[fixture-loader] ${msg}`, ...rest);\n }\n}\n\nexport function loadFixtureFile(filePath: string, logger?: Logger): Fixture[] {\n let raw: string;\n try {\n raw = readFileSync(filePath, \"utf-8\");\n } catch (err) {\n warn(logger, `Could not read file ${filePath}:`, err);\n return [];\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n warn(logger, `Invalid JSON in ${filePath}:`, err);\n return [];\n }\n\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n !Array.isArray((parsed as FixtureFile).fixtures)\n ) {\n warn(logger, `Missing or invalid \"fixtures\" array in ${filePath}`);\n return [];\n }\n\n return (parsed as FixtureFile).fixtures.map(entryToFixture);\n}\n\nexport function loadFixturesFromDir(dirPath: string, logger?: Logger): Fixture[] {\n let entries: string[];\n try {\n entries = readdirSync(dirPath);\n } catch (err) {\n warn(logger, `Could not read directory ${dirPath}:`, err);\n return [];\n }\n\n const jsonFiles: string[] = [];\n const subdirs: string[] = [];\n for (const name of entries) {\n const fullPath = join(dirPath, name);\n try {\n if (statSync(fullPath).isDirectory()) {\n subdirs.push(name);\n continue;\n }\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n warn(logger, `Could not stat ${fullPath}:`, err);\n }\n continue;\n }\n if (name.endsWith(\".json\")) {\n jsonFiles.push(name);\n }\n }\n jsonFiles.sort();\n\n const fixtures: Fixture[] = [];\n for (const name of jsonFiles) {\n const filePath = join(dirPath, name);\n fixtures.push(...loadFixtureFile(filePath, logger));\n }\n\n // Recurse one level into subdirectories to support snapshot-style layouts\n // where the recorder writes to <fixturePath>/<testId>/<provider>.json.\n subdirs.sort();\n for (const sub of subdirs) {\n const subPath = join(dirPath, sub);\n let subEntries: string[];\n try {\n subEntries = readdirSync(subPath);\n } catch (err) {\n warn(logger, `Could not read subdirectory ${subPath}:`, err);\n continue;\n }\n const subJsonFiles: string[] = [];\n for (const subName of subEntries) {\n const subFullPath = join(subPath, subName);\n try {\n if (statSync(subFullPath).isDirectory()) {\n // Only one level of recursion — skip deeper nesting\n continue;\n }\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n warn(logger, `Could not stat ${subFullPath}:`, err);\n }\n continue;\n }\n if (subName.endsWith(\".json\")) {\n subJsonFiles.push(subName);\n }\n }\n subJsonFiles.sort();\n for (const subName of subJsonFiles) {\n const filePath = join(subPath, subName);\n fixtures.push(...loadFixtureFile(filePath, logger));\n }\n }\n\n return fixtures;\n}\n\n// ---------------------------------------------------------------------------\n// Fixture validation\n// ---------------------------------------------------------------------------\n\nexport interface ValidationResult {\n severity: \"error\" | \"warning\";\n fixtureIndex: number;\n message: string;\n}\n\nfunction validateReasoning(\n response: { reasoning?: unknown },\n fixtureIndex: number,\n results: ValidationResult[],\n): void {\n if (response.reasoning !== undefined) {\n if (typeof response.reasoning !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex,\n message: \"reasoning must be a string\",\n });\n } else if (response.reasoning === \"\") {\n results.push({\n severity: \"warning\",\n fixtureIndex,\n message: \"reasoning is empty string — no reasoning events will be emitted\",\n });\n }\n }\n}\n\nfunction validateWebSearches(\n response: { webSearches?: unknown },\n fixtureIndex: number,\n results: ValidationResult[],\n): void {\n if (response.webSearches !== undefined) {\n if (!Array.isArray(response.webSearches)) {\n results.push({\n severity: \"error\",\n fixtureIndex,\n message: \"webSearches must be an array of strings\",\n });\n } else if (response.webSearches.length === 0) {\n results.push({\n severity: \"warning\",\n fixtureIndex,\n message: \"webSearches is empty array — no web search events will be emitted\",\n });\n } else {\n for (let j = 0; j < response.webSearches.length; j++) {\n if (typeof response.webSearches[j] !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex,\n message: `webSearches[${j}] is not a string`,\n });\n break;\n }\n if (response.webSearches[j] === \"\") {\n results.push({\n severity: \"warning\",\n fixtureIndex,\n message: `webSearches[${j}] is empty string`,\n });\n }\n }\n }\n }\n}\n\nexport function validateFixtures(fixtures: Fixture[]): ValidationResult[] {\n const results: ValidationResult[] = [];\n\n const seenUserMessages = new Map<string, number>();\n\n for (let i = 0; i < fixtures.length; i++) {\n const f = fixtures[i];\n const response = f.response;\n\n // Skip response-shape validation for function responses — they are\n // evaluated at runtime so we cannot statically inspect them.\n if (typeof response === \"function\") {\n // Still validate match fields and numeric options below.\n } else {\n // --- Error checks ---\n\n // Response type recognition\n // Note: isContentWithToolCallsResponse must be checked before isTextResponse\n // and isToolCallResponse since it is a structural superset of both.\n if (\n !isContentWithToolCallsResponse(response) &&\n !isTextResponse(response) &&\n !isToolCallResponse(response) &&\n !isErrorResponse(response) &&\n !isEmbeddingResponse(response) &&\n !isImageResponse(response) &&\n !isAudioResponse(response) &&\n !isTranscriptionResponse(response) &&\n !isVideoResponse(response) &&\n !isJSONResponse(response)\n ) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message:\n \"response is not a recognized type (must have content, toolCalls, error, embedding, image, audio, transcription, video, or json)\",\n });\n }\n\n // Text response checks\n if (isTextResponse(response)) {\n if (response.content === \"\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"content is empty string\",\n });\n }\n validateReasoning(response, i, results);\n validateWebSearches(response, i, results);\n }\n\n // ContentWithToolCalls response checks\n if (isContentWithToolCallsResponse(response)) {\n if (response.content === \"\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"content is empty string\",\n });\n }\n if (response.toolCalls.length === 0) {\n results.push({\n severity: \"warning\",\n fixtureIndex: i,\n message: \"toolCalls array is empty — fixture will never produce tool calls\",\n });\n }\n for (let j = 0; j < response.toolCalls.length; j++) {\n const tc = response.toolCalls[j];\n if (!tc.name) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `toolCalls[${j}].name is empty`,\n });\n }\n try {\n JSON.parse(tc.arguments);\n } catch {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}`,\n });\n }\n }\n validateReasoning(response, i, results);\n validateWebSearches(response, i, results);\n }\n\n // Tool call response checks\n if (isToolCallResponse(response)) {\n if (response.toolCalls.length === 0) {\n results.push({\n severity: \"warning\",\n fixtureIndex: i,\n message: \"toolCalls array is empty — fixture will never produce tool calls\",\n });\n }\n for (let j = 0; j < response.toolCalls.length; j++) {\n const tc = response.toolCalls[j];\n if (!tc.name) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `toolCalls[${j}].name is empty`,\n });\n }\n try {\n JSON.parse(tc.arguments);\n } catch {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}`,\n });\n }\n }\n validateWebSearches(response, i, results);\n }\n\n // Error response checks\n if (isErrorResponse(response)) {\n if (!response.error.message) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"error.message is empty\",\n });\n }\n if (response.status !== undefined && (response.status < 100 || response.status > 599)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `error status ${response.status} is not a valid HTTP status code`,\n });\n }\n }\n\n // Embedding response checks\n if (isEmbeddingResponse(response)) {\n if (response.embedding.length === 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"embedding array is empty\",\n });\n }\n for (let j = 0; j < response.embedding.length; j++) {\n if (typeof response.embedding[j] !== \"number\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `embedding[${j}] is not a number`,\n });\n break; // one error is enough\n }\n }\n }\n\n // Audio response checks — validate object-form audio\n if (isAudioResponse(response) && typeof response.audio === \"object\") {\n const audioObj = response.audio;\n if (typeof audioObj.b64Json !== \"string\" || audioObj.b64Json === \"\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"audio.b64Json must be a non-empty string\",\n });\n }\n if (audioObj.contentType !== undefined && typeof audioObj.contentType !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `audio.contentType must be a string, got ${typeof audioObj.contentType}`,\n });\n }\n }\n\n // Validate ResponseOverrides fields\n if (\n isTextResponse(response) ||\n isToolCallResponse(response) ||\n isContentWithToolCallsResponse(response)\n ) {\n const r = response as ResponseOverrides;\n if (r.id !== undefined && typeof r.id !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"id\" must be a string, got ${typeof r.id}`,\n });\n }\n if (r.created !== undefined && (typeof r.created !== \"number\" || r.created < 0)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"created\" must be a non-negative number`,\n });\n }\n if (r.model !== undefined && typeof r.model !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"model\" must be a string, got ${typeof r.model}`,\n });\n }\n if (r.finishReason !== undefined && typeof r.finishReason !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"finishReason\" must be a string, got ${typeof r.finishReason}`,\n });\n }\n if (r.role !== undefined && typeof r.role !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"role\" must be a string, got ${typeof r.role}`,\n });\n }\n if (r.systemFingerprint !== undefined && typeof r.systemFingerprint !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"systemFingerprint\" must be a string, got ${typeof r.systemFingerprint}`,\n });\n }\n if (r.usage !== undefined) {\n if (typeof r.usage !== \"object\" || r.usage === null || Array.isArray(r.usage)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"usage\" must be an object`,\n });\n } else {\n // Check all known usage fields are numbers if present\n for (const key of Object.keys(r.usage)) {\n const val = (r.usage as Record<string, unknown>)[key];\n if (val !== undefined && typeof val !== \"number\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"usage.${key}\" must be a number, got ${typeof val}`,\n });\n }\n }\n }\n }\n }\n } // end: skip response-shape validation for function responses\n\n // Numeric sanity checks\n if (f.latency !== undefined && f.latency < 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"latency must be >= 0\",\n });\n }\n if (f.chunkSize !== undefined && f.chunkSize < 1) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"chunkSize must be >= 1\",\n });\n }\n if (f.truncateAfterChunks !== undefined && f.truncateAfterChunks < 1) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"truncateAfterChunks must be >= 1\",\n });\n }\n if (f.disconnectAfterMs !== undefined && f.disconnectAfterMs < 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"disconnectAfterMs must be >= 0\",\n });\n }\n if (f.streamingProfile !== undefined) {\n const sp = f.streamingProfile;\n if (sp.ttft !== undefined && sp.ttft < 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"streamingProfile.ttft must be >= 0\",\n });\n }\n if (sp.tps !== undefined && sp.tps <= 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"streamingProfile.tps must be > 0\",\n });\n }\n if (sp.jitter !== undefined && (sp.jitter < 0 || sp.jitter > 1)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"streamingProfile.jitter must be between 0 and 1\",\n });\n }\n }\n if (f.chaos !== undefined) {\n const ch = f.chaos;\n if (ch.dropRate !== undefined && (ch.dropRate < 0 || ch.dropRate > 1)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"chaos.dropRate must be between 0 and 1\",\n });\n }\n if (ch.malformedRate !== undefined && (ch.malformedRate < 0 || ch.malformedRate > 1)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"chaos.malformedRate must be between 0 and 1\",\n });\n }\n if (ch.disconnectRate !== undefined && (ch.disconnectRate < 0 || ch.disconnectRate > 1)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"chaos.disconnectRate must be between 0 and 1\",\n });\n }\n }\n\n // Match field type checks\n if (f.match.turnIndex !== undefined) {\n if (\n typeof f.match.turnIndex !== \"number\" ||\n f.match.turnIndex < 0 ||\n !Number.isInteger(f.match.turnIndex)\n ) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"match.turnIndex must be a non-negative integer\",\n });\n }\n }\n if (f.match.sequenceIndex !== undefined) {\n if (\n typeof f.match.sequenceIndex !== \"number\" ||\n f.match.sequenceIndex < 0 ||\n !Number.isInteger(f.match.sequenceIndex)\n ) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"match.sequenceIndex must be a non-negative integer\",\n });\n }\n }\n if (f.match.hasToolResult !== undefined && typeof f.match.hasToolResult !== \"boolean\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `match.hasToolResult must be a boolean, got ${typeof f.match.hasToolResult}`,\n });\n }\n if (f.match.systemMessage !== undefined) {\n const sm = f.match.systemMessage;\n if (typeof sm === \"string\") {\n // ok\n } else if (Array.isArray(sm)) {\n if (sm.length === 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `match.systemMessage array must contain at least one substring`,\n });\n } else {\n for (let j = 0; j < sm.length; j++) {\n if (typeof sm[j] !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `match.systemMessage[${j}] must be a string, got ${typeof sm[j]}`,\n });\n }\n }\n }\n } else {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `match.systemMessage must be a string or string[], got ${typeof sm}`,\n });\n }\n }\n\n // --- Warning checks ---\n\n // Duplicate userMessage shadowing — include turnIndex, hasToolResult, and\n // sequenceIndex in the dedup key so that fixtures which share a userMessage\n // but differ on those fields are NOT considered duplicates.\n const um = f.match.userMessage;\n if (typeof um === \"string\" && um) {\n const dedupKey = `${um}|${f.match.turnIndex}|${f.match.hasToolResult}|${f.match.sequenceIndex}`;\n const prev = seenUserMessages.get(dedupKey);\n if (prev !== undefined) {\n results.push({\n severity: \"warning\",\n fixtureIndex: i,\n message: `duplicate userMessage '${um}' — shadows fixture ${prev}`,\n });\n } else {\n seenUserMessages.set(dedupKey, i);\n }\n }\n\n // Catch-all not in last position\n const match = f.match;\n const hasDiscriminator =\n match.endpoint !== undefined ||\n match.userMessage !== undefined ||\n match.systemMessage !== undefined ||\n match.inputText !== undefined ||\n match.responseFormat !== undefined ||\n match.toolCallId !== undefined ||\n match.toolName !== undefined ||\n match.model !== undefined ||\n match.predicate !== undefined ||\n match.turnIndex !== undefined ||\n match.sequenceIndex !== undefined ||\n match.hasToolResult !== undefined;\n\n if (!hasDiscriminator && i < fixtures.length - 1) {\n results.push({\n severity: \"warning\",\n fixtureIndex: i,\n message: `empty match acts as catch-all but is not the last fixture — shadows fixtures ${i + 1}+`,\n });\n }\n }\n\n return results;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixture-loader.d.cts","names":[],"sources":["../src/fixture-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;AA6BA;;;AAA6D,iBAA7C,iBAAA,CAA6C,GAAA,EAAtB,mBAAsB,CAAA,EAAA,eAAA;
|
|
1
|
+
{"version":3,"file":"fixture-loader.d.cts","names":[],"sources":["../src/fixture-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;AA6BA;;;AAA6D,iBAA7C,iBAAA,CAA6C,GAAA,EAAtB,mBAAsB,CAAA,EAAA,eAAA;AA+D7C,iBAAA,eAAA,CAAe,QAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAA4B,MAA5B,CAAA,EAAqC,OAArC,EAAA;AAAA,iBA6Bf,mBAAA,CA7Be,OAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EA6B+B,MA7B/B,CAAA,EA6BwC,OA7BxC,EAAA;AAA4B,UA+G1C,gBAAA,CA/G0C;UAAS,EAAA,OAAA,GAAA,SAAA;EAAO,YAAA,EAAA,MAAA;EA6B3D,OAAA,EAAA,MAAA;;AAA8C,iBAsJ9C,gBAAA,CAtJ8C,QAAA,EAsJnB,OAtJmB,EAAA,CAAA,EAsJP,gBAtJO,EAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixture-loader.d.ts","names":[],"sources":["../src/fixture-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;AA6BA;;;AAA6D,iBAA7C,iBAAA,CAA6C,GAAA,EAAtB,mBAAsB,CAAA,EAAA,eAAA;
|
|
1
|
+
{"version":3,"file":"fixture-loader.d.ts","names":[],"sources":["../src/fixture-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;AA6BA;;;AAA6D,iBAA7C,iBAAA,CAA6C,GAAA,EAAtB,mBAAsB,CAAA,EAAA,eAAA;AA+D7C,iBAAA,eAAA,CAAe,QAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAA4B,MAA5B,CAAA,EAAqC,OAArC,EAAA;AAAA,iBA6Bf,mBAAA,CA7Be,OAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EA6B+B,MA7B/B,CAAA,EA6BwC,OA7BxC,EAAA;AAA4B,UA+G1C,gBAAA,CA/G0C;UAAS,EAAA,OAAA,GAAA,SAAA;EAAO,YAAA,EAAA,MAAA;EA6B3D,OAAA,EAAA,MAAA;;AAA8C,iBAsJ9C,gBAAA,CAtJ8C,QAAA,EAsJnB,OAtJmB,EAAA,CAAA,EAsJP,gBAtJO,EAAA"}
|