@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
package/src/todoist.ts ADDED
@@ -0,0 +1,316 @@
1
+ /**
2
+ * Stateful Todoist emulator (API v1 under `https://api.todoist.com/api/v1`), built on the upstream
3
+ * `@emulators/core` custom runtime, with a request ledger, status faults, and an `/_emulate/*`
4
+ * control plane.
5
+ *
6
+ * Fixture-only: it never imports SDK code; its wire shapes and default seed are copied as data
7
+ * from the synthetic Todoist conformance fixtures, and every route names the conformance cases it
8
+ * follows in `todoistEmulatorRoutes`. Only the routes the seven Todoist cases (with their cleanup)
9
+ * need are emulated; anything the fixtures do not record answers one ledgered 400
10
+ * `{ error: { type: 'not_emulated', message } }`. That includes the project listing of the
11
+ * read-only leftover lookup (`findTodoistConformanceLeftovers`), which no fixture records: the
12
+ * lookup fails (`todoist_list_projects_failed`, HTTP 400), and runners print their lookup-failed
13
+ * WARN instead of leftover warnings.
14
+ *
15
+ * Fail closed: a request on no emulated route (or with a raw id segment that is not a Todoist id)
16
+ * is ledgered and answered with constant text only (`/<unrecognised>`, a standard method or
17
+ * `<other>`, no query or body, a constant reason). So is a request whose `Authorization` header is
18
+ * present but is not one recognisable bearer, whatever its route: its credential cannot be
19
+ * extracted and scrubbed.
20
+ *
21
+ * No answer is synthesised: a seeded project (whose object no fixture records) answers
22
+ * not-emulated on a read; only items created through the recorded create flow are updated,
23
+ * closed, or deleted; a case project takes one task and a seeded parent one sub-project (the
24
+ * fixtures record `child_order: 1` only). Minted ids use the reserved prefix `6XEmu`, which seeds
25
+ * may not use. The bearer value is guarded like the Telegram token: scrubbed from everything
26
+ * ledgered or answered, and a path, query, or body that repeats it is refused.
27
+ *
28
+ * Request-shape latitude (the only accepted deviations from the fixture requests): any credential
29
+ * value of at least 8 characters that occurs nowhere else in the request (its path, query, or body;
30
+ * never checked against anything, stored, or ledgered); extra request headers; `content-type`
31
+ * parameters; query parameters in any order; any Todoist id (1-64 of `[A-Za-z0-9_-]`) of an
32
+ * existing item where a fixture has an id (reads: seeded or created tasks and created projects;
33
+ * task listings: the paging project with `limit=2` and case projects created here without `limit`;
34
+ * writes: only items created through the recorded create flow; a new project's `parent_id`: a
35
+ * seeded project); any `run-` run id (at most 40 characters) in a case project name; any non-empty
36
+ * task `content`; a task update sending `content`, `due_datetime`, or both; a label listing `limit`
37
+ * of 1 to 200 that covers every label.
38
+ *
39
+ * Node-only: `@emulators/core` imports Node builtins, so the core is loaded lazily by
40
+ * `makeTodoistEmulator` (importing this module has no side effects).
41
+ *
42
+ * @experimental
43
+ */
44
+ import { Data, Predicate } from 'effect'
45
+ import type { EmulatorRouteEvidence } from './route-evidence.ts'
46
+ import { statefulCoreRuntime } from './stateful-core.ts'
47
+ import {
48
+ makeHeaderlessStatefulEmulator,
49
+ routeEvidence,
50
+ type StatefulCoverage,
51
+ type StatefulFault,
52
+ type StatefulFaultState,
53
+ type StatefulHeaderlessLedgerEntry,
54
+ type StatefulErrorTexts,
55
+ type StatefulInputKind,
56
+ type StatefulResolution
57
+ } from './stateful-emulator.ts'
58
+ import {
59
+ matchTodoistRoute,
60
+ todoistApiRoutes,
61
+ todoistQueryProblem,
62
+ type TodoistApiEnv,
63
+ type TodoistCursor,
64
+ type TodoistEmulatorDrills
65
+ } from './todoist/api.ts'
66
+ import {
67
+ buildTodoistSeedState,
68
+ decodeTodoistState,
69
+ type TodoistEmulatorSeed,
70
+ type TodoistEmulatorState
71
+ } from './todoist/state.ts'
72
+
73
+ export type { EmulatorEvidence, EmulatorRouteEvidence } from './route-evidence.ts'
74
+
75
+ export { emulatorEvidenceHeader } from './route-evidence.ts'
76
+
77
+ export {
78
+ StatefulFault as TodoistFault,
79
+ StatefulFaultMatch as TodoistFaultMatch
80
+ } from './stateful-emulator.ts'
81
+
82
+ export { todoistEmulatorBasePath, type TodoistEmulatorDrills } from './todoist/api.ts'
83
+
84
+ export {
85
+ TodoistEmulatorDue,
86
+ TodoistEmulatorLabel,
87
+ TodoistEmulatorProfile,
88
+ TodoistEmulatorProject,
89
+ TodoistEmulatorProjectSeed,
90
+ TodoistEmulatorSeed,
91
+ TodoistEmulatorStateSchema,
92
+ TodoistEmulatorTask,
93
+ TodoistEmulatorTaskSeed,
94
+ type TodoistEmulatorState
95
+ } from './todoist/state.ts'
96
+
97
+ /** Origin the connector calls. */
98
+ export const todoistEmulatorDefaultOrigin = 'https://api.todoist.com'
99
+
100
+ /**
101
+ * Route evidence manifest: every emulated route, whether it writes, and the conformance cases
102
+ * whose (currently synthetic, unverified) wire claims it follows. Kept in sync with the handlers
103
+ * by construction (both come from one route table). Every route cites at least one case.
104
+ */
105
+ export const todoistEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence> = todoistApiRoutes.map(
106
+ ({ route }) => routeEvidence(route)
107
+ )
108
+
109
+ export type TodoistFaultState = StatefulFaultState
110
+
111
+ export type TodoistLedgerEntry = StatefulHeaderlessLedgerEntry
112
+
113
+ export type TodoistCoverage = StatefulCoverage
114
+
115
+ /** Invalid emulator input from the JS API: a seed or a fault. A programmer error. */
116
+ export class TodoistEmulatorInputInvalid extends Data.TaggedError('TodoistEmulatorInputInvalid')<{
117
+ readonly input: 'seed' | 'fault'
118
+ readonly reason: string
119
+ }> {
120
+ override get message(): string {
121
+ return `Invalid Todoist emulator ${this.input}: ${this.reason}`
122
+ }
123
+ }
124
+
125
+ export type TodoistEmulatorOptions = {
126
+ /** Typed seed; defaults to the fixture entities (`profile: 'default'`). */
127
+ readonly seed?: TodoistEmulatorSeed
128
+ /** Clock in epoch milliseconds (created and closed timestamps). Defaults to `Date.now`. */
129
+ readonly now?: () => number
130
+ /** Drill knobs (tests only): make the emulator disagree with one conformance claim. */
131
+ readonly drills?: TodoistEmulatorDrills
132
+ }
133
+
134
+ export type TodoistEmulator = {
135
+ /** The fetch handler (Todoist routes and `/_emulate/*`). Never rejects. */
136
+ readonly fetch: (request: Request) => Promise<Response>
137
+ readonly ledger: {
138
+ readonly entries: () => ReadonlyArray<TodoistLedgerEntry>
139
+ readonly clear: () => void
140
+ }
141
+ readonly faults: {
142
+ /** Add a fault; throws `TodoistEmulatorInputInvalid` for an invalid fault. */
143
+ readonly add: (fault: StatefulFault) => TodoistFaultState
144
+ readonly list: () => ReadonlyArray<TodoistFaultState>
145
+ readonly clear: () => void
146
+ }
147
+ /** Task-listing cursors answered since the last reset or seed (runtime data). */
148
+ readonly cursors: () => ReadonlyArray<TodoistCursor>
149
+ /** Restore the current seed and clear the ledger, faults, and cursors. */
150
+ readonly reset: () => Promise<void>
151
+ /**
152
+ * Replace the state with a new seed, which becomes what `reset` restores (cursors are cleared).
153
+ * Rejects with `TodoistEmulatorInputInvalid` for an invalid seed.
154
+ */
155
+ readonly seed: (seed: TodoistEmulatorSeed) => Promise<void>
156
+ /** A deep copy of the current state (entities and counters). */
157
+ readonly snapshot: () => TodoistEmulatorState
158
+ readonly coverage: () => TodoistCoverage
159
+ /** Close the core runtime. Later requests answer 503. Idempotent. */
160
+ readonly close: () => Promise<void>
161
+ }
162
+
163
+ // A bearer credential of at least 8 characters. The value is never checked against anything,
164
+ // stored, forwarded, or ledgered; it is guarded like the Telegram token: a path, query, or body
165
+ // that repeats it is refused, and it is scrubbed from everything ledgered or answered.
166
+ const bearerPattern = /^bearer\s+(\S{8,})\s*$/i
167
+
168
+ const resolve = (
169
+ request: Request,
170
+ url: URL
171
+ ): StatefulResolution<TodoistEmulatorState, TodoistApiEnv> => {
172
+ const ledgerPath = url.pathname
173
+ const authorization = request.headers.get('authorization')
174
+ const bearer = bearerPattern.exec(authorization ?? '')?.[1]
175
+
176
+ // Fail closed: an Authorization header that is present but not one recognisable bearer (extra
177
+ // words, duplicated headers) means the credential cannot be extracted and scrubbed, so the
178
+ // request is ledgered and answered with constant text only, whatever the route.
179
+ if (authorization !== null && bearer === undefined) {
180
+ return {
181
+ kind: 'unrecognised',
182
+ reason: 'an unrecognisable Authorization header is not emulated'
183
+ }
184
+ }
185
+
186
+ // On a recognised route the bearer value is guarded (scrubbed and never repeated).
187
+ const secrets = bearer === undefined ? [] : [bearer]
188
+ const matched = matchTodoistRoute(request.method, url.pathname)
189
+
190
+ // Fail closed: a request on no emulated route is ledgered and answered with constant text only.
191
+ if (matched === undefined) {
192
+ return { kind: 'unrecognised', reason: 'no emulated Todoist route for this method and path' }
193
+ }
194
+
195
+ const refuse = (reason: string): StatefulResolution<TodoistEmulatorState, TodoistApiEnv> => ({
196
+ kind: 'refused',
197
+ ledgerPath,
198
+ reason,
199
+ route: matched.route.route,
200
+ secrets
201
+ })
202
+
203
+ if (bearer === undefined) {
204
+ return refuse(
205
+ 'requests without Authorization: Bearer <token of at least 8 characters> are not emulated'
206
+ )
207
+ }
208
+
209
+ const problem = todoistQueryProblem(matched.route, url.searchParams)
210
+
211
+ if (problem !== undefined) return refuse(problem)
212
+
213
+ return {
214
+ kind: 'route',
215
+ ledgerPath,
216
+ route: matched.route.route,
217
+ params: matched.params,
218
+ // The path and query the request must not repeat the bearer in.
219
+ guardedPath: `${url.pathname}${url.search}`,
220
+ secrets
221
+ }
222
+ }
223
+
224
+ /** The texts of Todoist's recovery answers (500 failed, 503 closed, 500 unhandled). */
225
+ const errorTexts: StatefulErrorTexts = {
226
+ failed: 'Synthetic: the emulator could not build the response.',
227
+ closed: 'Synthetic: the emulator is closed.',
228
+ unhandled: 'emulator failed to handle the request'
229
+ }
230
+
231
+ /** The wrapper reports invalid seeds and faults only (Todoist takes no other option input). */
232
+ const inputInvalid = (input: StatefulInputKind, reason: string) =>
233
+ new TodoistEmulatorInputInvalid({ input: input === 'seed' ? 'seed' : 'fault', reason })
234
+
235
+ /**
236
+ * Create a stateful Todoist emulator on the `@emulators/core` custom runtime. Each call has its
237
+ * own state, ledger, faults, and cursors. Rejects with `TodoistEmulatorInputInvalid` for an
238
+ * invalid seed.
239
+ *
240
+ * Routes (under `/api/v1`, `Authorization: Bearer <token of at least 8 characters>`): task
241
+ * listing with cursor paging, task read, create, update, and close, label listing, and project
242
+ * create, read, and delete.
243
+ * See `README.md` (Todoist emulator) for the wire claims.
244
+ */
245
+ export const makeTodoistEmulator = async (
246
+ options: TodoistEmulatorOptions = {}
247
+ ): Promise<TodoistEmulator> => {
248
+ const initial = buildTodoistSeedState(options.seed ?? {})
249
+
250
+ if (Predicate.isString(initial)) {
251
+ throw new TodoistEmulatorInputInvalid({ input: 'seed', reason: initial })
252
+ }
253
+
254
+ const drills = options.drills ?? {}
255
+
256
+ const env: TodoistApiEnv = {
257
+ now: options.now ?? (() => Date.now()),
258
+ drills: {
259
+ cursorRestarts: drills.cursorRestarts ?? false,
260
+ notFoundWithoutError: drills.notFoundWithoutError ?? false,
261
+ taskLabelsAsIds: drills.taskLabelsAsIds ?? false,
262
+ listIncludesClosed: drills.listIncludesClosed ?? false,
263
+ ignoreDue: drills.ignoreDue ?? false,
264
+ createOmitsParent: drills.createOmitsParent ?? false,
265
+ deleteKeepsTasks: drills.deleteKeepsTasks ?? false
266
+ },
267
+ cursors: new Map(),
268
+ cursorCounter: { next: 1 }
269
+ }
270
+
271
+ const cursorList = (): ReadonlyArray<TodoistCursor> => [...env.cursors.values()]
272
+
273
+ const emulator = await makeHeaderlessStatefulEmulator<
274
+ TodoistEmulatorState,
275
+ TodoistApiEnv,
276
+ TodoistEmulatorSeed
277
+ >(
278
+ {
279
+ routes: todoistApiRoutes.map(({ route }) => route),
280
+ env,
281
+ initial,
282
+ buildSeed: buildTodoistSeedState,
283
+ resolveRequest: resolve,
284
+ errorTexts,
285
+ clearRuntime: () => {
286
+ env.cursors.clear()
287
+ env.cursorCounter.next = 1
288
+ },
289
+ runtimeState: () => ({ runtime: { cursors: cursorList().map(cursor => ({ ...cursor })) } }),
290
+ seedSummary: state => ({
291
+ projects: state.projects.length,
292
+ tasks: state.tasks.length,
293
+ labels: state.labels.length
294
+ }),
295
+ inputInvalid
296
+ },
297
+ statefulCoreRuntime({
298
+ name: 'todoist',
299
+ initial,
300
+ decodeState: decodeTodoistState,
301
+ inputInvalid
302
+ })
303
+ )
304
+
305
+ return {
306
+ fetch: emulator.fetch,
307
+ ledger: emulator.ledger,
308
+ faults: emulator.faults,
309
+ cursors: cursorList,
310
+ reset: emulator.reset,
311
+ seed: emulator.seed,
312
+ snapshot: emulator.snapshot,
313
+ coverage: emulator.coverage,
314
+ close: emulator.close
315
+ }
316
+ }
package/src/xai.ts ADDED
@@ -0,0 +1,341 @@
1
+ /**
2
+ * xAI Grok CLI proxy Responses emulator: a plain fetch handler for `POST /v1/responses` (origin
3
+ * `https://cli-chat-proxy.grok.com`), with scripted turns, wire faults, a request ledger, and an
4
+ * `/_emulate/*` control plane.
5
+ *
6
+ * It never imports SDK code: its wire shapes follow the synthetic Grok Responses conformance
7
+ * fixtures (Responses SSE with typed `event:` names, the `response` JSON body, and the
8
+ * `{ error: { message, type, param, code } }` envelope) and are linked to those conformance case
9
+ * ids in `xAiGrokEmulatorRoutes`. The Responses machinery lives in the internal `responses.ts`
10
+ * core and is shared with the Codex emulator.
11
+ *
12
+ * Requests authenticate with a non-empty `Authorization: Bearer` credential (a Grok OAuth access
13
+ * token) and must also send, in this order, a non-empty `X-XAI-Token-Auth` (401 without it; never
14
+ * recorded or checked), `x-grok-client-version` (426 without it: the proxy version-gates
15
+ * requests), and `x-grok-model-override` (400 without it). The client version and model override
16
+ * are recorded in the ledger; the version value itself is not checked.
17
+ *
18
+ * The same fetch handler also answers the Grok subscription-usage route
19
+ * (`GET /v1/billing?format=credits`, `emulator.usage`) with its own manifest
20
+ * (`xAiGrokSubscriptionUsageEmulatorRoutes`), ledger, faults, and turns; see
21
+ * `makeXAiGrokEmulator`.
22
+ *
23
+ * Runtime-portable Web APIs only (`Request`, `Response`, `ReadableStream`, `TextEncoder`, `URL`);
24
+ * no Effect runtime is required to use it.
25
+ *
26
+ * @experimental
27
+ */
28
+ import { Data } from 'effect'
29
+ import type * as Schema from 'effect/Schema'
30
+ import { withSubscriptionUsage } from './emulator-compose.ts'
31
+ import type {
32
+ EmulatorCoverage,
33
+ EmulatorFaultState,
34
+ EmulatorRouteCoverage
35
+ } from './emulator-kernel.ts'
36
+ import { EmulatorFaultMatch, EmulatorScriptedError } from './emulator-kernel.ts'
37
+ import {
38
+ makeResponsesEmulator,
39
+ ResponsesErrorEventFault,
40
+ ResponsesFault,
41
+ ResponsesScriptedFunctionCall,
42
+ ResponsesScriptedResponse,
43
+ ResponsesScriptedTurn,
44
+ ResponsesScriptedUsage,
45
+ ResponsesStreamError,
46
+ type ResponsesEmulator,
47
+ type ResponsesFaultKind,
48
+ type ResponsesLedgerEntry,
49
+ type ResponsesWireError
50
+ } from './responses.ts'
51
+ import type { EmulatorRouteEvidence } from './route-evidence.ts'
52
+ import {
53
+ makeSubscriptionUsageEmulator,
54
+ recordedUsageBody,
55
+ SubscriptionUsageFault,
56
+ type SubscriptionUsageEmulator,
57
+ type SubscriptionUsageLedgerEntry,
58
+ type SubscriptionUsageScriptedTurn
59
+ } from './subscription-usage.ts'
60
+ import { xAiGrokUsageRecording } from './subscription-usage-recordings.ts'
61
+
62
+ export type { EmulatorEvidence, EmulatorRouteEvidence } from './route-evidence.ts'
63
+
64
+ export { emulatorEvidenceHeader } from './route-evidence.ts'
65
+
66
+ export const xAiGrokResponsesPath = '/v1/responses'
67
+
68
+ /**
69
+ * The Grok subscription-usage path; the SDK fetcher (`xAiGrokSubscriptionUsageUrl`) adds
70
+ * `?format=credits`, the recorded query.
71
+ */
72
+ export const xAiGrokSubscriptionUsagePath = '/v1/billing'
73
+
74
+ /**
75
+ * Route evidence manifest of the Grok subscription-usage route (served by the same fetch handler
76
+ * as the Responses route, with its own ledger and coverage). Synthetic, unverified.
77
+ */
78
+ export const xAiGrokSubscriptionUsageEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence> = [
79
+ {
80
+ method: 'GET',
81
+ path: xAiGrokSubscriptionUsagePath,
82
+ kind: 'provider',
83
+ write: false,
84
+ caseIds: ['xai.grok.usage.snapshot'],
85
+ evidence: 'unverified',
86
+ observedAt: undefined
87
+ }
88
+ ]
89
+
90
+ /**
91
+ * The recorded Grok usage body (the synthetic `xai.grok.usage.snapshot` fixture, copied as data):
92
+ * `config.creditUsagePercent` and `config.currentPeriod` `{ type, start, end }`.
93
+ */
94
+ export const xAiGrokSubscriptionUsageDefault: Schema.Json = recordedUsageBody(xAiGrokUsageRecording)
95
+
96
+ /** Synthetic-safe default model ids, including the Grok conformance defaults. */
97
+ export const xAiGrokEmulatorDefaultModels: ReadonlyArray<string> = ['grok-build', 'grok-4.6']
98
+
99
+ /**
100
+ * Route evidence manifest: every emulated Grok route and the conformance cases whose (currently
101
+ * synthetic, unverified) wire shapes it follows.
102
+ */
103
+ export const xAiGrokEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence> = [
104
+ {
105
+ method: 'POST',
106
+ path: xAiGrokResponsesPath,
107
+ kind: 'provider',
108
+ write: false,
109
+ caseIds: [
110
+ 'xai.grok.stream.plain-text',
111
+ 'xai.grok.stream.function-call-arguments',
112
+ 'xai.grok.stream.error-envelope',
113
+ 'xai.grok.stream.terminal-event'
114
+ ],
115
+ evidence: 'unverified',
116
+ observedAt: undefined
117
+ }
118
+ ]
119
+
120
+ /** Optional fault filter; an omitted field matches every request. `path` ending in `*` is a prefix. */
121
+ export const XAiGrokFaultMatch = EmulatorFaultMatch
122
+
123
+ export type XAiGrokFaultMatch = typeof EmulatorFaultMatch.Type
124
+
125
+ /** A mid-stream Responses error (`{ code, message }`). */
126
+ export const XAiGrokStreamError = ResponsesStreamError
127
+
128
+ export type XAiGrokStreamError = ResponsesStreamError
129
+
130
+ /** A mid-stream `error` or `response.failed` event fault, streamed responses only. */
131
+ export const XAiGrokErrorEventFault = ResponsesErrorEventFault
132
+
133
+ export type XAiGrokErrorEventFault = typeof ResponsesErrorEventFault.Type
134
+
135
+ /**
136
+ * Wire faults: `status` (for example 429 with `retry-after`; the body defaults to the error
137
+ * envelope for the status), `error-after-chunks` (a dropped connection),
138
+ * `truncate-after-chunks` (a clean close, for example before `response.completed`), and
139
+ * `error-event-after-chunks` (a mid-stream `error` or `response.failed` event). Statuses that
140
+ * cannot carry a body and redirects are rejected, as are invalid headers and `location`. A fault
141
+ * that cannot take effect answers 500 and is not consumed.
142
+ */
143
+ export const XAiGrokFault = ResponsesFault
144
+
145
+ export type XAiGrokFault = ResponsesFault
146
+
147
+ export type XAiGrokFaultKind = ResponsesFaultKind
148
+
149
+ /** Usage for a scripted turn (sent in `response.completed` or the JSON body). */
150
+ export const XAiGrokScriptedUsage = ResponsesScriptedUsage
151
+
152
+ export type XAiGrokScriptedUsage = ResponsesScriptedUsage
153
+
154
+ /** One scripted `function_call` item; its arguments stream as these fragments. */
155
+ export const XAiGrokScriptedFunctionCall = ResponsesScriptedFunctionCall
156
+
157
+ export type XAiGrokScriptedFunctionCall = ResponsesScriptedFunctionCall
158
+
159
+ /**
160
+ * A scripted response: `reasoning` (summary fragments), `text`, `functionCalls`, `order`,
161
+ * `usage` (`null` drops it), and `format` (`sse` or `json`, default: the request's `stream`).
162
+ */
163
+ export const XAiGrokScriptedResponse = ResponsesScriptedResponse
164
+
165
+ export type XAiGrokScriptedResponse = ResponsesScriptedResponse
166
+
167
+ /** A scripted error response: status, body (a string is sent as is), and optional headers. */
168
+ export const XAiGrokScriptedError = EmulatorScriptedError
169
+
170
+ export type XAiGrokScriptedError = typeof EmulatorScriptedError.Type
171
+
172
+ /** A turn queued for the next Grok request. */
173
+ export const XAiGrokScriptedTurn = ResponsesScriptedTurn
174
+
175
+ export type XAiGrokScriptedTurn = ResponsesScriptedTurn
176
+
177
+ /** Thrown by the JS API (`faults.add`, `script.enqueue`) for invalid input; a programmer error. */
178
+ export class XAiGrokEmulatorInputInvalid extends Data.TaggedError('XAiGrokEmulatorInputInvalid')<{
179
+ readonly input: 'fault' | 'turn'
180
+ readonly reason: string
181
+ }> {
182
+ override get message(): string {
183
+ return `Invalid xAI Grok emulator ${this.input}: ${this.reason}`
184
+ }
185
+ }
186
+
187
+ export type XAiGrokLedgerEntry = ResponsesLedgerEntry
188
+
189
+ export type XAiGrokFaultState = EmulatorFaultState<XAiGrokFault>
190
+
191
+ export type XAiGrokRouteCoverage = EmulatorRouteCoverage
192
+
193
+ export type XAiGrokCoverage = EmulatorCoverage
194
+
195
+ /** A usage-route fault (`status`, `error-after-chunks`, `truncate-after-chunks`). */
196
+ export const XAiGrokUsageFault = SubscriptionUsageFault
197
+
198
+ export type XAiGrokUsageFault = SubscriptionUsageFault
199
+
200
+ /** A usage turn: `{ usage }` (a body with the recorded JSON shape) or `{ error }`. */
201
+ export type XAiGrokUsageScriptedTurn = SubscriptionUsageScriptedTurn
202
+
203
+ export type XAiGrokUsageLedgerEntry = SubscriptionUsageLedgerEntry
204
+
205
+ export type XAiGrokUsageEmulator = SubscriptionUsageEmulator
206
+
207
+ export type XAiGrokEmulatorOptions = {
208
+ /** Model ids that exist. Defaults to `xAiGrokEmulatorDefaultModels`. */
209
+ readonly knownModels?: ReadonlyArray<string>
210
+ /**
211
+ * Replacement usage-route body; must have the recorded JSON shape (same keys and value kinds).
212
+ * Defaults to the recorded body (`xAiGrokSubscriptionUsageDefault`).
213
+ */
214
+ readonly subscriptionUsage?: Schema.Json
215
+ }
216
+
217
+ /** The Responses emulator, plus `usage`: the subscription-usage route's own emulator API. */
218
+ export type XAiGrokEmulator = ResponsesEmulator & { readonly usage: XAiGrokUsageEmulator }
219
+
220
+ const errorEnvelope = (error: ResponsesWireError): Schema.Json => ({
221
+ error: {
222
+ message: error.message,
223
+ type: error.type,
224
+ param: error.param ?? null,
225
+ code: error.code
226
+ }
227
+ })
228
+
229
+ /**
230
+ * Create a Grok CLI proxy Responses emulator. Each call has independent ledger, fault, and script
231
+ * state.
232
+ *
233
+ * Without a script, `POST /v1/responses` answers a known model with synthetic output: `stream:
234
+ * true` streams `response.created`, `response.in_progress`, the output items
235
+ * (`response.output_item.added`, their parts and deltas, `response.output_item.done`), and
236
+ * `response.completed` with usage; `stream: false` returns one `response` JSON body. A request
237
+ * whose `reasoning` asks for a `summary` gets a reasoning item first. A request with function
238
+ * `tools` gets one `function_call` item whose arguments are synthesized from the tool's JSON
239
+ * Schema and streamed as `response.function_call_arguments.delta` fragments. Unknown models get
240
+ * 400 `model_not_found`; a request without a bearer credential gets 401 `invalid_api_key`,
241
+ * without `X-XAI-Token-Auth` 401, without `x-grok-client-version` 426, and without
242
+ * `x-grok-model-override` 400; a non-positive `max_output_tokens` gets 400; unknown routes get a
243
+ * 404 envelope. Not enforced: that `x-grok-model-override` matches the body `model`, the client
244
+ * version value, and the output limit itself (recorded as `maxOutputTokens`).
245
+ *
246
+ * `GET /v1/billing?format=credits` is fixture-only: a request with the headers the SDK fetcher
247
+ * sends (a non-empty bearer, `X-XAI-Token-Auth: xai-grok-cli`, a non-empty `x-userid`, a
248
+ * non-empty `x-grok-client-version`, and `x-grok-client-mode: headless`; the bearer, user id, and
249
+ * client-version values are request-shape latitude, and the user id and token-auth are never
250
+ * recorded),
251
+ * `accept: application/json`, and exactly the recorded `format=credits` query gets the recorded
252
+ * body (`xAiGrokSubscriptionUsageDefault`, or a same-shaped `options.subscriptionUsage` / scripted
253
+ * `{ usage }`); anything else answers 400 not-emulated. The client version and mode are recorded.
254
+ * Its ledger, faults, turns, and coverage are `emulator.usage`
255
+ * (control plane `/_emulate/usage/*`); `reset()` and `POST /_emulate/reset` reset both routes.
256
+ */
257
+ export const makeXAiGrokEmulator = (options: XAiGrokEmulatorOptions = {}): XAiGrokEmulator =>
258
+ withSubscriptionUsage(
259
+ makeXAiGrokResponsesEmulator(options),
260
+ makeXAiGrokUsageEmulator(options),
261
+ xAiGrokSubscriptionUsagePath
262
+ )
263
+
264
+ const makeXAiGrokUsageEmulator = (options: XAiGrokEmulatorOptions): XAiGrokUsageEmulator =>
265
+ makeSubscriptionUsageEmulator({
266
+ path: xAiGrokSubscriptionUsagePath,
267
+ routes: xAiGrokSubscriptionUsageEmulatorRoutes,
268
+ recording: xAiGrokUsageRecording,
269
+ headers: [
270
+ { name: 'x-xai-token-auth', record: false, accepts: value => value === 'xai-grok-cli' },
271
+ { name: 'x-userid', record: false },
272
+ { name: 'x-grok-client-version', record: true },
273
+ { name: 'x-grok-client-mode', record: true, accepts: value => value === 'headless' }
274
+ ],
275
+ subscriptionUsage: options.subscriptionUsage,
276
+ inputInvalid: (input, reason) => new XAiGrokEmulatorInputInvalid({ input, reason })
277
+ })
278
+
279
+ const makeXAiGrokResponsesEmulator = (options: XAiGrokEmulatorOptions): ResponsesEmulator =>
280
+ makeResponsesEmulator({
281
+ path: xAiGrokResponsesPath,
282
+ routes: xAiGrokEmulatorRoutes,
283
+ knownModels: options.knownModels ?? xAiGrokEmulatorDefaultModels,
284
+ errorEnvelope,
285
+ // Copied data shape of the synthetic Grok error-envelope fixture (unknown model id).
286
+ unknownModel: {
287
+ status: 400,
288
+ error: {
289
+ message: 'Synthetic placeholder: the requested model does not exist.',
290
+ type: 'invalid_request_error',
291
+ code: 'model_not_found',
292
+ param: 'model'
293
+ }
294
+ },
295
+ unauthorized: {
296
+ message: 'Synthetic: missing or invalid bearer credential.',
297
+ type: 'invalid_request_error',
298
+ code: 'invalid_api_key'
299
+ },
300
+ headers: [
301
+ {
302
+ name: 'x-xai-token-auth',
303
+ record: false,
304
+ required: {
305
+ status: 401,
306
+ error: {
307
+ message: 'Synthetic: the X-XAI-Token-Auth header is required.',
308
+ type: 'invalid_request_error',
309
+ code: 'missing_token_auth'
310
+ }
311
+ }
312
+ },
313
+ {
314
+ name: 'x-grok-client-version',
315
+ record: true,
316
+ required: {
317
+ status: 426,
318
+ error: {
319
+ message: 'Synthetic: a supported x-grok-client-version header is required.',
320
+ type: 'invalid_request_error',
321
+ code: 'upgrade_required'
322
+ }
323
+ }
324
+ },
325
+ {
326
+ name: 'x-grok-model-override',
327
+ record: true,
328
+ required: {
329
+ status: 400,
330
+ error: {
331
+ message: 'Synthetic: the x-grok-model-override header is required.',
332
+ type: 'invalid_request_error',
333
+ code: 'missing_model_override'
334
+ }
335
+ }
336
+ }
337
+ ],
338
+ outputTokenLimit: 'optional',
339
+ defaultText: ['Hello', ' from the', ' synthetic Grok emulator.'],
340
+ inputInvalid: (input, reason) => new XAiGrokEmulatorInputInvalid({ input, reason })
341
+ })