@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,153 @@
1
+ /**
2
+ * AI Gateway classifier (`POST /v1/evaluate`) recordings the `/gateway` evaluate route answers
3
+ * from.
4
+ *
5
+ * Copied as data from the committed (synthetic, unverified) classifier conformance fixtures; never
6
+ * imported. `test/fixture-recordings.test.ts` fails when a fixture changes and this copy does not.
7
+ * Internal; not a package export.
8
+ */
9
+ import type { FixtureRecording } from './fixture-route.ts'
10
+
11
+ const approvalRequestBody = (model: string) => ({
12
+ model,
13
+ state: 'Synthetic reply: thanks, the fix works on my side.',
14
+ questions: {
15
+ approves: {
16
+ type: 'boolean',
17
+ instructions: 'Does the reply approve the delivered result?',
18
+ criteria: {
19
+ true: 'The reply accepts or approves the result.',
20
+ false: 'The reply rejects or questions the result.'
21
+ }
22
+ }
23
+ }
24
+ })
25
+
26
+ const jsonHeaders = {
27
+ accept: 'application/json',
28
+ 'content-type': 'application/json'
29
+ }
30
+
31
+ const jsonResponseHeaders = {
32
+ 'content-type': 'application/json'
33
+ }
34
+
35
+ export const gatewayEvaluateBooleanRecording: FixtureRecording = {
36
+ fixtureId: 'vercel-ai-gateway.classify.boolean.synthetic',
37
+ caseId: 'vercel-ai-gateway.classify.boolean',
38
+ request: {
39
+ method: 'POST',
40
+ path: '/v1/evaluate',
41
+ query: '',
42
+ headers: jsonHeaders,
43
+ body: approvalRequestBody('typesafe-ai/jev')
44
+ },
45
+ response: {
46
+ status: 200,
47
+ headers: jsonResponseHeaders,
48
+ streamed: false,
49
+ chunks: [
50
+ '{"model":"typesafe-ai/jev","answers":{"approves":{"type":"boolean","probability":0.93}},"usage":{"inputTokens":58,"outputTokens":0},"providerMetadata":{"gateway":{"cost":"0.000002436"}}}'
51
+ ]
52
+ }
53
+ }
54
+
55
+ export const gatewayEvaluateChoiceRecording: FixtureRecording = {
56
+ fixtureId: 'vercel-ai-gateway.classify.choice.synthetic',
57
+ caseId: 'vercel-ai-gateway.classify.choice',
58
+ request: {
59
+ method: 'POST',
60
+ path: '/v1/evaluate',
61
+ query: '',
62
+ headers: jsonHeaders,
63
+ body: {
64
+ model: 'typesafe-ai/jev',
65
+ state: {
66
+ subject: 'Synthetic invoice question',
67
+ body: 'I was charged twice for the synthetic plan.'
68
+ },
69
+ questions: {
70
+ route: {
71
+ type: 'choice',
72
+ instructions: 'Which team should handle this ticket?',
73
+ criteria: {
74
+ billing: 'Payments, invoices, and refunds.',
75
+ bug: 'Product defects and errors.',
76
+ other: 'Anything else.'
77
+ }
78
+ }
79
+ }
80
+ }
81
+ },
82
+ response: {
83
+ status: 200,
84
+ headers: jsonResponseHeaders,
85
+ streamed: false,
86
+ chunks: [
87
+ '{"model":"typesafe-ai/jev","answers":{"route":{"type":"choice","choice":"billing","probabilities":{"billing":0.91,"bug":0.06,"other":0.03}}},"usage":{"inputTokens":74,"outputTokens":0},"providerMetadata":{"gateway":{"cost":"0.000003108"},"typesafe":{"confidence":{"route":0.88}}}}'
88
+ ]
89
+ }
90
+ }
91
+
92
+ export const gatewayEvaluateScoreRecording: FixtureRecording = {
93
+ fixtureId: 'vercel-ai-gateway.classify.score.synthetic',
94
+ caseId: 'vercel-ai-gateway.classify.score',
95
+ request: {
96
+ method: 'POST',
97
+ path: '/v1/evaluate',
98
+ query: '',
99
+ headers: jsonHeaders,
100
+ body: {
101
+ model: 'typesafe-ai/jev',
102
+ state: [
103
+ {
104
+ author: 'synthetic-user',
105
+ text: 'The synthetic dashboard is down for everyone.'
106
+ }
107
+ ],
108
+ questions: {
109
+ urgency: {
110
+ type: 'score',
111
+ instructions: 'How urgent is this report?',
112
+ criteria: ['Not urgent.', 'Somewhat urgent.', 'Urgent.', 'Critical outage.']
113
+ }
114
+ }
115
+ }
116
+ },
117
+ response: {
118
+ status: 200,
119
+ headers: jsonResponseHeaders,
120
+ streamed: false,
121
+ chunks: [
122
+ '{"model":"typesafe-ai/jev","answers":{"urgency":{"type":"score","score":2.74,"probabilities":{"0":0.01,"1":0.04,"2":0.15,"3":0.8},"confidence":0.77}},"usage":{"inputTokens":66,"outputTokens":0},"providerMetadata":{"gateway":{"cost":"0.000002772"}}}'
123
+ ]
124
+ }
125
+ }
126
+
127
+ export const gatewayEvaluateErrorEnvelopeRecording: FixtureRecording = {
128
+ fixtureId: 'vercel-ai-gateway.classify.error-envelope.synthetic',
129
+ caseId: 'vercel-ai-gateway.classify.error-envelope',
130
+ request: {
131
+ method: 'POST',
132
+ path: '/v1/evaluate',
133
+ query: '',
134
+ headers: jsonHeaders,
135
+ body: approvalRequestBody('yolk-conformance/model-does-not-exist')
136
+ },
137
+ response: {
138
+ status: 404,
139
+ headers: jsonResponseHeaders,
140
+ streamed: false,
141
+ chunks: [
142
+ '{"message":"Model \'yolk-conformance/model-does-not-exist\' not found","error_type":"model_not_found"}'
143
+ ]
144
+ }
145
+ }
146
+
147
+ /** Every classifier recording, in conformance case order. */
148
+ export const gatewayEvaluateRecordings: ReadonlyArray<FixtureRecording> = [
149
+ gatewayEvaluateBooleanRecording,
150
+ gatewayEvaluateChoiceRecording,
151
+ gatewayEvaluateScoreRecording,
152
+ gatewayEvaluateErrorEnvelopeRecording
153
+ ]
package/src/gateway.ts ADDED
@@ -0,0 +1,530 @@
1
+ /**
2
+ * Vercel AI Gateway emulator: a plain fetch handler for the OpenAI-compatible
3
+ * `POST /v1/chat/completions` endpoint, with scripted turns, wire faults, a
4
+ * request ledger, and an `/_emulate/*` control plane.
5
+ *
6
+ * It never imports SDK code: its wire shapes follow the verified live Gateway
7
+ * conformance recordings (2026-09-30), copied as data: `chat.completion.chunk`
8
+ * SSE with several events per network chunk, `delta.reasoning` plus
9
+ * `reasoning_details` for DeepSeek reasoning, a finish event carrying
10
+ * `provider_metadata`, `usage`, `system_fingerprint`, `service_tier`, and
11
+ * `generationId`, then `data: [DONE]`, and the 404 `model_not_found`
12
+ * envelope `{ error: { message, type, param: { modelId } } }` for unknown
13
+ * models. Ids, costs, and routing metadata are synthetic stand-ins. The route
14
+ * is linked to those conformance case ids in `gatewayEmulatorRoutes`
15
+ * (`verified`). The Chat Completions machinery is shared with the OpenAI
16
+ * emulator (`chat-completions.ts`); this module supplies the Gateway's paths,
17
+ * models, envelope, reasoning, and wire profile.
18
+ *
19
+ * Not covered by a recording (synthetic): the 401 error, default fault
20
+ * bodies, the non-streamed `chat.completion` body, and the answer to a request
21
+ * without a `model` (404 `Model '' not found` with `param.modelId: null`).
22
+ *
23
+ * The same fetch handler also answers the classifier route `POST /v1/evaluate`
24
+ * (AI Gateway calls classification "evaluation"; `emulator.evaluate`) with its
25
+ * own manifest (`gatewayEvaluateEmulatorRoutes`, unverified), ledger, faults,
26
+ * and turns. It is fixture-only: a request matching one of the four synthetic
27
+ * classifier conformance recordings (boolean, choice, score, unknown-model
28
+ * error envelope; `gateway-evaluate-recordings.ts`, copied as data) within the
29
+ * shared request-shape latitude gets that recording's response; anything else
30
+ * answers 400 not-emulated.
31
+ *
32
+ * Runtime-portable Web APIs only (`Request`, `Response`, `ReadableStream`,
33
+ * `TextEncoder`, `URL`); no Effect runtime is required to use it.
34
+ *
35
+ * @experimental
36
+ */
37
+ import { Data } from 'effect'
38
+ import type * as Schema from 'effect/Schema'
39
+ import {
40
+ ChatFault,
41
+ ChatFaultMatch,
42
+ ChatScriptedReasoningCompletion,
43
+ ChatScriptedError,
44
+ ChatScriptedToolCall,
45
+ ChatScriptedReasoningTurn,
46
+ ChatScriptedUsage,
47
+ makeChatCompletionsEmulator,
48
+ type ChatCompletionsEmulator,
49
+ type ChatCoverage,
50
+ type ChatFaultKind,
51
+ type ChatFaultState,
52
+ type ChatLedgerEntry,
53
+ type ChatResponseIdentity,
54
+ type ChatRouteCoverage,
55
+ type ChatUsageCounts,
56
+ type ChatWireError,
57
+ type ChatWireProfile
58
+ } from './chat-completions.ts'
59
+ import { composeFetch } from './emulator-compose.ts'
60
+ import {
61
+ FixtureRouteFault,
62
+ makeFixtureRouteEmulator,
63
+ type FixtureRouteEmulator,
64
+ type FixtureRouteFaultKind,
65
+ type FixtureRouteLedgerEntry,
66
+ type FixtureRouteScriptedTurn
67
+ } from './fixture-route.ts'
68
+ import { gatewayEvaluateRecordings } from './gateway-evaluate-recordings.ts'
69
+ import type { EmulatorRouteEvidence } from './route-evidence.ts'
70
+
71
+ export type { EmulatorEvidence, EmulatorRouteEvidence } from './route-evidence.ts'
72
+
73
+ export { emulatorEvidenceHeader } from './route-evidence.ts'
74
+
75
+ export const gatewayChatCompletionsPath = '/v1/chat/completions'
76
+
77
+ /** The AI Gateway classifier ("evaluation") path. */
78
+ export const gatewayEvaluatePath = '/v1/evaluate'
79
+
80
+ /** Synthetic-safe default model ids, including the Gateway conformance defaults. */
81
+ export const gatewayEmulatorDefaultModels: ReadonlyArray<string> = [
82
+ 'openai/gpt-4.1-nano',
83
+ 'openai/gpt-4.1-mini',
84
+ 'deepseek/deepseek-v3.2',
85
+ 'deepseek/deepseek-v4.1-flash'
86
+ ]
87
+
88
+ /** Models that stream reasoning deltas when reasoning is requested. */
89
+ export const gatewayEmulatorDefaultReasoningModels: ReadonlyArray<string> = [
90
+ 'deepseek/deepseek-v3.2',
91
+ 'deepseek/deepseek-v4.1-flash'
92
+ ]
93
+
94
+ /**
95
+ * SSE events per network chunk by default. The live Gateway packs several
96
+ * events into one network chunk (the recordings carry one to four, always
97
+ * with the finish event and `data: [DONE]` together in the last chunk).
98
+ */
99
+ export const gatewayEmulatorDefaultEventsPerChunk = 2
100
+
101
+ /**
102
+ * Route evidence manifest: every emulated Gateway route and the conformance
103
+ * cases whose verified live recordings (2026-09-30) its wire shapes follow.
104
+ */
105
+ export const gatewayEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence> = [
106
+ {
107
+ method: 'POST',
108
+ path: gatewayChatCompletionsPath,
109
+ kind: 'provider',
110
+ write: false,
111
+ caseIds: [
112
+ 'vercel-ai-gateway.stream.plain-text',
113
+ 'vercel-ai-gateway.stream.deepseek-reasoning',
114
+ 'vercel-ai-gateway.stream.tool-call-deltas',
115
+ 'vercel-ai-gateway.stream.error-envelope'
116
+ ],
117
+ evidence: 'verified',
118
+ observedAt: '2026-09-30'
119
+ }
120
+ ]
121
+
122
+ /**
123
+ * Route evidence manifest of the classifier route (served by the same fetch handler, with its own
124
+ * ledger, faults, and coverage). Its recordings are synthetic placeholders, so it is unverified.
125
+ */
126
+ export const gatewayEvaluateEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence> = [
127
+ {
128
+ method: 'POST',
129
+ path: gatewayEvaluatePath,
130
+ kind: 'provider',
131
+ write: false,
132
+ caseIds: [
133
+ 'vercel-ai-gateway.classify.boolean',
134
+ 'vercel-ai-gateway.classify.choice',
135
+ 'vercel-ai-gateway.classify.score',
136
+ 'vercel-ai-gateway.classify.error-envelope'
137
+ ],
138
+ evidence: 'unverified',
139
+ observedAt: undefined
140
+ }
141
+ ]
142
+
143
+ /** Optional fault filter; an omitted field matches every request. `path` ending in `*` is a prefix. */
144
+ export const GatewayFaultMatch = ChatFaultMatch
145
+
146
+ export type GatewayFaultMatch = ChatFaultMatch
147
+
148
+ /**
149
+ * Wire faults for emulated routes:
150
+ *
151
+ * - `status`: answer with this status, headers, and body instead of a
152
+ * completion (for example 429 with `retry-after`). The body defaults to a
153
+ * Gateway error envelope. Statuses that cannot carry a body (1xx, 204, 205,
154
+ * 304) and redirects (3xx) are rejected, as are invalid header names or
155
+ * values and a `location` header; scripted errors follow the same rules.
156
+ * - `error-after-chunks`: send `chunks` body chunks, then error the body
157
+ * stream (a dropped connection).
158
+ * - `truncate-after-chunks`: send `chunks` body chunks, then close the body
159
+ * cleanly (for example without `data: [DONE]`).
160
+ *
161
+ * Chunk faults count network chunks: a streamed chunk carries up to
162
+ * `eventsPerChunk` SSE events (default 2), and a whole JSON body counts as one
163
+ * chunk. A chunk fault that cannot take effect (`error-after-chunks` beyond
164
+ * the chunk count, or `truncate-after-chunks` at or beyond it) answers 500
165
+ * with an emulator error instead of silently doing nothing, and is not
166
+ * consumed.
167
+ */
168
+ export const GatewayFault = ChatFault
169
+
170
+ export type GatewayFault = ChatFault
171
+
172
+ export type GatewayFaultKind = ChatFaultKind
173
+
174
+ /**
175
+ * Usage for a scripted turn, sent as the Gateway `usage` object (token counts,
176
+ * `completion_tokens_details.reasoning_tokens` defaulting to 0, and synthetic
177
+ * zero costs).
178
+ */
179
+ export const GatewayScriptedUsage = ChatScriptedUsage
180
+
181
+ export type GatewayScriptedUsage = ChatScriptedUsage
182
+
183
+ /** One scripted tool call; its JSON arguments stream as these fragments. */
184
+ export const GatewayScriptedToolCall = ChatScriptedToolCall
185
+
186
+ export type GatewayScriptedToolCall = ChatScriptedToolCall
187
+
188
+ /**
189
+ * A scripted completion. Every field is exact (nothing is filled in) except:
190
+ * `usage` omitted is synthesized when the request asks for usage, and `null`
191
+ * drops it; `finishReason` omitted is `tool_calls` with tool calls, else
192
+ * `stop`. `order` defaults to `reasoning-first`; `reasoningField` defaults to
193
+ * `reasoning` (streamed with `reasoning_details`, as recorded live); the
194
+ * DeepSeek-native `reasoning_content` is sent alone.
195
+ */
196
+ export const GatewayScriptedCompletion = ChatScriptedReasoningCompletion
197
+
198
+ export type GatewayScriptedCompletion = ChatScriptedReasoningCompletion
199
+
200
+ /** A scripted error response: status, body (a string is sent as is), and optional headers. */
201
+ export const GatewayScriptedError = ChatScriptedError
202
+
203
+ export type GatewayScriptedError = ChatScriptedError
204
+
205
+ /** A turn queued for the next chat completion request. */
206
+ export const GatewayScriptedTurn = ChatScriptedReasoningTurn
207
+
208
+ export type GatewayScriptedTurn = ChatScriptedReasoningTurn
209
+
210
+ /**
211
+ * Thrown by the JS API (`faults.add`, `script.enqueue`) for invalid input, and
212
+ * by `makeGatewayEmulator` for invalid options; a programmer error.
213
+ */
214
+ export class GatewayEmulatorInputInvalid extends Data.TaggedError('GatewayEmulatorInputInvalid')<{
215
+ readonly input: 'fault' | 'turn' | 'options'
216
+ readonly reason: string
217
+ }> {
218
+ override get message(): string {
219
+ return `Invalid Gateway emulator ${this.input}: ${this.reason}`
220
+ }
221
+ }
222
+
223
+ export type GatewayLedgerEntry = ChatLedgerEntry
224
+
225
+ export type GatewayFaultState = ChatFaultState
226
+
227
+ export type GatewayRouteCoverage = ChatRouteCoverage
228
+
229
+ export type GatewayCoverage = ChatCoverage
230
+
231
+ export type GatewayEmulatorOptions = {
232
+ /** Model ids that exist. Defaults to `gatewayEmulatorDefaultModels`. */
233
+ readonly knownModels?: ReadonlyArray<string>
234
+ /** Model ids that stream reasoning. Defaults to `gatewayEmulatorDefaultReasoningModels`. */
235
+ readonly reasoningModels?: ReadonlyArray<string>
236
+ /**
237
+ * SSE events per network chunk of a streamed body, a positive integer.
238
+ * Defaults to `gatewayEmulatorDefaultEventsPerChunk` (2); 1 sends one event
239
+ * per chunk. Events are packed counting from the end, so the last chunk
240
+ * holds the finish event and `data: [DONE]` together. Chunk faults count
241
+ * these network chunks.
242
+ */
243
+ readonly eventsPerChunk?: number
244
+ }
245
+
246
+ /** A classifier-route fault (`status` 400-599, `error-after-chunks`, `truncate-after-chunks`). */
247
+ export const GatewayEvaluateFault = FixtureRouteFault
248
+
249
+ export type GatewayEvaluateFault = FixtureRouteFault
250
+
251
+ export type GatewayEvaluateFaultKind = FixtureRouteFaultKind
252
+
253
+ /** A classifier-route turn: a scripted error (status 400-599) only. */
254
+ export type GatewayEvaluateScriptedTurn = FixtureRouteScriptedTurn
255
+
256
+ export type GatewayEvaluateLedgerEntry = FixtureRouteLedgerEntry
257
+
258
+ export type GatewayEvaluateEmulator = FixtureRouteEmulator
259
+
260
+ /** The chat emulator, plus `evaluate`: the classifier route's own emulator API. */
261
+ export type GatewayEmulator = ChatCompletionsEmulator<GatewayScriptedTurn> & {
262
+ readonly evaluate: GatewayEvaluateEmulator
263
+ }
264
+
265
+ const gatewayErrorEnvelope = (error: ChatWireError): Schema.Json => ({
266
+ error: { message: error.message, type: error.type, code: error.code }
267
+ })
268
+
269
+ // Synthetic stand-ins for the metadata the live Gateway attaches to its chunks. Keys and value
270
+ // types follow the recordings; every value is synthetic (no recorded id, fingerprint, or cost).
271
+ const syntheticFingerprint = 'fp_synthetic'
272
+
273
+ const syntheticAttemptTime = 1790000000000
274
+
275
+ const syntheticCost = '0'
276
+
277
+ const vendorOf = (model: string): string | undefined => {
278
+ const [vendor] = model.split('/')
279
+
280
+ return vendor === undefined || vendor.length === 0 || vendor === model ? undefined : vendor
281
+ }
282
+
283
+ // `service_tier` was recorded only for OpenAI models.
284
+ const isOpenAiModel = (model: string): boolean => vendorOf(model) === 'openai'
285
+
286
+ const syntheticResponseId = (identity: ChatResponseIdentity): string =>
287
+ `resp_synthetic_${identity.id}`
288
+
289
+ type GatewayUpstream = { readonly provider: string; readonly entry?: Schema.JsonObject }
290
+
291
+ /**
292
+ * The upstream provider the Gateway routes a model to, and its `provider_metadata` entry (keyed by
293
+ * that provider), per model family as recorded: `openai/*` routes to `openai` with
294
+ * `{ responseId, serviceTier }`; `deepseek/*` routes to `baseten` with
295
+ * `{ acceptedPredictionTokens, rejectedPredictionTokens }` (synthetic zero counts). Other families
296
+ * were not recorded: they route to their vendor prefix without an upstream entry.
297
+ */
298
+ const upstreamOf = (identity: ChatResponseIdentity): GatewayUpstream => {
299
+ const vendor = vendorOf(identity.model)
300
+
301
+ if (vendor === 'openai') {
302
+ return {
303
+ provider: 'openai',
304
+ entry: { responseId: syntheticResponseId(identity), serviceTier: 'default' }
305
+ }
306
+ }
307
+
308
+ if (vendor === 'deepseek') {
309
+ return {
310
+ provider: 'baseten',
311
+ entry: { acceptedPredictionTokens: 0, rejectedPredictionTokens: 0 }
312
+ }
313
+ }
314
+
315
+ return { provider: vendor ?? 'synthetic' }
316
+ }
317
+
318
+ /** The `gateway` entry of `provider_metadata`: routing, costs, and the generation id. */
319
+ const gatewayRoutingMetadata = (identity: ChatResponseIdentity): Schema.JsonObject => {
320
+ const { provider } = upstreamOf(identity)
321
+ const responseId = syntheticResponseId(identity)
322
+
323
+ return {
324
+ routing: {
325
+ originalModelId: identity.model,
326
+ resolvedProvider: provider,
327
+ fallbacksAvailable: ['synthetic-fallback'],
328
+ planningReasoning: 'Synthetic: routing planned by the emulator.',
329
+ canonicalSlug: identity.model,
330
+ finalProvider: provider,
331
+ modelAttemptCount: 1,
332
+ modelAttempts: [
333
+ {
334
+ canonicalSlug: identity.model,
335
+ success: true,
336
+ providerAttemptCount: 1,
337
+ providerAttempts: [
338
+ {
339
+ provider,
340
+ credentialType: 'system',
341
+ success: true,
342
+ startTime: syntheticAttemptTime,
343
+ endTime: syntheticAttemptTime + 1,
344
+ providerRequestId: `req_synthetic_${identity.id}`,
345
+ statusCode: 200,
346
+ providerResponseId: responseId
347
+ }
348
+ ]
349
+ }
350
+ ],
351
+ totalProviderAttemptCount: 1,
352
+ affinity: { outcome: 'skipped_below_min_prefix' },
353
+ clientSessionId: 'synthetic-client-session',
354
+ clientSessionIdSource: 'fingerprint'
355
+ },
356
+ cost: syntheticCost,
357
+ marketCost: syntheticCost,
358
+ surchargeCost: syntheticCost,
359
+ gatewayCost: syntheticCost,
360
+ inferenceCost: syntheticCost,
361
+ inputInferenceCost: syntheticCost,
362
+ outputInferenceCost: syntheticCost,
363
+ generationId: identity.id
364
+ }
365
+ }
366
+
367
+ /** `provider_metadata`: the upstream provider's entry first (when its family has one), then `gateway`. */
368
+ const gatewayProviderMetadata = (identity: ChatResponseIdentity): Schema.JsonObject => {
369
+ const metadata: Record<string, Schema.Json> = {}
370
+ const upstream = upstreamOf(identity)
371
+
372
+ if (upstream.entry !== undefined) {
373
+ metadata[upstream.provider] = upstream.entry
374
+ }
375
+
376
+ metadata.gateway = gatewayRoutingMetadata(identity)
377
+
378
+ return metadata
379
+ }
380
+
381
+ const gatewayUsage = (counts: ChatUsageCounts): Schema.JsonObject => ({
382
+ prompt_tokens: counts.promptTokens,
383
+ completion_tokens: counts.completionTokens,
384
+ total_tokens: counts.promptTokens + counts.completionTokens,
385
+ cost: 0,
386
+ is_byok: false,
387
+ prompt_tokens_details: { cached_tokens: 0, audio_tokens: 0, video_tokens: 0 },
388
+ cost_details: {
389
+ upstream_inference_cost: null,
390
+ upstream_inference_prompt_cost: 0,
391
+ upstream_inference_completions_cost: 0
392
+ },
393
+ completion_tokens_details: { reasoning_tokens: counts.reasoningTokens ?? 0, image_tokens: 0 },
394
+ cache_creation_input_tokens: 0,
395
+ market_cost: 0,
396
+ gateway_cost: 0
397
+ })
398
+
399
+ const gatewayWireProfile = (eventsPerChunk: number): ChatWireProfile => ({
400
+ eventsPerChunk,
401
+ streamUsage: 'finish-event',
402
+ openingDelta: { role: 'assistant' },
403
+ choiceFields: { logprobs: null },
404
+ reasoningField: 'reasoning',
405
+ reasoningDetails: true,
406
+ chunkFields: () => ({ system_fingerprint: syntheticFingerprint }),
407
+ finishFields: identity => {
408
+ const chunk: Record<string, Schema.Json> = {}
409
+
410
+ if (isOpenAiModel(identity.model)) {
411
+ chunk.service_tier = 'default'
412
+ }
413
+
414
+ chunk.generationId = identity.id
415
+
416
+ return { delta: { provider_metadata: gatewayProviderMetadata(identity) }, chunk }
417
+ },
418
+ usage: gatewayUsage
419
+ })
420
+
421
+ const validEventsPerChunk = (eventsPerChunk: number | undefined): number => {
422
+ const value = eventsPerChunk ?? gatewayEmulatorDefaultEventsPerChunk
423
+
424
+ if (!Number.isSafeInteger(value) || value < 1) {
425
+ throw new GatewayEmulatorInputInvalid({
426
+ input: 'options',
427
+ reason: `eventsPerChunk must be a positive integer, got ${String(eventsPerChunk)}`
428
+ })
429
+ }
430
+
431
+ return value
432
+ }
433
+
434
+ /**
435
+ * Create a Vercel AI Gateway emulator. Each call has independent ledger,
436
+ * fault, and script state. Throws `GatewayEmulatorInputInvalid` for an
437
+ * invalid `eventsPerChunk`.
438
+ *
439
+ * Without a script, `POST /v1/chat/completions` answers a known model with
440
+ * synthetic text deltas. `stream: true` sends `chat.completion.chunk` SSE in
441
+ * the recorded Gateway shape: a `{ role: 'assistant' }` opening delta, content
442
+ * deltas, and a finish event whose delta carries `provider_metadata` and which
443
+ * carries `usage` (when `stream_options.include_usage` is set),
444
+ * `system_fingerprint`, `service_tier` (for `openai/*` models), and
445
+ * `generationId`, then `data: [DONE]`; every chunk carries
446
+ * `system_fingerprint` and `logprobs: null`, and events are packed
447
+ * `eventsPerChunk` per network chunk. `stream: false` answers one
448
+ * `chat.completion` JSON body. Reasoning models asked for reasoning
449
+ * (`reasoning_effort`, or `thinking.type: 'enabled'`) stream
450
+ * `delta.reasoning` with `delta.reasoning_details` before the text. A request
451
+ * with `tools` gets one tool call whose arguments are synthesized from the
452
+ * tool's JSON Schema and streamed in fragments, finishing with `tool_calls`.
453
+ * Unknown models get the recorded 404 envelope (`type: 'model_not_found'`,
454
+ * `param: { modelId }`, no `code`); a missing bearer credential gets a 401
455
+ * envelope; unknown routes get a 404 JSON error. The ledger records the
456
+ * `max_tokens` limit as `maxCompletionTokens`.
457
+ *
458
+ * Precedence per chat request: authentication and JSON validation, then the
459
+ * first matching fault if it is a `status` fault, then the next scripted
460
+ * turn, then model validation and defaults; a first matching chunk fault then
461
+ * shapes the body. Only the first matching fault (in insertion order) applies. Credential headers are never
462
+ * recorded, and the bearer value is never checked or stored.
463
+ *
464
+ * `POST /v1/evaluate` is fixture-only: a request with a non-empty bearer credential (never checked
465
+ * or stored), the recorded `accept` and `content-type`, no query, and a body matching one of the
466
+ * four classifier recordings (the discriminators `model` and `type` exact; any other string; the
467
+ * same keys, so the recorded question ids, option keys, and level count) gets the recorded
468
+ * response; anything else (another model, `providerOptions`, other questions) answers 400
469
+ * not-emulated. Its ledger, faults (statuses 400-599), scripted errors, and coverage are
470
+ * `emulator.evaluate` (control plane `/_emulate/evaluate/*`); `emulator.faults` and the other
471
+ * top-level APIs stay the chat route's. `reset()` and `POST /_emulate/reset` reset both.
472
+ */
473
+ export const makeGatewayEmulator = (options: GatewayEmulatorOptions = {}): GatewayEmulator => {
474
+ const chat = makeChatCompletionsEmulator({
475
+ path: gatewayChatCompletionsPath,
476
+ routes: gatewayEmulatorRoutes,
477
+ knownModels: options.knownModels ?? gatewayEmulatorDefaultModels,
478
+ reasoningModels: options.reasoningModels ?? gatewayEmulatorDefaultReasoningModels,
479
+ errorEnvelope: gatewayErrorEnvelope,
480
+ // Copied data shape of the verified Gateway error-envelope recording (unknown model id): its
481
+ // own envelope, with `param.modelId` and no `code`.
482
+ unknownModel: {
483
+ status: 404,
484
+ body: model => ({
485
+ error: {
486
+ message: `Model '${model ?? ''}' not found`,
487
+ type: 'model_not_found',
488
+ param: { modelId: model ?? null }
489
+ }
490
+ })
491
+ },
492
+ auth: {
493
+ unauthorized: {
494
+ message: 'Synthetic: missing or invalid authorization.',
495
+ type: 'authentication_error',
496
+ code: 'unauthorized'
497
+ }
498
+ },
499
+ completionTokenField: 'max_tokens',
500
+ responseIdPrefix: 'gen-synthetic',
501
+ defaultText: ['Hello', ' from the', ' synthetic gateway.'],
502
+ turnSchema: GatewayScriptedTurn,
503
+ inputInvalid: (input, reason) => new GatewayEmulatorInputInvalid({ input, reason }),
504
+ wire: gatewayWireProfile(validEventsPerChunk(options.eventsPerChunk))
505
+ })
506
+
507
+ const evaluate = makeFixtureRouteEmulator({
508
+ method: 'POST',
509
+ path: gatewayEvaluatePath,
510
+ routes: gatewayEvaluateEmulatorRoutes,
511
+ recordings: gatewayEvaluateRecordings,
512
+ credential: 'bearer',
513
+ headers: [],
514
+ inputInvalid: (input, reason) => new GatewayEmulatorInputInvalid({ input, reason })
515
+ })
516
+
517
+ return {
518
+ ...chat,
519
+ fetch: composeFetch({
520
+ routes: [{ name: 'evaluate', paths: [gatewayEvaluatePath], part: evaluate }],
521
+ fallback: chat,
522
+ control: request => chat.fetch(request)
523
+ }),
524
+ reset: () => {
525
+ chat.reset()
526
+ evaluate.reset()
527
+ },
528
+ evaluate
529
+ }
530
+ }