@copilotkit/aimock 1.34.0 → 1.35.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 (150) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +16 -0
  4. package/README.md +2 -1
  5. package/dist/agui-types.d.cts.map +1 -1
  6. package/dist/agui-types.d.ts.map +1 -1
  7. package/dist/bedrock-converse.cjs +110 -8
  8. package/dist/bedrock-converse.cjs.map +1 -1
  9. package/dist/bedrock-converse.d.cts.map +1 -1
  10. package/dist/bedrock-converse.d.ts.map +1 -1
  11. package/dist/bedrock-converse.js +111 -9
  12. package/dist/bedrock-converse.js.map +1 -1
  13. package/dist/bedrock.cjs +141 -4
  14. package/dist/bedrock.cjs.map +1 -1
  15. package/dist/bedrock.d.cts.map +1 -1
  16. package/dist/bedrock.d.ts.map +1 -1
  17. package/dist/bedrock.js +142 -5
  18. package/dist/bedrock.js.map +1 -1
  19. package/dist/cohere.cjs +89 -7
  20. package/dist/cohere.cjs.map +1 -1
  21. package/dist/cohere.d.cts.map +1 -1
  22. package/dist/cohere.d.ts.map +1 -1
  23. package/dist/cohere.js +90 -8
  24. package/dist/cohere.js.map +1 -1
  25. package/dist/config-loader.d.cts.map +1 -1
  26. package/dist/fixture-loader.cjs +121 -20
  27. package/dist/fixture-loader.cjs.map +1 -1
  28. package/dist/fixture-loader.d.cts.map +1 -1
  29. package/dist/fixture-loader.d.ts.map +1 -1
  30. package/dist/fixture-loader.js +121 -20
  31. package/dist/fixture-loader.js.map +1 -1
  32. package/dist/gemini-interactions.cjs +97 -60
  33. package/dist/gemini-interactions.cjs.map +1 -1
  34. package/dist/gemini-interactions.d.cts.map +1 -1
  35. package/dist/gemini-interactions.d.ts.map +1 -1
  36. package/dist/gemini-interactions.js +98 -61
  37. package/dist/gemini-interactions.js.map +1 -1
  38. package/dist/gemini.cjs +71 -6
  39. package/dist/gemini.cjs.map +1 -1
  40. package/dist/gemini.d.cts.map +1 -1
  41. package/dist/gemini.d.ts.map +1 -1
  42. package/dist/gemini.js +72 -7
  43. package/dist/gemini.js.map +1 -1
  44. package/dist/grok-video.cjs +834 -0
  45. package/dist/grok-video.cjs.map +1 -0
  46. package/dist/grok-video.d.cts +74 -0
  47. package/dist/grok-video.d.cts.map +1 -0
  48. package/dist/grok-video.d.ts +74 -0
  49. package/dist/grok-video.d.ts.map +1 -0
  50. package/dist/grok-video.js +829 -0
  51. package/dist/grok-video.js.map +1 -0
  52. package/dist/helpers.cjs +181 -2
  53. package/dist/helpers.cjs.map +1 -1
  54. package/dist/helpers.d.cts +33 -3
  55. package/dist/helpers.d.cts.map +1 -1
  56. package/dist/helpers.d.ts +33 -3
  57. package/dist/helpers.d.ts.map +1 -1
  58. package/dist/helpers.js +181 -3
  59. package/dist/helpers.js.map +1 -1
  60. package/dist/index.cjs +11 -0
  61. package/dist/index.d.cts +5 -3
  62. package/dist/index.d.ts +5 -3
  63. package/dist/index.js +4 -2
  64. package/dist/messages.cjs +99 -10
  65. package/dist/messages.cjs.map +1 -1
  66. package/dist/messages.d.cts.map +1 -1
  67. package/dist/messages.d.ts.map +1 -1
  68. package/dist/messages.js +100 -11
  69. package/dist/messages.js.map +1 -1
  70. package/dist/metrics.cjs +11 -1
  71. package/dist/metrics.cjs.map +1 -1
  72. package/dist/metrics.d.cts.map +1 -1
  73. package/dist/metrics.d.ts.map +1 -1
  74. package/dist/metrics.js +8 -1
  75. package/dist/metrics.js.map +1 -1
  76. package/dist/ollama.cjs +74 -4
  77. package/dist/ollama.cjs.map +1 -1
  78. package/dist/ollama.d.cts.map +1 -1
  79. package/dist/ollama.d.ts.map +1 -1
  80. package/dist/ollama.js +75 -5
  81. package/dist/ollama.js.map +1 -1
  82. package/dist/openrouter-video.cjs +19 -138
  83. package/dist/openrouter-video.cjs.map +1 -1
  84. package/dist/openrouter-video.d.cts.map +1 -1
  85. package/dist/openrouter-video.d.ts.map +1 -1
  86. package/dist/openrouter-video.js +1 -120
  87. package/dist/openrouter-video.js.map +1 -1
  88. package/dist/recorder.cjs +12 -9
  89. package/dist/recorder.cjs.map +1 -1
  90. package/dist/recorder.js +12 -9
  91. package/dist/recorder.js.map +1 -1
  92. package/dist/responses.cjs +136 -58
  93. package/dist/responses.cjs.map +1 -1
  94. package/dist/responses.d.cts +2 -2
  95. package/dist/responses.d.cts.map +1 -1
  96. package/dist/responses.d.ts +2 -2
  97. package/dist/responses.d.ts.map +1 -1
  98. package/dist/responses.js +137 -59
  99. package/dist/responses.js.map +1 -1
  100. package/dist/server.cjs +162 -20
  101. package/dist/server.cjs.map +1 -1
  102. package/dist/server.d.cts +4 -0
  103. package/dist/server.d.cts.map +1 -1
  104. package/dist/server.d.ts +4 -0
  105. package/dist/server.d.ts.map +1 -1
  106. package/dist/server.js +164 -22
  107. package/dist/server.js.map +1 -1
  108. package/dist/stream-collapse.cjs +300 -74
  109. package/dist/stream-collapse.cjs.map +1 -1
  110. package/dist/stream-collapse.d.cts +17 -1
  111. package/dist/stream-collapse.d.cts.map +1 -1
  112. package/dist/stream-collapse.d.ts +17 -1
  113. package/dist/stream-collapse.d.ts.map +1 -1
  114. package/dist/stream-collapse.js +300 -74
  115. package/dist/stream-collapse.js.map +1 -1
  116. package/dist/types.d.cts +104 -7
  117. package/dist/types.d.cts.map +1 -1
  118. package/dist/types.d.ts +104 -7
  119. package/dist/types.d.ts.map +1 -1
  120. package/dist/veo-video.cjs +794 -0
  121. package/dist/veo-video.cjs.map +1 -0
  122. package/dist/veo-video.d.cts +50 -0
  123. package/dist/veo-video.d.cts.map +1 -0
  124. package/dist/veo-video.d.ts +50 -0
  125. package/dist/veo-video.d.ts.map +1 -0
  126. package/dist/veo-video.js +789 -0
  127. package/dist/veo-video.js.map +1 -0
  128. package/dist/video-proxy-shared.cjs +145 -0
  129. package/dist/video-proxy-shared.cjs.map +1 -0
  130. package/dist/video-proxy-shared.js +140 -0
  131. package/dist/video-proxy-shared.js.map +1 -0
  132. package/dist/ws-gemini-live.cjs +96 -3
  133. package/dist/ws-gemini-live.cjs.map +1 -1
  134. package/dist/ws-gemini-live.d.cts.map +1 -1
  135. package/dist/ws-gemini-live.d.ts.map +1 -1
  136. package/dist/ws-gemini-live.js +97 -4
  137. package/dist/ws-gemini-live.js.map +1 -1
  138. package/dist/ws-realtime.cjs +281 -4
  139. package/dist/ws-realtime.cjs.map +1 -1
  140. package/dist/ws-realtime.d.cts.map +1 -1
  141. package/dist/ws-realtime.d.ts.map +1 -1
  142. package/dist/ws-realtime.js +282 -5
  143. package/dist/ws-realtime.js.map +1 -1
  144. package/dist/ws-responses.cjs +1 -1
  145. package/dist/ws-responses.cjs.map +1 -1
  146. package/dist/ws-responses.js +1 -1
  147. package/dist/ws-responses.js.map +1 -1
  148. package/fixtures/examples/llm/blocks-tool-first.json +13 -0
  149. package/package.json +1 -1
  150. package/skills/write-fixtures/SKILL.md +19 -1
@@ -0,0 +1,834 @@
1
+ const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
2
+ const require_helpers = require('./helpers.cjs');
3
+ const require_router = require('./router.cjs');
4
+ const require_sse_writer = require('./sse-writer.cjs');
5
+ const require_chaos = require('./chaos.cjs');
6
+ const require_url = require('./url.cjs');
7
+ const require_recorder = require('./recorder.cjs');
8
+ const require_video = require('./video.cjs');
9
+ const require_fal = require('./fal.cjs');
10
+ const require_video_proxy_shared = require('./video-proxy-shared.cjs');
11
+ let node_crypto = require("node:crypto");
12
+ node_crypto = require_runtime.__toESM(node_crypto);
13
+
14
+ //#region src/grok-video.ts
15
+ const DEFAULT_GROK_VIDEO_MODEL = "grok-imagine-video";
16
+ const GROK_USD_TICKS_PER_USD = 1e10;
17
+ const GROK_DEFAULT_VIDEO_URL = "https://cdn.x.ai/aimock-placeholder-video.mp4";
18
+ const GROK_VIDEO_MAX_ENTRIES = 1e4;
19
+ const GROK_VIDEO_TTL_MS = 36e5;
20
+ /**
21
+ * Per-testId job state for the Grok video handler. Mirrors OpenRouterVideoJobMap
22
+ * (openrouter-video.ts): lazy TTL eviction on `get`, FIFO eviction of the
23
+ * oldest entries on `set` when over capacity, delete-before-set TTL refresh,
24
+ * monotonic world-generation counter for reset-mid-flight detection, no
25
+ * background sweep timer. Keys are `${testId}:${requestId}`.
26
+ */
27
+ var GrokVideoJobMap = class {
28
+ entries = /* @__PURE__ */ new Map();
29
+ worldGeneration = 0;
30
+ get generation() {
31
+ return this.worldGeneration;
32
+ }
33
+ get(key) {
34
+ const entry = this.entries.get(key);
35
+ if (!entry) return void 0;
36
+ if (Date.now() - entry.createdAt > GROK_VIDEO_TTL_MS) {
37
+ this.entries.delete(key);
38
+ return;
39
+ }
40
+ return entry.job;
41
+ }
42
+ set(key, job) {
43
+ this.entries.delete(key);
44
+ this.entries.set(key, {
45
+ job,
46
+ createdAt: Date.now()
47
+ });
48
+ if (this.entries.size > GROK_VIDEO_MAX_ENTRIES) {
49
+ const excess = this.entries.size - GROK_VIDEO_MAX_ENTRIES;
50
+ const iter = this.entries.keys();
51
+ for (let i = 0; i < excess; i++) {
52
+ const next = iter.next();
53
+ if (!next.done) this.entries.delete(next.value);
54
+ }
55
+ }
56
+ }
57
+ delete(key) {
58
+ return this.entries.delete(key);
59
+ }
60
+ clear() {
61
+ this.entries.clear();
62
+ this.worldGeneration++;
63
+ }
64
+ get size() {
65
+ return this.entries.size;
66
+ }
67
+ };
68
+ /**
69
+ * Maps the fixture's terminal video status onto the job lifecycle. Anything
70
+ * that is not "failed" — including a fixture authored as "processing" — is
71
+ * treated as completed, since this surface always drives jobs to a terminal
72
+ * state.
73
+ */
74
+ function terminalStatus(job) {
75
+ return job.video.status === "failed" ? "failed" : "completed";
76
+ }
77
+ /**
78
+ * Mutates a job in place to advance its state on a status poll.
79
+ * `pending → in_progress → completed | failed` based on poll-count thresholds.
80
+ * No-op once terminal. The in_progress threshold is checked first so a job
81
+ * whose thresholds are equal still spends one poll in in_progress instead of
82
+ * jumping straight to the terminal status (fal advanceJob semantics).
83
+ */
84
+ function advanceJob(job) {
85
+ if (job.status === "completed" || job.status === "failed") return;
86
+ job.pollCount += 1;
87
+ if (job.status === "pending" && job.pollCount >= job.pollsBeforeInProgress) job.status = "in_progress";
88
+ else if (job.pollCount >= job.pollsBeforeCompleted) job.status = terminalStatus(job);
89
+ }
90
+ /**
91
+ * Synthesize the Grok wire `progress` (0..100) from poll-count progress toward
92
+ * the completed threshold. A terminal-completed job is always 100 (the only way
93
+ * to reach 100 — it is the wire signal for "done"). Every other state reports
94
+ * its progress-at-failure/at-poll, capped at 99. A seed-terminal job with a
95
+ * non-positive completion target has done zero polls toward completion, so it
96
+ * reports 0 (a failed seed job is 0%, never 100).
97
+ */
98
+ function grokProgress(job) {
99
+ if (job.status === "completed") return 100;
100
+ const target = job.pollsBeforeCompleted;
101
+ if (target <= 0) return 0;
102
+ const ratio = Math.min(1, job.pollCount / target);
103
+ return Math.min(99, Math.round(ratio * 100));
104
+ }
105
+ /** Map the internal job status onto the Grok wire `status` token. */
106
+ function grokWireStatus(status) {
107
+ if (status === "completed") return "done";
108
+ if (status === "failed") return "failed";
109
+ return "pending";
110
+ }
111
+ /**
112
+ * Synthesizes a structurally valid journal body for field-validation 400s.
113
+ * Mirrors openrouter-video's validationJournalBody: `model` stays a string and
114
+ * `messages` is an empty array; underscore-prefixed keys are stripped so a
115
+ * request cannot spoof handler-set discriminators.
116
+ */
117
+ function validationJournalBody(videoReq) {
118
+ const rawModel = videoReq.model;
119
+ const model = typeof rawModel === "string" ? rawModel : rawModel === void 0 ? "" : JSON.stringify(rawModel);
120
+ return {
121
+ ...Object.fromEntries(Object.entries(videoReq).filter(([key]) => !key.startsWith("_"))),
122
+ model,
123
+ messages: []
124
+ };
125
+ }
126
+ async function handleGrokVideoCreate(req, res, raw, fixtures, journal, defaults, setCorsHeaders, jobs) {
127
+ setCorsHeaders(res);
128
+ const path = req.url ?? "/v1/videos/generations";
129
+ const method = req.method ?? "POST";
130
+ if (((Array.isArray(req.headers["content-type"]) ? req.headers["content-type"][0] : req.headers["content-type"]) ?? "").toLowerCase().includes("multipart/form-data")) {
131
+ journal.add({
132
+ method,
133
+ path,
134
+ headers: require_helpers.flattenHeaders(req.headers),
135
+ body: null,
136
+ response: {
137
+ status: 400,
138
+ fixture: null
139
+ }
140
+ });
141
+ require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({
142
+ code: "invalid_request",
143
+ error: "multipart/form-data is not supported — the Grok video API is JSON-only"
144
+ }));
145
+ return;
146
+ }
147
+ let videoReq;
148
+ try {
149
+ videoReq = JSON.parse(raw);
150
+ } catch (parseErr) {
151
+ const detail = parseErr instanceof Error ? parseErr.message : "unknown";
152
+ journal.add({
153
+ method,
154
+ path,
155
+ headers: require_helpers.flattenHeaders(req.headers),
156
+ body: null,
157
+ response: {
158
+ status: 400,
159
+ fixture: null
160
+ }
161
+ });
162
+ require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({
163
+ code: "invalid_json",
164
+ error: `Malformed JSON: ${detail}`
165
+ }));
166
+ return;
167
+ }
168
+ if (videoReq === null || typeof videoReq !== "object" || Array.isArray(videoReq)) {
169
+ journal.add({
170
+ method,
171
+ path,
172
+ headers: require_helpers.flattenHeaders(req.headers),
173
+ body: null,
174
+ response: {
175
+ status: 400,
176
+ fixture: null
177
+ }
178
+ });
179
+ require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({
180
+ code: "invalid_request",
181
+ error: "Request body must be a JSON object"
182
+ }));
183
+ return;
184
+ }
185
+ const parsedBody = validationJournalBody(videoReq);
186
+ if (typeof videoReq.prompt !== "string" || !videoReq.prompt) {
187
+ journal.add({
188
+ method,
189
+ path,
190
+ headers: require_helpers.flattenHeaders(req.headers),
191
+ body: parsedBody,
192
+ response: {
193
+ status: 400,
194
+ fixture: null
195
+ }
196
+ });
197
+ const message = videoReq.prompt === void 0 ? "Missing required parameter: 'prompt'" : "Invalid type for parameter: 'prompt' must be a non-empty string";
198
+ require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({
199
+ code: "invalid_request",
200
+ error: message
201
+ }));
202
+ return;
203
+ }
204
+ if (videoReq.model !== void 0 && (typeof videoReq.model !== "string" || !videoReq.model)) {
205
+ journal.add({
206
+ method,
207
+ path,
208
+ headers: require_helpers.flattenHeaders(req.headers),
209
+ body: parsedBody,
210
+ response: {
211
+ status: 400,
212
+ fixture: null
213
+ }
214
+ });
215
+ require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({
216
+ code: "invalid_request",
217
+ error: "Invalid type for parameter: 'model' must be a non-empty string"
218
+ }));
219
+ return;
220
+ }
221
+ const syntheticReq = {
222
+ model: videoReq.model ?? DEFAULT_GROK_VIDEO_MODEL,
223
+ messages: [{
224
+ role: "user",
225
+ content: videoReq.prompt
226
+ }],
227
+ _endpointType: "video",
228
+ _videoProvider: "grok",
229
+ _context: require_helpers.getContext(req)
230
+ };
231
+ const testId = require_helpers.getTestId(req);
232
+ const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, syntheticReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
233
+ if (fixture) {
234
+ journal.incrementFixtureMatchCount(fixture, fixtures, testId);
235
+ defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
236
+ } else {
237
+ const snippet = videoReq.prompt.slice(0, 80);
238
+ defaults.logger.debug(`No fixture matched for request (model=${syntheticReq.model}, msg="${snippet}")`);
239
+ }
240
+ if (require_chaos.applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
241
+ method,
242
+ path,
243
+ headers: require_helpers.flattenHeaders(req.headers),
244
+ body: syntheticReq
245
+ }, fixture ? "fixture" : require_helpers.resolveStrictMode(defaults.strict, req.headers) ? "internal" : defaults.record?.providers.grok ? "proxy" : "internal", defaults.registry, defaults.logger)) return;
246
+ if (!fixture) {
247
+ if (require_helpers.resolveStrictMode(defaults.strict, req.headers)) {
248
+ const strictMessage = require_helpers.strictNoMatchMessage(skippedBySequenceOrTurn);
249
+ defaults.logger.error(require_helpers.strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));
250
+ journal.add({
251
+ method,
252
+ path,
253
+ headers: require_helpers.flattenHeaders(req.headers),
254
+ body: syntheticReq,
255
+ response: {
256
+ status: 503,
257
+ fixture: null,
258
+ ...require_helpers.strictOverrideField(defaults.strict, req.headers)
259
+ }
260
+ });
261
+ require_sse_writer.writeErrorResponse(res, 503, JSON.stringify({
262
+ code: "no_fixture_match",
263
+ error: strictMessage
264
+ }));
265
+ return;
266
+ }
267
+ if (defaults.record) {
268
+ if (await proxyGrokVideoSubmit({
269
+ req,
270
+ res,
271
+ raw,
272
+ syntheticReq,
273
+ record: defaults.record,
274
+ journal,
275
+ defaults,
276
+ jobs,
277
+ method,
278
+ path
279
+ }) === "handled") return;
280
+ }
281
+ journal.add({
282
+ method,
283
+ path,
284
+ headers: require_helpers.flattenHeaders(req.headers),
285
+ body: syntheticReq,
286
+ response: {
287
+ status: 404,
288
+ fixture: null,
289
+ ...require_helpers.strictOverrideField(defaults.strict, req.headers)
290
+ }
291
+ });
292
+ require_sse_writer.writeErrorResponse(res, 404, JSON.stringify({
293
+ code: "not_found",
294
+ error: "No fixture matched"
295
+ }));
296
+ return;
297
+ }
298
+ const worldGeneration = jobs.generation;
299
+ const response = await require_helpers.resolveResponse(fixture, syntheticReq);
300
+ if (require_helpers.isErrorResponse(response)) {
301
+ if (res.destroyed || res.writableEnded) return;
302
+ const status = response.status ?? 500;
303
+ journal.add({
304
+ method,
305
+ path,
306
+ headers: require_helpers.flattenHeaders(req.headers),
307
+ body: syntheticReq,
308
+ response: {
309
+ status,
310
+ fixture
311
+ }
312
+ });
313
+ require_sse_writer.writeErrorResponse(res, status, require_helpers.serializeErrorResponse(response), { retryAfter: response.retryAfter });
314
+ return;
315
+ }
316
+ if (!require_helpers.isVideoResponse(response)) {
317
+ if (res.destroyed || res.writableEnded) return;
318
+ journal.add({
319
+ method,
320
+ path,
321
+ headers: require_helpers.flattenHeaders(req.headers),
322
+ body: syntheticReq,
323
+ response: {
324
+ status: 500,
325
+ fixture
326
+ }
327
+ });
328
+ require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
329
+ message: "Fixture response is not a video type",
330
+ type: "server_error"
331
+ } }));
332
+ return;
333
+ }
334
+ if (res.destroyed || res.writableEnded) return;
335
+ journal.add({
336
+ method,
337
+ path,
338
+ headers: require_helpers.flattenHeaders(req.headers),
339
+ body: syntheticReq,
340
+ response: {
341
+ status: 200,
342
+ fixture
343
+ }
344
+ });
345
+ const fixtureStatus = response.video.status;
346
+ if (fixtureStatus === "processing") defaults.logger.warn(`Video fixture has status "processing" — treated as completed for /v1/videos/generations jobs`);
347
+ else if (fixtureStatus !== "completed" && fixtureStatus !== "failed") defaults.logger.warn(`Video fixture has unknown status "${fixtureStatus}" — treating as completed for /v1/videos/generations jobs`);
348
+ const requestId = node_crypto.default.randomUUID();
349
+ const progression = require_fal.resolveProgression(defaults.grokVideo);
350
+ const job = {
351
+ kind: "replay",
352
+ requestId,
353
+ status: "pending",
354
+ pollCount: 0,
355
+ pollsBeforeInProgress: progression.pollsBeforeInProgress,
356
+ pollsBeforeCompleted: progression.pollsBeforeCompleted,
357
+ video: { ...response.video }
358
+ };
359
+ if (progression.pollsBeforeCompleted === 0) job.status = terminalStatus(job);
360
+ if (jobs.generation === worldGeneration) jobs.set(`${testId}:${requestId}`, job);
361
+ else defaults.logger.warn(`Grok video submit resolved after a fixtures reset — not inserting job ${requestId} into the new world (its polls will 404)`);
362
+ res.writeHead(200, { "Content-Type": "application/json" });
363
+ res.end(JSON.stringify({ request_id: requestId + require_video_proxy_shared.testIdSuffix(testId, "?") }));
364
+ }
365
+ async function handleGrokVideoStatus(req, res, id, fixtures, journal, defaults, setCorsHeaders, grokJobs, videoStates) {
366
+ const testId = require_helpers.getTestId(req);
367
+ const key = `${testId}:${id}`;
368
+ const job = grokJobs.get(key);
369
+ if (!job) {
370
+ require_video.handleVideoStatus(req, res, id, journal, defaults, setCorsHeaders, videoStates);
371
+ return;
372
+ }
373
+ setCorsHeaders(res);
374
+ const path = req.url ?? `/v1/videos/${id}`;
375
+ const method = req.method ?? "GET";
376
+ if (require_chaos.applyChaos(res, null, defaults.chaos, req.headers, journal, {
377
+ method,
378
+ path,
379
+ headers: require_helpers.flattenHeaders(req.headers),
380
+ body: null
381
+ }, "internal", defaults.registry, defaults.logger)) return;
382
+ if (job.kind === "record") {
383
+ if (require_helpers.resolveStrictMode(defaults.strict, req.headers)) {
384
+ defaults.logger.error(`STRICT: video job ${id} is proxied live upstream (record mode) — refusing the upstream poll`);
385
+ journal.add({
386
+ method,
387
+ path,
388
+ headers: require_helpers.flattenHeaders(req.headers),
389
+ body: null,
390
+ response: {
391
+ status: 503,
392
+ fixture: null,
393
+ ...require_helpers.strictOverrideField(defaults.strict, req.headers)
394
+ }
395
+ });
396
+ require_sse_writer.writeErrorResponse(res, 503, JSON.stringify({
397
+ code: "no_fixture_match",
398
+ error: `Strict mode: video job ${id} is proxied live upstream (record mode) — nothing reaches an upstream under strict mode`
399
+ }));
400
+ return;
401
+ }
402
+ await proxyGrokVideoRecordPoll({
403
+ req,
404
+ res,
405
+ job,
406
+ key,
407
+ testId,
408
+ fixtures,
409
+ journal,
410
+ defaults,
411
+ jobs: grokJobs,
412
+ method,
413
+ path
414
+ });
415
+ return;
416
+ }
417
+ if (res.destroyed || res.writableEnded) return;
418
+ advanceJob(job);
419
+ grokJobs.set(key, job);
420
+ journal.add({
421
+ method,
422
+ path,
423
+ headers: require_helpers.flattenHeaders(req.headers),
424
+ body: null,
425
+ response: {
426
+ status: 200,
427
+ fixture: null
428
+ }
429
+ });
430
+ const body = serializeGrokReplay(job, defaults);
431
+ res.writeHead(200, { "Content-Type": "application/json" });
432
+ res.end(JSON.stringify(body));
433
+ }
434
+ /**
435
+ * Serialize a replay job into the Grok wire body. STORED `video.status` is the
436
+ * 3-value VideoResponse union; the wire `status` is derived here
437
+ * (completed → "done"). cost_in_usd_ticks is reconstructed from the stored USD
438
+ * `video.cost` (round(cost * 1e10)).
439
+ */
440
+ function serializeGrokReplay(job, defaults) {
441
+ const body = {
442
+ request_id: job.requestId,
443
+ status: grokWireStatus(job.status),
444
+ progress: grokProgress(job)
445
+ };
446
+ if (job.status === "completed") {
447
+ body.video = {
448
+ url: job.video.url || GROK_DEFAULT_VIDEO_URL,
449
+ duration: job.video.duration ?? 0
450
+ };
451
+ body.usage = { cost_in_usd_ticks: Math.round((job.video.cost ?? 0) * GROK_USD_TICKS_PER_USD) };
452
+ } else if (job.status === "failed") {
453
+ if (job.video.error === "" && !job.emptyErrorWarned) {
454
+ job.emptyErrorWarned = true;
455
+ defaults.logger.warn(`Video fixture for job ${job.requestId} has an empty error message — using the default`);
456
+ }
457
+ body.code = "generation_failed";
458
+ body.error = job.video.error || "Video generation failed";
459
+ }
460
+ return body;
461
+ }
462
+ const GROK_VIDEO_SUBMIT_PATH = "/v1/videos/generations";
463
+ /**
464
+ * Proxy an unmatched Grok video submit to the configured upstream and answer
465
+ * the client with a mock-rewritten `{ request_id }`. The upstream lifecycle is
466
+ * then driven interactively by the client's own polls. Returns "no_upstream"
467
+ * when record mode has no grok provider URL — the caller falls through to 404.
468
+ */
469
+ async function proxyGrokVideoSubmit(args) {
470
+ const { req, res, raw, syntheticReq, record, journal, defaults, jobs, method, path } = args;
471
+ const upstreamBase = record.providers.grok;
472
+ if (!upstreamBase) {
473
+ defaults.logger.warn(`No upstream URL configured for provider "grok" — cannot proxy`);
474
+ return "no_upstream";
475
+ }
476
+ const proxyError = (msg) => {
477
+ defaults.logger.error(`Grok video submit proxy failed: ${msg}`);
478
+ if (res.destroyed || res.writableEnded) return "handled";
479
+ journal.add({
480
+ method,
481
+ path,
482
+ headers: require_helpers.flattenHeaders(req.headers),
483
+ body: syntheticReq,
484
+ response: {
485
+ status: 502,
486
+ fixture: null,
487
+ source: "proxy",
488
+ ...require_helpers.strictOverrideField(defaults.strict, req.headers)
489
+ }
490
+ });
491
+ require_sse_writer.writeErrorResponse(res, 502, JSON.stringify({
492
+ code: "proxy_error",
493
+ error: `Proxy to upstream failed: ${msg}`
494
+ }));
495
+ return "handled";
496
+ };
497
+ let submitUrl;
498
+ try {
499
+ submitUrl = require_url.resolveUpstreamUrl(upstreamBase, GROK_VIDEO_SUBMIT_PATH);
500
+ new URL(upstreamBase).origin;
501
+ } catch (err) {
502
+ return proxyError(`Invalid upstream URL: ${upstreamBase} (${err instanceof Error ? err.message : String(err)})`);
503
+ }
504
+ defaults.logger.warn(`NO FIXTURE MATCH — proxying video submit to ${upstreamBase}${GROK_VIDEO_SUBMIT_PATH}`);
505
+ const worldGeneration = jobs.generation;
506
+ let fetched;
507
+ try {
508
+ const upstreamRes = await fetch(submitUrl, {
509
+ method: "POST",
510
+ headers: require_recorder.buildForwardHeaders(req),
511
+ body: raw,
512
+ signal: require_video_proxy_shared.upstreamTimeoutSignal(record)
513
+ });
514
+ fetched = {
515
+ status: upstreamRes.status,
516
+ contentType: upstreamRes.headers.get("content-type"),
517
+ text: await require_video_proxy_shared.readEnvelopeText(upstreamRes, record)
518
+ };
519
+ } catch (err) {
520
+ return proxyError(err instanceof Error ? err.message : "Unknown proxy error");
521
+ }
522
+ if (fetched.status === 401 || fetched.status === 403) {
523
+ defaults.logger.warn(`Upstream rejected the video submit (${fetched.status}) — relaying the upstream status`);
524
+ if (res.destroyed || res.writableEnded) return "handled";
525
+ journal.add({
526
+ method,
527
+ path,
528
+ headers: require_helpers.flattenHeaders(req.headers),
529
+ body: syntheticReq,
530
+ response: {
531
+ status: fetched.status,
532
+ fixture: null,
533
+ source: "proxy",
534
+ ...require_helpers.strictOverrideField(defaults.strict, req.headers)
535
+ }
536
+ });
537
+ res.writeHead(fetched.status, { "Content-Type": fetched.contentType ?? "application/json" });
538
+ res.end(fetched.text);
539
+ return "handled";
540
+ }
541
+ let upstreamRequestId;
542
+ {
543
+ if (fetched.status < 200 || fetched.status >= 300) return proxyError(`Submit ${fetched.status}: ${fetched.text.slice(0, 200)}`);
544
+ let parsed;
545
+ try {
546
+ parsed = JSON.parse(fetched.text);
547
+ } catch {
548
+ return proxyError(`Submit returned non-JSON: ${fetched.text.slice(0, 200)}`);
549
+ }
550
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return proxyError("Submit response is not a JSON object");
551
+ const envelope = parsed;
552
+ upstreamRequestId = String(envelope.request_id ?? "").trim();
553
+ if (!upstreamRequestId) return proxyError("Submit response missing request_id");
554
+ }
555
+ const upstreamPollingUrl = require_url.resolveUpstreamUrl(upstreamBase, `/v1/videos/${encodeURIComponent(upstreamRequestId)}`).toString();
556
+ const testId = require_helpers.getTestId(req);
557
+ const matchRequest = defaults.requestTransform ? defaults.requestTransform(syntheticReq) : syntheticReq;
558
+ const requestId = node_crypto.default.randomUUID();
559
+ const job = {
560
+ kind: "record",
561
+ requestId,
562
+ status: "pending",
563
+ upstreamRequestId,
564
+ upstreamPollingUrl,
565
+ match: require_recorder.buildFixtureMatch(matchRequest, record)
566
+ };
567
+ if (jobs.generation === worldGeneration) jobs.set(`${testId}:${requestId}`, job);
568
+ else defaults.logger.warn(`Grok video submit for upstream request ${upstreamRequestId} completed after a fixtures reset — not inserting the job into the new world (its polls will 404)`);
569
+ if (res.destroyed || res.writableEnded) return "handled";
570
+ journal.add({
571
+ method,
572
+ path,
573
+ headers: require_helpers.flattenHeaders(req.headers),
574
+ body: syntheticReq,
575
+ response: {
576
+ status: 200,
577
+ fixture: null,
578
+ source: "proxy",
579
+ ...require_helpers.strictOverrideField(defaults.strict, req.headers)
580
+ }
581
+ });
582
+ res.writeHead(200, { "Content-Type": "application/json" });
583
+ res.end(JSON.stringify({ request_id: requestId + require_video_proxy_shared.testIdSuffix(testId, "?") }));
584
+ return "handled";
585
+ }
586
+ /**
587
+ * Proxy a status poll for a record-mode job 1:1 to the upstream and relay the
588
+ * result with the mock requestId substituted. On `done` the rewritten body is
589
+ * relayed IMMEDIATELY and an eager capture runs (synchronously here — there is
590
+ * NO byte download, only the url/duration/cost from the terminal poll body —
591
+ * but structured to mirror openrouter-video's terminal path). `failed`
592
+ * persists a failed fixture synchronously. `expired` (and any unrepresentable
593
+ * status) pass through with a warn and keep proxying. Every post-await map
594
+ * mutation is identity-guarded. Each successful proxied poll refreshes the TTL.
595
+ */
596
+ async function proxyGrokVideoRecordPoll(args) {
597
+ const { req, res, job, key, testId, fixtures, journal, defaults, jobs, method, path } = args;
598
+ const logger = defaults.logger;
599
+ const journalProxy = (status) => {
600
+ journal.add({
601
+ method,
602
+ path,
603
+ headers: require_helpers.flattenHeaders(req.headers),
604
+ body: null,
605
+ response: {
606
+ status,
607
+ fixture: null,
608
+ source: "proxy",
609
+ ...require_helpers.strictOverrideField(defaults.strict, req.headers)
610
+ }
611
+ });
612
+ };
613
+ const proxyError = (msg) => {
614
+ logger.error(`Grok video poll proxy failed: ${msg}`);
615
+ if (res.destroyed || res.writableEnded) return;
616
+ journalProxy(502);
617
+ require_sse_writer.writeErrorResponse(res, 502, JSON.stringify({
618
+ code: "proxy_error",
619
+ error: `Proxy to upstream failed: ${msg}`
620
+ }));
621
+ };
622
+ const record = defaults.record;
623
+ if (!record) {
624
+ proxyError("record mode is no longer configured for an in-flight record job");
625
+ return;
626
+ }
627
+ let fetched;
628
+ try {
629
+ const upstreamRes = await fetch(job.upstreamPollingUrl, {
630
+ headers: require_recorder.buildForwardHeaders(req),
631
+ signal: require_video_proxy_shared.upstreamTimeoutSignal(record)
632
+ });
633
+ fetched = {
634
+ status: upstreamRes.status,
635
+ contentType: upstreamRes.headers.get("content-type"),
636
+ text: await require_video_proxy_shared.readEnvelopeText(upstreamRes, record)
637
+ };
638
+ } catch (err) {
639
+ proxyError(err instanceof Error ? err.message : "Unknown proxy error");
640
+ return;
641
+ }
642
+ if (fetched.status === 401 || fetched.status === 403) {
643
+ logger.warn(`Upstream rejected the status poll for job ${job.upstreamRequestId} (${fetched.status}) — relaying the upstream status`);
644
+ if (res.destroyed || res.writableEnded) return;
645
+ journalProxy(fetched.status);
646
+ res.writeHead(fetched.status, { "Content-Type": fetched.contentType ?? "application/json" });
647
+ res.end(fetched.text);
648
+ return;
649
+ }
650
+ let upstreamBody;
651
+ {
652
+ if (fetched.status < 200 || fetched.status >= 300) {
653
+ proxyError(`Status ${fetched.status}: ${fetched.text.slice(0, 200)}`);
654
+ return;
655
+ }
656
+ let parsed;
657
+ try {
658
+ parsed = JSON.parse(fetched.text);
659
+ } catch {
660
+ proxyError(`Status returned non-JSON: ${fetched.text.slice(0, 200)}`);
661
+ return;
662
+ }
663
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
664
+ proxyError("Status response is not a JSON object");
665
+ return;
666
+ }
667
+ upstreamBody = parsed;
668
+ }
669
+ const relayBody = {
670
+ ...upstreamBody,
671
+ request_id: job.requestId
672
+ };
673
+ const relayJson = (body) => {
674
+ if (res.destroyed || res.writableEnded) return;
675
+ journalProxy(200);
676
+ res.writeHead(200, { "Content-Type": "application/json" });
677
+ res.end(JSON.stringify(body));
678
+ };
679
+ const upstreamStatus = String(upstreamBody.status ?? "");
680
+ if (upstreamStatus === "pending" || upstreamStatus === "in_progress") {
681
+ if (jobs.get(key) === job) {
682
+ if (!job.capturing && job.status !== "completed" && job.status !== "failed") job.status = upstreamStatus === "in_progress" ? "in_progress" : "pending";
683
+ jobs.set(key, job);
684
+ }
685
+ relayJson(relayBody);
686
+ return;
687
+ }
688
+ if (upstreamStatus === "done") {
689
+ if (record.proxyOnly) {
690
+ if (jobs.get(key) === job) {
691
+ job.status = "completed";
692
+ jobs.set(key, job);
693
+ }
694
+ relayJson(relayBody);
695
+ return;
696
+ }
697
+ if (job.capturing || jobs.get(key) !== job) {
698
+ if (jobs.get(key) === job) jobs.set(key, job);
699
+ relayJson(relayBody);
700
+ return;
701
+ }
702
+ job.capturing = true;
703
+ job.status = "completed";
704
+ jobs.set(key, job);
705
+ relayJson(relayBody);
706
+ captureGrokVideoRecordFixture({
707
+ job,
708
+ key,
709
+ testId,
710
+ fixtures,
711
+ defaults,
712
+ jobs,
713
+ record,
714
+ upstreamBody
715
+ });
716
+ return;
717
+ }
718
+ if (upstreamStatus === "failed") {
719
+ if (record.proxyOnly) {
720
+ if (jobs.get(key) === job) {
721
+ job.status = "failed";
722
+ jobs.set(key, job);
723
+ }
724
+ relayJson(relayBody);
725
+ return;
726
+ }
727
+ if (jobs.get(key) !== job) {
728
+ relayJson(relayBody);
729
+ return;
730
+ }
731
+ const rawError = upstreamBody.error;
732
+ let error;
733
+ if (typeof rawError === "string" && rawError) error = rawError;
734
+ else if (rawError !== null && typeof rawError === "object" && !Array.isArray(rawError)) {
735
+ const message = rawError.message;
736
+ if (typeof message === "string" && message) error = message;
737
+ }
738
+ if (error === void 0 && rawError !== void 0 && rawError !== null) logger.warn(`Upstream video job ${job.upstreamRequestId} failed with an unusable error value (${JSON.stringify(rawError).slice(0, 100)}) — recording the fixture without an error message (replay serves the default)`);
739
+ const video = {
740
+ id: job.upstreamRequestId,
741
+ status: "failed",
742
+ ...error !== void 0 ? { error } : {}
743
+ };
744
+ const persistResult = require_recorder.persistFixture({
745
+ record,
746
+ providerKey: "grok",
747
+ testId,
748
+ fixture: {
749
+ match: job.match,
750
+ response: { video }
751
+ },
752
+ fixtures,
753
+ logger
754
+ });
755
+ if (persistResult.kind === "failed" && !res.headersSent) res.setHeader("X-AIMock-Record-Error", require_recorder.sanitizeHeaderValue(persistResult.error));
756
+ jobs.set(key, {
757
+ kind: "replay",
758
+ requestId: job.requestId,
759
+ status: "failed",
760
+ pollCount: 0,
761
+ pollsBeforeInProgress: 0,
762
+ pollsBeforeCompleted: 0,
763
+ video: { ...video }
764
+ });
765
+ relayJson(relayBody);
766
+ return;
767
+ }
768
+ logger.warn(`Upstream video job ${job.upstreamRequestId} reported status "${upstreamStatus}" — not representable in fixture video.status; passing through without recording`);
769
+ if (jobs.get(key) === job) jobs.set(key, job);
770
+ relayJson(relayBody);
771
+ }
772
+ /**
773
+ * Capture a completed Grok record job into a fixture (PERSIST only — NO byte
774
+ * download): builds `video` from the terminal poll body (id = upstream
775
+ * request_id, status = stored "completed", url/duration/cost), persists it, and
776
+ * mutates the map entry into a terminal replay job. World-generation guard
777
+ * before persist (a fixtures reset mid-flight invalidates the world).
778
+ */
779
+ function captureGrokVideoRecordFixture(args) {
780
+ const { job, key, testId, fixtures, defaults, jobs, record, upstreamBody } = args;
781
+ const logger = defaults.logger;
782
+ try {
783
+ const upstreamVideo = upstreamBody.video !== null && typeof upstreamBody.video === "object" && !Array.isArray(upstreamBody.video) ? upstreamBody.video : void 0;
784
+ const rawUrl = upstreamVideo?.url;
785
+ const url = typeof rawUrl === "string" && rawUrl ? rawUrl : GROK_DEFAULT_VIDEO_URL;
786
+ const rawDuration = upstreamVideo?.duration;
787
+ const duration = typeof rawDuration === "number" ? rawDuration : void 0;
788
+ const rawTicks = (upstreamBody.usage !== null && typeof upstreamBody.usage === "object" && !Array.isArray(upstreamBody.usage) ? upstreamBody.usage : void 0)?.cost_in_usd_ticks;
789
+ const cost = typeof rawTicks === "number" ? rawTicks / GROK_USD_TICKS_PER_USD : void 0;
790
+ const video = {
791
+ id: job.upstreamRequestId,
792
+ status: "completed",
793
+ url,
794
+ ...duration !== void 0 ? { duration } : {},
795
+ ...cost !== void 0 ? { cost } : {}
796
+ };
797
+ if (jobs.get(key) !== job) {
798
+ logger.warn(`Grok video capture for job ${job.upstreamRequestId} discarded: the job map no longer holds this job (fixtures reset or TTL eviction) — nothing persisted`);
799
+ return;
800
+ }
801
+ require_recorder.persistFixture({
802
+ record,
803
+ providerKey: "grok",
804
+ testId,
805
+ fixture: {
806
+ match: job.match,
807
+ response: { video }
808
+ },
809
+ fixtures,
810
+ logger
811
+ });
812
+ if (jobs.get(key) === job) jobs.set(key, {
813
+ kind: "replay",
814
+ requestId: job.requestId,
815
+ status: "completed",
816
+ pollCount: 0,
817
+ pollsBeforeInProgress: 0,
818
+ pollsBeforeCompleted: 0,
819
+ video: { ...video }
820
+ });
821
+ } catch (err) {
822
+ const msg = err instanceof Error ? err.message : String(err);
823
+ logger.error(`Grok video capture for job ${job.upstreamRequestId} failed unexpectedly (${msg}) — fixture not persisted; the job keeps proxying live`);
824
+ } finally {
825
+ job.capturing = false;
826
+ }
827
+ }
828
+
829
+ //#endregion
830
+ exports.GROK_VIDEO_MAX_ENTRIES = GROK_VIDEO_MAX_ENTRIES;
831
+ exports.GrokVideoJobMap = GrokVideoJobMap;
832
+ exports.handleGrokVideoCreate = handleGrokVideoCreate;
833
+ exports.handleGrokVideoStatus = handleGrokVideoStatus;
834
+ //# sourceMappingURL=grok-video.cjs.map