@copilotkit/aimock 1.42.1 → 1.44.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 +65 -0
- package/README.md +108 -16
- package/dist/agui-handler.d.ts.map +1 -1
- package/dist/agui-mock.cjs +1 -1
- package/dist/agui-mock.cjs.map +1 -1
- package/dist/agui-mock.d.cts.map +1 -1
- package/dist/agui-mock.d.ts.map +1 -1
- package/dist/agui-mock.js +1 -1
- package/dist/agui-mock.js.map +1 -1
- package/dist/agui-recorder.cjs +33 -11
- package/dist/agui-recorder.cjs.map +1 -1
- package/dist/agui-recorder.d.cts +10 -1
- package/dist/agui-recorder.d.cts.map +1 -1
- package/dist/agui-recorder.d.ts +10 -1
- package/dist/agui-recorder.d.ts.map +1 -1
- package/dist/agui-recorder.js +33 -11
- package/dist/agui-recorder.js.map +1 -1
- package/dist/agui-types.d.cts +1 -0
- package/dist/agui-types.d.cts.map +1 -1
- package/dist/agui-types.d.ts +1 -0
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/aimock-cli.cjs +38 -10
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.d.cts +5 -0
- package/dist/aimock-cli.d.cts.map +1 -1
- package/dist/aimock-cli.d.ts +5 -0
- package/dist/aimock-cli.d.ts.map +1 -1
- package/dist/aimock-cli.js +38 -10
- package/dist/aimock-cli.js.map +1 -1
- package/dist/batches.cjs +458 -0
- package/dist/batches.cjs.map +1 -0
- package/dist/batches.d.cts +57 -0
- package/dist/batches.d.cts.map +1 -0
- package/dist/batches.d.ts +57 -0
- package/dist/batches.d.ts.map +1 -0
- package/dist/batches.js +453 -0
- package/dist/batches.js.map +1 -0
- package/dist/bedrock-converse.cjs +44 -12
- 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 +46 -14
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +10 -8
- 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 +12 -10
- package/dist/bedrock.js.map +1 -1
- package/dist/byteplus-video.cjs +3 -3
- package/dist/byteplus-video.cjs.map +1 -1
- package/dist/byteplus-video.js +4 -4
- package/dist/byteplus-video.js.map +1 -1
- package/dist/chaos.cjs +625 -72
- package/dist/chaos.cjs.map +1 -1
- package/dist/chaos.d.cts +164 -7
- package/dist/chaos.d.cts.map +1 -1
- package/dist/chaos.d.ts +164 -7
- package/dist/chaos.d.ts.map +1 -1
- package/dist/chaos.js +616 -74
- package/dist/chaos.js.map +1 -1
- package/dist/cli.cjs +131 -30
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +132 -31
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +37 -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 +39 -11
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.cjs +1 -1
- package/dist/config-loader.js +1 -1
- package/dist/constants.cjs +8 -0
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +6 -2
- package/dist/constants.d.ts +6 -2
- package/dist/constants.js +8 -1
- package/dist/constants.js.map +1 -1
- package/dist/convert.cjs +1 -1
- package/dist/convert.cjs.map +1 -1
- package/dist/convert.js +1 -1
- package/dist/convert.js.map +1 -1
- package/dist/echo-text.cjs +65 -0
- package/dist/echo-text.cjs.map +1 -0
- package/dist/echo-text.d.cts +22 -0
- package/dist/echo-text.d.cts.map +1 -0
- package/dist/echo-text.d.ts +22 -0
- package/dist/echo-text.d.ts.map +1 -0
- package/dist/echo-text.js +65 -0
- package/dist/echo-text.js.map +1 -0
- package/dist/elevenlabs-audio.cjs +3 -3
- package/dist/elevenlabs-audio.cjs.map +1 -1
- package/dist/elevenlabs-audio.js +4 -4
- package/dist/elevenlabs-audio.js.map +1 -1
- package/dist/elevenlabs-voice.cjs +1053 -0
- package/dist/elevenlabs-voice.cjs.map +1 -0
- package/dist/elevenlabs-voice.d.cts +72 -0
- package/dist/elevenlabs-voice.d.cts.map +1 -0
- package/dist/elevenlabs-voice.d.ts +72 -0
- package/dist/elevenlabs-voice.d.ts.map +1 -0
- package/dist/elevenlabs-voice.js +1048 -0
- package/dist/elevenlabs-voice.js.map +1 -0
- package/dist/embeddings.cjs +26 -5
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.d.cts +1 -1
- package/dist/embeddings.d.cts.map +1 -1
- package/dist/embeddings.d.ts +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +27 -6
- package/dist/embeddings.js.map +1 -1
- package/dist/fal-audio.cjs +3 -3
- package/dist/fal-audio.cjs.map +1 -1
- package/dist/fal-audio.js +4 -4
- package/dist/fal-audio.js.map +1 -1
- package/dist/fal.cjs +28 -4
- package/dist/fal.cjs.map +1 -1
- package/dist/fal.d.cts +16 -1
- package/dist/fal.d.cts.map +1 -1
- package/dist/fal.d.ts +16 -1
- package/dist/fal.d.ts.map +1 -1
- package/dist/fal.js +28 -5
- package/dist/fal.js.map +1 -1
- package/dist/files.cjs +1069 -0
- package/dist/files.cjs.map +1 -0
- package/dist/files.d.cts +68 -0
- package/dist/files.d.cts.map +1 -0
- package/dist/files.d.ts +68 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +1056 -0
- package/dist/files.js.map +1 -0
- package/dist/fine-tuning.cjs +1033 -0
- package/dist/fine-tuning.cjs.map +1 -0
- package/dist/fine-tuning.d.cts +96 -0
- package/dist/fine-tuning.d.cts.map +1 -0
- package/dist/fine-tuning.d.ts +96 -0
- package/dist/fine-tuning.d.ts.map +1 -0
- package/dist/fine-tuning.js +1026 -0
- package/dist/fine-tuning.js.map +1 -0
- package/dist/fixture-loader-services.cjs +116 -0
- package/dist/fixture-loader-services.cjs.map +1 -0
- package/dist/fixture-loader-services.d.cts +48 -0
- package/dist/fixture-loader-services.d.cts.map +1 -0
- package/dist/fixture-loader-services.d.ts +48 -0
- package/dist/fixture-loader-services.d.ts.map +1 -0
- package/dist/fixture-loader-services.js +114 -0
- package/dist/fixture-loader-services.js.map +1 -0
- package/dist/fixture-loader.cjs +0 -0
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +131 -5
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +131 -5
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +0 -0
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-embeddings.cjs +44 -4
- package/dist/gemini-embeddings.cjs.map +1 -1
- package/dist/gemini-embeddings.js +46 -6
- package/dist/gemini-embeddings.js.map +1 -1
- package/dist/gemini-interactions.cjs +51 -7
- 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 +53 -9
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +19 -8
- 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 +21 -10
- package/dist/gemini.js.map +1 -1
- package/dist/grok-video.cjs +14 -14
- package/dist/grok-video.cjs.map +1 -1
- package/dist/grok-video.js +15 -15
- package/dist/grok-video.js.map +1 -1
- package/dist/helpers.cjs +497 -10
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +38 -2
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +38 -2
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +487 -12
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +57 -125
- package/dist/images.cjs.map +1 -1
- package/dist/images.d.cts.map +1 -1
- package/dist/images.d.ts.map +1 -1
- package/dist/images.js +58 -126
- package/dist/images.js.map +1 -1
- package/dist/index.cjs +71 -9
- package/dist/index.d.cts +17 -7
- package/dist/index.d.ts +17 -7
- package/dist/index.js +21 -11
- package/dist/jest.cjs +24 -12
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +6 -3
- package/dist/jest.d.cts.map +1 -1
- package/dist/jest.d.ts +6 -3
- package/dist/jest.d.ts.map +1 -1
- package/dist/jest.js +25 -13
- package/dist/jest.js.map +1 -1
- package/dist/journal.cjs +61 -4
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts +60 -3
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts +60 -3
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +61 -5
- package/dist/journal.js.map +1 -1
- package/dist/live-fixture.cjs +1192 -0
- package/dist/live-fixture.cjs.map +1 -0
- package/dist/live-fixture.js +1187 -0
- package/dist/live-fixture.js.map +1 -0
- package/dist/live-recorder.cjs +256 -0
- package/dist/live-recorder.cjs.map +1 -0
- package/dist/live-recorder.js +254 -0
- package/dist/live-recorder.js.map +1 -0
- package/dist/live-replay.cjs +286 -0
- package/dist/live-replay.cjs.map +1 -0
- package/dist/live-replay.js +285 -0
- package/dist/live-replay.js.map +1 -0
- package/dist/live-sanitize.cjs +196 -0
- package/dist/live-sanitize.cjs.map +1 -0
- package/dist/live-sanitize.js +196 -0
- package/dist/live-sanitize.js.map +1 -0
- package/dist/live-types.d.cts +71 -0
- package/dist/live-types.d.cts.map +1 -0
- package/dist/live-types.d.ts +71 -0
- package/dist/live-types.d.ts.map +1 -0
- package/dist/llmock.cjs +132 -49
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +51 -5
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +51 -5
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +133 -50
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-fakes-mount.cjs +216 -0
- package/dist/mcp-fakes-mount.cjs.map +1 -0
- package/dist/mcp-fakes-mount.js +210 -0
- package/dist/mcp-fakes-mount.js.map +1 -0
- package/dist/mcp-fakes.cjs +1619 -0
- package/dist/mcp-fakes.cjs.map +1 -0
- package/dist/mcp-fakes.d.cts +101 -0
- package/dist/mcp-fakes.d.cts.map +1 -0
- package/dist/mcp-fakes.d.ts +101 -0
- package/dist/mcp-fakes.d.ts.map +1 -0
- package/dist/mcp-fakes.js +1614 -0
- package/dist/mcp-fakes.js.map +1 -0
- package/dist/mcp-handler.cjs +331 -17
- package/dist/mcp-handler.cjs.map +1 -1
- package/dist/mcp-handler.js +330 -18
- package/dist/mcp-handler.js.map +1 -1
- package/dist/mcp-mock.cjs +217 -49
- package/dist/mcp-mock.cjs.map +1 -1
- package/dist/mcp-mock.d.cts +66 -1
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts +66 -1
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +218 -50
- package/dist/mcp-mock.js.map +1 -1
- package/dist/mcp-stub.cjs +5 -1
- package/dist/mcp-stub.d.cts +3 -1
- package/dist/mcp-stub.d.ts +3 -1
- package/dist/mcp-stub.js +3 -1
- package/dist/mcp-types.d.cts +6 -0
- package/dist/mcp-types.d.ts +6 -0
- package/dist/message-text.cjs +192 -0
- package/dist/message-text.cjs.map +1 -0
- package/dist/message-text.d.cts +31 -0
- package/dist/message-text.d.cts.map +1 -0
- package/dist/message-text.d.ts +31 -0
- package/dist/message-text.d.ts.map +1 -0
- package/dist/message-text.js +184 -0
- package/dist/message-text.js.map +1 -0
- package/dist/messages.cjs +49 -7
- 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 +51 -9
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +278 -45
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts +17 -7
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts +17 -7
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +270 -29
- package/dist/metrics.js.map +1 -1
- package/dist/moderation.cjs +10 -1
- package/dist/moderation.cjs.map +1 -1
- package/dist/moderation.d.cts.map +1 -1
- package/dist/moderation.d.ts.map +1 -1
- package/dist/moderation.js +10 -1
- package/dist/moderation.js.map +1 -1
- package/dist/ollama.cjs +10 -6
- 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 +11 -7
- package/dist/ollama.js.map +1 -1
- package/dist/openapi.cjs +3405 -0
- package/dist/openapi.cjs.map +1 -0
- package/dist/openapi.d.cts +30 -0
- package/dist/openapi.d.cts.map +1 -0
- package/dist/openapi.d.ts +30 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +3404 -0
- package/dist/openapi.js.map +1 -0
- package/dist/openrouter-video.cjs +9 -9
- 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 +10 -10
- package/dist/openrouter-video.js.map +1 -1
- package/dist/recorder.cjs +54 -10
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +52 -12
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +47 -7
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +48 -9
- package/dist/responses.js.map +1 -1
- package/dist/route-registry.cjs +951 -0
- package/dist/route-registry.cjs.map +1 -0
- package/dist/route-registry.d.cts +33 -0
- package/dist/route-registry.d.cts.map +1 -0
- package/dist/route-registry.d.ts +33 -0
- package/dist/route-registry.d.ts.map +1 -0
- package/dist/route-registry.js +866 -0
- package/dist/route-registry.js.map +1 -0
- package/dist/router.cjs +21 -34
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts +4 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts +4 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +21 -35
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +1252 -777
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +5 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +5 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1185 -710
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +6 -4
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.js +7 -5
- package/dist/speech.js.map +1 -1
- package/dist/suite.cjs +1 -1
- package/dist/suite.js +1 -1
- package/dist/transcription.cjs +6 -6
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.d.cts.map +1 -1
- package/dist/transcription.d.ts.map +1 -1
- package/dist/transcription.js +7 -7
- package/dist/transcription.js.map +1 -1
- package/dist/types.cjs +18 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +320 -17
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +320 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/dist/validate-cli.cjs +983 -0
- package/dist/validate-cli.cjs.map +1 -0
- package/dist/validate-cli.js +982 -0
- package/dist/validate-cli.js.map +1 -0
- package/dist/vector-stores.cjs +1180 -0
- package/dist/vector-stores.cjs.map +1 -0
- package/dist/vector-stores.d.cts +106 -0
- package/dist/vector-stores.d.cts.map +1 -0
- package/dist/vector-stores.d.ts +106 -0
- package/dist/vector-stores.d.ts.map +1 -0
- package/dist/vector-stores.js +1164 -0
- package/dist/vector-stores.js.map +1 -0
- package/dist/vector-types.d.cts.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/veo-video.cjs +13 -13
- package/dist/veo-video.cjs.map +1 -1
- package/dist/veo-video.js +14 -14
- package/dist/veo-video.js.map +1 -1
- package/dist/video.cjs +23 -4
- package/dist/video.cjs.map +1 -1
- package/dist/video.d.cts +1 -1
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts +1 -1
- package/dist/video.d.ts.map +1 -1
- package/dist/video.js +24 -5
- package/dist/video.js.map +1 -1
- package/dist/vitest.cjs +23 -11
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +5 -2
- package/dist/vitest.d.cts.map +1 -1
- package/dist/vitest.d.ts +5 -2
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +25 -13
- package/dist/vitest.js.map +1 -1
- package/dist/watcher.cjs +2 -1
- package/dist/watcher.cjs.map +1 -1
- package/dist/watcher.js +2 -1
- package/dist/watcher.js.map +1 -1
- package/dist/ws-framing.cjs +150 -35
- package/dist/ws-framing.cjs.map +1 -1
- package/dist/ws-framing.d.cts +18 -3
- package/dist/ws-framing.d.cts.map +1 -1
- package/dist/ws-framing.d.ts +18 -3
- package/dist/ws-framing.d.ts.map +1 -1
- package/dist/ws-framing.js +151 -36
- package/dist/ws-framing.js.map +1 -1
- package/dist/ws-gemini-live.cjs +2 -2
- package/dist/ws-gemini-live.js +2 -2
- package/dist/ws-live.cjs +401 -0
- package/dist/ws-live.cjs.map +1 -0
- package/dist/ws-live.js +400 -0
- package/dist/ws-live.js.map +1 -0
- package/dist/ws-realtime.cjs +46 -2
- 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 +46 -2
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +16 -4
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.js +16 -4
- package/dist/ws-responses.js.map +1 -1
- package/dist/ws-upstream.cjs +118 -0
- package/dist/ws-upstream.cjs.map +1 -0
- package/dist/ws-upstream.js +115 -0
- package/dist/ws-upstream.js.map +1 -0
- package/fixtures/openai-live-client.json +390 -0
- package/fixtures/openai-live-managed.json +709 -0
- package/package.json +4 -1
package/dist/veo-video.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { flattenHeaders, getContext, getTestId, isErrorResponse, isVideoResponse, resolveResponse, resolveStrictMode, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
|
|
2
|
-
import { matchFixtureDiagnostic } from "./router.js";
|
|
3
2
|
import { writeErrorResponse } from "./sse-writer.js";
|
|
4
|
-
import {
|
|
3
|
+
import { applyChaosAsync, responseGone } from "./chaos.js";
|
|
4
|
+
import { matchFixtureDiagnostic } from "./router.js";
|
|
5
5
|
import { resolveUpstreamUrl } from "./url.js";
|
|
6
6
|
import { buildFixtureMatch, persistFixture, prepareEgressHeaders, sanitizeHeaderValue } from "./recorder.js";
|
|
7
7
|
import { resolveProgression } from "./fal.js";
|
|
@@ -190,7 +190,7 @@ async function handleVeoVideoCreate(req, res, raw, model, fixtures, journal, def
|
|
|
190
190
|
journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
191
191
|
defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
|
|
192
192
|
} else defaults.logger.debug(`No fixture matched for Veo request (model=${model}, msg="${prompt.slice(0, 80)}")`);
|
|
193
|
-
if (
|
|
193
|
+
if (await applyChaosAsync(res, fixture, defaults.chaos, req.headers, req.url, journal, {
|
|
194
194
|
method,
|
|
195
195
|
path,
|
|
196
196
|
headers: flattenHeaders(req.headers),
|
|
@@ -245,7 +245,7 @@ async function handleVeoVideoCreate(req, res, raw, model, fixtures, journal, def
|
|
|
245
245
|
const worldGeneration = jobs.generation;
|
|
246
246
|
const response = await resolveResponse(fixture, syntheticReq);
|
|
247
247
|
if (isErrorResponse(response)) {
|
|
248
|
-
if (res
|
|
248
|
+
if (responseGone(res)) return;
|
|
249
249
|
const status = response.status ?? 500;
|
|
250
250
|
journal.add({
|
|
251
251
|
method,
|
|
@@ -261,7 +261,7 @@ async function handleVeoVideoCreate(req, res, raw, model, fixtures, journal, def
|
|
|
261
261
|
return;
|
|
262
262
|
}
|
|
263
263
|
if (!isVideoResponse(response)) {
|
|
264
|
-
if (res
|
|
264
|
+
if (responseGone(res)) return;
|
|
265
265
|
journal.add({
|
|
266
266
|
method,
|
|
267
267
|
path,
|
|
@@ -275,7 +275,7 @@ async function handleVeoVideoCreate(req, res, raw, model, fixtures, journal, def
|
|
|
275
275
|
writeErrorResponse(res, 500, geminiError(500, "Fixture response is not a video type", "INTERNAL"));
|
|
276
276
|
return;
|
|
277
277
|
}
|
|
278
|
-
if (res
|
|
278
|
+
if (responseGone(res)) return;
|
|
279
279
|
journal.add({
|
|
280
280
|
method,
|
|
281
281
|
path,
|
|
@@ -310,7 +310,7 @@ async function handleVeoVideoStatus(req, res, operationName, fixtures, journal,
|
|
|
310
310
|
setCorsHeaders(res);
|
|
311
311
|
const path = req.url ?? `/v1beta/${operationName}`;
|
|
312
312
|
const method = req.method ?? "GET";
|
|
313
|
-
if (
|
|
313
|
+
if (await applyChaosAsync(res, null, defaults.chaos, req.headers, req.url, journal, {
|
|
314
314
|
method,
|
|
315
315
|
path,
|
|
316
316
|
headers: flattenHeaders(req.headers),
|
|
@@ -365,7 +365,7 @@ async function handleVeoVideoStatus(req, res, operationName, fixtures, journal,
|
|
|
365
365
|
});
|
|
366
366
|
return;
|
|
367
367
|
}
|
|
368
|
-
if (res
|
|
368
|
+
if (responseGone(res)) return;
|
|
369
369
|
advanceVeoJob(job);
|
|
370
370
|
jobs.set(key, job);
|
|
371
371
|
journal.add({
|
|
@@ -419,7 +419,7 @@ async function proxyVeoVideoSubmit(args) {
|
|
|
419
419
|
}
|
|
420
420
|
const proxyError = (msg) => {
|
|
421
421
|
defaults.logger.error(`Veo video submit proxy failed: ${msg}`);
|
|
422
|
-
if (res
|
|
422
|
+
if (responseGone(res)) return "handled";
|
|
423
423
|
journal.add({
|
|
424
424
|
method,
|
|
425
425
|
path,
|
|
@@ -469,7 +469,7 @@ async function proxyVeoVideoSubmit(args) {
|
|
|
469
469
|
}
|
|
470
470
|
if (fetched.status === 401 || fetched.status === 403) {
|
|
471
471
|
defaults.logger.warn(`Upstream rejected the Veo video submit (${fetched.status}) — relaying the upstream status`);
|
|
472
|
-
if (res
|
|
472
|
+
if (responseGone(res)) return "handled";
|
|
473
473
|
journal.add({
|
|
474
474
|
method,
|
|
475
475
|
path,
|
|
@@ -524,7 +524,7 @@ async function proxyVeoVideoSubmit(args) {
|
|
|
524
524
|
};
|
|
525
525
|
if (jobs.generation === worldGeneration) jobs.set(`${testId}:${operationName}`, job);
|
|
526
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
|
|
527
|
+
if (responseGone(res)) return "handled";
|
|
528
528
|
journal.add({
|
|
529
529
|
method,
|
|
530
530
|
path,
|
|
@@ -571,7 +571,7 @@ async function proxyVeoVideoRecordPoll(args) {
|
|
|
571
571
|
};
|
|
572
572
|
const proxyError = (msg) => {
|
|
573
573
|
logger.error(`Veo video poll proxy failed: ${msg}`);
|
|
574
|
-
if (res
|
|
574
|
+
if (responseGone(res)) return;
|
|
575
575
|
journalProxy(502);
|
|
576
576
|
writeErrorResponse(res, 502, JSON.stringify({ error: {
|
|
577
577
|
message: `Proxy to upstream failed: ${msg}`,
|
|
@@ -605,7 +605,7 @@ async function proxyVeoVideoRecordPoll(args) {
|
|
|
605
605
|
}
|
|
606
606
|
if (fetched.status === 401 || fetched.status === 403) {
|
|
607
607
|
logger.warn(`Upstream rejected the Veo status poll for operation ${job.upstreamOperationName} (${fetched.status}) — relaying the upstream status`);
|
|
608
|
-
if (res
|
|
608
|
+
if (responseGone(res)) return;
|
|
609
609
|
journalProxy(fetched.status);
|
|
610
610
|
res.writeHead(fetched.status, { "Content-Type": fetched.contentType ?? "application/json" });
|
|
611
611
|
res.end(fetched.text);
|
|
@@ -635,7 +635,7 @@ async function proxyVeoVideoRecordPoll(args) {
|
|
|
635
635
|
name: job.operationName
|
|
636
636
|
};
|
|
637
637
|
const relayJson = () => {
|
|
638
|
-
if (res
|
|
638
|
+
if (responseGone(res)) return;
|
|
639
639
|
journalProxy(200);
|
|
640
640
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
641
641
|
res.end(JSON.stringify(relayBody));
|
package/dist/veo-video.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"veo-video.js","names":[],"sources":["../src/veo-video.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport crypto from \"node:crypto\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RecordConfig,\n VideoResponse,\n} from \"./types.js\";\nimport {\n isVideoResponse,\n isErrorResponse,\n serializeErrorResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { resolveProgression } from \"./fal.js\";\nimport {\n buildFixtureMatch,\n prepareEgressHeaders,\n persistFixture,\n sanitizeHeaderValue,\n} from \"./recorder.js\";\nimport { resolveUpstreamUrl } from \"./url.js\";\nimport { readEnvelopeText, testIdSuffix, upstreamTimeoutSignal } from \"./video-proxy-shared.js\";\n\n/**\n * Google Veo async video lifecycle mock. Submit\n * `POST /v1beta/models/{model}:predictLongRunning` returns\n * `{ name: \"operations/...\" }`; status `GET /v1beta/operations/{name}` polls\n * `done:false → done:true`. The Files-API `uri` is served AS-IS — aimock does\n * NOT proxy or capture video bytes. With `record.providers.veo` configured an\n * unmatched submit becomes a live interactive proxy (submit + poll forwarded\n * 1:1, eager fixture capture on terminal status). Strict mode still wins.\n *\n * ASSUMED Veo create body shape (the `@google/genai` :predictLongRunning\n * envelope from issue #278): `{ instances: [{ prompt, image? }], parameters }`.\n * Match surface is `instances[0].prompt` only.\n */\n\n/** Files-API-shaped placeholder served when a completed fixture omits a uri. */\nconst DEFAULT_VEO_FILES_URI = \"https://generativelanguage.googleapis.com/v1beta/files/placeholder\";\n\n// The `:predictLongRunning` envelope nests the prompt under instances[].\ninterface VeoVideoRequest {\n instances?: Array<{ prompt?: unknown; [key: string]: unknown }>;\n parameters?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n// ─── VeoVideoJobMap (TTL + bounded) ─────────────────────────────────────────\n\nexport const VEO_VIDEO_MAX_ENTRIES = 10_000;\nconst VEO_VIDEO_TTL_MS = 3_600_000; // 1 hour\n\ntype VeoVideoStatus = \"pending\" | \"in_progress\" | \"completed\" | \"failed\";\n\ninterface VeoVideoReplayJob {\n kind: \"replay\";\n operationName: string;\n status: VeoVideoStatus;\n pollCount: number;\n pollsBeforeInProgress: number;\n pollsBeforeCompleted: number;\n video: VideoResponse[\"video\"];\n /** Latch for the empty-`error` authoring warn on failed polls (once/job). */\n emptyErrorWarned?: boolean;\n}\n\ninterface VeoVideoRecordJob {\n kind: \"record\";\n operationName: string;\n status: VeoVideoStatus;\n upstreamOperationName: string;\n upstreamPollingUrl: string;\n match: Fixture[\"match\"];\n capturing?: boolean;\n}\n\nexport type VeoVideoJob = VeoVideoReplayJob | VeoVideoRecordJob;\n\ninterface VeoVideoEntry {\n job: VeoVideoJob;\n createdAt: number;\n}\n\n/**\n * Per-testId job state for the Veo video handler. Mirrors OpenRouterVideoJobMap\n * (openrouter-video.ts): lazy TTL eviction on `get`, FIFO eviction of the\n * oldest entries on `set` when over capacity, delete-before-set TTL refresh,\n * monotonic world-generation counter for reset-mid-flight detection, no\n * background sweep timer. Keys are `${testId}:${operationName}`.\n */\nexport class VeoVideoJobMap {\n private readonly entries = new Map<string, VeoVideoEntry>();\n private worldGeneration = 0;\n\n get generation(): number {\n return this.worldGeneration;\n }\n\n get(key: string): VeoVideoJob | undefined {\n const entry = this.entries.get(key);\n if (!entry) return undefined;\n if (Date.now() - entry.createdAt > VEO_VIDEO_TTL_MS) {\n this.entries.delete(key);\n return undefined;\n }\n return entry.job;\n }\n\n set(key: string, job: VeoVideoJob): void {\n this.entries.delete(key);\n this.entries.set(key, { job, createdAt: Date.now() });\n if (this.entries.size > VEO_VIDEO_MAX_ENTRIES) {\n const excess = this.entries.size - VEO_VIDEO_MAX_ENTRIES;\n const iter = this.entries.keys();\n for (let i = 0; i < excess; i++) {\n const next = iter.next();\n if (!next.done) this.entries.delete(next.value);\n }\n }\n }\n\n delete(key: string): boolean {\n return this.entries.delete(key);\n }\n\n clear(): void {\n this.entries.clear();\n this.worldGeneration++;\n }\n\n get size(): number {\n return this.entries.size;\n }\n}\n\n// ─── Job progression ────────────────────────────────────────────────────────\n\n/**\n * Maps the fixture's terminal video status onto the job lifecycle. Anything\n * that is not \"failed\" — including a fixture authored as \"processing\" — is\n * treated as completed, since this surface always drives jobs to a terminal\n * state.\n */\nfunction terminalStatus(job: VeoVideoReplayJob): VeoVideoStatus {\n return job.video.status === \"failed\" ? \"failed\" : \"completed\";\n}\n\n/**\n * Mutates a job in place to advance its state on a status poll.\n * `pending → in_progress → completed | failed` based on poll-count thresholds.\n * No-op once terminal. The in_progress threshold is checked first so a job\n * whose thresholds are equal still spends one poll in in_progress instead of\n * jumping straight to the terminal status (fal advanceJob semantics). The wire\n * value is derived from this internal status (pending/in_progress → done:false,\n * completed/failed → done:true).\n */\nfunction advanceVeoJob(job: VeoVideoReplayJob): void {\n if (job.status === \"completed\" || job.status === \"failed\") return;\n\n job.pollCount += 1;\n if (job.status === \"pending\" && job.pollCount >= job.pollsBeforeInProgress) {\n job.status = \"in_progress\";\n } else if (job.pollCount >= job.pollsBeforeCompleted) {\n job.status = terminalStatus(job);\n }\n}\n\n/** Gemini-style error envelope used by every Veo error response. */\nfunction geminiError(code: number, message: string, status: string): string {\n return JSON.stringify({ error: { code, message, status } });\n}\n\n/**\n * The done:true operation body for a completed replay job — the Files-API uri\n * is served as-is (the fixture's stored `video.url`, or the documented\n * placeholder when omitted).\n */\nfunction completedOperationBody(operationName: string, uri: string): Record<string, unknown> {\n return {\n name: operationName,\n done: true,\n response: {\n generateVideoResponse: {\n generatedSamples: [{ video: { uri } }],\n },\n },\n };\n}\n\n// ─── POST /v1beta/models/{model}:predictLongRunning — submit ─────────────────\n\nexport async function handleVeoVideoCreate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n model: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n jobs: VeoVideoJobMap,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? `/v1beta/models/${model}:predictLongRunning`;\n const method = req.method ?? \"POST\";\n\n let videoReq: VeoVideoRequest;\n try {\n videoReq = JSON.parse(raw) as VeoVideoRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(res, 400, geminiError(400, `Malformed JSON: ${detail}`, \"INVALID_ARGUMENT\"));\n return;\n }\n\n // Reject bodies that parsed but are not a JSON object before touching fields.\n if (videoReq === null || typeof videoReq !== \"object\" || Array.isArray(videoReq)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n geminiError(400, \"Request body must be a JSON object\", \"INVALID_ARGUMENT\"),\n );\n return;\n }\n\n // Prompt lives at instances[0].prompt (the @google/genai envelope).\n const firstInstance = Array.isArray(videoReq.instances) ? videoReq.instances[0] : undefined;\n const prompt = firstInstance?.prompt;\n // Synthesize a structurally valid journal body for field-validation 400s\n // (JournalEntry.body is ChatCompletionRequest | null). Strip reserved\n // underscore-prefixed keys so a request cannot spoof handler discriminators.\n const sanitized = Object.fromEntries(\n Object.entries(videoReq).filter(([key]) => !key.startsWith(\"_\")),\n );\n const parsedBody: ChatCompletionRequest = { ...sanitized, model, messages: [] };\n\n if (typeof prompt !== \"string\" || !prompt) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: parsedBody,\n response: { status: 400, fixture: null },\n });\n const message =\n prompt === undefined\n ? \"Missing required parameter: 'instances[0].prompt'\"\n : \"Invalid type for parameter: 'instances[0].prompt' must be a non-empty string\";\n writeErrorResponse(res, 400, geminiError(400, message, \"INVALID_ARGUMENT\"));\n return;\n }\n\n const syntheticReq: ChatCompletionRequest = {\n model,\n messages: [{ role: \"user\", content: prompt }],\n _endpointType: \"video\",\n _videoProvider: \"veo\",\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(\n `No fixture matched for Veo request (model=${model}, msg=\"${prompt.slice(0, 80)}\")`,\n );\n }\n\n // Chaos rolls AFTER body validation and fixture matching (mirrors the\n // OpenRouter submit). An unmatched submit is proxied upstream when record\n // mode has a veo provider AND strict would not win — label that roll \"proxy\".\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture\n ? \"fixture\"\n : resolveStrictMode(defaults.strict, req.headers)\n ? \"internal\"\n : defaults.record?.providers.veo\n ? \"proxy\"\n : \"internal\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n // Strict mode wins over record: a strict no-match fails loudly with 503.\n if (resolveStrictMode(defaults.strict, req.headers)) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(res, 503, geminiError(503, strictMessage, \"UNAVAILABLE\"));\n return;\n }\n\n if (defaults.record) {\n const outcome = await proxyVeoVideoSubmit({\n req,\n res,\n raw,\n syntheticReq,\n record: defaults.record,\n journal,\n defaults,\n jobs,\n method,\n path,\n });\n if (outcome === \"handled\") return;\n // outcome === \"no_upstream\" — fall through to 404 (fal convention).\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(res, 404, geminiError(404, \"No fixture matched\", \"NOT_FOUND\"));\n return;\n }\n\n // World-generation snapshot (mirrors the record-submit guard): a fixtures\n // reset can land while the fixture's ResponseFactory below is awaited, and\n // the job insertion at the bottom must not seed the NEW world.\n const worldGeneration = jobs.generation;\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n if (res.destroyed || res.writableEnded) return;\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isVideoResponse(response)) {\n if (res.destroyed || res.writableEnded) return;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n geminiError(500, \"Fixture response is not a video type\", \"INTERNAL\"),\n );\n return;\n }\n\n if (res.destroyed || res.writableEnded) return;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const fixtureStatus: string = response.video.status;\n if (fixtureStatus === \"processing\") {\n defaults.logger.warn(\n `Veo video fixture has status \"processing\" — treated as completed for the operations lifecycle`,\n );\n } else if (fixtureStatus !== \"completed\" && fixtureStatus !== \"failed\") {\n defaults.logger.warn(\n `Veo video fixture has unknown status \"${fixtureStatus}\" — treating as completed`,\n );\n }\n\n const operationName = `operations/${crypto.randomUUID()}`;\n const progression = resolveProgression(defaults.veoVideo);\n const job: VeoVideoReplayJob = {\n kind: \"replay\",\n operationName,\n status: \"pending\",\n pollCount: 0,\n pollsBeforeInProgress: progression.pollsBeforeInProgress,\n pollsBeforeCompleted: progression.pollsBeforeCompleted,\n video: { ...response.video },\n };\n // Default 0/0 progression seeds the job terminal at submit; the submit\n // envelope still reports a not-done operation.\n if (progression.pollsBeforeCompleted === 0) {\n job.status = terminalStatus(job);\n }\n if (jobs.generation === worldGeneration) {\n jobs.set(`${testId}:${operationName}`, job);\n } else {\n defaults.logger.warn(\n `Veo video submit resolved after a fixtures reset — not inserting operation ${operationName} into the new world (its polls will 404)`,\n );\n }\n\n // Embed the testId in the RETURNED name (not the stored key) so a multi-tenant\n // client can poll the opaque name without an x-test-id header — the testId\n // travels via getTestId's `?testId=` fallback. Mirrors OpenRouter's polling_url\n // treatment; testIdSuffix is \"\" for the default testId, keeping the name bare.\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ name: operationName + testIdSuffix(testId, \"?\") }));\n}\n\n// ─── GET /v1beta/operations/{name} — status poll ─────────────────────────────\n\nexport async function handleVeoVideoStatus(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n operationName: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n jobs: VeoVideoJobMap,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? `/v1beta/${operationName}`;\n const method = req.method ?? \"GET\";\n\n // Chaos rolls BEFORE the job lookup — the label stays \"internal\" even in\n // record mode, and a chaos-dropped poll never reaches the upstream.\n if (\n applyChaos(\n res,\n null,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: null },\n \"internal\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n const testId = getTestId(req);\n const key = `${testId}:${operationName}`;\n const job = jobs.get(key);\n\n if (!job) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 404, fixture: null },\n });\n writeErrorResponse(\n res,\n 404,\n geminiError(404, `Operation ${operationName} not found`, \"NOT_FOUND\"),\n );\n return;\n }\n\n if (job.kind === \"record\") {\n // Strict means nothing reaches an upstream — a record job's polls are pure\n // upstream proxies, so an effective-strict request is refused with 503.\n if (resolveStrictMode(defaults.strict, req.headers)) {\n defaults.logger.error(\n `STRICT: Veo operation ${operationName} is proxied live upstream (record mode) — refusing the upstream poll`,\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n geminiError(\n 503,\n `Strict mode: Veo operation ${operationName} is proxied live upstream (record mode) — nothing reaches an upstream under strict mode`,\n \"UNAVAILABLE\",\n ),\n );\n return;\n }\n await proxyVeoVideoRecordPoll({\n req,\n res,\n job,\n key,\n testId,\n fixtures,\n journal,\n defaults,\n jobs,\n method,\n path,\n });\n return;\n }\n\n // Guard BEFORE advancing or journaling (file convention): a disconnected\n // client consumes no progression step or TTL refresh.\n if (res.destroyed || res.writableEnded) return;\n advanceVeoJob(job);\n // Refresh the TTL on every replay poll (delete-before-set also moves the\n // entry to the back of the FIFO eviction order).\n jobs.set(key, job);\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 200, fixture: null },\n });\n\n let body: Record<string, unknown>;\n if (job.status === \"completed\") {\n // Truthy guard (not `??`): an empty-string fixture url must still fall back\n // to the placeholder Files-API uri rather than being served as an empty uri.\n const rawUrl = job.video.url;\n body = completedOperationBody(\n operationName,\n typeof rawUrl === \"string\" && rawUrl ? rawUrl : DEFAULT_VEO_FILES_URI,\n );\n } else if (job.status === \"failed\") {\n if (job.video.error === \"\" && !job.emptyErrorWarned) {\n job.emptyErrorWarned = true;\n defaults.logger.warn(\n `Veo video fixture for operation ${operationName} has an empty error message — using the default`,\n );\n }\n body = {\n name: operationName,\n done: true,\n error: { code: 3, message: job.video.error || \"Video generation failed\" },\n };\n } else {\n body = { name: operationName, done: false };\n }\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n\n// ─── Record mode: live interactive proxy (submit) ───────────────────────────\n\n/**\n * Proxy an unmatched Veo submit to the configured upstream and answer the\n * client with a mock-rewritten envelope: a fresh aimock operation name. The\n * upstream lifecycle is driven interactively by the client's own polls.\n *\n * Returns \"no_upstream\" when record mode has no veo provider URL — the caller\n * falls through to its 404 branch (fal convention).\n */\nasync function proxyVeoVideoSubmit(args: {\n req: http.IncomingMessage;\n res: http.ServerResponse;\n raw: string;\n syntheticReq: ChatCompletionRequest;\n record: RecordConfig;\n journal: Journal;\n defaults: HandlerDefaults;\n jobs: VeoVideoJobMap;\n method: string;\n path: string;\n}): Promise<\"handled\" | \"no_upstream\"> {\n const { req, res, raw, syntheticReq, record, journal, defaults, jobs, method, path } = args;\n\n const upstreamBase = record.providers.veo;\n if (!upstreamBase) {\n defaults.logger.warn(`No upstream URL configured for provider \"veo\" — cannot proxy`);\n return \"no_upstream\";\n }\n\n const proxyError = (msg: string): \"handled\" => {\n defaults.logger.error(`Veo video submit proxy failed: ${msg}`);\n if (res.destroyed || res.writableEnded) return \"handled\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 502,\n fixture: null,\n source: \"proxy\",\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 502,\n JSON.stringify({\n error: { message: `Proxy to upstream failed: ${msg}`, type: \"proxy_error\" },\n }),\n );\n return \"handled\";\n };\n\n const submitPath = `/v1beta/models/${syntheticReq.model}:predictLongRunning`;\n let submitUrl: URL;\n let upstreamOrigin: string;\n try {\n submitUrl = resolveUpstreamUrl(upstreamBase, submitPath);\n upstreamOrigin = new URL(upstreamBase).origin;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return proxyError(`Invalid upstream URL: ${upstreamBase} (${msg})`);\n }\n\n defaults.logger.warn(\n `NO FIXTURE MATCH — proxying Veo video submit to ${upstreamBase}${submitPath}`,\n );\n\n // World-generation snapshot: a fixtures reset landing during the upstream\n // fetch clears the job map — the insertion guard compares against this.\n const worldGeneration = jobs.generation;\n\n let fetched: { status: number; contentType: string | null; text: string };\n try {\n const headers = prepareEgressHeaders(req, submitUrl, \"veo\", record.providerKeys?.veo);\n if (!headers) return proxyError(\"No configured provider credential\");\n const upstreamRes = await fetch(submitUrl, {\n method: \"POST\",\n headers,\n body: raw,\n signal: upstreamTimeoutSignal(record),\n });\n fetched = {\n status: upstreamRes.status,\n contentType: upstreamRes.headers.get(\"content-type\"),\n text: await readEnvelopeText(upstreamRes, record),\n };\n } catch (err) {\n return proxyError(err instanceof Error ? err.message : \"Unknown proxy error\");\n }\n\n if (fetched.status === 401 || fetched.status === 403) {\n // Real-API fidelity: relay an upstream auth rejection verbatim.\n defaults.logger.warn(\n `Upstream rejected the Veo video submit (${fetched.status}) — relaying the upstream status`,\n );\n if (res.destroyed || res.writableEnded) return \"handled\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: fetched.status,\n fixture: null,\n source: \"proxy\",\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n res.writeHead(fetched.status, { \"Content-Type\": fetched.contentType ?? \"application/json\" });\n res.end(fetched.text);\n return \"handled\";\n }\n\n let upstreamOperationName: string;\n {\n if (fetched.status < 200 || fetched.status >= 300) {\n return proxyError(`Submit ${fetched.status}: ${fetched.text.slice(0, 200)}`);\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(fetched.text);\n } catch {\n return proxyError(`Submit returned non-JSON: ${fetched.text.slice(0, 200)}`);\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return proxyError(\"Submit response is not a JSON object\");\n }\n upstreamOperationName = String((parsed as Record<string, unknown>).name ?? \"\").trim();\n if (!upstreamOperationName) {\n return proxyError(\"Submit response missing name\");\n }\n }\n\n // Veo has no polling_url field — the poll URL is constructed from the\n // provider base + `/v1beta/{operationName}`. Origin-validate the constructed\n // URL (the client's Authorization travels to it on every poll); fall back to\n // a string on the provider base on any parse failure.\n let upstreamPollingUrl: string;\n try {\n const constructed = resolveUpstreamUrl(\n upstreamBase,\n `/v1beta/${upstreamOperationName.replace(/^\\/+/, \"\")}`,\n );\n if (constructed.origin === upstreamOrigin) {\n upstreamPollingUrl = constructed.toString();\n } else {\n defaults.logger.warn(\n `Constructed Veo poll URL origin ${constructed.origin} differs from the configured provider origin ${upstreamOrigin} — using the provider-origin URL`,\n );\n upstreamPollingUrl = `${upstreamOrigin}/v1beta/${upstreamOperationName.replace(/^\\/+/, \"\")}`;\n }\n } catch {\n defaults.logger.warn(\n `Could not construct the Veo poll URL for ${upstreamOperationName} — using the provider-origin URL`,\n );\n upstreamPollingUrl = `${upstreamOrigin}/v1beta/${upstreamOperationName.replace(/^\\/+/, \"\")}`;\n }\n\n const testId = getTestId(req);\n const matchRequest = defaults.requestTransform\n ? defaults.requestTransform(syntheticReq)\n : syntheticReq;\n const operationName = `operations/${crypto.randomUUID()}`;\n const job: VeoVideoRecordJob = {\n kind: \"record\",\n operationName,\n status: \"pending\",\n upstreamOperationName,\n upstreamPollingUrl,\n match: buildFixtureMatch(matchRequest, record),\n };\n if (jobs.generation === worldGeneration) {\n jobs.set(`${testId}:${operationName}`, job);\n } else {\n defaults.logger.warn(\n `Veo video submit for upstream operation ${upstreamOperationName} completed after a fixtures reset — not inserting the job into the new world (its polls will 404)`,\n );\n }\n\n if (res.destroyed || res.writableEnded) return \"handled\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 200,\n fixture: null,\n source: \"proxy\",\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n\n // Embed the testId in the RETURNED name (see the replay submit) so record-mode\n // multi-tenant polls resolve via `?testId=`; the upstream poll still uses the\n // bare job.operationName / job.upstreamPollingUrl, untouched by the suffix.\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ name: operationName + testIdSuffix(testId, \"?\") }));\n return \"handled\";\n}\n\n// ─── Record mode: live interactive proxy (poll + eager capture) ─────────────\n\n/**\n * Proxy a status poll for a record-mode Veo job 1:1 to the upstream and relay\n * the result with the operation name rewritten to the mock operation (the\n * Files-API uri inside `response` passes through verbatim — aimock never\n * downloads bytes). When the upstream reports `done:true` with a `response`,\n * the rewritten body is relayed IMMEDIATELY and the eager capture (persist the\n * uri as a fixture, mutate to a terminal replay job) runs DETACHED. A\n * `done:true` with an `error` persists a failed fixture synchronously. Under\n * `record.proxyOnly` nothing is captured, persisted, or mutated. Every\n * post-await map mutation is identity-guarded.\n */\nasync function proxyVeoVideoRecordPoll(args: {\n req: http.IncomingMessage;\n res: http.ServerResponse;\n job: VeoVideoRecordJob;\n key: string;\n testId: string;\n fixtures: Fixture[];\n journal: Journal;\n defaults: HandlerDefaults;\n jobs: VeoVideoJobMap;\n method: string;\n path: string;\n}): Promise<void> {\n const { req, res, job, key, testId, fixtures, journal, defaults, jobs, method, path } = args;\n const logger = defaults.logger;\n\n const journalProxy = (status: number): void => {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: {\n status,\n fixture: null,\n source: \"proxy\",\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n };\n\n const proxyError = (msg: string): void => {\n logger.error(`Veo video poll proxy failed: ${msg}`);\n if (res.destroyed || res.writableEnded) return;\n journalProxy(502);\n writeErrorResponse(\n res,\n 502,\n JSON.stringify({\n error: { message: `Proxy to upstream failed: ${msg}`, type: \"proxy_error\" },\n }),\n );\n };\n\n // Recording can be disabled mid-flight — an orphaned record job must fail\n // loudly before contacting the upstream.\n const record = defaults.record;\n if (!record) {\n proxyError(\"record mode is no longer configured for an in-flight record job\");\n return;\n }\n\n let fetched: { status: number; contentType: string | null; text: string };\n try {\n const target = new URL(job.upstreamPollingUrl);\n const headers = prepareEgressHeaders(req, target, \"veo\", record.providerKeys?.veo);\n if (!headers) {\n proxyError(\"No configured provider credential\");\n return;\n }\n const upstreamRes = await fetch(job.upstreamPollingUrl, {\n headers,\n signal: upstreamTimeoutSignal(record),\n });\n fetched = {\n status: upstreamRes.status,\n contentType: upstreamRes.headers.get(\"content-type\"),\n text: await readEnvelopeText(upstreamRes, record),\n };\n } catch (err) {\n proxyError(err instanceof Error ? err.message : \"Unknown proxy error\");\n return;\n }\n\n if (fetched.status === 401 || fetched.status === 403) {\n logger.warn(\n `Upstream rejected the Veo status poll for operation ${job.upstreamOperationName} (${fetched.status}) — relaying the upstream status`,\n );\n if (res.destroyed || res.writableEnded) return;\n journalProxy(fetched.status);\n res.writeHead(fetched.status, { \"Content-Type\": fetched.contentType ?? \"application/json\" });\n res.end(fetched.text);\n return;\n }\n\n let upstreamBody: Record<string, unknown>;\n {\n if (fetched.status < 200 || fetched.status >= 300) {\n proxyError(`Status ${fetched.status}: ${fetched.text.slice(0, 200)}`);\n return;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(fetched.text);\n } catch {\n proxyError(`Status returned non-JSON: ${fetched.text.slice(0, 200)}`);\n return;\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n proxyError(\"Status response is not a JSON object\");\n return;\n }\n upstreamBody = parsed as Record<string, unknown>;\n }\n\n // Rewrite ONLY the top-level operation name so no upstream identifier\n // escapes — the `response` (carrying the Files-API uri the client needs) and\n // every other field pass through verbatim.\n const relayBody: Record<string, unknown> = { ...upstreamBody, name: job.operationName };\n\n const relayJson = (): void => {\n if (res.destroyed || res.writableEnded) return;\n journalProxy(200);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(relayBody));\n };\n\n const done = upstreamBody.done === true;\n const upstreamError = upstreamBody.error;\n const hasError =\n upstreamError !== undefined && upstreamError !== null && typeof upstreamError === \"object\";\n\n if (!done) {\n // Non-terminal: identity-guarded status write + TTL refresh, relay.\n if (jobs.get(key) === job) {\n if (!job.capturing && job.status !== \"completed\" && job.status !== \"failed\") {\n job.status = \"in_progress\";\n }\n jobs.set(key, job);\n }\n relayJson();\n return;\n }\n\n if (hasError) {\n // Terminal failure: persist a failed fixture synchronously (mirrors the\n // OpenRouter failed branch), then relay.\n if (record.proxyOnly) {\n if (jobs.get(key) === job) {\n job.status = \"failed\";\n jobs.set(key, job);\n }\n relayJson();\n return;\n }\n if (jobs.get(key) !== job) {\n // A concurrent terminal poll already persisted/replaced the entry, or a\n // fixtures reset cleared the world — relay without a duplicate fixture.\n relayJson();\n return;\n }\n const errObj = upstreamError as Record<string, unknown>;\n const rawMessage = errObj.message;\n const error = typeof rawMessage === \"string\" && rawMessage ? rawMessage : undefined;\n const video: VideoResponse[\"video\"] = {\n id: job.upstreamOperationName,\n status: \"failed\",\n ...(error !== undefined ? { error } : {}),\n };\n const persistResult = persistFixture({\n record,\n providerKey: \"veo\",\n testId,\n fixture: { match: job.match, response: { video } },\n fixtures,\n logger,\n });\n if (persistResult.kind === \"failed\" && !res.headersSent) {\n res.setHeader(\"X-AIMock-Record-Error\", sanitizeHeaderValue(persistResult.error));\n }\n jobs.set(key, {\n kind: \"replay\",\n operationName: job.operationName,\n status: \"failed\",\n pollCount: 0,\n pollsBeforeInProgress: 0,\n pollsBeforeCompleted: 0,\n video: { ...video },\n });\n relayJson();\n return;\n }\n\n // Terminal success (done:true with a response).\n if (record.proxyOnly) {\n if (jobs.get(key) === job) {\n job.status = \"completed\";\n jobs.set(key, job);\n }\n relayJson();\n return;\n }\n\n if (job.capturing || jobs.get(key) !== job) {\n // A concurrent poll already entered the capture sequence, or this\n // reference is detached — relay without starting a second capture and\n // never re-insert the stale object.\n if (jobs.get(key) === job) {\n jobs.set(key, job); // TTL refresh\n }\n relayJson();\n return;\n }\n\n // Open the capturing window SYNCHRONOUSLY before the first await.\n job.capturing = true;\n job.status = \"completed\";\n jobs.set(key, job); // TTL refresh — identity-checked above, no await since\n\n // Relay IMMEDIATELY; the persist + replay mutation runs DETACHED.\n relayJson();\n\n void captureVeoVideoRecordFixture({\n job,\n key,\n testId,\n fixtures,\n defaults,\n jobs,\n record,\n upstreamBody,\n });\n}\n\n/**\n * Detached eager-capture for a completed Veo record job. Extracts the\n * Files-API uri from the terminal poll body (NO download), persists a\n * fixture, and mutates the map entry into a terminal replay job. Every failure\n * is handled internally; the returned promise NEVER rejects. World-generation\n * guarded immediately before persist.\n */\nasync function captureVeoVideoRecordFixture(args: {\n job: VeoVideoRecordJob;\n key: string;\n testId: string;\n fixtures: Fixture[];\n defaults: HandlerDefaults;\n jobs: VeoVideoJobMap;\n record: RecordConfig;\n upstreamBody: Record<string, unknown>;\n}): Promise<void> {\n const { job, key, testId, fixtures, defaults, jobs, record, upstreamBody } = args;\n const logger = defaults.logger;\n\n try {\n // Drill into response.generateVideoResponse.generatedSamples[0].video.uri.\n let uri: string | undefined;\n const response = upstreamBody.response;\n if (response !== null && typeof response === \"object\" && !Array.isArray(response)) {\n const gvr = (response as Record<string, unknown>).generateVideoResponse;\n if (gvr !== null && typeof gvr === \"object\" && !Array.isArray(gvr)) {\n const samples = (gvr as Record<string, unknown>).generatedSamples;\n const first = Array.isArray(samples) ? samples[0] : undefined;\n if (first !== null && typeof first === \"object\" && !Array.isArray(first)) {\n const video = (first as Record<string, unknown>).video;\n if (video !== null && typeof video === \"object\" && !Array.isArray(video)) {\n const rawUri = (video as Record<string, unknown>).uri;\n if (typeof rawUri === \"string\" && rawUri) uri = rawUri;\n }\n }\n }\n }\n if (!uri) {\n logger.warn(\n `Upstream Veo operation ${job.upstreamOperationName} completed without a usable Files-API uri — capture skipped, nothing persisted; the next completed poll retries`,\n );\n return;\n }\n\n const video: VideoResponse[\"video\"] = {\n id: job.upstreamOperationName,\n status: \"completed\",\n url: uri,\n };\n\n // World-generation guard: a fixtures reset landing during the await above\n // clears the job map — map identity is a valid proxy for \"same world\".\n if (jobs.get(key) !== job) {\n logger.warn(\n `Veo video capture for operation ${job.upstreamOperationName} discarded: the job map no longer holds this job (fixtures reset or TTL eviction) — nothing persisted`,\n );\n return;\n }\n persistFixture({\n record,\n providerKey: \"veo\",\n testId,\n fixture: { match: job.match, response: { video } },\n fixtures,\n logger,\n });\n\n // Mutate the entry into a terminal replay job: later polls serve locally.\n if (jobs.get(key) === job) {\n jobs.set(key, {\n kind: \"replay\",\n operationName: job.operationName,\n status: \"completed\",\n pollCount: 0,\n pollsBeforeInProgress: 0,\n pollsBeforeCompleted: 0,\n video: { ...video },\n });\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.error(\n `Veo video capture for operation ${job.upstreamOperationName} failed unexpectedly (${msg}) — fixture not persisted; the job keeps proxying live`,\n );\n } finally {\n // Close the capturing window when the map still holds this record job (a\n // step threw before the replay mutation). On the success path the entry\n // was just replaced; the detached object deliberately keeps capturing=true.\n if (jobs.get(key) === job) {\n job.capturing = false;\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"veo-video.js","names":[],"sources":["../src/veo-video.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport crypto from \"node:crypto\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RecordConfig,\n VideoResponse,\n} from \"./types.js\";\nimport {\n isVideoResponse,\n isErrorResponse,\n serializeErrorResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaosAsync, responseGone } from \"./chaos.js\";\nimport { resolveProgression } from \"./fal.js\";\nimport {\n buildFixtureMatch,\n prepareEgressHeaders,\n persistFixture,\n sanitizeHeaderValue,\n} from \"./recorder.js\";\nimport { resolveUpstreamUrl } from \"./url.js\";\nimport { readEnvelopeText, testIdSuffix, upstreamTimeoutSignal } from \"./video-proxy-shared.js\";\n\n/**\n * Google Veo async video lifecycle mock. Submit\n * `POST /v1beta/models/{model}:predictLongRunning` returns\n * `{ name: \"operations/...\" }`; status `GET /v1beta/operations/{name}` polls\n * `done:false → done:true`. The Files-API `uri` is served AS-IS — aimock does\n * NOT proxy or capture video bytes. With `record.providers.veo` configured an\n * unmatched submit becomes a live interactive proxy (submit + poll forwarded\n * 1:1, eager fixture capture on terminal status). Strict mode still wins.\n *\n * ASSUMED Veo create body shape (the `@google/genai` :predictLongRunning\n * envelope from issue #278): `{ instances: [{ prompt, image? }], parameters }`.\n * Match surface is `instances[0].prompt` only.\n */\n\n/** Files-API-shaped placeholder served when a completed fixture omits a uri. */\nconst DEFAULT_VEO_FILES_URI = \"https://generativelanguage.googleapis.com/v1beta/files/placeholder\";\n\n// The `:predictLongRunning` envelope nests the prompt under instances[].\ninterface VeoVideoRequest {\n instances?: Array<{ prompt?: unknown; [key: string]: unknown }>;\n parameters?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n// ─── VeoVideoJobMap (TTL + bounded) ─────────────────────────────────────────\n\nexport const VEO_VIDEO_MAX_ENTRIES = 10_000;\nconst VEO_VIDEO_TTL_MS = 3_600_000; // 1 hour\n\ntype VeoVideoStatus = \"pending\" | \"in_progress\" | \"completed\" | \"failed\";\n\ninterface VeoVideoReplayJob {\n kind: \"replay\";\n operationName: string;\n status: VeoVideoStatus;\n pollCount: number;\n pollsBeforeInProgress: number;\n pollsBeforeCompleted: number;\n video: VideoResponse[\"video\"];\n /** Latch for the empty-`error` authoring warn on failed polls (once/job). */\n emptyErrorWarned?: boolean;\n}\n\ninterface VeoVideoRecordJob {\n kind: \"record\";\n operationName: string;\n status: VeoVideoStatus;\n upstreamOperationName: string;\n upstreamPollingUrl: string;\n match: Fixture[\"match\"];\n capturing?: boolean;\n}\n\nexport type VeoVideoJob = VeoVideoReplayJob | VeoVideoRecordJob;\n\ninterface VeoVideoEntry {\n job: VeoVideoJob;\n createdAt: number;\n}\n\n/**\n * Per-testId job state for the Veo video handler. Mirrors OpenRouterVideoJobMap\n * (openrouter-video.ts): lazy TTL eviction on `get`, FIFO eviction of the\n * oldest entries on `set` when over capacity, delete-before-set TTL refresh,\n * monotonic world-generation counter for reset-mid-flight detection, no\n * background sweep timer. Keys are `${testId}:${operationName}`.\n */\nexport class VeoVideoJobMap {\n private readonly entries = new Map<string, VeoVideoEntry>();\n private worldGeneration = 0;\n\n get generation(): number {\n return this.worldGeneration;\n }\n\n get(key: string): VeoVideoJob | undefined {\n const entry = this.entries.get(key);\n if (!entry) return undefined;\n if (Date.now() - entry.createdAt > VEO_VIDEO_TTL_MS) {\n this.entries.delete(key);\n return undefined;\n }\n return entry.job;\n }\n\n set(key: string, job: VeoVideoJob): void {\n this.entries.delete(key);\n this.entries.set(key, { job, createdAt: Date.now() });\n if (this.entries.size > VEO_VIDEO_MAX_ENTRIES) {\n const excess = this.entries.size - VEO_VIDEO_MAX_ENTRIES;\n const iter = this.entries.keys();\n for (let i = 0; i < excess; i++) {\n const next = iter.next();\n if (!next.done) this.entries.delete(next.value);\n }\n }\n }\n\n delete(key: string): boolean {\n return this.entries.delete(key);\n }\n\n clear(): void {\n this.entries.clear();\n this.worldGeneration++;\n }\n\n get size(): number {\n return this.entries.size;\n }\n}\n\n// ─── Job progression ────────────────────────────────────────────────────────\n\n/**\n * Maps the fixture's terminal video status onto the job lifecycle. Anything\n * that is not \"failed\" — including a fixture authored as \"processing\" — is\n * treated as completed, since this surface always drives jobs to a terminal\n * state.\n */\nfunction terminalStatus(job: VeoVideoReplayJob): VeoVideoStatus {\n return job.video.status === \"failed\" ? \"failed\" : \"completed\";\n}\n\n/**\n * Mutates a job in place to advance its state on a status poll.\n * `pending → in_progress → completed | failed` based on poll-count thresholds.\n * No-op once terminal. The in_progress threshold is checked first so a job\n * whose thresholds are equal still spends one poll in in_progress instead of\n * jumping straight to the terminal status (fal advanceJob semantics). The wire\n * value is derived from this internal status (pending/in_progress → done:false,\n * completed/failed → done:true).\n */\nfunction advanceVeoJob(job: VeoVideoReplayJob): void {\n if (job.status === \"completed\" || job.status === \"failed\") return;\n\n job.pollCount += 1;\n if (job.status === \"pending\" && job.pollCount >= job.pollsBeforeInProgress) {\n job.status = \"in_progress\";\n } else if (job.pollCount >= job.pollsBeforeCompleted) {\n job.status = terminalStatus(job);\n }\n}\n\n/** Gemini-style error envelope used by every Veo error response. */\nfunction geminiError(code: number, message: string, status: string): string {\n return JSON.stringify({ error: { code, message, status } });\n}\n\n/**\n * The done:true operation body for a completed replay job — the Files-API uri\n * is served as-is (the fixture's stored `video.url`, or the documented\n * placeholder when omitted).\n */\nfunction completedOperationBody(operationName: string, uri: string): Record<string, unknown> {\n return {\n name: operationName,\n done: true,\n response: {\n generateVideoResponse: {\n generatedSamples: [{ video: { uri } }],\n },\n },\n };\n}\n\n// ─── POST /v1beta/models/{model}:predictLongRunning — submit ─────────────────\n\nexport async function handleVeoVideoCreate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n model: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n jobs: VeoVideoJobMap,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? `/v1beta/models/${model}:predictLongRunning`;\n const method = req.method ?? \"POST\";\n\n let videoReq: VeoVideoRequest;\n try {\n videoReq = JSON.parse(raw) as VeoVideoRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(res, 400, geminiError(400, `Malformed JSON: ${detail}`, \"INVALID_ARGUMENT\"));\n return;\n }\n\n // Reject bodies that parsed but are not a JSON object before touching fields.\n if (videoReq === null || typeof videoReq !== \"object\" || Array.isArray(videoReq)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n geminiError(400, \"Request body must be a JSON object\", \"INVALID_ARGUMENT\"),\n );\n return;\n }\n\n // Prompt lives at instances[0].prompt (the @google/genai envelope).\n const firstInstance = Array.isArray(videoReq.instances) ? videoReq.instances[0] : undefined;\n const prompt = firstInstance?.prompt;\n // Synthesize a structurally valid journal body for field-validation 400s\n // (JournalEntry.body is ChatCompletionRequest | null). Strip reserved\n // underscore-prefixed keys so a request cannot spoof handler discriminators.\n const sanitized = Object.fromEntries(\n Object.entries(videoReq).filter(([key]) => !key.startsWith(\"_\")),\n );\n const parsedBody: ChatCompletionRequest = { ...sanitized, model, messages: [] };\n\n if (typeof prompt !== \"string\" || !prompt) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: parsedBody,\n response: { status: 400, fixture: null },\n });\n const message =\n prompt === undefined\n ? \"Missing required parameter: 'instances[0].prompt'\"\n : \"Invalid type for parameter: 'instances[0].prompt' must be a non-empty string\";\n writeErrorResponse(res, 400, geminiError(400, message, \"INVALID_ARGUMENT\"));\n return;\n }\n\n const syntheticReq: ChatCompletionRequest = {\n model,\n messages: [{ role: \"user\", content: prompt }],\n _endpointType: \"video\",\n _videoProvider: \"veo\",\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(\n `No fixture matched for Veo request (model=${model}, msg=\"${prompt.slice(0, 80)}\")`,\n );\n }\n\n // Chaos rolls AFTER body validation and fixture matching (mirrors the\n // OpenRouter submit). An unmatched submit is proxied upstream when record\n // mode has a veo provider AND strict would not win — label that roll \"proxy\".\n if (\n await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture\n ? \"fixture\"\n : resolveStrictMode(defaults.strict, req.headers)\n ? \"internal\"\n : defaults.record?.providers.veo\n ? \"proxy\"\n : \"internal\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n // Strict mode wins over record: a strict no-match fails loudly with 503.\n if (resolveStrictMode(defaults.strict, req.headers)) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(res, 503, geminiError(503, strictMessage, \"UNAVAILABLE\"));\n return;\n }\n\n if (defaults.record) {\n const outcome = await proxyVeoVideoSubmit({\n req,\n res,\n raw,\n syntheticReq,\n record: defaults.record,\n journal,\n defaults,\n jobs,\n method,\n path,\n });\n if (outcome === \"handled\") return;\n // outcome === \"no_upstream\" — fall through to 404 (fal convention).\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(res, 404, geminiError(404, \"No fixture matched\", \"NOT_FOUND\"));\n return;\n }\n\n // World-generation snapshot (mirrors the record-submit guard): a fixtures\n // reset can land while the fixture's ResponseFactory below is awaited, and\n // the job insertion at the bottom must not seed the NEW world.\n const worldGeneration = jobs.generation;\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n if (responseGone(res)) return;\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isVideoResponse(response)) {\n if (responseGone(res)) return;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n geminiError(500, \"Fixture response is not a video type\", \"INTERNAL\"),\n );\n return;\n }\n\n if (responseGone(res)) return;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const fixtureStatus: string = response.video.status;\n if (fixtureStatus === \"processing\") {\n defaults.logger.warn(\n `Veo video fixture has status \"processing\" — treated as completed for the operations lifecycle`,\n );\n } else if (fixtureStatus !== \"completed\" && fixtureStatus !== \"failed\") {\n defaults.logger.warn(\n `Veo video fixture has unknown status \"${fixtureStatus}\" — treating as completed`,\n );\n }\n\n const operationName = `operations/${crypto.randomUUID()}`;\n const progression = resolveProgression(defaults.veoVideo);\n const job: VeoVideoReplayJob = {\n kind: \"replay\",\n operationName,\n status: \"pending\",\n pollCount: 0,\n pollsBeforeInProgress: progression.pollsBeforeInProgress,\n pollsBeforeCompleted: progression.pollsBeforeCompleted,\n video: { ...response.video },\n };\n // Default 0/0 progression seeds the job terminal at submit; the submit\n // envelope still reports a not-done operation.\n if (progression.pollsBeforeCompleted === 0) {\n job.status = terminalStatus(job);\n }\n if (jobs.generation === worldGeneration) {\n jobs.set(`${testId}:${operationName}`, job);\n } else {\n defaults.logger.warn(\n `Veo video submit resolved after a fixtures reset — not inserting operation ${operationName} into the new world (its polls will 404)`,\n );\n }\n\n // Embed the testId in the RETURNED name (not the stored key) so a multi-tenant\n // client can poll the opaque name without an x-test-id header — the testId\n // travels via getTestId's `?testId=` fallback. Mirrors OpenRouter's polling_url\n // treatment; testIdSuffix is \"\" for the default testId, keeping the name bare.\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ name: operationName + testIdSuffix(testId, \"?\") }));\n}\n\n// ─── GET /v1beta/operations/{name} — status poll ─────────────────────────────\n\nexport async function handleVeoVideoStatus(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n operationName: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n jobs: VeoVideoJobMap,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? `/v1beta/${operationName}`;\n const method = req.method ?? \"GET\";\n\n // Chaos rolls BEFORE the job lookup — the label stays \"internal\" even in\n // record mode, and a chaos-dropped poll never reaches the upstream.\n if (\n await applyChaosAsync(\n res,\n null,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: null },\n \"internal\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n const testId = getTestId(req);\n const key = `${testId}:${operationName}`;\n const job = jobs.get(key);\n\n if (!job) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 404, fixture: null },\n });\n writeErrorResponse(\n res,\n 404,\n geminiError(404, `Operation ${operationName} not found`, \"NOT_FOUND\"),\n );\n return;\n }\n\n if (job.kind === \"record\") {\n // Strict means nothing reaches an upstream — a record job's polls are pure\n // upstream proxies, so an effective-strict request is refused with 503.\n if (resolveStrictMode(defaults.strict, req.headers)) {\n defaults.logger.error(\n `STRICT: Veo operation ${operationName} is proxied live upstream (record mode) — refusing the upstream poll`,\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n geminiError(\n 503,\n `Strict mode: Veo operation ${operationName} is proxied live upstream (record mode) — nothing reaches an upstream under strict mode`,\n \"UNAVAILABLE\",\n ),\n );\n return;\n }\n await proxyVeoVideoRecordPoll({\n req,\n res,\n job,\n key,\n testId,\n fixtures,\n journal,\n defaults,\n jobs,\n method,\n path,\n });\n return;\n }\n\n // Guard BEFORE advancing or journaling (file convention): a disconnected\n // client consumes no progression step or TTL refresh.\n if (responseGone(res)) return;\n advanceVeoJob(job);\n // Refresh the TTL on every replay poll (delete-before-set also moves the\n // entry to the back of the FIFO eviction order).\n jobs.set(key, job);\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 200, fixture: null },\n });\n\n let body: Record<string, unknown>;\n if (job.status === \"completed\") {\n // Truthy guard (not `??`): an empty-string fixture url must still fall back\n // to the placeholder Files-API uri rather than being served as an empty uri.\n const rawUrl = job.video.url;\n body = completedOperationBody(\n operationName,\n typeof rawUrl === \"string\" && rawUrl ? rawUrl : DEFAULT_VEO_FILES_URI,\n );\n } else if (job.status === \"failed\") {\n if (job.video.error === \"\" && !job.emptyErrorWarned) {\n job.emptyErrorWarned = true;\n defaults.logger.warn(\n `Veo video fixture for operation ${operationName} has an empty error message — using the default`,\n );\n }\n body = {\n name: operationName,\n done: true,\n error: { code: 3, message: job.video.error || \"Video generation failed\" },\n };\n } else {\n body = { name: operationName, done: false };\n }\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n\n// ─── Record mode: live interactive proxy (submit) ───────────────────────────\n\n/**\n * Proxy an unmatched Veo submit to the configured upstream and answer the\n * client with a mock-rewritten envelope: a fresh aimock operation name. The\n * upstream lifecycle is driven interactively by the client's own polls.\n *\n * Returns \"no_upstream\" when record mode has no veo provider URL — the caller\n * falls through to its 404 branch (fal convention).\n */\nasync function proxyVeoVideoSubmit(args: {\n req: http.IncomingMessage;\n res: http.ServerResponse;\n raw: string;\n syntheticReq: ChatCompletionRequest;\n record: RecordConfig;\n journal: Journal;\n defaults: HandlerDefaults;\n jobs: VeoVideoJobMap;\n method: string;\n path: string;\n}): Promise<\"handled\" | \"no_upstream\"> {\n const { req, res, raw, syntheticReq, record, journal, defaults, jobs, method, path } = args;\n\n const upstreamBase = record.providers.veo;\n if (!upstreamBase) {\n defaults.logger.warn(`No upstream URL configured for provider \"veo\" — cannot proxy`);\n return \"no_upstream\";\n }\n\n const proxyError = (msg: string): \"handled\" => {\n defaults.logger.error(`Veo video submit proxy failed: ${msg}`);\n if (responseGone(res)) return \"handled\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 502,\n fixture: null,\n source: \"proxy\",\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 502,\n JSON.stringify({\n error: { message: `Proxy to upstream failed: ${msg}`, type: \"proxy_error\" },\n }),\n );\n return \"handled\";\n };\n\n const submitPath = `/v1beta/models/${syntheticReq.model}:predictLongRunning`;\n let submitUrl: URL;\n let upstreamOrigin: string;\n try {\n submitUrl = resolveUpstreamUrl(upstreamBase, submitPath);\n upstreamOrigin = new URL(upstreamBase).origin;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return proxyError(`Invalid upstream URL: ${upstreamBase} (${msg})`);\n }\n\n defaults.logger.warn(\n `NO FIXTURE MATCH — proxying Veo video submit to ${upstreamBase}${submitPath}`,\n );\n\n // World-generation snapshot: a fixtures reset landing during the upstream\n // fetch clears the job map — the insertion guard compares against this.\n const worldGeneration = jobs.generation;\n\n let fetched: { status: number; contentType: string | null; text: string };\n try {\n const headers = prepareEgressHeaders(req, submitUrl, \"veo\", record.providerKeys?.veo);\n if (!headers) return proxyError(\"No configured provider credential\");\n const upstreamRes = await fetch(submitUrl, {\n method: \"POST\",\n headers,\n body: raw,\n signal: upstreamTimeoutSignal(record),\n });\n fetched = {\n status: upstreamRes.status,\n contentType: upstreamRes.headers.get(\"content-type\"),\n text: await readEnvelopeText(upstreamRes, record),\n };\n } catch (err) {\n return proxyError(err instanceof Error ? err.message : \"Unknown proxy error\");\n }\n\n if (fetched.status === 401 || fetched.status === 403) {\n // Real-API fidelity: relay an upstream auth rejection verbatim.\n defaults.logger.warn(\n `Upstream rejected the Veo video submit (${fetched.status}) — relaying the upstream status`,\n );\n if (responseGone(res)) return \"handled\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: fetched.status,\n fixture: null,\n source: \"proxy\",\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n res.writeHead(fetched.status, { \"Content-Type\": fetched.contentType ?? \"application/json\" });\n res.end(fetched.text);\n return \"handled\";\n }\n\n let upstreamOperationName: string;\n {\n if (fetched.status < 200 || fetched.status >= 300) {\n return proxyError(`Submit ${fetched.status}: ${fetched.text.slice(0, 200)}`);\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(fetched.text);\n } catch {\n return proxyError(`Submit returned non-JSON: ${fetched.text.slice(0, 200)}`);\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return proxyError(\"Submit response is not a JSON object\");\n }\n upstreamOperationName = String((parsed as Record<string, unknown>).name ?? \"\").trim();\n if (!upstreamOperationName) {\n return proxyError(\"Submit response missing name\");\n }\n }\n\n // Veo has no polling_url field — the poll URL is constructed from the\n // provider base + `/v1beta/{operationName}`. Origin-validate the constructed\n // URL (the client's Authorization travels to it on every poll); fall back to\n // a string on the provider base on any parse failure.\n let upstreamPollingUrl: string;\n try {\n const constructed = resolveUpstreamUrl(\n upstreamBase,\n `/v1beta/${upstreamOperationName.replace(/^\\/+/, \"\")}`,\n );\n if (constructed.origin === upstreamOrigin) {\n upstreamPollingUrl = constructed.toString();\n } else {\n defaults.logger.warn(\n `Constructed Veo poll URL origin ${constructed.origin} differs from the configured provider origin ${upstreamOrigin} — using the provider-origin URL`,\n );\n upstreamPollingUrl = `${upstreamOrigin}/v1beta/${upstreamOperationName.replace(/^\\/+/, \"\")}`;\n }\n } catch {\n defaults.logger.warn(\n `Could not construct the Veo poll URL for ${upstreamOperationName} — using the provider-origin URL`,\n );\n upstreamPollingUrl = `${upstreamOrigin}/v1beta/${upstreamOperationName.replace(/^\\/+/, \"\")}`;\n }\n\n const testId = getTestId(req);\n const matchRequest = defaults.requestTransform\n ? defaults.requestTransform(syntheticReq)\n : syntheticReq;\n const operationName = `operations/${crypto.randomUUID()}`;\n const job: VeoVideoRecordJob = {\n kind: \"record\",\n operationName,\n status: \"pending\",\n upstreamOperationName,\n upstreamPollingUrl,\n match: buildFixtureMatch(matchRequest, record),\n };\n if (jobs.generation === worldGeneration) {\n jobs.set(`${testId}:${operationName}`, job);\n } else {\n defaults.logger.warn(\n `Veo video submit for upstream operation ${upstreamOperationName} completed after a fixtures reset — not inserting the job into the new world (its polls will 404)`,\n );\n }\n\n if (responseGone(res)) return \"handled\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 200,\n fixture: null,\n source: \"proxy\",\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n\n // Embed the testId in the RETURNED name (see the replay submit) so record-mode\n // multi-tenant polls resolve via `?testId=`; the upstream poll still uses the\n // bare job.operationName / job.upstreamPollingUrl, untouched by the suffix.\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ name: operationName + testIdSuffix(testId, \"?\") }));\n return \"handled\";\n}\n\n// ─── Record mode: live interactive proxy (poll + eager capture) ─────────────\n\n/**\n * Proxy a status poll for a record-mode Veo job 1:1 to the upstream and relay\n * the result with the operation name rewritten to the mock operation (the\n * Files-API uri inside `response` passes through verbatim — aimock never\n * downloads bytes). When the upstream reports `done:true` with a `response`,\n * the rewritten body is relayed IMMEDIATELY and the eager capture (persist the\n * uri as a fixture, mutate to a terminal replay job) runs DETACHED. A\n * `done:true` with an `error` persists a failed fixture synchronously. Under\n * `record.proxyOnly` nothing is captured, persisted, or mutated. Every\n * post-await map mutation is identity-guarded.\n */\nasync function proxyVeoVideoRecordPoll(args: {\n req: http.IncomingMessage;\n res: http.ServerResponse;\n job: VeoVideoRecordJob;\n key: string;\n testId: string;\n fixtures: Fixture[];\n journal: Journal;\n defaults: HandlerDefaults;\n jobs: VeoVideoJobMap;\n method: string;\n path: string;\n}): Promise<void> {\n const { req, res, job, key, testId, fixtures, journal, defaults, jobs, method, path } = args;\n const logger = defaults.logger;\n\n const journalProxy = (status: number): void => {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: {\n status,\n fixture: null,\n source: \"proxy\",\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n };\n\n const proxyError = (msg: string): void => {\n logger.error(`Veo video poll proxy failed: ${msg}`);\n if (responseGone(res)) return;\n journalProxy(502);\n writeErrorResponse(\n res,\n 502,\n JSON.stringify({\n error: { message: `Proxy to upstream failed: ${msg}`, type: \"proxy_error\" },\n }),\n );\n };\n\n // Recording can be disabled mid-flight — an orphaned record job must fail\n // loudly before contacting the upstream.\n const record = defaults.record;\n if (!record) {\n proxyError(\"record mode is no longer configured for an in-flight record job\");\n return;\n }\n\n let fetched: { status: number; contentType: string | null; text: string };\n try {\n const target = new URL(job.upstreamPollingUrl);\n const headers = prepareEgressHeaders(req, target, \"veo\", record.providerKeys?.veo);\n if (!headers) {\n proxyError(\"No configured provider credential\");\n return;\n }\n const upstreamRes = await fetch(job.upstreamPollingUrl, {\n headers,\n signal: upstreamTimeoutSignal(record),\n });\n fetched = {\n status: upstreamRes.status,\n contentType: upstreamRes.headers.get(\"content-type\"),\n text: await readEnvelopeText(upstreamRes, record),\n };\n } catch (err) {\n proxyError(err instanceof Error ? err.message : \"Unknown proxy error\");\n return;\n }\n\n if (fetched.status === 401 || fetched.status === 403) {\n logger.warn(\n `Upstream rejected the Veo status poll for operation ${job.upstreamOperationName} (${fetched.status}) — relaying the upstream status`,\n );\n if (responseGone(res)) return;\n journalProxy(fetched.status);\n res.writeHead(fetched.status, { \"Content-Type\": fetched.contentType ?? \"application/json\" });\n res.end(fetched.text);\n return;\n }\n\n let upstreamBody: Record<string, unknown>;\n {\n if (fetched.status < 200 || fetched.status >= 300) {\n proxyError(`Status ${fetched.status}: ${fetched.text.slice(0, 200)}`);\n return;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(fetched.text);\n } catch {\n proxyError(`Status returned non-JSON: ${fetched.text.slice(0, 200)}`);\n return;\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n proxyError(\"Status response is not a JSON object\");\n return;\n }\n upstreamBody = parsed as Record<string, unknown>;\n }\n\n // Rewrite ONLY the top-level operation name so no upstream identifier\n // escapes — the `response` (carrying the Files-API uri the client needs) and\n // every other field pass through verbatim.\n const relayBody: Record<string, unknown> = { ...upstreamBody, name: job.operationName };\n\n const relayJson = (): void => {\n if (responseGone(res)) return;\n journalProxy(200);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(relayBody));\n };\n\n const done = upstreamBody.done === true;\n const upstreamError = upstreamBody.error;\n const hasError =\n upstreamError !== undefined && upstreamError !== null && typeof upstreamError === \"object\";\n\n if (!done) {\n // Non-terminal: identity-guarded status write + TTL refresh, relay.\n if (jobs.get(key) === job) {\n if (!job.capturing && job.status !== \"completed\" && job.status !== \"failed\") {\n job.status = \"in_progress\";\n }\n jobs.set(key, job);\n }\n relayJson();\n return;\n }\n\n if (hasError) {\n // Terminal failure: persist a failed fixture synchronously (mirrors the\n // OpenRouter failed branch), then relay.\n if (record.proxyOnly) {\n if (jobs.get(key) === job) {\n job.status = \"failed\";\n jobs.set(key, job);\n }\n relayJson();\n return;\n }\n if (jobs.get(key) !== job) {\n // A concurrent terminal poll already persisted/replaced the entry, or a\n // fixtures reset cleared the world — relay without a duplicate fixture.\n relayJson();\n return;\n }\n const errObj = upstreamError as Record<string, unknown>;\n const rawMessage = errObj.message;\n const error = typeof rawMessage === \"string\" && rawMessage ? rawMessage : undefined;\n const video: VideoResponse[\"video\"] = {\n id: job.upstreamOperationName,\n status: \"failed\",\n ...(error !== undefined ? { error } : {}),\n };\n const persistResult = persistFixture({\n record,\n providerKey: \"veo\",\n testId,\n fixture: { match: job.match, response: { video } },\n fixtures,\n logger,\n });\n if (persistResult.kind === \"failed\" && !res.headersSent) {\n res.setHeader(\"X-AIMock-Record-Error\", sanitizeHeaderValue(persistResult.error));\n }\n jobs.set(key, {\n kind: \"replay\",\n operationName: job.operationName,\n status: \"failed\",\n pollCount: 0,\n pollsBeforeInProgress: 0,\n pollsBeforeCompleted: 0,\n video: { ...video },\n });\n relayJson();\n return;\n }\n\n // Terminal success (done:true with a response).\n if (record.proxyOnly) {\n if (jobs.get(key) === job) {\n job.status = \"completed\";\n jobs.set(key, job);\n }\n relayJson();\n return;\n }\n\n if (job.capturing || jobs.get(key) !== job) {\n // A concurrent poll already entered the capture sequence, or this\n // reference is detached — relay without starting a second capture and\n // never re-insert the stale object.\n if (jobs.get(key) === job) {\n jobs.set(key, job); // TTL refresh\n }\n relayJson();\n return;\n }\n\n // Open the capturing window SYNCHRONOUSLY before the first await.\n job.capturing = true;\n job.status = \"completed\";\n jobs.set(key, job); // TTL refresh — identity-checked above, no await since\n\n // Relay IMMEDIATELY; the persist + replay mutation runs DETACHED.\n relayJson();\n\n void captureVeoVideoRecordFixture({\n job,\n key,\n testId,\n fixtures,\n defaults,\n jobs,\n record,\n upstreamBody,\n });\n}\n\n/**\n * Detached eager-capture for a completed Veo record job. Extracts the\n * Files-API uri from the terminal poll body (NO download), persists a\n * fixture, and mutates the map entry into a terminal replay job. Every failure\n * is handled internally; the returned promise NEVER rejects. World-generation\n * guarded immediately before persist.\n */\nasync function captureVeoVideoRecordFixture(args: {\n job: VeoVideoRecordJob;\n key: string;\n testId: string;\n fixtures: Fixture[];\n defaults: HandlerDefaults;\n jobs: VeoVideoJobMap;\n record: RecordConfig;\n upstreamBody: Record<string, unknown>;\n}): Promise<void> {\n const { job, key, testId, fixtures, defaults, jobs, record, upstreamBody } = args;\n const logger = defaults.logger;\n\n try {\n // Drill into response.generateVideoResponse.generatedSamples[0].video.uri.\n let uri: string | undefined;\n const response = upstreamBody.response;\n if (response !== null && typeof response === \"object\" && !Array.isArray(response)) {\n const gvr = (response as Record<string, unknown>).generateVideoResponse;\n if (gvr !== null && typeof gvr === \"object\" && !Array.isArray(gvr)) {\n const samples = (gvr as Record<string, unknown>).generatedSamples;\n const first = Array.isArray(samples) ? samples[0] : undefined;\n if (first !== null && typeof first === \"object\" && !Array.isArray(first)) {\n const video = (first as Record<string, unknown>).video;\n if (video !== null && typeof video === \"object\" && !Array.isArray(video)) {\n const rawUri = (video as Record<string, unknown>).uri;\n if (typeof rawUri === \"string\" && rawUri) uri = rawUri;\n }\n }\n }\n }\n if (!uri) {\n logger.warn(\n `Upstream Veo operation ${job.upstreamOperationName} completed without a usable Files-API uri — capture skipped, nothing persisted; the next completed poll retries`,\n );\n return;\n }\n\n const video: VideoResponse[\"video\"] = {\n id: job.upstreamOperationName,\n status: \"completed\",\n url: uri,\n };\n\n // World-generation guard: a fixtures reset landing during the await above\n // clears the job map — map identity is a valid proxy for \"same world\".\n if (jobs.get(key) !== job) {\n logger.warn(\n `Veo video capture for operation ${job.upstreamOperationName} discarded: the job map no longer holds this job (fixtures reset or TTL eviction) — nothing persisted`,\n );\n return;\n }\n persistFixture({\n record,\n providerKey: \"veo\",\n testId,\n fixture: { match: job.match, response: { video } },\n fixtures,\n logger,\n });\n\n // Mutate the entry into a terminal replay job: later polls serve locally.\n if (jobs.get(key) === job) {\n jobs.set(key, {\n kind: \"replay\",\n operationName: job.operationName,\n status: \"completed\",\n pollCount: 0,\n pollsBeforeInProgress: 0,\n pollsBeforeCompleted: 0,\n video: { ...video },\n });\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.error(\n `Veo video capture for operation ${job.upstreamOperationName} failed unexpectedly (${msg}) — fixture not persisted; the job keeps proxying live`,\n );\n } finally {\n // Close the capturing window when the map still holds this record job (a\n // step threw before the replay mutation). On the success path the entry\n // was just replaced; the detached object deliberately keeps capturing=true.\n if (jobs.get(key) === job) {\n job.capturing = false;\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/video.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const require_helpers = require('./helpers.cjs');
|
|
2
|
-
const require_router = require('./router.cjs');
|
|
3
2
|
const require_sse_writer = require('./sse-writer.cjs');
|
|
4
3
|
const require_chaos = require('./chaos.cjs');
|
|
4
|
+
const require_router = require('./router.cjs');
|
|
5
5
|
const require_recorder = require('./recorder.cjs');
|
|
6
6
|
const require_transcription = require('./transcription.cjs');
|
|
7
7
|
|
|
@@ -142,6 +142,25 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
142
142
|
} }));
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
145
|
+
const prompt = videoReq.prompt;
|
|
146
|
+
const validParts = Array.isArray(prompt) && prompt.length > 0 && prompt.every((part) => require_helpers.isJsonObject(part) && typeof part.type === "string" && (!("text" in part) || typeof part.text === "string"));
|
|
147
|
+
if (typeof prompt !== "string" && !validParts) {
|
|
148
|
+
journal.add({
|
|
149
|
+
method,
|
|
150
|
+
path,
|
|
151
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
152
|
+
body: null,
|
|
153
|
+
response: {
|
|
154
|
+
status: 400,
|
|
155
|
+
fixture: null
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
|
|
159
|
+
message: "Invalid parameter: 'prompt' must be a string or a non-empty array of content parts",
|
|
160
|
+
type: "invalid_request_error"
|
|
161
|
+
} }));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
145
164
|
const syntheticReq = {
|
|
146
165
|
model: videoReq.model ?? "sora-2",
|
|
147
166
|
messages: [{
|
|
@@ -157,7 +176,7 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
157
176
|
journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
158
177
|
defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
|
|
159
178
|
} else defaults.logger.debug(`No fixture matched for request`);
|
|
160
|
-
if (require_chaos.
|
|
179
|
+
if (await require_chaos.applyChaosAsync(res, fixture, defaults.chaos, req.headers, req.url, journal, {
|
|
161
180
|
method,
|
|
162
181
|
path,
|
|
163
182
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
@@ -285,11 +304,11 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
285
304
|
}));
|
|
286
305
|
}
|
|
287
306
|
}
|
|
288
|
-
function handleVideoStatus(req, res, videoId, journal, defaults, setCorsHeaders, videoStates) {
|
|
307
|
+
async function handleVideoStatus(req, res, videoId, journal, defaults, setCorsHeaders, videoStates) {
|
|
289
308
|
setCorsHeaders(res);
|
|
290
309
|
const path = req.url ?? `/v1/videos/${videoId}`;
|
|
291
310
|
const method = req.method ?? "GET";
|
|
292
|
-
if (require_chaos.
|
|
311
|
+
if (await require_chaos.applyChaosAsync(res, null, defaults.chaos, req.headers, req.url, journal, {
|
|
293
312
|
method,
|
|
294
313
|
path,
|
|
295
314
|
headers: require_helpers.flattenHeaders(req.headers),
|
package/dist/video.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video.cjs","names":["extractBoundary","extractFormField","flattenHeaders","isJsonObject","getContext","getTestId","matchFixtureDiagnostic","applyChaos","resolveStrictMode","strictNoMatchMessage","strictNoMatchLogLine","strictOverrideField","proxyAndRecord","resolveResponse","isErrorResponse","serializeErrorResponse","isVideoResponse"],"sources":["../src/video.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport type { ChatCompletionRequest, Fixture, HandlerDefaults, VideoResponse } from \"./types.js\";\nimport {\n isVideoResponse,\n isErrorResponse,\n isJsonObject,\n serializeErrorResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\nimport { extractBoundary, extractFormField } from \"./transcription.js\";\n\ninterface VideoRequest {\n model?: string;\n prompt: string;\n [key: string]: unknown;\n}\n\n// ─── VideoStateMap with TTL and size bound ────────────────────────────────\n\nconst VIDEO_STATE_MAX_ENTRIES = 10_000;\nconst VIDEO_STATE_TTL_MS = 3_600_000; // 1 hour\n\ninterface VideoStateEntry {\n video: VideoResponse[\"video\"];\n createdAt: number;\n}\n\n/**\n * A Map wrapper for video state that enforces a maximum size and per-entry TTL.\n * Entries older than VIDEO_STATE_TTL_MS are lazily evicted on `get`.\n * When the map exceeds VIDEO_STATE_MAX_ENTRIES on `set`, the oldest entries\n * are removed to stay within bounds.\n */\nexport class VideoStateMap {\n private readonly entries = new Map<string, VideoStateEntry>();\n private readonly sweepTimer: ReturnType<typeof setInterval>;\n\n constructor() {\n // Proactive sweep every 60 seconds to evict expired entries\n this.sweepTimer = setInterval(() => {\n const now = Date.now();\n for (const [key, entry] of this.entries) {\n if (now - entry.createdAt > VIDEO_STATE_TTL_MS) {\n this.entries.delete(key);\n }\n }\n }, 60_000);\n // Allow the process to exit even if the timer is still running\n if (this.sweepTimer.unref) {\n this.sweepTimer.unref();\n }\n }\n\n getEntry(key: string): { video: VideoResponse[\"video\"]; createdAtUnix: number } | undefined {\n const entry = this.entries.get(key);\n if (!entry) return undefined;\n if (Date.now() - entry.createdAt > VIDEO_STATE_TTL_MS) {\n this.entries.delete(key);\n return undefined;\n }\n return { video: entry.video, createdAtUnix: Math.floor(entry.createdAt / 1000) };\n }\n\n getCreatedAtUnix(key: string): number | undefined {\n const e = this.getEntry(key);\n return e?.createdAtUnix;\n }\n\n set(key: string, video: VideoResponse[\"video\"]): void {\n this.entries.set(key, { video, createdAt: Date.now() });\n // Evict oldest entries if over capacity\n if (this.entries.size > VIDEO_STATE_MAX_ENTRIES) {\n const excess = this.entries.size - VIDEO_STATE_MAX_ENTRIES;\n const iter = this.entries.keys();\n for (let i = 0; i < excess; i++) {\n const next = iter.next();\n if (!next.done) this.entries.delete(next.value);\n }\n }\n }\n\n delete(key: string): boolean {\n return this.entries.delete(key);\n }\n\n clear(): void {\n this.entries.clear();\n }\n\n destroy(): void {\n clearInterval(this.sweepTimer);\n this.entries.clear();\n }\n\n get size(): number {\n return this.entries.size;\n }\n}\n\nexport async function handleVideoCreate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n videoStates: VideoStateMap,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? \"/v1/videos\";\n const method = req.method ?? \"POST\";\n\n const contentType = Array.isArray(req.headers[\"content-type\"])\n ? req.headers[\"content-type\"][0]\n : req.headers[\"content-type\"];\n const isMultipart = (contentType ?? \"\").toLowerCase().includes(\"multipart/form-data\");\n\n let videoReq: VideoRequest;\n if (isMultipart) {\n // The OpenAI SDK (6.28.0+) sends POST /v1/videos as multipart/form-data;\n // older SDKs sent JSON for a File-less body. Parse the form fields into the\n // same shape the JSON path produces, reusing the transcription multipart\n // helpers. Numeric fields (e.g. `seconds`) arrive as strings and are\n // coerced to numbers to match the JSON body's types.\n const boundary = extractBoundary(contentType);\n const prompt = extractFormField(raw, \"prompt\", boundary);\n const model = extractFormField(raw, \"model\", boundary);\n const size = extractFormField(raw, \"size\", boundary);\n const secondsRaw = extractFormField(raw, \"seconds\", boundary);\n videoReq = { prompt: prompt ?? \"\" };\n if (model !== undefined) videoReq.model = model;\n if (size !== undefined) videoReq.size = size;\n if (secondsRaw !== undefined) {\n const secondsNum = Number(secondsRaw);\n videoReq.seconds = Number.isNaN(secondsNum) ? secondsRaw : secondsNum;\n }\n } else {\n try {\n videoReq = JSON.parse(raw) as VideoRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: `Malformed JSON: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n }\n\n // Reject bodies that parsed but are not a JSON object (e.g. `null`) before\n // touching fields — otherwise `videoReq.prompt` throws a TypeError that\n // surfaces as a 500 instead of a 400. The multipart branch above builds its\n // own object and needs no guard.\n if (!isJsonObject(videoReq)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Request body must be a JSON object\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!videoReq.prompt) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: { message: \"Missing required parameter: 'prompt'\", type: \"invalid_request_error\" },\n }),\n );\n return;\n }\n\n const syntheticReq: ChatCompletionRequest = {\n model: videoReq.model ?? \"sora-2\",\n messages: [{ role: \"user\", content: videoReq.prompt }],\n _endpointType: \"video\",\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"openai\",\n req.url ?? \"/v1/videos\",\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isVideoResponse(response)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: { message: \"Fixture response is not a video type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const video = response.video;\n\n // Store for GET status checks\n const stateKey = `${testId}:${video.id}`;\n videoStates.set(stateKey, video);\n const created_at = videoStates.getCreatedAtUnix(stateKey)!;\n\n if (video.status === \"completed\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ id: video.id, status: video.status, url: video.url, created_at }));\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ id: video.id, status: video.status, created_at }));\n }\n}\n\nexport function handleVideoStatus(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n videoId: string,\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n videoStates: VideoStateMap,\n): void {\n setCorsHeaders(res);\n const path = req.url ?? `/v1/videos/${videoId}`;\n const method = req.method ?? \"GET\";\n\n if (\n applyChaos(\n res,\n null,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: null },\n \"internal\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n const testId = getTestId(req);\n const stateKey = `${testId}:${videoId}`;\n const entry = videoStates.getEntry(stateKey);\n\n if (!entry) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 404, fixture: null },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({ error: { message: `Video ${videoId} not found`, type: \"not_found\" } }),\n );\n return;\n }\n\n const { video, createdAtUnix: created_at } = entry;\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 200, fixture: null },\n });\n\n const body: Record<string, unknown> = {\n id: video.id,\n status: video.status,\n created_at,\n };\n if (video.url) body.url = video.url;\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":";;;;;;;;AA+BA,MAAM,0BAA0B;AAChC,MAAM,qBAAqB;;;;;;;AAa3B,IAAa,gBAAb,MAA2B;CACzB,AAAiB,0BAAU,IAAI,KAA8B;CAC7D,AAAiB;CAEjB,cAAc;AAEZ,OAAK,aAAa,kBAAkB;GAClC,MAAM,MAAM,KAAK,KAAK;AACtB,QAAK,MAAM,CAAC,KAAK,UAAU,KAAK,QAC9B,KAAI,MAAM,MAAM,YAAY,mBAC1B,MAAK,QAAQ,OAAO,IAAI;KAG3B,IAAO;AAEV,MAAI,KAAK,WAAW,MAClB,MAAK,WAAW,OAAO;;CAI3B,SAAS,KAAmF;EAC1F,MAAM,QAAQ,KAAK,QAAQ,IAAI,IAAI;AACnC,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,KAAK,KAAK,GAAG,MAAM,YAAY,oBAAoB;AACrD,QAAK,QAAQ,OAAO,IAAI;AACxB;;AAEF,SAAO;GAAE,OAAO,MAAM;GAAO,eAAe,KAAK,MAAM,MAAM,YAAY,IAAK;GAAE;;CAGlF,iBAAiB,KAAiC;AAEhD,SADU,KAAK,SAAS,IAAI,EAClB;;CAGZ,IAAI,KAAa,OAAqC;AACpD,OAAK,QAAQ,IAAI,KAAK;GAAE;GAAO,WAAW,KAAK,KAAK;GAAE,CAAC;AAEvD,MAAI,KAAK,QAAQ,OAAO,yBAAyB;GAC/C,MAAM,SAAS,KAAK,QAAQ,OAAO;GACnC,MAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,QAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;IAC/B,MAAM,OAAO,KAAK,MAAM;AACxB,QAAI,CAAC,KAAK,KAAM,MAAK,QAAQ,OAAO,KAAK,MAAM;;;;CAKrD,OAAO,KAAsB;AAC3B,SAAO,KAAK,QAAQ,OAAO,IAAI;;CAGjC,QAAc;AACZ,OAAK,QAAQ,OAAO;;CAGtB,UAAgB;AACd,gBAAc,KAAK,WAAW;AAC9B,OAAK,QAAQ,OAAO;;CAGtB,IAAI,OAAe;AACjB,SAAO,KAAK,QAAQ;;;AAIxB,eAAsB,kBACpB,KACA,KACA,KACA,UACA,SACA,UACA,gBACA,aACe;AACf,gBAAe,IAAI;CACnB,MAAM,OAAO,IAAI,OAAO;CACxB,MAAM,SAAS,IAAI,UAAU;CAE7B,MAAM,cAAc,MAAM,QAAQ,IAAI,QAAQ,gBAAgB,GAC1D,IAAI,QAAQ,gBAAgB,KAC5B,IAAI,QAAQ;CAChB,MAAM,eAAe,eAAe,IAAI,aAAa,CAAC,SAAS,sBAAsB;CAErF,IAAI;AACJ,KAAI,aAAa;EAMf,MAAM,WAAWA,sCAAgB,YAAY;EAC7C,MAAM,SAASC,uCAAiB,KAAK,UAAU,SAAS;EACxD,MAAM,QAAQA,uCAAiB,KAAK,SAAS,SAAS;EACtD,MAAM,OAAOA,uCAAiB,KAAK,QAAQ,SAAS;EACpD,MAAM,aAAaA,uCAAiB,KAAK,WAAW,SAAS;AAC7D,aAAW,EAAE,QAAQ,UAAU,IAAI;AACnC,MAAI,UAAU,OAAW,UAAS,QAAQ;AAC1C,MAAI,SAAS,OAAW,UAAS,OAAO;AACxC,MAAI,eAAe,QAAW;GAC5B,MAAM,aAAa,OAAO,WAAW;AACrC,YAAS,UAAU,OAAO,MAAM,WAAW,GAAG,aAAa;;OAG7D,KAAI;AACF,aAAW,KAAK,MAAM,IAAI;UACnB,UAAU;EACjB,MAAM,SAAS,oBAAoB,QAAQ,SAAS,UAAU;AAC9D,UAAQ,IAAI;GACV;GACA;GACA,SAASC,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS,mBAAmB;GAC5B,MAAM;GACN,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAQJ,KAAI,CAACC,6BAAa,SAAS,EAAE;AAC3B,UAAQ,IAAI;GACV;GACA;GACA,SAASD,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAGF,KAAI,CAAC,SAAS,QAAQ;AACpB,UAAQ,IAAI;GACV;GACA;GACA,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GAAE,SAAS;GAAwC,MAAM;GAAyB,EAC1F,CAAC,CACH;AACD;;CAGF,MAAM,eAAsC;EAC1C,OAAO,SAAS,SAAS;EACzB,UAAU,CAAC;GAAE,MAAM;GAAQ,SAAS,SAAS;GAAQ,CAAC;EACtD,eAAe;EACf,UAAUE,2BAAW,IAAI;EAC1B;CAED,MAAM,SAASC,0BAAU,IAAI;CAC7B,MAAM,EAAE,SAAS,4BAA4BC,sCAC3C,UACA,cACA,QAAQ,6BAA6B,OAAO,EAC5C,SAAS,iBACV;AAED,KAAI,SAAS;AACX,UAAQ,2BAA2B,SAAS,UAAU,OAAO;AAC7D,WAAS,OAAO,MAAM,oBAAoB,KAAK,UAAU,QAAQ,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG;OAExF,UAAS,OAAO,MAAM,iCAAiC;AAGzD,KACEC,yBACE,KACA,SACA,SAAS,OACT,IAAI,SACJ,IAAI,KACJ,SACA;EAAE;EAAQ;EAAM,SAASL,+BAAe,IAAI,QAAQ;EAAE,MAAM;EAAc,EAC1E,UAAU,YAAY,SACtB,SAAS,UACT,SAAS,OACV,CAED;AAEF,KAAI,CAAC,SAAS;AAEZ,MADwBM,kCAAkB,SAAS,QAAQ,IAAI,QAAQ,EAClD;GACnB,MAAM,gBAAgBC,qCAAqB,wBAAwB;AACnE,YAAS,OAAO,MAAMC,qCAAqB,QAAQ,MAAM,wBAAwB,CAAC;AAClF,WAAQ,IAAI;IACV;IACA;IACA,SAASR,+BAAe,IAAI,QAAQ;IACpC,MAAM;IACN,UAAU;KACR,QAAQ;KACR,SAAS;KACT,GAAGS,oCAAoB,SAAS,QAAQ,IAAI,QAAQ;KACrD;IACF,CAAC;AACF,yCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;IACL,SAAS;IACT,MAAM;IACN,MAAM;IACP,EACF,CAAC,CACH;AACD;;AAEF,MAAI,SAAS,QAAQ;GACnB,MAAM,UAAU,MAAMC,gCACpB,KACA,KACA,cACA,UACA,IAAI,OAAO,cACX,UACA,UACA,IACD;AACD,OAAI,YAAY,kBAAmB;AACnC,OAAI,YAAY,kBAAkB;AAChC,YAAQ,IAAI;KACV;KACA;KACA,SAASV,+BAAe,IAAI,QAAQ;KACpC,MAAM;KACN,UAAU;MAAE,QAAQ,IAAI,cAAc;MAAK,SAAS;MAAM,QAAQ;MAAS;KAC5E,CAAC;AACF;;;AAIJ,UAAQ,IAAI;GACV;GACA;GACA,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IACR,QAAQ;IACR,SAAS;IACT,GAAGS,oCAAoB,SAAS,QAAQ,IAAI,QAAQ;IACrD;GACF,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACN,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAGF,MAAM,WAAW,MAAME,gCAAgB,SAAS,aAAa;AAE7D,KAAIC,gCAAgB,SAAS,EAAE;EAC7B,MAAM,SAAS,SAAS,UAAU;AAClC,UAAQ,IAAI;GACV;GACA;GACA,SAASZ,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE;IAAQ;IAAS;GAC9B,CAAC;AACF,wCAAmB,KAAK,QAAQa,uCAAuB,SAAS,EAAE,EAChE,YAAY,SAAS,YACtB,CAAC;AACF;;AAGF,KAAI,CAACC,gCAAgB,SAAS,EAAE;AAC9B,UAAQ,IAAI;GACV;GACA;GACA,SAASd,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GAAE,SAAS;GAAwC,MAAM;GAAgB,EACjF,CAAC,CACH;AACD;;AAGF,SAAQ,IAAI;EACV;EACA;EACA,SAASA,+BAAe,IAAI,QAAQ;EACpC,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK;GAAS;EACnC,CAAC;CAEF,MAAM,QAAQ,SAAS;CAGvB,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM;AACpC,aAAY,IAAI,UAAU,MAAM;CAChC,MAAM,aAAa,YAAY,iBAAiB,SAAS;AAEzD,KAAI,MAAM,WAAW,aAAa;AAChC,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IAAI,KAAK,UAAU;GAAE,IAAI,MAAM;GAAI,QAAQ,MAAM;GAAQ,KAAK,MAAM;GAAK;GAAY,CAAC,CAAC;QACtF;AACL,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IAAI,KAAK,UAAU;GAAE,IAAI,MAAM;GAAI,QAAQ,MAAM;GAAQ;GAAY,CAAC,CAAC;;;AAI/E,SAAgB,kBACd,KACA,KACA,SACA,SACA,UACA,gBACA,aACM;AACN,gBAAe,IAAI;CACnB,MAAM,OAAO,IAAI,OAAO,cAAc;CACtC,MAAM,SAAS,IAAI,UAAU;AAE7B,KACEK,yBACE,KACA,MACA,SAAS,OACT,IAAI,SACJ,IAAI,KACJ,SACA;EAAE;EAAQ;EAAM,SAASL,+BAAe,IAAI,QAAQ;EAAE,MAAM;EAAM,EAClE,YACA,SAAS,UACT,SAAS,OACV,CAED;CAGF,MAAM,WAAW,GADFG,0BAAU,IAAI,CACF,GAAG;CAC9B,MAAM,QAAQ,YAAY,SAAS,SAAS;AAE5C,KAAI,CAAC,OAAO;AACV,UAAQ,IAAI;GACV;GACA;GACA,SAASH,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EAAE,OAAO;GAAE,SAAS,SAAS,QAAQ;GAAa,MAAM;GAAa,EAAE,CAAC,CACxF;AACD;;CAGF,MAAM,EAAE,OAAO,eAAe,eAAe;AAE7C,SAAQ,IAAI;EACV;EACA;EACA,SAASA,+BAAe,IAAI,QAAQ;EACpC,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK,SAAS;GAAM;EACzC,CAAC;CAEF,MAAM,OAAgC;EACpC,IAAI,MAAM;EACV,QAAQ,MAAM;EACd;EACD;AACD,KAAI,MAAM,IAAK,MAAK,MAAM,MAAM;AAEhC,KAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,KAAI,IAAI,KAAK,UAAU,KAAK,CAAC"}
|
|
1
|
+
{"version":3,"file":"video.cjs","names":["extractBoundary","extractFormField","flattenHeaders","isJsonObject","getContext","getTestId","matchFixtureDiagnostic","applyChaosAsync","resolveStrictMode","strictNoMatchMessage","strictNoMatchLogLine","strictOverrideField","proxyAndRecord","resolveResponse","isErrorResponse","serializeErrorResponse","isVideoResponse"],"sources":["../src/video.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport type { ChatCompletionRequest, Fixture, HandlerDefaults, VideoResponse } from \"./types.js\";\nimport {\n isVideoResponse,\n isErrorResponse,\n isJsonObject,\n serializeErrorResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaosAsync } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\nimport { extractBoundary, extractFormField } from \"./transcription.js\";\n\ninterface VideoRequest {\n model?: string;\n prompt: string;\n [key: string]: unknown;\n}\n\n// ─── VideoStateMap with TTL and size bound ────────────────────────────────\n\nconst VIDEO_STATE_MAX_ENTRIES = 10_000;\nconst VIDEO_STATE_TTL_MS = 3_600_000; // 1 hour\n\ninterface VideoStateEntry {\n video: VideoResponse[\"video\"];\n createdAt: number;\n}\n\n/**\n * A Map wrapper for video state that enforces a maximum size and per-entry TTL.\n * Entries older than VIDEO_STATE_TTL_MS are lazily evicted on `get`.\n * When the map exceeds VIDEO_STATE_MAX_ENTRIES on `set`, the oldest entries\n * are removed to stay within bounds.\n */\nexport class VideoStateMap {\n private readonly entries = new Map<string, VideoStateEntry>();\n private readonly sweepTimer: ReturnType<typeof setInterval>;\n\n constructor() {\n // Proactive sweep every 60 seconds to evict expired entries\n this.sweepTimer = setInterval(() => {\n const now = Date.now();\n for (const [key, entry] of this.entries) {\n if (now - entry.createdAt > VIDEO_STATE_TTL_MS) {\n this.entries.delete(key);\n }\n }\n }, 60_000);\n // Allow the process to exit even if the timer is still running\n if (this.sweepTimer.unref) {\n this.sweepTimer.unref();\n }\n }\n\n getEntry(key: string): { video: VideoResponse[\"video\"]; createdAtUnix: number } | undefined {\n const entry = this.entries.get(key);\n if (!entry) return undefined;\n if (Date.now() - entry.createdAt > VIDEO_STATE_TTL_MS) {\n this.entries.delete(key);\n return undefined;\n }\n return { video: entry.video, createdAtUnix: Math.floor(entry.createdAt / 1000) };\n }\n\n getCreatedAtUnix(key: string): number | undefined {\n const e = this.getEntry(key);\n return e?.createdAtUnix;\n }\n\n set(key: string, video: VideoResponse[\"video\"]): void {\n this.entries.set(key, { video, createdAt: Date.now() });\n // Evict oldest entries if over capacity\n if (this.entries.size > VIDEO_STATE_MAX_ENTRIES) {\n const excess = this.entries.size - VIDEO_STATE_MAX_ENTRIES;\n const iter = this.entries.keys();\n for (let i = 0; i < excess; i++) {\n const next = iter.next();\n if (!next.done) this.entries.delete(next.value);\n }\n }\n }\n\n delete(key: string): boolean {\n return this.entries.delete(key);\n }\n\n clear(): void {\n this.entries.clear();\n }\n\n destroy(): void {\n clearInterval(this.sweepTimer);\n this.entries.clear();\n }\n\n get size(): number {\n return this.entries.size;\n }\n}\n\nexport async function handleVideoCreate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n videoStates: VideoStateMap,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? \"/v1/videos\";\n const method = req.method ?? \"POST\";\n\n const contentType = Array.isArray(req.headers[\"content-type\"])\n ? req.headers[\"content-type\"][0]\n : req.headers[\"content-type\"];\n const isMultipart = (contentType ?? \"\").toLowerCase().includes(\"multipart/form-data\");\n\n let videoReq: VideoRequest;\n if (isMultipart) {\n // The OpenAI SDK (6.28.0+) sends POST /v1/videos as multipart/form-data;\n // older SDKs sent JSON for a File-less body. Parse the form fields into the\n // same shape the JSON path produces, reusing the transcription multipart\n // helpers. Numeric fields (e.g. `seconds`) arrive as strings and are\n // coerced to numbers to match the JSON body's types.\n const boundary = extractBoundary(contentType);\n const prompt = extractFormField(raw, \"prompt\", boundary);\n const model = extractFormField(raw, \"model\", boundary);\n const size = extractFormField(raw, \"size\", boundary);\n const secondsRaw = extractFormField(raw, \"seconds\", boundary);\n videoReq = { prompt: prompt ?? \"\" };\n if (model !== undefined) videoReq.model = model;\n if (size !== undefined) videoReq.size = size;\n if (secondsRaw !== undefined) {\n const secondsNum = Number(secondsRaw);\n videoReq.seconds = Number.isNaN(secondsNum) ? secondsRaw : secondsNum;\n }\n } else {\n try {\n videoReq = JSON.parse(raw) as VideoRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: `Malformed JSON: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n }\n\n // Reject bodies that parsed but are not a JSON object (e.g. `null`) before\n // touching fields — otherwise `videoReq.prompt` throws a TypeError that\n // surfaces as a 500 instead of a 400. The multipart branch above builds its\n // own object and needs no guard.\n if (!isJsonObject(videoReq)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Request body must be a JSON object\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!videoReq.prompt) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: { message: \"Missing required parameter: 'prompt'\", type: \"invalid_request_error\" },\n }),\n );\n return;\n }\n\n const prompt = videoReq.prompt;\n const validParts =\n Array.isArray(prompt) &&\n prompt.length > 0 &&\n prompt.every(\n (part: unknown) =>\n isJsonObject(part) &&\n typeof part.type === \"string\" &&\n (!(\"text\" in part) || typeof part.text === \"string\"),\n );\n if (typeof prompt !== \"string\" && !validParts) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message:\n \"Invalid parameter: 'prompt' must be a string or a non-empty array of content parts\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const syntheticReq: ChatCompletionRequest = {\n model: videoReq.model ?? \"sora-2\",\n messages: [{ role: \"user\", content: videoReq.prompt }],\n _endpointType: \"video\",\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"openai\",\n req.url ?? \"/v1/videos\",\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isVideoResponse(response)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: { message: \"Fixture response is not a video type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const video = response.video;\n\n // Store for GET status checks\n const stateKey = `${testId}:${video.id}`;\n videoStates.set(stateKey, video);\n const created_at = videoStates.getCreatedAtUnix(stateKey)!;\n\n if (video.status === \"completed\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ id: video.id, status: video.status, url: video.url, created_at }));\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ id: video.id, status: video.status, created_at }));\n }\n}\n\nexport async function handleVideoStatus(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n videoId: string,\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n videoStates: VideoStateMap,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? `/v1/videos/${videoId}`;\n const method = req.method ?? \"GET\";\n\n if (\n await applyChaosAsync(\n res,\n null,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: null },\n \"internal\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n const testId = getTestId(req);\n const stateKey = `${testId}:${videoId}`;\n const entry = videoStates.getEntry(stateKey);\n\n if (!entry) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 404, fixture: null },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({ error: { message: `Video ${videoId} not found`, type: \"not_found\" } }),\n );\n return;\n }\n\n const { video, createdAtUnix: created_at } = entry;\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 200, fixture: null },\n });\n\n const body: Record<string, unknown> = {\n id: video.id,\n status: video.status,\n created_at,\n };\n if (video.url) body.url = video.url;\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":"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"}
|
package/dist/video.d.cts
CHANGED
|
@@ -26,7 +26,7 @@ declare class VideoStateMap {
|
|
|
26
26
|
get size(): number;
|
|
27
27
|
}
|
|
28
28
|
declare function handleVideoCreate(req: http$1.IncomingMessage, res: http$1.ServerResponse, raw: string, fixtures: Fixture[], journal: Journal, defaults: HandlerDefaults, setCorsHeaders: (res: http$1.ServerResponse) => void, videoStates: VideoStateMap): Promise<void>;
|
|
29
|
-
declare function handleVideoStatus(req: http$1.IncomingMessage, res: http$1.ServerResponse, videoId: string, journal: Journal, defaults: HandlerDefaults, setCorsHeaders: (res: http$1.ServerResponse) => void, videoStates: VideoStateMap): void
|
|
29
|
+
declare function handleVideoStatus(req: http$1.IncomingMessage, res: http$1.ServerResponse, videoId: string, journal: Journal, defaults: HandlerDefaults, setCorsHeaders: (res: http$1.ServerResponse) => void, videoStates: VideoStateMap): Promise<void>;
|
|
30
30
|
//# sourceMappingURL=video.d.ts.map
|
|
31
31
|
|
|
32
32
|
//#endregion
|
package/dist/video.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video.d.cts","names":[],"sources":["../src/video.ts"],"sourcesContent":[],"mappings":";;;;;;;;AA6CA;;;;AAmCuC,cAnC1B,aAAA,CAmC0B;EA+BjB,iBAAA,OAAiB;EAAA,iBAAA,UAAA;aAC3B,CAAA;UACL,CAAA,GAAK,EAAA,MAAA,CAAA,EAAA;IAEA,KAAA,EAlDsB,aAkDtB,CAAA,OAAA,CAAA;IACD,aAAA,EAAA,MAAA;MACC,SAAA;kBACiB,CAAA,GAAA,EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,SAAA;KACd,CAAA,GAAA,EAAA,MAAA,EAAA,KAAA,EAvCW,aAuCX,CAAA,OAAA,CAAA,CAAA,EAAA,IAAA;QACZ,CAAA,GAAA,EAAA,MAAA,CAAA,EAAA,OAAA;EAAO,KAAA,CAAA,CAAA,EAAA,IAAA;
|
|
1
|
+
{"version":3,"file":"video.d.cts","names":[],"sources":["../src/video.ts"],"sourcesContent":[],"mappings":";;;;;;;;AA6CA;;;;AAmCuC,cAnC1B,aAAA,CAmC0B;EA+BjB,iBAAA,OAAiB;EAAA,iBAAA,UAAA;aAC3B,CAAA;UACL,CAAA,GAAK,EAAA,MAAA,CAAA,EAAA;IAEA,KAAA,EAlDsB,aAkDtB,CAAA,OAAA,CAAA;IACD,aAAA,EAAA,MAAA;MACC,SAAA;kBACiB,CAAA,GAAA,EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,SAAA;KACd,CAAA,GAAA,EAAA,MAAA,EAAA,KAAA,EAvCW,aAuCX,CAAA,OAAA,CAAA,CAAA,EAAA,IAAA;QACZ,CAAA,GAAA,EAAA,MAAA,CAAA,EAAA,OAAA;EAAO,KAAA,CAAA,CAAA,EAAA,IAAA;EAkTY,OAAA,CAAA,CAAA,EAAA,IAAA;EAAiB,IAAA,IAAA,CAAA,CAAA,EAAA,MAAA;;AAEhC,iBA7Te,iBAAA,CA6TV,GAAA,EA5TL,MAAA,CAAK,eA4TA,EAAA,GAAA,EA3TL,MAAA,CAAK,cA2TA,EAAA,GAAA,EAAA,MAAA,EAAA,QAAA,EAzTA,OAyTA,EAAA,EAAA,OAAA,EAxTD,OAwTC,EAAA,QAAA,EAvTA,eAuTA,EAAA,cAAA,EAAA,CAAA,GAAA,EAtTY,MAAA,CAAK,cAsTjB,EAAA,GAAA,IAAA,EAAA,WAAA,EArTG,aAqTH,CAAA,EApTT,OAoTS,CAAA,IAAA,CAAA;AAED,iBAJW,iBAAA,CAIX,GAAA,EAHJ,MAAA,CAAK,eAGD,EAAA,GAAA,EAFJ,MAAA,CAAK,cAED,EAAA,OAAA,EAAA,MAAA,EAAA,OAAA,EAAA,OAAA,EAAA,QAAA,EACC,eADD,EAAA,cAAA,EAAA,CAAA,GAAA,EAEa,MAAA,CAAK,cAFlB,EAAA,GAAA,IAAA,EAAA,WAAA,EAGI,aAHJ,CAAA,EAIR,OAJQ,CAAA,IAAA,CAAA"}
|
package/dist/video.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ declare class VideoStateMap {
|
|
|
26
26
|
get size(): number;
|
|
27
27
|
}
|
|
28
28
|
declare function handleVideoCreate(req: http$1.IncomingMessage, res: http$1.ServerResponse, raw: string, fixtures: Fixture[], journal: Journal, defaults: HandlerDefaults, setCorsHeaders: (res: http$1.ServerResponse) => void, videoStates: VideoStateMap): Promise<void>;
|
|
29
|
-
declare function handleVideoStatus(req: http$1.IncomingMessage, res: http$1.ServerResponse, videoId: string, journal: Journal, defaults: HandlerDefaults, setCorsHeaders: (res: http$1.ServerResponse) => void, videoStates: VideoStateMap): void
|
|
29
|
+
declare function handleVideoStatus(req: http$1.IncomingMessage, res: http$1.ServerResponse, videoId: string, journal: Journal, defaults: HandlerDefaults, setCorsHeaders: (res: http$1.ServerResponse) => void, videoStates: VideoStateMap): Promise<void>;
|
|
30
30
|
//# sourceMappingURL=video.d.ts.map
|
|
31
31
|
|
|
32
32
|
//#endregion
|
package/dist/video.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video.d.ts","names":[],"sources":["../src/video.ts"],"sourcesContent":[],"mappings":";;;;;;;;AA6CA;;;;AAmCuC,cAnC1B,aAAA,CAmC0B;EA+BjB,iBAAA,OAAiB;EAAA,iBAAA,UAAA;aAC3B,CAAA;UACL,CAAA,GAAK,EAAA,MAAA,CAAA,EAAA;IAEA,KAAA,EAlDsB,aAkDtB,CAAA,OAAA,CAAA;IACD,aAAA,EAAA,MAAA;MACC,SAAA;kBACiB,CAAA,GAAA,EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,SAAA;KACd,CAAA,GAAA,EAAA,MAAA,EAAA,KAAA,EAvCW,aAuCX,CAAA,OAAA,CAAA,CAAA,EAAA,IAAA;QACZ,CAAA,GAAA,EAAA,MAAA,CAAA,EAAA,OAAA;EAAO,KAAA,CAAA,CAAA,EAAA,IAAA;
|
|
1
|
+
{"version":3,"file":"video.d.ts","names":[],"sources":["../src/video.ts"],"sourcesContent":[],"mappings":";;;;;;;;AA6CA;;;;AAmCuC,cAnC1B,aAAA,CAmC0B;EA+BjB,iBAAA,OAAiB;EAAA,iBAAA,UAAA;aAC3B,CAAA;UACL,CAAA,GAAK,EAAA,MAAA,CAAA,EAAA;IAEA,KAAA,EAlDsB,aAkDtB,CAAA,OAAA,CAAA;IACD,aAAA,EAAA,MAAA;MACC,SAAA;kBACiB,CAAA,GAAA,EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,SAAA;KACd,CAAA,GAAA,EAAA,MAAA,EAAA,KAAA,EAvCW,aAuCX,CAAA,OAAA,CAAA,CAAA,EAAA,IAAA;QACZ,CAAA,GAAA,EAAA,MAAA,CAAA,EAAA,OAAA;EAAO,KAAA,CAAA,CAAA,EAAA,IAAA;EAkTY,OAAA,CAAA,CAAA,EAAA,IAAA;EAAiB,IAAA,IAAA,CAAA,CAAA,EAAA,MAAA;;AAEhC,iBA7Te,iBAAA,CA6TV,GAAA,EA5TL,MAAA,CAAK,eA4TA,EAAA,GAAA,EA3TL,MAAA,CAAK,cA2TA,EAAA,GAAA,EAAA,MAAA,EAAA,QAAA,EAzTA,OAyTA,EAAA,EAAA,OAAA,EAxTD,OAwTC,EAAA,QAAA,EAvTA,eAuTA,EAAA,cAAA,EAAA,CAAA,GAAA,EAtTY,MAAA,CAAK,cAsTjB,EAAA,GAAA,IAAA,EAAA,WAAA,EArTG,aAqTH,CAAA,EApTT,OAoTS,CAAA,IAAA,CAAA;AAED,iBAJW,iBAAA,CAIX,GAAA,EAHJ,MAAA,CAAK,eAGD,EAAA,GAAA,EAFJ,MAAA,CAAK,cAED,EAAA,OAAA,EAAA,MAAA,EAAA,OAAA,EAAA,OAAA,EAAA,QAAA,EACC,eADD,EAAA,cAAA,EAAA,CAAA,GAAA,EAEa,MAAA,CAAK,cAFlB,EAAA,GAAA,IAAA,EAAA,WAAA,EAGI,aAHJ,CAAA,EAIR,OAJQ,CAAA,IAAA,CAAA"}
|