@copilotkit/aimock 1.42.1 → 1.44.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 (442) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +65 -0
  4. package/README.md +108 -16
  5. package/dist/agui-handler.d.ts.map +1 -1
  6. package/dist/agui-mock.cjs +1 -1
  7. package/dist/agui-mock.cjs.map +1 -1
  8. package/dist/agui-mock.d.cts.map +1 -1
  9. package/dist/agui-mock.d.ts.map +1 -1
  10. package/dist/agui-mock.js +1 -1
  11. package/dist/agui-mock.js.map +1 -1
  12. package/dist/agui-recorder.cjs +33 -11
  13. package/dist/agui-recorder.cjs.map +1 -1
  14. package/dist/agui-recorder.d.cts +10 -1
  15. package/dist/agui-recorder.d.cts.map +1 -1
  16. package/dist/agui-recorder.d.ts +10 -1
  17. package/dist/agui-recorder.d.ts.map +1 -1
  18. package/dist/agui-recorder.js +33 -11
  19. package/dist/agui-recorder.js.map +1 -1
  20. package/dist/agui-types.d.cts +1 -0
  21. package/dist/agui-types.d.cts.map +1 -1
  22. package/dist/agui-types.d.ts +1 -0
  23. package/dist/agui-types.d.ts.map +1 -1
  24. package/dist/aimock-cli.cjs +38 -10
  25. package/dist/aimock-cli.cjs.map +1 -1
  26. package/dist/aimock-cli.d.cts +5 -0
  27. package/dist/aimock-cli.d.cts.map +1 -1
  28. package/dist/aimock-cli.d.ts +5 -0
  29. package/dist/aimock-cli.d.ts.map +1 -1
  30. package/dist/aimock-cli.js +38 -10
  31. package/dist/aimock-cli.js.map +1 -1
  32. package/dist/batches.cjs +458 -0
  33. package/dist/batches.cjs.map +1 -0
  34. package/dist/batches.d.cts +57 -0
  35. package/dist/batches.d.cts.map +1 -0
  36. package/dist/batches.d.ts +57 -0
  37. package/dist/batches.d.ts.map +1 -0
  38. package/dist/batches.js +453 -0
  39. package/dist/batches.js.map +1 -0
  40. package/dist/bedrock-converse.cjs +44 -12
  41. package/dist/bedrock-converse.cjs.map +1 -1
  42. package/dist/bedrock-converse.d.cts.map +1 -1
  43. package/dist/bedrock-converse.d.ts.map +1 -1
  44. package/dist/bedrock-converse.js +46 -14
  45. package/dist/bedrock-converse.js.map +1 -1
  46. package/dist/bedrock.cjs +10 -8
  47. package/dist/bedrock.cjs.map +1 -1
  48. package/dist/bedrock.d.cts.map +1 -1
  49. package/dist/bedrock.d.ts.map +1 -1
  50. package/dist/bedrock.js +12 -10
  51. package/dist/bedrock.js.map +1 -1
  52. package/dist/byteplus-video.cjs +3 -3
  53. package/dist/byteplus-video.cjs.map +1 -1
  54. package/dist/byteplus-video.js +4 -4
  55. package/dist/byteplus-video.js.map +1 -1
  56. package/dist/chaos.cjs +625 -72
  57. package/dist/chaos.cjs.map +1 -1
  58. package/dist/chaos.d.cts +164 -7
  59. package/dist/chaos.d.cts.map +1 -1
  60. package/dist/chaos.d.ts +164 -7
  61. package/dist/chaos.d.ts.map +1 -1
  62. package/dist/chaos.js +616 -74
  63. package/dist/chaos.js.map +1 -1
  64. package/dist/cli.cjs +131 -30
  65. package/dist/cli.cjs.map +1 -1
  66. package/dist/cli.js +132 -31
  67. package/dist/cli.js.map +1 -1
  68. package/dist/cohere.cjs +37 -9
  69. package/dist/cohere.cjs.map +1 -1
  70. package/dist/cohere.d.cts.map +1 -1
  71. package/dist/cohere.d.ts.map +1 -1
  72. package/dist/cohere.js +39 -11
  73. package/dist/cohere.js.map +1 -1
  74. package/dist/config-loader.cjs +1 -1
  75. package/dist/config-loader.js +1 -1
  76. package/dist/constants.cjs +8 -0
  77. package/dist/constants.cjs.map +1 -1
  78. package/dist/constants.d.cts +6 -2
  79. package/dist/constants.d.ts +6 -2
  80. package/dist/constants.js +8 -1
  81. package/dist/constants.js.map +1 -1
  82. package/dist/convert.cjs +1 -1
  83. package/dist/convert.cjs.map +1 -1
  84. package/dist/convert.js +1 -1
  85. package/dist/convert.js.map +1 -1
  86. package/dist/echo-text.cjs +65 -0
  87. package/dist/echo-text.cjs.map +1 -0
  88. package/dist/echo-text.d.cts +22 -0
  89. package/dist/echo-text.d.cts.map +1 -0
  90. package/dist/echo-text.d.ts +22 -0
  91. package/dist/echo-text.d.ts.map +1 -0
  92. package/dist/echo-text.js +65 -0
  93. package/dist/echo-text.js.map +1 -0
  94. package/dist/elevenlabs-audio.cjs +3 -3
  95. package/dist/elevenlabs-audio.cjs.map +1 -1
  96. package/dist/elevenlabs-audio.js +4 -4
  97. package/dist/elevenlabs-audio.js.map +1 -1
  98. package/dist/elevenlabs-voice.cjs +1053 -0
  99. package/dist/elevenlabs-voice.cjs.map +1 -0
  100. package/dist/elevenlabs-voice.d.cts +72 -0
  101. package/dist/elevenlabs-voice.d.cts.map +1 -0
  102. package/dist/elevenlabs-voice.d.ts +72 -0
  103. package/dist/elevenlabs-voice.d.ts.map +1 -0
  104. package/dist/elevenlabs-voice.js +1048 -0
  105. package/dist/elevenlabs-voice.js.map +1 -0
  106. package/dist/embeddings.cjs +26 -5
  107. package/dist/embeddings.cjs.map +1 -1
  108. package/dist/embeddings.d.cts +1 -1
  109. package/dist/embeddings.d.cts.map +1 -1
  110. package/dist/embeddings.d.ts +1 -1
  111. package/dist/embeddings.d.ts.map +1 -1
  112. package/dist/embeddings.js +27 -6
  113. package/dist/embeddings.js.map +1 -1
  114. package/dist/fal-audio.cjs +3 -3
  115. package/dist/fal-audio.cjs.map +1 -1
  116. package/dist/fal-audio.js +4 -4
  117. package/dist/fal-audio.js.map +1 -1
  118. package/dist/fal.cjs +28 -4
  119. package/dist/fal.cjs.map +1 -1
  120. package/dist/fal.d.cts +16 -1
  121. package/dist/fal.d.cts.map +1 -1
  122. package/dist/fal.d.ts +16 -1
  123. package/dist/fal.d.ts.map +1 -1
  124. package/dist/fal.js +28 -5
  125. package/dist/fal.js.map +1 -1
  126. package/dist/files.cjs +1069 -0
  127. package/dist/files.cjs.map +1 -0
  128. package/dist/files.d.cts +68 -0
  129. package/dist/files.d.cts.map +1 -0
  130. package/dist/files.d.ts +68 -0
  131. package/dist/files.d.ts.map +1 -0
  132. package/dist/files.js +1056 -0
  133. package/dist/files.js.map +1 -0
  134. package/dist/fine-tuning.cjs +1033 -0
  135. package/dist/fine-tuning.cjs.map +1 -0
  136. package/dist/fine-tuning.d.cts +96 -0
  137. package/dist/fine-tuning.d.cts.map +1 -0
  138. package/dist/fine-tuning.d.ts +96 -0
  139. package/dist/fine-tuning.d.ts.map +1 -0
  140. package/dist/fine-tuning.js +1026 -0
  141. package/dist/fine-tuning.js.map +1 -0
  142. package/dist/fixture-loader-services.cjs +116 -0
  143. package/dist/fixture-loader-services.cjs.map +1 -0
  144. package/dist/fixture-loader-services.d.cts +48 -0
  145. package/dist/fixture-loader-services.d.cts.map +1 -0
  146. package/dist/fixture-loader-services.d.ts +48 -0
  147. package/dist/fixture-loader-services.d.ts.map +1 -0
  148. package/dist/fixture-loader-services.js +114 -0
  149. package/dist/fixture-loader-services.js.map +1 -0
  150. package/dist/fixture-loader.cjs +0 -0
  151. package/dist/fixture-loader.cjs.map +1 -1
  152. package/dist/fixture-loader.d.cts +131 -5
  153. package/dist/fixture-loader.d.cts.map +1 -1
  154. package/dist/fixture-loader.d.ts +131 -5
  155. package/dist/fixture-loader.d.ts.map +1 -1
  156. package/dist/fixture-loader.js +0 -0
  157. package/dist/fixture-loader.js.map +1 -1
  158. package/dist/gemini-embeddings.cjs +44 -4
  159. package/dist/gemini-embeddings.cjs.map +1 -1
  160. package/dist/gemini-embeddings.js +46 -6
  161. package/dist/gemini-embeddings.js.map +1 -1
  162. package/dist/gemini-interactions.cjs +51 -7
  163. package/dist/gemini-interactions.cjs.map +1 -1
  164. package/dist/gemini-interactions.d.cts.map +1 -1
  165. package/dist/gemini-interactions.d.ts.map +1 -1
  166. package/dist/gemini-interactions.js +53 -9
  167. package/dist/gemini-interactions.js.map +1 -1
  168. package/dist/gemini.cjs +19 -8
  169. package/dist/gemini.cjs.map +1 -1
  170. package/dist/gemini.d.cts.map +1 -1
  171. package/dist/gemini.d.ts.map +1 -1
  172. package/dist/gemini.js +21 -10
  173. package/dist/gemini.js.map +1 -1
  174. package/dist/grok-video.cjs +14 -14
  175. package/dist/grok-video.cjs.map +1 -1
  176. package/dist/grok-video.js +15 -15
  177. package/dist/grok-video.js.map +1 -1
  178. package/dist/helpers.cjs +497 -10
  179. package/dist/helpers.cjs.map +1 -1
  180. package/dist/helpers.d.cts +38 -2
  181. package/dist/helpers.d.cts.map +1 -1
  182. package/dist/helpers.d.ts +38 -2
  183. package/dist/helpers.d.ts.map +1 -1
  184. package/dist/helpers.js +487 -12
  185. package/dist/helpers.js.map +1 -1
  186. package/dist/images.cjs +57 -125
  187. package/dist/images.cjs.map +1 -1
  188. package/dist/images.d.cts.map +1 -1
  189. package/dist/images.d.ts.map +1 -1
  190. package/dist/images.js +58 -126
  191. package/dist/images.js.map +1 -1
  192. package/dist/index.cjs +71 -9
  193. package/dist/index.d.cts +17 -7
  194. package/dist/index.d.ts +17 -7
  195. package/dist/index.js +21 -11
  196. package/dist/jest.cjs +24 -12
  197. package/dist/jest.cjs.map +1 -1
  198. package/dist/jest.d.cts +6 -3
  199. package/dist/jest.d.cts.map +1 -1
  200. package/dist/jest.d.ts +6 -3
  201. package/dist/jest.d.ts.map +1 -1
  202. package/dist/jest.js +25 -13
  203. package/dist/jest.js.map +1 -1
  204. package/dist/journal.cjs +61 -4
  205. package/dist/journal.cjs.map +1 -1
  206. package/dist/journal.d.cts +60 -3
  207. package/dist/journal.d.cts.map +1 -1
  208. package/dist/journal.d.ts +60 -3
  209. package/dist/journal.d.ts.map +1 -1
  210. package/dist/journal.js +61 -5
  211. package/dist/journal.js.map +1 -1
  212. package/dist/live-fixture.cjs +1192 -0
  213. package/dist/live-fixture.cjs.map +1 -0
  214. package/dist/live-fixture.js +1187 -0
  215. package/dist/live-fixture.js.map +1 -0
  216. package/dist/live-recorder.cjs +256 -0
  217. package/dist/live-recorder.cjs.map +1 -0
  218. package/dist/live-recorder.js +254 -0
  219. package/dist/live-recorder.js.map +1 -0
  220. package/dist/live-replay.cjs +286 -0
  221. package/dist/live-replay.cjs.map +1 -0
  222. package/dist/live-replay.js +285 -0
  223. package/dist/live-replay.js.map +1 -0
  224. package/dist/live-sanitize.cjs +196 -0
  225. package/dist/live-sanitize.cjs.map +1 -0
  226. package/dist/live-sanitize.js +196 -0
  227. package/dist/live-sanitize.js.map +1 -0
  228. package/dist/live-types.d.cts +71 -0
  229. package/dist/live-types.d.cts.map +1 -0
  230. package/dist/live-types.d.ts +71 -0
  231. package/dist/live-types.d.ts.map +1 -0
  232. package/dist/llmock.cjs +132 -49
  233. package/dist/llmock.cjs.map +1 -1
  234. package/dist/llmock.d.cts +51 -5
  235. package/dist/llmock.d.cts.map +1 -1
  236. package/dist/llmock.d.ts +51 -5
  237. package/dist/llmock.d.ts.map +1 -1
  238. package/dist/llmock.js +133 -50
  239. package/dist/llmock.js.map +1 -1
  240. package/dist/mcp-fakes-mount.cjs +216 -0
  241. package/dist/mcp-fakes-mount.cjs.map +1 -0
  242. package/dist/mcp-fakes-mount.js +210 -0
  243. package/dist/mcp-fakes-mount.js.map +1 -0
  244. package/dist/mcp-fakes.cjs +1619 -0
  245. package/dist/mcp-fakes.cjs.map +1 -0
  246. package/dist/mcp-fakes.d.cts +101 -0
  247. package/dist/mcp-fakes.d.cts.map +1 -0
  248. package/dist/mcp-fakes.d.ts +101 -0
  249. package/dist/mcp-fakes.d.ts.map +1 -0
  250. package/dist/mcp-fakes.js +1614 -0
  251. package/dist/mcp-fakes.js.map +1 -0
  252. package/dist/mcp-handler.cjs +331 -17
  253. package/dist/mcp-handler.cjs.map +1 -1
  254. package/dist/mcp-handler.js +330 -18
  255. package/dist/mcp-handler.js.map +1 -1
  256. package/dist/mcp-mock.cjs +217 -49
  257. package/dist/mcp-mock.cjs.map +1 -1
  258. package/dist/mcp-mock.d.cts +66 -1
  259. package/dist/mcp-mock.d.cts.map +1 -1
  260. package/dist/mcp-mock.d.ts +66 -1
  261. package/dist/mcp-mock.d.ts.map +1 -1
  262. package/dist/mcp-mock.js +218 -50
  263. package/dist/mcp-mock.js.map +1 -1
  264. package/dist/mcp-stub.cjs +5 -1
  265. package/dist/mcp-stub.d.cts +3 -1
  266. package/dist/mcp-stub.d.ts +3 -1
  267. package/dist/mcp-stub.js +3 -1
  268. package/dist/mcp-types.d.cts +6 -0
  269. package/dist/mcp-types.d.ts +6 -0
  270. package/dist/message-text.cjs +192 -0
  271. package/dist/message-text.cjs.map +1 -0
  272. package/dist/message-text.d.cts +31 -0
  273. package/dist/message-text.d.cts.map +1 -0
  274. package/dist/message-text.d.ts +31 -0
  275. package/dist/message-text.d.ts.map +1 -0
  276. package/dist/message-text.js +184 -0
  277. package/dist/message-text.js.map +1 -0
  278. package/dist/messages.cjs +49 -7
  279. package/dist/messages.cjs.map +1 -1
  280. package/dist/messages.d.cts.map +1 -1
  281. package/dist/messages.d.ts.map +1 -1
  282. package/dist/messages.js +51 -9
  283. package/dist/messages.js.map +1 -1
  284. package/dist/metrics.cjs +278 -45
  285. package/dist/metrics.cjs.map +1 -1
  286. package/dist/metrics.d.cts +17 -7
  287. package/dist/metrics.d.cts.map +1 -1
  288. package/dist/metrics.d.ts +17 -7
  289. package/dist/metrics.d.ts.map +1 -1
  290. package/dist/metrics.js +270 -29
  291. package/dist/metrics.js.map +1 -1
  292. package/dist/moderation.cjs +10 -1
  293. package/dist/moderation.cjs.map +1 -1
  294. package/dist/moderation.d.cts.map +1 -1
  295. package/dist/moderation.d.ts.map +1 -1
  296. package/dist/moderation.js +10 -1
  297. package/dist/moderation.js.map +1 -1
  298. package/dist/ollama.cjs +10 -6
  299. package/dist/ollama.cjs.map +1 -1
  300. package/dist/ollama.d.cts.map +1 -1
  301. package/dist/ollama.d.ts.map +1 -1
  302. package/dist/ollama.js +11 -7
  303. package/dist/ollama.js.map +1 -1
  304. package/dist/openapi.cjs +3405 -0
  305. package/dist/openapi.cjs.map +1 -0
  306. package/dist/openapi.d.cts +30 -0
  307. package/dist/openapi.d.cts.map +1 -0
  308. package/dist/openapi.d.ts +30 -0
  309. package/dist/openapi.d.ts.map +1 -0
  310. package/dist/openapi.js +3404 -0
  311. package/dist/openapi.js.map +1 -0
  312. package/dist/openrouter-video.cjs +9 -9
  313. package/dist/openrouter-video.cjs.map +1 -1
  314. package/dist/openrouter-video.d.cts.map +1 -1
  315. package/dist/openrouter-video.d.ts.map +1 -1
  316. package/dist/openrouter-video.js +10 -10
  317. package/dist/openrouter-video.js.map +1 -1
  318. package/dist/recorder.cjs +54 -10
  319. package/dist/recorder.cjs.map +1 -1
  320. package/dist/recorder.d.cts.map +1 -1
  321. package/dist/recorder.d.ts.map +1 -1
  322. package/dist/recorder.js +52 -12
  323. package/dist/recorder.js.map +1 -1
  324. package/dist/responses.cjs +47 -7
  325. package/dist/responses.cjs.map +1 -1
  326. package/dist/responses.d.cts.map +1 -1
  327. package/dist/responses.d.ts.map +1 -1
  328. package/dist/responses.js +48 -9
  329. package/dist/responses.js.map +1 -1
  330. package/dist/route-registry.cjs +951 -0
  331. package/dist/route-registry.cjs.map +1 -0
  332. package/dist/route-registry.d.cts +33 -0
  333. package/dist/route-registry.d.cts.map +1 -0
  334. package/dist/route-registry.d.ts +33 -0
  335. package/dist/route-registry.d.ts.map +1 -0
  336. package/dist/route-registry.js +866 -0
  337. package/dist/route-registry.js.map +1 -0
  338. package/dist/router.cjs +21 -34
  339. package/dist/router.cjs.map +1 -1
  340. package/dist/router.d.cts +4 -1
  341. package/dist/router.d.cts.map +1 -1
  342. package/dist/router.d.ts +4 -1
  343. package/dist/router.d.ts.map +1 -1
  344. package/dist/router.js +21 -35
  345. package/dist/router.js.map +1 -1
  346. package/dist/server.cjs +1252 -777
  347. package/dist/server.cjs.map +1 -1
  348. package/dist/server.d.cts +5 -1
  349. package/dist/server.d.cts.map +1 -1
  350. package/dist/server.d.ts +5 -1
  351. package/dist/server.d.ts.map +1 -1
  352. package/dist/server.js +1185 -710
  353. package/dist/server.js.map +1 -1
  354. package/dist/speech.cjs +6 -4
  355. package/dist/speech.cjs.map +1 -1
  356. package/dist/speech.js +7 -5
  357. package/dist/speech.js.map +1 -1
  358. package/dist/suite.cjs +1 -1
  359. package/dist/suite.js +1 -1
  360. package/dist/transcription.cjs +6 -6
  361. package/dist/transcription.cjs.map +1 -1
  362. package/dist/transcription.d.cts.map +1 -1
  363. package/dist/transcription.d.ts.map +1 -1
  364. package/dist/transcription.js +7 -7
  365. package/dist/transcription.js.map +1 -1
  366. package/dist/types.cjs +18 -0
  367. package/dist/types.cjs.map +1 -0
  368. package/dist/types.d.cts +320 -17
  369. package/dist/types.d.cts.map +1 -1
  370. package/dist/types.d.ts +320 -17
  371. package/dist/types.d.ts.map +1 -1
  372. package/dist/types.js +17 -0
  373. package/dist/types.js.map +1 -0
  374. package/dist/validate-cli.cjs +983 -0
  375. package/dist/validate-cli.cjs.map +1 -0
  376. package/dist/validate-cli.js +982 -0
  377. package/dist/validate-cli.js.map +1 -0
  378. package/dist/vector-stores.cjs +1180 -0
  379. package/dist/vector-stores.cjs.map +1 -0
  380. package/dist/vector-stores.d.cts +106 -0
  381. package/dist/vector-stores.d.cts.map +1 -0
  382. package/dist/vector-stores.d.ts +106 -0
  383. package/dist/vector-stores.d.ts.map +1 -0
  384. package/dist/vector-stores.js +1164 -0
  385. package/dist/vector-stores.js.map +1 -0
  386. package/dist/vector-types.d.cts.map +1 -1
  387. package/dist/vector-types.d.ts.map +1 -1
  388. package/dist/veo-video.cjs +13 -13
  389. package/dist/veo-video.cjs.map +1 -1
  390. package/dist/veo-video.js +14 -14
  391. package/dist/veo-video.js.map +1 -1
  392. package/dist/video.cjs +23 -4
  393. package/dist/video.cjs.map +1 -1
  394. package/dist/video.d.cts +1 -1
  395. package/dist/video.d.cts.map +1 -1
  396. package/dist/video.d.ts +1 -1
  397. package/dist/video.d.ts.map +1 -1
  398. package/dist/video.js +24 -5
  399. package/dist/video.js.map +1 -1
  400. package/dist/vitest.cjs +23 -11
  401. package/dist/vitest.cjs.map +1 -1
  402. package/dist/vitest.d.cts +5 -2
  403. package/dist/vitest.d.cts.map +1 -1
  404. package/dist/vitest.d.ts +5 -2
  405. package/dist/vitest.d.ts.map +1 -1
  406. package/dist/vitest.js +25 -13
  407. package/dist/vitest.js.map +1 -1
  408. package/dist/watcher.cjs +2 -1
  409. package/dist/watcher.cjs.map +1 -1
  410. package/dist/watcher.js +2 -1
  411. package/dist/watcher.js.map +1 -1
  412. package/dist/ws-framing.cjs +150 -35
  413. package/dist/ws-framing.cjs.map +1 -1
  414. package/dist/ws-framing.d.cts +18 -3
  415. package/dist/ws-framing.d.cts.map +1 -1
  416. package/dist/ws-framing.d.ts +18 -3
  417. package/dist/ws-framing.d.ts.map +1 -1
  418. package/dist/ws-framing.js +151 -36
  419. package/dist/ws-framing.js.map +1 -1
  420. package/dist/ws-gemini-live.cjs +2 -2
  421. package/dist/ws-gemini-live.js +2 -2
  422. package/dist/ws-live.cjs +401 -0
  423. package/dist/ws-live.cjs.map +1 -0
  424. package/dist/ws-live.js +400 -0
  425. package/dist/ws-live.js.map +1 -0
  426. package/dist/ws-realtime.cjs +46 -2
  427. package/dist/ws-realtime.cjs.map +1 -1
  428. package/dist/ws-realtime.d.cts.map +1 -1
  429. package/dist/ws-realtime.d.ts.map +1 -1
  430. package/dist/ws-realtime.js +46 -2
  431. package/dist/ws-realtime.js.map +1 -1
  432. package/dist/ws-responses.cjs +16 -4
  433. package/dist/ws-responses.cjs.map +1 -1
  434. package/dist/ws-responses.js +16 -4
  435. package/dist/ws-responses.js.map +1 -1
  436. package/dist/ws-upstream.cjs +118 -0
  437. package/dist/ws-upstream.cjs.map +1 -0
  438. package/dist/ws-upstream.js +115 -0
  439. package/dist/ws-upstream.js.map +1 -0
  440. package/fixtures/openai-live-client.json +390 -0
  441. package/fixtures/openai-live-managed.json +709 -0
  442. package/package.json +4 -1
@@ -0,0 +1,1033 @@
1
+ const require_helpers = require('./helpers.cjs');
2
+ const require_chaos = require('./chaos.cjs');
3
+
4
+ //#region src/fine-tuning.ts
5
+ /**
6
+ * The statuses a job never leaves. Single source of truth: both `advance()`
7
+ * (which refuses to move a terminal job) and the cancel handler (which rejects
8
+ * a terminal job) read this set, so the two cannot drift apart again.
9
+ */
10
+ const TERMINAL_STATUSES = new Set([
11
+ "succeeded",
12
+ "cancelled",
13
+ "failed"
14
+ ]);
15
+ function isTerminal(status) {
16
+ return TERMINAL_STATUSES.has(status);
17
+ }
18
+ /** Stable stand-in for the owning org; the vendor requires the field, not a value. */
19
+ const ORGANIZATION_ID = "org-aimock";
20
+ const jobs = /* @__PURE__ */ new Map();
21
+ const polls = /* @__PURE__ */ new Map();
22
+ /**
23
+ * The append-only event log per job id, kept beside the job rather than on it
24
+ * so it never leaks into the `fine_tuning.job` payload. `appendEvent` is the
25
+ * only writer of entries, and only by pushing, so an event's index — and
26
+ * therefore its id — is fixed the moment it is recorded. The one other writer
27
+ * is `clearFineTuningStore`, which empties the whole map: a reset drops every
28
+ * log wholesale and never renumbers a surviving one.
29
+ */
30
+ const eventLogs = /* @__PURE__ */ new Map();
31
+ /**
32
+ * The last second this module stamped, which is what `stamp()` reads and
33
+ * writes. Module-wide rather than per job: a per-job clamp says nothing about
34
+ * two different jobs, and ordering ACROSS jobs is what the list endpoint's
35
+ * newest-first page rests on.
36
+ */
37
+ let lastStamp = 0;
38
+ /**
39
+ * The one clock the whole surface stamps from.
40
+ *
41
+ * The invariant: every timestamp this module ever exposes - every job's
42
+ * `created_at`, every event's `created_at`, and every `finished_at` - is
43
+ * non-decreasing in the order the module produced it, whichever way the wall
44
+ * clock moves between two stamps, and across all jobs rather than within one.
45
+ * The per-job reading is a consequence: a job's own stamps are a subsequence
46
+ * of the module's, so they are non-decreasing too.
47
+ *
48
+ * Wall clock is the source, so a forward-running clock shows through
49
+ * unchanged; a backwards step (NTP correction, a resumed VM, a test driving
50
+ * `Date.now`) is absorbed by holding the previous value rather than emitting a
51
+ * history that reads out of order. That makes this the only place a
52
+ * fine-tuning timestamp may come from - a second clamp somewhere downstream is
53
+ * exactly what used to be able to undo this one.
54
+ */
55
+ function stamp() {
56
+ lastStamp = Math.max(Math.floor(Date.now() / 1e3), lastStamp);
57
+ return lastStamp;
58
+ }
59
+ /**
60
+ * The `suffix` the create body asked for, per job id. Kept beside the job
61
+ * because `suffix` is a member of `CreateFineTuningJobRequest` and NOT of
62
+ * `fine_tuning.job`: storing it on the job would put a field on the wire that
63
+ * the vendor never sends. It is read once, when the job succeeds and takes its
64
+ * `fine_tuned_model` name.
65
+ */
66
+ const suffixes = /* @__PURE__ */ new Map();
67
+ function clearFineTuningStore() {
68
+ jobs.clear();
69
+ polls.clear();
70
+ eventLogs.clear();
71
+ lastStamp = 0;
72
+ suffixes.clear();
73
+ }
74
+ /**
75
+ * Journal one fine-tuning request.
76
+ *
77
+ * `body` is the request body the handler actually parsed (create), and is null
78
+ * in three cases: the bodyless GET/cancel routes, a create whose body never
79
+ * parsed into a JSON object, and any request the chaos gate faulted — `chaosHit`
80
+ * journals through `applyChaosAsync` with `body: null` because it rolls before
81
+ * the body is read at all. Recording it otherwise is what makes "inspect the
82
+ * request you sent" work against this surface: every create that got as far as
83
+ * a parsed JSON object carries it, every rejection included, because those are
84
+ * exactly the 400s whose offending payload a caller needs to read back.
85
+ *
86
+ * `path` is the raw request URL, query string INCLUDED. That is not cosmetic:
87
+ * `entry.path` is the only place an untagged-by-header `?testId=` survives into
88
+ * the journal, and `journalEntryTestId` (server.ts) parses it back out of
89
+ * `entry.path` to serve `GET /__aimock/journal?testId=…`. Stripping the query
90
+ * here — as `fal.ts` does with its `pathname` — silently drops every
91
+ * query-tagged fine-tuning request out of its own test's slice. It also matches
92
+ * the repo majority (images, responses, messages, moderation, embeddings,
93
+ * rerank, cohere, gemini, search and vector all journal `req.url`). A caller
94
+ * that wants route-level grouping should match on `service` or on a prefix,
95
+ * not on string equality against a bare path.
96
+ *
97
+ * One route reaches a response without reaching this function: a
98
+ * `POST /v1/fine_tuning/jobs` whose body read throws (over the size cap, or a
99
+ * socket error) is caught in `server.ts` and answered 500 before
100
+ * `handleFineTuningCreate` runs, so it is never journaled. That is deliberate
101
+ * consistency, not an oversight — every sibling route's body-read catch in
102
+ * `server.ts` (responses, messages, cohere, moderation, embeddings, rerank,
103
+ * the video surfaces) writes the same unjournaled 500. Journaling it here alone
104
+ * would make fine-tuning the one surface whose journal disagrees with the rest.
105
+ *
106
+ * `source: "internal"` marks the entry as served by aimock's own synthetic job
107
+ * logic rather than a fixture or a proxy, which is the same value this file's
108
+ * own chaos gate passes for a faulted request. It is a rare value elsewhere:
109
+ * `grep -rn 'source: "internal"' src/*.ts` finds it on exactly two other
110
+ * writers, `images.ts`'s removed-endpoint 404 and `openrouter-video.ts`'s
111
+ * models listing synthesized after a failed proxy attempt — every other
112
+ * service's synthesized journal writes `source: "proxy"`. Fine-tuning is the
113
+ * one surface where every entry carries it, success and error alike, because
114
+ * no fixture or proxy ever serves this store.
115
+ */
116
+ function journalFt(journal, method, path, headers, status, body = null) {
117
+ journal.add({
118
+ method,
119
+ path,
120
+ headers,
121
+ body,
122
+ service: "fine-tuning",
123
+ response: {
124
+ status,
125
+ fixture: null,
126
+ source: "internal"
127
+ }
128
+ });
129
+ }
130
+ function writeJson(res, status, payload, setCorsHeaders) {
131
+ setCorsHeaders(res);
132
+ res.writeHead(status, { "Content-Type": "application/json" });
133
+ res.end(JSON.stringify(payload));
134
+ }
135
+ function invalid(message) {
136
+ return { error: {
137
+ message,
138
+ type: "invalid_request_error"
139
+ } };
140
+ }
141
+ /**
142
+ * Roll the chaos dice for one fine-tuning request. Returns true when a fault
143
+ * fired and the response is already written, so the caller returns early.
144
+ *
145
+ * CORS headers go on BEFORE the roll, because a fault writes the response
146
+ * itself and never reaches `writeJson` (the only other place this file sets
147
+ * them). Without this a browser client under chaos reads an opaque CORS
148
+ * failure instead of the 429/500/malformed body the chaos config asked for —
149
+ * exactly the fault it is trying to exercise, hidden. `setCorsHeaders` uses
150
+ * `res.setHeader`, so the later `writeHead` on the non-fault path merges with
151
+ * it rather than replacing it, and setting them twice is a no-op.
152
+ * `src/images.ts` orders it the same way (CORS at the top of the handler,
153
+ * chaos gate after).
154
+ */
155
+ async function chaosHit(req, journal, defaults, method, path, res, setCorsHeaders) {
156
+ setCorsHeaders(res);
157
+ return await require_chaos.applyChaosAsync(res, null, defaults.chaos, req.headers, req.url, journal, {
158
+ method,
159
+ path,
160
+ headers: require_helpers.flattenHeaders(req.headers),
161
+ body: null,
162
+ service: "fine-tuning"
163
+ }, "internal", defaults.registry, defaults.logger);
164
+ }
165
+ /**
166
+ * Record one thing that just happened to `job`. Call sites append only at the
167
+ * moment of the transition they describe, so the log is the job's real
168
+ * history rather than a reconstruction from its current status.
169
+ *
170
+ * `created_at` comes from `stamp()`, so a log that reads out of order is not
171
+ * representable. Returns the second it stamped, which is what a terminal
172
+ * transition assigns to `finished_at` so that nothing can follow the finish.
173
+ */
174
+ function appendEvent(job, message) {
175
+ const log = eventLogs.get(job.id) ?? [];
176
+ const createdAt = stamp();
177
+ log.push({
178
+ id: `ftevent-${job.id.replace(/^ftjob-/, "")}-${log.length}`,
179
+ object: "fine_tuning.job.event",
180
+ created_at: createdAt,
181
+ level: "info",
182
+ message,
183
+ type: "message"
184
+ });
185
+ eventLogs.set(job.id, log);
186
+ return createdAt;
187
+ }
188
+ function advance(job) {
189
+ if (isTerminal(job.status)) return job;
190
+ const n = (polls.get(job.id) ?? 0) + 1;
191
+ polls.set(job.id, n);
192
+ if (n === 1) {
193
+ job.status = "queued";
194
+ appendEvent(job, "Training file validated, job queued");
195
+ } else if (n === 2) {
196
+ job.status = "running";
197
+ appendEvent(job, "Training started");
198
+ } else {
199
+ job.status = "succeeded";
200
+ const suffix = suffixes.get(job.id);
201
+ const tail = suffix === void 0 ? "" : `${suffix}:`;
202
+ job.fine_tuned_model = `ft:${job.model}:aimock:${tail}${job.id.slice(-6)}`;
203
+ job.result_files = [`file-${job.id.replace(/^ftjob-/, "")}`];
204
+ job.trained_tokens = job.seed % 1e5;
205
+ job.finished_at = appendEvent(job, `Training complete, model ${job.fine_tuned_model}`);
206
+ }
207
+ return job;
208
+ }
209
+ function eventsFor(job) {
210
+ return [...eventLogs.get(job.id) ?? []];
211
+ }
212
+ /** `limit` default for both fine-tuning list endpoints (openai-openapi v2.3.0). */
213
+ const DEFAULT_PAGE_LIMIT = 20;
214
+ /** Upper bound we enforce on `limit`. Ours, not the vendor's — see `paginate`. */
215
+ const MAX_PAGE_LIMIT = 100;
216
+ /**
217
+ * The query parameters of a request target.
218
+ *
219
+ * The query is everything after the FIRST `?`, per RFC 3986 section 3.4
220
+ * ("The query component ... is terminated by a number sign (#) character or by
221
+ * the end of the URI") read together with RFC 9112 section 3.2: a request
222
+ * target is an origin-form `absolute-path [ "?" query ]` and carries no
223
+ * fragment component at all. Both halves of that matter here:
224
+ *
225
+ * - `split("?")[1]` truncates at a SECOND `?`, which is a legal literal
226
+ * inside a query, so `?a=1?b=2&limit=5` used to lose `limit` entirely.
227
+ * Slicing from the first `?` keeps it.
228
+ * - `new URL(target, base).searchParams` — what the router and the other
229
+ * query-reading mocks use — truncates at a `#` instead, because `URL`
230
+ * implements the fragment rule that a request target does not have. A
231
+ * caller that sends `?after=abc#def` on the wire means the cursor
232
+ * `abc#def`; reading it as `abc` is the same silent truncation one `?`
233
+ * along, and it turns a bad cursor into a plausible-looking one. An origin
234
+ * server sees no fragment, so neither does this.
235
+ *
236
+ * `new URLSearchParams(string)` cannot throw, so there is no parse failure to
237
+ * swallow and no defaults-fallback to hide one: every byte of the target after
238
+ * the first `?` is query data by definition. (A target malformed enough to
239
+ * break `new URL` never reaches this module — `src/server.ts` builds a `URL`
240
+ * to route on and fails there first.)
241
+ */
242
+ function queryParams(url) {
243
+ const target = url ?? "/";
244
+ const start = target.indexOf("?");
245
+ return new URLSearchParams(start === -1 ? "" : target.slice(start + 1));
246
+ }
247
+ /**
248
+ * The RAW query text a parameter was sent with - what the caller typed, before
249
+ * percent- and `+`-decoding - or null when it was not sent at all.
250
+ *
251
+ * The unknown-`after` 400 below quotes the offending cursor, and the docs
252
+ * promise it quotes "what you sent". `URLSearchParams` hands back the DECODED
253
+ * value, so `?after=a%2Bb` was refused as `'a+b'` and `?after=a+b` as `'a b'` -
254
+ * neither of which appears anywhere in the request the caller is staring at,
255
+ * which is the one thing an error quoting a value is for.
256
+ *
257
+ * Pair splitting mirrors `URLSearchParams` itself, which splits on `&` alone
258
+ * (`;` has not been a separator since the URL spec absorbed it), and the NAME
259
+ * is matched through the platform's own decoder - one `URLSearchParams` per
260
+ * pair - so an encoded spelling of the name still matches and nothing here
261
+ * hand-rolls the percent codec. Only the VALUE is kept as raw text.
262
+ */
263
+ function rawParam(url, name) {
264
+ const target = url ?? "/";
265
+ const start = target.indexOf("?");
266
+ if (start === -1) return null;
267
+ for (const pair of target.slice(start + 1).split("&")) {
268
+ if (pair.length === 0) continue;
269
+ const [decodedName] = new URLSearchParams(pair).keys();
270
+ if (decodedName !== name) continue;
271
+ const eq = pair.indexOf("=");
272
+ return eq === -1 ? "" : pair.slice(eq + 1);
273
+ }
274
+ return null;
275
+ }
276
+ /**
277
+ * The single value of a query parameter, or the 400 a repeated one earns.
278
+ *
279
+ * `URLSearchParams.get` answers with the FIRST of a repeated parameter, which
280
+ * launders the rest: `?limit=1&limit=abc` used to page at 1 and never look at
281
+ * `abc`, so a caller building a URL twice over got a silent success where the
282
+ * value it actually meant was invalid. The spec declares each of `limit` and
283
+ * `after` as a single scalar (`listPaginatedFineTuningJobs`,
284
+ * `listFineTuningEvents`) and gives no combining rule for a repeat, so a repeat
285
+ * is a caller error here. That the 400 (rather than last-wins, or first-wins)
286
+ * is ours, not the vendor's, is recorded in this module's header.
287
+ */
288
+ function singleParam(params, name) {
289
+ const all = params.getAll(name);
290
+ if (all.length > 1) return {
291
+ ok: false,
292
+ message: `Invalid parameter: '${name}' was given ${all.length} times; it takes a single value`
293
+ };
294
+ return {
295
+ ok: true,
296
+ value: all.length === 0 ? null : all[0]
297
+ };
298
+ }
299
+ /**
300
+ * Cursor page over an array already ordered newest-first, mirroring the
301
+ * `after`/`limit` params both fine-tuning list endpoints accept. `has_more`
302
+ * reports whether anything was left behind, so it is never a constant.
303
+ *
304
+ * Sourced 2026-09-15 from openai/openai-openapi `openapi.yaml` v2.3.0
305
+ * (`listPaginatedFineTuningJobs`, `listFineTuningEvents`) and the vendored
306
+ * `openai` SDK 4.104.0 `pagination.d.ts`/`pagination.js` `CursorPage`:
307
+ * - `after` is a free-form string id; `CursorPage.nextPageInfo()` feeds the
308
+ * LAST id of the page back as `after`, and `hasNextPage()` stops on
309
+ * `has_more: false`. Confirmed by the spec.
310
+ * - `limit` is `type: integer, default: 20` on both endpoints. Confirmed by
311
+ * the spec; the default is the vendor's.
312
+ *
313
+ * Three rules the spec does NOT state, chosen here and flagged as ours:
314
+ * - Upper bound 100 on `limit`. The spec declares no `maximum`. We enforce
315
+ * one so an oversized page fails loudly instead of being silently clamped,
316
+ * and 100 is the number our own docs page already advertises.
317
+ * - An `after` that matches no item is a 400 rather than a silent page 1.
318
+ * Returning page 1 makes the SDK's `CursorPage` auto-paginator restart from
319
+ * the top forever on a stale cursor; failing loudly is the whole point of a
320
+ * mock. Not observed against api.openai.com.
321
+ * - Newest-first ordering. Neither list endpoint accepts an `order` param and
322
+ * the spec never states a direction; the `listFineTuningEvents` example
323
+ * response lists the completion event ahead of the earlier
324
+ * model-created event, which reads as reverse-chronological.
325
+ */
326
+ function paginate(items, url) {
327
+ const params = queryParams(url);
328
+ const readLimit = singleParam(params, "limit");
329
+ if (!readLimit.ok) return {
330
+ ok: false,
331
+ message: readLimit.message
332
+ };
333
+ const rawLimit = readLimit.value;
334
+ let limit = DEFAULT_PAGE_LIMIT;
335
+ if (rawLimit !== null) {
336
+ const n = require_helpers.parseStrictIntegerText(rawLimit);
337
+ if (n === null || n < 1 || n > MAX_PAGE_LIMIT) return {
338
+ ok: false,
339
+ message: `Invalid parameter: 'limit' must be an integer between 1 and ${MAX_PAGE_LIMIT}, got '${rawParam(url, "limit") ?? rawLimit}'`
340
+ };
341
+ limit = n;
342
+ }
343
+ const readAfter = singleParam(params, "after");
344
+ if (!readAfter.ok) return {
345
+ ok: false,
346
+ message: readAfter.message
347
+ };
348
+ const after = readAfter.value;
349
+ let start = 0;
350
+ if (after !== null) {
351
+ const idx = items.findIndex((it) => it.id === after);
352
+ if (idx === -1) return {
353
+ ok: false,
354
+ message: `Invalid parameter: 'after' is not a known cursor: '${rawParam(url, "after") ?? after}'`
355
+ };
356
+ start = idx + 1;
357
+ }
358
+ const rest = items.slice(start);
359
+ return {
360
+ ok: true,
361
+ page: {
362
+ object: "list",
363
+ data: rest.slice(0, limit),
364
+ has_more: rest.length > limit
365
+ }
366
+ };
367
+ }
368
+ /** Write a paginated list, or the 400 its `after`/`limit` params earned. */
369
+ function writePage(res, journal, req, method, path, items, setCorsHeaders) {
370
+ const result = paginate(items, req.url);
371
+ const status = result.ok ? 200 : 400;
372
+ journalFt(journal, method, path, require_helpers.flattenHeaders(req.headers), status);
373
+ writeJson(res, status, result.ok ? result.page : invalid(result.message), setCorsHeaders);
374
+ }
375
+ /**
376
+ * Deterministic seed in the spec's `[0, 2147483647]` range, derived from the
377
+ * job id so repeated reads of the same job report the same seed.
378
+ */
379
+ function seedFor(id) {
380
+ let h = 2166136261;
381
+ for (let i = 0; i < id.length; i++) h = Math.imul(h ^ id.charCodeAt(i), 16777619);
382
+ return (h >>> 0) % 2147483648;
383
+ }
384
+ /**
385
+ * The three members `CreateFineTuningJobRequest.hyperparameters` and
386
+ * `FineTuneSupervisedHyperparameters` both declare, quoted from
387
+ * openai/openai-openapi `openapi.yaml` v2.3.0 (`master`):
388
+ * - `n_epochs`: `type: integer, minimum: 1, maximum: 50`
389
+ * - `batch_size`: `type: integer, minimum: 1, maximum: 256`
390
+ * - `learning_rate_multiplier`: `type: number, minimum: 0,
391
+ * exclusiveMinimum: true` — strictly greater than zero, hence no `max`.
392
+ *
393
+ * The vendored SDK's `JobCreateParams.Hyperparameters` types each member as
394
+ * `"auto" | number`, which is as much as TypeScript can say; the bounds only
395
+ * exist in the spec, so they are enforced here from it.
396
+ */
397
+ const SUPERVISED_HYPER_RULES = {
398
+ n_epochs: {
399
+ kind: "integer",
400
+ min: 1,
401
+ max: 50
402
+ },
403
+ batch_size: {
404
+ kind: "integer",
405
+ min: 1,
406
+ max: 256
407
+ },
408
+ learning_rate_multiplier: {
409
+ kind: "number",
410
+ exclusiveMin: 0,
411
+ max: null
412
+ }
413
+ };
414
+ /**
415
+ * The deprecated top-level `hyperparameters` object. The spec declares exactly
416
+ * the three supervised members on it — `beta` and the reinforcement knobs live
417
+ * only under `method`, and the description says this whole object "is now
418
+ * deprecated in favor of `method`".
419
+ */
420
+ const LEGACY_HYPER_RULES = SUPERVISED_HYPER_RULES;
421
+ /**
422
+ * `FineTuneDPOHyperparameters`: the supervised three plus `beta`, declared
423
+ * `type: number, minimum: 0, exclusiveMinimum: true, maximum: 2`.
424
+ */
425
+ const DPO_HYPER_RULES = {
426
+ ...SUPERVISED_HYPER_RULES,
427
+ beta: {
428
+ kind: "number",
429
+ exclusiveMin: 0,
430
+ max: 2
431
+ }
432
+ };
433
+ /**
434
+ * `FineTuneReinforcementHyperparameters`: the supervised three plus
435
+ * `reasoning_effort` (`enum: [default, low, medium, high]`, no `"auto"`),
436
+ * `compute_multiplier` (`minimum: 0.00001, exclusiveMinimum: true,
437
+ * maximum: 10`), and `eval_interval`/`eval_samples` (`type: integer,
438
+ * minimum: 1`, no declared maximum).
439
+ */
440
+ const REINFORCEMENT_HYPER_RULES = {
441
+ ...SUPERVISED_HYPER_RULES,
442
+ reasoning_effort: {
443
+ kind: "enum",
444
+ values: [
445
+ "default",
446
+ "low",
447
+ "medium",
448
+ "high"
449
+ ]
450
+ },
451
+ compute_multiplier: {
452
+ kind: "number",
453
+ exclusiveMin: 1e-5,
454
+ max: 10
455
+ },
456
+ eval_interval: {
457
+ kind: "integer",
458
+ min: 1,
459
+ max: null
460
+ },
461
+ eval_samples: {
462
+ kind: "integer",
463
+ min: 1,
464
+ max: null
465
+ }
466
+ };
467
+ /** How a rule reads in an error message, so the caller learns the domain. */
468
+ function describeRule(rule) {
469
+ if (rule.kind === "enum") return `one of ${rule.values.map((v) => `"${v}"`).join(", ")}`;
470
+ const upper = rule.max === null ? "" : ` and at most ${rule.max}`;
471
+ return `${rule.kind === "integer" ? `an integer at least ${rule.min}` : `a number greater than ${rule.exclusiveMin}`}${upper}, or the string "auto"`;
472
+ }
473
+ /**
474
+ * Reads one hyperparameter against its rule. Anything outside the rule is a
475
+ * caller error, never a silent drop — a mock that accepted `n_epochs: 0` would
476
+ * let a bug reach the real API instead of failing where it is cheap to see.
477
+ *
478
+ * `path` names the object the member came from (`hyperparameters`,
479
+ * `method.dpo.hyperparameters`, …) so the message points at the thing the
480
+ * caller actually wrote.
481
+ */
482
+ function readHyper(table, path, key, value) {
483
+ const rule = Object.hasOwn(table, key) ? table[key] : void 0;
484
+ if (rule === void 0) return {
485
+ ok: false,
486
+ message: unknownMemberMessage(table, path, key)
487
+ };
488
+ const bad = {
489
+ ok: false,
490
+ message: `Invalid parameter: '${path}.${key}' must be ${describeRule(rule)}`
491
+ };
492
+ if (rule.kind === "enum") return typeof value === "string" && rule.values.includes(value) ? {
493
+ ok: true,
494
+ value
495
+ } : bad;
496
+ if (value === "auto") return {
497
+ ok: true,
498
+ value: "auto"
499
+ };
500
+ if (typeof value !== "number" || !Number.isFinite(value)) return bad;
501
+ if (rule.max !== null && value > rule.max) return bad;
502
+ if (rule.kind === "integer") return value >= rule.min && Number.isInteger(value) ? {
503
+ ok: true,
504
+ value
505
+ } : bad;
506
+ return value > rule.exclusiveMin ? {
507
+ ok: true,
508
+ value
509
+ } : bad;
510
+ }
511
+ /** The 400 an unrecognised member of a rule-table-backed object earns. */
512
+ function unknownMemberMessage(table, path, key) {
513
+ return `Invalid parameter: '${path}.${key}' is not a recognized member of '${path}'; it accepts ${Object.keys(table).join(", ")}`;
514
+ }
515
+ /**
516
+ * Reads a whole hyperparameters object against its rule table.
517
+ *
518
+ * An unrecognised member is a 400 rather than a silent drop. The spec does not
519
+ * mark these objects `additionalProperties: false`, so this rule is ours, not
520
+ * the vendor's — and it is the point of the surface: `{n_epochs: 3, beta: 0.5}`
521
+ * used to answer 200 echoing only `n_epochs`, so a caller who put a real DPO
522
+ * hyperparameter (`FineTuneDPOHyperparameters.beta`) in the deprecated
523
+ * top-level object instead of under `method.dpo.hyperparameters` was told it
524
+ * had been accepted. Naming the members the object does take is what turns
525
+ * that into a fixable message.
526
+ */
527
+ function readHyperObject(table, path, raw) {
528
+ const out = {};
529
+ for (const [key, value] of Object.entries(raw)) {
530
+ if (value === void 0) continue;
531
+ if (!Object.hasOwn(table, key)) return {
532
+ ok: false,
533
+ message: unknownMemberMessage(table, path, key)
534
+ };
535
+ const read = readHyper(table, path, key, value);
536
+ if (!read.ok) return read;
537
+ out[key] = read.value;
538
+ }
539
+ return {
540
+ ok: true,
541
+ value: out
542
+ };
543
+ }
544
+ /**
545
+ * Every property `CreateFineTuningJobRequest` declares (openai-openapi v2.3.0).
546
+ *
547
+ * Each one is read below; nothing on this list is accepted and dropped, and
548
+ * anything NOT on it is a 400. The spec does not mark the request object
549
+ * `additionalProperties: false`, so rejecting an unknown key is this mock's
550
+ * rule, not the vendor's: a create body is written once and reused for months,
551
+ * and a typo that answers 200 having ignored the field is exactly the bug a
552
+ * mock exists to catch.
553
+ */
554
+ const CREATE_KEYS = [
555
+ "model",
556
+ "training_file",
557
+ "hyperparameters",
558
+ "suffix",
559
+ "validation_file",
560
+ "integrations",
561
+ "seed",
562
+ "method",
563
+ "metadata"
564
+ ];
565
+ /** The members of the deprecated top-level `hyperparameters`, as the job echoes them. */
566
+ const LEGACY_HYPER_KEYS = [
567
+ "n_epochs",
568
+ "batch_size",
569
+ "learning_rate_multiplier"
570
+ ];
571
+ /** `Metadata` in the spec: at most 16 pairs, keys <= 64 chars, values <= 512. */
572
+ const METADATA_MAX_PAIRS = 16;
573
+ const METADATA_MAX_KEY = 64;
574
+ const METADATA_MAX_VALUE = 512;
575
+ /** `CreateFineTuningJobRequest.suffix`: `minLength: 1, maxLength: 64`. */
576
+ const SUFFIX_MAX_LENGTH = 64;
577
+ /** `CreateFineTuningJobRequest.seed`: `minimum: 0, maximum: 2147483647`. */
578
+ const SEED_MAX = 2147483647;
579
+ /** `FineTuningJob.integrations`: `maxItems: 5`. */
580
+ const MAX_INTEGRATIONS = 5;
581
+ /**
582
+ * Length in Unicode CODE POINTS, which is the unit the spec's limits are in.
583
+ *
584
+ * Every `maxLength` quoted in this module is a pydantic `max_length` on the
585
+ * vendor's side, and pydantic counts code points; `String.prototype.length`
586
+ * counts UTF-16 code units, which is the same number only while the string
587
+ * stays inside the BMP. One astral character - an emoji, a rarer CJK
588
+ * ideograph, a musical symbol - counts two there, so a `suffix` of exactly 64
589
+ * characters holding one used to earn a 400 the real API would not send, and a
590
+ * caller that trusted the mock would have shortened a name that was legal.
591
+ */
592
+ function codePointLength(value) {
593
+ return [...value].length;
594
+ }
595
+ /**
596
+ * `suffix`, per its spec description: "A string of up to 64 characters that
597
+ * will be added to your fine-tuned model name. For example, a `suffix` of
598
+ * "custom-model-name" would produce a model name like
599
+ * `ft:gpt-4o-mini:openai:custom-model-name:7p4lURel`." It is declared
600
+ * `type: string, minLength: 1, maxLength: 64, nullable: true, default: null`,
601
+ * so `null` means "no suffix" and `""` is a 400.
602
+ */
603
+ function readSuffix(value) {
604
+ if (value === void 0 || value === null) return {
605
+ ok: true,
606
+ value: null
607
+ };
608
+ if (typeof value !== "string" || value.length === 0 || codePointLength(value) > SUFFIX_MAX_LENGTH) return {
609
+ ok: false,
610
+ message: `Invalid parameter: 'suffix' must be a string of 1 to ${SUFFIX_MAX_LENGTH} characters or null`
611
+ };
612
+ return {
613
+ ok: true,
614
+ value
615
+ };
616
+ }
617
+ /**
618
+ * `seed`, per its spec description: "The seed controls the reproducibility of
619
+ * the job ... If a seed is not specified, one will be generated for you", and
620
+ * declared `type: integer, nullable: true, minimum: 0, maximum: 2147483647`.
621
+ * `null` and absence both mean "generate one", which is what `seedFor` does.
622
+ */
623
+ function readSeed(value) {
624
+ if (value === void 0 || value === null) return {
625
+ ok: true,
626
+ value: null
627
+ };
628
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > SEED_MAX) return {
629
+ ok: false,
630
+ message: `Invalid parameter: 'seed' must be an integer between 0 and ${SEED_MAX} or null`
631
+ };
632
+ return {
633
+ ok: true,
634
+ value
635
+ };
636
+ }
637
+ /**
638
+ * `metadata`, per the shared `Metadata` schema: an object of string values, at
639
+ * most 16 pairs, keys at most 64 characters and values at most 512, or `null`.
640
+ * `FineTuningJob` declares `metadata` too, so what is accepted is echoed back.
641
+ */
642
+ function readMetadata(value) {
643
+ if (value === void 0 || value === null) return {
644
+ ok: true,
645
+ value: null
646
+ };
647
+ if (!require_helpers.isJsonObject(value)) return {
648
+ ok: false,
649
+ message: "Invalid parameter: 'metadata' must be a JSON object or null"
650
+ };
651
+ const entries = Object.entries(value);
652
+ if (entries.length > METADATA_MAX_PAIRS) return {
653
+ ok: false,
654
+ message: `Invalid parameter: 'metadata' accepts at most ${METADATA_MAX_PAIRS} key-value pairs, got ${entries.length}`
655
+ };
656
+ const out = Object.create(null);
657
+ for (const [key, entry] of entries) {
658
+ const keyLength = codePointLength(key);
659
+ if (keyLength > METADATA_MAX_KEY) return {
660
+ ok: false,
661
+ message: `Invalid parameter: 'metadata' keys are at most ${METADATA_MAX_KEY} characters, got ${keyLength}`
662
+ };
663
+ if (typeof entry !== "string" || codePointLength(entry) > METADATA_MAX_VALUE) return {
664
+ ok: false,
665
+ message: `Invalid parameter: 'metadata.${key}' must be a string of at most ${METADATA_MAX_VALUE} characters`
666
+ };
667
+ out[key] = entry;
668
+ }
669
+ return {
670
+ ok: true,
671
+ value: out
672
+ };
673
+ }
674
+ /**
675
+ * One `integrations` entry. The spec requires both `type` and `wandb`, types
676
+ * `type` as the single-member enum `wandb`, and requires `project` inside
677
+ * `wandb`; `name` and `entity` are nullable strings and `tags` an array of
678
+ * strings. `FineTuningJob.integrations` caps the list at `maxItems: 5` — the
679
+ * request schema declares no cap, so enforcing the response's here is ours.
680
+ */
681
+ function readIntegration(index, raw) {
682
+ const at = `integrations[${index}]`;
683
+ if (!require_helpers.isJsonObject(raw)) return {
684
+ ok: false,
685
+ message: `Invalid parameter: '${at}' must be a JSON object`
686
+ };
687
+ for (const key of Object.keys(raw)) if (key !== "type" && key !== "wandb") return {
688
+ ok: false,
689
+ message: `Invalid parameter: '${at}.${key}' is not a recognized member of '${at}'; it accepts type, wandb`
690
+ };
691
+ if (raw["type"] !== "wandb") return {
692
+ ok: false,
693
+ message: `Invalid parameter: '${at}.type' must be the string "wandb"`
694
+ };
695
+ const wandb = raw["wandb"];
696
+ if (!require_helpers.isJsonObject(wandb)) return {
697
+ ok: false,
698
+ message: `Invalid parameter: '${at}.wandb' must be a JSON object`
699
+ };
700
+ for (const key of Object.keys(wandb)) if (key !== "project" && key !== "name" && key !== "entity" && key !== "tags") return {
701
+ ok: false,
702
+ message: `Invalid parameter: '${at}.wandb.${key}' is not a recognized member of '${at}.wandb'; it accepts project, name, entity, tags`
703
+ };
704
+ const project = wandb["project"];
705
+ if (typeof project !== "string" || project.length === 0) return {
706
+ ok: false,
707
+ message: `Invalid parameter: '${at}.wandb.project' must be a non-empty string`
708
+ };
709
+ const value = {
710
+ type: "wandb",
711
+ wandb: { project }
712
+ };
713
+ for (const key of ["name", "entity"]) {
714
+ const member = wandb[key];
715
+ if (member === void 0) continue;
716
+ if (member !== null && typeof member !== "string") return {
717
+ ok: false,
718
+ message: `Invalid parameter: '${at}.wandb.${key}' must be a string or null`
719
+ };
720
+ value.wandb[key] = member;
721
+ }
722
+ const tags = wandb["tags"];
723
+ if (tags !== void 0) {
724
+ if (!Array.isArray(tags) || tags.some((tag) => typeof tag !== "string")) return {
725
+ ok: false,
726
+ message: `Invalid parameter: '${at}.wandb.tags' must be an array of strings`
727
+ };
728
+ value.wandb.tags = tags.filter((tag) => typeof tag === "string");
729
+ }
730
+ return {
731
+ ok: true,
732
+ value
733
+ };
734
+ }
735
+ /** `integrations`: a nullable array of the entries above. */
736
+ function readIntegrations(value) {
737
+ if (value === void 0 || value === null) return {
738
+ ok: true,
739
+ value: null
740
+ };
741
+ if (!Array.isArray(value)) return {
742
+ ok: false,
743
+ message: "Invalid parameter: 'integrations' must be an array or null"
744
+ };
745
+ if (value.length > MAX_INTEGRATIONS) return {
746
+ ok: false,
747
+ message: `Invalid parameter: 'integrations' accepts at most ${MAX_INTEGRATIONS} entries, got ${value.length}`
748
+ };
749
+ const out = [];
750
+ for (const [index, entry] of value.entries()) {
751
+ const read = readIntegration(index, entry);
752
+ if (!read.ok) return read;
753
+ out.push(read.value);
754
+ }
755
+ return {
756
+ ok: true,
757
+ value: out
758
+ };
759
+ }
760
+ /** The rule table each `method` variant's `hyperparameters` is read against. */
761
+ const METHOD_HYPER_RULES = {
762
+ supervised: SUPERVISED_HYPER_RULES,
763
+ dpo: DPO_HYPER_RULES,
764
+ reinforcement: REINFORCEMENT_HYPER_RULES
765
+ };
766
+ const METHOD_TYPES = [
767
+ "supervised",
768
+ "dpo",
769
+ "reinforcement"
770
+ ];
771
+ /**
772
+ * `method`, per `FineTuneMethod`: `type` is required and is one of
773
+ * `supervised`, `dpo`, `reinforcement`, and each of those names an optional
774
+ * sub-object carrying that method's `hyperparameters`. `FineTuneReinforcementMethod`
775
+ * additionally requires a `grader`, so `type: "reinforcement"` requires one
776
+ * whether or not the `reinforcement` sub-object is present at all — the
777
+ * requirement is keyed on the chosen `type`, not on the sub-object happening
778
+ * to be there.
779
+ *
780
+ * Only the chosen type's sub-object is accepted. `FineTuneMethod` leaves all
781
+ * three optional and says nothing about a configuration for a type that was
782
+ * not chosen, and neither does SDK 4.104.0's `JobCreateParams.Method`; every
783
+ * example in the spec pairs a `type` with that type's own configuration and
784
+ * no other (`createFineTuningJob`, the "Reinforcement" example). With the real
785
+ * API's behaviour uncitable, a stray configuration is rejected rather than
786
+ * accepted-and-ignored: this mock never takes a member it then drops.
787
+ *
788
+ * The grader itself (`GraderStringCheck` | `GraderTextSimilarity` |
789
+ * `GraderPython` | `GraderScoreModel` | `GraderMulti`) is a schema tree of its
790
+ * own that this mock does not model: it is required to be a JSON object and is
791
+ * then echoed back verbatim on the job. That is a deliberate pass-through, not
792
+ * a drop — what the caller sent is what the caller reads back.
793
+ *
794
+ * `FineTuningJob` declares `method`, so an accepted method is echoed.
795
+ */
796
+ function readMethod(value) {
797
+ if (value === void 0 || value === null) return {
798
+ ok: true,
799
+ value: null
800
+ };
801
+ if (!require_helpers.isJsonObject(value)) return {
802
+ ok: false,
803
+ message: "Invalid parameter: 'method' must be a JSON object or null"
804
+ };
805
+ for (const key of Object.keys(value)) if (key !== "type" && !METHOD_TYPES.some((t) => t === key)) return {
806
+ ok: false,
807
+ message: `Invalid parameter: 'method.${key}' is not a recognized member of 'method'; it accepts type, ${METHOD_TYPES.join(", ")}`
808
+ };
809
+ const type = METHOD_TYPES.find((t) => t === value["type"]);
810
+ if (type === void 0) return {
811
+ ok: false,
812
+ message: `Invalid parameter: 'method.type' must be one of ${METHOD_TYPES.map((t) => `"${t}"`).join(", ")}`
813
+ };
814
+ const method = { type };
815
+ for (const variant of METHOD_TYPES) if (variant !== type && value[variant] !== void 0) return {
816
+ ok: false,
817
+ message: `Invalid parameter: 'method.${variant}' configures the "${variant}" method, but 'method.type' is "${type}"; a method carries only its own configuration`
818
+ };
819
+ const supplied = value[type];
820
+ const raw = supplied === void 0 && type === "reinforcement" ? {} : supplied;
821
+ if (raw !== void 0) {
822
+ if (!require_helpers.isJsonObject(raw)) return {
823
+ ok: false,
824
+ message: `Invalid parameter: 'method.${type}' must be a JSON object`
825
+ };
826
+ const allowed = type === "reinforcement" ? ["grader", "hyperparameters"] : ["hyperparameters"];
827
+ for (const key of Object.keys(raw)) if (!allowed.includes(key)) return {
828
+ ok: false,
829
+ message: `Invalid parameter: 'method.${type}.${key}' is not a recognized member of 'method.${type}'; it accepts ${allowed.join(", ")}`
830
+ };
831
+ const config = {};
832
+ if (type === "reinforcement") {
833
+ const grader = raw["grader"];
834
+ if (!require_helpers.isJsonObject(grader)) return {
835
+ ok: false,
836
+ message: "Invalid parameter: 'method.reinforcement.grader' is required and must be a JSON object"
837
+ };
838
+ config.grader = grader;
839
+ }
840
+ const hyper = raw["hyperparameters"];
841
+ if (hyper !== void 0) {
842
+ if (!require_helpers.isJsonObject(hyper)) return {
843
+ ok: false,
844
+ message: `Invalid parameter: 'method.${type}.hyperparameters' must be a JSON object`
845
+ };
846
+ const read = readHyperObject(METHOD_HYPER_RULES[type], `method.${type}.hyperparameters`, hyper);
847
+ if (!read.ok) return read;
848
+ config.hyperparameters = read.value;
849
+ }
850
+ method[type] = config;
851
+ }
852
+ return {
853
+ ok: true,
854
+ value: method
855
+ };
856
+ }
857
+ async function handleFineTuningCreate(req, res, raw, journal, defaults, setCorsHeaders) {
858
+ const path = req.url ?? "/v1/fine_tuning/jobs";
859
+ const method = req.method ?? "POST";
860
+ if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
861
+ let body;
862
+ try {
863
+ body = JSON.parse(raw);
864
+ } catch (err) {
865
+ journalFt(journal, method, path, require_helpers.flattenHeaders(req.headers), 400);
866
+ writeJson(res, 400, invalid(`Malformed JSON: ${err instanceof Error ? err.message : "unknown"}`), setCorsHeaders);
867
+ return;
868
+ }
869
+ if (!require_helpers.isJsonObject(body)) {
870
+ journalFt(journal, method, path, require_helpers.flattenHeaders(req.headers), 400);
871
+ writeJson(res, 400, invalid("Request body must be a JSON object"), setCorsHeaders);
872
+ return;
873
+ }
874
+ const journaledBody = body;
875
+ /**
876
+ * Every rejection below journals the parsed body before answering: these are
877
+ * exactly the 400s whose offending payload a caller needs to read back.
878
+ */
879
+ const reject = (message) => {
880
+ journalFt(journal, method, path, require_helpers.flattenHeaders(req.headers), 400, journaledBody);
881
+ writeJson(res, 400, invalid(message), setCorsHeaders);
882
+ };
883
+ for (const key of Object.keys(body)) if (!CREATE_KEYS.some((known) => known === key)) {
884
+ reject(`Invalid parameter: '${key}' is not a recognized request parameter; this endpoint accepts ${CREATE_KEYS.join(", ")}`);
885
+ return;
886
+ }
887
+ const trainingFile = body["training_file"];
888
+ const model = body["model"];
889
+ if (typeof trainingFile !== "string" || trainingFile.length === 0) {
890
+ reject("Invalid parameter: 'training_file' must be a non-empty string");
891
+ return;
892
+ }
893
+ if (typeof model !== "string" || model.length === 0) {
894
+ reject("Invalid parameter: 'model' must be a non-empty string");
895
+ return;
896
+ }
897
+ const validationFile = body["validation_file"];
898
+ if (validationFile !== void 0 && validationFile !== null && (typeof validationFile !== "string" || validationFile.length === 0)) {
899
+ reject("Invalid parameter: 'validation_file' must be a non-empty string or null");
900
+ return;
901
+ }
902
+ const hyper = body["hyperparameters"];
903
+ if (hyper !== void 0 && !require_helpers.isJsonObject(hyper)) {
904
+ reject("Invalid parameter: 'hyperparameters' must be a JSON object");
905
+ return;
906
+ }
907
+ const hyperparameters = {};
908
+ if (require_helpers.isJsonObject(hyper)) {
909
+ const read = readHyperObject(LEGACY_HYPER_RULES, "hyperparameters", hyper);
910
+ if (!read.ok) {
911
+ reject(read.message);
912
+ return;
913
+ }
914
+ for (const key of LEGACY_HYPER_KEYS) {
915
+ const value = read.value[key];
916
+ if (typeof value === "number" || value === "auto") hyperparameters[key] = value;
917
+ }
918
+ }
919
+ const suffix = readSuffix(body["suffix"]);
920
+ if (!suffix.ok) {
921
+ reject(suffix.message);
922
+ return;
923
+ }
924
+ const seed = readSeed(body["seed"]);
925
+ if (!seed.ok) {
926
+ reject(seed.message);
927
+ return;
928
+ }
929
+ const integrations = readIntegrations(body["integrations"]);
930
+ if (!integrations.ok) {
931
+ reject(integrations.message);
932
+ return;
933
+ }
934
+ const jobMethod = readMethod(body["method"]);
935
+ if (!jobMethod.ok) {
936
+ reject(jobMethod.message);
937
+ return;
938
+ }
939
+ const metadata = readMetadata(body["metadata"]);
940
+ if (!metadata.ok) {
941
+ reject(metadata.message);
942
+ return;
943
+ }
944
+ const id = require_helpers.generateId("ftjob");
945
+ const job = {
946
+ id,
947
+ object: "fine_tuning.job",
948
+ model,
949
+ training_file: trainingFile,
950
+ validation_file: typeof validationFile === "string" ? validationFile : null,
951
+ status: "validating_files",
952
+ created_at: stamp(),
953
+ finished_at: null,
954
+ fine_tuned_model: null,
955
+ hyperparameters,
956
+ error: null,
957
+ organization_id: ORGANIZATION_ID,
958
+ result_files: [],
959
+ seed: seed.value ?? seedFor(id),
960
+ trained_tokens: null
961
+ };
962
+ if (integrations.value !== null) job.integrations = integrations.value;
963
+ if (jobMethod.value !== null) job.method = jobMethod.value;
964
+ if (metadata.value !== null) job.metadata = metadata.value;
965
+ if (suffix.value !== null) suffixes.set(job.id, suffix.value);
966
+ jobs.set(job.id, job);
967
+ polls.set(job.id, 0);
968
+ appendEvent(job, "Job created, validating training file");
969
+ journalFt(journal, method, path, require_helpers.flattenHeaders(req.headers), 200, journaledBody);
970
+ writeJson(res, 200, job, setCorsHeaders);
971
+ }
972
+ async function handleFineTuningList(req, res, journal, defaults, setCorsHeaders) {
973
+ const path = req.url ?? "/v1/fine_tuning/jobs";
974
+ const method = req.method ?? "GET";
975
+ if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
976
+ writePage(res, journal, req, method, path, [...jobs.values()].reverse(), setCorsHeaders);
977
+ }
978
+ async function handleFineTuningRetrieve(req, res, id, journal, defaults, setCorsHeaders) {
979
+ const path = req.url ?? `/v1/fine_tuning/jobs/${id}`;
980
+ const method = req.method ?? "GET";
981
+ if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
982
+ const job = jobs.get(id);
983
+ if (!job) {
984
+ journalFt(journal, method, path, require_helpers.flattenHeaders(req.headers), 404);
985
+ writeJson(res, 404, invalid(`No such fine-tuning job: ${id}`), setCorsHeaders);
986
+ return;
987
+ }
988
+ journalFt(journal, method, path, require_helpers.flattenHeaders(req.headers), 200);
989
+ writeJson(res, 200, advance(job), setCorsHeaders);
990
+ }
991
+ async function handleFineTuningCancel(req, res, id, journal, defaults, setCorsHeaders) {
992
+ const path = req.url ?? `/v1/fine_tuning/jobs/${id}/cancel`;
993
+ const method = req.method ?? "POST";
994
+ if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
995
+ const job = jobs.get(id);
996
+ if (!job) {
997
+ journalFt(journal, method, path, require_helpers.flattenHeaders(req.headers), 404);
998
+ writeJson(res, 404, invalid(`No such fine-tuning job: ${id}`), setCorsHeaders);
999
+ return;
1000
+ }
1001
+ if (isTerminal(job.status)) {
1002
+ journalFt(journal, method, path, require_helpers.flattenHeaders(req.headers), 400);
1003
+ writeJson(res, 400, invalid(`Job ${id} is already terminal (${job.status})`), setCorsHeaders);
1004
+ return;
1005
+ }
1006
+ job.status = "cancelled";
1007
+ job.finished_at = appendEvent(job, "Job cancelled by user");
1008
+ journalFt(journal, method, path, require_helpers.flattenHeaders(req.headers), 200);
1009
+ writeJson(res, 200, job, setCorsHeaders);
1010
+ }
1011
+ async function handleFineTuningEvents(req, res, id, journal, defaults, setCorsHeaders) {
1012
+ const path = req.url ?? `/v1/fine_tuning/jobs/${id}/events`;
1013
+ const method = req.method ?? "GET";
1014
+ if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
1015
+ const job = jobs.get(id);
1016
+ if (!job) {
1017
+ journalFt(journal, method, path, require_helpers.flattenHeaders(req.headers), 404);
1018
+ writeJson(res, 404, invalid(`No such fine-tuning job: ${id}`), setCorsHeaders);
1019
+ return;
1020
+ }
1021
+ writePage(res, journal, req, method, path, eventsFor(job).reverse(), setCorsHeaders);
1022
+ }
1023
+
1024
+ //#endregion
1025
+ exports.clearFineTuningStore = clearFineTuningStore;
1026
+ exports.handleFineTuningCancel = handleFineTuningCancel;
1027
+ exports.handleFineTuningCreate = handleFineTuningCreate;
1028
+ exports.handleFineTuningEvents = handleFineTuningEvents;
1029
+ exports.handleFineTuningList = handleFineTuningList;
1030
+ exports.handleFineTuningRetrieve = handleFineTuningRetrieve;
1031
+ exports.paginate = paginate;
1032
+ exports.readMetadata = readMetadata;
1033
+ //# sourceMappingURL=fine-tuning.cjs.map