@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/batches.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batches.cjs","names":["applyChaosAsync","flattenHeaders","getStoredFileBytes","isJsonObject","buildTextResponse","generateId","FILES_MAX_BYTES","storeFile","readMetadata","paginate"],"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.cjs","names":["buildOpenAIError","applyChaosAsync","flattenHeaders","getStoredFileBytes","isJsonObject","buildTextResponse","generateId","FILES_MAX_BYTES","storeFile","readMetadata","paginate"],"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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"}
|
package/dist/batches.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batches.d.cts","names":[],"sources":["../src/batches.ts"],"sourcesContent":[],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"batches.d.cts","names":[],"sources":["../src/batches.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAslBO,UA1hBU,WAAA,CA0hBL;MACL,MAAK;QAED,EAAA,OAAA;UACC,EAAA,MAAA;eACiB,EAAA,MAAA;mBAC1B,EAAA,MAAA;EAAO,MAAA,EA1hBA,WA0hBA;;aAxhBG;;;;;;;;;;;UASsB;;;;;;;;;KAKvB,WAAA;;UAUK,UAAA;;;;;;iBAqCD,eAAA,CAAA;KAsCX,QAAA;UAAqB;UAAgB;aAA0B;;iBA0Q9C,mBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,sCAED,mBACC,gCACY,MAAA,CAAK,0BAC1B;iBAmGmB,iBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,yBACD,mBACC,gCACY,MAAA,CAAK,0BAC1B;iBA+BmB,qBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,0CAED,mBACC,gCACY,MAAA,CAAK,0BAC1B;iBAcmB,mBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,0CAED,mBACC,gCACY,MAAA,CAAK,0BAC1B"}
|
package/dist/batches.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batches.d.ts","names":[],"sources":["../src/batches.ts"],"sourcesContent":[],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"batches.d.ts","names":[],"sources":["../src/batches.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAslBO,UA1hBU,WAAA,CA0hBL;MACL,MAAK;QAED,EAAA,OAAA;UACC,EAAA,MAAA;eACiB,EAAA,MAAA;mBAC1B,EAAA,MAAA;EAAO,MAAA,EA1hBA,WA0hBA;;aAxhBG;;;;;;;;;;;UASsB;;;;;;;;;KAKvB,WAAA;;UAUK,UAAA;;;;;;iBAqCD,eAAA,CAAA;KAsCX,QAAA;UAAqB;UAAgB;aAA0B;;iBA0Q9C,mBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,sCAED,mBACC,gCACY,MAAA,CAAK,0BAC1B;iBAmGmB,iBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,yBACD,mBACC,gCACY,MAAA,CAAK,0BAC1B;iBA+BmB,qBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,0CAED,mBACC,gCACY,MAAA,CAAK,0BAC1B;iBAcmB,mBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,0CAED,mBACC,gCACY,MAAA,CAAK,0BAC1B"}
|
package/dist/batches.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildOpenAIError } from "./response/errors.js";
|
|
1
2
|
import { flattenHeaders, generateId, isJsonObject } from "./helpers.js";
|
|
2
3
|
import { applyChaosAsync } from "./chaos.js";
|
|
3
4
|
import { buildTextResponse } from "./responses.js";
|
|
@@ -61,10 +62,7 @@ function writeJson(res, status, payload, setCorsHeaders) {
|
|
|
61
62
|
res.end(JSON.stringify(payload));
|
|
62
63
|
}
|
|
63
64
|
function invalid(message) {
|
|
64
|
-
return {
|
|
65
|
-
message,
|
|
66
|
-
type: "invalid_request_error"
|
|
67
|
-
} };
|
|
65
|
+
return buildOpenAIError("service-validation", { message });
|
|
68
66
|
}
|
|
69
67
|
/**
|
|
70
68
|
* Roll the chaos dice for one batches request. Returns true when a fault
|
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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"}
|