@copilotkit/aimock 1.7.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 (368) hide show
  1. package/.claude-plugin/marketplace.json +17 -0
  2. package/.claude-plugin/plugin.json +12 -0
  3. package/LICENSE +21 -0
  4. package/README.md +82 -0
  5. package/dist/_virtual/_rolldown/runtime.cjs +29 -0
  6. package/dist/a2a-handler.cjs +203 -0
  7. package/dist/a2a-handler.cjs.map +1 -0
  8. package/dist/a2a-handler.js +199 -0
  9. package/dist/a2a-handler.js.map +1 -0
  10. package/dist/a2a-mock.cjs +292 -0
  11. package/dist/a2a-mock.cjs.map +1 -0
  12. package/dist/a2a-mock.d.cts +41 -0
  13. package/dist/a2a-mock.d.cts.map +1 -0
  14. package/dist/a2a-mock.d.ts +41 -0
  15. package/dist/a2a-mock.d.ts.map +1 -0
  16. package/dist/a2a-mock.js +290 -0
  17. package/dist/a2a-mock.js.map +1 -0
  18. package/dist/a2a-stub.cjs +4 -0
  19. package/dist/a2a-stub.d.cts +3 -0
  20. package/dist/a2a-stub.d.ts +3 -0
  21. package/dist/a2a-stub.js +3 -0
  22. package/dist/a2a-types.d.cts +68 -0
  23. package/dist/a2a-types.d.cts.map +1 -0
  24. package/dist/a2a-types.d.ts +68 -0
  25. package/dist/a2a-types.d.ts.map +1 -0
  26. package/dist/aimock-cli.cjs +112 -0
  27. package/dist/aimock-cli.cjs.map +1 -0
  28. package/dist/aimock-cli.d.cts +19 -0
  29. package/dist/aimock-cli.d.cts.map +1 -0
  30. package/dist/aimock-cli.d.ts +19 -0
  31. package/dist/aimock-cli.d.ts.map +1 -0
  32. package/dist/aimock-cli.js +110 -0
  33. package/dist/aimock-cli.js.map +1 -0
  34. package/dist/aws-event-stream.cjs +117 -0
  35. package/dist/aws-event-stream.cjs.map +1 -0
  36. package/dist/aws-event-stream.d.cts +38 -0
  37. package/dist/aws-event-stream.d.cts.map +1 -0
  38. package/dist/aws-event-stream.d.ts +38 -0
  39. package/dist/aws-event-stream.d.ts.map +1 -0
  40. package/dist/aws-event-stream.js +114 -0
  41. package/dist/aws-event-stream.js.map +1 -0
  42. package/dist/bedrock-converse.cjs +445 -0
  43. package/dist/bedrock-converse.cjs.map +1 -0
  44. package/dist/bedrock-converse.d.cts +50 -0
  45. package/dist/bedrock-converse.d.cts.map +1 -0
  46. package/dist/bedrock-converse.d.ts +50 -0
  47. package/dist/bedrock-converse.d.ts.map +1 -0
  48. package/dist/bedrock-converse.js +443 -0
  49. package/dist/bedrock-converse.js.map +1 -0
  50. package/dist/bedrock.cjs +557 -0
  51. package/dist/bedrock.cjs.map +1 -0
  52. package/dist/bedrock.d.cts +41 -0
  53. package/dist/bedrock.d.cts.map +1 -0
  54. package/dist/bedrock.d.ts +41 -0
  55. package/dist/bedrock.d.ts.map +1 -0
  56. package/dist/bedrock.js +553 -0
  57. package/dist/bedrock.js.map +1 -0
  58. package/dist/chaos.cjs +114 -0
  59. package/dist/chaos.cjs.map +1 -0
  60. package/dist/chaos.d.cts +27 -0
  61. package/dist/chaos.d.cts.map +1 -0
  62. package/dist/chaos.d.ts +27 -0
  63. package/dist/chaos.d.ts.map +1 -0
  64. package/dist/chaos.js +113 -0
  65. package/dist/chaos.js.map +1 -0
  66. package/dist/cli.cjs +268 -0
  67. package/dist/cli.cjs.map +1 -0
  68. package/dist/cli.d.cts +1 -0
  69. package/dist/cli.d.ts +1 -0
  70. package/dist/cli.js +268 -0
  71. package/dist/cli.js.map +1 -0
  72. package/dist/cohere.cjs +434 -0
  73. package/dist/cohere.cjs.map +1 -0
  74. package/dist/cohere.d.cts +34 -0
  75. package/dist/cohere.d.cts.map +1 -0
  76. package/dist/cohere.d.ts +34 -0
  77. package/dist/cohere.d.ts.map +1 -0
  78. package/dist/cohere.js +433 -0
  79. package/dist/cohere.js.map +1 -0
  80. package/dist/config-loader.cjs +111 -0
  81. package/dist/config-loader.cjs.map +1 -0
  82. package/dist/config-loader.d.cts +100 -0
  83. package/dist/config-loader.d.cts.map +1 -0
  84. package/dist/config-loader.d.ts +100 -0
  85. package/dist/config-loader.d.ts.map +1 -0
  86. package/dist/config-loader.js +107 -0
  87. package/dist/config-loader.js.map +1 -0
  88. package/dist/embeddings.cjs +150 -0
  89. package/dist/embeddings.cjs.map +1 -0
  90. package/dist/embeddings.d.cts +12 -0
  91. package/dist/embeddings.d.cts.map +1 -0
  92. package/dist/embeddings.d.ts +12 -0
  93. package/dist/embeddings.d.ts.map +1 -0
  94. package/dist/embeddings.js +150 -0
  95. package/dist/embeddings.js.map +1 -0
  96. package/dist/fixture-loader.cjs +269 -0
  97. package/dist/fixture-loader.cjs.map +1 -0
  98. package/dist/fixture-loader.d.cts +17 -0
  99. package/dist/fixture-loader.d.cts.map +1 -0
  100. package/dist/fixture-loader.d.ts +17 -0
  101. package/dist/fixture-loader.d.ts.map +1 -0
  102. package/dist/fixture-loader.js +265 -0
  103. package/dist/fixture-loader.js.map +1 -0
  104. package/dist/gemini.cjs +403 -0
  105. package/dist/gemini.cjs.map +1 -0
  106. package/dist/gemini.d.cts +10 -0
  107. package/dist/gemini.d.cts.map +1 -0
  108. package/dist/gemini.d.ts +10 -0
  109. package/dist/gemini.d.ts.map +1 -0
  110. package/dist/gemini.js +403 -0
  111. package/dist/gemini.js.map +1 -0
  112. package/dist/helpers.cjs +276 -0
  113. package/dist/helpers.cjs.map +1 -0
  114. package/dist/helpers.d.cts +39 -0
  115. package/dist/helpers.d.cts.map +1 -0
  116. package/dist/helpers.d.ts +39 -0
  117. package/dist/helpers.d.ts.map +1 -0
  118. package/dist/helpers.js +259 -0
  119. package/dist/helpers.js.map +1 -0
  120. package/dist/index.cjs +113 -0
  121. package/dist/index.d.cts +42 -0
  122. package/dist/index.d.ts +42 -0
  123. package/dist/index.js +39 -0
  124. package/dist/interruption.cjs +40 -0
  125. package/dist/interruption.cjs.map +1 -0
  126. package/dist/interruption.d.cts +15 -0
  127. package/dist/interruption.d.cts.map +1 -0
  128. package/dist/interruption.d.ts +15 -0
  129. package/dist/interruption.d.ts.map +1 -0
  130. package/dist/interruption.js +39 -0
  131. package/dist/interruption.js.map +1 -0
  132. package/dist/journal.cjs +65 -0
  133. package/dist/journal.cjs.map +1 -0
  134. package/dist/journal.d.cts +23 -0
  135. package/dist/journal.d.cts.map +1 -0
  136. package/dist/journal.d.ts +23 -0
  137. package/dist/journal.d.ts.map +1 -0
  138. package/dist/journal.js +65 -0
  139. package/dist/journal.js.map +1 -0
  140. package/dist/jsonrpc.cjs +91 -0
  141. package/dist/jsonrpc.cjs.map +1 -0
  142. package/dist/jsonrpc.d.cts +24 -0
  143. package/dist/jsonrpc.d.cts.map +1 -0
  144. package/dist/jsonrpc.d.ts +24 -0
  145. package/dist/jsonrpc.d.ts.map +1 -0
  146. package/dist/jsonrpc.js +90 -0
  147. package/dist/jsonrpc.js.map +1 -0
  148. package/dist/llmock.cjs +223 -0
  149. package/dist/llmock.cjs.map +1 -0
  150. package/dist/llmock.d.cts +70 -0
  151. package/dist/llmock.d.cts.map +1 -0
  152. package/dist/llmock.d.ts +70 -0
  153. package/dist/llmock.d.ts.map +1 -0
  154. package/dist/llmock.js +223 -0
  155. package/dist/llmock.js.map +1 -0
  156. package/dist/logger.cjs +29 -0
  157. package/dist/logger.cjs.map +1 -0
  158. package/dist/logger.d.cts +14 -0
  159. package/dist/logger.d.cts.map +1 -0
  160. package/dist/logger.d.ts +14 -0
  161. package/dist/logger.d.ts.map +1 -0
  162. package/dist/logger.js +28 -0
  163. package/dist/logger.js.map +1 -0
  164. package/dist/mcp-handler.cjs +189 -0
  165. package/dist/mcp-handler.cjs.map +1 -0
  166. package/dist/mcp-handler.js +188 -0
  167. package/dist/mcp-handler.js.map +1 -0
  168. package/dist/mcp-mock.cjs +169 -0
  169. package/dist/mcp-mock.cjs.map +1 -0
  170. package/dist/mcp-mock.d.cts +40 -0
  171. package/dist/mcp-mock.d.cts.map +1 -0
  172. package/dist/mcp-mock.d.ts +40 -0
  173. package/dist/mcp-mock.d.ts.map +1 -0
  174. package/dist/mcp-mock.js +167 -0
  175. package/dist/mcp-mock.js.map +1 -0
  176. package/dist/mcp-stub.cjs +4 -0
  177. package/dist/mcp-stub.d.cts +3 -0
  178. package/dist/mcp-stub.d.ts +3 -0
  179. package/dist/mcp-stub.js +3 -0
  180. package/dist/mcp-types.d.cts +65 -0
  181. package/dist/mcp-types.d.cts.map +1 -0
  182. package/dist/mcp-types.d.ts +65 -0
  183. package/dist/mcp-types.d.ts.map +1 -0
  184. package/dist/messages.cjs +489 -0
  185. package/dist/messages.cjs.map +1 -0
  186. package/dist/messages.d.cts +10 -0
  187. package/dist/messages.d.cts.map +1 -0
  188. package/dist/messages.d.ts +10 -0
  189. package/dist/messages.d.ts.map +1 -0
  190. package/dist/messages.js +489 -0
  191. package/dist/messages.js.map +1 -0
  192. package/dist/metrics.cjs +160 -0
  193. package/dist/metrics.cjs.map +1 -0
  194. package/dist/metrics.d.cts +24 -0
  195. package/dist/metrics.d.cts.map +1 -0
  196. package/dist/metrics.d.ts +24 -0
  197. package/dist/metrics.d.ts.map +1 -0
  198. package/dist/metrics.js +158 -0
  199. package/dist/metrics.js.map +1 -0
  200. package/dist/moderation.cjs +91 -0
  201. package/dist/moderation.cjs.map +1 -0
  202. package/dist/moderation.d.cts +23 -0
  203. package/dist/moderation.d.cts.map +1 -0
  204. package/dist/moderation.d.ts +23 -0
  205. package/dist/moderation.d.ts.map +1 -0
  206. package/dist/moderation.js +91 -0
  207. package/dist/moderation.js.map +1 -0
  208. package/dist/ndjson-writer.cjs +31 -0
  209. package/dist/ndjson-writer.cjs.map +1 -0
  210. package/dist/ndjson-writer.d.cts +17 -0
  211. package/dist/ndjson-writer.d.cts.map +1 -0
  212. package/dist/ndjson-writer.d.ts +17 -0
  213. package/dist/ndjson-writer.d.ts.map +1 -0
  214. package/dist/ndjson-writer.js +31 -0
  215. package/dist/ndjson-writer.js.map +1 -0
  216. package/dist/ollama.cjs +519 -0
  217. package/dist/ollama.cjs.map +1 -0
  218. package/dist/ollama.d.cts +34 -0
  219. package/dist/ollama.d.cts.map +1 -0
  220. package/dist/ollama.d.ts +34 -0
  221. package/dist/ollama.d.ts.map +1 -0
  222. package/dist/ollama.js +517 -0
  223. package/dist/ollama.js.map +1 -0
  224. package/dist/recorder.cjs +311 -0
  225. package/dist/recorder.cjs.map +1 -0
  226. package/dist/recorder.d.cts +23 -0
  227. package/dist/recorder.d.cts.map +1 -0
  228. package/dist/recorder.d.ts +23 -0
  229. package/dist/recorder.d.ts.map +1 -0
  230. package/dist/recorder.js +305 -0
  231. package/dist/recorder.js.map +1 -0
  232. package/dist/rerank.cjs +71 -0
  233. package/dist/rerank.cjs.map +1 -0
  234. package/dist/rerank.d.cts +22 -0
  235. package/dist/rerank.d.cts.map +1 -0
  236. package/dist/rerank.d.ts +22 -0
  237. package/dist/rerank.d.ts.map +1 -0
  238. package/dist/rerank.js +71 -0
  239. package/dist/rerank.js.map +1 -0
  240. package/dist/responses.cjs +637 -0
  241. package/dist/responses.cjs.map +1 -0
  242. package/dist/responses.d.cts +16 -0
  243. package/dist/responses.d.cts.map +1 -0
  244. package/dist/responses.d.ts +16 -0
  245. package/dist/responses.d.ts.map +1 -0
  246. package/dist/responses.js +634 -0
  247. package/dist/responses.js.map +1 -0
  248. package/dist/router.cjs +68 -0
  249. package/dist/router.cjs.map +1 -0
  250. package/dist/router.d.cts +16 -0
  251. package/dist/router.d.cts.map +1 -0
  252. package/dist/router.d.ts +16 -0
  253. package/dist/router.d.ts.map +1 -0
  254. package/dist/router.js +65 -0
  255. package/dist/router.js.map +1 -0
  256. package/dist/search.cjs +59 -0
  257. package/dist/search.cjs.map +1 -0
  258. package/dist/search.d.cts +23 -0
  259. package/dist/search.d.cts.map +1 -0
  260. package/dist/search.d.ts +23 -0
  261. package/dist/search.d.ts.map +1 -0
  262. package/dist/search.js +59 -0
  263. package/dist/search.js.map +1 -0
  264. package/dist/server.cjs +935 -0
  265. package/dist/server.cjs.map +1 -0
  266. package/dist/server.d.cts +28 -0
  267. package/dist/server.d.cts.map +1 -0
  268. package/dist/server.d.ts +28 -0
  269. package/dist/server.d.ts.map +1 -0
  270. package/dist/server.js +933 -0
  271. package/dist/server.js.map +1 -0
  272. package/dist/sse-writer.cjs +59 -0
  273. package/dist/sse-writer.cjs.map +1 -0
  274. package/dist/sse-writer.d.cts +19 -0
  275. package/dist/sse-writer.d.cts.map +1 -0
  276. package/dist/sse-writer.d.ts +19 -0
  277. package/dist/sse-writer.d.ts.map +1 -0
  278. package/dist/sse-writer.js +55 -0
  279. package/dist/sse-writer.js.map +1 -0
  280. package/dist/stream-collapse.cjs +496 -0
  281. package/dist/stream-collapse.cjs.map +1 -0
  282. package/dist/stream-collapse.d.cts +70 -0
  283. package/dist/stream-collapse.d.cts.map +1 -0
  284. package/dist/stream-collapse.d.ts +70 -0
  285. package/dist/stream-collapse.d.ts.map +1 -0
  286. package/dist/stream-collapse.js +489 -0
  287. package/dist/stream-collapse.js.map +1 -0
  288. package/dist/suite.cjs +46 -0
  289. package/dist/suite.cjs.map +1 -0
  290. package/dist/suite.d.cts +31 -0
  291. package/dist/suite.d.cts.map +1 -0
  292. package/dist/suite.d.ts +31 -0
  293. package/dist/suite.d.ts.map +1 -0
  294. package/dist/suite.js +46 -0
  295. package/dist/suite.js.map +1 -0
  296. package/dist/types.d.cts +243 -0
  297. package/dist/types.d.cts.map +1 -0
  298. package/dist/types.d.ts +243 -0
  299. package/dist/types.d.ts.map +1 -0
  300. package/dist/url.cjs +21 -0
  301. package/dist/url.cjs.map +1 -0
  302. package/dist/url.d.cts +16 -0
  303. package/dist/url.d.cts.map +1 -0
  304. package/dist/url.d.ts +16 -0
  305. package/dist/url.d.ts.map +1 -0
  306. package/dist/url.js +20 -0
  307. package/dist/url.js.map +1 -0
  308. package/dist/vector-handler.cjs +239 -0
  309. package/dist/vector-handler.cjs.map +1 -0
  310. package/dist/vector-handler.js +238 -0
  311. package/dist/vector-handler.js.map +1 -0
  312. package/dist/vector-mock.cjs +229 -0
  313. package/dist/vector-mock.cjs.map +1 -0
  314. package/dist/vector-mock.d.cts +39 -0
  315. package/dist/vector-mock.d.cts.map +1 -0
  316. package/dist/vector-mock.d.ts +39 -0
  317. package/dist/vector-mock.d.ts.map +1 -0
  318. package/dist/vector-mock.js +227 -0
  319. package/dist/vector-mock.js.map +1 -0
  320. package/dist/vector-stub.cjs +4 -0
  321. package/dist/vector-stub.d.cts +3 -0
  322. package/dist/vector-stub.d.ts +3 -0
  323. package/dist/vector-stub.js +3 -0
  324. package/dist/vector-types.d.cts +32 -0
  325. package/dist/vector-types.d.cts.map +1 -0
  326. package/dist/vector-types.d.ts +32 -0
  327. package/dist/vector-types.d.ts.map +1 -0
  328. package/dist/watcher.cjs +59 -0
  329. package/dist/watcher.cjs.map +1 -0
  330. package/dist/watcher.js +58 -0
  331. package/dist/watcher.js.map +1 -0
  332. package/dist/ws-framing.cjs +187 -0
  333. package/dist/ws-framing.cjs.map +1 -0
  334. package/dist/ws-framing.d.cts +26 -0
  335. package/dist/ws-framing.d.cts.map +1 -0
  336. package/dist/ws-framing.d.ts +26 -0
  337. package/dist/ws-framing.d.ts.map +1 -0
  338. package/dist/ws-framing.js +184 -0
  339. package/dist/ws-framing.js.map +1 -0
  340. package/dist/ws-gemini-live.cjs +364 -0
  341. package/dist/ws-gemini-live.cjs.map +1 -0
  342. package/dist/ws-gemini-live.d.cts +18 -0
  343. package/dist/ws-gemini-live.d.cts.map +1 -0
  344. package/dist/ws-gemini-live.d.ts +18 -0
  345. package/dist/ws-gemini-live.d.ts.map +1 -0
  346. package/dist/ws-gemini-live.js +364 -0
  347. package/dist/ws-gemini-live.js.map +1 -0
  348. package/dist/ws-realtime.cjs +435 -0
  349. package/dist/ws-realtime.cjs.map +1 -0
  350. package/dist/ws-realtime.d.cts +17 -0
  351. package/dist/ws-realtime.d.cts.map +1 -0
  352. package/dist/ws-realtime.d.ts +17 -0
  353. package/dist/ws-realtime.d.ts.map +1 -0
  354. package/dist/ws-realtime.js +435 -0
  355. package/dist/ws-realtime.js.map +1 -0
  356. package/dist/ws-responses.cjs +164 -0
  357. package/dist/ws-responses.cjs.map +1 -0
  358. package/dist/ws-responses.d.cts +18 -0
  359. package/dist/ws-responses.d.cts.map +1 -0
  360. package/dist/ws-responses.d.ts +18 -0
  361. package/dist/ws-responses.d.ts.map +1 -0
  362. package/dist/ws-responses.js +164 -0
  363. package/dist/ws-responses.js.map +1 -0
  364. package/fixtures/example-greeting.json +12 -0
  365. package/fixtures/example-multi-turn.json +14 -0
  366. package/fixtures/example-tool-call.json +15 -0
  367. package/package.json +118 -0
  368. package/skills/write-fixtures/SKILL.md +625 -0
package/dist/server.js ADDED
@@ -0,0 +1,933 @@
1
+ import { buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, flattenHeaders, isErrorResponse, isTextResponse, isToolCallResponse } from "./helpers.js";
2
+ import { Journal } from "./journal.js";
3
+ import { matchFixture } from "./router.js";
4
+ import { entryToFixture, validateFixtures } from "./fixture-loader.js";
5
+ import { writeErrorResponse, writeSSEStream } from "./sse-writer.js";
6
+ import { createInterruptionSignal } from "./interruption.js";
7
+ import { applyChaos } from "./chaos.js";
8
+ import { proxyAndRecord } from "./recorder.js";
9
+ import { handleResponses } from "./responses.js";
10
+ import { handleMessages } from "./messages.js";
11
+ import { handleGemini } from "./gemini.js";
12
+ import { handleBedrock, handleBedrockStream } from "./bedrock.js";
13
+ import { handleConverse, handleConverseStream } from "./bedrock-converse.js";
14
+ import { handleEmbeddings } from "./embeddings.js";
15
+ import { handleOllama, handleOllamaGenerate } from "./ollama.js";
16
+ import { handleCohere } from "./cohere.js";
17
+ import { handleSearch } from "./search.js";
18
+ import { handleRerank } from "./rerank.js";
19
+ import { handleModeration } from "./moderation.js";
20
+ import { upgradeToWebSocket } from "./ws-framing.js";
21
+ import { handleWebSocketResponses } from "./ws-responses.js";
22
+ import { handleWebSocketRealtime } from "./ws-realtime.js";
23
+ import { handleWebSocketGeminiLive } from "./ws-gemini-live.js";
24
+ import { Logger } from "./logger.js";
25
+ import { createMetricsRegistry, normalizePathLabel } from "./metrics.js";
26
+ import * as http from "node:http";
27
+
28
+ //#region src/server.ts
29
+ const COMPLETIONS_PATH = "/v1/chat/completions";
30
+ const RESPONSES_PATH = "/v1/responses";
31
+ const REALTIME_PATH = "/v1/realtime";
32
+ const GEMINI_LIVE_PATH = "/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent";
33
+ const MESSAGES_PATH = "/v1/messages";
34
+ const EMBEDDINGS_PATH = "/v1/embeddings";
35
+ const COHERE_CHAT_PATH = "/v2/chat";
36
+ const SEARCH_PATH = "/search";
37
+ const RERANK_PATH = "/v2/rerank";
38
+ const MODERATIONS_PATH = "/v1/moderations";
39
+ const DEFAULT_CHUNK_SIZE = 20;
40
+ const GEMINI_PATH_RE = /^\/v1beta\/models\/([^:]+):(generateContent|streamGenerateContent)$/;
41
+ const AZURE_DEPLOYMENT_RE = /^\/openai\/deployments\/([^/]+)\/(chat\/completions|embeddings)$/;
42
+ const BEDROCK_INVOKE_RE = /^\/model\/([^/]+)\/invoke$/;
43
+ const BEDROCK_STREAM_RE = /^\/model\/([^/]+)\/invoke-with-response-stream$/;
44
+ const BEDROCK_CONVERSE_RE = /^\/model\/([^/]+)\/converse$/;
45
+ const BEDROCK_CONVERSE_STREAM_RE = /^\/model\/([^/]+)\/converse-stream$/;
46
+ const VERTEX_AI_RE = /^\/v1\/projects\/[^/]+\/locations\/[^/]+\/publishers\/google\/models\/([^/:]+):(generateContent|streamGenerateContent)$/;
47
+ const OLLAMA_CHAT_PATH = "/api/chat";
48
+ const OLLAMA_GENERATE_PATH = "/api/generate";
49
+ const OLLAMA_TAGS_PATH = "/api/tags";
50
+ const HEALTH_PATH = "/health";
51
+ const READY_PATH = "/ready";
52
+ const MODELS_PATH = "/v1/models";
53
+ const REQUESTS_PATH = "/v1/_requests";
54
+ const DEFAULT_MODELS = [
55
+ "gpt-4",
56
+ "gpt-4o",
57
+ "claude-3-5-sonnet-20241022",
58
+ "gemini-2.0-flash",
59
+ "text-embedding-3-small"
60
+ ];
61
+ const CORS_HEADERS = {
62
+ "Access-Control-Allow-Origin": "*",
63
+ "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
64
+ "Access-Control-Allow-Headers": "Content-Type, Authorization"
65
+ };
66
+ function setCorsHeaders(res) {
67
+ for (const [key, value] of Object.entries(CORS_HEADERS)) res.setHeader(key, value);
68
+ }
69
+ async function readBody(req) {
70
+ const buffers = [];
71
+ for await (const chunk of req) buffers.push(chunk);
72
+ return Buffer.concat(buffers).toString();
73
+ }
74
+ function handleOptions(res) {
75
+ setCorsHeaders(res);
76
+ res.writeHead(204);
77
+ res.end();
78
+ }
79
+ function handleNotFound(res, message) {
80
+ setCorsHeaders(res);
81
+ writeErrorResponse(res, 404, JSON.stringify({ error: {
82
+ message,
83
+ type: "not_found"
84
+ } }));
85
+ }
86
+ const CONTROL_PREFIX = "/__aimock";
87
+ /**
88
+ * Handle requests under `/__aimock/`. Returns `true` if the request was
89
+ * handled, `false` if the path doesn't match the control prefix.
90
+ */
91
+ async function handleControlAPI(req, res, pathname, fixtures, journal) {
92
+ if (!pathname.startsWith(CONTROL_PREFIX)) return false;
93
+ const subPath = pathname.slice(9);
94
+ setCorsHeaders(res);
95
+ if (subPath === "/health" && req.method === "GET") {
96
+ res.writeHead(200, { "Content-Type": "application/json" });
97
+ res.end(JSON.stringify({ status: "ok" }));
98
+ return true;
99
+ }
100
+ if (subPath === "/journal" && req.method === "GET") {
101
+ res.writeHead(200, { "Content-Type": "application/json" });
102
+ res.end(JSON.stringify(journal.getAll()));
103
+ return true;
104
+ }
105
+ if (subPath === "/fixtures" && req.method === "POST") {
106
+ let raw;
107
+ try {
108
+ raw = await readBody(req);
109
+ } catch {
110
+ res.writeHead(400, { "Content-Type": "application/json" });
111
+ res.end(JSON.stringify({ error: "Failed to read request body" }));
112
+ return true;
113
+ }
114
+ let parsed;
115
+ try {
116
+ parsed = JSON.parse(raw);
117
+ } catch {
118
+ res.writeHead(400, { "Content-Type": "application/json" });
119
+ res.end(JSON.stringify({ error: "Invalid JSON" }));
120
+ return true;
121
+ }
122
+ if (!Array.isArray(parsed.fixtures)) {
123
+ res.writeHead(400, { "Content-Type": "application/json" });
124
+ res.end(JSON.stringify({ error: "Missing or invalid \"fixtures\" array" }));
125
+ return true;
126
+ }
127
+ const converted = parsed.fixtures.map(entryToFixture);
128
+ const errors = validateFixtures(converted).filter((i) => i.severity === "error");
129
+ if (errors.length > 0) {
130
+ res.writeHead(400, { "Content-Type": "application/json" });
131
+ res.end(JSON.stringify({
132
+ error: "Validation failed",
133
+ details: errors
134
+ }));
135
+ return true;
136
+ }
137
+ fixtures.push(...converted);
138
+ res.writeHead(200, { "Content-Type": "application/json" });
139
+ res.end(JSON.stringify({ added: converted.length }));
140
+ return true;
141
+ }
142
+ if (subPath === "/fixtures" && req.method === "DELETE") {
143
+ fixtures.length = 0;
144
+ res.writeHead(200, { "Content-Type": "application/json" });
145
+ res.end(JSON.stringify({ cleared: true }));
146
+ return true;
147
+ }
148
+ if (subPath === "/reset" && req.method === "POST") {
149
+ fixtures.length = 0;
150
+ journal.clear();
151
+ res.writeHead(200, { "Content-Type": "application/json" });
152
+ res.end(JSON.stringify({ reset: true }));
153
+ return true;
154
+ }
155
+ if (subPath === "/error" && req.method === "POST") {
156
+ let raw;
157
+ try {
158
+ raw = await readBody(req);
159
+ } catch {
160
+ res.writeHead(400, { "Content-Type": "application/json" });
161
+ res.end(JSON.stringify({ error: "Failed to read request body" }));
162
+ return true;
163
+ }
164
+ let parsed;
165
+ try {
166
+ parsed = JSON.parse(raw);
167
+ } catch {
168
+ res.writeHead(400, { "Content-Type": "application/json" });
169
+ res.end(JSON.stringify({ error: "Invalid JSON" }));
170
+ return true;
171
+ }
172
+ const status = parsed.status ?? 500;
173
+ const errorBody = parsed.body;
174
+ const errorFixture = {
175
+ match: { predicate: () => true },
176
+ response: {
177
+ error: {
178
+ message: errorBody?.message ?? "Injected error",
179
+ type: errorBody?.type ?? "server_error",
180
+ code: errorBody?.code
181
+ },
182
+ status
183
+ }
184
+ };
185
+ fixtures.unshift(errorFixture);
186
+ const original = errorFixture.match.predicate;
187
+ errorFixture.match.predicate = (req) => {
188
+ const result = original(req);
189
+ if (result) queueMicrotask(() => {
190
+ const idx = fixtures.indexOf(errorFixture);
191
+ if (idx !== -1) fixtures.splice(idx, 1);
192
+ });
193
+ return result;
194
+ };
195
+ res.writeHead(200, { "Content-Type": "application/json" });
196
+ res.end(JSON.stringify({ queued: true }));
197
+ return true;
198
+ }
199
+ handleNotFound(res, `Unknown control endpoint: ${pathname}`);
200
+ return true;
201
+ }
202
+ async function handleCompletions(req, res, fixtures, journal, defaults, modelFallback, providerKey) {
203
+ setCorsHeaders(res);
204
+ let raw;
205
+ try {
206
+ raw = await readBody(req);
207
+ } catch (err) {
208
+ const msg = err instanceof Error ? err.message : "Failed to read request body";
209
+ journal.add({
210
+ method: req.method ?? "POST",
211
+ path: req.url ?? COMPLETIONS_PATH,
212
+ headers: flattenHeaders(req.headers),
213
+ body: null,
214
+ response: {
215
+ status: 500,
216
+ fixture: null
217
+ }
218
+ });
219
+ writeErrorResponse(res, 500, JSON.stringify({ error: {
220
+ message: `Request body read failed: ${msg}`,
221
+ type: "server_error"
222
+ } }));
223
+ return;
224
+ }
225
+ let body;
226
+ try {
227
+ body = JSON.parse(raw);
228
+ if (modelFallback && !body.model) body.model = modelFallback;
229
+ } catch {
230
+ journal.add({
231
+ method: req.method ?? "POST",
232
+ path: req.url ?? COMPLETIONS_PATH,
233
+ headers: flattenHeaders(req.headers),
234
+ body: null,
235
+ response: {
236
+ status: 400,
237
+ fixture: null
238
+ }
239
+ });
240
+ writeErrorResponse(res, 400, JSON.stringify({ error: {
241
+ message: "Malformed JSON",
242
+ type: "invalid_request_error",
243
+ code: "invalid_json"
244
+ } }));
245
+ return;
246
+ }
247
+ const fixture = matchFixture(fixtures, body, journal.fixtureMatchCounts);
248
+ if (fixture) journal.incrementFixtureMatchCount(fixture, fixtures);
249
+ const method = req.method ?? "POST";
250
+ const path = req.url ?? COMPLETIONS_PATH;
251
+ const flatHeaders = flattenHeaders(req.headers);
252
+ if (applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
253
+ method,
254
+ path,
255
+ headers: flatHeaders,
256
+ body
257
+ }, defaults.registry, defaults.logger)) return;
258
+ if (!fixture) {
259
+ if (defaults.record && providerKey) {
260
+ if (await proxyAndRecord(req, res, body, providerKey, req.url ?? COMPLETIONS_PATH, fixtures, defaults, raw)) {
261
+ journal.add({
262
+ method: req.method ?? "POST",
263
+ path: req.url ?? COMPLETIONS_PATH,
264
+ headers: flattenHeaders(req.headers),
265
+ body,
266
+ response: {
267
+ status: res.statusCode ?? 200,
268
+ fixture: null
269
+ }
270
+ });
271
+ return;
272
+ }
273
+ }
274
+ const strictStatus = defaults.strict ? 503 : 404;
275
+ const strictMessage = defaults.strict ? "Strict mode: no fixture matched" : "No fixture matched";
276
+ if (defaults.strict) defaults.logger.error(`STRICT: No fixture matched for ${req.method ?? "POST"} ${req.url ?? COMPLETIONS_PATH}`);
277
+ journal.add({
278
+ method: req.method ?? "POST",
279
+ path: req.url ?? COMPLETIONS_PATH,
280
+ headers: flattenHeaders(req.headers),
281
+ body,
282
+ response: {
283
+ status: strictStatus,
284
+ fixture: null
285
+ }
286
+ });
287
+ writeErrorResponse(res, strictStatus, JSON.stringify({ error: {
288
+ message: strictMessage,
289
+ type: "invalid_request_error",
290
+ code: "no_fixture_match"
291
+ } }));
292
+ return;
293
+ }
294
+ const response = fixture.response;
295
+ const latency = fixture.latency ?? defaults.latency;
296
+ const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
297
+ if (isErrorResponse(response)) {
298
+ const status = response.status ?? 500;
299
+ journal.add({
300
+ method: req.method ?? "POST",
301
+ path: req.url ?? COMPLETIONS_PATH,
302
+ headers: flattenHeaders(req.headers),
303
+ body,
304
+ response: {
305
+ status,
306
+ fixture
307
+ }
308
+ });
309
+ writeErrorResponse(res, status, JSON.stringify(response));
310
+ return;
311
+ }
312
+ if (isTextResponse(response)) {
313
+ const journalEntry = journal.add({
314
+ method: req.method ?? "POST",
315
+ path: req.url ?? COMPLETIONS_PATH,
316
+ headers: flattenHeaders(req.headers),
317
+ body,
318
+ response: {
319
+ status: 200,
320
+ fixture
321
+ }
322
+ });
323
+ if (body.stream !== true) {
324
+ const completion = buildTextCompletion(response.content, body.model);
325
+ res.writeHead(200, { "Content-Type": "application/json" });
326
+ res.end(JSON.stringify(completion));
327
+ } else {
328
+ const chunks = buildTextChunks(response.content, body.model, chunkSize);
329
+ const interruption = createInterruptionSignal(fixture);
330
+ if (!await writeSSEStream(res, chunks, {
331
+ latency,
332
+ streamingProfile: fixture.streamingProfile,
333
+ signal: interruption?.signal,
334
+ onChunkSent: interruption?.tick
335
+ })) {
336
+ if (!res.writableEnded) res.destroy();
337
+ journalEntry.response.interrupted = true;
338
+ journalEntry.response.interruptReason = interruption?.reason();
339
+ }
340
+ interruption?.cleanup();
341
+ }
342
+ return;
343
+ }
344
+ if (isToolCallResponse(response)) {
345
+ const journalEntry = journal.add({
346
+ method: req.method ?? "POST",
347
+ path: req.url ?? COMPLETIONS_PATH,
348
+ headers: flattenHeaders(req.headers),
349
+ body,
350
+ response: {
351
+ status: 200,
352
+ fixture
353
+ }
354
+ });
355
+ if (body.stream !== true) {
356
+ const completion = buildToolCallCompletion(response.toolCalls, body.model);
357
+ res.writeHead(200, { "Content-Type": "application/json" });
358
+ res.end(JSON.stringify(completion));
359
+ } else {
360
+ const chunks = buildToolCallChunks(response.toolCalls, body.model, chunkSize);
361
+ const interruption = createInterruptionSignal(fixture);
362
+ if (!await writeSSEStream(res, chunks, {
363
+ latency,
364
+ streamingProfile: fixture.streamingProfile,
365
+ signal: interruption?.signal,
366
+ onChunkSent: interruption?.tick
367
+ })) {
368
+ if (!res.writableEnded) res.destroy();
369
+ journalEntry.response.interrupted = true;
370
+ journalEntry.response.interruptReason = interruption?.reason();
371
+ }
372
+ interruption?.cleanup();
373
+ }
374
+ return;
375
+ }
376
+ journal.add({
377
+ method: req.method ?? "POST",
378
+ path: req.url ?? COMPLETIONS_PATH,
379
+ headers: flattenHeaders(req.headers),
380
+ body,
381
+ response: {
382
+ status: 500,
383
+ fixture
384
+ }
385
+ });
386
+ writeErrorResponse(res, 500, JSON.stringify({ error: {
387
+ message: "Fixture response did not match any known type",
388
+ type: "server_error"
389
+ } }));
390
+ }
391
+ async function createServer(fixtures, options, mounts, serviceFixtures) {
392
+ const host = options?.host ?? "127.0.0.1";
393
+ const port = options?.port ?? 0;
394
+ const logger = new Logger(options?.logLevel ?? "silent");
395
+ const registry = options?.metrics ? createMetricsRegistry() : void 0;
396
+ const serverOptions = options ?? {};
397
+ const defaults = {
398
+ latency: serverOptions.latency ?? 0,
399
+ chunkSize: Math.max(1, serverOptions.chunkSize ?? DEFAULT_CHUNK_SIZE),
400
+ logger,
401
+ get chaos() {
402
+ return serverOptions.chaos;
403
+ },
404
+ registry,
405
+ get record() {
406
+ return serverOptions.record;
407
+ },
408
+ get strict() {
409
+ return serverOptions.strict;
410
+ }
411
+ };
412
+ if (options?.chaos) {
413
+ const chaosRates = [
414
+ {
415
+ name: "dropRate",
416
+ value: options.chaos.dropRate
417
+ },
418
+ {
419
+ name: "malformedRate",
420
+ value: options.chaos.malformedRate
421
+ },
422
+ {
423
+ name: "disconnectRate",
424
+ value: options.chaos.disconnectRate
425
+ }
426
+ ];
427
+ for (const { name, value } of chaosRates) if (value !== void 0 && (value < 0 || value > 1)) logger.warn(`Chaos ${name} (${value}) is outside 0-1 range — will be clamped at runtime`);
428
+ }
429
+ const journal = new Journal();
430
+ if (mounts) for (const { handler } of mounts) {
431
+ if (handler.setJournal) handler.setJournal(journal);
432
+ if (registry && handler.setRegistry) handler.setRegistry(registry);
433
+ }
434
+ if (registry) registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
435
+ const server = http.createServer((req, res) => {
436
+ handleHttpRequest(req, res).catch((err) => {
437
+ const msg = err instanceof Error ? err.message : "Internal error";
438
+ defaults.logger.warn(`Unhandled request error: ${msg}`);
439
+ if (!res.headersSent) {
440
+ res.writeHead(500, { "Content-Type": "application/json" });
441
+ res.end(JSON.stringify({ error: {
442
+ message: msg,
443
+ type: "server_error"
444
+ } }));
445
+ }
446
+ });
447
+ });
448
+ async function handleHttpRequest(req, res) {
449
+ if (req.method === "OPTIONS") {
450
+ handleOptions(res);
451
+ return;
452
+ }
453
+ const startTime = registry ? process.hrtime.bigint() : 0n;
454
+ const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
455
+ let pathname = parsedUrl.pathname;
456
+ if (registry) {
457
+ const rawPathname = pathname;
458
+ res.on("finish", () => {
459
+ try {
460
+ const normalizedPath = normalizePathLabel(rawPathname);
461
+ const method = req.method ?? "UNKNOWN";
462
+ const status = String(res.statusCode);
463
+ registry.incrementCounter("aimock_requests_total", {
464
+ method,
465
+ path: normalizedPath,
466
+ status
467
+ });
468
+ const elapsed = Number(process.hrtime.bigint() - startTime) / 1e9;
469
+ registry.observeHistogram("aimock_request_duration_seconds", {
470
+ method,
471
+ path: normalizedPath
472
+ }, elapsed);
473
+ } catch (err) {
474
+ defaults.logger.warn("metrics instrumentation error", err);
475
+ }
476
+ });
477
+ }
478
+ if (pathname.startsWith(CONTROL_PREFIX)) {
479
+ await handleControlAPI(req, res, pathname, fixtures, journal);
480
+ return;
481
+ }
482
+ if (mounts) {
483
+ for (const { path: mountPath, handler } of mounts) if (pathname === mountPath || pathname.startsWith(mountPath + "/")) {
484
+ const subPath = pathname.slice(mountPath.length) || "/";
485
+ if (await handler.handleRequest(req, res, subPath)) return;
486
+ }
487
+ }
488
+ let azureDeploymentId;
489
+ const azureMatch = pathname.match(AZURE_DEPLOYMENT_RE);
490
+ if (azureMatch && req.method === "POST") {
491
+ azureDeploymentId = azureMatch[1];
492
+ pathname = `/v1/${azureMatch[2]}`;
493
+ }
494
+ if (!azureDeploymentId && pathname.startsWith("/openai/")) pathname = pathname.slice(7);
495
+ if (pathname === HEALTH_PATH && req.method === "GET") {
496
+ setCorsHeaders(res);
497
+ if (mounts && mounts.length > 0) {
498
+ const services = { llm: {
499
+ status: "ok",
500
+ fixtures: fixtures.length
501
+ } };
502
+ for (const { path: mountPath, handler } of mounts) if (handler.health) {
503
+ const name = mountPath.replace(/^\//, "");
504
+ services[name] = handler.health();
505
+ }
506
+ res.writeHead(200, { "Content-Type": "application/json" });
507
+ res.end(JSON.stringify({
508
+ status: "ok",
509
+ services
510
+ }));
511
+ } else {
512
+ res.writeHead(200, { "Content-Type": "application/json" });
513
+ res.end(JSON.stringify({ status: "ok" }));
514
+ }
515
+ return;
516
+ }
517
+ if (pathname === READY_PATH && req.method === "GET") {
518
+ setCorsHeaders(res);
519
+ res.writeHead(200, { "Content-Type": "application/json" });
520
+ res.end(JSON.stringify({ status: "ready" }));
521
+ return;
522
+ }
523
+ if (pathname === "/metrics" && req.method === "GET") {
524
+ if (!registry) {
525
+ handleNotFound(res, "Not found");
526
+ return;
527
+ }
528
+ setCorsHeaders(res);
529
+ res.writeHead(200, { "Content-Type": "text/plain; version=0.0.4; charset=utf-8" });
530
+ res.end(registry.serialize());
531
+ return;
532
+ }
533
+ if (pathname === MODELS_PATH && req.method === "GET") {
534
+ setCorsHeaders(res);
535
+ const modelIds = /* @__PURE__ */ new Set();
536
+ for (const f of fixtures) if (f.match.model && typeof f.match.model === "string") modelIds.add(f.match.model);
537
+ const data = (modelIds.size > 0 ? [...modelIds] : DEFAULT_MODELS).map((id) => ({
538
+ id,
539
+ object: "model",
540
+ created: 1686935002,
541
+ owned_by: "aimock"
542
+ }));
543
+ res.writeHead(200, { "Content-Type": "application/json" });
544
+ res.end(JSON.stringify({
545
+ object: "list",
546
+ data
547
+ }));
548
+ return;
549
+ }
550
+ if (pathname === REQUESTS_PATH) {
551
+ setCorsHeaders(res);
552
+ if (req.method === "GET") {
553
+ const limitParam = parsedUrl.searchParams.get("limit");
554
+ let opts;
555
+ if (limitParam) {
556
+ const limit = parseInt(limitParam, 10);
557
+ if (Number.isNaN(limit) || limit <= 0) {
558
+ writeErrorResponse(res, 400, JSON.stringify({ error: {
559
+ message: `Invalid limit parameter: "${limitParam}"`,
560
+ type: "invalid_request_error"
561
+ } }));
562
+ return;
563
+ }
564
+ opts = { limit };
565
+ }
566
+ const entries = journal.getAll(opts);
567
+ res.writeHead(200, { "Content-Type": "application/json" });
568
+ res.end(JSON.stringify(entries));
569
+ return;
570
+ }
571
+ if (req.method === "DELETE") {
572
+ journal.clear();
573
+ res.writeHead(204);
574
+ res.end();
575
+ return;
576
+ }
577
+ handleNotFound(res, "Not found");
578
+ return;
579
+ }
580
+ if (pathname === RESPONSES_PATH && req.method === "POST") {
581
+ readBody(req).then((raw) => handleResponses(req, res, raw, fixtures, journal, defaults, setCorsHeaders)).catch((err) => {
582
+ const msg = err instanceof Error ? err.message : "Internal error";
583
+ if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
584
+ message: msg,
585
+ type: "server_error"
586
+ } }));
587
+ else if (!res.writableEnded) {
588
+ try {
589
+ res.write(`event: error\ndata: ${JSON.stringify({ error: { message: msg } })}\n\n`);
590
+ } catch (writeErr) {
591
+ logger.debug("Failed to write error recovery response:", writeErr);
592
+ }
593
+ res.end();
594
+ }
595
+ });
596
+ return;
597
+ }
598
+ if (pathname === MESSAGES_PATH && req.method === "POST") {
599
+ readBody(req).then((raw) => handleMessages(req, res, raw, fixtures, journal, defaults, setCorsHeaders)).catch((err) => {
600
+ const msg = err instanceof Error ? err.message : "Internal error";
601
+ if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
602
+ message: msg,
603
+ type: "server_error"
604
+ } }));
605
+ else if (!res.writableEnded) {
606
+ try {
607
+ res.write(`event: error\ndata: ${JSON.stringify({ error: { message: msg } })}\n\n`);
608
+ } catch (writeErr) {
609
+ logger.debug("Failed to write error recovery response:", writeErr);
610
+ }
611
+ res.end();
612
+ }
613
+ });
614
+ return;
615
+ }
616
+ if (pathname === COHERE_CHAT_PATH && req.method === "POST") {
617
+ readBody(req).then((raw) => handleCohere(req, res, raw, fixtures, journal, defaults, setCorsHeaders)).catch((err) => {
618
+ const msg = err instanceof Error ? err.message : "Internal error";
619
+ if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
620
+ message: msg,
621
+ type: "server_error"
622
+ } }));
623
+ else if (!res.writableEnded) {
624
+ try {
625
+ res.write(`event: error\ndata: ${JSON.stringify({ error: { message: msg } })}\n\n`);
626
+ } catch (writeErr) {
627
+ logger.debug("Failed to write error recovery response:", writeErr);
628
+ }
629
+ res.end();
630
+ }
631
+ });
632
+ return;
633
+ }
634
+ if (pathname === EMBEDDINGS_PATH && req.method === "POST") {
635
+ const deploymentId = azureDeploymentId;
636
+ readBody(req).then((raw) => {
637
+ if (deploymentId) try {
638
+ const parsed = JSON.parse(raw);
639
+ if (!parsed.model) {
640
+ parsed.model = deploymentId;
641
+ return handleEmbeddings(req, res, JSON.stringify(parsed), fixtures, journal, defaults, setCorsHeaders);
642
+ }
643
+ } catch {}
644
+ return handleEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
645
+ }).catch((err) => {
646
+ const msg = err instanceof Error ? err.message : "Internal error";
647
+ if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
648
+ message: msg,
649
+ type: "server_error"
650
+ } }));
651
+ else if (!res.writableEnded) res.destroy();
652
+ });
653
+ return;
654
+ }
655
+ const geminiMatch = pathname.match(GEMINI_PATH_RE);
656
+ if (geminiMatch && req.method === "POST") {
657
+ const geminiModel = geminiMatch[1];
658
+ const streaming = geminiMatch[2] === "streamGenerateContent";
659
+ readBody(req).then((raw) => handleGemini(req, res, raw, geminiModel, streaming, fixtures, journal, defaults, setCorsHeaders)).catch((err) => {
660
+ const msg = err instanceof Error ? err.message : "Internal error";
661
+ if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
662
+ message: msg,
663
+ type: "server_error"
664
+ } }));
665
+ else if (!res.writableEnded) {
666
+ try {
667
+ res.write(`data: ${JSON.stringify({ error: { message: msg } })}\n\n`);
668
+ } catch (writeErr) {
669
+ logger.debug("Failed to write error recovery response:", writeErr);
670
+ }
671
+ res.end();
672
+ }
673
+ });
674
+ return;
675
+ }
676
+ const vertexMatch = pathname.match(VERTEX_AI_RE);
677
+ if (vertexMatch && req.method === "POST") {
678
+ const vertexModel = vertexMatch[1];
679
+ const streaming = vertexMatch[2] === "streamGenerateContent";
680
+ readBody(req).then((raw) => handleGemini(req, res, raw, vertexModel, streaming, fixtures, journal, defaults, setCorsHeaders, "vertexai")).catch((err) => {
681
+ const msg = err instanceof Error ? err.message : "Internal error";
682
+ if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
683
+ message: msg,
684
+ type: "server_error"
685
+ } }));
686
+ else if (!res.writableEnded) {
687
+ try {
688
+ res.write(`data: ${JSON.stringify({ error: { message: msg } })}\n\n`);
689
+ } catch (writeErr) {
690
+ logger.debug("Failed to write error recovery response:", writeErr);
691
+ }
692
+ res.end();
693
+ }
694
+ });
695
+ return;
696
+ }
697
+ const bedrockMatch = pathname.match(BEDROCK_INVOKE_RE);
698
+ if (bedrockMatch && req.method === "POST") {
699
+ const bedrockModelId = bedrockMatch[1];
700
+ readBody(req).then((raw) => handleBedrock(req, res, raw, bedrockModelId, fixtures, journal, defaults, setCorsHeaders)).catch((err) => {
701
+ const msg = err instanceof Error ? err.message : "Internal error";
702
+ if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
703
+ message: msg,
704
+ type: "server_error"
705
+ } }));
706
+ else if (!res.writableEnded) res.destroy();
707
+ });
708
+ return;
709
+ }
710
+ const bedrockStreamMatch = pathname.match(BEDROCK_STREAM_RE);
711
+ if (bedrockStreamMatch && req.method === "POST") {
712
+ const bedrockModelId = bedrockStreamMatch[1];
713
+ readBody(req).then((raw) => handleBedrockStream(req, res, raw, bedrockModelId, fixtures, journal, defaults, setCorsHeaders)).catch((err) => {
714
+ const msg = err instanceof Error ? err.message : "Internal error";
715
+ if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
716
+ message: msg,
717
+ type: "server_error"
718
+ } }));
719
+ else if (!res.writableEnded) res.destroy();
720
+ });
721
+ return;
722
+ }
723
+ const converseMatch = pathname.match(BEDROCK_CONVERSE_RE);
724
+ if (converseMatch && req.method === "POST") {
725
+ const converseModelId = converseMatch[1];
726
+ readBody(req).then((raw) => handleConverse(req, res, raw, converseModelId, fixtures, journal, defaults, setCorsHeaders)).catch((err) => {
727
+ const msg = err instanceof Error ? err.message : "Internal error";
728
+ if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
729
+ message: msg,
730
+ type: "server_error"
731
+ } }));
732
+ else if (!res.writableEnded) res.destroy();
733
+ });
734
+ return;
735
+ }
736
+ const converseStreamMatch = pathname.match(BEDROCK_CONVERSE_STREAM_RE);
737
+ if (converseStreamMatch && req.method === "POST") {
738
+ const converseStreamModelId = converseStreamMatch[1];
739
+ readBody(req).then((raw) => handleConverseStream(req, res, raw, converseStreamModelId, fixtures, journal, defaults, setCorsHeaders)).catch((err) => {
740
+ const msg = err instanceof Error ? err.message : "Internal error";
741
+ if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
742
+ message: msg,
743
+ type: "server_error"
744
+ } }));
745
+ else if (!res.writableEnded) res.destroy();
746
+ });
747
+ return;
748
+ }
749
+ if (pathname === OLLAMA_CHAT_PATH && req.method === "POST") {
750
+ readBody(req).then((raw) => handleOllama(req, res, raw, fixtures, journal, defaults, setCorsHeaders)).catch((err) => {
751
+ const msg = err instanceof Error ? err.message : "Internal error";
752
+ if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
753
+ message: msg,
754
+ type: "server_error"
755
+ } }));
756
+ else if (!res.writableEnded) res.destroy();
757
+ });
758
+ return;
759
+ }
760
+ if (pathname === OLLAMA_GENERATE_PATH && req.method === "POST") {
761
+ readBody(req).then((raw) => handleOllamaGenerate(req, res, raw, fixtures, journal, defaults, setCorsHeaders)).catch((err) => {
762
+ const msg = err instanceof Error ? err.message : "Internal error";
763
+ if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
764
+ message: msg,
765
+ type: "server_error"
766
+ } }));
767
+ else if (!res.writableEnded) res.destroy();
768
+ });
769
+ return;
770
+ }
771
+ if (pathname === OLLAMA_TAGS_PATH && req.method === "GET") {
772
+ setCorsHeaders(res);
773
+ const modelIds = /* @__PURE__ */ new Set();
774
+ for (const f of fixtures) if (f.match.model && typeof f.match.model === "string") modelIds.add(f.match.model);
775
+ const models = (modelIds.size > 0 ? [...modelIds] : DEFAULT_MODELS).map((name) => ({
776
+ name,
777
+ model: name,
778
+ modified_at: (/* @__PURE__ */ new Date()).toISOString(),
779
+ size: 0,
780
+ digest: "",
781
+ details: {}
782
+ }));
783
+ res.writeHead(200, { "Content-Type": "application/json" });
784
+ res.end(JSON.stringify({ models }));
785
+ return;
786
+ }
787
+ if (pathname === SEARCH_PATH && req.method === "POST") {
788
+ readBody(req).then((raw) => handleSearch(req, res, raw, serviceFixtures?.search ?? [], journal, defaults, setCorsHeaders)).catch((err) => {
789
+ const msg = err instanceof Error ? err.message : "Internal error";
790
+ if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
791
+ message: msg,
792
+ type: "server_error"
793
+ } }));
794
+ else if (!res.writableEnded) res.destroy();
795
+ });
796
+ return;
797
+ }
798
+ if (pathname === RERANK_PATH && req.method === "POST") {
799
+ readBody(req).then((raw) => handleRerank(req, res, raw, serviceFixtures?.rerank ?? [], journal, defaults, setCorsHeaders)).catch((err) => {
800
+ const msg = err instanceof Error ? err.message : "Internal error";
801
+ if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
802
+ message: msg,
803
+ type: "server_error"
804
+ } }));
805
+ else if (!res.writableEnded) res.destroy();
806
+ });
807
+ return;
808
+ }
809
+ if (pathname === MODERATIONS_PATH && req.method === "POST") {
810
+ readBody(req).then((raw) => handleModeration(req, res, raw, serviceFixtures?.moderation ?? [], journal, defaults, setCorsHeaders)).catch((err) => {
811
+ const msg = err instanceof Error ? err.message : "Internal error";
812
+ if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
813
+ message: msg,
814
+ type: "server_error"
815
+ } }));
816
+ else if (!res.writableEnded) res.destroy();
817
+ });
818
+ return;
819
+ }
820
+ if (pathname !== COMPLETIONS_PATH) {
821
+ handleNotFound(res, "Not found");
822
+ return;
823
+ }
824
+ if (req.method !== "POST") {
825
+ handleNotFound(res, "Not found");
826
+ return;
827
+ }
828
+ handleCompletions(req, res, fixtures, journal, defaults, azureDeploymentId, azureDeploymentId ? "azure" : "openai").catch((err) => {
829
+ const msg = err instanceof Error ? err.message : "Internal error";
830
+ if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
831
+ message: msg,
832
+ type: "server_error"
833
+ } }));
834
+ else if (!res.writableEnded) {
835
+ try {
836
+ res.write(`data: ${JSON.stringify({ error: {
837
+ message: msg,
838
+ type: "server_error"
839
+ } })}\n\n`);
840
+ } catch (writeErr) {
841
+ logger.debug("Failed to write error recovery response:", writeErr);
842
+ }
843
+ res.end();
844
+ }
845
+ });
846
+ }
847
+ const activeConnections = /* @__PURE__ */ new Set();
848
+ server.on("upgrade", (req, socket, head) => {
849
+ handleUpgradeRequest(req, socket, head).catch((err) => {
850
+ const msg = err instanceof Error ? err.message : "Internal error";
851
+ defaults.logger.warn(`Unhandled upgrade error: ${msg}`);
852
+ if (!socket.destroyed) socket.destroy();
853
+ });
854
+ });
855
+ async function handleUpgradeRequest(req, socket, head) {
856
+ const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
857
+ const pathname = parsedUrl.pathname;
858
+ if (mounts) {
859
+ for (const { path: mountPath, handler } of mounts) if ((pathname === mountPath || pathname.startsWith(mountPath + "/")) && handler.handleUpgrade) {
860
+ const subPath = pathname.slice(mountPath.length) || "/";
861
+ if (await handler.handleUpgrade(socket, head, subPath)) return;
862
+ }
863
+ }
864
+ if (pathname !== RESPONSES_PATH && pathname !== REALTIME_PATH && pathname !== GEMINI_LIVE_PATH) {
865
+ socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
866
+ socket.destroy();
867
+ return;
868
+ }
869
+ if (head.length > 0) socket.unshift(head);
870
+ let ws;
871
+ try {
872
+ ws = upgradeToWebSocket(req, socket);
873
+ } catch (err) {
874
+ const msg = err instanceof Error ? err.message : "WebSocket upgrade failed";
875
+ logger.error(`WebSocket upgrade error: ${msg}`);
876
+ if (!socket.destroyed) socket.destroy();
877
+ return;
878
+ }
879
+ activeConnections.add(ws);
880
+ ws.on("error", (err) => {
881
+ logger.error(`WebSocket error: ${err.message}`);
882
+ activeConnections.delete(ws);
883
+ });
884
+ ws.on("close", () => {
885
+ activeConnections.delete(ws);
886
+ });
887
+ if (pathname === RESPONSES_PATH) handleWebSocketResponses(ws, fixtures, journal, {
888
+ ...defaults,
889
+ model: "gpt-4"
890
+ });
891
+ else if (pathname === REALTIME_PATH) {
892
+ const model = parsedUrl.searchParams.get("model") ?? "gpt-4o-realtime";
893
+ handleWebSocketRealtime(ws, fixtures, journal, {
894
+ ...defaults,
895
+ model
896
+ });
897
+ } else if (pathname === GEMINI_LIVE_PATH) handleWebSocketGeminiLive(ws, fixtures, journal, {
898
+ ...defaults,
899
+ model: "gemini-2.0-flash"
900
+ });
901
+ }
902
+ const originalClose = server.close.bind(server);
903
+ server.close = function(callback) {
904
+ for (const ws of activeConnections) ws.close(1001, "Server shutting down");
905
+ activeConnections.clear();
906
+ originalClose(callback);
907
+ return this;
908
+ };
909
+ return new Promise((resolve, reject) => {
910
+ server.on("error", reject);
911
+ server.listen(port, host, () => {
912
+ const addr = server.address();
913
+ if (!addr || typeof addr === "string") {
914
+ reject(/* @__PURE__ */ new Error("Unexpected address format"));
915
+ return;
916
+ }
917
+ const url = `http://${addr.address}:${addr.port}`;
918
+ if (mounts) {
919
+ for (const { path: mountPath, handler } of mounts) if (handler.setBaseUrl) handler.setBaseUrl(url + mountPath);
920
+ }
921
+ resolve({
922
+ server,
923
+ journal,
924
+ url,
925
+ defaults
926
+ });
927
+ });
928
+ });
929
+ }
930
+
931
+ //#endregion
932
+ export { createServer };
933
+ //# sourceMappingURL=server.js.map