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