@copilotkit/aimock 1.45.1 → 1.47.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 +25 -1
- package/README.md +11 -2
- package/dist/agui-mock.cjs +25 -25
- 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 +25 -25
- package/dist/agui-mock.js.map +1 -1
- package/dist/agui-types.d.cts.map +1 -1
- package/dist/aimock-cli.cjs +15 -1
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.d.cts.map +1 -1
- package/dist/aimock-cli.d.ts.map +1 -1
- package/dist/aimock-cli.js +16 -2
- package/dist/aimock-cli.js.map +1 -1
- package/dist/api-key-auth.cjs +10 -9
- package/dist/api-key-auth.cjs.map +1 -1
- package/dist/api-key-auth.d.cts.map +1 -1
- package/dist/api-key-auth.d.ts.map +1 -1
- package/dist/api-key-auth.js +11 -10
- package/dist/api-key-auth.js.map +1 -1
- package/dist/aws-event-stream.cjs +20 -23
- package/dist/aws-event-stream.cjs.map +1 -1
- package/dist/aws-event-stream.d.cts +0 -1
- package/dist/aws-event-stream.d.ts +0 -1
- package/dist/aws-event-stream.js +20 -24
- package/dist/aws-event-stream.js.map +1 -1
- package/dist/batches.cjs +2 -4
- package/dist/batches.cjs.map +1 -1
- package/dist/batches.d.cts.map +1 -1
- package/dist/batches.d.ts.map +1 -1
- package/dist/batches.js +2 -4
- package/dist/batches.js.map +1 -1
- package/dist/bedrock-converse.cjs +139 -161
- 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 +137 -159
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +175 -216
- 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 +172 -213
- package/dist/bedrock.js.map +1 -1
- package/dist/byteplus-video.cjs +11 -10
- package/dist/byteplus-video.cjs.map +1 -1
- package/dist/byteplus-video.d.cts.map +1 -1
- package/dist/byteplus-video.d.ts.map +1 -1
- package/dist/byteplus-video.js +5 -4
- package/dist/byteplus-video.js.map +1 -1
- package/dist/chaos.cjs +5 -4
- package/dist/chaos.cjs.map +1 -1
- package/dist/chaos.d.cts.map +1 -1
- package/dist/chaos.d.ts.map +1 -1
- package/dist/chaos.js +5 -4
- package/dist/chaos.js.map +1 -1
- package/dist/cli.cjs +15 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +15 -0
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +107 -180
- 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 +106 -179
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.cjs +29 -1
- package/dist/config-loader.cjs.map +1 -1
- package/dist/config-loader.d.cts +7 -1
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts +7 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +29 -2
- package/dist/config-loader.js.map +1 -1
- package/dist/count-tokens.cjs +146 -0
- package/dist/count-tokens.cjs.map +1 -0
- package/dist/count-tokens.js +146 -0
- package/dist/count-tokens.js.map +1 -0
- package/dist/elevenlabs-audio.cjs +36 -34
- package/dist/elevenlabs-audio.cjs.map +1 -1
- package/dist/elevenlabs-audio.d.cts.map +1 -1
- package/dist/elevenlabs-audio.d.ts.map +1 -1
- package/dist/elevenlabs-audio.js +33 -31
- package/dist/elevenlabs-audio.js.map +1 -1
- package/dist/elevenlabs-voice.cjs +38 -37
- package/dist/elevenlabs-voice.cjs.map +1 -1
- package/dist/elevenlabs-voice.d.cts.map +1 -1
- package/dist/elevenlabs-voice.d.ts.map +1 -1
- package/dist/elevenlabs-voice.js +33 -32
- package/dist/elevenlabs-voice.js.map +1 -1
- package/dist/embedding-request.cjs +16 -0
- package/dist/embedding-request.cjs.map +1 -0
- package/dist/embedding-request.js +14 -0
- package/dist/embedding-request.js.map +1 -0
- package/dist/embeddings.cjs +30 -20
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.d.cts.map +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +30 -20
- package/dist/embeddings.js.map +1 -1
- package/dist/fal-audio.cjs +45 -43
- package/dist/fal-audio.cjs.map +1 -1
- package/dist/fal-audio.d.cts.map +1 -1
- package/dist/fal-audio.d.ts.map +1 -1
- package/dist/fal-audio.js +42 -40
- package/dist/fal-audio.js.map +1 -1
- package/dist/fal.cjs +17 -15
- package/dist/fal.cjs.map +1 -1
- package/dist/fal.js +16 -14
- package/dist/fal.js.map +1 -1
- package/dist/files.cjs +2 -4
- package/dist/files.cjs.map +1 -1
- package/dist/files.d.cts.map +1 -1
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +2 -4
- package/dist/files.js.map +1 -1
- package/dist/fine-tuning.cjs +2 -4
- package/dist/fine-tuning.cjs.map +1 -1
- package/dist/fine-tuning.d.cts.map +1 -1
- package/dist/fine-tuning.d.ts.map +1 -1
- package/dist/fine-tuning.js +2 -4
- package/dist/fine-tuning.js.map +1 -1
- package/dist/fixture-loader.cjs +140 -44
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +141 -46
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-embeddings.cjs +26 -24
- package/dist/gemini-embeddings.cjs.map +1 -1
- package/dist/gemini-embeddings.js +26 -24
- package/dist/gemini-embeddings.js.map +1 -1
- package/dist/gemini-interactions.cjs +113 -156
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts +0 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts +0 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +113 -156
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +295 -218
- 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 +295 -219
- package/dist/gemini.js.map +1 -1
- package/dist/grok-video.cjs +14 -12
- package/dist/grok-video.cjs.map +1 -1
- package/dist/grok-video.d.cts.map +1 -1
- package/dist/grok-video.d.ts.map +1 -1
- package/dist/grok-video.js +8 -6
- package/dist/grok-video.js.map +1 -1
- package/dist/helpers.cjs +411 -75
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +51 -1
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +51 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +402 -74
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +30 -28
- 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 +27 -25
- package/dist/images.js.map +1 -1
- package/dist/index.cjs +4 -2
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/journal.cjs +3 -1
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +2 -1
- package/dist/journal.js.map +1 -1
- package/dist/llmock.cjs +3 -0
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +3 -0
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-fakes.d.cts.map +1 -1
- package/dist/mcp-handler.cjs +7 -3
- package/dist/mcp-handler.cjs.map +1 -1
- package/dist/mcp-handler.js +7 -3
- package/dist/mcp-handler.js.map +1 -1
- package/dist/mcp-recorder.cjs +2 -1
- package/dist/mcp-recorder.cjs.map +1 -1
- package/dist/mcp-recorder.js +3 -2
- package/dist/mcp-recorder.js.map +1 -1
- package/dist/messages.cjs +235 -202
- 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 +234 -202
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +5 -2
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +6 -3
- package/dist/metrics.js.map +1 -1
- package/dist/misbehavior.cjs +62 -3
- package/dist/misbehavior.cjs.map +1 -1
- package/dist/misbehavior.d.cts.map +1 -1
- package/dist/misbehavior.d.ts.map +1 -1
- package/dist/misbehavior.js +63 -4
- package/dist/misbehavior.js.map +1 -1
- package/dist/model-utils.cjs +1 -0
- package/dist/model-utils.cjs.map +1 -1
- package/dist/model-utils.js +1 -1
- package/dist/model-utils.js.map +1 -1
- package/dist/moderation.cjs +15 -12
- package/dist/moderation.cjs.map +1 -1
- package/dist/moderation.js +14 -11
- package/dist/moderation.js.map +1 -1
- package/dist/ndjson-writer.cjs +21 -23
- package/dist/ndjson-writer.cjs.map +1 -1
- package/dist/ndjson-writer.d.cts +0 -1
- package/dist/ndjson-writer.d.cts.map +1 -1
- package/dist/ndjson-writer.d.ts +0 -1
- package/dist/ndjson-writer.d.ts.map +1 -1
- package/dist/ndjson-writer.js +21 -24
- package/dist/ndjson-writer.js.map +1 -1
- package/dist/ollama.cjs +276 -210
- 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 +273 -207
- package/dist/ollama.js.map +1 -1
- package/dist/openapi.cjs +323 -3
- package/dist/openapi.cjs.map +1 -1
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +324 -4
- package/dist/openapi.js.map +1 -1
- package/dist/openrouter-chat.cjs +11 -155
- package/dist/openrouter-chat.cjs.map +1 -1
- package/dist/openrouter-chat.js +11 -155
- package/dist/openrouter-chat.js.map +1 -1
- package/dist/openrouter-video.cjs +51 -74
- 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 +37 -60
- package/dist/openrouter-video.js.map +1 -1
- package/dist/raw-validation.cjs +177 -0
- package/dist/raw-validation.cjs.map +1 -0
- package/dist/raw-validation.js +176 -0
- package/dist/raw-validation.js.map +1 -0
- package/dist/recorder.cjs +176 -95
- 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 +176 -95
- package/dist/recorder.js.map +1 -1
- package/dist/rerank.cjs +13 -11
- package/dist/rerank.cjs.map +1 -1
- package/dist/rerank.js +12 -10
- package/dist/rerank.js.map +1 -1
- package/dist/response/catalog/anthropic-page.cjs +39 -0
- package/dist/response/catalog/anthropic-page.cjs.map +1 -0
- package/dist/response/catalog/anthropic-page.js +38 -0
- package/dist/response/catalog/anthropic-page.js.map +1 -0
- package/dist/response/catalog/collect.cjs +61 -0
- package/dist/response/catalog/collect.cjs.map +1 -0
- package/dist/response/catalog/collect.js +59 -0
- package/dist/response/catalog/collect.js.map +1 -0
- package/dist/response/catalog/gemini-page.cjs +25 -0
- package/dist/response/catalog/gemini-page.cjs.map +1 -0
- package/dist/response/catalog/gemini-page.js +24 -0
- package/dist/response/catalog/gemini-page.js.map +1 -0
- package/dist/response/catalog/render.cjs +182 -0
- package/dist/response/catalog/render.cjs.map +1 -0
- package/dist/response/catalog/render.js +171 -0
- package/dist/response/catalog/render.js.map +1 -0
- package/dist/response/deliver.cjs +144 -0
- package/dist/response/deliver.cjs.map +1 -0
- package/dist/response/deliver.js +143 -0
- package/dist/response/deliver.js.map +1 -0
- package/dist/response/errors.cjs +225 -0
- package/dist/response/errors.cjs.map +1 -0
- package/dist/response/errors.js +224 -0
- package/dist/response/errors.js.map +1 -0
- package/dist/response/finish.cjs +39 -0
- package/dist/response/finish.cjs.map +1 -0
- package/dist/response/finish.js +35 -0
- package/dist/response/finish.js.map +1 -0
- package/dist/response/frame-loop.cjs +102 -0
- package/dist/response/frame-loop.cjs.map +1 -0
- package/dist/response/frame-loop.js +100 -0
- package/dist/response/frame-loop.js.map +1 -0
- package/dist/response/lifecycle.cjs +223 -0
- package/dist/response/lifecycle.cjs.map +1 -0
- package/dist/response/lifecycle.js +211 -0
- package/dist/response/lifecycle.js.map +1 -0
- package/dist/response/policy.cjs +126 -0
- package/dist/response/policy.cjs.map +1 -0
- package/dist/response/policy.js +120 -0
- package/dist/response/policy.js.map +1 -0
- package/dist/response/profiles.cjs +69 -0
- package/dist/response/profiles.cjs.map +1 -0
- package/dist/response/profiles.js +65 -0
- package/dist/response/profiles.js.map +1 -0
- package/dist/response/proxy.cjs +71 -0
- package/dist/response/proxy.cjs.map +1 -0
- package/dist/response/proxy.js +70 -0
- package/dist/response/proxy.js.map +1 -0
- package/dist/response/raw.cjs +228 -0
- package/dist/response/raw.cjs.map +1 -0
- package/dist/response/raw.js +227 -0
- package/dist/response/raw.js.map +1 -0
- package/dist/response/timing.cjs +157 -0
- package/dist/response/timing.cjs.map +1 -0
- package/dist/response/timing.js +150 -0
- package/dist/response/timing.js.map +1 -0
- package/dist/response/usage/codecs/anthropic.cjs +52 -0
- package/dist/response/usage/codecs/anthropic.cjs.map +1 -0
- package/dist/response/usage/codecs/anthropic.js +50 -0
- package/dist/response/usage/codecs/anthropic.js.map +1 -0
- package/dist/response/usage/codecs/cohere.cjs +36 -0
- package/dist/response/usage/codecs/cohere.cjs.map +1 -0
- package/dist/response/usage/codecs/cohere.js +35 -0
- package/dist/response/usage/codecs/cohere.js.map +1 -0
- package/dist/response/usage/codecs/converse.cjs +16 -0
- package/dist/response/usage/codecs/converse.cjs.map +1 -0
- package/dist/response/usage/codecs/converse.js +15 -0
- package/dist/response/usage/codecs/converse.js.map +1 -0
- package/dist/response/usage/codecs/gemini.cjs +31 -0
- package/dist/response/usage/codecs/gemini.cjs.map +1 -0
- package/dist/response/usage/codecs/gemini.js +31 -0
- package/dist/response/usage/codecs/gemini.js.map +1 -0
- package/dist/response/usage/codecs/interactions.cjs +22 -0
- package/dist/response/usage/codecs/interactions.cjs.map +1 -0
- package/dist/response/usage/codecs/interactions.js +21 -0
- package/dist/response/usage/codecs/interactions.js.map +1 -0
- package/dist/response/usage/codecs/ollama.cjs +17 -0
- package/dist/response/usage/codecs/ollama.cjs.map +1 -0
- package/dist/response/usage/codecs/ollama.js +17 -0
- package/dist/response/usage/codecs/ollama.js.map +1 -0
- package/dist/response/usage/codecs/openai.cjs +52 -0
- package/dist/response/usage/codecs/openai.cjs.map +1 -0
- package/dist/response/usage/codecs/openai.js +50 -0
- package/dist/response/usage/codecs/openai.js.map +1 -0
- package/dist/response/usage/codecs/openrouter.cjs +68 -0
- package/dist/response/usage/codecs/openrouter.cjs.map +1 -0
- package/dist/response/usage/codecs/openrouter.js +67 -0
- package/dist/response/usage/codecs/openrouter.js.map +1 -0
- package/dist/response/usage/codecs/realtime.cjs +19 -0
- package/dist/response/usage/codecs/realtime.cjs.map +1 -0
- package/dist/response/usage/codecs/realtime.js +18 -0
- package/dist/response/usage/codecs/realtime.js.map +1 -0
- package/dist/response/usage/codecs/responses.cjs +21 -0
- package/dist/response/usage/codecs/responses.cjs.map +1 -0
- package/dist/response/usage/codecs/responses.js +20 -0
- package/dist/response/usage/codecs/responses.js.map +1 -0
- package/dist/response/usage/estimate.cjs +23 -0
- package/dist/response/usage/estimate.cjs.map +1 -0
- package/dist/response/usage/estimate.js +21 -0
- package/dist/response/usage/estimate.js.map +1 -0
- package/dist/response/usage/fields.cjs +72 -0
- package/dist/response/usage/fields.cjs.map +1 -0
- package/dist/response/usage/fields.js +70 -0
- package/dist/response/usage/fields.js.map +1 -0
- package/dist/response/usage/recording.cjs +52 -0
- package/dist/response/usage/recording.cjs.map +1 -0
- package/dist/response/usage/recording.js +52 -0
- package/dist/response/usage/recording.js.map +1 -0
- package/dist/responses.cjs +304 -196
- 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 +303 -195
- package/dist/responses.js.map +1 -1
- package/dist/route-registry.cjs +45 -0
- package/dist/route-registry.cjs.map +1 -1
- package/dist/route-registry.d.cts.map +1 -1
- package/dist/route-registry.d.ts.map +1 -1
- package/dist/route-registry.js +41 -1
- package/dist/route-registry.js.map +1 -1
- package/dist/router.cjs +24 -4
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts +7 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts +7 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +24 -5
- package/dist/router.js.map +1 -1
- package/dist/search.cjs +13 -11
- package/dist/search.cjs.map +1 -1
- package/dist/search.js +12 -10
- package/dist/search.js.map +1 -1
- package/dist/server.cjs +293 -248
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +294 -249
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +20 -18
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.d.cts.map +1 -1
- package/dist/speech.d.ts.map +1 -1
- package/dist/speech.js +19 -17
- package/dist/speech.js.map +1 -1
- package/dist/sse-writer.cjs +56 -30
- package/dist/sse-writer.cjs.map +1 -1
- package/dist/sse-writer.d.cts +0 -1
- package/dist/sse-writer.d.cts.map +1 -1
- package/dist/sse-writer.d.ts +0 -1
- package/dist/sse-writer.d.ts.map +1 -1
- package/dist/sse-writer.js +55 -31
- package/dist/sse-writer.js.map +1 -1
- package/dist/stream-collapse.cjs +118 -11
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +10 -9
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +10 -9
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +118 -11
- package/dist/stream-collapse.js.map +1 -1
- package/dist/transcription.cjs +31 -43
- 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 +31 -43
- package/dist/transcription.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +231 -9
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +231 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vector-mock.cjs +3 -8
- package/dist/vector-mock.cjs.map +1 -1
- package/dist/vector-mock.d.cts.map +1 -1
- package/dist/vector-mock.d.ts.map +1 -1
- package/dist/vector-mock.js +3 -8
- package/dist/vector-mock.js.map +1 -1
- package/dist/vector-stores.cjs +2 -4
- package/dist/vector-stores.cjs.map +1 -1
- package/dist/vector-stores.d.cts.map +1 -1
- package/dist/vector-stores.d.ts.map +1 -1
- package/dist/vector-stores.js +2 -4
- package/dist/vector-stores.js.map +1 -1
- package/dist/veo-video.cjs +16 -14
- package/dist/veo-video.cjs.map +1 -1
- package/dist/veo-video.js +10 -8
- package/dist/veo-video.js.map +1 -1
- package/dist/video.cjs +21 -19
- package/dist/video.cjs.map +1 -1
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts.map +1 -1
- package/dist/video.js +20 -18
- package/dist/video.js.map +1 -1
- package/dist/ws-framing.cjs +12 -6
- package/dist/ws-framing.cjs.map +1 -1
- package/dist/ws-framing.d.cts +2 -0
- package/dist/ws-framing.d.cts.map +1 -1
- package/dist/ws-framing.d.ts +2 -0
- package/dist/ws-framing.d.ts.map +1 -1
- package/dist/ws-framing.js +12 -6
- package/dist/ws-framing.js.map +1 -1
- package/dist/ws-gemini-live.cjs +131 -96
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.d.cts +0 -1
- package/dist/ws-gemini-live.d.cts.map +1 -1
- package/dist/ws-gemini-live.d.ts +0 -1
- package/dist/ws-gemini-live.d.ts.map +1 -1
- package/dist/ws-gemini-live.js +130 -95
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-live.cjs +6 -5
- package/dist/ws-live.cjs.map +1 -1
- package/dist/ws-live.js +5 -4
- package/dist/ws-live.js.map +1 -1
- package/dist/ws-realtime.cjs +108 -124
- 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 +105 -121
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +80 -59
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.d.cts.map +1 -1
- package/dist/ws-responses.d.ts.map +1 -1
- package/dist/ws-responses.js +79 -58
- package/dist/ws-responses.js.map +1 -1
- package/package.json +4 -1
- package/skills/write-fixtures/SKILL.md +57 -16
package/dist/video.cjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const require_errors = require('./response/errors.cjs');
|
|
2
|
+
const require_policy = require('./response/policy.cjs');
|
|
1
3
|
const require_helpers = require('./helpers.cjs');
|
|
2
4
|
const require_sse_writer = require('./sse-writer.cjs');
|
|
3
5
|
const require_chaos = require('./chaos.cjs');
|
|
@@ -101,11 +103,11 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
101
103
|
fixture: null
|
|
102
104
|
}
|
|
103
105
|
});
|
|
104
|
-
require_sse_writer.writeErrorResponse(res, 400,
|
|
106
|
+
require_sse_writer.writeErrorResponse(res, 400, require_errors.renderError("openai", "validation", {
|
|
105
107
|
message: `Malformed JSON: ${detail}`,
|
|
106
108
|
type: "invalid_request_error",
|
|
107
109
|
code: "invalid_json"
|
|
108
|
-
}
|
|
110
|
+
}).body);
|
|
109
111
|
return;
|
|
110
112
|
}
|
|
111
113
|
if (!require_helpers.isJsonObject(videoReq)) {
|
|
@@ -119,10 +121,10 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
119
121
|
fixture: null
|
|
120
122
|
}
|
|
121
123
|
});
|
|
122
|
-
require_sse_writer.writeErrorResponse(res, 400,
|
|
124
|
+
require_sse_writer.writeErrorResponse(res, 400, require_errors.renderError("openai", "validation", {
|
|
123
125
|
message: "Request body must be a JSON object",
|
|
124
126
|
type: "invalid_request_error"
|
|
125
|
-
}
|
|
127
|
+
}).body);
|
|
126
128
|
return;
|
|
127
129
|
}
|
|
128
130
|
if (!videoReq.prompt) {
|
|
@@ -136,10 +138,10 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
136
138
|
fixture: null
|
|
137
139
|
}
|
|
138
140
|
});
|
|
139
|
-
require_sse_writer.writeErrorResponse(res, 400,
|
|
141
|
+
require_sse_writer.writeErrorResponse(res, 400, require_errors.renderError("openai", "validation", {
|
|
140
142
|
message: "Missing required parameter: 'prompt'",
|
|
141
143
|
type: "invalid_request_error"
|
|
142
|
-
}
|
|
144
|
+
}).body);
|
|
143
145
|
return;
|
|
144
146
|
}
|
|
145
147
|
const prompt = videoReq.prompt;
|
|
@@ -155,10 +157,10 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
155
157
|
fixture: null
|
|
156
158
|
}
|
|
157
159
|
});
|
|
158
|
-
require_sse_writer.writeErrorResponse(res, 400,
|
|
160
|
+
require_sse_writer.writeErrorResponse(res, 400, require_errors.renderError("openai", "validation", {
|
|
159
161
|
message: "Invalid parameter: 'prompt' must be a string or a non-empty array of content parts",
|
|
160
162
|
type: "invalid_request_error"
|
|
161
|
-
}
|
|
163
|
+
}).body);
|
|
162
164
|
return;
|
|
163
165
|
}
|
|
164
166
|
const syntheticReq = {
|
|
@@ -183,7 +185,7 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
183
185
|
body: syntheticReq
|
|
184
186
|
}, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
|
|
185
187
|
if (!fixture) {
|
|
186
|
-
if (
|
|
188
|
+
if (require_policy.resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {
|
|
187
189
|
const strictMessage = require_helpers.strictNoMatchMessage(skippedBySequenceOrTurn);
|
|
188
190
|
defaults.logger.error(require_helpers.strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));
|
|
189
191
|
journal.add({
|
|
@@ -194,14 +196,14 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
194
196
|
response: {
|
|
195
197
|
status: 503,
|
|
196
198
|
fixture: null,
|
|
197
|
-
...
|
|
199
|
+
...require_policy.strictOverrideField(defaults.strict, req.headers)
|
|
198
200
|
}
|
|
199
201
|
});
|
|
200
|
-
require_sse_writer.writeErrorResponse(res, 503,
|
|
202
|
+
require_sse_writer.writeErrorResponse(res, 503, require_errors.renderError("openai", "strict-miss", {
|
|
201
203
|
message: strictMessage,
|
|
202
204
|
type: "invalid_request_error",
|
|
203
205
|
code: "no_fixture_match"
|
|
204
|
-
}
|
|
206
|
+
}).body);
|
|
205
207
|
return;
|
|
206
208
|
}
|
|
207
209
|
if (defaults.record) {
|
|
@@ -230,14 +232,14 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
230
232
|
response: {
|
|
231
233
|
status: 404,
|
|
232
234
|
fixture: null,
|
|
233
|
-
...
|
|
235
|
+
...require_policy.strictOverrideField(defaults.strict, req.headers)
|
|
234
236
|
}
|
|
235
237
|
});
|
|
236
|
-
require_sse_writer.writeErrorResponse(res, 404,
|
|
238
|
+
require_sse_writer.writeErrorResponse(res, 404, require_errors.renderError("openai", "fixture-miss", {
|
|
237
239
|
message: "No fixture matched",
|
|
238
240
|
type: "invalid_request_error",
|
|
239
241
|
code: "no_fixture_match"
|
|
240
|
-
}
|
|
242
|
+
}).body);
|
|
241
243
|
return;
|
|
242
244
|
}
|
|
243
245
|
const response = await require_helpers.resolveResponse(fixture, syntheticReq);
|
|
@@ -267,10 +269,10 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
267
269
|
fixture
|
|
268
270
|
}
|
|
269
271
|
});
|
|
270
|
-
require_sse_writer.writeErrorResponse(res, 500,
|
|
272
|
+
require_sse_writer.writeErrorResponse(res, 500, require_errors.renderError("openai", "unknown-response", {
|
|
271
273
|
message: "Fixture response is not a video type",
|
|
272
274
|
type: "server_error"
|
|
273
|
-
}
|
|
275
|
+
}).body);
|
|
274
276
|
return;
|
|
275
277
|
}
|
|
276
278
|
journal.add({
|
|
@@ -327,10 +329,10 @@ async function handleVideoStatus(req, res, videoId, journal, defaults, setCorsHe
|
|
|
327
329
|
fixture: null
|
|
328
330
|
}
|
|
329
331
|
});
|
|
330
|
-
require_sse_writer.writeErrorResponse(res, 404,
|
|
332
|
+
require_sse_writer.writeErrorResponse(res, 404, require_errors.renderError("openai", "route-failure", {
|
|
331
333
|
message: `Video ${videoId} not found`,
|
|
332
334
|
type: "not_found"
|
|
333
|
-
}
|
|
335
|
+
}).body);
|
|
334
336
|
return;
|
|
335
337
|
}
|
|
336
338
|
const { video, createdAtUnix: created_at } = entry;
|
package/dist/video.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
1
|
+
{"version":3,"file":"video.cjs","names":["extractBoundary","extractFormField","flattenHeaders","renderError","isJsonObject","getContext","getTestId","matchFixtureDiagnostic","applyChaosAsync","resolveRequestDecisions","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 getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { resolveRequestDecisions, strictOverrideField } from \"./response/policy.js\";\nimport { renderError } from \"./response/errors.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 renderError(\"openai\", \"validation\", {\n message: `Malformed JSON: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n }).body,\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 renderError(\"openai\", \"validation\", {\n message: \"Request body must be a JSON object\",\n type: \"invalid_request_error\",\n }).body,\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 renderError(\"openai\", \"validation\", {\n message: \"Missing required parameter: 'prompt'\",\n type: \"invalid_request_error\",\n }).body,\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 renderError(\"openai\", \"validation\", {\n message:\n \"Invalid parameter: 'prompt' must be a string or a non-empty array of content parts\",\n type: \"invalid_request_error\",\n }).body,\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 fixtureMiss = resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss;\n if (fixtureMiss) {\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 renderError(\"openai\", \"strict-miss\", {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n }).body,\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 renderError(\"openai\", \"fixture-miss\", {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n }).body,\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 renderError(\"openai\", \"unknown-response\", {\n message: \"Fixture response is not a video type\",\n type: \"server_error\",\n }).body,\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 renderError(\"openai\", \"route-failure\", {\n message: `Video ${videoId} not found`,\n type: \"not_found\",\n }).body,\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.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;EA0SY,OAAA,CAAA,CAAA,EAAA,IAAA;EAAiB,IAAA,IAAA,CAAA,CAAA,EAAA,MAAA;;AAEhC,iBArTe,iBAAA,CAqTV,GAAA,EApTL,MAAA,CAAK,eAoTA,EAAA,GAAA,EAnTL,MAAA,CAAK,cAmTA,EAAA,GAAA,EAAA,MAAA,EAAA,QAAA,EAjTA,OAiTA,EAAA,EAAA,OAAA,EAhTD,OAgTC,EAAA,QAAA,EA/SA,eA+SA,EAAA,cAAA,EAAA,CAAA,GAAA,EA9SY,MAAA,CAAK,cA8SjB,EAAA,GAAA,IAAA,EAAA,WAAA,EA7SG,aA6SH,CAAA,EA5ST,OA4SS,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.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;EA0SY,OAAA,CAAA,CAAA,EAAA,IAAA;EAAiB,IAAA,IAAA,CAAA,CAAA,EAAA,MAAA;;AAEhC,iBArTe,iBAAA,CAqTV,GAAA,EApTL,MAAA,CAAK,eAoTA,EAAA,GAAA,EAnTL,MAAA,CAAK,cAmTA,EAAA,GAAA,EAAA,MAAA,EAAA,QAAA,EAjTA,OAiTA,EAAA,EAAA,OAAA,EAhTD,OAgTC,EAAA,QAAA,EA/SA,eA+SA,EAAA,cAAA,EAAA,CAAA,GAAA,EA9SY,MAAA,CAAK,cA8SjB,EAAA,GAAA,IAAA,EAAA,WAAA,EA7SG,aA6SH,CAAA,EA5ST,OA4SS,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.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { renderError } from "./response/errors.js";
|
|
2
|
+
import { resolveRequestDecisions, strictOverrideField } from "./response/policy.js";
|
|
3
|
+
import { flattenHeaders, getContext, getTestId, isErrorResponse, isJsonObject, isVideoResponse, resolveResponse, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage } from "./helpers.js";
|
|
2
4
|
import { writeErrorResponse } from "./sse-writer.js";
|
|
3
5
|
import { applyChaosAsync } from "./chaos.js";
|
|
4
6
|
import { matchFixtureDiagnostic } from "./router.js";
|
|
@@ -101,11 +103,11 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
101
103
|
fixture: null
|
|
102
104
|
}
|
|
103
105
|
});
|
|
104
|
-
writeErrorResponse(res, 400,
|
|
106
|
+
writeErrorResponse(res, 400, renderError("openai", "validation", {
|
|
105
107
|
message: `Malformed JSON: ${detail}`,
|
|
106
108
|
type: "invalid_request_error",
|
|
107
109
|
code: "invalid_json"
|
|
108
|
-
}
|
|
110
|
+
}).body);
|
|
109
111
|
return;
|
|
110
112
|
}
|
|
111
113
|
if (!isJsonObject(videoReq)) {
|
|
@@ -119,10 +121,10 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
119
121
|
fixture: null
|
|
120
122
|
}
|
|
121
123
|
});
|
|
122
|
-
writeErrorResponse(res, 400,
|
|
124
|
+
writeErrorResponse(res, 400, renderError("openai", "validation", {
|
|
123
125
|
message: "Request body must be a JSON object",
|
|
124
126
|
type: "invalid_request_error"
|
|
125
|
-
}
|
|
127
|
+
}).body);
|
|
126
128
|
return;
|
|
127
129
|
}
|
|
128
130
|
if (!videoReq.prompt) {
|
|
@@ -136,10 +138,10 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
136
138
|
fixture: null
|
|
137
139
|
}
|
|
138
140
|
});
|
|
139
|
-
writeErrorResponse(res, 400,
|
|
141
|
+
writeErrorResponse(res, 400, renderError("openai", "validation", {
|
|
140
142
|
message: "Missing required parameter: 'prompt'",
|
|
141
143
|
type: "invalid_request_error"
|
|
142
|
-
}
|
|
144
|
+
}).body);
|
|
143
145
|
return;
|
|
144
146
|
}
|
|
145
147
|
const prompt = videoReq.prompt;
|
|
@@ -155,10 +157,10 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
155
157
|
fixture: null
|
|
156
158
|
}
|
|
157
159
|
});
|
|
158
|
-
writeErrorResponse(res, 400,
|
|
160
|
+
writeErrorResponse(res, 400, renderError("openai", "validation", {
|
|
159
161
|
message: "Invalid parameter: 'prompt' must be a string or a non-empty array of content parts",
|
|
160
162
|
type: "invalid_request_error"
|
|
161
|
-
}
|
|
163
|
+
}).body);
|
|
162
164
|
return;
|
|
163
165
|
}
|
|
164
166
|
const syntheticReq = {
|
|
@@ -183,7 +185,7 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
183
185
|
body: syntheticReq
|
|
184
186
|
}, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
|
|
185
187
|
if (!fixture) {
|
|
186
|
-
if (
|
|
188
|
+
if (resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {
|
|
187
189
|
const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);
|
|
188
190
|
defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));
|
|
189
191
|
journal.add({
|
|
@@ -197,11 +199,11 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
197
199
|
...strictOverrideField(defaults.strict, req.headers)
|
|
198
200
|
}
|
|
199
201
|
});
|
|
200
|
-
writeErrorResponse(res, 503,
|
|
202
|
+
writeErrorResponse(res, 503, renderError("openai", "strict-miss", {
|
|
201
203
|
message: strictMessage,
|
|
202
204
|
type: "invalid_request_error",
|
|
203
205
|
code: "no_fixture_match"
|
|
204
|
-
}
|
|
206
|
+
}).body);
|
|
205
207
|
return;
|
|
206
208
|
}
|
|
207
209
|
if (defaults.record) {
|
|
@@ -233,11 +235,11 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
233
235
|
...strictOverrideField(defaults.strict, req.headers)
|
|
234
236
|
}
|
|
235
237
|
});
|
|
236
|
-
writeErrorResponse(res, 404,
|
|
238
|
+
writeErrorResponse(res, 404, renderError("openai", "fixture-miss", {
|
|
237
239
|
message: "No fixture matched",
|
|
238
240
|
type: "invalid_request_error",
|
|
239
241
|
code: "no_fixture_match"
|
|
240
|
-
}
|
|
242
|
+
}).body);
|
|
241
243
|
return;
|
|
242
244
|
}
|
|
243
245
|
const response = await resolveResponse(fixture, syntheticReq);
|
|
@@ -267,10 +269,10 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
267
269
|
fixture
|
|
268
270
|
}
|
|
269
271
|
});
|
|
270
|
-
writeErrorResponse(res, 500,
|
|
272
|
+
writeErrorResponse(res, 500, renderError("openai", "unknown-response", {
|
|
271
273
|
message: "Fixture response is not a video type",
|
|
272
274
|
type: "server_error"
|
|
273
|
-
}
|
|
275
|
+
}).body);
|
|
274
276
|
return;
|
|
275
277
|
}
|
|
276
278
|
journal.add({
|
|
@@ -327,10 +329,10 @@ async function handleVideoStatus(req, res, videoId, journal, defaults, setCorsHe
|
|
|
327
329
|
fixture: null
|
|
328
330
|
}
|
|
329
331
|
});
|
|
330
|
-
writeErrorResponse(res, 404,
|
|
332
|
+
writeErrorResponse(res, 404, renderError("openai", "route-failure", {
|
|
331
333
|
message: `Video ${videoId} not found`,
|
|
332
334
|
type: "not_found"
|
|
333
|
-
}
|
|
335
|
+
}).body);
|
|
334
336
|
return;
|
|
335
337
|
}
|
|
336
338
|
const { video, createdAtUnix: created_at } = entry;
|
package/dist/video.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video.js","names":[],"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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"}
|
|
1
|
+
{"version":3,"file":"video.js","names":[],"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 getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { resolveRequestDecisions, strictOverrideField } from \"./response/policy.js\";\nimport { renderError } from \"./response/errors.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 renderError(\"openai\", \"validation\", {\n message: `Malformed JSON: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n }).body,\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 renderError(\"openai\", \"validation\", {\n message: \"Request body must be a JSON object\",\n type: \"invalid_request_error\",\n }).body,\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 renderError(\"openai\", \"validation\", {\n message: \"Missing required parameter: 'prompt'\",\n type: \"invalid_request_error\",\n }).body,\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 renderError(\"openai\", \"validation\", {\n message:\n \"Invalid parameter: 'prompt' must be a string or a non-empty array of content parts\",\n type: \"invalid_request_error\",\n }).body,\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 fixtureMiss = resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss;\n if (fixtureMiss) {\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 renderError(\"openai\", \"strict-miss\", {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n }).body,\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 renderError(\"openai\", \"fixture-miss\", {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n }).body,\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 renderError(\"openai\", \"unknown-response\", {\n message: \"Fixture response is not a video type\",\n type: \"server_error\",\n }).body,\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 renderError(\"openai\", \"route-failure\", {\n message: `Video ${videoId} not found`,\n type: \"not_found\",\n }).body,\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/ws-framing.cjs
CHANGED
|
@@ -50,12 +50,11 @@ var WebSocketConnection = class extends node_events.EventEmitter {
|
|
|
50
50
|
this.parseFrames();
|
|
51
51
|
}
|
|
52
52
|
});
|
|
53
|
-
socket.on("close", () =>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
53
|
+
socket.on("close", () => this.lost());
|
|
54
|
+
socket.on("end", () => {
|
|
55
|
+
if (this.closed) return;
|
|
56
|
+
this.socket.destroy();
|
|
57
|
+
this.lost();
|
|
59
58
|
});
|
|
60
59
|
socket.on("error", (err) => {
|
|
61
60
|
this.emit("error", err);
|
|
@@ -103,6 +102,13 @@ var WebSocketConnection = class extends node_events.EventEmitter {
|
|
|
103
102
|
}
|
|
104
103
|
});
|
|
105
104
|
}
|
|
105
|
+
/** Mark an unannounced connection loss and emit close once. */
|
|
106
|
+
lost() {
|
|
107
|
+
if (this.closed) return;
|
|
108
|
+
this.closed = true;
|
|
109
|
+
this.release(/* @__PURE__ */ new Error("WebSocket closed"));
|
|
110
|
+
this.emit("close", 1006, "Connection lost");
|
|
111
|
+
}
|
|
106
112
|
release(error) {
|
|
107
113
|
this.buffer = Buffer.alloc(0);
|
|
108
114
|
this.fragments = [];
|
package/dist/ws-framing.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws-framing.cjs","names":["EventEmitter"],"sources":["../src/ws-framing.ts"],"sourcesContent":["/**\n * Minimal RFC 6455 WebSocket server implementation.\n *\n * Zero dependencies — uses only Node.js builtins (node:crypto, node:events).\n * Supports text frames, ping/pong, close handshake, and client frame unmasking.\n * Designed for a mock server — no extensions, no binary frames, no compression.\n */\n\nimport { createHash, randomBytes } from \"node:crypto\";\nimport { EventEmitter } from \"node:events\";\nimport type * as net from \"node:net\";\nimport type * as http from \"node:http\";\n\nconst WS_GUID = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\";\n\n// Opcodes\nconst OP_CONTINUATION = 0x0;\nconst OP_TEXT = 0x1;\nconst OP_CLOSE = 0x8;\nconst OP_PING = 0x9;\nconst OP_PONG = 0xa;\n\nexport interface WebSocketLimits {\n maxMessageBytes?: number;\n maxBufferedBytes?: number;\n maxWriteBytes?: number;\n /** Client role: mask outgoing frames and require unmasked incoming frames. */\n maskOutgoing?: boolean;\n}\n\nexport class WebSocketConnection extends EventEmitter {\n private socket: net.Socket;\n private buffer: Buffer = Buffer.alloc(0);\n private closed = false;\n\n // For fragmented messages (continuation frames)\n private fragments: Buffer[] = [];\n\n private fragmentBytes = 0;\n private fragmented = false;\n private pendingWriteBytes = 0;\n private pendingWrites = new Set<(error: Error) => void>();\n private limits: WebSocketLimits;\n\n constructor(socket: net.Socket, limits: WebSocketLimits = {}) {\n super();\n for (const value of [limits.maxMessageBytes, limits.maxBufferedBytes, limits.maxWriteBytes]) {\n if (value !== undefined && (!Number.isSafeInteger(value) || value <= 0)) {\n throw new RangeError(\"WebSocket limits must be positive safe integers\");\n }\n }\n this.limits = { ...limits };\n this.socket = socket;\n\n socket.on(\"data\", (data: Buffer) => {\n // Byte-idle consumers must see partial frames and control traffic too.\n // Emit no payload, and notify before parsing can dispatch a message.\n if (data.length > 0 && !this.closed) this.emit(\"activity\");\n let offset = 0;\n while (offset < data.length && !this.closed) {\n const room =\n (this.limits.maxBufferedBytes ?? Infinity) - this.fragmentBytes - this.buffer.length;\n if (room <= 0) {\n this.close(1009, \"WebSocket buffer limit exceeded\");\n return;\n }\n const end = offset + Math.min(room, data.length - offset);\n this.buffer = Buffer.concat([this.buffer, data.subarray(offset, end)]);\n offset = end;\n this.parseFrames();\n }\n });\n\n socket.on(\"close\", () => {\n if (!this.closed) {\n this.closed = true;\n this.release(new Error(\"WebSocket closed\"));\n this.emit(\"close\", 1006, \"Connection lost\");\n }\n });\n\n socket.on(\"error\", (err: Error) => {\n this.emit(\"error\", err);\n });\n }\n\n send(data: string): void {\n if (this.closed) return;\n void this.sendAsync(data).catch(() => {\n /* Legacy fire-and-forget API. */\n });\n }\n\n sendAsync(data: string, signal?: AbortSignal): Promise<void> {\n if (this.closed || this.socket.destroyed) return Promise.reject(new Error(\"WebSocket closed\"));\n if (signal?.aborted) return Promise.reject(new Error(\"WebSocket send aborted\"));\n const length = Buffer.byteLength(data);\n const frameBytes =\n length + (length < 126 ? 2 : length < 65536 ? 4 : 10) + (this.limits.maskOutgoing ? 4 : 0);\n if (\n frameBytes >\n (this.limits.maxWriteBytes ?? Infinity) -\n Math.max(this.pendingWriteBytes, this.socket.writableLength)\n ) {\n this.close(1009, \"WebSocket write limit exceeded\");\n return Promise.reject(new Error(\"WebSocket write limit exceeded\"));\n }\n this.pendingWriteBytes += frameBytes;\n return new Promise<void>((resolve, reject) => {\n let settled = false;\n const finish = (error?: Error) => {\n if (settled) return;\n settled = true;\n this.pendingWriteBytes -= frameBytes;\n this.pendingWrites.delete(fail);\n signal?.removeEventListener(\"abort\", abort);\n if (error) reject(error);\n else resolve();\n };\n const fail = (error: Error) => finish(error);\n const abort = () => {\n finish(new Error(\"WebSocket send aborted\"));\n this.destroy();\n };\n this.pendingWrites.add(fail);\n signal?.addEventListener(\"abort\", abort, { once: true });\n try {\n this.socket.write(this.encodeFrame(OP_TEXT, Buffer.from(data)), (error?: Error | null) => {\n if (error) finish(new Error(\"WebSocket write failed\"));\n else finish();\n });\n } catch {\n finish(new Error(\"WebSocket write failed\"));\n }\n });\n }\n\n private release(error: Error): void {\n this.buffer = Buffer.alloc(0);\n this.fragments = [];\n this.fragmentBytes = 0;\n for (const fail of this.pendingWrites) fail(error);\n }\n\n close(code = 1000, reason = \"\"): void {\n if (this.closed) return;\n this.closed = true;\n this.release(new Error(\"WebSocket closed\"));\n\n const encodedReason = Buffer.from(reason, \"utf-8\");\n let reasonEnd = Math.min(encodedReason.length, 123);\n // A continuation byte at the cut means its code point straddles the limit.\n while (reasonEnd < encodedReason.length && (encodedReason[reasonEnd] & 0xc0) === 0x80) {\n reasonEnd--;\n }\n const reasonBuf = encodedReason.subarray(0, reasonEnd);\n const payload = Buffer.alloc(2 + reasonBuf.length);\n payload.writeUInt16BE(code, 0);\n reasonBuf.copy(payload, 2);\n this.writeFrame(OP_CLOSE, payload);\n\n // Give the client a moment to receive the close frame before destroying.\n // If writeFrame failed (socket already destroyed), this is a no-op.\n setTimeout(() => {\n if (!this.socket.destroyed) {\n this.socket.destroy();\n }\n // Emit close event for server-initiated closes so listeners\n // (e.g. activeConnections.delete) always fire.\n this.emit(\"close\", code, reason);\n }, 100);\n }\n\n destroy(): void {\n if (this.closed) return;\n this.closed = true;\n this.release(new Error(\"WebSocket destroyed\"));\n if (!this.socket.destroyed) {\n this.socket.destroy();\n }\n this.emit(\"close\", 1006, \"Connection destroyed\");\n }\n\n get isClosed(): boolean {\n return this.closed;\n }\n\n private encodeFrame(opcode: number, payload: Buffer): Buffer {\n const length = payload.length;\n const headerSize = length < 126 ? 2 : length < 65536 ? 4 : 10;\n const maskSize = this.limits.maskOutgoing ? 4 : 0;\n const frame = Buffer.allocUnsafe(headerSize + maskSize + length);\n frame[0] = 0x80 | opcode;\n frame[1] = (maskSize ? 0x80 : 0) | (length < 126 ? length : length < 65536 ? 126 : 127);\n if (headerSize === 4) frame.writeUInt16BE(length, 2);\n if (headerSize === 10) frame.writeBigUInt64BE(BigInt(length), 2);\n if (maskSize) {\n const mask = randomBytes(4);\n mask.copy(frame, headerSize);\n for (let i = 0; i < length; i++) frame[headerSize + 4 + i] = payload[i] ^ mask[i % 4];\n } else payload.copy(frame, headerSize);\n return frame;\n }\n\n private writeFrame(opcode: number, payload: Buffer): void {\n if (this.socket.destroyed) return;\n const available =\n (this.limits.maxWriteBytes ?? Infinity) -\n Math.max(this.pendingWriteBytes, this.socket.writableLength);\n const headerBytes = 2 + (this.limits.maskOutgoing ? 4 : 0);\n if (opcode === OP_CLOSE && payload.length + headerBytes > available) {\n // Keep even the final control frame inside the configured socket budget.\n if (available < headerBytes + 2) {\n this.socket.destroy();\n return;\n }\n payload = payload.subarray(0, 2);\n } else if (payload.length + headerBytes > available) {\n this.close(1009, \"WebSocket write limit exceeded\");\n return;\n }\n const frame = this.encodeFrame(opcode, payload);\n this.socket.write(frame);\n }\n\n private parseFrames(): void {\n while (this.buffer.length >= 2 && !this.closed) {\n const byte0 = this.buffer[0];\n const byte1 = this.buffer[1];\n\n const fin = (byte0 & 0x80) !== 0;\n const opcode = byte0 & 0x0f;\n const masked = (byte1 & 0x80) !== 0;\n if (\n byte0 & 0x70 ||\n masked === Boolean(this.limits.maskOutgoing) ||\n ![OP_CONTINUATION, OP_TEXT, 2, OP_CLOSE, OP_PING, OP_PONG].includes(opcode) ||\n (opcode >= 8 && (!fin || (byte1 & 0x7f) > 125)) ||\n (opcode === OP_CONTINUATION && !this.fragmented) ||\n (opcode === OP_TEXT && this.fragmented)\n ) {\n this.close(1002, \"Invalid WebSocket frame\");\n return;\n }\n let payloadLength = byte1 & 0x7f;\n let offset = 2;\n\n if (payloadLength === 126) {\n if (this.buffer.length < 4) return; // need more data\n payloadLength = this.buffer.readUInt16BE(2);\n offset = 4;\n } else if (payloadLength === 127) {\n if (this.buffer.length < 10) return;\n const advertised = this.buffer.readBigUInt64BE(2);\n if (advertised & (1n << 63n)) {\n this.close(1002, \"Invalid WebSocket length\");\n return;\n }\n if (advertised > BigInt(Number.MAX_SAFE_INTEGER)) {\n this.close(1009, \"WebSocket message limit exceeded\");\n return;\n }\n payloadLength = Number(advertised);\n offset = 10;\n }\n\n if (\n ((byte1 & 0x7f) === 126 && payloadLength < 126) ||\n ((byte1 & 0x7f) === 127 && payloadLength < 65536)\n ) {\n this.close(1002, \"Invalid WebSocket length\");\n return;\n }\n if (\n opcode < 8 &&\n payloadLength > (this.limits.maxMessageBytes ?? Infinity) - this.fragmentBytes\n ) {\n this.close(1009, \"WebSocket message limit exceeded\");\n return;\n }\n if (\n payloadLength + offset + (masked ? 4 : 0) >\n (this.limits.maxBufferedBytes ?? Infinity) - this.fragmentBytes\n ) {\n this.close(1009, \"WebSocket buffer limit exceeded\");\n return;\n }\n const maskSize = masked ? 4 : 0;\n const totalFrameSize = offset + maskSize + payloadLength;\n\n if (this.buffer.length < totalFrameSize) return; // need more data\n\n let maskKey: Buffer | null = null;\n if (masked) {\n maskKey = this.buffer.subarray(offset, offset + 4);\n offset += 4;\n }\n\n let payload = this.buffer.subarray(offset, offset + payloadLength);\n\n // Unmask client payload\n if (maskKey) {\n payload = Buffer.from(payload); // copy before mutating\n for (let i = 0; i < payload.length; i++) {\n payload[i] ^= maskKey[i % 4];\n }\n }\n\n // Consume the frame from the buffer\n this.buffer = this.buffer.subarray(totalFrameSize);\n\n this.handleFrame(fin, opcode, payload);\n }\n }\n\n private handleFrame(fin: boolean, opcode: number, payload: Buffer): void {\n // Control frames (opcode >= 0x8) must not be fragmented\n if (opcode === OP_PING) {\n this.writeFrame(OP_PONG, payload);\n return;\n }\n\n if (opcode === OP_PONG) {\n // Ignore unsolicited pongs\n return;\n }\n\n if (opcode === OP_CLOSE) {\n if (payload.length === 1) {\n this.close(1002, \"Invalid WebSocket close\");\n return;\n }\n const code = payload.length >= 2 ? payload.readUInt16BE(0) : 1005;\n const reason = payload.length > 2 ? payload.subarray(2).toString(\"utf-8\") : \"\";\n\n if (!this.closed) {\n this.closed = true;\n this.release(new Error(\"WebSocket closed\"));\n // Echo close frame back\n this.writeFrame(OP_CLOSE, payload);\n this.socket.end();\n this.emit(\"close\", code, reason);\n }\n // If already closed (server-initiated or duplicate), ignore — the\n // close event was already emitted by close() or the first OP_CLOSE.\n return;\n }\n\n // Text or continuation frames\n if (opcode === OP_TEXT || opcode === OP_CONTINUATION) {\n this.fragmented = !fin;\n this.fragmentBytes += payload.length;\n if (payload.length) this.fragments.push(payload);\n\n if (fin) {\n const message = Buffer.concat(this.fragments).toString(\"utf-8\");\n this.fragments = [];\n this.fragmentBytes = 0;\n this.emit(\"message\", message);\n }\n // If !fin, wait for more continuation frames\n return;\n }\n\n // Binary or unknown — just ignore for a mock server\n }\n}\n\nexport function computeAcceptKey(wsKey: string): string {\n return createHash(\"sha1\")\n .update(wsKey + WS_GUID)\n .digest(\"base64\");\n}\n\nexport function upgradeToWebSocket(\n req: http.IncomingMessage,\n socket: net.Socket,\n limits?: WebSocketLimits,\n): WebSocketConnection {\n const key = req.headers[\"sec-websocket-key\"];\n if (!key) {\n socket.write(\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\");\n socket.destroy();\n throw new Error(\"Missing Sec-WebSocket-Key header\");\n }\n\n const acceptKey = computeAcceptKey(key);\n\n let responseHeaders =\n \"HTTP/1.1 101 Switching Protocols\\r\\n\" +\n \"Upgrade: websocket\\r\\n\" +\n \"Connection: Upgrade\\r\\n\" +\n `Sec-WebSocket-Accept: ${acceptKey}\\r\\n`;\n\n // Echo back requested subprotocol if present\n const protocol = req.headers[\"sec-websocket-protocol\"];\n if (protocol) {\n // Take the first offered protocol\n const first = protocol.split(\",\")[0].trim();\n responseHeaders += `Sec-WebSocket-Protocol: ${first}\\r\\n`;\n }\n\n responseHeaders += \"\\r\\n\";\n\n socket.write(responseHeaders);\n\n return new WebSocketConnection(socket, limits);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ws-framing.cjs","names":["EventEmitter"],"sources":["../src/ws-framing.ts"],"sourcesContent":["/**\n * Minimal RFC 6455 WebSocket server implementation.\n *\n * Zero dependencies — uses only Node.js builtins (node:crypto, node:events).\n * Supports text frames, ping/pong, close handshake, and client frame unmasking.\n * Designed for a mock server — no extensions, no binary frames, no compression.\n */\n\nimport { createHash, randomBytes } from \"node:crypto\";\nimport { EventEmitter } from \"node:events\";\nimport type * as net from \"node:net\";\nimport type * as http from \"node:http\";\n\nconst WS_GUID = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\";\n\n// Opcodes\nconst OP_CONTINUATION = 0x0;\nconst OP_TEXT = 0x1;\nconst OP_CLOSE = 0x8;\nconst OP_PING = 0x9;\nconst OP_PONG = 0xa;\n\nexport interface WebSocketLimits {\n maxMessageBytes?: number;\n maxBufferedBytes?: number;\n maxWriteBytes?: number;\n /** Client role: mask outgoing frames and require unmasked incoming frames. */\n maskOutgoing?: boolean;\n}\n\nexport class WebSocketConnection extends EventEmitter {\n private socket: net.Socket;\n private buffer: Buffer = Buffer.alloc(0);\n private closed = false;\n\n // For fragmented messages (continuation frames)\n private fragments: Buffer[] = [];\n\n private fragmentBytes = 0;\n private fragmented = false;\n private pendingWriteBytes = 0;\n private pendingWrites = new Set<(error: Error) => void>();\n private limits: WebSocketLimits;\n\n constructor(socket: net.Socket, limits: WebSocketLimits = {}) {\n super();\n for (const value of [limits.maxMessageBytes, limits.maxBufferedBytes, limits.maxWriteBytes]) {\n if (value !== undefined && (!Number.isSafeInteger(value) || value <= 0)) {\n throw new RangeError(\"WebSocket limits must be positive safe integers\");\n }\n }\n this.limits = { ...limits };\n this.socket = socket;\n\n socket.on(\"data\", (data: Buffer) => {\n // Byte-idle consumers must see partial frames and control traffic too.\n // Emit no payload, and notify before parsing can dispatch a message.\n if (data.length > 0 && !this.closed) this.emit(\"activity\");\n let offset = 0;\n while (offset < data.length && !this.closed) {\n const room =\n (this.limits.maxBufferedBytes ?? Infinity) - this.fragmentBytes - this.buffer.length;\n if (room <= 0) {\n this.close(1009, \"WebSocket buffer limit exceeded\");\n return;\n }\n const end = offset + Math.min(room, data.length - offset);\n this.buffer = Buffer.concat([this.buffer, data.subarray(offset, end)]);\n offset = end;\n this.parseFrames();\n }\n });\n\n socket.on(\"close\", () => this.lost());\n\n // A peer FIN without a close frame is a disconnect. HTTP upgrade sockets\n // allow half-open, so without this the loss surfaces only on a later\n // failed write. Data events precede \"end\", so frames that arrive just\n // before the FIN still parse, but any turn they start is cancelled at once\n // because the connection is then lost.\n socket.on(\"end\", () => {\n if (this.closed) return;\n this.socket.destroy();\n this.lost();\n });\n\n socket.on(\"error\", (err: Error) => {\n this.emit(\"error\", err);\n });\n }\n\n send(data: string): void {\n if (this.closed) return;\n void this.sendAsync(data).catch(() => {\n /* Legacy fire-and-forget API. */\n });\n }\n\n sendAsync(data: string, signal?: AbortSignal): Promise<void> {\n if (this.closed || this.socket.destroyed) return Promise.reject(new Error(\"WebSocket closed\"));\n if (signal?.aborted) return Promise.reject(new Error(\"WebSocket send aborted\"));\n const length = Buffer.byteLength(data);\n const frameBytes =\n length + (length < 126 ? 2 : length < 65536 ? 4 : 10) + (this.limits.maskOutgoing ? 4 : 0);\n if (\n frameBytes >\n (this.limits.maxWriteBytes ?? Infinity) -\n Math.max(this.pendingWriteBytes, this.socket.writableLength)\n ) {\n this.close(1009, \"WebSocket write limit exceeded\");\n return Promise.reject(new Error(\"WebSocket write limit exceeded\"));\n }\n this.pendingWriteBytes += frameBytes;\n return new Promise<void>((resolve, reject) => {\n let settled = false;\n const finish = (error?: Error) => {\n if (settled) return;\n settled = true;\n this.pendingWriteBytes -= frameBytes;\n this.pendingWrites.delete(fail);\n signal?.removeEventListener(\"abort\", abort);\n if (error) reject(error);\n else resolve();\n };\n const fail = (error: Error) => finish(error);\n const abort = () => {\n finish(new Error(\"WebSocket send aborted\"));\n this.destroy();\n };\n this.pendingWrites.add(fail);\n signal?.addEventListener(\"abort\", abort, { once: true });\n try {\n this.socket.write(this.encodeFrame(OP_TEXT, Buffer.from(data)), (error?: Error | null) => {\n if (error) finish(new Error(\"WebSocket write failed\"));\n else finish();\n });\n } catch {\n finish(new Error(\"WebSocket write failed\"));\n }\n });\n }\n\n /** Mark an unannounced connection loss and emit close once. */\n private lost(): void {\n if (this.closed) return;\n this.closed = true;\n this.release(new Error(\"WebSocket closed\"));\n this.emit(\"close\", 1006, \"Connection lost\");\n }\n\n private release(error: Error): void {\n this.buffer = Buffer.alloc(0);\n this.fragments = [];\n this.fragmentBytes = 0;\n for (const fail of this.pendingWrites) fail(error);\n }\n\n close(code = 1000, reason = \"\"): void {\n if (this.closed) return;\n this.closed = true;\n this.release(new Error(\"WebSocket closed\"));\n\n const encodedReason = Buffer.from(reason, \"utf-8\");\n let reasonEnd = Math.min(encodedReason.length, 123);\n // A continuation byte at the cut means its code point straddles the limit.\n while (reasonEnd < encodedReason.length && (encodedReason[reasonEnd] & 0xc0) === 0x80) {\n reasonEnd--;\n }\n const reasonBuf = encodedReason.subarray(0, reasonEnd);\n const payload = Buffer.alloc(2 + reasonBuf.length);\n payload.writeUInt16BE(code, 0);\n reasonBuf.copy(payload, 2);\n this.writeFrame(OP_CLOSE, payload);\n\n // Give the client a moment to receive the close frame before destroying.\n // If writeFrame failed (socket already destroyed), this is a no-op.\n setTimeout(() => {\n if (!this.socket.destroyed) {\n this.socket.destroy();\n }\n // Emit close event for server-initiated closes so listeners\n // (e.g. activeConnections.delete) always fire.\n this.emit(\"close\", code, reason);\n }, 100);\n }\n\n destroy(): void {\n if (this.closed) return;\n this.closed = true;\n this.release(new Error(\"WebSocket destroyed\"));\n if (!this.socket.destroyed) {\n this.socket.destroy();\n }\n this.emit(\"close\", 1006, \"Connection destroyed\");\n }\n\n get isClosed(): boolean {\n return this.closed;\n }\n\n private encodeFrame(opcode: number, payload: Buffer): Buffer {\n const length = payload.length;\n const headerSize = length < 126 ? 2 : length < 65536 ? 4 : 10;\n const maskSize = this.limits.maskOutgoing ? 4 : 0;\n const frame = Buffer.allocUnsafe(headerSize + maskSize + length);\n frame[0] = 0x80 | opcode;\n frame[1] = (maskSize ? 0x80 : 0) | (length < 126 ? length : length < 65536 ? 126 : 127);\n if (headerSize === 4) frame.writeUInt16BE(length, 2);\n if (headerSize === 10) frame.writeBigUInt64BE(BigInt(length), 2);\n if (maskSize) {\n const mask = randomBytes(4);\n mask.copy(frame, headerSize);\n for (let i = 0; i < length; i++) frame[headerSize + 4 + i] = payload[i] ^ mask[i % 4];\n } else payload.copy(frame, headerSize);\n return frame;\n }\n\n private writeFrame(opcode: number, payload: Buffer): void {\n if (this.socket.destroyed) return;\n const available =\n (this.limits.maxWriteBytes ?? Infinity) -\n Math.max(this.pendingWriteBytes, this.socket.writableLength);\n const headerBytes = 2 + (this.limits.maskOutgoing ? 4 : 0);\n if (opcode === OP_CLOSE && payload.length + headerBytes > available) {\n // Keep even the final control frame inside the configured socket budget.\n if (available < headerBytes + 2) {\n this.socket.destroy();\n return;\n }\n payload = payload.subarray(0, 2);\n } else if (payload.length + headerBytes > available) {\n this.close(1009, \"WebSocket write limit exceeded\");\n return;\n }\n const frame = this.encodeFrame(opcode, payload);\n this.socket.write(frame);\n }\n\n private parseFrames(): void {\n while (this.buffer.length >= 2 && !this.closed) {\n const byte0 = this.buffer[0];\n const byte1 = this.buffer[1];\n\n const fin = (byte0 & 0x80) !== 0;\n const opcode = byte0 & 0x0f;\n const masked = (byte1 & 0x80) !== 0;\n if (\n byte0 & 0x70 ||\n masked === Boolean(this.limits.maskOutgoing) ||\n ![OP_CONTINUATION, OP_TEXT, 2, OP_CLOSE, OP_PING, OP_PONG].includes(opcode) ||\n (opcode >= 8 && (!fin || (byte1 & 0x7f) > 125)) ||\n (opcode === OP_CONTINUATION && !this.fragmented) ||\n (opcode === OP_TEXT && this.fragmented)\n ) {\n this.close(1002, \"Invalid WebSocket frame\");\n return;\n }\n let payloadLength = byte1 & 0x7f;\n let offset = 2;\n\n if (payloadLength === 126) {\n if (this.buffer.length < 4) return; // need more data\n payloadLength = this.buffer.readUInt16BE(2);\n offset = 4;\n } else if (payloadLength === 127) {\n if (this.buffer.length < 10) return;\n const advertised = this.buffer.readBigUInt64BE(2);\n if (advertised & (1n << 63n)) {\n this.close(1002, \"Invalid WebSocket length\");\n return;\n }\n if (advertised > BigInt(Number.MAX_SAFE_INTEGER)) {\n this.close(1009, \"WebSocket message limit exceeded\");\n return;\n }\n payloadLength = Number(advertised);\n offset = 10;\n }\n\n if (\n ((byte1 & 0x7f) === 126 && payloadLength < 126) ||\n ((byte1 & 0x7f) === 127 && payloadLength < 65536)\n ) {\n this.close(1002, \"Invalid WebSocket length\");\n return;\n }\n if (\n opcode < 8 &&\n payloadLength > (this.limits.maxMessageBytes ?? Infinity) - this.fragmentBytes\n ) {\n this.close(1009, \"WebSocket message limit exceeded\");\n return;\n }\n if (\n payloadLength + offset + (masked ? 4 : 0) >\n (this.limits.maxBufferedBytes ?? Infinity) - this.fragmentBytes\n ) {\n this.close(1009, \"WebSocket buffer limit exceeded\");\n return;\n }\n const maskSize = masked ? 4 : 0;\n const totalFrameSize = offset + maskSize + payloadLength;\n\n if (this.buffer.length < totalFrameSize) return; // need more data\n\n let maskKey: Buffer | null = null;\n if (masked) {\n maskKey = this.buffer.subarray(offset, offset + 4);\n offset += 4;\n }\n\n let payload = this.buffer.subarray(offset, offset + payloadLength);\n\n // Unmask client payload\n if (maskKey) {\n payload = Buffer.from(payload); // copy before mutating\n for (let i = 0; i < payload.length; i++) {\n payload[i] ^= maskKey[i % 4];\n }\n }\n\n // Consume the frame from the buffer\n this.buffer = this.buffer.subarray(totalFrameSize);\n\n this.handleFrame(fin, opcode, payload);\n }\n }\n\n private handleFrame(fin: boolean, opcode: number, payload: Buffer): void {\n // Control frames (opcode >= 0x8) must not be fragmented\n if (opcode === OP_PING) {\n this.writeFrame(OP_PONG, payload);\n return;\n }\n\n if (opcode === OP_PONG) {\n // Ignore unsolicited pongs\n return;\n }\n\n if (opcode === OP_CLOSE) {\n if (payload.length === 1) {\n this.close(1002, \"Invalid WebSocket close\");\n return;\n }\n const code = payload.length >= 2 ? payload.readUInt16BE(0) : 1005;\n const reason = payload.length > 2 ? payload.subarray(2).toString(\"utf-8\") : \"\";\n\n if (!this.closed) {\n this.closed = true;\n this.release(new Error(\"WebSocket closed\"));\n // Echo close frame back\n this.writeFrame(OP_CLOSE, payload);\n this.socket.end();\n this.emit(\"close\", code, reason);\n }\n // If already closed (server-initiated or duplicate), ignore — the\n // close event was already emitted by close() or the first OP_CLOSE.\n return;\n }\n\n // Text or continuation frames\n if (opcode === OP_TEXT || opcode === OP_CONTINUATION) {\n this.fragmented = !fin;\n this.fragmentBytes += payload.length;\n if (payload.length) this.fragments.push(payload);\n\n if (fin) {\n const message = Buffer.concat(this.fragments).toString(\"utf-8\");\n this.fragments = [];\n this.fragmentBytes = 0;\n this.emit(\"message\", message);\n }\n // If !fin, wait for more continuation frames\n return;\n }\n\n // Binary or unknown — just ignore for a mock server\n }\n}\n\nexport function computeAcceptKey(wsKey: string): string {\n return createHash(\"sha1\")\n .update(wsKey + WS_GUID)\n .digest(\"base64\");\n}\n\nexport function upgradeToWebSocket(\n req: http.IncomingMessage,\n socket: net.Socket,\n limits?: WebSocketLimits,\n): WebSocketConnection {\n const key = req.headers[\"sec-websocket-key\"];\n if (!key) {\n socket.write(\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\");\n socket.destroy();\n throw new Error(\"Missing Sec-WebSocket-Key header\");\n }\n\n const acceptKey = computeAcceptKey(key);\n\n let responseHeaders =\n \"HTTP/1.1 101 Switching Protocols\\r\\n\" +\n \"Upgrade: websocket\\r\\n\" +\n \"Connection: Upgrade\\r\\n\" +\n `Sec-WebSocket-Accept: ${acceptKey}\\r\\n`;\n\n // Echo back requested subprotocol if present\n const protocol = req.headers[\"sec-websocket-protocol\"];\n if (protocol) {\n // Take the first offered protocol\n const first = protocol.split(\",\")[0].trim();\n responseHeaders += `Sec-WebSocket-Protocol: ${first}\\r\\n`;\n }\n\n responseHeaders += \"\\r\\n\";\n\n socket.write(responseHeaders);\n\n return new WebSocketConnection(socket, limits);\n}\n"],"mappings":"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"}
|
package/dist/ws-framing.d.cts
CHANGED
|
@@ -24,6 +24,8 @@ declare class WebSocketConnection extends EventEmitter {
|
|
|
24
24
|
constructor(socket: net.Socket, limits?: WebSocketLimits);
|
|
25
25
|
send(data: string): void;
|
|
26
26
|
sendAsync(data: string, signal?: AbortSignal): Promise<void>;
|
|
27
|
+
/** Mark an unannounced connection loss and emit close once. */
|
|
28
|
+
private lost;
|
|
27
29
|
private release;
|
|
28
30
|
close(code?: number, reason?: string): void;
|
|
29
31
|
destroy(): void;
|