@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,271 @@
1
+ /**
2
+ * OpenCode Go emulator: one plain fetch handler for the origin `https://opencode.ai`, serving the
3
+ * routes the OpenCode Go provider and usage fetcher call under `/zen/go/v1`:
4
+ *
5
+ * - `POST /zen/go/v1/chat/completions` (Bearer, `max_tokens`, streamed with usage);
6
+ * - `POST /zen/go/v1/messages` (`x-api-key`, `anthropic-version: 2023-06-01`, streamed);
7
+ * - `POST /zen/go/v1/responses` (Bearer, `max_output_tokens`, streamed; plain text or a replayed
8
+ * tool turn with commentary-tagged text);
9
+ * - `GET /zen/go/v1/usage` (Bearer).
10
+ *
11
+ * Fixture-only (the owner rule for new routes; `fixture-route.ts`): a request matching a recorded
12
+ * request shape, within the documented request-shape latitude, gets that Go conformance fixture's
13
+ * recorded response, copied as data (`opencode-recordings.ts`); everything else (unknown routes,
14
+ * missing credentials or headers, other models, non-streamed modes, tools other than the recorded
15
+ * replay, reasoning, extra fields) answers one ledgered 400 not-emulated.
16
+ *
17
+ * Each route keeps its own ledger, faults, scripted turns, and control plane (`emulator.chat`,
18
+ * `emulator.messages`, `emulator.responses`, `emulator.usage`; over HTTP `/_emulate/<part>/*`);
19
+ * `emulator.coverage()` and `GET /_emulate/coverage` combine them, and `emulator.reset()` and
20
+ * `POST /_emulate/reset` reset all four. Requests on no route are ledgered by the chat part.
21
+ *
22
+ * It never imports SDK code. All routes are `unverified` (the fixtures are synthetic).
23
+ *
24
+ * Runtime-portable Web APIs only (`Request`, `Response`, `ReadableStream`, `TextEncoder`, `URL`);
25
+ * no Effect runtime is required to use it.
26
+ *
27
+ * @experimental
28
+ */
29
+ import { Data } from 'effect'
30
+ import type * as Schema from 'effect/Schema'
31
+ import { combinedCoverage, composeFetch, type ComposedPart } from './emulator-compose.ts'
32
+ import { jsonResponse, stringField, type EmulatorCoverage } from './emulator-kernel.ts'
33
+ import {
34
+ makeFixtureRouteEmulator,
35
+ type FixtureRecording,
36
+ type FixtureRouteEmulator
37
+ } from './fixture-route.ts'
38
+ import { openCodeGoRecordings } from './opencode-recordings.ts'
39
+ import type { EmulatorRouteEvidence } from './route-evidence.ts'
40
+ import { makeSubscriptionUsageEmulator, recordedUsageBody } from './subscription-usage.ts'
41
+
42
+ export type { EmulatorEvidence, EmulatorRouteEvidence } from './route-evidence.ts'
43
+
44
+ export { emulatorEvidenceHeader } from './route-evidence.ts'
45
+
46
+ export type {
47
+ FixtureRouteFault as OpenCodeGoFault,
48
+ FixtureRouteLedgerEntry as OpenCodeGoLedgerEntry,
49
+ FixtureRouteScriptedTurn as OpenCodeGoScriptedTurn
50
+ } from './fixture-route.ts'
51
+
52
+ export const openCodeGoChatCompletionsPath = '/zen/go/v1/chat/completions'
53
+
54
+ export const openCodeGoMessagesPath = '/zen/go/v1/messages'
55
+
56
+ export const openCodeGoResponsesPath = '/zen/go/v1/responses'
57
+
58
+ /** The Go subscription-usage path (`openCodeGoSubscriptionUsageUrl` in the SDK). */
59
+ export const openCodeGoUsagePath = '/zen/go/v1/usage'
60
+
61
+ const recordingsFor = (path: string): ReadonlyArray<FixtureRecording> =>
62
+ openCodeGoRecordings.filter(recording => recording.request.path === path)
63
+
64
+ const usageRecording: FixtureRecording = (() => {
65
+ const [recording] = recordingsFor(openCodeGoUsagePath)
66
+
67
+ // Packaging-data invariant: the Go usage fixture is copied into this package.
68
+ if (recording === undefined) throw new Error('the Go usage recording is missing')
69
+
70
+ return recording
71
+ })()
72
+
73
+ /**
74
+ * The model ids the Go fixtures record, one per protocol (synthetic placeholders). A protocol
75
+ * route answers only its recorded model; any other model is not emulated.
76
+ */
77
+ export const openCodeGoEmulatorDefaultModels: ReadonlyArray<string> = [
78
+ ...new Set(
79
+ openCodeGoRecordings.flatMap(recording => {
80
+ const model = stringField(recording.request.body, 'model')
81
+
82
+ return model === undefined ? [] : [model]
83
+ })
84
+ )
85
+ ]
86
+
87
+ /**
88
+ * Route evidence manifest: every emulated OpenCode Go route and the conformance cases whose
89
+ * (synthetic, unverified) fixtures it answers from.
90
+ */
91
+ export const openCodeGoEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence> = [
92
+ {
93
+ method: 'POST',
94
+ path: openCodeGoChatCompletionsPath,
95
+ kind: 'provider',
96
+ write: false,
97
+ caseIds: ['opencode.go.chat.stream.plain-text'],
98
+ evidence: 'unverified',
99
+ observedAt: undefined
100
+ },
101
+ {
102
+ method: 'POST',
103
+ path: openCodeGoMessagesPath,
104
+ kind: 'provider',
105
+ write: false,
106
+ caseIds: ['opencode.go.messages.stream.plain-text'],
107
+ evidence: 'unverified',
108
+ observedAt: undefined
109
+ },
110
+ {
111
+ method: 'POST',
112
+ path: openCodeGoResponsesPath,
113
+ kind: 'provider',
114
+ write: false,
115
+ caseIds: [
116
+ 'opencode.go.responses.stream.plain-text',
117
+ 'opencode.go.responses.stream.commentary-replay'
118
+ ],
119
+ evidence: 'unverified',
120
+ observedAt: undefined
121
+ },
122
+ {
123
+ method: 'GET',
124
+ path: openCodeGoUsagePath,
125
+ kind: 'provider',
126
+ write: false,
127
+ caseIds: ['opencode.go.usage.snapshot'],
128
+ evidence: 'unverified',
129
+ observedAt: undefined
130
+ }
131
+ ]
132
+
133
+ /**
134
+ * The recorded Go usage body (the synthetic `opencode.go.usage.snapshot` fixture, copied as
135
+ * data): `usage.rolling`, `usage.weekly`, and `usage.monthly` as `{ percent, resetsAt }`.
136
+ */
137
+ export const openCodeGoUsageDefault: Schema.Json = recordedUsageBody(usageRecording)
138
+
139
+ /** Thrown by the JS API (`faults.add`, `script.enqueue`) and at construction for invalid input. */
140
+ export class OpenCodeGoEmulatorInputInvalid extends Data.TaggedError(
141
+ 'OpenCodeGoEmulatorInputInvalid'
142
+ )<{
143
+ readonly protocol: 'chat-completions' | 'messages' | 'responses' | 'usage'
144
+ readonly input: 'fault' | 'turn'
145
+ readonly reason: string
146
+ }> {
147
+ override get message(): string {
148
+ return `Invalid OpenCode Go ${this.protocol} emulator ${this.input}: ${this.reason}`
149
+ }
150
+ }
151
+
152
+ export type OpenCodeGoEmulatorOptions = {
153
+ /**
154
+ * Replacement usage-route body; must have the recorded JSON shape (same keys and value kinds).
155
+ * Defaults to the recorded body (`openCodeGoUsageDefault`).
156
+ */
157
+ readonly subscriptionUsage?: Schema.Json
158
+ }
159
+
160
+ /** One Go route: a fixture-only emulator API (ledger, faults, scripted turns, coverage). */
161
+ export type OpenCodeGoRouteEmulator = FixtureRouteEmulator
162
+
163
+ export type OpenCodeGoEmulator = {
164
+ /** The fetch handler for every route and the `/_emulate/*` control plane. Never rejects. */
165
+ readonly fetch: (request: Request) => Promise<Response>
166
+ /** Reset every part (ledgers, faults, and scripted turns). */
167
+ readonly reset: () => void
168
+ /** Every route's coverage, in manifest order. */
169
+ readonly coverage: () => EmulatorCoverage
170
+ /** `POST /zen/go/v1/chat/completions` (control plane `/_emulate/chat/*`). */
171
+ readonly chat: OpenCodeGoRouteEmulator
172
+ /** `POST /zen/go/v1/messages` (control plane `/_emulate/messages/*`). */
173
+ readonly messages: OpenCodeGoRouteEmulator
174
+ /** `POST /zen/go/v1/responses` (control plane `/_emulate/responses/*`). */
175
+ readonly responses: OpenCodeGoRouteEmulator
176
+ /** `GET /zen/go/v1/usage` (control plane `/_emulate/usage/*`). */
177
+ readonly usage: OpenCodeGoRouteEmulator
178
+ }
179
+
180
+ const routesFor = (path: string): ReadonlyArray<EmulatorRouteEvidence> =>
181
+ openCodeGoEmulatorRoutes.filter(route => route.path === path)
182
+
183
+ /**
184
+ * Create an OpenCode Go emulator. Each call has independent state for every part. Throws
185
+ * `OpenCodeGoEmulatorInputInvalid` when `subscriptionUsage` does not have the recorded shape.
186
+ *
187
+ * Per route, a request carrying the credential and headers the Go provider or usage fetcher sends
188
+ * (Bearer for chat, Responses, and usage; `x-api-key` and `anthropic-version: 2023-06-01` for
189
+ * Messages), the recorded `accept` and `content-type`, and a body matching a recorded request
190
+ * shape gets the recorded response; anything else answers 400 not-emulated. Test controls per
191
+ * part: the shared kernel faults and scripted error turns (and, on usage, a scripted same-shaped
192
+ * `{ usage }` body).
193
+ */
194
+ export const makeOpenCodeGoEmulator = (
195
+ options: OpenCodeGoEmulatorOptions = {}
196
+ ): OpenCodeGoEmulator => {
197
+ const inputInvalid =
198
+ (protocol: OpenCodeGoEmulatorInputInvalid['protocol']) =>
199
+ (input: 'fault' | 'turn', reason: string) =>
200
+ new OpenCodeGoEmulatorInputInvalid({ protocol, input, reason })
201
+
202
+ const chat = makeFixtureRouteEmulator({
203
+ method: 'POST',
204
+ path: openCodeGoChatCompletionsPath,
205
+ routes: routesFor(openCodeGoChatCompletionsPath),
206
+ recordings: recordingsFor(openCodeGoChatCompletionsPath),
207
+ credential: 'bearer',
208
+ headers: [],
209
+ inputInvalid: inputInvalid('chat-completions')
210
+ })
211
+
212
+ const messages = makeFixtureRouteEmulator({
213
+ method: 'POST',
214
+ path: openCodeGoMessagesPath,
215
+ routes: routesFor(openCodeGoMessagesPath),
216
+ recordings: recordingsFor(openCodeGoMessagesPath),
217
+ credential: 'x-api-key',
218
+ headers: [
219
+ { name: 'anthropic-version', record: true, accepts: value => value === '2023-06-01' }
220
+ ],
221
+ inputInvalid: inputInvalid('messages')
222
+ })
223
+
224
+ const responses = makeFixtureRouteEmulator({
225
+ method: 'POST',
226
+ path: openCodeGoResponsesPath,
227
+ routes: routesFor(openCodeGoResponsesPath),
228
+ recordings: recordingsFor(openCodeGoResponsesPath),
229
+ credential: 'bearer',
230
+ headers: [],
231
+ inputInvalid: inputInvalid('responses')
232
+ })
233
+
234
+ const usage = makeSubscriptionUsageEmulator({
235
+ path: openCodeGoUsagePath,
236
+ routes: routesFor(openCodeGoUsagePath),
237
+ recording: usageRecording,
238
+ headers: [],
239
+ subscriptionUsage: options.subscriptionUsage,
240
+ inputInvalid: inputInvalid('usage')
241
+ })
242
+
243
+ const parts: ReadonlyArray<ComposedPart> = [chat, messages, responses, usage]
244
+ const coverage = () => combinedCoverage(parts)
245
+
246
+ const reset = () => {
247
+ for (const part of parts) part.reset()
248
+ }
249
+
250
+ const fetch = composeFetch({
251
+ routes: [
252
+ { name: 'chat', paths: [openCodeGoChatCompletionsPath], part: chat },
253
+ { name: 'messages', paths: [openCodeGoMessagesPath], part: messages },
254
+ { name: 'responses', paths: [openCodeGoResponsesPath], part: responses },
255
+ { name: 'usage', paths: [openCodeGoUsagePath], part: usage }
256
+ ],
257
+ fallback: chat,
258
+ control: async (request, path) =>
259
+ path === '/_emulate/coverage' && request.method === 'GET'
260
+ ? jsonResponse(200, coverage())
261
+ : jsonResponse(404, {
262
+ error: {
263
+ message:
264
+ 'unknown control-plane route: use /_emulate/{chat,messages,responses,usage}/*, GET /_emulate/coverage, or POST /_emulate/reset',
265
+ type: 'emulator_error'
266
+ }
267
+ })
268
+ })
269
+
270
+ return { fetch, reset, coverage, chat, messages, responses, usage }
271
+ }
@@ -0,0 +1,295 @@
1
+ /**
2
+ * The R2 conformance fixtures, copied as data (internal; re-exported by `r2.ts`).
3
+ *
4
+ * Emulators never import SDK code: this is a verbatim copy of `r2ConformanceFixtures` from
5
+ * `@yolk-sdk/connectors/r2-storage/conformance` (synthetic `PortFixture`s; none observed live).
6
+ * The presigned URL carries only the canonical synthetic SigV4 placeholders
7
+ * (`syntheticPortCredentialParams` of `@yolk-sdk/conformance`). `test/r2-conformance.test.ts`
8
+ * fails when the two drift apart; update both together.
9
+ */
10
+ import type * as Schema from 'effect/Schema'
11
+
12
+ /** A port failure as a fixture records it (mirrors `PortFailure` in `@yolk-sdk/conformance`). */
13
+ export type R2EmulatorFailure = {
14
+ readonly kind: 'expected' | 'error'
15
+ readonly code: string
16
+ readonly message: string
17
+ readonly status?: number
18
+ }
19
+
20
+ type R2EmulatorFixtureFields = {
21
+ readonly id: string
22
+ readonly port: string
23
+ readonly method: string
24
+ readonly request: Schema.Json
25
+ readonly observed?: { readonly account: string; readonly date: string }
26
+ readonly note?: string
27
+ }
28
+
29
+ /**
30
+ * One recorded `R2Presigner` or `R2ObjectClient` call (the plain-JSON shape of a `PortFixture`):
31
+ * exactly one of `response` or `failure`.
32
+ */
33
+ export type R2EmulatorFixture =
34
+ | (R2EmulatorFixtureFields & { readonly response: Schema.Json; readonly failure?: never })
35
+ | (R2EmulatorFixtureFields & {
36
+ readonly failure: R2EmulatorFailure
37
+ readonly response?: never
38
+ })
39
+
40
+ export const r2EmulatorFixtures: ReadonlyArray<R2EmulatorFixture> = [
41
+ {
42
+ id: 'r2.presign.put-upload-url.presign.synthetic',
43
+ port: 'R2Presigner',
44
+ method: 'presignPutObject',
45
+ request: {
46
+ endpoint: 'https://synthetic-account.r2.example.test',
47
+ bucket: 'yolk-synthetic-bucket',
48
+ key: 'yolk-conformance/run-synthetic/presign.txt',
49
+ contentType: 'text/plain'
50
+ },
51
+ response: {
52
+ uploadUrl:
53
+ 'https://synthetic-account.r2.example.test/yolk-synthetic-bucket/yolk-conformance/run-synthetic/presign.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=yolk-synthetic-access-key-id%2F20260930%2Fauto%2Fs3%2Faws4_request&X-Amz-Date=20260930T120000Z&X-Amz-Expires=900&X-Amz-Signature=yolk-synthetic-signature&X-Amz-SignedHeaders=content-type%3Bhost&x-id=PutObject'
54
+ },
55
+ note: 'Local SigV4 query signing (15-minute expiry, content-type signed); nothing sent to R2. Synthetic.'
56
+ },
57
+ {
58
+ id: 'r2.objects.get-max-bytes.within-budget.synthetic',
59
+ port: 'R2ObjectClient',
60
+ method: 'get',
61
+ request: {
62
+ bucket: 'yolk-synthetic-bucket',
63
+ key: 'fixtures/synthetic-object.txt',
64
+ maxBytes: 1048576
65
+ },
66
+ response: {
67
+ etag: '"a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5"',
68
+ size: 44,
69
+ bodyBase64: 'U3ludGhldGljIGNvbmZvcm1hbmNlIG9iamVjdDogc2FmZSB0byByZWFkLgo='
70
+ },
71
+ note: 'The seeded object within the budget. Synthetic.'
72
+ },
73
+ {
74
+ id: 'r2.objects.get-max-bytes.over-budget.synthetic',
75
+ port: 'R2ObjectClient',
76
+ method: 'get',
77
+ request: {
78
+ bucket: 'yolk-synthetic-bucket',
79
+ key: 'fixtures/synthetic-object.txt',
80
+ maxBytes: 43
81
+ },
82
+ failure: {
83
+ kind: 'error',
84
+ code: 'response_too_large',
85
+ message: 'The object is larger than maxBytes; no bytes were returned.'
86
+ },
87
+ note: 'The host stops at maxBytes (Content-Length or streamed count). Synthetic.'
88
+ },
89
+ {
90
+ id: 'r2.objects.get-expected-etag.plain.synthetic',
91
+ port: 'R2ObjectClient',
92
+ method: 'get',
93
+ request: {
94
+ bucket: 'yolk-synthetic-bucket',
95
+ key: 'fixtures/synthetic-object.txt',
96
+ maxBytes: 1048576
97
+ },
98
+ response: {
99
+ etag: '"a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5"',
100
+ size: 44,
101
+ bodyBase64: 'U3ludGhldGljIGNvbmZvcm1hbmNlIG9iamVjdDogc2FmZSB0byByZWFkLgo='
102
+ },
103
+ note: 'A plain read that learns the etag. Synthetic.'
104
+ },
105
+ {
106
+ id: 'r2.objects.get-expected-etag.current.synthetic',
107
+ port: 'R2ObjectClient',
108
+ method: 'get',
109
+ request: {
110
+ bucket: 'yolk-synthetic-bucket',
111
+ key: 'fixtures/synthetic-object.txt',
112
+ expectedEtag: '"a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5"',
113
+ maxBytes: 1048576
114
+ },
115
+ response: {
116
+ etag: '"a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5"',
117
+ size: 44,
118
+ bodyBase64: 'U3ludGhldGljIGNvbmZvcm1hbmNlIG9iamVjdDogc2FmZSB0byByZWFkLgo='
119
+ },
120
+ note: 'If-Match with the current etag. Synthetic.'
121
+ },
122
+ {
123
+ id: 'r2.objects.get-expected-etag.stale.synthetic',
124
+ port: 'R2ObjectClient',
125
+ method: 'get',
126
+ request: {
127
+ bucket: 'yolk-synthetic-bucket',
128
+ key: 'fixtures/synthetic-object.txt',
129
+ expectedEtag: '"00000000000000000000000000000000"',
130
+ maxBytes: 1048576
131
+ },
132
+ failure: {
133
+ kind: 'error',
134
+ code: 'conflict',
135
+ message: 'Precondition failed (If-Match).'
136
+ },
137
+ note: 'If-Match with a stale etag: HTTP 412 or a binding onlyIf without body. Synthetic.'
138
+ },
139
+ {
140
+ id: 'r2.objects.get-missing-not-found.get.synthetic',
141
+ port: 'R2ObjectClient',
142
+ method: 'get',
143
+ request: {
144
+ bucket: 'yolk-synthetic-bucket',
145
+ key: 'yolk-conformance/run-synthetic/absent.txt',
146
+ maxBytes: 1048576
147
+ },
148
+ failure: {
149
+ kind: 'error',
150
+ code: 'not_found',
151
+ message: 'No object with that key.'
152
+ },
153
+ note: 'HTTP 404 NoSuchKey, or a binding get answering null. Synthetic.'
154
+ },
155
+ {
156
+ id: 'r2.objects.create-if-absent.create.synthetic',
157
+ port: 'R2ObjectClient',
158
+ method: 'put',
159
+ request: {
160
+ bucket: 'yolk-synthetic-bucket',
161
+ key: 'yolk-conformance/run-synthetic/create-if-absent.txt',
162
+ condition: {
163
+ kind: 'absent'
164
+ },
165
+ bodyBase64:
166
+ 'eW9say1jb25mb3JtYW5jZSBydW4tc3ludGhldGljOiBmaXJzdCBzeW50aGV0aWMgYm9keSwgc2FmZSB0byBkZWxldGU=',
167
+ maxUploadBytes: 1048576
168
+ },
169
+ response: {
170
+ etag: '"c0ffee00c0ffee00c0ffee00c0ffee01"',
171
+ size: 68
172
+ },
173
+ note: 'PUT with If-None-Match: * on a fresh key. Synthetic.'
174
+ },
175
+ {
176
+ id: 'r2.objects.create-if-absent.create-again.synthetic',
177
+ port: 'R2ObjectClient',
178
+ method: 'put',
179
+ request: {
180
+ bucket: 'yolk-synthetic-bucket',
181
+ key: 'yolk-conformance/run-synthetic/create-if-absent.txt',
182
+ condition: {
183
+ kind: 'absent'
184
+ },
185
+ bodyBase64:
186
+ 'eW9say1jb25mb3JtYW5jZSBydW4tc3ludGhldGljOiBzZWNvbmQgc3ludGhldGljIGJvZHksIG5ldmVyIHN0b3JlZA==',
187
+ maxUploadBytes: 1048576
188
+ },
189
+ failure: {
190
+ kind: 'error',
191
+ code: 'conflict',
192
+ message: 'Precondition failed (If-None-Match).'
193
+ },
194
+ note: 'The same key again: HTTP 412, or a binding put answering null. Synthetic.'
195
+ },
196
+ {
197
+ id: 'r2.objects.create-if-absent.read.synthetic',
198
+ port: 'R2ObjectClient',
199
+ method: 'get',
200
+ request: {
201
+ bucket: 'yolk-synthetic-bucket',
202
+ key: 'yolk-conformance/run-synthetic/create-if-absent.txt',
203
+ expectedEtag: '"c0ffee00c0ffee00c0ffee00c0ffee01"',
204
+ maxBytes: 1048576
205
+ },
206
+ response: {
207
+ etag: '"c0ffee00c0ffee00c0ffee00c0ffee01"',
208
+ size: 68,
209
+ bodyBase64:
210
+ 'eW9say1jb25mb3JtYW5jZSBydW4tc3ludGhldGljOiBmaXJzdCBzeW50aGV0aWMgYm9keSwgc2FmZSB0byBkZWxldGU='
211
+ },
212
+ note: 'The first bytes, unchanged. Synthetic.'
213
+ },
214
+ {
215
+ id: 'r2.objects.update-if-match.create.synthetic',
216
+ port: 'R2ObjectClient',
217
+ method: 'put',
218
+ request: {
219
+ bucket: 'yolk-synthetic-bucket',
220
+ key: 'yolk-conformance/run-synthetic/update-if-match.txt',
221
+ condition: {
222
+ kind: 'absent'
223
+ },
224
+ bodyBase64:
225
+ 'eW9say1jb25mb3JtYW5jZSBydW4tc3ludGhldGljOiBvcmlnaW5hbCBzeW50aGV0aWMgYm9keSwgc2FmZSB0byBkZWxldGU=',
226
+ maxUploadBytes: 1048576
227
+ },
228
+ response: {
229
+ etag: '"c0ffee00c0ffee00c0ffee00c0ffee02"',
230
+ size: 71
231
+ },
232
+ note: 'PUT with If-None-Match: * on a fresh key. Synthetic.'
233
+ },
234
+ {
235
+ id: 'r2.objects.update-if-match.stale.synthetic',
236
+ port: 'R2ObjectClient',
237
+ method: 'put',
238
+ request: {
239
+ bucket: 'yolk-synthetic-bucket',
240
+ key: 'yolk-conformance/run-synthetic/update-if-match.txt',
241
+ condition: {
242
+ kind: 'etag',
243
+ etag: '"00000000000000000000000000000000"'
244
+ },
245
+ bodyBase64:
246
+ 'eW9say1jb25mb3JtYW5jZSBydW4tc3ludGhldGljOiByZXBsYWNlbWVudCBzeW50aGV0aWMgYm9keSwgc2FmZSB0byBkZWxldGU=',
247
+ maxUploadBytes: 1048576
248
+ },
249
+ failure: {
250
+ kind: 'error',
251
+ code: 'conflict',
252
+ message: 'Precondition failed (If-Match).'
253
+ },
254
+ note: 'PUT with a stale If-Match: HTTP 412, or a binding put answering null. Synthetic.'
255
+ },
256
+ {
257
+ id: 'r2.objects.update-if-match.current.synthetic',
258
+ port: 'R2ObjectClient',
259
+ method: 'put',
260
+ request: {
261
+ bucket: 'yolk-synthetic-bucket',
262
+ key: 'yolk-conformance/run-synthetic/update-if-match.txt',
263
+ condition: {
264
+ kind: 'etag',
265
+ etag: '"c0ffee00c0ffee00c0ffee00c0ffee02"'
266
+ },
267
+ bodyBase64:
268
+ 'eW9say1jb25mb3JtYW5jZSBydW4tc3ludGhldGljOiByZXBsYWNlbWVudCBzeW50aGV0aWMgYm9keSwgc2FmZSB0byBkZWxldGU=',
269
+ maxUploadBytes: 1048576
270
+ },
271
+ response: {
272
+ etag: '"c0ffee00c0ffee00c0ffee00c0ffee03"',
273
+ size: 74
274
+ },
275
+ note: 'PUT with the current If-Match: a new MD5 etag. Synthetic.'
276
+ },
277
+ {
278
+ id: 'r2.objects.update-if-match.read.synthetic',
279
+ port: 'R2ObjectClient',
280
+ method: 'get',
281
+ request: {
282
+ bucket: 'yolk-synthetic-bucket',
283
+ key: 'yolk-conformance/run-synthetic/update-if-match.txt',
284
+ expectedEtag: '"c0ffee00c0ffee00c0ffee00c0ffee03"',
285
+ maxBytes: 1048576
286
+ },
287
+ response: {
288
+ etag: '"c0ffee00c0ffee00c0ffee00c0ffee03"',
289
+ size: 74,
290
+ bodyBase64:
291
+ 'eW9say1jb25mb3JtYW5jZSBydW4tc3ludGhldGljOiByZXBsYWNlbWVudCBzeW50aGV0aWMgYm9keSwgc2FmZSB0byBkZWxldGU='
292
+ },
293
+ note: 'The replacement bytes under the new etag. Synthetic.'
294
+ }
295
+ ]