@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/aimock-cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { loadConfig, startFromConfig } from "./config-loader.js";
|
|
2
|
+
import { loadConfig, parsePolicyFlags, startFromConfig } from "./config-loader.js";
|
|
3
3
|
import { runConvertCli } from "./convert.js";
|
|
4
4
|
import { runValidateCli } from "./validate-cli.js";
|
|
5
5
|
import { basename, resolve } from "node:path";
|
|
@@ -20,6 +20,9 @@ Options:
|
|
|
20
20
|
instead of serving {} (same as llm.strictToolArguments: true)
|
|
21
21
|
--responses-tools <mode> OpenAI Responses tool handling: legacy (default) or
|
|
22
22
|
extended (overrides llm.responsesTools)
|
|
23
|
+
--policy <key=value> Request policy (repeatable): requiredHeaders=off|enforce,
|
|
24
|
+
authErrors=legacy|native, modelCatalog=legacy|native
|
|
25
|
+
Overrides individual llm.policy keys (defaults: off, legacy, legacy)
|
|
23
26
|
-h, --help Show this help message
|
|
24
27
|
|
|
25
28
|
Subcommands:
|
|
@@ -78,6 +81,10 @@ function runAimockCli(deps = {}) {
|
|
|
78
81
|
default: false
|
|
79
82
|
},
|
|
80
83
|
"responses-tools": { type: "string" },
|
|
84
|
+
policy: {
|
|
85
|
+
type: "string",
|
|
86
|
+
multiple: true
|
|
87
|
+
},
|
|
81
88
|
help: {
|
|
82
89
|
type: "boolean",
|
|
83
90
|
short: "h",
|
|
@@ -135,12 +142,19 @@ function runAimockCli(deps = {}) {
|
|
|
135
142
|
return;
|
|
136
143
|
}
|
|
137
144
|
const responsesTools = responsesToolsFlag;
|
|
145
|
+
const parsedPolicy = parsePolicyFlags(values.policy ?? []);
|
|
146
|
+
if (!parsedPolicy.ok) {
|
|
147
|
+
logError(`${parsedPolicy.error}\n\n${HELP}`);
|
|
148
|
+
exit(1);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
138
151
|
const overrides = {
|
|
139
152
|
port,
|
|
140
153
|
host,
|
|
141
154
|
...values.misbehavior ? { enableMisbehavior: true } : {},
|
|
142
155
|
...values["strict-tool-arguments"] ? { strictToolArguments: true } : {},
|
|
143
|
-
...responsesTools !== void 0 ? { responsesTools } : {}
|
|
156
|
+
...responsesTools !== void 0 ? { responsesTools } : {},
|
|
157
|
+
...values.policy !== void 0 ? { policy: parsedPolicy.policy } : {}
|
|
144
158
|
};
|
|
145
159
|
async function main() {
|
|
146
160
|
const { llmock, url } = await startFromConfigFn(config, overrides);
|
package/dist/aimock-cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aimock-cli.js","names":[],"sources":["../src/aimock-cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from \"node:util\";\nimport { resolve, basename } from \"node:path\";\nimport { loadConfig, startFromConfig, type StartFromConfigOverrides } from \"./config-loader.js\";\nimport { runConvertCli, type ConvertCliDeps } from \"./convert.js\";\nimport { runValidateCli } from \"./validate-cli.js\";\n\nconst HELP = `\nUsage: aimock [options]\n aimock convert <format> <input> [output]\n aimock validate [--strict] [--json] [--] <path> [more paths ...]\n\nOptions:\n -c, --config <path> Path to aimock config JSON file (required)\n -p, --port <number> Port override (default: from config or 0)\n --host <string> Host override (default: from config or 127.0.0.1)\n --misbehavior Enable model misbehavior (same as llm.enableMisbehavior: true)\n --strict-tool-arguments Reject fixture tool calls with invalid JSON arguments\n instead of serving {} (same as llm.strictToolArguments: true)\n --responses-tools <mode> OpenAI Responses tool handling: legacy (default) or\n extended (overrides llm.responsesTools)\n -h, --help Show this help message\n\nSubcommands:\n convert Convert third-party mock configs to aimock format\n Run \"aimock convert --help\" for details\n validate Validate fixture files or directories offline\n Run \"aimock validate --help\" for details\n`.trim();\n\nexport interface AimockCliDeps {\n argv?: string[];\n log?: (msg: string) => void;\n logError?: (msg: string) => void;\n exit?: (code: number) => void;\n loadConfigFn?: typeof loadConfig;\n startFromConfigFn?: typeof startFromConfig;\n onReady?: (ctx: { shutdown: () => void }) => void;\n convertDeps?: Partial<ConvertCliDeps>;\n validateDeps?: {\n log?: (msg: string) => void;\n logError?: (msg: string) => void;\n exit?: (code: number) => void;\n };\n}\n\nexport function runAimockCli(deps: AimockCliDeps = {}): void {\n /* v8 ignore next 6 -- defaults used only when called from CLI entry point */\n const argv = deps.argv ?? process.argv.slice(2);\n const log = deps.log ?? console.log.bind(console);\n const logError = deps.logError ?? console.error.bind(console);\n const exit = deps.exit ?? process.exit.bind(process);\n const loadConfigFn = deps.loadConfigFn ?? loadConfig;\n const startFromConfigFn = deps.startFromConfigFn ?? startFromConfig;\n\n // Intercept \"convert\" subcommand before parseArgs (which uses strict mode)\n if (argv[0] === \"convert\") {\n runConvertCli({\n argv: argv.slice(1),\n log,\n logError,\n exit,\n ...deps.convertDeps,\n });\n return;\n }\n\n // Intercept \"validate\" the same way — it takes file paths, not --config.\n if (argv[0] === \"validate\") {\n runValidateCli({\n argv: argv.slice(1),\n log: deps.validateDeps?.log ?? log,\n logError: deps.validateDeps?.logError ?? logError,\n exit: deps.validateDeps?.exit ?? exit,\n });\n return;\n }\n\n // Short-flag rule for the `aimock` bin: a short flag means the same thing at\n // the top level and in every subcommand. `-h` is `--help` everywhere, which\n // is what `convert` (src/convert.ts) and `validate` (src/validate-cli.ts)\n // already did and what a reader typing `aimock -h` expects; the host\n // override is long-form `--host` only, and had no documented short use.\n // Short flags are per-bin: the separate `llmock` bin (src/cli.ts, the\n // Docker ENTRYPOINT) keeps its own documented set, where `-h` is `--host`\n // and `-c` is `--chunk-size`.\n let values;\n try {\n ({ values } = parseArgs({\n args: argv,\n options: {\n config: { type: \"string\", short: \"c\" },\n port: { type: \"string\", short: \"p\" },\n host: { type: \"string\" },\n misbehavior: { type: \"boolean\", default: false },\n \"strict-tool-arguments\": { type: \"boolean\", default: false },\n \"responses-tools\": { type: \"string\" },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n strict: true,\n }));\n } catch (err) {\n /* v8 ignore next -- parseArgs always throws Error subclasses */\n const msg = err instanceof Error ? err.message : String(err);\n logError(`Error: ${msg}\\n\\n${HELP}`);\n exit(1);\n return;\n }\n\n if (values.help) {\n log(HELP);\n exit(0);\n return;\n }\n if (values.config === undefined) {\n logError(\"Error: --config is required.\\n\\n\" + HELP);\n exit(1);\n return;\n }\n // A value-taking option that was GIVEN an empty (or blank) value is a usage\n // error, not an absent option. Testing these for truthiness instead let\n // `--config \"\"` read as \"not given\", and let `--port \"\"` / `--host \"\"` be\n // dropped in silence — `--host \"\"` in particular reached the server as an\n // empty bind address, which listens on every interface rather than on the\n // documented 127.0.0.1 default.\n const blank = (\n [\n [\"config\", values.config],\n [\"port\", values.port],\n [\"host\", values.host],\n [\"responses-tools\", values[\"responses-tools\"]],\n ] as const\n ).find(([, value]) => value !== undefined && value.trim() === \"\");\n if (blank !== undefined) {\n logError(`Error: --${blank[0]} requires a non-empty value.\\n\\n${HELP}`);\n exit(1);\n return;\n }\n\n const configPath = resolve(values.config);\n let config;\n try {\n config = loadConfigFn(configPath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logError(`Failed to load config from ${configPath}: ${msg}`);\n exit(1);\n return;\n }\n\n const port = values.port === undefined ? undefined : Number(values.port);\n if (\n port !== undefined &&\n (Number.isNaN(port) || !Number.isInteger(port) || port < 0 || port > 65535)\n ) {\n logError(`Error: invalid port \"${values.port}\".\\n\\n${HELP}`);\n exit(1);\n return;\n }\n const host = values.host;\n const responsesToolsFlag = values[\"responses-tools\"];\n if (\n responsesToolsFlag !== undefined &&\n responsesToolsFlag !== \"legacy\" &&\n responsesToolsFlag !== \"extended\"\n ) {\n logError(\n `Error: invalid --responses-tools \"${responsesToolsFlag}\" (expected \"legacy\" or \"extended\").\\n\\n${HELP}`,\n );\n exit(1);\n return;\n }\n // The boolean opt-ins override the config only when given, so an absent flag\n // leaves llm.enableMisbehavior / llm.strictToolArguments in charge.\n const responsesTools: StartFromConfigOverrides[\"responsesTools\"] = responsesToolsFlag;\n const overrides: StartFromConfigOverrides = {\n port,\n host,\n ...(values.misbehavior ? { enableMisbehavior: true } : {}),\n ...(values[\"strict-tool-arguments\"] ? { strictToolArguments: true } : {}),\n ...(responsesTools !== undefined ? { responsesTools } : {}),\n };\n\n async function main() {\n const { llmock, url } = await startFromConfigFn(config!, overrides);\n\n function shutdown() {\n log(\"Shutting down...\");\n process.removeListener(\"SIGINT\", shutdown);\n process.removeListener(\"SIGTERM\", shutdown);\n llmock.stop().then(\n () => exit(0),\n (err) => {\n logError(`Shutdown error: ${err instanceof Error ? err.message : String(err)}`);\n exit(1);\n },\n );\n }\n // Register BEFORE announcing readiness — see the note in src/cli.ts. A supervisor\n // that reacts to the readiness line must never win a race against this listener.\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n log(`aimock server listening on ${url}`);\n\n if (deps.onReady) {\n deps.onReady({ shutdown });\n }\n }\n\n main().catch((err) => {\n logError(err instanceof Error ? err.message : String(err));\n exit(1);\n });\n}\n\n// Run when executed as a script (not when imported for testing).\n/* v8 ignore start -- entry-point guard, exercised by integration tests */\n// The basenames this module is ever argv[1] under: the `aimock` bin (the\n// package.json \"bin\" name), both build outputs — tsdown.config.ts builds\n// src/aimock-cli.ts in \"esm\" AND \"cjs\" format, which under `\"type\": \"module\"`\n// emits dist/aimock-cli.js and dist/aimock-cli.cjs — and the TypeScript\n// source, run through tsx. Omitting the .cjs made `node dist/aimock-cli.cjs`\n// print nothing and exit 0 despite its shebang and exec bit.\nconst ENTRY_BASENAMES = new Set([\"aimock\", \"aimock-cli.js\", \"aimock-cli.cjs\", \"aimock-cli.ts\"]);\nconst scriptName = process.argv[1] ?? \"\";\nif (ENTRY_BASENAMES.has(basename(scriptName))) {\n runAimockCli();\n}\n/* v8 ignore stop */\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"aimock-cli.js","names":[],"sources":["../src/aimock-cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from \"node:util\";\nimport { resolve, basename } from \"node:path\";\nimport {\n loadConfig,\n parsePolicyFlags,\n startFromConfig,\n type StartFromConfigOverrides,\n} from \"./config-loader.js\";\nimport { runConvertCli, type ConvertCliDeps } from \"./convert.js\";\nimport { runValidateCli } from \"./validate-cli.js\";\n\nconst HELP = `\nUsage: aimock [options]\n aimock convert <format> <input> [output]\n aimock validate [--strict] [--json] [--] <path> [more paths ...]\n\nOptions:\n -c, --config <path> Path to aimock config JSON file (required)\n -p, --port <number> Port override (default: from config or 0)\n --host <string> Host override (default: from config or 127.0.0.1)\n --misbehavior Enable model misbehavior (same as llm.enableMisbehavior: true)\n --strict-tool-arguments Reject fixture tool calls with invalid JSON arguments\n instead of serving {} (same as llm.strictToolArguments: true)\n --responses-tools <mode> OpenAI Responses tool handling: legacy (default) or\n extended (overrides llm.responsesTools)\n --policy <key=value> Request policy (repeatable): requiredHeaders=off|enforce,\n authErrors=legacy|native, modelCatalog=legacy|native\n Overrides individual llm.policy keys (defaults: off, legacy, legacy)\n -h, --help Show this help message\n\nSubcommands:\n convert Convert third-party mock configs to aimock format\n Run \"aimock convert --help\" for details\n validate Validate fixture files or directories offline\n Run \"aimock validate --help\" for details\n`.trim();\n\nexport interface AimockCliDeps {\n argv?: string[];\n log?: (msg: string) => void;\n logError?: (msg: string) => void;\n exit?: (code: number) => void;\n loadConfigFn?: typeof loadConfig;\n startFromConfigFn?: typeof startFromConfig;\n onReady?: (ctx: { shutdown: () => void }) => void;\n convertDeps?: Partial<ConvertCliDeps>;\n validateDeps?: {\n log?: (msg: string) => void;\n logError?: (msg: string) => void;\n exit?: (code: number) => void;\n };\n}\n\nexport function runAimockCli(deps: AimockCliDeps = {}): void {\n /* v8 ignore next 6 -- defaults used only when called from CLI entry point */\n const argv = deps.argv ?? process.argv.slice(2);\n const log = deps.log ?? console.log.bind(console);\n const logError = deps.logError ?? console.error.bind(console);\n const exit = deps.exit ?? process.exit.bind(process);\n const loadConfigFn = deps.loadConfigFn ?? loadConfig;\n const startFromConfigFn = deps.startFromConfigFn ?? startFromConfig;\n\n // Intercept \"convert\" subcommand before parseArgs (which uses strict mode)\n if (argv[0] === \"convert\") {\n runConvertCli({\n argv: argv.slice(1),\n log,\n logError,\n exit,\n ...deps.convertDeps,\n });\n return;\n }\n\n // Intercept \"validate\" the same way — it takes file paths, not --config.\n if (argv[0] === \"validate\") {\n runValidateCli({\n argv: argv.slice(1),\n log: deps.validateDeps?.log ?? log,\n logError: deps.validateDeps?.logError ?? logError,\n exit: deps.validateDeps?.exit ?? exit,\n });\n return;\n }\n\n // Short-flag rule for the `aimock` bin: a short flag means the same thing at\n // the top level and in every subcommand. `-h` is `--help` everywhere, which\n // is what `convert` (src/convert.ts) and `validate` (src/validate-cli.ts)\n // already did and what a reader typing `aimock -h` expects; the host\n // override is long-form `--host` only, and had no documented short use.\n // Short flags are per-bin: the separate `llmock` bin (src/cli.ts, the\n // Docker ENTRYPOINT) keeps its own documented set, where `-h` is `--host`\n // and `-c` is `--chunk-size`.\n let values;\n try {\n ({ values } = parseArgs({\n args: argv,\n options: {\n config: { type: \"string\", short: \"c\" },\n port: { type: \"string\", short: \"p\" },\n host: { type: \"string\" },\n misbehavior: { type: \"boolean\", default: false },\n \"strict-tool-arguments\": { type: \"boolean\", default: false },\n \"responses-tools\": { type: \"string\" },\n policy: { type: \"string\", multiple: true },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n strict: true,\n }));\n } catch (err) {\n /* v8 ignore next -- parseArgs always throws Error subclasses */\n const msg = err instanceof Error ? err.message : String(err);\n logError(`Error: ${msg}\\n\\n${HELP}`);\n exit(1);\n return;\n }\n\n if (values.help) {\n log(HELP);\n exit(0);\n return;\n }\n if (values.config === undefined) {\n logError(\"Error: --config is required.\\n\\n\" + HELP);\n exit(1);\n return;\n }\n // A value-taking option that was GIVEN an empty (or blank) value is a usage\n // error, not an absent option. Testing these for truthiness instead let\n // `--config \"\"` read as \"not given\", and let `--port \"\"` / `--host \"\"` be\n // dropped in silence — `--host \"\"` in particular reached the server as an\n // empty bind address, which listens on every interface rather than on the\n // documented 127.0.0.1 default.\n const blank = (\n [\n [\"config\", values.config],\n [\"port\", values.port],\n [\"host\", values.host],\n [\"responses-tools\", values[\"responses-tools\"]],\n ] as const\n ).find(([, value]) => value !== undefined && value.trim() === \"\");\n if (blank !== undefined) {\n logError(`Error: --${blank[0]} requires a non-empty value.\\n\\n${HELP}`);\n exit(1);\n return;\n }\n\n const configPath = resolve(values.config);\n let config;\n try {\n config = loadConfigFn(configPath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logError(`Failed to load config from ${configPath}: ${msg}`);\n exit(1);\n return;\n }\n\n const port = values.port === undefined ? undefined : Number(values.port);\n if (\n port !== undefined &&\n (Number.isNaN(port) || !Number.isInteger(port) || port < 0 || port > 65535)\n ) {\n logError(`Error: invalid port \"${values.port}\".\\n\\n${HELP}`);\n exit(1);\n return;\n }\n const host = values.host;\n const responsesToolsFlag = values[\"responses-tools\"];\n if (\n responsesToolsFlag !== undefined &&\n responsesToolsFlag !== \"legacy\" &&\n responsesToolsFlag !== \"extended\"\n ) {\n logError(\n `Error: invalid --responses-tools \"${responsesToolsFlag}\" (expected \"legacy\" or \"extended\").\\n\\n${HELP}`,\n );\n exit(1);\n return;\n }\n // The boolean opt-ins override the config only when given, so an absent flag\n // leaves llm.enableMisbehavior / llm.strictToolArguments in charge.\n const responsesTools: StartFromConfigOverrides[\"responsesTools\"] = responsesToolsFlag;\n const parsedPolicy = parsePolicyFlags(values.policy ?? []);\n if (!parsedPolicy.ok) {\n logError(`${parsedPolicy.error}\\n\\n${HELP}`);\n exit(1);\n return;\n }\n const overrides: StartFromConfigOverrides = {\n port,\n host,\n ...(values.misbehavior ? { enableMisbehavior: true } : {}),\n ...(values[\"strict-tool-arguments\"] ? { strictToolArguments: true } : {}),\n ...(responsesTools !== undefined ? { responsesTools } : {}),\n ...(values.policy !== undefined ? { policy: parsedPolicy.policy } : {}),\n };\n\n async function main() {\n const { llmock, url } = await startFromConfigFn(config!, overrides);\n\n function shutdown() {\n log(\"Shutting down...\");\n process.removeListener(\"SIGINT\", shutdown);\n process.removeListener(\"SIGTERM\", shutdown);\n llmock.stop().then(\n () => exit(0),\n (err) => {\n logError(`Shutdown error: ${err instanceof Error ? err.message : String(err)}`);\n exit(1);\n },\n );\n }\n // Register BEFORE announcing readiness — see the note in src/cli.ts. A supervisor\n // that reacts to the readiness line must never win a race against this listener.\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n log(`aimock server listening on ${url}`);\n\n if (deps.onReady) {\n deps.onReady({ shutdown });\n }\n }\n\n main().catch((err) => {\n logError(err instanceof Error ? err.message : String(err));\n exit(1);\n });\n}\n\n// Run when executed as a script (not when imported for testing).\n/* v8 ignore start -- entry-point guard, exercised by integration tests */\n// The basenames this module is ever argv[1] under: the `aimock` bin (the\n// package.json \"bin\" name), both build outputs — tsdown.config.ts builds\n// src/aimock-cli.ts in \"esm\" AND \"cjs\" format, which under `\"type\": \"module\"`\n// emits dist/aimock-cli.js and dist/aimock-cli.cjs — and the TypeScript\n// source, run through tsx. Omitting the .cjs made `node dist/aimock-cli.cjs`\n// print nothing and exit 0 despite its shebang and exec bit.\nconst ENTRY_BASENAMES = new Set([\"aimock\", \"aimock-cli.js\", \"aimock-cli.cjs\", \"aimock-cli.ts\"]);\nconst scriptName = process.argv[1] ?? \"\";\nif (ENTRY_BASENAMES.has(basename(scriptName))) {\n runAimockCli();\n}\n/* v8 ignore stop */\n"],"mappings":"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"}
|
package/dist/api-key-auth.cjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
|
+
const require_profiles = require('./response/profiles.cjs');
|
|
3
|
+
const require_errors = require('./response/errors.cjs');
|
|
2
4
|
let node_crypto = require("node:crypto");
|
|
3
5
|
|
|
4
6
|
//#region src/api-key-auth.ts
|
|
@@ -10,11 +12,7 @@ const INBOUND_API_KEY_HEADERS = [
|
|
|
10
12
|
"xi-api-key"
|
|
11
13
|
];
|
|
12
14
|
const RECOGNIZED_HEADERS = new Set(INBOUND_API_KEY_HEADERS);
|
|
13
|
-
const API_KEY_ERROR_BODY =
|
|
14
|
-
message: "Invalid API key",
|
|
15
|
-
type: "authentication_error",
|
|
16
|
-
code: "invalid_api_key"
|
|
17
|
-
} });
|
|
15
|
+
const API_KEY_ERROR_BODY = require_errors.renderError("openai", "legacy-auth", { message: "Invalid API key" }).body;
|
|
18
16
|
const DISABLED_POLICY = {
|
|
19
17
|
enabled: false,
|
|
20
18
|
keys: []
|
|
@@ -132,14 +130,17 @@ function markAuthenticatedRequest(req, policy) {
|
|
|
132
130
|
function isAuthenticatedRequest(req) {
|
|
133
131
|
return authenticatedRequests.has(req);
|
|
134
132
|
}
|
|
135
|
-
function writeApiKeyHttpRejection(res, setCorsHeaders) {
|
|
133
|
+
function writeApiKeyHttpRejection(res, setCorsHeaders, pathname = "", authErrors = "legacy") {
|
|
136
134
|
setCorsHeaders(res);
|
|
135
|
+
const dialect = authErrors === "native" ? require_profiles.authErrorDialectForPath(pathname) : "openai";
|
|
136
|
+
const rendered = require_errors.renderError(dialect, "authentication", { message: "Invalid API key" });
|
|
137
137
|
res.writeHead(401, {
|
|
138
138
|
"Content-Type": "application/json",
|
|
139
|
-
"Content-Length": Buffer.byteLength(
|
|
140
|
-
"WWW-Authenticate": "Bearer realm=\"aimock\""
|
|
139
|
+
"Content-Length": Buffer.byteLength(rendered.body),
|
|
140
|
+
"WWW-Authenticate": "Bearer realm=\"aimock\"",
|
|
141
|
+
...dialect === "messages" ? { "request-id": `req_${(0, node_crypto.randomBytes)(12).toString("base64url")}` } : {}
|
|
141
142
|
});
|
|
142
|
-
res.end(
|
|
143
|
+
res.end(rendered.body);
|
|
143
144
|
}
|
|
144
145
|
function writeApiKeyUpgradeRejection(socket) {
|
|
145
146
|
socket.write([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-key-auth.cjs","names":[],"sources":["../src/api-key-auth.ts"],"sourcesContent":["import { timingSafeEqual } from \"node:crypto\";\nimport type * as http from \"node:http\";\nimport type * as net from \"node:net\";\nimport type { ApiKeyAuthConfig } from \"./types.js\";\n\nexport const INBOUND_API_KEY_HEADERS = [\n \"authorization\",\n \"x-api-key\",\n \"x-goog-api-key\",\n \"api-key\",\n \"xi-api-key\",\n] as const;\n\nconst RECOGNIZED_HEADERS = new Set<string>(INBOUND_API_KEY_HEADERS);\n\nexport const API_KEY_ERROR_BODY = JSON.stringify({\n error: {\n message: \"Invalid API key\",\n type: \"authentication_error\",\n code: \"invalid_api_key\",\n },\n});\n\n/** @internal */\nexport interface ApiKeyPolicy {\n readonly enabled: boolean;\n readonly keys: readonly Buffer[];\n}\n\n/** @internal */\nexport interface ResolvedInboundAuth {\n readonly publicConfig?: ApiKeyAuthConfig;\n readonly policy: ApiKeyPolicy;\n}\n\nexport interface AuthResult {\n readonly ok: boolean;\n readonly reason?: \"missing\" | \"invalid\" | \"conflict\";\n}\n\nconst DISABLED_POLICY: ApiKeyPolicy = { enabled: false, keys: [] };\nconst authenticatedRequests = new WeakSet<http.IncomingMessage>();\nconst OWS_EDGES = /^[ \\t]+|[ \\t]+$/g;\n\nfunction fail(label: string, suffix: string): never {\n throw new Error(`${label}${suffix}`);\n}\n\nfunction hasControl(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const code = value.charCodeAt(index);\n if (code <= 0x1f || code === 0x7f) return true;\n }\n return false;\n}\n\nfunction normalizeKey(value: unknown, label: string): string {\n if (typeof value !== \"string\") fail(label, \" must be a string\");\n const normalized = value.replace(OWS_EDGES, \"\");\n if (normalized.length === 0) fail(label, \" must not be empty\");\n if (hasControl(normalized)) fail(label, \" must not contain control characters\");\n return normalized;\n}\n\n/** Parse a public auth source once and construct its private server policy. */\nexport function resolveInboundAuth(source: { value: unknown; label: string }): ResolvedInboundAuth {\n if (source.value === undefined) return { policy: DISABLED_POLICY };\n if (source.value === null || typeof source.value !== \"object\" || Array.isArray(source.value)) {\n fail(source.label, \" must be an object\");\n }\n const apiKeys = (source.value as { apiKeys?: unknown }).apiKeys;\n if (!Array.isArray(apiKeys)) fail(`${source.label}.apiKeys`, \" must be an array\");\n if (apiKeys.length === 0) fail(`${source.label}.apiKeys`, \" must not be empty\");\n const keys = apiKeys.map((value, index) =>\n normalizeKey(value, `${source.label}.apiKeys[${index}]`),\n );\n const seen = new Set<string>();\n for (const key of keys) {\n if (seen.has(key)) fail(source.label, \" must not contain duplicate API keys\");\n seen.add(key);\n }\n return {\n publicConfig: { apiKeys: keys },\n policy: { enabled: true, keys: keys.map((key) => Buffer.from(key, \"utf8\")) },\n };\n}\n\n/** Select configuration without parsing it, so an environment override bypasses malformed JSON auth. */\nexport function selectInboundAuthSource(\n configAuth: unknown,\n env: NodeJS.ProcessEnv = process.env,\n): { value: unknown; label: string } {\n if (Object.prototype.hasOwnProperty.call(env, \"AIMOCK_API_KEYS\")) {\n return { value: { apiKeys: (env.AIMOCK_API_KEYS ?? \"\").split(\",\") }, label: \"AIMOCK_API_KEYS\" };\n }\n return { value: configAuth, label: \"aimock.json.auth\" };\n}\n\nexport function isRecognizedApiKeyHeader(name: string): boolean {\n return RECOGNIZED_HEADERS.has(name.toLowerCase());\n}\n\nfunction rawCandidates(req: http.IncomingMessage): {\n candidates?: string[];\n reason?: \"missing\" | \"invalid\";\n} {\n const candidates: string[] = [];\n for (let index = 0; index < req.rawHeaders.length; index += 2) {\n const name = req.rawHeaders[index];\n const value = req.rawHeaders[index + 1] ?? \"\";\n const lower = name.toLowerCase();\n if (!isRecognizedApiKeyHeader(lower)) continue;\n if (hasControl(value)) return { reason: \"invalid\" };\n if (lower === \"authorization\") {\n const match = /^(?:Bearer|Key)[ \\t]+(.+)$/i.exec(value.replace(OWS_EDGES, \"\"));\n if (!match) return { reason: \"invalid\" };\n const candidate = match[1].replace(OWS_EDGES, \"\");\n if (candidate.length === 0 || hasControl(candidate)) return { reason: \"invalid\" };\n candidates.push(candidate);\n } else {\n const candidate = value.replace(OWS_EDGES, \"\");\n if (candidate.length === 0) return { reason: \"invalid\" };\n candidates.push(candidate);\n }\n }\n return candidates.length > 0 ? { candidates } : { reason: \"missing\" };\n}\n\nfunction matchingIndex(candidate: string, keys: readonly Buffer[]): number | undefined {\n const encoded = Buffer.from(candidate, \"utf8\");\n for (let index = 0; index < keys.length; index += 1) {\n const key = keys[index];\n if (encoded.length === key.length && timingSafeEqual(encoded, key)) return index;\n }\n return undefined;\n}\n\nexport function validateRequestApiKey(req: http.IncomingMessage, policy: ApiKeyPolicy): AuthResult {\n if (!policy.enabled) return { ok: true };\n const extracted = rawCandidates(req);\n if (!extracted.candidates) return { ok: false, reason: extracted.reason };\n let resolved: number | undefined;\n for (const candidate of extracted.candidates) {\n const index = matchingIndex(candidate, policy.keys);\n if (index === undefined) return { ok: false, reason: \"invalid\" };\n if (resolved !== undefined && resolved !== index) return { ok: false, reason: \"conflict\" };\n resolved = index;\n }\n return { ok: true };\n}\n\n/** @internal Opaque egress marker; it deliberately carries no key material. */\nexport function markAuthenticatedRequest(req: http.IncomingMessage, policy: ApiKeyPolicy): void {\n if (policy.enabled) authenticatedRequests.add(req);\n}\n\n/** @internal Used by egress paths to select safe credential handling. */\nexport function isAuthenticatedRequest(req: http.IncomingMessage): boolean {\n return authenticatedRequests.has(req);\n}\n\nexport function writeApiKeyHttpRejection(\n res: http.ServerResponse,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): void {\n setCorsHeaders(res);\n res.writeHead(401, {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(API_KEY_ERROR_BODY),\n \"WWW-Authenticate\": 'Bearer realm=\"aimock\"',\n });\n res.end(API_KEY_ERROR_BODY);\n}\n\nexport function writeApiKeyUpgradeRejection(socket: net.Socket): void {\n socket.write(\n [\n \"HTTP/1.1 401 Unauthorized\",\n \"Content-Type: application/json\",\n `Content-Length: ${Buffer.byteLength(API_KEY_ERROR_BODY)}`,\n 'WWW-Authenticate: Bearer realm=\"aimock\"',\n \"Connection: close\",\n \"\",\n API_KEY_ERROR_BODY,\n ].join(\"\\r\\n\"),\n );\n socket.destroy();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api-key-auth.cjs","names":["renderError","authErrorDialectForPath"],"sources":["../src/api-key-auth.ts"],"sourcesContent":["import { randomBytes, timingSafeEqual } from \"node:crypto\";\nimport type * as http from \"node:http\";\nimport type * as net from \"node:net\";\nimport type { ApiKeyAuthConfig } from \"./types.js\";\nimport { renderError } from \"./response/errors.js\";\nimport { authErrorDialectForPath } from \"./response/profiles.js\";\n\nexport const INBOUND_API_KEY_HEADERS = [\n \"authorization\",\n \"x-api-key\",\n \"x-goog-api-key\",\n \"api-key\",\n \"xi-api-key\",\n] as const;\n\nconst RECOGNIZED_HEADERS = new Set<string>(INBOUND_API_KEY_HEADERS);\n\nexport const API_KEY_ERROR_BODY = renderError(\"openai\", \"legacy-auth\", {\n message: \"Invalid API key\",\n}).body;\n\n/** @internal */\nexport interface ApiKeyPolicy {\n readonly enabled: boolean;\n readonly keys: readonly Buffer[];\n}\n\n/** @internal */\nexport interface ResolvedInboundAuth {\n readonly publicConfig?: ApiKeyAuthConfig;\n readonly policy: ApiKeyPolicy;\n}\n\nexport interface AuthResult {\n readonly ok: boolean;\n readonly reason?: \"missing\" | \"invalid\" | \"conflict\";\n}\n\nconst DISABLED_POLICY: ApiKeyPolicy = { enabled: false, keys: [] };\nconst authenticatedRequests = new WeakSet<http.IncomingMessage>();\nconst OWS_EDGES = /^[ \\t]+|[ \\t]+$/g;\n\nfunction fail(label: string, suffix: string): never {\n throw new Error(`${label}${suffix}`);\n}\n\nfunction hasControl(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const code = value.charCodeAt(index);\n if (code <= 0x1f || code === 0x7f) return true;\n }\n return false;\n}\n\nfunction normalizeKey(value: unknown, label: string): string {\n if (typeof value !== \"string\") fail(label, \" must be a string\");\n const normalized = value.replace(OWS_EDGES, \"\");\n if (normalized.length === 0) fail(label, \" must not be empty\");\n if (hasControl(normalized)) fail(label, \" must not contain control characters\");\n return normalized;\n}\n\n/** Parse a public auth source once and construct its private server policy. */\nexport function resolveInboundAuth(source: { value: unknown; label: string }): ResolvedInboundAuth {\n if (source.value === undefined) return { policy: DISABLED_POLICY };\n if (source.value === null || typeof source.value !== \"object\" || Array.isArray(source.value)) {\n fail(source.label, \" must be an object\");\n }\n const apiKeys = (source.value as { apiKeys?: unknown }).apiKeys;\n if (!Array.isArray(apiKeys)) fail(`${source.label}.apiKeys`, \" must be an array\");\n if (apiKeys.length === 0) fail(`${source.label}.apiKeys`, \" must not be empty\");\n const keys = apiKeys.map((value, index) =>\n normalizeKey(value, `${source.label}.apiKeys[${index}]`),\n );\n const seen = new Set<string>();\n for (const key of keys) {\n if (seen.has(key)) fail(source.label, \" must not contain duplicate API keys\");\n seen.add(key);\n }\n return {\n publicConfig: { apiKeys: keys },\n policy: { enabled: true, keys: keys.map((key) => Buffer.from(key, \"utf8\")) },\n };\n}\n\n/** Select configuration without parsing it, so an environment override bypasses malformed JSON auth. */\nexport function selectInboundAuthSource(\n configAuth: unknown,\n env: NodeJS.ProcessEnv = process.env,\n): { value: unknown; label: string } {\n if (Object.prototype.hasOwnProperty.call(env, \"AIMOCK_API_KEYS\")) {\n return { value: { apiKeys: (env.AIMOCK_API_KEYS ?? \"\").split(\",\") }, label: \"AIMOCK_API_KEYS\" };\n }\n return { value: configAuth, label: \"aimock.json.auth\" };\n}\n\nexport function isRecognizedApiKeyHeader(name: string): boolean {\n return RECOGNIZED_HEADERS.has(name.toLowerCase());\n}\n\nfunction rawCandidates(req: http.IncomingMessage): {\n candidates?: string[];\n reason?: \"missing\" | \"invalid\";\n} {\n const candidates: string[] = [];\n for (let index = 0; index < req.rawHeaders.length; index += 2) {\n const name = req.rawHeaders[index];\n const value = req.rawHeaders[index + 1] ?? \"\";\n const lower = name.toLowerCase();\n if (!isRecognizedApiKeyHeader(lower)) continue;\n if (hasControl(value)) return { reason: \"invalid\" };\n if (lower === \"authorization\") {\n const match = /^(?:Bearer|Key)[ \\t]+(.+)$/i.exec(value.replace(OWS_EDGES, \"\"));\n if (!match) return { reason: \"invalid\" };\n const candidate = match[1].replace(OWS_EDGES, \"\");\n if (candidate.length === 0 || hasControl(candidate)) return { reason: \"invalid\" };\n candidates.push(candidate);\n } else {\n const candidate = value.replace(OWS_EDGES, \"\");\n if (candidate.length === 0) return { reason: \"invalid\" };\n candidates.push(candidate);\n }\n }\n return candidates.length > 0 ? { candidates } : { reason: \"missing\" };\n}\n\nfunction matchingIndex(candidate: string, keys: readonly Buffer[]): number | undefined {\n const encoded = Buffer.from(candidate, \"utf8\");\n for (let index = 0; index < keys.length; index += 1) {\n const key = keys[index];\n if (encoded.length === key.length && timingSafeEqual(encoded, key)) return index;\n }\n return undefined;\n}\n\nexport function validateRequestApiKey(req: http.IncomingMessage, policy: ApiKeyPolicy): AuthResult {\n if (!policy.enabled) return { ok: true };\n const extracted = rawCandidates(req);\n if (!extracted.candidates) return { ok: false, reason: extracted.reason };\n let resolved: number | undefined;\n for (const candidate of extracted.candidates) {\n const index = matchingIndex(candidate, policy.keys);\n if (index === undefined) return { ok: false, reason: \"invalid\" };\n if (resolved !== undefined && resolved !== index) return { ok: false, reason: \"conflict\" };\n resolved = index;\n }\n return { ok: true };\n}\n\n/** @internal Opaque egress marker; it deliberately carries no key material. */\nexport function markAuthenticatedRequest(req: http.IncomingMessage, policy: ApiKeyPolicy): void {\n if (policy.enabled) authenticatedRequests.add(req);\n}\n\n/** @internal Used by egress paths to select safe credential handling. */\nexport function isAuthenticatedRequest(req: http.IncomingMessage): boolean {\n return authenticatedRequests.has(req);\n}\n\nexport function writeApiKeyHttpRejection(\n res: http.ServerResponse,\n setCorsHeaders: (res: http.ServerResponse) => void,\n pathname = \"\",\n authErrors: \"legacy\" | \"native\" = \"legacy\",\n): void {\n setCorsHeaders(res);\n // The 1.45 body stays the default on every route; native bodies are opt-in.\n const dialect = authErrors === \"native\" ? authErrorDialectForPath(pathname) : \"openai\";\n const rendered = renderError(dialect, \"authentication\", { message: \"Invalid API key\" });\n res.writeHead(401, {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(rendered.body),\n \"WWW-Authenticate\": 'Bearer realm=\"aimock\"',\n ...(dialect === \"messages\"\n ? { \"request-id\": `req_${randomBytes(12).toString(\"base64url\")}` }\n : {}),\n });\n res.end(rendered.body);\n}\n\nexport function writeApiKeyUpgradeRejection(socket: net.Socket): void {\n socket.write(\n [\n \"HTTP/1.1 401 Unauthorized\",\n \"Content-Type: application/json\",\n `Content-Length: ${Buffer.byteLength(API_KEY_ERROR_BODY)}`,\n 'WWW-Authenticate: Bearer realm=\"aimock\"',\n \"Connection: close\",\n \"\",\n API_KEY_ERROR_BODY,\n ].join(\"\\r\\n\"),\n );\n socket.destroy();\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-key-auth.d.cts","names":[],"sources":["../src/api-key-auth.ts"],"sourcesContent":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"api-key-auth.d.cts","names":[],"sources":["../src/api-key-auth.ts"],"sourcesContent":[],"mappings":";;;;;AA6B0B,UAPT,YAAA,CAOS;WACP,OAAA,EAAA,OAAA;EAAY,SAAA,IAAA,EAAA,SANL,MAMK,EAAA;;;UAFd,mBAAA;0BACS;mBACP"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-key-auth.d.ts","names":[],"sources":["../src/api-key-auth.ts"],"sourcesContent":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"api-key-auth.d.ts","names":[],"sources":["../src/api-key-auth.ts"],"sourcesContent":[],"mappings":";;;;;AA6B0B,UAPT,YAAA,CAOS;WACP,OAAA,EAAA,OAAA;EAAY,SAAA,IAAA,EAAA,SANL,MAMK,EAAA;;;UAFd,mBAAA;0BACS;mBACP"}
|
package/dist/api-key-auth.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { authErrorDialectForPath } from "./response/profiles.js";
|
|
2
|
+
import { renderError } from "./response/errors.js";
|
|
3
|
+
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
4
|
|
|
3
5
|
//#region src/api-key-auth.ts
|
|
4
6
|
const INBOUND_API_KEY_HEADERS = [
|
|
@@ -9,11 +11,7 @@ const INBOUND_API_KEY_HEADERS = [
|
|
|
9
11
|
"xi-api-key"
|
|
10
12
|
];
|
|
11
13
|
const RECOGNIZED_HEADERS = new Set(INBOUND_API_KEY_HEADERS);
|
|
12
|
-
const API_KEY_ERROR_BODY =
|
|
13
|
-
message: "Invalid API key",
|
|
14
|
-
type: "authentication_error",
|
|
15
|
-
code: "invalid_api_key"
|
|
16
|
-
} });
|
|
14
|
+
const API_KEY_ERROR_BODY = renderError("openai", "legacy-auth", { message: "Invalid API key" }).body;
|
|
17
15
|
const DISABLED_POLICY = {
|
|
18
16
|
enabled: false,
|
|
19
17
|
keys: []
|
|
@@ -131,14 +129,17 @@ function markAuthenticatedRequest(req, policy) {
|
|
|
131
129
|
function isAuthenticatedRequest(req) {
|
|
132
130
|
return authenticatedRequests.has(req);
|
|
133
131
|
}
|
|
134
|
-
function writeApiKeyHttpRejection(res, setCorsHeaders) {
|
|
132
|
+
function writeApiKeyHttpRejection(res, setCorsHeaders, pathname = "", authErrors = "legacy") {
|
|
135
133
|
setCorsHeaders(res);
|
|
134
|
+
const dialect = authErrors === "native" ? authErrorDialectForPath(pathname) : "openai";
|
|
135
|
+
const rendered = renderError(dialect, "authentication", { message: "Invalid API key" });
|
|
136
136
|
res.writeHead(401, {
|
|
137
137
|
"Content-Type": "application/json",
|
|
138
|
-
"Content-Length": Buffer.byteLength(
|
|
139
|
-
"WWW-Authenticate": "Bearer realm=\"aimock\""
|
|
138
|
+
"Content-Length": Buffer.byteLength(rendered.body),
|
|
139
|
+
"WWW-Authenticate": "Bearer realm=\"aimock\"",
|
|
140
|
+
...dialect === "messages" ? { "request-id": `req_${randomBytes(12).toString("base64url")}` } : {}
|
|
140
141
|
});
|
|
141
|
-
res.end(
|
|
142
|
+
res.end(rendered.body);
|
|
142
143
|
}
|
|
143
144
|
function writeApiKeyUpgradeRejection(socket) {
|
|
144
145
|
socket.write([
|
package/dist/api-key-auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-key-auth.js","names":[],"sources":["../src/api-key-auth.ts"],"sourcesContent":["import { timingSafeEqual } from \"node:crypto\";\nimport type * as http from \"node:http\";\nimport type * as net from \"node:net\";\nimport type { ApiKeyAuthConfig } from \"./types.js\";\n\nexport const INBOUND_API_KEY_HEADERS = [\n \"authorization\",\n \"x-api-key\",\n \"x-goog-api-key\",\n \"api-key\",\n \"xi-api-key\",\n] as const;\n\nconst RECOGNIZED_HEADERS = new Set<string>(INBOUND_API_KEY_HEADERS);\n\nexport const API_KEY_ERROR_BODY = JSON.stringify({\n error: {\n message: \"Invalid API key\",\n type: \"authentication_error\",\n code: \"invalid_api_key\",\n },\n});\n\n/** @internal */\nexport interface ApiKeyPolicy {\n readonly enabled: boolean;\n readonly keys: readonly Buffer[];\n}\n\n/** @internal */\nexport interface ResolvedInboundAuth {\n readonly publicConfig?: ApiKeyAuthConfig;\n readonly policy: ApiKeyPolicy;\n}\n\nexport interface AuthResult {\n readonly ok: boolean;\n readonly reason?: \"missing\" | \"invalid\" | \"conflict\";\n}\n\nconst DISABLED_POLICY: ApiKeyPolicy = { enabled: false, keys: [] };\nconst authenticatedRequests = new WeakSet<http.IncomingMessage>();\nconst OWS_EDGES = /^[ \\t]+|[ \\t]+$/g;\n\nfunction fail(label: string, suffix: string): never {\n throw new Error(`${label}${suffix}`);\n}\n\nfunction hasControl(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const code = value.charCodeAt(index);\n if (code <= 0x1f || code === 0x7f) return true;\n }\n return false;\n}\n\nfunction normalizeKey(value: unknown, label: string): string {\n if (typeof value !== \"string\") fail(label, \" must be a string\");\n const normalized = value.replace(OWS_EDGES, \"\");\n if (normalized.length === 0) fail(label, \" must not be empty\");\n if (hasControl(normalized)) fail(label, \" must not contain control characters\");\n return normalized;\n}\n\n/** Parse a public auth source once and construct its private server policy. */\nexport function resolveInboundAuth(source: { value: unknown; label: string }): ResolvedInboundAuth {\n if (source.value === undefined) return { policy: DISABLED_POLICY };\n if (source.value === null || typeof source.value !== \"object\" || Array.isArray(source.value)) {\n fail(source.label, \" must be an object\");\n }\n const apiKeys = (source.value as { apiKeys?: unknown }).apiKeys;\n if (!Array.isArray(apiKeys)) fail(`${source.label}.apiKeys`, \" must be an array\");\n if (apiKeys.length === 0) fail(`${source.label}.apiKeys`, \" must not be empty\");\n const keys = apiKeys.map((value, index) =>\n normalizeKey(value, `${source.label}.apiKeys[${index}]`),\n );\n const seen = new Set<string>();\n for (const key of keys) {\n if (seen.has(key)) fail(source.label, \" must not contain duplicate API keys\");\n seen.add(key);\n }\n return {\n publicConfig: { apiKeys: keys },\n policy: { enabled: true, keys: keys.map((key) => Buffer.from(key, \"utf8\")) },\n };\n}\n\n/** Select configuration without parsing it, so an environment override bypasses malformed JSON auth. */\nexport function selectInboundAuthSource(\n configAuth: unknown,\n env: NodeJS.ProcessEnv = process.env,\n): { value: unknown; label: string } {\n if (Object.prototype.hasOwnProperty.call(env, \"AIMOCK_API_KEYS\")) {\n return { value: { apiKeys: (env.AIMOCK_API_KEYS ?? \"\").split(\",\") }, label: \"AIMOCK_API_KEYS\" };\n }\n return { value: configAuth, label: \"aimock.json.auth\" };\n}\n\nexport function isRecognizedApiKeyHeader(name: string): boolean {\n return RECOGNIZED_HEADERS.has(name.toLowerCase());\n}\n\nfunction rawCandidates(req: http.IncomingMessage): {\n candidates?: string[];\n reason?: \"missing\" | \"invalid\";\n} {\n const candidates: string[] = [];\n for (let index = 0; index < req.rawHeaders.length; index += 2) {\n const name = req.rawHeaders[index];\n const value = req.rawHeaders[index + 1] ?? \"\";\n const lower = name.toLowerCase();\n if (!isRecognizedApiKeyHeader(lower)) continue;\n if (hasControl(value)) return { reason: \"invalid\" };\n if (lower === \"authorization\") {\n const match = /^(?:Bearer|Key)[ \\t]+(.+)$/i.exec(value.replace(OWS_EDGES, \"\"));\n if (!match) return { reason: \"invalid\" };\n const candidate = match[1].replace(OWS_EDGES, \"\");\n if (candidate.length === 0 || hasControl(candidate)) return { reason: \"invalid\" };\n candidates.push(candidate);\n } else {\n const candidate = value.replace(OWS_EDGES, \"\");\n if (candidate.length === 0) return { reason: \"invalid\" };\n candidates.push(candidate);\n }\n }\n return candidates.length > 0 ? { candidates } : { reason: \"missing\" };\n}\n\nfunction matchingIndex(candidate: string, keys: readonly Buffer[]): number | undefined {\n const encoded = Buffer.from(candidate, \"utf8\");\n for (let index = 0; index < keys.length; index += 1) {\n const key = keys[index];\n if (encoded.length === key.length && timingSafeEqual(encoded, key)) return index;\n }\n return undefined;\n}\n\nexport function validateRequestApiKey(req: http.IncomingMessage, policy: ApiKeyPolicy): AuthResult {\n if (!policy.enabled) return { ok: true };\n const extracted = rawCandidates(req);\n if (!extracted.candidates) return { ok: false, reason: extracted.reason };\n let resolved: number | undefined;\n for (const candidate of extracted.candidates) {\n const index = matchingIndex(candidate, policy.keys);\n if (index === undefined) return { ok: false, reason: \"invalid\" };\n if (resolved !== undefined && resolved !== index) return { ok: false, reason: \"conflict\" };\n resolved = index;\n }\n return { ok: true };\n}\n\n/** @internal Opaque egress marker; it deliberately carries no key material. */\nexport function markAuthenticatedRequest(req: http.IncomingMessage, policy: ApiKeyPolicy): void {\n if (policy.enabled) authenticatedRequests.add(req);\n}\n\n/** @internal Used by egress paths to select safe credential handling. */\nexport function isAuthenticatedRequest(req: http.IncomingMessage): boolean {\n return authenticatedRequests.has(req);\n}\n\nexport function writeApiKeyHttpRejection(\n res: http.ServerResponse,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): void {\n setCorsHeaders(res);\n res.writeHead(401, {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(API_KEY_ERROR_BODY),\n \"WWW-Authenticate\": 'Bearer realm=\"aimock\"',\n });\n res.end(API_KEY_ERROR_BODY);\n}\n\nexport function writeApiKeyUpgradeRejection(socket: net.Socket): void {\n socket.write(\n [\n \"HTTP/1.1 401 Unauthorized\",\n \"Content-Type: application/json\",\n `Content-Length: ${Buffer.byteLength(API_KEY_ERROR_BODY)}`,\n 'WWW-Authenticate: Bearer realm=\"aimock\"',\n \"Connection: close\",\n \"\",\n API_KEY_ERROR_BODY,\n ].join(\"\\r\\n\"),\n );\n socket.destroy();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api-key-auth.js","names":[],"sources":["../src/api-key-auth.ts"],"sourcesContent":["import { randomBytes, timingSafeEqual } from \"node:crypto\";\nimport type * as http from \"node:http\";\nimport type * as net from \"node:net\";\nimport type { ApiKeyAuthConfig } from \"./types.js\";\nimport { renderError } from \"./response/errors.js\";\nimport { authErrorDialectForPath } from \"./response/profiles.js\";\n\nexport const INBOUND_API_KEY_HEADERS = [\n \"authorization\",\n \"x-api-key\",\n \"x-goog-api-key\",\n \"api-key\",\n \"xi-api-key\",\n] as const;\n\nconst RECOGNIZED_HEADERS = new Set<string>(INBOUND_API_KEY_HEADERS);\n\nexport const API_KEY_ERROR_BODY = renderError(\"openai\", \"legacy-auth\", {\n message: \"Invalid API key\",\n}).body;\n\n/** @internal */\nexport interface ApiKeyPolicy {\n readonly enabled: boolean;\n readonly keys: readonly Buffer[];\n}\n\n/** @internal */\nexport interface ResolvedInboundAuth {\n readonly publicConfig?: ApiKeyAuthConfig;\n readonly policy: ApiKeyPolicy;\n}\n\nexport interface AuthResult {\n readonly ok: boolean;\n readonly reason?: \"missing\" | \"invalid\" | \"conflict\";\n}\n\nconst DISABLED_POLICY: ApiKeyPolicy = { enabled: false, keys: [] };\nconst authenticatedRequests = new WeakSet<http.IncomingMessage>();\nconst OWS_EDGES = /^[ \\t]+|[ \\t]+$/g;\n\nfunction fail(label: string, suffix: string): never {\n throw new Error(`${label}${suffix}`);\n}\n\nfunction hasControl(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const code = value.charCodeAt(index);\n if (code <= 0x1f || code === 0x7f) return true;\n }\n return false;\n}\n\nfunction normalizeKey(value: unknown, label: string): string {\n if (typeof value !== \"string\") fail(label, \" must be a string\");\n const normalized = value.replace(OWS_EDGES, \"\");\n if (normalized.length === 0) fail(label, \" must not be empty\");\n if (hasControl(normalized)) fail(label, \" must not contain control characters\");\n return normalized;\n}\n\n/** Parse a public auth source once and construct its private server policy. */\nexport function resolveInboundAuth(source: { value: unknown; label: string }): ResolvedInboundAuth {\n if (source.value === undefined) return { policy: DISABLED_POLICY };\n if (source.value === null || typeof source.value !== \"object\" || Array.isArray(source.value)) {\n fail(source.label, \" must be an object\");\n }\n const apiKeys = (source.value as { apiKeys?: unknown }).apiKeys;\n if (!Array.isArray(apiKeys)) fail(`${source.label}.apiKeys`, \" must be an array\");\n if (apiKeys.length === 0) fail(`${source.label}.apiKeys`, \" must not be empty\");\n const keys = apiKeys.map((value, index) =>\n normalizeKey(value, `${source.label}.apiKeys[${index}]`),\n );\n const seen = new Set<string>();\n for (const key of keys) {\n if (seen.has(key)) fail(source.label, \" must not contain duplicate API keys\");\n seen.add(key);\n }\n return {\n publicConfig: { apiKeys: keys },\n policy: { enabled: true, keys: keys.map((key) => Buffer.from(key, \"utf8\")) },\n };\n}\n\n/** Select configuration without parsing it, so an environment override bypasses malformed JSON auth. */\nexport function selectInboundAuthSource(\n configAuth: unknown,\n env: NodeJS.ProcessEnv = process.env,\n): { value: unknown; label: string } {\n if (Object.prototype.hasOwnProperty.call(env, \"AIMOCK_API_KEYS\")) {\n return { value: { apiKeys: (env.AIMOCK_API_KEYS ?? \"\").split(\",\") }, label: \"AIMOCK_API_KEYS\" };\n }\n return { value: configAuth, label: \"aimock.json.auth\" };\n}\n\nexport function isRecognizedApiKeyHeader(name: string): boolean {\n return RECOGNIZED_HEADERS.has(name.toLowerCase());\n}\n\nfunction rawCandidates(req: http.IncomingMessage): {\n candidates?: string[];\n reason?: \"missing\" | \"invalid\";\n} {\n const candidates: string[] = [];\n for (let index = 0; index < req.rawHeaders.length; index += 2) {\n const name = req.rawHeaders[index];\n const value = req.rawHeaders[index + 1] ?? \"\";\n const lower = name.toLowerCase();\n if (!isRecognizedApiKeyHeader(lower)) continue;\n if (hasControl(value)) return { reason: \"invalid\" };\n if (lower === \"authorization\") {\n const match = /^(?:Bearer|Key)[ \\t]+(.+)$/i.exec(value.replace(OWS_EDGES, \"\"));\n if (!match) return { reason: \"invalid\" };\n const candidate = match[1].replace(OWS_EDGES, \"\");\n if (candidate.length === 0 || hasControl(candidate)) return { reason: \"invalid\" };\n candidates.push(candidate);\n } else {\n const candidate = value.replace(OWS_EDGES, \"\");\n if (candidate.length === 0) return { reason: \"invalid\" };\n candidates.push(candidate);\n }\n }\n return candidates.length > 0 ? { candidates } : { reason: \"missing\" };\n}\n\nfunction matchingIndex(candidate: string, keys: readonly Buffer[]): number | undefined {\n const encoded = Buffer.from(candidate, \"utf8\");\n for (let index = 0; index < keys.length; index += 1) {\n const key = keys[index];\n if (encoded.length === key.length && timingSafeEqual(encoded, key)) return index;\n }\n return undefined;\n}\n\nexport function validateRequestApiKey(req: http.IncomingMessage, policy: ApiKeyPolicy): AuthResult {\n if (!policy.enabled) return { ok: true };\n const extracted = rawCandidates(req);\n if (!extracted.candidates) return { ok: false, reason: extracted.reason };\n let resolved: number | undefined;\n for (const candidate of extracted.candidates) {\n const index = matchingIndex(candidate, policy.keys);\n if (index === undefined) return { ok: false, reason: \"invalid\" };\n if (resolved !== undefined && resolved !== index) return { ok: false, reason: \"conflict\" };\n resolved = index;\n }\n return { ok: true };\n}\n\n/** @internal Opaque egress marker; it deliberately carries no key material. */\nexport function markAuthenticatedRequest(req: http.IncomingMessage, policy: ApiKeyPolicy): void {\n if (policy.enabled) authenticatedRequests.add(req);\n}\n\n/** @internal Used by egress paths to select safe credential handling. */\nexport function isAuthenticatedRequest(req: http.IncomingMessage): boolean {\n return authenticatedRequests.has(req);\n}\n\nexport function writeApiKeyHttpRejection(\n res: http.ServerResponse,\n setCorsHeaders: (res: http.ServerResponse) => void,\n pathname = \"\",\n authErrors: \"legacy\" | \"native\" = \"legacy\",\n): void {\n setCorsHeaders(res);\n // The 1.45 body stays the default on every route; native bodies are opt-in.\n const dialect = authErrors === \"native\" ? authErrorDialectForPath(pathname) : \"openai\";\n const rendered = renderError(dialect, \"authentication\", { message: \"Invalid API key\" });\n res.writeHead(401, {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(rendered.body),\n \"WWW-Authenticate\": 'Bearer realm=\"aimock\"',\n ...(dialect === \"messages\"\n ? { \"request-id\": `req_${randomBytes(12).toString(\"base64url\")}` }\n : {}),\n });\n res.end(rendered.body);\n}\n\nexport function writeApiKeyUpgradeRejection(socket: net.Socket): void {\n socket.write(\n [\n \"HTTP/1.1 401 Unauthorized\",\n \"Content-Type: application/json\",\n `Content-Length: ${Buffer.byteLength(API_KEY_ERROR_BODY)}`,\n 'WWW-Authenticate: Bearer realm=\"aimock\"',\n \"Connection: close\",\n \"\",\n API_KEY_ERROR_BODY,\n ].join(\"\\r\\n\"),\n );\n socket.destroy();\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
|
-
const
|
|
2
|
+
const require_frame_loop = require('./response/frame-loop.cjs');
|
|
3
3
|
let node_zlib = require("node:zlib");
|
|
4
4
|
|
|
5
5
|
//#region src/aws-event-stream.ts
|
|
@@ -87,32 +87,29 @@ function encodeEventStreamMessage(eventType, jsonPayload) {
|
|
|
87
87
|
*/
|
|
88
88
|
async function writeEventStream(res, events, options) {
|
|
89
89
|
const opts = options ?? {};
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
chunkIndex++;
|
|
109
|
-
}
|
|
110
|
-
if (!res.writableEnded) res.end();
|
|
111
|
-
return true;
|
|
90
|
+
return require_frame_loop.runFrameLoop(require_frame_loop.httpSink(res), events, eventStreamCodec(), {
|
|
91
|
+
latency: opts.latency,
|
|
92
|
+
streamingProfile: opts.streamingProfile,
|
|
93
|
+
recordedTimings: opts.recordedTimings,
|
|
94
|
+
replaySpeed: opts.replaySpeed
|
|
95
|
+
}, {
|
|
96
|
+
signal: events.length > 0 ? opts.signal : void 0,
|
|
97
|
+
onChunkSent: opts.onChunkSent
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function eventStreamCodec(encode) {
|
|
101
|
+
return {
|
|
102
|
+
headers: {
|
|
103
|
+
"Content-Type": "application/vnd.amazon.eventstream",
|
|
104
|
+
"Transfer-Encoding": "chunked"
|
|
105
|
+
},
|
|
106
|
+
encode: encode ?? ((event) => encodeEventStreamMessage(event.eventType, event.payload))
|
|
107
|
+
};
|
|
112
108
|
}
|
|
113
109
|
|
|
114
110
|
//#endregion
|
|
115
111
|
exports.encodeEventStreamFrame = encodeEventStreamFrame;
|
|
116
112
|
exports.encodeEventStreamMessage = encodeEventStreamMessage;
|
|
113
|
+
exports.eventStreamCodec = eventStreamCodec;
|
|
117
114
|
exports.writeEventStream = writeEventStream;
|
|
118
115
|
//# sourceMappingURL=aws-event-stream.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aws-event-stream.cjs","names":["
|
|
1
|
+
{"version":3,"file":"aws-event-stream.cjs","names":["runFrameLoop","httpSink"],"sources":["../src/aws-event-stream.ts"],"sourcesContent":["/**\n * AWS Event Stream binary frame encoder.\n *\n * Implements the AWS binary event stream framing protocol used by Bedrock's\n * streaming (invoke-with-response-stream) endpoint. Each frame carries a set of\n * string headers and a raw-bytes payload, wrapped in a prelude with CRC32\n * checksums for integrity.\n *\n * Binary frame layout:\n * [total_length: 4B uint32-BE]\n * [headers_length: 4B uint32-BE]\n * [prelude_crc32: 4B CRC32 of first 8 bytes]\n * [headers: variable]\n * [payload: variable, raw JSON bytes]\n * [message_crc32: 4B CRC32 of entire frame minus last 4 bytes]\n */\n\nimport { crc32 } from \"node:zlib\";\nimport type * as http from \"node:http\";\nimport type { StreamingProfile, RecordedTimings } from \"./types.js\";\nimport { httpSink, runFrameLoop, type FrameCodec } from \"./response/frame-loop.js\";\n\n// ─── Header encoding ────────────────────────────────────────────────────────\n\nfunction encodeHeaders(headers: Record<string, string>): Buffer {\n const parts: Buffer[] = [];\n for (const [name, value] of Object.entries(headers)) {\n const nameBytes = Buffer.from(name, \"utf8\");\n const valueBytes = Buffer.from(value, \"utf8\");\n\n // name_length (1 byte) + name + type (1 byte, 7 = STRING) +\n // value_length (2 bytes BE) + value\n const header = Buffer.alloc(1 + nameBytes.length + 1 + 2 + valueBytes.length);\n let offset = 0;\n header.writeUInt8(nameBytes.length, offset);\n offset += 1;\n nameBytes.copy(header, offset);\n offset += nameBytes.length;\n header.writeUInt8(7, offset); // STRING type\n offset += 1;\n header.writeUInt16BE(valueBytes.length, offset);\n offset += 2;\n valueBytes.copy(header, offset);\n\n parts.push(header);\n }\n return Buffer.concat(parts);\n}\n\n// ─── Frame encoding ─────────────────────────────────────────────────────────\n\n/**\n * Encode a single AWS Event Stream binary frame with the given headers and\n * payload buffer.\n */\nexport function encodeEventStreamFrame(headers: Record<string, string>, payload: Buffer): Buffer {\n const headersBuffer = encodeHeaders(headers);\n const headersLength = headersBuffer.length;\n\n // prelude (8) + prelude_crc (4) + headers + payload + message_crc (4)\n const totalLength = 4 + 4 + 4 + headersLength + payload.length + 4;\n\n const frame = Buffer.alloc(totalLength);\n let offset = 0;\n\n // Prelude\n frame.writeUInt32BE(totalLength, offset);\n offset += 4;\n frame.writeUInt32BE(headersLength, offset);\n offset += 4;\n\n // Prelude CRC32 (covers first 8 bytes)\n const preludeCrc = crc32(frame.subarray(0, 8));\n frame.writeUInt32BE(preludeCrc >>> 0, offset);\n offset += 4;\n\n // Headers\n headersBuffer.copy(frame, offset);\n offset += headersLength;\n\n // Payload\n payload.copy(frame, offset);\n offset += payload.length;\n\n // Message CRC32 (covers entire frame minus last 4 bytes)\n const messageCrc = crc32(frame.subarray(0, totalLength - 4));\n frame.writeUInt32BE(messageCrc >>> 0, offset);\n\n return frame;\n}\n\n// ─── Convenience wrappers ───────────────────────────────────────────────────\n\n/**\n * Encode an event-stream message with standard AWS headers for a JSON event.\n *\n * Sets `:content-type` = `application/json`, `:event-type` = eventType,\n * `:message-type` = `event`.\n */\nexport function encodeEventStreamMessage(eventType: string, jsonPayload: object): Buffer {\n const headers: Record<string, string> = {\n \":content-type\": \"application/json\",\n \":event-type\": eventType,\n \":message-type\": \"event\",\n };\n const payload = Buffer.from(JSON.stringify(jsonPayload), \"utf8\");\n return encodeEventStreamFrame(headers, payload);\n}\n\n/**\n * Write a sequence of event-stream frames to an HTTP response with optional\n * timing control. Mirrors the writeSSEStream pattern from sse-writer.ts.\n *\n * Returns `true` when all events are written (including when the response\n * was already ended before writing began), or `false` if interrupted by\n * the provided abort signal.\n */\nexport async function writeEventStream(\n res: http.ServerResponse,\n events: Array<{ eventType: string; payload: object }>,\n options?: {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n },\n): Promise<boolean> {\n const opts = options ?? {};\n // Base contract (public API): with no frames, an aborted signal does not\n // stop the writer; it still finishes the stream (any trailer, then end())\n // and returns true. The frame loop returns false there, so pass it no\n // signal when there are no frames (it reads the signal only per frame and\n // after the loop).\n return runFrameLoop(\n httpSink(res),\n events,\n eventStreamCodec(),\n {\n latency: opts.latency,\n streamingProfile: opts.streamingProfile,\n recordedTimings: opts.recordedTimings,\n replaySpeed: opts.replaySpeed,\n },\n { signal: events.length > 0 ? opts.signal : undefined, onChunkSent: opts.onChunkSent },\n );\n}\n\n// Internal (not part of the public API): the AWS binary event-stream codec.\n// Each event becomes one complete frame, encoded by encodeEventStreamMessage\n// unless an `encode` argument replaces it. There is no prelude or trailer.\n// These are line comments, not JSDoc, so the bundled .d.ts does not attach\n// them to a public declaration.\nexport function eventStreamCodec<F extends { eventType: string; payload: object }>(\n encode?: (event: F) => Buffer,\n): FrameCodec<F> {\n return {\n headers: {\n \"Content-Type\": \"application/vnd.amazon.eventstream\",\n \"Transfer-Encoding\": \"chunked\",\n },\n encode: encode ?? ((event) => encodeEventStreamMessage(event.eventType, event.payload)),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAwBA,SAAS,cAAc,SAAyC;CAC9D,MAAM,QAAkB,EAAE;AAC1B,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,QAAQ,EAAE;EACnD,MAAM,YAAY,OAAO,KAAK,MAAM,OAAO;EAC3C,MAAM,aAAa,OAAO,KAAK,OAAO,OAAO;EAI7C,MAAM,SAAS,OAAO,MAAM,IAAI,UAAU,SAAS,IAAI,IAAI,WAAW,OAAO;EAC7E,IAAI,SAAS;AACb,SAAO,WAAW,UAAU,QAAQ,OAAO;AAC3C,YAAU;AACV,YAAU,KAAK,QAAQ,OAAO;AAC9B,YAAU,UAAU;AACpB,SAAO,WAAW,GAAG,OAAO;AAC5B,YAAU;AACV,SAAO,cAAc,WAAW,QAAQ,OAAO;AAC/C,YAAU;AACV,aAAW,KAAK,QAAQ,OAAO;AAE/B,QAAM,KAAK,OAAO;;AAEpB,QAAO,OAAO,OAAO,MAAM;;;;;;AAS7B,SAAgB,uBAAuB,SAAiC,SAAyB;CAC/F,MAAM,gBAAgB,cAAc,QAAQ;CAC5C,MAAM,gBAAgB,cAAc;CAGpC,MAAM,cAAc,KAAY,gBAAgB,QAAQ,SAAS;CAEjE,MAAM,QAAQ,OAAO,MAAM,YAAY;CACvC,IAAI,SAAS;AAGb,OAAM,cAAc,aAAa,OAAO;AACxC,WAAU;AACV,OAAM,cAAc,eAAe,OAAO;AAC1C,WAAU;CAGV,MAAM,kCAAmB,MAAM,SAAS,GAAG,EAAE,CAAC;AAC9C,OAAM,cAAc,eAAe,GAAG,OAAO;AAC7C,WAAU;AAGV,eAAc,KAAK,OAAO,OAAO;AACjC,WAAU;AAGV,SAAQ,KAAK,OAAO,OAAO;AAC3B,WAAU,QAAQ;CAGlB,MAAM,kCAAmB,MAAM,SAAS,GAAG,cAAc,EAAE,CAAC;AAC5D,OAAM,cAAc,eAAe,GAAG,OAAO;AAE7C,QAAO;;;;;;;;AAWT,SAAgB,yBAAyB,WAAmB,aAA6B;AAOvF,QAAO,uBANiC;EACtC,iBAAiB;EACjB,eAAe;EACf,iBAAiB;EAClB,EACe,OAAO,KAAK,KAAK,UAAU,YAAY,EAAE,OAAO,CACjB;;;;;;;;;;AAWjD,eAAsB,iBACpB,KACA,QACA,SAQkB;CAClB,MAAM,OAAO,WAAW,EAAE;AAM1B,QAAOA,gCACLC,4BAAS,IAAI,EACb,QACA,kBAAkB,EAClB;EACE,SAAS,KAAK;EACd,kBAAkB,KAAK;EACvB,iBAAiB,KAAK;EACtB,aAAa,KAAK;EACnB,EACD;EAAE,QAAQ,OAAO,SAAS,IAAI,KAAK,SAAS;EAAW,aAAa,KAAK;EAAa,CACvF;;AAQH,SAAgB,iBACd,QACe;AACf,QAAO;EACL,SAAS;GACP,gBAAgB;GAChB,qBAAqB;GACtB;EACD,QAAQ,YAAY,UAAU,yBAAyB,MAAM,WAAW,MAAM,QAAQ;EACvF"}
|
|
@@ -34,7 +34,6 @@ declare function writeEventStream(res: http$1.ServerResponse, events: Array<{
|
|
|
34
34
|
signal?: AbortSignal;
|
|
35
35
|
onChunkSent?: () => void;
|
|
36
36
|
}): Promise<boolean>;
|
|
37
|
-
//# sourceMappingURL=aws-event-stream.d.ts.map
|
|
38
37
|
//#endregion
|
|
39
38
|
export { encodeEventStreamFrame, encodeEventStreamMessage, writeEventStream };
|
|
40
39
|
//# sourceMappingURL=aws-event-stream.d.cts.map
|
|
@@ -34,7 +34,6 @@ declare function writeEventStream(res: http$1.ServerResponse, events: Array<{
|
|
|
34
34
|
signal?: AbortSignal;
|
|
35
35
|
onChunkSent?: () => void;
|
|
36
36
|
}): Promise<boolean>;
|
|
37
|
-
//# sourceMappingURL=aws-event-stream.d.ts.map
|
|
38
37
|
//#endregion
|
|
39
38
|
export { encodeEventStreamFrame, encodeEventStreamMessage, writeEventStream };
|
|
40
39
|
//# sourceMappingURL=aws-event-stream.d.ts.map
|
package/dist/aws-event-stream.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { httpSink, runFrameLoop } from "./response/frame-loop.js";
|
|
2
2
|
import { crc32 } from "node:zlib";
|
|
3
3
|
|
|
4
4
|
//#region src/aws-event-stream.ts
|
|
@@ -86,30 +86,26 @@ function encodeEventStreamMessage(eventType, jsonPayload) {
|
|
|
86
86
|
*/
|
|
87
87
|
async function writeEventStream(res, events, options) {
|
|
88
88
|
const opts = options ?? {};
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
chunkIndex++;
|
|
108
|
-
}
|
|
109
|
-
if (!res.writableEnded) res.end();
|
|
110
|
-
return true;
|
|
89
|
+
return runFrameLoop(httpSink(res), events, eventStreamCodec(), {
|
|
90
|
+
latency: opts.latency,
|
|
91
|
+
streamingProfile: opts.streamingProfile,
|
|
92
|
+
recordedTimings: opts.recordedTimings,
|
|
93
|
+
replaySpeed: opts.replaySpeed
|
|
94
|
+
}, {
|
|
95
|
+
signal: events.length > 0 ? opts.signal : void 0,
|
|
96
|
+
onChunkSent: opts.onChunkSent
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function eventStreamCodec(encode) {
|
|
100
|
+
return {
|
|
101
|
+
headers: {
|
|
102
|
+
"Content-Type": "application/vnd.amazon.eventstream",
|
|
103
|
+
"Transfer-Encoding": "chunked"
|
|
104
|
+
},
|
|
105
|
+
encode: encode ?? ((event) => encodeEventStreamMessage(event.eventType, event.payload))
|
|
106
|
+
};
|
|
111
107
|
}
|
|
112
108
|
|
|
113
109
|
//#endregion
|
|
114
|
-
export { encodeEventStreamFrame, encodeEventStreamMessage, writeEventStream };
|
|
110
|
+
export { encodeEventStreamFrame, encodeEventStreamMessage, eventStreamCodec, writeEventStream };
|
|
115
111
|
//# sourceMappingURL=aws-event-stream.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aws-event-stream.js","names":[],"sources":["../src/aws-event-stream.ts"],"sourcesContent":["/**\n * AWS Event Stream binary frame encoder.\n *\n * Implements the AWS binary event stream framing protocol used by Bedrock's\n * streaming (invoke-with-response-stream) endpoint. Each frame carries a set of\n * string headers and a raw-bytes payload, wrapped in a prelude with CRC32\n * checksums for integrity.\n *\n * Binary frame layout:\n * [total_length: 4B uint32-BE]\n * [headers_length: 4B uint32-BE]\n * [prelude_crc32: 4B CRC32 of first 8 bytes]\n * [headers: variable]\n * [payload: variable, raw JSON bytes]\n * [message_crc32: 4B CRC32 of entire frame minus last 4 bytes]\n */\n\nimport { crc32 } from \"node:zlib\";\nimport type * as http from \"node:http\";\nimport type { StreamingProfile, RecordedTimings } from \"./types.js\";\nimport {
|
|
1
|
+
{"version":3,"file":"aws-event-stream.js","names":[],"sources":["../src/aws-event-stream.ts"],"sourcesContent":["/**\n * AWS Event Stream binary frame encoder.\n *\n * Implements the AWS binary event stream framing protocol used by Bedrock's\n * streaming (invoke-with-response-stream) endpoint. Each frame carries a set of\n * string headers and a raw-bytes payload, wrapped in a prelude with CRC32\n * checksums for integrity.\n *\n * Binary frame layout:\n * [total_length: 4B uint32-BE]\n * [headers_length: 4B uint32-BE]\n * [prelude_crc32: 4B CRC32 of first 8 bytes]\n * [headers: variable]\n * [payload: variable, raw JSON bytes]\n * [message_crc32: 4B CRC32 of entire frame minus last 4 bytes]\n */\n\nimport { crc32 } from \"node:zlib\";\nimport type * as http from \"node:http\";\nimport type { StreamingProfile, RecordedTimings } from \"./types.js\";\nimport { httpSink, runFrameLoop, type FrameCodec } from \"./response/frame-loop.js\";\n\n// ─── Header encoding ────────────────────────────────────────────────────────\n\nfunction encodeHeaders(headers: Record<string, string>): Buffer {\n const parts: Buffer[] = [];\n for (const [name, value] of Object.entries(headers)) {\n const nameBytes = Buffer.from(name, \"utf8\");\n const valueBytes = Buffer.from(value, \"utf8\");\n\n // name_length (1 byte) + name + type (1 byte, 7 = STRING) +\n // value_length (2 bytes BE) + value\n const header = Buffer.alloc(1 + nameBytes.length + 1 + 2 + valueBytes.length);\n let offset = 0;\n header.writeUInt8(nameBytes.length, offset);\n offset += 1;\n nameBytes.copy(header, offset);\n offset += nameBytes.length;\n header.writeUInt8(7, offset); // STRING type\n offset += 1;\n header.writeUInt16BE(valueBytes.length, offset);\n offset += 2;\n valueBytes.copy(header, offset);\n\n parts.push(header);\n }\n return Buffer.concat(parts);\n}\n\n// ─── Frame encoding ─────────────────────────────────────────────────────────\n\n/**\n * Encode a single AWS Event Stream binary frame with the given headers and\n * payload buffer.\n */\nexport function encodeEventStreamFrame(headers: Record<string, string>, payload: Buffer): Buffer {\n const headersBuffer = encodeHeaders(headers);\n const headersLength = headersBuffer.length;\n\n // prelude (8) + prelude_crc (4) + headers + payload + message_crc (4)\n const totalLength = 4 + 4 + 4 + headersLength + payload.length + 4;\n\n const frame = Buffer.alloc(totalLength);\n let offset = 0;\n\n // Prelude\n frame.writeUInt32BE(totalLength, offset);\n offset += 4;\n frame.writeUInt32BE(headersLength, offset);\n offset += 4;\n\n // Prelude CRC32 (covers first 8 bytes)\n const preludeCrc = crc32(frame.subarray(0, 8));\n frame.writeUInt32BE(preludeCrc >>> 0, offset);\n offset += 4;\n\n // Headers\n headersBuffer.copy(frame, offset);\n offset += headersLength;\n\n // Payload\n payload.copy(frame, offset);\n offset += payload.length;\n\n // Message CRC32 (covers entire frame minus last 4 bytes)\n const messageCrc = crc32(frame.subarray(0, totalLength - 4));\n frame.writeUInt32BE(messageCrc >>> 0, offset);\n\n return frame;\n}\n\n// ─── Convenience wrappers ───────────────────────────────────────────────────\n\n/**\n * Encode an event-stream message with standard AWS headers for a JSON event.\n *\n * Sets `:content-type` = `application/json`, `:event-type` = eventType,\n * `:message-type` = `event`.\n */\nexport function encodeEventStreamMessage(eventType: string, jsonPayload: object): Buffer {\n const headers: Record<string, string> = {\n \":content-type\": \"application/json\",\n \":event-type\": eventType,\n \":message-type\": \"event\",\n };\n const payload = Buffer.from(JSON.stringify(jsonPayload), \"utf8\");\n return encodeEventStreamFrame(headers, payload);\n}\n\n/**\n * Write a sequence of event-stream frames to an HTTP response with optional\n * timing control. Mirrors the writeSSEStream pattern from sse-writer.ts.\n *\n * Returns `true` when all events are written (including when the response\n * was already ended before writing began), or `false` if interrupted by\n * the provided abort signal.\n */\nexport async function writeEventStream(\n res: http.ServerResponse,\n events: Array<{ eventType: string; payload: object }>,\n options?: {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n },\n): Promise<boolean> {\n const opts = options ?? {};\n // Base contract (public API): with no frames, an aborted signal does not\n // stop the writer; it still finishes the stream (any trailer, then end())\n // and returns true. The frame loop returns false there, so pass it no\n // signal when there are no frames (it reads the signal only per frame and\n // after the loop).\n return runFrameLoop(\n httpSink(res),\n events,\n eventStreamCodec(),\n {\n latency: opts.latency,\n streamingProfile: opts.streamingProfile,\n recordedTimings: opts.recordedTimings,\n replaySpeed: opts.replaySpeed,\n },\n { signal: events.length > 0 ? opts.signal : undefined, onChunkSent: opts.onChunkSent },\n );\n}\n\n// Internal (not part of the public API): the AWS binary event-stream codec.\n// Each event becomes one complete frame, encoded by encodeEventStreamMessage\n// unless an `encode` argument replaces it. There is no prelude or trailer.\n// These are line comments, not JSDoc, so the bundled .d.ts does not attach\n// them to a public declaration.\nexport function eventStreamCodec<F extends { eventType: string; payload: object }>(\n encode?: (event: F) => Buffer,\n): FrameCodec<F> {\n return {\n headers: {\n \"Content-Type\": \"application/vnd.amazon.eventstream\",\n \"Transfer-Encoding\": \"chunked\",\n },\n encode: encode ?? ((event) => encodeEventStreamMessage(event.eventType, event.payload)),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAwBA,SAAS,cAAc,SAAyC;CAC9D,MAAM,QAAkB,EAAE;AAC1B,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,QAAQ,EAAE;EACnD,MAAM,YAAY,OAAO,KAAK,MAAM,OAAO;EAC3C,MAAM,aAAa,OAAO,KAAK,OAAO,OAAO;EAI7C,MAAM,SAAS,OAAO,MAAM,IAAI,UAAU,SAAS,IAAI,IAAI,WAAW,OAAO;EAC7E,IAAI,SAAS;AACb,SAAO,WAAW,UAAU,QAAQ,OAAO;AAC3C,YAAU;AACV,YAAU,KAAK,QAAQ,OAAO;AAC9B,YAAU,UAAU;AACpB,SAAO,WAAW,GAAG,OAAO;AAC5B,YAAU;AACV,SAAO,cAAc,WAAW,QAAQ,OAAO;AAC/C,YAAU;AACV,aAAW,KAAK,QAAQ,OAAO;AAE/B,QAAM,KAAK,OAAO;;AAEpB,QAAO,OAAO,OAAO,MAAM;;;;;;AAS7B,SAAgB,uBAAuB,SAAiC,SAAyB;CAC/F,MAAM,gBAAgB,cAAc,QAAQ;CAC5C,MAAM,gBAAgB,cAAc;CAGpC,MAAM,cAAc,KAAY,gBAAgB,QAAQ,SAAS;CAEjE,MAAM,QAAQ,OAAO,MAAM,YAAY;CACvC,IAAI,SAAS;AAGb,OAAM,cAAc,aAAa,OAAO;AACxC,WAAU;AACV,OAAM,cAAc,eAAe,OAAO;AAC1C,WAAU;CAGV,MAAM,aAAa,MAAM,MAAM,SAAS,GAAG,EAAE,CAAC;AAC9C,OAAM,cAAc,eAAe,GAAG,OAAO;AAC7C,WAAU;AAGV,eAAc,KAAK,OAAO,OAAO;AACjC,WAAU;AAGV,SAAQ,KAAK,OAAO,OAAO;AAC3B,WAAU,QAAQ;CAGlB,MAAM,aAAa,MAAM,MAAM,SAAS,GAAG,cAAc,EAAE,CAAC;AAC5D,OAAM,cAAc,eAAe,GAAG,OAAO;AAE7C,QAAO;;;;;;;;AAWT,SAAgB,yBAAyB,WAAmB,aAA6B;AAOvF,QAAO,uBANiC;EACtC,iBAAiB;EACjB,eAAe;EACf,iBAAiB;EAClB,EACe,OAAO,KAAK,KAAK,UAAU,YAAY,EAAE,OAAO,CACjB;;;;;;;;;;AAWjD,eAAsB,iBACpB,KACA,QACA,SAQkB;CAClB,MAAM,OAAO,WAAW,EAAE;AAM1B,QAAO,aACL,SAAS,IAAI,EACb,QACA,kBAAkB,EAClB;EACE,SAAS,KAAK;EACd,kBAAkB,KAAK;EACvB,iBAAiB,KAAK;EACtB,aAAa,KAAK;EACnB,EACD;EAAE,QAAQ,OAAO,SAAS,IAAI,KAAK,SAAS;EAAW,aAAa,KAAK;EAAa,CACvF;;AAQH,SAAgB,iBACd,QACe;AACf,QAAO;EACL,SAAS;GACP,gBAAgB;GAChB,qBAAqB;GACtB;EACD,QAAQ,YAAY,UAAU,yBAAyB,MAAM,WAAW,MAAM,QAAQ;EACvF"}
|
package/dist/batches.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
|
+
const require_errors = require('./response/errors.cjs');
|
|
2
3
|
const require_helpers = require('./helpers.cjs');
|
|
3
4
|
const require_chaos = require('./chaos.cjs');
|
|
4
5
|
const require_responses = require('./responses.cjs');
|
|
@@ -62,10 +63,7 @@ function writeJson(res, status, payload, setCorsHeaders) {
|
|
|
62
63
|
res.end(JSON.stringify(payload));
|
|
63
64
|
}
|
|
64
65
|
function invalid(message) {
|
|
65
|
-
return {
|
|
66
|
-
message,
|
|
67
|
-
type: "invalid_request_error"
|
|
68
|
-
} };
|
|
66
|
+
return require_errors.buildOpenAIError("service-validation", { message });
|
|
69
67
|
}
|
|
70
68
|
/**
|
|
71
69
|
* Roll the chaos dice for one batches request. Returns true when a fault
|