@copilotkit/aimock 1.41.0 → 1.42.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 +12 -1
- package/README.md +2 -1
- package/dist/a2a-mock.cjs +18 -11
- package/dist/a2a-mock.cjs.map +1 -1
- package/dist/a2a-mock.d.cts +1 -0
- package/dist/a2a-mock.d.cts.map +1 -1
- package/dist/a2a-mock.d.ts +1 -0
- package/dist/a2a-mock.d.ts.map +1 -1
- package/dist/a2a-mock.js +18 -11
- package/dist/a2a-mock.js.map +1 -1
- package/dist/agui-handler.d.ts.map +1 -1
- package/dist/agui-mock.cjs +2 -0
- package/dist/agui-mock.cjs.map +1 -1
- package/dist/agui-mock.d.cts.map +1 -1
- package/dist/agui-mock.d.ts.map +1 -1
- package/dist/agui-mock.js +2 -0
- package/dist/agui-mock.js.map +1 -1
- package/dist/bedrock-converse.cjs +36 -2
- package/dist/bedrock-converse.cjs.map +1 -1
- package/dist/bedrock-converse.d.cts.map +1 -1
- package/dist/bedrock-converse.d.ts.map +1 -1
- package/dist/bedrock-converse.js +37 -3
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +50 -13
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.d.cts.map +1 -1
- package/dist/bedrock.d.ts.map +1 -1
- package/dist/bedrock.js +51 -14
- package/dist/bedrock.js.map +1 -1
- package/dist/byteplus-video.cjs +2 -2
- package/dist/byteplus-video.cjs.map +1 -1
- package/dist/byteplus-video.d.cts.map +1 -1
- package/dist/byteplus-video.d.ts.map +1 -1
- package/dist/byteplus-video.js +2 -2
- package/dist/byteplus-video.js.map +1 -1
- package/dist/chaos.cjs +36 -7
- package/dist/chaos.cjs.map +1 -1
- package/dist/chaos.d.cts +8 -3
- package/dist/chaos.d.cts.map +1 -1
- package/dist/chaos.d.ts +8 -3
- package/dist/chaos.d.ts.map +1 -1
- package/dist/chaos.js +36 -8
- package/dist/chaos.js.map +1 -1
- package/dist/cohere.cjs +58 -5
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.d.cts.map +1 -1
- package/dist/cohere.d.ts.map +1 -1
- package/dist/cohere.js +59 -6
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/elevenlabs-audio.cjs +36 -2
- package/dist/elevenlabs-audio.cjs.map +1 -1
- package/dist/elevenlabs-audio.d.cts.map +1 -1
- package/dist/elevenlabs-audio.d.ts.map +1 -1
- package/dist/elevenlabs-audio.js +37 -3
- package/dist/elevenlabs-audio.js.map +1 -1
- package/dist/embeddings.cjs +58 -3
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.d.cts.map +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +59 -4
- package/dist/embeddings.js.map +1 -1
- package/dist/fal-audio.cjs +38 -2
- package/dist/fal-audio.cjs.map +1 -1
- package/dist/fal-audio.d.cts.map +1 -1
- package/dist/fal-audio.d.ts.map +1 -1
- package/dist/fal-audio.js +39 -3
- package/dist/fal-audio.js.map +1 -1
- package/dist/fixture-loader.cjs +0 -0
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +9 -1
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +9 -1
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +0 -0
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-embeddings.cjs +19 -1
- package/dist/gemini-embeddings.cjs.map +1 -1
- package/dist/gemini-embeddings.js +20 -2
- package/dist/gemini-embeddings.js.map +1 -1
- package/dist/gemini-interactions.cjs +15 -1
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +16 -2
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +70 -6
- 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 +71 -7
- package/dist/gemini.js.map +1 -1
- package/dist/grok-video.cjs +2 -2
- package/dist/grok-video.cjs.map +1 -1
- package/dist/grok-video.d.cts.map +1 -1
- package/dist/grok-video.d.ts.map +1 -1
- package/dist/grok-video.js +2 -2
- package/dist/grok-video.js.map +1 -1
- package/dist/helpers.cjs +160 -5
- 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 +152 -6
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +22 -4
- package/dist/images.cjs.map +1 -1
- package/dist/images.d.cts.map +1 -1
- package/dist/images.d.ts.map +1 -1
- package/dist/images.js +23 -5
- package/dist/images.js.map +1 -1
- package/dist/llmock.cjs +15 -0
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +12 -0
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +12 -0
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +16 -1
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-mock.cjs +11 -0
- package/dist/mcp-mock.cjs.map +1 -1
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +11 -0
- package/dist/mcp-mock.js.map +1 -1
- package/dist/messages.cjs +18 -1
- 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 +19 -2
- package/dist/messages.js.map +1 -1
- package/dist/moderation.cjs +67 -3
- package/dist/moderation.cjs.map +1 -1
- package/dist/moderation.d.cts +1 -0
- package/dist/moderation.d.cts.map +1 -1
- package/dist/moderation.d.ts +1 -0
- package/dist/moderation.d.ts.map +1 -1
- package/dist/moderation.js +68 -4
- package/dist/moderation.js.map +1 -1
- package/dist/ollama.cjs +75 -5
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts.map +1 -1
- package/dist/ollama.js +76 -6
- package/dist/ollama.js.map +1 -1
- package/dist/openrouter-video.cjs +4 -4
- package/dist/openrouter-video.cjs.map +1 -1
- package/dist/openrouter-video.d.cts.map +1 -1
- package/dist/openrouter-video.d.ts.map +1 -1
- package/dist/openrouter-video.js +4 -4
- package/dist/openrouter-video.js.map +1 -1
- package/dist/rerank.cjs +65 -2
- package/dist/rerank.cjs.map +1 -1
- package/dist/rerank.d.cts +1 -1
- package/dist/rerank.d.cts.map +1 -1
- package/dist/rerank.d.ts +1 -1
- package/dist/rerank.d.ts.map +1 -1
- package/dist/rerank.js +66 -3
- package/dist/rerank.js.map +1 -1
- package/dist/responses.cjs +18 -1
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +19 -2
- package/dist/responses.js.map +1 -1
- package/dist/search.cjs +67 -3
- package/dist/search.cjs.map +1 -1
- package/dist/search.d.cts +1 -0
- package/dist/search.d.cts.map +1 -1
- package/dist/search.d.ts +1 -0
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +68 -4
- package/dist/search.js.map +1 -1
- package/dist/server.cjs +317 -13
- 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 +320 -16
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +18 -1
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.d.cts.map +1 -1
- package/dist/speech.d.ts.map +1 -1
- package/dist/speech.js +19 -2
- package/dist/speech.js.map +1 -1
- package/dist/transcription.cjs +21 -2
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.d.cts.map +1 -1
- package/dist/transcription.d.ts.map +1 -1
- package/dist/transcription.js +21 -2
- package/dist/transcription.js.map +1 -1
- package/dist/types.d.cts +39 -2
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +39 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-mock.cjs +88 -0
- package/dist/vector-mock.cjs.map +1 -1
- package/dist/vector-mock.d.cts.map +1 -1
- package/dist/vector-mock.d.ts.map +1 -1
- package/dist/vector-mock.js +89 -1
- package/dist/vector-mock.js.map +1 -1
- package/dist/veo-video.cjs +2 -2
- package/dist/veo-video.cjs.map +1 -1
- package/dist/veo-video.d.cts.map +1 -1
- package/dist/veo-video.d.ts.map +1 -1
- package/dist/veo-video.js +2 -2
- package/dist/veo-video.js.map +1 -1
- package/dist/video.cjs +19 -2
- package/dist/video.cjs.map +1 -1
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts.map +1 -1
- package/dist/video.js +20 -3
- package/dist/video.js.map +1 -1
- package/package.json +1 -1
package/dist/images.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { flattenHeaders, getContext, getTestId, isErrorResponse, isImageResponse, resolveResponse, resolveStrictMode, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
|
|
1
|
+
import { flattenHeaders, getContext, getTestId, isErrorResponse, isImageResponse, isJsonObject, resolveResponse, resolveStrictMode, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
|
|
2
2
|
import { matchFixtureDiagnostic } from "./router.js";
|
|
3
3
|
import { writeErrorResponse } from "./sse-writer.js";
|
|
4
4
|
import { applyChaos } from "./chaos.js";
|
|
@@ -23,8 +23,26 @@ async function handleImages(req, res, raw, fixtures, journal, defaults, setCorsH
|
|
|
23
23
|
const method = req.method ?? "POST";
|
|
24
24
|
let model;
|
|
25
25
|
let prompt;
|
|
26
|
+
let body;
|
|
26
27
|
try {
|
|
27
|
-
|
|
28
|
+
body = JSON.parse(raw);
|
|
29
|
+
if (!isJsonObject(body)) {
|
|
30
|
+
journal.add({
|
|
31
|
+
method,
|
|
32
|
+
path,
|
|
33
|
+
headers: flattenHeaders(req.headers),
|
|
34
|
+
body: null,
|
|
35
|
+
response: {
|
|
36
|
+
status: 400,
|
|
37
|
+
fixture: null
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
writeErrorResponse(res, 400, JSON.stringify({ error: {
|
|
41
|
+
message: "Request body must be a JSON object",
|
|
42
|
+
type: "invalid_request_error"
|
|
43
|
+
} }));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
28
46
|
if (format === "gemini") {
|
|
29
47
|
prompt = body.instances?.[0]?.prompt ?? "";
|
|
30
48
|
model = geminiModel ?? "imagen";
|
|
@@ -76,7 +94,7 @@ async function handleImages(req, res, raw, fixtures, journal, defaults, setCorsH
|
|
|
76
94
|
journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
77
95
|
defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
|
|
78
96
|
} else defaults.logger.debug(`No fixture matched for request`);
|
|
79
|
-
if (applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
|
|
97
|
+
if (applyChaos(res, fixture, defaults.chaos, req.headers, req.url, journal, {
|
|
80
98
|
method,
|
|
81
99
|
path,
|
|
82
100
|
headers: flattenHeaders(req.headers),
|
|
@@ -251,7 +269,7 @@ async function handleImageEdit(req, res, raw, fixtures, journal, defaults, setCo
|
|
|
251
269
|
journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
252
270
|
defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
|
|
253
271
|
} else defaults.logger.debug(`No fixture matched for request`);
|
|
254
|
-
if (applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
|
|
272
|
+
if (applyChaos(res, fixture, defaults.chaos, req.headers, req.url, journal, {
|
|
255
273
|
method,
|
|
256
274
|
path,
|
|
257
275
|
headers: flattenHeaders(req.headers),
|
|
@@ -380,7 +398,7 @@ async function handleImageVariations(req, res, raw, fixtures, journal, defaults,
|
|
|
380
398
|
journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
381
399
|
defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
|
|
382
400
|
} else defaults.logger.debug(`No fixture matched for request`);
|
|
383
|
-
if (applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
|
|
401
|
+
if (applyChaos(res, fixture, defaults.chaos, req.headers, req.url, journal, {
|
|
384
402
|
method,
|
|
385
403
|
path,
|
|
386
404
|
headers: flattenHeaders(req.headers),
|
package/dist/images.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"images.js","names":[],"sources":["../src/images.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RecordProviderKey,\n} from \"./types.js\";\nimport {\n isImageResponse,\n isErrorResponse,\n serializeErrorResponse,\n flattenHeaders,\n getContext,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\nimport { extractBoundary, extractFormField } from \"./transcription.js\";\n\ninterface OpenAIImageRequest {\n model?: string;\n prompt: string;\n n?: number;\n size?: string;\n response_format?: \"url\" | \"b64_json\";\n [key: string]: unknown;\n}\n\ninterface GeminiPredictRequest {\n instances: Array<{ prompt: string }>;\n parameters?: { sampleCount?: number };\n [key: string]: unknown;\n}\n\nfunction buildSyntheticRequest(\n model: string,\n prompt: string,\n context?: string,\n): ChatCompletionRequest {\n return {\n model,\n messages: [{ role: \"user\", content: prompt }],\n _endpointType: \"image\",\n ...(context !== undefined && { _context: context }),\n };\n}\n\nexport async function handleImages(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n format: \"openai\" | \"gemini\" = \"openai\",\n geminiModel?: string,\n /**\n * Provider key this request records under. Defaults to `\"openai\"` — the\n * value hardcoded here before BytePlus Ark needed images attribution — so the\n * Gemini `:predict` call site keeps today's behavior by omitting it. Only the\n * OpenAI-images call site passes it, and only with a non-default value behind\n * server.ts's configuration gate (a byteplus upstream must be configured AND\n * the request must carry the /api/v3 prefix).\n */\n recordProviderKey?: RecordProviderKey,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? \"/v1/images/generations\";\n const method = req.method ?? \"POST\";\n\n let model: string;\n let prompt: string;\n\n try {\n const body = JSON.parse(raw);\n if (format === \"gemini\") {\n const geminiReq = body as GeminiPredictRequest;\n prompt = geminiReq.instances?.[0]?.prompt ?? \"\";\n model = geminiModel ?? \"imagen\";\n } else {\n const openaiReq = body as OpenAIImageRequest;\n prompt = openaiReq.prompt ?? \"\";\n model = openaiReq.model ?? \"dall-e-3\";\n }\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: `Malformed JSON: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n\n if (!prompt) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: { message: \"Missing required parameter: 'prompt'\", type: \"invalid_request_error\" },\n }),\n );\n return;\n }\n\n const syntheticReq = buildSyntheticRequest(model, prompt, getContext(req));\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n format === \"gemini\" ? \"gemini\" : (recordProviderKey ?? \"openai\"),\n req.url ?? \"/v1/images/generations\",\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isImageResponse(response)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: { message: \"Fixture response is not an image type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n // Normalize to array of image items\n const items = response.images ?? (response.image ? [response.image] : []);\n\n if (format === \"gemini\") {\n const predictions = items.map((item) => ({\n bytesBase64Encoded: item.b64Json ?? \"\",\n mimeType: \"image/png\" as const,\n }));\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ predictions }));\n } else {\n serializeOpenAIImageResponse(res, items);\n }\n}\n\n/**\n * Write the standard OpenAI image response envelope (`{ created, data }`).\n * Shared by generations, edit, and variations endpoints.\n */\nfunction serializeOpenAIImageResponse(\n res: http.ServerResponse,\n items: Array<{ url?: string; b64Json?: string; revisedPrompt?: string }>,\n): void {\n const data = items.map((item) => {\n const entry: Record<string, string> = {};\n if (item.url) entry.url = item.url;\n if (item.b64Json) entry.b64_json = item.b64Json;\n if (item.revisedPrompt) entry.revised_prompt = item.revisedPrompt;\n return entry;\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ created: Math.floor(Date.now() / 1000), data }));\n}\n\n/**\n * Handle POST /v1/images/edits — OpenAI Image Edit API.\n *\n * Request uses multipart/form-data. We extract text fields (`prompt`, `model`,\n * `n`, `size`, `response_format`) and ignore binary fields (`image`, `mask`)\n * since aimock doesn't process actual image data.\n *\n * The response envelope is identical to /v1/images/generations.\n */\nexport async function handleImageEdit(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? \"/v1/images/edits\";\n const method = req.method ?? \"POST\";\n\n const contentType = Array.isArray(req.headers[\"content-type\"])\n ? req.headers[\"content-type\"][0]\n : req.headers[\"content-type\"];\n const boundary = extractBoundary(contentType);\n\n const prompt = extractFormField(raw, \"prompt\", boundary) ?? \"\";\n const model = extractFormField(raw, \"model\", boundary) ?? \"dall-e-2\";\n\n if (!prompt) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: { message: \"Missing required parameter: 'prompt'\", type: \"invalid_request_error\" },\n }),\n );\n return;\n }\n\n const syntheticReq = buildSyntheticRequest(model, prompt, getContext(req));\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"openai\",\n req.url ?? \"/v1/images/edits\",\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isImageResponse(response)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: { message: \"Fixture response is not an image type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const items = response.images ?? (response.image ? [response.image] : []);\n serializeOpenAIImageResponse(res, items);\n}\n\n/**\n * Handle POST /v1/images/variations — OpenAI Image Variations API.\n *\n * Request uses multipart/form-data. We extract text fields (`model`, `n`,\n * `size`, `response_format`) and ignore the binary `image` field.\n * Unlike edit, no `prompt` field is required.\n *\n * The response envelope is identical to /v1/images/generations.\n */\nexport async function handleImageVariations(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? \"/v1/images/variations\";\n const method = req.method ?? \"POST\";\n\n const contentType = Array.isArray(req.headers[\"content-type\"])\n ? req.headers[\"content-type\"][0]\n : req.headers[\"content-type\"];\n const boundary = extractBoundary(contentType);\n\n const model = extractFormField(raw, \"model\", boundary) ?? \"dall-e-2\";\n\n // Variations don't have a prompt — use a synthetic placeholder for fixture matching\n const syntheticReq = buildSyntheticRequest(model, \"[variation]\", getContext(req));\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"openai\",\n req.url ?? \"/v1/images/variations\",\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isImageResponse(response)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: { message: \"Fixture response is not an image type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const items = response.images ?? (response.image ? [response.image] : []);\n serializeOpenAIImageResponse(res, items);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"images.js","names":[],"sources":["../src/images.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RecordProviderKey,\n} from \"./types.js\";\nimport {\n isImageResponse,\n isErrorResponse,\n isJsonObject,\n serializeErrorResponse,\n flattenHeaders,\n getContext,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\nimport { extractBoundary, extractFormField } from \"./transcription.js\";\n\ninterface OpenAIImageRequest {\n model?: string;\n prompt: string;\n n?: number;\n size?: string;\n response_format?: \"url\" | \"b64_json\";\n [key: string]: unknown;\n}\n\ninterface GeminiPredictRequest {\n instances: Array<{ prompt: string }>;\n parameters?: { sampleCount?: number };\n [key: string]: unknown;\n}\n\nfunction buildSyntheticRequest(\n model: string,\n prompt: string,\n context?: string,\n): ChatCompletionRequest {\n return {\n model,\n messages: [{ role: \"user\", content: prompt }],\n _endpointType: \"image\",\n ...(context !== undefined && { _context: context }),\n };\n}\n\nexport async function handleImages(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n format: \"openai\" | \"gemini\" = \"openai\",\n geminiModel?: string,\n /**\n * Provider key this request records under. Defaults to `\"openai\"` — the\n * value hardcoded here before BytePlus Ark needed images attribution — so the\n * Gemini `:predict` call site keeps today's behavior by omitting it. Only the\n * OpenAI-images call site passes it, and only with a non-default value behind\n * server.ts's configuration gate (a byteplus upstream must be configured AND\n * the request must carry the /api/v3 prefix).\n */\n recordProviderKey?: RecordProviderKey,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? \"/v1/images/generations\";\n const method = req.method ?? \"POST\";\n\n let model: string;\n let prompt: string;\n\n let body: unknown;\n try {\n body = JSON.parse(raw);\n // Reject bodies that parsed but are not a JSON object (e.g. `null`) before\n // touching fields — without this the TypeError below lands in the\n // malformed-JSON branch with a misleading message.\n if (!isJsonObject(body)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Request body must be a JSON object\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n if (format === \"gemini\") {\n const geminiReq = body as GeminiPredictRequest;\n prompt = geminiReq.instances?.[0]?.prompt ?? \"\";\n model = geminiModel ?? \"imagen\";\n } else {\n const openaiReq = body as OpenAIImageRequest;\n prompt = openaiReq.prompt ?? \"\";\n model = openaiReq.model ?? \"dall-e-3\";\n }\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: `Malformed JSON: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n\n if (!prompt) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: { message: \"Missing required parameter: 'prompt'\", type: \"invalid_request_error\" },\n }),\n );\n return;\n }\n\n const syntheticReq = buildSyntheticRequest(model, prompt, getContext(req));\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n format === \"gemini\" ? \"gemini\" : (recordProviderKey ?? \"openai\"),\n req.url ?? \"/v1/images/generations\",\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isImageResponse(response)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: { message: \"Fixture response is not an image type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n // Normalize to array of image items\n const items = response.images ?? (response.image ? [response.image] : []);\n\n if (format === \"gemini\") {\n const predictions = items.map((item) => ({\n bytesBase64Encoded: item.b64Json ?? \"\",\n mimeType: \"image/png\" as const,\n }));\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ predictions }));\n } else {\n serializeOpenAIImageResponse(res, items);\n }\n}\n\n/**\n * Write the standard OpenAI image response envelope (`{ created, data }`).\n * Shared by generations, edit, and variations endpoints.\n */\nfunction serializeOpenAIImageResponse(\n res: http.ServerResponse,\n items: Array<{ url?: string; b64Json?: string; revisedPrompt?: string }>,\n): void {\n const data = items.map((item) => {\n const entry: Record<string, string> = {};\n if (item.url) entry.url = item.url;\n if (item.b64Json) entry.b64_json = item.b64Json;\n if (item.revisedPrompt) entry.revised_prompt = item.revisedPrompt;\n return entry;\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ created: Math.floor(Date.now() / 1000), data }));\n}\n\n/**\n * Handle POST /v1/images/edits — OpenAI Image Edit API.\n *\n * Request uses multipart/form-data. We extract text fields (`prompt`, `model`,\n * `n`, `size`, `response_format`) and ignore binary fields (`image`, `mask`)\n * since aimock doesn't process actual image data.\n *\n * The response envelope is identical to /v1/images/generations.\n */\nexport async function handleImageEdit(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? \"/v1/images/edits\";\n const method = req.method ?? \"POST\";\n\n const contentType = Array.isArray(req.headers[\"content-type\"])\n ? req.headers[\"content-type\"][0]\n : req.headers[\"content-type\"];\n const boundary = extractBoundary(contentType);\n\n const prompt = extractFormField(raw, \"prompt\", boundary) ?? \"\";\n const model = extractFormField(raw, \"model\", boundary) ?? \"dall-e-2\";\n\n if (!prompt) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: { message: \"Missing required parameter: 'prompt'\", type: \"invalid_request_error\" },\n }),\n );\n return;\n }\n\n const syntheticReq = buildSyntheticRequest(model, prompt, getContext(req));\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"openai\",\n req.url ?? \"/v1/images/edits\",\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isImageResponse(response)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: { message: \"Fixture response is not an image type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const items = response.images ?? (response.image ? [response.image] : []);\n serializeOpenAIImageResponse(res, items);\n}\n\n/**\n * Handle POST /v1/images/variations — OpenAI Image Variations API.\n *\n * Request uses multipart/form-data. We extract text fields (`model`, `n`,\n * `size`, `response_format`) and ignore the binary `image` field.\n * Unlike edit, no `prompt` field is required.\n *\n * The response envelope is identical to /v1/images/generations.\n */\nexport async function handleImageVariations(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? \"/v1/images/variations\";\n const method = req.method ?? \"POST\";\n\n const contentType = Array.isArray(req.headers[\"content-type\"])\n ? req.headers[\"content-type\"][0]\n : req.headers[\"content-type\"];\n const boundary = extractBoundary(contentType);\n\n const model = extractFormField(raw, \"model\", boundary) ?? \"dall-e-2\";\n\n // Variations don't have a prompt — use a synthetic placeholder for fixture matching\n const syntheticReq = buildSyntheticRequest(model, \"[variation]\", getContext(req));\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"openai\",\n req.url ?? \"/v1/images/variations\",\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isImageResponse(response)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: { message: \"Fixture response is not an image type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const items = response.images ?? (response.image ? [response.image] : []);\n serializeOpenAIImageResponse(res, items);\n}\n"],"mappings":"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"}
|
package/dist/llmock.cjs
CHANGED
|
@@ -237,6 +237,7 @@ var LLMock = class LLMock {
|
|
|
237
237
|
* at the front of the fixture list.
|
|
238
238
|
*/
|
|
239
239
|
nextRequestError(status, errorBody) {
|
|
240
|
+
if (!require_fixture_loader.isInjectableStatus(status)) throw new RangeError(`nextRequestError: invalid status ${String(status)} — must be ${require_fixture_loader.INJECTED_STATUS_RANGE}`);
|
|
240
241
|
const errorResponse = {
|
|
241
242
|
error: {
|
|
242
243
|
message: errorBody?.message ?? "Injected error",
|
|
@@ -292,12 +293,26 @@ var LLMock = class LLMock {
|
|
|
292
293
|
if (this.serverInstance) this.serverInstance.journal.clearMatchCounts(testId);
|
|
293
294
|
return this;
|
|
294
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Set the server-wide chaos baseline. Writes through to a RUNNING server as
|
|
298
|
+
* well as the construction options: an untagged `POST /__aimock/chaos`
|
|
299
|
+
* installs a baseline override that shadows `options.chaos`, and without this
|
|
300
|
+
* the call would silently do nothing until the next `reset()`. Per-testId
|
|
301
|
+
* overrides still win over the baseline.
|
|
302
|
+
*/
|
|
295
303
|
setChaos(config) {
|
|
296
304
|
this.options.chaos = config;
|
|
305
|
+
if (this.serverInstance) this.serverInstance.defaults.chaos = config;
|
|
297
306
|
return this;
|
|
298
307
|
}
|
|
308
|
+
/**
|
|
309
|
+
* Turn chaos off everywhere: the construction config, the runtime baseline,
|
|
310
|
+
* and every per-testId override (assigning `undefined` is the full-clear
|
|
311
|
+
* signal the `defaults.chaos` setter defines).
|
|
312
|
+
*/
|
|
299
313
|
clearChaos() {
|
|
300
314
|
delete this.options.chaos;
|
|
315
|
+
if (this.serverInstance) this.serverInstance.defaults.chaos = void 0;
|
|
301
316
|
return this;
|
|
302
317
|
}
|
|
303
318
|
enableRecording(config) {
|
package/dist/llmock.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llmock.cjs","names":["loadFixtureFile","loadFixturesFromDir","entryToFixture","validateFixtures","normalizeResponse","imageResponseToFalJson","videoResponseToFalJson","createServerWithResolvedAuth","createServer"],"sources":["../src/llmock.ts"],"sourcesContent":["import type {\n AudioResponse,\n ChaosConfig,\n EmbeddingFixtureOpts,\n FalQueueOpts,\n Fixture,\n FixtureFileEntry,\n FixtureFileResponse,\n FixtureMatch,\n FixtureOpts,\n FixtureResponse,\n ImageResponse,\n MockServerOptions,\n Mountable,\n RecordConfig,\n ResponseFactory,\n TranscriptionResponse,\n VideoResponse,\n} from \"./types.js\";\nimport {\n createServer,\n createServerWithResolvedAuth,\n performFullReset,\n type ServerInstance,\n} from \"./server.js\";\nimport type { ResolvedInboundAuth } from \"./api-key-auth.js\";\nimport {\n loadFixtureFile,\n loadFixturesFromDir,\n entryToFixture,\n normalizeResponse,\n validateFixtures,\n} from \"./fixture-loader.js\";\nimport { Journal } from \"./journal.js\";\nimport type { SearchFixture, SearchResult } from \"./search.js\";\nimport type { RerankFixture, RerankResult } from \"./rerank.js\";\nimport type { ModerationFixture, ModerationResult } from \"./moderation.js\";\nimport { imageResponseToFalJson, videoResponseToFalJson } from \"./fal.js\";\n\nexport class LLMock {\n private fixtures: Fixture[] = [];\n private searchFixtures: SearchFixture[] = [];\n private rerankFixtures: RerankFixture[] = [];\n private moderationFixtures: ModerationFixture[] = [];\n private mounts: Array<{ path: string; handler: Mountable }> = [];\n private serverInstance: ServerInstance | null = null;\n private options: MockServerOptions;\n private readonly resolvedInboundAuth?: ResolvedInboundAuth;\n\n constructor(options?: MockServerOptions, resolvedInboundAuth?: ResolvedInboundAuth) {\n this.options = options ?? {};\n this.resolvedInboundAuth = resolvedInboundAuth;\n }\n\n // ---- Fixture management ----\n\n addFixture(fixture: Fixture): this {\n this.fixtures.push(fixture);\n return this;\n }\n\n addFixtures(fixtures: Fixture[]): this {\n this.fixtures.push(...fixtures);\n return this;\n }\n\n prependFixture(fixture: Fixture): this {\n this.fixtures.unshift(fixture);\n return this;\n }\n\n getFixtures(): readonly Fixture[] {\n return this.fixtures;\n }\n\n loadFixtureFile(filePath: string): this {\n this.fixtures.push(...loadFixtureFile(filePath));\n return this;\n }\n\n loadFixtureDir(dirPath: string): this {\n this.fixtures.push(...loadFixturesFromDir(dirPath));\n return this;\n }\n\n /**\n * Add fixtures from a JSON string or pre-parsed array of fixture entries.\n * Validates all fixtures and throws if any have severity \"error\".\n */\n addFixturesFromJSON(input: string | FixtureFileEntry[]): this {\n let entries: FixtureFileEntry[];\n if (typeof input === \"string\") {\n try {\n entries = JSON.parse(input);\n } catch (err) {\n throw new Error(\n `addFixturesFromJSON: invalid JSON — ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n } else {\n entries = input;\n }\n const converted = entries.map((e) => entryToFixture(e));\n const issues = validateFixtures(converted);\n const errors = issues.filter((i) => i.severity === \"error\");\n if (errors.length > 0) {\n throw new Error(`Fixture validation failed: ${JSON.stringify(errors)}`);\n }\n this.fixtures.push(...converted);\n return this;\n }\n\n // Uses length = 0 to preserve array reference identity — the running\n // server reads this same array on every request.\n clearFixtures(): this {\n this.fixtures.length = 0;\n return this;\n }\n\n // ---- Convenience ----\n\n on(\n match: FixtureMatch,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.addFixture({\n match,\n response: typeof response === \"function\" ? response : normalizeResponse(response),\n ...opts,\n });\n }\n\n onMessage(\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ userMessage: pattern }, response, opts);\n }\n\n onEmbedding(\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: EmbeddingFixtureOpts,\n ): this {\n return this.on({ inputText: pattern }, response, opts);\n }\n\n onJsonOutput(pattern: string | RegExp, jsonContent: object | string, opts?: FixtureOpts): this {\n const content = typeof jsonContent === \"string\" ? jsonContent : JSON.stringify(jsonContent);\n return this.on({ userMessage: pattern, responseFormat: \"json_object\" }, { content }, opts);\n }\n\n onToolCall(\n name: string,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ toolName: name }, response, opts);\n }\n\n onToolResult(\n id: string,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ toolCallId: id }, response, opts);\n }\n\n onTurn(\n turn: number,\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ userMessage: pattern, turnIndex: turn }, response, opts);\n }\n\n onImage(prompt: string | RegExp, response: ImageResponse): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"image\" },\n response,\n });\n }\n\n onSpeech(input: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: input, endpoint: \"speech\" },\n response,\n });\n }\n\n onTranscription(response: TranscriptionResponse): this {\n return this.addFixture({\n match: { endpoint: \"transcription\" },\n response,\n });\n }\n\n onTranslation(response: TranscriptionResponse): this {\n return this.addFixture({\n match: { endpoint: \"translation\" },\n response,\n });\n }\n\n onVideo(prompt: string | RegExp, response: VideoResponse): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"video\" },\n response,\n });\n }\n\n onAudio(input: string | RegExp, response: AudioResponse): this {\n return this.addFixture({ match: { userMessage: input }, response });\n }\n\n onSoundEffect(text: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: text, endpoint: \"audio-gen\" },\n response,\n });\n }\n\n onMusic(prompt: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"audio-gen\" },\n response,\n });\n }\n\n onElevenLabsTTS(text: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: text, endpoint: \"elevenlabs-tts\" },\n response,\n });\n }\n\n onFalAudio(prompt: string | RegExp, response: AudioResponse, model?: string): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"fal-audio\", ...(model ? { model } : {}) },\n response,\n });\n }\n\n // fal.queue.* is the dominant client API; onFalRun is a sync alias.\n //\n // `opts.billableUnits` rides through to the completed `queue-result`\n // response's `x-fal-billable-units` header (emitted alongside\n // `x-fal-request-id`), letting consumers like `@tanstack/ai-fal` surface a\n // billed-units value on replay. Omit it to preserve the header-less default.\n onFalQueue(modelOrPrompt: string | RegExp, response: unknown, opts?: FalQueueOpts): this {\n const { billableUnits, ...fixtureOpts } = opts ?? {};\n return this.addFixture({\n match: { model: modelOrPrompt, endpoint: \"fal\" },\n response: { json: response, ...(billableUnits != null ? { billableUnits } : {}) },\n ...fixtureOpts,\n });\n }\n\n onFalRun(modelOrPrompt: string | RegExp, response: unknown, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, response, opts);\n }\n\n /**\n * Register a fal.ai image fixture. Wraps an `ImageResponse` (the shape used\n * by `onImage` and OpenAI/Azure image fixtures) into fal's image envelope\n * before storing it as a `RawJSONResponse`. Defaults `width`/`height` to\n * 1024 when the fixture's `ImageItem` doesn't carry them.\n */\n onFalImage(modelOrPrompt: string | RegExp, response: ImageResponse, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, imageResponseToFalJson(response), opts);\n }\n\n /**\n * Register a fal.ai video fixture. Wraps a `VideoResponse` into fal's video\n * envelope (`{ video: { url, content_type, file_name, file_size }, seed }`)\n * before storing it as a `RawJSONResponse`.\n */\n onFalVideo(modelOrPrompt: string | RegExp, response: VideoResponse, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, videoResponseToFalJson(response), opts);\n }\n\n // ---- Service mock convenience methods ----\n\n onSearch(pattern: string | RegExp, results: SearchResult[]): this {\n this.searchFixtures.push({ match: pattern, results });\n return this;\n }\n\n onRerank(pattern: string | RegExp, results: RerankResult[]): this {\n this.rerankFixtures.push({ match: pattern, results });\n return this;\n }\n\n onModerate(pattern: string | RegExp, result: ModerationResult): this {\n this.moderationFixtures.push({ match: pattern, result });\n return this;\n }\n\n /**\n * Queue a one-shot error that will be returned for the next matching\n * request, then automatically removed. Implemented as an internal fixture\n * with a `predicate` that always matches (so it fires first) and spliced\n * at the front of the fixture list.\n */\n nextRequestError(\n status: number,\n errorBody?: { message?: string; type?: string; code?: string },\n ): this {\n const errorResponse: FixtureResponse = {\n error: {\n message: errorBody?.message ?? \"Injected error\",\n type: errorBody?.type ?? \"server_error\",\n code: errorBody?.code,\n },\n status,\n };\n // An injected error is only a valid response for endpoints that can carry\n // an error envelope. Mirror the router's endpoint-compat table\n // (matchFixtureDiagnostic in router.ts): error responses are compatible\n // with chat / embedding / realtime* / fal and with requests that carry no\n // endpoint type, but NOT with multimedia endpoints (image, speech, video,\n // transcription, …). Gating consumption on this prevents an incompatible\n // request from matching the predicate and splicing — and thereby\n // destroying — a one-shot error intended for a different endpoint before\n // the router's own compat check would have skipped it.\n const errorEndpointCompatible = (req: import(\"./types.js\").ChatCompletionRequest) => {\n const reqEndpoint = req._endpointType as string | undefined;\n if (\n reqEndpoint === undefined ||\n reqEndpoint === \"chat\" ||\n reqEndpoint === \"embedding\" ||\n reqEndpoint.startsWith(\"realtime\") ||\n reqEndpoint === \"fal\"\n ) {\n return true;\n }\n return false;\n };\n const fixture: Fixture = {\n match: { predicate: errorEndpointCompatible },\n response: errorResponse,\n };\n // Insert at front so it matches before everything else\n this.fixtures.unshift(fixture);\n // Remove after first match — the journal records it so tests can assert.\n // Only consume (and splice) when the request endpoint is compatible; an\n // incompatible request returns false here, falls through to other\n // fixtures, and leaves this error pending for its intended endpoint.\n const original = fixture.match.predicate!;\n fixture.match.predicate = (req) => {\n const result = original(req);\n if (result) {\n // Remove synchronously on first match to prevent race conditions\n const idx = this.fixtures.indexOf(fixture);\n if (idx !== -1) this.fixtures.splice(idx, 1);\n }\n return result;\n };\n return this;\n }\n\n // ---- Mounts ----\n\n mount(path: string, handler: Mountable): this {\n this.mounts.push({ path, handler });\n\n // If server is already running, wire up journal, registry, and baseUrl immediately\n // so late mounts behave identically to pre-start mounts.\n if (this.serverInstance) {\n if (handler.setJournal) handler.setJournal(this.serverInstance.journal);\n if (handler.setBaseUrl) handler.setBaseUrl(this.serverInstance.url + path);\n const registry = this.serverInstance.defaults.registry;\n if (registry && handler.setRegistry) handler.setRegistry(registry);\n }\n\n return this;\n }\n\n // ---- Journal proxies ----\n\n getRequests(): import(\"./types.js\").JournalEntry[] {\n return this.journal.getAll();\n }\n\n getLastRequest(): import(\"./types.js\").JournalEntry | null {\n return this.journal.getLast();\n }\n\n clearRequests(): void {\n this.journal.clear();\n }\n\n resetMatchCounts(testId?: string): this {\n if (this.serverInstance) {\n this.serverInstance.journal.clearMatchCounts(testId);\n }\n return this;\n }\n\n // ---- Chaos ----\n\n setChaos(config: ChaosConfig): this {\n this.options.chaos = config;\n return this;\n }\n\n clearChaos(): this {\n delete this.options.chaos;\n return this;\n }\n\n // ---- Recording ----\n\n enableRecording(config: RecordConfig): this {\n this.options.record = config;\n return this;\n }\n\n disableRecording(): this {\n delete this.options.record;\n return this;\n }\n\n // ---- Reset ----\n\n /**\n * Full reset — the in-process equivalent of `POST /__aimock/reset`. Shares\n * one implementation with the control-API route so the two cannot drift.\n *\n * The one deliberate difference: search / rerank / moderation fixtures are\n * also cleared here. Those are registered through this class only — the\n * control API has no route that creates them, so the HTTP reset can neither\n * reach nor observe them.\n *\n * NOT ALL OF THIS IS PER-INSTANCE. `performFullReset` clears module-global\n * state as well: the Gemini interaction and event-id counters\n * (`resetInteractionCounter` / `resetEventIdCounter` in\n * `./gemini-interactions.js`) and the fal.ai job/queue maps (`falJobs`,\n * `falQueueStates`). With two `LLMock` instances live in one process,\n * `a.reset()` rewinds the Gemini id sequence that `b` is mid-way through —\n * `b` then re-emits `aimock-int-0` / `evt_1`, ids it has already handed\n * out — and drops `b`'s in-flight fal jobs. Give each instance its own\n * process (or its own vitest worker) if that matters.\n *\n * The global stores are cleared even before `start()`, when there is no\n * server instance to reset.\n */\n reset(): this {\n this.searchFixtures.length = 0;\n this.rerankFixtures.length = 0;\n this.moderationFixtures.length = 0;\n performFullReset(this.fixtures, this.serverInstance);\n return this;\n }\n\n // ---- Server lifecycle ----\n\n async start(): Promise<string> {\n if (this.serverInstance) {\n throw new Error(\"Server already started\");\n }\n this.serverInstance = await (this.resolvedInboundAuth\n ? createServerWithResolvedAuth(\n this.fixtures,\n this.options,\n this.resolvedInboundAuth,\n this.mounts,\n {\n search: this.searchFixtures,\n rerank: this.rerankFixtures,\n moderation: this.moderationFixtures,\n },\n )\n : createServer(this.fixtures, this.options, this.mounts, {\n search: this.searchFixtures,\n rerank: this.rerankFixtures,\n moderation: this.moderationFixtures,\n }));\n return this.serverInstance.url;\n }\n\n async stop(): Promise<void> {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n const { server } = this.serverInstance;\n await new Promise<void>((resolve, reject) => {\n server.close((err: Error | undefined) => (err ? reject(err) : resolve()));\n });\n this.serverInstance = null;\n }\n\n // ---- Accessors ----\n\n get journal(): Journal {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n return this.serverInstance.journal;\n }\n\n get url(): string {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n return this.serverInstance.url;\n }\n\n get baseUrl(): string {\n return this.url;\n }\n\n get port(): number {\n const parsed = new URL(this.url); // this.url throws if not started\n if (!parsed.port) {\n throw new Error(`Server URL has no explicit port: ${this.url}`);\n }\n return parseInt(parsed.port, 10);\n }\n\n // ---- Static factory ----\n\n static async create(options?: MockServerOptions): Promise<LLMock> {\n const instance = new LLMock(options);\n await instance.start();\n return instance;\n }\n}\n\n/** @internal Configuration startup preserves a policy resolved from the selected source. */\nexport function createLLMockWithResolvedAuth(\n options: MockServerOptions,\n resolvedAuth: ResolvedInboundAuth,\n): LLMock {\n return new LLMock(options, resolvedAuth);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"llmock.cjs","names":["loadFixtureFile","loadFixturesFromDir","entryToFixture","validateFixtures","normalizeResponse","imageResponseToFalJson","videoResponseToFalJson","isInjectableStatus","INJECTED_STATUS_RANGE","createServerWithResolvedAuth","createServer"],"sources":["../src/llmock.ts"],"sourcesContent":["import type {\n AudioResponse,\n ChaosConfig,\n EmbeddingFixtureOpts,\n FalQueueOpts,\n Fixture,\n FixtureFileEntry,\n FixtureFileResponse,\n FixtureMatch,\n FixtureOpts,\n FixtureResponse,\n ImageResponse,\n MockServerOptions,\n Mountable,\n RecordConfig,\n ResponseFactory,\n TranscriptionResponse,\n VideoResponse,\n} from \"./types.js\";\nimport {\n createServer,\n createServerWithResolvedAuth,\n performFullReset,\n type ServerInstance,\n} from \"./server.js\";\nimport type { ResolvedInboundAuth } from \"./api-key-auth.js\";\nimport {\n isInjectableStatus,\n INJECTED_STATUS_RANGE,\n loadFixtureFile,\n loadFixturesFromDir,\n entryToFixture,\n normalizeResponse,\n validateFixtures,\n} from \"./fixture-loader.js\";\nimport { Journal } from \"./journal.js\";\nimport type { SearchFixture, SearchResult } from \"./search.js\";\nimport type { RerankFixture, RerankResult } from \"./rerank.js\";\nimport type { ModerationFixture, ModerationResult } from \"./moderation.js\";\nimport { imageResponseToFalJson, videoResponseToFalJson } from \"./fal.js\";\n\nexport class LLMock {\n private fixtures: Fixture[] = [];\n private searchFixtures: SearchFixture[] = [];\n private rerankFixtures: RerankFixture[] = [];\n private moderationFixtures: ModerationFixture[] = [];\n private mounts: Array<{ path: string; handler: Mountable }> = [];\n private serverInstance: ServerInstance | null = null;\n private options: MockServerOptions;\n private readonly resolvedInboundAuth?: ResolvedInboundAuth;\n\n constructor(options?: MockServerOptions, resolvedInboundAuth?: ResolvedInboundAuth) {\n this.options = options ?? {};\n this.resolvedInboundAuth = resolvedInboundAuth;\n }\n\n // ---- Fixture management ----\n\n addFixture(fixture: Fixture): this {\n this.fixtures.push(fixture);\n return this;\n }\n\n addFixtures(fixtures: Fixture[]): this {\n this.fixtures.push(...fixtures);\n return this;\n }\n\n prependFixture(fixture: Fixture): this {\n this.fixtures.unshift(fixture);\n return this;\n }\n\n getFixtures(): readonly Fixture[] {\n return this.fixtures;\n }\n\n loadFixtureFile(filePath: string): this {\n this.fixtures.push(...loadFixtureFile(filePath));\n return this;\n }\n\n loadFixtureDir(dirPath: string): this {\n this.fixtures.push(...loadFixturesFromDir(dirPath));\n return this;\n }\n\n /**\n * Add fixtures from a JSON string or pre-parsed array of fixture entries.\n * Validates all fixtures and throws if any have severity \"error\".\n */\n addFixturesFromJSON(input: string | FixtureFileEntry[]): this {\n let entries: FixtureFileEntry[];\n if (typeof input === \"string\") {\n try {\n entries = JSON.parse(input);\n } catch (err) {\n throw new Error(\n `addFixturesFromJSON: invalid JSON — ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n } else {\n entries = input;\n }\n const converted = entries.map((e) => entryToFixture(e));\n const issues = validateFixtures(converted);\n const errors = issues.filter((i) => i.severity === \"error\");\n if (errors.length > 0) {\n throw new Error(`Fixture validation failed: ${JSON.stringify(errors)}`);\n }\n this.fixtures.push(...converted);\n return this;\n }\n\n // Uses length = 0 to preserve array reference identity — the running\n // server reads this same array on every request.\n clearFixtures(): this {\n this.fixtures.length = 0;\n return this;\n }\n\n // ---- Convenience ----\n\n on(\n match: FixtureMatch,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.addFixture({\n match,\n response: typeof response === \"function\" ? response : normalizeResponse(response),\n ...opts,\n });\n }\n\n onMessage(\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ userMessage: pattern }, response, opts);\n }\n\n onEmbedding(\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: EmbeddingFixtureOpts,\n ): this {\n return this.on({ inputText: pattern }, response, opts);\n }\n\n onJsonOutput(pattern: string | RegExp, jsonContent: object | string, opts?: FixtureOpts): this {\n const content = typeof jsonContent === \"string\" ? jsonContent : JSON.stringify(jsonContent);\n return this.on({ userMessage: pattern, responseFormat: \"json_object\" }, { content }, opts);\n }\n\n onToolCall(\n name: string,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ toolName: name }, response, opts);\n }\n\n onToolResult(\n id: string,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ toolCallId: id }, response, opts);\n }\n\n onTurn(\n turn: number,\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ userMessage: pattern, turnIndex: turn }, response, opts);\n }\n\n onImage(prompt: string | RegExp, response: ImageResponse): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"image\" },\n response,\n });\n }\n\n onSpeech(input: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: input, endpoint: \"speech\" },\n response,\n });\n }\n\n onTranscription(response: TranscriptionResponse): this {\n return this.addFixture({\n match: { endpoint: \"transcription\" },\n response,\n });\n }\n\n onTranslation(response: TranscriptionResponse): this {\n return this.addFixture({\n match: { endpoint: \"translation\" },\n response,\n });\n }\n\n onVideo(prompt: string | RegExp, response: VideoResponse): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"video\" },\n response,\n });\n }\n\n onAudio(input: string | RegExp, response: AudioResponse): this {\n return this.addFixture({ match: { userMessage: input }, response });\n }\n\n onSoundEffect(text: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: text, endpoint: \"audio-gen\" },\n response,\n });\n }\n\n onMusic(prompt: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"audio-gen\" },\n response,\n });\n }\n\n onElevenLabsTTS(text: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: text, endpoint: \"elevenlabs-tts\" },\n response,\n });\n }\n\n onFalAudio(prompt: string | RegExp, response: AudioResponse, model?: string): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"fal-audio\", ...(model ? { model } : {}) },\n response,\n });\n }\n\n // fal.queue.* is the dominant client API; onFalRun is a sync alias.\n //\n // `opts.billableUnits` rides through to the completed `queue-result`\n // response's `x-fal-billable-units` header (emitted alongside\n // `x-fal-request-id`), letting consumers like `@tanstack/ai-fal` surface a\n // billed-units value on replay. Omit it to preserve the header-less default.\n onFalQueue(modelOrPrompt: string | RegExp, response: unknown, opts?: FalQueueOpts): this {\n const { billableUnits, ...fixtureOpts } = opts ?? {};\n return this.addFixture({\n match: { model: modelOrPrompt, endpoint: \"fal\" },\n response: { json: response, ...(billableUnits != null ? { billableUnits } : {}) },\n ...fixtureOpts,\n });\n }\n\n onFalRun(modelOrPrompt: string | RegExp, response: unknown, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, response, opts);\n }\n\n /**\n * Register a fal.ai image fixture. Wraps an `ImageResponse` (the shape used\n * by `onImage` and OpenAI/Azure image fixtures) into fal's image envelope\n * before storing it as a `RawJSONResponse`. Defaults `width`/`height` to\n * 1024 when the fixture's `ImageItem` doesn't carry them.\n */\n onFalImage(modelOrPrompt: string | RegExp, response: ImageResponse, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, imageResponseToFalJson(response), opts);\n }\n\n /**\n * Register a fal.ai video fixture. Wraps a `VideoResponse` into fal's video\n * envelope (`{ video: { url, content_type, file_name, file_size }, seed }`)\n * before storing it as a `RawJSONResponse`.\n */\n onFalVideo(modelOrPrompt: string | RegExp, response: VideoResponse, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, videoResponseToFalJson(response), opts);\n }\n\n // ---- Service mock convenience methods ----\n\n onSearch(pattern: string | RegExp, results: SearchResult[]): this {\n this.searchFixtures.push({ match: pattern, results });\n return this;\n }\n\n onRerank(pattern: string | RegExp, results: RerankResult[]): this {\n this.rerankFixtures.push({ match: pattern, results });\n return this;\n }\n\n onModerate(pattern: string | RegExp, result: ModerationResult): this {\n this.moderationFixtures.push({ match: pattern, result });\n return this;\n }\n\n /**\n * Queue a one-shot error that will be returned for the next matching\n * request, then automatically removed. Implemented as an internal fixture\n * with a `predicate` that always matches (so it fires first) and spliced\n * at the front of the fixture list.\n */\n nextRequestError(\n status: number,\n errorBody?: { message?: string; type?: string; code?: string },\n ): this {\n // Same gate as `POST /__aimock/error` and fixture validation — this is the\n // form the docs use, and an unchecked status reaches `res.writeHead` on\n // the next matched request: 99/1000 throw there (the injected error is\n // lost and the caller sees a generic 500), and 1xx hangs the request until\n // the client times out. Throwing HERE names the offending call instead.\n if (!isInjectableStatus(status)) {\n throw new RangeError(\n `nextRequestError: invalid status ${String(status)} — must be ${INJECTED_STATUS_RANGE}`,\n );\n }\n const errorResponse: FixtureResponse = {\n error: {\n message: errorBody?.message ?? \"Injected error\",\n type: errorBody?.type ?? \"server_error\",\n code: errorBody?.code,\n },\n status,\n };\n // An injected error is only a valid response for endpoints that can carry\n // an error envelope. Mirror the router's endpoint-compat table\n // (matchFixtureDiagnostic in router.ts): error responses are compatible\n // with chat / embedding / realtime* / fal and with requests that carry no\n // endpoint type, but NOT with multimedia endpoints (image, speech, video,\n // transcription, …). Gating consumption on this prevents an incompatible\n // request from matching the predicate and splicing — and thereby\n // destroying — a one-shot error intended for a different endpoint before\n // the router's own compat check would have skipped it.\n const errorEndpointCompatible = (req: import(\"./types.js\").ChatCompletionRequest) => {\n const reqEndpoint = req._endpointType as string | undefined;\n if (\n reqEndpoint === undefined ||\n reqEndpoint === \"chat\" ||\n reqEndpoint === \"embedding\" ||\n reqEndpoint.startsWith(\"realtime\") ||\n reqEndpoint === \"fal\"\n ) {\n return true;\n }\n return false;\n };\n const fixture: Fixture = {\n match: { predicate: errorEndpointCompatible },\n response: errorResponse,\n };\n // Insert at front so it matches before everything else\n this.fixtures.unshift(fixture);\n // Remove after first match — the journal records it so tests can assert.\n // Only consume (and splice) when the request endpoint is compatible; an\n // incompatible request returns false here, falls through to other\n // fixtures, and leaves this error pending for its intended endpoint.\n const original = fixture.match.predicate!;\n fixture.match.predicate = (req) => {\n const result = original(req);\n if (result) {\n // Remove synchronously on first match to prevent race conditions\n const idx = this.fixtures.indexOf(fixture);\n if (idx !== -1) this.fixtures.splice(idx, 1);\n }\n return result;\n };\n return this;\n }\n\n // ---- Mounts ----\n\n mount(path: string, handler: Mountable): this {\n this.mounts.push({ path, handler });\n\n // If server is already running, wire up journal, registry, and baseUrl immediately\n // so late mounts behave identically to pre-start mounts.\n if (this.serverInstance) {\n if (handler.setJournal) handler.setJournal(this.serverInstance.journal);\n if (handler.setBaseUrl) handler.setBaseUrl(this.serverInstance.url + path);\n const registry = this.serverInstance.defaults.registry;\n if (registry && handler.setRegistry) handler.setRegistry(registry);\n }\n\n return this;\n }\n\n // ---- Journal proxies ----\n\n getRequests(): import(\"./types.js\").JournalEntry[] {\n return this.journal.getAll();\n }\n\n getLastRequest(): import(\"./types.js\").JournalEntry | null {\n return this.journal.getLast();\n }\n\n clearRequests(): void {\n this.journal.clear();\n }\n\n resetMatchCounts(testId?: string): this {\n if (this.serverInstance) {\n this.serverInstance.journal.clearMatchCounts(testId);\n }\n return this;\n }\n\n // ---- Chaos ----\n\n /**\n * Set the server-wide chaos baseline. Writes through to a RUNNING server as\n * well as the construction options: an untagged `POST /__aimock/chaos`\n * installs a baseline override that shadows `options.chaos`, and without this\n * the call would silently do nothing until the next `reset()`. Per-testId\n * overrides still win over the baseline.\n */\n setChaos(config: ChaosConfig): this {\n this.options.chaos = config;\n if (this.serverInstance) this.serverInstance.defaults.chaos = config;\n return this;\n }\n\n /**\n * Turn chaos off everywhere: the construction config, the runtime baseline,\n * and every per-testId override (assigning `undefined` is the full-clear\n * signal the `defaults.chaos` setter defines).\n */\n clearChaos(): this {\n delete this.options.chaos;\n if (this.serverInstance) this.serverInstance.defaults.chaos = undefined;\n return this;\n }\n\n // ---- Recording ----\n\n enableRecording(config: RecordConfig): this {\n this.options.record = config;\n return this;\n }\n\n disableRecording(): this {\n delete this.options.record;\n return this;\n }\n\n // ---- Reset ----\n\n /**\n * Full reset — the in-process equivalent of `POST /__aimock/reset`. Shares\n * one implementation with the control-API route so the two cannot drift.\n *\n * The one deliberate difference: search / rerank / moderation fixtures are\n * also cleared here. Those are registered through this class only — the\n * control API has no route that creates them, so the HTTP reset can neither\n * reach nor observe them.\n *\n * NOT ALL OF THIS IS PER-INSTANCE. `performFullReset` clears module-global\n * state as well: the Gemini interaction and event-id counters\n * (`resetInteractionCounter` / `resetEventIdCounter` in\n * `./gemini-interactions.js`) and the fal.ai job/queue maps (`falJobs`,\n * `falQueueStates`). With two `LLMock` instances live in one process,\n * `a.reset()` rewinds the Gemini id sequence that `b` is mid-way through —\n * `b` then re-emits `aimock-int-0` / `evt_1`, ids it has already handed\n * out — and drops `b`'s in-flight fal jobs. Give each instance its own\n * process (or its own vitest worker) if that matters.\n *\n * The global stores are cleared even before `start()`, when there is no\n * server instance to reset.\n */\n reset(): this {\n this.searchFixtures.length = 0;\n this.rerankFixtures.length = 0;\n this.moderationFixtures.length = 0;\n performFullReset(this.fixtures, this.serverInstance);\n return this;\n }\n\n // ---- Server lifecycle ----\n\n async start(): Promise<string> {\n if (this.serverInstance) {\n throw new Error(\"Server already started\");\n }\n this.serverInstance = await (this.resolvedInboundAuth\n ? createServerWithResolvedAuth(\n this.fixtures,\n this.options,\n this.resolvedInboundAuth,\n this.mounts,\n {\n search: this.searchFixtures,\n rerank: this.rerankFixtures,\n moderation: this.moderationFixtures,\n },\n )\n : createServer(this.fixtures, this.options, this.mounts, {\n search: this.searchFixtures,\n rerank: this.rerankFixtures,\n moderation: this.moderationFixtures,\n }));\n return this.serverInstance.url;\n }\n\n async stop(): Promise<void> {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n const { server } = this.serverInstance;\n await new Promise<void>((resolve, reject) => {\n server.close((err: Error | undefined) => (err ? reject(err) : resolve()));\n });\n this.serverInstance = null;\n }\n\n // ---- Accessors ----\n\n get journal(): Journal {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n return this.serverInstance.journal;\n }\n\n get url(): string {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n return this.serverInstance.url;\n }\n\n get baseUrl(): string {\n return this.url;\n }\n\n get port(): number {\n const parsed = new URL(this.url); // this.url throws if not started\n if (!parsed.port) {\n throw new Error(`Server URL has no explicit port: ${this.url}`);\n }\n return parseInt(parsed.port, 10);\n }\n\n // ---- Static factory ----\n\n static async create(options?: MockServerOptions): Promise<LLMock> {\n const instance = new LLMock(options);\n await instance.start();\n return instance;\n }\n}\n\n/** @internal Configuration startup preserves a policy resolved from the selected source. */\nexport function createLLMockWithResolvedAuth(\n options: MockServerOptions,\n resolvedAuth: ResolvedInboundAuth,\n): LLMock {\n return new LLMock(options, resolvedAuth);\n}\n"],"mappings":"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"}
|
package/dist/llmock.d.cts
CHANGED
|
@@ -79,7 +79,19 @@ declare class LLMock {
|
|
|
79
79
|
getLastRequest(): JournalEntry | null;
|
|
80
80
|
clearRequests(): void;
|
|
81
81
|
resetMatchCounts(testId?: string): this;
|
|
82
|
+
/**
|
|
83
|
+
* Set the server-wide chaos baseline. Writes through to a RUNNING server as
|
|
84
|
+
* well as the construction options: an untagged `POST /__aimock/chaos`
|
|
85
|
+
* installs a baseline override that shadows `options.chaos`, and without this
|
|
86
|
+
* the call would silently do nothing until the next `reset()`. Per-testId
|
|
87
|
+
* overrides still win over the baseline.
|
|
88
|
+
*/
|
|
82
89
|
setChaos(config: ChaosConfig): this;
|
|
90
|
+
/**
|
|
91
|
+
* Turn chaos off everywhere: the construction config, the runtime baseline,
|
|
92
|
+
* and every per-testId override (assigning `undefined` is the full-clear
|
|
93
|
+
* signal the `defaults.chaos` setter defines).
|
|
94
|
+
*/
|
|
83
95
|
clearChaos(): this;
|
|
84
96
|
enableRecording(config: RecordConfig): this;
|
|
85
97
|
disableRecording(): this;
|
package/dist/llmock.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llmock.d.cts","names":[],"sources":["../src/llmock.ts"],"sourcesContent":[],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"llmock.d.cts","names":[],"sources":["../src/llmock.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/llmock.d.ts
CHANGED
|
@@ -79,7 +79,19 @@ declare class LLMock {
|
|
|
79
79
|
getLastRequest(): JournalEntry | null;
|
|
80
80
|
clearRequests(): void;
|
|
81
81
|
resetMatchCounts(testId?: string): this;
|
|
82
|
+
/**
|
|
83
|
+
* Set the server-wide chaos baseline. Writes through to a RUNNING server as
|
|
84
|
+
* well as the construction options: an untagged `POST /__aimock/chaos`
|
|
85
|
+
* installs a baseline override that shadows `options.chaos`, and without this
|
|
86
|
+
* the call would silently do nothing until the next `reset()`. Per-testId
|
|
87
|
+
* overrides still win over the baseline.
|
|
88
|
+
*/
|
|
82
89
|
setChaos(config: ChaosConfig): this;
|
|
90
|
+
/**
|
|
91
|
+
* Turn chaos off everywhere: the construction config, the runtime baseline,
|
|
92
|
+
* and every per-testId override (assigning `undefined` is the full-clear
|
|
93
|
+
* signal the `defaults.chaos` setter defines).
|
|
94
|
+
*/
|
|
83
95
|
clearChaos(): this;
|
|
84
96
|
enableRecording(config: RecordConfig): this;
|
|
85
97
|
disableRecording(): this;
|
package/dist/llmock.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llmock.d.ts","names":[],"sources":["../src/llmock.ts"],"sourcesContent":[],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"llmock.d.ts","names":[],"sources":["../src/llmock.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/llmock.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { entryToFixture, loadFixtureFile, loadFixturesFromDir, normalizeResponse, validateFixtures } from "./fixture-loader.js";
|
|
1
|
+
import { INJECTED_STATUS_RANGE, entryToFixture, isInjectableStatus, loadFixtureFile, loadFixturesFromDir, normalizeResponse, validateFixtures } from "./fixture-loader.js";
|
|
2
2
|
import { imageResponseToFalJson, videoResponseToFalJson } from "./fal.js";
|
|
3
3
|
import { createServer, createServerWithResolvedAuth, performFullReset } from "./server.js";
|
|
4
4
|
|
|
@@ -237,6 +237,7 @@ var LLMock = class LLMock {
|
|
|
237
237
|
* at the front of the fixture list.
|
|
238
238
|
*/
|
|
239
239
|
nextRequestError(status, errorBody) {
|
|
240
|
+
if (!isInjectableStatus(status)) throw new RangeError(`nextRequestError: invalid status ${String(status)} — must be ${INJECTED_STATUS_RANGE}`);
|
|
240
241
|
const errorResponse = {
|
|
241
242
|
error: {
|
|
242
243
|
message: errorBody?.message ?? "Injected error",
|
|
@@ -292,12 +293,26 @@ var LLMock = class LLMock {
|
|
|
292
293
|
if (this.serverInstance) this.serverInstance.journal.clearMatchCounts(testId);
|
|
293
294
|
return this;
|
|
294
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Set the server-wide chaos baseline. Writes through to a RUNNING server as
|
|
298
|
+
* well as the construction options: an untagged `POST /__aimock/chaos`
|
|
299
|
+
* installs a baseline override that shadows `options.chaos`, and without this
|
|
300
|
+
* the call would silently do nothing until the next `reset()`. Per-testId
|
|
301
|
+
* overrides still win over the baseline.
|
|
302
|
+
*/
|
|
295
303
|
setChaos(config) {
|
|
296
304
|
this.options.chaos = config;
|
|
305
|
+
if (this.serverInstance) this.serverInstance.defaults.chaos = config;
|
|
297
306
|
return this;
|
|
298
307
|
}
|
|
308
|
+
/**
|
|
309
|
+
* Turn chaos off everywhere: the construction config, the runtime baseline,
|
|
310
|
+
* and every per-testId override (assigning `undefined` is the full-clear
|
|
311
|
+
* signal the `defaults.chaos` setter defines).
|
|
312
|
+
*/
|
|
299
313
|
clearChaos() {
|
|
300
314
|
delete this.options.chaos;
|
|
315
|
+
if (this.serverInstance) this.serverInstance.defaults.chaos = void 0;
|
|
301
316
|
return this;
|
|
302
317
|
}
|
|
303
318
|
enableRecording(config) {
|
package/dist/llmock.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llmock.js","names":[],"sources":["../src/llmock.ts"],"sourcesContent":["import type {\n AudioResponse,\n ChaosConfig,\n EmbeddingFixtureOpts,\n FalQueueOpts,\n Fixture,\n FixtureFileEntry,\n FixtureFileResponse,\n FixtureMatch,\n FixtureOpts,\n FixtureResponse,\n ImageResponse,\n MockServerOptions,\n Mountable,\n RecordConfig,\n ResponseFactory,\n TranscriptionResponse,\n VideoResponse,\n} from \"./types.js\";\nimport {\n createServer,\n createServerWithResolvedAuth,\n performFullReset,\n type ServerInstance,\n} from \"./server.js\";\nimport type { ResolvedInboundAuth } from \"./api-key-auth.js\";\nimport {\n loadFixtureFile,\n loadFixturesFromDir,\n entryToFixture,\n normalizeResponse,\n validateFixtures,\n} from \"./fixture-loader.js\";\nimport { Journal } from \"./journal.js\";\nimport type { SearchFixture, SearchResult } from \"./search.js\";\nimport type { RerankFixture, RerankResult } from \"./rerank.js\";\nimport type { ModerationFixture, ModerationResult } from \"./moderation.js\";\nimport { imageResponseToFalJson, videoResponseToFalJson } from \"./fal.js\";\n\nexport class LLMock {\n private fixtures: Fixture[] = [];\n private searchFixtures: SearchFixture[] = [];\n private rerankFixtures: RerankFixture[] = [];\n private moderationFixtures: ModerationFixture[] = [];\n private mounts: Array<{ path: string; handler: Mountable }> = [];\n private serverInstance: ServerInstance | null = null;\n private options: MockServerOptions;\n private readonly resolvedInboundAuth?: ResolvedInboundAuth;\n\n constructor(options?: MockServerOptions, resolvedInboundAuth?: ResolvedInboundAuth) {\n this.options = options ?? {};\n this.resolvedInboundAuth = resolvedInboundAuth;\n }\n\n // ---- Fixture management ----\n\n addFixture(fixture: Fixture): this {\n this.fixtures.push(fixture);\n return this;\n }\n\n addFixtures(fixtures: Fixture[]): this {\n this.fixtures.push(...fixtures);\n return this;\n }\n\n prependFixture(fixture: Fixture): this {\n this.fixtures.unshift(fixture);\n return this;\n }\n\n getFixtures(): readonly Fixture[] {\n return this.fixtures;\n }\n\n loadFixtureFile(filePath: string): this {\n this.fixtures.push(...loadFixtureFile(filePath));\n return this;\n }\n\n loadFixtureDir(dirPath: string): this {\n this.fixtures.push(...loadFixturesFromDir(dirPath));\n return this;\n }\n\n /**\n * Add fixtures from a JSON string or pre-parsed array of fixture entries.\n * Validates all fixtures and throws if any have severity \"error\".\n */\n addFixturesFromJSON(input: string | FixtureFileEntry[]): this {\n let entries: FixtureFileEntry[];\n if (typeof input === \"string\") {\n try {\n entries = JSON.parse(input);\n } catch (err) {\n throw new Error(\n `addFixturesFromJSON: invalid JSON — ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n } else {\n entries = input;\n }\n const converted = entries.map((e) => entryToFixture(e));\n const issues = validateFixtures(converted);\n const errors = issues.filter((i) => i.severity === \"error\");\n if (errors.length > 0) {\n throw new Error(`Fixture validation failed: ${JSON.stringify(errors)}`);\n }\n this.fixtures.push(...converted);\n return this;\n }\n\n // Uses length = 0 to preserve array reference identity — the running\n // server reads this same array on every request.\n clearFixtures(): this {\n this.fixtures.length = 0;\n return this;\n }\n\n // ---- Convenience ----\n\n on(\n match: FixtureMatch,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.addFixture({\n match,\n response: typeof response === \"function\" ? response : normalizeResponse(response),\n ...opts,\n });\n }\n\n onMessage(\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ userMessage: pattern }, response, opts);\n }\n\n onEmbedding(\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: EmbeddingFixtureOpts,\n ): this {\n return this.on({ inputText: pattern }, response, opts);\n }\n\n onJsonOutput(pattern: string | RegExp, jsonContent: object | string, opts?: FixtureOpts): this {\n const content = typeof jsonContent === \"string\" ? jsonContent : JSON.stringify(jsonContent);\n return this.on({ userMessage: pattern, responseFormat: \"json_object\" }, { content }, opts);\n }\n\n onToolCall(\n name: string,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ toolName: name }, response, opts);\n }\n\n onToolResult(\n id: string,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ toolCallId: id }, response, opts);\n }\n\n onTurn(\n turn: number,\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ userMessage: pattern, turnIndex: turn }, response, opts);\n }\n\n onImage(prompt: string | RegExp, response: ImageResponse): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"image\" },\n response,\n });\n }\n\n onSpeech(input: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: input, endpoint: \"speech\" },\n response,\n });\n }\n\n onTranscription(response: TranscriptionResponse): this {\n return this.addFixture({\n match: { endpoint: \"transcription\" },\n response,\n });\n }\n\n onTranslation(response: TranscriptionResponse): this {\n return this.addFixture({\n match: { endpoint: \"translation\" },\n response,\n });\n }\n\n onVideo(prompt: string | RegExp, response: VideoResponse): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"video\" },\n response,\n });\n }\n\n onAudio(input: string | RegExp, response: AudioResponse): this {\n return this.addFixture({ match: { userMessage: input }, response });\n }\n\n onSoundEffect(text: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: text, endpoint: \"audio-gen\" },\n response,\n });\n }\n\n onMusic(prompt: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"audio-gen\" },\n response,\n });\n }\n\n onElevenLabsTTS(text: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: text, endpoint: \"elevenlabs-tts\" },\n response,\n });\n }\n\n onFalAudio(prompt: string | RegExp, response: AudioResponse, model?: string): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"fal-audio\", ...(model ? { model } : {}) },\n response,\n });\n }\n\n // fal.queue.* is the dominant client API; onFalRun is a sync alias.\n //\n // `opts.billableUnits` rides through to the completed `queue-result`\n // response's `x-fal-billable-units` header (emitted alongside\n // `x-fal-request-id`), letting consumers like `@tanstack/ai-fal` surface a\n // billed-units value on replay. Omit it to preserve the header-less default.\n onFalQueue(modelOrPrompt: string | RegExp, response: unknown, opts?: FalQueueOpts): this {\n const { billableUnits, ...fixtureOpts } = opts ?? {};\n return this.addFixture({\n match: { model: modelOrPrompt, endpoint: \"fal\" },\n response: { json: response, ...(billableUnits != null ? { billableUnits } : {}) },\n ...fixtureOpts,\n });\n }\n\n onFalRun(modelOrPrompt: string | RegExp, response: unknown, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, response, opts);\n }\n\n /**\n * Register a fal.ai image fixture. Wraps an `ImageResponse` (the shape used\n * by `onImage` and OpenAI/Azure image fixtures) into fal's image envelope\n * before storing it as a `RawJSONResponse`. Defaults `width`/`height` to\n * 1024 when the fixture's `ImageItem` doesn't carry them.\n */\n onFalImage(modelOrPrompt: string | RegExp, response: ImageResponse, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, imageResponseToFalJson(response), opts);\n }\n\n /**\n * Register a fal.ai video fixture. Wraps a `VideoResponse` into fal's video\n * envelope (`{ video: { url, content_type, file_name, file_size }, seed }`)\n * before storing it as a `RawJSONResponse`.\n */\n onFalVideo(modelOrPrompt: string | RegExp, response: VideoResponse, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, videoResponseToFalJson(response), opts);\n }\n\n // ---- Service mock convenience methods ----\n\n onSearch(pattern: string | RegExp, results: SearchResult[]): this {\n this.searchFixtures.push({ match: pattern, results });\n return this;\n }\n\n onRerank(pattern: string | RegExp, results: RerankResult[]): this {\n this.rerankFixtures.push({ match: pattern, results });\n return this;\n }\n\n onModerate(pattern: string | RegExp, result: ModerationResult): this {\n this.moderationFixtures.push({ match: pattern, result });\n return this;\n }\n\n /**\n * Queue a one-shot error that will be returned for the next matching\n * request, then automatically removed. Implemented as an internal fixture\n * with a `predicate` that always matches (so it fires first) and spliced\n * at the front of the fixture list.\n */\n nextRequestError(\n status: number,\n errorBody?: { message?: string; type?: string; code?: string },\n ): this {\n const errorResponse: FixtureResponse = {\n error: {\n message: errorBody?.message ?? \"Injected error\",\n type: errorBody?.type ?? \"server_error\",\n code: errorBody?.code,\n },\n status,\n };\n // An injected error is only a valid response for endpoints that can carry\n // an error envelope. Mirror the router's endpoint-compat table\n // (matchFixtureDiagnostic in router.ts): error responses are compatible\n // with chat / embedding / realtime* / fal and with requests that carry no\n // endpoint type, but NOT with multimedia endpoints (image, speech, video,\n // transcription, …). Gating consumption on this prevents an incompatible\n // request from matching the predicate and splicing — and thereby\n // destroying — a one-shot error intended for a different endpoint before\n // the router's own compat check would have skipped it.\n const errorEndpointCompatible = (req: import(\"./types.js\").ChatCompletionRequest) => {\n const reqEndpoint = req._endpointType as string | undefined;\n if (\n reqEndpoint === undefined ||\n reqEndpoint === \"chat\" ||\n reqEndpoint === \"embedding\" ||\n reqEndpoint.startsWith(\"realtime\") ||\n reqEndpoint === \"fal\"\n ) {\n return true;\n }\n return false;\n };\n const fixture: Fixture = {\n match: { predicate: errorEndpointCompatible },\n response: errorResponse,\n };\n // Insert at front so it matches before everything else\n this.fixtures.unshift(fixture);\n // Remove after first match — the journal records it so tests can assert.\n // Only consume (and splice) when the request endpoint is compatible; an\n // incompatible request returns false here, falls through to other\n // fixtures, and leaves this error pending for its intended endpoint.\n const original = fixture.match.predicate!;\n fixture.match.predicate = (req) => {\n const result = original(req);\n if (result) {\n // Remove synchronously on first match to prevent race conditions\n const idx = this.fixtures.indexOf(fixture);\n if (idx !== -1) this.fixtures.splice(idx, 1);\n }\n return result;\n };\n return this;\n }\n\n // ---- Mounts ----\n\n mount(path: string, handler: Mountable): this {\n this.mounts.push({ path, handler });\n\n // If server is already running, wire up journal, registry, and baseUrl immediately\n // so late mounts behave identically to pre-start mounts.\n if (this.serverInstance) {\n if (handler.setJournal) handler.setJournal(this.serverInstance.journal);\n if (handler.setBaseUrl) handler.setBaseUrl(this.serverInstance.url + path);\n const registry = this.serverInstance.defaults.registry;\n if (registry && handler.setRegistry) handler.setRegistry(registry);\n }\n\n return this;\n }\n\n // ---- Journal proxies ----\n\n getRequests(): import(\"./types.js\").JournalEntry[] {\n return this.journal.getAll();\n }\n\n getLastRequest(): import(\"./types.js\").JournalEntry | null {\n return this.journal.getLast();\n }\n\n clearRequests(): void {\n this.journal.clear();\n }\n\n resetMatchCounts(testId?: string): this {\n if (this.serverInstance) {\n this.serverInstance.journal.clearMatchCounts(testId);\n }\n return this;\n }\n\n // ---- Chaos ----\n\n setChaos(config: ChaosConfig): this {\n this.options.chaos = config;\n return this;\n }\n\n clearChaos(): this {\n delete this.options.chaos;\n return this;\n }\n\n // ---- Recording ----\n\n enableRecording(config: RecordConfig): this {\n this.options.record = config;\n return this;\n }\n\n disableRecording(): this {\n delete this.options.record;\n return this;\n }\n\n // ---- Reset ----\n\n /**\n * Full reset — the in-process equivalent of `POST /__aimock/reset`. Shares\n * one implementation with the control-API route so the two cannot drift.\n *\n * The one deliberate difference: search / rerank / moderation fixtures are\n * also cleared here. Those are registered through this class only — the\n * control API has no route that creates them, so the HTTP reset can neither\n * reach nor observe them.\n *\n * NOT ALL OF THIS IS PER-INSTANCE. `performFullReset` clears module-global\n * state as well: the Gemini interaction and event-id counters\n * (`resetInteractionCounter` / `resetEventIdCounter` in\n * `./gemini-interactions.js`) and the fal.ai job/queue maps (`falJobs`,\n * `falQueueStates`). With two `LLMock` instances live in one process,\n * `a.reset()` rewinds the Gemini id sequence that `b` is mid-way through —\n * `b` then re-emits `aimock-int-0` / `evt_1`, ids it has already handed\n * out — and drops `b`'s in-flight fal jobs. Give each instance its own\n * process (or its own vitest worker) if that matters.\n *\n * The global stores are cleared even before `start()`, when there is no\n * server instance to reset.\n */\n reset(): this {\n this.searchFixtures.length = 0;\n this.rerankFixtures.length = 0;\n this.moderationFixtures.length = 0;\n performFullReset(this.fixtures, this.serverInstance);\n return this;\n }\n\n // ---- Server lifecycle ----\n\n async start(): Promise<string> {\n if (this.serverInstance) {\n throw new Error(\"Server already started\");\n }\n this.serverInstance = await (this.resolvedInboundAuth\n ? createServerWithResolvedAuth(\n this.fixtures,\n this.options,\n this.resolvedInboundAuth,\n this.mounts,\n {\n search: this.searchFixtures,\n rerank: this.rerankFixtures,\n moderation: this.moderationFixtures,\n },\n )\n : createServer(this.fixtures, this.options, this.mounts, {\n search: this.searchFixtures,\n rerank: this.rerankFixtures,\n moderation: this.moderationFixtures,\n }));\n return this.serverInstance.url;\n }\n\n async stop(): Promise<void> {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n const { server } = this.serverInstance;\n await new Promise<void>((resolve, reject) => {\n server.close((err: Error | undefined) => (err ? reject(err) : resolve()));\n });\n this.serverInstance = null;\n }\n\n // ---- Accessors ----\n\n get journal(): Journal {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n return this.serverInstance.journal;\n }\n\n get url(): string {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n return this.serverInstance.url;\n }\n\n get baseUrl(): string {\n return this.url;\n }\n\n get port(): number {\n const parsed = new URL(this.url); // this.url throws if not started\n if (!parsed.port) {\n throw new Error(`Server URL has no explicit port: ${this.url}`);\n }\n return parseInt(parsed.port, 10);\n }\n\n // ---- Static factory ----\n\n static async create(options?: MockServerOptions): Promise<LLMock> {\n const instance = new LLMock(options);\n await instance.start();\n return instance;\n }\n}\n\n/** @internal Configuration startup preserves a policy resolved from the selected source. */\nexport function createLLMockWithResolvedAuth(\n options: MockServerOptions,\n resolvedAuth: ResolvedInboundAuth,\n): LLMock {\n return new LLMock(options, resolvedAuth);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"llmock.js","names":[],"sources":["../src/llmock.ts"],"sourcesContent":["import type {\n AudioResponse,\n ChaosConfig,\n EmbeddingFixtureOpts,\n FalQueueOpts,\n Fixture,\n FixtureFileEntry,\n FixtureFileResponse,\n FixtureMatch,\n FixtureOpts,\n FixtureResponse,\n ImageResponse,\n MockServerOptions,\n Mountable,\n RecordConfig,\n ResponseFactory,\n TranscriptionResponse,\n VideoResponse,\n} from \"./types.js\";\nimport {\n createServer,\n createServerWithResolvedAuth,\n performFullReset,\n type ServerInstance,\n} from \"./server.js\";\nimport type { ResolvedInboundAuth } from \"./api-key-auth.js\";\nimport {\n isInjectableStatus,\n INJECTED_STATUS_RANGE,\n loadFixtureFile,\n loadFixturesFromDir,\n entryToFixture,\n normalizeResponse,\n validateFixtures,\n} from \"./fixture-loader.js\";\nimport { Journal } from \"./journal.js\";\nimport type { SearchFixture, SearchResult } from \"./search.js\";\nimport type { RerankFixture, RerankResult } from \"./rerank.js\";\nimport type { ModerationFixture, ModerationResult } from \"./moderation.js\";\nimport { imageResponseToFalJson, videoResponseToFalJson } from \"./fal.js\";\n\nexport class LLMock {\n private fixtures: Fixture[] = [];\n private searchFixtures: SearchFixture[] = [];\n private rerankFixtures: RerankFixture[] = [];\n private moderationFixtures: ModerationFixture[] = [];\n private mounts: Array<{ path: string; handler: Mountable }> = [];\n private serverInstance: ServerInstance | null = null;\n private options: MockServerOptions;\n private readonly resolvedInboundAuth?: ResolvedInboundAuth;\n\n constructor(options?: MockServerOptions, resolvedInboundAuth?: ResolvedInboundAuth) {\n this.options = options ?? {};\n this.resolvedInboundAuth = resolvedInboundAuth;\n }\n\n // ---- Fixture management ----\n\n addFixture(fixture: Fixture): this {\n this.fixtures.push(fixture);\n return this;\n }\n\n addFixtures(fixtures: Fixture[]): this {\n this.fixtures.push(...fixtures);\n return this;\n }\n\n prependFixture(fixture: Fixture): this {\n this.fixtures.unshift(fixture);\n return this;\n }\n\n getFixtures(): readonly Fixture[] {\n return this.fixtures;\n }\n\n loadFixtureFile(filePath: string): this {\n this.fixtures.push(...loadFixtureFile(filePath));\n return this;\n }\n\n loadFixtureDir(dirPath: string): this {\n this.fixtures.push(...loadFixturesFromDir(dirPath));\n return this;\n }\n\n /**\n * Add fixtures from a JSON string or pre-parsed array of fixture entries.\n * Validates all fixtures and throws if any have severity \"error\".\n */\n addFixturesFromJSON(input: string | FixtureFileEntry[]): this {\n let entries: FixtureFileEntry[];\n if (typeof input === \"string\") {\n try {\n entries = JSON.parse(input);\n } catch (err) {\n throw new Error(\n `addFixturesFromJSON: invalid JSON — ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n } else {\n entries = input;\n }\n const converted = entries.map((e) => entryToFixture(e));\n const issues = validateFixtures(converted);\n const errors = issues.filter((i) => i.severity === \"error\");\n if (errors.length > 0) {\n throw new Error(`Fixture validation failed: ${JSON.stringify(errors)}`);\n }\n this.fixtures.push(...converted);\n return this;\n }\n\n // Uses length = 0 to preserve array reference identity — the running\n // server reads this same array on every request.\n clearFixtures(): this {\n this.fixtures.length = 0;\n return this;\n }\n\n // ---- Convenience ----\n\n on(\n match: FixtureMatch,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.addFixture({\n match,\n response: typeof response === \"function\" ? response : normalizeResponse(response),\n ...opts,\n });\n }\n\n onMessage(\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ userMessage: pattern }, response, opts);\n }\n\n onEmbedding(\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: EmbeddingFixtureOpts,\n ): this {\n return this.on({ inputText: pattern }, response, opts);\n }\n\n onJsonOutput(pattern: string | RegExp, jsonContent: object | string, opts?: FixtureOpts): this {\n const content = typeof jsonContent === \"string\" ? jsonContent : JSON.stringify(jsonContent);\n return this.on({ userMessage: pattern, responseFormat: \"json_object\" }, { content }, opts);\n }\n\n onToolCall(\n name: string,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ toolName: name }, response, opts);\n }\n\n onToolResult(\n id: string,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ toolCallId: id }, response, opts);\n }\n\n onTurn(\n turn: number,\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ userMessage: pattern, turnIndex: turn }, response, opts);\n }\n\n onImage(prompt: string | RegExp, response: ImageResponse): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"image\" },\n response,\n });\n }\n\n onSpeech(input: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: input, endpoint: \"speech\" },\n response,\n });\n }\n\n onTranscription(response: TranscriptionResponse): this {\n return this.addFixture({\n match: { endpoint: \"transcription\" },\n response,\n });\n }\n\n onTranslation(response: TranscriptionResponse): this {\n return this.addFixture({\n match: { endpoint: \"translation\" },\n response,\n });\n }\n\n onVideo(prompt: string | RegExp, response: VideoResponse): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"video\" },\n response,\n });\n }\n\n onAudio(input: string | RegExp, response: AudioResponse): this {\n return this.addFixture({ match: { userMessage: input }, response });\n }\n\n onSoundEffect(text: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: text, endpoint: \"audio-gen\" },\n response,\n });\n }\n\n onMusic(prompt: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"audio-gen\" },\n response,\n });\n }\n\n onElevenLabsTTS(text: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: text, endpoint: \"elevenlabs-tts\" },\n response,\n });\n }\n\n onFalAudio(prompt: string | RegExp, response: AudioResponse, model?: string): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"fal-audio\", ...(model ? { model } : {}) },\n response,\n });\n }\n\n // fal.queue.* is the dominant client API; onFalRun is a sync alias.\n //\n // `opts.billableUnits` rides through to the completed `queue-result`\n // response's `x-fal-billable-units` header (emitted alongside\n // `x-fal-request-id`), letting consumers like `@tanstack/ai-fal` surface a\n // billed-units value on replay. Omit it to preserve the header-less default.\n onFalQueue(modelOrPrompt: string | RegExp, response: unknown, opts?: FalQueueOpts): this {\n const { billableUnits, ...fixtureOpts } = opts ?? {};\n return this.addFixture({\n match: { model: modelOrPrompt, endpoint: \"fal\" },\n response: { json: response, ...(billableUnits != null ? { billableUnits } : {}) },\n ...fixtureOpts,\n });\n }\n\n onFalRun(modelOrPrompt: string | RegExp, response: unknown, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, response, opts);\n }\n\n /**\n * Register a fal.ai image fixture. Wraps an `ImageResponse` (the shape used\n * by `onImage` and OpenAI/Azure image fixtures) into fal's image envelope\n * before storing it as a `RawJSONResponse`. Defaults `width`/`height` to\n * 1024 when the fixture's `ImageItem` doesn't carry them.\n */\n onFalImage(modelOrPrompt: string | RegExp, response: ImageResponse, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, imageResponseToFalJson(response), opts);\n }\n\n /**\n * Register a fal.ai video fixture. Wraps a `VideoResponse` into fal's video\n * envelope (`{ video: { url, content_type, file_name, file_size }, seed }`)\n * before storing it as a `RawJSONResponse`.\n */\n onFalVideo(modelOrPrompt: string | RegExp, response: VideoResponse, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, videoResponseToFalJson(response), opts);\n }\n\n // ---- Service mock convenience methods ----\n\n onSearch(pattern: string | RegExp, results: SearchResult[]): this {\n this.searchFixtures.push({ match: pattern, results });\n return this;\n }\n\n onRerank(pattern: string | RegExp, results: RerankResult[]): this {\n this.rerankFixtures.push({ match: pattern, results });\n return this;\n }\n\n onModerate(pattern: string | RegExp, result: ModerationResult): this {\n this.moderationFixtures.push({ match: pattern, result });\n return this;\n }\n\n /**\n * Queue a one-shot error that will be returned for the next matching\n * request, then automatically removed. Implemented as an internal fixture\n * with a `predicate` that always matches (so it fires first) and spliced\n * at the front of the fixture list.\n */\n nextRequestError(\n status: number,\n errorBody?: { message?: string; type?: string; code?: string },\n ): this {\n // Same gate as `POST /__aimock/error` and fixture validation — this is the\n // form the docs use, and an unchecked status reaches `res.writeHead` on\n // the next matched request: 99/1000 throw there (the injected error is\n // lost and the caller sees a generic 500), and 1xx hangs the request until\n // the client times out. Throwing HERE names the offending call instead.\n if (!isInjectableStatus(status)) {\n throw new RangeError(\n `nextRequestError: invalid status ${String(status)} — must be ${INJECTED_STATUS_RANGE}`,\n );\n }\n const errorResponse: FixtureResponse = {\n error: {\n message: errorBody?.message ?? \"Injected error\",\n type: errorBody?.type ?? \"server_error\",\n code: errorBody?.code,\n },\n status,\n };\n // An injected error is only a valid response for endpoints that can carry\n // an error envelope. Mirror the router's endpoint-compat table\n // (matchFixtureDiagnostic in router.ts): error responses are compatible\n // with chat / embedding / realtime* / fal and with requests that carry no\n // endpoint type, but NOT with multimedia endpoints (image, speech, video,\n // transcription, …). Gating consumption on this prevents an incompatible\n // request from matching the predicate and splicing — and thereby\n // destroying — a one-shot error intended for a different endpoint before\n // the router's own compat check would have skipped it.\n const errorEndpointCompatible = (req: import(\"./types.js\").ChatCompletionRequest) => {\n const reqEndpoint = req._endpointType as string | undefined;\n if (\n reqEndpoint === undefined ||\n reqEndpoint === \"chat\" ||\n reqEndpoint === \"embedding\" ||\n reqEndpoint.startsWith(\"realtime\") ||\n reqEndpoint === \"fal\"\n ) {\n return true;\n }\n return false;\n };\n const fixture: Fixture = {\n match: { predicate: errorEndpointCompatible },\n response: errorResponse,\n };\n // Insert at front so it matches before everything else\n this.fixtures.unshift(fixture);\n // Remove after first match — the journal records it so tests can assert.\n // Only consume (and splice) when the request endpoint is compatible; an\n // incompatible request returns false here, falls through to other\n // fixtures, and leaves this error pending for its intended endpoint.\n const original = fixture.match.predicate!;\n fixture.match.predicate = (req) => {\n const result = original(req);\n if (result) {\n // Remove synchronously on first match to prevent race conditions\n const idx = this.fixtures.indexOf(fixture);\n if (idx !== -1) this.fixtures.splice(idx, 1);\n }\n return result;\n };\n return this;\n }\n\n // ---- Mounts ----\n\n mount(path: string, handler: Mountable): this {\n this.mounts.push({ path, handler });\n\n // If server is already running, wire up journal, registry, and baseUrl immediately\n // so late mounts behave identically to pre-start mounts.\n if (this.serverInstance) {\n if (handler.setJournal) handler.setJournal(this.serverInstance.journal);\n if (handler.setBaseUrl) handler.setBaseUrl(this.serverInstance.url + path);\n const registry = this.serverInstance.defaults.registry;\n if (registry && handler.setRegistry) handler.setRegistry(registry);\n }\n\n return this;\n }\n\n // ---- Journal proxies ----\n\n getRequests(): import(\"./types.js\").JournalEntry[] {\n return this.journal.getAll();\n }\n\n getLastRequest(): import(\"./types.js\").JournalEntry | null {\n return this.journal.getLast();\n }\n\n clearRequests(): void {\n this.journal.clear();\n }\n\n resetMatchCounts(testId?: string): this {\n if (this.serverInstance) {\n this.serverInstance.journal.clearMatchCounts(testId);\n }\n return this;\n }\n\n // ---- Chaos ----\n\n /**\n * Set the server-wide chaos baseline. Writes through to a RUNNING server as\n * well as the construction options: an untagged `POST /__aimock/chaos`\n * installs a baseline override that shadows `options.chaos`, and without this\n * the call would silently do nothing until the next `reset()`. Per-testId\n * overrides still win over the baseline.\n */\n setChaos(config: ChaosConfig): this {\n this.options.chaos = config;\n if (this.serverInstance) this.serverInstance.defaults.chaos = config;\n return this;\n }\n\n /**\n * Turn chaos off everywhere: the construction config, the runtime baseline,\n * and every per-testId override (assigning `undefined` is the full-clear\n * signal the `defaults.chaos` setter defines).\n */\n clearChaos(): this {\n delete this.options.chaos;\n if (this.serverInstance) this.serverInstance.defaults.chaos = undefined;\n return this;\n }\n\n // ---- Recording ----\n\n enableRecording(config: RecordConfig): this {\n this.options.record = config;\n return this;\n }\n\n disableRecording(): this {\n delete this.options.record;\n return this;\n }\n\n // ---- Reset ----\n\n /**\n * Full reset — the in-process equivalent of `POST /__aimock/reset`. Shares\n * one implementation with the control-API route so the two cannot drift.\n *\n * The one deliberate difference: search / rerank / moderation fixtures are\n * also cleared here. Those are registered through this class only — the\n * control API has no route that creates them, so the HTTP reset can neither\n * reach nor observe them.\n *\n * NOT ALL OF THIS IS PER-INSTANCE. `performFullReset` clears module-global\n * state as well: the Gemini interaction and event-id counters\n * (`resetInteractionCounter` / `resetEventIdCounter` in\n * `./gemini-interactions.js`) and the fal.ai job/queue maps (`falJobs`,\n * `falQueueStates`). With two `LLMock` instances live in one process,\n * `a.reset()` rewinds the Gemini id sequence that `b` is mid-way through —\n * `b` then re-emits `aimock-int-0` / `evt_1`, ids it has already handed\n * out — and drops `b`'s in-flight fal jobs. Give each instance its own\n * process (or its own vitest worker) if that matters.\n *\n * The global stores are cleared even before `start()`, when there is no\n * server instance to reset.\n */\n reset(): this {\n this.searchFixtures.length = 0;\n this.rerankFixtures.length = 0;\n this.moderationFixtures.length = 0;\n performFullReset(this.fixtures, this.serverInstance);\n return this;\n }\n\n // ---- Server lifecycle ----\n\n async start(): Promise<string> {\n if (this.serverInstance) {\n throw new Error(\"Server already started\");\n }\n this.serverInstance = await (this.resolvedInboundAuth\n ? createServerWithResolvedAuth(\n this.fixtures,\n this.options,\n this.resolvedInboundAuth,\n this.mounts,\n {\n search: this.searchFixtures,\n rerank: this.rerankFixtures,\n moderation: this.moderationFixtures,\n },\n )\n : createServer(this.fixtures, this.options, this.mounts, {\n search: this.searchFixtures,\n rerank: this.rerankFixtures,\n moderation: this.moderationFixtures,\n }));\n return this.serverInstance.url;\n }\n\n async stop(): Promise<void> {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n const { server } = this.serverInstance;\n await new Promise<void>((resolve, reject) => {\n server.close((err: Error | undefined) => (err ? reject(err) : resolve()));\n });\n this.serverInstance = null;\n }\n\n // ---- Accessors ----\n\n get journal(): Journal {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n return this.serverInstance.journal;\n }\n\n get url(): string {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n return this.serverInstance.url;\n }\n\n get baseUrl(): string {\n return this.url;\n }\n\n get port(): number {\n const parsed = new URL(this.url); // this.url throws if not started\n if (!parsed.port) {\n throw new Error(`Server URL has no explicit port: ${this.url}`);\n }\n return parseInt(parsed.port, 10);\n }\n\n // ---- Static factory ----\n\n static async create(options?: MockServerOptions): Promise<LLMock> {\n const instance = new LLMock(options);\n await instance.start();\n return instance;\n }\n}\n\n/** @internal Configuration startup preserves a policy resolved from the selected source. */\nexport function createLLMockWithResolvedAuth(\n options: MockServerOptions,\n resolvedAuth: ResolvedInboundAuth,\n): LLMock {\n return new LLMock(options, resolvedAuth);\n}\n"],"mappings":"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"}
|