@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +30 -0
- package/README.md +6 -1
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/bedrock-converse.cjs +112 -10
- package/dist/bedrock-converse.cjs.map +1 -1
- package/dist/bedrock-converse.d.cts.map +1 -1
- package/dist/bedrock-converse.d.ts.map +1 -1
- package/dist/bedrock-converse.js +114 -12
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +143 -6
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.d.cts.map +1 -1
- package/dist/bedrock.d.ts.map +1 -1
- package/dist/bedrock.js +145 -8
- package/dist/bedrock.js.map +1 -1
- package/dist/cohere.cjs +91 -9
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.d.cts.map +1 -1
- package/dist/cohere.d.ts.map +1 -1
- package/dist/cohere.js +93 -11
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/fixture-loader.cjs +121 -20
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +121 -20
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-interactions.cjs +146 -108
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +147 -109
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +72 -7
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts.map +1 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +74 -9
- package/dist/gemini.js.map +1 -1
- package/dist/grok-video.cjs +834 -0
- package/dist/grok-video.cjs.map +1 -0
- package/dist/grok-video.d.cts +74 -0
- package/dist/grok-video.d.cts.map +1 -0
- package/dist/grok-video.d.ts +74 -0
- package/dist/grok-video.d.ts.map +1 -0
- package/dist/grok-video.js +829 -0
- package/dist/grok-video.js.map +1 -0
- package/dist/helpers.cjs +181 -2
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +33 -3
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +33 -3
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +181 -3
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +11 -0
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -2
- package/dist/messages.cjs +100 -11
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.d.cts.map +1 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +102 -13
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +11 -1
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +8 -1
- package/dist/metrics.js.map +1 -1
- package/dist/ollama.cjs +77 -7
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts.map +1 -1
- package/dist/ollama.js +79 -9
- package/dist/ollama.js.map +1 -1
- package/dist/openrouter-video.cjs +19 -138
- package/dist/openrouter-video.cjs.map +1 -1
- package/dist/openrouter-video.d.cts.map +1 -1
- package/dist/openrouter-video.d.ts.map +1 -1
- package/dist/openrouter-video.js +1 -120
- package/dist/openrouter-video.js.map +1 -1
- package/dist/recorder.cjs +12 -9
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.js +12 -9
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +137 -59
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts +2 -2
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts +2 -2
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +139 -61
- package/dist/responses.js.map +1 -1
- package/dist/router.cjs +236 -33
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts +54 -2
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts +54 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +236 -34
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +163 -21
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +4 -0
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +166 -24
- package/dist/server.js.map +1 -1
- package/dist/stream-collapse.cjs +355 -79
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +27 -4
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +27 -4
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +355 -79
- package/dist/stream-collapse.js.map +1 -1
- package/dist/types.d.cts +104 -7
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +104 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/veo-video.cjs +794 -0
- package/dist/veo-video.cjs.map +1 -0
- package/dist/veo-video.d.cts +50 -0
- package/dist/veo-video.d.cts.map +1 -0
- package/dist/veo-video.d.ts +50 -0
- package/dist/veo-video.d.ts.map +1 -0
- package/dist/veo-video.js +789 -0
- package/dist/veo-video.js.map +1 -0
- package/dist/video-proxy-shared.cjs +145 -0
- package/dist/video-proxy-shared.cjs.map +1 -0
- package/dist/video-proxy-shared.js +140 -0
- package/dist/video-proxy-shared.js.map +1 -0
- package/dist/ws-gemini-live.cjs +96 -3
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.d.cts.map +1 -1
- package/dist/ws-gemini-live.d.ts.map +1 -1
- package/dist/ws-gemini-live.js +97 -4
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +281 -4
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +282 -5
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +1 -1
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.js +1 -1
- package/dist/ws-responses.js.map +1 -1
- package/fixtures/examples/llm/blocks-tool-first.json +13 -0
- package/package.json +1 -1
- 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
|