@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,127 @@
1
+ import { emulatorEvidenceHeader } from "./route-evidence.mjs";
2
+ import { StatefulFault, StatefulFaultMatch, checkBooleanDrills, makeStatefulEmulator, routeEvidence } from "./stateful-emulator.mjs";
3
+ import { statefulCoreRuntime } from "./stateful-core.mjs";
4
+ import { NotionEmulatorBlock, NotionEmulatorBotUser, NotionEmulatorDataSource, NotionEmulatorDatabase, NotionEmulatorImpliedPage, NotionEmulatorPage, NotionEmulatorParent, NotionEmulatorProfile, NotionEmulatorPropertyItems, NotionEmulatorSeed, NotionEmulatorStateSchema, buildSeedState, decodeState } from "./notion/state.mjs";
5
+ import { notionApiRoutes, notionEmulatorBasePath, notionEmulatorDrillKnobs, notionEmulatorOrigin, notionEmulatorVersion } from "./notion/api.mjs";
6
+ import { Data, Predicate } from "effect";
7
+ //#region src/notion.ts
8
+ /**
9
+ * Stateful Notion emulator (the `/v1` routes on `https://api.notion.com` at API version
10
+ * 2025-09-03), built on the upstream `@emulators/core` custom runtime, with a request ledger,
11
+ * status faults, and an `/_emulate/*` control plane.
12
+ *
13
+ * It never imports SDK code: its wire shapes, error envelopes, and default seed are copied as data
14
+ * from the synthetic Notion conformance fixtures, and every route names the conformance cases it
15
+ * follows in `notionEmulatorRoutes`. Only the routes those eight cases (with their cleanup) send
16
+ * are emulated. Response behaviour comes only from the fixtures: anything they do not show answers
17
+ * one ledgered 400 not-emulated, writes nothing, and uses up no fault. That includes a search
18
+ * without matches or matching a trashed page, so the read-only leftover lookup fails on a clean
19
+ * workspace and after the write case (the live runner prints its lookup-failed WARN). Search
20
+ * considers only the pages whose content the state holds (an implied page never matches) and
21
+ * refuses a match shown only as a query row. Every route answers only on the recorded origin
22
+ * `https://api.notion.com` (`fetchOn(origin)` serves it behind a loopback rewrite).
23
+ *
24
+ * Request-shape latitude (`/notion`, the only accepted deviations): any bearer value (never
25
+ * checked or stored); extra request headers; JSON key order; `content-type` media-type
26
+ * parameters; the order of query parameters; Notion ids with or without dashes, in any case; any
27
+ * search `query` (looked up in the state); and any `page_size` from 1 to 100 whose page shows
28
+ * only recorded results (the data source query: 1). `Notion-Version` must be `2025-09-03`.
29
+ * Everything else (other keys, filters, booleans, sorts, query parameters, another origin,
30
+ * titles, repeated or missing `page_size`, and cursors not issued for the same list since the
31
+ * last reset or whose list changed) is not emulated.
32
+ *
33
+ * Node-only: `@emulators/core` imports Node builtins, so the core is loaded lazily by
34
+ * `makeNotionEmulator` (importing this module has no side effects).
35
+ *
36
+ * @experimental
37
+ */
38
+ /**
39
+ * Route evidence manifest: every emulated route, whether it writes, and the conformance cases
40
+ * whose (currently synthetic, unverified) wire claims it follows. Kept in sync with the handlers
41
+ * by construction (both come from one route table).
42
+ */
43
+ const notionEmulatorRoutes = notionApiRoutes.map(routeEvidence);
44
+ /**
45
+ * Optional fault filter; an omitted field matches every request. `method` is the HTTP method;
46
+ * `path` is the raw request path (ending in `*`, a prefix); `route` is a route template of the
47
+ * manifest (another value is rejected when the fault is added).
48
+ */
49
+ const NotionFaultMatch = StatefulFaultMatch;
50
+ /**
51
+ * A status fault (400-599): answer matching requests with this status, headers, and body instead
52
+ * of the route's write, so nothing is written. The body defaults to
53
+ * `{ error: { type: 'emulator_fault', message } }`; `count` limits how many requests it answers.
54
+ * Only a request the emulator would answer reaches a fault: a request that is not emulated, by
55
+ * its shape or by the state, never uses one up.
56
+ */
57
+ const NotionFault = StatefulFault;
58
+ /** Invalid emulator input from the JS API: a seed, a fault, or an option. A programmer error. */
59
+ var NotionEmulatorInputInvalid = class extends Data.TaggedError("NotionEmulatorInputInvalid") {
60
+ get message() {
61
+ return `Invalid Notion emulator ${this.input}: ${this.reason}`;
62
+ }
63
+ };
64
+ const inputInvalid = (input, reason) => new NotionEmulatorInputInvalid({
65
+ input,
66
+ reason
67
+ });
68
+ /**
69
+ * Create a stateful Notion emulator on the `@emulators/core` custom runtime. Each call has its
70
+ * own state, ledger, and faults. Rejects with `NotionEmulatorInputInvalid` for an invalid seed or
71
+ * option. Every request needs `Notion-Version: 2025-09-03` (else not emulated). See
72
+ * `src/stateful-emulator.ts` for the request precedence.
73
+ */
74
+ const makeNotionEmulator = async (options = {}) => {
75
+ const initial = buildSeedState(options.seed ?? {});
76
+ if (Predicate.isString(initial)) throw inputInvalid("seed", initial);
77
+ checkBooleanDrills(options.drills, notionEmulatorDrillKnobs, inputInvalid);
78
+ const drills = options.drills ?? {};
79
+ const env = {
80
+ now: options.now ?? (() => Date.now()),
81
+ drills: {
82
+ searchRepeatsResults: drills.searchRepeatsResults === true,
83
+ botUserAsPerson: drills.botUserAsPerson === true,
84
+ envelopeStatusMismatch: drills.envelopeStatusMismatch === true,
85
+ omitTitlePlainText: drills.omitTitlePlainText === true,
86
+ blockCursorRepeats: drills.blockCursorRepeats === true,
87
+ rejectDoubleEncodedPropertyId: drills.rejectDoubleEncodedPropertyId === true,
88
+ rowParentAsDatabase: drills.rowParentAsDatabase === true,
89
+ trashedPageNotFound: drills.trashedPageNotFound === true
90
+ },
91
+ cursors: /* @__PURE__ */ new Map(),
92
+ propertyCursorCounter: { next: 1 },
93
+ generation: { current: 0 },
94
+ firstIssued: /* @__PURE__ */ new Map()
95
+ };
96
+ return await makeStatefulEmulator({
97
+ routes: notionApiRoutes,
98
+ env,
99
+ initial,
100
+ buildSeed: buildSeedState,
101
+ requestProblem: (header) => header("notion-version") === "2025-09-03" ? void 0 : `Notion-Version other than ${notionEmulatorVersion} (the version every fixture sends) is not emulated`,
102
+ recordHeaders: [{
103
+ name: "notion-version",
104
+ json: false
105
+ }],
106
+ clearRuntime: () => {
107
+ env.cursors.clear();
108
+ env.generation.current += 1;
109
+ },
110
+ runtimeState: () => ({}),
111
+ seedSummary: (state) => ({
112
+ pages: state.pages.length,
113
+ blocks: state.blocks.length,
114
+ dataSources: state.dataSources.length
115
+ }),
116
+ inputInvalid
117
+ }, statefulCoreRuntime({
118
+ name: "notion",
119
+ initial,
120
+ decodeState,
121
+ inputInvalid
122
+ }));
123
+ };
124
+ //#endregion
125
+ export { NotionEmulatorBlock, NotionEmulatorBotUser, NotionEmulatorDataSource, NotionEmulatorDatabase, NotionEmulatorImpliedPage, NotionEmulatorInputInvalid, NotionEmulatorPage, NotionEmulatorParent, NotionEmulatorProfile, NotionEmulatorPropertyItems, NotionEmulatorSeed, NotionEmulatorStateSchema, NotionFault, NotionFaultMatch, emulatorEvidenceHeader, makeNotionEmulator, notionEmulatorBasePath, notionEmulatorOrigin, notionEmulatorRoutes, notionEmulatorVersion };
126
+
127
+ //# sourceMappingURL=notion.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notion.mjs","names":[],"sources":["../src/notion.ts"],"sourcesContent":["/**\n * Stateful Notion emulator (the `/v1` routes on `https://api.notion.com` at API version\n * 2025-09-03), built on the upstream `@emulators/core` custom runtime, with a request ledger,\n * status faults, and an `/_emulate/*` control plane.\n *\n * It never imports SDK code: its wire shapes, error envelopes, and default seed are copied as data\n * from the synthetic Notion conformance fixtures, and every route names the conformance cases it\n * follows in `notionEmulatorRoutes`. Only the routes those eight cases (with their cleanup) send\n * are emulated. Response behaviour comes only from the fixtures: anything they do not show answers\n * one ledgered 400 not-emulated, writes nothing, and uses up no fault. That includes a search\n * without matches or matching a trashed page, so the read-only leftover lookup fails on a clean\n * workspace and after the write case (the live runner prints its lookup-failed WARN). Search\n * considers only the pages whose content the state holds (an implied page never matches) and\n * refuses a match shown only as a query row. Every route answers only on the recorded origin\n * `https://api.notion.com` (`fetchOn(origin)` serves it behind a loopback rewrite).\n *\n * Request-shape latitude (`/notion`, the only accepted deviations): any bearer value (never\n * checked or stored); extra request headers; JSON key order; `content-type` media-type\n * parameters; the order of query parameters; Notion ids with or without dashes, in any case; any\n * search `query` (looked up in the state); and any `page_size` from 1 to 100 whose page shows\n * only recorded results (the data source query: 1). `Notion-Version` must be `2025-09-03`.\n * Everything else (other keys, filters, booleans, sorts, query parameters, another origin,\n * titles, repeated or missing `page_size`, and cursors not issued for the same list since the\n * last reset or whose list changed) is not emulated.\n *\n * Node-only: `@emulators/core` imports Node builtins, so the core is loaded lazily by\n * `makeNotionEmulator` (importing this module has no side effects).\n *\n * @experimental\n */\nimport { Data, Predicate } from 'effect'\nimport {\n notionApiRoutes,\n notionEmulatorDrillKnobs,\n notionEmulatorVersion,\n type NotionApiEnv,\n type NotionEmulatorDrills\n} from './notion/api.ts'\nimport {\n buildSeedState,\n decodeState,\n type NotionEmulatorSeed,\n type NotionEmulatorState\n} from './notion/state.ts'\nimport type { EmulatorRouteEvidence } from './route-evidence.ts'\nimport {\n StatefulFault,\n StatefulFaultMatch,\n checkBooleanDrills,\n makeStatefulEmulator,\n routeEvidence,\n type StatefulCoverage,\n type StatefulEmulatorApi,\n type StatefulFaultState,\n type StatefulInputKind,\n type StatefulLedgerEntry\n} from './stateful-emulator.ts'\nimport { statefulCoreRuntime } from './stateful-core.ts'\n\nexport type { EmulatorEvidence, EmulatorRouteEvidence } from './route-evidence.ts'\n\nexport { emulatorEvidenceHeader } from './route-evidence.ts'\n\nexport {\n notionEmulatorBasePath,\n notionEmulatorOrigin,\n notionEmulatorVersion,\n type NotionEmulatorDrills\n} from './notion/api.ts'\n\nexport {\n NotionEmulatorBlock,\n NotionEmulatorBotUser,\n NotionEmulatorDataSource,\n NotionEmulatorDatabase,\n NotionEmulatorImpliedPage,\n NotionEmulatorPage,\n NotionEmulatorParent,\n NotionEmulatorProfile,\n NotionEmulatorPropertyItems,\n NotionEmulatorSeed,\n NotionEmulatorStateSchema,\n type NotionEmulatorState\n} from './notion/state.ts'\n\n/**\n * Route evidence manifest: every emulated route, whether it writes, and the conformance cases\n * whose (currently synthetic, unverified) wire claims it follows. Kept in sync with the handlers\n * by construction (both come from one route table).\n */\nexport const notionEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence> =\n notionApiRoutes.map(routeEvidence)\n\n/**\n * Optional fault filter; an omitted field matches every request. `method` is the HTTP method;\n * `path` is the raw request path (ending in `*`, a prefix); `route` is a route template of the\n * manifest (another value is rejected when the fault is added).\n */\nexport const NotionFaultMatch = StatefulFaultMatch\n\nexport type NotionFaultMatch = typeof StatefulFaultMatch.Type\n\n/**\n * A status fault (400-599): answer matching requests with this status, headers, and body instead\n * of the route's write, so nothing is written. The body defaults to\n * `{ error: { type: 'emulator_fault', message } }`; `count` limits how many requests it answers.\n * Only a request the emulator would answer reaches a fault: a request that is not emulated, by\n * its shape or by the state, never uses one up.\n */\nexport const NotionFault = StatefulFault\n\nexport type NotionFault = StatefulFault\n\nexport type NotionFaultState = StatefulFaultState\n\nexport type NotionLedgerEntry = StatefulLedgerEntry\n\nexport type NotionCoverage = StatefulCoverage\n\n/** Invalid emulator input from the JS API: a seed, a fault, or an option. A programmer error. */\nexport class NotionEmulatorInputInvalid extends Data.TaggedError('NotionEmulatorInputInvalid')<{\n readonly input: StatefulInputKind\n readonly reason: string\n}> {\n override get message(): string {\n return `Invalid Notion emulator ${this.input}: ${this.reason}`\n }\n}\n\nexport type NotionEmulatorOptions = {\n /** Typed seed; defaults to the fixture entities (`profile: 'default'`). */\n readonly seed?: NotionEmulatorSeed\n /** Clock in epoch milliseconds (created page timestamps). Defaults to `Date.now`. */\n readonly now?: () => number\n /** Drill knobs (tests only): make the emulator disagree with one conformance claim. */\n readonly drills?: NotionEmulatorDrills\n}\n\nexport type NotionEmulator = StatefulEmulatorApi<NotionEmulatorState, NotionEmulatorSeed>\n\nconst inputInvalid = (input: StatefulInputKind, reason: string) =>\n new NotionEmulatorInputInvalid({ input, reason })\n\n/**\n * Create a stateful Notion emulator on the `@emulators/core` custom runtime. Each call has its\n * own state, ledger, and faults. Rejects with `NotionEmulatorInputInvalid` for an invalid seed or\n * option. Every request needs `Notion-Version: 2025-09-03` (else not emulated). See\n * `src/stateful-emulator.ts` for the request precedence.\n */\nexport const makeNotionEmulator = async (\n options: NotionEmulatorOptions = {}\n): Promise<NotionEmulator> => {\n const initial = buildSeedState(options.seed ?? {})\n\n if (Predicate.isString(initial)) {\n throw inputInvalid('seed', initial)\n }\n\n checkBooleanDrills(options.drills, notionEmulatorDrillKnobs, inputInvalid)\n\n const drills = options.drills ?? {}\n\n const env: NotionApiEnv = {\n now: options.now ?? (() => Date.now()),\n drills: {\n searchRepeatsResults: drills.searchRepeatsResults === true,\n botUserAsPerson: drills.botUserAsPerson === true,\n envelopeStatusMismatch: drills.envelopeStatusMismatch === true,\n omitTitlePlainText: drills.omitTitlePlainText === true,\n blockCursorRepeats: drills.blockCursorRepeats === true,\n rejectDoubleEncodedPropertyId: drills.rejectDoubleEncodedPropertyId === true,\n rowParentAsDatabase: drills.rowParentAsDatabase === true,\n trashedPageNotFound: drills.trashedPageNotFound === true\n },\n cursors: new Map(),\n propertyCursorCounter: { next: 1 },\n generation: { current: 0 },\n firstIssued: new Map()\n }\n\n const api = await makeStatefulEmulator<NotionEmulatorState, NotionApiEnv, NotionEmulatorSeed>(\n {\n routes: notionApiRoutes,\n env,\n initial,\n buildSeed: buildSeedState,\n requestProblem: header =>\n header('notion-version') === notionEmulatorVersion\n ? undefined\n : `Notion-Version other than ${notionEmulatorVersion} (the version every fixture sends) is not emulated`,\n recordHeaders: [{ name: 'notion-version', json: false }],\n // Cursor values never cross a reset or seed: property cursor numbers keep advancing, and the\n // generation makes later search and block cursors distinct from earlier ones.\n clearRuntime: () => {\n env.cursors.clear()\n env.generation.current += 1\n },\n runtimeState: () => ({}),\n seedSummary: state => ({\n pages: state.pages.length,\n blocks: state.blocks.length,\n dataSources: state.dataSources.length\n }),\n inputInvalid\n },\n statefulCoreRuntime({ name: 'notion', initial, decodeState, inputInvalid })\n )\n\n return api\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0FA,MAAa,uBACX,gBAAgB,IAAI,aAAa;;;;;;AAOnC,MAAa,mBAAmB;;;;;;;;AAWhC,MAAa,cAAc;;AAW3B,IAAa,6BAAb,cAAgD,KAAK,YAAY,4BAA4B,EAG1F;CACD,IAAa,UAAkB;EAC7B,OAAO,2BAA2B,KAAK,MAAM,IAAI,KAAK;CACxD;AACF;AAaA,MAAM,gBAAgB,OAA0B,WAC9C,IAAI,2BAA2B;CAAE;CAAO;AAAO,CAAC;;;;;;;AAQlD,MAAa,qBAAqB,OAChC,UAAiC,CAAC,MACN;CAC5B,MAAM,UAAU,eAAe,QAAQ,QAAQ,CAAC,CAAC;CAEjD,IAAI,UAAU,SAAS,OAAO,GAC5B,MAAM,aAAa,QAAQ,OAAO;CAGpC,mBAAmB,QAAQ,QAAQ,0BAA0B,YAAY;CAEzE,MAAM,SAAS,QAAQ,UAAU,CAAC;CAElC,MAAM,MAAoB;EACxB,KAAK,QAAQ,cAAc,KAAK,IAAI;EACpC,QAAQ;GACN,sBAAsB,OAAO,yBAAyB;GACtD,iBAAiB,OAAO,oBAAoB;GAC5C,wBAAwB,OAAO,2BAA2B;GAC1D,oBAAoB,OAAO,uBAAuB;GAClD,oBAAoB,OAAO,uBAAuB;GAClD,+BAA+B,OAAO,kCAAkC;GACxE,qBAAqB,OAAO,wBAAwB;GACpD,qBAAqB,OAAO,wBAAwB;EACtD;EACA,yBAAS,IAAI,IAAI;EACjB,uBAAuB,EAAE,MAAM,EAAE;EACjC,YAAY,EAAE,SAAS,EAAE;EACzB,6BAAa,IAAI,IAAI;CACvB;CA8BA,OAAO,MA5BW,qBAChB;EACE,QAAQ;EACR;EACA;EACA,WAAW;EACX,iBAAgB,WACd,OAAO,gBAAgB,MAAA,eACnB,KAAA,IACA,6BAA6B,sBAAsB;EACzD,eAAe,CAAC;GAAE,MAAM;GAAkB,MAAM;EAAM,CAAC;EAGvD,oBAAoB;GAClB,IAAI,QAAQ,MAAM;GAClB,IAAI,WAAW,WAAW;EAC5B;EACA,qBAAqB,CAAC;EACtB,cAAa,WAAU;GACrB,OAAO,MAAM,MAAM;GACnB,QAAQ,MAAM,OAAO;GACrB,aAAa,MAAM,YAAY;EACjC;EACA;CACF,GACA,oBAAoB;EAAE,MAAM;EAAU;EAAS;EAAa;CAAa,CAAC,CAC5E;AAGF"}
@@ -0,0 +1,165 @@
1
+ import { EmulatorEvidence, EmulatorRouteEvidence, emulatorEvidenceHeader } from "./route-evidence.mjs";
2
+ import { ChatCompletionsEmulator, ChatCoverage, ChatFault, ChatFaultKind, ChatFaultMatch, ChatFaultState, ChatLedgerEntry, ChatRouteCoverage, ChatScriptedError, ChatScriptedToolCall, ChatScriptedUsage } from "./chat-completions.mjs";
3
+ import * as Schema from "effect/Schema";
4
+
5
+ //#region src/openai.d.ts
6
+ declare const openAiChatCompletionsPath = "/v1/chat/completions";
7
+ /** Synthetic-safe default model ids, including the OpenAI chat conformance defaults. */
8
+ declare const openAiEmulatorDefaultModels: ReadonlyArray<string>;
9
+ /**
10
+ * Route evidence manifest: every emulated OpenAI route and the conformance
11
+ * cases whose (currently synthetic, unverified) wire shapes it follows.
12
+ */
13
+ declare const openAiEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence>;
14
+ /** Optional fault filter; an omitted field matches every request. `path` ending in `*` is a prefix. */
15
+ declare const OpenAiFaultMatch: Schema.Struct<{
16
+ readonly path: Schema.optionalKey<Schema.String>;
17
+ readonly model: Schema.optionalKey<Schema.String>;
18
+ }>;
19
+ type OpenAiFaultMatch = ChatFaultMatch;
20
+ /**
21
+ * Wire faults for emulated routes, the same kinds as the Gateway emulator:
22
+ * `status` (the body defaults to an OpenAI error envelope),
23
+ * `error-after-chunks`, and `truncate-after-chunks`. Statuses that cannot
24
+ * carry a body and redirects are rejected, as are invalid headers and
25
+ * `location`. A chunk fault that cannot take effect answers 500 and is not
26
+ * consumed.
27
+ */
28
+ declare const OpenAiFault: Schema.Union<readonly [Schema.Struct<{
29
+ readonly match: Schema.optionalKey<Schema.Struct<{
30
+ readonly path: Schema.optionalKey<Schema.String>;
31
+ readonly model: Schema.optionalKey<Schema.String>;
32
+ }>>;
33
+ readonly count: Schema.optionalKey<Schema.Int>;
34
+ readonly kind: Schema.Literal<"status">;
35
+ readonly status: Schema.Int;
36
+ readonly headers: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
37
+ readonly body: Schema.optionalKey<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
38
+ }>, Schema.Struct<{
39
+ readonly match: Schema.optionalKey<Schema.Struct<{
40
+ readonly path: Schema.optionalKey<Schema.String>;
41
+ readonly model: Schema.optionalKey<Schema.String>;
42
+ }>>;
43
+ readonly count: Schema.optionalKey<Schema.Int>;
44
+ readonly kind: Schema.Literal<"error-after-chunks">;
45
+ readonly chunks: Schema.Int;
46
+ }>, Schema.Struct<{
47
+ readonly match: Schema.optionalKey<Schema.Struct<{
48
+ readonly path: Schema.optionalKey<Schema.String>;
49
+ readonly model: Schema.optionalKey<Schema.String>;
50
+ }>>;
51
+ readonly count: Schema.optionalKey<Schema.Int>;
52
+ readonly kind: Schema.Literal<"truncate-after-chunks">;
53
+ readonly chunks: Schema.Int;
54
+ }>]>;
55
+ type OpenAiFault = ChatFault;
56
+ type OpenAiFaultKind = ChatFaultKind;
57
+ /** Usage for a scripted turn (sent as the wire `usage` object). */
58
+ declare const OpenAiScriptedUsage: Schema.Struct<{
59
+ readonly promptTokens: Schema.Int;
60
+ readonly completionTokens: Schema.Int;
61
+ readonly reasoningTokens: Schema.optionalKey<Schema.Int>;
62
+ }>;
63
+ type OpenAiScriptedUsage = ChatScriptedUsage;
64
+ /** One scripted tool call; its JSON arguments stream as these fragments. */
65
+ declare const OpenAiScriptedToolCall: Schema.Struct<{
66
+ readonly name: Schema.NonEmptyString;
67
+ readonly argumentFragments: Schema.$Array<Schema.String>;
68
+ readonly id: Schema.optionalKey<Schema.NonEmptyString>;
69
+ }>;
70
+ type OpenAiScriptedToolCall = ChatScriptedToolCall;
71
+ /**
72
+ * A scripted completion. Every field is exact (nothing is filled in) except:
73
+ * `usage` omitted is synthesized when the request asks for usage, and `null`
74
+ * drops it; `finishReason` omitted is `tool_calls` with tool calls, else
75
+ * `stop`. Reasoning is not emulated: reasoning fields are rejected.
76
+ */
77
+ declare const OpenAiScriptedCompletion: Schema.Struct<{
78
+ text: Schema.optionalKey<Schema.$Array<Schema.String>>;
79
+ toolCalls: Schema.optionalKey<Schema.$Array<Schema.Struct<{
80
+ readonly name: Schema.NonEmptyString;
81
+ readonly argumentFragments: Schema.$Array<Schema.String>;
82
+ readonly id: Schema.optionalKey<Schema.NonEmptyString>;
83
+ }>>>;
84
+ usage: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
85
+ readonly promptTokens: Schema.Int;
86
+ readonly completionTokens: Schema.Int;
87
+ readonly reasoningTokens: Schema.optionalKey<Schema.Int>;
88
+ }>>>;
89
+ finishReason: Schema.optionalKey<Schema.NonEmptyString>;
90
+ }>;
91
+ type OpenAiScriptedCompletion = typeof OpenAiScriptedCompletion.Type;
92
+ /** A scripted error response: status, body (a string is sent as is), and optional headers. */
93
+ declare const OpenAiScriptedError: Schema.Struct<{
94
+ readonly error: Schema.Struct<{
95
+ readonly status: Schema.Int;
96
+ readonly body: Schema.Codec<Schema.Json, Schema.Json, never, never>;
97
+ readonly headers: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
98
+ }>;
99
+ }>;
100
+ type OpenAiScriptedError = ChatScriptedError;
101
+ /** A turn queued for the next chat completion request. */
102
+ declare const OpenAiScriptedTurn: Schema.Union<readonly [Schema.Struct<{
103
+ readonly error: Schema.Struct<{
104
+ readonly status: Schema.Int;
105
+ readonly body: Schema.Codec<Schema.Json, Schema.Json, never, never>;
106
+ readonly headers: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
107
+ }>;
108
+ }>, Schema.Struct<{
109
+ text: Schema.optionalKey<Schema.$Array<Schema.String>>;
110
+ toolCalls: Schema.optionalKey<Schema.$Array<Schema.Struct<{
111
+ readonly name: Schema.NonEmptyString;
112
+ readonly argumentFragments: Schema.$Array<Schema.String>;
113
+ readonly id: Schema.optionalKey<Schema.NonEmptyString>;
114
+ }>>>;
115
+ usage: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
116
+ readonly promptTokens: Schema.Int;
117
+ readonly completionTokens: Schema.Int;
118
+ readonly reasoningTokens: Schema.optionalKey<Schema.Int>;
119
+ }>>>;
120
+ finishReason: Schema.optionalKey<Schema.NonEmptyString>;
121
+ }>]>;
122
+ type OpenAiScriptedTurn = typeof OpenAiScriptedTurn.Type;
123
+ declare const OpenAiEmulatorInputInvalid_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
124
+ readonly _tag: "OpenAiEmulatorInputInvalid";
125
+ } & Readonly<A>;
126
+ /** Thrown by the JS API (`faults.add`, `script.enqueue`) for invalid input; a programmer error. */
127
+ declare class OpenAiEmulatorInputInvalid extends OpenAiEmulatorInputInvalid_base<{
128
+ readonly input: 'fault' | 'turn';
129
+ readonly reason: string;
130
+ }> {
131
+ get message(): string;
132
+ }
133
+ type OpenAiLedgerEntry = ChatLedgerEntry;
134
+ type OpenAiFaultState = ChatFaultState;
135
+ type OpenAiRouteCoverage = ChatRouteCoverage;
136
+ type OpenAiCoverage = ChatCoverage;
137
+ type OpenAiEmulatorOptions = {
138
+ /** Model ids that exist. Defaults to `openAiEmulatorDefaultModels`. */readonly knownModels?: ReadonlyArray<string>;
139
+ };
140
+ type OpenAiEmulator = ChatCompletionsEmulator<OpenAiScriptedTurn>;
141
+ /**
142
+ * Create an OpenAI Chat Completions emulator. Each call has independent
143
+ * ledger, fault, and script state.
144
+ *
145
+ * Without a script, `POST /v1/chat/completions` answers a known model with
146
+ * synthetic text deltas (`stream: true`: `chat.completion.chunk` SSE, a finish
147
+ * chunk, a usage chunk when `stream_options.include_usage` is set, and
148
+ * `data: [DONE]`; `stream: false`: one `chat.completion` JSON body). A request
149
+ * with `tools` gets one tool call whose arguments are synthesized from the
150
+ * tool's JSON Schema and streamed in fragments, finishing with `tool_calls`.
151
+ * Unknown models get the OpenAI error envelope (404, `model_not_found`); a
152
+ * missing bearer credential gets a 401 envelope (`invalid_api_key`); unknown
153
+ * routes get a 404 JSON error. The ledger records the
154
+ * `max_completion_tokens` limit as `maxCompletionTokens`.
155
+ *
156
+ * Precedence per chat request matches the Gateway emulator: authentication and
157
+ * JSON validation, then the first matching `status` fault, then the next
158
+ * scripted turn, then model validation and defaults; a first matching chunk
159
+ * fault then shapes the body. Credential headers are never recorded, and the
160
+ * bearer value is never checked or stored.
161
+ */
162
+ declare const makeOpenAiEmulator: (options?: OpenAiEmulatorOptions) => OpenAiEmulator;
163
+ //#endregion
164
+ export { type EmulatorEvidence, type EmulatorRouteEvidence, OpenAiCoverage, OpenAiEmulator, OpenAiEmulatorInputInvalid, OpenAiEmulatorOptions, OpenAiFault, OpenAiFaultKind, OpenAiFaultMatch, OpenAiFaultState, OpenAiLedgerEntry, OpenAiRouteCoverage, OpenAiScriptedCompletion, OpenAiScriptedError, OpenAiScriptedToolCall, OpenAiScriptedTurn, OpenAiScriptedUsage, emulatorEvidenceHeader, makeOpenAiEmulator, openAiChatCompletionsPath, openAiEmulatorDefaultModels, openAiEmulatorRoutes };
165
+ //# sourceMappingURL=openai.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.d.mts","names":[],"sources":["../src/openai.ts"],"mappings":";;;;;cA4Ca,yBAAA;;cAGA,2BAAA,EAA6B,aAAa;;AAHjB;AAGtC;;cAMa,oBAAA,EAAsB,aAAa,CAAC,qBAAA;;cAkBpC,gBAAA,EAAgB,MAAA,CAAA,MAAA;EAAA;;;KAEjB,gBAAA,GAAmB,cAAc;AApByB;AAkBtE;;;;;;;AAlBsE,cA8BzD,WAAA,EAAW,MAAA,CAAA,KAAA,WAAA,MAAA,CAAA,MAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;KAEZ,WAAA,GAAc,SAAS;AAAA,KAEvB,eAAA,GAAkB,aAAa;;cAG9B,mBAAA,EAAmB,MAAA,CAAA,MAAA;EAAA;;;;KAEpB,mBAAA,GAAsB,iBAAiB;;cAGtC,sBAAA,EAAsB,MAAA,CAAA,MAAA;EAAA;;;;KAEvB,sBAAA,GAAyB,oBAAoB;;;;;;;cAQ5C,wBAAA,EAAwB,MAAA,CAAA,MAAA;;;;;;;;;;;;;;KAEzB,wBAAA,UAAkC,wBAAA,CAAyB,IAAI;;cAG9D,mBAAA,EAAmB,MAAA,CAAA,MAAA;EAAA;;;;;;KAEpB,mBAAA,GAAsB,iBAAiB;;cAGtC,kBAAA,EAAkB,MAAA,CAAA,KAAA,WAAA,MAAA,CAAA,MAAA;EAAA;;;;;;;;;;;;;;;;;;;KAEnB,kBAAA,UAA4B,kBAAA,CAAmB,IAAI;AAAA,cAAA,+BAAA;;;;cAGlD,0BAAA,SAAmC,+BAAA;EAAA,SACrC,KAAA;EAAA,SACA,MAAA;AAAA;EAAA,IAEI,OAAA,CAAA;AAAA;AAAA,KAKH,iBAAA,GAAoB,eAAe;AAAA,KAEnC,gBAAA,GAAmB,cAAc;AAAA,KAEjC,mBAAA,GAAsB,iBAAiB;AAAA,KAEvC,cAAA,GAAiB,YAAY;AAAA,KAE7B,qBAAA;kFAED,WAAA,GAAc,aAAa;AAAA;AAAA,KAG1B,cAAA,GAAiB,uBAAuB,CAAC,kBAAA;;;;;;;;;;;;;;;;;;;;;;cA2BxC,kBAAA,GAAsB,OAAA,GAAS,qBAAA,KAA6B,cA4BrE"}
@@ -0,0 +1,141 @@
1
+ import { emulatorEvidenceHeader } from "./route-evidence.mjs";
2
+ import { ChatFault, ChatFaultMatch, ChatScriptedError, ChatScriptedToolCall, ChatScriptedUsage, chatScriptedCompletionFields, makeChatCompletionsEmulator } from "./chat-completions.mjs";
3
+ import { Data } from "effect";
4
+ import * as Schema from "effect/Schema";
5
+ //#region src/openai.ts
6
+ /**
7
+ * OpenAI Chat Completions emulator: a plain fetch handler for
8
+ * `POST /v1/chat/completions` (origin `https://api.openai.com`), with scripted
9
+ * turns, wire faults, a request ledger, and an `/_emulate/*` control plane.
10
+ *
11
+ * It never imports SDK code: its wire shapes follow the synthetic OpenAI chat
12
+ * conformance fixtures (`chat.completion.chunk` SSE, usage chunk,
13
+ * `data: [DONE]`, the `chat.completion` JSON body, and the
14
+ * `{ error: { message, type, param, code } }` envelope) and are linked to
15
+ * those conformance case ids in `openAiEmulatorRoutes`. The Chat Completions
16
+ * machinery is shared with the Gateway emulator (`chat-completions.ts`).
17
+ *
18
+ * Reasoning models are not emulated yet: no default model streams reasoning,
19
+ * and scripted turns cannot carry reasoning.
20
+ *
21
+ * Runtime-portable Web APIs only (`Request`, `Response`, `ReadableStream`,
22
+ * `TextEncoder`, `URL`); no Effect runtime is required to use it.
23
+ *
24
+ * @experimental
25
+ */
26
+ const openAiChatCompletionsPath = "/v1/chat/completions";
27
+ /** Synthetic-safe default model ids, including the OpenAI chat conformance defaults. */
28
+ const openAiEmulatorDefaultModels = ["gpt-4.1-nano", "gpt-4.1-mini"];
29
+ /**
30
+ * Route evidence manifest: every emulated OpenAI route and the conformance
31
+ * cases whose (currently synthetic, unverified) wire shapes it follows.
32
+ */
33
+ const openAiEmulatorRoutes = [{
34
+ method: "POST",
35
+ path: openAiChatCompletionsPath,
36
+ kind: "provider",
37
+ write: false,
38
+ caseIds: [
39
+ "openai.chat.stream.plain-text",
40
+ "openai.chat.stream.tool-call-deltas",
41
+ "openai.chat.stream.error-envelope",
42
+ "openai.chat.json.plain-text"
43
+ ],
44
+ evidence: "unverified",
45
+ observedAt: void 0
46
+ }];
47
+ /** Optional fault filter; an omitted field matches every request. `path` ending in `*` is a prefix. */
48
+ const OpenAiFaultMatch = ChatFaultMatch;
49
+ /**
50
+ * Wire faults for emulated routes, the same kinds as the Gateway emulator:
51
+ * `status` (the body defaults to an OpenAI error envelope),
52
+ * `error-after-chunks`, and `truncate-after-chunks`. Statuses that cannot
53
+ * carry a body and redirects are rejected, as are invalid headers and
54
+ * `location`. A chunk fault that cannot take effect answers 500 and is not
55
+ * consumed.
56
+ */
57
+ const OpenAiFault = ChatFault;
58
+ /** Usage for a scripted turn (sent as the wire `usage` object). */
59
+ const OpenAiScriptedUsage = ChatScriptedUsage;
60
+ /** One scripted tool call; its JSON arguments stream as these fragments. */
61
+ const OpenAiScriptedToolCall = ChatScriptedToolCall;
62
+ /**
63
+ * A scripted completion. Every field is exact (nothing is filled in) except:
64
+ * `usage` omitted is synthesized when the request asks for usage, and `null`
65
+ * drops it; `finishReason` omitted is `tool_calls` with tool calls, else
66
+ * `stop`. Reasoning is not emulated: reasoning fields are rejected.
67
+ */
68
+ const OpenAiScriptedCompletion = Schema.Struct(chatScriptedCompletionFields);
69
+ /** A scripted error response: status, body (a string is sent as is), and optional headers. */
70
+ const OpenAiScriptedError = ChatScriptedError;
71
+ /** A turn queued for the next chat completion request. */
72
+ const OpenAiScriptedTurn = Schema.Union([OpenAiScriptedError, OpenAiScriptedCompletion]);
73
+ /** Thrown by the JS API (`faults.add`, `script.enqueue`) for invalid input; a programmer error. */
74
+ var OpenAiEmulatorInputInvalid = class extends Data.TaggedError("OpenAiEmulatorInputInvalid") {
75
+ get message() {
76
+ return `Invalid OpenAI emulator ${this.input}: ${this.reason}`;
77
+ }
78
+ };
79
+ const openAiErrorEnvelope = (error) => ({ error: {
80
+ message: error.message,
81
+ type: error.type,
82
+ param: null,
83
+ code: error.code
84
+ } });
85
+ /**
86
+ * Create an OpenAI Chat Completions emulator. Each call has independent
87
+ * ledger, fault, and script state.
88
+ *
89
+ * Without a script, `POST /v1/chat/completions` answers a known model with
90
+ * synthetic text deltas (`stream: true`: `chat.completion.chunk` SSE, a finish
91
+ * chunk, a usage chunk when `stream_options.include_usage` is set, and
92
+ * `data: [DONE]`; `stream: false`: one `chat.completion` JSON body). A request
93
+ * with `tools` gets one tool call whose arguments are synthesized from the
94
+ * tool's JSON Schema and streamed in fragments, finishing with `tool_calls`.
95
+ * Unknown models get the OpenAI error envelope (404, `model_not_found`); a
96
+ * missing bearer credential gets a 401 envelope (`invalid_api_key`); unknown
97
+ * routes get a 404 JSON error. The ledger records the
98
+ * `max_completion_tokens` limit as `maxCompletionTokens`.
99
+ *
100
+ * Precedence per chat request matches the Gateway emulator: authentication and
101
+ * JSON validation, then the first matching `status` fault, then the next
102
+ * scripted turn, then model validation and defaults; a first matching chunk
103
+ * fault then shapes the body. Credential headers are never recorded, and the
104
+ * bearer value is never checked or stored.
105
+ */
106
+ const makeOpenAiEmulator = (options = {}) => makeChatCompletionsEmulator({
107
+ path: openAiChatCompletionsPath,
108
+ routes: openAiEmulatorRoutes,
109
+ knownModels: options.knownModels ?? openAiEmulatorDefaultModels,
110
+ reasoningModels: void 0,
111
+ errorEnvelope: openAiErrorEnvelope,
112
+ unknownModel: {
113
+ status: 404,
114
+ error: {
115
+ message: "Synthetic placeholder: the requested model does not exist.",
116
+ type: "invalid_request_error",
117
+ code: "model_not_found"
118
+ }
119
+ },
120
+ auth: { unauthorized: {
121
+ message: "Synthetic: missing or invalid API key.",
122
+ type: "invalid_request_error",
123
+ code: "invalid_api_key"
124
+ } },
125
+ completionTokenField: "max_completion_tokens",
126
+ responseIdPrefix: "chatcmpl-synthetic",
127
+ defaultText: [
128
+ "Hello",
129
+ " from the",
130
+ " synthetic OpenAI emulator."
131
+ ],
132
+ turnSchema: OpenAiScriptedTurn,
133
+ inputInvalid: (input, reason) => new OpenAiEmulatorInputInvalid({
134
+ input,
135
+ reason
136
+ })
137
+ });
138
+ //#endregion
139
+ export { OpenAiEmulatorInputInvalid, OpenAiFault, OpenAiFaultMatch, OpenAiScriptedCompletion, OpenAiScriptedError, OpenAiScriptedToolCall, OpenAiScriptedTurn, OpenAiScriptedUsage, emulatorEvidenceHeader, makeOpenAiEmulator, openAiChatCompletionsPath, openAiEmulatorDefaultModels, openAiEmulatorRoutes };
140
+
141
+ //# sourceMappingURL=openai.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.mjs","names":[],"sources":["../src/openai.ts"],"sourcesContent":["/**\n * OpenAI Chat Completions emulator: a plain fetch handler for\n * `POST /v1/chat/completions` (origin `https://api.openai.com`), with scripted\n * turns, wire faults, a request ledger, and an `/_emulate/*` control plane.\n *\n * It never imports SDK code: its wire shapes follow the synthetic OpenAI chat\n * conformance fixtures (`chat.completion.chunk` SSE, usage chunk,\n * `data: [DONE]`, the `chat.completion` JSON body, and the\n * `{ error: { message, type, param, code } }` envelope) and are linked to\n * those conformance case ids in `openAiEmulatorRoutes`. The Chat Completions\n * machinery is shared with the Gateway emulator (`chat-completions.ts`).\n *\n * Reasoning models are not emulated yet: no default model streams reasoning,\n * and scripted turns cannot carry reasoning.\n *\n * Runtime-portable Web APIs only (`Request`, `Response`, `ReadableStream`,\n * `TextEncoder`, `URL`); no Effect runtime is required to use it.\n *\n * @experimental\n */\nimport { Data } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport {\n ChatFault,\n ChatFaultMatch,\n ChatScriptedError,\n ChatScriptedToolCall,\n ChatScriptedUsage,\n chatScriptedCompletionFields,\n makeChatCompletionsEmulator,\n type ChatCompletionsEmulator,\n type ChatCoverage,\n type ChatFaultKind,\n type ChatFaultState,\n type ChatLedgerEntry,\n type ChatRouteCoverage,\n type ChatWireError\n} from './chat-completions.ts'\nimport type { EmulatorRouteEvidence } from './route-evidence.ts'\n\nexport type { EmulatorEvidence, EmulatorRouteEvidence } from './route-evidence.ts'\n\nexport { emulatorEvidenceHeader } from './route-evidence.ts'\n\nexport const openAiChatCompletionsPath = '/v1/chat/completions'\n\n/** Synthetic-safe default model ids, including the OpenAI chat conformance defaults. */\nexport const openAiEmulatorDefaultModels: ReadonlyArray<string> = ['gpt-4.1-nano', 'gpt-4.1-mini']\n\n/**\n * Route evidence manifest: every emulated OpenAI route and the conformance\n * cases whose (currently synthetic, unverified) wire shapes it follows.\n */\nexport const openAiEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence> = [\n {\n method: 'POST',\n path: openAiChatCompletionsPath,\n kind: 'provider',\n write: false,\n caseIds: [\n 'openai.chat.stream.plain-text',\n 'openai.chat.stream.tool-call-deltas',\n 'openai.chat.stream.error-envelope',\n 'openai.chat.json.plain-text'\n ],\n evidence: 'unverified',\n observedAt: undefined\n }\n]\n\n/** Optional fault filter; an omitted field matches every request. `path` ending in `*` is a prefix. */\nexport const OpenAiFaultMatch = ChatFaultMatch\n\nexport type OpenAiFaultMatch = ChatFaultMatch\n\n/**\n * Wire faults for emulated routes, the same kinds as the Gateway emulator:\n * `status` (the body defaults to an OpenAI error envelope),\n * `error-after-chunks`, and `truncate-after-chunks`. Statuses that cannot\n * carry a body and redirects are rejected, as are invalid headers and\n * `location`. A chunk fault that cannot take effect answers 500 and is not\n * consumed.\n */\nexport const OpenAiFault = ChatFault\n\nexport type OpenAiFault = ChatFault\n\nexport type OpenAiFaultKind = ChatFaultKind\n\n/** Usage for a scripted turn (sent as the wire `usage` object). */\nexport const OpenAiScriptedUsage = ChatScriptedUsage\n\nexport type OpenAiScriptedUsage = ChatScriptedUsage\n\n/** One scripted tool call; its JSON arguments stream as these fragments. */\nexport const OpenAiScriptedToolCall = ChatScriptedToolCall\n\nexport type OpenAiScriptedToolCall = ChatScriptedToolCall\n\n/**\n * A scripted completion. Every field is exact (nothing is filled in) except:\n * `usage` omitted is synthesized when the request asks for usage, and `null`\n * drops it; `finishReason` omitted is `tool_calls` with tool calls, else\n * `stop`. Reasoning is not emulated: reasoning fields are rejected.\n */\nexport const OpenAiScriptedCompletion = Schema.Struct(chatScriptedCompletionFields)\n\nexport type OpenAiScriptedCompletion = typeof OpenAiScriptedCompletion.Type\n\n/** A scripted error response: status, body (a string is sent as is), and optional headers. */\nexport const OpenAiScriptedError = ChatScriptedError\n\nexport type OpenAiScriptedError = ChatScriptedError\n\n/** A turn queued for the next chat completion request. */\nexport const OpenAiScriptedTurn = Schema.Union([OpenAiScriptedError, OpenAiScriptedCompletion])\n\nexport type OpenAiScriptedTurn = typeof OpenAiScriptedTurn.Type\n\n/** Thrown by the JS API (`faults.add`, `script.enqueue`) for invalid input; a programmer error. */\nexport class OpenAiEmulatorInputInvalid extends Data.TaggedError('OpenAiEmulatorInputInvalid')<{\n readonly input: 'fault' | 'turn'\n readonly reason: string\n}> {\n override get message(): string {\n return `Invalid OpenAI emulator ${this.input}: ${this.reason}`\n }\n}\n\nexport type OpenAiLedgerEntry = ChatLedgerEntry\n\nexport type OpenAiFaultState = ChatFaultState\n\nexport type OpenAiRouteCoverage = ChatRouteCoverage\n\nexport type OpenAiCoverage = ChatCoverage\n\nexport type OpenAiEmulatorOptions = {\n /** Model ids that exist. Defaults to `openAiEmulatorDefaultModels`. */\n readonly knownModels?: ReadonlyArray<string>\n}\n\nexport type OpenAiEmulator = ChatCompletionsEmulator<OpenAiScriptedTurn>\n\nconst openAiErrorEnvelope = (error: ChatWireError): Schema.Json => ({\n error: { message: error.message, type: error.type, param: null, code: error.code }\n})\n\n/**\n * Create an OpenAI Chat Completions emulator. Each call has independent\n * ledger, fault, and script state.\n *\n * Without a script, `POST /v1/chat/completions` answers a known model with\n * synthetic text deltas (`stream: true`: `chat.completion.chunk` SSE, a finish\n * chunk, a usage chunk when `stream_options.include_usage` is set, and\n * `data: [DONE]`; `stream: false`: one `chat.completion` JSON body). A request\n * with `tools` gets one tool call whose arguments are synthesized from the\n * tool's JSON Schema and streamed in fragments, finishing with `tool_calls`.\n * Unknown models get the OpenAI error envelope (404, `model_not_found`); a\n * missing bearer credential gets a 401 envelope (`invalid_api_key`); unknown\n * routes get a 404 JSON error. The ledger records the\n * `max_completion_tokens` limit as `maxCompletionTokens`.\n *\n * Precedence per chat request matches the Gateway emulator: authentication and\n * JSON validation, then the first matching `status` fault, then the next\n * scripted turn, then model validation and defaults; a first matching chunk\n * fault then shapes the body. Credential headers are never recorded, and the\n * bearer value is never checked or stored.\n */\nexport const makeOpenAiEmulator = (options: OpenAiEmulatorOptions = {}): OpenAiEmulator =>\n makeChatCompletionsEmulator({\n path: openAiChatCompletionsPath,\n routes: openAiEmulatorRoutes,\n knownModels: options.knownModels ?? openAiEmulatorDefaultModels,\n reasoningModels: undefined,\n errorEnvelope: openAiErrorEnvelope,\n // Copied data shape of the synthetic OpenAI error-envelope fixture (unknown model id).\n unknownModel: {\n status: 404,\n error: {\n message: 'Synthetic placeholder: the requested model does not exist.',\n type: 'invalid_request_error',\n code: 'model_not_found'\n }\n },\n auth: {\n unauthorized: {\n message: 'Synthetic: missing or invalid API key.',\n type: 'invalid_request_error',\n code: 'invalid_api_key'\n }\n },\n completionTokenField: 'max_completion_tokens',\n responseIdPrefix: 'chatcmpl-synthetic',\n defaultText: ['Hello', ' from the', ' synthetic OpenAI emulator.'],\n turnSchema: OpenAiScriptedTurn,\n inputInvalid: (input, reason) => new OpenAiEmulatorInputInvalid({ input, reason })\n })\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,MAAa,4BAA4B;;AAGzC,MAAa,8BAAqD,CAAC,gBAAgB,cAAc;;;;;AAMjG,MAAa,uBAA6D,CACxE;CACE,QAAQ;CACR,MAAM;CACN,MAAM;CACN,OAAO;CACP,SAAS;EACP;EACA;EACA;EACA;CACF;CACA,UAAU;CACV,YAAY,KAAA;AACd,CACF;;AAGA,MAAa,mBAAmB;;;;;;;;;AAYhC,MAAa,cAAc;;AAO3B,MAAa,sBAAsB;;AAKnC,MAAa,yBAAyB;;;;;;;AAUtC,MAAa,2BAA2B,OAAO,OAAO,4BAA4B;;AAKlF,MAAa,sBAAsB;;AAKnC,MAAa,qBAAqB,OAAO,MAAM,CAAC,qBAAqB,wBAAwB,CAAC;;AAK9F,IAAa,6BAAb,cAAgD,KAAK,YAAY,4BAA4B,EAG1F;CACD,IAAa,UAAkB;EAC7B,OAAO,2BAA2B,KAAK,MAAM,IAAI,KAAK;CACxD;AACF;AAiBA,MAAM,uBAAuB,WAAuC,EAClE,OAAO;CAAE,SAAS,MAAM;CAAS,MAAM,MAAM;CAAM,OAAO;CAAM,MAAM,MAAM;AAAK,EACnF;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,sBAAsB,UAAiC,CAAC,MACnE,4BAA4B;CAC1B,MAAM;CACN,QAAQ;CACR,aAAa,QAAQ,eAAe;CACpC,iBAAiB,KAAA;CACjB,eAAe;CAEf,cAAc;EACZ,QAAQ;EACR,OAAO;GACL,SAAS;GACT,MAAM;GACN,MAAM;EACR;CACF;CACA,MAAM,EACJ,cAAc;EACZ,SAAS;EACT,MAAM;EACN,MAAM;CACR,EACF;CACA,sBAAsB;CACtB,kBAAkB;CAClB,aAAa;EAAC;EAAS;EAAa;CAA6B;CACjE,YAAY;CACZ,eAAe,OAAO,WAAW,IAAI,2BAA2B;EAAE;EAAO;CAAO,CAAC;AACnF,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { FixtureRecording } from "./fixture-route.mjs";
2
+
3
+ //#region src/opencode-recordings.d.ts
4
+ declare const openCodeGoRecordings: ReadonlyArray<FixtureRecording>;
5
+ //#endregion
6
+ export { openCodeGoRecordings };
7
+ //# sourceMappingURL=opencode-recordings.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-recordings.d.mts","names":[],"sources":["../src/opencode-recordings.ts"],"mappings":";;;cASa,oBAAA,EAAsB,aAAa,CAAC,gBAAA"}