@copilotkit/aimock 1.17.0 → 1.18.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.
Files changed (140) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +11 -0
  4. package/dist/agui-types.d.ts.map +1 -1
  5. package/dist/bedrock-converse.cjs +4 -4
  6. package/dist/bedrock-converse.cjs.map +1 -1
  7. package/dist/bedrock-converse.d.cts.map +1 -1
  8. package/dist/bedrock-converse.d.ts.map +1 -1
  9. package/dist/bedrock-converse.js +4 -4
  10. package/dist/bedrock-converse.js.map +1 -1
  11. package/dist/bedrock.cjs +4 -4
  12. package/dist/bedrock.cjs.map +1 -1
  13. package/dist/bedrock.d.cts.map +1 -1
  14. package/dist/bedrock.d.ts.map +1 -1
  15. package/dist/bedrock.js +4 -4
  16. package/dist/bedrock.js.map +1 -1
  17. package/dist/chaos.cjs +35 -9
  18. package/dist/chaos.cjs.map +1 -1
  19. package/dist/chaos.d.cts +17 -2
  20. package/dist/chaos.d.cts.map +1 -1
  21. package/dist/chaos.d.ts +17 -2
  22. package/dist/chaos.d.ts.map +1 -1
  23. package/dist/chaos.js +35 -10
  24. package/dist/chaos.js.map +1 -1
  25. package/dist/cohere.cjs +2 -2
  26. package/dist/cohere.cjs.map +1 -1
  27. package/dist/cohere.js +2 -2
  28. package/dist/cohere.js.map +1 -1
  29. package/dist/elevenlabs-audio.cjs +5 -2
  30. package/dist/elevenlabs-audio.cjs.map +1 -1
  31. package/dist/elevenlabs-audio.js +5 -2
  32. package/dist/elevenlabs-audio.js.map +1 -1
  33. package/dist/embeddings.cjs +2 -2
  34. package/dist/embeddings.cjs.map +1 -1
  35. package/dist/embeddings.js +2 -2
  36. package/dist/embeddings.js.map +1 -1
  37. package/dist/fal-audio.cjs +11 -4
  38. package/dist/fal-audio.cjs.map +1 -1
  39. package/dist/fal-audio.js +11 -5
  40. package/dist/fal-audio.js.map +1 -1
  41. package/dist/fal.cjs +424 -0
  42. package/dist/fal.cjs.map +1 -0
  43. package/dist/fal.d.cts +39 -0
  44. package/dist/fal.d.cts.map +1 -0
  45. package/dist/fal.d.ts +39 -0
  46. package/dist/fal.d.ts.map +1 -0
  47. package/dist/fal.js +420 -0
  48. package/dist/fal.js.map +1 -0
  49. package/dist/fixture-loader.cjs +2 -2
  50. package/dist/fixture-loader.cjs.map +1 -1
  51. package/dist/fixture-loader.d.cts.map +1 -1
  52. package/dist/fixture-loader.d.ts.map +1 -1
  53. package/dist/fixture-loader.js +3 -3
  54. package/dist/fixture-loader.js.map +1 -1
  55. package/dist/gemini-interactions.cjs +4 -2
  56. package/dist/gemini-interactions.cjs.map +1 -1
  57. package/dist/gemini-interactions.js +4 -2
  58. package/dist/gemini-interactions.js.map +1 -1
  59. package/dist/gemini.cjs +2 -2
  60. package/dist/gemini.cjs.map +1 -1
  61. package/dist/gemini.js +2 -2
  62. package/dist/gemini.js.map +1 -1
  63. package/dist/helpers.cjs +4 -0
  64. package/dist/helpers.cjs.map +1 -1
  65. package/dist/helpers.d.cts.map +1 -1
  66. package/dist/helpers.d.ts.map +1 -1
  67. package/dist/helpers.js +4 -1
  68. package/dist/helpers.js.map +1 -1
  69. package/dist/images.cjs +2 -2
  70. package/dist/images.cjs.map +1 -1
  71. package/dist/images.js +2 -2
  72. package/dist/images.js.map +1 -1
  73. package/dist/index.cjs +3 -0
  74. package/dist/index.d.cts +3 -2
  75. package/dist/index.d.ts +3 -2
  76. package/dist/index.js +2 -1
  77. package/dist/llmock.cjs +15 -0
  78. package/dist/llmock.cjs.map +1 -1
  79. package/dist/llmock.d.cts +2 -0
  80. package/dist/llmock.d.cts.map +1 -1
  81. package/dist/llmock.d.ts +2 -0
  82. package/dist/llmock.d.ts.map +1 -1
  83. package/dist/llmock.js +15 -0
  84. package/dist/llmock.js.map +1 -1
  85. package/dist/messages.cjs +2 -2
  86. package/dist/messages.cjs.map +1 -1
  87. package/dist/messages.js +2 -2
  88. package/dist/messages.js.map +1 -1
  89. package/dist/ollama.cjs +4 -4
  90. package/dist/ollama.cjs.map +1 -1
  91. package/dist/ollama.d.cts.map +1 -1
  92. package/dist/ollama.d.ts.map +1 -1
  93. package/dist/ollama.js +4 -4
  94. package/dist/ollama.js.map +1 -1
  95. package/dist/recorder.cjs +42 -17
  96. package/dist/recorder.cjs.map +1 -1
  97. package/dist/recorder.d.cts +50 -5
  98. package/dist/recorder.d.cts.map +1 -1
  99. package/dist/recorder.d.ts +50 -5
  100. package/dist/recorder.d.ts.map +1 -1
  101. package/dist/recorder.js +42 -17
  102. package/dist/recorder.js.map +1 -1
  103. package/dist/responses.cjs +2 -2
  104. package/dist/responses.cjs.map +1 -1
  105. package/dist/responses.js +2 -2
  106. package/dist/responses.js.map +1 -1
  107. package/dist/router.cjs +1 -1
  108. package/dist/router.cjs.map +1 -1
  109. package/dist/router.d.cts.map +1 -1
  110. package/dist/router.d.ts.map +1 -1
  111. package/dist/router.js +2 -2
  112. package/dist/router.js.map +1 -1
  113. package/dist/server.cjs +128 -52
  114. package/dist/server.cjs.map +1 -1
  115. package/dist/server.d.cts.map +1 -1
  116. package/dist/server.d.ts.map +1 -1
  117. package/dist/server.js +129 -53
  118. package/dist/server.js.map +1 -1
  119. package/dist/speech.cjs +2 -2
  120. package/dist/speech.cjs.map +1 -1
  121. package/dist/speech.js +2 -2
  122. package/dist/speech.js.map +1 -1
  123. package/dist/transcription.cjs +2 -2
  124. package/dist/transcription.cjs.map +1 -1
  125. package/dist/transcription.js +2 -2
  126. package/dist/transcription.js.map +1 -1
  127. package/dist/types.d.cts +30 -6
  128. package/dist/types.d.cts.map +1 -1
  129. package/dist/types.d.ts +30 -6
  130. package/dist/types.d.ts.map +1 -1
  131. package/dist/vector-types.d.cts.map +1 -1
  132. package/dist/video.cjs +9 -3
  133. package/dist/video.cjs.map +1 -1
  134. package/dist/video.d.cts +1 -1
  135. package/dist/video.d.cts.map +1 -1
  136. package/dist/video.d.ts +1 -1
  137. package/dist/video.d.ts.map +1 -1
  138. package/dist/video.js +9 -3
  139. package/dist/video.js.map +1 -1
  140. package/package.json +1 -1
package/dist/router.js CHANGED
@@ -1,4 +1,4 @@
1
- import { isAudioResponse, isImageResponse, isTranscriptionResponse, isVideoResponse } from "./helpers.js";
1
+ import { isAudioResponse, isErrorResponse, isImageResponse, isJSONResponse, isTranscriptionResponse, isVideoResponse } from "./helpers.js";
2
2
 
3
3
  //#region src/router.ts
4
4
  function getLastMessageByRole(messages, role) {
@@ -31,7 +31,7 @@ function matchFixture(fixtures, req, matchCounts, requestTransform) {
31
31
  if (match.endpoint !== reqEndpoint) continue;
32
32
  } else if (reqEndpoint && reqEndpoint !== "chat" && reqEndpoint !== "embedding") {
33
33
  const r = fixture.response;
34
- if (!(reqEndpoint === "image" && isImageResponse(r) || reqEndpoint === "speech" && isAudioResponse(r) || reqEndpoint === "audio-gen" && isAudioResponse(r) || reqEndpoint === "fal-audio" && isAudioResponse(r) || reqEndpoint === "transcription" && isTranscriptionResponse(r) || reqEndpoint === "video" && isVideoResponse(r))) continue;
34
+ if (!(reqEndpoint === "image" && isImageResponse(r) || reqEndpoint === "speech" && isAudioResponse(r) || reqEndpoint === "audio-gen" && isAudioResponse(r) || reqEndpoint === "fal-audio" && isAudioResponse(r) || reqEndpoint === "fal" && (isJSONResponse(r) || isErrorResponse(r)) || reqEndpoint === "transcription" && isTranscriptionResponse(r) || reqEndpoint === "video" && isVideoResponse(r))) continue;
35
35
  }
36
36
  if (match.userMessage !== void 0) {
37
37
  const msg = getLastMessageByRole(effective.messages, "user");
@@ -1 +1 @@
1
- {"version":3,"file":"router.js","names":[],"sources":["../src/router.ts"],"sourcesContent":["import type { ChatCompletionRequest, ChatMessage, ContentPart, Fixture } from \"./types.js\";\nimport {\n isImageResponse,\n isAudioResponse,\n isTranscriptionResponse,\n isVideoResponse,\n} from \"./helpers.js\";\n\nexport function getLastMessageByRole(messages: ChatMessage[], role: string): ChatMessage | null {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === role) return messages[i];\n }\n return null;\n}\n\n/**\n * Extract the text content from a message's content field.\n * Handles both plain string content and array-of-parts content\n * (e.g. `[{type: \"text\", text: \"...\"}]` as sent by some SDKs).\n */\nexport function getTextContent(content: string | ContentPart[] | null): string | null {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n const texts = content\n .filter((p) => p.type === \"text\" && typeof p.text === \"string\" && p.text !== \"\")\n .map((p) => p.text as string);\n return texts.length > 0 ? texts.join(\"\") : null;\n }\n return null;\n}\n\nexport function matchFixture(\n fixtures: Fixture[],\n req: ChatCompletionRequest,\n matchCounts?: Map<Fixture, number>,\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest,\n): Fixture | null {\n // Apply transform once before matching — used for stripping dynamic data\n const effective = requestTransform ? requestTransform(req) : req;\n const useExactMatch = !!requestTransform;\n\n for (const fixture of fixtures) {\n const { match } = fixture;\n\n // predicate — if present, must return true (receives original request)\n if (match.predicate !== undefined) {\n if (!match.predicate(req)) continue;\n }\n\n // endpoint — bidirectional filtering:\n // 1. If fixture has endpoint set, only match requests of that type\n // 2. If request has _endpointType but fixture doesn't, skip fixtures\n // whose response type is incompatible (prevents generic chat fixtures\n // from matching image/speech/video requests and causing 500s)\n const reqEndpoint = effective._endpointType as string | undefined;\n if (match.endpoint !== undefined) {\n if (match.endpoint !== reqEndpoint) continue;\n } else if (reqEndpoint && reqEndpoint !== \"chat\" && reqEndpoint !== \"embedding\") {\n // Fixture has no endpoint restriction but request is multimedia —\n // only match if the response type is compatible\n const r = fixture.response;\n const compatible =\n (reqEndpoint === \"image\" && isImageResponse(r)) ||\n (reqEndpoint === \"speech\" && isAudioResponse(r)) ||\n (reqEndpoint === \"audio-gen\" && isAudioResponse(r)) ||\n (reqEndpoint === \"fal-audio\" && isAudioResponse(r)) ||\n (reqEndpoint === \"transcription\" && isTranscriptionResponse(r)) ||\n (reqEndpoint === \"video\" && isVideoResponse(r));\n if (!compatible) continue;\n }\n\n // userMessage — case-sensitive match against the last user message content.\n // String matching is intentionally case-sensitive so fixture authors can\n // rely on exact string values. This differs from the case-insensitive\n // matchesPattern() in helpers.ts, which is used for search/rerank/moderation\n // where exact casing rarely matters.\n if (match.userMessage !== undefined) {\n const msg = getLastMessageByRole(effective.messages, \"user\");\n const text = msg ? getTextContent(msg.content) : null;\n if (!text) continue;\n if (typeof match.userMessage === \"string\") {\n if (useExactMatch) {\n if (text !== match.userMessage) continue;\n } else {\n if (!text.includes(match.userMessage)) continue;\n }\n } else {\n match.userMessage.lastIndex = 0;\n if (!match.userMessage.test(text)) continue;\n }\n }\n\n // toolCallId — a toolCallId fixture answers the model's response to a tool\n // result, which by API contract only happens when the conversation's LAST\n // message is a tool result. If a newer user (or other) turn follows the\n // tool message, the stale tool_call_id must not shadow userMessage matchers.\n if (match.toolCallId !== undefined) {\n const last = effective.messages[effective.messages.length - 1];\n if (!last || last.role !== \"tool\" || last.tool_call_id !== match.toolCallId) continue;\n }\n\n // toolName — match against any tool definition by function.name\n if (match.toolName !== undefined) {\n const tools = effective.tools ?? [];\n const found = tools.some((t) => t.function.name === match.toolName);\n if (!found) continue;\n }\n\n // inputText — case-sensitive match against the embedding input text.\n // Same rationale as userMessage above: fixture authors specify exact strings.\n if (match.inputText !== undefined) {\n const embeddingInput = effective.embeddingInput;\n if (!embeddingInput) continue;\n if (typeof match.inputText === \"string\") {\n if (useExactMatch) {\n if (embeddingInput !== match.inputText) continue;\n } else {\n if (!embeddingInput.includes(match.inputText)) continue;\n }\n } else {\n match.inputText.lastIndex = 0;\n if (!match.inputText.test(embeddingInput)) continue;\n }\n }\n\n // responseFormat — exact string match against request response_format.type\n if (match.responseFormat !== undefined) {\n const reqType = effective.response_format?.type;\n if (reqType !== match.responseFormat) continue;\n }\n\n // model — exact string or regexp\n if (match.model !== undefined) {\n if (typeof match.model === \"string\") {\n if (effective.model !== match.model) continue;\n } else {\n match.model.lastIndex = 0;\n if (!match.model.test(effective.model)) continue;\n }\n }\n\n // sequenceIndex — check against the fixture's match count\n if (match.sequenceIndex !== undefined && matchCounts !== undefined) {\n const count = matchCounts.get(fixture) ?? 0;\n if (count !== match.sequenceIndex) continue;\n }\n\n if (match.turnIndex !== undefined) {\n const assistantCount = effective.messages.filter((m) => m.role === \"assistant\").length;\n if (assistantCount !== match.turnIndex) continue;\n }\n\n if (match.hasToolResult !== undefined) {\n const hasTool = effective.messages.some((m) => m.role === \"tool\");\n if (hasTool !== match.hasToolResult) continue;\n }\n\n return fixture;\n }\n\n return null;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"router.js","names":[],"sources":["../src/router.ts"],"sourcesContent":["import type { ChatCompletionRequest, ChatMessage, ContentPart, Fixture } from \"./types.js\";\nimport {\n isImageResponse,\n isAudioResponse,\n isTranscriptionResponse,\n isVideoResponse,\n isJSONResponse,\n isErrorResponse,\n} from \"./helpers.js\";\n\nexport function getLastMessageByRole(messages: ChatMessage[], role: string): ChatMessage | null {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === role) return messages[i];\n }\n return null;\n}\n\n/**\n * Extract the text content from a message's content field.\n * Handles both plain string content and array-of-parts content\n * (e.g. `[{type: \"text\", text: \"...\"}]` as sent by some SDKs).\n */\nexport function getTextContent(content: string | ContentPart[] | null): string | null {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n const texts = content\n .filter((p) => p.type === \"text\" && typeof p.text === \"string\" && p.text !== \"\")\n .map((p) => p.text as string);\n return texts.length > 0 ? texts.join(\"\") : null;\n }\n return null;\n}\n\nexport function matchFixture(\n fixtures: Fixture[],\n req: ChatCompletionRequest,\n matchCounts?: Map<Fixture, number>,\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest,\n): Fixture | null {\n // Apply transform once before matching — used for stripping dynamic data\n const effective = requestTransform ? requestTransform(req) : req;\n const useExactMatch = !!requestTransform;\n\n for (const fixture of fixtures) {\n const { match } = fixture;\n\n // predicate — if present, must return true (receives original request)\n if (match.predicate !== undefined) {\n if (!match.predicate(req)) continue;\n }\n\n // endpoint — bidirectional filtering:\n // 1. If fixture has endpoint set, only match requests of that type\n // 2. If request has _endpointType but fixture doesn't, skip fixtures\n // whose response type is incompatible (prevents generic chat fixtures\n // from matching image/speech/video requests and causing 500s)\n const reqEndpoint = effective._endpointType as string | undefined;\n if (match.endpoint !== undefined) {\n if (match.endpoint !== reqEndpoint) continue;\n } else if (reqEndpoint && reqEndpoint !== \"chat\" && reqEndpoint !== \"embedding\") {\n // Fixture has no endpoint restriction but request is multimedia —\n // only match if the response type is compatible\n const r = fixture.response;\n const compatible =\n (reqEndpoint === \"image\" && isImageResponse(r)) ||\n (reqEndpoint === \"speech\" && isAudioResponse(r)) ||\n (reqEndpoint === \"audio-gen\" && isAudioResponse(r)) ||\n (reqEndpoint === \"fal-audio\" && isAudioResponse(r)) ||\n (reqEndpoint === \"fal\" && (isJSONResponse(r) || isErrorResponse(r))) ||\n (reqEndpoint === \"transcription\" && isTranscriptionResponse(r)) ||\n (reqEndpoint === \"video\" && isVideoResponse(r));\n if (!compatible) continue;\n }\n\n // userMessage — case-sensitive match against the last user message content.\n // String matching is intentionally case-sensitive so fixture authors can\n // rely on exact string values. This differs from the case-insensitive\n // matchesPattern() in helpers.ts, which is used for search/rerank/moderation\n // where exact casing rarely matters.\n if (match.userMessage !== undefined) {\n const msg = getLastMessageByRole(effective.messages, \"user\");\n const text = msg ? getTextContent(msg.content) : null;\n if (!text) continue;\n if (typeof match.userMessage === \"string\") {\n if (useExactMatch) {\n if (text !== match.userMessage) continue;\n } else {\n if (!text.includes(match.userMessage)) continue;\n }\n } else {\n match.userMessage.lastIndex = 0;\n if (!match.userMessage.test(text)) continue;\n }\n }\n\n // toolCallId — a toolCallId fixture answers the model's response to a tool\n // result, which by API contract only happens when the conversation's LAST\n // message is a tool result. If a newer user (or other) turn follows the\n // tool message, the stale tool_call_id must not shadow userMessage matchers.\n if (match.toolCallId !== undefined) {\n const last = effective.messages[effective.messages.length - 1];\n if (!last || last.role !== \"tool\" || last.tool_call_id !== match.toolCallId) continue;\n }\n\n // toolName — match against any tool definition by function.name\n if (match.toolName !== undefined) {\n const tools = effective.tools ?? [];\n const found = tools.some((t) => t.function.name === match.toolName);\n if (!found) continue;\n }\n\n // inputText — case-sensitive match against the embedding input text.\n // Same rationale as userMessage above: fixture authors specify exact strings.\n if (match.inputText !== undefined) {\n const embeddingInput = effective.embeddingInput;\n if (!embeddingInput) continue;\n if (typeof match.inputText === \"string\") {\n if (useExactMatch) {\n if (embeddingInput !== match.inputText) continue;\n } else {\n if (!embeddingInput.includes(match.inputText)) continue;\n }\n } else {\n match.inputText.lastIndex = 0;\n if (!match.inputText.test(embeddingInput)) continue;\n }\n }\n\n // responseFormat — exact string match against request response_format.type\n if (match.responseFormat !== undefined) {\n const reqType = effective.response_format?.type;\n if (reqType !== match.responseFormat) continue;\n }\n\n // model — exact string or regexp\n if (match.model !== undefined) {\n if (typeof match.model === \"string\") {\n if (effective.model !== match.model) continue;\n } else {\n match.model.lastIndex = 0;\n if (!match.model.test(effective.model)) continue;\n }\n }\n\n // sequenceIndex — check against the fixture's match count\n if (match.sequenceIndex !== undefined && matchCounts !== undefined) {\n const count = matchCounts.get(fixture) ?? 0;\n if (count !== match.sequenceIndex) continue;\n }\n\n if (match.turnIndex !== undefined) {\n const assistantCount = effective.messages.filter((m) => m.role === \"assistant\").length;\n if (assistantCount !== match.turnIndex) continue;\n }\n\n if (match.hasToolResult !== undefined) {\n const hasTool = effective.messages.some((m) => m.role === \"tool\");\n if (hasTool !== match.hasToolResult) continue;\n }\n\n return fixture;\n }\n\n return null;\n}\n"],"mappings":"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"}
package/dist/server.cjs CHANGED
@@ -21,6 +21,7 @@ const require_transcription = require('./transcription.cjs');
21
21
  const require_video = require('./video.cjs');
22
22
  const require_elevenlabs_audio = require('./elevenlabs-audio.cjs');
23
23
  const require_fal_audio = require('./fal-audio.cjs');
24
+ const require_fal = require('./fal.cjs');
24
25
  const require_ollama = require('./ollama.cjs');
25
26
  const require_cohere = require('./cohere.cjs');
26
27
  const require_search = require('./search.cjs');
@@ -56,6 +57,7 @@ const ELEVENLABS_MUSIC_RE = /^\/v1\/music(?:\/(.+))?$/;
56
57
  const FAL_QUEUE_SUBMIT_RE = /^\/fal\/queue\/submit\/(.+)$/;
57
58
  const FAL_QUEUE_REQUESTS_RE = /^\/fal\/queue\/requests\/(.+)$/;
58
59
  const FAL_RUN_RE = /^\/fal\/run\/(.+)$/;
60
+ const FAL_PREFIX_RE = /^\/fal(?:\/.*)?$/;
59
61
  const DEFAULT_CHUNK_SIZE = 20;
60
62
  const COMPAT_SUFFIXES = [
61
63
  "/chat/completions",
@@ -211,6 +213,7 @@ async function handleControlAPI(req, res, pathname, fixtures, journal, videoStat
211
213
  journal.clear();
212
214
  videoStates.clear();
213
215
  require_fal_audio.falJobs.clear();
216
+ require_fal.falQueueStates.clear();
214
217
  if (defaults.registry) defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
215
218
  res.writeHead(200, { "Content-Type": "application/json" });
216
219
  res.end(JSON.stringify({ reset: true }));
@@ -329,6 +332,9 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
329
332
  } }));
330
333
  return;
331
334
  }
335
+ const method = req.method ?? "POST";
336
+ const path = req.url ?? COMPLETIONS_PATH;
337
+ const flatHeaders = require_helpers.flattenHeaders(req.headers);
332
338
  body._endpointType = "chat";
333
339
  const testId = require_helpers.getTestId(req);
334
340
  const fixture = require_router.matchFixture(fixtures, body, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
@@ -340,18 +346,40 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
340
346
  const snippet = typeof lastUserMsg?.content === "string" ? lastUserMsg.content.slice(0, 80) : "";
341
347
  defaults.logger.debug(`No fixture matched for request (model=${body.model ?? "?"}, msg="${snippet}")`);
342
348
  }
343
- const method = req.method ?? "POST";
344
- const path = req.url ?? COMPLETIONS_PATH;
345
- const flatHeaders = require_helpers.flattenHeaders(req.headers);
346
- if (require_chaos.applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
349
+ const chaosAction = require_chaos.evaluateChaos(fixture, defaults.chaos, req.headers, defaults.logger);
350
+ const chaosContext = {
347
351
  method,
348
352
  path,
349
353
  headers: flatHeaders,
350
354
  body
351
- }, defaults.registry, defaults.logger)) return;
355
+ };
356
+ if (chaosAction === "drop" || chaosAction === "disconnect") {
357
+ require_chaos.applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : "proxy", defaults.registry);
358
+ return;
359
+ }
360
+ if (fixture && chaosAction === "malformed") {
361
+ require_chaos.applyChaosAction(chaosAction, res, fixture, journal, chaosContext, "fixture", defaults.registry);
362
+ return;
363
+ }
352
364
  if (!fixture) {
353
365
  if (defaults.record && providerKey) {
354
- if (await require_recorder.proxyAndRecord(req, res, body, providerKey, req.url ?? COMPLETIONS_PATH, fixtures, defaults, raw)) {
366
+ const hookOptions = chaosAction === "malformed" ? {
367
+ beforeWriteResponse: () => {
368
+ require_chaos.applyChaosAction(chaosAction, res, null, journal, chaosContext, "proxy", defaults.registry);
369
+ return true;
370
+ },
371
+ onHookBypassed: (reason) => {
372
+ defaults.logger.warn(`[chaos] malformed bypassed on proxy: upstream returned SSE (${reason})`);
373
+ defaults.registry?.incrementCounter("aimock_chaos_bypassed_total", {
374
+ action: "malformed",
375
+ source: "proxy",
376
+ reason
377
+ });
378
+ }
379
+ } : void 0;
380
+ const outcome = await require_recorder.proxyAndRecord(req, res, body, providerKey, req.url ?? COMPLETIONS_PATH, fixtures, defaults, raw, hookOptions);
381
+ if (outcome === "handled_by_hook") return;
382
+ if (outcome === "relayed") {
355
383
  journal.add({
356
384
  method: req.method ?? "POST",
357
385
  path: req.url ?? COMPLETIONS_PATH,
@@ -868,7 +896,7 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
868
896
  const videoStatusMatch = pathname.match(VIDEOS_STATUS_RE);
869
897
  if (videoStatusMatch && req.method === "GET") {
870
898
  const videoId = videoStatusMatch[1];
871
- require_video.handleVideoStatus(req, res, videoId, journal, setCorsHeaders, videoStates);
899
+ require_video.handleVideoStatus(req, res, videoId, journal, defaults, setCorsHeaders, videoStates);
872
900
  return;
873
901
  }
874
902
  const geminiPredictMatch = pathname.match(GEMINI_PREDICT_RE);
@@ -1097,15 +1125,19 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
1097
1125
  setCorsHeaders(res);
1098
1126
  try {
1099
1127
  const raw = await readBody(req);
1100
- if (require_chaos.applyChaos(res, null, defaults.chaos, req.headers, journal, {
1101
- method: req.method ?? "POST",
1102
- path: pathname,
1103
- headers: require_helpers.flattenHeaders(req.headers),
1104
- body: {
1105
- model: "",
1106
- messages: []
1107
- }
1108
- }, defaults.registry, defaults.logger)) return;
1128
+ const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger);
1129
+ if (chaosAction) {
1130
+ require_chaos.applyChaosAction(chaosAction, res, null, journal, {
1131
+ method: req.method ?? "POST",
1132
+ path: pathname,
1133
+ headers: require_helpers.flattenHeaders(req.headers),
1134
+ body: {
1135
+ model: "",
1136
+ messages: []
1137
+ }
1138
+ }, "fixture", defaults.registry);
1139
+ return;
1140
+ }
1109
1141
  await require_elevenlabs_audio.handleElevenLabsAudio(req, res, raw, fixtures, defaults, journal, "sound-generation");
1110
1142
  } catch (err) {
1111
1143
  const msg = err instanceof Error ? err.message : "Internal error";
@@ -1123,15 +1155,19 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
1123
1155
  const musicSubType = musicMatch[1] ?? "music";
1124
1156
  try {
1125
1157
  const raw = await readBody(req);
1126
- if (require_chaos.applyChaos(res, null, defaults.chaos, req.headers, journal, {
1127
- method: req.method ?? "POST",
1128
- path: pathname,
1129
- headers: require_helpers.flattenHeaders(req.headers),
1130
- body: {
1131
- model: "",
1132
- messages: []
1133
- }
1134
- }, defaults.registry, defaults.logger)) return;
1158
+ const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger);
1159
+ if (chaosAction) {
1160
+ require_chaos.applyChaosAction(chaosAction, res, null, journal, {
1161
+ method: req.method ?? "POST",
1162
+ path: pathname,
1163
+ headers: require_helpers.flattenHeaders(req.headers),
1164
+ body: {
1165
+ model: "",
1166
+ messages: []
1167
+ }
1168
+ }, "fixture", defaults.registry);
1169
+ return;
1170
+ }
1135
1171
  await require_elevenlabs_audio.handleElevenLabsAudio(req, res, raw, fixtures, defaults, journal, musicSubType);
1136
1172
  } catch (err) {
1137
1173
  const msg = err instanceof Error ? err.message : "Internal error";
@@ -1143,19 +1179,51 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
1143
1179
  }
1144
1180
  return;
1145
1181
  }
1182
+ if (FAL_PREFIX_RE.test(pathname) && req.headers["x-fal-target-host"]) {
1183
+ setCorsHeaders(res);
1184
+ try {
1185
+ const raw = req.method === "POST" || req.method === "PUT" ? await readBody(req) : "";
1186
+ const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger);
1187
+ if (chaosAction) {
1188
+ require_chaos.applyChaosAction(chaosAction, res, null, journal, {
1189
+ method: req.method ?? "GET",
1190
+ path: pathname,
1191
+ headers: require_helpers.flattenHeaders(req.headers),
1192
+ body: {
1193
+ model: "",
1194
+ messages: []
1195
+ }
1196
+ }, "fixture", defaults.registry);
1197
+ return;
1198
+ }
1199
+ if (await require_fal.handleFal(req, res, raw, pathname, fixtures, defaults, journal) === "handled") return;
1200
+ } catch (err) {
1201
+ const msg = err instanceof Error ? err.message : "Internal error";
1202
+ if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1203
+ message: msg,
1204
+ type: "server_error"
1205
+ } }));
1206
+ else if (!res.writableEnded) res.destroy();
1207
+ return;
1208
+ }
1209
+ }
1146
1210
  if (pathname.match(FAL_QUEUE_SUBMIT_RE) && req.method === "POST") {
1147
1211
  setCorsHeaders(res);
1148
1212
  try {
1149
1213
  const raw = await readBody(req);
1150
- if (require_chaos.applyChaos(res, null, defaults.chaos, req.headers, journal, {
1151
- method: req.method ?? "POST",
1152
- path: pathname,
1153
- headers: require_helpers.flattenHeaders(req.headers),
1154
- body: {
1155
- model: "",
1156
- messages: []
1157
- }
1158
- }, defaults.registry, defaults.logger)) return;
1214
+ const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger);
1215
+ if (chaosAction) {
1216
+ require_chaos.applyChaosAction(chaosAction, res, null, journal, {
1217
+ method: req.method ?? "POST",
1218
+ path: pathname,
1219
+ headers: require_helpers.flattenHeaders(req.headers),
1220
+ body: {
1221
+ model: "",
1222
+ messages: []
1223
+ }
1224
+ }, "fixture", defaults.registry);
1225
+ return;
1226
+ }
1159
1227
  await require_fal_audio.handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
1160
1228
  } catch (err) {
1161
1229
  const msg = err instanceof Error ? err.message : "Internal error";
@@ -1171,15 +1239,19 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
1171
1239
  setCorsHeaders(res);
1172
1240
  try {
1173
1241
  const raw = req.method === "POST" ? await readBody(req) : "{}";
1174
- if (require_chaos.applyChaos(res, null, defaults.chaos, req.headers, journal, {
1175
- method: req.method ?? "GET",
1176
- path: pathname,
1177
- headers: require_helpers.flattenHeaders(req.headers),
1178
- body: {
1179
- model: "",
1180
- messages: []
1181
- }
1182
- }, defaults.registry, defaults.logger)) return;
1242
+ const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger);
1243
+ if (chaosAction) {
1244
+ require_chaos.applyChaosAction(chaosAction, res, null, journal, {
1245
+ method: req.method ?? "GET",
1246
+ path: pathname,
1247
+ headers: require_helpers.flattenHeaders(req.headers),
1248
+ body: {
1249
+ model: "",
1250
+ messages: []
1251
+ }
1252
+ }, "fixture", defaults.registry);
1253
+ return;
1254
+ }
1183
1255
  await require_fal_audio.handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
1184
1256
  } catch (err) {
1185
1257
  const msg = err instanceof Error ? err.message : "Internal error";
@@ -1195,15 +1267,19 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
1195
1267
  setCorsHeaders(res);
1196
1268
  try {
1197
1269
  const raw = await readBody(req);
1198
- if (require_chaos.applyChaos(res, null, defaults.chaos, req.headers, journal, {
1199
- method: req.method ?? "POST",
1200
- path: pathname,
1201
- headers: require_helpers.flattenHeaders(req.headers),
1202
- body: {
1203
- model: "",
1204
- messages: []
1205
- }
1206
- }, defaults.registry, defaults.logger)) return;
1270
+ const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger);
1271
+ if (chaosAction) {
1272
+ require_chaos.applyChaosAction(chaosAction, res, null, journal, {
1273
+ method: req.method ?? "POST",
1274
+ path: pathname,
1275
+ headers: require_helpers.flattenHeaders(req.headers),
1276
+ body: {
1277
+ model: "",
1278
+ messages: []
1279
+ }
1280
+ }, "fixture", defaults.registry);
1281
+ return;
1282
+ }
1207
1283
  await require_fal_audio.handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
1208
1284
  } catch (err) {
1209
1285
  const msg = err instanceof Error ? err.message : "Internal error";