@copilotkit/aimock 1.42.0 → 1.43.0

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 (341) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +56 -0
  4. package/README.md +102 -16
  5. package/dist/agui-mock.cjs +1 -1
  6. package/dist/agui-mock.cjs.map +1 -1
  7. package/dist/agui-mock.d.cts.map +1 -1
  8. package/dist/agui-mock.d.ts.map +1 -1
  9. package/dist/agui-mock.js +1 -1
  10. package/dist/agui-mock.js.map +1 -1
  11. package/dist/agui-recorder.cjs +33 -11
  12. package/dist/agui-recorder.cjs.map +1 -1
  13. package/dist/agui-recorder.d.cts +10 -1
  14. package/dist/agui-recorder.d.cts.map +1 -1
  15. package/dist/agui-recorder.d.ts +10 -1
  16. package/dist/agui-recorder.d.ts.map +1 -1
  17. package/dist/agui-recorder.js +33 -11
  18. package/dist/agui-recorder.js.map +1 -1
  19. package/dist/agui-types.d.cts +1 -0
  20. package/dist/agui-types.d.cts.map +1 -1
  21. package/dist/agui-types.d.ts +1 -0
  22. package/dist/agui-types.d.ts.map +1 -1
  23. package/dist/aimock-cli.cjs +38 -10
  24. package/dist/aimock-cli.cjs.map +1 -1
  25. package/dist/aimock-cli.d.cts +5 -0
  26. package/dist/aimock-cli.d.cts.map +1 -1
  27. package/dist/aimock-cli.d.ts +5 -0
  28. package/dist/aimock-cli.d.ts.map +1 -1
  29. package/dist/aimock-cli.js +38 -10
  30. package/dist/aimock-cli.js.map +1 -1
  31. package/dist/batches.cjs +458 -0
  32. package/dist/batches.cjs.map +1 -0
  33. package/dist/batches.d.cts +57 -0
  34. package/dist/batches.d.cts.map +1 -0
  35. package/dist/batches.d.ts +57 -0
  36. package/dist/batches.d.ts.map +1 -0
  37. package/dist/batches.js +453 -0
  38. package/dist/batches.js.map +1 -0
  39. package/dist/bedrock-converse.cjs +12 -8
  40. package/dist/bedrock-converse.cjs.map +1 -1
  41. package/dist/bedrock-converse.d.cts.map +1 -1
  42. package/dist/bedrock-converse.d.ts.map +1 -1
  43. package/dist/bedrock-converse.js +14 -10
  44. package/dist/bedrock-converse.js.map +1 -1
  45. package/dist/bedrock.cjs +10 -8
  46. package/dist/bedrock.cjs.map +1 -1
  47. package/dist/bedrock.d.cts.map +1 -1
  48. package/dist/bedrock.d.ts.map +1 -1
  49. package/dist/bedrock.js +12 -10
  50. package/dist/bedrock.js.map +1 -1
  51. package/dist/byteplus-video.cjs +3 -3
  52. package/dist/byteplus-video.cjs.map +1 -1
  53. package/dist/byteplus-video.js +4 -4
  54. package/dist/byteplus-video.js.map +1 -1
  55. package/dist/chaos.cjs +622 -72
  56. package/dist/chaos.cjs.map +1 -1
  57. package/dist/chaos.d.cts +164 -7
  58. package/dist/chaos.d.cts.map +1 -1
  59. package/dist/chaos.d.ts +164 -7
  60. package/dist/chaos.d.ts.map +1 -1
  61. package/dist/chaos.js +613 -74
  62. package/dist/chaos.js.map +1 -1
  63. package/dist/cli.cjs +42 -23
  64. package/dist/cli.cjs.map +1 -1
  65. package/dist/cli.js +42 -23
  66. package/dist/cli.js.map +1 -1
  67. package/dist/cohere.cjs +10 -8
  68. package/dist/cohere.cjs.map +1 -1
  69. package/dist/cohere.d.cts.map +1 -1
  70. package/dist/cohere.d.ts.map +1 -1
  71. package/dist/cohere.js +12 -10
  72. package/dist/cohere.js.map +1 -1
  73. package/dist/config-loader.d.cts.map +1 -1
  74. package/dist/config-loader.d.ts.map +1 -1
  75. package/dist/convert.cjs +1 -1
  76. package/dist/convert.cjs.map +1 -1
  77. package/dist/convert.js +1 -1
  78. package/dist/convert.js.map +1 -1
  79. package/dist/elevenlabs-audio.cjs +3 -3
  80. package/dist/elevenlabs-audio.cjs.map +1 -1
  81. package/dist/elevenlabs-audio.js +4 -4
  82. package/dist/elevenlabs-audio.js.map +1 -1
  83. package/dist/elevenlabs-voice.cjs +1053 -0
  84. package/dist/elevenlabs-voice.cjs.map +1 -0
  85. package/dist/elevenlabs-voice.d.cts +72 -0
  86. package/dist/elevenlabs-voice.d.cts.map +1 -0
  87. package/dist/elevenlabs-voice.d.ts +72 -0
  88. package/dist/elevenlabs-voice.d.ts.map +1 -0
  89. package/dist/elevenlabs-voice.js +1048 -0
  90. package/dist/elevenlabs-voice.js.map +1 -0
  91. package/dist/embeddings.cjs +8 -4
  92. package/dist/embeddings.cjs.map +1 -1
  93. package/dist/embeddings.d.cts.map +1 -1
  94. package/dist/embeddings.d.ts.map +1 -1
  95. package/dist/embeddings.js +9 -5
  96. package/dist/embeddings.js.map +1 -1
  97. package/dist/fal-audio.cjs +3 -3
  98. package/dist/fal-audio.cjs.map +1 -1
  99. package/dist/fal-audio.js +4 -4
  100. package/dist/fal-audio.js.map +1 -1
  101. package/dist/fal.cjs +28 -4
  102. package/dist/fal.cjs.map +1 -1
  103. package/dist/fal.d.cts +16 -1
  104. package/dist/fal.d.cts.map +1 -1
  105. package/dist/fal.d.ts +16 -1
  106. package/dist/fal.d.ts.map +1 -1
  107. package/dist/fal.js +28 -5
  108. package/dist/fal.js.map +1 -1
  109. package/dist/files.cjs +1071 -0
  110. package/dist/files.cjs.map +1 -0
  111. package/dist/files.d.cts +68 -0
  112. package/dist/files.d.cts.map +1 -0
  113. package/dist/files.d.ts +68 -0
  114. package/dist/files.d.ts.map +1 -0
  115. package/dist/files.js +1059 -0
  116. package/dist/files.js.map +1 -0
  117. package/dist/fine-tuning.cjs +1051 -0
  118. package/dist/fine-tuning.cjs.map +1 -0
  119. package/dist/fine-tuning.d.cts +96 -0
  120. package/dist/fine-tuning.d.cts.map +1 -0
  121. package/dist/fine-tuning.d.ts +96 -0
  122. package/dist/fine-tuning.d.ts.map +1 -0
  123. package/dist/fine-tuning.js +1044 -0
  124. package/dist/fine-tuning.js.map +1 -0
  125. package/dist/fixture-loader.cjs +0 -0
  126. package/dist/fixture-loader.cjs.map +1 -1
  127. package/dist/fixture-loader.d.cts +37 -5
  128. package/dist/fixture-loader.d.cts.map +1 -1
  129. package/dist/fixture-loader.d.ts +37 -5
  130. package/dist/fixture-loader.d.ts.map +1 -1
  131. package/dist/fixture-loader.js +0 -0
  132. package/dist/fixture-loader.js.map +1 -1
  133. package/dist/gemini-embeddings.cjs +2 -2
  134. package/dist/gemini-embeddings.cjs.map +1 -1
  135. package/dist/gemini-embeddings.js +3 -3
  136. package/dist/gemini-embeddings.js.map +1 -1
  137. package/dist/gemini-interactions.cjs +13 -7
  138. package/dist/gemini-interactions.cjs.map +1 -1
  139. package/dist/gemini-interactions.d.cts.map +1 -1
  140. package/dist/gemini-interactions.d.ts.map +1 -1
  141. package/dist/gemini-interactions.js +15 -9
  142. package/dist/gemini-interactions.js.map +1 -1
  143. package/dist/gemini.cjs +11 -7
  144. package/dist/gemini.cjs.map +1 -1
  145. package/dist/gemini.d.cts.map +1 -1
  146. package/dist/gemini.d.ts.map +1 -1
  147. package/dist/gemini.js +13 -9
  148. package/dist/gemini.js.map +1 -1
  149. package/dist/grok-video.cjs +14 -14
  150. package/dist/grok-video.cjs.map +1 -1
  151. package/dist/grok-video.js +15 -15
  152. package/dist/grok-video.js.map +1 -1
  153. package/dist/helpers.cjs +229 -7
  154. package/dist/helpers.cjs.map +1 -1
  155. package/dist/helpers.d.cts +38 -2
  156. package/dist/helpers.d.cts.map +1 -1
  157. package/dist/helpers.d.ts +38 -2
  158. package/dist/helpers.d.ts.map +1 -1
  159. package/dist/helpers.js +221 -8
  160. package/dist/helpers.js.map +1 -1
  161. package/dist/images.cjs +54 -123
  162. package/dist/images.cjs.map +1 -1
  163. package/dist/images.d.cts.map +1 -1
  164. package/dist/images.d.ts.map +1 -1
  165. package/dist/images.js +55 -124
  166. package/dist/images.js.map +1 -1
  167. package/dist/index.cjs +41 -8
  168. package/dist/index.d.cts +12 -7
  169. package/dist/index.d.ts +12 -7
  170. package/dist/index.js +14 -10
  171. package/dist/jest.cjs +14 -7
  172. package/dist/jest.cjs.map +1 -1
  173. package/dist/jest.d.cts +3 -2
  174. package/dist/jest.d.cts.map +1 -1
  175. package/dist/jest.d.ts +3 -2
  176. package/dist/jest.d.ts.map +1 -1
  177. package/dist/jest.js +14 -7
  178. package/dist/jest.js.map +1 -1
  179. package/dist/journal.cjs +41 -4
  180. package/dist/journal.cjs.map +1 -1
  181. package/dist/journal.d.cts +44 -3
  182. package/dist/journal.d.cts.map +1 -1
  183. package/dist/journal.d.ts +44 -3
  184. package/dist/journal.d.ts.map +1 -1
  185. package/dist/journal.js +41 -5
  186. package/dist/journal.js.map +1 -1
  187. package/dist/live-fixture.cjs +1192 -0
  188. package/dist/live-fixture.cjs.map +1 -0
  189. package/dist/live-fixture.js +1187 -0
  190. package/dist/live-fixture.js.map +1 -0
  191. package/dist/live-recorder.cjs +256 -0
  192. package/dist/live-recorder.cjs.map +1 -0
  193. package/dist/live-recorder.js +254 -0
  194. package/dist/live-recorder.js.map +1 -0
  195. package/dist/live-replay.cjs +286 -0
  196. package/dist/live-replay.cjs.map +1 -0
  197. package/dist/live-replay.js +285 -0
  198. package/dist/live-replay.js.map +1 -0
  199. package/dist/live-sanitize.cjs +196 -0
  200. package/dist/live-sanitize.cjs.map +1 -0
  201. package/dist/live-sanitize.js +196 -0
  202. package/dist/live-sanitize.js.map +1 -0
  203. package/dist/live-types.d.cts +71 -0
  204. package/dist/live-types.d.cts.map +1 -0
  205. package/dist/live-types.d.ts +71 -0
  206. package/dist/live-types.d.ts.map +1 -0
  207. package/dist/llmock.cjs +52 -41
  208. package/dist/llmock.cjs.map +1 -1
  209. package/dist/llmock.d.cts +16 -5
  210. package/dist/llmock.d.cts.map +1 -1
  211. package/dist/llmock.d.ts +16 -5
  212. package/dist/llmock.d.ts.map +1 -1
  213. package/dist/llmock.js +53 -42
  214. package/dist/llmock.js.map +1 -1
  215. package/dist/messages.cjs +11 -7
  216. package/dist/messages.cjs.map +1 -1
  217. package/dist/messages.d.cts.map +1 -1
  218. package/dist/messages.d.ts.map +1 -1
  219. package/dist/messages.js +13 -9
  220. package/dist/messages.js.map +1 -1
  221. package/dist/metrics.cjs +258 -5
  222. package/dist/metrics.cjs.map +1 -1
  223. package/dist/metrics.d.cts +3 -1
  224. package/dist/metrics.d.cts.map +1 -1
  225. package/dist/metrics.d.ts +3 -1
  226. package/dist/metrics.d.ts.map +1 -1
  227. package/dist/metrics.js +255 -6
  228. package/dist/metrics.js.map +1 -1
  229. package/dist/moderation.cjs +10 -1
  230. package/dist/moderation.cjs.map +1 -1
  231. package/dist/moderation.d.cts.map +1 -1
  232. package/dist/moderation.d.ts.map +1 -1
  233. package/dist/moderation.js +10 -1
  234. package/dist/moderation.js.map +1 -1
  235. package/dist/ollama.cjs +5 -5
  236. package/dist/ollama.cjs.map +1 -1
  237. package/dist/ollama.js +6 -6
  238. package/dist/ollama.js.map +1 -1
  239. package/dist/openrouter-video.cjs +5 -5
  240. package/dist/openrouter-video.cjs.map +1 -1
  241. package/dist/openrouter-video.js +6 -6
  242. package/dist/openrouter-video.js.map +1 -1
  243. package/dist/recorder.cjs +45 -9
  244. package/dist/recorder.cjs.map +1 -1
  245. package/dist/recorder.d.cts.map +1 -1
  246. package/dist/recorder.d.ts.map +1 -1
  247. package/dist/recorder.js +43 -11
  248. package/dist/recorder.js.map +1 -1
  249. package/dist/responses.cjs +4 -3
  250. package/dist/responses.cjs.map +1 -1
  251. package/dist/responses.js +5 -5
  252. package/dist/responses.js.map +1 -1
  253. package/dist/router.cjs +20 -33
  254. package/dist/router.cjs.map +1 -1
  255. package/dist/router.d.cts +4 -1
  256. package/dist/router.d.cts.map +1 -1
  257. package/dist/router.d.ts +4 -1
  258. package/dist/router.d.ts.map +1 -1
  259. package/dist/router.js +20 -34
  260. package/dist/router.js.map +1 -1
  261. package/dist/server.cjs +701 -625
  262. package/dist/server.cjs.map +1 -1
  263. package/dist/server.d.cts +1 -0
  264. package/dist/server.d.cts.map +1 -1
  265. package/dist/server.d.ts +1 -0
  266. package/dist/server.d.ts.map +1 -1
  267. package/dist/server.js +704 -628
  268. package/dist/server.js.map +1 -1
  269. package/dist/speech.cjs +2 -2
  270. package/dist/speech.cjs.map +1 -1
  271. package/dist/speech.js +3 -3
  272. package/dist/speech.js.map +1 -1
  273. package/dist/transcription.cjs +4 -4
  274. package/dist/transcription.cjs.map +1 -1
  275. package/dist/transcription.js +5 -5
  276. package/dist/transcription.js.map +1 -1
  277. package/dist/types.d.cts +119 -16
  278. package/dist/types.d.cts.map +1 -1
  279. package/dist/types.d.ts +119 -16
  280. package/dist/types.d.ts.map +1 -1
  281. package/dist/validate-cli.cjs +925 -0
  282. package/dist/validate-cli.cjs.map +1 -0
  283. package/dist/validate-cli.js +924 -0
  284. package/dist/validate-cli.js.map +1 -0
  285. package/dist/vector-types.d.cts.map +1 -1
  286. package/dist/vector-types.d.ts.map +1 -1
  287. package/dist/veo-video.cjs +13 -13
  288. package/dist/veo-video.cjs.map +1 -1
  289. package/dist/veo-video.js +14 -14
  290. package/dist/veo-video.js.map +1 -1
  291. package/dist/video.cjs +4 -4
  292. package/dist/video.cjs.map +1 -1
  293. package/dist/video.d.cts +1 -1
  294. package/dist/video.d.cts.map +1 -1
  295. package/dist/video.d.ts +1 -1
  296. package/dist/video.d.ts.map +1 -1
  297. package/dist/video.js +5 -5
  298. package/dist/video.js.map +1 -1
  299. package/dist/vitest.cjs +13 -6
  300. package/dist/vitest.cjs.map +1 -1
  301. package/dist/vitest.d.cts +2 -1
  302. package/dist/vitest.d.cts.map +1 -1
  303. package/dist/vitest.d.ts +2 -1
  304. package/dist/vitest.d.ts.map +1 -1
  305. package/dist/vitest.js +14 -7
  306. package/dist/vitest.js.map +1 -1
  307. package/dist/watcher.cjs +2 -1
  308. package/dist/watcher.cjs.map +1 -1
  309. package/dist/watcher.js +2 -1
  310. package/dist/watcher.js.map +1 -1
  311. package/dist/ws-framing.cjs +150 -35
  312. package/dist/ws-framing.cjs.map +1 -1
  313. package/dist/ws-framing.d.cts +18 -3
  314. package/dist/ws-framing.d.cts.map +1 -1
  315. package/dist/ws-framing.d.ts +18 -3
  316. package/dist/ws-framing.d.ts.map +1 -1
  317. package/dist/ws-framing.js +151 -36
  318. package/dist/ws-framing.js.map +1 -1
  319. package/dist/ws-gemini-live.cjs +2 -2
  320. package/dist/ws-gemini-live.d.ts +2 -2
  321. package/dist/ws-gemini-live.js +2 -2
  322. package/dist/ws-live.cjs +401 -0
  323. package/dist/ws-live.cjs.map +1 -0
  324. package/dist/ws-live.js +400 -0
  325. package/dist/ws-live.js.map +1 -0
  326. package/dist/ws-realtime.cjs +46 -2
  327. package/dist/ws-realtime.cjs.map +1 -1
  328. package/dist/ws-realtime.d.cts.map +1 -1
  329. package/dist/ws-realtime.d.ts +2 -2
  330. package/dist/ws-realtime.d.ts.map +1 -1
  331. package/dist/ws-realtime.js +46 -2
  332. package/dist/ws-realtime.js.map +1 -1
  333. package/dist/ws-responses.cjs +2 -2
  334. package/dist/ws-responses.js +2 -2
  335. package/dist/ws-upstream.cjs +118 -0
  336. package/dist/ws-upstream.cjs.map +1 -0
  337. package/dist/ws-upstream.js +115 -0
  338. package/dist/ws-upstream.js.map +1 -0
  339. package/fixtures/openai-live-client.json +390 -0
  340. package/fixtures/openai-live-managed.json +709 -0
  341. package/package.json +1 -1
@@ -0,0 +1,254 @@
1
+ import { DEFAULT_TEST_ID } from "./constants.js";
2
+ import { bindingSites, normalizeLiveOptions, validateLiveClientEvent, validateLiveTranscript } from "./live-fixture.js";
3
+ import { DEFAULT_MAX_PROXY_BUFFER_BYTES, DEFAULT_MAX_PROXY_BUFFER_FRAMES, PROXY_BUFFER_HARD_CEILING, persistFixture } from "./recorder.js";
4
+ import { Logger } from "./logger.js";
5
+ import { sanitizeLiveTranscript } from "./live-sanitize.js";
6
+ import { performance } from "node:perf_hooks";
7
+
8
+ //#region src/live-recorder.ts
9
+ var LiveRecorderError = class extends Error {
10
+ constructor(category) {
11
+ super(`Live capture failed: ${category}`);
12
+ this.category = category;
13
+ }
14
+ };
15
+ function object(value) {
16
+ return value !== null && typeof value === "object" && !Array.isArray(value);
17
+ }
18
+ function boundedJson(value, depth = 0) {
19
+ if (depth > 32) return false;
20
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
21
+ if (typeof value === "number") return Number.isFinite(value);
22
+ if (Array.isArray(value)) return value.every((v) => boundedJson(v, depth + 1));
23
+ return typeof value === "object" && Object.entries(value).every(([key, v]) => ![
24
+ "__proto__",
25
+ "prototype",
26
+ "constructor"
27
+ ].includes(key) && boundedJson(v, depth + 1));
28
+ }
29
+ function field(event, pointer) {
30
+ let value = event;
31
+ for (const part of pointer.slice(1).split("/")) {
32
+ const key = part.replace(/~1/g, "/").replace(/~0/g, "~");
33
+ if (!object(value)) throw new LiveRecorderError("protocol-divergence");
34
+ value = value[key];
35
+ }
36
+ if (typeof value !== "string") throw new LiveRecorderError("protocol-divergence");
37
+ return value;
38
+ }
39
+ function limit(value, fallback) {
40
+ const result = value ?? fallback;
41
+ if (!Number.isSafeInteger(result) || result <= 0) throw new LiveRecorderError("resource-limit");
42
+ return result;
43
+ }
44
+ /** Capture at arrival/acceptance, before awaiting any downstream socket write.
45
+ * Inputs are complete wire JSON strings, so byte caps apply before parse/copy.
46
+ * The socket owner owns deadlines, mode/lifecycle forwarding checks and cleanup;
47
+ * abort on any failed send or premature socket close before calling finish.
48
+ */
49
+ function createLiveRecorder(options) {
50
+ const limits = normalizeLiveOptions(options.live);
51
+ const maxBytes = Math.min(limit(options.record.maxProxyBufferBytes, DEFAULT_MAX_PROXY_BUFFER_BYTES), PROXY_BUFFER_HARD_CEILING);
52
+ const maxFrames = limit(options.record.maxProxyBufferFrames, DEFAULT_MAX_PROXY_BUFFER_FRAMES);
53
+ const started = performance.now();
54
+ const entries = [];
55
+ const bindings = [];
56
+ const identities = /* @__PURE__ */ new Map();
57
+ let configuration;
58
+ let bytes = 0, frames = 0, lastTime = 0, command = 0, audioBytes = 0;
59
+ const audioTotals = {
60
+ client: 0,
61
+ server: 0
62
+ };
63
+ let terminal = false;
64
+ let failure;
65
+ let result;
66
+ function clear() {
67
+ entries.length = 0;
68
+ bindings.length = 0;
69
+ identities.clear();
70
+ configuration = void 0;
71
+ }
72
+ function abort(category = "incomplete-capture") {
73
+ if (result) return;
74
+ failure ??= category;
75
+ clear();
76
+ }
77
+ function fail(category) {
78
+ abort(category);
79
+ throw new LiveRecorderError(failure ?? category);
80
+ }
81
+ function annotate(event, direction) {
82
+ const sites = [...bindingSites(event, direction)].sort((a, b) => Number(!a[1].define) - Number(!b[1].define) || Number(!a[1].imported) - Number(!b[1].imported));
83
+ for (const [pointer, site] of sites) {
84
+ const value = field(event, pointer);
85
+ const key = JSON.stringify([site.namespace, value]);
86
+ const known = identities.get(key);
87
+ if (known && known.owner !== site.owner) fail("protocol-divergence");
88
+ if (!known && !site.define && !site.imported) fail("protocol-divergence");
89
+ const name = known?.name ?? `${site.namespace}:${identities.size}`;
90
+ if (!known) identities.set(key, {
91
+ name,
92
+ owner: site.owner
93
+ });
94
+ if (!options.record.proxyOnly) bindings.push({
95
+ entry: entries.length,
96
+ pointer,
97
+ name,
98
+ owner: site.owner,
99
+ action: known ? "reference" : "define",
100
+ ...!known && !site.define && site.imported ? { origin: "imported-context" } : {}
101
+ });
102
+ }
103
+ }
104
+ function accept(direction, raw, atMs = Math.floor(performance.now() - started)) {
105
+ if (result) throw new LiveRecorderError("incomplete-capture");
106
+ if (failure) throw new LiveRecorderError(failure);
107
+ if (terminal) fail("protocol-divergence");
108
+ if (!Number.isSafeInteger(atMs) || atMs < lastTime) fail("protocol-divergence");
109
+ if (atMs > limits.maxDurationMs) fail("timeout");
110
+ const length = Buffer.byteLength(raw);
111
+ if (length > limits.maxMessageBytes || length > maxBytes - bytes || frames >= maxFrames) fail("resource-limit");
112
+ let value;
113
+ try {
114
+ value = JSON.parse(raw);
115
+ } catch {
116
+ fail(direction === "client" ? "invalid-client" : "protocol-divergence");
117
+ }
118
+ if (!boundedJson(value) || !object(value) || typeof value.type !== "string") fail(direction === "client" ? "invalid-client" : "protocol-divergence");
119
+ const event = value;
120
+ if (frames === 0 && (direction !== "client" || event.type !== "session.start")) fail("invalid-client");
121
+ if (event.type === "session.start") {
122
+ if (frames !== 0 || direction !== "client" || !object(event.session)) fail("invalid-client");
123
+ configuration = event.session;
124
+ }
125
+ if (event.type === "error") fail("protocol-divergence");
126
+ if (event.type === "session.closed") {
127
+ if (direction !== "server" || !["close_requested", "remote_hangup"].includes(String(event.reason))) fail("incomplete-capture");
128
+ terminal = true;
129
+ }
130
+ let decoded = 0;
131
+ if (direction === "client" && event.type === "session.input_audio.append" || direction === "server" && event.type === "session.output_audio.delta") {
132
+ const pcm = event[direction === "client" ? "audio" : "delta"];
133
+ if (typeof pcm !== "string" || pcm.length % 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(pcm)) fail(direction === "client" ? "invalid-client" : "protocol-divergence");
134
+ decoded = pcm.length / 4 * 3 - (pcm.endsWith("==") ? 2 : pcm.endsWith("=") ? 1 : 0);
135
+ if (decoded % 2) fail(direction === "client" ? "invalid-client" : "protocol-divergence");
136
+ if (decoded > limits.maxDecodedAudioBytes - audioTotals[direction]) fail("resource-limit");
137
+ if (Buffer.from(pcm, "base64").toString("base64") !== pcm) fail("protocol-divergence");
138
+ audioTotals[direction] += decoded;
139
+ }
140
+ if (direction === "client") try {
141
+ validateLiveClientEvent(event, limits);
142
+ } catch {
143
+ fail("invalid-client");
144
+ }
145
+ if (direction === "client") if (event.type === "session.input_audio.append") audioBytes += decoded;
146
+ else {
147
+ command++;
148
+ audioBytes = 0;
149
+ }
150
+ bytes += length;
151
+ frames++;
152
+ lastTime = atMs;
153
+ try {
154
+ annotate(event, direction);
155
+ } catch {
156
+ fail("protocol-divergence");
157
+ }
158
+ if (!options.record.proxyOnly) entries.push({
159
+ direction,
160
+ atMs,
161
+ event,
162
+ ...direction === "server" ? { barrier: {
163
+ command,
164
+ audioBytes
165
+ } } : {}
166
+ });
167
+ }
168
+ function finish() {
169
+ if (result) return result;
170
+ if (!terminal && !failure) abort("incomplete-capture");
171
+ if (failure) return result = {
172
+ kind: "failed",
173
+ category: failure,
174
+ error: `Live capture failed: ${failure}`
175
+ };
176
+ if (options.record.proxyOnly) {
177
+ clear();
178
+ return result = { kind: "skipped" };
179
+ }
180
+ if (!configuration || !object(configuration.delegation)) {
181
+ abort();
182
+ return finish();
183
+ }
184
+ const mode = configuration.delegation.type === "client" ? "client" : "managed";
185
+ let transcript;
186
+ try {
187
+ transcript = validateLiveTranscript({
188
+ version: 1,
189
+ model: "gpt-live-1",
190
+ mode,
191
+ audio: {
192
+ encoding: "pcm16le",
193
+ sampleRateHz: 24e3,
194
+ channels: 1
195
+ },
196
+ configuration,
197
+ entries,
198
+ bindings,
199
+ capture: {
200
+ source: "provider",
201
+ complete: true,
202
+ terminalEntry: entries.length - 1,
203
+ ...options.provenance ? { provenance: options.provenance } : {}
204
+ }
205
+ }, limits);
206
+ } catch {
207
+ abort("protocol-divergence");
208
+ return finish();
209
+ }
210
+ try {
211
+ transcript = sanitizeLiveTranscript(transcript, [...limits.secretValues, ...options.secretValues ?? []], options.credentialFields, limits);
212
+ } catch {
213
+ abort("unsafe-export");
214
+ return finish();
215
+ }
216
+ const fixture = {
217
+ match: {
218
+ endpoint: "openai-live",
219
+ model: "gpt-live-1"
220
+ },
221
+ response: { live: transcript }
222
+ };
223
+ const persisted = persistFixture({
224
+ record: options.record,
225
+ providerKey: "openai",
226
+ filePrefix: "openai-live",
227
+ testId: options.testId ?? DEFAULT_TEST_ID,
228
+ fixture,
229
+ fixtures: options.fixtures,
230
+ logger: new Logger("silent")
231
+ });
232
+ clear();
233
+ if (persisted.kind === "failed") {
234
+ options.logger.warn("Live capture persistence failed");
235
+ return result = {
236
+ kind: "failed",
237
+ category: "incomplete-capture",
238
+ error: "Live capture persistence failed"
239
+ };
240
+ }
241
+ options.logger.info("Complete Live capture recorded");
242
+ return result = persisted;
243
+ }
244
+ return {
245
+ acceptClient: (raw, atMs) => accept("client", raw, atMs),
246
+ acceptServer: (raw, atMs) => accept("server", raw, atMs),
247
+ finish,
248
+ abort
249
+ };
250
+ }
251
+
252
+ //#endregion
253
+ export { LiveRecorderError, createLiveRecorder };
254
+ //# sourceMappingURL=live-recorder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-recorder.js","names":[],"sources":["../src/live-recorder.ts"],"sourcesContent":["import { performance } from \"node:perf_hooks\";\nimport {\n bindingSites,\n normalizeLiveOptions,\n validateLiveClientEvent,\n validateLiveTranscript,\n} from \"./live-fixture.js\";\nimport { sanitizeLiveTranscript, type LiveCredentialField } from \"./live-sanitize.js\";\nimport type {\n LiveBinding,\n LiveEntry,\n LiveFailureCategory,\n LiveJson,\n LiveObject,\n LiveOptions,\n LiveTranscript,\n} from \"./live-types.js\";\nimport type { Fixture, RecordConfig } from \"./types.js\";\nimport { Logger } from \"./logger.js\";\nimport { DEFAULT_TEST_ID } from \"./constants.js\";\nimport {\n DEFAULT_MAX_PROXY_BUFFER_BYTES,\n DEFAULT_MAX_PROXY_BUFFER_FRAMES,\n PROXY_BUFFER_HARD_CEILING,\n persistFixture,\n type PersistFixtureResult,\n} from \"./recorder.js\";\n\nexport interface LiveRecorderOptions {\n record: RecordConfig;\n fixtures: Fixture[];\n logger: Logger;\n testId?: string;\n live?: LiveOptions;\n /** Include the actual selected upstream credential as well as configured Live secrets. */\n secretValues?: readonly string[];\n credentialFields?: readonly LiveCredentialField[];\n provenance?: NonNullable<LiveTranscript[\"capture\"][\"provenance\"]>;\n}\nexport type LiveRecorderResult =\n | Exclude<PersistFixtureResult, { kind: \"failed\" }>\n | {\n kind: \"failed\";\n category: LiveFailureCategory;\n error: string;\n };\nexport class LiveRecorderError extends Error {\n constructor(readonly category: LiveFailureCategory) {\n super(`Live capture failed: ${category}`);\n }\n}\nfunction object(value: LiveJson | undefined): value is LiveObject {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\nfunction boundedJson(value: unknown, depth = 0): value is LiveJson {\n if (depth > 32) return false;\n if (value === null || typeof value === \"string\" || typeof value === \"boolean\") return true;\n if (typeof value === \"number\") return Number.isFinite(value);\n if (Array.isArray(value)) return value.every((v) => boundedJson(v, depth + 1));\n return (\n typeof value === \"object\" &&\n Object.entries(value).every(\n ([key, v]) =>\n ![\"__proto__\", \"prototype\", \"constructor\"].includes(key) && boundedJson(v, depth + 1),\n )\n );\n}\nfunction field(event: LiveObject, pointer: string): string {\n let value: LiveJson = event;\n for (const part of pointer.slice(1).split(\"/\")) {\n const key = part.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n if (!object(value)) throw new LiveRecorderError(\"protocol-divergence\");\n value = value[key];\n }\n if (typeof value !== \"string\") throw new LiveRecorderError(\"protocol-divergence\");\n return value;\n}\nfunction limit(value: number | undefined, fallback: number): number {\n const result = value ?? fallback;\n if (!Number.isSafeInteger(result) || result <= 0) throw new LiveRecorderError(\"resource-limit\");\n return result;\n}\n\n/** Capture at arrival/acceptance, before awaiting any downstream socket write.\n * Inputs are complete wire JSON strings, so byte caps apply before parse/copy.\n * The socket owner owns deadlines, mode/lifecycle forwarding checks and cleanup;\n * abort on any failed send or premature socket close before calling finish.\n */\nexport function createLiveRecorder(options: LiveRecorderOptions) {\n const limits = normalizeLiveOptions(options.live);\n const maxBytes = Math.min(\n limit(options.record.maxProxyBufferBytes, DEFAULT_MAX_PROXY_BUFFER_BYTES),\n PROXY_BUFFER_HARD_CEILING,\n );\n const maxFrames = limit(options.record.maxProxyBufferFrames, DEFAULT_MAX_PROXY_BUFFER_FRAMES);\n const started = performance.now();\n const entries: LiveEntry[] = [];\n const bindings: LiveBinding[] = [];\n const identities = new Map<string, { name: string; owner: \"client\" | \"server\" }>();\n let configuration: LiveObject | undefined;\n let bytes = 0,\n frames = 0,\n lastTime = 0,\n command = 0,\n audioBytes = 0;\n const audioTotals = { client: 0, server: 0 };\n let terminal = false;\n let failure: LiveFailureCategory | undefined;\n let result: LiveRecorderResult | undefined;\n function clear() {\n entries.length = 0;\n bindings.length = 0;\n identities.clear();\n configuration = undefined;\n }\n function abort(category: LiveFailureCategory = \"incomplete-capture\") {\n if (result) return;\n failure ??= category;\n clear();\n }\n function fail(category: LiveFailureCategory): never {\n abort(category);\n throw new LiveRecorderError(failure ?? category);\n }\n function annotate(event: LiveObject, direction: \"client\" | \"server\") {\n const sites = [...bindingSites(event, direction)].sort(\n (a, b) =>\n Number(!a[1].define) - Number(!b[1].define) ||\n Number(!a[1].imported) - Number(!b[1].imported),\n );\n for (const [pointer, site] of sites) {\n const value = field(event, pointer);\n const key = JSON.stringify([site.namespace, value]);\n const known = identities.get(key);\n if (known && known.owner !== site.owner) fail(\"protocol-divergence\");\n if (!known && !site.define && !site.imported) fail(\"protocol-divergence\");\n const name = known?.name ?? `${site.namespace}:${identities.size}`;\n if (!known) identities.set(key, { name, owner: site.owner });\n if (!options.record.proxyOnly)\n bindings.push({\n entry: entries.length,\n pointer,\n name,\n owner: site.owner,\n action: known ? \"reference\" : \"define\",\n ...(!known && !site.define && site.imported\n ? { origin: \"imported-context\" as const }\n : {}),\n });\n }\n }\n function accept(\n direction: \"client\" | \"server\",\n raw: string,\n atMs = Math.floor(performance.now() - started),\n ) {\n if (result) throw new LiveRecorderError(\"incomplete-capture\");\n if (failure) throw new LiveRecorderError(failure);\n if (terminal) fail(\"protocol-divergence\");\n if (!Number.isSafeInteger(atMs) || atMs < lastTime) fail(\"protocol-divergence\");\n if (atMs > limits.maxDurationMs) fail(\"timeout\");\n const length = Buffer.byteLength(raw);\n if (length > limits.maxMessageBytes || length > maxBytes - bytes || frames >= maxFrames)\n fail(\"resource-limit\");\n let value: unknown;\n try {\n value = JSON.parse(raw);\n } catch {\n fail(direction === \"client\" ? \"invalid-client\" : \"protocol-divergence\");\n }\n if (!boundedJson(value) || !object(value) || typeof value.type !== \"string\")\n fail(direction === \"client\" ? \"invalid-client\" : \"protocol-divergence\");\n const event = value;\n if (frames === 0 && (direction !== \"client\" || event.type !== \"session.start\"))\n fail(\"invalid-client\");\n if (event.type === \"session.start\") {\n if (frames !== 0 || direction !== \"client\" || !object(event.session)) fail(\"invalid-client\");\n configuration = event.session;\n }\n if (event.type === \"error\") fail(\"protocol-divergence\");\n if (event.type === \"session.closed\") {\n if (\n direction !== \"server\" ||\n ![\"close_requested\", \"remote_hangup\"].includes(String(event.reason))\n )\n fail(\"incomplete-capture\");\n terminal = true;\n }\n let decoded = 0;\n if (\n (direction === \"client\" && event.type === \"session.input_audio.append\") ||\n (direction === \"server\" && event.type === \"session.output_audio.delta\")\n ) {\n const pcm = event[direction === \"client\" ? \"audio\" : \"delta\"];\n if (\n typeof pcm !== \"string\" ||\n pcm.length % 4 ||\n !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(pcm)\n )\n fail(direction === \"client\" ? \"invalid-client\" : \"protocol-divergence\");\n decoded = (pcm.length / 4) * 3 - (pcm.endsWith(\"==\") ? 2 : pcm.endsWith(\"=\") ? 1 : 0);\n if (decoded % 2) fail(direction === \"client\" ? \"invalid-client\" : \"protocol-divergence\");\n if (decoded > limits.maxDecodedAudioBytes - audioTotals[direction]) fail(\"resource-limit\");\n if (Buffer.from(pcm, \"base64\").toString(\"base64\") !== pcm) fail(\"protocol-divergence\");\n audioTotals[direction] += decoded;\n }\n if (direction === \"client\") {\n try {\n validateLiveClientEvent(event, limits);\n } catch {\n fail(\"invalid-client\");\n }\n }\n if (direction === \"client\") {\n if (event.type === \"session.input_audio.append\") audioBytes += decoded;\n else {\n command++;\n audioBytes = 0;\n }\n }\n bytes += length;\n frames++;\n lastTime = atMs;\n try {\n annotate(event, direction);\n } catch {\n fail(\"protocol-divergence\");\n }\n if (!options.record.proxyOnly) {\n entries.push({\n direction,\n atMs,\n event,\n ...(direction === \"server\" ? { barrier: { command, audioBytes } } : {}),\n });\n }\n }\n function finish(): LiveRecorderResult {\n if (result) return result;\n if (!terminal && !failure) abort(\"incomplete-capture\");\n if (failure)\n return (result = {\n kind: \"failed\",\n category: failure,\n error: `Live capture failed: ${failure}`,\n });\n if (options.record.proxyOnly) {\n clear();\n return (result = { kind: \"skipped\" });\n }\n if (!configuration || !object(configuration.delegation)) {\n abort();\n return finish();\n }\n const mode = configuration.delegation.type === \"client\" ? \"client\" : \"managed\";\n let transcript: LiveTranscript;\n try {\n transcript = validateLiveTranscript(\n {\n version: 1,\n model: \"gpt-live-1\",\n mode,\n audio: { encoding: \"pcm16le\", sampleRateHz: 24000, channels: 1 },\n configuration,\n entries,\n bindings,\n capture: {\n source: \"provider\",\n complete: true,\n terminalEntry: entries.length - 1,\n ...(options.provenance ? { provenance: options.provenance } : {}),\n },\n },\n limits,\n );\n } catch {\n abort(\"protocol-divergence\");\n return finish();\n }\n try {\n transcript = sanitizeLiveTranscript(\n transcript,\n [...limits.secretValues, ...(options.secretValues ?? [])],\n options.credentialFields,\n limits,\n );\n } catch {\n abort(\"unsafe-export\");\n return finish();\n }\n const fixture: Fixture = {\n match: { endpoint: \"openai-live\", model: \"gpt-live-1\" },\n response: { live: transcript },\n };\n // Existing persistence diagnostics include configured paths. Do not expose those\n // through Live's safe journal/error surface; report only a category here.\n const persisted = persistFixture({\n record: options.record,\n providerKey: \"openai\",\n filePrefix: \"openai-live\",\n testId: options.testId ?? DEFAULT_TEST_ID,\n fixture,\n fixtures: options.fixtures,\n logger: new Logger(\"silent\"),\n });\n clear();\n if (persisted.kind === \"failed\") {\n options.logger.warn(\"Live capture persistence failed\");\n return (result = {\n kind: \"failed\",\n category: \"incomplete-capture\",\n error: \"Live capture persistence failed\",\n });\n }\n options.logger.info(\"Complete Live capture recorded\");\n return (result = persisted);\n }\n return {\n acceptClient: (raw: string, atMs?: number) => accept(\"client\", raw, atMs),\n acceptServer: (raw: string, atMs?: number) => accept(\"server\", raw, atMs),\n finish,\n abort,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,286 @@
1
+ const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
2
+ let node_crypto = require("node:crypto");
3
+
4
+ //#region src/live-replay.ts
5
+ function boundedJson(root, limit) {
6
+ let budget = limit;
7
+ function visit(value, depth) {
8
+ if (depth > 32 || --budget < 0) return false;
9
+ if (typeof value === "string") {
10
+ budget -= Buffer.byteLength(value);
11
+ return budget >= 0;
12
+ }
13
+ if (value === null || typeof value === "boolean") return true;
14
+ if (typeof value === "number") return Number.isFinite(value);
15
+ if (typeof value !== "object") return false;
16
+ const prototype = Object.getPrototypeOf(value);
17
+ if (Array.isArray(value) ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) return false;
18
+ for (const key of Object.keys(value)) {
19
+ if ([
20
+ "__proto__",
21
+ "constructor",
22
+ "prototype"
23
+ ].includes(key)) return false;
24
+ budget -= Buffer.byteLength(key);
25
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
26
+ if (!descriptor || !("value" in descriptor) || !visit(descriptor.value, depth + 1)) return false;
27
+ }
28
+ return budget >= 0;
29
+ }
30
+ return visit(root, 0);
31
+ }
32
+ function canonical(value) {
33
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
34
+ if (value !== null && typeof value === "object") return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
35
+ return JSON.stringify(value);
36
+ }
37
+ function field(event, pointer) {
38
+ const parts = pointer.slice(1).split("/").map((p) => p.replace(/~1/g, "/").replace(/~0/g, "~"));
39
+ const key = parts.pop();
40
+ let parent = event;
41
+ for (const part of parts) {
42
+ if (parent === null || typeof parent !== "object") throw new Error("missing identifier");
43
+ parent = Array.isArray(parent) ? parent[Number(part)] : parent[part];
44
+ }
45
+ if (parent === null || typeof parent !== "object" || !Object.hasOwn(parent, key)) throw new Error("missing identifier");
46
+ return {
47
+ parent,
48
+ key
49
+ };
50
+ }
51
+ function read(event, pointer) {
52
+ const { parent, key } = field(event, pointer);
53
+ return Array.isArray(parent) ? parent[Number(key)] : parent[key];
54
+ }
55
+ function replace(event, pointer, value) {
56
+ const { parent, key } = field(event, pointer);
57
+ if (Array.isArray(parent)) parent[Number(key)] = value;
58
+ else parent[key] = value;
59
+ }
60
+ function namespace(binding) {
61
+ const p = binding.pointer;
62
+ if (p.endsWith("client_event_id")) return "client-event";
63
+ if (p === "/event_id") return `${binding.owner}-event`;
64
+ if (p === "/session/id") return "session";
65
+ if (p === "/delegation/id" || p === "/delegation_id") return "delegation";
66
+ if (p.endsWith("/call_id")) return "tool-call";
67
+ if (p === "/event/item/id" || p === "/event/item_id") return "response-item";
68
+ return "response";
69
+ }
70
+ function decode(value, remaining) {
71
+ if (typeof value !== "string" || value.length % 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) throw new Error("invalid audio");
72
+ const size = value.length / 4 * 3 - (value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0);
73
+ if (size % 2 || size > remaining) throw new Error("invalid audio size");
74
+ const bytes = Buffer.from(value, "base64");
75
+ if (bytes.toString("base64") !== value) throw new Error("invalid audio encoding");
76
+ return bytes;
77
+ }
78
+ /** Accepts synchronously; the single output pump alone waits for transport pressure.
79
+ * The caller supplies a validated, immutable transcript and owns cancellation of send(). */
80
+ function createLiveReplay(transcript, options) {
81
+ if (!Number.isFinite(options.replaySpeed) || options.replaySpeed <= 0) throw new Error("replaySpeed must be positive");
82
+ const { limits } = options;
83
+ const annotations = /* @__PURE__ */ new Map();
84
+ for (const binding of transcript.bindings) {
85
+ const bucket = annotations.get(binding.entry) ?? [];
86
+ bucket.push(binding);
87
+ annotations.set(binding.entry, bucket);
88
+ }
89
+ for (const bucket of annotations.values()) bucket.sort((a, b) => Number(a.action === "reference") - Number(b.action === "reference"));
90
+ const tape = [];
91
+ const servers = [];
92
+ const audioChunks = /* @__PURE__ */ new Map();
93
+ let fixtureBytes = 0;
94
+ for (const [index, entry] of transcript.entries.entries()) {
95
+ if (entry.direction === "server") {
96
+ servers.push(index);
97
+ continue;
98
+ }
99
+ if (entry.event.type !== "session.input_audio.append") {
100
+ tape.push({
101
+ kind: "control",
102
+ index
103
+ });
104
+ continue;
105
+ }
106
+ const bytes = decode(entry.event.audio, limits.maxDecodedAudioBytes - fixtureBytes);
107
+ fixtureBytes += bytes.length;
108
+ const metadata = { ...entry.event };
109
+ delete metadata.audio;
110
+ const previous = tape.at(-1);
111
+ if (annotations.has(index)) {
112
+ tape.push({
113
+ kind: "control",
114
+ index
115
+ });
116
+ continue;
117
+ }
118
+ if (previous?.kind === "audio" && canonical(previous.metadata) === canonical(metadata)) audioChunks.get(previous).push(bytes);
119
+ else {
120
+ const run = {
121
+ kind: "audio",
122
+ bytes,
123
+ metadata
124
+ };
125
+ tape.push(run);
126
+ audioChunks.set(run, [bytes]);
127
+ }
128
+ }
129
+ for (const [run, chunks] of audioChunks) run.bytes = Buffer.concat(chunks);
130
+ audioChunks.clear();
131
+ const values = /* @__PURE__ */ new Map();
132
+ const reverse = /* @__PURE__ */ new Map();
133
+ let cursor = 0, offset = 0, command = 0, audioBytes = 0, totalAudio = 0, next = 0;
134
+ let stopped = false, sending = false, start;
135
+ let timer;
136
+ const created = Date.now();
137
+ let lastActivity = created, missingSince = created;
138
+ function close() {
139
+ if (stopped) return;
140
+ stopped = true;
141
+ if (timer !== void 0) clearTimeout(timer);
142
+ timer = void 0;
143
+ options.signal.removeEventListener("abort", close);
144
+ }
145
+ function fail(category, detail) {
146
+ if (!stopped) {
147
+ close();
148
+ options.fail(category, detail);
149
+ }
150
+ }
151
+ function bind(binding, value) {
152
+ const existing = values.get(binding.name);
153
+ if (binding.action === "reference") {
154
+ if (existing !== value) throw new Error("identifier reference mismatch");
155
+ } else {
156
+ const key = JSON.stringify([namespace(binding), value]);
157
+ if (existing !== void 0 || reverse.has(key)) throw new Error("identifier bijection mismatch");
158
+ values.set(binding.name, value);
159
+ reverse.set(key, binding.name);
160
+ }
161
+ }
162
+ function eligible() {
163
+ const barrier = transcript.entries[servers[next]]?.barrier;
164
+ return barrier !== void 0 && (command > barrier.command || command === barrier.command && audioBytes >= barrier.audioBytes);
165
+ }
166
+ function due() {
167
+ return options.timing === "immediate" ? 0 : transcript.entries[servers[next]].atMs / options.replaySpeed;
168
+ }
169
+ function schedule() {
170
+ if (stopped) return;
171
+ if (timer !== void 0) clearTimeout(timer);
172
+ const now = Date.now();
173
+ const deadlines = [(start ?? created) + limits.maxDurationMs, lastActivity + limits.idleTimeoutMs];
174
+ if (cursor < tape.length && (next >= servers.length || !eligible())) deadlines.push(missingSince + limits.mismatchTimeoutMs);
175
+ if (!sending && start !== void 0 && next < servers.length && eligible()) deadlines.push(start + due());
176
+ timer = setTimeout(tick, Math.max(0, Math.min(2147483647, Math.min(...deadlines) - now)));
177
+ }
178
+ function tick() {
179
+ timer = void 0;
180
+ if (stopped) return;
181
+ const now = Date.now();
182
+ if (now >= (start ?? created) + limits.maxDurationMs || now >= lastActivity + limits.idleTimeoutMs) {
183
+ fail("timeout", "live replay deadline exceeded");
184
+ return;
185
+ }
186
+ if (cursor < tape.length && (next >= servers.length || !eligible()) && now >= missingSince + limits.mismatchTimeoutMs) {
187
+ fail("fixture-mismatch", "expected client input deadline exceeded");
188
+ return;
189
+ }
190
+ if (!sending && start !== void 0 && next < servers.length && eligible() && now >= start + due()) pump();
191
+ schedule();
192
+ }
193
+ async function pump() {
194
+ sending = true;
195
+ try {
196
+ while (!stopped && start !== void 0 && next < servers.length && eligible() && Date.now() >= start + due()) {
197
+ const index = servers[next];
198
+ const event = structuredClone(transcript.entries[index].event);
199
+ for (const binding of annotations.get(index) ?? []) {
200
+ if (binding.action === "define") bind(binding, `aimock-${(0, node_crypto.randomUUID)()}`);
201
+ const value = values.get(binding.name);
202
+ if (value === void 0) throw new Error("unavailable identifier");
203
+ replace(event, binding.pointer, value);
204
+ }
205
+ if (Buffer.byteLength(JSON.stringify(event)) > limits.maxWriteBytes) {
206
+ fail("resource-limit", "live output exceeds write limit");
207
+ break;
208
+ }
209
+ await options.send(event);
210
+ if (stopped) break;
211
+ next++;
212
+ lastActivity = Date.now();
213
+ missingSince = Date.now();
214
+ if (next === servers.length) close();
215
+ }
216
+ } catch {
217
+ fail("protocol-divergence", "live output failed");
218
+ } finally {
219
+ sending = false;
220
+ schedule();
221
+ }
222
+ }
223
+ function accept(event) {
224
+ if (stopped) return;
225
+ try {
226
+ if (!boundedJson(event, limits.maxMessageBytes)) {
227
+ fail("invalid-client", "live input must be bounded JSON");
228
+ return;
229
+ }
230
+ if (Buffer.byteLength(JSON.stringify(event)) > limits.maxMessageBytes) {
231
+ fail("resource-limit", "live input exceeds message limit");
232
+ return;
233
+ }
234
+ let expected = tape[cursor];
235
+ const metadata = { ...event };
236
+ delete metadata.audio;
237
+ while (expected?.kind === "audio" && offset === expected.bytes.length && !(event.audio === "" && canonical(metadata) === canonical(expected.metadata))) {
238
+ expected = tape[++cursor];
239
+ offset = 0;
240
+ }
241
+ if (!expected) throw new Error("unexpected client input");
242
+ if (expected.kind === "audio") {
243
+ const bytes = decode(event.audio, Math.min(limits.maxDecodedAudioBytes - totalAudio, expected.bytes.length - offset));
244
+ if (canonical(metadata) !== canonical(expected.metadata) || !bytes.equals(expected.bytes.subarray(offset, offset + bytes.length))) throw new Error("audio mismatch");
245
+ offset += bytes.length;
246
+ audioBytes += bytes.length;
247
+ totalAudio += bytes.length;
248
+ } else {
249
+ const template = structuredClone(transcript.entries[expected.index].event);
250
+ for (const binding of annotations.get(expected.index) ?? []) {
251
+ const value = read(event, binding.pointer);
252
+ if (typeof value !== "string" || !value.length) throw new Error("missing identifier");
253
+ bind(binding, value);
254
+ replace(template, binding.pointer, value);
255
+ }
256
+ if (canonical(template) !== canonical(event)) throw new Error("command mismatch");
257
+ if (event.type === "session.input_audio.append") {
258
+ const bytes = decode(event.audio, limits.maxDecodedAudioBytes - totalAudio);
259
+ audioBytes += bytes.length;
260
+ totalAudio += bytes.length;
261
+ } else {
262
+ command++;
263
+ audioBytes = 0;
264
+ }
265
+ cursor++;
266
+ }
267
+ start ??= Date.now();
268
+ lastActivity = Date.now();
269
+ missingSince = Date.now();
270
+ schedule();
271
+ } catch {
272
+ fail("fixture-mismatch", "client input does not match live fixture");
273
+ }
274
+ }
275
+ options.signal.addEventListener("abort", close, { once: true });
276
+ if (options.signal.aborted) close();
277
+ else schedule();
278
+ return {
279
+ accept,
280
+ close
281
+ };
282
+ }
283
+
284
+ //#endregion
285
+ exports.createLiveReplay = createLiveReplay;
286
+ //# sourceMappingURL=live-replay.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-replay.cjs","names":[],"sources":["../src/live-replay.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type {\n LiveBinding,\n LiveFailureCategory,\n LiveJson,\n LiveObject,\n LiveOptions,\n LiveTranscript,\n} from \"./live-types.js\";\n\nexport interface LiveReplayOptions {\n send: (event: LiveObject) => Promise<void>;\n fail: (category: LiveFailureCategory, detail: string) => void;\n signal: AbortSignal;\n replaySpeed: number;\n timing: \"recorded\" | \"immediate\";\n limits: Required<LiveOptions>;\n}\n// Inspect descriptors before serialization: in-process callers must not smuggle\n// getters, cycles, prototypes, or unbounded nesting into the receive path.\nfunction boundedJson(root: LiveObject, limit: number): boolean {\n let budget = limit;\n function visit(value: LiveJson, depth: number): boolean {\n if (depth > 32 || --budget < 0) return false;\n if (typeof value === \"string\") {\n budget -= Buffer.byteLength(value);\n return budget >= 0;\n }\n if (value === null || typeof value === \"boolean\") return true;\n if (typeof value === \"number\") return Number.isFinite(value);\n if (typeof value !== \"object\") return false;\n const prototype = Object.getPrototypeOf(value);\n if (\n Array.isArray(value)\n ? prototype !== Array.prototype\n : prototype !== Object.prototype && prototype !== null\n )\n return false;\n for (const key of Object.keys(value)) {\n if ([\"__proto__\", \"constructor\", \"prototype\"].includes(key)) return false;\n budget -= Buffer.byteLength(key);\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n if (!descriptor || !(\"value\" in descriptor) || !visit(descriptor.value, depth + 1))\n return false;\n }\n return budget >= 0;\n }\n return visit(root, 0);\n}\nfunction canonical(value: LiveJson): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(\",\")}]`;\n if (value !== null && typeof value === \"object\")\n return `{${Object.keys(value)\n .sort()\n .map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)\n .join(\",\")}}`;\n return JSON.stringify(value);\n}\nfunction field(\n event: LiveObject,\n pointer: string,\n): { parent: LiveObject | LiveJson[]; key: string } {\n const parts = pointer\n .slice(1)\n .split(\"/\")\n .map((p) => p.replace(/~1/g, \"/\").replace(/~0/g, \"~\"));\n const key = parts.pop()!;\n let parent: LiveJson = event;\n for (const part of parts) {\n if (parent === null || typeof parent !== \"object\") throw new Error(\"missing identifier\");\n parent = Array.isArray(parent) ? parent[Number(part)] : parent[part];\n }\n if (parent === null || typeof parent !== \"object\" || !Object.hasOwn(parent, key))\n throw new Error(\"missing identifier\");\n return { parent, key };\n}\nfunction read(event: LiveObject, pointer: string): LiveJson {\n const { parent, key } = field(event, pointer);\n return Array.isArray(parent) ? parent[Number(key)] : parent[key];\n}\nfunction replace(event: LiveObject, pointer: string, value: string): void {\n const { parent, key } = field(event, pointer);\n if (Array.isArray(parent)) parent[Number(key)] = value;\n else parent[key] = value;\n}\n// Namespace comes from the reviewed operand, never the author's arbitrary binding name.\nfunction namespace(binding: LiveBinding): string {\n const p = binding.pointer;\n if (p.endsWith(\"client_event_id\")) return \"client-event\";\n if (p === \"/event_id\") return `${binding.owner}-event`;\n if (p === \"/session/id\") return \"session\";\n if (p === \"/delegation/id\" || p === \"/delegation_id\") return \"delegation\";\n if (p.endsWith(\"/call_id\")) return \"tool-call\";\n if (p === \"/event/item/id\" || p === \"/event/item_id\") return \"response-item\";\n return \"response\";\n}\nfunction decode(value: LiveJson, remaining: number): Buffer {\n if (\n typeof value !== \"string\" ||\n value.length % 4 ||\n !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)\n )\n throw new Error(\"invalid audio\");\n const size = (value.length / 4) * 3 - (value.endsWith(\"==\") ? 2 : value.endsWith(\"=\") ? 1 : 0);\n if (size % 2 || size > remaining) throw new Error(\"invalid audio size\");\n const bytes = Buffer.from(value, \"base64\");\n if (bytes.toString(\"base64\") !== value) throw new Error(\"invalid audio encoding\");\n return bytes;\n}\ninterface Control {\n kind: \"control\";\n index: number;\n}\ninterface Audio {\n kind: \"audio\";\n bytes: Buffer;\n metadata: LiveObject;\n}\n\n/** Accepts synchronously; the single output pump alone waits for transport pressure.\n * The caller supplies a validated, immutable transcript and owns cancellation of send(). */\nexport function createLiveReplay(\n transcript: LiveTranscript,\n options: LiveReplayOptions,\n): { accept(event: LiveObject): void; close(): void } {\n if (!Number.isFinite(options.replaySpeed) || options.replaySpeed <= 0)\n throw new Error(\"replaySpeed must be positive\");\n const { limits } = options;\n const annotations = new Map<number, LiveBinding[]>();\n for (const binding of transcript.bindings) {\n const bucket = annotations.get(binding.entry) ?? [];\n bucket.push(binding);\n annotations.set(binding.entry, bucket);\n }\n for (const bucket of annotations.values())\n bucket.sort((a, b) => Number(a.action === \"reference\") - Number(b.action === \"reference\"));\n const tape: (Control | Audio)[] = [];\n const servers: number[] = [];\n const audioChunks = new Map<Audio, Buffer[]>();\n let fixtureBytes = 0;\n for (const [index, entry] of transcript.entries.entries()) {\n if (entry.direction === \"server\") {\n servers.push(index);\n continue;\n }\n if (entry.event.type !== \"session.input_audio.append\") {\n tape.push({ kind: \"control\", index });\n continue;\n }\n const bytes = decode(entry.event.audio, limits.maxDecodedAudioBytes - fixtureBytes);\n fixtureBytes += bytes.length;\n const metadata = { ...entry.event };\n delete metadata.audio;\n const previous = tape.at(-1);\n // Annotated audio commands retain their original frame boundary. Plain PCM runs\n // coalesce across intervening server output, never across a client control.\n if (annotations.has(index)) {\n tape.push({ kind: \"control\", index });\n continue;\n }\n if (previous?.kind === \"audio\" && canonical(previous.metadata) === canonical(metadata))\n audioChunks.get(previous)!.push(bytes);\n else {\n const run: Audio = { kind: \"audio\", bytes, metadata };\n tape.push(run);\n audioChunks.set(run, [bytes]);\n }\n }\n for (const [run, chunks] of audioChunks) run.bytes = Buffer.concat(chunks);\n audioChunks.clear();\n const values = new Map<string, string>();\n const reverse = new Map<string, string>();\n let cursor = 0,\n offset = 0,\n command = 0,\n audioBytes = 0,\n totalAudio = 0,\n next = 0;\n let stopped = false,\n sending = false,\n start: number | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const created = Date.now();\n let lastActivity = created,\n missingSince = created;\n function close() {\n if (stopped) return;\n stopped = true;\n if (timer !== undefined) clearTimeout(timer);\n timer = undefined;\n options.signal.removeEventListener(\"abort\", close);\n }\n function fail(category: LiveFailureCategory, detail: string) {\n if (!stopped) {\n close();\n options.fail(category, detail);\n }\n }\n function bind(binding: LiveBinding, value: string) {\n const existing = values.get(binding.name);\n if (binding.action === \"reference\") {\n if (existing !== value) throw new Error(\"identifier reference mismatch\");\n } else {\n const key = JSON.stringify([namespace(binding), value]);\n if (existing !== undefined || reverse.has(key))\n throw new Error(\"identifier bijection mismatch\");\n values.set(binding.name, value);\n reverse.set(key, binding.name);\n }\n }\n function eligible() {\n const barrier = transcript.entries[servers[next]]?.barrier;\n return (\n barrier !== undefined &&\n (command > barrier.command ||\n (command === barrier.command && audioBytes >= barrier.audioBytes))\n );\n }\n function due() {\n return options.timing === \"immediate\"\n ? 0\n : transcript.entries[servers[next]].atMs / options.replaySpeed;\n }\n function schedule() {\n if (stopped) return;\n if (timer !== undefined) clearTimeout(timer);\n const now = Date.now();\n const deadlines = [\n (start ?? created) + limits.maxDurationMs,\n lastActivity + limits.idleTimeoutMs,\n ];\n if (cursor < tape.length && (next >= servers.length || !eligible()))\n deadlines.push(missingSince + limits.mismatchTimeoutMs);\n if (!sending && start !== undefined && next < servers.length && eligible())\n deadlines.push(start + due());\n timer = setTimeout(tick, Math.max(0, Math.min(2_147_483_647, Math.min(...deadlines) - now)));\n }\n function tick() {\n timer = undefined;\n if (stopped) return;\n const now = Date.now();\n if (\n now >= (start ?? created) + limits.maxDurationMs ||\n now >= lastActivity + limits.idleTimeoutMs\n ) {\n fail(\"timeout\", \"live replay deadline exceeded\");\n return;\n }\n if (\n cursor < tape.length &&\n (next >= servers.length || !eligible()) &&\n now >= missingSince + limits.mismatchTimeoutMs\n ) {\n fail(\"fixture-mismatch\", \"expected client input deadline exceeded\");\n return;\n }\n if (\n !sending &&\n start !== undefined &&\n next < servers.length &&\n eligible() &&\n now >= start + due()\n )\n void pump();\n schedule();\n }\n async function pump() {\n sending = true;\n try {\n while (\n !stopped &&\n start !== undefined &&\n next < servers.length &&\n eligible() &&\n Date.now() >= start + due()\n ) {\n const index = servers[next];\n const event = structuredClone(transcript.entries[index].event);\n for (const binding of annotations.get(index) ?? []) {\n if (binding.action === \"define\") bind(binding, `aimock-${randomUUID()}`);\n const value = values.get(binding.name);\n if (value === undefined) throw new Error(\"unavailable identifier\");\n replace(event, binding.pointer, value);\n }\n if (Buffer.byteLength(JSON.stringify(event)) > limits.maxWriteBytes) {\n fail(\"resource-limit\", \"live output exceeds write limit\");\n break;\n }\n await options.send(event);\n if (stopped) break;\n next++;\n lastActivity = Date.now();\n missingSince = Date.now();\n if (next === servers.length) close();\n }\n } catch {\n fail(\"protocol-divergence\", \"live output failed\");\n } finally {\n sending = false;\n schedule();\n }\n }\n function accept(event: LiveObject): void {\n if (stopped) return;\n try {\n if (!boundedJson(event, limits.maxMessageBytes)) {\n fail(\"invalid-client\", \"live input must be bounded JSON\");\n return;\n }\n if (Buffer.byteLength(JSON.stringify(event)) > limits.maxMessageBytes) {\n fail(\"resource-limit\", \"live input exceeds message limit\");\n return;\n }\n let expected = tape[cursor];\n const metadata = { ...event };\n delete metadata.audio;\n // Empty frames do not advance PCM position, even at the end of a run.\n // Move past exhausted runs when the next input changes metadata or supplies\n // more bytes/a control, without crossing an unfinished run or control.\n while (\n expected?.kind === \"audio\" &&\n offset === expected.bytes.length &&\n !(event.audio === \"\" && canonical(metadata) === canonical(expected.metadata))\n ) {\n expected = tape[++cursor];\n offset = 0;\n }\n if (!expected) throw new Error(\"unexpected client input\");\n if (expected.kind === \"audio\") {\n const bytes = decode(\n event.audio,\n Math.min(limits.maxDecodedAudioBytes - totalAudio, expected.bytes.length - offset),\n );\n if (\n canonical(metadata) !== canonical(expected.metadata) ||\n !bytes.equals(expected.bytes.subarray(offset, offset + bytes.length))\n )\n throw new Error(\"audio mismatch\");\n offset += bytes.length;\n audioBytes += bytes.length;\n totalAudio += bytes.length;\n } else {\n const template = structuredClone(transcript.entries[expected.index].event);\n for (const binding of annotations.get(expected.index) ?? []) {\n const value = read(event, binding.pointer);\n if (typeof value !== \"string\" || !value.length) throw new Error(\"missing identifier\");\n bind(binding, value);\n replace(template, binding.pointer, value);\n }\n if (canonical(template) !== canonical(event)) throw new Error(\"command mismatch\");\n if (event.type === \"session.input_audio.append\") {\n const bytes = decode(event.audio, limits.maxDecodedAudioBytes - totalAudio);\n audioBytes += bytes.length;\n totalAudio += bytes.length;\n } else {\n command++;\n audioBytes = 0;\n }\n cursor++;\n }\n start ??= Date.now();\n lastActivity = Date.now();\n missingSince = Date.now();\n schedule();\n } catch {\n fail(\"fixture-mismatch\", \"client input does not match live fixture\");\n }\n }\n options.signal.addEventListener(\"abort\", close, { once: true });\n if (options.signal.aborted) close();\n else schedule();\n return { accept, close };\n}\n"],"mappings":";;;;AAoBA,SAAS,YAAY,MAAkB,OAAwB;CAC7D,IAAI,SAAS;CACb,SAAS,MAAM,OAAiB,OAAwB;AACtD,MAAI,QAAQ,MAAM,EAAE,SAAS,EAAG,QAAO;AACvC,MAAI,OAAO,UAAU,UAAU;AAC7B,aAAU,OAAO,WAAW,MAAM;AAClC,UAAO,UAAU;;AAEnB,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAW,QAAO;AACzD,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,SAAS,MAAM;AAC5D,MAAI,OAAO,UAAU,SAAU,QAAO;EACtC,MAAM,YAAY,OAAO,eAAe,MAAM;AAC9C,MACE,MAAM,QAAQ,MAAM,GAChB,cAAc,MAAM,YACpB,cAAc,OAAO,aAAa,cAAc,KAEpD,QAAO;AACT,OAAK,MAAM,OAAO,OAAO,KAAK,MAAM,EAAE;AACpC,OAAI;IAAC;IAAa;IAAe;IAAY,CAAC,SAAS,IAAI,CAAE,QAAO;AACpE,aAAU,OAAO,WAAW,IAAI;GAChC,MAAM,aAAa,OAAO,yBAAyB,OAAO,IAAI;AAC9D,OAAI,CAAC,cAAc,EAAE,WAAW,eAAe,CAAC,MAAM,WAAW,OAAO,QAAQ,EAAE,CAChF,QAAO;;AAEX,SAAO,UAAU;;AAEnB,QAAO,MAAM,MAAM,EAAE;;AAEvB,SAAS,UAAU,OAAyB;AAC1C,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,IAAI,MAAM,IAAI,UAAU,CAAC,KAAK,IAAI,CAAC;AACpE,KAAI,UAAU,QAAQ,OAAO,UAAU,SACrC,QAAO,IAAI,OAAO,KAAK,MAAM,CAC1B,MAAM,CACN,KAAK,QAAQ,GAAG,KAAK,UAAU,IAAI,CAAC,GAAG,UAAU,MAAM,KAAK,GAAG,CAC/D,KAAK,IAAI,CAAC;AACf,QAAO,KAAK,UAAU,MAAM;;AAE9B,SAAS,MACP,OACA,SACkD;CAClD,MAAM,QAAQ,QACX,MAAM,EAAE,CACR,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,QAAQ,OAAO,IAAI,CAAC,QAAQ,OAAO,IAAI,CAAC;CACxD,MAAM,MAAM,MAAM,KAAK;CACvB,IAAI,SAAmB;AACvB,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,WAAW,QAAQ,OAAO,WAAW,SAAU,OAAM,IAAI,MAAM,qBAAqB;AACxF,WAAS,MAAM,QAAQ,OAAO,GAAG,OAAO,OAAO,KAAK,IAAI,OAAO;;AAEjE,KAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,CAAC,OAAO,OAAO,QAAQ,IAAI,CAC9E,OAAM,IAAI,MAAM,qBAAqB;AACvC,QAAO;EAAE;EAAQ;EAAK;;AAExB,SAAS,KAAK,OAAmB,SAA2B;CAC1D,MAAM,EAAE,QAAQ,QAAQ,MAAM,OAAO,QAAQ;AAC7C,QAAO,MAAM,QAAQ,OAAO,GAAG,OAAO,OAAO,IAAI,IAAI,OAAO;;AAE9D,SAAS,QAAQ,OAAmB,SAAiB,OAAqB;CACxE,MAAM,EAAE,QAAQ,QAAQ,MAAM,OAAO,QAAQ;AAC7C,KAAI,MAAM,QAAQ,OAAO,CAAE,QAAO,OAAO,IAAI,IAAI;KAC5C,QAAO,OAAO;;AAGrB,SAAS,UAAU,SAA8B;CAC/C,MAAM,IAAI,QAAQ;AAClB,KAAI,EAAE,SAAS,kBAAkB,CAAE,QAAO;AAC1C,KAAI,MAAM,YAAa,QAAO,GAAG,QAAQ,MAAM;AAC/C,KAAI,MAAM,cAAe,QAAO;AAChC,KAAI,MAAM,oBAAoB,MAAM,iBAAkB,QAAO;AAC7D,KAAI,EAAE,SAAS,WAAW,CAAE,QAAO;AACnC,KAAI,MAAM,oBAAoB,MAAM,iBAAkB,QAAO;AAC7D,QAAO;;AAET,SAAS,OAAO,OAAiB,WAA2B;AAC1D,KACE,OAAO,UAAU,YACjB,MAAM,SAAS,KACf,CAAC,mEAAmE,KAAK,MAAM,CAE/E,OAAM,IAAI,MAAM,gBAAgB;CAClC,MAAM,OAAQ,MAAM,SAAS,IAAK,KAAK,MAAM,SAAS,KAAK,GAAG,IAAI,MAAM,SAAS,IAAI,GAAG,IAAI;AAC5F,KAAI,OAAO,KAAK,OAAO,UAAW,OAAM,IAAI,MAAM,qBAAqB;CACvE,MAAM,QAAQ,OAAO,KAAK,OAAO,SAAS;AAC1C,KAAI,MAAM,SAAS,SAAS,KAAK,MAAO,OAAM,IAAI,MAAM,yBAAyB;AACjF,QAAO;;;;AAcT,SAAgB,iBACd,YACA,SACoD;AACpD,KAAI,CAAC,OAAO,SAAS,QAAQ,YAAY,IAAI,QAAQ,eAAe,EAClE,OAAM,IAAI,MAAM,+BAA+B;CACjD,MAAM,EAAE,WAAW;CACnB,MAAM,8BAAc,IAAI,KAA4B;AACpD,MAAK,MAAM,WAAW,WAAW,UAAU;EACzC,MAAM,SAAS,YAAY,IAAI,QAAQ,MAAM,IAAI,EAAE;AACnD,SAAO,KAAK,QAAQ;AACpB,cAAY,IAAI,QAAQ,OAAO,OAAO;;AAExC,MAAK,MAAM,UAAU,YAAY,QAAQ,CACvC,QAAO,MAAM,GAAG,MAAM,OAAO,EAAE,WAAW,YAAY,GAAG,OAAO,EAAE,WAAW,YAAY,CAAC;CAC5F,MAAM,OAA4B,EAAE;CACpC,MAAM,UAAoB,EAAE;CAC5B,MAAM,8BAAc,IAAI,KAAsB;CAC9C,IAAI,eAAe;AACnB,MAAK,MAAM,CAAC,OAAO,UAAU,WAAW,QAAQ,SAAS,EAAE;AACzD,MAAI,MAAM,cAAc,UAAU;AAChC,WAAQ,KAAK,MAAM;AACnB;;AAEF,MAAI,MAAM,MAAM,SAAS,8BAA8B;AACrD,QAAK,KAAK;IAAE,MAAM;IAAW;IAAO,CAAC;AACrC;;EAEF,MAAM,QAAQ,OAAO,MAAM,MAAM,OAAO,OAAO,uBAAuB,aAAa;AACnF,kBAAgB,MAAM;EACtB,MAAM,WAAW,EAAE,GAAG,MAAM,OAAO;AACnC,SAAO,SAAS;EAChB,MAAM,WAAW,KAAK,GAAG,GAAG;AAG5B,MAAI,YAAY,IAAI,MAAM,EAAE;AAC1B,QAAK,KAAK;IAAE,MAAM;IAAW;IAAO,CAAC;AACrC;;AAEF,MAAI,UAAU,SAAS,WAAW,UAAU,SAAS,SAAS,KAAK,UAAU,SAAS,CACpF,aAAY,IAAI,SAAS,CAAE,KAAK,MAAM;OACnC;GACH,MAAM,MAAa;IAAE,MAAM;IAAS;IAAO;IAAU;AACrD,QAAK,KAAK,IAAI;AACd,eAAY,IAAI,KAAK,CAAC,MAAM,CAAC;;;AAGjC,MAAK,MAAM,CAAC,KAAK,WAAW,YAAa,KAAI,QAAQ,OAAO,OAAO,OAAO;AAC1E,aAAY,OAAO;CACnB,MAAM,yBAAS,IAAI,KAAqB;CACxC,MAAM,0BAAU,IAAI,KAAqB;CACzC,IAAI,SAAS,GACX,SAAS,GACT,UAAU,GACV,aAAa,GACb,aAAa,GACb,OAAO;CACT,IAAI,UAAU,OACZ,UAAU,OACV;CACF,IAAI;CACJ,MAAM,UAAU,KAAK,KAAK;CAC1B,IAAI,eAAe,SACjB,eAAe;CACjB,SAAS,QAAQ;AACf,MAAI,QAAS;AACb,YAAU;AACV,MAAI,UAAU,OAAW,cAAa,MAAM;AAC5C,UAAQ;AACR,UAAQ,OAAO,oBAAoB,SAAS,MAAM;;CAEpD,SAAS,KAAK,UAA+B,QAAgB;AAC3D,MAAI,CAAC,SAAS;AACZ,UAAO;AACP,WAAQ,KAAK,UAAU,OAAO;;;CAGlC,SAAS,KAAK,SAAsB,OAAe;EACjD,MAAM,WAAW,OAAO,IAAI,QAAQ,KAAK;AACzC,MAAI,QAAQ,WAAW,aACrB;OAAI,aAAa,MAAO,OAAM,IAAI,MAAM,gCAAgC;SACnE;GACL,MAAM,MAAM,KAAK,UAAU,CAAC,UAAU,QAAQ,EAAE,MAAM,CAAC;AACvD,OAAI,aAAa,UAAa,QAAQ,IAAI,IAAI,CAC5C,OAAM,IAAI,MAAM,gCAAgC;AAClD,UAAO,IAAI,QAAQ,MAAM,MAAM;AAC/B,WAAQ,IAAI,KAAK,QAAQ,KAAK;;;CAGlC,SAAS,WAAW;EAClB,MAAM,UAAU,WAAW,QAAQ,QAAQ,QAAQ;AACnD,SACE,YAAY,WACX,UAAU,QAAQ,WAChB,YAAY,QAAQ,WAAW,cAAc,QAAQ;;CAG5D,SAAS,MAAM;AACb,SAAO,QAAQ,WAAW,cACtB,IACA,WAAW,QAAQ,QAAQ,OAAO,OAAO,QAAQ;;CAEvD,SAAS,WAAW;AAClB,MAAI,QAAS;AACb,MAAI,UAAU,OAAW,cAAa,MAAM;EAC5C,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,YAAY,EACf,SAAS,WAAW,OAAO,eAC5B,eAAe,OAAO,cACvB;AACD,MAAI,SAAS,KAAK,WAAW,QAAQ,QAAQ,UAAU,CAAC,UAAU,EAChE,WAAU,KAAK,eAAe,OAAO,kBAAkB;AACzD,MAAI,CAAC,WAAW,UAAU,UAAa,OAAO,QAAQ,UAAU,UAAU,CACxE,WAAU,KAAK,QAAQ,KAAK,CAAC;AAC/B,UAAQ,WAAW,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,YAAe,KAAK,IAAI,GAAG,UAAU,GAAG,IAAI,CAAC,CAAC;;CAE9F,SAAS,OAAO;AACd,UAAQ;AACR,MAAI,QAAS;EACb,MAAM,MAAM,KAAK,KAAK;AACtB,MACE,QAAQ,SAAS,WAAW,OAAO,iBACnC,OAAO,eAAe,OAAO,eAC7B;AACA,QAAK,WAAW,gCAAgC;AAChD;;AAEF,MACE,SAAS,KAAK,WACb,QAAQ,QAAQ,UAAU,CAAC,UAAU,KACtC,OAAO,eAAe,OAAO,mBAC7B;AACA,QAAK,oBAAoB,0CAA0C;AACnE;;AAEF,MACE,CAAC,WACD,UAAU,UACV,OAAO,QAAQ,UACf,UAAU,IACV,OAAO,QAAQ,KAAK,CAEpB,CAAK,MAAM;AACb,YAAU;;CAEZ,eAAe,OAAO;AACpB,YAAU;AACV,MAAI;AACF,UACE,CAAC,WACD,UAAU,UACV,OAAO,QAAQ,UACf,UAAU,IACV,KAAK,KAAK,IAAI,QAAQ,KAAK,EAC3B;IACA,MAAM,QAAQ,QAAQ;IACtB,MAAM,QAAQ,gBAAgB,WAAW,QAAQ,OAAO,MAAM;AAC9D,SAAK,MAAM,WAAW,YAAY,IAAI,MAAM,IAAI,EAAE,EAAE;AAClD,SAAI,QAAQ,WAAW,SAAU,MAAK,SAAS,uCAAsB,GAAG;KACxE,MAAM,QAAQ,OAAO,IAAI,QAAQ,KAAK;AACtC,SAAI,UAAU,OAAW,OAAM,IAAI,MAAM,yBAAyB;AAClE,aAAQ,OAAO,QAAQ,SAAS,MAAM;;AAExC,QAAI,OAAO,WAAW,KAAK,UAAU,MAAM,CAAC,GAAG,OAAO,eAAe;AACnE,UAAK,kBAAkB,kCAAkC;AACzD;;AAEF,UAAM,QAAQ,KAAK,MAAM;AACzB,QAAI,QAAS;AACb;AACA,mBAAe,KAAK,KAAK;AACzB,mBAAe,KAAK,KAAK;AACzB,QAAI,SAAS,QAAQ,OAAQ,QAAO;;UAEhC;AACN,QAAK,uBAAuB,qBAAqB;YACzC;AACR,aAAU;AACV,aAAU;;;CAGd,SAAS,OAAO,OAAyB;AACvC,MAAI,QAAS;AACb,MAAI;AACF,OAAI,CAAC,YAAY,OAAO,OAAO,gBAAgB,EAAE;AAC/C,SAAK,kBAAkB,kCAAkC;AACzD;;AAEF,OAAI,OAAO,WAAW,KAAK,UAAU,MAAM,CAAC,GAAG,OAAO,iBAAiB;AACrE,SAAK,kBAAkB,mCAAmC;AAC1D;;GAEF,IAAI,WAAW,KAAK;GACpB,MAAM,WAAW,EAAE,GAAG,OAAO;AAC7B,UAAO,SAAS;AAIhB,UACE,UAAU,SAAS,WACnB,WAAW,SAAS,MAAM,UAC1B,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK,UAAU,SAAS,SAAS,GAC5E;AACA,eAAW,KAAK,EAAE;AAClB,aAAS;;AAEX,OAAI,CAAC,SAAU,OAAM,IAAI,MAAM,0BAA0B;AACzD,OAAI,SAAS,SAAS,SAAS;IAC7B,MAAM,QAAQ,OACZ,MAAM,OACN,KAAK,IAAI,OAAO,uBAAuB,YAAY,SAAS,MAAM,SAAS,OAAO,CACnF;AACD,QACE,UAAU,SAAS,KAAK,UAAU,SAAS,SAAS,IACpD,CAAC,MAAM,OAAO,SAAS,MAAM,SAAS,QAAQ,SAAS,MAAM,OAAO,CAAC,CAErE,OAAM,IAAI,MAAM,iBAAiB;AACnC,cAAU,MAAM;AAChB,kBAAc,MAAM;AACpB,kBAAc,MAAM;UACf;IACL,MAAM,WAAW,gBAAgB,WAAW,QAAQ,SAAS,OAAO,MAAM;AAC1E,SAAK,MAAM,WAAW,YAAY,IAAI,SAAS,MAAM,IAAI,EAAE,EAAE;KAC3D,MAAM,QAAQ,KAAK,OAAO,QAAQ,QAAQ;AAC1C,SAAI,OAAO,UAAU,YAAY,CAAC,MAAM,OAAQ,OAAM,IAAI,MAAM,qBAAqB;AACrF,UAAK,SAAS,MAAM;AACpB,aAAQ,UAAU,QAAQ,SAAS,MAAM;;AAE3C,QAAI,UAAU,SAAS,KAAK,UAAU,MAAM,CAAE,OAAM,IAAI,MAAM,mBAAmB;AACjF,QAAI,MAAM,SAAS,8BAA8B;KAC/C,MAAM,QAAQ,OAAO,MAAM,OAAO,OAAO,uBAAuB,WAAW;AAC3E,mBAAc,MAAM;AACpB,mBAAc,MAAM;WACf;AACL;AACA,kBAAa;;AAEf;;AAEF,aAAU,KAAK,KAAK;AACpB,kBAAe,KAAK,KAAK;AACzB,kBAAe,KAAK,KAAK;AACzB,aAAU;UACJ;AACN,QAAK,oBAAoB,2CAA2C;;;AAGxE,SAAQ,OAAO,iBAAiB,SAAS,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/D,KAAI,QAAQ,OAAO,QAAS,QAAO;KAC9B,WAAU;AACf,QAAO;EAAE;EAAQ;EAAO"}