@copilotkit/aimock 1.45.0 → 1.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +24 -0
- package/README.md +7 -1
- package/dist/agui-mock.cjs +25 -25
- package/dist/agui-mock.cjs.map +1 -1
- package/dist/agui-mock.d.cts.map +1 -1
- package/dist/agui-mock.d.ts.map +1 -1
- package/dist/agui-mock.js +25 -25
- package/dist/agui-mock.js.map +1 -1
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/aimock-cli.cjs +15 -1
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.d.cts.map +1 -1
- package/dist/aimock-cli.d.ts.map +1 -1
- package/dist/aimock-cli.js +16 -2
- package/dist/aimock-cli.js.map +1 -1
- package/dist/api-key-auth.cjs +10 -9
- package/dist/api-key-auth.cjs.map +1 -1
- package/dist/api-key-auth.d.cts.map +1 -1
- package/dist/api-key-auth.d.ts.map +1 -1
- package/dist/api-key-auth.js +11 -10
- package/dist/api-key-auth.js.map +1 -1
- package/dist/batches.cjs +2 -4
- package/dist/batches.cjs.map +1 -1
- package/dist/batches.d.cts.map +1 -1
- package/dist/batches.d.ts.map +1 -1
- package/dist/batches.js +2 -4
- package/dist/batches.js.map +1 -1
- package/dist/bedrock-converse.cjs +61 -80
- package/dist/bedrock-converse.cjs.map +1 -1
- package/dist/bedrock-converse.d.cts.map +1 -1
- package/dist/bedrock-converse.d.ts.map +1 -1
- package/dist/bedrock-converse.js +58 -77
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +65 -97
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.d.cts.map +1 -1
- package/dist/bedrock.d.ts.map +1 -1
- package/dist/bedrock.js +62 -94
- package/dist/bedrock.js.map +1 -1
- package/dist/byteplus-video.cjs +11 -10
- package/dist/byteplus-video.cjs.map +1 -1
- package/dist/byteplus-video.d.cts.map +1 -1
- package/dist/byteplus-video.d.ts.map +1 -1
- package/dist/byteplus-video.js +5 -4
- package/dist/byteplus-video.js.map +1 -1
- package/dist/chaos.cjs +5 -4
- package/dist/chaos.cjs.map +1 -1
- package/dist/chaos.d.cts.map +1 -1
- package/dist/chaos.d.ts.map +1 -1
- package/dist/chaos.js +5 -4
- package/dist/chaos.js.map +1 -1
- package/dist/cli.cjs +15 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +15 -0
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +49 -74
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.d.cts.map +1 -1
- package/dist/cohere.d.ts.map +1 -1
- package/dist/cohere.js +47 -72
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.cjs +29 -1
- package/dist/config-loader.cjs.map +1 -1
- package/dist/config-loader.d.cts +7 -1
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts +7 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +29 -2
- package/dist/config-loader.js.map +1 -1
- package/dist/count-tokens.cjs +146 -0
- package/dist/count-tokens.cjs.map +1 -0
- package/dist/count-tokens.js +146 -0
- package/dist/count-tokens.js.map +1 -0
- package/dist/elevenlabs-audio.cjs +36 -34
- package/dist/elevenlabs-audio.cjs.map +1 -1
- package/dist/elevenlabs-audio.d.cts.map +1 -1
- package/dist/elevenlabs-audio.d.ts.map +1 -1
- package/dist/elevenlabs-audio.js +33 -31
- package/dist/elevenlabs-audio.js.map +1 -1
- package/dist/elevenlabs-voice.cjs +38 -37
- package/dist/elevenlabs-voice.cjs.map +1 -1
- package/dist/elevenlabs-voice.d.cts.map +1 -1
- package/dist/elevenlabs-voice.d.ts.map +1 -1
- package/dist/elevenlabs-voice.js +33 -32
- package/dist/elevenlabs-voice.js.map +1 -1
- package/dist/embedding-request.cjs +16 -0
- package/dist/embedding-request.cjs.map +1 -0
- package/dist/embedding-request.js +14 -0
- package/dist/embedding-request.js.map +1 -0
- package/dist/embeddings.cjs +30 -20
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.d.cts.map +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +30 -20
- package/dist/embeddings.js.map +1 -1
- package/dist/fal-audio.cjs +45 -43
- package/dist/fal-audio.cjs.map +1 -1
- package/dist/fal-audio.d.cts.map +1 -1
- package/dist/fal-audio.d.ts.map +1 -1
- package/dist/fal-audio.js +42 -40
- package/dist/fal-audio.js.map +1 -1
- package/dist/fal.cjs +17 -15
- package/dist/fal.cjs.map +1 -1
- package/dist/fal.js +16 -14
- package/dist/fal.js.map +1 -1
- package/dist/files.cjs +2 -4
- package/dist/files.cjs.map +1 -1
- package/dist/files.d.cts.map +1 -1
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +2 -4
- package/dist/files.js.map +1 -1
- package/dist/fine-tuning.cjs +2 -4
- package/dist/fine-tuning.cjs.map +1 -1
- package/dist/fine-tuning.d.cts.map +1 -1
- package/dist/fine-tuning.d.ts.map +1 -1
- package/dist/fine-tuning.js +2 -4
- package/dist/fine-tuning.js.map +1 -1
- package/dist/fixture-loader.cjs +37 -42
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +37 -42
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-embeddings.cjs +26 -24
- package/dist/gemini-embeddings.cjs.map +1 -1
- package/dist/gemini-embeddings.js +26 -24
- package/dist/gemini-embeddings.js.map +1 -1
- package/dist/gemini-interactions.cjs +50 -35
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts +0 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts +0 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +49 -34
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +57 -67
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts.map +1 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +56 -67
- package/dist/gemini.js.map +1 -1
- package/dist/grok-video.cjs +14 -12
- package/dist/grok-video.cjs.map +1 -1
- package/dist/grok-video.d.cts.map +1 -1
- package/dist/grok-video.d.ts.map +1 -1
- package/dist/grok-video.js +8 -6
- package/dist/grok-video.js.map +1 -1
- package/dist/helpers.cjs +11 -68
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +12 -67
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +30 -28
- package/dist/images.cjs.map +1 -1
- package/dist/images.d.cts.map +1 -1
- package/dist/images.d.ts.map +1 -1
- package/dist/images.js +27 -25
- package/dist/images.js.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/journal.cjs +3 -1
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +2 -1
- package/dist/journal.js.map +1 -1
- package/dist/llmock.cjs +3 -0
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +3 -0
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-fakes.d.cts.map +1 -1
- package/dist/mcp-handler.cjs +14 -4
- package/dist/mcp-handler.cjs.map +1 -1
- package/dist/mcp-handler.js +14 -4
- package/dist/mcp-handler.js.map +1 -1
- package/dist/mcp-recorder.cjs +2 -1
- package/dist/mcp-recorder.cjs.map +1 -1
- package/dist/mcp-recorder.js +3 -2
- package/dist/mcp-recorder.js.map +1 -1
- package/dist/messages.cjs +111 -96
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.d.cts.map +1 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +109 -95
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +5 -2
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +6 -3
- package/dist/metrics.js.map +1 -1
- package/dist/model-utils.cjs +1 -0
- package/dist/model-utils.cjs.map +1 -1
- package/dist/model-utils.js +1 -1
- package/dist/model-utils.js.map +1 -1
- package/dist/moderation.cjs +15 -12
- package/dist/moderation.cjs.map +1 -1
- package/dist/moderation.js +14 -11
- package/dist/moderation.js.map +1 -1
- package/dist/ollama.cjs +128 -68
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts.map +1 -1
- package/dist/ollama.js +124 -64
- package/dist/ollama.js.map +1 -1
- package/dist/openapi.cjs +323 -3
- package/dist/openapi.cjs.map +1 -1
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +324 -4
- package/dist/openapi.js.map +1 -1
- package/dist/openrouter-chat.cjs +11 -155
- package/dist/openrouter-chat.cjs.map +1 -1
- package/dist/openrouter-chat.js +11 -155
- package/dist/openrouter-chat.js.map +1 -1
- package/dist/openrouter-video.cjs +76 -71
- package/dist/openrouter-video.cjs.map +1 -1
- package/dist/openrouter-video.d.cts.map +1 -1
- package/dist/openrouter-video.d.ts.map +1 -1
- package/dist/openrouter-video.js +63 -58
- package/dist/openrouter-video.js.map +1 -1
- package/dist/recorder.cjs +176 -95
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +176 -95
- package/dist/recorder.js.map +1 -1
- package/dist/rerank.cjs +13 -11
- package/dist/rerank.cjs.map +1 -1
- package/dist/rerank.js +12 -10
- package/dist/rerank.js.map +1 -1
- package/dist/response/catalog/anthropic-page.cjs +39 -0
- package/dist/response/catalog/anthropic-page.cjs.map +1 -0
- package/dist/response/catalog/anthropic-page.js +38 -0
- package/dist/response/catalog/anthropic-page.js.map +1 -0
- package/dist/response/catalog/collect.cjs +61 -0
- package/dist/response/catalog/collect.cjs.map +1 -0
- package/dist/response/catalog/collect.js +59 -0
- package/dist/response/catalog/collect.js.map +1 -0
- package/dist/response/catalog/gemini-page.cjs +25 -0
- package/dist/response/catalog/gemini-page.cjs.map +1 -0
- package/dist/response/catalog/gemini-page.js +24 -0
- package/dist/response/catalog/gemini-page.js.map +1 -0
- package/dist/response/catalog/render.cjs +182 -0
- package/dist/response/catalog/render.cjs.map +1 -0
- package/dist/response/catalog/render.js +171 -0
- package/dist/response/catalog/render.js.map +1 -0
- package/dist/response/errors.cjs +225 -0
- package/dist/response/errors.cjs.map +1 -0
- package/dist/response/errors.js +224 -0
- package/dist/response/errors.js.map +1 -0
- package/dist/response/finish.cjs +39 -0
- package/dist/response/finish.cjs.map +1 -0
- package/dist/response/finish.js +35 -0
- package/dist/response/finish.js.map +1 -0
- package/dist/response/policy.cjs +126 -0
- package/dist/response/policy.cjs.map +1 -0
- package/dist/response/policy.js +120 -0
- package/dist/response/policy.js.map +1 -0
- package/dist/response/profiles.cjs +69 -0
- package/dist/response/profiles.cjs.map +1 -0
- package/dist/response/profiles.js +65 -0
- package/dist/response/profiles.js.map +1 -0
- package/dist/response/usage/codecs/anthropic.cjs +52 -0
- package/dist/response/usage/codecs/anthropic.cjs.map +1 -0
- package/dist/response/usage/codecs/anthropic.js +50 -0
- package/dist/response/usage/codecs/anthropic.js.map +1 -0
- package/dist/response/usage/codecs/cohere.cjs +36 -0
- package/dist/response/usage/codecs/cohere.cjs.map +1 -0
- package/dist/response/usage/codecs/cohere.js +35 -0
- package/dist/response/usage/codecs/cohere.js.map +1 -0
- package/dist/response/usage/codecs/converse.cjs +16 -0
- package/dist/response/usage/codecs/converse.cjs.map +1 -0
- package/dist/response/usage/codecs/converse.js +15 -0
- package/dist/response/usage/codecs/converse.js.map +1 -0
- package/dist/response/usage/codecs/gemini.cjs +31 -0
- package/dist/response/usage/codecs/gemini.cjs.map +1 -0
- package/dist/response/usage/codecs/gemini.js +31 -0
- package/dist/response/usage/codecs/gemini.js.map +1 -0
- package/dist/response/usage/codecs/interactions.cjs +22 -0
- package/dist/response/usage/codecs/interactions.cjs.map +1 -0
- package/dist/response/usage/codecs/interactions.js +21 -0
- package/dist/response/usage/codecs/interactions.js.map +1 -0
- package/dist/response/usage/codecs/ollama.cjs +17 -0
- package/dist/response/usage/codecs/ollama.cjs.map +1 -0
- package/dist/response/usage/codecs/ollama.js +17 -0
- package/dist/response/usage/codecs/ollama.js.map +1 -0
- package/dist/response/usage/codecs/openai.cjs +52 -0
- package/dist/response/usage/codecs/openai.cjs.map +1 -0
- package/dist/response/usage/codecs/openai.js +50 -0
- package/dist/response/usage/codecs/openai.js.map +1 -0
- package/dist/response/usage/codecs/openrouter.cjs +68 -0
- package/dist/response/usage/codecs/openrouter.cjs.map +1 -0
- package/dist/response/usage/codecs/openrouter.js +67 -0
- package/dist/response/usage/codecs/openrouter.js.map +1 -0
- package/dist/response/usage/codecs/realtime.cjs +19 -0
- package/dist/response/usage/codecs/realtime.cjs.map +1 -0
- package/dist/response/usage/codecs/realtime.js +18 -0
- package/dist/response/usage/codecs/realtime.js.map +1 -0
- package/dist/response/usage/codecs/responses.cjs +21 -0
- package/dist/response/usage/codecs/responses.cjs.map +1 -0
- package/dist/response/usage/codecs/responses.js +20 -0
- package/dist/response/usage/codecs/responses.js.map +1 -0
- package/dist/response/usage/estimate.cjs +23 -0
- package/dist/response/usage/estimate.cjs.map +1 -0
- package/dist/response/usage/estimate.js +21 -0
- package/dist/response/usage/estimate.js.map +1 -0
- package/dist/response/usage/fields.cjs +72 -0
- package/dist/response/usage/fields.cjs.map +1 -0
- package/dist/response/usage/fields.js +70 -0
- package/dist/response/usage/fields.js.map +1 -0
- package/dist/response/usage/recording.cjs +52 -0
- package/dist/response/usage/recording.cjs.map +1 -0
- package/dist/response/usage/recording.js +52 -0
- package/dist/response/usage/recording.js.map +1 -0
- package/dist/responses.cjs +45 -58
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +44 -57
- package/dist/responses.js.map +1 -1
- package/dist/route-registry.cjs +45 -0
- package/dist/route-registry.cjs.map +1 -1
- package/dist/route-registry.d.cts.map +1 -1
- package/dist/route-registry.d.ts.map +1 -1
- package/dist/route-registry.js +41 -1
- package/dist/route-registry.js.map +1 -1
- package/dist/router.cjs +4 -0
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +3 -0
- package/dist/router.js.map +1 -1
- package/dist/search.cjs +13 -11
- package/dist/search.cjs.map +1 -1
- package/dist/search.js +12 -10
- package/dist/search.js.map +1 -1
- package/dist/server.cjs +222 -146
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +223 -147
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +20 -18
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.d.cts.map +1 -1
- package/dist/speech.d.ts.map +1 -1
- package/dist/speech.js +19 -17
- package/dist/speech.js.map +1 -1
- package/dist/sse-writer.cjs +7 -0
- package/dist/sse-writer.cjs.map +1 -1
- package/dist/sse-writer.d.cts.map +1 -1
- package/dist/sse-writer.d.ts.map +1 -1
- package/dist/sse-writer.js +7 -1
- package/dist/sse-writer.js.map +1 -1
- package/dist/stream-collapse.cjs +118 -11
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +10 -9
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +10 -9
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +118 -11
- package/dist/stream-collapse.js.map +1 -1
- package/dist/transcription.cjs +12 -10
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.d.cts.map +1 -1
- package/dist/transcription.d.ts.map +1 -1
- package/dist/transcription.js +11 -9
- package/dist/transcription.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +62 -3
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +62 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vector-mock.cjs +3 -8
- package/dist/vector-mock.cjs.map +1 -1
- package/dist/vector-mock.d.cts.map +1 -1
- package/dist/vector-mock.d.ts.map +1 -1
- package/dist/vector-mock.js +3 -8
- package/dist/vector-mock.js.map +1 -1
- package/dist/vector-stores.cjs +2 -4
- package/dist/vector-stores.cjs.map +1 -1
- package/dist/vector-stores.d.cts.map +1 -1
- package/dist/vector-stores.d.ts.map +1 -1
- package/dist/vector-stores.js +2 -4
- package/dist/vector-stores.js.map +1 -1
- package/dist/veo-video.cjs +16 -14
- package/dist/veo-video.cjs.map +1 -1
- package/dist/veo-video.js +10 -8
- package/dist/veo-video.js.map +1 -1
- package/dist/video.cjs +21 -19
- package/dist/video.cjs.map +1 -1
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts.map +1 -1
- package/dist/video.js +20 -18
- package/dist/video.js.map +1 -1
- package/dist/ws-gemini-live.cjs +11 -3
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.d.cts +0 -1
- package/dist/ws-gemini-live.d.cts.map +1 -1
- package/dist/ws-gemini-live.d.ts +0 -1
- package/dist/ws-gemini-live.d.ts.map +1 -1
- package/dist/ws-gemini-live.js +10 -2
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-live.cjs +6 -5
- package/dist/ws-live.cjs.map +1 -1
- package/dist/ws-live.js +5 -4
- package/dist/ws-live.js.map +1 -1
- package/dist/ws-realtime.cjs +28 -54
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +25 -51
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +8 -6
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.js +7 -5
- package/dist/ws-responses.js.map +1 -1
- package/package.json +4 -1
- package/skills/write-fixtures/SKILL.md +25 -16
package/dist/batches.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batches.js","names":[],"sources":["../src/batches.ts"],"sourcesContent":["/**\n * OpenAI Batches API mock.\n *\n * Covers `POST /v1/batches`, `GET /v1/batches`, `GET /v1/batches/{id}` and\n * `POST /v1/batches/{id}/cancel` with a deterministic poll progression so\n * batch flows can be tested without waiting 24h:\n *\n * validating → in_progress → completed | failed | expired | cancelled\n *\n * The first retrieve after create advances to `in_progress`, the second (and\n * later) to the batch's outcome. Terminal batches are stable. On `completed`\n * the mock registers a REAL file in the Files store (`output_file_id`, purpose\n * `batch_output`, filename `<batch-id>_output.jsonl`) holding one JSONL line\n * per input request, so `GET /v1/files/{id}/content` serves it back and the\n * upload → create → poll → download flow closes. When `input_file_id` names a\n * file in the store, its JSONL lines drive the `custom_id`s and\n * `request_counts.total`; when it does not (the id was never uploaded here),\n * the mock synthesizes ONE request (`custom_id: \"request-1\"`, `total: 1`) so\n * the output file is still downloadable.\n *\n * Outcome control: the real API only ever completes a healthy batch, so a\n * suite that has to exercise its failure handling needs a lever. Send\n * `X-AIMock-Batch-Outcome: completed | failed | expired | cancelled` on\n * `POST /v1/batches` (default `completed`, unknown values 400). `failed`\n * populates a one-entry `errors` list and a real `error_file_id` (one JSONL\n * error line per request) and stamps `failed_at`; `expired` stamps `expired_at`; `cancelled`\n * lands the batch on `cancelled` without a cancel call. Same `X-AIMock-*`\n * family as `X-AIMock-Strict` and the chaos headers.\n *\n * `cancel` on a non-terminal batch answers `cancelling` (stamping\n * `cancelling_at`); the next retrieve shows `cancelled` with `cancelled_at`.\n * Cancelling a terminal batch is 400 `Cannot cancel a batch with status\n * <status>.` (the SDK's `Batch.status` union, openai@4.104.0\n * `resources/batches.d.ts`, is the source for the status set and the `*_at`\n * stamps; the exact 400 message text is UNVERIFIED against the live API).\n *\n * Output-line shape (`{id: \"batch_req_…\", custom_id, response: {status_code,\n * request_id, body}, error}` / error lines with `response: null` and\n * `error: {code, message}`) follows the OpenAI Batch guide's documented\n * result-file format; neither the vendored SDK nor `openai/openai-openapi`\n * types the file's lines, so that shape is UNVERIFIED against a live capture.\n *\n * State is in-memory per process and cleared by the full reset path.\n * Every branch journals with `service: \"batches\"` and runs through the\n * chaos gate so fault-injection suites can target the batch surface.\n */\n\nimport type * as http from \"node:http\";\nimport { randomBytes } from \"node:crypto\";\nimport { flattenHeaders, generateId, isJsonObject } from \"./helpers.js\";\nimport { applyChaosAsync, type ChaosAsyncOutcome } from \"./chaos.js\";\nimport { paginate, readMetadata } from \"./fine-tuning.js\";\nimport { FILES_MAX_BYTES, getStoredFileBytes, storeFile } from \"./files.js\";\nimport { buildTextResponse } from \"./responses.js\";\nimport type { ChaosDefaults } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\n\nexport interface BatchObject {\n id: string;\n object: \"batch\";\n endpoint: string;\n input_file_id: string;\n completion_window: string;\n status: BatchStatus;\n created_at: number;\n metadata?: Record<string, string>;\n in_progress_at?: number;\n completed_at?: number;\n failed_at?: number;\n expired_at?: number;\n cancelling_at?: number;\n cancelled_at?: number;\n output_file_id?: string;\n error_file_id?: string;\n errors?: { object: \"list\"; data: BatchError[] };\n request_counts?: { total: number; completed: number; failed: number };\n}\n\n/** `Batch.status` minus `finalizing`, which this mock never lingers in. */\nexport type BatchStatus =\n | \"validating\"\n | \"in_progress\"\n | \"completed\"\n | \"failed\"\n | \"expired\"\n | \"cancelling\"\n | \"cancelled\";\n\n/** `BatchError` from the SDK (`openai@4.104.0` `resources/batches.d.ts`). */\nexport interface BatchError {\n code?: string;\n line?: number | null;\n message?: string;\n param?: string | null;\n}\n\n/**\n * The statuses a batch never leaves. Single source of truth: `advance()`\n * refuses to move a terminal batch and the cancel handler rejects one, so the\n * two cannot drift apart.\n */\nconst TERMINAL_STATUSES = new Set<BatchStatus>([\"completed\", \"failed\", \"expired\", \"cancelled\"]);\n\nfunction isTerminal(status: BatchStatus): boolean {\n return TERMINAL_STATUSES.has(status);\n}\n\n/** Values accepted on `X-AIMock-Batch-Outcome`; where a batch lands on its 2nd retrieve. */\nconst OUTCOMES = new Set([\"completed\", \"failed\", \"expired\", \"cancelled\"]);\ntype BatchOutcome = \"completed\" | \"failed\" | \"expired\" | \"cancelled\";\nconst OUTCOME_HEADER = \"x-aimock-batch-outcome\";\n\n// The `BatchCreateParams.endpoint` union in the vendored `openai` SDK 4.104.0\n// (`resources/batches.d.ts`): `/v1/responses`, `/v1/chat/completions`,\n// `/v1/embeddings`, `/v1/completions`.\nconst VALID_ENDPOINTS = new Set([\n \"/v1/responses\",\n \"/v1/chat/completions\",\n \"/v1/embeddings\",\n \"/v1/completions\",\n]);\n\nconst batches = new Map<string, BatchObject>();\nconst batchPolls = new Map<string, number>();\nconst batchOutcomes = new Map<string, BatchOutcome>();\n\nexport function clearBatchStore(): void {\n batches.clear();\n batchPolls.clear();\n batchOutcomes.clear();\n}\n\nfunction journalBatches(\n journal: Journal,\n method: string,\n path: string,\n headers: Record<string, string>,\n status: number,\n): void {\n journal.add({\n method,\n path,\n headers,\n body: null,\n service: \"batches\",\n response: { status, fixture: null },\n });\n}\n\nfunction writeJson(\n res: http.ServerResponse,\n status: number,\n payload: unknown,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): void {\n setCorsHeaders(res);\n res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(payload));\n}\n\nfunction invalid(message: string): { error: { message: string; type: string } } {\n return { error: { message, type: \"invalid_request_error\" } };\n}\n\ntype Defaults = { logger: Logger; chaos?: ChaosDefaults; registry?: MetricsRegistry };\n\n/**\n * Roll the chaos dice for one batches request. Returns true when a fault\n * fired and the response is already written, so the caller returns early.\n *\n * CORS headers go on BEFORE the roll, because a fault writes the response\n * itself and never reaches `writeJson`. `service: \"batches\"` on the journal\n * context keeps faulted requests visible under `?service=batches`. Mirrors\n * the `chaosHit` in `src/fine-tuning.ts`.\n */\nasync function chaosHit(\n req: http.IncomingMessage,\n journal: Journal,\n defaults: Defaults,\n method: string,\n path: string,\n res: http.ServerResponse,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<ChaosAsyncOutcome> {\n setCorsHeaders(res);\n return await applyChaosAsync(\n res,\n null,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n {\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"batches\",\n },\n \"internal\",\n defaults.registry,\n defaults.logger,\n );\n}\n\nfunction now(): number {\n return Math.floor(Date.now() / 1000);\n}\n\n/**\n * The input file's requests, as `{ custom_id, model }` per JSONL line. Lines\n * that are not JSON objects still count as requests (the real API reports\n * them as errors by line; here they get a positional `custom_id`). An\n * `input_file_id` that was never uploaded to this mock yields one synthesized\n * request — see the module docblock.\n */\nfunction inputRequests(batch: BatchObject): { custom_id: string; model: string }[] {\n const bytes = getStoredFileBytes(batch.input_file_id);\n const lines =\n bytes === undefined\n ? []\n : bytes\n .toString(\"utf8\")\n .split(\"\\n\")\n .filter((l) => l.trim() !== \"\");\n const requests = lines.map((line, i) => {\n let parsed: unknown;\n try {\n parsed = JSON.parse(line) as unknown;\n } catch {\n parsed = undefined;\n }\n const obj = isJsonObject(parsed) ? parsed : {};\n const body = isJsonObject(obj[\"body\"]) ? obj[\"body\"] : {};\n return {\n custom_id: typeof obj[\"custom_id\"] === \"string\" ? obj[\"custom_id\"] : `request-${i + 1}`,\n model: typeof body[\"model\"] === \"string\" ? body[\"model\"] : \"aimock\",\n };\n });\n return requests.length > 0 ? requests : [{ custom_id: \"request-1\", model: \"aimock\" }];\n}\n\n/** A minimal per-endpoint success body for one output line. */\nfunction outputBody(endpoint: string, model: string): Record<string, unknown> {\n const created = now();\n if (endpoint === \"/v1/responses\") {\n // The same `Response` envelope the live `/v1/responses` mock writes\n // (`object: \"response\"`, `status`, `output[]` message with `output_text`,\n // `usage`), per openai@4.104.0 `resources/responses/responses.d.ts`.\n return buildTextResponse(\"Mock batch completion\", model) as Record<string, unknown>;\n }\n if (endpoint === \"/v1/embeddings\") {\n return {\n object: \"list\",\n model,\n data: [{ object: \"embedding\", index: 0, embedding: [0, 0, 0] }],\n usage: { prompt_tokens: 1, total_tokens: 1 },\n };\n }\n if (endpoint === \"/v1/completions\") {\n return {\n id: generateId(\"cmpl\"),\n object: \"text_completion\",\n created,\n model,\n choices: [{ index: 0, text: \"Mock batch completion\", finish_reason: \"stop\" }],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },\n };\n }\n return {\n id: generateId(\"chatcmpl\"),\n object: \"chat.completion\",\n created,\n model,\n choices: [\n {\n index: 0,\n message: { role: \"assistant\", content: \"Mock batch completion\" },\n finish_reason: \"stop\",\n },\n ],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },\n };\n}\n\n/** `batch_req_…` / `req_…` — underscore-joined like the real ids, unlike `generateId`. */\nfunction underscoreId(prefix: string): string {\n return `${prefix}_${randomBytes(12).toString(\"base64url\")}`;\n}\n\n/**\n * Serialise one JSONL line per request, stopping the moment the running byte\n * count passes the Files store's per-file cap. Every output line is a fixed\n * ~430-byte canned body regardless of how small the input line was (a 3-byte\n * `{}` line amplifies ~143x), so an input file that is itself under the\n * upload cap can describe an output far over it; building the lines eagerly\n * would hold that whole over-cap buffer before anything could refuse it.\n * Returns `undefined` when the cap is exceeded, with at most `FILES_MAX_BYTES`\n * ever materialised.\n */\nfunction jsonlUnderCap<T>(items: T[], line: (item: T) => unknown): Buffer | undefined {\n const chunks: Buffer[] = [];\n let bytes = 0;\n for (const item of items) {\n const chunk = Buffer.from(JSON.stringify(line(item)) + \"\\n\", \"utf8\");\n bytes += chunk.length;\n if (bytes > FILES_MAX_BYTES) {\n return undefined;\n }\n chunks.push(chunk);\n }\n return Buffer.concat(chunks, bytes);\n}\n\n/**\n * The batch could not mint its result file under {@link FILES_MAX_BYTES}: it\n * fails with one `errors` entry naming the cap. `BatchError.code` is an open\n * `string` in the SDK types and the real API has no equivalent condition, so\n * the code value is UNVERIFIED against a live capture.\n */\nfunction failOverCap(batch: BatchObject, total: number, kind: \"output\" | \"error\"): void {\n batch.status = \"failed\";\n batch.errors = {\n object: \"list\",\n data: [\n {\n code: \"output_file_too_large\",\n message: `Batch ${kind} file would exceed the ${FILES_MAX_BYTES} byte per-file cap`,\n line: null,\n param: null,\n },\n ],\n };\n batch.request_counts = { total, completed: 0, failed: total };\n batch.failed_at = now();\n}\n\nfunction finish(batch: BatchObject, outcome: BatchOutcome): void {\n const requests = inputRequests(batch);\n const total = requests.length;\n batch.status = outcome;\n if (outcome === \"completed\") {\n const content = jsonlUnderCap(requests, (r) => ({\n id: underscoreId(\"batch_req\"),\n custom_id: r.custom_id,\n response: {\n status_code: 200,\n request_id: underscoreId(\"req\"),\n body: outputBody(batch.endpoint, r.model),\n },\n error: null,\n }));\n if (content === undefined) {\n failOverCap(batch, total, \"output\");\n return;\n }\n batch.output_file_id = storeFile({\n purpose: \"batch_output\",\n filename: `${batch.id}_output.jsonl`,\n content,\n }).id;\n batch.request_counts = { total, completed: total, failed: 0 };\n batch.completed_at = now();\n } else if (outcome === \"failed\") {\n const message = \"Mock batch failure (X-AIMock-Batch-Outcome: failed)\";\n const content = jsonlUnderCap(requests, (r) => ({\n id: underscoreId(\"batch_req\"),\n custom_id: r.custom_id,\n response: null,\n error: { code: \"mock_failure\", message },\n }));\n if (content === undefined) {\n failOverCap(batch, total, \"error\");\n return;\n }\n batch.error_file_id = storeFile({\n purpose: \"batch_output\",\n filename: `${batch.id}_error.jsonl`,\n content,\n }).id;\n // One file-level entry, not one per request: `errors` rides inline on\n // every retrieve and every list page, so a per-line list scales the batch\n // object with the input (2000 lines -> ~225 KB per GET). The per-request\n // detail lives in the error file. (The live API's `Batch.Errors` is typed\n // as a plain `BatchError[]` in openai@4.104.0 `resources/batches.d.ts`;\n // that it stays small is UNVERIFIED there but matches its purpose of\n // reporting input-file validation errors, not per-request outcomes.)\n batch.errors = {\n object: \"list\",\n data: [\n { code: \"mock_failure\", message: `${message}: see error_file_id`, line: null, param: null },\n ],\n };\n batch.request_counts = { total, completed: 0, failed: total };\n batch.failed_at = now();\n } else if (outcome === \"expired\") {\n batch.request_counts = { total, completed: 0, failed: 0 };\n batch.expired_at = now();\n } else {\n batch.request_counts = { total, completed: 0, failed: 0 };\n batch.cancelled_at = now();\n }\n}\n\nfunction advance(batch: BatchObject): BatchObject {\n if (isTerminal(batch.status)) {\n return batch;\n }\n if (batch.status === \"cancelling\") {\n // Same arm the `X-AIMock-Batch-Outcome: cancelled` path lands on, so a\n // route-cancelled batch reports the same `request_counts`.\n finish(batch, \"cancelled\");\n return batch;\n }\n const polls = (batchPolls.get(batch.id) ?? 0) + 1;\n batchPolls.set(batch.id, polls);\n if (polls === 1) {\n batch.status = \"in_progress\";\n batch.in_progress_at = now();\n // `total` is known once the input has been read; the terminal arms in\n // `finish()` fill in `completed` / `failed`. (Whether the live API stamps\n // `total` exactly at `in_progress` is UNVERIFIED: openai@4.104.0\n // `resources/batches.d.ts` types `BatchRequestCounts` but not its timing.)\n batch.request_counts = { total: inputRequests(batch).length, completed: 0, failed: 0 };\n } else {\n finish(batch, batchOutcomes.get(batch.id) ?? \"completed\");\n }\n return batch;\n}\n\nexport async function handleBatchesCreate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n journal: Journal,\n defaults: Defaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const path = req.url ?? \"/v1/batches\";\n const method = req.method ?? \"POST\";\n if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;\n\n let body: unknown;\n try {\n body = JSON.parse(raw) as unknown;\n } catch (err) {\n const detail = err instanceof Error ? err.message : \"unknown\";\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(res, 400, invalid(`Malformed JSON: ${detail}`), setCorsHeaders);\n return;\n }\n if (!isJsonObject(body)) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(res, 400, invalid(\"Request body must be a JSON object\"), setCorsHeaders);\n return;\n }\n const inputFileId = body[\"input_file_id\"];\n const endpoint = body[\"endpoint\"];\n const window = body[\"completion_window\"];\n if (typeof inputFileId !== \"string\" || inputFileId.length === 0) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(\"Invalid parameter: 'input_file_id' must be a non-empty string\"),\n setCorsHeaders,\n );\n return;\n }\n if (typeof endpoint !== \"string\" || !VALID_ENDPOINTS.has(endpoint)) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(\n `Invalid endpoint '${String(endpoint)}'. Expected one of: ${[...VALID_ENDPOINTS].join(\", \")}`,\n ),\n setCorsHeaders,\n );\n return;\n }\n if (window !== \"24h\") {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(\"Invalid parameter: 'completion_window' must be '24h'\"),\n setCorsHeaders,\n );\n return;\n }\n // Same shared `Metadata` schema the fine-tuning mock validates and echoes.\n const metadata = readMetadata(body[\"metadata\"]);\n if (!metadata.ok) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(res, 400, invalid(metadata.message), setCorsHeaders);\n return;\n }\n\n const outcomeHeader = req.headers[OUTCOME_HEADER];\n const outcomeRaw = (Array.isArray(outcomeHeader) ? outcomeHeader[0] : outcomeHeader)\n ?.trim()\n .toLowerCase();\n if (outcomeRaw !== undefined && !OUTCOMES.has(outcomeRaw)) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(\n `Invalid X-AIMock-Batch-Outcome '${outcomeRaw}'. Expected one of: ${[...OUTCOMES].join(\", \")}`,\n ),\n setCorsHeaders,\n );\n return;\n }\n\n const id = generateId(\"batch\");\n const batch: BatchObject = {\n id,\n object: \"batch\",\n endpoint,\n input_file_id: inputFileId,\n completion_window: \"24h\",\n status: \"validating\",\n created_at: Math.floor(Date.now() / 1000),\n request_counts: { total: 0, completed: 0, failed: 0 },\n };\n if (metadata.value !== null) batch.metadata = metadata.value;\n batches.set(id, batch);\n batchPolls.set(id, 0);\n if (outcomeRaw !== undefined) batchOutcomes.set(id, outcomeRaw as BatchOutcome);\n defaults.logger.debug(`Batches mock: created ${id}`);\n journalBatches(journal, method, path, flattenHeaders(req.headers), 200);\n writeJson(res, 200, batch, setCorsHeaders);\n}\n\nexport async function handleBatchesList(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n journal: Journal,\n defaults: Defaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const path = req.url ?? \"/v1/batches\";\n const method = req.method ?? \"GET\";\n if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;\n // Newest-first, mirroring `handleFineTuningList`: `paginate()` documents a\n // newest-first input, and reversing insertion order is the stable total\n // order a cursor walk needs (`created_at` has one-second resolution, so a\n // sort on it cannot order the ties that are the normal case).\n const data = [...batches.values()].reverse();\n // `after`/`limit` cursor paging shared with the fine-tuning mock; the real\n // list response also carries `first_id`/`last_id` (null on an empty page).\n const result = paginate(data, req.url);\n if (!result.ok) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(res, 400, invalid(result.message), setCorsHeaders);\n return;\n }\n const page = result.page.data;\n journalBatches(journal, method, path, flattenHeaders(req.headers), 200);\n writeJson(\n res,\n 200,\n {\n ...result.page,\n first_id: page[0]?.id ?? null,\n last_id: page[page.length - 1]?.id ?? null,\n },\n setCorsHeaders,\n );\n}\n\nexport async function handleBatchesRetrieve(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n batchId: string,\n journal: Journal,\n defaults: Defaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const path = req.url ?? `/v1/batches/${batchId}`;\n const method = req.method ?? \"GET\";\n if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;\n const batch = batches.get(batchId);\n if (!batch) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 404);\n writeJson(res, 404, invalid(`No such batch: ${batchId}`), setCorsHeaders);\n return;\n }\n journalBatches(journal, method, path, flattenHeaders(req.headers), 200);\n writeJson(res, 200, advance(batch), setCorsHeaders);\n}\n\nexport async function handleBatchesCancel(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n batchId: string,\n journal: Journal,\n defaults: Defaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const path = req.url ?? `/v1/batches/${batchId}/cancel`;\n const method = req.method ?? \"POST\";\n if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;\n const batch = batches.get(batchId);\n if (!batch) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 404);\n writeJson(res, 404, invalid(`No such batch: ${batchId}`), setCorsHeaders);\n return;\n }\n if (isTerminal(batch.status)) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(`Cannot cancel a batch with status ${batch.status}.`),\n setCorsHeaders,\n );\n return;\n }\n // Idempotent on an already-cancelling batch: no second stamp.\n if (batch.status !== \"cancelling\") {\n batch.status = \"cancelling\";\n batch.cancelling_at = now();\n }\n journalBatches(journal, method, path, flattenHeaders(req.headers), 200);\n writeJson(res, 200, batch, setCorsHeaders);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"batches.js","names":[],"sources":["../src/batches.ts"],"sourcesContent":["/**\n * OpenAI Batches API mock.\n *\n * Covers `POST /v1/batches`, `GET /v1/batches`, `GET /v1/batches/{id}` and\n * `POST /v1/batches/{id}/cancel` with a deterministic poll progression so\n * batch flows can be tested without waiting 24h:\n *\n * validating → in_progress → completed | failed | expired | cancelled\n *\n * The first retrieve after create advances to `in_progress`, the second (and\n * later) to the batch's outcome. Terminal batches are stable. On `completed`\n * the mock registers a REAL file in the Files store (`output_file_id`, purpose\n * `batch_output`, filename `<batch-id>_output.jsonl`) holding one JSONL line\n * per input request, so `GET /v1/files/{id}/content` serves it back and the\n * upload → create → poll → download flow closes. When `input_file_id` names a\n * file in the store, its JSONL lines drive the `custom_id`s and\n * `request_counts.total`; when it does not (the id was never uploaded here),\n * the mock synthesizes ONE request (`custom_id: \"request-1\"`, `total: 1`) so\n * the output file is still downloadable.\n *\n * Outcome control: the real API only ever completes a healthy batch, so a\n * suite that has to exercise its failure handling needs a lever. Send\n * `X-AIMock-Batch-Outcome: completed | failed | expired | cancelled` on\n * `POST /v1/batches` (default `completed`, unknown values 400). `failed`\n * populates a one-entry `errors` list and a real `error_file_id` (one JSONL\n * error line per request) and stamps `failed_at`; `expired` stamps `expired_at`; `cancelled`\n * lands the batch on `cancelled` without a cancel call. Same `X-AIMock-*`\n * family as `X-AIMock-Strict` and the chaos headers.\n *\n * `cancel` on a non-terminal batch answers `cancelling` (stamping\n * `cancelling_at`); the next retrieve shows `cancelled` with `cancelled_at`.\n * Cancelling a terminal batch is 400 `Cannot cancel a batch with status\n * <status>.` (the SDK's `Batch.status` union, openai@4.104.0\n * `resources/batches.d.ts`, is the source for the status set and the `*_at`\n * stamps; the exact 400 message text is UNVERIFIED against the live API).\n *\n * Output-line shape (`{id: \"batch_req_…\", custom_id, response: {status_code,\n * request_id, body}, error}` / error lines with `response: null` and\n * `error: {code, message}`) follows the OpenAI Batch guide's documented\n * result-file format; neither the vendored SDK nor `openai/openai-openapi`\n * types the file's lines, so that shape is UNVERIFIED against a live capture.\n *\n * State is in-memory per process and cleared by the full reset path.\n * Every branch journals with `service: \"batches\"` and runs through the\n * chaos gate so fault-injection suites can target the batch surface.\n */\n\nimport type * as http from \"node:http\";\nimport { randomBytes } from \"node:crypto\";\nimport { flattenHeaders, generateId, isJsonObject } from \"./helpers.js\";\nimport { applyChaosAsync, type ChaosAsyncOutcome } from \"./chaos.js\";\nimport { paginate, readMetadata } from \"./fine-tuning.js\";\nimport { FILES_MAX_BYTES, getStoredFileBytes, storeFile } from \"./files.js\";\nimport { buildTextResponse } from \"./responses.js\";\nimport type { ChaosDefaults } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport { buildOpenAIError } from \"./response/errors.js\";\n\nexport interface BatchObject {\n id: string;\n object: \"batch\";\n endpoint: string;\n input_file_id: string;\n completion_window: string;\n status: BatchStatus;\n created_at: number;\n metadata?: Record<string, string>;\n in_progress_at?: number;\n completed_at?: number;\n failed_at?: number;\n expired_at?: number;\n cancelling_at?: number;\n cancelled_at?: number;\n output_file_id?: string;\n error_file_id?: string;\n errors?: { object: \"list\"; data: BatchError[] };\n request_counts?: { total: number; completed: number; failed: number };\n}\n\n/** `Batch.status` minus `finalizing`, which this mock never lingers in. */\nexport type BatchStatus =\n | \"validating\"\n | \"in_progress\"\n | \"completed\"\n | \"failed\"\n | \"expired\"\n | \"cancelling\"\n | \"cancelled\";\n\n/** `BatchError` from the SDK (`openai@4.104.0` `resources/batches.d.ts`). */\nexport interface BatchError {\n code?: string;\n line?: number | null;\n message?: string;\n param?: string | null;\n}\n\n/**\n * The statuses a batch never leaves. Single source of truth: `advance()`\n * refuses to move a terminal batch and the cancel handler rejects one, so the\n * two cannot drift apart.\n */\nconst TERMINAL_STATUSES = new Set<BatchStatus>([\"completed\", \"failed\", \"expired\", \"cancelled\"]);\n\nfunction isTerminal(status: BatchStatus): boolean {\n return TERMINAL_STATUSES.has(status);\n}\n\n/** Values accepted on `X-AIMock-Batch-Outcome`; where a batch lands on its 2nd retrieve. */\nconst OUTCOMES = new Set([\"completed\", \"failed\", \"expired\", \"cancelled\"]);\ntype BatchOutcome = \"completed\" | \"failed\" | \"expired\" | \"cancelled\";\nconst OUTCOME_HEADER = \"x-aimock-batch-outcome\";\n\n// The `BatchCreateParams.endpoint` union in the vendored `openai` SDK 4.104.0\n// (`resources/batches.d.ts`): `/v1/responses`, `/v1/chat/completions`,\n// `/v1/embeddings`, `/v1/completions`.\nconst VALID_ENDPOINTS = new Set([\n \"/v1/responses\",\n \"/v1/chat/completions\",\n \"/v1/embeddings\",\n \"/v1/completions\",\n]);\n\nconst batches = new Map<string, BatchObject>();\nconst batchPolls = new Map<string, number>();\nconst batchOutcomes = new Map<string, BatchOutcome>();\n\nexport function clearBatchStore(): void {\n batches.clear();\n batchPolls.clear();\n batchOutcomes.clear();\n}\n\nfunction journalBatches(\n journal: Journal,\n method: string,\n path: string,\n headers: Record<string, string>,\n status: number,\n): void {\n journal.add({\n method,\n path,\n headers,\n body: null,\n service: \"batches\",\n response: { status, fixture: null },\n });\n}\n\nfunction writeJson(\n res: http.ServerResponse,\n status: number,\n payload: unknown,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): void {\n setCorsHeaders(res);\n res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(payload));\n}\n\nfunction invalid(message: string): { error: { message: string; type: string } } {\n return buildOpenAIError(\"service-validation\", { message });\n}\n\ntype Defaults = { logger: Logger; chaos?: ChaosDefaults; registry?: MetricsRegistry };\n\n/**\n * Roll the chaos dice for one batches request. Returns true when a fault\n * fired and the response is already written, so the caller returns early.\n *\n * CORS headers go on BEFORE the roll, because a fault writes the response\n * itself and never reaches `writeJson`. `service: \"batches\"` on the journal\n * context keeps faulted requests visible under `?service=batches`. Mirrors\n * the `chaosHit` in `src/fine-tuning.ts`.\n */\nasync function chaosHit(\n req: http.IncomingMessage,\n journal: Journal,\n defaults: Defaults,\n method: string,\n path: string,\n res: http.ServerResponse,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<ChaosAsyncOutcome> {\n setCorsHeaders(res);\n return await applyChaosAsync(\n res,\n null,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n {\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"batches\",\n },\n \"internal\",\n defaults.registry,\n defaults.logger,\n );\n}\n\nfunction now(): number {\n return Math.floor(Date.now() / 1000);\n}\n\n/**\n * The input file's requests, as `{ custom_id, model }` per JSONL line. Lines\n * that are not JSON objects still count as requests (the real API reports\n * them as errors by line; here they get a positional `custom_id`). An\n * `input_file_id` that was never uploaded to this mock yields one synthesized\n * request — see the module docblock.\n */\nfunction inputRequests(batch: BatchObject): { custom_id: string; model: string }[] {\n const bytes = getStoredFileBytes(batch.input_file_id);\n const lines =\n bytes === undefined\n ? []\n : bytes\n .toString(\"utf8\")\n .split(\"\\n\")\n .filter((l) => l.trim() !== \"\");\n const requests = lines.map((line, i) => {\n let parsed: unknown;\n try {\n parsed = JSON.parse(line) as unknown;\n } catch {\n parsed = undefined;\n }\n const obj = isJsonObject(parsed) ? parsed : {};\n const body = isJsonObject(obj[\"body\"]) ? obj[\"body\"] : {};\n return {\n custom_id: typeof obj[\"custom_id\"] === \"string\" ? obj[\"custom_id\"] : `request-${i + 1}`,\n model: typeof body[\"model\"] === \"string\" ? body[\"model\"] : \"aimock\",\n };\n });\n return requests.length > 0 ? requests : [{ custom_id: \"request-1\", model: \"aimock\" }];\n}\n\n/** A minimal per-endpoint success body for one output line. */\nfunction outputBody(endpoint: string, model: string): Record<string, unknown> {\n const created = now();\n if (endpoint === \"/v1/responses\") {\n // The same `Response` envelope the live `/v1/responses` mock writes\n // (`object: \"response\"`, `status`, `output[]` message with `output_text`,\n // `usage`), per openai@4.104.0 `resources/responses/responses.d.ts`.\n return buildTextResponse(\"Mock batch completion\", model) as Record<string, unknown>;\n }\n if (endpoint === \"/v1/embeddings\") {\n return {\n object: \"list\",\n model,\n data: [{ object: \"embedding\", index: 0, embedding: [0, 0, 0] }],\n usage: { prompt_tokens: 1, total_tokens: 1 },\n };\n }\n if (endpoint === \"/v1/completions\") {\n return {\n id: generateId(\"cmpl\"),\n object: \"text_completion\",\n created,\n model,\n choices: [{ index: 0, text: \"Mock batch completion\", finish_reason: \"stop\" }],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },\n };\n }\n return {\n id: generateId(\"chatcmpl\"),\n object: \"chat.completion\",\n created,\n model,\n choices: [\n {\n index: 0,\n message: { role: \"assistant\", content: \"Mock batch completion\" },\n finish_reason: \"stop\",\n },\n ],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },\n };\n}\n\n/** `batch_req_…` / `req_…` — underscore-joined like the real ids, unlike `generateId`. */\nfunction underscoreId(prefix: string): string {\n return `${prefix}_${randomBytes(12).toString(\"base64url\")}`;\n}\n\n/**\n * Serialise one JSONL line per request, stopping the moment the running byte\n * count passes the Files store's per-file cap. Every output line is a fixed\n * ~430-byte canned body regardless of how small the input line was (a 3-byte\n * `{}` line amplifies ~143x), so an input file that is itself under the\n * upload cap can describe an output far over it; building the lines eagerly\n * would hold that whole over-cap buffer before anything could refuse it.\n * Returns `undefined` when the cap is exceeded, with at most `FILES_MAX_BYTES`\n * ever materialised.\n */\nfunction jsonlUnderCap<T>(items: T[], line: (item: T) => unknown): Buffer | undefined {\n const chunks: Buffer[] = [];\n let bytes = 0;\n for (const item of items) {\n const chunk = Buffer.from(JSON.stringify(line(item)) + \"\\n\", \"utf8\");\n bytes += chunk.length;\n if (bytes > FILES_MAX_BYTES) {\n return undefined;\n }\n chunks.push(chunk);\n }\n return Buffer.concat(chunks, bytes);\n}\n\n/**\n * The batch could not mint its result file under {@link FILES_MAX_BYTES}: it\n * fails with one `errors` entry naming the cap. `BatchError.code` is an open\n * `string` in the SDK types and the real API has no equivalent condition, so\n * the code value is UNVERIFIED against a live capture.\n */\nfunction failOverCap(batch: BatchObject, total: number, kind: \"output\" | \"error\"): void {\n batch.status = \"failed\";\n batch.errors = {\n object: \"list\",\n data: [\n {\n code: \"output_file_too_large\",\n message: `Batch ${kind} file would exceed the ${FILES_MAX_BYTES} byte per-file cap`,\n line: null,\n param: null,\n },\n ],\n };\n batch.request_counts = { total, completed: 0, failed: total };\n batch.failed_at = now();\n}\n\nfunction finish(batch: BatchObject, outcome: BatchOutcome): void {\n const requests = inputRequests(batch);\n const total = requests.length;\n batch.status = outcome;\n if (outcome === \"completed\") {\n const content = jsonlUnderCap(requests, (r) => ({\n id: underscoreId(\"batch_req\"),\n custom_id: r.custom_id,\n response: {\n status_code: 200,\n request_id: underscoreId(\"req\"),\n body: outputBody(batch.endpoint, r.model),\n },\n error: null,\n }));\n if (content === undefined) {\n failOverCap(batch, total, \"output\");\n return;\n }\n batch.output_file_id = storeFile({\n purpose: \"batch_output\",\n filename: `${batch.id}_output.jsonl`,\n content,\n }).id;\n batch.request_counts = { total, completed: total, failed: 0 };\n batch.completed_at = now();\n } else if (outcome === \"failed\") {\n const message = \"Mock batch failure (X-AIMock-Batch-Outcome: failed)\";\n const content = jsonlUnderCap(requests, (r) => ({\n id: underscoreId(\"batch_req\"),\n custom_id: r.custom_id,\n response: null,\n error: { code: \"mock_failure\", message },\n }));\n if (content === undefined) {\n failOverCap(batch, total, \"error\");\n return;\n }\n batch.error_file_id = storeFile({\n purpose: \"batch_output\",\n filename: `${batch.id}_error.jsonl`,\n content,\n }).id;\n // One file-level entry, not one per request: `errors` rides inline on\n // every retrieve and every list page, so a per-line list scales the batch\n // object with the input (2000 lines -> ~225 KB per GET). The per-request\n // detail lives in the error file. (The live API's `Batch.Errors` is typed\n // as a plain `BatchError[]` in openai@4.104.0 `resources/batches.d.ts`;\n // that it stays small is UNVERIFIED there but matches its purpose of\n // reporting input-file validation errors, not per-request outcomes.)\n batch.errors = {\n object: \"list\",\n data: [\n { code: \"mock_failure\", message: `${message}: see error_file_id`, line: null, param: null },\n ],\n };\n batch.request_counts = { total, completed: 0, failed: total };\n batch.failed_at = now();\n } else if (outcome === \"expired\") {\n batch.request_counts = { total, completed: 0, failed: 0 };\n batch.expired_at = now();\n } else {\n batch.request_counts = { total, completed: 0, failed: 0 };\n batch.cancelled_at = now();\n }\n}\n\nfunction advance(batch: BatchObject): BatchObject {\n if (isTerminal(batch.status)) {\n return batch;\n }\n if (batch.status === \"cancelling\") {\n // Same arm the `X-AIMock-Batch-Outcome: cancelled` path lands on, so a\n // route-cancelled batch reports the same `request_counts`.\n finish(batch, \"cancelled\");\n return batch;\n }\n const polls = (batchPolls.get(batch.id) ?? 0) + 1;\n batchPolls.set(batch.id, polls);\n if (polls === 1) {\n batch.status = \"in_progress\";\n batch.in_progress_at = now();\n // `total` is known once the input has been read; the terminal arms in\n // `finish()` fill in `completed` / `failed`. (Whether the live API stamps\n // `total` exactly at `in_progress` is UNVERIFIED: openai@4.104.0\n // `resources/batches.d.ts` types `BatchRequestCounts` but not its timing.)\n batch.request_counts = { total: inputRequests(batch).length, completed: 0, failed: 0 };\n } else {\n finish(batch, batchOutcomes.get(batch.id) ?? \"completed\");\n }\n return batch;\n}\n\nexport async function handleBatchesCreate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n journal: Journal,\n defaults: Defaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const path = req.url ?? \"/v1/batches\";\n const method = req.method ?? \"POST\";\n if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;\n\n let body: unknown;\n try {\n body = JSON.parse(raw) as unknown;\n } catch (err) {\n const detail = err instanceof Error ? err.message : \"unknown\";\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(res, 400, invalid(`Malformed JSON: ${detail}`), setCorsHeaders);\n return;\n }\n if (!isJsonObject(body)) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(res, 400, invalid(\"Request body must be a JSON object\"), setCorsHeaders);\n return;\n }\n const inputFileId = body[\"input_file_id\"];\n const endpoint = body[\"endpoint\"];\n const window = body[\"completion_window\"];\n if (typeof inputFileId !== \"string\" || inputFileId.length === 0) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(\"Invalid parameter: 'input_file_id' must be a non-empty string\"),\n setCorsHeaders,\n );\n return;\n }\n if (typeof endpoint !== \"string\" || !VALID_ENDPOINTS.has(endpoint)) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(\n `Invalid endpoint '${String(endpoint)}'. Expected one of: ${[...VALID_ENDPOINTS].join(\", \")}`,\n ),\n setCorsHeaders,\n );\n return;\n }\n if (window !== \"24h\") {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(\"Invalid parameter: 'completion_window' must be '24h'\"),\n setCorsHeaders,\n );\n return;\n }\n // Same shared `Metadata` schema the fine-tuning mock validates and echoes.\n const metadata = readMetadata(body[\"metadata\"]);\n if (!metadata.ok) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(res, 400, invalid(metadata.message), setCorsHeaders);\n return;\n }\n\n const outcomeHeader = req.headers[OUTCOME_HEADER];\n const outcomeRaw = (Array.isArray(outcomeHeader) ? outcomeHeader[0] : outcomeHeader)\n ?.trim()\n .toLowerCase();\n if (outcomeRaw !== undefined && !OUTCOMES.has(outcomeRaw)) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(\n `Invalid X-AIMock-Batch-Outcome '${outcomeRaw}'. Expected one of: ${[...OUTCOMES].join(\", \")}`,\n ),\n setCorsHeaders,\n );\n return;\n }\n\n const id = generateId(\"batch\");\n const batch: BatchObject = {\n id,\n object: \"batch\",\n endpoint,\n input_file_id: inputFileId,\n completion_window: \"24h\",\n status: \"validating\",\n created_at: Math.floor(Date.now() / 1000),\n request_counts: { total: 0, completed: 0, failed: 0 },\n };\n if (metadata.value !== null) batch.metadata = metadata.value;\n batches.set(id, batch);\n batchPolls.set(id, 0);\n if (outcomeRaw !== undefined) batchOutcomes.set(id, outcomeRaw as BatchOutcome);\n defaults.logger.debug(`Batches mock: created ${id}`);\n journalBatches(journal, method, path, flattenHeaders(req.headers), 200);\n writeJson(res, 200, batch, setCorsHeaders);\n}\n\nexport async function handleBatchesList(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n journal: Journal,\n defaults: Defaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const path = req.url ?? \"/v1/batches\";\n const method = req.method ?? \"GET\";\n if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;\n // Newest-first, mirroring `handleFineTuningList`: `paginate()` documents a\n // newest-first input, and reversing insertion order is the stable total\n // order a cursor walk needs (`created_at` has one-second resolution, so a\n // sort on it cannot order the ties that are the normal case).\n const data = [...batches.values()].reverse();\n // `after`/`limit` cursor paging shared with the fine-tuning mock; the real\n // list response also carries `first_id`/`last_id` (null on an empty page).\n const result = paginate(data, req.url);\n if (!result.ok) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(res, 400, invalid(result.message), setCorsHeaders);\n return;\n }\n const page = result.page.data;\n journalBatches(journal, method, path, flattenHeaders(req.headers), 200);\n writeJson(\n res,\n 200,\n {\n ...result.page,\n first_id: page[0]?.id ?? null,\n last_id: page[page.length - 1]?.id ?? null,\n },\n setCorsHeaders,\n );\n}\n\nexport async function handleBatchesRetrieve(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n batchId: string,\n journal: Journal,\n defaults: Defaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const path = req.url ?? `/v1/batches/${batchId}`;\n const method = req.method ?? \"GET\";\n if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;\n const batch = batches.get(batchId);\n if (!batch) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 404);\n writeJson(res, 404, invalid(`No such batch: ${batchId}`), setCorsHeaders);\n return;\n }\n journalBatches(journal, method, path, flattenHeaders(req.headers), 200);\n writeJson(res, 200, advance(batch), setCorsHeaders);\n}\n\nexport async function handleBatchesCancel(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n batchId: string,\n journal: Journal,\n defaults: Defaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const path = req.url ?? `/v1/batches/${batchId}/cancel`;\n const method = req.method ?? \"POST\";\n if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;\n const batch = batches.get(batchId);\n if (!batch) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 404);\n writeJson(res, 404, invalid(`No such batch: ${batchId}`), setCorsHeaders);\n return;\n }\n if (isTerminal(batch.status)) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(`Cannot cancel a batch with status ${batch.status}.`),\n setCorsHeaders,\n );\n return;\n }\n // Idempotent on an already-cancelling batch: no second stamp.\n if (batch.status !== \"cancelling\") {\n batch.status = \"cancelling\";\n batch.cancelling_at = now();\n }\n journalBatches(journal, method, path, flattenHeaders(req.headers), 200);\n writeJson(res, 200, batch, setCorsHeaders);\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const require_errors = require('./response/errors.cjs');
|
|
2
|
+
const require_policy = require('./response/policy.cjs');
|
|
1
3
|
const require_helpers = require('./helpers.cjs');
|
|
2
4
|
const require_sse_writer = require('./sse-writer.cjs');
|
|
3
5
|
const require_chaos = require('./chaos.cjs');
|
|
@@ -5,15 +7,14 @@ const require_misbehavior = require('./misbehavior.cjs');
|
|
|
5
7
|
const require_router = require('./router.cjs');
|
|
6
8
|
const require_recorder = require('./recorder.cjs');
|
|
7
9
|
const require_interruption = require('./interruption.cjs');
|
|
10
|
+
const require_finish = require('./response/finish.cjs');
|
|
8
11
|
const require_aws_event_stream = require('./aws-event-stream.cjs');
|
|
12
|
+
const require_converse = require('./response/usage/codecs/converse.cjs');
|
|
9
13
|
|
|
10
14
|
//#region src/bedrock-converse.ts
|
|
11
15
|
function converseStopReason(overrideFinishReason, defaultReason) {
|
|
12
16
|
if (!overrideFinishReason) return defaultReason;
|
|
13
|
-
|
|
14
|
-
if (overrideFinishReason === "tool_calls") return "tool_use";
|
|
15
|
-
if (overrideFinishReason === "length") return "max_tokens";
|
|
16
|
-
return overrideFinishReason;
|
|
17
|
+
return require_finish.mapAnthropicFinish(overrideFinishReason);
|
|
17
18
|
}
|
|
18
19
|
/**
|
|
19
20
|
* Build Converse-format usage from fixture overrides.
|
|
@@ -24,18 +25,7 @@ function converseStopReason(overrideFinishReason, defaultReason) {
|
|
|
24
25
|
* usage should supply explicit `usage` overrides in their fixture.
|
|
25
26
|
*/
|
|
26
27
|
function converseUsage(overrides) {
|
|
27
|
-
|
|
28
|
-
inputTokens: 0,
|
|
29
|
-
outputTokens: 0,
|
|
30
|
-
totalTokens: 0
|
|
31
|
-
};
|
|
32
|
-
const inputTokens = overrides.usage.input_tokens ?? overrides.usage.prompt_tokens ?? 0;
|
|
33
|
-
const outputTokens = overrides.usage.output_tokens ?? overrides.usage.completion_tokens ?? 0;
|
|
34
|
-
return {
|
|
35
|
-
inputTokens,
|
|
36
|
-
outputTokens,
|
|
37
|
-
totalTokens: inputTokens + outputTokens
|
|
38
|
-
};
|
|
28
|
+
return require_converse.render(overrides);
|
|
39
29
|
}
|
|
40
30
|
function parseConverseToolArgumentsForStream(toolCall, logger) {
|
|
41
31
|
return require_helpers.servedToolArgs(toolCall, "string", logger).text;
|
|
@@ -519,7 +509,7 @@ function prepareConverseMisbehavior(plan, request, stream, defaults, headers) {
|
|
|
519
509
|
} : block;
|
|
520
510
|
});
|
|
521
511
|
}
|
|
522
|
-
const reasoning = plan.reasoning ?? require_helpers.resolveReasoningForModel("reasoning" in response ? response.reasoning : void 0, request.model,
|
|
512
|
+
const reasoning = plan.reasoning ?? require_helpers.resolveReasoningForModel("reasoning" in response ? response.reasoning : void 0, request.model, require_policy.resolveRequestDecisions(defaults.strict, headers).reasoningSuppression, defaults.logger) ?? "";
|
|
523
513
|
const servedToolCalls = blocks.flatMap((block) => block.type === "toolCall" ? [{
|
|
524
514
|
name: block.name,
|
|
525
515
|
arguments: block.arguments,
|
|
@@ -538,11 +528,10 @@ function prepareConverseMisbehavior(plan, request, stream, defaults, headers) {
|
|
|
538
528
|
blocks,
|
|
539
529
|
reasoning,
|
|
540
530
|
stopReason,
|
|
541
|
-
usage: {
|
|
542
|
-
inputTokens,
|
|
543
|
-
outputTokens
|
|
544
|
-
|
|
545
|
-
}
|
|
531
|
+
usage: require_converse.render(void 0, {
|
|
532
|
+
input: inputTokens,
|
|
533
|
+
output: outputTokens
|
|
534
|
+
})
|
|
546
535
|
};
|
|
547
536
|
}
|
|
548
537
|
function buildConverseMisbehaviorResponse(prepared) {
|
|
@@ -651,10 +640,10 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
651
640
|
fixture: null
|
|
652
641
|
}
|
|
653
642
|
});
|
|
654
|
-
require_sse_writer.writeErrorResponse(res, 400,
|
|
643
|
+
require_sse_writer.writeErrorResponse(res, 400, require_errors.renderError("bedrock", "validation", {
|
|
655
644
|
message: `Malformed JSON: ${detail}`,
|
|
656
645
|
type: "invalid_request_error"
|
|
657
|
-
}
|
|
646
|
+
}).body);
|
|
658
647
|
return;
|
|
659
648
|
}
|
|
660
649
|
if (!require_helpers.isJsonObject(converseReq)) {
|
|
@@ -668,10 +657,10 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
668
657
|
fixture: null
|
|
669
658
|
}
|
|
670
659
|
});
|
|
671
|
-
require_sse_writer.writeErrorResponse(res, 400,
|
|
660
|
+
require_sse_writer.writeErrorResponse(res, 400, require_errors.renderError("bedrock", "validation", {
|
|
672
661
|
message: "Request body must be a JSON object",
|
|
673
662
|
type: "invalid_request_error"
|
|
674
|
-
}
|
|
663
|
+
}).body);
|
|
675
664
|
return;
|
|
676
665
|
}
|
|
677
666
|
const messageError = !Array.isArray(converseReq.messages) ? "Invalid request: messages array is required" : validateConverseMessages(converseReq) ?? validateConverseSystem(converseReq) ?? validateConverseTools(converseReq);
|
|
@@ -686,10 +675,10 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
686
675
|
fixture: null
|
|
687
676
|
}
|
|
688
677
|
});
|
|
689
|
-
require_sse_writer.writeErrorResponse(res, 400,
|
|
678
|
+
require_sse_writer.writeErrorResponse(res, 400, require_errors.renderError("bedrock", "validation", {
|
|
690
679
|
message: messageError,
|
|
691
680
|
type: "invalid_request_error"
|
|
692
|
-
}
|
|
681
|
+
}).body);
|
|
693
682
|
return;
|
|
694
683
|
}
|
|
695
684
|
const completionReq = converseToCompletionRequest(converseReq, modelId, logger);
|
|
@@ -707,7 +696,7 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
707
696
|
body: completionReq
|
|
708
697
|
}, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
|
|
709
698
|
if (!fixture) {
|
|
710
|
-
if (
|
|
699
|
+
if (require_policy.resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {
|
|
711
700
|
const strictStatus = 503;
|
|
712
701
|
const strictMessage = require_helpers.strictNoMatchMessage(skippedBySequenceOrTurn);
|
|
713
702
|
logger.error(require_helpers.strictNoMatchLogLine(req.method ?? "POST", urlPath, skippedBySequenceOrTurn));
|
|
@@ -719,13 +708,13 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
719
708
|
response: {
|
|
720
709
|
status: strictStatus,
|
|
721
710
|
fixture: null,
|
|
722
|
-
...
|
|
711
|
+
...require_policy.strictOverrideField(defaults.strict, req.headers)
|
|
723
712
|
}
|
|
724
713
|
});
|
|
725
|
-
require_sse_writer.writeErrorResponse(res, strictStatus,
|
|
714
|
+
require_sse_writer.writeErrorResponse(res, strictStatus, require_errors.renderError("bedrock", "strict-miss", {
|
|
726
715
|
message: strictMessage,
|
|
727
716
|
type: "invalid_request_error"
|
|
728
|
-
}
|
|
717
|
+
}).body);
|
|
729
718
|
return;
|
|
730
719
|
}
|
|
731
720
|
if (defaults.record) {
|
|
@@ -754,13 +743,13 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
754
743
|
response: {
|
|
755
744
|
status: 404,
|
|
756
745
|
fixture: null,
|
|
757
|
-
...
|
|
746
|
+
...require_policy.strictOverrideField(defaults.strict, req.headers)
|
|
758
747
|
}
|
|
759
748
|
});
|
|
760
|
-
require_sse_writer.writeErrorResponse(res, 404,
|
|
749
|
+
require_sse_writer.writeErrorResponse(res, 404, require_errors.renderError("bedrock", "fixture-miss", {
|
|
761
750
|
message: "No fixture matched",
|
|
762
751
|
type: "invalid_request_error"
|
|
763
|
-
}
|
|
752
|
+
}).body);
|
|
764
753
|
return;
|
|
765
754
|
}
|
|
766
755
|
const response = await require_helpers.resolveResponse(fixture, completionReq);
|
|
@@ -800,11 +789,11 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
800
789
|
});
|
|
801
790
|
const type = misbehavior.status === 400 ? "ValidationException" : "InternalServerException";
|
|
802
791
|
res.setHeader("x-amzn-errortype", type);
|
|
803
|
-
require_sse_writer.writeErrorResponse(res, misbehavior.status,
|
|
804
|
-
|
|
792
|
+
require_sse_writer.writeErrorResponse(res, misbehavior.status, require_errors.renderError("bedrock-converse", "misbehavior", {
|
|
793
|
+
type,
|
|
805
794
|
message: misbehavior.message,
|
|
806
795
|
code: misbehavior.code
|
|
807
|
-
}));
|
|
796
|
+
}).body);
|
|
808
797
|
return;
|
|
809
798
|
}
|
|
810
799
|
if (misbehavior.kind === "applied") {
|
|
@@ -837,20 +826,16 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
837
826
|
fixture
|
|
838
827
|
}
|
|
839
828
|
});
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
message: response.error.message
|
|
845
|
-
}
|
|
846
|
-
};
|
|
847
|
-
require_sse_writer.writeErrorResponse(res, status, JSON.stringify(errBody), { retryAfter: response.retryAfter });
|
|
829
|
+
require_sse_writer.writeErrorResponse(res, status, require_errors.renderError("bedrock", "fixture", {
|
|
830
|
+
message: response.error.message,
|
|
831
|
+
type: response.error.type ?? "invalid_request_error"
|
|
832
|
+
}).body, { retryAfter: response.retryAfter });
|
|
848
833
|
return;
|
|
849
834
|
}
|
|
850
835
|
if (require_helpers.isCombinedFixtureResponse(response)) {
|
|
851
836
|
if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Bedrock Converse API — ignoring");
|
|
852
837
|
const overrides = require_helpers.extractOverrides(response);
|
|
853
|
-
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model,
|
|
838
|
+
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression, logger);
|
|
854
839
|
addResponseEntry({
|
|
855
840
|
method: req.method ?? "POST",
|
|
856
841
|
path: urlPath,
|
|
@@ -870,7 +855,7 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
870
855
|
if (require_helpers.isTextResponse(response)) {
|
|
871
856
|
if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Bedrock Converse API — ignoring");
|
|
872
857
|
const overrides = require_helpers.extractOverrides(response);
|
|
873
|
-
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model,
|
|
858
|
+
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression, logger);
|
|
874
859
|
addResponseEntry({
|
|
875
860
|
method: req.method ?? "POST",
|
|
876
861
|
path: urlPath,
|
|
@@ -889,7 +874,7 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
889
874
|
if (require_helpers.isToolCallResponse(response)) {
|
|
890
875
|
if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Bedrock Converse API — ignoring");
|
|
891
876
|
const overrides = require_helpers.extractOverrides(response);
|
|
892
|
-
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model,
|
|
877
|
+
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression, logger);
|
|
893
878
|
addResponseEntry({
|
|
894
879
|
method: req.method ?? "POST",
|
|
895
880
|
path: urlPath,
|
|
@@ -915,10 +900,10 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
915
900
|
fixture
|
|
916
901
|
}
|
|
917
902
|
});
|
|
918
|
-
require_sse_writer.writeErrorResponse(res, 500,
|
|
903
|
+
require_sse_writer.writeErrorResponse(res, 500, require_errors.renderError("bedrock", "unknown-response", {
|
|
919
904
|
message: "Fixture response did not match any known type",
|
|
920
905
|
type: "server_error"
|
|
921
|
-
}
|
|
906
|
+
}).body);
|
|
922
907
|
}
|
|
923
908
|
async function handleConverseStream(req, res, raw, modelId, fixtures, journal, defaults, setCorsHeaders) {
|
|
924
909
|
const { logger } = defaults;
|
|
@@ -939,10 +924,10 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
939
924
|
fixture: null
|
|
940
925
|
}
|
|
941
926
|
});
|
|
942
|
-
require_sse_writer.writeErrorResponse(res, 400,
|
|
927
|
+
require_sse_writer.writeErrorResponse(res, 400, require_errors.renderError("bedrock", "validation", {
|
|
943
928
|
message: `Malformed JSON: ${detail}`,
|
|
944
929
|
type: "invalid_request_error"
|
|
945
|
-
}
|
|
930
|
+
}).body);
|
|
946
931
|
return;
|
|
947
932
|
}
|
|
948
933
|
if (!require_helpers.isJsonObject(converseReq)) {
|
|
@@ -956,10 +941,10 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
956
941
|
fixture: null
|
|
957
942
|
}
|
|
958
943
|
});
|
|
959
|
-
require_sse_writer.writeErrorResponse(res, 400,
|
|
944
|
+
require_sse_writer.writeErrorResponse(res, 400, require_errors.renderError("bedrock", "validation", {
|
|
960
945
|
message: "Request body must be a JSON object",
|
|
961
946
|
type: "invalid_request_error"
|
|
962
|
-
}
|
|
947
|
+
}).body);
|
|
963
948
|
return;
|
|
964
949
|
}
|
|
965
950
|
const messageError = !Array.isArray(converseReq.messages) ? "Invalid request: messages array is required" : validateConverseMessages(converseReq) ?? validateConverseSystem(converseReq) ?? validateConverseTools(converseReq);
|
|
@@ -974,10 +959,10 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
974
959
|
fixture: null
|
|
975
960
|
}
|
|
976
961
|
});
|
|
977
|
-
require_sse_writer.writeErrorResponse(res, 400,
|
|
962
|
+
require_sse_writer.writeErrorResponse(res, 400, require_errors.renderError("bedrock", "validation", {
|
|
978
963
|
message: messageError,
|
|
979
964
|
type: "invalid_request_error"
|
|
980
|
-
}
|
|
965
|
+
}).body);
|
|
981
966
|
return;
|
|
982
967
|
}
|
|
983
968
|
const completionReq = converseToCompletionRequest(converseReq, modelId, logger);
|
|
@@ -996,7 +981,7 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
996
981
|
body: completionReq
|
|
997
982
|
}, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
|
|
998
983
|
if (!fixture) {
|
|
999
|
-
if (
|
|
984
|
+
if (require_policy.resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {
|
|
1000
985
|
const strictStatus = 503;
|
|
1001
986
|
const strictMessage = require_helpers.strictNoMatchMessage(skippedBySequenceOrTurn);
|
|
1002
987
|
logger.error(require_helpers.strictNoMatchLogLine(req.method ?? "POST", urlPath, skippedBySequenceOrTurn));
|
|
@@ -1008,13 +993,13 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
1008
993
|
response: {
|
|
1009
994
|
status: strictStatus,
|
|
1010
995
|
fixture: null,
|
|
1011
|
-
...
|
|
996
|
+
...require_policy.strictOverrideField(defaults.strict, req.headers)
|
|
1012
997
|
}
|
|
1013
998
|
});
|
|
1014
|
-
require_sse_writer.writeErrorResponse(res, strictStatus,
|
|
999
|
+
require_sse_writer.writeErrorResponse(res, strictStatus, require_errors.renderError("bedrock", "strict-miss", {
|
|
1015
1000
|
message: strictMessage,
|
|
1016
1001
|
type: "invalid_request_error"
|
|
1017
|
-
}
|
|
1002
|
+
}).body);
|
|
1018
1003
|
return;
|
|
1019
1004
|
}
|
|
1020
1005
|
if (defaults.record) {
|
|
@@ -1043,13 +1028,13 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
1043
1028
|
response: {
|
|
1044
1029
|
status: 404,
|
|
1045
1030
|
fixture: null,
|
|
1046
|
-
...
|
|
1031
|
+
...require_policy.strictOverrideField(defaults.strict, req.headers)
|
|
1047
1032
|
}
|
|
1048
1033
|
});
|
|
1049
|
-
require_sse_writer.writeErrorResponse(res, 404,
|
|
1034
|
+
require_sse_writer.writeErrorResponse(res, 404, require_errors.renderError("bedrock", "fixture-miss", {
|
|
1050
1035
|
message: "No fixture matched",
|
|
1051
1036
|
type: "invalid_request_error"
|
|
1052
|
-
}
|
|
1037
|
+
}).body);
|
|
1053
1038
|
return;
|
|
1054
1039
|
}
|
|
1055
1040
|
const response = await require_helpers.resolveResponse(fixture, completionReq);
|
|
@@ -1089,11 +1074,11 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
1089
1074
|
});
|
|
1090
1075
|
const type = misbehavior.status === 400 ? "ValidationException" : "InternalServerException";
|
|
1091
1076
|
res.setHeader("x-amzn-errortype", type);
|
|
1092
|
-
require_sse_writer.writeErrorResponse(res, misbehavior.status,
|
|
1093
|
-
|
|
1077
|
+
require_sse_writer.writeErrorResponse(res, misbehavior.status, require_errors.renderError("bedrock-converse", "misbehavior", {
|
|
1078
|
+
type,
|
|
1094
1079
|
message: misbehavior.message,
|
|
1095
1080
|
code: misbehavior.code
|
|
1096
|
-
}));
|
|
1081
|
+
}).body);
|
|
1097
1082
|
return;
|
|
1098
1083
|
}
|
|
1099
1084
|
const latency = fixture.latency ?? defaults.latency;
|
|
@@ -1140,20 +1125,16 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
1140
1125
|
fixture
|
|
1141
1126
|
}
|
|
1142
1127
|
});
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
message: response.error.message
|
|
1148
|
-
}
|
|
1149
|
-
};
|
|
1150
|
-
require_sse_writer.writeErrorResponse(res, status, JSON.stringify(errBody), { retryAfter: response.retryAfter });
|
|
1128
|
+
require_sse_writer.writeErrorResponse(res, status, require_errors.renderError("bedrock", "fixture", {
|
|
1129
|
+
message: response.error.message,
|
|
1130
|
+
type: response.error.type ?? "invalid_request_error"
|
|
1131
|
+
}).body, { retryAfter: response.retryAfter });
|
|
1151
1132
|
return;
|
|
1152
1133
|
}
|
|
1153
1134
|
if (require_helpers.isCombinedFixtureResponse(response)) {
|
|
1154
1135
|
if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Bedrock Converse API — ignoring");
|
|
1155
1136
|
const overrides = require_helpers.extractOverrides(response);
|
|
1156
|
-
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model,
|
|
1137
|
+
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression, logger);
|
|
1157
1138
|
const journalEntry = addResponseEntry({
|
|
1158
1139
|
method: req.method ?? "POST",
|
|
1159
1140
|
path: urlPath,
|
|
@@ -1185,7 +1166,7 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
1185
1166
|
if (require_helpers.isTextResponse(response)) {
|
|
1186
1167
|
if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Bedrock Converse API — ignoring");
|
|
1187
1168
|
const overrides = require_helpers.extractOverrides(response);
|
|
1188
|
-
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model,
|
|
1169
|
+
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression, logger);
|
|
1189
1170
|
const journalEntry = addResponseEntry({
|
|
1190
1171
|
method: req.method ?? "POST",
|
|
1191
1172
|
path: urlPath,
|
|
@@ -1216,7 +1197,7 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
1216
1197
|
if (require_helpers.isToolCallResponse(response)) {
|
|
1217
1198
|
if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Bedrock Converse API — ignoring");
|
|
1218
1199
|
const overrides = require_helpers.extractOverrides(response);
|
|
1219
|
-
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model,
|
|
1200
|
+
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression, logger);
|
|
1220
1201
|
const journalEntry = addResponseEntry({
|
|
1221
1202
|
method: req.method ?? "POST",
|
|
1222
1203
|
path: urlPath,
|
|
@@ -1254,10 +1235,10 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
1254
1235
|
fixture
|
|
1255
1236
|
}
|
|
1256
1237
|
});
|
|
1257
|
-
require_sse_writer.writeErrorResponse(res, 500,
|
|
1238
|
+
require_sse_writer.writeErrorResponse(res, 500, require_errors.renderError("bedrock", "unknown-response", {
|
|
1258
1239
|
message: "Fixture response did not match any known type",
|
|
1259
1240
|
type: "server_error"
|
|
1260
|
-
}
|
|
1241
|
+
}).body);
|
|
1261
1242
|
}
|
|
1262
1243
|
|
|
1263
1244
|
//#endregion
|