@copilotkit/aimock 1.45.0 → 1.46.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 +24 -0
- package/README.md +7 -1
- 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.ts.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/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 +61 -80
- 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 +58 -77
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +65 -97
- 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 +62 -94
- 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 +49 -74
- 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 +47 -72
- 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 +37 -42
- 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 +37 -42
- 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 +50 -35
- 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 +49 -34
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +57 -67
- 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 +56 -67
- 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 +11 -68
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +12 -67
- 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.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- 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 +14 -4
- package/dist/mcp-handler.cjs.map +1 -1
- package/dist/mcp-handler.js +14 -4
- 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 +111 -96
- 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 +109 -95
- 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/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/ollama.cjs +128 -68
- 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 +124 -64
- 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 +76 -71
- 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 +63 -58
- package/dist/openrouter-video.js.map +1 -1
- 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/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/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/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 +45 -58
- 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 +44 -57
- 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 +4 -0
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +3 -0
- 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 +222 -146
- 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 +223 -147
- 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 +7 -0
- package/dist/sse-writer.cjs.map +1 -1
- package/dist/sse-writer.d.cts.map +1 -1
- package/dist/sse-writer.d.ts.map +1 -1
- package/dist/sse-writer.js +7 -1
- 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 +12 -10
- 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 +11 -9
- package/dist/transcription.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +62 -3
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +62 -3
- 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-gemini-live.cjs +11 -3
- 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 +10 -2
- 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 +28 -54
- 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 +25 -51
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +8 -6
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.js +7 -5
- package/dist/ws-responses.js.map +1 -1
- package/package.json +4 -1
- package/skills/write-fixtures/SKILL.md +25 -16
package/dist/embeddings.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embeddings.cjs","names":["flattenHeaders","isJsonObject","normalizeEmbeddingInput","getContext","getTestId","selectFixtureForServing","applyChaosAsync","resolveResponse","isErrorResponse","serializeErrorResponse","isEmbeddingResponse","buildEmbeddingResponse","resolveStrictMode","strictNoMatchMessage","strictNoMatchLogLine","strictOverrideField","proxyAndRecord","validateEmbeddingDimensions","MAX_EMBEDDING_DIMENSIONS","generateDeterministicEmbedding"],"sources":["../src/embeddings.ts"],"sourcesContent":["/**\n * OpenAI Embeddings API support for aimock.\n *\n * Handles POST /v1/embeddings requests. Matches fixtures using the `inputText`\n * field, and falls back to generating a deterministic embedding from the input\n * text hash when no fixture matches.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RecordProviderKey,\n} from \"./types.js\";\nimport {\n isEmbeddingResponse,\n isErrorResponse,\n serializeErrorResponse,\n generateDeterministicEmbedding,\n buildEmbeddingResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n validateEmbeddingDimensions,\n isJsonObject,\n normalizeEmbeddingInput,\n MAX_EMBEDDING_DIMENSIONS,\n} from \"./helpers.js\";\nimport { releaseOneShotError } from \"./fixture-loader.js\";\nimport { selectFixtureForServing } 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\";\n\n// ─── Embeddings API request types ──────────────────────────────────────────\n\ninterface EmbeddingRequest {\n // Validated at runtime — these arrive as arbitrary JSON, so the declared\n // type must not claim a shape the parser cannot guarantee.\n input: unknown;\n model: string;\n encoding_format?: \"float\" | \"base64\";\n dimensions?: unknown;\n [key: string]: unknown;\n}\n\n// ─── Request handler ───────────────────────────────────────────────────────\n\nexport async function handleEmbeddings(\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 providerKey: RecordProviderKey = \"openai\",\n // Native HTTP dispatch opts in; direct callers and compatible routes retain defaults.\n requireModel = false,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let embeddingReq: EmbeddingRequest;\n try {\n embeddingReq = JSON.parse(raw) as EmbeddingRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 body: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n\n // Reject bodies that parsed but are not a JSON object (e.g. `null`) before\n // touching fields — otherwise `embeddingReq.input` throws a TypeError that\n // surfaces as a 500 instead of a 400.\n if (!isJsonObject(embeddingReq)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 // Validate required input parameter\n if (embeddingReq.input === undefined || embeddingReq.input === null) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: \"Missing required parameter: 'input'\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (requireModel && embeddingReq.model === undefined) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: \"Missing required parameter: 'model'\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Normalize input to array of strings — reject non-string inputs with 400\n // instead of crashing downstream in createHash().update() (500).\n const inputs = normalizeEmbeddingInput(embeddingReq.input);\n if (inputs === null) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: 'input' must be a string, an array of strings, or an array of tokens\",\n type: \"invalid_request_error\",\n param: null,\n code: null,\n },\n }),\n );\n return;\n }\n\n // Concatenate all inputs for matching purposes\n const combinedInput = inputs.join(\" \");\n\n // Build a synthetic ChatCompletionRequest for the fixture router.\n // We attach `embeddingInput` so the router's inputText matching can use it.\n const syntheticReq: ChatCompletionRequest = {\n model: embeddingReq.model,\n messages: [],\n embeddingInput: combinedInput,\n _endpointType: \"embedding\",\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = selectFixtureForServing(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n 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 {\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n ) {\n releaseOneShotError(fixtures, fixture);\n return;\n }\n\n if (fixture) {\n const response = await resolveResponse(fixture, syntheticReq);\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 // Embedding response — use the fixture's embedding for each input\n if (isEmbeddingResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n const embeddings = inputs.map(() => [...response.embedding]);\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Fixture matched but response type is not compatible with embeddings\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: {\n message:\n \"Fixture response did not match any known embedding type (must have embedding or error)\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — check strict mode first, then try proxy\n if (resolveStrictMode(defaults.strict, req.headers)) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n logger.error(\n strictNoMatchLogLine(\n req.method ?? \"POST\",\n req.url ?? \"/v1/embeddings\",\n skippedBySequenceOrTurn,\n ),\n );\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n providerKey,\n req.url ?? \"/v1/embeddings\",\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 // No fixture match — generate deterministic embeddings from input text\n logger.warn(\n `No embedding fixture matched for \"${combinedInput.slice(0, 80)}\" — returning deterministic fallback`,\n );\n // Validate dimensions here, on the only path that reads it: a bad value\n // either throws RangeError in `new Array()` below or, past the serialization\n // budget MAX_EMBEDDING_DIMENSIONS encodes, kills the process outright. The\n // fixture-replay, chaos, strict and record/proxy branches above never touch\n // it, so they must not be gated.\n const dimensions = validateEmbeddingDimensions(embeddingReq.dimensions);\n if (dimensions === null) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: `Invalid parameter: 'dimensions' must be an integer between 1 and ${MAX_EMBEDDING_DIMENSIONS}`,\n type: \"invalid_request_error\",\n param: null,\n code: null,\n },\n }),\n );\n return;\n }\n\n const embeddings = inputs.map((input) => generateDeterministicEmbedding(input, dimensions));\n\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":";;;;;;;;AAuDA,eAAsB,iBACpB,KACA,KACA,KACA,UACA,SACA,UACA,gBACA,cAAiC,UAEjC,eAAe,OACA;CACf,MAAM,EAAE,WAAW;AACnB,gBAAe,IAAI;CAEnB,IAAI;AACJ,KAAI;AACF,iBAAe,KAAK,MAAM,IAAI;UACvB,UAAU;EACjB,MAAM,SAAS,oBAAoB,QAAQ,SAAS,UAAU;AAC9D,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS,wBAAwB;GACjC,MAAM;GACN,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAMF,KAAI,CAACC,6BAAa,aAAa,EAAE;AAC/B,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASD,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAIF,KAAI,aAAa,UAAU,UAAa,aAAa,UAAU,MAAM;AACnE,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAGF,KAAI,gBAAgB,aAAa,UAAU,QAAW;AACpD,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAKF,MAAM,SAASE,wCAAwB,aAAa,MAAM;AAC1D,KAAI,WAAW,MAAM;AACnB,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASF,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SACE;GACF,MAAM;GACN,OAAO;GACP,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAIF,MAAM,gBAAgB,OAAO,KAAK,IAAI;CAItC,MAAM,eAAsC;EAC1C,OAAO,aAAa;EACpB,UAAU,EAAE;EACZ,gBAAgB;EAChB,eAAe;EACf,UAAUG,2BAAW,IAAI;EAC1B;CAED,MAAM,SAASC,0BAAU,IAAI;CAC7B,MAAM,EAAE,SAAS,4BAA4BC,uCAC3C,UACA,cACA,QAAQ,6BAA6B,OAAO,EAC5C,SAAS,iBACV;AAED,KAAI,SAAS;AACX,UAAQ,2BAA2B,SAAS,UAAU,OAAO;AAC7D,SAAO,MAAM,oBAAoB,KAAK,UAAU,QAAQ,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG;OAE/E,QAAO,MAAM,iCAAiC;AAGhD,KACE,MAAMC,8BACJ,KACA,SACA,SAAS,OACT,IAAI,SACJ,IAAI,KACJ,SACA;EACE,QAAQ,IAAI,UAAU;EACtB,MAAM,IAAI,OAAO;EACjB,SAASN,+BAAe,IAAI,QAAQ;EACpC,MAAM;EACP,EACD,UAAU,YAAY,SACtB,SAAS,UACT,SAAS,OACV,EACD;AACA,6CAAoB,UAAU,QAAQ;AACtC;;AAGF,KAAI,SAAS;EACX,MAAM,WAAW,MAAMO,gCAAgB,SAAS,aAAa;AAG7D,MAAIC,gCAAgB,SAAS,EAAE;GAC7B,MAAM,SAAS,SAAS,UAAU;AAClC,WAAQ,IAAI;IACV,QAAQ,IAAI,UAAU;IACtB,MAAM,IAAI,OAAO;IACjB,SAASR,+BAAe,IAAI,QAAQ;IACpC,MAAM;IACN,UAAU;KAAE;KAAQ;KAAS;IAC9B,CAAC;AACF,yCAAmB,KAAK,QAAQS,uCAAuB,SAAS,EAAE,EAChE,YAAY,SAAS,YACtB,CAAC;AACF;;AAIF,MAAIC,oCAAoB,SAAS,EAAE;AACjC,WAAQ,IAAI;IACV,QAAQ,IAAI,UAAU;IACtB,MAAM,IAAI,OAAO;IACjB,SAASV,+BAAe,IAAI,QAAQ;IACpC,MAAM;IACN,UAAU;KAAE,QAAQ;KAAK;KAAS;IACnC,CAAC;GAEF,MAAM,OAAOW,uCADM,OAAO,UAAU,CAAC,GAAG,SAAS,UAAU,CAAC,EACZ,aAAa,MAAM;AACnE,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,KAAK,CAAC;AAC7B;;AAIF,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASX,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SACE;GACF,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAIF,KAAIY,kCAAkB,SAAS,QAAQ,IAAI,QAAQ,EAAE;EACnD,MAAM,gBAAgBC,qCAAqB,wBAAwB;AACnE,SAAO,MACLC,qCACE,IAAI,UAAU,QACd,IAAI,OAAO,kBACX,wBACD,CACF;AACD,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASd,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IACR,QAAQ;IACR,SAAS;IACT,GAAGe,oCAAoB,SAAS,QAAQ,IAAI,QAAQ;IACrD;GACF,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACN,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAGF,KAAI,SAAS,QAAQ;EACnB,MAAM,UAAU,MAAMC,gCACpB,KACA,KACA,cACA,aACA,IAAI,OAAO,kBACX,UACA,UACA,IACD;AACD,MAAI,YAAY,kBAAmB;AACnC,MAAI,YAAY,kBAAkB;AAChC,WAAQ,IAAI;IACV,QAAQ,IAAI,UAAU;IACtB,MAAM,IAAI,OAAO;IACjB,SAAShB,+BAAe,IAAI,QAAQ;IACpC,MAAM;IACN,UAAU;KAAE,QAAQ,IAAI,cAAc;KAAK,SAAS;KAAM,QAAQ;KAAS;IAC5E,CAAC;AACF;;;AAKJ,QAAO,KACL,qCAAqC,cAAc,MAAM,GAAG,GAAG,CAAC,sCACjE;CAMD,MAAM,aAAaiB,4CAA4B,aAAa,WAAW;AACvE,KAAI,eAAe,MAAM;AACvB,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASjB,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS,oEAAoEkB;GAC7E,MAAM;GACN,OAAO;GACP,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAGF,MAAM,aAAa,OAAO,KAAK,UAAUC,+CAA+B,OAAO,WAAW,CAAC;AAE3F,SAAQ,IAAI;EACV,QAAQ,IAAI,UAAU;EACtB,MAAM,IAAI,OAAO;EACjB,SAASnB,+BAAe,IAAI,QAAQ;EACpC,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK,SAAS;GAAM;EACzC,CAAC;CAEF,MAAM,OAAOW,uCAAuB,YAAY,aAAa,MAAM;AACnE,KAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,KAAI,IAAI,KAAK,UAAU,KAAK,CAAC"}
|
|
1
|
+
{"version":3,"file":"embeddings.cjs","names":["flattenHeaders","renderError","isJsonObject","normalizeEmbeddingInput","getContext","getTestId","selectFixtureForServing","applyChaosAsync","resolveResponse","isErrorResponse","serializeErrorResponse","isEmbeddingResponse","buildEmbeddingResponse","resolveRequestDecisions","strictNoMatchMessage","strictNoMatchLogLine","strictOverrideField","proxyAndRecord","validateEmbeddingDimensions","MAX_EMBEDDING_DIMENSIONS","generateDeterministicEmbedding"],"sources":["../src/embeddings.ts"],"sourcesContent":["/**\n * OpenAI Embeddings API support for aimock.\n *\n * Handles POST /v1/embeddings requests. Matches fixtures using the `inputText`\n * field, and falls back to generating a deterministic embedding from the input\n * text hash when no fixture matches.\n */\n\nimport type * as http from \"node:http\";\nimport { setEmbeddingRequest } from \"./embedding-request.js\";\nimport { resolveRequestDecisions, strictOverrideField } from \"./response/policy.js\";\nimport { renderError } from \"./response/errors.js\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RecordProviderKey,\n} from \"./types.js\";\nimport {\n isEmbeddingResponse,\n isErrorResponse,\n serializeErrorResponse,\n generateDeterministicEmbedding,\n buildEmbeddingResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n validateEmbeddingDimensions,\n isJsonObject,\n normalizeEmbeddingInput,\n MAX_EMBEDDING_DIMENSIONS,\n} from \"./helpers.js\";\nimport { releaseOneShotError } from \"./fixture-loader.js\";\nimport { selectFixtureForServing } 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\";\n\n// ─── Embeddings API request types ──────────────────────────────────────────\n\ninterface EmbeddingRequest {\n // Validated at runtime — these arrive as arbitrary JSON, so the declared\n // type must not claim a shape the parser cannot guarantee.\n input: unknown;\n model: string;\n encoding_format?: \"float\" | \"base64\";\n dimensions?: unknown;\n [key: string]: unknown;\n}\n\n// ─── Request handler ───────────────────────────────────────────────────────\n\nexport async function handleEmbeddings(\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 providerKey: RecordProviderKey = \"openai\",\n // Native HTTP dispatch opts in; direct callers and compatible routes retain defaults.\n requireModel = false,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let embeddingReq: EmbeddingRequest;\n try {\n embeddingReq = JSON.parse(raw) as EmbeddingRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 body: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n }).body,\n );\n return;\n }\n\n // Reject bodies that parsed but are not a JSON object (e.g. `null`) before\n // touching fields — otherwise `embeddingReq.input` throws a TypeError that\n // surfaces as a 500 instead of a 400.\n if (!isJsonObject(embeddingReq)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 // Validate required input parameter\n if (embeddingReq.input === undefined || embeddingReq.input === null) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: 'input'\",\n type: \"invalid_request_error\",\n }).body,\n );\n return;\n }\n\n if (requireModel && embeddingReq.model === undefined) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: 'model'\",\n type: \"invalid_request_error\",\n }).body,\n );\n return;\n }\n\n // Normalize input to array of strings — reject non-string inputs with 400\n // instead of crashing downstream in createHash().update() (500).\n const inputs = normalizeEmbeddingInput(embeddingReq.input);\n if (inputs === null) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: 'input' must be a string, an array of strings, or an array of tokens\",\n type: \"invalid_request_error\",\n param: null,\n code: null,\n includeParam: true,\n }).body,\n );\n return;\n }\n\n // Concatenate all inputs for matching purposes\n const combinedInput = inputs.join(\" \");\n\n // Build a synthetic ChatCompletionRequest for the fixture router.\n // We attach `embeddingInput` so the router's inputText matching can use it.\n const syntheticReq: ChatCompletionRequest = {\n model: embeddingReq.model,\n messages: [],\n embeddingInput: combinedInput,\n _endpointType: \"embedding\",\n _context: getContext(req),\n };\n setEmbeddingRequest(syntheticReq, {\n operation: \"/v1/embeddings\",\n input: embeddingReq.input,\n ...(embeddingReq.dimensions !== undefined && { dimensions: embeddingReq.dimensions }),\n });\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = selectFixtureForServing(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n 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 {\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n ) {\n releaseOneShotError(fixtures, fixture);\n return;\n }\n\n if (fixture) {\n const response = await resolveResponse(fixture, syntheticReq);\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 // Embedding response — use the fixture's embedding for each input\n if (isEmbeddingResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n const embeddings = inputs.map(() => [...response.embedding]);\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Fixture matched but response type is not compatible with embeddings\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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:\n \"Fixture response did not match any known embedding type (must have embedding or error)\",\n type: \"server_error\",\n }).body,\n );\n return;\n }\n\n // No fixture match — check strict mode first, then try proxy\n if (resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n logger.error(\n strictNoMatchLogLine(\n req.method ?? \"POST\",\n req.url ?? \"/v1/embeddings\",\n skippedBySequenceOrTurn,\n ),\n );\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n providerKey,\n req.url ?? \"/v1/embeddings\",\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 // No fixture match — generate deterministic embeddings from input text\n logger.warn(\n `No embedding fixture matched for \"${combinedInput.slice(0, 80)}\" — returning deterministic fallback`,\n );\n // Validate dimensions here, on the only path that reads it: a bad value\n // either throws RangeError in `new Array()` below or, past the serialization\n // budget MAX_EMBEDDING_DIMENSIONS encodes, kills the process outright. The\n // fixture-replay, chaos, strict and record/proxy branches above never touch\n // it, so they must not be gated.\n const dimensions = validateEmbeddingDimensions(embeddingReq.dimensions);\n if (dimensions === null) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: `Invalid parameter: 'dimensions' must be an integer between 1 and ${MAX_EMBEDDING_DIMENSIONS}`,\n type: \"invalid_request_error\",\n param: null,\n code: null,\n includeParam: true,\n }).body,\n );\n return;\n }\n\n const embeddings = inputs.map((input) => generateDeterministicEmbedding(input, dimensions));\n\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embeddings.d.cts","names":[],"sources":["../src/embeddings.ts"],"sourcesContent":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"embeddings.d.cts","names":[],"sources":["../src/embeddings.ts"],"sourcesContent":[],"mappings":";;;;;;AA6DW,iBALW,gBAAA,CAKX,GAAA,EAJJ,MAAA,CAAK,eAID,EAAA,GAAA,EAHJ,MAAA,CAAK,cAGD,EAAA,GAAA,EAAA,MAAA,EAAA,QAAA,EADC,OACD,EAAA,EAAA,OAAA,EAAA,OAAA,EAAA,QAAA,EACC,eADD,EAAA,cAAA,EAAA,CAAA,GAAA,EAEa,MAAA,CAAK,cAFlB,EAAA,GAAA,IAAA,EAAA,WAAA,CAAA,EAGI,iBAHJ,EAAA,YAAA,CAAA,EAAA,OAAA,CAAA,EAMR,OANQ,CAAA,IAAA,CAAA"}
|
package/dist/embeddings.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embeddings.d.ts","names":[],"sources":["../src/embeddings.ts"],"sourcesContent":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"embeddings.d.ts","names":[],"sources":["../src/embeddings.ts"],"sourcesContent":[],"mappings":";;;;;;AA6DW,iBALW,gBAAA,CAKX,GAAA,EAJJ,MAAA,CAAK,eAID,EAAA,GAAA,EAHJ,MAAA,CAAK,cAGD,EAAA,GAAA,EAAA,MAAA,EAAA,QAAA,EADC,OACD,EAAA,EAAA,OAAA,EAAA,OAAA,EAAA,QAAA,EACC,eADD,EAAA,cAAA,EAAA,CAAA,GAAA,EAEa,MAAA,CAAK,cAFlB,EAAA,GAAA,IAAA,EAAA,WAAA,CAAA,EAGI,iBAHJ,EAAA,YAAA,CAAA,EAAA,OAAA,CAAA,EAMR,OANQ,CAAA,IAAA,CAAA"}
|
package/dist/embeddings.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { renderError } from "./response/errors.js";
|
|
2
|
+
import { resolveRequestDecisions, strictOverrideField } from "./response/policy.js";
|
|
3
|
+
import { MAX_EMBEDDING_DIMENSIONS, buildEmbeddingResponse, flattenHeaders, generateDeterministicEmbedding, getContext, getTestId, isEmbeddingResponse, isErrorResponse, isJsonObject, normalizeEmbeddingInput, resolveResponse, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, validateEmbeddingDimensions } from "./helpers.js";
|
|
2
4
|
import { writeErrorResponse } from "./sse-writer.js";
|
|
3
5
|
import { applyChaosAsync } from "./chaos.js";
|
|
4
6
|
import { releaseOneShotError } from "./fixture-loader.js";
|
|
7
|
+
import { setEmbeddingRequest } from "./embedding-request.js";
|
|
5
8
|
import { selectFixtureForServing } from "./router.js";
|
|
6
9
|
import { proxyAndRecord } from "./recorder.js";
|
|
7
10
|
|
|
@@ -24,11 +27,11 @@ async function handleEmbeddings(req, res, raw, fixtures, journal, defaults, setC
|
|
|
24
27
|
fixture: null
|
|
25
28
|
}
|
|
26
29
|
});
|
|
27
|
-
writeErrorResponse(res, 400,
|
|
30
|
+
writeErrorResponse(res, 400, renderError("openai", "validation", {
|
|
28
31
|
message: `Malformed JSON body: ${detail}`,
|
|
29
32
|
type: "invalid_request_error",
|
|
30
33
|
code: "invalid_json"
|
|
31
|
-
}
|
|
34
|
+
}).body);
|
|
32
35
|
return;
|
|
33
36
|
}
|
|
34
37
|
if (!isJsonObject(embeddingReq)) {
|
|
@@ -42,10 +45,10 @@ async function handleEmbeddings(req, res, raw, fixtures, journal, defaults, setC
|
|
|
42
45
|
fixture: null
|
|
43
46
|
}
|
|
44
47
|
});
|
|
45
|
-
writeErrorResponse(res, 400,
|
|
48
|
+
writeErrorResponse(res, 400, renderError("openai", "validation", {
|
|
46
49
|
message: "Request body must be a JSON object",
|
|
47
50
|
type: "invalid_request_error"
|
|
48
|
-
}
|
|
51
|
+
}).body);
|
|
49
52
|
return;
|
|
50
53
|
}
|
|
51
54
|
if (embeddingReq.input === void 0 || embeddingReq.input === null) {
|
|
@@ -59,10 +62,10 @@ async function handleEmbeddings(req, res, raw, fixtures, journal, defaults, setC
|
|
|
59
62
|
fixture: null
|
|
60
63
|
}
|
|
61
64
|
});
|
|
62
|
-
writeErrorResponse(res, 400,
|
|
65
|
+
writeErrorResponse(res, 400, renderError("openai", "validation", {
|
|
63
66
|
message: "Missing required parameter: 'input'",
|
|
64
67
|
type: "invalid_request_error"
|
|
65
|
-
}
|
|
68
|
+
}).body);
|
|
66
69
|
return;
|
|
67
70
|
}
|
|
68
71
|
if (requireModel && embeddingReq.model === void 0) {
|
|
@@ -76,10 +79,10 @@ async function handleEmbeddings(req, res, raw, fixtures, journal, defaults, setC
|
|
|
76
79
|
fixture: null
|
|
77
80
|
}
|
|
78
81
|
});
|
|
79
|
-
writeErrorResponse(res, 400,
|
|
82
|
+
writeErrorResponse(res, 400, renderError("openai", "validation", {
|
|
80
83
|
message: "Missing required parameter: 'model'",
|
|
81
84
|
type: "invalid_request_error"
|
|
82
|
-
}
|
|
85
|
+
}).body);
|
|
83
86
|
return;
|
|
84
87
|
}
|
|
85
88
|
const inputs = normalizeEmbeddingInput(embeddingReq.input);
|
|
@@ -94,12 +97,13 @@ async function handleEmbeddings(req, res, raw, fixtures, journal, defaults, setC
|
|
|
94
97
|
fixture: null
|
|
95
98
|
}
|
|
96
99
|
});
|
|
97
|
-
writeErrorResponse(res, 400,
|
|
100
|
+
writeErrorResponse(res, 400, renderError("openai", "validation", {
|
|
98
101
|
message: "Invalid parameter: 'input' must be a string, an array of strings, or an array of tokens",
|
|
99
102
|
type: "invalid_request_error",
|
|
100
103
|
param: null,
|
|
101
|
-
code: null
|
|
102
|
-
|
|
104
|
+
code: null,
|
|
105
|
+
includeParam: true
|
|
106
|
+
}).body);
|
|
103
107
|
return;
|
|
104
108
|
}
|
|
105
109
|
const combinedInput = inputs.join(" ");
|
|
@@ -110,6 +114,11 @@ async function handleEmbeddings(req, res, raw, fixtures, journal, defaults, setC
|
|
|
110
114
|
_endpointType: "embedding",
|
|
111
115
|
_context: getContext(req)
|
|
112
116
|
};
|
|
117
|
+
setEmbeddingRequest(syntheticReq, {
|
|
118
|
+
operation: "/v1/embeddings",
|
|
119
|
+
input: embeddingReq.input,
|
|
120
|
+
...embeddingReq.dimensions !== void 0 && { dimensions: embeddingReq.dimensions }
|
|
121
|
+
});
|
|
113
122
|
const testId = getTestId(req);
|
|
114
123
|
const { fixture, skippedBySequenceOrTurn } = selectFixtureForServing(fixtures, syntheticReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
|
|
115
124
|
if (fixture) {
|
|
@@ -168,13 +177,13 @@ async function handleEmbeddings(req, res, raw, fixtures, journal, defaults, setC
|
|
|
168
177
|
fixture
|
|
169
178
|
}
|
|
170
179
|
});
|
|
171
|
-
writeErrorResponse(res, 500,
|
|
180
|
+
writeErrorResponse(res, 500, renderError("openai", "unknown-response", {
|
|
172
181
|
message: "Fixture response did not match any known embedding type (must have embedding or error)",
|
|
173
182
|
type: "server_error"
|
|
174
|
-
}
|
|
183
|
+
}).body);
|
|
175
184
|
return;
|
|
176
185
|
}
|
|
177
|
-
if (
|
|
186
|
+
if (resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {
|
|
178
187
|
const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);
|
|
179
188
|
logger.error(strictNoMatchLogLine(req.method ?? "POST", req.url ?? "/v1/embeddings", skippedBySequenceOrTurn));
|
|
180
189
|
journal.add({
|
|
@@ -188,11 +197,11 @@ async function handleEmbeddings(req, res, raw, fixtures, journal, defaults, setC
|
|
|
188
197
|
...strictOverrideField(defaults.strict, req.headers)
|
|
189
198
|
}
|
|
190
199
|
});
|
|
191
|
-
writeErrorResponse(res, 503,
|
|
200
|
+
writeErrorResponse(res, 503, renderError("openai", "strict-miss", {
|
|
192
201
|
message: strictMessage,
|
|
193
202
|
type: "invalid_request_error",
|
|
194
203
|
code: "no_fixture_match"
|
|
195
|
-
}
|
|
204
|
+
}).body);
|
|
196
205
|
return;
|
|
197
206
|
}
|
|
198
207
|
if (defaults.record) {
|
|
@@ -226,12 +235,13 @@ async function handleEmbeddings(req, res, raw, fixtures, journal, defaults, setC
|
|
|
226
235
|
fixture: null
|
|
227
236
|
}
|
|
228
237
|
});
|
|
229
|
-
writeErrorResponse(res, 400,
|
|
238
|
+
writeErrorResponse(res, 400, renderError("openai", "validation", {
|
|
230
239
|
message: `Invalid parameter: 'dimensions' must be an integer between 1 and ${MAX_EMBEDDING_DIMENSIONS}`,
|
|
231
240
|
type: "invalid_request_error",
|
|
232
241
|
param: null,
|
|
233
|
-
code: null
|
|
234
|
-
|
|
242
|
+
code: null,
|
|
243
|
+
includeParam: true
|
|
244
|
+
}).body);
|
|
235
245
|
return;
|
|
236
246
|
}
|
|
237
247
|
const embeddings = inputs.map((input) => generateDeterministicEmbedding(input, dimensions));
|
package/dist/embeddings.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embeddings.js","names":[],"sources":["../src/embeddings.ts"],"sourcesContent":["/**\n * OpenAI Embeddings API support for aimock.\n *\n * Handles POST /v1/embeddings requests. Matches fixtures using the `inputText`\n * field, and falls back to generating a deterministic embedding from the input\n * text hash when no fixture matches.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RecordProviderKey,\n} from \"./types.js\";\nimport {\n isEmbeddingResponse,\n isErrorResponse,\n serializeErrorResponse,\n generateDeterministicEmbedding,\n buildEmbeddingResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n validateEmbeddingDimensions,\n isJsonObject,\n normalizeEmbeddingInput,\n MAX_EMBEDDING_DIMENSIONS,\n} from \"./helpers.js\";\nimport { releaseOneShotError } from \"./fixture-loader.js\";\nimport { selectFixtureForServing } 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\";\n\n// ─── Embeddings API request types ──────────────────────────────────────────\n\ninterface EmbeddingRequest {\n // Validated at runtime — these arrive as arbitrary JSON, so the declared\n // type must not claim a shape the parser cannot guarantee.\n input: unknown;\n model: string;\n encoding_format?: \"float\" | \"base64\";\n dimensions?: unknown;\n [key: string]: unknown;\n}\n\n// ─── Request handler ───────────────────────────────────────────────────────\n\nexport async function handleEmbeddings(\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 providerKey: RecordProviderKey = \"openai\",\n // Native HTTP dispatch opts in; direct callers and compatible routes retain defaults.\n requireModel = false,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let embeddingReq: EmbeddingRequest;\n try {\n embeddingReq = JSON.parse(raw) as EmbeddingRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 body: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n\n // Reject bodies that parsed but are not a JSON object (e.g. `null`) before\n // touching fields — otherwise `embeddingReq.input` throws a TypeError that\n // surfaces as a 500 instead of a 400.\n if (!isJsonObject(embeddingReq)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 // Validate required input parameter\n if (embeddingReq.input === undefined || embeddingReq.input === null) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: \"Missing required parameter: 'input'\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (requireModel && embeddingReq.model === undefined) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: \"Missing required parameter: 'model'\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Normalize input to array of strings — reject non-string inputs with 400\n // instead of crashing downstream in createHash().update() (500).\n const inputs = normalizeEmbeddingInput(embeddingReq.input);\n if (inputs === null) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: 'input' must be a string, an array of strings, or an array of tokens\",\n type: \"invalid_request_error\",\n param: null,\n code: null,\n },\n }),\n );\n return;\n }\n\n // Concatenate all inputs for matching purposes\n const combinedInput = inputs.join(\" \");\n\n // Build a synthetic ChatCompletionRequest for the fixture router.\n // We attach `embeddingInput` so the router's inputText matching can use it.\n const syntheticReq: ChatCompletionRequest = {\n model: embeddingReq.model,\n messages: [],\n embeddingInput: combinedInput,\n _endpointType: \"embedding\",\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = selectFixtureForServing(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n 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 {\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n ) {\n releaseOneShotError(fixtures, fixture);\n return;\n }\n\n if (fixture) {\n const response = await resolveResponse(fixture, syntheticReq);\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 // Embedding response — use the fixture's embedding for each input\n if (isEmbeddingResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n const embeddings = inputs.map(() => [...response.embedding]);\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Fixture matched but response type is not compatible with embeddings\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: {\n message:\n \"Fixture response did not match any known embedding type (must have embedding or error)\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — check strict mode first, then try proxy\n if (resolveStrictMode(defaults.strict, req.headers)) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n logger.error(\n strictNoMatchLogLine(\n req.method ?? \"POST\",\n req.url ?? \"/v1/embeddings\",\n skippedBySequenceOrTurn,\n ),\n );\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n providerKey,\n req.url ?? \"/v1/embeddings\",\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 // No fixture match — generate deterministic embeddings from input text\n logger.warn(\n `No embedding fixture matched for \"${combinedInput.slice(0, 80)}\" — returning deterministic fallback`,\n );\n // Validate dimensions here, on the only path that reads it: a bad value\n // either throws RangeError in `new Array()` below or, past the serialization\n // budget MAX_EMBEDDING_DIMENSIONS encodes, kills the process outright. The\n // fixture-replay, chaos, strict and record/proxy branches above never touch\n // it, so they must not be gated.\n const dimensions = validateEmbeddingDimensions(embeddingReq.dimensions);\n if (dimensions === null) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: `Invalid parameter: 'dimensions' must be an integer between 1 and ${MAX_EMBEDDING_DIMENSIONS}`,\n type: \"invalid_request_error\",\n param: null,\n code: null,\n },\n }),\n );\n return;\n }\n\n const embeddings = inputs.map((input) => generateDeterministicEmbedding(input, dimensions));\n\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\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":"embeddings.js","names":[],"sources":["../src/embeddings.ts"],"sourcesContent":["/**\n * OpenAI Embeddings API support for aimock.\n *\n * Handles POST /v1/embeddings requests. Matches fixtures using the `inputText`\n * field, and falls back to generating a deterministic embedding from the input\n * text hash when no fixture matches.\n */\n\nimport type * as http from \"node:http\";\nimport { setEmbeddingRequest } from \"./embedding-request.js\";\nimport { resolveRequestDecisions, strictOverrideField } from \"./response/policy.js\";\nimport { renderError } from \"./response/errors.js\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RecordProviderKey,\n} from \"./types.js\";\nimport {\n isEmbeddingResponse,\n isErrorResponse,\n serializeErrorResponse,\n generateDeterministicEmbedding,\n buildEmbeddingResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n validateEmbeddingDimensions,\n isJsonObject,\n normalizeEmbeddingInput,\n MAX_EMBEDDING_DIMENSIONS,\n} from \"./helpers.js\";\nimport { releaseOneShotError } from \"./fixture-loader.js\";\nimport { selectFixtureForServing } 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\";\n\n// ─── Embeddings API request types ──────────────────────────────────────────\n\ninterface EmbeddingRequest {\n // Validated at runtime — these arrive as arbitrary JSON, so the declared\n // type must not claim a shape the parser cannot guarantee.\n input: unknown;\n model: string;\n encoding_format?: \"float\" | \"base64\";\n dimensions?: unknown;\n [key: string]: unknown;\n}\n\n// ─── Request handler ───────────────────────────────────────────────────────\n\nexport async function handleEmbeddings(\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 providerKey: RecordProviderKey = \"openai\",\n // Native HTTP dispatch opts in; direct callers and compatible routes retain defaults.\n requireModel = false,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let embeddingReq: EmbeddingRequest;\n try {\n embeddingReq = JSON.parse(raw) as EmbeddingRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 body: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n }).body,\n );\n return;\n }\n\n // Reject bodies that parsed but are not a JSON object (e.g. `null`) before\n // touching fields — otherwise `embeddingReq.input` throws a TypeError that\n // surfaces as a 500 instead of a 400.\n if (!isJsonObject(embeddingReq)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 // Validate required input parameter\n if (embeddingReq.input === undefined || embeddingReq.input === null) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: 'input'\",\n type: \"invalid_request_error\",\n }).body,\n );\n return;\n }\n\n if (requireModel && embeddingReq.model === undefined) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: 'model'\",\n type: \"invalid_request_error\",\n }).body,\n );\n return;\n }\n\n // Normalize input to array of strings — reject non-string inputs with 400\n // instead of crashing downstream in createHash().update() (500).\n const inputs = normalizeEmbeddingInput(embeddingReq.input);\n if (inputs === null) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: 'input' must be a string, an array of strings, or an array of tokens\",\n type: \"invalid_request_error\",\n param: null,\n code: null,\n includeParam: true,\n }).body,\n );\n return;\n }\n\n // Concatenate all inputs for matching purposes\n const combinedInput = inputs.join(\" \");\n\n // Build a synthetic ChatCompletionRequest for the fixture router.\n // We attach `embeddingInput` so the router's inputText matching can use it.\n const syntheticReq: ChatCompletionRequest = {\n model: embeddingReq.model,\n messages: [],\n embeddingInput: combinedInput,\n _endpointType: \"embedding\",\n _context: getContext(req),\n };\n setEmbeddingRequest(syntheticReq, {\n operation: \"/v1/embeddings\",\n input: embeddingReq.input,\n ...(embeddingReq.dimensions !== undefined && { dimensions: embeddingReq.dimensions }),\n });\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = selectFixtureForServing(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n 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 {\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n ) {\n releaseOneShotError(fixtures, fixture);\n return;\n }\n\n if (fixture) {\n const response = await resolveResponse(fixture, syntheticReq);\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 // Embedding response — use the fixture's embedding for each input\n if (isEmbeddingResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n const embeddings = inputs.map(() => [...response.embedding]);\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Fixture matched but response type is not compatible with embeddings\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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:\n \"Fixture response did not match any known embedding type (must have embedding or error)\",\n type: \"server_error\",\n }).body,\n );\n return;\n }\n\n // No fixture match — check strict mode first, then try proxy\n if (resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n logger.error(\n strictNoMatchLogLine(\n req.method ?? \"POST\",\n req.url ?? \"/v1/embeddings\",\n skippedBySequenceOrTurn,\n ),\n );\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n providerKey,\n req.url ?? \"/v1/embeddings\",\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 // No fixture match — generate deterministic embeddings from input text\n logger.warn(\n `No embedding fixture matched for \"${combinedInput.slice(0, 80)}\" — returning deterministic fallback`,\n );\n // Validate dimensions here, on the only path that reads it: a bad value\n // either throws RangeError in `new Array()` below or, past the serialization\n // budget MAX_EMBEDDING_DIMENSIONS encodes, kills the process outright. The\n // fixture-replay, chaos, strict and record/proxy branches above never touch\n // it, so they must not be gated.\n const dimensions = validateEmbeddingDimensions(embeddingReq.dimensions);\n if (dimensions === null) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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: `Invalid parameter: 'dimensions' must be an integer between 1 and ${MAX_EMBEDDING_DIMENSIONS}`,\n type: \"invalid_request_error\",\n param: null,\n code: null,\n includeParam: true,\n }).body,\n );\n return;\n }\n\n const embeddings = inputs.map((input) => generateDeterministicEmbedding(input, dimensions));\n\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":"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"}
|
package/dist/fal-audio.cjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
|
+
const require_errors = require('./response/errors.cjs');
|
|
2
3
|
const require_api_key_auth = require('./api-key-auth.cjs');
|
|
4
|
+
const require_policy = require('./response/policy.cjs');
|
|
3
5
|
const require_helpers = require('./helpers.cjs');
|
|
4
6
|
const require_sse_writer = require('./sse-writer.cjs');
|
|
5
7
|
const require_chaos = require('./chaos.cjs');
|
|
@@ -131,10 +133,10 @@ async function handleFalQueue(req, res, body, pathname, fixtures, defaults, jour
|
|
|
131
133
|
const modelId = runMatch[1];
|
|
132
134
|
return handleSyncRun(req, res, body, pathname, modelId, fixtures, defaults, matchCounts, journal);
|
|
133
135
|
}
|
|
134
|
-
const errorBody =
|
|
136
|
+
const errorBody = require_errors.renderError("openai", "route-failure", {
|
|
135
137
|
message: "Unknown fal.ai endpoint",
|
|
136
138
|
type: "not_found"
|
|
137
|
-
}
|
|
139
|
+
}).body;
|
|
138
140
|
journal.add({
|
|
139
141
|
method: req.method ?? "GET",
|
|
140
142
|
path: pathname,
|
|
@@ -146,7 +148,7 @@ async function handleFalQueue(req, res, body, pathname, fixtures, defaults, jour
|
|
|
146
148
|
}
|
|
147
149
|
});
|
|
148
150
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
149
|
-
res.end(
|
|
151
|
+
res.end(errorBody);
|
|
150
152
|
}
|
|
151
153
|
async function handleQueueSubmit(req, res, body, pathname, modelId, testId, fixtures, defaults, matchCounts, journal) {
|
|
152
154
|
let parsed = {};
|
|
@@ -165,10 +167,10 @@ async function handleQueueSubmit(req, res, body, pathname, modelId, testId, fixt
|
|
|
165
167
|
}
|
|
166
168
|
});
|
|
167
169
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
168
|
-
res.end(
|
|
170
|
+
res.end(require_errors.renderError("openai", "validation", {
|
|
169
171
|
message: `Malformed JSON: ${detail}`,
|
|
170
172
|
type: "invalid_request_error"
|
|
171
|
-
}
|
|
173
|
+
}).body);
|
|
172
174
|
return;
|
|
173
175
|
}
|
|
174
176
|
if (!require_helpers.isJsonObject(parsed)) {
|
|
@@ -183,10 +185,10 @@ async function handleQueueSubmit(req, res, body, pathname, modelId, testId, fixt
|
|
|
183
185
|
}
|
|
184
186
|
});
|
|
185
187
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
186
|
-
res.end(
|
|
188
|
+
res.end(require_errors.renderError("openai", "validation", {
|
|
187
189
|
message: "Request body must be a JSON object",
|
|
188
190
|
type: "invalid_request_error"
|
|
189
|
-
}
|
|
191
|
+
}).body);
|
|
190
192
|
return;
|
|
191
193
|
}
|
|
192
194
|
const syntheticReq = {
|
|
@@ -207,7 +209,7 @@ async function handleQueueSubmit(req, res, body, pathname, modelId, testId, fixt
|
|
|
207
209
|
body: syntheticReq
|
|
208
210
|
}, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
|
|
209
211
|
if (!fixture) {
|
|
210
|
-
if (
|
|
212
|
+
if (require_policy.resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {
|
|
211
213
|
const strictMessage = require_helpers.strictNoMatchMessage(skippedBySequenceOrTurn);
|
|
212
214
|
defaults.logger.error(require_helpers.strictNoMatchLogLine(req.method ?? "POST", pathname, skippedBySequenceOrTurn));
|
|
213
215
|
journal.add({
|
|
@@ -218,15 +220,15 @@ async function handleQueueSubmit(req, res, body, pathname, modelId, testId, fixt
|
|
|
218
220
|
response: {
|
|
219
221
|
status: 503,
|
|
220
222
|
fixture: null,
|
|
221
|
-
...
|
|
223
|
+
...require_policy.strictOverrideField(defaults.strict, req.headers)
|
|
222
224
|
}
|
|
223
225
|
});
|
|
224
226
|
res.writeHead(503, { "Content-Type": "application/json" });
|
|
225
|
-
res.end(
|
|
227
|
+
res.end(require_errors.renderError("openai", "strict-miss", {
|
|
226
228
|
message: strictMessage,
|
|
227
229
|
type: "invalid_request_error",
|
|
228
230
|
code: "no_fixture_match"
|
|
229
|
-
}
|
|
231
|
+
}).body);
|
|
230
232
|
return;
|
|
231
233
|
}
|
|
232
234
|
if (defaults.record) {
|
|
@@ -251,15 +253,15 @@ async function handleQueueSubmit(req, res, body, pathname, modelId, testId, fixt
|
|
|
251
253
|
response: {
|
|
252
254
|
status: 404,
|
|
253
255
|
fixture: null,
|
|
254
|
-
...
|
|
256
|
+
...require_policy.strictOverrideField(defaults.strict, req.headers)
|
|
255
257
|
}
|
|
256
258
|
});
|
|
257
259
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
258
|
-
res.end(
|
|
260
|
+
res.end(require_errors.renderError("openai", "fixture-miss", {
|
|
259
261
|
message: "No fixture matched",
|
|
260
262
|
type: "invalid_request_error",
|
|
261
263
|
code: "no_fixture_match"
|
|
262
|
-
}
|
|
264
|
+
}).body);
|
|
263
265
|
return;
|
|
264
266
|
}
|
|
265
267
|
const response = await require_helpers.resolveResponse(fixture, syntheticReq);
|
|
@@ -275,7 +277,7 @@ async function handleQueueSubmit(req, res, body, pathname, modelId, testId, fixt
|
|
|
275
277
|
fixture
|
|
276
278
|
}
|
|
277
279
|
});
|
|
278
|
-
require_sse_writer.writeErrorResponse(res, status,
|
|
280
|
+
require_sse_writer.writeErrorResponse(res, status, require_errors.renderError("openai", "fixture", response.error).body, { retryAfter: response.retryAfter });
|
|
279
281
|
return;
|
|
280
282
|
}
|
|
281
283
|
let result;
|
|
@@ -294,10 +296,10 @@ async function handleQueueSubmit(req, res, body, pathname, modelId, testId, fixt
|
|
|
294
296
|
}
|
|
295
297
|
});
|
|
296
298
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
297
|
-
res.end(
|
|
299
|
+
res.end(require_errors.renderError("openai", "unknown-response", {
|
|
298
300
|
message: "Recorded fal audio fixture has non-object json",
|
|
299
301
|
type: "server_error"
|
|
300
|
-
}
|
|
302
|
+
}).body);
|
|
301
303
|
return;
|
|
302
304
|
}
|
|
303
305
|
result = json;
|
|
@@ -313,10 +315,10 @@ async function handleQueueSubmit(req, res, body, pathname, modelId, testId, fixt
|
|
|
313
315
|
}
|
|
314
316
|
});
|
|
315
317
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
316
|
-
res.end(
|
|
318
|
+
res.end(require_errors.renderError("openai", "unknown-response", {
|
|
317
319
|
message: "Fixture response is not an audio type",
|
|
318
320
|
type: "server_error"
|
|
319
|
-
}
|
|
321
|
+
}).body);
|
|
320
322
|
return;
|
|
321
323
|
}
|
|
322
324
|
const requestId = node_crypto.default.randomUUID();
|
|
@@ -416,10 +418,10 @@ async function tryRecordAudioQueueWalk(args) {
|
|
|
416
418
|
}
|
|
417
419
|
});
|
|
418
420
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
419
|
-
res.end(
|
|
421
|
+
res.end(require_errors.renderError("openai", "transport-failure", {
|
|
420
422
|
message: `Proxy to upstream failed: ${msg}`,
|
|
421
423
|
type: "proxy_error"
|
|
422
|
-
}
|
|
424
|
+
}).body);
|
|
423
425
|
return true;
|
|
424
426
|
}
|
|
425
427
|
if (!finalBody || typeof finalBody !== "object") {
|
|
@@ -437,10 +439,10 @@ async function tryRecordAudioQueueWalk(args) {
|
|
|
437
439
|
}
|
|
438
440
|
});
|
|
439
441
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
440
|
-
res.end(
|
|
442
|
+
res.end(require_errors.renderError("openai", "transport-failure", {
|
|
441
443
|
message: "Upstream result body was not a JSON object",
|
|
442
444
|
type: "proxy_error"
|
|
443
|
-
}
|
|
445
|
+
}).body);
|
|
444
446
|
return true;
|
|
445
447
|
}
|
|
446
448
|
const persistResult = require_recorder.persistFixture({
|
|
@@ -503,10 +505,10 @@ function handleQueueStatus(req, res, pathname, requestId, testId, journal) {
|
|
|
503
505
|
}
|
|
504
506
|
});
|
|
505
507
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
506
|
-
res.end(
|
|
508
|
+
res.end(require_errors.renderError("openai", "route-failure", {
|
|
507
509
|
message: `Request ${requestId} not found`,
|
|
508
510
|
type: "not_found"
|
|
509
|
-
}
|
|
511
|
+
}).body);
|
|
510
512
|
return;
|
|
511
513
|
}
|
|
512
514
|
journal.add({
|
|
@@ -541,10 +543,10 @@ function handleQueueResult(req, res, pathname, requestId, testId, journal) {
|
|
|
541
543
|
}
|
|
542
544
|
});
|
|
543
545
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
544
|
-
res.end(
|
|
546
|
+
res.end(require_errors.renderError("openai", "route-failure", {
|
|
545
547
|
message: `Request ${requestId} not found`,
|
|
546
548
|
type: "not_found"
|
|
547
|
-
}
|
|
549
|
+
}).body);
|
|
548
550
|
return;
|
|
549
551
|
}
|
|
550
552
|
if (job.result === null) {
|
|
@@ -624,10 +626,10 @@ async function handleSyncRun(req, res, body, pathname, modelId, fixtures, defaul
|
|
|
624
626
|
}
|
|
625
627
|
});
|
|
626
628
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
627
|
-
res.end(
|
|
629
|
+
res.end(require_errors.renderError("openai", "validation", {
|
|
628
630
|
message: `Malformed JSON: ${detail}`,
|
|
629
631
|
type: "invalid_request_error"
|
|
630
|
-
}
|
|
632
|
+
}).body);
|
|
631
633
|
return;
|
|
632
634
|
}
|
|
633
635
|
if (!require_helpers.isJsonObject(parsed)) {
|
|
@@ -642,10 +644,10 @@ async function handleSyncRun(req, res, body, pathname, modelId, fixtures, defaul
|
|
|
642
644
|
}
|
|
643
645
|
});
|
|
644
646
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
645
|
-
res.end(
|
|
647
|
+
res.end(require_errors.renderError("openai", "validation", {
|
|
646
648
|
message: "Request body must be a JSON object",
|
|
647
649
|
type: "invalid_request_error"
|
|
648
|
-
}
|
|
650
|
+
}).body);
|
|
649
651
|
return;
|
|
650
652
|
}
|
|
651
653
|
const syntheticReq = {
|
|
@@ -666,7 +668,7 @@ async function handleSyncRun(req, res, body, pathname, modelId, fixtures, defaul
|
|
|
666
668
|
body: syntheticReq
|
|
667
669
|
}, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
|
|
668
670
|
if (!fixture) {
|
|
669
|
-
if (
|
|
671
|
+
if (require_policy.resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {
|
|
670
672
|
const strictMessage = require_helpers.strictNoMatchMessage(skippedBySequenceOrTurn);
|
|
671
673
|
defaults.logger.error(require_helpers.strictNoMatchLogLine(req.method ?? "POST", pathname, skippedBySequenceOrTurn));
|
|
672
674
|
journal.add({
|
|
@@ -677,15 +679,15 @@ async function handleSyncRun(req, res, body, pathname, modelId, fixtures, defaul
|
|
|
677
679
|
response: {
|
|
678
680
|
status: 503,
|
|
679
681
|
fixture: null,
|
|
680
|
-
...
|
|
682
|
+
...require_policy.strictOverrideField(defaults.strict, req.headers)
|
|
681
683
|
}
|
|
682
684
|
});
|
|
683
685
|
res.writeHead(503, { "Content-Type": "application/json" });
|
|
684
|
-
res.end(
|
|
686
|
+
res.end(require_errors.renderError("openai", "strict-miss", {
|
|
685
687
|
message: strictMessage,
|
|
686
688
|
type: "invalid_request_error",
|
|
687
689
|
code: "no_fixture_match"
|
|
688
|
-
}
|
|
690
|
+
}).body);
|
|
689
691
|
return;
|
|
690
692
|
}
|
|
691
693
|
if (defaults.record) {
|
|
@@ -714,15 +716,15 @@ async function handleSyncRun(req, res, body, pathname, modelId, fixtures, defaul
|
|
|
714
716
|
response: {
|
|
715
717
|
status: 404,
|
|
716
718
|
fixture: null,
|
|
717
|
-
...
|
|
719
|
+
...require_policy.strictOverrideField(defaults.strict, req.headers)
|
|
718
720
|
}
|
|
719
721
|
});
|
|
720
722
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
721
|
-
res.end(
|
|
723
|
+
res.end(require_errors.renderError("openai", "fixture-miss", {
|
|
722
724
|
message: "No fixture matched",
|
|
723
725
|
type: "invalid_request_error",
|
|
724
726
|
code: "no_fixture_match"
|
|
725
|
-
}
|
|
727
|
+
}).body);
|
|
726
728
|
return;
|
|
727
729
|
}
|
|
728
730
|
const response = await require_helpers.resolveResponse(fixture, syntheticReq);
|
|
@@ -738,7 +740,7 @@ async function handleSyncRun(req, res, body, pathname, modelId, fixtures, defaul
|
|
|
738
740
|
fixture
|
|
739
741
|
}
|
|
740
742
|
});
|
|
741
|
-
require_sse_writer.writeErrorResponse(res, status,
|
|
743
|
+
require_sse_writer.writeErrorResponse(res, status, require_errors.renderError("openai", "fixture", response.error).body, { retryAfter: response.retryAfter });
|
|
742
744
|
return;
|
|
743
745
|
}
|
|
744
746
|
let result;
|
|
@@ -759,10 +761,10 @@ async function handleSyncRun(req, res, body, pathname, modelId, fixtures, defaul
|
|
|
759
761
|
}
|
|
760
762
|
});
|
|
761
763
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
762
|
-
res.end(
|
|
764
|
+
res.end(require_errors.renderError("openai", "unknown-response", {
|
|
763
765
|
message: "Recorded fal audio fixture has non-object json",
|
|
764
766
|
type: "server_error"
|
|
765
|
-
}
|
|
767
|
+
}).body);
|
|
766
768
|
return;
|
|
767
769
|
}
|
|
768
770
|
result = json;
|
|
@@ -778,10 +780,10 @@ async function handleSyncRun(req, res, body, pathname, modelId, fixtures, defaul
|
|
|
778
780
|
}
|
|
779
781
|
});
|
|
780
782
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
781
|
-
res.end(
|
|
783
|
+
res.end(require_errors.renderError("openai", "unknown-response", {
|
|
782
784
|
message: "Fixture response is not an audio type",
|
|
783
785
|
type: "server_error"
|
|
784
|
-
}
|
|
786
|
+
}).body);
|
|
785
787
|
return;
|
|
786
788
|
}
|
|
787
789
|
journal.add({
|