@yolk-sdk/emulators 0.1.0-canary.96

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 (333) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2135 -0
  3. package/dist/anthropic.d.mts +269 -0
  4. package/dist/anthropic.d.mts.map +1 -0
  5. package/dist/anthropic.mjs +177 -0
  6. package/dist/anthropic.mjs.map +1 -0
  7. package/dist/chat-completions.d.mts +288 -0
  8. package/dist/chat-completions.d.mts.map +1 -0
  9. package/dist/chat-completions.mjs +451 -0
  10. package/dist/chat-completions.mjs.map +1 -0
  11. package/dist/codex.d.mts +265 -0
  12. package/dist/codex.d.mts.map +1 -0
  13. package/dist/codex.mjs +199 -0
  14. package/dist/codex.mjs.map +1 -0
  15. package/dist/dropbox/api.d.mts +63 -0
  16. package/dist/dropbox/api.d.mts.map +1 -0
  17. package/dist/dropbox/api.mjs +565 -0
  18. package/dist/dropbox/api.mjs.map +1 -0
  19. package/dist/dropbox/state.d.mts +136 -0
  20. package/dist/dropbox/state.d.mts.map +1 -0
  21. package/dist/dropbox/state.mjs +209 -0
  22. package/dist/dropbox/state.mjs.map +1 -0
  23. package/dist/dropbox.d.mts +79 -0
  24. package/dist/dropbox.d.mts.map +1 -0
  25. package/dist/dropbox.mjs +124 -0
  26. package/dist/dropbox.mjs.map +1 -0
  27. package/dist/email-fixtures.d.mts +36 -0
  28. package/dist/email-fixtures.d.mts.map +1 -0
  29. package/dist/email-fixtures.mjs +1080 -0
  30. package/dist/email-fixtures.mjs.map +1 -0
  31. package/dist/email.d.mts +160 -0
  32. package/dist/email.d.mts.map +1 -0
  33. package/dist/email.mjs +608 -0
  34. package/dist/email.mjs.map +1 -0
  35. package/dist/emulator-compose.d.mts +40 -0
  36. package/dist/emulator-compose.d.mts.map +1 -0
  37. package/dist/emulator-compose.mjs +91 -0
  38. package/dist/emulator-compose.mjs.map +1 -0
  39. package/dist/emulator-http.d.mts +53 -0
  40. package/dist/emulator-http.d.mts.map +1 -0
  41. package/dist/emulator-http.mjs +117 -0
  42. package/dist/emulator-http.mjs.map +1 -0
  43. package/dist/emulator-kernel.d.mts +176 -0
  44. package/dist/emulator-kernel.d.mts.map +1 -0
  45. package/dist/emulator-kernel.mjs +413 -0
  46. package/dist/emulator-kernel.mjs.map +1 -0
  47. package/dist/fixture-route.d.mts +129 -0
  48. package/dist/fixture-route.d.mts.map +1 -0
  49. package/dist/fixture-route.mjs +257 -0
  50. package/dist/fixture-route.mjs.map +1 -0
  51. package/dist/fortnox/api.d.mts +92 -0
  52. package/dist/fortnox/api.d.mts.map +1 -0
  53. package/dist/fortnox/api.mjs +750 -0
  54. package/dist/fortnox/api.mjs.map +1 -0
  55. package/dist/fortnox/state.d.mts +533 -0
  56. package/dist/fortnox/state.d.mts.map +1 -0
  57. package/dist/fortnox/state.mjs +612 -0
  58. package/dist/fortnox/state.mjs.map +1 -0
  59. package/dist/fortnox.d.mts +128 -0
  60. package/dist/fortnox.d.mts.map +1 -0
  61. package/dist/fortnox.mjs +403 -0
  62. package/dist/fortnox.mjs.map +1 -0
  63. package/dist/gateway-evaluate-recordings.d.mts +12 -0
  64. package/dist/gateway-evaluate-recordings.d.mts.map +1 -0
  65. package/dist/gateway-evaluate-recordings.mjs +128 -0
  66. package/dist/gateway-evaluate-recordings.mjs.map +1 -0
  67. package/dist/gateway.d.mts +272 -0
  68. package/dist/gateway.d.mts.map +1 -0
  69. package/dist/gateway.mjs +400 -0
  70. package/dist/gateway.mjs.map +1 -0
  71. package/dist/github/api.d.mts +63 -0
  72. package/dist/github/api.d.mts.map +1 -0
  73. package/dist/github/api.mjs +527 -0
  74. package/dist/github/api.mjs.map +1 -0
  75. package/dist/github/state.d.mts +272 -0
  76. package/dist/github/state.d.mts.map +1 -0
  77. package/dist/github/state.mjs +303 -0
  78. package/dist/github/state.mjs.map +1 -0
  79. package/dist/github.d.mts +77 -0
  80. package/dist/github.d.mts.map +1 -0
  81. package/dist/github.mjs +180 -0
  82. package/dist/github.mjs.map +1 -0
  83. package/dist/google/calendar.d.mts +10 -0
  84. package/dist/google/calendar.d.mts.map +1 -0
  85. package/dist/google/calendar.mjs +252 -0
  86. package/dist/google/calendar.mjs.map +1 -0
  87. package/dist/google/drive.d.mts +8 -0
  88. package/dist/google/drive.d.mts.map +1 -0
  89. package/dist/google/drive.mjs +252 -0
  90. package/dist/google/drive.mjs.map +1 -0
  91. package/dist/google/gmail.d.mts +10 -0
  92. package/dist/google/gmail.d.mts.map +1 -0
  93. package/dist/google/gmail.mjs +591 -0
  94. package/dist/google/gmail.mjs.map +1 -0
  95. package/dist/google/shared.d.mts +107 -0
  96. package/dist/google/shared.d.mts.map +1 -0
  97. package/dist/google/shared.mjs +146 -0
  98. package/dist/google/shared.mjs.map +1 -0
  99. package/dist/google/state.d.mts +385 -0
  100. package/dist/google/state.d.mts.map +1 -0
  101. package/dist/google/state.mjs +496 -0
  102. package/dist/google/state.mjs.map +1 -0
  103. package/dist/google.d.mts +75 -0
  104. package/dist/google.d.mts.map +1 -0
  105. package/dist/google.mjs +210 -0
  106. package/dist/google.mjs.map +1 -0
  107. package/dist/linkedin-search/api.d.mts +45 -0
  108. package/dist/linkedin-search/api.d.mts.map +1 -0
  109. package/dist/linkedin-search/api.mjs +181 -0
  110. package/dist/linkedin-search/api.mjs.map +1 -0
  111. package/dist/linkedin-search/state.d.mts +123 -0
  112. package/dist/linkedin-search/state.d.mts.map +1 -0
  113. package/dist/linkedin-search/state.mjs +261 -0
  114. package/dist/linkedin-search/state.mjs.map +1 -0
  115. package/dist/linkedin-search.d.mts +69 -0
  116. package/dist/linkedin-search.d.mts.map +1 -0
  117. package/dist/linkedin-search.mjs +158 -0
  118. package/dist/linkedin-search.mjs.map +1 -0
  119. package/dist/mcp/api.d.mts +44 -0
  120. package/dist/mcp/api.d.mts.map +1 -0
  121. package/dist/mcp/api.mjs +557 -0
  122. package/dist/mcp/api.mjs.map +1 -0
  123. package/dist/mcp/recordings.d.mts +40 -0
  124. package/dist/mcp/recordings.d.mts.map +1 -0
  125. package/dist/mcp/recordings.mjs +2390 -0
  126. package/dist/mcp/recordings.mjs.map +1 -0
  127. package/dist/mcp/state.d.mts +71 -0
  128. package/dist/mcp/state.d.mts.map +1 -0
  129. package/dist/mcp/state.mjs +83 -0
  130. package/dist/mcp/state.mjs.map +1 -0
  131. package/dist/mcp.d.mts +84 -0
  132. package/dist/mcp.d.mts.map +1 -0
  133. package/dist/mcp.mjs +241 -0
  134. package/dist/mcp.mjs.map +1 -0
  135. package/dist/messages.d.mts +183 -0
  136. package/dist/messages.d.mts.map +1 -0
  137. package/dist/messages.mjs +532 -0
  138. package/dist/messages.mjs.map +1 -0
  139. package/dist/microsoft/api.d.mts +47 -0
  140. package/dist/microsoft/api.d.mts.map +1 -0
  141. package/dist/microsoft/api.mjs +178 -0
  142. package/dist/microsoft/api.mjs.map +1 -0
  143. package/dist/microsoft/calendar.d.mts +30 -0
  144. package/dist/microsoft/calendar.d.mts.map +1 -0
  145. package/dist/microsoft/calendar.mjs +271 -0
  146. package/dist/microsoft/calendar.mjs.map +1 -0
  147. package/dist/microsoft/drive.d.mts +36 -0
  148. package/dist/microsoft/drive.d.mts.map +1 -0
  149. package/dist/microsoft/drive.mjs +298 -0
  150. package/dist/microsoft/drive.mjs.map +1 -0
  151. package/dist/microsoft/graph.d.mts +198 -0
  152. package/dist/microsoft/graph.d.mts.map +1 -0
  153. package/dist/microsoft/graph.mjs +264 -0
  154. package/dist/microsoft/graph.mjs.map +1 -0
  155. package/dist/microsoft/mail.d.mts +48 -0
  156. package/dist/microsoft/mail.d.mts.map +1 -0
  157. package/dist/microsoft/mail.mjs +488 -0
  158. package/dist/microsoft/mail.mjs.map +1 -0
  159. package/dist/microsoft/state.d.mts +480 -0
  160. package/dist/microsoft/state.d.mts.map +1 -0
  161. package/dist/microsoft/state.mjs +625 -0
  162. package/dist/microsoft/state.mjs.map +1 -0
  163. package/dist/microsoft.d.mts +168 -0
  164. package/dist/microsoft.d.mts.map +1 -0
  165. package/dist/microsoft.mjs +483 -0
  166. package/dist/microsoft.mjs.map +1 -0
  167. package/dist/node.d.mts +47 -0
  168. package/dist/node.d.mts.map +1 -0
  169. package/dist/node.mjs +178 -0
  170. package/dist/node.mjs.map +1 -0
  171. package/dist/notion/api.d.mts +51 -0
  172. package/dist/notion/api.d.mts.map +1 -0
  173. package/dist/notion/api.mjs +507 -0
  174. package/dist/notion/api.mjs.map +1 -0
  175. package/dist/notion/state.d.mts +329 -0
  176. package/dist/notion/state.d.mts.map +1 -0
  177. package/dist/notion/state.mjs +432 -0
  178. package/dist/notion/state.mjs.map +1 -0
  179. package/dist/notion.d.mts +72 -0
  180. package/dist/notion.d.mts.map +1 -0
  181. package/dist/notion.mjs +127 -0
  182. package/dist/notion.mjs.map +1 -0
  183. package/dist/openai.d.mts +165 -0
  184. package/dist/openai.d.mts.map +1 -0
  185. package/dist/openai.mjs +141 -0
  186. package/dist/openai.mjs.map +1 -0
  187. package/dist/opencode-recordings.d.mts +7 -0
  188. package/dist/opencode-recordings.d.mts.map +1 -0
  189. package/dist/opencode-recordings.mjs +222 -0
  190. package/dist/opencode-recordings.mjs.map +1 -0
  191. package/dist/opencode.d.mts +70 -0
  192. package/dist/opencode.d.mts.map +1 -0
  193. package/dist/opencode.mjs +217 -0
  194. package/dist/opencode.mjs.map +1 -0
  195. package/dist/r2-fixtures.d.mts +36 -0
  196. package/dist/r2-fixtures.d.mts.map +1 -0
  197. package/dist/r2-fixtures.mjs +245 -0
  198. package/dist/r2-fixtures.mjs.map +1 -0
  199. package/dist/r2-guard.d.mts +43 -0
  200. package/dist/r2-guard.d.mts.map +1 -0
  201. package/dist/r2-guard.mjs +215 -0
  202. package/dist/r2-guard.mjs.map +1 -0
  203. package/dist/r2.d.mts +166 -0
  204. package/dist/r2.d.mts.map +1 -0
  205. package/dist/r2.mjs +517 -0
  206. package/dist/r2.mjs.map +1 -0
  207. package/dist/responses.d.mts +231 -0
  208. package/dist/responses.d.mts.map +1 -0
  209. package/dist/responses.mjs +556 -0
  210. package/dist/responses.mjs.map +1 -0
  211. package/dist/route-evidence.d.mts +45 -0
  212. package/dist/route-evidence.d.mts.map +1 -0
  213. package/dist/route-evidence.mjs +56 -0
  214. package/dist/route-evidence.mjs.map +1 -0
  215. package/dist/router.d.mts +93 -0
  216. package/dist/router.d.mts.map +1 -0
  217. package/dist/router.mjs +259 -0
  218. package/dist/router.mjs.map +1 -0
  219. package/dist/stateful-core.d.mts +17 -0
  220. package/dist/stateful-core.d.mts.map +1 -0
  221. package/dist/stateful-core.mjs +41 -0
  222. package/dist/stateful-core.mjs.map +1 -0
  223. package/dist/stateful-emulator.d.mts +565 -0
  224. package/dist/stateful-emulator.d.mts.map +1 -0
  225. package/dist/stateful-emulator.mjs +1228 -0
  226. package/dist/stateful-emulator.mjs.map +1 -0
  227. package/dist/stateful-secrets.d.mts +84 -0
  228. package/dist/stateful-secrets.d.mts.map +1 -0
  229. package/dist/stateful-secrets.mjs +216 -0
  230. package/dist/stateful-secrets.mjs.map +1 -0
  231. package/dist/subscription-usage-recordings.d.mts +9 -0
  232. package/dist/subscription-usage-recordings.d.mts.map +1 -0
  233. package/dist/subscription-usage-recordings.mjs +53 -0
  234. package/dist/subscription-usage-recordings.mjs.map +1 -0
  235. package/dist/subscription-usage.d.mts +53 -0
  236. package/dist/subscription-usage.d.mts.map +1 -0
  237. package/dist/subscription-usage.mjs +21 -0
  238. package/dist/subscription-usage.mjs.map +1 -0
  239. package/dist/telegram/api.d.mts +34 -0
  240. package/dist/telegram/api.d.mts.map +1 -0
  241. package/dist/telegram/api.mjs +257 -0
  242. package/dist/telegram/api.mjs.map +1 -0
  243. package/dist/telegram/state.d.mts +128 -0
  244. package/dist/telegram/state.d.mts.map +1 -0
  245. package/dist/telegram/state.mjs +154 -0
  246. package/dist/telegram/state.mjs.map +1 -0
  247. package/dist/telegram.d.mts +62 -0
  248. package/dist/telegram.d.mts.map +1 -0
  249. package/dist/telegram.mjs +127 -0
  250. package/dist/telegram.mjs.map +1 -0
  251. package/dist/todoist/api.d.mts +55 -0
  252. package/dist/todoist/api.d.mts.map +1 -0
  253. package/dist/todoist/api.mjs +415 -0
  254. package/dist/todoist/api.mjs.map +1 -0
  255. package/dist/todoist/state.d.mts +275 -0
  256. package/dist/todoist/state.d.mts.map +1 -0
  257. package/dist/todoist/state.mjs +345 -0
  258. package/dist/todoist/state.mjs.map +1 -0
  259. package/dist/todoist.d.mts +68 -0
  260. package/dist/todoist.d.mts.map +1 -0
  261. package/dist/todoist.mjs +181 -0
  262. package/dist/todoist.mjs.map +1 -0
  263. package/dist/xai.d.mts +267 -0
  264. package/dist/xai.d.mts.map +1 -0
  265. package/dist/xai.mjs +252 -0
  266. package/dist/xai.mjs.map +1 -0
  267. package/package.json +157 -0
  268. package/src/anthropic.ts +289 -0
  269. package/src/chat-completions.ts +924 -0
  270. package/src/codex.ts +296 -0
  271. package/src/dropbox/api.ts +1084 -0
  272. package/src/dropbox/state.ts +364 -0
  273. package/src/dropbox.ts +203 -0
  274. package/src/email-fixtures.ts +1297 -0
  275. package/src/email.ts +1108 -0
  276. package/src/emulator-compose.ts +147 -0
  277. package/src/emulator-http.ts +184 -0
  278. package/src/emulator-kernel.ts +844 -0
  279. package/src/fixture-route.ts +561 -0
  280. package/src/fortnox/api.ts +1352 -0
  281. package/src/fortnox/state.ts +798 -0
  282. package/src/fortnox.ts +801 -0
  283. package/src/gateway-evaluate-recordings.ts +153 -0
  284. package/src/gateway.ts +530 -0
  285. package/src/github/api.ts +986 -0
  286. package/src/github/state.ts +439 -0
  287. package/src/github.ts +271 -0
  288. package/src/google/calendar.ts +486 -0
  289. package/src/google/drive.ts +471 -0
  290. package/src/google/gmail.ts +1011 -0
  291. package/src/google/shared.ts +321 -0
  292. package/src/google/state.ts +686 -0
  293. package/src/google.ts +298 -0
  294. package/src/linkedin-search/api.ts +363 -0
  295. package/src/linkedin-search/state.ts +373 -0
  296. package/src/linkedin-search.ts +241 -0
  297. package/src/mcp/api.ts +995 -0
  298. package/src/mcp/recordings.ts +2665 -0
  299. package/src/mcp/state.ts +125 -0
  300. package/src/mcp.ts +319 -0
  301. package/src/messages.ts +844 -0
  302. package/src/microsoft/api.ts +426 -0
  303. package/src/microsoft/calendar.ts +491 -0
  304. package/src/microsoft/drive.ts +541 -0
  305. package/src/microsoft/graph.ts +550 -0
  306. package/src/microsoft/mail.ts +833 -0
  307. package/src/microsoft/state.ts +822 -0
  308. package/src/microsoft.ts +982 -0
  309. package/src/node.ts +293 -0
  310. package/src/notion/api.ts +976 -0
  311. package/src/notion/state.ts +512 -0
  312. package/src/notion.ts +210 -0
  313. package/src/openai.ts +198 -0
  314. package/src/opencode-recordings.ts +257 -0
  315. package/src/opencode.ts +271 -0
  316. package/src/r2-fixtures.ts +295 -0
  317. package/src/r2-guard.ts +323 -0
  318. package/src/r2.ts +890 -0
  319. package/src/responses.ts +901 -0
  320. package/src/route-evidence.ts +90 -0
  321. package/src/router.ts +490 -0
  322. package/src/stateful-core.ts +70 -0
  323. package/src/stateful-emulator.ts +2571 -0
  324. package/src/stateful-secrets.ts +299 -0
  325. package/src/subscription-usage-recordings.ts +77 -0
  326. package/src/subscription-usage.ts +68 -0
  327. package/src/telegram/api.ts +437 -0
  328. package/src/telegram/state.ts +229 -0
  329. package/src/telegram.ts +227 -0
  330. package/src/todoist/api.ts +736 -0
  331. package/src/todoist/state.ts +456 -0
  332. package/src/todoist.ts +316 -0
  333. package/src/xai.ts +341 -0
@@ -0,0 +1,451 @@
1
+ import { emulatorRouteKey } from "./route-evidence.mjs";
2
+ import { EmulatorErrorAfterChunksFault, EmulatorFaultMatch, EmulatorScriptedError, EmulatorStatusFault, EmulatorTruncateAfterChunksFault, approximateTokens, jsonField, jsonResponse, makeEmulatorKernel, parseJson, readText, splitFragments, stringField, synthesizeValue } from "./emulator-kernel.mjs";
3
+ import { Predicate, Result } from "effect";
4
+ import * as Schema from "effect/Schema";
5
+ //#region src/chat-completions.ts
6
+ /**
7
+ * OpenAI-compatible Chat Completions emulator core (internal; not a package export).
8
+ *
9
+ * One `POST .../chat/completions` route with request parsing, `chat.completion.chunk` SSE
10
+ * framing (text, reasoning, tool-call argument fragments, a finish event, usage, and
11
+ * `data: [DONE]`), the non-streamed `chat.completion` JSON body, and scripted turns. Faults, the
12
+ * request ledger, the `/_emulate/*` control plane, evidence tagging, and route binding come from
13
+ * the shared kernel (`emulator-kernel.ts`).
14
+ *
15
+ * Each emulator subpath (`gateway`, `openai`) supplies what differs: the path and route evidence
16
+ * manifest, model lists, the error envelope and the unknown-model status and error, the 401
17
+ * error, the completion-token request field, whether reasoning is emulated, its scripted-turn
18
+ * schema, and its wire profile (`ChatWireProfile`: events per network chunk, usage placement, the
19
+ * reasoning field, and extra per-chunk fields). Without a profile the plain OpenAI Chat
20
+ * Completions wire is sent.
21
+ *
22
+ * Runtime-portable Web APIs only (`Request`, `Response`, `ReadableStream`, `TextEncoder`, `URL`);
23
+ * no Node builtins and no SDK imports.
24
+ */
25
+ /** Optional fault filter; an omitted field matches every request. `path` ending in `*` is a prefix. */
26
+ const ChatFaultMatch = EmulatorFaultMatch;
27
+ /**
28
+ * Wire faults for emulated routes:
29
+ *
30
+ * - `status`: answer with this status, headers, and body instead of a
31
+ * completion (for example 429 with `retry-after`). The body defaults to the
32
+ * emulator's error envelope. Statuses that cannot carry a body (1xx, 204,
33
+ * 205, 304) and redirects (3xx) are rejected, as are invalid header names or
34
+ * values and a `location` header; scripted errors follow the same rules.
35
+ * - `error-after-chunks`: send `chunks` body chunks, then error the body
36
+ * stream (a dropped connection).
37
+ * - `truncate-after-chunks`: send `chunks` body chunks, then close the body
38
+ * cleanly (for example without `data: [DONE]`).
39
+ *
40
+ * A whole JSON body counts as one chunk. A chunk fault that cannot take
41
+ * effect (`error-after-chunks` beyond the chunk count, or
42
+ * `truncate-after-chunks` at or beyond it) answers 500 with an emulator error
43
+ * instead of silently doing nothing, and is not consumed.
44
+ */
45
+ const ChatFault = Schema.Union([
46
+ EmulatorStatusFault,
47
+ EmulatorErrorAfterChunksFault,
48
+ EmulatorTruncateAfterChunksFault
49
+ ]);
50
+ const TokenCount = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
51
+ /** Usage for a scripted turn (sent as the wire `usage` object). */
52
+ const ChatScriptedUsage = Schema.Struct({
53
+ promptTokens: TokenCount,
54
+ completionTokens: TokenCount,
55
+ reasoningTokens: Schema.optionalKey(TokenCount)
56
+ });
57
+ /** One scripted tool call; its JSON arguments stream as these fragments. */
58
+ const ChatScriptedToolCall = Schema.Struct({
59
+ name: Schema.NonEmptyString,
60
+ argumentFragments: Schema.Array(Schema.String),
61
+ id: Schema.optionalKey(Schema.NonEmptyString)
62
+ });
63
+ /** Scripted completion fields every Chat Completions emulator accepts. */
64
+ const chatScriptedCompletionFields = {
65
+ text: Schema.optionalKey(Schema.Array(Schema.String)),
66
+ toolCalls: Schema.optionalKey(Schema.Array(ChatScriptedToolCall)),
67
+ usage: Schema.optionalKey(Schema.NullOr(ChatScriptedUsage)),
68
+ finishReason: Schema.optionalKey(Schema.NonEmptyString)
69
+ };
70
+ /** Scripted completion fields for emulators that emulate streamed reasoning. */
71
+ const chatScriptedReasoningFields = {
72
+ reasoning: Schema.optionalKey(Schema.Array(Schema.String)),
73
+ reasoningField: Schema.optionalKey(Schema.Literals(["reasoning_content", "reasoning"])),
74
+ order: Schema.optionalKey(Schema.Literals(["reasoning-first", "text-first"]))
75
+ };
76
+ /**
77
+ * The reasoning variant of a scripted completion (the Gateway's shape; `/openai` uses the
78
+ * reasoning-free `OpenAiScriptedCompletion`). Every field is exact (nothing is
79
+ * filled in) except: `usage` omitted is synthesized when the request asks for
80
+ * usage, and `null` drops it; `finishReason` omitted is `tool_calls` with tool
81
+ * calls, else `stop`. `order` defaults to `reasoning-first`; `reasoningField`
82
+ * defaults to the emulator's wire profile field (`reasoning` for the Gateway,
83
+ * as recorded live; `reasoning_content` is the DeepSeek-native alternative).
84
+ */
85
+ const ChatScriptedReasoningCompletion = Schema.Struct({
86
+ text: chatScriptedCompletionFields.text,
87
+ reasoning: chatScriptedReasoningFields.reasoning,
88
+ reasoningField: chatScriptedReasoningFields.reasoningField,
89
+ order: chatScriptedReasoningFields.order,
90
+ toolCalls: chatScriptedCompletionFields.toolCalls,
91
+ usage: chatScriptedCompletionFields.usage,
92
+ finishReason: chatScriptedCompletionFields.finishReason
93
+ });
94
+ /** A scripted error response: status, body (a string is sent as is), and optional headers. */
95
+ const ChatScriptedError = EmulatorScriptedError;
96
+ /**
97
+ * The reasoning variant of a scripted turn, queued for the next chat completion request. It is
98
+ * also the widest turn shape, so it bounds every emulator's turn type.
99
+ */
100
+ const ChatScriptedReasoningTurn = Schema.Union([ChatScriptedError, ChatScriptedReasoningCompletion]);
101
+ const ChatTool = Schema.Struct({
102
+ type: Schema.optionalKey(Schema.String),
103
+ function: Schema.Struct({
104
+ name: Schema.String,
105
+ description: Schema.optionalKey(Schema.String),
106
+ parameters: Schema.optionalKey(Schema.Json)
107
+ })
108
+ });
109
+ const ChatRequest = Schema.Struct({
110
+ model: Schema.optionalKey(Schema.String),
111
+ messages: Schema.Array(Schema.Json),
112
+ stream: Schema.optionalKey(Schema.NullOr(Schema.Boolean)),
113
+ stream_options: Schema.optionalKey(Schema.NullOr(Schema.Struct({ include_usage: Schema.optionalKey(Schema.NullOr(Schema.Boolean)) }))),
114
+ reasoning_effort: Schema.optionalKey(Schema.NullOr(Schema.String)),
115
+ thinking: Schema.optionalKey(Schema.Json),
116
+ tools: Schema.optionalKey(Schema.NullOr(Schema.Array(ChatTool))),
117
+ tool_choice: Schema.optionalKey(Schema.Json)
118
+ });
119
+ const decodeChatRequest = Schema.decodeUnknownResult(ChatRequest);
120
+ const syntheticCreated = 179e7;
121
+ const bearerPattern = /^bearer\s+\S+/i;
122
+ const defaultReasoning = ["The user wants a short reply.", " Keep it brief."];
123
+ const reasoningRequested = (request) => {
124
+ const effort = request.reasoning_effort?.trim().toLowerCase();
125
+ return effort !== void 0 && effort.length > 0 && effort !== "none" || stringField(request.thinking, "type") === "enabled";
126
+ };
127
+ const chosenTool = (request) => {
128
+ const tools = request.tools ?? [];
129
+ if (tools.length === 0 || request.tool_choice === "none") return;
130
+ const forced = stringField(jsonField(request.tool_choice, "function"), "name");
131
+ return tools.find((tool) => tool.function.name === forced) ?? tools[0];
132
+ };
133
+ const defaultPlan = (request, seq, reasoningModels, defaultText, reasoningField) => {
134
+ const reasoning = request.model !== void 0 && reasoningModels.includes(request.model) && reasoningRequested(request) ? defaultReasoning : [];
135
+ const tool = chosenTool(request);
136
+ const base = {
137
+ reasoning,
138
+ reasoningField,
139
+ order: "reasoning-first",
140
+ usage: void 0
141
+ };
142
+ if (tool === void 0) return {
143
+ ...base,
144
+ text: defaultText,
145
+ toolCalls: [],
146
+ finishReason: "stop"
147
+ };
148
+ const argumentsText = JSON.stringify(synthesizeValue(tool.function.parameters ?? { type: "object" }, tool.function.name, 0));
149
+ return {
150
+ ...base,
151
+ text: [],
152
+ toolCalls: [{
153
+ id: `call_synthetic_${seq}_0`,
154
+ name: tool.function.name,
155
+ fragments: splitFragments(argumentsText, 3)
156
+ }],
157
+ finishReason: "tool_calls"
158
+ };
159
+ };
160
+ const scriptedPlan = (turn, seq, reasoningField) => {
161
+ const toolCalls = (turn.toolCalls ?? []).map((call, index) => ({
162
+ id: call.id ?? `call_synthetic_${seq}_${index}`,
163
+ name: call.name,
164
+ fragments: call.argumentFragments
165
+ }));
166
+ return {
167
+ reasoning: turn.reasoning ?? [],
168
+ reasoningField: turn.reasoningField ?? reasoningField,
169
+ order: turn.order ?? "reasoning-first",
170
+ text: turn.text ?? [],
171
+ toolCalls,
172
+ usage: turn.usage,
173
+ finishReason: turn.finishReason ?? (toolCalls.length > 0 ? "tool_calls" : "stop")
174
+ };
175
+ };
176
+ /** The plain OpenAI Chat Completions usage object. */
177
+ const openAiUsage = (counts) => {
178
+ const usage = {
179
+ prompt_tokens: counts.promptTokens,
180
+ completion_tokens: counts.completionTokens,
181
+ total_tokens: counts.promptTokens + counts.completionTokens
182
+ };
183
+ return counts.reasoningTokens === void 0 ? usage : {
184
+ ...usage,
185
+ completion_tokens_details: { reasoning_tokens: counts.reasoningTokens }
186
+ };
187
+ };
188
+ const noFields = () => ({});
189
+ const resolveWireProfile = (profile = {}) => ({
190
+ eventsPerChunk: profile.eventsPerChunk ?? 1,
191
+ streamUsage: profile.streamUsage ?? "usage-chunk",
192
+ openingDelta: profile.openingDelta ?? {
193
+ role: "assistant",
194
+ content: ""
195
+ },
196
+ choiceFields: profile.choiceFields ?? {},
197
+ reasoningField: profile.reasoningField ?? "reasoning_content",
198
+ reasoningDetails: profile.reasoningDetails ?? false,
199
+ chunkFields: profile.chunkFields ?? noFields,
200
+ finishFields: profile.finishFields ?? (() => ({
201
+ chunk: {},
202
+ delta: {}
203
+ })),
204
+ usage: profile.usage ?? openAiUsage
205
+ });
206
+ const usageCounts = (plan, requestText) => {
207
+ if (plan.usage === null) return;
208
+ if (plan.usage !== void 0) return {
209
+ promptTokens: plan.usage.promptTokens,
210
+ completionTokens: plan.usage.completionTokens,
211
+ reasoningTokens: plan.usage.reasoningTokens
212
+ };
213
+ const reasoningText = plan.reasoning.join("");
214
+ const outputText = [
215
+ ...plan.text,
216
+ ...plan.reasoning,
217
+ ...plan.toolCalls.flatMap((call) => call.fragments)
218
+ ].join("");
219
+ return {
220
+ promptTokens: approximateTokens(requestText),
221
+ completionTokens: approximateTokens(outputText),
222
+ reasoningTokens: reasoningText.length > 0 ? approximateTokens(reasoningText) : void 0
223
+ };
224
+ };
225
+ /** One SSE event from its `data:` text (a JSON payload, or `[DONE]`). */
226
+ const sseEvent = (data) => `data: ${data}\n\n`;
227
+ /**
228
+ * Pack SSE events into network chunks of `perChunk` events, counting from the end: the last
229
+ * chunk always holds the final events (the finish event and `data: [DONE]` when `perChunk` is at
230
+ * least 2, as the live Gateway sends them), and the first chunk may hold fewer.
231
+ */
232
+ const packEvents = (events, perChunk) => {
233
+ if (perChunk <= 1) return events;
234
+ const chunks = [];
235
+ const first = events.length % perChunk;
236
+ if (first > 0) chunks.push(events.slice(0, first).join(""));
237
+ for (let index = first; index < events.length; index += perChunk) chunks.push(events.slice(index, index + perChunk).join(""));
238
+ return chunks;
239
+ };
240
+ const reasoningDelta = (field, text, details) => field === "reasoning_content" ? { reasoning_content: text } : details ? {
241
+ reasoning: text,
242
+ reasoning_details: [{
243
+ type: "reasoning.text",
244
+ text,
245
+ format: "unknown",
246
+ index: 0
247
+ }]
248
+ } : { reasoning: text };
249
+ /** SSE events for a streamed completion, one per array entry (packed into chunks later). */
250
+ const streamEvents = (plan, identity, usage, wire) => {
251
+ const events = [];
252
+ const extras = wire.chunkFields(identity);
253
+ const event = (choices, fields = {}, trailing = {}) => events.push(sseEvent(JSON.stringify({
254
+ id: identity.id,
255
+ object: "chat.completion.chunk",
256
+ created: syntheticCreated,
257
+ model: identity.model,
258
+ choices,
259
+ ...fields,
260
+ ...extras,
261
+ ...trailing
262
+ })));
263
+ const choice = (delta, finishReason = null) => ({
264
+ index: 0,
265
+ delta,
266
+ ...wire.choiceFields,
267
+ finish_reason: finishReason
268
+ });
269
+ const reasoningEvents = () => {
270
+ for (const text of plan.reasoning) event([choice(reasoningDelta(plan.reasoningField, text, wire.reasoningDetails))]);
271
+ };
272
+ const textEvents = () => {
273
+ for (const text of plan.text) event([choice({ content: text })]);
274
+ };
275
+ event([choice(wire.openingDelta)]);
276
+ if (plan.order === "reasoning-first") {
277
+ reasoningEvents();
278
+ textEvents();
279
+ } else {
280
+ textEvents();
281
+ reasoningEvents();
282
+ }
283
+ plan.toolCalls.forEach((call, index) => {
284
+ event([choice({ tool_calls: [{
285
+ index,
286
+ id: call.id,
287
+ type: "function",
288
+ function: {
289
+ name: call.name,
290
+ arguments: ""
291
+ }
292
+ }] })]);
293
+ for (const fragment of call.fragments) event([choice({ tool_calls: [{
294
+ index,
295
+ function: { arguments: fragment }
296
+ }] })]);
297
+ });
298
+ const finish = wire.finishFields(identity);
299
+ const usageOnFinish = wire.streamUsage === "finish-event" && usage !== void 0;
300
+ const finishFields = {};
301
+ if (usageOnFinish) finishFields.usage = usage;
302
+ event([choice({ ...finish.delta }, plan.finishReason)], finishFields, finish.chunk);
303
+ if (!usageOnFinish && usage !== void 0) event([], { usage });
304
+ events.push(sseEvent("[DONE]"));
305
+ return events;
306
+ };
307
+ /** The non-streamed `chat.completion` JSON body. */
308
+ const completionBody = (plan, identity, usage) => {
309
+ const message = {
310
+ role: "assistant",
311
+ content: plan.text.length > 0 ? plan.text.join("") : null
312
+ };
313
+ if (plan.reasoning.length > 0 && plan.reasoningField === "reasoning") message.reasoning = plan.reasoning.join("");
314
+ else if (plan.reasoning.length > 0) message.reasoning_content = plan.reasoning.join("");
315
+ if (plan.toolCalls.length > 0) message.tool_calls = plan.toolCalls.map((call) => ({
316
+ id: call.id,
317
+ type: "function",
318
+ function: {
319
+ name: call.name,
320
+ arguments: call.fragments.join("")
321
+ }
322
+ }));
323
+ const body = {
324
+ id: identity.id,
325
+ object: "chat.completion",
326
+ created: syntheticCreated,
327
+ model: identity.model,
328
+ choices: [{
329
+ index: 0,
330
+ message,
331
+ finish_reason: plan.finishReason
332
+ }]
333
+ };
334
+ if (usage !== void 0) body.usage = usage;
335
+ return body;
336
+ };
337
+ /**
338
+ * Create a Chat Completions emulator from its config. Each call has
339
+ * independent ledger, fault, and script state.
340
+ *
341
+ * Precedence per chat request: authentication and JSON validation, then the
342
+ * first matching fault if it is a `status` fault, then the next scripted
343
+ * turn, then model validation and defaults; a first matching chunk fault then
344
+ * shapes the body. Only the first matching fault (in insertion order)
345
+ * applies. Credential headers are never recorded, and the bearer value is
346
+ * never checked or stored.
347
+ */
348
+ const makeChatCompletionsEmulator = (config) => {
349
+ const knownModels = [...config.knownModels];
350
+ const reasoningModels = config.reasoningModels === void 0 ? [] : [...config.reasoningModels];
351
+ const wire = resolveWireProfile(config.wire);
352
+ const unknownModelResponse = (model) => "body" in config.unknownModel ? jsonResponse(config.unknownModel.status, config.unknownModel.body(model)) : errorResponse(config.unknownModel.status, config.unknownModel.error);
353
+ const errorResponse = (status, error) => jsonResponse(status, config.errorEnvelope(error));
354
+ const defaultFaultBody = (status) => status === 429 ? config.errorEnvelope({
355
+ message: "Synthetic rate limit: too many requests.",
356
+ type: "rate_limit_exceeded",
357
+ code: "rate_limit_exceeded"
358
+ }) : config.errorEnvelope({
359
+ message: `Synthetic upstream error (${status}).`,
360
+ type: "api_error",
361
+ code: "upstream_error"
362
+ });
363
+ const invalidRequest = (message, code) => errorResponse(400, {
364
+ message,
365
+ type: "invalid_request_error",
366
+ code
367
+ });
368
+ const kernel = makeEmulatorKernel({
369
+ routes: config.routes,
370
+ faultSchema: ChatFault,
371
+ turnSchema: config.turnSchema,
372
+ newEntry: (base) => ({
373
+ ...base,
374
+ toolNames: []
375
+ }),
376
+ snapshotEntry: (entry) => ({
377
+ ...entry,
378
+ toolNames: [...entry.toolNames]
379
+ }),
380
+ unknownRoute: () => errorResponse(404, {
381
+ message: "Synthetic: no emulated route.",
382
+ type: "not_found_error",
383
+ code: "not_found"
384
+ }),
385
+ stateFields: () => config.reasoningModels === void 0 ? { knownModels } : {
386
+ knownModels,
387
+ reasoningModels
388
+ },
389
+ inputInvalid: config.inputInvalid
390
+ });
391
+ const authorized = (request) => bearerPattern.test(request.headers.get("authorization") ?? "");
392
+ const chatCompletion = async (request, entry, path) => {
393
+ if (!authorized(request)) {
394
+ entry.status = 401;
395
+ return errorResponse(401, config.auth.unauthorized);
396
+ }
397
+ const text = await readText(request);
398
+ const json = text === void 0 ? void 0 : parseJson(text);
399
+ if (text === void 0 || json === void 0) {
400
+ entry.status = 400;
401
+ return invalidRequest("Synthetic: the request body is not valid JSON.", "invalid_json");
402
+ }
403
+ entry.body = json;
404
+ const decoded = decodeChatRequest(json);
405
+ if (Result.isFailure(decoded)) {
406
+ entry.status = 400;
407
+ return invalidRequest("Synthetic: the request body is not a chat completion request.", "invalid_request");
408
+ }
409
+ const chat = decoded.success;
410
+ const seq = entry.seq;
411
+ if (chat.model !== void 0) entry.model = chat.model;
412
+ entry.stream = chat.stream === true;
413
+ const tokenLimit = jsonField(json, config.completionTokenField);
414
+ if (Predicate.isNumber(tokenLimit)) entry.maxCompletionTokens = tokenLimit;
415
+ entry.toolNames = (chat.tools ?? []).map((tool) => tool.function.name);
416
+ if (chat.reasoning_effort !== void 0 && chat.reasoning_effort !== null) entry.reasoningEffort = chat.reasoning_effort;
417
+ if (chat.thinking !== void 0) entry.thinking = chat.thinking;
418
+ const fault = kernel.takeFault(path, chat.model);
419
+ if (fault !== void 0 && fault.fault.kind === "status") return kernel.respondWithStatusFault(entry, fault, fault.fault, defaultFaultBody);
420
+ const turn = kernel.nextTurn();
421
+ if (turn !== void 0 && "error" in turn) {
422
+ entry.scripted = "error";
423
+ return kernel.respondWithScriptedError(entry, turn.error, fault);
424
+ }
425
+ if (turn === void 0 && (chat.model === void 0 || !knownModels.includes(chat.model))) {
426
+ entry.status = config.unknownModel.status;
427
+ return unknownModelResponse(chat.model);
428
+ }
429
+ if (turn !== void 0) entry.scripted = "completion";
430
+ const plan = turn === void 0 ? defaultPlan(chat, seq, reasoningModels, config.defaultText, wire.reasoningField) : scriptedPlan(turn, seq, wire.reasoningField);
431
+ const identity = {
432
+ id: `${config.responseIdPrefix}-${seq}`,
433
+ model: chat.model ?? "unknown"
434
+ };
435
+ const counts = usageCounts(plan, text);
436
+ const usage = counts === void 0 ? void 0 : wire.usage(counts);
437
+ if (chat.stream === true) {
438
+ const streamUsage = chat.stream_options?.include_usage === true ? usage : void 0;
439
+ return kernel.respondWithBody(entry, 200, {
440
+ "content-type": "text/event-stream",
441
+ "cache-control": "no-cache"
442
+ }, packEvents(streamEvents(plan, identity, streamUsage, wire), wire.eventsPerChunk), fault);
443
+ }
444
+ return kernel.respondWithBody(entry, 200, { "content-type": "application/json" }, [JSON.stringify(completionBody(plan, identity, usage))], fault);
445
+ };
446
+ return kernel.serve(new Map([[emulatorRouteKey("POST", config.path), chatCompletion]]));
447
+ };
448
+ //#endregion
449
+ export { ChatFault, ChatFaultMatch, ChatScriptedError, ChatScriptedReasoningCompletion, ChatScriptedReasoningTurn, ChatScriptedToolCall, ChatScriptedUsage, chatScriptedCompletionFields, chatScriptedReasoningFields, makeChatCompletionsEmulator };
450
+
451
+ //# sourceMappingURL=chat-completions.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-completions.mjs","names":[],"sources":["../src/chat-completions.ts"],"sourcesContent":["/**\n * OpenAI-compatible Chat Completions emulator core (internal; not a package export).\n *\n * One `POST .../chat/completions` route with request parsing, `chat.completion.chunk` SSE\n * framing (text, reasoning, tool-call argument fragments, a finish event, usage, and\n * `data: [DONE]`), the non-streamed `chat.completion` JSON body, and scripted turns. Faults, the\n * request ledger, the `/_emulate/*` control plane, evidence tagging, and route binding come from\n * the shared kernel (`emulator-kernel.ts`).\n *\n * Each emulator subpath (`gateway`, `openai`) supplies what differs: the path and route evidence\n * manifest, model lists, the error envelope and the unknown-model status and error, the 401\n * error, the completion-token request field, whether reasoning is emulated, its scripted-turn\n * schema, and its wire profile (`ChatWireProfile`: events per network chunk, usage placement, the\n * reasoning field, and extra per-chunk fields). Without a profile the plain OpenAI Chat\n * Completions wire is sent.\n *\n * Runtime-portable Web APIs only (`Request`, `Response`, `ReadableStream`, `TextEncoder`, `URL`);\n * no Node builtins and no SDK imports.\n */\nimport { Predicate, Result } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport {\n approximateTokens,\n EmulatorErrorAfterChunksFault,\n EmulatorFaultMatch,\n EmulatorScriptedError,\n EmulatorStatusFault,\n EmulatorTruncateAfterChunksFault,\n jsonField,\n jsonResponse,\n makeEmulatorKernel,\n parseJson,\n readText,\n splitFragments,\n stringField,\n synthesizeValue,\n type EmulatorApi,\n type EmulatorCoverage,\n type EmulatorFaultState,\n type EmulatorRouteCoverage,\n type KernelLedgerEntry\n} from './emulator-kernel.ts'\nimport {\n emulatorRouteKey,\n type EmulatorEvidence,\n type EmulatorRouteEvidence\n} from './route-evidence.ts'\n\n/** Optional fault filter; an omitted field matches every request. `path` ending in `*` is a prefix. */\nexport const ChatFaultMatch = EmulatorFaultMatch\n\nexport type ChatFaultMatch = typeof ChatFaultMatch.Type\n\n/**\n * Wire faults for emulated routes:\n *\n * - `status`: answer with this status, headers, and body instead of a\n * completion (for example 429 with `retry-after`). The body defaults to the\n * emulator's error envelope. Statuses that cannot carry a body (1xx, 204,\n * 205, 304) and redirects (3xx) are rejected, as are invalid header names or\n * values and a `location` header; scripted errors follow the same rules.\n * - `error-after-chunks`: send `chunks` body chunks, then error the body\n * stream (a dropped connection).\n * - `truncate-after-chunks`: send `chunks` body chunks, then close the body\n * cleanly (for example without `data: [DONE]`).\n *\n * A whole JSON body counts as one chunk. A chunk fault that cannot take\n * effect (`error-after-chunks` beyond the chunk count, or\n * `truncate-after-chunks` at or beyond it) answers 500 with an emulator error\n * instead of silently doing nothing, and is not consumed.\n */\nexport const ChatFault = Schema.Union([\n EmulatorStatusFault,\n EmulatorErrorAfterChunksFault,\n EmulatorTruncateAfterChunksFault\n])\n\nexport type ChatFault = typeof ChatFault.Type\n\nexport type ChatFaultKind = ChatFault['kind']\n\nconst TokenCount = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))\n\n/** Usage for a scripted turn (sent as the wire `usage` object). */\nexport const ChatScriptedUsage = Schema.Struct({\n promptTokens: TokenCount,\n completionTokens: TokenCount,\n reasoningTokens: Schema.optionalKey(TokenCount)\n})\n\nexport type ChatScriptedUsage = typeof ChatScriptedUsage.Type\n\n/** One scripted tool call; its JSON arguments stream as these fragments. */\nexport const ChatScriptedToolCall = Schema.Struct({\n name: Schema.NonEmptyString,\n argumentFragments: Schema.Array(Schema.String),\n id: Schema.optionalKey(Schema.NonEmptyString)\n})\n\nexport type ChatScriptedToolCall = typeof ChatScriptedToolCall.Type\n\n/** Scripted completion fields every Chat Completions emulator accepts. */\nexport const chatScriptedCompletionFields = {\n text: Schema.optionalKey(Schema.Array(Schema.String)),\n toolCalls: Schema.optionalKey(Schema.Array(ChatScriptedToolCall)),\n usage: Schema.optionalKey(Schema.NullOr(ChatScriptedUsage)),\n finishReason: Schema.optionalKey(Schema.NonEmptyString)\n}\n\n/** Scripted completion fields for emulators that emulate streamed reasoning. */\nexport const chatScriptedReasoningFields = {\n reasoning: Schema.optionalKey(Schema.Array(Schema.String)),\n reasoningField: Schema.optionalKey(Schema.Literals(['reasoning_content', 'reasoning'])),\n order: Schema.optionalKey(Schema.Literals(['reasoning-first', 'text-first']))\n}\n\n/**\n * The reasoning variant of a scripted completion (the Gateway's shape; `/openai` uses the\n * reasoning-free `OpenAiScriptedCompletion`). Every field is exact (nothing is\n * filled in) except: `usage` omitted is synthesized when the request asks for\n * usage, and `null` drops it; `finishReason` omitted is `tool_calls` with tool\n * calls, else `stop`. `order` defaults to `reasoning-first`; `reasoningField`\n * defaults to the emulator's wire profile field (`reasoning` for the Gateway,\n * as recorded live; `reasoning_content` is the DeepSeek-native alternative).\n */\nexport const ChatScriptedReasoningCompletion = Schema.Struct({\n text: chatScriptedCompletionFields.text,\n reasoning: chatScriptedReasoningFields.reasoning,\n reasoningField: chatScriptedReasoningFields.reasoningField,\n order: chatScriptedReasoningFields.order,\n toolCalls: chatScriptedCompletionFields.toolCalls,\n usage: chatScriptedCompletionFields.usage,\n finishReason: chatScriptedCompletionFields.finishReason\n})\n\nexport type ChatScriptedReasoningCompletion = typeof ChatScriptedReasoningCompletion.Type\n\n/** A scripted error response: status, body (a string is sent as is), and optional headers. */\nexport const ChatScriptedError = EmulatorScriptedError\n\nexport type ChatScriptedError = typeof ChatScriptedError.Type\n\n/**\n * The reasoning variant of a scripted turn, queued for the next chat completion request. It is\n * also the widest turn shape, so it bounds every emulator's turn type.\n */\nexport const ChatScriptedReasoningTurn = Schema.Union([\n ChatScriptedError,\n ChatScriptedReasoningCompletion\n])\n\nexport type ChatScriptedReasoningTurn = typeof ChatScriptedReasoningTurn.Type\n\nexport type ChatLedgerEntry = {\n /** 1-based arrival order since the last ledger clear or reset. */\n readonly seq: number\n readonly method: string\n readonly path: string\n /** Parsed JSON request body, when it was valid JSON. */\n readonly body?: Schema.Json\n readonly model?: string\n readonly stream?: boolean\n /**\n * The output-token limit from the emulator's completion-token request field (`max_tokens` or\n * `max_completion_tokens`), when it is a number. Recorded as sent; never validated.\n */\n readonly maxCompletionTokens?: number\n readonly reasoningEffort?: string\n readonly thinking?: Schema.Json\n readonly toolNames: ReadonlyArray<string>\n /** Kind of the fault that shaped the response. */\n readonly fault?: ChatFaultKind\n /** Why a matching fault could not take effect (the response was a 500 emulator error). */\n readonly faultError?: string\n /**\n * Set when the emulator could not build the planned response; the request was\n * answered with a 500 emulator error (still evidence-tagged) and no fault was used up.\n */\n readonly responseError?: string\n /** Set when a scripted turn answered the request. */\n readonly scripted?: 'completion' | 'error'\n /** Evidence of the matched route; `unknown-route` for requests that failed closed. */\n readonly evidence: EmulatorEvidence | 'unknown-route'\n readonly status: number\n /** Body chunks handed to the transport so far (each produced when pulled). */\n readonly bodyChunks: number\n}\n\nexport type ChatFaultState = EmulatorFaultState<ChatFault>\n\nexport type ChatRouteCoverage = EmulatorRouteCoverage\n\nexport type ChatCoverage = EmulatorCoverage\n\nexport type ChatCompletionsEmulator<Turn> = EmulatorApi<Turn, ChatFault, ChatLedgerEntry>\n\n/** One wire error: the emulator's envelope renders it. */\nexport type ChatWireError = {\n readonly message: string\n readonly type: string\n readonly code: string\n}\n\n/** The id and model every chunk of one emulated response repeats. */\nexport type ChatResponseIdentity = { readonly id: string; readonly model: string }\n\n/** Token counts behind a wire `usage` object. */\nexport type ChatUsageCounts = {\n readonly promptTokens: number\n readonly completionTokens: number\n readonly reasoningTokens: number | undefined\n}\n\n/**\n * Per-service response details on top of the shared framing. Every field is optional; an omitted\n * field keeps the plain OpenAI Chat Completions wire (the `/openai` emulator passes no profile).\n */\nexport type ChatWireProfile = {\n /**\n * SSE events per network (body) chunk; default 1. Events are packed counting from the end, so\n * the last chunk carries the finish event together with `data: [DONE]` and the first chunk may\n * carry fewer. Chunk faults count these network chunks.\n */\n readonly eventsPerChunk?: number\n /**\n * Where streamed usage goes when `stream_options.include_usage` is set: a trailing chunk with\n * empty `choices` (`usage-chunk`, default) or the finish event itself (`finish-event`).\n */\n readonly streamUsage?: 'usage-chunk' | 'finish-event'\n /** The first streamed delta; default `{ role: 'assistant', content: '' }`. */\n readonly openingDelta?: Schema.JsonObject\n /** Extra fields on every streamed choice, placed before `finish_reason` (for example `logprobs`). */\n readonly choiceFields?: Schema.JsonObject\n /** The default reasoning field for default and scripted output; default `reasoning_content`. */\n readonly reasoningField?: 'reasoning_content' | 'reasoning'\n /** Streamed `reasoning` deltas also carry `reasoning_details` (`reasoning.text` entries). */\n readonly reasoningDetails?: boolean\n /** Extra top-level fields on every streamed chunk (for example `system_fingerprint`). */\n readonly chunkFields?: (identity: ChatResponseIdentity) => Schema.JsonObject\n /** Extra fields on the finish event: top-level (`chunk`) and inside its `delta`. */\n readonly finishFields?: (identity: ChatResponseIdentity) => {\n readonly chunk: Schema.JsonObject\n readonly delta: Schema.JsonObject\n }\n /** Renders token counts as the wire `usage` object (streamed and JSON). */\n readonly usage?: (counts: ChatUsageCounts) => Schema.JsonObject\n}\n\n/**\n * What differs between Chat Completions emulators. The core owns everything else (framing,\n * faults, scripting, ledger, control plane, evidence).\n */\nexport type ChatCompletionsEmulatorConfig<Turn extends ChatScriptedReasoningTurn> = {\n /** The chat completions path, for example `/v1/chat/completions`. */\n readonly path: string\n /** Route evidence manifest; must list exactly the chat completions route. */\n readonly routes: ReadonlyArray<EmulatorRouteEvidence>\n readonly knownModels: ReadonlyArray<string>\n /**\n * Streamed reasoning: `undefined` when the emulator does not emulate reasoning (default plans\n * never reason and `/_emulate/state` omits `reasoningModels`); otherwise the models that stream\n * reasoning when a request asks for it.\n */\n readonly reasoningModels: ReadonlyArray<string> | undefined\n /** Renders a wire error as the service's JSON error envelope. */\n readonly errorEnvelope: (error: ChatWireError) => Schema.Json\n /**\n * Status and answer for an unknown (or missing) model id: an `error` rendered with\n * `errorEnvelope`, or a `body` built from the requested id (for a service whose unknown-model\n * envelope differs from its other errors).\n */\n readonly unknownModel:\n | { readonly status: number; readonly error: ChatWireError }\n | { readonly status: number; readonly body: (model: string | undefined) => Schema.Json }\n /**\n * Requests authenticate with a non-empty `Authorization: Bearer` credential, never checked or\n * stored; anything else answers 401 with `unauthorized`.\n */\n readonly auth: { readonly unauthorized: ChatWireError }\n /** Request field carrying the output-token limit, recorded in the ledger. */\n readonly completionTokenField: 'max_tokens' | 'max_completion_tokens'\n /** Response id prefix; ids are `${prefix}-${seq}`. */\n readonly responseIdPrefix: string\n /** Default answer text deltas. */\n readonly defaultText: ReadonlyArray<string>\n /** The emulator's scripted-turn schema (decoded strictly). */\n readonly turnSchema: Schema.Decoder<Turn>\n /** Builds the error thrown by the JS API (`faults.add`, `script.enqueue`) for invalid input. */\n readonly inputInvalid: (input: 'fault' | 'turn', reason: string) => Error\n /** Per-service wire details; omitted: the plain OpenAI Chat Completions wire. */\n readonly wire?: ChatWireProfile\n}\n\nconst ChatTool = Schema.Struct({\n type: Schema.optionalKey(Schema.String),\n function: Schema.Struct({\n name: Schema.String,\n description: Schema.optionalKey(Schema.String),\n parameters: Schema.optionalKey(Schema.Json)\n })\n})\n\nconst ChatRequest = Schema.Struct({\n model: Schema.optionalKey(Schema.String),\n messages: Schema.Array(Schema.Json),\n stream: Schema.optionalKey(Schema.NullOr(Schema.Boolean)),\n stream_options: Schema.optionalKey(\n Schema.NullOr(\n Schema.Struct({ include_usage: Schema.optionalKey(Schema.NullOr(Schema.Boolean)) })\n )\n ),\n reasoning_effort: Schema.optionalKey(Schema.NullOr(Schema.String)),\n thinking: Schema.optionalKey(Schema.Json),\n tools: Schema.optionalKey(Schema.NullOr(Schema.Array(ChatTool))),\n tool_choice: Schema.optionalKey(Schema.Json)\n})\n\ntype ChatRequest = typeof ChatRequest.Type\n\nconst decodeChatRequest = Schema.decodeUnknownResult(ChatRequest)\n\n// Synthetic creation time shared by every emulated response (deterministic output).\nconst syntheticCreated = 1790000000\n\n// A non-empty bearer credential. The value is never checked or stored.\nconst bearerPattern = /^bearer\\s+\\S+/i\n\ntype CompletionPlan = {\n readonly reasoning: ReadonlyArray<string>\n readonly reasoningField: 'reasoning_content' | 'reasoning'\n readonly order: 'reasoning-first' | 'text-first'\n readonly text: ReadonlyArray<string>\n readonly toolCalls: ReadonlyArray<{\n readonly id: string\n readonly name: string\n readonly fragments: ReadonlyArray<string>\n }>\n /** `null`: never send usage; `undefined`: synthesize when requested. */\n readonly usage: ChatScriptedUsage | null | undefined\n readonly finishReason: string\n}\n\nconst defaultReasoning = ['The user wants a short reply.', ' Keep it brief.']\n\nconst reasoningRequested = (request: ChatRequest): boolean => {\n const effort = request.reasoning_effort?.trim().toLowerCase()\n\n return (\n (effort !== undefined && effort.length > 0 && effort !== 'none') ||\n stringField(request.thinking, 'type') === 'enabled'\n )\n}\n\nconst chosenTool = (request: ChatRequest) => {\n const tools = request.tools ?? []\n\n if (tools.length === 0 || request.tool_choice === 'none') {\n return undefined\n }\n\n const forced = stringField(jsonField(request.tool_choice, 'function'), 'name')\n\n return tools.find(tool => tool.function.name === forced) ?? tools[0]\n}\n\nconst defaultPlan = (\n request: ChatRequest,\n seq: number,\n reasoningModels: ReadonlyArray<string>,\n defaultText: ReadonlyArray<string>,\n reasoningField: CompletionPlan['reasoningField']\n): CompletionPlan => {\n const reasoning =\n request.model !== undefined &&\n reasoningModels.includes(request.model) &&\n reasoningRequested(request)\n ? defaultReasoning\n : []\n\n const tool = chosenTool(request)\n\n const base: Pick<CompletionPlan, 'reasoning' | 'reasoningField' | 'order' | 'usage'> = {\n reasoning,\n reasoningField,\n order: 'reasoning-first',\n usage: undefined\n }\n\n if (tool === undefined) {\n return { ...base, text: defaultText, toolCalls: [], finishReason: 'stop' }\n }\n\n const argumentsText = JSON.stringify(\n synthesizeValue(tool.function.parameters ?? { type: 'object' }, tool.function.name, 0)\n )\n\n return {\n ...base,\n text: [],\n toolCalls: [\n {\n id: `call_synthetic_${seq}_0`,\n name: tool.function.name,\n fragments: splitFragments(argumentsText, 3)\n }\n ],\n finishReason: 'tool_calls'\n }\n}\n\nconst scriptedPlan = (\n turn: ChatScriptedReasoningCompletion,\n seq: number,\n reasoningField: CompletionPlan['reasoningField']\n): CompletionPlan => {\n const toolCalls = (turn.toolCalls ?? []).map((call, index) => ({\n id: call.id ?? `call_synthetic_${seq}_${index}`,\n name: call.name,\n fragments: call.argumentFragments\n }))\n\n return {\n reasoning: turn.reasoning ?? [],\n reasoningField: turn.reasoningField ?? reasoningField,\n order: turn.order ?? 'reasoning-first',\n text: turn.text ?? [],\n toolCalls,\n usage: turn.usage,\n finishReason: turn.finishReason ?? (toolCalls.length > 0 ? 'tool_calls' : 'stop')\n }\n}\n\n// Wire shapes of the OpenAI-compatible Chat Completions responses the emulator sends. Profile\n// fields (`ChatWireProfile`) add to them.\ntype WireToolCallDelta = {\n readonly index: number\n readonly id?: string\n readonly type?: 'function'\n readonly function: { readonly name?: string; readonly arguments: string }\n}\n\ntype WireReasoningDetail = {\n readonly type: 'reasoning.text'\n readonly text: string\n readonly format: 'unknown'\n readonly index: number\n}\n\ntype WireDelta = {\n readonly role?: 'assistant'\n readonly content?: string\n readonly reasoning_content?: string\n readonly reasoning?: string\n readonly reasoning_details?: ReadonlyArray<WireReasoningDetail>\n readonly tool_calls?: ReadonlyArray<WireToolCallDelta>\n}\n\ntype WireStreamChoice = {\n readonly index: number\n readonly delta: WireDelta | Schema.JsonObject\n readonly finish_reason: string | null\n}\n\ntype WireMessage = {\n role: 'assistant'\n content: string | null\n reasoning_content?: string\n reasoning?: string\n tool_calls?: ReadonlyArray<{\n readonly id: string\n readonly type: 'function'\n readonly function: { readonly name: string; readonly arguments: string }\n }>\n}\n\ntype WireCompletion = {\n id: string\n object: 'chat.completion'\n created: number\n model: string\n choices: ReadonlyArray<{\n readonly index: number\n readonly message: WireMessage\n readonly finish_reason: string\n }>\n usage?: Schema.JsonObject\n}\n\n/** A wire profile with every default filled in. */\ntype ResolvedWireProfile = {\n readonly eventsPerChunk: number\n readonly streamUsage: 'usage-chunk' | 'finish-event'\n readonly openingDelta: Schema.JsonObject\n readonly choiceFields: Schema.JsonObject\n readonly reasoningField: 'reasoning_content' | 'reasoning'\n readonly reasoningDetails: boolean\n readonly chunkFields: (identity: ChatResponseIdentity) => Schema.JsonObject\n readonly finishFields: (identity: ChatResponseIdentity) => {\n readonly chunk: Schema.JsonObject\n readonly delta: Schema.JsonObject\n }\n readonly usage: (counts: ChatUsageCounts) => Schema.JsonObject\n}\n\n/** The plain OpenAI Chat Completions usage object. */\nconst openAiUsage = (counts: ChatUsageCounts): Schema.JsonObject => {\n const usage = {\n prompt_tokens: counts.promptTokens,\n completion_tokens: counts.completionTokens,\n total_tokens: counts.promptTokens + counts.completionTokens\n }\n\n return counts.reasoningTokens === undefined\n ? usage\n : { ...usage, completion_tokens_details: { reasoning_tokens: counts.reasoningTokens } }\n}\n\nconst noFields = (): Schema.JsonObject => ({})\n\nconst resolveWireProfile = (profile: ChatWireProfile = {}): ResolvedWireProfile => ({\n eventsPerChunk: profile.eventsPerChunk ?? 1,\n streamUsage: profile.streamUsage ?? 'usage-chunk',\n openingDelta: profile.openingDelta ?? { role: 'assistant', content: '' },\n choiceFields: profile.choiceFields ?? {},\n reasoningField: profile.reasoningField ?? 'reasoning_content',\n reasoningDetails: profile.reasoningDetails ?? false,\n chunkFields: profile.chunkFields ?? noFields,\n finishFields: profile.finishFields ?? (() => ({ chunk: {}, delta: {} })),\n usage: profile.usage ?? openAiUsage\n})\n\nconst usageCounts = (plan: CompletionPlan, requestText: string): ChatUsageCounts | undefined => {\n if (plan.usage === null) {\n return undefined\n }\n\n if (plan.usage !== undefined) {\n return {\n promptTokens: plan.usage.promptTokens,\n completionTokens: plan.usage.completionTokens,\n reasoningTokens: plan.usage.reasoningTokens\n }\n }\n\n const reasoningText = plan.reasoning.join('')\n\n const outputText = [\n ...plan.text,\n ...plan.reasoning,\n ...plan.toolCalls.flatMap(call => call.fragments)\n ].join('')\n\n return {\n promptTokens: approximateTokens(requestText),\n completionTokens: approximateTokens(outputText),\n reasoningTokens: reasoningText.length > 0 ? approximateTokens(reasoningText) : undefined\n }\n}\n\n/** One SSE event from its `data:` text (a JSON payload, or `[DONE]`). */\nconst sseEvent = (data: string): string => `data: ${data}\\n\\n`\n\n/**\n * Pack SSE events into network chunks of `perChunk` events, counting from the end: the last\n * chunk always holds the final events (the finish event and `data: [DONE]` when `perChunk` is at\n * least 2, as the live Gateway sends them), and the first chunk may hold fewer.\n */\nconst packEvents = (events: ReadonlyArray<string>, perChunk: number): ReadonlyArray<string> => {\n if (perChunk <= 1) {\n return events\n }\n\n const chunks: Array<string> = []\n const first = events.length % perChunk\n\n if (first > 0) {\n chunks.push(events.slice(0, first).join(''))\n }\n\n for (let index = first; index < events.length; index += perChunk) {\n chunks.push(events.slice(index, index + perChunk).join(''))\n }\n\n return chunks\n}\n\nconst reasoningDelta = (\n field: CompletionPlan['reasoningField'],\n text: string,\n details: boolean\n): WireDelta =>\n field === 'reasoning_content'\n ? { reasoning_content: text }\n : details\n ? {\n reasoning: text,\n reasoning_details: [{ type: 'reasoning.text', text, format: 'unknown', index: 0 }]\n }\n : { reasoning: text }\n\n/** SSE events for a streamed completion, one per array entry (packed into chunks later). */\nconst streamEvents = (\n plan: CompletionPlan,\n identity: ChatResponseIdentity,\n usage: Schema.JsonObject | undefined,\n wire: ResolvedWireProfile\n): ReadonlyArray<string> => {\n const events: Array<string> = []\n const extras = wire.chunkFields(identity)\n\n const event = (\n choices: ReadonlyArray<WireStreamChoice>,\n fields: Schema.JsonObject = {},\n trailing: Schema.JsonObject = {}\n ) =>\n events.push(\n sseEvent(\n JSON.stringify({\n id: identity.id,\n object: 'chat.completion.chunk',\n created: syntheticCreated,\n model: identity.model,\n choices,\n ...fields,\n ...extras,\n ...trailing\n })\n )\n )\n\n const choice = (\n delta: WireDelta | Schema.JsonObject,\n finishReason: string | null = null\n ): WireStreamChoice => ({\n index: 0,\n delta,\n ...wire.choiceFields,\n finish_reason: finishReason\n })\n\n const reasoningEvents = () => {\n for (const text of plan.reasoning) {\n event([choice(reasoningDelta(plan.reasoningField, text, wire.reasoningDetails))])\n }\n }\n\n const textEvents = () => {\n for (const text of plan.text) {\n event([choice({ content: text })])\n }\n }\n\n event([choice(wire.openingDelta)])\n\n if (plan.order === 'reasoning-first') {\n reasoningEvents()\n textEvents()\n } else {\n textEvents()\n reasoningEvents()\n }\n\n plan.toolCalls.forEach((call, index) => {\n event([\n choice({\n tool_calls: [\n { index, id: call.id, type: 'function', function: { name: call.name, arguments: '' } }\n ]\n })\n ])\n\n for (const fragment of call.fragments) {\n event([choice({ tool_calls: [{ index, function: { arguments: fragment } }] })])\n }\n })\n\n const finish = wire.finishFields(identity)\n const usageOnFinish = wire.streamUsage === 'finish-event' && usage !== undefined\n\n const finishFields: Record<string, Schema.Json> = {}\n\n if (usageOnFinish) {\n finishFields.usage = usage\n }\n\n event([choice({ ...finish.delta }, plan.finishReason)], finishFields, finish.chunk)\n\n if (!usageOnFinish && usage !== undefined) {\n event([], { usage })\n }\n\n events.push(sseEvent('[DONE]'))\n\n return events\n}\n\n/** The non-streamed `chat.completion` JSON body. */\nconst completionBody = (\n plan: CompletionPlan,\n identity: ChatResponseIdentity,\n usage: Schema.JsonObject | undefined\n): WireCompletion => {\n const message: WireMessage = {\n role: 'assistant',\n content: plan.text.length > 0 ? plan.text.join('') : null\n }\n\n if (plan.reasoning.length > 0 && plan.reasoningField === 'reasoning') {\n message.reasoning = plan.reasoning.join('')\n } else if (plan.reasoning.length > 0) {\n message.reasoning_content = plan.reasoning.join('')\n }\n\n if (plan.toolCalls.length > 0) {\n message.tool_calls = plan.toolCalls.map(call => ({\n id: call.id,\n type: 'function',\n function: { name: call.name, arguments: call.fragments.join('') }\n }))\n }\n\n const body: WireCompletion = {\n id: identity.id,\n object: 'chat.completion',\n created: syntheticCreated,\n model: identity.model,\n choices: [{ index: 0, message, finish_reason: plan.finishReason }]\n }\n\n if (usage !== undefined) {\n body.usage = usage\n }\n\n return body\n}\n\ntype MutableLedgerEntry = KernelLedgerEntry<ChatFaultKind> & {\n body?: Schema.Json\n model?: string\n stream?: boolean\n maxCompletionTokens?: number\n reasoningEffort?: string\n thinking?: Schema.Json\n toolNames: ReadonlyArray<string>\n scripted?: 'completion' | 'error'\n}\n\n/**\n * Create a Chat Completions emulator from its config. Each call has\n * independent ledger, fault, and script state.\n *\n * Precedence per chat request: authentication and JSON validation, then the\n * first matching fault if it is a `status` fault, then the next scripted\n * turn, then model validation and defaults; a first matching chunk fault then\n * shapes the body. Only the first matching fault (in insertion order)\n * applies. Credential headers are never recorded, and the bearer value is\n * never checked or stored.\n */\nexport const makeChatCompletionsEmulator = <Turn extends ChatScriptedReasoningTurn>(\n config: ChatCompletionsEmulatorConfig<Turn>\n): ChatCompletionsEmulator<Turn> => {\n const knownModels = [...config.knownModels]\n const reasoningModels = config.reasoningModels === undefined ? [] : [...config.reasoningModels]\n const wire = resolveWireProfile(config.wire)\n\n const unknownModelResponse = (model: string | undefined): Response =>\n 'body' in config.unknownModel\n ? jsonResponse(config.unknownModel.status, config.unknownModel.body(model))\n : errorResponse(config.unknownModel.status, config.unknownModel.error)\n\n const errorResponse = (status: number, error: ChatWireError): Response =>\n jsonResponse(status, config.errorEnvelope(error))\n\n const defaultFaultBody = (status: number): Schema.Json =>\n status === 429\n ? config.errorEnvelope({\n message: 'Synthetic rate limit: too many requests.',\n type: 'rate_limit_exceeded',\n code: 'rate_limit_exceeded'\n })\n : config.errorEnvelope({\n message: `Synthetic upstream error (${status}).`,\n type: 'api_error',\n code: 'upstream_error'\n })\n\n const invalidRequest = (message: string, code: string): Response =>\n errorResponse(400, { message, type: 'invalid_request_error', code })\n\n const kernel = makeEmulatorKernel({\n routes: config.routes,\n faultSchema: ChatFault,\n turnSchema: config.turnSchema,\n newEntry: (base): MutableLedgerEntry => ({ ...base, toolNames: [] }),\n snapshotEntry: (entry): ChatLedgerEntry => ({ ...entry, toolNames: [...entry.toolNames] }),\n unknownRoute: () =>\n errorResponse(404, {\n message: 'Synthetic: no emulated route.',\n type: 'not_found_error',\n code: 'not_found'\n }),\n stateFields: () =>\n config.reasoningModels === undefined ? { knownModels } : { knownModels, reasoningModels },\n inputInvalid: config.inputInvalid\n })\n\n const authorized = (request: Request): boolean =>\n bearerPattern.test(request.headers.get('authorization') ?? '')\n\n const chatCompletion = async (\n request: Request,\n entry: MutableLedgerEntry,\n path: string\n ): Promise<Response> => {\n if (!authorized(request)) {\n entry.status = 401\n\n return errorResponse(401, config.auth.unauthorized)\n }\n\n const text = await readText(request)\n const json = text === undefined ? undefined : parseJson(text)\n\n if (text === undefined || json === undefined) {\n entry.status = 400\n\n return invalidRequest('Synthetic: the request body is not valid JSON.', 'invalid_json')\n }\n\n entry.body = json\n\n const decoded = decodeChatRequest(json)\n\n if (Result.isFailure(decoded)) {\n entry.status = 400\n\n return invalidRequest(\n 'Synthetic: the request body is not a chat completion request.',\n 'invalid_request'\n )\n }\n\n const chat = decoded.success\n const seq = entry.seq\n\n if (chat.model !== undefined) entry.model = chat.model\n\n entry.stream = chat.stream === true\n\n const tokenLimit = jsonField(json, config.completionTokenField)\n\n if (Predicate.isNumber(tokenLimit)) entry.maxCompletionTokens = tokenLimit\n\n entry.toolNames = (chat.tools ?? []).map(tool => tool.function.name)\n\n if (chat.reasoning_effort !== undefined && chat.reasoning_effort !== null) {\n entry.reasoningEffort = chat.reasoning_effort\n }\n\n if (chat.thinking !== undefined) entry.thinking = chat.thinking\n\n const fault = kernel.takeFault(path, chat.model)\n\n if (fault !== undefined && fault.fault.kind === 'status') {\n return kernel.respondWithStatusFault(entry, fault, fault.fault, defaultFaultBody)\n }\n\n // A scripted turn is used up when its request arrives. Header and status validation at\n // `script.enqueue` time means its response can always be built.\n const turn = kernel.nextTurn()\n\n if (turn !== undefined && 'error' in turn) {\n entry.scripted = 'error'\n\n return kernel.respondWithScriptedError(entry, turn.error, fault)\n }\n\n if (turn === undefined && (chat.model === undefined || !knownModels.includes(chat.model))) {\n entry.status = config.unknownModel.status\n\n return unknownModelResponse(chat.model)\n }\n\n if (turn !== undefined) entry.scripted = 'completion'\n\n const plan =\n turn === undefined\n ? defaultPlan(chat, seq, reasoningModels, config.defaultText, wire.reasoningField)\n : scriptedPlan(turn, seq, wire.reasoningField)\n\n const identity: ChatResponseIdentity = {\n id: `${config.responseIdPrefix}-${seq}`,\n model: chat.model ?? 'unknown'\n }\n\n const counts = usageCounts(plan, text)\n const usage = counts === undefined ? undefined : wire.usage(counts)\n\n if (chat.stream === true) {\n const streamUsage = chat.stream_options?.include_usage === true ? usage : undefined\n\n // Chunk faults count these network chunks (several events each when packed).\n return kernel.respondWithBody(\n entry,\n 200,\n { 'content-type': 'text/event-stream', 'cache-control': 'no-cache' },\n packEvents(streamEvents(plan, identity, streamUsage, wire), wire.eventsPerChunk),\n fault\n )\n }\n\n return kernel.respondWithBody(\n entry,\n 200,\n { 'content-type': 'application/json' },\n [JSON.stringify(completionBody(plan, identity, usage))],\n fault\n )\n }\n\n // Every manifest route maps to its own handler; construction throws `EmulatorRouteUnmapped`\n // when a manifest route has no handler (or a handler has no manifest route).\n return kernel.serve(new Map([[emulatorRouteKey('POST', config.path), chatCompletion]]))\n}\n"],"mappings":"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"}