@copilotkit/aimock 1.40.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.
Files changed (251) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +28 -1
  4. package/README.md +5 -3
  5. package/dist/a2a-mock.cjs +18 -11
  6. package/dist/a2a-mock.cjs.map +1 -1
  7. package/dist/a2a-mock.d.cts +1 -0
  8. package/dist/a2a-mock.d.cts.map +1 -1
  9. package/dist/a2a-mock.d.ts +1 -0
  10. package/dist/a2a-mock.d.ts.map +1 -1
  11. package/dist/a2a-mock.js +18 -11
  12. package/dist/a2a-mock.js.map +1 -1
  13. package/dist/agui-handler.d.cts.map +1 -1
  14. package/dist/agui-handler.d.ts.map +1 -1
  15. package/dist/agui-mock.cjs +2 -0
  16. package/dist/agui-mock.cjs.map +1 -1
  17. package/dist/agui-mock.d.cts.map +1 -1
  18. package/dist/agui-mock.d.ts.map +1 -1
  19. package/dist/agui-mock.js +2 -0
  20. package/dist/agui-mock.js.map +1 -1
  21. package/dist/bedrock-converse.cjs +36 -2
  22. package/dist/bedrock-converse.cjs.map +1 -1
  23. package/dist/bedrock-converse.d.cts.map +1 -1
  24. package/dist/bedrock-converse.d.ts.map +1 -1
  25. package/dist/bedrock-converse.js +37 -3
  26. package/dist/bedrock-converse.js.map +1 -1
  27. package/dist/bedrock.cjs +50 -13
  28. package/dist/bedrock.cjs.map +1 -1
  29. package/dist/bedrock.d.cts.map +1 -1
  30. package/dist/bedrock.d.ts.map +1 -1
  31. package/dist/bedrock.js +51 -14
  32. package/dist/bedrock.js.map +1 -1
  33. package/dist/byteplus-video.cjs +954 -0
  34. package/dist/byteplus-video.cjs.map +1 -0
  35. package/dist/byteplus-video.d.cts +176 -0
  36. package/dist/byteplus-video.d.cts.map +1 -0
  37. package/dist/byteplus-video.d.ts +176 -0
  38. package/dist/byteplus-video.d.ts.map +1 -0
  39. package/dist/byteplus-video.js +947 -0
  40. package/dist/byteplus-video.js.map +1 -0
  41. package/dist/chaos.cjs +36 -7
  42. package/dist/chaos.cjs.map +1 -1
  43. package/dist/chaos.d.cts +8 -3
  44. package/dist/chaos.d.cts.map +1 -1
  45. package/dist/chaos.d.ts +8 -3
  46. package/dist/chaos.d.ts.map +1 -1
  47. package/dist/chaos.js +36 -8
  48. package/dist/chaos.js.map +1 -1
  49. package/dist/cli.cjs +5 -1
  50. package/dist/cli.cjs.map +1 -1
  51. package/dist/cli.js +5 -1
  52. package/dist/cli.js.map +1 -1
  53. package/dist/cohere.cjs +58 -5
  54. package/dist/cohere.cjs.map +1 -1
  55. package/dist/cohere.d.cts.map +1 -1
  56. package/dist/cohere.d.ts.map +1 -1
  57. package/dist/cohere.js +59 -6
  58. package/dist/cohere.js.map +1 -1
  59. package/dist/config-loader.d.cts.map +1 -1
  60. package/dist/elevenlabs-audio.cjs +36 -2
  61. package/dist/elevenlabs-audio.cjs.map +1 -1
  62. package/dist/elevenlabs-audio.d.cts.map +1 -1
  63. package/dist/elevenlabs-audio.d.ts.map +1 -1
  64. package/dist/elevenlabs-audio.js +37 -3
  65. package/dist/elevenlabs-audio.js.map +1 -1
  66. package/dist/embeddings.cjs +58 -3
  67. package/dist/embeddings.cjs.map +1 -1
  68. package/dist/embeddings.d.cts.map +1 -1
  69. package/dist/embeddings.d.ts.map +1 -1
  70. package/dist/embeddings.js +59 -4
  71. package/dist/embeddings.js.map +1 -1
  72. package/dist/fal-audio.cjs +38 -2
  73. package/dist/fal-audio.cjs.map +1 -1
  74. package/dist/fal-audio.d.cts.map +1 -1
  75. package/dist/fal-audio.d.ts.map +1 -1
  76. package/dist/fal-audio.js +39 -3
  77. package/dist/fal-audio.js.map +1 -1
  78. package/dist/fixture-loader.cjs +0 -0
  79. package/dist/fixture-loader.cjs.map +1 -1
  80. package/dist/fixture-loader.d.cts +9 -1
  81. package/dist/fixture-loader.d.cts.map +1 -1
  82. package/dist/fixture-loader.d.ts +9 -1
  83. package/dist/fixture-loader.d.ts.map +1 -1
  84. package/dist/fixture-loader.js +0 -0
  85. package/dist/fixture-loader.js.map +1 -1
  86. package/dist/gemini-embeddings.cjs +19 -1
  87. package/dist/gemini-embeddings.cjs.map +1 -1
  88. package/dist/gemini-embeddings.js +20 -2
  89. package/dist/gemini-embeddings.js.map +1 -1
  90. package/dist/gemini-interactions.cjs +15 -1
  91. package/dist/gemini-interactions.cjs.map +1 -1
  92. package/dist/gemini-interactions.d.cts.map +1 -1
  93. package/dist/gemini-interactions.d.ts.map +1 -1
  94. package/dist/gemini-interactions.js +16 -2
  95. package/dist/gemini-interactions.js.map +1 -1
  96. package/dist/gemini.cjs +70 -6
  97. package/dist/gemini.cjs.map +1 -1
  98. package/dist/gemini.d.cts.map +1 -1
  99. package/dist/gemini.d.ts.map +1 -1
  100. package/dist/gemini.js +71 -7
  101. package/dist/gemini.js.map +1 -1
  102. package/dist/grok-video.cjs +2 -2
  103. package/dist/grok-video.cjs.map +1 -1
  104. package/dist/grok-video.d.cts.map +1 -1
  105. package/dist/grok-video.d.ts.map +1 -1
  106. package/dist/grok-video.js +2 -2
  107. package/dist/grok-video.js.map +1 -1
  108. package/dist/helpers.cjs +160 -5
  109. package/dist/helpers.cjs.map +1 -1
  110. package/dist/helpers.d.cts.map +1 -1
  111. package/dist/helpers.d.ts.map +1 -1
  112. package/dist/helpers.js +152 -6
  113. package/dist/helpers.js.map +1 -1
  114. package/dist/images.cjs +24 -6
  115. package/dist/images.cjs.map +1 -1
  116. package/dist/images.d.cts +11 -3
  117. package/dist/images.d.cts.map +1 -1
  118. package/dist/images.d.ts +11 -3
  119. package/dist/images.d.ts.map +1 -1
  120. package/dist/images.js +25 -7
  121. package/dist/images.js.map +1 -1
  122. package/dist/index.cjs +7 -0
  123. package/dist/index.d.cts +2 -1
  124. package/dist/index.d.ts +2 -1
  125. package/dist/index.js +2 -1
  126. package/dist/llmock.cjs +15 -0
  127. package/dist/llmock.cjs.map +1 -1
  128. package/dist/llmock.d.cts +12 -0
  129. package/dist/llmock.d.cts.map +1 -1
  130. package/dist/llmock.d.ts +12 -0
  131. package/dist/llmock.d.ts.map +1 -1
  132. package/dist/llmock.js +16 -1
  133. package/dist/llmock.js.map +1 -1
  134. package/dist/mcp-mock.cjs +11 -0
  135. package/dist/mcp-mock.cjs.map +1 -1
  136. package/dist/mcp-mock.d.cts.map +1 -1
  137. package/dist/mcp-mock.d.ts.map +1 -1
  138. package/dist/mcp-mock.js +11 -0
  139. package/dist/mcp-mock.js.map +1 -1
  140. package/dist/messages.cjs +18 -1
  141. package/dist/messages.cjs.map +1 -1
  142. package/dist/messages.d.cts.map +1 -1
  143. package/dist/messages.d.ts.map +1 -1
  144. package/dist/messages.js +19 -2
  145. package/dist/messages.js.map +1 -1
  146. package/dist/metrics.cjs +20 -0
  147. package/dist/metrics.cjs.map +1 -1
  148. package/dist/metrics.d.cts.map +1 -1
  149. package/dist/metrics.d.ts.map +1 -1
  150. package/dist/metrics.js +19 -1
  151. package/dist/metrics.js.map +1 -1
  152. package/dist/moderation.cjs +67 -3
  153. package/dist/moderation.cjs.map +1 -1
  154. package/dist/moderation.d.cts +1 -0
  155. package/dist/moderation.d.cts.map +1 -1
  156. package/dist/moderation.d.ts +1 -0
  157. package/dist/moderation.d.ts.map +1 -1
  158. package/dist/moderation.js +68 -4
  159. package/dist/moderation.js.map +1 -1
  160. package/dist/ollama.cjs +75 -5
  161. package/dist/ollama.cjs.map +1 -1
  162. package/dist/ollama.d.cts.map +1 -1
  163. package/dist/ollama.d.ts.map +1 -1
  164. package/dist/ollama.js +76 -6
  165. package/dist/ollama.js.map +1 -1
  166. package/dist/openrouter-video.cjs +4 -4
  167. package/dist/openrouter-video.cjs.map +1 -1
  168. package/dist/openrouter-video.d.cts.map +1 -1
  169. package/dist/openrouter-video.d.ts.map +1 -1
  170. package/dist/openrouter-video.js +4 -4
  171. package/dist/openrouter-video.js.map +1 -1
  172. package/dist/provider-auth.cjs +2 -0
  173. package/dist/provider-auth.cjs.map +1 -1
  174. package/dist/provider-auth.js +2 -0
  175. package/dist/provider-auth.js.map +1 -1
  176. package/dist/rerank.cjs +65 -2
  177. package/dist/rerank.cjs.map +1 -1
  178. package/dist/rerank.d.cts +1 -1
  179. package/dist/rerank.d.cts.map +1 -1
  180. package/dist/rerank.d.ts +1 -1
  181. package/dist/rerank.d.ts.map +1 -1
  182. package/dist/rerank.js +66 -3
  183. package/dist/rerank.js.map +1 -1
  184. package/dist/responses.cjs +18 -1
  185. package/dist/responses.cjs.map +1 -1
  186. package/dist/responses.d.cts.map +1 -1
  187. package/dist/responses.d.ts.map +1 -1
  188. package/dist/responses.js +19 -2
  189. package/dist/responses.js.map +1 -1
  190. package/dist/router.cjs +1 -0
  191. package/dist/router.cjs.map +1 -1
  192. package/dist/router.d.cts.map +1 -1
  193. package/dist/router.d.ts.map +1 -1
  194. package/dist/router.js +1 -0
  195. package/dist/router.js.map +1 -1
  196. package/dist/search.cjs +67 -3
  197. package/dist/search.cjs.map +1 -1
  198. package/dist/search.d.cts +1 -0
  199. package/dist/search.d.cts.map +1 -1
  200. package/dist/search.d.ts +1 -0
  201. package/dist/search.d.ts.map +1 -1
  202. package/dist/search.js +68 -4
  203. package/dist/search.js.map +1 -1
  204. package/dist/server.cjs +406 -17
  205. package/dist/server.cjs.map +1 -1
  206. package/dist/server.d.cts +2 -0
  207. package/dist/server.d.cts.map +1 -1
  208. package/dist/server.d.ts +2 -0
  209. package/dist/server.d.ts.map +1 -1
  210. package/dist/server.js +410 -21
  211. package/dist/server.js.map +1 -1
  212. package/dist/speech.cjs +18 -1
  213. package/dist/speech.cjs.map +1 -1
  214. package/dist/speech.d.cts.map +1 -1
  215. package/dist/speech.d.ts.map +1 -1
  216. package/dist/speech.js +19 -2
  217. package/dist/speech.js.map +1 -1
  218. package/dist/stream-collapse.cjs +2 -1
  219. package/dist/stream-collapse.cjs.map +1 -1
  220. package/dist/stream-collapse.js +2 -1
  221. package/dist/stream-collapse.js.map +1 -1
  222. package/dist/transcription.cjs +21 -2
  223. package/dist/transcription.cjs.map +1 -1
  224. package/dist/transcription.d.cts.map +1 -1
  225. package/dist/transcription.d.ts.map +1 -1
  226. package/dist/transcription.js +21 -2
  227. package/dist/transcription.js.map +1 -1
  228. package/dist/types.d.cts +58 -5
  229. package/dist/types.d.cts.map +1 -1
  230. package/dist/types.d.ts +58 -5
  231. package/dist/types.d.ts.map +1 -1
  232. package/dist/vector-mock.cjs +88 -0
  233. package/dist/vector-mock.cjs.map +1 -1
  234. package/dist/vector-mock.d.cts.map +1 -1
  235. package/dist/vector-mock.d.ts.map +1 -1
  236. package/dist/vector-mock.js +89 -1
  237. package/dist/vector-mock.js.map +1 -1
  238. package/dist/vector-types.d.cts.map +1 -1
  239. package/dist/veo-video.cjs +2 -2
  240. package/dist/veo-video.cjs.map +1 -1
  241. package/dist/veo-video.d.cts.map +1 -1
  242. package/dist/veo-video.d.ts.map +1 -1
  243. package/dist/veo-video.js +2 -2
  244. package/dist/veo-video.js.map +1 -1
  245. package/dist/video.cjs +19 -2
  246. package/dist/video.cjs.map +1 -1
  247. package/dist/video.d.cts.map +1 -1
  248. package/dist/video.d.ts.map +1 -1
  249. package/dist/video.js +20 -3
  250. package/dist/video.js.map +1 -1
  251. package/package.json +2 -2
package/dist/server.d.cts CHANGED
@@ -7,6 +7,7 @@ import { VideoStateMap } from "./video.cjs";
7
7
  import { OpenRouterVideoJobMap } from "./openrouter-video.cjs";
8
8
  import { VeoVideoJobMap } from "./veo-video.cjs";
9
9
  import { GrokVideoJobMap } from "./grok-video.cjs";
10
+ import { BytePlusVideoJobMap } from "./byteplus-video.cjs";
10
11
  import * as http$1 from "node:http";
11
12
 
12
13
  //#region src/server.d.ts
@@ -19,6 +20,7 @@ interface ServerInstance {
19
20
  openRouterVideoJobs: OpenRouterVideoJobMap;
20
21
  veoVideoJobs: VeoVideoJobMap;
21
22
  grokVideoJobs: GrokVideoJobMap;
23
+ bytePlusVideoJobs: BytePlusVideoJobMap;
22
24
  }
23
25
  /**
24
26
  * The per-server state a full reset clears. `ServerInstance` structurally
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.cts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;UAyHiB,cAAA;EAAA,MAAA,EACP,MAAA,CAAK,MADgB;EAAA,OAAA,EAEpB,OAFoB;KACrB,EAAA,MAAK;UACJ,EAEC,eAFD;aAEC,EACG,aADH;qBACG,EACQ,qBADR;cACQ,EACP,cADO;eACP,EACC,eADD;;;AA2rChB;;;;;UAAiB,eAAA;UACP;UACA;cACI;;iBAMQ,YAAA,WACV,qBACA,4BACD;;WAA+B;sBACtB,kBACjB,QAAQ"}
1
+ {"version":3,"file":"server.d.cts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;UAgJiB,cAAA;EAAA,MAAA,EACP,MAAA,CAAK,MADgB;EAAA,OAAA,EAEpB,OAFoB;KACrB,EAAA,MAAK;UACJ,EAEC,eAFD;aAEC,EACG,aADH;qBACG,EACQ,qBADR;cACQ,EACP,cADO;eACP,EACC,eADD;mBACC,EACI,mBADJ;;;AAqkDjB;;;;;UAAiB,eAAA;UACP;UACA;cACI;;iBAMQ,YAAA,WACV,qBACA,4BACD;;WAA+B;sBACtB,kBACjB,QAAQ"}
package/dist/server.d.ts CHANGED
@@ -7,6 +7,7 @@ import { VideoStateMap } from "./video.js";
7
7
  import { OpenRouterVideoJobMap } from "./openrouter-video.js";
8
8
  import { VeoVideoJobMap } from "./veo-video.js";
9
9
  import { GrokVideoJobMap } from "./grok-video.js";
10
+ import { BytePlusVideoJobMap } from "./byteplus-video.js";
10
11
  import * as http$1 from "node:http";
11
12
 
12
13
  //#region src/server.d.ts
@@ -19,6 +20,7 @@ interface ServerInstance {
19
20
  openRouterVideoJobs: OpenRouterVideoJobMap;
20
21
  veoVideoJobs: VeoVideoJobMap;
21
22
  grokVideoJobs: GrokVideoJobMap;
23
+ bytePlusVideoJobs: BytePlusVideoJobMap;
22
24
  }
23
25
  /**
24
26
  * The per-server state a full reset clears. `ServerInstance` structurally
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;UAyHiB,cAAA;EAAA,MAAA,EACP,MAAA,CAAK,MADgB;EAAA,OAAA,EAEpB,OAFoB;KACrB,EAAA,MAAK;UACJ,EAEC,eAFD;aAEC,EACG,aADH;qBACG,EACQ,qBADR;cACQ,EACP,cADO;eACP,EACC,eADD;;;AA2rChB;;;;;UAAiB,eAAA;UACP;UACA;cACI;;iBAMQ,YAAA,WACV,qBACA,4BACD;;WAA+B;sBACtB,kBACjB,QAAQ"}
1
+ {"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;UAgJiB,cAAA;EAAA,MAAA,EACP,MAAA,CAAK,MADgB;EAAA,OAAA,EAEpB,OAFoB;KACrB,EAAA,MAAK;UACJ,EAEC,eAFD;aAEC,EACG,aADH;qBACG,EACQ,qBADR;cACQ,EACP,cADO;eACP,EACC,eADD;mBACC,EACI,mBADJ;;;AAqkDjB;;;;;UAAiB,eAAA;UACP;UACA;cACI;;iBAMQ,YAAA,WACV,qBACA,4BACD;;WAA+B;sBACtB,kBACjB,QAAQ"}
package/dist/server.js CHANGED
@@ -1,13 +1,14 @@
1
+ import { DEFAULT_TEST_ID } from "./constants.js";
1
2
  import { markAuthenticatedRequest, resolveInboundAuth, validateRequestApiKey, writeApiKeyHttpRejection, writeApiKeyUpgradeRejection } from "./api-key-auth.js";
2
- import { buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, extractOverrides, flattenHeaders, getContext, getTestId, isAudioResponse, isContentWithToolCallsResponse, isErrorResponse, isTextResponse, isToolCallResponse, readBody, resolveFixtureBlocks, resolveReasoningForModel, resolveResponse, resolveStrictMode, resolveUsage, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
3
+ import { buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, extractOverrides, flattenHeaders, getContext, getTestId, isAudioResponse, isContentWithToolCallsResponse, isEmbeddingResponse, isErrorResponse, isImageResponse, isJSONResponse, isJsonObject, isResponseFactory, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, readBody, resolveFixtureBlocks, resolveReasoningForModel, resolveResponse, resolveStrictMode, resolveUsage, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
3
4
  import { Logger } from "./logger.js";
4
5
  import { Journal } from "./journal.js";
5
6
  import { matchFixtureDiagnostic, recordMatchOptions } from "./router.js";
6
- import { entryToFixture, validateFixtures } from "./fixture-loader.js";
7
+ import { INJECTED_STATUS_RANGE, entryToFixture, isInjectableStatus, validateFixtures } from "./fixture-loader.js";
7
8
  import { writeErrorResponse, writeSSEStream } from "./sse-writer.js";
8
9
  import { createInterruptionSignal } from "./interruption.js";
9
10
  import { buildOpenRouterCandidates, handleOpenRouterCredits, handleOpenRouterKey, handleOpenRouterModels, isOpenRouterPath, resolveOpenRouterShaping, serializeOpenRouterError, shapeOpenRouterChunks, shapeOpenRouterCompletion } from "./openrouter-chat.js";
10
- import { applyChaosAction, evaluateChaos } from "./chaos.js";
11
+ import { applyChaosAction, evaluateChaos, isChaosScope } from "./chaos.js";
11
12
  import { proxyAndRecord } from "./recorder.js";
12
13
  import { handleResponses } from "./responses.js";
13
14
  import { handleMessages } from "./messages.js";
@@ -26,6 +27,7 @@ import { falQueueStates, handleFal } from "./fal.js";
26
27
  import { OPENROUTER_VIDEO_DEFAULT_MAX_CONTENT_BYTES, OpenRouterVideoJobMap, handleOpenRouterVideoContent, handleOpenRouterVideoCreate, handleOpenRouterVideoModels, handleOpenRouterVideoStatus } from "./openrouter-video.js";
27
28
  import { VeoVideoJobMap, handleVeoVideoCreate, handleVeoVideoStatus } from "./veo-video.js";
28
29
  import { GrokVideoJobMap, handleGrokVideoCreate, handleGrokVideoStatus } from "./grok-video.js";
30
+ import { BytePlusVideoJobMap, handleBytePlusVideoCreate, handleBytePlusVideoStatus } from "./byteplus-video.js";
29
31
  import { handleElevenLabsAudio, handleElevenLabsTTS } from "./elevenlabs-audio.js";
30
32
  import { handleOllama, handleOllamaEmbeddings, handleOllamaGenerate } from "./ollama.js";
31
33
  import { handleCohere, handleCohereEmbed } from "./cohere.js";
@@ -36,7 +38,7 @@ import { upgradeToWebSocket } from "./ws-framing.js";
36
38
  import { handleWebSocketResponses } from "./ws-responses.js";
37
39
  import { handleWebSocketRealtime } from "./ws-realtime.js";
38
40
  import { handleWebSocketGeminiLive } from "./ws-gemini-live.js";
39
- import { GROK_VIDEO_STATUS_RE, GROK_VIDEO_SUBMIT_PATH, OPENROUTER_VIDEO_CONTENT_RE, OPENROUTER_VIDEO_STATUS_RE, VEO_OPERATION_RE, VEO_PREDICT_LRO_RE, createMetricsRegistry, normalizePathLabel } from "./metrics.js";
41
+ import { BYTEPLUS_VIDEO_STATUS_RE, BYTEPLUS_VIDEO_SUBMIT_RE, GROK_VIDEO_STATUS_RE, GROK_VIDEO_SUBMIT_PATH, OPENROUTER_VIDEO_CONTENT_RE, OPENROUTER_VIDEO_STATUS_RE, VEO_OPERATION_RE, VEO_PREDICT_LRO_RE, createMetricsRegistry, normalizePathLabel } from "./metrics.js";
40
42
  import * as http$1 from "node:http";
41
43
 
42
44
  //#region src/server.ts
@@ -124,7 +126,8 @@ const DEFAULT_MODELS = [
124
126
  const CORS_HEADERS = {
125
127
  "Access-Control-Allow-Origin": "*",
126
128
  "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
127
- "Access-Control-Allow-Headers": "*"
129
+ "Access-Control-Allow-Headers": "*",
130
+ "Access-Control-Expose-Headers": "X-Total-Count"
128
131
  };
129
132
  function setCorsHeaders(res) {
130
133
  for (const [key, value] of Object.entries(CORS_HEADERS)) res.setHeader(key, value);
@@ -142,11 +145,24 @@ function handleNotFound(res, message) {
142
145
  } }));
143
146
  }
144
147
  const CONTROL_PREFIX = "/__aimock";
148
+ /** The complete `GET /__aimock/fixtures` query-param vocabulary; anything else 400s. */
149
+ const FIXTURES_PARAMS = new Set(["include"]);
150
+ /** The complete `GET /__aimock/journal` query-param vocabulary; anything else 400s. */
151
+ const JOURNAL_PARAMS = new Set([
152
+ "limit",
153
+ "offset",
154
+ "path",
155
+ "method",
156
+ "status",
157
+ "service",
158
+ "testId"
159
+ ]);
145
160
  /**
146
161
  * Perform a full reset: clear the fixtures array, the journal (entries *and*
147
162
  * per-test fixture match-counts, i.e. sequence position), the video and fal.ai
148
- * job/queue state, and the Gemini interaction/event-id counters, then re-zero
149
- * the `aimock_fixtures_loaded` gauge.
163
+ * job/queue state, the Gemini interaction/event-id counters, and any runtime
164
+ * chaos override (reverting to the construction-time chaos config), then
165
+ * re-zero the `aimock_fixtures_loaded` gauge.
150
166
  *
151
167
  * `targets` is `null` when no server is running (an in-process `reset()` before
152
168
  * `start()`). The process-global generation state is reset either way, since it
@@ -163,17 +179,112 @@ function performFullReset(fixtures, targets) {
163
179
  resetEventIdCounter();
164
180
  if (!targets) return;
165
181
  targets.journal.clear();
182
+ targets.defaults.chaos = void 0;
166
183
  targets.videoStates.clear();
167
184
  targets.openRouterVideoJobs.clear();
168
185
  targets.veoVideoJobs.clear();
169
186
  targets.grokVideoJobs.clear();
187
+ targets.bytePlusVideoJobs.clear();
170
188
  if (targets.defaults.registry) targets.defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
171
189
  }
172
190
  /**
191
+ * JSON-safe redaction for control-API inspection output. Functions (fixture
192
+ * `predicate`s, response factories) and RegExps do not survive
193
+ * JSON.stringify as anything useful, so they become marker strings.
194
+ */
195
+ function redactForInspection(value) {
196
+ if (typeof value === "function") return "[function]";
197
+ if (value instanceof RegExp) return String(value);
198
+ if (Array.isArray(value)) return value.map(redactForInspection);
199
+ if (value !== null && typeof value === "object") {
200
+ const out = {};
201
+ for (const [key, entry] of Object.entries(value)) out[key] = redactForInspection(entry);
202
+ return out;
203
+ }
204
+ return value;
205
+ }
206
+ /**
207
+ * One-line kind label for a fixture response used by
208
+ * `GET /__aimock/fixtures?include=fixtures` (e.g. "text", "error").
209
+ * Response factories are reported as "factory".
210
+ *
211
+ * Discriminates with the same `is*Response` guards the request handlers use,
212
+ * in the same order — NOT by inspecting keys. Every response shape extends
213
+ * `ResponseOverrides` (`id`/`created`/`model`/`usage`), so a recorded fixture's
214
+ * first key is usually an envelope override rather than the discriminant. The
215
+ * audio-before-content/toolCalls order is the ORDERING CONTRACT documented on
216
+ * `AudioResponse` in types.ts: those shapes structurally overlap.
217
+ */
218
+ function fixtureResponseKind(response) {
219
+ if (isResponseFactory(response)) return "factory";
220
+ if (isErrorResponse(response)) return "error";
221
+ if (isAudioResponse(response)) return "audio";
222
+ if (isVideoResponse(response)) return "video";
223
+ if (isTranscriptionResponse(response)) return "transcription";
224
+ if (isImageResponse(response)) return "image";
225
+ if (isEmbeddingResponse(response)) return "embedding";
226
+ if (isJSONResponse(response)) return "json";
227
+ if (isContentWithToolCallsResponse(response)) return "contentWithToolCalls";
228
+ if (isToolCallResponse(response)) return "toolCalls";
229
+ if (isTextResponse(response)) return "text";
230
+ return "unknown";
231
+ }
232
+ /**
233
+ * Resolve a journal entry's testId exactly the way `getTestId` resolves an
234
+ * incoming request's: the `X-Test-Id` header wins, then `?testId=` parsed out
235
+ * of the query string, then `DEFAULT_TEST_ID`. The
236
+ * filter and the server's own per-test isolation must never disagree — and a
237
+ * raw `path.includes("testId=t1")` both prefix-collides with `t10` and matches
238
+ * unrelated params like `notTestId`.
239
+ */
240
+ function journalEntryTestId(entry) {
241
+ const header = entry.headers["x-test-id"];
242
+ if (header) return header;
243
+ const qIdx = entry.path.indexOf("?");
244
+ if (qIdx !== -1) {
245
+ const queryValue = new URLSearchParams(entry.path.slice(qIdx + 1)).get("testId");
246
+ if (queryValue) return queryValue;
247
+ }
248
+ return DEFAULT_TEST_ID;
249
+ }
250
+ /**
251
+ * The testId a chaos override is stored under, and read back with — resolved by
252
+ * `getTestId`, i.e. `X-Test-Id` header then `?testId=`, exactly as
253
+ * `resolveScopedDefaults` resolves the traffic being evaluated. A control call
254
+ * and the requests it is meant to affect can then never land in different
255
+ * scopes. `DEFAULT_TEST_ID` (neither header nor param) means the server-wide
256
+ * baseline.
257
+ *
258
+ * Returns `null` when `X-Test-Id` is PRESENT but blank. `String(testId ?? "")`
259
+ * in a harness produces that trivially, and silently treating it as "untagged"
260
+ * would install a server-wide baseline that fails every other test — the exact
261
+ * cross-test leak per-testId scoping exists to prevent. Callers must 400.
262
+ */
263
+ function chaosScopeId(req) {
264
+ const headerValue = req.headers["x-test-id"];
265
+ const raw = Array.isArray(headerValue) ? headerValue[0] : headerValue;
266
+ if (typeof raw === "string" && raw.trim() === "") return null;
267
+ return getTestId(req);
268
+ }
269
+ /** 400 for a control call whose `X-Test-Id` is present but blank. */
270
+ function writeBlankTestId(res) {
271
+ res.writeHead(400, { "Content-Type": "application/json" });
272
+ res.end(JSON.stringify({ error: "Invalid 'X-Test-Id': header is present but empty" }));
273
+ return true;
274
+ }
275
+ /** The chaos config in effect for one testId: its override, else the baseline. */
276
+ function effectiveChaos(defaults, scopeId) {
277
+ const scoped = defaults.chaosByTestId?.get(scopeId);
278
+ if (scoped) return scoped;
279
+ const current = defaults.chaos;
280
+ if (!current) return {};
281
+ return isChaosScope(current) ? current.base ?? {} : current;
282
+ }
283
+ /**
173
284
  * Handle requests under `/__aimock/`. Returns `true` if the request was
174
285
  * handled, `false` if the path doesn't match the control prefix.
175
286
  */
176
- async function handleControlAPI(req, res, pathname, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, defaults) {
287
+ async function handleControlAPI(req, res, pathname, searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults) {
177
288
  if (!pathname.startsWith(CONTROL_PREFIX)) return false;
178
289
  const subPath = pathname.slice(9);
179
290
  setCorsHeaders(res);
@@ -183,13 +294,148 @@ async function handleControlAPI(req, res, pathname, fixtures, journal, videoStat
183
294
  return true;
184
295
  }
185
296
  if (subPath === "/journal" && req.method === "GET") {
186
- res.writeHead(200, { "Content-Type": "application/json" });
187
- res.end(JSON.stringify(journal.getAll()));
297
+ const bad = (message) => {
298
+ res.writeHead(400, { "Content-Type": "application/json" });
299
+ res.end(JSON.stringify({ error: message }));
300
+ return true;
301
+ };
302
+ for (const key of searchParams.keys()) if (!JOURNAL_PARAMS.has(key)) return bad(`Unknown query parameter: '${key}'. Supported: ${[...JOURNAL_PARAMS].join(", ")}`);
303
+ const parseNonNegativeInt = (name, fallback) => {
304
+ const raw = searchParams.get(name);
305
+ if (raw === null) return fallback;
306
+ if (!/^\d+$/.test(raw.trim())) return null;
307
+ return Number(raw);
308
+ };
309
+ const limit = parseNonNegativeInt("limit", -1);
310
+ const offset = parseNonNegativeInt("offset", 0);
311
+ if (limit === null) return bad("Invalid 'limit': must be an integer >= 0");
312
+ if (offset === null) return bad("Invalid 'offset': must be an integer >= 0");
313
+ let statusFilter;
314
+ const statusRaw = searchParams.get("status");
315
+ if (statusRaw !== null) {
316
+ if (!/^\d+$/.test(statusRaw.trim())) return bad("Invalid 'status': must be an integer");
317
+ statusFilter = Number(statusRaw);
318
+ }
319
+ const pathFilter = searchParams.get("path");
320
+ const methodFilter = searchParams.get("method");
321
+ const serviceFilter = searchParams.get("service");
322
+ const testIdFilter = searchParams.get("testId");
323
+ let entries = journal.getAll();
324
+ if (methodFilter !== null) {
325
+ const want = methodFilter.toUpperCase();
326
+ entries = entries.filter((e) => e.method.toUpperCase() === want);
327
+ }
328
+ if (pathFilter !== null) entries = entries.filter((e) => e.path.includes(pathFilter));
329
+ if (statusFilter !== void 0) entries = entries.filter((e) => e.response.status === statusFilter);
330
+ if (serviceFilter !== null) entries = entries.filter((e) => e.service === serviceFilter);
331
+ if (testIdFilter !== null) entries = entries.filter((e) => journalEntryTestId(e) === testIdFilter);
332
+ const total = entries.length;
333
+ if (offset > 0) entries = entries.slice(offset);
334
+ if (limit >= 0) entries = entries.slice(0, limit);
335
+ res.writeHead(200, {
336
+ "Content-Type": "application/json",
337
+ "X-Total-Count": String(total)
338
+ });
339
+ res.end(JSON.stringify(entries));
188
340
  return true;
189
341
  }
190
342
  if (subPath === "/fixtures" && req.method === "GET") {
343
+ for (const key of searchParams.keys()) if (!FIXTURES_PARAMS.has(key)) {
344
+ res.writeHead(400, { "Content-Type": "application/json" });
345
+ res.end(JSON.stringify({ error: `Unknown query parameter: '${key}'. Supported: ${[...FIXTURES_PARAMS].join(", ")}` }));
346
+ return true;
347
+ }
348
+ const include = searchParams.get("include");
349
+ if (include === null) {
350
+ res.writeHead(200, { "Content-Type": "application/json" });
351
+ res.end(JSON.stringify({ count: fixtures.length }));
352
+ return true;
353
+ }
354
+ if (include !== "fixtures") {
355
+ res.writeHead(400, { "Content-Type": "application/json" });
356
+ res.end(JSON.stringify({ error: "Invalid 'include': expected 'fixtures'" }));
357
+ return true;
358
+ }
359
+ res.writeHead(200, { "Content-Type": "application/json" });
360
+ res.end(JSON.stringify({
361
+ count: fixtures.length,
362
+ fixtures: fixtures.map((fixture, index) => ({
363
+ index,
364
+ match: redactForInspection(fixture.match),
365
+ responseKind: fixtureResponseKind(fixture.response),
366
+ ...fixture.latency !== void 0 ? { latency: fixture.latency } : {},
367
+ ...fixture.chaos !== void 0 ? { chaos: fixture.chaos } : {}
368
+ }))
369
+ }));
370
+ return true;
371
+ }
372
+ if (subPath === "/chaos" && req.method === "GET") {
373
+ const scopeId = chaosScopeId(req);
374
+ if (scopeId === null) return writeBlankTestId(res);
191
375
  res.writeHead(200, { "Content-Type": "application/json" });
192
- res.end(JSON.stringify({ count: fixtures.length }));
376
+ res.end(JSON.stringify({ chaos: effectiveChaos(defaults, scopeId) }));
377
+ return true;
378
+ }
379
+ if (subPath === "/chaos" && req.method === "DELETE") {
380
+ const scopeId = chaosScopeId(req);
381
+ if (scopeId === null) return writeBlankTestId(res);
382
+ defaults.chaosByTestId?.delete(scopeId);
383
+ res.writeHead(200, { "Content-Type": "application/json" });
384
+ res.end(JSON.stringify({ chaos: effectiveChaos(defaults, scopeId) }));
385
+ return true;
386
+ }
387
+ if (subPath === "/chaos" && req.method === "POST") {
388
+ const scopeId = chaosScopeId(req);
389
+ if (scopeId === null) return writeBlankTestId(res);
390
+ let raw;
391
+ try {
392
+ raw = await readBody(req);
393
+ } catch (err) {
394
+ const msg = err instanceof Error ? err.message : String(err);
395
+ defaults.logger.error(`POST /__aimock/chaos: failed to read body: ${msg}`);
396
+ res.writeHead(400, { "Content-Type": "application/json" });
397
+ res.end(JSON.stringify({ error: `Failed to read request body: ${msg}` }));
398
+ return true;
399
+ }
400
+ let parsed;
401
+ try {
402
+ parsed = JSON.parse(raw);
403
+ } catch (err) {
404
+ const msg = err instanceof Error ? err.message : String(err);
405
+ defaults.logger.error(`POST /__aimock/chaos: invalid JSON: ${msg}`);
406
+ res.writeHead(400, { "Content-Type": "application/json" });
407
+ res.end(JSON.stringify({ error: `Invalid JSON: ${msg}` }));
408
+ return true;
409
+ }
410
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
411
+ res.writeHead(400, { "Content-Type": "application/json" });
412
+ res.end(JSON.stringify({ error: "Invalid body: expected a JSON object" }));
413
+ return true;
414
+ }
415
+ const allowed = [
416
+ "dropRate",
417
+ "malformedRate",
418
+ "disconnectRate"
419
+ ];
420
+ for (const key of Object.keys(parsed)) if (!allowed.includes(key)) {
421
+ res.writeHead(400, { "Content-Type": "application/json" });
422
+ res.end(JSON.stringify({ error: `Unknown chaos field: '${key}'` }));
423
+ return true;
424
+ }
425
+ const next = {};
426
+ for (const key of allowed) {
427
+ const value = parsed[key];
428
+ if (value === void 0) continue;
429
+ if (typeof value !== "number" || Number.isNaN(value) || value < 0 || value > 1) {
430
+ res.writeHead(400, { "Content-Type": "application/json" });
431
+ res.end(JSON.stringify({ error: `Invalid '${key}': must be a number between 0 and 1` }));
432
+ return true;
433
+ }
434
+ next[key] = value;
435
+ }
436
+ defaults.chaosByTestId?.set(scopeId, next);
437
+ res.writeHead(200, { "Content-Type": "application/json" });
438
+ res.end(JSON.stringify({ chaos: effectiveChaos(defaults, scopeId) }));
193
439
  return true;
194
440
  }
195
441
  if (subPath === "/fixtures" && req.method === "POST") {
@@ -247,6 +493,7 @@ async function handleControlAPI(req, res, pathname, fixtures, journal, videoStat
247
493
  openRouterVideoJobs,
248
494
  veoVideoJobs,
249
495
  grokVideoJobs,
496
+ bytePlusVideoJobs,
250
497
  defaults
251
498
  });
252
499
  if (subPath === "/reset" && req.method === "POST") {
@@ -297,8 +544,37 @@ async function handleControlAPI(req, res, pathname, fixtures, journal, videoStat
297
544
  res.end(JSON.stringify({ error: `Invalid JSON: ${msg}` }));
298
545
  return true;
299
546
  }
547
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
548
+ res.writeHead(400, { "Content-Type": "application/json" });
549
+ res.end(JSON.stringify({ error: "Invalid body: expected a JSON object" }));
550
+ return true;
551
+ }
300
552
  const status = parsed.status ?? 500;
553
+ if (!isInjectableStatus(status)) {
554
+ res.writeHead(400, { "Content-Type": "application/json" });
555
+ res.end(JSON.stringify({ error: `Invalid 'status': must be ${INJECTED_STATUS_RANGE}` }));
556
+ return true;
557
+ }
301
558
  const errorBody = parsed.body;
559
+ if (errorBody !== void 0 && errorBody !== null) {
560
+ if (typeof errorBody !== "object" || Array.isArray(errorBody)) {
561
+ res.writeHead(400, { "Content-Type": "application/json" });
562
+ res.end(JSON.stringify({ error: "Invalid 'body': must be an object" }));
563
+ return true;
564
+ }
565
+ for (const field of [
566
+ "message",
567
+ "type",
568
+ "code"
569
+ ]) {
570
+ const value = errorBody[field];
571
+ if (value !== void 0 && value !== null && typeof value !== "string") {
572
+ res.writeHead(400, { "Content-Type": "application/json" });
573
+ res.end(JSON.stringify({ error: `Invalid 'body.${field}': must be a string` }));
574
+ return true;
575
+ }
576
+ }
577
+ }
302
578
  const errorFixture = {
303
579
  match: { predicate: () => true },
304
580
  response: {
@@ -358,7 +634,6 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
358
634
  let body;
359
635
  try {
360
636
  body = JSON.parse(raw);
361
- if (modelFallback && !body.model) body.model = modelFallback;
362
637
  } catch (parseErr) {
363
638
  const detail = parseErr instanceof Error ? parseErr.message : "unknown parse error";
364
639
  journal.add({
@@ -379,6 +654,24 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
379
654
  } }));
380
655
  return;
381
656
  }
657
+ if (!isJsonObject(body)) {
658
+ journal.add({
659
+ method: req.method ?? "POST",
660
+ path: req.url ?? COMPLETIONS_PATH,
661
+ headers: flattenHeaders(req.headers),
662
+ body: null,
663
+ response: {
664
+ status: 400,
665
+ fixture: null
666
+ }
667
+ });
668
+ writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, "Request body must be a JSON object") : JSON.stringify({ error: {
669
+ message: "Request body must be a JSON object",
670
+ type: "invalid_request_error"
671
+ } }));
672
+ return;
673
+ }
674
+ if (modelFallback && !body.model) body.model = modelFallback;
382
675
  if (!Array.isArray(body.messages)) {
383
676
  journal.add({
384
677
  method: req.method ?? "POST",
@@ -470,7 +763,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
470
763
  const snippet = typeof lastUserMsg?.content === "string" ? lastUserMsg.content.slice(0, 80) : "";
471
764
  defaults.logger.debug(`No fixture matched for request (model=${body.model ?? "?"}, msg="${snippet}")`);
472
765
  }
473
- const chaosAction = evaluateChaos(fixture, defaults.chaos, req.headers, defaults.logger);
766
+ const chaosAction = evaluateChaos(fixture, defaults.chaos, req.headers, defaults.logger, req.url);
474
767
  const chaosContext = {
475
768
  method,
476
769
  path,
@@ -767,13 +1060,27 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
767
1060
  const logger = new Logger(options?.logLevel ?? "silent");
768
1061
  const registry = options?.metrics ? createMetricsRegistry() : void 0;
769
1062
  const serverOptions = options ?? {};
1063
+ const chaosByTestId = /* @__PURE__ */ new Map();
770
1064
  const defaults = {
771
1065
  latency: serverOptions.latency ?? 0,
772
1066
  chunkSize: Math.max(1, serverOptions.chunkSize ?? DEFAULT_CHUNK_SIZE),
773
1067
  replaySpeed: serverOptions.replaySpeed ?? 1,
774
1068
  logger,
1069
+ chaosByTestId,
775
1070
  get chaos() {
776
- return serverOptions.chaos;
1071
+ return {
1072
+ base: chaosByTestId.get(DEFAULT_TEST_ID) ?? serverOptions.chaos,
1073
+ byTestId: chaosByTestId
1074
+ };
1075
+ },
1076
+ set chaos(value) {
1077
+ if (value === void 0) {
1078
+ chaosByTestId.clear();
1079
+ return;
1080
+ }
1081
+ const base = isChaosScope(value) ? value.base : value;
1082
+ if (base === void 0) chaosByTestId.delete(DEFAULT_TEST_ID);
1083
+ else chaosByTestId.set(DEFAULT_TEST_ID, base);
777
1084
  },
778
1085
  registry,
779
1086
  get record() {
@@ -796,6 +1103,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
796
1103
  },
797
1104
  get grokVideo() {
798
1105
  return serverOptions.grokVideo;
1106
+ },
1107
+ get bytePlusVideo() {
1108
+ return serverOptions.bytePlusVideo;
799
1109
  }
800
1110
  };
801
1111
  if (options?.chaos) {
@@ -831,6 +1141,10 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
831
1141
  {
832
1142
  name: "grokVideo",
833
1143
  config: options?.grokVideo
1144
+ },
1145
+ {
1146
+ name: "bytePlusVideo",
1147
+ config: options?.bytePlusVideo
834
1148
  }
835
1149
  ]) {
836
1150
  if (!config) continue;
@@ -845,6 +1159,16 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
845
1159
  const cap = options?.record?.openRouterVideo?.maxContentBytes;
846
1160
  if (cap !== void 0 && (!Number.isInteger(cap) || cap < 0)) logger.warn(`record.openRouterVideo.maxContentBytes (${cap}) is not a non-negative integer — using the default cap (${OPENROUTER_VIDEO_DEFAULT_MAX_CONTENT_BYTES})`);
847
1161
  }
1162
+ {
1163
+ const bytePlusBase = options?.record?.providers?.byteplus;
1164
+ if (bytePlusBase !== void 0) {
1165
+ let basePath;
1166
+ try {
1167
+ basePath = new URL(bytePlusBase).pathname;
1168
+ } catch {}
1169
+ if (basePath !== void 0 && /(^|\/)api\/v3\/?$/.test(basePath)) throw new Error(`record.providers.byteplus (${bytePlusBase}) ends in /api/v3, but aimock appends the /api/v3 prefix itself — every BytePlus request would go to /api/v3/api/v3/... and fail upstream. Configure the ORIGIN only (e.g. https://ark.ap-southeast.bytepluses.com).`);
1170
+ }
1171
+ }
848
1172
  const journal = new Journal({
849
1173
  maxEntries: options?.journalMaxEntries ?? 1e3,
850
1174
  fixtureCountsMaxTestIds: options?.fixtureCountsMaxTestIds ?? 500
@@ -853,6 +1177,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
853
1177
  const openRouterVideoJobs = new OpenRouterVideoJobMap();
854
1178
  const veoVideoJobs = new VeoVideoJobMap();
855
1179
  const grokVideoJobs = new GrokVideoJobMap();
1180
+ const bytePlusVideoJobs = new BytePlusVideoJobMap();
856
1181
  if (mounts) for (const { handler } of mounts) {
857
1182
  if (handler.setJournal) handler.setJournal(journal);
858
1183
  if (registry && handler.setRegistry) handler.setRegistry(registry);
@@ -878,7 +1203,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
878
1203
  const startTime = registry ? process.hrtime.bigint() : 0n;
879
1204
  const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
880
1205
  let pathname = parsedUrl.pathname;
881
- const isOpenRouter = isOpenRouterPath(pathname);
1206
+ const originalPathname = pathname;
1207
+ const isOpenRouter = isOpenRouterPath(originalPathname);
1208
+ const isBytePlusArk = defaults.record?.providers.byteplus !== void 0 && originalPathname.startsWith("/api/v3/");
882
1209
  if (registry) res.on("finish", () => {
883
1210
  try {
884
1211
  const normalizedPath = normalizePathLabel(pathname);
@@ -910,7 +1237,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
910
1237
  markAuthenticatedRequest(req, resolvedAuth.policy);
911
1238
  }
912
1239
  if (pathname.startsWith(CONTROL_PREFIX)) {
913
- await handleControlAPI(req, res, pathname, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, defaults);
1240
+ await handleControlAPI(req, res, pathname, parsedUrl.searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults);
914
1241
  return;
915
1242
  }
916
1243
  if (mounts) {
@@ -1093,6 +1420,66 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1093
1420
  }
1094
1421
  return;
1095
1422
  }
1423
+ const bytePlusVideoStatusMatch = pathname.match(BYTEPLUS_VIDEO_STATUS_RE);
1424
+ if (bytePlusVideoStatusMatch && req.method === "GET") {
1425
+ try {
1426
+ await handleBytePlusVideoStatus(req, res, bytePlusVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
1427
+ } catch (err) {
1428
+ const msg = err instanceof Error ? err.message : "Internal error";
1429
+ defaults.logger.error(`byteplus-video status: ${msg}`);
1430
+ if (!res.headersSent) {
1431
+ try {
1432
+ journal.add({
1433
+ method: req.method ?? "GET",
1434
+ path: req.url ?? pathname,
1435
+ headers: flattenHeaders(req.headers),
1436
+ body: null,
1437
+ response: {
1438
+ status: 500,
1439
+ fixture: null
1440
+ }
1441
+ });
1442
+ } catch (jErr) {
1443
+ defaults.logger.warn(`byteplus-video status: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1444
+ }
1445
+ writeErrorResponse(res, 500, JSON.stringify({ error: {
1446
+ message: msg,
1447
+ type: "server_error"
1448
+ } }));
1449
+ } else if (!res.writableEnded) res.destroy();
1450
+ }
1451
+ return;
1452
+ }
1453
+ if (BYTEPLUS_VIDEO_SUBMIT_RE.test(pathname) && req.method === "POST") {
1454
+ setCorsHeaders(res);
1455
+ try {
1456
+ await handleBytePlusVideoCreate(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
1457
+ } catch (err) {
1458
+ const msg = err instanceof Error ? err.message : "Internal error";
1459
+ defaults.logger.error(`byteplus-video submit: ${msg}`);
1460
+ if (!res.headersSent) {
1461
+ try {
1462
+ journal.add({
1463
+ method: req.method ?? "POST",
1464
+ path: req.url ?? pathname,
1465
+ headers: flattenHeaders(req.headers),
1466
+ body: null,
1467
+ response: {
1468
+ status: 500,
1469
+ fixture: null
1470
+ }
1471
+ });
1472
+ } catch (jErr) {
1473
+ defaults.logger.warn(`byteplus-video submit: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1474
+ }
1475
+ writeErrorResponse(res, 500, JSON.stringify({ error: {
1476
+ message: msg,
1477
+ type: "server_error"
1478
+ } }));
1479
+ } else if (!res.writableEnded) res.destroy();
1480
+ }
1481
+ return;
1482
+ }
1096
1483
  if (pathname === "/api/v1/models" && req.method === "GET") {
1097
1484
  handleOpenRouterModels(req, res, fixtures, journal, defaults, setCorsHeaders);
1098
1485
  return;
@@ -1291,7 +1678,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1291
1678
  }
1292
1679
  if (pathname === IMAGES_PATH && req.method === "POST") {
1293
1680
  try {
1294
- await handleImages(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
1681
+ await handleImages(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, "openai", void 0, isBytePlusArk ? "byteplus" : void 0);
1295
1682
  } catch (err) {
1296
1683
  const msg = err instanceof Error ? err.message : "Internal error";
1297
1684
  if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
@@ -1742,7 +2129,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1742
2129
  try {
1743
2130
  falBody = req.method === "POST" || req.method === "PUT" ? await readBody(req) : "";
1744
2131
  const raw = falBody;
1745
- const chaosAction = evaluateChaos(null, defaults.chaos, req.headers, defaults.logger);
2132
+ const chaosAction = evaluateChaos(null, defaults.chaos, req.headers, defaults.logger, req.url);
1746
2133
  if (chaosAction) {
1747
2134
  applyChaosAction(chaosAction, res, null, journal, {
1748
2135
  method: req.method ?? "GET",
@@ -1784,7 +2171,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1784
2171
  setCorsHeaders(res);
1785
2172
  try {
1786
2173
  const raw = req.method === "POST" || req.method === "PUT" ? falBody ?? await readBody(req) : "{}";
1787
- const chaosAction = evaluateChaos(null, defaults.chaos, req.headers, defaults.logger);
2174
+ const chaosAction = evaluateChaos(null, defaults.chaos, req.headers, defaults.logger, req.url);
1788
2175
  if (chaosAction) {
1789
2176
  applyChaosAction(chaosAction, res, null, journal, {
1790
2177
  method: req.method ?? "GET",
@@ -1830,7 +2217,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1830
2217
  handleNotFound(res, "Not found");
1831
2218
  return;
1832
2219
  }
1833
- const completionsProvider = azureDeploymentId ? "azure" : isOpenRouter ? "openrouter" : "openai";
2220
+ const completionsProvider = azureDeploymentId ? "azure" : isOpenRouter ? "openrouter" : isBytePlusArk ? "byteplus" : "openai";
1834
2221
  try {
1835
2222
  await handleCompletions(req, res, fixtures, journal, defaults, azureDeploymentId, completionsProvider, isOpenRouter);
1836
2223
  } catch (err) {
@@ -1968,6 +2355,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1968
2355
  openRouterVideoJobs.clear();
1969
2356
  veoVideoJobs.clear();
1970
2357
  grokVideoJobs.clear();
2358
+ bytePlusVideoJobs.clear();
1971
2359
  originalClose(callback);
1972
2360
  return this;
1973
2361
  };
@@ -1991,7 +2379,8 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1991
2379
  videoStates,
1992
2380
  openRouterVideoJobs,
1993
2381
  veoVideoJobs,
1994
- grokVideoJobs
2382
+ grokVideoJobs,
2383
+ bytePlusVideoJobs
1995
2384
  });
1996
2385
  });
1997
2386
  });