@copilotkit/aimock 1.42.1 → 1.44.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 +65 -0
- package/README.md +108 -16
- package/dist/agui-handler.d.ts.map +1 -1
- package/dist/agui-mock.cjs +1 -1
- 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 +1 -1
- package/dist/agui-mock.js.map +1 -1
- package/dist/agui-recorder.cjs +33 -11
- package/dist/agui-recorder.cjs.map +1 -1
- package/dist/agui-recorder.d.cts +10 -1
- package/dist/agui-recorder.d.cts.map +1 -1
- package/dist/agui-recorder.d.ts +10 -1
- package/dist/agui-recorder.d.ts.map +1 -1
- package/dist/agui-recorder.js +33 -11
- package/dist/agui-recorder.js.map +1 -1
- package/dist/agui-types.d.cts +1 -0
- package/dist/agui-types.d.cts.map +1 -1
- package/dist/agui-types.d.ts +1 -0
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/aimock-cli.cjs +38 -10
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.d.cts +5 -0
- package/dist/aimock-cli.d.cts.map +1 -1
- package/dist/aimock-cli.d.ts +5 -0
- package/dist/aimock-cli.d.ts.map +1 -1
- package/dist/aimock-cli.js +38 -10
- package/dist/aimock-cli.js.map +1 -1
- package/dist/batches.cjs +458 -0
- package/dist/batches.cjs.map +1 -0
- package/dist/batches.d.cts +57 -0
- package/dist/batches.d.cts.map +1 -0
- package/dist/batches.d.ts +57 -0
- package/dist/batches.d.ts.map +1 -0
- package/dist/batches.js +453 -0
- package/dist/batches.js.map +1 -0
- package/dist/bedrock-converse.cjs +44 -12
- 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 +46 -14
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +10 -8
- 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 +12 -10
- package/dist/bedrock.js.map +1 -1
- package/dist/byteplus-video.cjs +3 -3
- package/dist/byteplus-video.cjs.map +1 -1
- package/dist/byteplus-video.js +4 -4
- package/dist/byteplus-video.js.map +1 -1
- package/dist/chaos.cjs +625 -72
- package/dist/chaos.cjs.map +1 -1
- package/dist/chaos.d.cts +164 -7
- package/dist/chaos.d.cts.map +1 -1
- package/dist/chaos.d.ts +164 -7
- package/dist/chaos.d.ts.map +1 -1
- package/dist/chaos.js +616 -74
- package/dist/chaos.js.map +1 -1
- package/dist/cli.cjs +131 -30
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +132 -31
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +37 -9
- 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 +39 -11
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.cjs +1 -1
- package/dist/config-loader.js +1 -1
- package/dist/constants.cjs +8 -0
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +6 -2
- package/dist/constants.d.ts +6 -2
- package/dist/constants.js +8 -1
- package/dist/constants.js.map +1 -1
- package/dist/convert.cjs +1 -1
- package/dist/convert.cjs.map +1 -1
- package/dist/convert.js +1 -1
- package/dist/convert.js.map +1 -1
- package/dist/echo-text.cjs +65 -0
- package/dist/echo-text.cjs.map +1 -0
- package/dist/echo-text.d.cts +22 -0
- package/dist/echo-text.d.cts.map +1 -0
- package/dist/echo-text.d.ts +22 -0
- package/dist/echo-text.d.ts.map +1 -0
- package/dist/echo-text.js +65 -0
- package/dist/echo-text.js.map +1 -0
- package/dist/elevenlabs-audio.cjs +3 -3
- package/dist/elevenlabs-audio.cjs.map +1 -1
- package/dist/elevenlabs-audio.js +4 -4
- package/dist/elevenlabs-audio.js.map +1 -1
- package/dist/elevenlabs-voice.cjs +1053 -0
- package/dist/elevenlabs-voice.cjs.map +1 -0
- package/dist/elevenlabs-voice.d.cts +72 -0
- package/dist/elevenlabs-voice.d.cts.map +1 -0
- package/dist/elevenlabs-voice.d.ts +72 -0
- package/dist/elevenlabs-voice.d.ts.map +1 -0
- package/dist/elevenlabs-voice.js +1048 -0
- package/dist/elevenlabs-voice.js.map +1 -0
- package/dist/embeddings.cjs +26 -5
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.d.cts +1 -1
- package/dist/embeddings.d.cts.map +1 -1
- package/dist/embeddings.d.ts +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +27 -6
- package/dist/embeddings.js.map +1 -1
- package/dist/fal-audio.cjs +3 -3
- package/dist/fal-audio.cjs.map +1 -1
- package/dist/fal-audio.js +4 -4
- package/dist/fal-audio.js.map +1 -1
- package/dist/fal.cjs +28 -4
- package/dist/fal.cjs.map +1 -1
- package/dist/fal.d.cts +16 -1
- package/dist/fal.d.cts.map +1 -1
- package/dist/fal.d.ts +16 -1
- package/dist/fal.d.ts.map +1 -1
- package/dist/fal.js +28 -5
- package/dist/fal.js.map +1 -1
- package/dist/files.cjs +1069 -0
- package/dist/files.cjs.map +1 -0
- package/dist/files.d.cts +68 -0
- package/dist/files.d.cts.map +1 -0
- package/dist/files.d.ts +68 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +1056 -0
- package/dist/files.js.map +1 -0
- package/dist/fine-tuning.cjs +1033 -0
- package/dist/fine-tuning.cjs.map +1 -0
- package/dist/fine-tuning.d.cts +96 -0
- package/dist/fine-tuning.d.cts.map +1 -0
- package/dist/fine-tuning.d.ts +96 -0
- package/dist/fine-tuning.d.ts.map +1 -0
- package/dist/fine-tuning.js +1026 -0
- package/dist/fine-tuning.js.map +1 -0
- package/dist/fixture-loader-services.cjs +116 -0
- package/dist/fixture-loader-services.cjs.map +1 -0
- package/dist/fixture-loader-services.d.cts +48 -0
- package/dist/fixture-loader-services.d.cts.map +1 -0
- package/dist/fixture-loader-services.d.ts +48 -0
- package/dist/fixture-loader-services.d.ts.map +1 -0
- package/dist/fixture-loader-services.js +114 -0
- package/dist/fixture-loader-services.js.map +1 -0
- package/dist/fixture-loader.cjs +0 -0
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +131 -5
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +131 -5
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +0 -0
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-embeddings.cjs +44 -4
- package/dist/gemini-embeddings.cjs.map +1 -1
- package/dist/gemini-embeddings.js +46 -6
- package/dist/gemini-embeddings.js.map +1 -1
- package/dist/gemini-interactions.cjs +51 -7
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +53 -9
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +19 -8
- 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 +21 -10
- package/dist/gemini.js.map +1 -1
- package/dist/grok-video.cjs +14 -14
- package/dist/grok-video.cjs.map +1 -1
- package/dist/grok-video.js +15 -15
- package/dist/grok-video.js.map +1 -1
- package/dist/helpers.cjs +497 -10
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +38 -2
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +38 -2
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +487 -12
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +57 -125
- 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 +58 -126
- package/dist/images.js.map +1 -1
- package/dist/index.cjs +71 -9
- package/dist/index.d.cts +17 -7
- package/dist/index.d.ts +17 -7
- package/dist/index.js +21 -11
- package/dist/jest.cjs +24 -12
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +6 -3
- package/dist/jest.d.cts.map +1 -1
- package/dist/jest.d.ts +6 -3
- package/dist/jest.d.ts.map +1 -1
- package/dist/jest.js +25 -13
- package/dist/jest.js.map +1 -1
- package/dist/journal.cjs +61 -4
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts +60 -3
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts +60 -3
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +61 -5
- package/dist/journal.js.map +1 -1
- package/dist/live-fixture.cjs +1192 -0
- package/dist/live-fixture.cjs.map +1 -0
- package/dist/live-fixture.js +1187 -0
- package/dist/live-fixture.js.map +1 -0
- package/dist/live-recorder.cjs +256 -0
- package/dist/live-recorder.cjs.map +1 -0
- package/dist/live-recorder.js +254 -0
- package/dist/live-recorder.js.map +1 -0
- package/dist/live-replay.cjs +286 -0
- package/dist/live-replay.cjs.map +1 -0
- package/dist/live-replay.js +285 -0
- package/dist/live-replay.js.map +1 -0
- package/dist/live-sanitize.cjs +196 -0
- package/dist/live-sanitize.cjs.map +1 -0
- package/dist/live-sanitize.js +196 -0
- package/dist/live-sanitize.js.map +1 -0
- package/dist/live-types.d.cts +71 -0
- package/dist/live-types.d.cts.map +1 -0
- package/dist/live-types.d.ts +71 -0
- package/dist/live-types.d.ts.map +1 -0
- package/dist/llmock.cjs +132 -49
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +51 -5
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +51 -5
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +133 -50
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-fakes-mount.cjs +216 -0
- package/dist/mcp-fakes-mount.cjs.map +1 -0
- package/dist/mcp-fakes-mount.js +210 -0
- package/dist/mcp-fakes-mount.js.map +1 -0
- package/dist/mcp-fakes.cjs +1619 -0
- package/dist/mcp-fakes.cjs.map +1 -0
- package/dist/mcp-fakes.d.cts +101 -0
- package/dist/mcp-fakes.d.cts.map +1 -0
- package/dist/mcp-fakes.d.ts +101 -0
- package/dist/mcp-fakes.d.ts.map +1 -0
- package/dist/mcp-fakes.js +1614 -0
- package/dist/mcp-fakes.js.map +1 -0
- package/dist/mcp-handler.cjs +331 -17
- package/dist/mcp-handler.cjs.map +1 -1
- package/dist/mcp-handler.js +330 -18
- package/dist/mcp-handler.js.map +1 -1
- package/dist/mcp-mock.cjs +217 -49
- package/dist/mcp-mock.cjs.map +1 -1
- package/dist/mcp-mock.d.cts +66 -1
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts +66 -1
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +218 -50
- package/dist/mcp-mock.js.map +1 -1
- package/dist/mcp-stub.cjs +5 -1
- package/dist/mcp-stub.d.cts +3 -1
- package/dist/mcp-stub.d.ts +3 -1
- package/dist/mcp-stub.js +3 -1
- package/dist/mcp-types.d.cts +6 -0
- package/dist/mcp-types.d.ts +6 -0
- package/dist/message-text.cjs +192 -0
- package/dist/message-text.cjs.map +1 -0
- package/dist/message-text.d.cts +31 -0
- package/dist/message-text.d.cts.map +1 -0
- package/dist/message-text.d.ts +31 -0
- package/dist/message-text.d.ts.map +1 -0
- package/dist/message-text.js +184 -0
- package/dist/message-text.js.map +1 -0
- package/dist/messages.cjs +49 -7
- 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 +51 -9
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +278 -45
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts +17 -7
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts +17 -7
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +270 -29
- package/dist/metrics.js.map +1 -1
- package/dist/moderation.cjs +10 -1
- package/dist/moderation.cjs.map +1 -1
- package/dist/moderation.d.cts.map +1 -1
- package/dist/moderation.d.ts.map +1 -1
- package/dist/moderation.js +10 -1
- package/dist/moderation.js.map +1 -1
- package/dist/ollama.cjs +10 -6
- 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 +11 -7
- package/dist/ollama.js.map +1 -1
- package/dist/openapi.cjs +3405 -0
- package/dist/openapi.cjs.map +1 -0
- package/dist/openapi.d.cts +30 -0
- package/dist/openapi.d.cts.map +1 -0
- package/dist/openapi.d.ts +30 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +3404 -0
- package/dist/openapi.js.map +1 -0
- package/dist/openrouter-video.cjs +9 -9
- 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 +10 -10
- package/dist/openrouter-video.js.map +1 -1
- package/dist/recorder.cjs +54 -10
- 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 +52 -12
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +47 -7
- 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 +48 -9
- package/dist/responses.js.map +1 -1
- package/dist/route-registry.cjs +951 -0
- package/dist/route-registry.cjs.map +1 -0
- package/dist/route-registry.d.cts +33 -0
- package/dist/route-registry.d.cts.map +1 -0
- package/dist/route-registry.d.ts +33 -0
- package/dist/route-registry.d.ts.map +1 -0
- package/dist/route-registry.js +866 -0
- package/dist/route-registry.js.map +1 -0
- package/dist/router.cjs +21 -34
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts +4 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts +4 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +21 -35
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +1252 -777
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +5 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +5 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1185 -710
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +6 -4
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.js +7 -5
- package/dist/speech.js.map +1 -1
- package/dist/suite.cjs +1 -1
- package/dist/suite.js +1 -1
- package/dist/transcription.cjs +6 -6
- 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 +7 -7
- package/dist/transcription.js.map +1 -1
- package/dist/types.cjs +18 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +320 -17
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +320 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/dist/validate-cli.cjs +983 -0
- package/dist/validate-cli.cjs.map +1 -0
- package/dist/validate-cli.js +982 -0
- package/dist/validate-cli.js.map +1 -0
- package/dist/vector-stores.cjs +1180 -0
- package/dist/vector-stores.cjs.map +1 -0
- package/dist/vector-stores.d.cts +106 -0
- package/dist/vector-stores.d.cts.map +1 -0
- package/dist/vector-stores.d.ts +106 -0
- package/dist/vector-stores.d.ts.map +1 -0
- package/dist/vector-stores.js +1164 -0
- package/dist/vector-stores.js.map +1 -0
- package/dist/vector-types.d.cts.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/veo-video.cjs +13 -13
- package/dist/veo-video.cjs.map +1 -1
- package/dist/veo-video.js +14 -14
- package/dist/veo-video.js.map +1 -1
- package/dist/video.cjs +23 -4
- package/dist/video.cjs.map +1 -1
- package/dist/video.d.cts +1 -1
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts +1 -1
- package/dist/video.d.ts.map +1 -1
- package/dist/video.js +24 -5
- package/dist/video.js.map +1 -1
- package/dist/vitest.cjs +23 -11
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +5 -2
- package/dist/vitest.d.cts.map +1 -1
- package/dist/vitest.d.ts +5 -2
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +25 -13
- package/dist/vitest.js.map +1 -1
- package/dist/watcher.cjs +2 -1
- package/dist/watcher.cjs.map +1 -1
- package/dist/watcher.js +2 -1
- package/dist/watcher.js.map +1 -1
- package/dist/ws-framing.cjs +150 -35
- package/dist/ws-framing.cjs.map +1 -1
- package/dist/ws-framing.d.cts +18 -3
- package/dist/ws-framing.d.cts.map +1 -1
- package/dist/ws-framing.d.ts +18 -3
- package/dist/ws-framing.d.ts.map +1 -1
- package/dist/ws-framing.js +151 -36
- package/dist/ws-framing.js.map +1 -1
- package/dist/ws-gemini-live.cjs +2 -2
- package/dist/ws-gemini-live.js +2 -2
- package/dist/ws-live.cjs +401 -0
- package/dist/ws-live.cjs.map +1 -0
- package/dist/ws-live.js +400 -0
- package/dist/ws-live.js.map +1 -0
- package/dist/ws-realtime.cjs +46 -2
- 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 +46 -2
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +16 -4
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.js +16 -4
- package/dist/ws-responses.js.map +1 -1
- package/dist/ws-upstream.cjs +118 -0
- package/dist/ws-upstream.cjs.map +1 -0
- package/dist/ws-upstream.js +115 -0
- package/dist/ws-upstream.js.map +1 -0
- package/fixtures/openai-live-client.json +390 -0
- package/fixtures/openai-live-managed.json +709 -0
- package/package.json +4 -1
package/dist/server.cjs
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
2
|
const require_constants = require('./constants.cjs');
|
|
3
|
+
const require_message_text = require('./message-text.cjs');
|
|
3
4
|
const require_api_key_auth = require('./api-key-auth.cjs');
|
|
4
5
|
const require_helpers = require('./helpers.cjs');
|
|
6
|
+
const require_live_fixture = require('./live-fixture.cjs');
|
|
7
|
+
const require_sse_writer = require('./sse-writer.cjs');
|
|
8
|
+
const require_chaos = require('./chaos.cjs');
|
|
9
|
+
const require_fixture_loader = require('./fixture-loader.cjs');
|
|
10
|
+
const require_router = require('./router.cjs');
|
|
11
|
+
const require_recorder = require('./recorder.cjs');
|
|
5
12
|
const require_logger = require('./logger.cjs');
|
|
6
13
|
const require_journal = require('./journal.cjs');
|
|
7
|
-
const require_router = require('./router.cjs');
|
|
8
|
-
const require_fixture_loader = require('./fixture-loader.cjs');
|
|
9
|
-
const require_sse_writer = require('./sse-writer.cjs');
|
|
10
14
|
const require_interruption = require('./interruption.cjs');
|
|
11
15
|
const require_openrouter_chat = require('./openrouter-chat.cjs');
|
|
12
|
-
const require_chaos = require('./chaos.cjs');
|
|
13
|
-
const require_recorder = require('./recorder.cjs');
|
|
14
16
|
const require_responses = require('./responses.cjs');
|
|
15
17
|
const require_messages = require('./messages.cjs');
|
|
16
18
|
const require_gemini = require('./gemini.cjs');
|
|
@@ -30,46 +32,33 @@ const require_veo_video = require('./veo-video.cjs');
|
|
|
30
32
|
const require_grok_video = require('./grok-video.cjs');
|
|
31
33
|
const require_byteplus_video = require('./byteplus-video.cjs');
|
|
32
34
|
const require_elevenlabs_audio = require('./elevenlabs-audio.cjs');
|
|
35
|
+
const require_elevenlabs_voice = require('./elevenlabs-voice.cjs');
|
|
33
36
|
const require_ollama = require('./ollama.cjs');
|
|
34
37
|
const require_cohere = require('./cohere.cjs');
|
|
35
38
|
const require_search = require('./search.cjs');
|
|
36
39
|
const require_rerank = require('./rerank.cjs');
|
|
37
40
|
const require_moderation = require('./moderation.cjs');
|
|
41
|
+
const require_fine_tuning = require('./fine-tuning.cjs');
|
|
42
|
+
const require_vector_stores = require('./vector-stores.cjs');
|
|
43
|
+
const require_files = require('./files.cjs');
|
|
44
|
+
const require_batches = require('./batches.cjs');
|
|
38
45
|
const require_ws_framing = require('./ws-framing.cjs');
|
|
46
|
+
const require_ws_live = require('./ws-live.cjs');
|
|
39
47
|
const require_ws_responses = require('./ws-responses.cjs');
|
|
40
48
|
const require_ws_realtime = require('./ws-realtime.cjs');
|
|
41
49
|
const require_ws_gemini_live = require('./ws-gemini-live.cjs');
|
|
50
|
+
const require_route_registry = require('./route-registry.cjs');
|
|
51
|
+
const require_openapi = require('./openapi.cjs');
|
|
42
52
|
const require_metrics = require('./metrics.cjs');
|
|
53
|
+
const require_mcp_fakes = require('./mcp-fakes.cjs');
|
|
54
|
+
const require_mcp_mock = require('./mcp-mock.cjs');
|
|
55
|
+
const require_mcp_fakes_mount = require('./mcp-fakes-mount.cjs');
|
|
43
56
|
let node_http = require("node:http");
|
|
44
57
|
node_http = require_runtime.__toESM(node_http);
|
|
58
|
+
let node_async_hooks = require("node:async_hooks");
|
|
45
59
|
|
|
46
60
|
//#region src/server.ts
|
|
47
|
-
const
|
|
48
|
-
const RESPONSES_PATH = "/v1/responses";
|
|
49
|
-
const REALTIME_PATH = "/v1/realtime";
|
|
50
|
-
const GEMINI_LIVE_PATH = "/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent";
|
|
51
|
-
const MESSAGES_PATH = "/v1/messages";
|
|
52
|
-
const EMBEDDINGS_PATH = "/v1/embeddings";
|
|
53
|
-
const COHERE_CHAT_PATH = "/v2/chat";
|
|
54
|
-
const COHERE_EMBED_PATH = "/v2/embed";
|
|
55
|
-
const SEARCH_PATH = "/search";
|
|
56
|
-
const RERANK_PATH = "/v2/rerank";
|
|
57
|
-
const MODERATIONS_PATH = "/v1/moderations";
|
|
58
|
-
const IMAGES_PATH = "/v1/images/generations";
|
|
59
|
-
const IMAGES_EDIT_PATH = "/v1/images/edits";
|
|
60
|
-
const IMAGES_VARIATIONS_PATH = "/v1/images/variations";
|
|
61
|
-
const SPEECH_PATH = "/v1/audio/speech";
|
|
62
|
-
const TRANSCRIPTIONS_PATH = "/v1/audio/transcriptions";
|
|
63
|
-
const TRANSLATIONS_PATH = "/v1/audio/translations";
|
|
64
|
-
const VIDEOS_PATH = "/v1/videos";
|
|
65
|
-
const GEMINI_PREDICT_RE = /^\/v1beta\/models\/([^:]+):predict$/;
|
|
66
|
-
const ELEVENLABS_SOUND_GENERATION_PATH = "/v1/sound-generation";
|
|
67
|
-
const ELEVENLABS_TTS_RE = /^\/v1\/text-to-speech\/([^/]+)$/;
|
|
68
|
-
const ELEVENLABS_MUSIC_RE = /^\/v1\/music(?:\/(.+))?$/;
|
|
69
|
-
const FAL_QUEUE_SUBMIT_RE = /^\/fal\/queue\/submit\/(.+)$/;
|
|
70
|
-
const FAL_QUEUE_REQUESTS_RE = /^\/fal\/queue\/requests\/(.+)$/;
|
|
71
|
-
const FAL_RUN_RE = /^\/fal\/run\/(.+)$/;
|
|
72
|
-
const FAL_PREFIX_RE = /^\/fal(?:\/.*)?$/;
|
|
61
|
+
const liveClosers = /* @__PURE__ */ new WeakMap();
|
|
73
62
|
const DEFAULT_CHUNK_SIZE = 20;
|
|
74
63
|
const COMPAT_SUFFIXES = [
|
|
75
64
|
"/chat/completions",
|
|
@@ -98,26 +87,6 @@ function normalizeCompatPath(pathname, logger) {
|
|
|
98
87
|
}
|
|
99
88
|
return pathname;
|
|
100
89
|
}
|
|
101
|
-
const GEMINI_INTERACTIONS_PATH = "/v1beta/interactions";
|
|
102
|
-
const GEMINI_PATH_RE = /^\/v1beta\/models\/([^:]+):(generateContent|streamGenerateContent)$/;
|
|
103
|
-
const GEMINI_EMBED_RE = /^\/v1beta\/models\/([^:]+):embedContent$/;
|
|
104
|
-
const AZURE_DEPLOYMENT_RE = /^\/openai\/deployments\/([^/]+)\/(chat\/completions|embeddings)$/;
|
|
105
|
-
const BEDROCK_INVOKE_RE = /^\/model\/([^/]+)\/invoke$/;
|
|
106
|
-
const BEDROCK_STREAM_RE = /^\/model\/([^/]+)\/invoke-with-response-stream$/;
|
|
107
|
-
const BEDROCK_CONVERSE_RE = /^\/model\/([^/]+)\/converse$/;
|
|
108
|
-
const BEDROCK_CONVERSE_STREAM_RE = /^\/model\/([^/]+)\/converse-stream$/;
|
|
109
|
-
const VERTEX_AI_RE = /^\/v1\/projects\/[^/]+\/locations\/[^/]+\/publishers\/google\/models\/([^/:]+):(generateContent|streamGenerateContent)$/;
|
|
110
|
-
const OLLAMA_CHAT_PATH = "/api/chat";
|
|
111
|
-
const OLLAMA_GENERATE_PATH = "/api/generate";
|
|
112
|
-
const OLLAMA_EMBEDDINGS_PATH = "/api/embeddings";
|
|
113
|
-
const OLLAMA_EMBED_PATH = "/api/embed";
|
|
114
|
-
const OLLAMA_TAGS_PATH = "/api/tags";
|
|
115
|
-
const OPENROUTER_VIDEOS_PATH = "/api/v1/videos";
|
|
116
|
-
const OPENROUTER_VIDEO_MODELS_PATH = "/api/v1/videos/models";
|
|
117
|
-
const HEALTH_PATH = "/health";
|
|
118
|
-
const READY_PATH = "/ready";
|
|
119
|
-
const MODELS_PATH = "/v1/models";
|
|
120
|
-
const REQUESTS_PATH = "/v1/_requests";
|
|
121
90
|
const DEFAULT_MODELS = [
|
|
122
91
|
"gpt-4",
|
|
123
92
|
"gpt-4o",
|
|
@@ -127,9 +96,9 @@ const DEFAULT_MODELS = [
|
|
|
127
96
|
];
|
|
128
97
|
const CORS_HEADERS = {
|
|
129
98
|
"Access-Control-Allow-Origin": "*",
|
|
130
|
-
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
|
99
|
+
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
131
100
|
"Access-Control-Allow-Headers": "*",
|
|
132
|
-
"Access-Control-Expose-Headers": "X-Total-Count"
|
|
101
|
+
"Access-Control-Expose-Headers": "X-Total-Count, X-Request-Id"
|
|
133
102
|
};
|
|
134
103
|
function setCorsHeaders(res) {
|
|
135
104
|
for (const [key, value] of Object.entries(CORS_HEADERS)) res.setHeader(key, value);
|
|
@@ -146,7 +115,6 @@ function handleNotFound(res, message) {
|
|
|
146
115
|
type: "not_found"
|
|
147
116
|
} }));
|
|
148
117
|
}
|
|
149
|
-
const CONTROL_PREFIX = "/__aimock";
|
|
150
118
|
/** The complete `GET /__aimock/fixtures` query-param vocabulary; anything else 400s. */
|
|
151
119
|
const FIXTURES_PARAMS = new Set(["include"]);
|
|
152
120
|
/** The complete `GET /__aimock/journal` query-param vocabulary; anything else 400s. */
|
|
@@ -157,14 +125,18 @@ const JOURNAL_PARAMS = new Set([
|
|
|
157
125
|
"method",
|
|
158
126
|
"status",
|
|
159
127
|
"service",
|
|
160
|
-
"testId"
|
|
128
|
+
"testId",
|
|
129
|
+
"requestId"
|
|
161
130
|
]);
|
|
162
131
|
/**
|
|
163
132
|
* Perform a full reset: clear the fixtures array, the journal (entries *and*
|
|
164
133
|
* per-test fixture match-counts, i.e. sequence position), the video and fal.ai
|
|
165
|
-
* job/queue state, the
|
|
166
|
-
*
|
|
167
|
-
*
|
|
134
|
+
* job/queue state, the fine-tuning store (jobs, their append-only event logs,
|
|
135
|
+
* their poll counts, the create-time suffixes their model names are built from,
|
|
136
|
+
* and the module's monotonic clock), the ElevenLabs Voice Design store, the
|
|
137
|
+
* Gemini interaction/event-id counters, and any runtime chaos override
|
|
138
|
+
* (reverting to the construction-time chaos config), then re-zero the
|
|
139
|
+
* `aimock_fixtures_loaded` gauge.
|
|
168
140
|
*
|
|
169
141
|
* `targets` is `null` when no server is running (an in-process `reset()` before
|
|
170
142
|
* `start()`). The process-global generation state is reset either way, since it
|
|
@@ -174,19 +146,27 @@ const JOURNAL_PARAMS = new Set([
|
|
|
174
146
|
* `POST /__aimock/reset/fixtures` alias, and `LLMock.reset()`.
|
|
175
147
|
*/
|
|
176
148
|
function performFullReset(fixtures, targets) {
|
|
177
|
-
fixtures
|
|
149
|
+
require_fixture_loader.clearFixtureQueue(fixtures);
|
|
178
150
|
require_fal_audio.falJobs.clear();
|
|
179
151
|
require_fal.falQueueStates.clear();
|
|
152
|
+
require_batches.clearBatchStore();
|
|
153
|
+
require_elevenlabs_voice.clearElevenLabsVoices();
|
|
154
|
+
require_files.clearFileStore();
|
|
155
|
+
require_fine_tuning.clearFineTuningStore();
|
|
156
|
+
require_vector_stores.clearVectorStoreStore();
|
|
180
157
|
require_gemini_interactions.resetInteractionCounter();
|
|
181
158
|
require_gemini_interactions.resetEventIdCounter();
|
|
182
159
|
if (!targets) return;
|
|
160
|
+
liveClosers.get(targets.defaults)?.();
|
|
183
161
|
targets.journal.clear();
|
|
162
|
+
require_chaos.resetChaosWarnings(targets.defaults.logger);
|
|
184
163
|
targets.defaults.chaos = void 0;
|
|
185
164
|
targets.videoStates.clear();
|
|
186
165
|
targets.openRouterVideoJobs.clear();
|
|
187
166
|
targets.veoVideoJobs.clear();
|
|
188
167
|
targets.grokVideoJobs.clear();
|
|
189
168
|
targets.bytePlusVideoJobs.clear();
|
|
169
|
+
for (const { handler } of targets.mounts ?? []) handler.clearMcpFakes?.();
|
|
190
170
|
if (targets.defaults.registry) targets.defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
|
|
191
171
|
}
|
|
192
172
|
/**
|
|
@@ -232,22 +212,23 @@ function fixtureResponseKind(response) {
|
|
|
232
212
|
return "unknown";
|
|
233
213
|
}
|
|
234
214
|
/**
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
215
|
+
* The testId a journal entry was scoped under — `resolveTestId` applied to the
|
|
216
|
+
* journaled headers and path, i.e. the SAME function that scoped the request
|
|
217
|
+
* (fixture match-counts, chaos) when it arrived. Node folds a repeated
|
|
218
|
+
* `X-Test-Id` into one string ("a, a") before either side sees it, and the
|
|
219
|
+
* request is counted and chaos-evaluated under that folded string, so the
|
|
220
|
+
* `?testId=` filter matches it verbatim too: no token-splitting here that the
|
|
221
|
+
* scoping path does not also do. (Splitting on "," additionally broke every
|
|
222
|
+
* legitimately comma-bearing id.) A raw `path.includes("testId=t1")` both
|
|
223
|
+
* prefix-collides with `t10` and matches unrelated params like `notTestId`.
|
|
224
|
+
*
|
|
225
|
+
* An MCP mount records the test id it resolved on the entry (I9, B3): a
|
|
226
|
+
* decoded header or query value, or the one bound to the session at
|
|
227
|
+
* `initialize`. That value wins; LLM entries carry none and are attributed as
|
|
228
|
+
* before.
|
|
241
229
|
*/
|
|
242
230
|
function journalEntryTestId(entry) {
|
|
243
|
-
|
|
244
|
-
if (header) return header;
|
|
245
|
-
const qIdx = entry.path.indexOf("?");
|
|
246
|
-
if (qIdx !== -1) {
|
|
247
|
-
const queryValue = new URLSearchParams(entry.path.slice(qIdx + 1)).get("testId");
|
|
248
|
-
if (queryValue) return queryValue;
|
|
249
|
-
}
|
|
250
|
-
return require_constants.DEFAULT_TEST_ID;
|
|
231
|
+
return entry.testId ?? require_helpers.resolveTestId(entry.headers, entry.path);
|
|
251
232
|
}
|
|
252
233
|
/**
|
|
253
234
|
* The testId a chaos override is stored under, and read back with — resolved by
|
|
@@ -274,27 +255,198 @@ function writeBlankTestId(res) {
|
|
|
274
255
|
res.end(JSON.stringify({ error: "Invalid 'X-Test-Id': header is present but empty" }));
|
|
275
256
|
return true;
|
|
276
257
|
}
|
|
277
|
-
/**
|
|
258
|
+
/**
|
|
259
|
+
* The chaos config in effect for one testId: its override, else the baseline —
|
|
260
|
+
* one or the other, never the two merged. Mirrors `resolveScopedDefaults` in
|
|
261
|
+
* chaos.ts, so what `GET /__aimock/chaos` reports for a scope is exactly what
|
|
262
|
+
* that scope's traffic is evaluated against.
|
|
263
|
+
*/
|
|
278
264
|
function effectiveChaos(defaults, scopeId) {
|
|
279
|
-
|
|
280
|
-
if (scoped) return scoped;
|
|
265
|
+
if (defaults.chaosByTestId?.has(scopeId)) return defaults.chaosByTestId.get(scopeId) ?? {};
|
|
281
266
|
const current = defaults.chaos;
|
|
282
267
|
if (!current) return {};
|
|
283
268
|
return require_chaos.isChaosScope(current) ? current.base ?? {} : current;
|
|
284
269
|
}
|
|
270
|
+
/** The `GET /__aimock/mcp/fakes` query-param vocabulary; anything else 400s. */
|
|
271
|
+
const MCP_FAKES_PARAMS = new Set([
|
|
272
|
+
"testId",
|
|
273
|
+
"context",
|
|
274
|
+
"mount"
|
|
275
|
+
]);
|
|
276
|
+
/** A block's `mount` path, read without running getters; the add validates it. */
|
|
277
|
+
function controlFakeMountOf(raw) {
|
|
278
|
+
if (typeof raw !== "object" || raw === null) return require_mcp_fakes.MCP_FAKES_DEFAULT_MOUNT;
|
|
279
|
+
const value = Object.getOwnPropertyDescriptor(raw, "mount")?.value;
|
|
280
|
+
return typeof value === "string" ? value : require_mcp_fakes.MCP_FAKES_DEFAULT_MOUNT;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Check one `POST /__aimock/fixtures` `mcpFakes` value against the live
|
|
284
|
+
* mounts (F10, W6) without changing anything. Returns every error in input
|
|
285
|
+
* order (an empty list when the value can be added), and `apply`, which adds
|
|
286
|
+
* the blocks (auto-mounting an MCPMock where no mount serves a block's path)
|
|
287
|
+
* and returns the number of blocks added.
|
|
288
|
+
*
|
|
289
|
+
* The whole value is checked before any mount is touched, and the check
|
|
290
|
+
* never adds to a real mount: each group is added to a throwaway store,
|
|
291
|
+
* whose errors are the ones reported (it numbers the add `control-api#1`, as
|
|
292
|
+
* a new mount would; for an existing MCPMock the rejected group is re-run on
|
|
293
|
+
* its atomic add, so the errors name that mount's next `<n>`, I7). A group
|
|
294
|
+
* whose path no request to it can reach (a mount that is not an MCPMock
|
|
295
|
+
* serves it, an MCPMock answers it as its root, or it is under the control
|
|
296
|
+
* prefix) is a mount conflict (one error per block).
|
|
297
|
+
*/
|
|
298
|
+
function planControlApiFakes(raw, mounts) {
|
|
299
|
+
const items = Array.isArray(raw) ? raw.map((block, position) => ({
|
|
300
|
+
position,
|
|
301
|
+
source: {
|
|
302
|
+
source: "control-api",
|
|
303
|
+
blockIndex: position,
|
|
304
|
+
raw: block
|
|
305
|
+
}
|
|
306
|
+
})) : [{
|
|
307
|
+
position: 0,
|
|
308
|
+
source: {
|
|
309
|
+
source: "control-api",
|
|
310
|
+
blockIndex: null,
|
|
311
|
+
raw
|
|
312
|
+
}
|
|
313
|
+
}];
|
|
314
|
+
const groups = /* @__PURE__ */ new Map();
|
|
315
|
+
if (items.length === 0) groups.set(require_mcp_fakes.MCP_FAKES_DEFAULT_MOUNT, {
|
|
316
|
+
path: require_mcp_fakes.MCP_FAKES_DEFAULT_MOUNT,
|
|
317
|
+
positions: [],
|
|
318
|
+
sources: []
|
|
319
|
+
});
|
|
320
|
+
for (const { position, source } of items) {
|
|
321
|
+
const path = controlFakeMountOf(source.raw);
|
|
322
|
+
const group = groups.get(path) ?? {
|
|
323
|
+
path,
|
|
324
|
+
positions: [],
|
|
325
|
+
sources: []
|
|
326
|
+
};
|
|
327
|
+
group.positions.push(position);
|
|
328
|
+
group.sources.push(source);
|
|
329
|
+
groups.set(path, group);
|
|
330
|
+
}
|
|
331
|
+
const found = [];
|
|
332
|
+
/** The paths `apply` auto-mounts, in order. */
|
|
333
|
+
const pending = [];
|
|
334
|
+
for (const group of groups.values()) {
|
|
335
|
+
const target = require_mcp_fakes_mount.findFakeMount(mounts, group.path, pending);
|
|
336
|
+
if (target.kind === "conflict") {
|
|
337
|
+
group.positions.forEach((position) => {
|
|
338
|
+
const where = Array.isArray(raw) ? require_message_text.msg`mcpFakes[${position}]` : require_message_text.msg`mcpFakes`;
|
|
339
|
+
found.push({
|
|
340
|
+
position,
|
|
341
|
+
error: new require_fixture_loader.FixtureLoadError({
|
|
342
|
+
rule: "mcp-fakes/mount-conflict",
|
|
343
|
+
file: null,
|
|
344
|
+
blockId: null,
|
|
345
|
+
entryId: null,
|
|
346
|
+
detail: require_message_text.msg`${where}: ${require_mcp_fakes_mount.mountConflictDetail(group.path, target)}`
|
|
347
|
+
})
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
let failure = addFailure(() => new require_mcp_fakes.McpFakeStore().add(group.sources, { kind: "control-api" }));
|
|
353
|
+
if (target.kind === "none") pending.push(group.path);
|
|
354
|
+
if (!failure) continue;
|
|
355
|
+
if (target.kind === "mcp" && target.handler.addMcpFakes === require_mcp_mock.MCPMock.prototype.addMcpFakes) {
|
|
356
|
+
const handler = target.handler;
|
|
357
|
+
failure = addFailure(() => handler.addMcpFakes?.(group.sources, { kind: "control-api" })) ?? failure;
|
|
358
|
+
}
|
|
359
|
+
for (const error of failure.errors) found.push({
|
|
360
|
+
position: positionOf(error, group),
|
|
361
|
+
error
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
found.sort((a, b) => a.position - b.position);
|
|
365
|
+
return {
|
|
366
|
+
errors: found.map((f) => f.error),
|
|
367
|
+
apply(wiring) {
|
|
368
|
+
let added = 0;
|
|
369
|
+
for (const group of groups.values()) {
|
|
370
|
+
const target = require_mcp_fakes_mount.ensureFakeMount(mounts, group.path, {
|
|
371
|
+
journal: wiring.journal,
|
|
372
|
+
registry: wiring.defaults.registry,
|
|
373
|
+
logger: wiring.defaults.logger
|
|
374
|
+
}, wiring.defaults.logger);
|
|
375
|
+
if ("conflict" in target) continue;
|
|
376
|
+
const result = target.handler.addMcpFakes?.(group.sources, { kind: "control-api" });
|
|
377
|
+
for (const warning of result?.warnings ?? []) wiring.defaults.logger.warn(warning.message);
|
|
378
|
+
added += group.sources.length;
|
|
379
|
+
}
|
|
380
|
+
return added;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
/** The `McpFakesAddError` that `add` throws, or `null` when it adds. */
|
|
385
|
+
function addFailure(add) {
|
|
386
|
+
try {
|
|
387
|
+
add();
|
|
388
|
+
return null;
|
|
389
|
+
} catch (err) {
|
|
390
|
+
if (err instanceof require_mcp_fakes.McpFakesAddError) return err;
|
|
391
|
+
throw err;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* The request position of the block an add error names. The add numbers a
|
|
396
|
+
* run-time group's blocks by their place in the group (`[<j>]`), or gives no
|
|
397
|
+
* index to a single-object value; an error tied to no block sorts with the
|
|
398
|
+
* group's first block.
|
|
399
|
+
*/
|
|
400
|
+
function positionOf(error, group) {
|
|
401
|
+
const first = group.positions[0] ?? 0;
|
|
402
|
+
if (error.blockId == null || error.file === null) return first;
|
|
403
|
+
const at = group.positions.findIndex((_, j) => error.blockId === require_mcp_fakes.blockIdOf(error.file ?? "", "", j));
|
|
404
|
+
return at === -1 ? first : group.positions[at];
|
|
405
|
+
}
|
|
406
|
+
function hasFakesSnapshot(handler) {
|
|
407
|
+
return "fakesSnapshot" in handler && typeof handler.fakesSnapshot === "function";
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* `GET /__aimock/mcp/fakes` (spec 6.5, I9): for each MCP mount (or the one
|
|
411
|
+
* named by `mount`), the blocks that apply to the requested test id and
|
|
412
|
+
* context, with every entry id and its consumed state for that test id.
|
|
413
|
+
* A mount is listed when it takes fakes (it implements `addMcpFakes`, the
|
|
414
|
+
* check every other fakes path uses); one with no `fakesSnapshot` lists no
|
|
415
|
+
* blocks.
|
|
416
|
+
*/
|
|
417
|
+
function listControlApiFakes(mounts, testId, context, mount) {
|
|
418
|
+
const listed = mounts.flatMap(({ path, handler }) => require_mcp_fakes_mount.isFakeMount(handler) && (mount === null || path === mount) ? [{
|
|
419
|
+
mount: path,
|
|
420
|
+
blocks: hasFakesSnapshot(handler) ? handler.fakesSnapshot(testId, context) : []
|
|
421
|
+
}] : []);
|
|
422
|
+
return mount !== null && listed.length === 0 ? null : listed;
|
|
423
|
+
}
|
|
285
424
|
/**
|
|
286
425
|
* Handle requests under `/__aimock/`. Returns `true` if the request was
|
|
287
426
|
* handled, `false` if the path doesn't match the control prefix.
|
|
288
427
|
*/
|
|
289
|
-
async function handleControlAPI(req, res, pathname, searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults) {
|
|
290
|
-
if (!pathname.startsWith(CONTROL_PREFIX)) return false;
|
|
291
|
-
const subPath = pathname.slice(
|
|
428
|
+
async function handleControlAPI(req, res, pathname, searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults, mounts) {
|
|
429
|
+
if (!pathname.startsWith(require_route_registry.CONTROL_PREFIX)) return false;
|
|
430
|
+
const subPath = pathname.slice(require_route_registry.CONTROL_PREFIX.length);
|
|
292
431
|
setCorsHeaders(res);
|
|
293
432
|
if (subPath === "/health" && req.method === "GET") {
|
|
294
433
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
295
434
|
res.end(JSON.stringify({ status: "ok" }));
|
|
296
435
|
return true;
|
|
297
436
|
}
|
|
437
|
+
if (subPath === "/openapi.json" && req.method === "GET") {
|
|
438
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
439
|
+
res.end(JSON.stringify(require_openapi.buildOpenApiDocument()));
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
if (subPath === "/routes" && req.method === "GET") {
|
|
443
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
444
|
+
res.end(JSON.stringify({
|
|
445
|
+
count: require_openapi.CATALOG_ROUTES.length,
|
|
446
|
+
routes: require_openapi.CATALOG_ROUTES
|
|
447
|
+
}));
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
298
450
|
if (subPath === "/journal" && req.method === "GET") {
|
|
299
451
|
const bad = (message) => {
|
|
300
452
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
@@ -322,6 +474,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
322
474
|
const methodFilter = searchParams.get("method");
|
|
323
475
|
const serviceFilter = searchParams.get("service");
|
|
324
476
|
const testIdFilter = searchParams.get("testId");
|
|
477
|
+
const requestIdFilter = searchParams.get("requestId");
|
|
325
478
|
let entries = journal.getAll();
|
|
326
479
|
if (methodFilter !== null) {
|
|
327
480
|
const want = methodFilter.toUpperCase();
|
|
@@ -331,6 +484,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
331
484
|
if (statusFilter !== void 0) entries = entries.filter((e) => e.response.status === statusFilter);
|
|
332
485
|
if (serviceFilter !== null) entries = entries.filter((e) => e.service === serviceFilter);
|
|
333
486
|
if (testIdFilter !== null) entries = entries.filter((e) => journalEntryTestId(e) === testIdFilter);
|
|
487
|
+
if (requestIdFilter !== null) entries = entries.filter((e) => e.headers["x-request-id"] === requestIdFilter);
|
|
334
488
|
const total = entries.length;
|
|
335
489
|
if (offset > 0) entries = entries.slice(offset);
|
|
336
490
|
if (limit >= 0) entries = entries.slice(0, limit);
|
|
@@ -364,7 +518,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
364
518
|
fixtures: fixtures.map((fixture, index) => ({
|
|
365
519
|
index,
|
|
366
520
|
match: redactForInspection(fixture.match),
|
|
367
|
-
responseKind: fixtureResponseKind(fixture.response),
|
|
521
|
+
responseKind: require_fixture_loader.isOneShotError(fixture) ? "error" : fixtureResponseKind(fixture.response),
|
|
368
522
|
...fixture.latency !== void 0 ? { latency: fixture.latency } : {},
|
|
369
523
|
...fixture.chaos !== void 0 ? { chaos: fixture.chaos } : {}
|
|
370
524
|
}))
|
|
@@ -414,32 +568,53 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
414
568
|
res.end(JSON.stringify({ error: "Invalid body: expected a JSON object" }));
|
|
415
569
|
return true;
|
|
416
570
|
}
|
|
417
|
-
const
|
|
418
|
-
"dropRate",
|
|
419
|
-
"malformedRate",
|
|
420
|
-
"disconnectRate"
|
|
421
|
-
];
|
|
422
|
-
for (const key of Object.keys(parsed)) if (!allowed.includes(key)) {
|
|
571
|
+
for (const key of Object.keys(parsed)) if (!require_chaos.CHAOS_FIELD_NAMES.includes(key)) {
|
|
423
572
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
424
573
|
res.end(JSON.stringify({ error: `Unknown chaos field: '${key}'` }));
|
|
425
574
|
return true;
|
|
426
575
|
}
|
|
427
576
|
const next = {};
|
|
428
|
-
for (const key of
|
|
577
|
+
for (const key of require_chaos.CHAOS_FIELD_NAMES) {
|
|
429
578
|
const value = parsed[key];
|
|
430
579
|
if (value === void 0) continue;
|
|
431
|
-
|
|
580
|
+
const accepted = typeof value === "number" ? require_chaos.parseChaosField(key, value) : void 0;
|
|
581
|
+
if (accepted === void 0) {
|
|
582
|
+
const shape = require_chaos.CHAOS_FIELDS[key].integer ? "a whole number of ms" : "a number";
|
|
432
583
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
433
|
-
res.end(JSON.stringify({ error: `Invalid '${key}': must be
|
|
584
|
+
res.end(JSON.stringify({ error: `Invalid '${key}': must be ${shape} between 0 and ${require_chaos.CHAOS_FIELDS[key].max}` }));
|
|
434
585
|
return true;
|
|
435
586
|
}
|
|
436
|
-
next[key] =
|
|
587
|
+
next[key] = accepted;
|
|
588
|
+
}
|
|
589
|
+
const shadowed = effectiveChaos(defaults, scopeId);
|
|
590
|
+
const droppedFields = Object.keys(shadowed).filter((key) => shadowed[key] !== void 0 && next[key] === void 0);
|
|
591
|
+
if (droppedFields.length > 0) {
|
|
592
|
+
const lost = droppedFields.map((key) => `${key}=${shadowed[key]}`).join(", ");
|
|
593
|
+
defaults.logger.warn(`[chaos] POST /__aimock/chaos (testId '${scopeId}') replaces the chaos config for this scope wholesale, it is not merged: ${lost} no longer applies here. Restate the field in the body to keep it.`);
|
|
437
594
|
}
|
|
438
595
|
defaults.chaosByTestId?.set(scopeId, next);
|
|
439
596
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
440
597
|
res.end(JSON.stringify({ chaos: effectiveChaos(defaults, scopeId) }));
|
|
441
598
|
return true;
|
|
442
599
|
}
|
|
600
|
+
if (subPath === "/mcp/fakes" && req.method === "GET") {
|
|
601
|
+
const reply = (status, body) => {
|
|
602
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
603
|
+
res.end(JSON.stringify(body));
|
|
604
|
+
return true;
|
|
605
|
+
};
|
|
606
|
+
for (const key of searchParams.keys()) if (!MCP_FAKES_PARAMS.has(key)) return reply(400, { error: `Unknown query parameter: '${key}'. Supported: ${[...MCP_FAKES_PARAMS].join(", ")}` });
|
|
607
|
+
const testId = searchParams.get("testId") || null;
|
|
608
|
+
const context = searchParams.get("context") || null;
|
|
609
|
+
const mount = searchParams.get("mount") || null;
|
|
610
|
+
const listed = listControlApiFakes(mounts, testId, context, mount);
|
|
611
|
+
if (listed === null) return reply(404, { error: require_message_text.build(require_message_text.msg`No MCP mount at ${require_message_text.quote(mount ?? "")}`) });
|
|
612
|
+
return reply(200, {
|
|
613
|
+
testId,
|
|
614
|
+
context,
|
|
615
|
+
mounts: listed
|
|
616
|
+
});
|
|
617
|
+
}
|
|
443
618
|
if (subPath === "/fixtures" && req.method === "POST") {
|
|
444
619
|
let raw;
|
|
445
620
|
try {
|
|
@@ -461,29 +636,60 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
461
636
|
res.end(JSON.stringify({ error: `Invalid JSON: ${msg}` }));
|
|
462
637
|
return true;
|
|
463
638
|
}
|
|
464
|
-
|
|
639
|
+
const withFakes = typeof parsed === "object" && parsed !== null && Object.hasOwn(parsed, "mcpFakes");
|
|
640
|
+
if (parsed === null || !Array.isArray(parsed.fixtures) && !(withFakes && parsed.fixtures === void 0)) {
|
|
465
641
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
466
642
|
res.end(JSON.stringify({ error: "Missing or invalid \"fixtures\" array" }));
|
|
467
643
|
return true;
|
|
468
644
|
}
|
|
469
|
-
const
|
|
645
|
+
const entries = Array.isArray(parsed.fixtures) ? parsed.fixtures : [];
|
|
646
|
+
const missingMatchIndex = entries.findIndex((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && entry.match == null);
|
|
647
|
+
if (missingMatchIndex !== -1) {
|
|
648
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
649
|
+
res.end(JSON.stringify({ error: `Fixture at index ${missingMatchIndex} is missing match` }));
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
const invalidMatchIndex = entries.findIndex((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && entry.match != null && (typeof entry.match !== "object" || Array.isArray(entry.match)));
|
|
653
|
+
if (invalidMatchIndex !== -1) {
|
|
654
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
655
|
+
res.end(JSON.stringify({ error: `Fixture at index ${invalidMatchIndex}: match must be an object` }));
|
|
656
|
+
return true;
|
|
657
|
+
}
|
|
658
|
+
const converted = entries.map((e) => require_fixture_loader.entryToFixture(e));
|
|
470
659
|
const errors = require_fixture_loader.validateFixtures(converted).filter((i) => i.severity === "error");
|
|
471
|
-
|
|
660
|
+
const fakes = withFakes ? planControlApiFakes(parsed.mcpFakes, mounts) : null;
|
|
661
|
+
const fakeErrors = fakes?.errors ?? [];
|
|
662
|
+
const validationFailed = (details) => {
|
|
472
663
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
473
664
|
res.end(JSON.stringify({
|
|
474
665
|
error: "Validation failed",
|
|
475
|
-
details
|
|
666
|
+
details
|
|
476
667
|
}));
|
|
477
668
|
return true;
|
|
669
|
+
};
|
|
670
|
+
if (errors.length > 0 || fakeErrors.length > 0) return validationFailed([...errors, ...fakeErrors.map((e) => e.toJSON())]);
|
|
671
|
+
let mcpFakesAdded = 0;
|
|
672
|
+
if (fakes) try {
|
|
673
|
+
mcpFakesAdded = fakes.apply({
|
|
674
|
+
journal,
|
|
675
|
+
defaults
|
|
676
|
+
});
|
|
677
|
+
} catch (err) {
|
|
678
|
+
if (!(err instanceof require_mcp_fakes.McpFakesAddError)) throw err;
|
|
679
|
+
return validationFailed(err.errors.map((e) => e.toJSON()));
|
|
478
680
|
}
|
|
479
681
|
fixtures.push(...converted);
|
|
480
682
|
if (defaults.registry) defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
|
|
481
683
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
482
|
-
res.end(JSON.stringify(
|
|
684
|
+
res.end(JSON.stringify(withFakes ? {
|
|
685
|
+
added: converted.length,
|
|
686
|
+
mcpFakesAdded
|
|
687
|
+
} : { added: converted.length }));
|
|
483
688
|
return true;
|
|
484
689
|
}
|
|
485
690
|
if (subPath === "/fixtures" && req.method === "DELETE") {
|
|
486
|
-
fixtures
|
|
691
|
+
require_fixture_loader.clearFixtureQueue(fixtures);
|
|
692
|
+
for (const { handler } of mounts) handler.clearMcpFakes?.();
|
|
487
693
|
if (defaults.registry) defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
|
|
488
694
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
489
695
|
res.end(JSON.stringify({ cleared: true }));
|
|
@@ -496,7 +702,8 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
496
702
|
veoVideoJobs,
|
|
497
703
|
grokVideoJobs,
|
|
498
704
|
bytePlusVideoJobs,
|
|
499
|
-
defaults
|
|
705
|
+
defaults,
|
|
706
|
+
mounts
|
|
500
707
|
});
|
|
501
708
|
if (subPath === "/reset" && req.method === "POST") {
|
|
502
709
|
performFullReset(fixtures, resetTargets());
|
|
@@ -577,32 +784,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
577
784
|
}
|
|
578
785
|
}
|
|
579
786
|
}
|
|
580
|
-
|
|
581
|
-
match: { predicate: () => true },
|
|
582
|
-
response: {
|
|
583
|
-
error: {
|
|
584
|
-
message: errorBody?.message ?? "Injected error",
|
|
585
|
-
type: errorBody?.type ?? "server_error",
|
|
586
|
-
code: errorBody?.code
|
|
587
|
-
},
|
|
588
|
-
status
|
|
589
|
-
}
|
|
590
|
-
};
|
|
591
|
-
fixtures.unshift(errorFixture);
|
|
592
|
-
let consumed = false;
|
|
593
|
-
const original = errorFixture.match.predicate;
|
|
594
|
-
errorFixture.match.predicate = (req) => {
|
|
595
|
-
if (consumed) return false;
|
|
596
|
-
const result = original(req);
|
|
597
|
-
if (result) {
|
|
598
|
-
consumed = true;
|
|
599
|
-
queueMicrotask(() => {
|
|
600
|
-
const idx = fixtures.indexOf(errorFixture);
|
|
601
|
-
if (idx !== -1) fixtures.splice(idx, 1);
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
return result;
|
|
605
|
-
};
|
|
787
|
+
require_fixture_loader.queueOneShotError(fixtures, status, errorBody ?? void 0);
|
|
606
788
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
607
789
|
res.end(JSON.stringify({ queued: true }));
|
|
608
790
|
return true;
|
|
@@ -610,6 +792,19 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
610
792
|
handleNotFound(res, `Unknown control endpoint: ${pathname}`);
|
|
611
793
|
return true;
|
|
612
794
|
}
|
|
795
|
+
/**
|
|
796
|
+
* The client tore the connection down before its request body had fully
|
|
797
|
+
* arrived. Node rejects the pending body read with `aborted` (ECONNRESET) and
|
|
798
|
+
* destroys the response, so the rejection surfaces in the handler's read arm
|
|
799
|
+
* looking like any other thrown error. It is caller behaviour, not a server
|
|
800
|
+
* fault: no status can reach the client, so none must be journaled as
|
|
801
|
+
* delivered. A body that blew its size cap also leaves the request destroyed
|
|
802
|
+
* and incomplete, but that one is aimock's own decision, not a peer abort.
|
|
803
|
+
*/
|
|
804
|
+
function clientAbortedMidBody(req, res, err) {
|
|
805
|
+
if (err instanceof require_helpers.RequestBodyTooLargeError) return false;
|
|
806
|
+
return res.destroyed && !req.complete;
|
|
807
|
+
}
|
|
613
808
|
async function handleCompletions(req, res, fixtures, journal, defaults, modelFallback, providerKey, openRouter = false) {
|
|
614
809
|
setCorsHeaders(res);
|
|
615
810
|
let raw;
|
|
@@ -617,19 +812,43 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
617
812
|
raw = await require_helpers.readBody(req);
|
|
618
813
|
} catch (err) {
|
|
619
814
|
const msg = err instanceof Error ? err.message : "Failed to read request body";
|
|
815
|
+
const clientFault = err instanceof require_helpers.RequestBodyTooLargeError;
|
|
816
|
+
const route = `${req.method ?? "POST"} ${req.url ?? require_route_registry.COMPLETIONS_PATH}`;
|
|
817
|
+
const clientAbort = clientAbortedMidBody(req, res, err);
|
|
818
|
+
const bodyLimitDisconnect = clientFault && (req.destroyed || res.destroyed);
|
|
819
|
+
if (clientAbort || bodyLimitDisconnect) {
|
|
820
|
+
const interruptReason = bodyLimitDisconnect ? "request body exceeded size limit" : "client aborted";
|
|
821
|
+
defaults.logger.warn(bodyLimitDisconnect ? `${route}: ${msg}` : `${route}: client aborted mid-body (${msg})`);
|
|
822
|
+
journal.add({
|
|
823
|
+
method: req.method ?? "POST",
|
|
824
|
+
path: req.url ?? require_route_registry.COMPLETIONS_PATH,
|
|
825
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
826
|
+
body: null,
|
|
827
|
+
response: {
|
|
828
|
+
status: 0,
|
|
829
|
+
fixture: null,
|
|
830
|
+
interrupted: true,
|
|
831
|
+
interruptReason
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
const status = clientFault ? 400 : 500;
|
|
837
|
+
if (clientFault) defaults.logger.warn(`${route}: ${msg}`);
|
|
838
|
+
else defaults.logger.error(`${route}: ${msg}`);
|
|
620
839
|
journal.add({
|
|
621
840
|
method: req.method ?? "POST",
|
|
622
|
-
path: req.url ?? COMPLETIONS_PATH,
|
|
841
|
+
path: req.url ?? require_route_registry.COMPLETIONS_PATH,
|
|
623
842
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
624
843
|
body: null,
|
|
625
844
|
response: {
|
|
626
|
-
status
|
|
845
|
+
status,
|
|
627
846
|
fixture: null
|
|
628
847
|
}
|
|
629
848
|
});
|
|
630
|
-
require_sse_writer.writeErrorResponse(res,
|
|
849
|
+
require_sse_writer.writeErrorResponse(res, status, openRouter ? require_openrouter_chat.serializeOpenRouterError(status, `Request body read failed: ${msg}`) : JSON.stringify({ error: {
|
|
631
850
|
message: `Request body read failed: ${msg}`,
|
|
632
|
-
type: "server_error"
|
|
851
|
+
type: clientFault ? "invalid_request_error" : "server_error"
|
|
633
852
|
} }));
|
|
634
853
|
return;
|
|
635
854
|
}
|
|
@@ -640,7 +859,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
640
859
|
const detail = parseErr instanceof Error ? parseErr.message : "unknown parse error";
|
|
641
860
|
journal.add({
|
|
642
861
|
method: req.method ?? "POST",
|
|
643
|
-
path: req.url ?? COMPLETIONS_PATH,
|
|
862
|
+
path: req.url ?? require_route_registry.COMPLETIONS_PATH,
|
|
644
863
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
645
864
|
body: null,
|
|
646
865
|
response: {
|
|
@@ -659,7 +878,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
659
878
|
if (!require_helpers.isJsonObject(body)) {
|
|
660
879
|
journal.add({
|
|
661
880
|
method: req.method ?? "POST",
|
|
662
|
-
path: req.url ?? COMPLETIONS_PATH,
|
|
881
|
+
path: req.url ?? require_route_registry.COMPLETIONS_PATH,
|
|
663
882
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
664
883
|
body: null,
|
|
665
884
|
response: {
|
|
@@ -677,7 +896,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
677
896
|
if (!Array.isArray(body.messages)) {
|
|
678
897
|
journal.add({
|
|
679
898
|
method: req.method ?? "POST",
|
|
680
|
-
path: req.url ?? COMPLETIONS_PATH,
|
|
899
|
+
path: req.url ?? require_route_registry.COMPLETIONS_PATH,
|
|
681
900
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
682
901
|
body: null,
|
|
683
902
|
response: {
|
|
@@ -693,8 +912,53 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
693
912
|
} }));
|
|
694
913
|
return;
|
|
695
914
|
}
|
|
915
|
+
const nullMessageIndex = body.messages.findIndex((message) => message === null);
|
|
916
|
+
if (nullMessageIndex !== -1) {
|
|
917
|
+
const message = `Invalid request: messages[${nullMessageIndex}] must be an object`;
|
|
918
|
+
journal.add({
|
|
919
|
+
method: req.method ?? "POST",
|
|
920
|
+
path: req.url ?? require_route_registry.COMPLETIONS_PATH,
|
|
921
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
922
|
+
body: null,
|
|
923
|
+
response: {
|
|
924
|
+
status: 400,
|
|
925
|
+
fixture: null
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
require_sse_writer.writeErrorResponse(res, 400, openRouter ? require_openrouter_chat.serializeOpenRouterError(400, message) : JSON.stringify({ error: {
|
|
929
|
+
message,
|
|
930
|
+
type: "invalid_request_error",
|
|
931
|
+
param: null,
|
|
932
|
+
code: null
|
|
933
|
+
} }));
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
if (new URL(req.url ?? require_route_registry.COMPLETIONS_PATH, "http://localhost").pathname === require_route_registry.COMPLETIONS_PATH) {
|
|
937
|
+
let toolsError = require_helpers.validateToolsField(body.tools);
|
|
938
|
+
if (!toolsError && Array.isArray(body.tools)) {
|
|
939
|
+
const nullFunctionIndex = body.tools.findIndex((tool) => tool?.function === null);
|
|
940
|
+
if (nullFunctionIndex !== -1) toolsError = `tools[${nullFunctionIndex}].function must not be null`;
|
|
941
|
+
}
|
|
942
|
+
if (toolsError) {
|
|
943
|
+
journal.add({
|
|
944
|
+
method: req.method ?? "POST",
|
|
945
|
+
path: req.url ?? require_route_registry.COMPLETIONS_PATH,
|
|
946
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
947
|
+
body: null,
|
|
948
|
+
response: {
|
|
949
|
+
status: 400,
|
|
950
|
+
fixture: null
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
|
|
954
|
+
message: toolsError,
|
|
955
|
+
type: "invalid_request_error"
|
|
956
|
+
} }));
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
696
960
|
const method = req.method ?? "POST";
|
|
697
|
-
const path = req.url ?? COMPLETIONS_PATH;
|
|
961
|
+
const path = req.url ?? require_route_registry.COMPLETIONS_PATH;
|
|
698
962
|
const flatHeaders = require_helpers.flattenHeaders(req.headers);
|
|
699
963
|
body._endpointType = "chat";
|
|
700
964
|
body._context = require_helpers.getContext(req);
|
|
@@ -705,7 +969,15 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
705
969
|
let preResolvedResponse = null;
|
|
706
970
|
let responseModel = body.model;
|
|
707
971
|
let fixtureCountIncremented = false;
|
|
708
|
-
|
|
972
|
+
const openRouterFallback = openRouter && Array.isArray(body.models) && body.models.length > 0;
|
|
973
|
+
const selectFixture = (probe, counts) => {
|
|
974
|
+
for (;;) {
|
|
975
|
+
const attempt = require_router.matchFixtureDiagnostic(fixtures, probe, counts, defaults.requestTransform, matchOptions);
|
|
976
|
+
if (attempt.fixture && require_fixture_loader.isOneShotError(attempt.fixture) && !require_fixture_loader.claimOneShotError(fixtures, attempt.fixture)) continue;
|
|
977
|
+
return attempt;
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
if (openRouterFallback) {
|
|
709
981
|
const candidates = require_openrouter_chat.buildOpenRouterCandidates(body);
|
|
710
982
|
let matchCounts = journal.getFixtureMatchCountsForTest(testId);
|
|
711
983
|
const allowFallbacks = body.provider?.allow_fallbacks !== false;
|
|
@@ -716,7 +988,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
716
988
|
...body,
|
|
717
989
|
model: candidate
|
|
718
990
|
};
|
|
719
|
-
const attempt =
|
|
991
|
+
const attempt = selectFixture(probe, matchCounts);
|
|
720
992
|
skippedBySequenceOrTurn = Math.max(skippedBySequenceOrTurn, attempt.skippedBySequenceOrTurn);
|
|
721
993
|
if (!attempt.fixture) return null;
|
|
722
994
|
const response = await require_helpers.resolveResponse(attempt.fixture, probe);
|
|
@@ -753,41 +1025,54 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
753
1025
|
preResolvedResponse = lastErrorResponse;
|
|
754
1026
|
}
|
|
755
1027
|
} else {
|
|
756
|
-
const single =
|
|
1028
|
+
const single = selectFixture(body, journal.getFixtureMatchCountsForTest(testId));
|
|
757
1029
|
fixture = single.fixture;
|
|
758
1030
|
skippedBySequenceOrTurn = single.skippedBySequenceOrTurn;
|
|
759
1031
|
}
|
|
760
1032
|
if (fixture) {
|
|
761
1033
|
if (!fixtureCountIncremented) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
762
|
-
defaults.logger.debug(`Fixture matched: ${
|
|
1034
|
+
defaults.logger.debug(`Fixture matched: ${require_helpers.describeMatch(fixture.match, fixtures.indexOf(fixture))}`);
|
|
763
1035
|
} else {
|
|
764
1036
|
const lastUserMsg = body.messages.filter((m) => m.role === "user").pop();
|
|
765
1037
|
const snippet = typeof lastUserMsg?.content === "string" ? lastUserMsg.content.slice(0, 80) : "";
|
|
766
1038
|
defaults.logger.debug(`No fixture matched for request (model=${body.model ?? "?"}, msg="${snippet}")`);
|
|
767
1039
|
}
|
|
768
|
-
const
|
|
1040
|
+
const chaosConfig = require_chaos.resolveChaosConfig(fixture, defaults.chaos, req.headers, defaults.logger, req.url);
|
|
1041
|
+
await require_chaos.awaitChaosLatency(fixture, defaults.chaos, req.headers, defaults.logger, req.url, res, chaosConfig);
|
|
1042
|
+
const goneAfterLatency = require_chaos.responseGoneReason(res);
|
|
1043
|
+
if (goneAfterLatency !== null) {
|
|
1044
|
+
require_fixture_loader.releaseOneShotError(fixtures, fixture);
|
|
1045
|
+
defaults.logger.debug(`[chaos] ${method} ${path}: ${require_chaos.describeUnwritableReason(goneAfterLatency)} after the latency delay — not served, not journalled`);
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
const chaosAction = require_chaos.evaluateChaos(fixture, defaults.chaos, req.headers, defaults.logger, req.url, chaosConfig);
|
|
769
1049
|
const chaosContext = {
|
|
770
1050
|
method,
|
|
771
1051
|
path,
|
|
772
1052
|
headers: flatHeaders,
|
|
773
1053
|
body
|
|
774
1054
|
};
|
|
775
|
-
|
|
776
|
-
|
|
1055
|
+
const effectiveStrict = require_helpers.resolveStrictMode(defaults.strict, req.headers);
|
|
1056
|
+
const missWouldProxy = require_helpers.wouldProxyMiss(effectiveStrict, defaults.record, providerKey);
|
|
1057
|
+
const noFixtureSource = missWouldProxy ? "proxy" : "internal";
|
|
1058
|
+
if (chaosAction === "drop" || chaosAction === "disconnect" || chaosAction === "rateLimit") {
|
|
1059
|
+
require_chaos.applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : noFixtureSource, defaults.registry, defaults.logger);
|
|
1060
|
+
require_fixture_loader.releaseOneShotError(fixtures, fixture);
|
|
777
1061
|
return;
|
|
778
1062
|
}
|
|
779
|
-
if (
|
|
780
|
-
require_chaos.applyChaosAction(chaosAction, res, fixture, journal, chaosContext, "fixture", defaults.registry);
|
|
1063
|
+
if (chaosAction === "malformed" && (fixture || !missWouldProxy)) {
|
|
1064
|
+
require_chaos.applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : "internal", defaults.registry, defaults.logger);
|
|
1065
|
+
require_fixture_loader.releaseOneShotError(fixtures, fixture);
|
|
781
1066
|
return;
|
|
782
1067
|
}
|
|
783
1068
|
if (!fixture) {
|
|
784
|
-
if (
|
|
1069
|
+
if (effectiveStrict) {
|
|
785
1070
|
const strictStatus = 503;
|
|
786
1071
|
const strictMessage = require_helpers.strictNoMatchMessage(skippedBySequenceOrTurn);
|
|
787
|
-
defaults.logger.error(require_helpers.strictNoMatchLogLine(req.method ?? "POST", req.url ?? COMPLETIONS_PATH, skippedBySequenceOrTurn));
|
|
1072
|
+
defaults.logger.error(require_helpers.strictNoMatchLogLine(req.method ?? "POST", req.url ?? require_route_registry.COMPLETIONS_PATH, skippedBySequenceOrTurn));
|
|
788
1073
|
journal.add({
|
|
789
1074
|
method: req.method ?? "POST",
|
|
790
|
-
path: req.url ?? COMPLETIONS_PATH,
|
|
1075
|
+
path: req.url ?? require_route_registry.COMPLETIONS_PATH,
|
|
791
1076
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
792
1077
|
body,
|
|
793
1078
|
response: {
|
|
@@ -807,7 +1092,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
807
1092
|
if (defaults.record && providerKey) {
|
|
808
1093
|
const hookOptions = chaosAction === "malformed" ? {
|
|
809
1094
|
beforeWriteResponse: () => {
|
|
810
|
-
require_chaos.applyChaosAction(chaosAction, res, null, journal, chaosContext, "proxy", defaults.registry);
|
|
1095
|
+
require_chaos.applyChaosAction(chaosAction, res, null, journal, chaosContext, "proxy", defaults.registry, defaults.logger);
|
|
811
1096
|
return true;
|
|
812
1097
|
},
|
|
813
1098
|
onHookBypassed: (reason) => {
|
|
@@ -819,26 +1104,47 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
819
1104
|
});
|
|
820
1105
|
}
|
|
821
1106
|
} : void 0;
|
|
822
|
-
|
|
1107
|
+
let clientHungUp = false;
|
|
1108
|
+
const noteClientHangUp = () => {
|
|
1109
|
+
if (!res.writableEnded) clientHungUp = true;
|
|
1110
|
+
};
|
|
1111
|
+
req.socket?.on("end", noteClientHangUp);
|
|
1112
|
+
req.socket?.on("error", noteClientHangUp);
|
|
1113
|
+
let outcome;
|
|
1114
|
+
try {
|
|
1115
|
+
outcome = await require_recorder.proxyAndRecord(req, res, body, providerKey, req.url ?? require_route_registry.COMPLETIONS_PATH, fixtures, defaults, raw, hookOptions);
|
|
1116
|
+
} finally {
|
|
1117
|
+
req.socket?.off("end", noteClientHangUp);
|
|
1118
|
+
req.socket?.off("error", noteClientHangUp);
|
|
1119
|
+
}
|
|
823
1120
|
if (outcome === "handled_by_hook") return;
|
|
824
1121
|
if (outcome !== "not_configured") {
|
|
1122
|
+
const destroyedMidStream = res.destroyed && !res.writableEnded;
|
|
825
1123
|
journal.add({
|
|
826
1124
|
method: req.method ?? "POST",
|
|
827
|
-
path: req.url ?? COMPLETIONS_PATH,
|
|
1125
|
+
path: req.url ?? require_route_registry.COMPLETIONS_PATH,
|
|
828
1126
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
829
1127
|
body,
|
|
830
1128
|
response: {
|
|
831
1129
|
status: res.statusCode ?? 200,
|
|
832
1130
|
fixture: null,
|
|
833
|
-
source: "proxy"
|
|
1131
|
+
source: "proxy",
|
|
1132
|
+
...destroyedMidStream ? {
|
|
1133
|
+
interrupted: true,
|
|
1134
|
+
interruptReason: clientHungUp ? "client aborted" : "proxy stream destroyed"
|
|
1135
|
+
} : {}
|
|
834
1136
|
}
|
|
835
1137
|
});
|
|
836
1138
|
return;
|
|
837
1139
|
}
|
|
1140
|
+
if (chaosAction === "malformed") {
|
|
1141
|
+
require_chaos.applyChaosAction(chaosAction, res, null, journal, chaosContext, "internal", defaults.registry, defaults.logger);
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
838
1144
|
}
|
|
839
1145
|
journal.add({
|
|
840
1146
|
method: req.method ?? "POST",
|
|
841
|
-
path: req.url ?? COMPLETIONS_PATH,
|
|
1147
|
+
path: req.url ?? require_route_registry.COMPLETIONS_PATH,
|
|
842
1148
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
843
1149
|
body,
|
|
844
1150
|
response: {
|
|
@@ -876,7 +1182,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
876
1182
|
const status = response.status ?? 500;
|
|
877
1183
|
journal.add({
|
|
878
1184
|
method: req.method ?? "POST",
|
|
879
|
-
path: req.url ?? COMPLETIONS_PATH,
|
|
1185
|
+
path: req.url ?? require_route_registry.COMPLETIONS_PATH,
|
|
880
1186
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
881
1187
|
body,
|
|
882
1188
|
response: {
|
|
@@ -890,7 +1196,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
890
1196
|
if (require_helpers.isAudioResponse(response)) {
|
|
891
1197
|
journal.add({
|
|
892
1198
|
method: req.method ?? "POST",
|
|
893
|
-
path: req.url ?? COMPLETIONS_PATH,
|
|
1199
|
+
path: req.url ?? require_route_registry.COMPLETIONS_PATH,
|
|
894
1200
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
895
1201
|
body,
|
|
896
1202
|
response: {
|
|
@@ -910,10 +1216,10 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
910
1216
|
const effectiveStrict = require_helpers.resolveStrictMode(defaults.strict, req.headers);
|
|
911
1217
|
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
912
1218
|
const streaming = body.stream === true;
|
|
913
|
-
const
|
|
1219
|
+
const blockOutcome = response.blocks && response.blocks.length > 0 ? require_helpers.resolveFixtureBlockOutcome(response.blocks) : void 0;
|
|
914
1220
|
const journalEntry = journal.add({
|
|
915
1221
|
method: req.method ?? "POST",
|
|
916
|
-
path: req.url ?? COMPLETIONS_PATH,
|
|
1222
|
+
path: req.url ?? require_route_registry.COMPLETIONS_PATH,
|
|
917
1223
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
918
1224
|
body,
|
|
919
1225
|
response: {
|
|
@@ -922,12 +1228,15 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
922
1228
|
}
|
|
923
1229
|
});
|
|
924
1230
|
if (!streaming) {
|
|
925
|
-
const completion = require_helpers.buildContentWithToolCallsCompletion(response.content ?? "", response.toolCalls ?? [], responseModel, effReasoning,
|
|
1231
|
+
const completion = require_helpers.buildContentWithToolCallsCompletion(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? response.toolCalls ?? [], responseModel, effReasoning, blockOutcome ? {
|
|
1232
|
+
...overrides,
|
|
1233
|
+
finishReason: overrides?.finishReason ?? (blockOutcome.hasToolCalls ? "tool_calls" : "stop")
|
|
1234
|
+
} : overrides, body.messages);
|
|
926
1235
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
927
1236
|
res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
|
|
928
1237
|
} else {
|
|
929
|
-
const chunks = require_helpers.buildContentWithToolCallsChunks(response.content ?? "", response.toolCalls ?? [], responseModel, chunkSize, effReasoning, overrides,
|
|
930
|
-
const completionText =
|
|
1238
|
+
const chunks = require_helpers.buildContentWithToolCallsChunks(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? response.toolCalls ?? [], responseModel, chunkSize, effReasoning, overrides, blockOutcome?.ordered);
|
|
1239
|
+
const completionText = blockOutcome ? blockOutcome.content + blockOutcome.toolCalls.map((tc) => tc.name + tc.arguments).join("") : (response.content ?? "") + (response.toolCalls ?? []).map((tc) => tc.name + tc.arguments).join("");
|
|
931
1240
|
const usageChunk = emitStreamingUsage ? require_helpers.buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? responseModel, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), resolveStreamingUsageTokens(overrides, completionText), overrides?.systemFingerprint) : void 0;
|
|
932
1241
|
shapeORChunks(chunks, usageChunk, overrides);
|
|
933
1242
|
const interruption = require_interruption.createInterruptionSignal(fixture);
|
|
@@ -956,7 +1265,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
956
1265
|
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
957
1266
|
const journalEntry = journal.add({
|
|
958
1267
|
method: req.method ?? "POST",
|
|
959
|
-
path: req.url ?? COMPLETIONS_PATH,
|
|
1268
|
+
path: req.url ?? require_route_registry.COMPLETIONS_PATH,
|
|
960
1269
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
961
1270
|
body,
|
|
962
1271
|
response: {
|
|
@@ -998,7 +1307,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
998
1307
|
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
999
1308
|
const journalEntry = journal.add({
|
|
1000
1309
|
method: req.method ?? "POST",
|
|
1001
|
-
path: req.url ?? COMPLETIONS_PATH,
|
|
1310
|
+
path: req.url ?? require_route_registry.COMPLETIONS_PATH,
|
|
1002
1311
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
1003
1312
|
body,
|
|
1004
1313
|
response: {
|
|
@@ -1036,7 +1345,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1036
1345
|
}
|
|
1037
1346
|
journal.add({
|
|
1038
1347
|
method: req.method ?? "POST",
|
|
1039
|
-
path: req.url ?? COMPLETIONS_PATH,
|
|
1348
|
+
path: req.url ?? require_route_registry.COMPLETIONS_PATH,
|
|
1040
1349
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
1041
1350
|
body,
|
|
1042
1351
|
response: {
|
|
@@ -1057,9 +1366,24 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
1057
1366
|
}
|
|
1058
1367
|
/** @internal Config startup uses this to avoid parsing a selected auth source twice. */
|
|
1059
1368
|
async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mounts, serviceFixtures) {
|
|
1369
|
+
const logger = new require_logger.Logger(options?.logLevel ?? "silent");
|
|
1370
|
+
const mountList = mounts ?? [];
|
|
1371
|
+
const handOff = serviceFixtures?.mcpFakes && serviceFixtures.mcpFakes.length > 0 ? require_mcp_fakes_mount.handOffMcpFakes(mountList, serviceFixtures.mcpFakes, logger) : null;
|
|
1372
|
+
try {
|
|
1373
|
+
return await startServer(fixtures, options, resolvedAuth, mountList, logger, serviceFixtures, () => handOff?.commit());
|
|
1374
|
+
} catch (err) {
|
|
1375
|
+
handOff?.undo();
|
|
1376
|
+
throw err;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* The server of `createServerWithResolvedAuth`, after the MCP fakes hand-off.
|
|
1381
|
+
* `commitHandOff` runs once the server listens; if it throws, the server is
|
|
1382
|
+
* closed and start rejects.
|
|
1383
|
+
*/
|
|
1384
|
+
async function startServer(fixtures, options, resolvedAuth, mountList, logger, serviceFixtures, commitHandOff) {
|
|
1060
1385
|
const host = options?.host ?? "127.0.0.1";
|
|
1061
1386
|
const port = options?.port ?? 0;
|
|
1062
|
-
const logger = new require_logger.Logger(options?.logLevel ?? "silent");
|
|
1063
1387
|
const registry = options?.metrics ? require_metrics.createMetricsRegistry() : void 0;
|
|
1064
1388
|
const serverOptions = options ?? {};
|
|
1065
1389
|
const chaosByTestId = /* @__PURE__ */ new Map();
|
|
@@ -1111,21 +1435,14 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1111
1435
|
}
|
|
1112
1436
|
};
|
|
1113
1437
|
if (options?.chaos) {
|
|
1114
|
-
const
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
},
|
|
1123
|
-
{
|
|
1124
|
-
name: "disconnectRate",
|
|
1125
|
-
value: options.chaos.disconnectRate
|
|
1126
|
-
}
|
|
1127
|
-
];
|
|
1128
|
-
for (const { name, value } of chaosRates) if (value !== void 0 && (value < 0 || value > 1)) logger.warn(`Chaos ${name} (${value}) is outside 0-1 range — will be clamped at runtime`);
|
|
1438
|
+
const chaosDefaults = options.chaos;
|
|
1439
|
+
for (const field of require_chaos.CHAOS_FIELD_NAMES) {
|
|
1440
|
+
const value = chaosDefaults[field];
|
|
1441
|
+
if (value === void 0) continue;
|
|
1442
|
+
if (typeof value === "number" && require_chaos.parseChaosField(field, value) !== void 0) continue;
|
|
1443
|
+
const shape = require_chaos.CHAOS_FIELDS[field].integer ? "a whole number of ms" : "a number";
|
|
1444
|
+
logger.warn(`Chaos default ${field} value ${JSON.stringify(value)} — must be ${shape} in [0,${require_chaos.CHAOS_FIELDS[field].max}]; rejected at runtime, never clamped: this default is ignored`);
|
|
1445
|
+
}
|
|
1129
1446
|
}
|
|
1130
1447
|
for (const { name, config } of [
|
|
1131
1448
|
{
|
|
@@ -1171,123 +1488,315 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1171
1488
|
if (basePath !== void 0 && /(^|\/)api\/v3\/?$/.test(basePath)) throw new Error(`record.providers.byteplus (${bytePlusBase}) ends in /api/v3, but aimock appends the /api/v3 prefix itself — every BytePlus request would go to /api/v3/api/v3/... and fail upstream. Configure the ORIGIN only (e.g. https://ark.ap-southeast.bytepluses.com).`);
|
|
1172
1489
|
}
|
|
1173
1490
|
}
|
|
1491
|
+
/**
|
|
1492
|
+
* The in-flight request, scoped to the async execution that serves it.
|
|
1493
|
+
* Entered once in the `http.createServer` callback, so every handler —
|
|
1494
|
+
* including the ones in other modules that only ever see the shared
|
|
1495
|
+
* `journal` — runs inside it.
|
|
1496
|
+
*/
|
|
1497
|
+
const requestScope = new node_async_hooks.AsyncLocalStorage();
|
|
1498
|
+
/** The journal entry each request produced, keyed by the request object. */
|
|
1499
|
+
const ownJournalEntry = /* @__PURE__ */ new WeakMap();
|
|
1174
1500
|
const journal = new require_journal.Journal({
|
|
1175
1501
|
maxEntries: options?.journalMaxEntries ?? 1e3,
|
|
1176
|
-
fixtureCountsMaxTestIds: options?.fixtureCountsMaxTestIds ?? 500
|
|
1502
|
+
fixtureCountsMaxTestIds: options?.fixtureCountsMaxTestIds ?? 500,
|
|
1503
|
+
onAdd: (entry) => {
|
|
1504
|
+
const req = requestScope.getStore();
|
|
1505
|
+
if (req) ownJournalEntry.set(req, entry);
|
|
1506
|
+
}
|
|
1177
1507
|
});
|
|
1178
1508
|
const videoStates = new require_video.VideoStateMap();
|
|
1179
1509
|
const openRouterVideoJobs = new require_openrouter_video.OpenRouterVideoJobMap();
|
|
1180
1510
|
const veoVideoJobs = new require_veo_video.VeoVideoJobMap();
|
|
1181
1511
|
const grokVideoJobs = new require_grok_video.GrokVideoJobMap();
|
|
1182
1512
|
const bytePlusVideoJobs = new require_byteplus_video.BytePlusVideoJobMap();
|
|
1183
|
-
|
|
1513
|
+
/**
|
|
1514
|
+
* The OpenAI error `type` that goes with a status: 4xx is the caller's
|
|
1515
|
+
* fault, everything else is ours. Shared by `routeError` and the envelopes
|
|
1516
|
+
* it calls so the body can never disagree with the status line.
|
|
1517
|
+
*/
|
|
1518
|
+
function errorTypeForStatus(status) {
|
|
1519
|
+
return status >= 400 && status < 500 ? "invalid_request_error" : "server_error";
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* The newest journal entry this request already produced, or null.
|
|
1523
|
+
*
|
|
1524
|
+
* Attribution is by the identity of the `IncomingMessage` the entry was
|
|
1525
|
+
* written under — never by `x-request-id`. That header is CALLER-supplied
|
|
1526
|
+
* (`resolveRequestId` echoes a well-formed one) and nothing makes it
|
|
1527
|
+
* unique: a retry, a harness that pins one id, or a load generator sends
|
|
1528
|
+
* the same id on many requests, and keying off it made a crash rewrite an
|
|
1529
|
+
* EARLIER request's entry while leaving the crashing request untraced. The
|
|
1530
|
+
* request object cannot be collided by a caller, so one entry belongs to
|
|
1531
|
+
* exactly one request. A handler writes at most one entry; if it writes
|
|
1532
|
+
* more, the newest wins, which is the entry the old scan would have found.
|
|
1533
|
+
*/
|
|
1534
|
+
function lastJournalEntryFor(req) {
|
|
1535
|
+
return ownJournalEntry.get(req) ?? null;
|
|
1536
|
+
}
|
|
1537
|
+
/**
|
|
1538
|
+
* Uniform terminal arm for a route handler that threw. Every route error
|
|
1539
|
+
* path logs the route, journals the failure, sets CORS and answers the error
|
|
1540
|
+
* envelope — so a handler crash is never invisible in the logs or in
|
|
1541
|
+
* `/__aimock/journal`.
|
|
1542
|
+
*
|
|
1543
|
+
* ONE request produces ONE entry. `res.headersSent` is not the test for that:
|
|
1544
|
+
* a handler that journaled its 200 and then threw before `writeHead` has
|
|
1545
|
+
* sent no headers, and journaling again would leave two contradicting
|
|
1546
|
+
* entries for one request. So the existing entry is looked up by request
|
|
1547
|
+
* identity ({@link lastJournalEntryFor}) and AMENDED — status to what the
|
|
1548
|
+
* client actually got when the response is still changeable, or interrupted
|
|
1549
|
+
* when it isn't — and a fresh entry is
|
|
1550
|
+
* added only when the handler journaled nothing. That holds after the
|
|
1551
|
+
* response too: a handler that answered and then crashed before its own
|
|
1552
|
+
* journal write gets an entry carrying the status already sent, so a
|
|
1553
|
+
* post-response crash is never invisible.
|
|
1554
|
+
*
|
|
1555
|
+
* Once headers are on the wire the status can't change, so the socket is
|
|
1556
|
+
* destroyed: a clean `end()` reads as EOF and the client treats a truncated
|
|
1557
|
+
* stream as a complete response. The chat-style SSE routes whose clients
|
|
1558
|
+
* surface an error frame pass `streamEvent`; that frame is written first,
|
|
1559
|
+
* but the tear-down still follows it.
|
|
1560
|
+
*
|
|
1561
|
+
* A body that blew its size cap is the CALLER's fault, not a server fault:
|
|
1562
|
+
* it logs at warn, but if the reader destroyed the socket it journals an
|
|
1563
|
+
* interruption with status 0 because no 4xx reached the client. A client
|
|
1564
|
+
* that hung up before its body arrived
|
|
1565
|
+
* ({@link clientAbortedMidBody}) is not a fault at all: it logs at warn and
|
|
1566
|
+
* journals a torn socket (`interrupted`, status 0), never a 500 the client
|
|
1567
|
+
* could not have received.
|
|
1568
|
+
*/
|
|
1569
|
+
function routeError(req, res, err, pathname, opts) {
|
|
1570
|
+
const route = `${req.method ?? "?"} ${pathname}`;
|
|
1571
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
1572
|
+
const clientFault = err instanceof require_helpers.RequestBodyTooLargeError;
|
|
1573
|
+
const clientAbort = clientAbortedMidBody(req, res, err);
|
|
1574
|
+
const status = clientFault ? 400 : 500;
|
|
1575
|
+
if (clientAbort) logger.warn(`${route}: client aborted mid-body (${msg})`);
|
|
1576
|
+
else if (clientFault) logger.warn(`${route}: ${msg}`);
|
|
1577
|
+
else logger.error(`${route}: ${msg}`);
|
|
1578
|
+
if (err instanceof Error && err.stack) logger.debug(err.stack);
|
|
1579
|
+
const existing = lastJournalEntryFor(req);
|
|
1580
|
+
const bodyLimitDisconnect = clientFault && (req.destroyed || res.destroyed);
|
|
1581
|
+
if ((clientAbort || bodyLimitDisconnect) && !res.headersSent) {
|
|
1582
|
+
const interruptReason = bodyLimitDisconnect ? "request body exceeded size limit" : "client aborted";
|
|
1583
|
+
if (existing) {
|
|
1584
|
+
if (!existing.response.interrupted) {
|
|
1585
|
+
existing.response.status = 0;
|
|
1586
|
+
existing.response.interrupted = true;
|
|
1587
|
+
existing.response.interruptReason = interruptReason;
|
|
1588
|
+
}
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
try {
|
|
1592
|
+
journal.add({
|
|
1593
|
+
method: req.method ?? "?",
|
|
1594
|
+
path: req.url ?? "?",
|
|
1595
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
1596
|
+
body: null,
|
|
1597
|
+
...opts?.service ? { service: opts.service } : {},
|
|
1598
|
+
response: {
|
|
1599
|
+
status: 0,
|
|
1600
|
+
fixture: null,
|
|
1601
|
+
source: "internal",
|
|
1602
|
+
interrupted: true,
|
|
1603
|
+
interruptReason
|
|
1604
|
+
}
|
|
1605
|
+
});
|
|
1606
|
+
} catch (jErr) {
|
|
1607
|
+
logger.warn(`${route}: journal write failed after body-read interruption: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1608
|
+
}
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
if (!res.headersSent) {
|
|
1612
|
+
if (existing) existing.response.status = status;
|
|
1613
|
+
else try {
|
|
1614
|
+
journal.add({
|
|
1615
|
+
method: req.method ?? "?",
|
|
1616
|
+
path: req.url ?? "?",
|
|
1617
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
1618
|
+
body: null,
|
|
1619
|
+
...opts?.service ? { service: opts.service } : {},
|
|
1620
|
+
response: {
|
|
1621
|
+
status,
|
|
1622
|
+
fixture: null,
|
|
1623
|
+
source: "internal"
|
|
1624
|
+
}
|
|
1625
|
+
});
|
|
1626
|
+
} catch (jErr) {
|
|
1627
|
+
logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1628
|
+
}
|
|
1629
|
+
setCorsHeaders(res);
|
|
1630
|
+
require_sse_writer.writeErrorResponse(res, status, opts?.envelope ? opts.envelope(msg, status) : JSON.stringify({ error: {
|
|
1631
|
+
message: msg,
|
|
1632
|
+
type: errorTypeForStatus(status)
|
|
1633
|
+
} }));
|
|
1634
|
+
} else {
|
|
1635
|
+
const midStreamReason = clientAbort ? "client aborted" : "handler crashed mid-stream";
|
|
1636
|
+
if (existing) {
|
|
1637
|
+
if (res.writableEnded) {
|
|
1638
|
+
if (existing.response.error === void 0) existing.response.error = msg;
|
|
1639
|
+
} else if (!existing.response.interrupted) {
|
|
1640
|
+
existing.response.interrupted = true;
|
|
1641
|
+
existing.response.interruptReason = midStreamReason;
|
|
1642
|
+
}
|
|
1643
|
+
} else try {
|
|
1644
|
+
journal.add({
|
|
1645
|
+
method: req.method ?? "?",
|
|
1646
|
+
path: req.url ?? "?",
|
|
1647
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
1648
|
+
body: null,
|
|
1649
|
+
...opts?.service ? { service: opts.service } : {},
|
|
1650
|
+
response: {
|
|
1651
|
+
status: res.statusCode,
|
|
1652
|
+
fixture: null,
|
|
1653
|
+
source: "internal",
|
|
1654
|
+
...res.writableEnded ? { error: msg } : {
|
|
1655
|
+
interrupted: true,
|
|
1656
|
+
interruptReason: midStreamReason
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
});
|
|
1660
|
+
} catch (jErr) {
|
|
1661
|
+
logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1662
|
+
}
|
|
1663
|
+
if (res.writableEnded) return;
|
|
1664
|
+
if (opts?.streamEvent) try {
|
|
1665
|
+
res.write(opts.streamEvent(msg, status), () => {
|
|
1666
|
+
if (!res.destroyed) res.destroy();
|
|
1667
|
+
});
|
|
1668
|
+
} catch (writeErr) {
|
|
1669
|
+
logger.debug("Failed to write error recovery response:", writeErr);
|
|
1670
|
+
res.destroy();
|
|
1671
|
+
}
|
|
1672
|
+
else res.destroy();
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
for (const { handler } of mountList) {
|
|
1184
1676
|
if (handler.setJournal) handler.setJournal(journal);
|
|
1185
1677
|
if (registry && handler.setRegistry) handler.setRegistry(registry);
|
|
1678
|
+
if (handler.setLogger) handler.setLogger(logger);
|
|
1186
1679
|
}
|
|
1187
1680
|
if (registry) registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
|
|
1188
1681
|
const server = node_http.createServer((req, res) => {
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
const url = req.url ?? "?";
|
|
1194
|
-
defaults.logger.warn(`Unhandled request error on ${method} ${url}: ${msg}\n${stack}`);
|
|
1195
|
-
if (!res.headersSent) {
|
|
1196
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1197
|
-
res.end(JSON.stringify({ error: {
|
|
1198
|
-
message: msg,
|
|
1199
|
-
type: "server_error"
|
|
1200
|
-
} }));
|
|
1201
|
-
} else if (!res.writableEnded) res.end();
|
|
1682
|
+
requestScope.run(req, () => {
|
|
1683
|
+
handleHttpRequest(req, res).catch((err) => {
|
|
1684
|
+
routeError(req, res, err, req.url ?? "?");
|
|
1685
|
+
});
|
|
1202
1686
|
});
|
|
1203
1687
|
});
|
|
1204
1688
|
async function handleHttpRequest(req, res) {
|
|
1205
1689
|
const startTime = registry ? process.hrtime.bigint() : 0n;
|
|
1206
|
-
const
|
|
1207
|
-
|
|
1690
|
+
const { id: requestId, generated: requestIdMinted } = require_helpers.resolveRequestId(req.headers);
|
|
1691
|
+
req.headers["x-request-id"] = requestId;
|
|
1692
|
+
require_helpers.markMintedRequestId(req, requestIdMinted);
|
|
1693
|
+
res.setHeader("X-Request-Id", requestId);
|
|
1694
|
+
let pathname = "";
|
|
1695
|
+
if (registry) {
|
|
1696
|
+
let recorded = false;
|
|
1697
|
+
const recordOutcome = (status) => {
|
|
1698
|
+
if (recorded) return;
|
|
1699
|
+
recorded = true;
|
|
1700
|
+
try {
|
|
1701
|
+
const normalizedPath = require_metrics.normalizePathLabel(pathname, mountList.map((m) => m.path));
|
|
1702
|
+
const method = req.method ?? "UNKNOWN";
|
|
1703
|
+
registry.incrementCounter("aimock_requests_total", {
|
|
1704
|
+
method,
|
|
1705
|
+
path: normalizedPath,
|
|
1706
|
+
status
|
|
1707
|
+
});
|
|
1708
|
+
const elapsed = Number(process.hrtime.bigint() - startTime) / 1e9;
|
|
1709
|
+
registry.observeHistogram("aimock_request_duration_seconds", {
|
|
1710
|
+
method,
|
|
1711
|
+
path: normalizedPath
|
|
1712
|
+
}, elapsed);
|
|
1713
|
+
} catch (err) {
|
|
1714
|
+
defaults.logger.warn("metrics instrumentation error", err);
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
res.on("finish", () => recordOutcome(String(res.statusCode)));
|
|
1718
|
+
res.on("close", () => {
|
|
1719
|
+
if (!res.writableFinished) recordOutcome(require_metrics.DESTROYED_STATUS_LABEL);
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
let parsedUrl;
|
|
1723
|
+
try {
|
|
1724
|
+
parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
1725
|
+
} catch (err) {
|
|
1726
|
+
const route = `${req.method ?? "?"} ${req.url ?? "?"}`;
|
|
1727
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
1728
|
+
logger.error(`${route}: ${msg}`);
|
|
1729
|
+
if (err instanceof Error && err.stack) logger.debug(err.stack);
|
|
1730
|
+
try {
|
|
1731
|
+
journal.add({
|
|
1732
|
+
method: req.method ?? "?",
|
|
1733
|
+
path: req.url ?? "?",
|
|
1734
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
1735
|
+
body: null,
|
|
1736
|
+
response: {
|
|
1737
|
+
status: 400,
|
|
1738
|
+
fixture: null,
|
|
1739
|
+
source: "internal"
|
|
1740
|
+
}
|
|
1741
|
+
});
|
|
1742
|
+
} catch (jErr) {
|
|
1743
|
+
logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1744
|
+
}
|
|
1745
|
+
setCorsHeaders(res);
|
|
1746
|
+
require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
|
|
1747
|
+
message: "Invalid request target",
|
|
1748
|
+
type: "invalid_request_error"
|
|
1749
|
+
} }));
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1752
|
+
pathname = parsedUrl.pathname;
|
|
1208
1753
|
const originalPathname = pathname;
|
|
1209
1754
|
const isOpenRouter = require_openrouter_chat.isOpenRouterPath(originalPathname);
|
|
1210
1755
|
const isBytePlusArk = defaults.record?.providers.byteplus !== void 0 && originalPathname.startsWith("/api/v3/");
|
|
1211
|
-
if (registry) res.on("finish", () => {
|
|
1212
|
-
try {
|
|
1213
|
-
const normalizedPath = require_metrics.normalizePathLabel(pathname);
|
|
1214
|
-
const method = req.method ?? "UNKNOWN";
|
|
1215
|
-
const status = String(res.statusCode);
|
|
1216
|
-
registry.incrementCounter("aimock_requests_total", {
|
|
1217
|
-
method,
|
|
1218
|
-
path: normalizedPath,
|
|
1219
|
-
status
|
|
1220
|
-
});
|
|
1221
|
-
const elapsed = Number(process.hrtime.bigint() - startTime) / 1e9;
|
|
1222
|
-
registry.observeHistogram("aimock_request_duration_seconds", {
|
|
1223
|
-
method,
|
|
1224
|
-
path: normalizedPath
|
|
1225
|
-
}, elapsed);
|
|
1226
|
-
} catch (err) {
|
|
1227
|
-
defaults.logger.warn("metrics instrumentation error", err);
|
|
1228
|
-
}
|
|
1229
|
-
});
|
|
1230
1756
|
if (req.method === "OPTIONS" && (!resolvedAuth.policy.enabled || req.headers.origin !== void 0 && req.headers["access-control-request-method"] !== void 0)) {
|
|
1231
1757
|
handleOptions(res);
|
|
1232
1758
|
return;
|
|
1233
1759
|
}
|
|
1234
|
-
if (!(req.method === "GET" && (pathname === HEALTH_PATH || pathname === READY_PATH || pathname ===
|
|
1760
|
+
if (!(req.method === "GET" && (pathname === require_route_registry.HEALTH_PATH || pathname === require_route_registry.READY_PATH || pathname === require_route_registry.METRICS_PATH))) {
|
|
1235
1761
|
if (!require_api_key_auth.validateRequestApiKey(req, resolvedAuth.policy).ok) {
|
|
1236
1762
|
require_api_key_auth.writeApiKeyHttpRejection(res, setCorsHeaders);
|
|
1237
1763
|
return;
|
|
1238
1764
|
}
|
|
1239
1765
|
require_api_key_auth.markAuthenticatedRequest(req, resolvedAuth.policy);
|
|
1240
1766
|
}
|
|
1241
|
-
if (pathname.startsWith(CONTROL_PREFIX)) {
|
|
1242
|
-
await handleControlAPI(req, res, pathname, parsedUrl.searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults);
|
|
1767
|
+
if (pathname.startsWith(require_route_registry.CONTROL_PREFIX)) {
|
|
1768
|
+
await handleControlAPI(req, res, pathname, parsedUrl.searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults, mountList);
|
|
1243
1769
|
return;
|
|
1244
1770
|
}
|
|
1245
|
-
if (
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
if (await handler.handleRequest(req, res, subPath)) return;
|
|
1249
|
-
}
|
|
1771
|
+
for (const { path: mountPath, handler } of mountList) if (pathname === mountPath || pathname.startsWith(mountPath + "/")) {
|
|
1772
|
+
const subPath = pathname.slice(mountPath.length) || "/";
|
|
1773
|
+
if (await handler.handleRequest(req, res, subPath)) return;
|
|
1250
1774
|
}
|
|
1251
|
-
if (pathname === OLLAMA_CHAT_PATH && req.method === "POST") {
|
|
1775
|
+
if (pathname === require_route_registry.OLLAMA_CHAT_PATH && req.method === "POST") {
|
|
1252
1776
|
try {
|
|
1253
1777
|
await require_ollama.handleOllama(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1254
1778
|
} catch (err) {
|
|
1255
|
-
|
|
1256
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1257
|
-
message: msg,
|
|
1258
|
-
type: "server_error"
|
|
1259
|
-
} }));
|
|
1260
|
-
else if (!res.writableEnded) res.destroy();
|
|
1779
|
+
routeError(req, res, err, pathname);
|
|
1261
1780
|
}
|
|
1262
1781
|
return;
|
|
1263
1782
|
}
|
|
1264
|
-
if (pathname === OLLAMA_GENERATE_PATH && req.method === "POST") {
|
|
1783
|
+
if (pathname === require_route_registry.OLLAMA_GENERATE_PATH && req.method === "POST") {
|
|
1265
1784
|
try {
|
|
1266
1785
|
await require_ollama.handleOllamaGenerate(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1267
1786
|
} catch (err) {
|
|
1268
|
-
|
|
1269
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1270
|
-
message: msg,
|
|
1271
|
-
type: "server_error"
|
|
1272
|
-
} }));
|
|
1273
|
-
else if (!res.writableEnded) res.destroy();
|
|
1787
|
+
routeError(req, res, err, pathname);
|
|
1274
1788
|
}
|
|
1275
1789
|
return;
|
|
1276
1790
|
}
|
|
1277
|
-
if ((pathname === OLLAMA_EMBEDDINGS_PATH || pathname === OLLAMA_EMBED_PATH) && req.method === "POST") {
|
|
1791
|
+
if ((pathname === require_route_registry.OLLAMA_EMBEDDINGS_PATH || pathname === require_route_registry.OLLAMA_EMBED_PATH) && req.method === "POST") {
|
|
1278
1792
|
try {
|
|
1279
1793
|
await require_ollama.handleOllamaEmbeddings(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1280
1794
|
} catch (err) {
|
|
1281
|
-
|
|
1282
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1283
|
-
message: msg,
|
|
1284
|
-
type: "server_error"
|
|
1285
|
-
} }));
|
|
1286
|
-
else if (!res.writableEnded) res.destroy();
|
|
1795
|
+
routeError(req, res, err, pathname);
|
|
1287
1796
|
}
|
|
1288
1797
|
return;
|
|
1289
1798
|
}
|
|
1290
|
-
if (pathname === OLLAMA_TAGS_PATH && req.method === "GET") {
|
|
1799
|
+
if (pathname === require_route_registry.OLLAMA_TAGS_PATH && req.method === "GET") {
|
|
1291
1800
|
setCorsHeaders(res);
|
|
1292
1801
|
const modelIds = /* @__PURE__ */ new Set();
|
|
1293
1802
|
for (const f of fixtures) if (f.match.model && typeof f.match.model === "string") modelIds.add(f.match.model);
|
|
@@ -1303,212 +1812,86 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1303
1812
|
res.end(JSON.stringify({ models }));
|
|
1304
1813
|
return;
|
|
1305
1814
|
}
|
|
1306
|
-
const openRouterVideoContentMatch = pathname.match(
|
|
1815
|
+
const openRouterVideoContentMatch = pathname.match(require_route_registry.OPENROUTER_VIDEO_CONTENT_RE);
|
|
1307
1816
|
if (openRouterVideoContentMatch && req.method === "GET") {
|
|
1308
1817
|
try {
|
|
1309
1818
|
await require_openrouter_video.handleOpenRouterVideoContent(req, res, openRouterVideoContentMatch[1], journal, defaults, setCorsHeaders, openRouterVideoJobs);
|
|
1310
1819
|
} catch (err) {
|
|
1311
|
-
|
|
1312
|
-
defaults.logger.error(`openrouter-video content: ${msg}`);
|
|
1313
|
-
if (!res.headersSent) {
|
|
1314
|
-
try {
|
|
1315
|
-
journal.add({
|
|
1316
|
-
method: req.method ?? "GET",
|
|
1317
|
-
path: req.url ?? pathname,
|
|
1318
|
-
headers: require_helpers.flattenHeaders(req.headers),
|
|
1319
|
-
body: null,
|
|
1320
|
-
response: {
|
|
1321
|
-
status: 500,
|
|
1322
|
-
fixture: null
|
|
1323
|
-
}
|
|
1324
|
-
});
|
|
1325
|
-
} catch (jErr) {
|
|
1326
|
-
defaults.logger.warn(`openrouter-video content: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1327
|
-
}
|
|
1328
|
-
require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1329
|
-
message: msg,
|
|
1330
|
-
type: "server_error"
|
|
1331
|
-
} }));
|
|
1332
|
-
} else if (!res.writableEnded) res.destroy();
|
|
1820
|
+
routeError(req, res, err, pathname);
|
|
1333
1821
|
}
|
|
1334
1822
|
return;
|
|
1335
1823
|
}
|
|
1336
|
-
if (pathname === OPENROUTER_VIDEO_MODELS_PATH && req.method === "GET") {
|
|
1824
|
+
if (pathname === require_route_registry.OPENROUTER_VIDEO_MODELS_PATH && req.method === "GET") {
|
|
1337
1825
|
try {
|
|
1338
1826
|
await require_openrouter_video.handleOpenRouterVideoModels(req, res, fixtures, journal, defaults, setCorsHeaders);
|
|
1339
1827
|
} catch (err) {
|
|
1340
|
-
|
|
1341
|
-
defaults.logger.error(`openrouter-video models: ${msg}`);
|
|
1342
|
-
if (!res.headersSent) {
|
|
1343
|
-
try {
|
|
1344
|
-
journal.add({
|
|
1345
|
-
method: req.method ?? "GET",
|
|
1346
|
-
path: req.url ?? pathname,
|
|
1347
|
-
headers: require_helpers.flattenHeaders(req.headers),
|
|
1348
|
-
body: null,
|
|
1349
|
-
response: {
|
|
1350
|
-
status: 500,
|
|
1351
|
-
fixture: null
|
|
1352
|
-
}
|
|
1353
|
-
});
|
|
1354
|
-
} catch (jErr) {
|
|
1355
|
-
defaults.logger.warn(`openrouter-video models: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1356
|
-
}
|
|
1357
|
-
require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1358
|
-
message: msg,
|
|
1359
|
-
type: "server_error"
|
|
1360
|
-
} }));
|
|
1361
|
-
} else if (!res.writableEnded) res.destroy();
|
|
1828
|
+
routeError(req, res, err, pathname);
|
|
1362
1829
|
}
|
|
1363
1830
|
return;
|
|
1364
1831
|
}
|
|
1365
|
-
const openRouterVideoStatusMatch = pathname.match(
|
|
1832
|
+
const openRouterVideoStatusMatch = pathname.match(require_route_registry.OPENROUTER_VIDEO_STATUS_RE);
|
|
1366
1833
|
if (openRouterVideoStatusMatch && req.method === "GET") {
|
|
1367
1834
|
try {
|
|
1368
1835
|
await require_openrouter_video.handleOpenRouterVideoStatus(req, res, openRouterVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
|
|
1369
1836
|
} catch (err) {
|
|
1370
|
-
|
|
1371
|
-
defaults.logger.error(`openrouter-video status: ${msg}`);
|
|
1372
|
-
if (!res.headersSent) {
|
|
1373
|
-
try {
|
|
1374
|
-
journal.add({
|
|
1375
|
-
method: req.method ?? "GET",
|
|
1376
|
-
path: req.url ?? pathname,
|
|
1377
|
-
headers: require_helpers.flattenHeaders(req.headers),
|
|
1378
|
-
body: null,
|
|
1379
|
-
response: {
|
|
1380
|
-
status: 500,
|
|
1381
|
-
fixture: null
|
|
1382
|
-
}
|
|
1383
|
-
});
|
|
1384
|
-
} catch (jErr) {
|
|
1385
|
-
defaults.logger.warn(`openrouter-video status: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1386
|
-
}
|
|
1387
|
-
require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1388
|
-
message: msg,
|
|
1389
|
-
type: "server_error"
|
|
1390
|
-
} }));
|
|
1391
|
-
} else if (!res.writableEnded) res.destroy();
|
|
1837
|
+
routeError(req, res, err, pathname);
|
|
1392
1838
|
}
|
|
1393
1839
|
return;
|
|
1394
1840
|
}
|
|
1395
|
-
if (pathname === OPENROUTER_VIDEOS_PATH && req.method === "POST") {
|
|
1841
|
+
if (pathname === require_route_registry.OPENROUTER_VIDEOS_PATH && req.method === "POST") {
|
|
1396
1842
|
setCorsHeaders(res);
|
|
1397
1843
|
try {
|
|
1398
1844
|
await require_openrouter_video.handleOpenRouterVideoCreate(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
|
|
1399
1845
|
} catch (err) {
|
|
1400
|
-
|
|
1401
|
-
defaults.logger.error(`openrouter-video submit: ${msg}`);
|
|
1402
|
-
if (!res.headersSent) {
|
|
1403
|
-
try {
|
|
1404
|
-
journal.add({
|
|
1405
|
-
method: req.method ?? "POST",
|
|
1406
|
-
path: req.url ?? pathname,
|
|
1407
|
-
headers: require_helpers.flattenHeaders(req.headers),
|
|
1408
|
-
body: null,
|
|
1409
|
-
response: {
|
|
1410
|
-
status: 500,
|
|
1411
|
-
fixture: null
|
|
1412
|
-
}
|
|
1413
|
-
});
|
|
1414
|
-
} catch (jErr) {
|
|
1415
|
-
defaults.logger.warn(`openrouter-video submit: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1416
|
-
}
|
|
1417
|
-
require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1418
|
-
message: msg,
|
|
1419
|
-
type: "server_error"
|
|
1420
|
-
} }));
|
|
1421
|
-
} else if (!res.writableEnded) res.destroy();
|
|
1846
|
+
routeError(req, res, err, pathname);
|
|
1422
1847
|
}
|
|
1423
1848
|
return;
|
|
1424
1849
|
}
|
|
1425
|
-
const bytePlusVideoStatusMatch = pathname.match(
|
|
1850
|
+
const bytePlusVideoStatusMatch = pathname.match(require_route_registry.BYTEPLUS_VIDEO_STATUS_RE);
|
|
1426
1851
|
if (bytePlusVideoStatusMatch && req.method === "GET") {
|
|
1427
1852
|
try {
|
|
1428
1853
|
await require_byteplus_video.handleBytePlusVideoStatus(req, res, bytePlusVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
|
|
1429
1854
|
} catch (err) {
|
|
1430
|
-
|
|
1431
|
-
defaults.logger.error(`byteplus-video status: ${msg}`);
|
|
1432
|
-
if (!res.headersSent) {
|
|
1433
|
-
try {
|
|
1434
|
-
journal.add({
|
|
1435
|
-
method: req.method ?? "GET",
|
|
1436
|
-
path: req.url ?? pathname,
|
|
1437
|
-
headers: require_helpers.flattenHeaders(req.headers),
|
|
1438
|
-
body: null,
|
|
1439
|
-
response: {
|
|
1440
|
-
status: 500,
|
|
1441
|
-
fixture: null
|
|
1442
|
-
}
|
|
1443
|
-
});
|
|
1444
|
-
} catch (jErr) {
|
|
1445
|
-
defaults.logger.warn(`byteplus-video status: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1446
|
-
}
|
|
1447
|
-
require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1448
|
-
message: msg,
|
|
1449
|
-
type: "server_error"
|
|
1450
|
-
} }));
|
|
1451
|
-
} else if (!res.writableEnded) res.destroy();
|
|
1855
|
+
routeError(req, res, err, pathname);
|
|
1452
1856
|
}
|
|
1453
1857
|
return;
|
|
1454
1858
|
}
|
|
1455
|
-
if (
|
|
1859
|
+
if (require_route_registry.BYTEPLUS_VIDEO_SUBMIT_RE.test(pathname) && req.method === "POST") {
|
|
1456
1860
|
setCorsHeaders(res);
|
|
1457
1861
|
try {
|
|
1458
1862
|
await require_byteplus_video.handleBytePlusVideoCreate(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
|
|
1459
1863
|
} catch (err) {
|
|
1460
|
-
|
|
1461
|
-
defaults.logger.error(`byteplus-video submit: ${msg}`);
|
|
1462
|
-
if (!res.headersSent) {
|
|
1463
|
-
try {
|
|
1464
|
-
journal.add({
|
|
1465
|
-
method: req.method ?? "POST",
|
|
1466
|
-
path: req.url ?? pathname,
|
|
1467
|
-
headers: require_helpers.flattenHeaders(req.headers),
|
|
1468
|
-
body: null,
|
|
1469
|
-
response: {
|
|
1470
|
-
status: 500,
|
|
1471
|
-
fixture: null
|
|
1472
|
-
}
|
|
1473
|
-
});
|
|
1474
|
-
} catch (jErr) {
|
|
1475
|
-
defaults.logger.warn(`byteplus-video submit: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1476
|
-
}
|
|
1477
|
-
require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1478
|
-
message: msg,
|
|
1479
|
-
type: "server_error"
|
|
1480
|
-
} }));
|
|
1481
|
-
} else if (!res.writableEnded) res.destroy();
|
|
1864
|
+
routeError(req, res, err, pathname);
|
|
1482
1865
|
}
|
|
1483
1866
|
return;
|
|
1484
1867
|
}
|
|
1485
|
-
if (pathname ===
|
|
1868
|
+
if (pathname === require_route_registry.OPENROUTER_MODELS_PATH && req.method === "GET") {
|
|
1486
1869
|
require_openrouter_chat.handleOpenRouterModels(req, res, fixtures, journal, defaults, setCorsHeaders);
|
|
1487
1870
|
return;
|
|
1488
1871
|
}
|
|
1489
|
-
if (pathname ===
|
|
1872
|
+
if (pathname === require_route_registry.OPENROUTER_KEY_PATH && req.method === "GET") {
|
|
1490
1873
|
require_openrouter_chat.handleOpenRouterKey(req, res, journal, setCorsHeaders);
|
|
1491
1874
|
return;
|
|
1492
1875
|
}
|
|
1493
|
-
if (pathname ===
|
|
1876
|
+
if (pathname === require_route_registry.OPENROUTER_CREDITS_PATH && req.method === "GET") {
|
|
1494
1877
|
require_openrouter_chat.handleOpenRouterCredits(req, res, journal, setCorsHeaders);
|
|
1495
1878
|
return;
|
|
1496
1879
|
}
|
|
1497
1880
|
let azureDeploymentId;
|
|
1498
|
-
const azureMatch = pathname.match(AZURE_DEPLOYMENT_RE);
|
|
1881
|
+
const azureMatch = pathname.match(require_route_registry.AZURE_DEPLOYMENT_RE);
|
|
1499
1882
|
if (azureMatch && req.method === "POST") {
|
|
1500
1883
|
azureDeploymentId = azureMatch[1];
|
|
1501
1884
|
pathname = `/v1/${azureMatch[2]}`;
|
|
1502
1885
|
}
|
|
1503
1886
|
if (!azureDeploymentId) pathname = normalizeCompatPath(pathname, logger);
|
|
1504
|
-
if (pathname === HEALTH_PATH && req.method === "GET") {
|
|
1887
|
+
if (pathname === require_route_registry.HEALTH_PATH && req.method === "GET") {
|
|
1505
1888
|
setCorsHeaders(res);
|
|
1506
|
-
if (
|
|
1889
|
+
if (mountList.length > 0) {
|
|
1507
1890
|
const services = { llm: {
|
|
1508
1891
|
status: "ok",
|
|
1509
1892
|
fixtures: fixtures.length
|
|
1510
1893
|
} };
|
|
1511
|
-
for (const { path: mountPath, handler } of
|
|
1894
|
+
for (const { path: mountPath, handler } of mountList) if (handler.health) {
|
|
1512
1895
|
const name = mountPath.replace(/^\//, "");
|
|
1513
1896
|
services[name] = handler.health();
|
|
1514
1897
|
}
|
|
@@ -1523,13 +1906,13 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1523
1906
|
}
|
|
1524
1907
|
return;
|
|
1525
1908
|
}
|
|
1526
|
-
if (pathname === READY_PATH && req.method === "GET") {
|
|
1909
|
+
if (pathname === require_route_registry.READY_PATH && req.method === "GET") {
|
|
1527
1910
|
setCorsHeaders(res);
|
|
1528
1911
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1529
1912
|
res.end(JSON.stringify({ status: "ready" }));
|
|
1530
1913
|
return;
|
|
1531
1914
|
}
|
|
1532
|
-
if (pathname ===
|
|
1915
|
+
if (pathname === require_route_registry.METRICS_PATH && req.method === "GET") {
|
|
1533
1916
|
if (!registry) {
|
|
1534
1917
|
handleNotFound(res, "Not found");
|
|
1535
1918
|
return;
|
|
@@ -1539,7 +1922,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1539
1922
|
res.end(registry.serialize());
|
|
1540
1923
|
return;
|
|
1541
1924
|
}
|
|
1542
|
-
if (pathname === MODELS_PATH && req.method === "GET") {
|
|
1925
|
+
if (pathname === require_route_registry.MODELS_PATH && req.method === "GET") {
|
|
1543
1926
|
setCorsHeaders(res);
|
|
1544
1927
|
const modelIds = /* @__PURE__ */ new Set();
|
|
1545
1928
|
for (const f of fixtures) if (f.match.model && typeof f.match.model === "string") modelIds.add(f.match.model);
|
|
@@ -1556,14 +1939,261 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1556
1939
|
}));
|
|
1557
1940
|
return;
|
|
1558
1941
|
}
|
|
1559
|
-
|
|
1942
|
+
const batchesCancelMatch = pathname.match(require_route_registry.BATCHES_CANCEL_RE);
|
|
1943
|
+
if (batchesCancelMatch && req.method === "POST") {
|
|
1944
|
+
try {
|
|
1945
|
+
await require_helpers.readBody(req);
|
|
1946
|
+
await require_batches.handleBatchesCancel(req, res, batchesCancelMatch[1], journal, defaults, setCorsHeaders);
|
|
1947
|
+
} catch (err) {
|
|
1948
|
+
routeError(req, res, err, pathname, { service: "batches" });
|
|
1949
|
+
}
|
|
1950
|
+
return;
|
|
1951
|
+
}
|
|
1952
|
+
const batchesIdMatch = pathname.match(require_route_registry.BATCHES_ID_RE);
|
|
1953
|
+
if (batchesIdMatch && req.method === "GET") {
|
|
1954
|
+
try {
|
|
1955
|
+
await require_batches.handleBatchesRetrieve(req, res, batchesIdMatch[1], journal, defaults, setCorsHeaders);
|
|
1956
|
+
} catch (err) {
|
|
1957
|
+
routeError(req, res, err, pathname, { service: "batches" });
|
|
1958
|
+
}
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
if (pathname === require_route_registry.BATCHES_PATH && req.method === "GET") {
|
|
1962
|
+
try {
|
|
1963
|
+
await require_batches.handleBatchesList(req, res, journal, defaults, setCorsHeaders);
|
|
1964
|
+
} catch (err) {
|
|
1965
|
+
routeError(req, res, err, pathname, { service: "batches" });
|
|
1966
|
+
}
|
|
1967
|
+
return;
|
|
1968
|
+
}
|
|
1969
|
+
if (pathname === require_route_registry.BATCHES_PATH && req.method === "POST") {
|
|
1970
|
+
try {
|
|
1971
|
+
await require_batches.handleBatchesCreate(req, res, await require_helpers.readBody(req), journal, defaults, setCorsHeaders);
|
|
1972
|
+
} catch (err) {
|
|
1973
|
+
routeError(req, res, err, pathname, { service: "batches" });
|
|
1974
|
+
}
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
const filesContentMatch = pathname.match(require_route_registry.FILES_CONTENT_RE);
|
|
1978
|
+
if (filesContentMatch && req.method === "GET") {
|
|
1979
|
+
try {
|
|
1980
|
+
await require_files.handleFilesContent(req, res, filesContentMatch[1], journal, defaults, setCorsHeaders);
|
|
1981
|
+
} catch (err) {
|
|
1982
|
+
routeError(req, res, err, pathname, { service: "files" });
|
|
1983
|
+
}
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
const filesIdMatch = pathname.match(require_route_registry.FILES_ID_RE);
|
|
1987
|
+
if (filesIdMatch && req.method === "GET") {
|
|
1988
|
+
try {
|
|
1989
|
+
await require_files.handleFilesRetrieve(req, res, filesIdMatch[1], journal, defaults, setCorsHeaders);
|
|
1990
|
+
} catch (err) {
|
|
1991
|
+
routeError(req, res, err, pathname, { service: "files" });
|
|
1992
|
+
}
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
if (filesIdMatch && req.method === "DELETE") {
|
|
1996
|
+
try {
|
|
1997
|
+
await require_files.handleFilesDelete(req, res, filesIdMatch[1], journal, defaults, setCorsHeaders);
|
|
1998
|
+
} catch (err) {
|
|
1999
|
+
routeError(req, res, err, pathname, { service: "files" });
|
|
2000
|
+
}
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
if (pathname === require_route_registry.FILES_PATH && req.method === "GET") {
|
|
2004
|
+
try {
|
|
2005
|
+
await require_files.handleFilesList(req, res, journal, defaults, setCorsHeaders);
|
|
2006
|
+
} catch (err) {
|
|
2007
|
+
routeError(req, res, err, pathname, { service: "files" });
|
|
2008
|
+
}
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
if (pathname === require_route_registry.FILES_PATH && req.method === "POST") {
|
|
2012
|
+
try {
|
|
2013
|
+
await require_files.handleFilesCreate(req, res, (await require_helpers.readBodyBufferBounded(req, require_files.FILES_BODY_MAX_BYTES, require_files.FILES_BODY_DRAIN_MAX_BYTES)).buffer ?? require_files.FILES_BODY_OVERSIZED, journal, defaults, setCorsHeaders);
|
|
2014
|
+
} catch (err) {
|
|
2015
|
+
routeError(req, res, err, pathname, { service: "files" });
|
|
2016
|
+
}
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
const ftCancelMatch = pathname.match(require_route_registry.FINE_TUNING_CANCEL_RE);
|
|
2020
|
+
if (ftCancelMatch && req.method === "POST") {
|
|
2021
|
+
try {
|
|
2022
|
+
await require_helpers.readBody(req);
|
|
2023
|
+
await require_fine_tuning.handleFineTuningCancel(req, res, ftCancelMatch[1], journal, defaults, setCorsHeaders);
|
|
2024
|
+
} catch (err) {
|
|
2025
|
+
routeError(req, res, err, pathname, { service: "fine-tuning" });
|
|
2026
|
+
}
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
const ftEventsMatch = pathname.match(require_route_registry.FINE_TUNING_EVENTS_RE);
|
|
2030
|
+
if (ftEventsMatch && req.method === "GET") {
|
|
2031
|
+
try {
|
|
2032
|
+
await require_fine_tuning.handleFineTuningEvents(req, res, ftEventsMatch[1], journal, defaults, setCorsHeaders);
|
|
2033
|
+
} catch (err) {
|
|
2034
|
+
routeError(req, res, err, pathname, { service: "fine-tuning" });
|
|
2035
|
+
}
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
const ftIdMatch = pathname.match(require_route_registry.FINE_TUNING_ID_RE);
|
|
2039
|
+
if (ftIdMatch && req.method === "GET") {
|
|
2040
|
+
try {
|
|
2041
|
+
await require_fine_tuning.handleFineTuningRetrieve(req, res, ftIdMatch[1], journal, defaults, setCorsHeaders);
|
|
2042
|
+
} catch (err) {
|
|
2043
|
+
routeError(req, res, err, pathname, { service: "fine-tuning" });
|
|
2044
|
+
}
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2047
|
+
if (pathname === require_route_registry.FINE_TUNING_JOBS_PATH && req.method === "GET") {
|
|
2048
|
+
try {
|
|
2049
|
+
await require_fine_tuning.handleFineTuningList(req, res, journal, defaults, setCorsHeaders);
|
|
2050
|
+
} catch (err) {
|
|
2051
|
+
routeError(req, res, err, pathname, { service: "fine-tuning" });
|
|
2052
|
+
}
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
2055
|
+
if (pathname === require_route_registry.FINE_TUNING_JOBS_PATH && req.method === "POST") {
|
|
2056
|
+
try {
|
|
2057
|
+
await require_fine_tuning.handleFineTuningCreate(req, res, await require_helpers.readBody(req), journal, defaults, setCorsHeaders);
|
|
2058
|
+
} catch (err) {
|
|
2059
|
+
routeError(req, res, err, pathname, { service: "fine-tuning" });
|
|
2060
|
+
}
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
const vsSearchMatch = pathname.match(require_route_registry.VECTOR_STORES_SEARCH_RE);
|
|
2064
|
+
if (vsSearchMatch && req.method === "POST") {
|
|
2065
|
+
try {
|
|
2066
|
+
const raw = await require_helpers.readBody(req);
|
|
2067
|
+
await require_vector_stores.handleVectorStoresSearch(req, res, vsSearchMatch[1], raw, journal, defaults, setCorsHeaders);
|
|
2068
|
+
} catch (err) {
|
|
2069
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2070
|
+
}
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2073
|
+
const vsBatchFilesMatch = pathname.match(require_route_registry.VECTOR_STORES_BATCH_FILES_RE);
|
|
2074
|
+
if (vsBatchFilesMatch && req.method === "GET") {
|
|
2075
|
+
try {
|
|
2076
|
+
await require_vector_stores.handleVectorFileBatchesFiles(req, res, vsBatchFilesMatch[1], vsBatchFilesMatch[2], journal, defaults, setCorsHeaders);
|
|
2077
|
+
} catch (err) {
|
|
2078
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2079
|
+
}
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
const vsBatchCancelMatch = pathname.match(require_route_registry.VECTOR_STORES_BATCH_CANCEL_RE);
|
|
2083
|
+
if (vsBatchCancelMatch && req.method === "POST") {
|
|
2084
|
+
try {
|
|
2085
|
+
await require_helpers.readBody(req);
|
|
2086
|
+
await require_vector_stores.handleVectorFileBatchesCancel(req, res, vsBatchCancelMatch[1], vsBatchCancelMatch[2], journal, defaults, setCorsHeaders);
|
|
2087
|
+
} catch (err) {
|
|
2088
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2089
|
+
}
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
const vsBatchMatch = pathname.match(require_route_registry.VECTOR_STORES_BATCH_RE);
|
|
2093
|
+
if (vsBatchMatch && req.method === "GET") {
|
|
2094
|
+
try {
|
|
2095
|
+
await require_vector_stores.handleVectorFileBatchesRetrieve(req, res, vsBatchMatch[1], vsBatchMatch[2], journal, defaults, setCorsHeaders);
|
|
2096
|
+
} catch (err) {
|
|
2097
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2098
|
+
}
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
const vsFileMatch = pathname.match(require_route_registry.VECTOR_STORES_FILE_RE);
|
|
2102
|
+
if (vsFileMatch && req.method === "GET") {
|
|
2103
|
+
try {
|
|
2104
|
+
await require_vector_stores.handleVectorStoreFilesRetrieve(req, res, vsFileMatch[1], vsFileMatch[2], journal, defaults, setCorsHeaders);
|
|
2105
|
+
} catch (err) {
|
|
2106
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2107
|
+
}
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
if (vsFileMatch && req.method === "DELETE") {
|
|
2111
|
+
try {
|
|
2112
|
+
await require_vector_stores.handleVectorStoreFilesDelete(req, res, vsFileMatch[1], vsFileMatch[2], journal, defaults, setCorsHeaders);
|
|
2113
|
+
} catch (err) {
|
|
2114
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2115
|
+
}
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
const vsIdMatch = pathname.match(require_route_registry.VECTOR_STORES_ID_RE);
|
|
2119
|
+
if (vsIdMatch && req.method === "GET") {
|
|
2120
|
+
try {
|
|
2121
|
+
await require_vector_stores.handleVectorStoresRetrieve(req, res, vsIdMatch[1], journal, defaults, setCorsHeaders);
|
|
2122
|
+
} catch (err) {
|
|
2123
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2124
|
+
}
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
2127
|
+
if (vsIdMatch && req.method === "POST") {
|
|
2128
|
+
try {
|
|
2129
|
+
const raw = await require_helpers.readBody(req);
|
|
2130
|
+
await require_vector_stores.handleVectorStoresModify(req, res, vsIdMatch[1], raw, journal, defaults, setCorsHeaders);
|
|
2131
|
+
} catch (err) {
|
|
2132
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2133
|
+
}
|
|
2134
|
+
return;
|
|
2135
|
+
}
|
|
2136
|
+
if (vsIdMatch && req.method === "DELETE") {
|
|
2137
|
+
try {
|
|
2138
|
+
await require_vector_stores.handleVectorStoresDelete(req, res, vsIdMatch[1], journal, defaults, setCorsHeaders);
|
|
2139
|
+
} catch (err) {
|
|
2140
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2141
|
+
}
|
|
2142
|
+
return;
|
|
2143
|
+
}
|
|
2144
|
+
if (require_route_registry.VECTOR_STORES_FILES_RE.test(pathname) && req.method === "POST") {
|
|
2145
|
+
try {
|
|
2146
|
+
const raw = await require_helpers.readBody(req);
|
|
2147
|
+
const storeId = pathname.split("/")[3];
|
|
2148
|
+
await require_vector_stores.handleVectorStoreFilesCreate(req, res, storeId, raw, journal, defaults, setCorsHeaders);
|
|
2149
|
+
} catch (err) {
|
|
2150
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2151
|
+
}
|
|
2152
|
+
return;
|
|
2153
|
+
}
|
|
2154
|
+
if (require_route_registry.VECTOR_STORES_FILES_RE.test(pathname) && req.method === "GET") {
|
|
2155
|
+
try {
|
|
2156
|
+
const storeId = pathname.split("/")[3];
|
|
2157
|
+
await require_vector_stores.handleVectorStoreFilesList(req, res, storeId, journal, defaults, setCorsHeaders);
|
|
2158
|
+
} catch (err) {
|
|
2159
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2160
|
+
}
|
|
2161
|
+
return;
|
|
2162
|
+
}
|
|
2163
|
+
if (require_route_registry.VECTOR_STORES_FILE_BATCHES_RE.test(pathname) && req.method === "POST") {
|
|
2164
|
+
try {
|
|
2165
|
+
const raw = await require_helpers.readBody(req);
|
|
2166
|
+
const storeId = pathname.split("/")[3];
|
|
2167
|
+
await require_vector_stores.handleVectorFileBatchesCreate(req, res, storeId, raw, journal, defaults, setCorsHeaders);
|
|
2168
|
+
} catch (err) {
|
|
2169
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2170
|
+
}
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
if (pathname === require_route_registry.VECTOR_STORES_PATH && req.method === "GET") {
|
|
2174
|
+
try {
|
|
2175
|
+
await require_vector_stores.handleVectorStoresList(req, res, journal, defaults, setCorsHeaders);
|
|
2176
|
+
} catch (err) {
|
|
2177
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2178
|
+
}
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
if (pathname === require_route_registry.VECTOR_STORES_PATH && req.method === "POST") {
|
|
2182
|
+
try {
|
|
2183
|
+
await require_vector_stores.handleVectorStoresCreate(req, res, await require_helpers.readBody(req), journal, defaults, setCorsHeaders);
|
|
2184
|
+
} catch (err) {
|
|
2185
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2186
|
+
}
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
if (pathname === require_route_registry.REQUESTS_PATH) {
|
|
1560
2190
|
setCorsHeaders(res);
|
|
1561
2191
|
if (req.method === "GET") {
|
|
1562
2192
|
const limitParam = parsedUrl.searchParams.get("limit");
|
|
1563
2193
|
let opts;
|
|
1564
2194
|
if (limitParam) {
|
|
1565
|
-
const limit =
|
|
1566
|
-
if (Number.
|
|
2195
|
+
const limit = Number(limitParam);
|
|
2196
|
+
if (!/^\+?\d+$/.test(limitParam.trim()) || !Number.isSafeInteger(limit) || limit <= 0) {
|
|
1567
2197
|
require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
|
|
1568
2198
|
message: `Invalid limit parameter: "${limitParam}"`,
|
|
1569
2199
|
type: "invalid_request_error"
|
|
@@ -1586,74 +2216,39 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1586
2216
|
handleNotFound(res, "Not found");
|
|
1587
2217
|
return;
|
|
1588
2218
|
}
|
|
1589
|
-
if (pathname === RESPONSES_PATH && req.method === "POST") {
|
|
2219
|
+
if (pathname === require_route_registry.RESPONSES_PATH && req.method === "POST") {
|
|
1590
2220
|
try {
|
|
1591
2221
|
await require_responses.handleResponses(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1592
2222
|
} catch (err) {
|
|
1593
|
-
|
|
1594
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1595
|
-
message: msg,
|
|
1596
|
-
type: "server_error"
|
|
1597
|
-
} }));
|
|
1598
|
-
else if (!res.writableEnded) try {
|
|
1599
|
-
res.write(`event: error\ndata: ${JSON.stringify({ error: { message: msg } })}\n\n`);
|
|
1600
|
-
res.end();
|
|
1601
|
-
} catch (writeErr) {
|
|
1602
|
-
logger.debug("Failed to write error recovery response:", writeErr);
|
|
1603
|
-
}
|
|
2223
|
+
routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
1604
2224
|
}
|
|
1605
2225
|
return;
|
|
1606
2226
|
}
|
|
1607
|
-
if (pathname === MESSAGES_PATH && req.method === "POST") {
|
|
2227
|
+
if (pathname === require_route_registry.MESSAGES_PATH && req.method === "POST") {
|
|
1608
2228
|
try {
|
|
1609
2229
|
await require_messages.handleMessages(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1610
2230
|
} catch (err) {
|
|
1611
|
-
|
|
1612
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1613
|
-
message: msg,
|
|
1614
|
-
type: "server_error"
|
|
1615
|
-
} }));
|
|
1616
|
-
else if (!res.writableEnded) try {
|
|
1617
|
-
res.write(`event: error\ndata: ${JSON.stringify({ error: { message: msg } })}\n\n`);
|
|
1618
|
-
res.end();
|
|
1619
|
-
} catch (writeErr) {
|
|
1620
|
-
logger.debug("Failed to write error recovery response:", writeErr);
|
|
1621
|
-
}
|
|
2231
|
+
routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
1622
2232
|
}
|
|
1623
2233
|
return;
|
|
1624
2234
|
}
|
|
1625
|
-
if (pathname === COHERE_CHAT_PATH && req.method === "POST") {
|
|
2235
|
+
if (pathname === require_route_registry.COHERE_CHAT_PATH && req.method === "POST") {
|
|
1626
2236
|
try {
|
|
1627
2237
|
await require_cohere.handleCohere(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1628
2238
|
} catch (err) {
|
|
1629
|
-
|
|
1630
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1631
|
-
message: msg,
|
|
1632
|
-
type: "server_error"
|
|
1633
|
-
} }));
|
|
1634
|
-
else if (!res.writableEnded) try {
|
|
1635
|
-
res.write(`event: error\ndata: ${JSON.stringify({ error: { message: msg } })}\n\n`);
|
|
1636
|
-
res.end();
|
|
1637
|
-
} catch (writeErr) {
|
|
1638
|
-
logger.debug("Failed to write error recovery response:", writeErr);
|
|
1639
|
-
}
|
|
2239
|
+
routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
1640
2240
|
}
|
|
1641
2241
|
return;
|
|
1642
2242
|
}
|
|
1643
|
-
if (pathname === COHERE_EMBED_PATH && req.method === "POST") {
|
|
2243
|
+
if (pathname === require_route_registry.COHERE_EMBED_PATH && req.method === "POST") {
|
|
1644
2244
|
try {
|
|
1645
2245
|
await require_cohere.handleCohereEmbed(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1646
2246
|
} catch (err) {
|
|
1647
|
-
|
|
1648
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1649
|
-
message: msg,
|
|
1650
|
-
type: "server_error"
|
|
1651
|
-
} }));
|
|
1652
|
-
else if (!res.writableEnded) res.destroy();
|
|
2247
|
+
routeError(req, res, err, pathname);
|
|
1653
2248
|
}
|
|
1654
2249
|
return;
|
|
1655
2250
|
}
|
|
1656
|
-
if (pathname === EMBEDDINGS_PATH && req.method === "POST") {
|
|
2251
|
+
if (pathname === require_route_registry.EMBEDDINGS_PATH && req.method === "POST") {
|
|
1657
2252
|
try {
|
|
1658
2253
|
const deploymentId = azureDeploymentId;
|
|
1659
2254
|
const embeddingsProvider = azureDeploymentId ? "azure" : "openai";
|
|
@@ -1667,551 +2262,379 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1667
2262
|
} catch (err) {
|
|
1668
2263
|
if (!(err instanceof SyntaxError)) defaults.logger.error(`Unexpected error in Azure model injection: ${err instanceof Error ? err.message : String(err)}`);
|
|
1669
2264
|
}
|
|
1670
|
-
await require_embeddings.handleEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders, embeddingsProvider);
|
|
2265
|
+
await require_embeddings.handleEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders, embeddingsProvider, originalPathname === require_route_registry.EMBEDDINGS_PATH);
|
|
1671
2266
|
} catch (err) {
|
|
1672
|
-
|
|
1673
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1674
|
-
message: msg,
|
|
1675
|
-
type: "server_error"
|
|
1676
|
-
} }));
|
|
1677
|
-
else if (!res.writableEnded) res.destroy();
|
|
2267
|
+
routeError(req, res, err, pathname);
|
|
1678
2268
|
}
|
|
1679
2269
|
return;
|
|
1680
2270
|
}
|
|
1681
|
-
if (pathname === IMAGES_PATH && req.method === "POST") {
|
|
2271
|
+
if (pathname === require_route_registry.IMAGES_PATH && req.method === "POST") {
|
|
1682
2272
|
try {
|
|
1683
2273
|
await require_images.handleImages(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, "openai", void 0, isBytePlusArk ? "byteplus" : void 0);
|
|
1684
2274
|
} catch (err) {
|
|
1685
|
-
|
|
1686
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1687
|
-
message: msg,
|
|
1688
|
-
type: "server_error"
|
|
1689
|
-
} }));
|
|
1690
|
-
else if (!res.writableEnded) res.destroy();
|
|
2275
|
+
routeError(req, res, err, pathname);
|
|
1691
2276
|
}
|
|
1692
2277
|
return;
|
|
1693
2278
|
}
|
|
1694
|
-
if (pathname === IMAGES_EDIT_PATH && req.method === "POST") {
|
|
2279
|
+
if (pathname === require_route_registry.IMAGES_EDIT_PATH && req.method === "POST") {
|
|
1695
2280
|
try {
|
|
1696
2281
|
await require_images.handleImageEdit(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1697
2282
|
} catch (err) {
|
|
1698
|
-
|
|
1699
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1700
|
-
message: msg,
|
|
1701
|
-
type: "server_error"
|
|
1702
|
-
} }));
|
|
1703
|
-
else if (!res.writableEnded) res.destroy();
|
|
2283
|
+
routeError(req, res, err, pathname);
|
|
1704
2284
|
}
|
|
1705
2285
|
return;
|
|
1706
2286
|
}
|
|
1707
|
-
if (pathname === IMAGES_VARIATIONS_PATH && req.method === "POST") {
|
|
2287
|
+
if (pathname === require_route_registry.IMAGES_VARIATIONS_PATH && req.method === "POST") {
|
|
1708
2288
|
try {
|
|
1709
2289
|
await require_images.handleImageVariations(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1710
2290
|
} catch (err) {
|
|
1711
|
-
|
|
1712
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1713
|
-
message: msg,
|
|
1714
|
-
type: "server_error"
|
|
1715
|
-
} }));
|
|
1716
|
-
else if (!res.writableEnded) res.destroy();
|
|
2291
|
+
routeError(req, res, err, pathname);
|
|
1717
2292
|
}
|
|
1718
2293
|
return;
|
|
1719
2294
|
}
|
|
1720
|
-
if (pathname === SPEECH_PATH && req.method === "POST") {
|
|
2295
|
+
if (pathname === require_route_registry.SPEECH_PATH && req.method === "POST") {
|
|
1721
2296
|
try {
|
|
1722
2297
|
await require_speech.handleSpeech(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1723
2298
|
} catch (err) {
|
|
1724
|
-
|
|
1725
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1726
|
-
message: msg,
|
|
1727
|
-
type: "server_error"
|
|
1728
|
-
} }));
|
|
1729
|
-
else if (!res.writableEnded) res.destroy();
|
|
2299
|
+
routeError(req, res, err, pathname);
|
|
1730
2300
|
}
|
|
1731
2301
|
return;
|
|
1732
2302
|
}
|
|
1733
|
-
if (pathname === TRANSCRIPTIONS_PATH && req.method === "POST") {
|
|
2303
|
+
if (pathname === require_route_registry.TRANSCRIPTIONS_PATH && req.method === "POST") {
|
|
1734
2304
|
try {
|
|
1735
2305
|
await require_transcription.handleTranscription(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1736
2306
|
} catch (err) {
|
|
1737
|
-
|
|
1738
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1739
|
-
message: msg,
|
|
1740
|
-
type: "server_error"
|
|
1741
|
-
} }));
|
|
1742
|
-
else if (!res.writableEnded) res.destroy();
|
|
2307
|
+
routeError(req, res, err, pathname);
|
|
1743
2308
|
}
|
|
1744
2309
|
return;
|
|
1745
2310
|
}
|
|
1746
|
-
if (pathname === TRANSLATIONS_PATH && req.method === "POST") {
|
|
2311
|
+
if (pathname === require_route_registry.TRANSLATIONS_PATH && req.method === "POST") {
|
|
1747
2312
|
try {
|
|
1748
2313
|
await require_transcription.handleTranscription(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, "translation");
|
|
1749
2314
|
} catch (err) {
|
|
1750
|
-
|
|
1751
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1752
|
-
message: msg,
|
|
1753
|
-
type: "server_error"
|
|
1754
|
-
} }));
|
|
1755
|
-
else if (!res.writableEnded) res.destroy();
|
|
2315
|
+
routeError(req, res, err, pathname);
|
|
1756
2316
|
}
|
|
1757
2317
|
return;
|
|
1758
2318
|
}
|
|
1759
|
-
if (pathname ===
|
|
2319
|
+
if (pathname === require_route_registry.GROK_VIDEO_SUBMIT_PATH && req.method === "POST") {
|
|
1760
2320
|
setCorsHeaders(res);
|
|
1761
2321
|
try {
|
|
1762
2322
|
await require_grok_video.handleGrokVideoCreate(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, grokVideoJobs);
|
|
1763
2323
|
} catch (err) {
|
|
1764
|
-
|
|
1765
|
-
defaults.logger.error(`grok-video submit: ${msg}`);
|
|
1766
|
-
if (!res.headersSent) {
|
|
1767
|
-
try {
|
|
1768
|
-
journal.add({
|
|
1769
|
-
method: req.method ?? "POST",
|
|
1770
|
-
path: req.url ?? pathname,
|
|
1771
|
-
headers: require_helpers.flattenHeaders(req.headers),
|
|
1772
|
-
body: null,
|
|
1773
|
-
response: {
|
|
1774
|
-
status: 500,
|
|
1775
|
-
fixture: null
|
|
1776
|
-
}
|
|
1777
|
-
});
|
|
1778
|
-
} catch (jErr) {
|
|
1779
|
-
defaults.logger.warn(`grok-video submit: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1780
|
-
}
|
|
1781
|
-
require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1782
|
-
message: msg,
|
|
1783
|
-
type: "server_error"
|
|
1784
|
-
} }));
|
|
1785
|
-
} else if (!res.writableEnded) res.destroy();
|
|
2324
|
+
routeError(req, res, err, pathname);
|
|
1786
2325
|
}
|
|
1787
2326
|
return;
|
|
1788
2327
|
}
|
|
1789
|
-
if (pathname === VIDEOS_PATH && req.method === "POST") {
|
|
2328
|
+
if (pathname === require_route_registry.VIDEOS_PATH && req.method === "POST") {
|
|
1790
2329
|
try {
|
|
1791
2330
|
await require_video.handleVideoCreate(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, videoStates);
|
|
1792
2331
|
} catch (err) {
|
|
1793
|
-
|
|
1794
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1795
|
-
message: msg,
|
|
1796
|
-
type: "server_error"
|
|
1797
|
-
} }));
|
|
1798
|
-
else if (!res.writableEnded) res.destroy();
|
|
2332
|
+
routeError(req, res, err, pathname);
|
|
1799
2333
|
}
|
|
1800
2334
|
return;
|
|
1801
2335
|
}
|
|
1802
|
-
const grokVideoStatusMatch = pathname.match(
|
|
2336
|
+
const grokVideoStatusMatch = pathname.match(require_route_registry.GROK_VIDEO_STATUS_RE);
|
|
1803
2337
|
if (grokVideoStatusMatch && grokVideoStatusMatch[1] !== "generations" && req.method === "GET") {
|
|
1804
2338
|
try {
|
|
1805
2339
|
await require_grok_video.handleGrokVideoStatus(req, res, grokVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, grokVideoJobs, videoStates);
|
|
1806
2340
|
} catch (err) {
|
|
1807
|
-
|
|
1808
|
-
defaults.logger.error(`grok-video status: ${msg}`);
|
|
1809
|
-
if (!res.headersSent) {
|
|
1810
|
-
try {
|
|
1811
|
-
journal.add({
|
|
1812
|
-
method: req.method ?? "GET",
|
|
1813
|
-
path: req.url ?? pathname,
|
|
1814
|
-
headers: require_helpers.flattenHeaders(req.headers),
|
|
1815
|
-
body: null,
|
|
1816
|
-
response: {
|
|
1817
|
-
status: 500,
|
|
1818
|
-
fixture: null
|
|
1819
|
-
}
|
|
1820
|
-
});
|
|
1821
|
-
} catch (jErr) {
|
|
1822
|
-
defaults.logger.warn(`grok-video status: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1823
|
-
}
|
|
1824
|
-
require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1825
|
-
message: msg,
|
|
1826
|
-
type: "server_error"
|
|
1827
|
-
} }));
|
|
1828
|
-
} else if (!res.writableEnded) res.destroy();
|
|
2341
|
+
routeError(req, res, err, pathname);
|
|
1829
2342
|
}
|
|
1830
2343
|
return;
|
|
1831
2344
|
}
|
|
1832
|
-
const veoPredictMatch = pathname.match(
|
|
2345
|
+
const veoPredictMatch = pathname.match(require_route_registry.VEO_PREDICT_LRO_RE);
|
|
1833
2346
|
if (veoPredictMatch && req.method === "POST") {
|
|
1834
2347
|
setCorsHeaders(res);
|
|
1835
2348
|
try {
|
|
1836
2349
|
await require_veo_video.handleVeoVideoCreate(req, res, await require_helpers.readBody(req), veoPredictMatch[1], fixtures, journal, defaults, setCorsHeaders, veoVideoJobs);
|
|
1837
2350
|
} catch (err) {
|
|
1838
|
-
|
|
1839
|
-
defaults.logger.error(`veo-video submit: ${msg}`);
|
|
1840
|
-
if (!res.headersSent) {
|
|
1841
|
-
try {
|
|
1842
|
-
journal.add({
|
|
1843
|
-
method: req.method ?? "POST",
|
|
1844
|
-
path: req.url ?? pathname,
|
|
1845
|
-
headers: require_helpers.flattenHeaders(req.headers),
|
|
1846
|
-
body: null,
|
|
1847
|
-
response: {
|
|
1848
|
-
status: 500,
|
|
1849
|
-
fixture: null
|
|
1850
|
-
}
|
|
1851
|
-
});
|
|
1852
|
-
} catch (jErr) {
|
|
1853
|
-
defaults.logger.warn(`veo-video submit: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1854
|
-
}
|
|
1855
|
-
require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1856
|
-
message: msg,
|
|
1857
|
-
type: "server_error"
|
|
1858
|
-
} }));
|
|
1859
|
-
} else if (!res.writableEnded) res.destroy();
|
|
2351
|
+
routeError(req, res, err, pathname);
|
|
1860
2352
|
}
|
|
1861
2353
|
return;
|
|
1862
2354
|
}
|
|
1863
|
-
const veoOperationMatch = pathname.match(
|
|
2355
|
+
const veoOperationMatch = pathname.match(require_route_registry.VEO_OPERATION_RE);
|
|
1864
2356
|
if (veoOperationMatch && req.method === "GET") {
|
|
1865
2357
|
try {
|
|
1866
2358
|
await require_veo_video.handleVeoVideoStatus(req, res, veoOperationMatch[1], fixtures, journal, defaults, setCorsHeaders, veoVideoJobs);
|
|
1867
2359
|
} catch (err) {
|
|
1868
|
-
|
|
1869
|
-
defaults.logger.error(`veo-video status: ${msg}`);
|
|
1870
|
-
if (!res.headersSent) {
|
|
1871
|
-
try {
|
|
1872
|
-
journal.add({
|
|
1873
|
-
method: req.method ?? "GET",
|
|
1874
|
-
path: req.url ?? pathname,
|
|
1875
|
-
headers: require_helpers.flattenHeaders(req.headers),
|
|
1876
|
-
body: null,
|
|
1877
|
-
response: {
|
|
1878
|
-
status: 500,
|
|
1879
|
-
fixture: null
|
|
1880
|
-
}
|
|
1881
|
-
});
|
|
1882
|
-
} catch (jErr) {
|
|
1883
|
-
defaults.logger.warn(`veo-video status: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1884
|
-
}
|
|
1885
|
-
require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1886
|
-
message: msg,
|
|
1887
|
-
type: "server_error"
|
|
1888
|
-
} }));
|
|
1889
|
-
} else if (!res.writableEnded) res.destroy();
|
|
2360
|
+
routeError(req, res, err, pathname);
|
|
1890
2361
|
}
|
|
1891
2362
|
return;
|
|
1892
2363
|
}
|
|
1893
|
-
const geminiPredictMatch = pathname.match(GEMINI_PREDICT_RE);
|
|
2364
|
+
const geminiPredictMatch = pathname.match(require_route_registry.GEMINI_PREDICT_RE);
|
|
1894
2365
|
if (geminiPredictMatch && req.method === "POST") {
|
|
1895
2366
|
const predictModel = geminiPredictMatch[1];
|
|
1896
2367
|
try {
|
|
1897
2368
|
await require_images.handleImages(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, "gemini", predictModel);
|
|
1898
2369
|
} catch (err) {
|
|
1899
|
-
|
|
1900
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1901
|
-
message: msg,
|
|
1902
|
-
type: "server_error"
|
|
1903
|
-
} }));
|
|
1904
|
-
else if (!res.writableEnded) res.destroy();
|
|
2370
|
+
routeError(req, res, err, pathname);
|
|
1905
2371
|
}
|
|
1906
2372
|
return;
|
|
1907
2373
|
}
|
|
1908
|
-
if (pathname === GEMINI_INTERACTIONS_PATH && req.method === "POST") {
|
|
2374
|
+
if (pathname === require_route_registry.GEMINI_INTERACTIONS_PATH && req.method === "POST") {
|
|
1909
2375
|
try {
|
|
1910
2376
|
await require_gemini_interactions.handleGeminiInteractions(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1911
2377
|
} catch (err) {
|
|
1912
|
-
|
|
1913
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1914
|
-
message: msg,
|
|
1915
|
-
type: "server_error"
|
|
1916
|
-
} }));
|
|
1917
|
-
else if (!res.writableEnded) try {
|
|
1918
|
-
res.write(`data: ${JSON.stringify({ error: { message: msg } })}\n\n`);
|
|
1919
|
-
res.end();
|
|
1920
|
-
} catch (writeErr) {
|
|
1921
|
-
logger.debug("Failed to write error recovery response:", writeErr);
|
|
1922
|
-
}
|
|
2378
|
+
routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
1923
2379
|
}
|
|
1924
2380
|
return;
|
|
1925
2381
|
}
|
|
1926
|
-
const geminiEmbedMatch = pathname.match(GEMINI_EMBED_RE);
|
|
2382
|
+
const geminiEmbedMatch = pathname.match(require_route_registry.GEMINI_EMBED_RE);
|
|
1927
2383
|
if (geminiEmbedMatch && req.method === "POST") {
|
|
1928
2384
|
const embedModel = geminiEmbedMatch[1];
|
|
1929
2385
|
try {
|
|
1930
2386
|
await require_gemini_embeddings.handleGeminiEmbedContent(req, res, await require_helpers.readBody(req), embedModel, fixtures, journal, defaults, setCorsHeaders);
|
|
1931
2387
|
} catch (err) {
|
|
1932
|
-
|
|
1933
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1934
|
-
message: msg,
|
|
1935
|
-
type: "server_error"
|
|
1936
|
-
} }));
|
|
1937
|
-
else if (!res.writableEnded) res.destroy();
|
|
2388
|
+
routeError(req, res, err, pathname);
|
|
1938
2389
|
}
|
|
1939
2390
|
return;
|
|
1940
2391
|
}
|
|
1941
|
-
const geminiMatch = pathname.match(GEMINI_PATH_RE);
|
|
2392
|
+
const geminiMatch = pathname.match(require_route_registry.GEMINI_PATH_RE);
|
|
1942
2393
|
if (geminiMatch && req.method === "POST") {
|
|
1943
2394
|
const geminiModel = geminiMatch[1];
|
|
1944
2395
|
const streaming = geminiMatch[2] === "streamGenerateContent";
|
|
1945
2396
|
try {
|
|
1946
2397
|
await require_gemini.handleGemini(req, res, await require_helpers.readBody(req), geminiModel, streaming, fixtures, journal, defaults, setCorsHeaders);
|
|
1947
2398
|
} catch (err) {
|
|
1948
|
-
|
|
1949
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1950
|
-
message: msg,
|
|
1951
|
-
type: "server_error"
|
|
1952
|
-
} }));
|
|
1953
|
-
else if (!res.writableEnded) try {
|
|
1954
|
-
res.write(`data: ${JSON.stringify({ error: { message: msg } })}\n\n`);
|
|
1955
|
-
res.end();
|
|
1956
|
-
} catch (writeErr) {
|
|
1957
|
-
logger.debug("Failed to write error recovery response:", writeErr);
|
|
1958
|
-
}
|
|
2399
|
+
routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
1959
2400
|
}
|
|
1960
2401
|
return;
|
|
1961
2402
|
}
|
|
1962
|
-
const vertexMatch = pathname.match(VERTEX_AI_RE);
|
|
2403
|
+
const vertexMatch = pathname.match(require_route_registry.VERTEX_AI_RE);
|
|
1963
2404
|
if (vertexMatch && req.method === "POST") {
|
|
1964
2405
|
const vertexModel = vertexMatch[1];
|
|
1965
2406
|
const streaming = vertexMatch[2] === "streamGenerateContent";
|
|
1966
2407
|
try {
|
|
1967
2408
|
await require_gemini.handleGemini(req, res, await require_helpers.readBody(req), vertexModel, streaming, fixtures, journal, defaults, setCorsHeaders, "vertexai");
|
|
1968
2409
|
} catch (err) {
|
|
1969
|
-
|
|
1970
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1971
|
-
message: msg,
|
|
1972
|
-
type: "server_error"
|
|
1973
|
-
} }));
|
|
1974
|
-
else if (!res.writableEnded) try {
|
|
1975
|
-
res.write(`data: ${JSON.stringify({ error: { message: msg } })}\n\n`);
|
|
1976
|
-
res.end();
|
|
1977
|
-
} catch (writeErr) {
|
|
1978
|
-
logger.debug("Failed to write error recovery response:", writeErr);
|
|
1979
|
-
}
|
|
2410
|
+
routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
1980
2411
|
}
|
|
1981
2412
|
return;
|
|
1982
2413
|
}
|
|
1983
|
-
const bedrockMatch = pathname.match(BEDROCK_INVOKE_RE);
|
|
2414
|
+
const bedrockMatch = pathname.match(require_route_registry.BEDROCK_INVOKE_RE);
|
|
1984
2415
|
if (bedrockMatch && req.method === "POST") {
|
|
1985
2416
|
const bedrockModelId = bedrockMatch[1];
|
|
1986
2417
|
try {
|
|
1987
2418
|
await require_bedrock.handleBedrock(req, res, await require_helpers.readBody(req), bedrockModelId, fixtures, journal, defaults, setCorsHeaders);
|
|
1988
2419
|
} catch (err) {
|
|
1989
|
-
|
|
1990
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1991
|
-
message: msg,
|
|
1992
|
-
type: "server_error"
|
|
1993
|
-
} }));
|
|
1994
|
-
else if (!res.writableEnded) res.destroy();
|
|
2420
|
+
routeError(req, res, err, pathname);
|
|
1995
2421
|
}
|
|
1996
2422
|
return;
|
|
1997
2423
|
}
|
|
1998
|
-
const bedrockStreamMatch = pathname.match(BEDROCK_STREAM_RE);
|
|
2424
|
+
const bedrockStreamMatch = pathname.match(require_route_registry.BEDROCK_STREAM_RE);
|
|
1999
2425
|
if (bedrockStreamMatch && req.method === "POST") {
|
|
2000
2426
|
const bedrockModelId = bedrockStreamMatch[1];
|
|
2001
2427
|
try {
|
|
2002
2428
|
await require_bedrock.handleBedrockStream(req, res, await require_helpers.readBody(req), bedrockModelId, fixtures, journal, defaults, setCorsHeaders);
|
|
2003
2429
|
} catch (err) {
|
|
2004
|
-
|
|
2005
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2006
|
-
message: msg,
|
|
2007
|
-
type: "server_error"
|
|
2008
|
-
} }));
|
|
2009
|
-
else if (!res.writableEnded) res.destroy();
|
|
2430
|
+
routeError(req, res, err, pathname);
|
|
2010
2431
|
}
|
|
2011
2432
|
return;
|
|
2012
2433
|
}
|
|
2013
|
-
const converseMatch = pathname.match(BEDROCK_CONVERSE_RE);
|
|
2434
|
+
const converseMatch = pathname.match(require_route_registry.BEDROCK_CONVERSE_RE);
|
|
2014
2435
|
if (converseMatch && req.method === "POST") {
|
|
2015
2436
|
const converseModelId = converseMatch[1];
|
|
2016
2437
|
try {
|
|
2017
2438
|
await require_bedrock_converse.handleConverse(req, res, await require_helpers.readBody(req), converseModelId, fixtures, journal, defaults, setCorsHeaders);
|
|
2018
2439
|
} catch (err) {
|
|
2019
|
-
|
|
2020
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2021
|
-
message: msg,
|
|
2022
|
-
type: "server_error"
|
|
2023
|
-
} }));
|
|
2024
|
-
else if (!res.writableEnded) res.destroy();
|
|
2440
|
+
routeError(req, res, err, pathname);
|
|
2025
2441
|
}
|
|
2026
2442
|
return;
|
|
2027
2443
|
}
|
|
2028
|
-
const converseStreamMatch = pathname.match(BEDROCK_CONVERSE_STREAM_RE);
|
|
2444
|
+
const converseStreamMatch = pathname.match(require_route_registry.BEDROCK_CONVERSE_STREAM_RE);
|
|
2029
2445
|
if (converseStreamMatch && req.method === "POST") {
|
|
2030
2446
|
const converseStreamModelId = converseStreamMatch[1];
|
|
2031
2447
|
try {
|
|
2032
2448
|
await require_bedrock_converse.handleConverseStream(req, res, await require_helpers.readBody(req), converseStreamModelId, fixtures, journal, defaults, setCorsHeaders);
|
|
2033
2449
|
} catch (err) {
|
|
2034
|
-
|
|
2035
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2036
|
-
message: msg,
|
|
2037
|
-
type: "server_error"
|
|
2038
|
-
} }));
|
|
2039
|
-
else if (!res.writableEnded) res.destroy();
|
|
2450
|
+
routeError(req, res, err, pathname);
|
|
2040
2451
|
}
|
|
2041
2452
|
return;
|
|
2042
2453
|
}
|
|
2043
|
-
if (pathname === SEARCH_PATH && req.method === "POST") {
|
|
2454
|
+
if (pathname === require_route_registry.SEARCH_PATH && req.method === "POST") {
|
|
2044
2455
|
try {
|
|
2045
2456
|
await require_search.handleSearch(req, res, await require_helpers.readBody(req), serviceFixtures?.search ?? [], journal, defaults, setCorsHeaders);
|
|
2046
2457
|
} catch (err) {
|
|
2047
|
-
|
|
2048
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2049
|
-
message: msg,
|
|
2050
|
-
type: "server_error"
|
|
2051
|
-
} }));
|
|
2052
|
-
else if (!res.writableEnded) res.destroy();
|
|
2458
|
+
routeError(req, res, err, pathname, { service: "search" });
|
|
2053
2459
|
}
|
|
2054
2460
|
return;
|
|
2055
2461
|
}
|
|
2056
|
-
if (pathname === RERANK_PATH && req.method === "POST") {
|
|
2462
|
+
if (pathname === require_route_registry.RERANK_PATH && req.method === "POST") {
|
|
2057
2463
|
try {
|
|
2058
2464
|
await require_rerank.handleRerank(req, res, await require_helpers.readBody(req), serviceFixtures?.rerank ?? [], journal, defaults, setCorsHeaders);
|
|
2059
2465
|
} catch (err) {
|
|
2060
|
-
|
|
2061
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2062
|
-
message: msg,
|
|
2063
|
-
type: "server_error"
|
|
2064
|
-
} }));
|
|
2065
|
-
else if (!res.writableEnded) res.destroy();
|
|
2466
|
+
routeError(req, res, err, pathname, { service: "rerank" });
|
|
2066
2467
|
}
|
|
2067
2468
|
return;
|
|
2068
2469
|
}
|
|
2069
|
-
if (pathname === MODERATIONS_PATH && req.method === "POST") {
|
|
2470
|
+
if (pathname === require_route_registry.MODERATIONS_PATH && req.method === "POST") {
|
|
2070
2471
|
try {
|
|
2071
2472
|
await require_moderation.handleModeration(req, res, await require_helpers.readBody(req), serviceFixtures?.moderation ?? [], journal, defaults, setCorsHeaders);
|
|
2072
2473
|
} catch (err) {
|
|
2073
|
-
|
|
2074
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2075
|
-
message: msg,
|
|
2076
|
-
type: "server_error"
|
|
2077
|
-
} }));
|
|
2078
|
-
else if (!res.writableEnded) res.destroy();
|
|
2474
|
+
routeError(req, res, err, pathname, { service: "moderation" });
|
|
2079
2475
|
}
|
|
2080
2476
|
return;
|
|
2081
2477
|
}
|
|
2082
|
-
if (pathname === ELEVENLABS_SOUND_GENERATION_PATH && req.method === "POST") {
|
|
2478
|
+
if (pathname === require_route_registry.ELEVENLABS_SOUND_GENERATION_PATH && req.method === "POST") {
|
|
2083
2479
|
setCorsHeaders(res);
|
|
2084
2480
|
try {
|
|
2085
2481
|
await require_elevenlabs_audio.handleElevenLabsAudio(req, res, await require_helpers.readBody(req), fixtures, defaults, journal, "sound-generation");
|
|
2086
2482
|
} catch (err) {
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2483
|
+
routeError(req, res, err, pathname);
|
|
2484
|
+
}
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
if (pathname === require_route_registry.ELEVENLABS_VOICE_DESIGN_PATH && req.method === "POST") {
|
|
2488
|
+
setCorsHeaders(res);
|
|
2489
|
+
try {
|
|
2490
|
+
await require_elevenlabs_voice.handleElevenLabsVoiceDesign(req, res, await require_helpers.readBody(req), fixtures, defaults, journal);
|
|
2491
|
+
} catch (err) {
|
|
2492
|
+
routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
|
|
2493
|
+
}
|
|
2494
|
+
return;
|
|
2495
|
+
}
|
|
2496
|
+
if (pathname === require_route_registry.ELEVENLABS_VOICE_CREATE_PATH && req.method === "POST") {
|
|
2497
|
+
setCorsHeaders(res);
|
|
2498
|
+
try {
|
|
2499
|
+
await require_elevenlabs_voice.handleElevenLabsVoiceCreate(req, res, await require_helpers.readBody(req), fixtures, defaults, journal);
|
|
2500
|
+
} catch (err) {
|
|
2501
|
+
routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
|
|
2502
|
+
}
|
|
2503
|
+
return;
|
|
2504
|
+
}
|
|
2505
|
+
const elevenLabsVoiceMatch = pathname.match(require_route_registry.ELEVENLABS_VOICE_RE);
|
|
2506
|
+
if (elevenLabsVoiceMatch && (req.method === "GET" || req.method === "DELETE")) {
|
|
2507
|
+
setCorsHeaders(res);
|
|
2508
|
+
let voiceId;
|
|
2509
|
+
try {
|
|
2510
|
+
voiceId = decodeURIComponent(elevenLabsVoiceMatch[1]);
|
|
2511
|
+
} catch {
|
|
2512
|
+
require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
|
|
2513
|
+
message: `Invalid voice id '${elevenLabsVoiceMatch[1]}': malformed percent-encoding`,
|
|
2514
|
+
type: "invalid_request_error"
|
|
2091
2515
|
} }));
|
|
2092
|
-
|
|
2516
|
+
journal.add({
|
|
2517
|
+
method: req.method ?? "GET",
|
|
2518
|
+
path: req.url ?? pathname,
|
|
2519
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
2520
|
+
service: "elevenlabs-voice",
|
|
2521
|
+
body: null,
|
|
2522
|
+
response: {
|
|
2523
|
+
status: 400,
|
|
2524
|
+
fixture: null
|
|
2525
|
+
}
|
|
2526
|
+
});
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
try {
|
|
2530
|
+
if (req.method === "GET") await require_elevenlabs_voice.handleElevenLabsVoiceGet(req, res, voiceId, fixtures, defaults, journal);
|
|
2531
|
+
else await require_elevenlabs_voice.handleElevenLabsVoiceDelete(req, res, voiceId, fixtures, defaults, journal);
|
|
2532
|
+
} catch (err) {
|
|
2533
|
+
routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
|
|
2093
2534
|
}
|
|
2094
2535
|
return;
|
|
2095
2536
|
}
|
|
2096
|
-
const elevenLabsTTSMatch = pathname.match(ELEVENLABS_TTS_RE);
|
|
2537
|
+
const elevenLabsTTSMatch = pathname.match(require_route_registry.ELEVENLABS_TTS_RE);
|
|
2097
2538
|
if (elevenLabsTTSMatch && req.method === "POST") {
|
|
2098
2539
|
setCorsHeaders(res);
|
|
2099
2540
|
const voiceId = elevenLabsTTSMatch[1];
|
|
2100
2541
|
try {
|
|
2101
2542
|
await require_elevenlabs_audio.handleElevenLabsTTS(req, res, await require_helpers.readBody(req), fixtures, defaults, journal, voiceId);
|
|
2102
2543
|
} catch (err) {
|
|
2103
|
-
|
|
2104
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2105
|
-
message: msg,
|
|
2106
|
-
type: "server_error"
|
|
2107
|
-
} }));
|
|
2108
|
-
else if (!res.writableEnded) res.destroy();
|
|
2544
|
+
routeError(req, res, err, pathname);
|
|
2109
2545
|
}
|
|
2110
2546
|
return;
|
|
2111
2547
|
}
|
|
2112
|
-
const musicMatch = pathname.match(ELEVENLABS_MUSIC_RE);
|
|
2548
|
+
const musicMatch = pathname.match(require_route_registry.ELEVENLABS_MUSIC_RE);
|
|
2113
2549
|
if (musicMatch && req.method === "POST") {
|
|
2114
2550
|
setCorsHeaders(res);
|
|
2115
2551
|
const musicSubType = musicMatch[1] ?? "music";
|
|
2116
2552
|
try {
|
|
2117
2553
|
await require_elevenlabs_audio.handleElevenLabsAudio(req, res, await require_helpers.readBody(req), fixtures, defaults, journal, musicSubType);
|
|
2118
2554
|
} catch (err) {
|
|
2119
|
-
|
|
2120
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2121
|
-
message: msg,
|
|
2122
|
-
type: "server_error"
|
|
2123
|
-
} }));
|
|
2124
|
-
else if (!res.writableEnded) res.destroy();
|
|
2555
|
+
routeError(req, res, err, pathname);
|
|
2125
2556
|
}
|
|
2126
2557
|
return;
|
|
2127
2558
|
}
|
|
2128
2559
|
let falBody;
|
|
2129
|
-
if (
|
|
2560
|
+
if (require_route_registry.FAL_ROUTE_RE.test(pathname) && req.headers["x-fal-target-host"]) {
|
|
2130
2561
|
setCorsHeaders(res);
|
|
2131
2562
|
try {
|
|
2132
2563
|
falBody = req.method === "POST" || req.method === "PUT" ? await require_helpers.readBody(req) : "";
|
|
2133
2564
|
const raw = falBody;
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
require_chaos.
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2565
|
+
if (require_fal.falWillHandle(req, pathname)) {
|
|
2566
|
+
const chaosConfig = require_chaos.resolveChaosConfig(null, defaults.chaos, req.headers, defaults.logger, req.url);
|
|
2567
|
+
await require_chaos.awaitChaosLatency(null, defaults.chaos, req.headers, defaults.logger, req.url, res, chaosConfig);
|
|
2568
|
+
const goneAfterLatency = require_chaos.responseGoneReason(res);
|
|
2569
|
+
if (goneAfterLatency !== null) {
|
|
2570
|
+
defaults.logger.debug(`[chaos] ${req.method ?? "GET"} ${pathname}: ${require_chaos.describeUnwritableReason(goneAfterLatency)} after the latency delay — not served, not journalled`);
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
2573
|
+
const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger, req.url, chaosConfig);
|
|
2574
|
+
if (chaosAction) {
|
|
2575
|
+
require_chaos.applyChaosAction(chaosAction, res, null, journal, {
|
|
2576
|
+
method: req.method ?? "GET",
|
|
2577
|
+
path: pathname,
|
|
2578
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
2579
|
+
body: null
|
|
2580
|
+
}, "internal", defaults.registry, defaults.logger);
|
|
2581
|
+
return;
|
|
2582
|
+
}
|
|
2146
2583
|
}
|
|
2147
2584
|
if (await require_fal.handleFal(req, res, raw, pathname, fixtures, defaults, journal) === "handled") return;
|
|
2148
2585
|
} catch (err) {
|
|
2149
|
-
|
|
2150
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2151
|
-
message: msg,
|
|
2152
|
-
type: "server_error"
|
|
2153
|
-
} }));
|
|
2154
|
-
else if (!res.writableEnded) res.destroy();
|
|
2586
|
+
routeError(req, res, err, pathname);
|
|
2155
2587
|
return;
|
|
2156
2588
|
}
|
|
2157
2589
|
}
|
|
2158
|
-
if (pathname.match(FAL_QUEUE_SUBMIT_RE) && req.method === "POST") {
|
|
2590
|
+
if (pathname.match(require_route_registry.FAL_QUEUE_SUBMIT_RE) && req.method === "POST") {
|
|
2159
2591
|
setCorsHeaders(res);
|
|
2160
2592
|
try {
|
|
2161
2593
|
await require_fal_audio.handleFalQueue(req, res, falBody ?? await require_helpers.readBody(req), pathname, fixtures, defaults, journal);
|
|
2162
2594
|
} catch (err) {
|
|
2163
|
-
|
|
2164
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2165
|
-
message: msg,
|
|
2166
|
-
type: "server_error"
|
|
2167
|
-
} }));
|
|
2168
|
-
else if (!res.writableEnded) res.destroy();
|
|
2595
|
+
routeError(req, res, err, pathname);
|
|
2169
2596
|
}
|
|
2170
2597
|
return;
|
|
2171
2598
|
}
|
|
2172
|
-
if (pathname.match(FAL_QUEUE_REQUESTS_RE) && (req.method === "GET" || req.method === "POST" || req.method === "PUT")) {
|
|
2599
|
+
if (pathname.match(require_route_registry.FAL_QUEUE_REQUESTS_RE) && (req.method === "GET" || req.method === "POST" || req.method === "PUT")) {
|
|
2173
2600
|
setCorsHeaders(res);
|
|
2174
2601
|
try {
|
|
2175
2602
|
const raw = req.method === "POST" || req.method === "PUT" ? falBody ?? await require_helpers.readBody(req) : "{}";
|
|
2176
|
-
const
|
|
2603
|
+
const chaosConfig = require_chaos.resolveChaosConfig(null, defaults.chaos, req.headers, defaults.logger, req.url);
|
|
2604
|
+
await require_chaos.awaitChaosLatency(null, defaults.chaos, req.headers, defaults.logger, req.url, res, chaosConfig);
|
|
2605
|
+
const goneAfterLatency = require_chaos.responseGoneReason(res);
|
|
2606
|
+
if (goneAfterLatency !== null) {
|
|
2607
|
+
defaults.logger.debug(`[chaos] ${req.method ?? "GET"} ${pathname}: ${require_chaos.describeUnwritableReason(goneAfterLatency)} after the latency delay — not served, not journalled`);
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger, req.url, chaosConfig);
|
|
2177
2611
|
if (chaosAction) {
|
|
2178
2612
|
require_chaos.applyChaosAction(chaosAction, res, null, journal, {
|
|
2179
2613
|
method: req.method ?? "GET",
|
|
2180
2614
|
path: pathname,
|
|
2181
2615
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
2182
|
-
body:
|
|
2183
|
-
|
|
2184
|
-
messages: []
|
|
2185
|
-
}
|
|
2186
|
-
}, "fixture", defaults.registry);
|
|
2616
|
+
body: null
|
|
2617
|
+
}, "internal", defaults.registry, defaults.logger);
|
|
2187
2618
|
return;
|
|
2188
2619
|
}
|
|
2189
2620
|
await require_fal_audio.handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
|
|
2190
2621
|
} catch (err) {
|
|
2191
|
-
|
|
2192
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2193
|
-
message: msg,
|
|
2194
|
-
type: "server_error"
|
|
2195
|
-
} }));
|
|
2196
|
-
else if (!res.writableEnded) res.destroy();
|
|
2622
|
+
routeError(req, res, err, pathname);
|
|
2197
2623
|
}
|
|
2198
2624
|
return;
|
|
2199
2625
|
}
|
|
2200
|
-
if (pathname.match(FAL_RUN_RE) && req.method === "POST") {
|
|
2626
|
+
if (pathname.match(require_route_registry.FAL_RUN_RE) && req.method === "POST") {
|
|
2201
2627
|
setCorsHeaders(res);
|
|
2202
2628
|
try {
|
|
2203
2629
|
await require_fal_audio.handleFalQueue(req, res, falBody ?? await require_helpers.readBody(req), pathname, fixtures, defaults, journal);
|
|
2204
2630
|
} catch (err) {
|
|
2205
|
-
|
|
2206
|
-
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2207
|
-
message: msg,
|
|
2208
|
-
type: "server_error"
|
|
2209
|
-
} }));
|
|
2210
|
-
else if (!res.writableEnded) res.destroy();
|
|
2631
|
+
routeError(req, res, err, pathname);
|
|
2211
2632
|
}
|
|
2212
2633
|
return;
|
|
2213
2634
|
}
|
|
2214
|
-
if (pathname !== COMPLETIONS_PATH) {
|
|
2635
|
+
if (pathname !== require_route_registry.COMPLETIONS_PATH) {
|
|
2636
|
+
const catalogMatch = require_route_registry.matchRouteDefinition(req.method ?? "", pathname);
|
|
2637
|
+
if (catalogMatch && !(pathname === require_route_registry.METRICS_PATH && !registry)) defaults.logger.warn(`Route drift: ${req.method ?? "?"} ${pathname} matches the route catalog (${catalogMatch.method} ${catalogMatch.path} — ${catalogMatch.description}) but no dispatcher branch served it. The catalog entry or the dispatcher is stale — see route-registry.ts.`);
|
|
2215
2638
|
handleNotFound(res, "Not found");
|
|
2216
2639
|
return;
|
|
2217
2640
|
}
|
|
@@ -2223,23 +2646,25 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2223
2646
|
try {
|
|
2224
2647
|
await handleCompletions(req, res, fixtures, journal, defaults, azureDeploymentId, completionsProvider, isOpenRouter);
|
|
2225
2648
|
} catch (err) {
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
res.end();
|
|
2237
|
-
} catch (writeErr) {
|
|
2238
|
-
logger.debug("Failed to write error recovery response:", writeErr);
|
|
2239
|
-
}
|
|
2649
|
+
routeError(req, res, err, pathname, {
|
|
2650
|
+
envelope: (m, status) => isOpenRouter ? require_openrouter_chat.serializeOpenRouterError(status, m) : JSON.stringify({ error: {
|
|
2651
|
+
message: m,
|
|
2652
|
+
type: errorTypeForStatus(status)
|
|
2653
|
+
} }),
|
|
2654
|
+
streamEvent: (m, status) => `data: ${isOpenRouter ? require_openrouter_chat.serializeOpenRouterError(status, m) : JSON.stringify({ error: {
|
|
2655
|
+
message: m,
|
|
2656
|
+
type: errorTypeForStatus(status)
|
|
2657
|
+
} })}\n\n`
|
|
2658
|
+
});
|
|
2240
2659
|
}
|
|
2241
2660
|
}
|
|
2242
2661
|
const activeConnections = /* @__PURE__ */ new Set();
|
|
2662
|
+
const liveLimits = require_live_fixture.normalizeLiveOptions(options?.live);
|
|
2663
|
+
const liveSessions = /* @__PURE__ */ new Map();
|
|
2664
|
+
const closeLiveSessions = (testId) => {
|
|
2665
|
+
for (const session of [...liveSessions.values()]) if (testId === void 0 || session.testId === testId) session.dispose();
|
|
2666
|
+
};
|
|
2667
|
+
liveClosers.set(defaults, closeLiveSessions);
|
|
2243
2668
|
server.on("upgrade", (req, socket, head) => {
|
|
2244
2669
|
handleUpgradeRequest(req, socket, head).catch((err) => {
|
|
2245
2670
|
const msg = err instanceof Error ? err.message : "Internal error";
|
|
@@ -2289,30 +2714,57 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2289
2714
|
server.emit("connection", socket);
|
|
2290
2715
|
return;
|
|
2291
2716
|
}
|
|
2292
|
-
|
|
2717
|
+
let parsedUrl;
|
|
2718
|
+
try {
|
|
2719
|
+
parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
2720
|
+
} catch (err) {
|
|
2721
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
2722
|
+
defaults.logger.warn(`Unhandled upgrade error: ${msg}`);
|
|
2723
|
+
socket.end("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
|
|
2724
|
+
return;
|
|
2725
|
+
}
|
|
2293
2726
|
let pathname = parsedUrl.pathname;
|
|
2294
2727
|
if (!require_api_key_auth.validateRequestApiKey(req, resolvedAuth.policy).ok) {
|
|
2295
2728
|
require_api_key_auth.writeApiKeyUpgradeRejection(socket);
|
|
2296
2729
|
return;
|
|
2297
2730
|
}
|
|
2298
|
-
if (
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
if (await handler.handleUpgrade(socket, head, subPath)) return;
|
|
2302
|
-
}
|
|
2731
|
+
for (const { path: mountPath, handler } of mountList) if ((pathname === mountPath || pathname.startsWith(mountPath + "/")) && handler.handleUpgrade) {
|
|
2732
|
+
const subPath = pathname.slice(mountPath.length) || "/";
|
|
2733
|
+
if (await handler.handleUpgrade(socket, head, subPath)) return;
|
|
2303
2734
|
}
|
|
2304
|
-
if (!pathname.match(AZURE_DEPLOYMENT_RE)) pathname = normalizeCompatPath(pathname, logger);
|
|
2305
|
-
if (pathname !== RESPONSES_PATH && pathname !== REALTIME_PATH && pathname !== GEMINI_LIVE_PATH) {
|
|
2735
|
+
if (!pathname.match(require_route_registry.AZURE_DEPLOYMENT_RE)) pathname = normalizeCompatPath(pathname, logger);
|
|
2736
|
+
if (pathname !== require_route_registry.RESPONSES_PATH && pathname !== require_route_registry.REALTIME_PATH && pathname !== require_route_registry.GEMINI_LIVE_PATH && pathname !== require_route_registry.LIVE_PATH) {
|
|
2306
2737
|
socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
|
|
2307
2738
|
socket.destroy();
|
|
2308
2739
|
return;
|
|
2309
2740
|
}
|
|
2741
|
+
let liveId;
|
|
2742
|
+
if (pathname === require_route_registry.LIVE_PATH) {
|
|
2743
|
+
if (req.method !== "GET" || parsedUrl.searchParams.has("model")) {
|
|
2744
|
+
socket.end("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
|
|
2745
|
+
return;
|
|
2746
|
+
}
|
|
2747
|
+
if (liveSessions.size >= liveLimits.maxSessions) {
|
|
2748
|
+
socket.end("HTTP/1.1 429 Too Many Requests\r\nConnection: close\r\n\r\n");
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
liveId = Symbol("live-session");
|
|
2752
|
+
const reservedId = liveId;
|
|
2753
|
+
liveSessions.set(reservedId, {
|
|
2754
|
+
testId: require_helpers.getTestId(req),
|
|
2755
|
+
dispose: () => {
|
|
2756
|
+
socket.destroy();
|
|
2757
|
+
liveSessions.delete(reservedId);
|
|
2758
|
+
}
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2310
2761
|
if (head.length > 0) socket.unshift(head);
|
|
2311
2762
|
let ws;
|
|
2312
2763
|
try {
|
|
2313
|
-
ws = require_ws_framing.upgradeToWebSocket(req, socket);
|
|
2764
|
+
ws = require_ws_framing.upgradeToWebSocket(req, socket, pathname === require_route_registry.LIVE_PATH ? liveLimits : void 0);
|
|
2314
2765
|
} catch (err) {
|
|
2315
2766
|
const msg = err instanceof Error ? err.message : "WebSocket upgrade failed";
|
|
2767
|
+
if (liveId !== void 0) liveSessions.delete(liveId);
|
|
2316
2768
|
logger.error(`WebSocket upgrade error: ${msg}`);
|
|
2317
2769
|
if (!socket.destroyed) socket.destroy();
|
|
2318
2770
|
return;
|
|
@@ -2326,13 +2778,30 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2326
2778
|
activeConnections.delete(ws);
|
|
2327
2779
|
});
|
|
2328
2780
|
const wsTestId = require_helpers.getTestId(req);
|
|
2329
|
-
if (pathname ===
|
|
2781
|
+
if (pathname === require_route_registry.LIVE_PATH && liveId !== void 0) {
|
|
2782
|
+
const sessionId = liveId;
|
|
2783
|
+
const dispose = require_ws_live.handleLiveSession(ws, fixtures, journal, {
|
|
2784
|
+
defaults,
|
|
2785
|
+
limits: liveLimits,
|
|
2786
|
+
localApiKeys: resolvedAuth.publicConfig?.apiKeys,
|
|
2787
|
+
testId: wsTestId,
|
|
2788
|
+
headers: req.headers,
|
|
2789
|
+
onDispose: () => {
|
|
2790
|
+
liveSessions.delete(sessionId);
|
|
2791
|
+
activeConnections.delete(ws);
|
|
2792
|
+
}
|
|
2793
|
+
});
|
|
2794
|
+
liveSessions.set(sessionId, {
|
|
2795
|
+
testId: wsTestId,
|
|
2796
|
+
dispose
|
|
2797
|
+
});
|
|
2798
|
+
} else if (pathname === require_route_registry.RESPONSES_PATH) require_ws_responses.handleWebSocketResponses(ws, fixtures, journal, {
|
|
2330
2799
|
...defaults,
|
|
2331
2800
|
model: "gpt-4",
|
|
2332
2801
|
testId: wsTestId,
|
|
2333
2802
|
upgradeHeaders: req.headers
|
|
2334
2803
|
});
|
|
2335
|
-
else if (pathname === REALTIME_PATH) {
|
|
2804
|
+
else if (pathname === require_route_registry.REALTIME_PATH) {
|
|
2336
2805
|
const transcriptionIntent = parsedUrl.searchParams.get("intent") === "transcription";
|
|
2337
2806
|
const model = transcriptionIntent ? "gpt-transcribe" : parsedUrl.searchParams.get("model") ?? "gpt-realtime-2";
|
|
2338
2807
|
require_ws_realtime.handleWebSocketRealtime(ws, fixtures, journal, {
|
|
@@ -2342,7 +2811,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2342
2811
|
testId: wsTestId,
|
|
2343
2812
|
upgradeHeaders: req.headers
|
|
2344
2813
|
});
|
|
2345
|
-
} else if (pathname === GEMINI_LIVE_PATH) require_ws_gemini_live.handleWebSocketGeminiLive(ws, fixtures, journal, {
|
|
2814
|
+
} else if (pathname === require_route_registry.GEMINI_LIVE_PATH) require_ws_gemini_live.handleWebSocketGeminiLive(ws, fixtures, journal, {
|
|
2346
2815
|
...defaults,
|
|
2347
2816
|
model: "gemini-2.0-flash",
|
|
2348
2817
|
testId: wsTestId,
|
|
@@ -2351,6 +2820,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2351
2820
|
}
|
|
2352
2821
|
const originalClose = server.close.bind(server);
|
|
2353
2822
|
server.close = function(callback) {
|
|
2823
|
+
closeLiveSessions();
|
|
2354
2824
|
for (const ws of activeConnections) ws.close(1001, "Server shutting down");
|
|
2355
2825
|
activeConnections.clear();
|
|
2356
2826
|
videoStates.clear();
|
|
@@ -2370,10 +2840,15 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2370
2840
|
return;
|
|
2371
2841
|
}
|
|
2372
2842
|
const url = `http://${addr.address}:${addr.port}`;
|
|
2373
|
-
|
|
2374
|
-
|
|
2843
|
+
try {
|
|
2844
|
+
commitHandOff();
|
|
2845
|
+
} catch (err) {
|
|
2846
|
+
server.close(() => reject(err));
|
|
2847
|
+
return;
|
|
2375
2848
|
}
|
|
2849
|
+
for (const { path: mountPath, handler } of mountList) if (handler.setBaseUrl) handler.setBaseUrl(url + mountPath);
|
|
2376
2850
|
resolve({
|
|
2851
|
+
closeLiveSessions,
|
|
2377
2852
|
server,
|
|
2378
2853
|
journal,
|
|
2379
2854
|
url,
|