@copilotkit/aimock 1.12.0 → 1.14.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 +3 -3
- package/.claude-plugin/plugin.json +5 -5
- package/README.md +26 -3
- package/dist/_virtual/_rolldown/runtime.cjs +2 -0
- package/dist/_virtual/_rolldown/runtime.js +29 -0
- package/dist/a2a-types.d.cts.map +1 -1
- package/dist/a2a-types.d.ts.map +1 -1
- package/dist/aimock-cli.cjs +16 -0
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.d.cts +2 -0
- package/dist/aimock-cli.d.cts.map +1 -1
- package/dist/aimock-cli.d.ts +2 -0
- package/dist/aimock-cli.d.ts.map +1 -1
- package/dist/aimock-cli.js +16 -0
- package/dist/aimock-cli.js.map +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/convert-mockllm.cjs +232 -0
- package/dist/convert-mockllm.cjs.map +1 -0
- package/dist/convert-mockllm.js +230 -0
- package/dist/convert-mockllm.js.map +1 -0
- package/dist/convert-vidaimock.cjs +110 -0
- package/dist/convert-vidaimock.cjs.map +1 -0
- package/dist/convert-vidaimock.js +108 -0
- package/dist/convert-vidaimock.js.map +1 -0
- package/dist/convert.cjs +158 -0
- package/dist/convert.cjs.map +1 -0
- package/dist/convert.d.cts +16 -0
- package/dist/convert.d.cts.map +1 -0
- package/dist/convert.d.ts +16 -0
- package/dist/convert.d.ts.map +1 -0
- package/dist/convert.js +157 -0
- package/dist/convert.js.map +1 -0
- package/dist/fixture-loader.cjs +131 -29
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +9 -2
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +9 -2
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +132 -31
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini.cjs +76 -55
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts.map +1 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +77 -56
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +142 -76
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +14 -4
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +14 -4
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +142 -77
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +10 -0
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/jest.cjs +70 -0
- package/dist/jest.cjs.map +1 -0
- package/dist/jest.d.cts +33 -0
- package/dist/jest.d.cts.map +1 -0
- package/dist/jest.d.ts +33 -0
- package/dist/jest.d.ts.map +1 -0
- package/dist/jest.js +67 -0
- package/dist/jest.js.map +1 -0
- package/dist/llmock.cjs +1 -1
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +6 -6
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +6 -6
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +2 -2
- package/dist/llmock.js.map +1 -1
- package/dist/messages.cjs +69 -63
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.d.cts.map +1 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +70 -64
- package/dist/messages.js.map +1 -1
- package/dist/node_modules/.pnpm/@vitest_pretty-format@3.2.4/node_modules/@vitest/pretty-format/dist/index.cjs +934 -0
- package/dist/node_modules/.pnpm/@vitest_pretty-format@3.2.4/node_modules/@vitest/pretty-format/dist/index.cjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_pretty-format@3.2.4/node_modules/@vitest/pretty-format/dist/index.js +934 -0
- package/dist/node_modules/.pnpm/@vitest_pretty-format@3.2.4/node_modules/@vitest/pretty-format/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.cjs +1051 -0
- package/dist/node_modules/.pnpm/@vitest_runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.cjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js +1042 -0
- package/dist/node_modules/.pnpm/@vitest_runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_runner@3.2.4/node_modules/@vitest/runner/dist/index.cjs +1 -0
- package/dist/node_modules/.pnpm/@vitest_runner@3.2.4/node_modules/@vitest/runner/dist/index.js +3 -0
- package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/chunk-_commonjsHelpers.cjs +96 -0
- package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/chunk-_commonjsHelpers.cjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/chunk-_commonjsHelpers.js +93 -0
- package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/chunk-_commonjsHelpers.js.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/helpers.cjs +49 -0
- package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/helpers.cjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/helpers.js +43 -0
- package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/helpers.js.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/index.cjs +456 -0
- package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/index.cjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/index.js +456 -0
- package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/source-map.cjs +170 -0
- package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/source-map.cjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/source-map.js +169 -0
- package/dist/node_modules/.pnpm/@vitest_utils@3.2.4/node_modules/@vitest/utils/dist/source-map.js.map +1 -0
- package/dist/node_modules/.pnpm/js-tokens@9.0.1/node_modules/js-tokens/index.cjs +388 -0
- package/dist/node_modules/.pnpm/js-tokens@9.0.1/node_modules/js-tokens/index.cjs.map +1 -0
- package/dist/node_modules/.pnpm/js-tokens@9.0.1/node_modules/js-tokens/index.js +385 -0
- package/dist/node_modules/.pnpm/js-tokens@9.0.1/node_modules/js-tokens/index.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/arguments.cjs +12 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/arguments.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/arguments.js +12 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/arguments.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/array.cjs +17 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/array.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/array.js +17 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/array.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/bigint.cjs +12 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/bigint.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/bigint.js +12 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/bigint.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/class.cjs +16 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/class.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/class.js +16 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/class.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/date.cjs +14 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/date.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/date.js +14 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/date.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/error.cjs +35 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/error.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/error.js +35 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/error.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/function.cjs +13 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/function.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/function.js +13 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/function.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/helpers.cjs +128 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/helpers.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/helpers.js +123 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/helpers.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/html.cjs +41 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/html.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/html.js +40 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/html.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/index.cjs +100 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/index.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/index.js +100 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/index.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/map.cjs +26 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/map.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/map.js +26 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/map.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/number.cjs +15 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/number.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/number.js +15 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/number.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/object.cjs +22 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/object.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/object.js +22 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/object.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/promise.cjs +7 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/promise.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/promise.js +6 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/promise.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/regexp.cjs +13 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/regexp.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/regexp.js +13 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/regexp.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/set.cjs +19 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/set.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/set.js +19 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/set.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/string.cjs +26 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/string.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/string.js +26 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/string.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/symbol.cjs +10 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/symbol.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/symbol.js +9 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/symbol.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/typedarray.cjs +31 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/typedarray.cjs.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/typedarray.js +31 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/typedarray.js.map +1 -0
- package/dist/node_modules/.pnpm/strip-literal@3.1.0/node_modules/strip-literal/dist/index.cjs +52 -0
- package/dist/node_modules/.pnpm/strip-literal@3.1.0/node_modules/strip-literal/dist/index.cjs.map +1 -0
- package/dist/node_modules/.pnpm/strip-literal@3.1.0/node_modules/strip-literal/dist/index.js +52 -0
- package/dist/node_modules/.pnpm/strip-literal@3.1.0/node_modules/strip-literal/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/tinyrainbow@2.0.0/node_modules/tinyrainbow/dist/chunk-BVHSVHOK.cjs +83 -0
- package/dist/node_modules/.pnpm/tinyrainbow@2.0.0/node_modules/tinyrainbow/dist/chunk-BVHSVHOK.cjs.map +1 -0
- package/dist/node_modules/.pnpm/tinyrainbow@2.0.0/node_modules/tinyrainbow/dist/chunk-BVHSVHOK.js +82 -0
- package/dist/node_modules/.pnpm/tinyrainbow@2.0.0/node_modules/tinyrainbow/dist/chunk-BVHSVHOK.js.map +1 -0
- package/dist/node_modules/.pnpm/tinyrainbow@2.0.0/node_modules/tinyrainbow/dist/node.cjs +10 -0
- package/dist/node_modules/.pnpm/tinyrainbow@2.0.0/node_modules/tinyrainbow/dist/node.cjs.map +1 -0
- package/dist/node_modules/.pnpm/tinyrainbow@2.0.0/node_modules/tinyrainbow/dist/node.js +10 -0
- package/dist/node_modules/.pnpm/tinyrainbow@2.0.0/node_modules/tinyrainbow/dist/node.js.map +1 -0
- package/dist/recorder.cjs +1 -1
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.js +1 -1
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +66 -57
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts +3 -3
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts +3 -3
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +67 -58
- package/dist/responses.js.map +1 -1
- package/dist/server.cjs +57 -30
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +58 -31
- package/dist/server.js.map +1 -1
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js.map +1 -1
- package/dist/types.d.cts +64 -11
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +64 -11
- package/dist/types.d.ts.map +1 -1
- package/dist/vitest.cjs +80 -0
- package/dist/vitest.cjs.map +1 -0
- package/dist/vitest.d.cts +30 -0
- package/dist/vitest.d.cts.map +1 -0
- package/dist/vitest.d.ts +30 -0
- package/dist/vitest.d.ts.map +1 -0
- package/dist/vitest.js +77 -0
- package/dist/vitest.js.map +1 -0
- package/fixtures/example-multi-turn.json +1 -1
- package/fixtures/example-tool-call.json +1 -1
- package/fixtures/examples/a2a/a2a-config.json +42 -0
- package/fixtures/examples/adk/gemini-agent.json +47 -0
- package/fixtures/examples/agui/agui-text-response.json +35 -0
- package/fixtures/examples/chaos/chaos-config.json +10 -0
- package/fixtures/examples/crewai/multi-agent-crew.json +16 -0
- package/fixtures/examples/full-suite.json +116 -0
- package/fixtures/examples/langchain/agent-loop.json +27 -0
- package/fixtures/examples/llamaindex/aimock-config.json +62 -0
- package/fixtures/examples/llamaindex/rag-pipeline.json +34 -0
- package/fixtures/examples/llm/embeddings.json +10 -0
- package/fixtures/examples/llm/error-injection.json +15 -0
- package/fixtures/examples/llm/sequential-responses.json +20 -0
- package/fixtures/examples/llm/streaming-physics.json +15 -0
- package/fixtures/examples/mastra/agent-workflow.json +32 -0
- package/fixtures/examples/mcp/mcp-config.json +26 -0
- package/fixtures/examples/pydanticai/structured-output.json +15 -0
- package/fixtures/examples/record-replay/record-config.json +11 -0
- package/fixtures/examples/vector/vector-config.json +34 -0
- package/package.json +60 -1
- package/skills/write-fixtures/SKILL.md +148 -22
package/dist/fixture-loader.cjs
CHANGED
|
@@ -4,6 +4,23 @@ let node_path = require("node:path");
|
|
|
4
4
|
let node_fs = require("node:fs");
|
|
5
5
|
|
|
6
6
|
//#region src/fixture-loader.ts
|
|
7
|
+
/**
|
|
8
|
+
* Auto-stringify object-valued `content` and `toolCalls[].arguments` fields.
|
|
9
|
+
* This lets fixture authors write plain JSON objects instead of escaped strings.
|
|
10
|
+
* All other fields (including ResponseOverrides) pass through unmodified.
|
|
11
|
+
*/
|
|
12
|
+
function normalizeResponse(raw) {
|
|
13
|
+
const response = { ...raw };
|
|
14
|
+
if (typeof response.content === "object" && response.content !== null) response.content = JSON.stringify(response.content);
|
|
15
|
+
if (Array.isArray(response.toolCalls)) response.toolCalls = response.toolCalls.map((tc) => {
|
|
16
|
+
if (typeof tc.arguments === "object" && tc.arguments !== null) return {
|
|
17
|
+
...tc,
|
|
18
|
+
arguments: JSON.stringify(tc.arguments)
|
|
19
|
+
};
|
|
20
|
+
return tc;
|
|
21
|
+
});
|
|
22
|
+
return response;
|
|
23
|
+
}
|
|
7
24
|
function entryToFixture(entry) {
|
|
8
25
|
return {
|
|
9
26
|
match: {
|
|
@@ -13,9 +30,10 @@ function entryToFixture(entry) {
|
|
|
13
30
|
toolName: entry.match.toolName,
|
|
14
31
|
model: entry.match.model,
|
|
15
32
|
responseFormat: entry.match.responseFormat,
|
|
33
|
+
endpoint: entry.match.endpoint,
|
|
16
34
|
...entry.match.sequenceIndex !== void 0 && { sequenceIndex: entry.match.sequenceIndex }
|
|
17
35
|
},
|
|
18
|
-
response: entry.response,
|
|
36
|
+
response: normalizeResponse(entry.response),
|
|
19
37
|
...entry.latency !== void 0 && { latency: entry.latency },
|
|
20
38
|
...entry.chunkSize !== void 0 && { chunkSize: entry.chunkSize },
|
|
21
39
|
...entry.truncateAfterChunks !== void 0 && { truncateAfterChunks: entry.truncateAfterChunks },
|
|
@@ -79,16 +97,57 @@ function loadFixturesFromDir(dirPath, logger) {
|
|
|
79
97
|
}
|
|
80
98
|
return fixtures;
|
|
81
99
|
}
|
|
100
|
+
function validateReasoning(response, fixtureIndex, results) {
|
|
101
|
+
if (response.reasoning !== void 0) {
|
|
102
|
+
if (typeof response.reasoning !== "string") results.push({
|
|
103
|
+
severity: "error",
|
|
104
|
+
fixtureIndex,
|
|
105
|
+
message: "reasoning must be a string"
|
|
106
|
+
});
|
|
107
|
+
else if (response.reasoning === "") results.push({
|
|
108
|
+
severity: "warning",
|
|
109
|
+
fixtureIndex,
|
|
110
|
+
message: "reasoning is empty string — no reasoning events will be emitted"
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function validateWebSearches(response, fixtureIndex, results) {
|
|
115
|
+
if (response.webSearches !== void 0) if (!Array.isArray(response.webSearches)) results.push({
|
|
116
|
+
severity: "error",
|
|
117
|
+
fixtureIndex,
|
|
118
|
+
message: "webSearches must be an array of strings"
|
|
119
|
+
});
|
|
120
|
+
else if (response.webSearches.length === 0) results.push({
|
|
121
|
+
severity: "warning",
|
|
122
|
+
fixtureIndex,
|
|
123
|
+
message: "webSearches is empty array — no web search events will be emitted"
|
|
124
|
+
});
|
|
125
|
+
else for (let j = 0; j < response.webSearches.length; j++) {
|
|
126
|
+
if (typeof response.webSearches[j] !== "string") {
|
|
127
|
+
results.push({
|
|
128
|
+
severity: "error",
|
|
129
|
+
fixtureIndex,
|
|
130
|
+
message: `webSearches[${j}] is not a string`
|
|
131
|
+
});
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
if (response.webSearches[j] === "") results.push({
|
|
135
|
+
severity: "warning",
|
|
136
|
+
fixtureIndex,
|
|
137
|
+
message: `webSearches[${j}] is empty string`
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
82
141
|
function validateFixtures(fixtures) {
|
|
83
142
|
const results = [];
|
|
84
143
|
const seenUserMessages = /* @__PURE__ */ new Map();
|
|
85
144
|
for (let i = 0; i < fixtures.length; i++) {
|
|
86
145
|
const f = fixtures[i];
|
|
87
146
|
const response = f.response;
|
|
88
|
-
if (!require_helpers.isTextResponse(response) && !require_helpers.isToolCallResponse(response) && !require_helpers.isErrorResponse(response) && !require_helpers.isEmbeddingResponse(response)) results.push({
|
|
147
|
+
if (!require_helpers.isContentWithToolCallsResponse(response) && !require_helpers.isTextResponse(response) && !require_helpers.isToolCallResponse(response) && !require_helpers.isErrorResponse(response) && !require_helpers.isEmbeddingResponse(response) && !require_helpers.isImageResponse(response) && !require_helpers.isAudioResponse(response) && !require_helpers.isTranscriptionResponse(response) && !require_helpers.isVideoResponse(response)) results.push({
|
|
89
148
|
severity: "error",
|
|
90
149
|
fixtureIndex: i,
|
|
91
|
-
message: "response is not a recognized type (must have content, toolCalls, error, or
|
|
150
|
+
message: "response is not a recognized type (must have content, toolCalls, error, embedding, image, audio, transcription, or video)"
|
|
92
151
|
});
|
|
93
152
|
if (require_helpers.isTextResponse(response)) {
|
|
94
153
|
if (response.content === "") results.push({
|
|
@@ -96,43 +155,39 @@ function validateFixtures(fixtures) {
|
|
|
96
155
|
fixtureIndex: i,
|
|
97
156
|
message: "content is empty string"
|
|
98
157
|
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
});
|
|
105
|
-
else if (response.reasoning === "") results.push({
|
|
106
|
-
severity: "warning",
|
|
107
|
-
fixtureIndex: i,
|
|
108
|
-
message: "reasoning is empty string — no reasoning events will be emitted"
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
if (response.webSearches !== void 0) if (!Array.isArray(response.webSearches)) results.push({
|
|
158
|
+
validateReasoning(response, i, results);
|
|
159
|
+
validateWebSearches(response, i, results);
|
|
160
|
+
}
|
|
161
|
+
if (require_helpers.isContentWithToolCallsResponse(response)) {
|
|
162
|
+
if (response.content === "") results.push({
|
|
112
163
|
severity: "error",
|
|
113
164
|
fixtureIndex: i,
|
|
114
|
-
message: "
|
|
165
|
+
message: "content is empty string"
|
|
115
166
|
});
|
|
116
|
-
|
|
167
|
+
if (response.toolCalls.length === 0) results.push({
|
|
117
168
|
severity: "warning",
|
|
118
169
|
fixtureIndex: i,
|
|
119
|
-
message: "
|
|
170
|
+
message: "toolCalls array is empty — fixture will never produce tool calls"
|
|
120
171
|
});
|
|
121
|
-
|
|
122
|
-
|
|
172
|
+
for (let j = 0; j < response.toolCalls.length; j++) {
|
|
173
|
+
const tc = response.toolCalls[j];
|
|
174
|
+
if (!tc.name) results.push({
|
|
175
|
+
severity: "error",
|
|
176
|
+
fixtureIndex: i,
|
|
177
|
+
message: `toolCalls[${j}].name is empty`
|
|
178
|
+
});
|
|
179
|
+
try {
|
|
180
|
+
JSON.parse(tc.arguments);
|
|
181
|
+
} catch {
|
|
123
182
|
results.push({
|
|
124
183
|
severity: "error",
|
|
125
184
|
fixtureIndex: i,
|
|
126
|
-
message: `
|
|
185
|
+
message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}`
|
|
127
186
|
});
|
|
128
|
-
break;
|
|
129
187
|
}
|
|
130
|
-
if (response.webSearches[j] === "") results.push({
|
|
131
|
-
severity: "warning",
|
|
132
|
-
fixtureIndex: i,
|
|
133
|
-
message: `webSearches[${j}] is empty string`
|
|
134
|
-
});
|
|
135
188
|
}
|
|
189
|
+
validateReasoning(response, i, results);
|
|
190
|
+
validateWebSearches(response, i, results);
|
|
136
191
|
}
|
|
137
192
|
if (require_helpers.isToolCallResponse(response)) {
|
|
138
193
|
if (response.toolCalls.length === 0) results.push({
|
|
@@ -185,6 +240,52 @@ function validateFixtures(fixtures) {
|
|
|
185
240
|
break;
|
|
186
241
|
}
|
|
187
242
|
}
|
|
243
|
+
if (require_helpers.isTextResponse(response) || require_helpers.isToolCallResponse(response) || require_helpers.isContentWithToolCallsResponse(response)) {
|
|
244
|
+
const r = response;
|
|
245
|
+
if (r.id !== void 0 && typeof r.id !== "string") results.push({
|
|
246
|
+
severity: "error",
|
|
247
|
+
fixtureIndex: i,
|
|
248
|
+
message: `override "id" must be a string, got ${typeof r.id}`
|
|
249
|
+
});
|
|
250
|
+
if (r.created !== void 0 && (typeof r.created !== "number" || r.created < 0)) results.push({
|
|
251
|
+
severity: "error",
|
|
252
|
+
fixtureIndex: i,
|
|
253
|
+
message: `override "created" must be a non-negative number`
|
|
254
|
+
});
|
|
255
|
+
if (r.model !== void 0 && typeof r.model !== "string") results.push({
|
|
256
|
+
severity: "error",
|
|
257
|
+
fixtureIndex: i,
|
|
258
|
+
message: `override "model" must be a string, got ${typeof r.model}`
|
|
259
|
+
});
|
|
260
|
+
if (r.finishReason !== void 0 && typeof r.finishReason !== "string") results.push({
|
|
261
|
+
severity: "error",
|
|
262
|
+
fixtureIndex: i,
|
|
263
|
+
message: `override "finishReason" must be a string, got ${typeof r.finishReason}`
|
|
264
|
+
});
|
|
265
|
+
if (r.role !== void 0 && typeof r.role !== "string") results.push({
|
|
266
|
+
severity: "error",
|
|
267
|
+
fixtureIndex: i,
|
|
268
|
+
message: `override "role" must be a string, got ${typeof r.role}`
|
|
269
|
+
});
|
|
270
|
+
if (r.systemFingerprint !== void 0 && typeof r.systemFingerprint !== "string") results.push({
|
|
271
|
+
severity: "error",
|
|
272
|
+
fixtureIndex: i,
|
|
273
|
+
message: `override "systemFingerprint" must be a string, got ${typeof r.systemFingerprint}`
|
|
274
|
+
});
|
|
275
|
+
if (r.usage !== void 0) if (typeof r.usage !== "object" || r.usage === null || Array.isArray(r.usage)) results.push({
|
|
276
|
+
severity: "error",
|
|
277
|
+
fixtureIndex: i,
|
|
278
|
+
message: `override "usage" must be an object`
|
|
279
|
+
});
|
|
280
|
+
else for (const key of Object.keys(r.usage)) {
|
|
281
|
+
const val = r.usage[key];
|
|
282
|
+
if (val !== void 0 && typeof val !== "number") results.push({
|
|
283
|
+
severity: "error",
|
|
284
|
+
fixtureIndex: i,
|
|
285
|
+
message: `override "usage.${key}" must be a number, got ${typeof val}`
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
188
289
|
if (f.latency !== void 0 && f.latency < 0) results.push({
|
|
189
290
|
severity: "error",
|
|
190
291
|
fixtureIndex: i,
|
|
@@ -252,7 +353,7 @@ function validateFixtures(fixtures) {
|
|
|
252
353
|
else seenUserMessages.set(um, i);
|
|
253
354
|
}
|
|
254
355
|
const match = f.match;
|
|
255
|
-
if (!(match.userMessage !== void 0 || match.inputText !== void 0 || match.responseFormat !== void 0 || match.toolCallId !== void 0 || match.toolName !== void 0 || match.model !== void 0 || match.predicate !== void 0) && i < fixtures.length - 1) results.push({
|
|
356
|
+
if (!(match.endpoint !== void 0 || match.userMessage !== void 0 || match.inputText !== void 0 || match.responseFormat !== void 0 || match.toolCallId !== void 0 || match.toolName !== void 0 || match.model !== void 0 || match.predicate !== void 0) && i < fixtures.length - 1) results.push({
|
|
256
357
|
severity: "warning",
|
|
257
358
|
fixtureIndex: i,
|
|
258
359
|
message: `empty match acts as catch-all but is not the last fixture — shadows fixtures ${i + 1}+`
|
|
@@ -265,5 +366,6 @@ function validateFixtures(fixtures) {
|
|
|
265
366
|
exports.entryToFixture = entryToFixture;
|
|
266
367
|
exports.loadFixtureFile = loadFixtureFile;
|
|
267
368
|
exports.loadFixturesFromDir = loadFixturesFromDir;
|
|
369
|
+
exports.normalizeResponse = normalizeResponse;
|
|
268
370
|
exports.validateFixtures = validateFixtures;
|
|
269
371
|
//# sourceMappingURL=fixture-loader.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixture-loader.cjs","names":["isTextResponse","isToolCallResponse","isErrorResponse","isEmbeddingResponse"],"sources":["../src/fixture-loader.ts"],"sourcesContent":["import { readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Fixture, FixtureFile, FixtureFileEntry } from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isErrorResponse,\n isEmbeddingResponse,\n} from \"./helpers.js\";\nimport type { Logger } from \"./logger.js\";\n\nexport function entryToFixture(entry: FixtureFileEntry): Fixture {\n return {\n match: {\n userMessage: entry.match.userMessage,\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 ...(entry.match.sequenceIndex !== undefined && { sequenceIndex: entry.match.sequenceIndex }),\n },\n response: 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 for (const name of entries) {\n const fullPath = join(dirPath, name);\n try {\n if (statSync(fullPath).isDirectory()) {\n warn(logger, `Skipping subdirectory ${fullPath} (fixtures are not loaded recursively)`);\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 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\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 // --- Error checks ---\n\n // Response type recognition\n if (\n !isTextResponse(response) &&\n !isToolCallResponse(response) &&\n !isErrorResponse(response) &&\n !isEmbeddingResponse(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, or embedding)\",\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 if (response.reasoning !== undefined) {\n if (typeof response.reasoning !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"reasoning must be a string\",\n });\n } else if (response.reasoning === \"\") {\n results.push({\n severity: \"warning\",\n fixtureIndex: i,\n message: \"reasoning is empty string — no reasoning events will be emitted\",\n });\n }\n }\n if (response.webSearches !== undefined) {\n if (!Array.isArray(response.webSearches)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\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: i,\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: i,\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: i,\n message: `webSearches[${j}] is empty string`,\n });\n }\n }\n }\n }\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 }\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 // 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 // --- Warning checks ---\n\n // Duplicate userMessage shadowing\n const um = f.match.userMessage;\n if (typeof um === \"string\" && um) {\n const prev = seenUserMessages.get(um);\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(um, i);\n }\n }\n\n // Catch-all not in last position\n const match = f.match;\n const hasDiscriminator =\n match.userMessage !== 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\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"],"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} 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 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 },\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 for (const name of entries) {\n const fullPath = join(dirPath, name);\n try {\n if (statSync(fullPath).isDirectory()) {\n warn(logger, `Skipping subdirectory ${fullPath} (fixtures are not loaded recursively)`);\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 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 // --- 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 ) {\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, or video)\",\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 }\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 // 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\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 // --- Warning checks ---\n\n // Duplicate userMessage shadowing\n const um = f.match.userMessage;\n if (typeof um === \"string\" && um) {\n const prev = seenUserMessages.get(um);\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(um, 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.inputText !== undefined ||\n match.responseFormat !== undefined ||\n match.toolCallId !== undefined ||\n match.toolName !== undefined ||\n match.model !== undefined ||\n match.predicate !== 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":";;;;;;;;;;;AA4BA,SAAgB,kBAAkB,KAA2C;CAE3E,MAAM,WAAW,EAAE,GAAG,KAAK;AAG3B,KAAI,OAAO,SAAS,YAAY,YAAY,SAAS,YAAY,KAC/D,UAAS,UAAU,KAAK,UAAU,SAAS,QAAQ;AAIrD,KAAI,MAAM,QAAQ,SAAS,UAAU,CACnC,UAAS,YAAa,SAAS,UAA6C,KAAK,OAAO;AACtF,MAAI,OAAO,GAAG,cAAc,YAAY,GAAG,cAAc,KACvD,QAAO;GAAE,GAAG;GAAI,WAAW,KAAK,UAAU,GAAG,UAAU;GAAE;AAE3D,SAAO;GACP;AAGJ,QAAO;;AAGT,SAAgB,eAAe,OAAkC;AAC/D,QAAO;EACL,OAAO;GACL,aAAa,MAAM,MAAM;GACzB,WAAW,MAAM,MAAM;GACvB,YAAY,MAAM,MAAM;GACxB,UAAU,MAAM,MAAM;GACtB,OAAO,MAAM,MAAM;GACnB,gBAAgB,MAAM,MAAM;GAC5B,UAAU,MAAM,MAAM;GACtB,GAAI,MAAM,MAAM,kBAAkB,UAAa,EAAE,eAAe,MAAM,MAAM,eAAe;GAC5F;EACD,UAAU,kBAAkB,MAAM,SAAS;EAC3C,GAAI,MAAM,YAAY,UAAa,EAAE,SAAS,MAAM,SAAS;EAC7D,GAAI,MAAM,cAAc,UAAa,EAAE,WAAW,MAAM,WAAW;EACnE,GAAI,MAAM,wBAAwB,UAAa,EAC7C,qBAAqB,MAAM,qBAC5B;EACD,GAAI,MAAM,sBAAsB,UAAa,EAAE,mBAAmB,MAAM,mBAAmB;EAC3F,GAAI,MAAM,qBAAqB,UAAa,EAAE,kBAAkB,MAAM,kBAAkB;EACxF,GAAI,MAAM,UAAU,UAAa,EAAE,OAAO,MAAM,OAAO;EACxD;;AAIH,SAAS,KAAK,QAA4B,KAAa,GAAG,MAAuB;AAC/E,KAAI,OACF,QAAO,KAAK,KAAK,GAAG,KAAK;KAEzB,SAAQ,KAAK,oBAAoB,OAAO,GAAG,KAAK;;AAIpD,SAAgB,gBAAgB,UAAkB,QAA4B;CAC5E,IAAI;AACJ,KAAI;AACF,kCAAmB,UAAU,QAAQ;UAC9B,KAAK;AACZ,OAAK,QAAQ,uBAAuB,SAAS,IAAI,IAAI;AACrD,SAAO,EAAE;;CAGX,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,IAAI;UACjB,KAAK;AACZ,OAAK,QAAQ,mBAAmB,SAAS,IAAI,IAAI;AACjD,SAAO,EAAE;;AAGX,KACE,OAAO,WAAW,YAClB,WAAW,QACX,CAAC,MAAM,QAAS,OAAuB,SAAS,EAChD;AACA,OAAK,QAAQ,0CAA0C,WAAW;AAClE,SAAO,EAAE;;AAGX,QAAQ,OAAuB,SAAS,IAAI,eAAe;;AAG7D,SAAgB,oBAAoB,SAAiB,QAA4B;CAC/E,IAAI;AACJ,KAAI;AACF,qCAAsB,QAAQ;UACvB,KAAK;AACZ,OAAK,QAAQ,4BAA4B,QAAQ,IAAI,IAAI;AACzD,SAAO,EAAE;;CAGX,MAAM,YAAsB,EAAE;AAC9B,MAAK,MAAM,QAAQ,SAAS;EAC1B,MAAM,+BAAgB,SAAS,KAAK;AACpC,MAAI;AACF,6BAAa,SAAS,CAAC,aAAa,EAAE;AACpC,SAAK,QAAQ,yBAAyB,SAAS,wCAAwC;AACvF;;WAEK,KAAK;AAEZ,OADc,IAA8B,SAC/B,SACX,MAAK,QAAQ,kBAAkB,SAAS,IAAI,IAAI;AAElD;;AAEF,MAAI,KAAK,SAAS,QAAQ,CACxB,WAAU,KAAK,KAAK;;AAGxB,WAAU,MAAM;CAEhB,MAAM,WAAsB,EAAE;AAC9B,MAAK,MAAM,QAAQ,WAAW;EAC5B,MAAM,+BAAgB,SAAS,KAAK;AACpC,WAAS,KAAK,GAAG,gBAAgB,UAAU,OAAO,CAAC;;AAGrD,QAAO;;AAaT,SAAS,kBACP,UACA,cACA,SACM;AACN,KAAI,SAAS,cAAc,QACzB;MAAI,OAAO,SAAS,cAAc,SAChC,SAAQ,KAAK;GACX,UAAU;GACV;GACA,SAAS;GACV,CAAC;WACO,SAAS,cAAc,GAChC,SAAQ,KAAK;GACX,UAAU;GACV;GACA,SAAS;GACV,CAAC;;;AAKR,SAAS,oBACP,UACA,cACA,SACM;AACN,KAAI,SAAS,gBAAgB,OAC3B,KAAI,CAAC,MAAM,QAAQ,SAAS,YAAY,CACtC,SAAQ,KAAK;EACX,UAAU;EACV;EACA,SAAS;EACV,CAAC;UACO,SAAS,YAAY,WAAW,EACzC,SAAQ,KAAK;EACX,UAAU;EACV;EACA,SAAS;EACV,CAAC;KAEF,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,YAAY,QAAQ,KAAK;AACpD,MAAI,OAAO,SAAS,YAAY,OAAO,UAAU;AAC/C,WAAQ,KAAK;IACX,UAAU;IACV;IACA,SAAS,eAAe,EAAE;IAC3B,CAAC;AACF;;AAEF,MAAI,SAAS,YAAY,OAAO,GAC9B,SAAQ,KAAK;GACX,UAAU;GACV;GACA,SAAS,eAAe,EAAE;GAC3B,CAAC;;;AAOZ,SAAgB,iBAAiB,UAAyC;CACxE,MAAM,UAA8B,EAAE;CAEtC,MAAM,mCAAmB,IAAI,KAAqB;AAElD,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,IAAI,SAAS;EACnB,MAAM,WAAW,EAAE;AAOnB,MACE,CAACA,+CAA+B,SAAS,IACzC,CAACC,+BAAe,SAAS,IACzB,CAACC,mCAAmB,SAAS,IAC7B,CAACC,gCAAgB,SAAS,IAC1B,CAACC,oCAAoB,SAAS,IAC9B,CAACC,gCAAgB,SAAS,IAC1B,CAACC,gCAAgB,SAAS,IAC1B,CAACC,wCAAwB,SAAS,IAClC,CAACC,gCAAgB,SAAS,CAE1B,SAAQ,KAAK;GACX,UAAU;GACV,cAAc;GACd,SACE;GACH,CAAC;AAIJ,MAAIP,+BAAe,SAAS,EAAE;AAC5B,OAAI,SAAS,YAAY,GACvB,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS;IACV,CAAC;AAEJ,qBAAkB,UAAU,GAAG,QAAQ;AACvC,uBAAoB,UAAU,GAAG,QAAQ;;AAI3C,MAAID,+CAA+B,SAAS,EAAE;AAC5C,OAAI,SAAS,YAAY,GACvB,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS;IACV,CAAC;AAEJ,OAAI,SAAS,UAAU,WAAW,EAChC,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS;IACV,CAAC;AAEJ,QAAK,IAAI,IAAI,GAAG,IAAI,SAAS,UAAU,QAAQ,KAAK;IAClD,MAAM,KAAK,SAAS,UAAU;AAC9B,QAAI,CAAC,GAAG,KACN,SAAQ,KAAK;KACX,UAAU;KACV,cAAc;KACd,SAAS,aAAa,EAAE;KACzB,CAAC;AAEJ,QAAI;AACF,UAAK,MAAM,GAAG,UAAU;YAClB;AACN,aAAQ,KAAK;MACX,UAAU;MACV,cAAc;MACd,SAAS,aAAa,EAAE,iCAAiC,GAAG;MAC7D,CAAC;;;AAGN,qBAAkB,UAAU,GAAG,QAAQ;AACvC,uBAAoB,UAAU,GAAG,QAAQ;;AAI3C,MAAIE,mCAAmB,SAAS,EAAE;AAChC,OAAI,SAAS,UAAU,WAAW,EAChC,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS;IACV,CAAC;AAEJ,QAAK,IAAI,IAAI,GAAG,IAAI,SAAS,UAAU,QAAQ,KAAK;IAClD,MAAM,KAAK,SAAS,UAAU;AAC9B,QAAI,CAAC,GAAG,KACN,SAAQ,KAAK;KACX,UAAU;KACV,cAAc;KACd,SAAS,aAAa,EAAE;KACzB,CAAC;AAEJ,QAAI;AACF,UAAK,MAAM,GAAG,UAAU;YAClB;AACN,aAAQ,KAAK;MACX,UAAU;MACV,cAAc;MACd,SAAS,aAAa,EAAE,iCAAiC,GAAG;MAC7D,CAAC;;;;AAMR,MAAIC,gCAAgB,SAAS,EAAE;AAC7B,OAAI,CAAC,SAAS,MAAM,QAClB,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS;IACV,CAAC;AAEJ,OAAI,SAAS,WAAW,WAAc,SAAS,SAAS,OAAO,SAAS,SAAS,KAC/E,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS,gBAAgB,SAAS,OAAO;IAC1C,CAAC;;AAKN,MAAIC,oCAAoB,SAAS,EAAE;AACjC,OAAI,SAAS,UAAU,WAAW,EAChC,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS;IACV,CAAC;AAEJ,QAAK,IAAI,IAAI,GAAG,IAAI,SAAS,UAAU,QAAQ,IAC7C,KAAI,OAAO,SAAS,UAAU,OAAO,UAAU;AAC7C,YAAQ,KAAK;KACX,UAAU;KACV,cAAc;KACd,SAAS,aAAa,EAAE;KACzB,CAAC;AACF;;;AAMN,MACEH,+BAAe,SAAS,IACxBC,mCAAmB,SAAS,IAC5BF,+CAA+B,SAAS,EACxC;GACA,MAAM,IAAI;AACV,OAAI,EAAE,OAAO,UAAa,OAAO,EAAE,OAAO,SACxC,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS,uCAAuC,OAAO,EAAE;IAC1D,CAAC;AAEJ,OAAI,EAAE,YAAY,WAAc,OAAO,EAAE,YAAY,YAAY,EAAE,UAAU,GAC3E,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS;IACV,CAAC;AAEJ,OAAI,EAAE,UAAU,UAAa,OAAO,EAAE,UAAU,SAC9C,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS,0CAA0C,OAAO,EAAE;IAC7D,CAAC;AAEJ,OAAI,EAAE,iBAAiB,UAAa,OAAO,EAAE,iBAAiB,SAC5D,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS,iDAAiD,OAAO,EAAE;IACpE,CAAC;AAEJ,OAAI,EAAE,SAAS,UAAa,OAAO,EAAE,SAAS,SAC5C,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS,yCAAyC,OAAO,EAAE;IAC5D,CAAC;AAEJ,OAAI,EAAE,sBAAsB,UAAa,OAAO,EAAE,sBAAsB,SACtE,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS,sDAAsD,OAAO,EAAE;IACzE,CAAC;AAEJ,OAAI,EAAE,UAAU,OACd,KAAI,OAAO,EAAE,UAAU,YAAY,EAAE,UAAU,QAAQ,MAAM,QAAQ,EAAE,MAAM,CAC3E,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS;IACV,CAAC;OAGF,MAAK,MAAM,OAAO,OAAO,KAAK,EAAE,MAAM,EAAE;IACtC,MAAM,MAAO,EAAE,MAAkC;AACjD,QAAI,QAAQ,UAAa,OAAO,QAAQ,SACtC,SAAQ,KAAK;KACX,UAAU;KACV,cAAc;KACd,SAAS,mBAAmB,IAAI,0BAA0B,OAAO;KAClE,CAAC;;;AAQZ,MAAI,EAAE,YAAY,UAAa,EAAE,UAAU,EACzC,SAAQ,KAAK;GACX,UAAU;GACV,cAAc;GACd,SAAS;GACV,CAAC;AAEJ,MAAI,EAAE,cAAc,UAAa,EAAE,YAAY,EAC7C,SAAQ,KAAK;GACX,UAAU;GACV,cAAc;GACd,SAAS;GACV,CAAC;AAEJ,MAAI,EAAE,wBAAwB,UAAa,EAAE,sBAAsB,EACjE,SAAQ,KAAK;GACX,UAAU;GACV,cAAc;GACd,SAAS;GACV,CAAC;AAEJ,MAAI,EAAE,sBAAsB,UAAa,EAAE,oBAAoB,EAC7D,SAAQ,KAAK;GACX,UAAU;GACV,cAAc;GACd,SAAS;GACV,CAAC;AAEJ,MAAI,EAAE,qBAAqB,QAAW;GACpC,MAAM,KAAK,EAAE;AACb,OAAI,GAAG,SAAS,UAAa,GAAG,OAAO,EACrC,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS;IACV,CAAC;AAEJ,OAAI,GAAG,QAAQ,UAAa,GAAG,OAAO,EACpC,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS;IACV,CAAC;AAEJ,OAAI,GAAG,WAAW,WAAc,GAAG,SAAS,KAAK,GAAG,SAAS,GAC3D,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS;IACV,CAAC;;AAGN,MAAI,EAAE,UAAU,QAAW;GACzB,MAAM,KAAK,EAAE;AACb,OAAI,GAAG,aAAa,WAAc,GAAG,WAAW,KAAK,GAAG,WAAW,GACjE,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS;IACV,CAAC;AAEJ,OAAI,GAAG,kBAAkB,WAAc,GAAG,gBAAgB,KAAK,GAAG,gBAAgB,GAChF,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS;IACV,CAAC;AAEJ,OAAI,GAAG,mBAAmB,WAAc,GAAG,iBAAiB,KAAK,GAAG,iBAAiB,GACnF,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS;IACV,CAAC;;EAON,MAAM,KAAK,EAAE,MAAM;AACnB,MAAI,OAAO,OAAO,YAAY,IAAI;GAChC,MAAM,OAAO,iBAAiB,IAAI,GAAG;AACrC,OAAI,SAAS,OACX,SAAQ,KAAK;IACX,UAAU;IACV,cAAc;IACd,SAAS,0BAA0B,GAAG,sBAAsB;IAC7D,CAAC;OAEF,kBAAiB,IAAI,IAAI,EAAE;;EAK/B,MAAM,QAAQ,EAAE;AAWhB,MAAI,EATF,MAAM,aAAa,UACnB,MAAM,gBAAgB,UACtB,MAAM,cAAc,UACpB,MAAM,mBAAmB,UACzB,MAAM,eAAe,UACrB,MAAM,aAAa,UACnB,MAAM,UAAU,UAChB,MAAM,cAAc,WAEG,IAAI,SAAS,SAAS,EAC7C,SAAQ,KAAK;GACX,UAAU;GACV,cAAc;GACd,SAAS,gFAAgF,IAAI,EAAE;GAChG,CAAC;;AAIN,QAAO"}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { Logger } from "./logger.cjs";
|
|
2
|
-
import { Fixture } from "./types.cjs";
|
|
2
|
+
import { Fixture, FixtureFileResponse, FixtureResponse } from "./types.cjs";
|
|
3
3
|
|
|
4
4
|
//#region src/fixture-loader.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Auto-stringify object-valued `content` and `toolCalls[].arguments` fields.
|
|
8
|
+
* This lets fixture authors write plain JSON objects instead of escaped strings.
|
|
9
|
+
* All other fields (including ResponseOverrides) pass through unmodified.
|
|
10
|
+
*/
|
|
11
|
+
declare function normalizeResponse(raw: FixtureFileResponse): FixtureResponse;
|
|
5
12
|
declare function loadFixtureFile(filePath: string, logger?: Logger): Fixture[];
|
|
6
13
|
declare function loadFixturesFromDir(dirPath: string, logger?: Logger): Fixture[];
|
|
7
14
|
interface ValidationResult {
|
|
@@ -13,5 +20,5 @@ declare function validateFixtures(fixtures: Fixture[]): ValidationResult[];
|
|
|
13
20
|
//# sourceMappingURL=fixture-loader.d.ts.map
|
|
14
21
|
|
|
15
22
|
//#endregion
|
|
16
|
-
export { ValidationResult, loadFixtureFile, loadFixturesFromDir, validateFixtures };
|
|
23
|
+
export { ValidationResult, loadFixtureFile, loadFixturesFromDir, normalizeResponse, validateFixtures };
|
|
17
24
|
//# sourceMappingURL=fixture-loader.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixture-loader.d.cts","names":[],"sources":["../src/fixture-loader.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fixture-loader.d.cts","names":[],"sources":["../src/fixture-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;AA4BA;;;AAA6D,iBAA7C,iBAAA,CAA6C,GAAA,EAAtB,mBAAsB,CAAA,EAAA,eAAA;AAuD7C,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,UAwE1C,gBAAA,CAxE0C;UAAS,EAAA,OAAA,GAAA,SAAA;EAAO,YAAA,EAAA,MAAA;EA6B3D,OAAA,EAAA,MAAA;;AAA8C,iBA+G9C,gBAAA,CA/G8C,QAAA,EA+GnB,OA/GmB,EAAA,CAAA,EA+GP,gBA/GO,EAAA"}
|
package/dist/fixture-loader.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { Logger } from "./logger.js";
|
|
2
|
-
import { Fixture } from "./types.js";
|
|
2
|
+
import { Fixture, FixtureFileResponse, FixtureResponse } from "./types.js";
|
|
3
3
|
|
|
4
4
|
//#region src/fixture-loader.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Auto-stringify object-valued `content` and `toolCalls[].arguments` fields.
|
|
8
|
+
* This lets fixture authors write plain JSON objects instead of escaped strings.
|
|
9
|
+
* All other fields (including ResponseOverrides) pass through unmodified.
|
|
10
|
+
*/
|
|
11
|
+
declare function normalizeResponse(raw: FixtureFileResponse): FixtureResponse;
|
|
5
12
|
declare function loadFixtureFile(filePath: string, logger?: Logger): Fixture[];
|
|
6
13
|
declare function loadFixturesFromDir(dirPath: string, logger?: Logger): Fixture[];
|
|
7
14
|
interface ValidationResult {
|
|
@@ -13,5 +20,5 @@ declare function validateFixtures(fixtures: Fixture[]): ValidationResult[];
|
|
|
13
20
|
//# sourceMappingURL=fixture-loader.d.ts.map
|
|
14
21
|
|
|
15
22
|
//#endregion
|
|
16
|
-
export { ValidationResult, loadFixtureFile, loadFixturesFromDir, validateFixtures };
|
|
23
|
+
export { ValidationResult, loadFixtureFile, loadFixturesFromDir, normalizeResponse, validateFixtures };
|
|
17
24
|
//# sourceMappingURL=fixture-loader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixture-loader.d.ts","names":[],"sources":["../src/fixture-loader.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fixture-loader.d.ts","names":[],"sources":["../src/fixture-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;AA4BA;;;AAA6D,iBAA7C,iBAAA,CAA6C,GAAA,EAAtB,mBAAsB,CAAA,EAAA,eAAA;AAuD7C,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,UAwE1C,gBAAA,CAxE0C;UAAS,EAAA,OAAA,GAAA,SAAA;EAAO,YAAA,EAAA,MAAA;EA6B3D,OAAA,EAAA,MAAA;;AAA8C,iBA+G9C,gBAAA,CA/G8C,QAAA,EA+GnB,OA/GmB,EAAA,CAAA,EA+GP,gBA/GO,EAAA"}
|
package/dist/fixture-loader.js
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
|
-
import { isEmbeddingResponse, isErrorResponse, isTextResponse, isToolCallResponse } from "./helpers.js";
|
|
1
|
+
import { isAudioResponse, isContentWithToolCallsResponse, isEmbeddingResponse, isErrorResponse, isImageResponse, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse } from "./helpers.js";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
4
4
|
|
|
5
5
|
//#region src/fixture-loader.ts
|
|
6
|
+
/**
|
|
7
|
+
* Auto-stringify object-valued `content` and `toolCalls[].arguments` fields.
|
|
8
|
+
* This lets fixture authors write plain JSON objects instead of escaped strings.
|
|
9
|
+
* All other fields (including ResponseOverrides) pass through unmodified.
|
|
10
|
+
*/
|
|
11
|
+
function normalizeResponse(raw) {
|
|
12
|
+
const response = { ...raw };
|
|
13
|
+
if (typeof response.content === "object" && response.content !== null) response.content = JSON.stringify(response.content);
|
|
14
|
+
if (Array.isArray(response.toolCalls)) response.toolCalls = response.toolCalls.map((tc) => {
|
|
15
|
+
if (typeof tc.arguments === "object" && tc.arguments !== null) return {
|
|
16
|
+
...tc,
|
|
17
|
+
arguments: JSON.stringify(tc.arguments)
|
|
18
|
+
};
|
|
19
|
+
return tc;
|
|
20
|
+
});
|
|
21
|
+
return response;
|
|
22
|
+
}
|
|
6
23
|
function entryToFixture(entry) {
|
|
7
24
|
return {
|
|
8
25
|
match: {
|
|
@@ -12,9 +29,10 @@ function entryToFixture(entry) {
|
|
|
12
29
|
toolName: entry.match.toolName,
|
|
13
30
|
model: entry.match.model,
|
|
14
31
|
responseFormat: entry.match.responseFormat,
|
|
32
|
+
endpoint: entry.match.endpoint,
|
|
15
33
|
...entry.match.sequenceIndex !== void 0 && { sequenceIndex: entry.match.sequenceIndex }
|
|
16
34
|
},
|
|
17
|
-
response: entry.response,
|
|
35
|
+
response: normalizeResponse(entry.response),
|
|
18
36
|
...entry.latency !== void 0 && { latency: entry.latency },
|
|
19
37
|
...entry.chunkSize !== void 0 && { chunkSize: entry.chunkSize },
|
|
20
38
|
...entry.truncateAfterChunks !== void 0 && { truncateAfterChunks: entry.truncateAfterChunks },
|
|
@@ -78,16 +96,57 @@ function loadFixturesFromDir(dirPath, logger) {
|
|
|
78
96
|
}
|
|
79
97
|
return fixtures;
|
|
80
98
|
}
|
|
99
|
+
function validateReasoning(response, fixtureIndex, results) {
|
|
100
|
+
if (response.reasoning !== void 0) {
|
|
101
|
+
if (typeof response.reasoning !== "string") results.push({
|
|
102
|
+
severity: "error",
|
|
103
|
+
fixtureIndex,
|
|
104
|
+
message: "reasoning must be a string"
|
|
105
|
+
});
|
|
106
|
+
else if (response.reasoning === "") results.push({
|
|
107
|
+
severity: "warning",
|
|
108
|
+
fixtureIndex,
|
|
109
|
+
message: "reasoning is empty string — no reasoning events will be emitted"
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function validateWebSearches(response, fixtureIndex, results) {
|
|
114
|
+
if (response.webSearches !== void 0) if (!Array.isArray(response.webSearches)) results.push({
|
|
115
|
+
severity: "error",
|
|
116
|
+
fixtureIndex,
|
|
117
|
+
message: "webSearches must be an array of strings"
|
|
118
|
+
});
|
|
119
|
+
else if (response.webSearches.length === 0) results.push({
|
|
120
|
+
severity: "warning",
|
|
121
|
+
fixtureIndex,
|
|
122
|
+
message: "webSearches is empty array — no web search events will be emitted"
|
|
123
|
+
});
|
|
124
|
+
else for (let j = 0; j < response.webSearches.length; j++) {
|
|
125
|
+
if (typeof response.webSearches[j] !== "string") {
|
|
126
|
+
results.push({
|
|
127
|
+
severity: "error",
|
|
128
|
+
fixtureIndex,
|
|
129
|
+
message: `webSearches[${j}] is not a string`
|
|
130
|
+
});
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
if (response.webSearches[j] === "") results.push({
|
|
134
|
+
severity: "warning",
|
|
135
|
+
fixtureIndex,
|
|
136
|
+
message: `webSearches[${j}] is empty string`
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
81
140
|
function validateFixtures(fixtures) {
|
|
82
141
|
const results = [];
|
|
83
142
|
const seenUserMessages = /* @__PURE__ */ new Map();
|
|
84
143
|
for (let i = 0; i < fixtures.length; i++) {
|
|
85
144
|
const f = fixtures[i];
|
|
86
145
|
const response = f.response;
|
|
87
|
-
if (!isTextResponse(response) && !isToolCallResponse(response) && !isErrorResponse(response) && !isEmbeddingResponse(response)) results.push({
|
|
146
|
+
if (!isContentWithToolCallsResponse(response) && !isTextResponse(response) && !isToolCallResponse(response) && !isErrorResponse(response) && !isEmbeddingResponse(response) && !isImageResponse(response) && !isAudioResponse(response) && !isTranscriptionResponse(response) && !isVideoResponse(response)) results.push({
|
|
88
147
|
severity: "error",
|
|
89
148
|
fixtureIndex: i,
|
|
90
|
-
message: "response is not a recognized type (must have content, toolCalls, error, or
|
|
149
|
+
message: "response is not a recognized type (must have content, toolCalls, error, embedding, image, audio, transcription, or video)"
|
|
91
150
|
});
|
|
92
151
|
if (isTextResponse(response)) {
|
|
93
152
|
if (response.content === "") results.push({
|
|
@@ -95,43 +154,39 @@ function validateFixtures(fixtures) {
|
|
|
95
154
|
fixtureIndex: i,
|
|
96
155
|
message: "content is empty string"
|
|
97
156
|
});
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
});
|
|
104
|
-
else if (response.reasoning === "") results.push({
|
|
105
|
-
severity: "warning",
|
|
106
|
-
fixtureIndex: i,
|
|
107
|
-
message: "reasoning is empty string — no reasoning events will be emitted"
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
if (response.webSearches !== void 0) if (!Array.isArray(response.webSearches)) results.push({
|
|
157
|
+
validateReasoning(response, i, results);
|
|
158
|
+
validateWebSearches(response, i, results);
|
|
159
|
+
}
|
|
160
|
+
if (isContentWithToolCallsResponse(response)) {
|
|
161
|
+
if (response.content === "") results.push({
|
|
111
162
|
severity: "error",
|
|
112
163
|
fixtureIndex: i,
|
|
113
|
-
message: "
|
|
164
|
+
message: "content is empty string"
|
|
114
165
|
});
|
|
115
|
-
|
|
166
|
+
if (response.toolCalls.length === 0) results.push({
|
|
116
167
|
severity: "warning",
|
|
117
168
|
fixtureIndex: i,
|
|
118
|
-
message: "
|
|
169
|
+
message: "toolCalls array is empty — fixture will never produce tool calls"
|
|
119
170
|
});
|
|
120
|
-
|
|
121
|
-
|
|
171
|
+
for (let j = 0; j < response.toolCalls.length; j++) {
|
|
172
|
+
const tc = response.toolCalls[j];
|
|
173
|
+
if (!tc.name) results.push({
|
|
174
|
+
severity: "error",
|
|
175
|
+
fixtureIndex: i,
|
|
176
|
+
message: `toolCalls[${j}].name is empty`
|
|
177
|
+
});
|
|
178
|
+
try {
|
|
179
|
+
JSON.parse(tc.arguments);
|
|
180
|
+
} catch {
|
|
122
181
|
results.push({
|
|
123
182
|
severity: "error",
|
|
124
183
|
fixtureIndex: i,
|
|
125
|
-
message: `
|
|
184
|
+
message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}`
|
|
126
185
|
});
|
|
127
|
-
break;
|
|
128
186
|
}
|
|
129
|
-
if (response.webSearches[j] === "") results.push({
|
|
130
|
-
severity: "warning",
|
|
131
|
-
fixtureIndex: i,
|
|
132
|
-
message: `webSearches[${j}] is empty string`
|
|
133
|
-
});
|
|
134
187
|
}
|
|
188
|
+
validateReasoning(response, i, results);
|
|
189
|
+
validateWebSearches(response, i, results);
|
|
135
190
|
}
|
|
136
191
|
if (isToolCallResponse(response)) {
|
|
137
192
|
if (response.toolCalls.length === 0) results.push({
|
|
@@ -184,6 +239,52 @@ function validateFixtures(fixtures) {
|
|
|
184
239
|
break;
|
|
185
240
|
}
|
|
186
241
|
}
|
|
242
|
+
if (isTextResponse(response) || isToolCallResponse(response) || isContentWithToolCallsResponse(response)) {
|
|
243
|
+
const r = response;
|
|
244
|
+
if (r.id !== void 0 && typeof r.id !== "string") results.push({
|
|
245
|
+
severity: "error",
|
|
246
|
+
fixtureIndex: i,
|
|
247
|
+
message: `override "id" must be a string, got ${typeof r.id}`
|
|
248
|
+
});
|
|
249
|
+
if (r.created !== void 0 && (typeof r.created !== "number" || r.created < 0)) results.push({
|
|
250
|
+
severity: "error",
|
|
251
|
+
fixtureIndex: i,
|
|
252
|
+
message: `override "created" must be a non-negative number`
|
|
253
|
+
});
|
|
254
|
+
if (r.model !== void 0 && typeof r.model !== "string") results.push({
|
|
255
|
+
severity: "error",
|
|
256
|
+
fixtureIndex: i,
|
|
257
|
+
message: `override "model" must be a string, got ${typeof r.model}`
|
|
258
|
+
});
|
|
259
|
+
if (r.finishReason !== void 0 && typeof r.finishReason !== "string") results.push({
|
|
260
|
+
severity: "error",
|
|
261
|
+
fixtureIndex: i,
|
|
262
|
+
message: `override "finishReason" must be a string, got ${typeof r.finishReason}`
|
|
263
|
+
});
|
|
264
|
+
if (r.role !== void 0 && typeof r.role !== "string") results.push({
|
|
265
|
+
severity: "error",
|
|
266
|
+
fixtureIndex: i,
|
|
267
|
+
message: `override "role" must be a string, got ${typeof r.role}`
|
|
268
|
+
});
|
|
269
|
+
if (r.systemFingerprint !== void 0 && typeof r.systemFingerprint !== "string") results.push({
|
|
270
|
+
severity: "error",
|
|
271
|
+
fixtureIndex: i,
|
|
272
|
+
message: `override "systemFingerprint" must be a string, got ${typeof r.systemFingerprint}`
|
|
273
|
+
});
|
|
274
|
+
if (r.usage !== void 0) if (typeof r.usage !== "object" || r.usage === null || Array.isArray(r.usage)) results.push({
|
|
275
|
+
severity: "error",
|
|
276
|
+
fixtureIndex: i,
|
|
277
|
+
message: `override "usage" must be an object`
|
|
278
|
+
});
|
|
279
|
+
else for (const key of Object.keys(r.usage)) {
|
|
280
|
+
const val = r.usage[key];
|
|
281
|
+
if (val !== void 0 && typeof val !== "number") results.push({
|
|
282
|
+
severity: "error",
|
|
283
|
+
fixtureIndex: i,
|
|
284
|
+
message: `override "usage.${key}" must be a number, got ${typeof val}`
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
187
288
|
if (f.latency !== void 0 && f.latency < 0) results.push({
|
|
188
289
|
severity: "error",
|
|
189
290
|
fixtureIndex: i,
|
|
@@ -251,7 +352,7 @@ function validateFixtures(fixtures) {
|
|
|
251
352
|
else seenUserMessages.set(um, i);
|
|
252
353
|
}
|
|
253
354
|
const match = f.match;
|
|
254
|
-
if (!(match.userMessage !== void 0 || match.inputText !== void 0 || match.responseFormat !== void 0 || match.toolCallId !== void 0 || match.toolName !== void 0 || match.model !== void 0 || match.predicate !== void 0) && i < fixtures.length - 1) results.push({
|
|
355
|
+
if (!(match.endpoint !== void 0 || match.userMessage !== void 0 || match.inputText !== void 0 || match.responseFormat !== void 0 || match.toolCallId !== void 0 || match.toolName !== void 0 || match.model !== void 0 || match.predicate !== void 0) && i < fixtures.length - 1) results.push({
|
|
255
356
|
severity: "warning",
|
|
256
357
|
fixtureIndex: i,
|
|
257
358
|
message: `empty match acts as catch-all but is not the last fixture — shadows fixtures ${i + 1}+`
|
|
@@ -261,5 +362,5 @@ function validateFixtures(fixtures) {
|
|
|
261
362
|
}
|
|
262
363
|
|
|
263
364
|
//#endregion
|
|
264
|
-
export { entryToFixture, loadFixtureFile, loadFixturesFromDir, validateFixtures };
|
|
365
|
+
export { entryToFixture, loadFixtureFile, loadFixturesFromDir, normalizeResponse, validateFixtures };
|
|
265
366
|
//# sourceMappingURL=fixture-loader.js.map
|