@copilotkit/aimock 1.42.0 → 1.43.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 +56 -0
- package/README.md +102 -16
- 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 +12 -8
- 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 +14 -10
- 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 +622 -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 +613 -74
- package/dist/chaos.js.map +1 -1
- package/dist/cli.cjs +42 -23
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +42 -23
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +10 -8
- 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 +12 -10
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts.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/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 +8 -4
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.d.cts.map +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +9 -5
- 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 +1071 -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 +1059 -0
- package/dist/files.js.map +1 -0
- package/dist/fine-tuning.cjs +1051 -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 +1044 -0
- package/dist/fine-tuning.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 +37 -5
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +37 -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 +2 -2
- package/dist/gemini-embeddings.cjs.map +1 -1
- package/dist/gemini-embeddings.js +3 -3
- package/dist/gemini-embeddings.js.map +1 -1
- package/dist/gemini-interactions.cjs +13 -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 +15 -9
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +11 -7
- 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 +13 -9
- 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 +229 -7
- 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 +221 -8
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +54 -123
- 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 +55 -124
- package/dist/images.js.map +1 -1
- package/dist/index.cjs +41 -8
- package/dist/index.d.cts +12 -7
- package/dist/index.d.ts +12 -7
- package/dist/index.js +14 -10
- package/dist/jest.cjs +14 -7
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +3 -2
- package/dist/jest.d.cts.map +1 -1
- package/dist/jest.d.ts +3 -2
- package/dist/jest.d.ts.map +1 -1
- package/dist/jest.js +14 -7
- package/dist/jest.js.map +1 -1
- package/dist/journal.cjs +41 -4
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts +44 -3
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts +44 -3
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +41 -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 +52 -41
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +16 -5
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +16 -5
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +53 -42
- package/dist/llmock.js.map +1 -1
- package/dist/messages.cjs +11 -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 +13 -9
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +258 -5
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts +3 -1
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts +3 -1
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +255 -6
- 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 +5 -5
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.js +6 -6
- package/dist/ollama.js.map +1 -1
- package/dist/openrouter-video.cjs +5 -5
- package/dist/openrouter-video.cjs.map +1 -1
- package/dist/openrouter-video.js +6 -6
- package/dist/openrouter-video.js.map +1 -1
- package/dist/recorder.cjs +45 -9
- 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 +43 -11
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +4 -3
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.js +5 -5
- package/dist/responses.js.map +1 -1
- package/dist/router.cjs +20 -33
- 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 +20 -34
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +701 -625
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +1 -0
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +704 -628
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +2 -2
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.js +3 -3
- package/dist/speech.js.map +1 -1
- package/dist/transcription.cjs +4 -4
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.js +5 -5
- package/dist/transcription.js.map +1 -1
- package/dist/types.d.cts +119 -16
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +119 -16
- package/dist/types.d.ts.map +1 -1
- package/dist/validate-cli.cjs +925 -0
- package/dist/validate-cli.cjs.map +1 -0
- package/dist/validate-cli.js +924 -0
- package/dist/validate-cli.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 +4 -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 +5 -5
- package/dist/video.js.map +1 -1
- package/dist/vitest.cjs +13 -6
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +2 -1
- package/dist/vitest.d.cts.map +1 -1
- package/dist/vitest.d.ts +2 -1
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +14 -7
- 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.d.ts +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 +2 -2
- 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 +2 -2
- package/dist/ws-responses.js +2 -2
- 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 +1 -1
package/dist/server.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { DEFAULT_TEST_ID } from "./constants.js";
|
|
2
2
|
import { markAuthenticatedRequest, resolveInboundAuth, validateRequestApiKey, writeApiKeyHttpRejection, writeApiKeyUpgradeRejection } from "./api-key-auth.js";
|
|
3
|
-
import { buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, extractOverrides, flattenHeaders, getContext, getTestId, isAudioResponse, isContentWithToolCallsResponse, isEmbeddingResponse, isErrorResponse, isImageResponse, isJSONResponse, isJsonObject, isResponseFactory, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, readBody,
|
|
3
|
+
import { RequestBodyTooLargeError, buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, describeMatch, extractOverrides, flattenHeaders, getContext, getTestId, isAudioResponse, isContentWithToolCallsResponse, isEmbeddingResponse, isErrorResponse, isImageResponse, isJSONResponse, isJsonObject, isResponseFactory, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, markMintedRequestId, readBody, readBodyBufferBounded, resolveFixtureBlockOutcome, resolveReasoningForModel, resolveRequestId, resolveResponse, resolveStrictMode, resolveTestId, resolveUsage, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, wouldProxyMiss } from "./helpers.js";
|
|
4
|
+
import { normalizeLiveOptions } from "./live-fixture.js";
|
|
5
|
+
import { writeErrorResponse, writeSSEStream } from "./sse-writer.js";
|
|
6
|
+
import { CHAOS_FIELDS, CHAOS_FIELD_NAMES, applyChaosAction, awaitChaosLatency, describeUnwritableReason, evaluateChaos, isChaosScope, parseChaosField, resetChaosWarnings, resolveChaosConfig, responseGoneReason } from "./chaos.js";
|
|
7
|
+
import { INJECTED_STATUS_RANGE, claimOneShotError, clearFixtureQueue, entryToFixture, isInjectableStatus, isOneShotError, queueOneShotError, releaseOneShotError, validateFixtures } from "./fixture-loader.js";
|
|
8
|
+
import { matchFixtureDiagnostic, recordMatchOptions } from "./router.js";
|
|
9
|
+
import { proxyAndRecord } from "./recorder.js";
|
|
4
10
|
import { Logger } from "./logger.js";
|
|
5
11
|
import { Journal } from "./journal.js";
|
|
6
|
-
import { matchFixtureDiagnostic, recordMatchOptions } from "./router.js";
|
|
7
|
-
import { INJECTED_STATUS_RANGE, entryToFixture, isInjectableStatus, validateFixtures } from "./fixture-loader.js";
|
|
8
|
-
import { writeErrorResponse, writeSSEStream } from "./sse-writer.js";
|
|
9
12
|
import { createInterruptionSignal } from "./interruption.js";
|
|
10
13
|
import { buildOpenRouterCandidates, handleOpenRouterCredits, handleOpenRouterKey, handleOpenRouterModels, isOpenRouterPath, resolveOpenRouterShaping, serializeOpenRouterError, shapeOpenRouterChunks, shapeOpenRouterCompletion } from "./openrouter-chat.js";
|
|
11
|
-
import { applyChaosAction, evaluateChaos, isChaosScope } from "./chaos.js";
|
|
12
|
-
import { proxyAndRecord } from "./recorder.js";
|
|
13
14
|
import { handleResponses } from "./responses.js";
|
|
14
15
|
import { handleMessages } from "./messages.js";
|
|
15
16
|
import { handleGemini } from "./gemini.js";
|
|
@@ -23,27 +24,35 @@ import { handleImageEdit, handleImageVariations, handleImages } from "./images.j
|
|
|
23
24
|
import { handleSpeech } from "./speech.js";
|
|
24
25
|
import { VideoStateMap, handleVideoCreate } from "./video.js";
|
|
25
26
|
import { falJobs, handleFalQueue } from "./fal-audio.js";
|
|
26
|
-
import { falQueueStates, handleFal } from "./fal.js";
|
|
27
|
+
import { falQueueStates, falWillHandle, handleFal } from "./fal.js";
|
|
27
28
|
import { OPENROUTER_VIDEO_DEFAULT_MAX_CONTENT_BYTES, OpenRouterVideoJobMap, handleOpenRouterVideoContent, handleOpenRouterVideoCreate, handleOpenRouterVideoModels, handleOpenRouterVideoStatus } from "./openrouter-video.js";
|
|
28
29
|
import { VeoVideoJobMap, handleVeoVideoCreate, handleVeoVideoStatus } from "./veo-video.js";
|
|
29
30
|
import { GrokVideoJobMap, handleGrokVideoCreate, handleGrokVideoStatus } from "./grok-video.js";
|
|
30
31
|
import { BytePlusVideoJobMap, handleBytePlusVideoCreate, handleBytePlusVideoStatus } from "./byteplus-video.js";
|
|
31
32
|
import { handleElevenLabsAudio, handleElevenLabsTTS } from "./elevenlabs-audio.js";
|
|
33
|
+
import { clearElevenLabsVoices, handleElevenLabsVoiceCreate, handleElevenLabsVoiceDelete, handleElevenLabsVoiceDesign, handleElevenLabsVoiceGet } from "./elevenlabs-voice.js";
|
|
32
34
|
import { handleOllama, handleOllamaEmbeddings, handleOllamaGenerate } from "./ollama.js";
|
|
33
35
|
import { handleCohere, handleCohereEmbed } from "./cohere.js";
|
|
34
36
|
import { handleSearch } from "./search.js";
|
|
35
37
|
import { handleRerank } from "./rerank.js";
|
|
36
38
|
import { handleModeration } from "./moderation.js";
|
|
39
|
+
import { clearFineTuningStore, handleFineTuningCancel, handleFineTuningCreate, handleFineTuningEvents, handleFineTuningList, handleFineTuningRetrieve } from "./fine-tuning.js";
|
|
40
|
+
import { FILES_BODY_DRAIN_MAX_BYTES, FILES_BODY_MAX_BYTES, FILES_BODY_OVERSIZED, clearFileStore, handleFilesContent, handleFilesCreate, handleFilesDelete, handleFilesList, handleFilesRetrieve } from "./files.js";
|
|
41
|
+
import { clearBatchStore, handleBatchesCancel, handleBatchesCreate, handleBatchesList, handleBatchesRetrieve } from "./batches.js";
|
|
37
42
|
import { upgradeToWebSocket } from "./ws-framing.js";
|
|
43
|
+
import { handleLiveSession } from "./ws-live.js";
|
|
38
44
|
import { handleWebSocketResponses } from "./ws-responses.js";
|
|
39
45
|
import { handleWebSocketRealtime } from "./ws-realtime.js";
|
|
40
46
|
import { handleWebSocketGeminiLive } from "./ws-gemini-live.js";
|
|
41
|
-
import { BYTEPLUS_VIDEO_STATUS_RE, BYTEPLUS_VIDEO_SUBMIT_RE, GROK_VIDEO_STATUS_RE, GROK_VIDEO_SUBMIT_PATH, OPENROUTER_VIDEO_CONTENT_RE, OPENROUTER_VIDEO_STATUS_RE, VEO_OPERATION_RE, VEO_PREDICT_LRO_RE, createMetricsRegistry, normalizePathLabel } from "./metrics.js";
|
|
47
|
+
import { BYTEPLUS_VIDEO_STATUS_RE, BYTEPLUS_VIDEO_SUBMIT_RE, DESTROYED_STATUS_LABEL, FAL_ROUTE_RE, FILES_CONTENT_RE, FILES_ID_RE, GROK_VIDEO_STATUS_RE, GROK_VIDEO_SUBMIT_PATH, OPENROUTER_VIDEO_CONTENT_RE, OPENROUTER_VIDEO_STATUS_RE, VEO_OPERATION_RE, VEO_PREDICT_LRO_RE, createMetricsRegistry, normalizePathLabel } from "./metrics.js";
|
|
42
48
|
import * as http$1 from "node:http";
|
|
49
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
43
50
|
|
|
44
51
|
//#region src/server.ts
|
|
52
|
+
const liveClosers = /* @__PURE__ */ new WeakMap();
|
|
45
53
|
const COMPLETIONS_PATH = "/v1/chat/completions";
|
|
46
54
|
const RESPONSES_PATH = "/v1/responses";
|
|
55
|
+
const LIVE_PATH = "/v1/live/sessions";
|
|
47
56
|
const REALTIME_PATH = "/v1/realtime";
|
|
48
57
|
const GEMINI_LIVE_PATH = "/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent";
|
|
49
58
|
const MESSAGES_PATH = "/v1/messages";
|
|
@@ -64,10 +73,12 @@ const GEMINI_PREDICT_RE = /^\/v1beta\/models\/([^:]+):predict$/;
|
|
|
64
73
|
const ELEVENLABS_SOUND_GENERATION_PATH = "/v1/sound-generation";
|
|
65
74
|
const ELEVENLABS_TTS_RE = /^\/v1\/text-to-speech\/([^/]+)$/;
|
|
66
75
|
const ELEVENLABS_MUSIC_RE = /^\/v1\/music(?:\/(.+))?$/;
|
|
76
|
+
const ELEVENLABS_VOICE_DESIGN_PATH = "/v1/text-to-voice/design";
|
|
77
|
+
const ELEVENLABS_VOICE_CREATE_PATH = "/v1/text-to-voice";
|
|
78
|
+
const ELEVENLABS_VOICE_RE = /^\/v1\/voices\/([^/]+)$/;
|
|
67
79
|
const FAL_QUEUE_SUBMIT_RE = /^\/fal\/queue\/submit\/(.+)$/;
|
|
68
80
|
const FAL_QUEUE_REQUESTS_RE = /^\/fal\/queue\/requests\/(.+)$/;
|
|
69
81
|
const FAL_RUN_RE = /^\/fal\/run\/(.+)$/;
|
|
70
|
-
const FAL_PREFIX_RE = /^\/fal(?:\/.*)?$/;
|
|
71
82
|
const DEFAULT_CHUNK_SIZE = 20;
|
|
72
83
|
const COMPAT_SUFFIXES = [
|
|
73
84
|
"/chat/completions",
|
|
@@ -116,6 +127,14 @@ const HEALTH_PATH = "/health";
|
|
|
116
127
|
const READY_PATH = "/ready";
|
|
117
128
|
const MODELS_PATH = "/v1/models";
|
|
118
129
|
const REQUESTS_PATH = "/v1/_requests";
|
|
130
|
+
const BATCHES_PATH = "/v1/batches";
|
|
131
|
+
const BATCHES_ID_RE = /^\/v1\/batches\/([^/]+)$/;
|
|
132
|
+
const BATCHES_CANCEL_RE = /^\/v1\/batches\/([^/]+)\/cancel$/;
|
|
133
|
+
const FILES_PATH = "/v1/files";
|
|
134
|
+
const FINE_TUNING_JOBS_PATH = "/v1/fine_tuning/jobs";
|
|
135
|
+
const FINE_TUNING_ID_RE = /^\/v1\/fine_tuning\/jobs\/([^/]+)$/;
|
|
136
|
+
const FINE_TUNING_CANCEL_RE = /^\/v1\/fine_tuning\/jobs\/([^/]+)\/cancel$/;
|
|
137
|
+
const FINE_TUNING_EVENTS_RE = /^\/v1\/fine_tuning\/jobs\/([^/]+)\/events$/;
|
|
119
138
|
const DEFAULT_MODELS = [
|
|
120
139
|
"gpt-4",
|
|
121
140
|
"gpt-4o",
|
|
@@ -127,7 +146,7 @@ const CORS_HEADERS = {
|
|
|
127
146
|
"Access-Control-Allow-Origin": "*",
|
|
128
147
|
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
|
129
148
|
"Access-Control-Allow-Headers": "*",
|
|
130
|
-
"Access-Control-Expose-Headers": "X-Total-Count"
|
|
149
|
+
"Access-Control-Expose-Headers": "X-Total-Count, X-Request-Id"
|
|
131
150
|
};
|
|
132
151
|
function setCorsHeaders(res) {
|
|
133
152
|
for (const [key, value] of Object.entries(CORS_HEADERS)) res.setHeader(key, value);
|
|
@@ -155,14 +174,18 @@ const JOURNAL_PARAMS = new Set([
|
|
|
155
174
|
"method",
|
|
156
175
|
"status",
|
|
157
176
|
"service",
|
|
158
|
-
"testId"
|
|
177
|
+
"testId",
|
|
178
|
+
"requestId"
|
|
159
179
|
]);
|
|
160
180
|
/**
|
|
161
181
|
* Perform a full reset: clear the fixtures array, the journal (entries *and*
|
|
162
182
|
* per-test fixture match-counts, i.e. sequence position), the video and fal.ai
|
|
163
|
-
* job/queue state, the
|
|
164
|
-
*
|
|
165
|
-
*
|
|
183
|
+
* job/queue state, the fine-tuning store (jobs, their append-only event logs,
|
|
184
|
+
* their poll counts, the create-time suffixes their model names are built from,
|
|
185
|
+
* and the module's monotonic clock), the ElevenLabs Voice Design store, the
|
|
186
|
+
* Gemini interaction/event-id counters, and any runtime chaos override
|
|
187
|
+
* (reverting to the construction-time chaos config), then re-zero the
|
|
188
|
+
* `aimock_fixtures_loaded` gauge.
|
|
166
189
|
*
|
|
167
190
|
* `targets` is `null` when no server is running (an in-process `reset()` before
|
|
168
191
|
* `start()`). The process-global generation state is reset either way, since it
|
|
@@ -172,13 +195,19 @@ const JOURNAL_PARAMS = new Set([
|
|
|
172
195
|
* `POST /__aimock/reset/fixtures` alias, and `LLMock.reset()`.
|
|
173
196
|
*/
|
|
174
197
|
function performFullReset(fixtures, targets) {
|
|
175
|
-
fixtures
|
|
198
|
+
clearFixtureQueue(fixtures);
|
|
176
199
|
falJobs.clear();
|
|
177
200
|
falQueueStates.clear();
|
|
201
|
+
clearBatchStore();
|
|
202
|
+
clearElevenLabsVoices();
|
|
203
|
+
clearFileStore();
|
|
204
|
+
clearFineTuningStore();
|
|
178
205
|
resetInteractionCounter();
|
|
179
206
|
resetEventIdCounter();
|
|
180
207
|
if (!targets) return;
|
|
208
|
+
liveClosers.get(targets.defaults)?.();
|
|
181
209
|
targets.journal.clear();
|
|
210
|
+
resetChaosWarnings(targets.defaults.logger);
|
|
182
211
|
targets.defaults.chaos = void 0;
|
|
183
212
|
targets.videoStates.clear();
|
|
184
213
|
targets.openRouterVideoJobs.clear();
|
|
@@ -230,22 +259,18 @@ function fixtureResponseKind(response) {
|
|
|
230
259
|
return "unknown";
|
|
231
260
|
}
|
|
232
261
|
/**
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
262
|
+
* The testId a journal entry was scoped under — `resolveTestId` applied to the
|
|
263
|
+
* journaled headers and path, i.e. the SAME function that scoped the request
|
|
264
|
+
* (fixture match-counts, chaos) when it arrived. Node folds a repeated
|
|
265
|
+
* `X-Test-Id` into one string ("a, a") before either side sees it, and the
|
|
266
|
+
* request is counted and chaos-evaluated under that folded string, so the
|
|
267
|
+
* `?testId=` filter matches it verbatim too: no token-splitting here that the
|
|
268
|
+
* scoping path does not also do. (Splitting on "," additionally broke every
|
|
269
|
+
* legitimately comma-bearing id.) A raw `path.includes("testId=t1")` both
|
|
270
|
+
* prefix-collides with `t10` and matches unrelated params like `notTestId`.
|
|
239
271
|
*/
|
|
240
272
|
function journalEntryTestId(entry) {
|
|
241
|
-
|
|
242
|
-
if (header) return header;
|
|
243
|
-
const qIdx = entry.path.indexOf("?");
|
|
244
|
-
if (qIdx !== -1) {
|
|
245
|
-
const queryValue = new URLSearchParams(entry.path.slice(qIdx + 1)).get("testId");
|
|
246
|
-
if (queryValue) return queryValue;
|
|
247
|
-
}
|
|
248
|
-
return DEFAULT_TEST_ID;
|
|
273
|
+
return resolveTestId(entry.headers, entry.path);
|
|
249
274
|
}
|
|
250
275
|
/**
|
|
251
276
|
* The testId a chaos override is stored under, and read back with — resolved by
|
|
@@ -272,10 +297,14 @@ function writeBlankTestId(res) {
|
|
|
272
297
|
res.end(JSON.stringify({ error: "Invalid 'X-Test-Id': header is present but empty" }));
|
|
273
298
|
return true;
|
|
274
299
|
}
|
|
275
|
-
/**
|
|
300
|
+
/**
|
|
301
|
+
* The chaos config in effect for one testId: its override, else the baseline —
|
|
302
|
+
* one or the other, never the two merged. Mirrors `resolveScopedDefaults` in
|
|
303
|
+
* chaos.ts, so what `GET /__aimock/chaos` reports for a scope is exactly what
|
|
304
|
+
* that scope's traffic is evaluated against.
|
|
305
|
+
*/
|
|
276
306
|
function effectiveChaos(defaults, scopeId) {
|
|
277
|
-
|
|
278
|
-
if (scoped) return scoped;
|
|
307
|
+
if (defaults.chaosByTestId?.has(scopeId)) return defaults.chaosByTestId.get(scopeId) ?? {};
|
|
279
308
|
const current = defaults.chaos;
|
|
280
309
|
if (!current) return {};
|
|
281
310
|
return isChaosScope(current) ? current.base ?? {} : current;
|
|
@@ -320,6 +349,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
320
349
|
const methodFilter = searchParams.get("method");
|
|
321
350
|
const serviceFilter = searchParams.get("service");
|
|
322
351
|
const testIdFilter = searchParams.get("testId");
|
|
352
|
+
const requestIdFilter = searchParams.get("requestId");
|
|
323
353
|
let entries = journal.getAll();
|
|
324
354
|
if (methodFilter !== null) {
|
|
325
355
|
const want = methodFilter.toUpperCase();
|
|
@@ -329,6 +359,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
329
359
|
if (statusFilter !== void 0) entries = entries.filter((e) => e.response.status === statusFilter);
|
|
330
360
|
if (serviceFilter !== null) entries = entries.filter((e) => e.service === serviceFilter);
|
|
331
361
|
if (testIdFilter !== null) entries = entries.filter((e) => journalEntryTestId(e) === testIdFilter);
|
|
362
|
+
if (requestIdFilter !== null) entries = entries.filter((e) => e.headers["x-request-id"] === requestIdFilter);
|
|
332
363
|
const total = entries.length;
|
|
333
364
|
if (offset > 0) entries = entries.slice(offset);
|
|
334
365
|
if (limit >= 0) entries = entries.slice(0, limit);
|
|
@@ -362,7 +393,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
362
393
|
fixtures: fixtures.map((fixture, index) => ({
|
|
363
394
|
index,
|
|
364
395
|
match: redactForInspection(fixture.match),
|
|
365
|
-
responseKind: fixtureResponseKind(fixture.response),
|
|
396
|
+
responseKind: isOneShotError(fixture) ? "error" : fixtureResponseKind(fixture.response),
|
|
366
397
|
...fixture.latency !== void 0 ? { latency: fixture.latency } : {},
|
|
367
398
|
...fixture.chaos !== void 0 ? { chaos: fixture.chaos } : {}
|
|
368
399
|
}))
|
|
@@ -412,26 +443,29 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
412
443
|
res.end(JSON.stringify({ error: "Invalid body: expected a JSON object" }));
|
|
413
444
|
return true;
|
|
414
445
|
}
|
|
415
|
-
const
|
|
416
|
-
"dropRate",
|
|
417
|
-
"malformedRate",
|
|
418
|
-
"disconnectRate"
|
|
419
|
-
];
|
|
420
|
-
for (const key of Object.keys(parsed)) if (!allowed.includes(key)) {
|
|
446
|
+
for (const key of Object.keys(parsed)) if (!CHAOS_FIELD_NAMES.includes(key)) {
|
|
421
447
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
422
448
|
res.end(JSON.stringify({ error: `Unknown chaos field: '${key}'` }));
|
|
423
449
|
return true;
|
|
424
450
|
}
|
|
425
451
|
const next = {};
|
|
426
|
-
for (const key of
|
|
452
|
+
for (const key of CHAOS_FIELD_NAMES) {
|
|
427
453
|
const value = parsed[key];
|
|
428
454
|
if (value === void 0) continue;
|
|
429
|
-
|
|
455
|
+
const accepted = typeof value === "number" ? parseChaosField(key, value) : void 0;
|
|
456
|
+
if (accepted === void 0) {
|
|
457
|
+
const shape = CHAOS_FIELDS[key].integer ? "a whole number of ms" : "a number";
|
|
430
458
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
431
|
-
res.end(JSON.stringify({ error: `Invalid '${key}': must be
|
|
459
|
+
res.end(JSON.stringify({ error: `Invalid '${key}': must be ${shape} between 0 and ${CHAOS_FIELDS[key].max}` }));
|
|
432
460
|
return true;
|
|
433
461
|
}
|
|
434
|
-
next[key] =
|
|
462
|
+
next[key] = accepted;
|
|
463
|
+
}
|
|
464
|
+
const shadowed = effectiveChaos(defaults, scopeId);
|
|
465
|
+
const droppedFields = Object.keys(shadowed).filter((key) => shadowed[key] !== void 0 && next[key] === void 0);
|
|
466
|
+
if (droppedFields.length > 0) {
|
|
467
|
+
const lost = droppedFields.map((key) => `${key}=${shadowed[key]}`).join(", ");
|
|
468
|
+
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.`);
|
|
435
469
|
}
|
|
436
470
|
defaults.chaosByTestId?.set(scopeId, next);
|
|
437
471
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -481,7 +515,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
481
515
|
return true;
|
|
482
516
|
}
|
|
483
517
|
if (subPath === "/fixtures" && req.method === "DELETE") {
|
|
484
|
-
fixtures
|
|
518
|
+
clearFixtureQueue(fixtures);
|
|
485
519
|
if (defaults.registry) defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
|
|
486
520
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
487
521
|
res.end(JSON.stringify({ cleared: true }));
|
|
@@ -575,32 +609,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
575
609
|
}
|
|
576
610
|
}
|
|
577
611
|
}
|
|
578
|
-
|
|
579
|
-
match: { predicate: () => true },
|
|
580
|
-
response: {
|
|
581
|
-
error: {
|
|
582
|
-
message: errorBody?.message ?? "Injected error",
|
|
583
|
-
type: errorBody?.type ?? "server_error",
|
|
584
|
-
code: errorBody?.code
|
|
585
|
-
},
|
|
586
|
-
status
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
fixtures.unshift(errorFixture);
|
|
590
|
-
let consumed = false;
|
|
591
|
-
const original = errorFixture.match.predicate;
|
|
592
|
-
errorFixture.match.predicate = (req) => {
|
|
593
|
-
if (consumed) return false;
|
|
594
|
-
const result = original(req);
|
|
595
|
-
if (result) {
|
|
596
|
-
consumed = true;
|
|
597
|
-
queueMicrotask(() => {
|
|
598
|
-
const idx = fixtures.indexOf(errorFixture);
|
|
599
|
-
if (idx !== -1) fixtures.splice(idx, 1);
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
return result;
|
|
603
|
-
};
|
|
612
|
+
queueOneShotError(fixtures, status, errorBody ?? void 0);
|
|
604
613
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
605
614
|
res.end(JSON.stringify({ queued: true }));
|
|
606
615
|
return true;
|
|
@@ -608,6 +617,19 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
608
617
|
handleNotFound(res, `Unknown control endpoint: ${pathname}`);
|
|
609
618
|
return true;
|
|
610
619
|
}
|
|
620
|
+
/**
|
|
621
|
+
* The client tore the connection down before its request body had fully
|
|
622
|
+
* arrived. Node rejects the pending body read with `aborted` (ECONNRESET) and
|
|
623
|
+
* destroys the response, so the rejection surfaces in the handler's read arm
|
|
624
|
+
* looking like any other thrown error. It is caller behaviour, not a server
|
|
625
|
+
* fault: no status can reach the client, so none must be journaled as
|
|
626
|
+
* delivered. A body that blew its size cap also leaves the request destroyed
|
|
627
|
+
* and incomplete, but that one is aimock's own decision, not a peer abort.
|
|
628
|
+
*/
|
|
629
|
+
function clientAbortedMidBody(req, res, err) {
|
|
630
|
+
if (err instanceof RequestBodyTooLargeError) return false;
|
|
631
|
+
return res.destroyed && !req.complete;
|
|
632
|
+
}
|
|
611
633
|
async function handleCompletions(req, res, fixtures, journal, defaults, modelFallback, providerKey, openRouter = false) {
|
|
612
634
|
setCorsHeaders(res);
|
|
613
635
|
let raw;
|
|
@@ -615,19 +637,43 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
615
637
|
raw = await readBody(req);
|
|
616
638
|
} catch (err) {
|
|
617
639
|
const msg = err instanceof Error ? err.message : "Failed to read request body";
|
|
640
|
+
const clientFault = err instanceof RequestBodyTooLargeError;
|
|
641
|
+
const route = `${req.method ?? "POST"} ${req.url ?? COMPLETIONS_PATH}`;
|
|
642
|
+
const clientAbort = clientAbortedMidBody(req, res, err);
|
|
643
|
+
const bodyLimitDisconnect = clientFault && (req.destroyed || res.destroyed);
|
|
644
|
+
if (clientAbort || bodyLimitDisconnect) {
|
|
645
|
+
const interruptReason = bodyLimitDisconnect ? "request body exceeded size limit" : "client aborted";
|
|
646
|
+
defaults.logger.warn(bodyLimitDisconnect ? `${route}: ${msg}` : `${route}: client aborted mid-body (${msg})`);
|
|
647
|
+
journal.add({
|
|
648
|
+
method: req.method ?? "POST",
|
|
649
|
+
path: req.url ?? COMPLETIONS_PATH,
|
|
650
|
+
headers: flattenHeaders(req.headers),
|
|
651
|
+
body: null,
|
|
652
|
+
response: {
|
|
653
|
+
status: 0,
|
|
654
|
+
fixture: null,
|
|
655
|
+
interrupted: true,
|
|
656
|
+
interruptReason
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const status = clientFault ? 400 : 500;
|
|
662
|
+
if (clientFault) defaults.logger.warn(`${route}: ${msg}`);
|
|
663
|
+
else defaults.logger.error(`${route}: ${msg}`);
|
|
618
664
|
journal.add({
|
|
619
665
|
method: req.method ?? "POST",
|
|
620
666
|
path: req.url ?? COMPLETIONS_PATH,
|
|
621
667
|
headers: flattenHeaders(req.headers),
|
|
622
668
|
body: null,
|
|
623
669
|
response: {
|
|
624
|
-
status
|
|
670
|
+
status,
|
|
625
671
|
fixture: null
|
|
626
672
|
}
|
|
627
673
|
});
|
|
628
|
-
writeErrorResponse(res,
|
|
674
|
+
writeErrorResponse(res, status, openRouter ? serializeOpenRouterError(status, `Request body read failed: ${msg}`) : JSON.stringify({ error: {
|
|
629
675
|
message: `Request body read failed: ${msg}`,
|
|
630
|
-
type: "server_error"
|
|
676
|
+
type: clientFault ? "invalid_request_error" : "server_error"
|
|
631
677
|
} }));
|
|
632
678
|
return;
|
|
633
679
|
}
|
|
@@ -703,7 +749,15 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
703
749
|
let preResolvedResponse = null;
|
|
704
750
|
let responseModel = body.model;
|
|
705
751
|
let fixtureCountIncremented = false;
|
|
706
|
-
|
|
752
|
+
const openRouterFallback = openRouter && Array.isArray(body.models) && body.models.length > 0;
|
|
753
|
+
const selectFixture = (probe, counts) => {
|
|
754
|
+
for (;;) {
|
|
755
|
+
const attempt = matchFixtureDiagnostic(fixtures, probe, counts, defaults.requestTransform, matchOptions);
|
|
756
|
+
if (attempt.fixture && isOneShotError(attempt.fixture) && !claimOneShotError(fixtures, attempt.fixture)) continue;
|
|
757
|
+
return attempt;
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
if (openRouterFallback) {
|
|
707
761
|
const candidates = buildOpenRouterCandidates(body);
|
|
708
762
|
let matchCounts = journal.getFixtureMatchCountsForTest(testId);
|
|
709
763
|
const allowFallbacks = body.provider?.allow_fallbacks !== false;
|
|
@@ -714,7 +768,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
714
768
|
...body,
|
|
715
769
|
model: candidate
|
|
716
770
|
};
|
|
717
|
-
const attempt =
|
|
771
|
+
const attempt = selectFixture(probe, matchCounts);
|
|
718
772
|
skippedBySequenceOrTurn = Math.max(skippedBySequenceOrTurn, attempt.skippedBySequenceOrTurn);
|
|
719
773
|
if (!attempt.fixture) return null;
|
|
720
774
|
const response = await resolveResponse(attempt.fixture, probe);
|
|
@@ -751,35 +805,48 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
751
805
|
preResolvedResponse = lastErrorResponse;
|
|
752
806
|
}
|
|
753
807
|
} else {
|
|
754
|
-
const single =
|
|
808
|
+
const single = selectFixture(body, journal.getFixtureMatchCountsForTest(testId));
|
|
755
809
|
fixture = single.fixture;
|
|
756
810
|
skippedBySequenceOrTurn = single.skippedBySequenceOrTurn;
|
|
757
811
|
}
|
|
758
812
|
if (fixture) {
|
|
759
813
|
if (!fixtureCountIncremented) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
760
|
-
defaults.logger.debug(`Fixture matched: ${
|
|
814
|
+
defaults.logger.debug(`Fixture matched: ${describeMatch(fixture.match, fixtures.indexOf(fixture))}`);
|
|
761
815
|
} else {
|
|
762
816
|
const lastUserMsg = body.messages.filter((m) => m.role === "user").pop();
|
|
763
817
|
const snippet = typeof lastUserMsg?.content === "string" ? lastUserMsg.content.slice(0, 80) : "";
|
|
764
818
|
defaults.logger.debug(`No fixture matched for request (model=${body.model ?? "?"}, msg="${snippet}")`);
|
|
765
819
|
}
|
|
766
|
-
const
|
|
820
|
+
const chaosConfig = resolveChaosConfig(fixture, defaults.chaos, req.headers, defaults.logger, req.url);
|
|
821
|
+
await awaitChaosLatency(fixture, defaults.chaos, req.headers, defaults.logger, req.url, res, chaosConfig);
|
|
822
|
+
const goneAfterLatency = responseGoneReason(res);
|
|
823
|
+
if (goneAfterLatency !== null) {
|
|
824
|
+
releaseOneShotError(fixtures, fixture);
|
|
825
|
+
defaults.logger.debug(`[chaos] ${method} ${path}: ${describeUnwritableReason(goneAfterLatency)} after the latency delay — not served, not journalled`);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
const chaosAction = evaluateChaos(fixture, defaults.chaos, req.headers, defaults.logger, req.url, chaosConfig);
|
|
767
829
|
const chaosContext = {
|
|
768
830
|
method,
|
|
769
831
|
path,
|
|
770
832
|
headers: flatHeaders,
|
|
771
833
|
body
|
|
772
834
|
};
|
|
773
|
-
|
|
774
|
-
|
|
835
|
+
const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
|
|
836
|
+
const missWouldProxy = wouldProxyMiss(effectiveStrict, defaults.record, providerKey);
|
|
837
|
+
const noFixtureSource = missWouldProxy ? "proxy" : "internal";
|
|
838
|
+
if (chaosAction === "drop" || chaosAction === "disconnect" || chaosAction === "rateLimit") {
|
|
839
|
+
applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : noFixtureSource, defaults.registry, defaults.logger);
|
|
840
|
+
releaseOneShotError(fixtures, fixture);
|
|
775
841
|
return;
|
|
776
842
|
}
|
|
777
|
-
if (
|
|
778
|
-
applyChaosAction(chaosAction, res, fixture, journal, chaosContext, "fixture", defaults.registry);
|
|
843
|
+
if (chaosAction === "malformed" && (fixture || !missWouldProxy)) {
|
|
844
|
+
applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : "internal", defaults.registry, defaults.logger);
|
|
845
|
+
releaseOneShotError(fixtures, fixture);
|
|
779
846
|
return;
|
|
780
847
|
}
|
|
781
848
|
if (!fixture) {
|
|
782
|
-
if (
|
|
849
|
+
if (effectiveStrict) {
|
|
783
850
|
const strictStatus = 503;
|
|
784
851
|
const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);
|
|
785
852
|
defaults.logger.error(strictNoMatchLogLine(req.method ?? "POST", req.url ?? COMPLETIONS_PATH, skippedBySequenceOrTurn));
|
|
@@ -805,7 +872,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
805
872
|
if (defaults.record && providerKey) {
|
|
806
873
|
const hookOptions = chaosAction === "malformed" ? {
|
|
807
874
|
beforeWriteResponse: () => {
|
|
808
|
-
applyChaosAction(chaosAction, res, null, journal, chaosContext, "proxy", defaults.registry);
|
|
875
|
+
applyChaosAction(chaosAction, res, null, journal, chaosContext, "proxy", defaults.registry, defaults.logger);
|
|
809
876
|
return true;
|
|
810
877
|
},
|
|
811
878
|
onHookBypassed: (reason) => {
|
|
@@ -817,9 +884,22 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
817
884
|
});
|
|
818
885
|
}
|
|
819
886
|
} : void 0;
|
|
820
|
-
|
|
887
|
+
let clientHungUp = false;
|
|
888
|
+
const noteClientHangUp = () => {
|
|
889
|
+
if (!res.writableEnded) clientHungUp = true;
|
|
890
|
+
};
|
|
891
|
+
req.socket?.on("end", noteClientHangUp);
|
|
892
|
+
req.socket?.on("error", noteClientHangUp);
|
|
893
|
+
let outcome;
|
|
894
|
+
try {
|
|
895
|
+
outcome = await proxyAndRecord(req, res, body, providerKey, req.url ?? COMPLETIONS_PATH, fixtures, defaults, raw, hookOptions);
|
|
896
|
+
} finally {
|
|
897
|
+
req.socket?.off("end", noteClientHangUp);
|
|
898
|
+
req.socket?.off("error", noteClientHangUp);
|
|
899
|
+
}
|
|
821
900
|
if (outcome === "handled_by_hook") return;
|
|
822
901
|
if (outcome !== "not_configured") {
|
|
902
|
+
const destroyedMidStream = res.destroyed && !res.writableEnded;
|
|
823
903
|
journal.add({
|
|
824
904
|
method: req.method ?? "POST",
|
|
825
905
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -828,11 +908,19 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
828
908
|
response: {
|
|
829
909
|
status: res.statusCode ?? 200,
|
|
830
910
|
fixture: null,
|
|
831
|
-
source: "proxy"
|
|
911
|
+
source: "proxy",
|
|
912
|
+
...destroyedMidStream ? {
|
|
913
|
+
interrupted: true,
|
|
914
|
+
interruptReason: clientHungUp ? "client aborted" : "proxy stream destroyed"
|
|
915
|
+
} : {}
|
|
832
916
|
}
|
|
833
917
|
});
|
|
834
918
|
return;
|
|
835
919
|
}
|
|
920
|
+
if (chaosAction === "malformed") {
|
|
921
|
+
applyChaosAction(chaosAction, res, null, journal, chaosContext, "internal", defaults.registry, defaults.logger);
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
836
924
|
}
|
|
837
925
|
journal.add({
|
|
838
926
|
method: req.method ?? "POST",
|
|
@@ -908,7 +996,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
908
996
|
const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
|
|
909
997
|
const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
910
998
|
const streaming = body.stream === true;
|
|
911
|
-
const
|
|
999
|
+
const blockOutcome = response.blocks && response.blocks.length > 0 ? resolveFixtureBlockOutcome(response.blocks) : void 0;
|
|
912
1000
|
const journalEntry = journal.add({
|
|
913
1001
|
method: req.method ?? "POST",
|
|
914
1002
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -920,12 +1008,15 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
920
1008
|
}
|
|
921
1009
|
});
|
|
922
1010
|
if (!streaming) {
|
|
923
|
-
const completion = buildContentWithToolCallsCompletion(response.content ?? "", response.toolCalls ?? [], responseModel, effReasoning,
|
|
1011
|
+
const completion = buildContentWithToolCallsCompletion(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? response.toolCalls ?? [], responseModel, effReasoning, blockOutcome ? {
|
|
1012
|
+
...overrides,
|
|
1013
|
+
finishReason: overrides?.finishReason ?? (blockOutcome.hasToolCalls ? "tool_calls" : "stop")
|
|
1014
|
+
} : overrides, body.messages);
|
|
924
1015
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
925
1016
|
res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
|
|
926
1017
|
} else {
|
|
927
|
-
const chunks = buildContentWithToolCallsChunks(response.content ?? "", response.toolCalls ?? [], responseModel, chunkSize, effReasoning, overrides,
|
|
928
|
-
const completionText =
|
|
1018
|
+
const chunks = buildContentWithToolCallsChunks(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? response.toolCalls ?? [], responseModel, chunkSize, effReasoning, overrides, blockOutcome?.ordered);
|
|
1019
|
+
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("");
|
|
929
1020
|
const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? responseModel, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), resolveStreamingUsageTokens(overrides, completionText), overrides?.systemFingerprint) : void 0;
|
|
930
1021
|
shapeORChunks(chunks, usageChunk, overrides);
|
|
931
1022
|
const interruption = createInterruptionSignal(fixture);
|
|
@@ -1109,21 +1200,14 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1109
1200
|
}
|
|
1110
1201
|
};
|
|
1111
1202
|
if (options?.chaos) {
|
|
1112
|
-
const
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
},
|
|
1121
|
-
{
|
|
1122
|
-
name: "disconnectRate",
|
|
1123
|
-
value: options.chaos.disconnectRate
|
|
1124
|
-
}
|
|
1125
|
-
];
|
|
1126
|
-
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`);
|
|
1203
|
+
const chaosDefaults = options.chaos;
|
|
1204
|
+
for (const field of CHAOS_FIELD_NAMES) {
|
|
1205
|
+
const value = chaosDefaults[field];
|
|
1206
|
+
if (value === void 0) continue;
|
|
1207
|
+
if (typeof value === "number" && parseChaosField(field, value) !== void 0) continue;
|
|
1208
|
+
const shape = CHAOS_FIELDS[field].integer ? "a whole number of ms" : "a number";
|
|
1209
|
+
logger.warn(`Chaos default ${field} value ${JSON.stringify(value)} — must be ${shape} in [0,${CHAOS_FIELDS[field].max}]; rejected at runtime, never clamped: this default is ignored`);
|
|
1210
|
+
}
|
|
1127
1211
|
}
|
|
1128
1212
|
for (const { name, config } of [
|
|
1129
1213
|
{
|
|
@@ -1169,62 +1253,241 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1169
1253
|
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).`);
|
|
1170
1254
|
}
|
|
1171
1255
|
}
|
|
1256
|
+
/**
|
|
1257
|
+
* The in-flight request, scoped to the async execution that serves it.
|
|
1258
|
+
* Entered once in the `http.createServer` callback, so every handler —
|
|
1259
|
+
* including the ones in other modules that only ever see the shared
|
|
1260
|
+
* `journal` — runs inside it.
|
|
1261
|
+
*/
|
|
1262
|
+
const requestScope = new AsyncLocalStorage();
|
|
1263
|
+
/** The journal entry each request produced, keyed by the request object. */
|
|
1264
|
+
const ownJournalEntry = /* @__PURE__ */ new WeakMap();
|
|
1172
1265
|
const journal = new Journal({
|
|
1173
1266
|
maxEntries: options?.journalMaxEntries ?? 1e3,
|
|
1174
|
-
fixtureCountsMaxTestIds: options?.fixtureCountsMaxTestIds ?? 500
|
|
1267
|
+
fixtureCountsMaxTestIds: options?.fixtureCountsMaxTestIds ?? 500,
|
|
1268
|
+
onAdd: (entry) => {
|
|
1269
|
+
const req = requestScope.getStore();
|
|
1270
|
+
if (req) ownJournalEntry.set(req, entry);
|
|
1271
|
+
}
|
|
1175
1272
|
});
|
|
1176
1273
|
const videoStates = new VideoStateMap();
|
|
1177
1274
|
const openRouterVideoJobs = new OpenRouterVideoJobMap();
|
|
1178
1275
|
const veoVideoJobs = new VeoVideoJobMap();
|
|
1179
1276
|
const grokVideoJobs = new GrokVideoJobMap();
|
|
1180
1277
|
const bytePlusVideoJobs = new BytePlusVideoJobMap();
|
|
1278
|
+
/**
|
|
1279
|
+
* The OpenAI error `type` that goes with a status: 4xx is the caller's
|
|
1280
|
+
* fault, everything else is ours. Shared by `routeError` and the envelopes
|
|
1281
|
+
* it calls so the body can never disagree with the status line.
|
|
1282
|
+
*/
|
|
1283
|
+
function errorTypeForStatus(status) {
|
|
1284
|
+
return status >= 400 && status < 500 ? "invalid_request_error" : "server_error";
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* The newest journal entry this request already produced, or null.
|
|
1288
|
+
*
|
|
1289
|
+
* Attribution is by the identity of the `IncomingMessage` the entry was
|
|
1290
|
+
* written under — never by `x-request-id`. That header is CALLER-supplied
|
|
1291
|
+
* (`resolveRequestId` echoes a well-formed one) and nothing makes it
|
|
1292
|
+
* unique: a retry, a harness that pins one id, or a load generator sends
|
|
1293
|
+
* the same id on many requests, and keying off it made a crash rewrite an
|
|
1294
|
+
* EARLIER request's entry while leaving the crashing request untraced. The
|
|
1295
|
+
* request object cannot be collided by a caller, so one entry belongs to
|
|
1296
|
+
* exactly one request. A handler writes at most one entry; if it writes
|
|
1297
|
+
* more, the newest wins, which is the entry the old scan would have found.
|
|
1298
|
+
*/
|
|
1299
|
+
function lastJournalEntryFor(req) {
|
|
1300
|
+
return ownJournalEntry.get(req) ?? null;
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Uniform terminal arm for a route handler that threw. Every route error
|
|
1304
|
+
* path logs the route, journals the failure, sets CORS and answers the error
|
|
1305
|
+
* envelope — so a handler crash is never invisible in the logs or in
|
|
1306
|
+
* `/__aimock/journal`.
|
|
1307
|
+
*
|
|
1308
|
+
* ONE request produces ONE entry. `res.headersSent` is not the test for that:
|
|
1309
|
+
* a handler that journaled its 200 and then threw before `writeHead` has
|
|
1310
|
+
* sent no headers, and journaling again would leave two contradicting
|
|
1311
|
+
* entries for one request. So the existing entry is looked up by request
|
|
1312
|
+
* identity ({@link lastJournalEntryFor}) and AMENDED — status to what the
|
|
1313
|
+
* client actually got when the response is still changeable, or interrupted
|
|
1314
|
+
* when it isn't — and a fresh entry is
|
|
1315
|
+
* added only when the handler journaled nothing. That holds after the
|
|
1316
|
+
* response too: a handler that answered and then crashed before its own
|
|
1317
|
+
* journal write gets an entry carrying the status already sent, so a
|
|
1318
|
+
* post-response crash is never invisible.
|
|
1319
|
+
*
|
|
1320
|
+
* Once headers are on the wire the status can't change, so the socket is
|
|
1321
|
+
* destroyed: a clean `end()` reads as EOF and the client treats a truncated
|
|
1322
|
+
* stream as a complete response. The chat-style SSE routes whose clients
|
|
1323
|
+
* surface an error frame pass `streamEvent`; that frame is written first,
|
|
1324
|
+
* but the tear-down still follows it.
|
|
1325
|
+
*
|
|
1326
|
+
* A body that blew its size cap is the CALLER's fault, not a server fault:
|
|
1327
|
+
* it logs at warn, but if the reader destroyed the socket it journals an
|
|
1328
|
+
* interruption with status 0 because no 4xx reached the client. A client
|
|
1329
|
+
* that hung up before its body arrived
|
|
1330
|
+
* ({@link clientAbortedMidBody}) is not a fault at all: it logs at warn and
|
|
1331
|
+
* journals a torn socket (`interrupted`, status 0), never a 500 the client
|
|
1332
|
+
* could not have received.
|
|
1333
|
+
*/
|
|
1334
|
+
function routeError(req, res, err, pathname, opts) {
|
|
1335
|
+
const route = `${req.method ?? "?"} ${pathname}`;
|
|
1336
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
1337
|
+
const clientFault = err instanceof RequestBodyTooLargeError;
|
|
1338
|
+
const clientAbort = clientAbortedMidBody(req, res, err);
|
|
1339
|
+
const status = clientFault ? 400 : 500;
|
|
1340
|
+
if (clientAbort) logger.warn(`${route}: client aborted mid-body (${msg})`);
|
|
1341
|
+
else if (clientFault) logger.warn(`${route}: ${msg}`);
|
|
1342
|
+
else logger.error(`${route}: ${msg}`);
|
|
1343
|
+
if (err instanceof Error && err.stack) logger.debug(err.stack);
|
|
1344
|
+
const existing = lastJournalEntryFor(req);
|
|
1345
|
+
const bodyLimitDisconnect = clientFault && (req.destroyed || res.destroyed);
|
|
1346
|
+
if ((clientAbort || bodyLimitDisconnect) && !res.headersSent) {
|
|
1347
|
+
const interruptReason = bodyLimitDisconnect ? "request body exceeded size limit" : "client aborted";
|
|
1348
|
+
if (existing) {
|
|
1349
|
+
if (!existing.response.interrupted) {
|
|
1350
|
+
existing.response.status = 0;
|
|
1351
|
+
existing.response.interrupted = true;
|
|
1352
|
+
existing.response.interruptReason = interruptReason;
|
|
1353
|
+
}
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
try {
|
|
1357
|
+
journal.add({
|
|
1358
|
+
method: req.method ?? "?",
|
|
1359
|
+
path: req.url ?? "?",
|
|
1360
|
+
headers: flattenHeaders(req.headers),
|
|
1361
|
+
body: null,
|
|
1362
|
+
...opts?.service ? { service: opts.service } : {},
|
|
1363
|
+
response: {
|
|
1364
|
+
status: 0,
|
|
1365
|
+
fixture: null,
|
|
1366
|
+
source: "internal",
|
|
1367
|
+
interrupted: true,
|
|
1368
|
+
interruptReason
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
} catch (jErr) {
|
|
1372
|
+
logger.warn(`${route}: journal write failed after body-read interruption: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1373
|
+
}
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
if (!res.headersSent) {
|
|
1377
|
+
if (existing) existing.response.status = status;
|
|
1378
|
+
else try {
|
|
1379
|
+
journal.add({
|
|
1380
|
+
method: req.method ?? "?",
|
|
1381
|
+
path: req.url ?? "?",
|
|
1382
|
+
headers: flattenHeaders(req.headers),
|
|
1383
|
+
body: null,
|
|
1384
|
+
...opts?.service ? { service: opts.service } : {},
|
|
1385
|
+
response: {
|
|
1386
|
+
status,
|
|
1387
|
+
fixture: null,
|
|
1388
|
+
source: "internal"
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
} catch (jErr) {
|
|
1392
|
+
logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1393
|
+
}
|
|
1394
|
+
setCorsHeaders(res);
|
|
1395
|
+
writeErrorResponse(res, status, opts?.envelope ? opts.envelope(msg, status) : JSON.stringify({ error: {
|
|
1396
|
+
message: msg,
|
|
1397
|
+
type: errorTypeForStatus(status)
|
|
1398
|
+
} }));
|
|
1399
|
+
} else {
|
|
1400
|
+
const midStreamReason = clientAbort ? "client aborted" : "handler crashed mid-stream";
|
|
1401
|
+
if (existing) {
|
|
1402
|
+
if (res.writableEnded) {
|
|
1403
|
+
if (existing.response.error === void 0) existing.response.error = msg;
|
|
1404
|
+
} else if (!existing.response.interrupted) {
|
|
1405
|
+
existing.response.interrupted = true;
|
|
1406
|
+
existing.response.interruptReason = midStreamReason;
|
|
1407
|
+
}
|
|
1408
|
+
} else try {
|
|
1409
|
+
journal.add({
|
|
1410
|
+
method: req.method ?? "?",
|
|
1411
|
+
path: req.url ?? "?",
|
|
1412
|
+
headers: flattenHeaders(req.headers),
|
|
1413
|
+
body: null,
|
|
1414
|
+
...opts?.service ? { service: opts.service } : {},
|
|
1415
|
+
response: {
|
|
1416
|
+
status: res.statusCode,
|
|
1417
|
+
fixture: null,
|
|
1418
|
+
source: "internal",
|
|
1419
|
+
...res.writableEnded ? { error: msg } : {
|
|
1420
|
+
interrupted: true,
|
|
1421
|
+
interruptReason: midStreamReason
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
} catch (jErr) {
|
|
1426
|
+
logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1427
|
+
}
|
|
1428
|
+
if (res.writableEnded) return;
|
|
1429
|
+
if (opts?.streamEvent) try {
|
|
1430
|
+
res.write(opts.streamEvent(msg, status), () => {
|
|
1431
|
+
if (!res.destroyed) res.destroy();
|
|
1432
|
+
});
|
|
1433
|
+
} catch (writeErr) {
|
|
1434
|
+
logger.debug("Failed to write error recovery response:", writeErr);
|
|
1435
|
+
res.destroy();
|
|
1436
|
+
}
|
|
1437
|
+
else res.destroy();
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1181
1440
|
if (mounts) for (const { handler } of mounts) {
|
|
1182
1441
|
if (handler.setJournal) handler.setJournal(journal);
|
|
1183
1442
|
if (registry && handler.setRegistry) handler.setRegistry(registry);
|
|
1184
1443
|
}
|
|
1185
1444
|
if (registry) registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
|
|
1186
1445
|
const server = http$1.createServer((req, res) => {
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
const url = req.url ?? "?";
|
|
1192
|
-
defaults.logger.warn(`Unhandled request error on ${method} ${url}: ${msg}\n${stack}`);
|
|
1193
|
-
if (!res.headersSent) {
|
|
1194
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1195
|
-
res.end(JSON.stringify({ error: {
|
|
1196
|
-
message: msg,
|
|
1197
|
-
type: "server_error"
|
|
1198
|
-
} }));
|
|
1199
|
-
} else if (!res.writableEnded) res.end();
|
|
1446
|
+
requestScope.run(req, () => {
|
|
1447
|
+
handleHttpRequest(req, res).catch((err) => {
|
|
1448
|
+
routeError(req, res, err, req.url ?? "?");
|
|
1449
|
+
});
|
|
1200
1450
|
});
|
|
1201
1451
|
});
|
|
1202
1452
|
async function handleHttpRequest(req, res) {
|
|
1203
1453
|
const startTime = registry ? process.hrtime.bigint() : 0n;
|
|
1454
|
+
const { id: requestId, generated: requestIdMinted } = resolveRequestId(req.headers);
|
|
1455
|
+
req.headers["x-request-id"] = requestId;
|
|
1456
|
+
markMintedRequestId(req, requestIdMinted);
|
|
1457
|
+
res.setHeader("X-Request-Id", requestId);
|
|
1458
|
+
let pathname = "";
|
|
1459
|
+
if (registry) {
|
|
1460
|
+
let recorded = false;
|
|
1461
|
+
const recordOutcome = (status) => {
|
|
1462
|
+
if (recorded) return;
|
|
1463
|
+
recorded = true;
|
|
1464
|
+
try {
|
|
1465
|
+
const normalizedPath = normalizePathLabel(pathname, mounts ? mounts.map((m) => m.path) : []);
|
|
1466
|
+
const method = req.method ?? "UNKNOWN";
|
|
1467
|
+
registry.incrementCounter("aimock_requests_total", {
|
|
1468
|
+
method,
|
|
1469
|
+
path: normalizedPath,
|
|
1470
|
+
status
|
|
1471
|
+
});
|
|
1472
|
+
const elapsed = Number(process.hrtime.bigint() - startTime) / 1e9;
|
|
1473
|
+
registry.observeHistogram("aimock_request_duration_seconds", {
|
|
1474
|
+
method,
|
|
1475
|
+
path: normalizedPath
|
|
1476
|
+
}, elapsed);
|
|
1477
|
+
} catch (err) {
|
|
1478
|
+
defaults.logger.warn("metrics instrumentation error", err);
|
|
1479
|
+
}
|
|
1480
|
+
};
|
|
1481
|
+
res.on("finish", () => recordOutcome(String(res.statusCode)));
|
|
1482
|
+
res.on("close", () => {
|
|
1483
|
+
if (!res.writableFinished) recordOutcome(DESTROYED_STATUS_LABEL);
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1204
1486
|
const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
1205
|
-
|
|
1487
|
+
pathname = parsedUrl.pathname;
|
|
1206
1488
|
const originalPathname = pathname;
|
|
1207
1489
|
const isOpenRouter = isOpenRouterPath(originalPathname);
|
|
1208
1490
|
const isBytePlusArk = defaults.record?.providers.byteplus !== void 0 && originalPathname.startsWith("/api/v3/");
|
|
1209
|
-
if (registry) res.on("finish", () => {
|
|
1210
|
-
try {
|
|
1211
|
-
const normalizedPath = normalizePathLabel(pathname);
|
|
1212
|
-
const method = req.method ?? "UNKNOWN";
|
|
1213
|
-
const status = String(res.statusCode);
|
|
1214
|
-
registry.incrementCounter("aimock_requests_total", {
|
|
1215
|
-
method,
|
|
1216
|
-
path: normalizedPath,
|
|
1217
|
-
status
|
|
1218
|
-
});
|
|
1219
|
-
const elapsed = Number(process.hrtime.bigint() - startTime) / 1e9;
|
|
1220
|
-
registry.observeHistogram("aimock_request_duration_seconds", {
|
|
1221
|
-
method,
|
|
1222
|
-
path: normalizedPath
|
|
1223
|
-
}, elapsed);
|
|
1224
|
-
} catch (err) {
|
|
1225
|
-
defaults.logger.warn("metrics instrumentation error", err);
|
|
1226
|
-
}
|
|
1227
|
-
});
|
|
1228
1491
|
if (req.method === "OPTIONS" && (!resolvedAuth.policy.enabled || req.headers.origin !== void 0 && req.headers["access-control-request-method"] !== void 0)) {
|
|
1229
1492
|
handleOptions(res);
|
|
1230
1493
|
return;
|
|
@@ -1250,12 +1513,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1250
1513
|
try {
|
|
1251
1514
|
await handleOllama(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1252
1515
|
} catch (err) {
|
|
1253
|
-
|
|
1254
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1255
|
-
message: msg,
|
|
1256
|
-
type: "server_error"
|
|
1257
|
-
} }));
|
|
1258
|
-
else if (!res.writableEnded) res.destroy();
|
|
1516
|
+
routeError(req, res, err, pathname);
|
|
1259
1517
|
}
|
|
1260
1518
|
return;
|
|
1261
1519
|
}
|
|
@@ -1263,12 +1521,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1263
1521
|
try {
|
|
1264
1522
|
await handleOllamaGenerate(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1265
1523
|
} catch (err) {
|
|
1266
|
-
|
|
1267
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1268
|
-
message: msg,
|
|
1269
|
-
type: "server_error"
|
|
1270
|
-
} }));
|
|
1271
|
-
else if (!res.writableEnded) res.destroy();
|
|
1524
|
+
routeError(req, res, err, pathname);
|
|
1272
1525
|
}
|
|
1273
1526
|
return;
|
|
1274
1527
|
}
|
|
@@ -1276,12 +1529,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1276
1529
|
try {
|
|
1277
1530
|
await handleOllamaEmbeddings(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1278
1531
|
} catch (err) {
|
|
1279
|
-
|
|
1280
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1281
|
-
message: msg,
|
|
1282
|
-
type: "server_error"
|
|
1283
|
-
} }));
|
|
1284
|
-
else if (!res.writableEnded) res.destroy();
|
|
1532
|
+
routeError(req, res, err, pathname);
|
|
1285
1533
|
}
|
|
1286
1534
|
return;
|
|
1287
1535
|
}
|
|
@@ -1306,28 +1554,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1306
1554
|
try {
|
|
1307
1555
|
await handleOpenRouterVideoContent(req, res, openRouterVideoContentMatch[1], journal, defaults, setCorsHeaders, openRouterVideoJobs);
|
|
1308
1556
|
} catch (err) {
|
|
1309
|
-
|
|
1310
|
-
defaults.logger.error(`openrouter-video content: ${msg}`);
|
|
1311
|
-
if (!res.headersSent) {
|
|
1312
|
-
try {
|
|
1313
|
-
journal.add({
|
|
1314
|
-
method: req.method ?? "GET",
|
|
1315
|
-
path: req.url ?? pathname,
|
|
1316
|
-
headers: flattenHeaders(req.headers),
|
|
1317
|
-
body: null,
|
|
1318
|
-
response: {
|
|
1319
|
-
status: 500,
|
|
1320
|
-
fixture: null
|
|
1321
|
-
}
|
|
1322
|
-
});
|
|
1323
|
-
} catch (jErr) {
|
|
1324
|
-
defaults.logger.warn(`openrouter-video content: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1325
|
-
}
|
|
1326
|
-
writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1327
|
-
message: msg,
|
|
1328
|
-
type: "server_error"
|
|
1329
|
-
} }));
|
|
1330
|
-
} else if (!res.writableEnded) res.destroy();
|
|
1557
|
+
routeError(req, res, err, pathname);
|
|
1331
1558
|
}
|
|
1332
1559
|
return;
|
|
1333
1560
|
}
|
|
@@ -1335,28 +1562,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1335
1562
|
try {
|
|
1336
1563
|
await handleOpenRouterVideoModels(req, res, fixtures, journal, defaults, setCorsHeaders);
|
|
1337
1564
|
} catch (err) {
|
|
1338
|
-
|
|
1339
|
-
defaults.logger.error(`openrouter-video models: ${msg}`);
|
|
1340
|
-
if (!res.headersSent) {
|
|
1341
|
-
try {
|
|
1342
|
-
journal.add({
|
|
1343
|
-
method: req.method ?? "GET",
|
|
1344
|
-
path: req.url ?? pathname,
|
|
1345
|
-
headers: flattenHeaders(req.headers),
|
|
1346
|
-
body: null,
|
|
1347
|
-
response: {
|
|
1348
|
-
status: 500,
|
|
1349
|
-
fixture: null
|
|
1350
|
-
}
|
|
1351
|
-
});
|
|
1352
|
-
} catch (jErr) {
|
|
1353
|
-
defaults.logger.warn(`openrouter-video models: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1354
|
-
}
|
|
1355
|
-
writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1356
|
-
message: msg,
|
|
1357
|
-
type: "server_error"
|
|
1358
|
-
} }));
|
|
1359
|
-
} else if (!res.writableEnded) res.destroy();
|
|
1565
|
+
routeError(req, res, err, pathname);
|
|
1360
1566
|
}
|
|
1361
1567
|
return;
|
|
1362
1568
|
}
|
|
@@ -1365,28 +1571,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1365
1571
|
try {
|
|
1366
1572
|
await handleOpenRouterVideoStatus(req, res, openRouterVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
|
|
1367
1573
|
} catch (err) {
|
|
1368
|
-
|
|
1369
|
-
defaults.logger.error(`openrouter-video status: ${msg}`);
|
|
1370
|
-
if (!res.headersSent) {
|
|
1371
|
-
try {
|
|
1372
|
-
journal.add({
|
|
1373
|
-
method: req.method ?? "GET",
|
|
1374
|
-
path: req.url ?? pathname,
|
|
1375
|
-
headers: flattenHeaders(req.headers),
|
|
1376
|
-
body: null,
|
|
1377
|
-
response: {
|
|
1378
|
-
status: 500,
|
|
1379
|
-
fixture: null
|
|
1380
|
-
}
|
|
1381
|
-
});
|
|
1382
|
-
} catch (jErr) {
|
|
1383
|
-
defaults.logger.warn(`openrouter-video status: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1384
|
-
}
|
|
1385
|
-
writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1386
|
-
message: msg,
|
|
1387
|
-
type: "server_error"
|
|
1388
|
-
} }));
|
|
1389
|
-
} else if (!res.writableEnded) res.destroy();
|
|
1574
|
+
routeError(req, res, err, pathname);
|
|
1390
1575
|
}
|
|
1391
1576
|
return;
|
|
1392
1577
|
}
|
|
@@ -1395,28 +1580,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1395
1580
|
try {
|
|
1396
1581
|
await handleOpenRouterVideoCreate(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
|
|
1397
1582
|
} catch (err) {
|
|
1398
|
-
|
|
1399
|
-
defaults.logger.error(`openrouter-video submit: ${msg}`);
|
|
1400
|
-
if (!res.headersSent) {
|
|
1401
|
-
try {
|
|
1402
|
-
journal.add({
|
|
1403
|
-
method: req.method ?? "POST",
|
|
1404
|
-
path: req.url ?? pathname,
|
|
1405
|
-
headers: flattenHeaders(req.headers),
|
|
1406
|
-
body: null,
|
|
1407
|
-
response: {
|
|
1408
|
-
status: 500,
|
|
1409
|
-
fixture: null
|
|
1410
|
-
}
|
|
1411
|
-
});
|
|
1412
|
-
} catch (jErr) {
|
|
1413
|
-
defaults.logger.warn(`openrouter-video submit: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1414
|
-
}
|
|
1415
|
-
writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1416
|
-
message: msg,
|
|
1417
|
-
type: "server_error"
|
|
1418
|
-
} }));
|
|
1419
|
-
} else if (!res.writableEnded) res.destroy();
|
|
1583
|
+
routeError(req, res, err, pathname);
|
|
1420
1584
|
}
|
|
1421
1585
|
return;
|
|
1422
1586
|
}
|
|
@@ -1425,28 +1589,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1425
1589
|
try {
|
|
1426
1590
|
await handleBytePlusVideoStatus(req, res, bytePlusVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
|
|
1427
1591
|
} catch (err) {
|
|
1428
|
-
|
|
1429
|
-
defaults.logger.error(`byteplus-video status: ${msg}`);
|
|
1430
|
-
if (!res.headersSent) {
|
|
1431
|
-
try {
|
|
1432
|
-
journal.add({
|
|
1433
|
-
method: req.method ?? "GET",
|
|
1434
|
-
path: req.url ?? pathname,
|
|
1435
|
-
headers: flattenHeaders(req.headers),
|
|
1436
|
-
body: null,
|
|
1437
|
-
response: {
|
|
1438
|
-
status: 500,
|
|
1439
|
-
fixture: null
|
|
1440
|
-
}
|
|
1441
|
-
});
|
|
1442
|
-
} catch (jErr) {
|
|
1443
|
-
defaults.logger.warn(`byteplus-video status: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1444
|
-
}
|
|
1445
|
-
writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1446
|
-
message: msg,
|
|
1447
|
-
type: "server_error"
|
|
1448
|
-
} }));
|
|
1449
|
-
} else if (!res.writableEnded) res.destroy();
|
|
1592
|
+
routeError(req, res, err, pathname);
|
|
1450
1593
|
}
|
|
1451
1594
|
return;
|
|
1452
1595
|
}
|
|
@@ -1455,28 +1598,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1455
1598
|
try {
|
|
1456
1599
|
await handleBytePlusVideoCreate(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
|
|
1457
1600
|
} catch (err) {
|
|
1458
|
-
|
|
1459
|
-
defaults.logger.error(`byteplus-video submit: ${msg}`);
|
|
1460
|
-
if (!res.headersSent) {
|
|
1461
|
-
try {
|
|
1462
|
-
journal.add({
|
|
1463
|
-
method: req.method ?? "POST",
|
|
1464
|
-
path: req.url ?? pathname,
|
|
1465
|
-
headers: flattenHeaders(req.headers),
|
|
1466
|
-
body: null,
|
|
1467
|
-
response: {
|
|
1468
|
-
status: 500,
|
|
1469
|
-
fixture: null
|
|
1470
|
-
}
|
|
1471
|
-
});
|
|
1472
|
-
} catch (jErr) {
|
|
1473
|
-
defaults.logger.warn(`byteplus-video submit: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1474
|
-
}
|
|
1475
|
-
writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1476
|
-
message: msg,
|
|
1477
|
-
type: "server_error"
|
|
1478
|
-
} }));
|
|
1479
|
-
} else if (!res.writableEnded) res.destroy();
|
|
1601
|
+
routeError(req, res, err, pathname);
|
|
1480
1602
|
}
|
|
1481
1603
|
return;
|
|
1482
1604
|
}
|
|
@@ -1554,6 +1676,127 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1554
1676
|
}));
|
|
1555
1677
|
return;
|
|
1556
1678
|
}
|
|
1679
|
+
const batchesCancelMatch = pathname.match(BATCHES_CANCEL_RE);
|
|
1680
|
+
if (batchesCancelMatch && req.method === "POST") {
|
|
1681
|
+
try {
|
|
1682
|
+
await readBody(req);
|
|
1683
|
+
await handleBatchesCancel(req, res, batchesCancelMatch[1], journal, defaults, setCorsHeaders);
|
|
1684
|
+
} catch (err) {
|
|
1685
|
+
routeError(req, res, err, pathname, { service: "batches" });
|
|
1686
|
+
}
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
const batchesIdMatch = pathname.match(BATCHES_ID_RE);
|
|
1690
|
+
if (batchesIdMatch && req.method === "GET") {
|
|
1691
|
+
try {
|
|
1692
|
+
await handleBatchesRetrieve(req, res, batchesIdMatch[1], journal, defaults, setCorsHeaders);
|
|
1693
|
+
} catch (err) {
|
|
1694
|
+
routeError(req, res, err, pathname, { service: "batches" });
|
|
1695
|
+
}
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
if (pathname === BATCHES_PATH && req.method === "GET") {
|
|
1699
|
+
try {
|
|
1700
|
+
await handleBatchesList(req, res, journal, defaults, setCorsHeaders);
|
|
1701
|
+
} catch (err) {
|
|
1702
|
+
routeError(req, res, err, pathname, { service: "batches" });
|
|
1703
|
+
}
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
if (pathname === BATCHES_PATH && req.method === "POST") {
|
|
1707
|
+
try {
|
|
1708
|
+
await handleBatchesCreate(req, res, await readBody(req), journal, defaults, setCorsHeaders);
|
|
1709
|
+
} catch (err) {
|
|
1710
|
+
routeError(req, res, err, pathname, { service: "batches" });
|
|
1711
|
+
}
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
const filesContentMatch = pathname.match(FILES_CONTENT_RE);
|
|
1715
|
+
if (filesContentMatch && req.method === "GET") {
|
|
1716
|
+
try {
|
|
1717
|
+
await handleFilesContent(req, res, filesContentMatch[1], journal, defaults, setCorsHeaders);
|
|
1718
|
+
} catch (err) {
|
|
1719
|
+
routeError(req, res, err, pathname, { service: "files" });
|
|
1720
|
+
}
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
const filesIdMatch = pathname.match(FILES_ID_RE);
|
|
1724
|
+
if (filesIdMatch && req.method === "GET") {
|
|
1725
|
+
try {
|
|
1726
|
+
await handleFilesRetrieve(req, res, filesIdMatch[1], journal, defaults, setCorsHeaders);
|
|
1727
|
+
} catch (err) {
|
|
1728
|
+
routeError(req, res, err, pathname, { service: "files" });
|
|
1729
|
+
}
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
if (filesIdMatch && req.method === "DELETE") {
|
|
1733
|
+
try {
|
|
1734
|
+
await handleFilesDelete(req, res, filesIdMatch[1], journal, defaults, setCorsHeaders);
|
|
1735
|
+
} catch (err) {
|
|
1736
|
+
routeError(req, res, err, pathname, { service: "files" });
|
|
1737
|
+
}
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
if (pathname === FILES_PATH && req.method === "GET") {
|
|
1741
|
+
try {
|
|
1742
|
+
await handleFilesList(req, res, journal, defaults, setCorsHeaders);
|
|
1743
|
+
} catch (err) {
|
|
1744
|
+
routeError(req, res, err, pathname, { service: "files" });
|
|
1745
|
+
}
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1748
|
+
if (pathname === FILES_PATH && req.method === "POST") {
|
|
1749
|
+
try {
|
|
1750
|
+
await handleFilesCreate(req, res, (await readBodyBufferBounded(req, FILES_BODY_MAX_BYTES, FILES_BODY_DRAIN_MAX_BYTES)).buffer ?? FILES_BODY_OVERSIZED, journal, defaults, setCorsHeaders);
|
|
1751
|
+
} catch (err) {
|
|
1752
|
+
routeError(req, res, err, pathname, { service: "files" });
|
|
1753
|
+
}
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
const ftCancelMatch = pathname.match(FINE_TUNING_CANCEL_RE);
|
|
1757
|
+
if (ftCancelMatch && req.method === "POST") {
|
|
1758
|
+
try {
|
|
1759
|
+
await readBody(req);
|
|
1760
|
+
await handleFineTuningCancel(req, res, ftCancelMatch[1], journal, defaults, setCorsHeaders);
|
|
1761
|
+
} catch (err) {
|
|
1762
|
+
routeError(req, res, err, pathname, { service: "fine-tuning" });
|
|
1763
|
+
}
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
const ftEventsMatch = pathname.match(FINE_TUNING_EVENTS_RE);
|
|
1767
|
+
if (ftEventsMatch && req.method === "GET") {
|
|
1768
|
+
try {
|
|
1769
|
+
await handleFineTuningEvents(req, res, ftEventsMatch[1], journal, defaults, setCorsHeaders);
|
|
1770
|
+
} catch (err) {
|
|
1771
|
+
routeError(req, res, err, pathname, { service: "fine-tuning" });
|
|
1772
|
+
}
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
const ftIdMatch = pathname.match(FINE_TUNING_ID_RE);
|
|
1776
|
+
if (ftIdMatch && req.method === "GET") {
|
|
1777
|
+
try {
|
|
1778
|
+
await handleFineTuningRetrieve(req, res, ftIdMatch[1], journal, defaults, setCorsHeaders);
|
|
1779
|
+
} catch (err) {
|
|
1780
|
+
routeError(req, res, err, pathname, { service: "fine-tuning" });
|
|
1781
|
+
}
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1784
|
+
if (pathname === FINE_TUNING_JOBS_PATH && req.method === "GET") {
|
|
1785
|
+
try {
|
|
1786
|
+
await handleFineTuningList(req, res, journal, defaults, setCorsHeaders);
|
|
1787
|
+
} catch (err) {
|
|
1788
|
+
routeError(req, res, err, pathname, { service: "fine-tuning" });
|
|
1789
|
+
}
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
if (pathname === FINE_TUNING_JOBS_PATH && req.method === "POST") {
|
|
1793
|
+
try {
|
|
1794
|
+
await handleFineTuningCreate(req, res, await readBody(req), journal, defaults, setCorsHeaders);
|
|
1795
|
+
} catch (err) {
|
|
1796
|
+
routeError(req, res, err, pathname, { service: "fine-tuning" });
|
|
1797
|
+
}
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1557
1800
|
if (pathname === REQUESTS_PATH) {
|
|
1558
1801
|
setCorsHeaders(res);
|
|
1559
1802
|
if (req.method === "GET") {
|
|
@@ -1588,17 +1831,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1588
1831
|
try {
|
|
1589
1832
|
await handleResponses(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1590
1833
|
} catch (err) {
|
|
1591
|
-
|
|
1592
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1593
|
-
message: msg,
|
|
1594
|
-
type: "server_error"
|
|
1595
|
-
} }));
|
|
1596
|
-
else if (!res.writableEnded) try {
|
|
1597
|
-
res.write(`event: error\ndata: ${JSON.stringify({ error: { message: msg } })}\n\n`);
|
|
1598
|
-
res.end();
|
|
1599
|
-
} catch (writeErr) {
|
|
1600
|
-
logger.debug("Failed to write error recovery response:", writeErr);
|
|
1601
|
-
}
|
|
1834
|
+
routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
1602
1835
|
}
|
|
1603
1836
|
return;
|
|
1604
1837
|
}
|
|
@@ -1606,17 +1839,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1606
1839
|
try {
|
|
1607
1840
|
await handleMessages(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1608
1841
|
} catch (err) {
|
|
1609
|
-
|
|
1610
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1611
|
-
message: msg,
|
|
1612
|
-
type: "server_error"
|
|
1613
|
-
} }));
|
|
1614
|
-
else if (!res.writableEnded) try {
|
|
1615
|
-
res.write(`event: error\ndata: ${JSON.stringify({ error: { message: msg } })}\n\n`);
|
|
1616
|
-
res.end();
|
|
1617
|
-
} catch (writeErr) {
|
|
1618
|
-
logger.debug("Failed to write error recovery response:", writeErr);
|
|
1619
|
-
}
|
|
1842
|
+
routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
1620
1843
|
}
|
|
1621
1844
|
return;
|
|
1622
1845
|
}
|
|
@@ -1624,17 +1847,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1624
1847
|
try {
|
|
1625
1848
|
await handleCohere(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1626
1849
|
} catch (err) {
|
|
1627
|
-
|
|
1628
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1629
|
-
message: msg,
|
|
1630
|
-
type: "server_error"
|
|
1631
|
-
} }));
|
|
1632
|
-
else if (!res.writableEnded) try {
|
|
1633
|
-
res.write(`event: error\ndata: ${JSON.stringify({ error: { message: msg } })}\n\n`);
|
|
1634
|
-
res.end();
|
|
1635
|
-
} catch (writeErr) {
|
|
1636
|
-
logger.debug("Failed to write error recovery response:", writeErr);
|
|
1637
|
-
}
|
|
1850
|
+
routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
1638
1851
|
}
|
|
1639
1852
|
return;
|
|
1640
1853
|
}
|
|
@@ -1642,12 +1855,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1642
1855
|
try {
|
|
1643
1856
|
await handleCohereEmbed(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1644
1857
|
} catch (err) {
|
|
1645
|
-
|
|
1646
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1647
|
-
message: msg,
|
|
1648
|
-
type: "server_error"
|
|
1649
|
-
} }));
|
|
1650
|
-
else if (!res.writableEnded) res.destroy();
|
|
1858
|
+
routeError(req, res, err, pathname);
|
|
1651
1859
|
}
|
|
1652
1860
|
return;
|
|
1653
1861
|
}
|
|
@@ -1667,12 +1875,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1667
1875
|
}
|
|
1668
1876
|
await handleEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders, embeddingsProvider);
|
|
1669
1877
|
} catch (err) {
|
|
1670
|
-
|
|
1671
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1672
|
-
message: msg,
|
|
1673
|
-
type: "server_error"
|
|
1674
|
-
} }));
|
|
1675
|
-
else if (!res.writableEnded) res.destroy();
|
|
1878
|
+
routeError(req, res, err, pathname);
|
|
1676
1879
|
}
|
|
1677
1880
|
return;
|
|
1678
1881
|
}
|
|
@@ -1680,12 +1883,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1680
1883
|
try {
|
|
1681
1884
|
await handleImages(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, "openai", void 0, isBytePlusArk ? "byteplus" : void 0);
|
|
1682
1885
|
} catch (err) {
|
|
1683
|
-
|
|
1684
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1685
|
-
message: msg,
|
|
1686
|
-
type: "server_error"
|
|
1687
|
-
} }));
|
|
1688
|
-
else if (!res.writableEnded) res.destroy();
|
|
1886
|
+
routeError(req, res, err, pathname);
|
|
1689
1887
|
}
|
|
1690
1888
|
return;
|
|
1691
1889
|
}
|
|
@@ -1693,12 +1891,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1693
1891
|
try {
|
|
1694
1892
|
await handleImageEdit(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1695
1893
|
} catch (err) {
|
|
1696
|
-
|
|
1697
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1698
|
-
message: msg,
|
|
1699
|
-
type: "server_error"
|
|
1700
|
-
} }));
|
|
1701
|
-
else if (!res.writableEnded) res.destroy();
|
|
1894
|
+
routeError(req, res, err, pathname);
|
|
1702
1895
|
}
|
|
1703
1896
|
return;
|
|
1704
1897
|
}
|
|
@@ -1706,12 +1899,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1706
1899
|
try {
|
|
1707
1900
|
await handleImageVariations(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1708
1901
|
} catch (err) {
|
|
1709
|
-
|
|
1710
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1711
|
-
message: msg,
|
|
1712
|
-
type: "server_error"
|
|
1713
|
-
} }));
|
|
1714
|
-
else if (!res.writableEnded) res.destroy();
|
|
1902
|
+
routeError(req, res, err, pathname);
|
|
1715
1903
|
}
|
|
1716
1904
|
return;
|
|
1717
1905
|
}
|
|
@@ -1719,12 +1907,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1719
1907
|
try {
|
|
1720
1908
|
await handleSpeech(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1721
1909
|
} catch (err) {
|
|
1722
|
-
|
|
1723
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1724
|
-
message: msg,
|
|
1725
|
-
type: "server_error"
|
|
1726
|
-
} }));
|
|
1727
|
-
else if (!res.writableEnded) res.destroy();
|
|
1910
|
+
routeError(req, res, err, pathname);
|
|
1728
1911
|
}
|
|
1729
1912
|
return;
|
|
1730
1913
|
}
|
|
@@ -1732,12 +1915,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1732
1915
|
try {
|
|
1733
1916
|
await handleTranscription(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1734
1917
|
} catch (err) {
|
|
1735
|
-
|
|
1736
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1737
|
-
message: msg,
|
|
1738
|
-
type: "server_error"
|
|
1739
|
-
} }));
|
|
1740
|
-
else if (!res.writableEnded) res.destroy();
|
|
1918
|
+
routeError(req, res, err, pathname);
|
|
1741
1919
|
}
|
|
1742
1920
|
return;
|
|
1743
1921
|
}
|
|
@@ -1745,12 +1923,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1745
1923
|
try {
|
|
1746
1924
|
await handleTranscription(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, "translation");
|
|
1747
1925
|
} catch (err) {
|
|
1748
|
-
|
|
1749
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1750
|
-
message: msg,
|
|
1751
|
-
type: "server_error"
|
|
1752
|
-
} }));
|
|
1753
|
-
else if (!res.writableEnded) res.destroy();
|
|
1926
|
+
routeError(req, res, err, pathname);
|
|
1754
1927
|
}
|
|
1755
1928
|
return;
|
|
1756
1929
|
}
|
|
@@ -1759,28 +1932,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1759
1932
|
try {
|
|
1760
1933
|
await handleGrokVideoCreate(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, grokVideoJobs);
|
|
1761
1934
|
} catch (err) {
|
|
1762
|
-
|
|
1763
|
-
defaults.logger.error(`grok-video submit: ${msg}`);
|
|
1764
|
-
if (!res.headersSent) {
|
|
1765
|
-
try {
|
|
1766
|
-
journal.add({
|
|
1767
|
-
method: req.method ?? "POST",
|
|
1768
|
-
path: req.url ?? pathname,
|
|
1769
|
-
headers: flattenHeaders(req.headers),
|
|
1770
|
-
body: null,
|
|
1771
|
-
response: {
|
|
1772
|
-
status: 500,
|
|
1773
|
-
fixture: null
|
|
1774
|
-
}
|
|
1775
|
-
});
|
|
1776
|
-
} catch (jErr) {
|
|
1777
|
-
defaults.logger.warn(`grok-video submit: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1778
|
-
}
|
|
1779
|
-
writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1780
|
-
message: msg,
|
|
1781
|
-
type: "server_error"
|
|
1782
|
-
} }));
|
|
1783
|
-
} else if (!res.writableEnded) res.destroy();
|
|
1935
|
+
routeError(req, res, err, pathname);
|
|
1784
1936
|
}
|
|
1785
1937
|
return;
|
|
1786
1938
|
}
|
|
@@ -1788,12 +1940,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1788
1940
|
try {
|
|
1789
1941
|
await handleVideoCreate(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, videoStates);
|
|
1790
1942
|
} catch (err) {
|
|
1791
|
-
|
|
1792
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1793
|
-
message: msg,
|
|
1794
|
-
type: "server_error"
|
|
1795
|
-
} }));
|
|
1796
|
-
else if (!res.writableEnded) res.destroy();
|
|
1943
|
+
routeError(req, res, err, pathname);
|
|
1797
1944
|
}
|
|
1798
1945
|
return;
|
|
1799
1946
|
}
|
|
@@ -1802,28 +1949,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1802
1949
|
try {
|
|
1803
1950
|
await handleGrokVideoStatus(req, res, grokVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, grokVideoJobs, videoStates);
|
|
1804
1951
|
} catch (err) {
|
|
1805
|
-
|
|
1806
|
-
defaults.logger.error(`grok-video status: ${msg}`);
|
|
1807
|
-
if (!res.headersSent) {
|
|
1808
|
-
try {
|
|
1809
|
-
journal.add({
|
|
1810
|
-
method: req.method ?? "GET",
|
|
1811
|
-
path: req.url ?? pathname,
|
|
1812
|
-
headers: flattenHeaders(req.headers),
|
|
1813
|
-
body: null,
|
|
1814
|
-
response: {
|
|
1815
|
-
status: 500,
|
|
1816
|
-
fixture: null
|
|
1817
|
-
}
|
|
1818
|
-
});
|
|
1819
|
-
} catch (jErr) {
|
|
1820
|
-
defaults.logger.warn(`grok-video status: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1821
|
-
}
|
|
1822
|
-
writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1823
|
-
message: msg,
|
|
1824
|
-
type: "server_error"
|
|
1825
|
-
} }));
|
|
1826
|
-
} else if (!res.writableEnded) res.destroy();
|
|
1952
|
+
routeError(req, res, err, pathname);
|
|
1827
1953
|
}
|
|
1828
1954
|
return;
|
|
1829
1955
|
}
|
|
@@ -1833,28 +1959,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1833
1959
|
try {
|
|
1834
1960
|
await handleVeoVideoCreate(req, res, await readBody(req), veoPredictMatch[1], fixtures, journal, defaults, setCorsHeaders, veoVideoJobs);
|
|
1835
1961
|
} catch (err) {
|
|
1836
|
-
|
|
1837
|
-
defaults.logger.error(`veo-video submit: ${msg}`);
|
|
1838
|
-
if (!res.headersSent) {
|
|
1839
|
-
try {
|
|
1840
|
-
journal.add({
|
|
1841
|
-
method: req.method ?? "POST",
|
|
1842
|
-
path: req.url ?? pathname,
|
|
1843
|
-
headers: flattenHeaders(req.headers),
|
|
1844
|
-
body: null,
|
|
1845
|
-
response: {
|
|
1846
|
-
status: 500,
|
|
1847
|
-
fixture: null
|
|
1848
|
-
}
|
|
1849
|
-
});
|
|
1850
|
-
} catch (jErr) {
|
|
1851
|
-
defaults.logger.warn(`veo-video submit: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1852
|
-
}
|
|
1853
|
-
writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1854
|
-
message: msg,
|
|
1855
|
-
type: "server_error"
|
|
1856
|
-
} }));
|
|
1857
|
-
} else if (!res.writableEnded) res.destroy();
|
|
1962
|
+
routeError(req, res, err, pathname);
|
|
1858
1963
|
}
|
|
1859
1964
|
return;
|
|
1860
1965
|
}
|
|
@@ -1863,28 +1968,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1863
1968
|
try {
|
|
1864
1969
|
await handleVeoVideoStatus(req, res, veoOperationMatch[1], fixtures, journal, defaults, setCorsHeaders, veoVideoJobs);
|
|
1865
1970
|
} catch (err) {
|
|
1866
|
-
|
|
1867
|
-
defaults.logger.error(`veo-video status: ${msg}`);
|
|
1868
|
-
if (!res.headersSent) {
|
|
1869
|
-
try {
|
|
1870
|
-
journal.add({
|
|
1871
|
-
method: req.method ?? "GET",
|
|
1872
|
-
path: req.url ?? pathname,
|
|
1873
|
-
headers: flattenHeaders(req.headers),
|
|
1874
|
-
body: null,
|
|
1875
|
-
response: {
|
|
1876
|
-
status: 500,
|
|
1877
|
-
fixture: null
|
|
1878
|
-
}
|
|
1879
|
-
});
|
|
1880
|
-
} catch (jErr) {
|
|
1881
|
-
defaults.logger.warn(`veo-video status: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1882
|
-
}
|
|
1883
|
-
writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1884
|
-
message: msg,
|
|
1885
|
-
type: "server_error"
|
|
1886
|
-
} }));
|
|
1887
|
-
} else if (!res.writableEnded) res.destroy();
|
|
1971
|
+
routeError(req, res, err, pathname);
|
|
1888
1972
|
}
|
|
1889
1973
|
return;
|
|
1890
1974
|
}
|
|
@@ -1894,12 +1978,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1894
1978
|
try {
|
|
1895
1979
|
await handleImages(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, "gemini", predictModel);
|
|
1896
1980
|
} catch (err) {
|
|
1897
|
-
|
|
1898
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1899
|
-
message: msg,
|
|
1900
|
-
type: "server_error"
|
|
1901
|
-
} }));
|
|
1902
|
-
else if (!res.writableEnded) res.destroy();
|
|
1981
|
+
routeError(req, res, err, pathname);
|
|
1903
1982
|
}
|
|
1904
1983
|
return;
|
|
1905
1984
|
}
|
|
@@ -1907,17 +1986,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1907
1986
|
try {
|
|
1908
1987
|
await handleGeminiInteractions(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1909
1988
|
} catch (err) {
|
|
1910
|
-
|
|
1911
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1912
|
-
message: msg,
|
|
1913
|
-
type: "server_error"
|
|
1914
|
-
} }));
|
|
1915
|
-
else if (!res.writableEnded) try {
|
|
1916
|
-
res.write(`data: ${JSON.stringify({ error: { message: msg } })}\n\n`);
|
|
1917
|
-
res.end();
|
|
1918
|
-
} catch (writeErr) {
|
|
1919
|
-
logger.debug("Failed to write error recovery response:", writeErr);
|
|
1920
|
-
}
|
|
1989
|
+
routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
1921
1990
|
}
|
|
1922
1991
|
return;
|
|
1923
1992
|
}
|
|
@@ -1927,12 +1996,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1927
1996
|
try {
|
|
1928
1997
|
await handleGeminiEmbedContent(req, res, await readBody(req), embedModel, fixtures, journal, defaults, setCorsHeaders);
|
|
1929
1998
|
} catch (err) {
|
|
1930
|
-
|
|
1931
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1932
|
-
message: msg,
|
|
1933
|
-
type: "server_error"
|
|
1934
|
-
} }));
|
|
1935
|
-
else if (!res.writableEnded) res.destroy();
|
|
1999
|
+
routeError(req, res, err, pathname);
|
|
1936
2000
|
}
|
|
1937
2001
|
return;
|
|
1938
2002
|
}
|
|
@@ -1943,17 +2007,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1943
2007
|
try {
|
|
1944
2008
|
await handleGemini(req, res, await readBody(req), geminiModel, streaming, fixtures, journal, defaults, setCorsHeaders);
|
|
1945
2009
|
} catch (err) {
|
|
1946
|
-
|
|
1947
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1948
|
-
message: msg,
|
|
1949
|
-
type: "server_error"
|
|
1950
|
-
} }));
|
|
1951
|
-
else if (!res.writableEnded) try {
|
|
1952
|
-
res.write(`data: ${JSON.stringify({ error: { message: msg } })}\n\n`);
|
|
1953
|
-
res.end();
|
|
1954
|
-
} catch (writeErr) {
|
|
1955
|
-
logger.debug("Failed to write error recovery response:", writeErr);
|
|
1956
|
-
}
|
|
2010
|
+
routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
1957
2011
|
}
|
|
1958
2012
|
return;
|
|
1959
2013
|
}
|
|
@@ -1964,17 +2018,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1964
2018
|
try {
|
|
1965
2019
|
await handleGemini(req, res, await readBody(req), vertexModel, streaming, fixtures, journal, defaults, setCorsHeaders, "vertexai");
|
|
1966
2020
|
} catch (err) {
|
|
1967
|
-
|
|
1968
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1969
|
-
message: msg,
|
|
1970
|
-
type: "server_error"
|
|
1971
|
-
} }));
|
|
1972
|
-
else if (!res.writableEnded) try {
|
|
1973
|
-
res.write(`data: ${JSON.stringify({ error: { message: msg } })}\n\n`);
|
|
1974
|
-
res.end();
|
|
1975
|
-
} catch (writeErr) {
|
|
1976
|
-
logger.debug("Failed to write error recovery response:", writeErr);
|
|
1977
|
-
}
|
|
2021
|
+
routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
1978
2022
|
}
|
|
1979
2023
|
return;
|
|
1980
2024
|
}
|
|
@@ -1984,12 +2028,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1984
2028
|
try {
|
|
1985
2029
|
await handleBedrock(req, res, await readBody(req), bedrockModelId, fixtures, journal, defaults, setCorsHeaders);
|
|
1986
2030
|
} catch (err) {
|
|
1987
|
-
|
|
1988
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1989
|
-
message: msg,
|
|
1990
|
-
type: "server_error"
|
|
1991
|
-
} }));
|
|
1992
|
-
else if (!res.writableEnded) res.destroy();
|
|
2031
|
+
routeError(req, res, err, pathname);
|
|
1993
2032
|
}
|
|
1994
2033
|
return;
|
|
1995
2034
|
}
|
|
@@ -1999,12 +2038,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1999
2038
|
try {
|
|
2000
2039
|
await handleBedrockStream(req, res, await readBody(req), bedrockModelId, fixtures, journal, defaults, setCorsHeaders);
|
|
2001
2040
|
} catch (err) {
|
|
2002
|
-
|
|
2003
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2004
|
-
message: msg,
|
|
2005
|
-
type: "server_error"
|
|
2006
|
-
} }));
|
|
2007
|
-
else if (!res.writableEnded) res.destroy();
|
|
2041
|
+
routeError(req, res, err, pathname);
|
|
2008
2042
|
}
|
|
2009
2043
|
return;
|
|
2010
2044
|
}
|
|
@@ -2014,12 +2048,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2014
2048
|
try {
|
|
2015
2049
|
await handleConverse(req, res, await readBody(req), converseModelId, fixtures, journal, defaults, setCorsHeaders);
|
|
2016
2050
|
} catch (err) {
|
|
2017
|
-
|
|
2018
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2019
|
-
message: msg,
|
|
2020
|
-
type: "server_error"
|
|
2021
|
-
} }));
|
|
2022
|
-
else if (!res.writableEnded) res.destroy();
|
|
2051
|
+
routeError(req, res, err, pathname);
|
|
2023
2052
|
}
|
|
2024
2053
|
return;
|
|
2025
2054
|
}
|
|
@@ -2029,12 +2058,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2029
2058
|
try {
|
|
2030
2059
|
await handleConverseStream(req, res, await readBody(req), converseStreamModelId, fixtures, journal, defaults, setCorsHeaders);
|
|
2031
2060
|
} catch (err) {
|
|
2032
|
-
|
|
2033
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2034
|
-
message: msg,
|
|
2035
|
-
type: "server_error"
|
|
2036
|
-
} }));
|
|
2037
|
-
else if (!res.writableEnded) res.destroy();
|
|
2061
|
+
routeError(req, res, err, pathname);
|
|
2038
2062
|
}
|
|
2039
2063
|
return;
|
|
2040
2064
|
}
|
|
@@ -2042,12 +2066,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2042
2066
|
try {
|
|
2043
2067
|
await handleSearch(req, res, await readBody(req), serviceFixtures?.search ?? [], journal, defaults, setCorsHeaders);
|
|
2044
2068
|
} catch (err) {
|
|
2045
|
-
|
|
2046
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2047
|
-
message: msg,
|
|
2048
|
-
type: "server_error"
|
|
2049
|
-
} }));
|
|
2050
|
-
else if (!res.writableEnded) res.destroy();
|
|
2069
|
+
routeError(req, res, err, pathname, { service: "search" });
|
|
2051
2070
|
}
|
|
2052
2071
|
return;
|
|
2053
2072
|
}
|
|
@@ -2055,12 +2074,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2055
2074
|
try {
|
|
2056
2075
|
await handleRerank(req, res, await readBody(req), serviceFixtures?.rerank ?? [], journal, defaults, setCorsHeaders);
|
|
2057
2076
|
} catch (err) {
|
|
2058
|
-
|
|
2059
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2060
|
-
message: msg,
|
|
2061
|
-
type: "server_error"
|
|
2062
|
-
} }));
|
|
2063
|
-
else if (!res.writableEnded) res.destroy();
|
|
2077
|
+
routeError(req, res, err, pathname, { service: "rerank" });
|
|
2064
2078
|
}
|
|
2065
2079
|
return;
|
|
2066
2080
|
}
|
|
@@ -2068,12 +2082,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2068
2082
|
try {
|
|
2069
2083
|
await handleModeration(req, res, await readBody(req), serviceFixtures?.moderation ?? [], journal, defaults, setCorsHeaders);
|
|
2070
2084
|
} catch (err) {
|
|
2071
|
-
|
|
2072
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2073
|
-
message: msg,
|
|
2074
|
-
type: "server_error"
|
|
2075
|
-
} }));
|
|
2076
|
-
else if (!res.writableEnded) res.destroy();
|
|
2085
|
+
routeError(req, res, err, pathname, { service: "moderation" });
|
|
2077
2086
|
}
|
|
2078
2087
|
return;
|
|
2079
2088
|
}
|
|
@@ -2082,12 +2091,57 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2082
2091
|
try {
|
|
2083
2092
|
await handleElevenLabsAudio(req, res, await readBody(req), fixtures, defaults, journal, "sound-generation");
|
|
2084
2093
|
} catch (err) {
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2094
|
+
routeError(req, res, err, pathname);
|
|
2095
|
+
}
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2098
|
+
if (pathname === ELEVENLABS_VOICE_DESIGN_PATH && req.method === "POST") {
|
|
2099
|
+
setCorsHeaders(res);
|
|
2100
|
+
try {
|
|
2101
|
+
await handleElevenLabsVoiceDesign(req, res, await readBody(req), fixtures, defaults, journal);
|
|
2102
|
+
} catch (err) {
|
|
2103
|
+
routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
|
|
2104
|
+
}
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
if (pathname === ELEVENLABS_VOICE_CREATE_PATH && req.method === "POST") {
|
|
2108
|
+
setCorsHeaders(res);
|
|
2109
|
+
try {
|
|
2110
|
+
await handleElevenLabsVoiceCreate(req, res, await readBody(req), fixtures, defaults, journal);
|
|
2111
|
+
} catch (err) {
|
|
2112
|
+
routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
|
|
2113
|
+
}
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
const elevenLabsVoiceMatch = pathname.match(ELEVENLABS_VOICE_RE);
|
|
2117
|
+
if (elevenLabsVoiceMatch && (req.method === "GET" || req.method === "DELETE")) {
|
|
2118
|
+
setCorsHeaders(res);
|
|
2119
|
+
let voiceId;
|
|
2120
|
+
try {
|
|
2121
|
+
voiceId = decodeURIComponent(elevenLabsVoiceMatch[1]);
|
|
2122
|
+
} catch {
|
|
2123
|
+
writeErrorResponse(res, 400, JSON.stringify({ error: {
|
|
2124
|
+
message: `Invalid voice id '${elevenLabsVoiceMatch[1]}': malformed percent-encoding`,
|
|
2125
|
+
type: "invalid_request_error"
|
|
2089
2126
|
} }));
|
|
2090
|
-
|
|
2127
|
+
journal.add({
|
|
2128
|
+
method: req.method ?? "GET",
|
|
2129
|
+
path: req.url ?? pathname,
|
|
2130
|
+
headers: flattenHeaders(req.headers),
|
|
2131
|
+
service: "elevenlabs-voice",
|
|
2132
|
+
body: null,
|
|
2133
|
+
response: {
|
|
2134
|
+
status: 400,
|
|
2135
|
+
fixture: null
|
|
2136
|
+
}
|
|
2137
|
+
});
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
try {
|
|
2141
|
+
if (req.method === "GET") await handleElevenLabsVoiceGet(req, res, voiceId, fixtures, defaults, journal);
|
|
2142
|
+
else await handleElevenLabsVoiceDelete(req, res, voiceId, fixtures, defaults, journal);
|
|
2143
|
+
} catch (err) {
|
|
2144
|
+
routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
|
|
2091
2145
|
}
|
|
2092
2146
|
return;
|
|
2093
2147
|
}
|
|
@@ -2098,12 +2152,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2098
2152
|
try {
|
|
2099
2153
|
await handleElevenLabsTTS(req, res, await readBody(req), fixtures, defaults, journal, voiceId);
|
|
2100
2154
|
} catch (err) {
|
|
2101
|
-
|
|
2102
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2103
|
-
message: msg,
|
|
2104
|
-
type: "server_error"
|
|
2105
|
-
} }));
|
|
2106
|
-
else if (!res.writableEnded) res.destroy();
|
|
2155
|
+
routeError(req, res, err, pathname);
|
|
2107
2156
|
}
|
|
2108
2157
|
return;
|
|
2109
2158
|
}
|
|
@@ -2114,42 +2163,38 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2114
2163
|
try {
|
|
2115
2164
|
await handleElevenLabsAudio(req, res, await readBody(req), fixtures, defaults, journal, musicSubType);
|
|
2116
2165
|
} catch (err) {
|
|
2117
|
-
|
|
2118
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2119
|
-
message: msg,
|
|
2120
|
-
type: "server_error"
|
|
2121
|
-
} }));
|
|
2122
|
-
else if (!res.writableEnded) res.destroy();
|
|
2166
|
+
routeError(req, res, err, pathname);
|
|
2123
2167
|
}
|
|
2124
2168
|
return;
|
|
2125
2169
|
}
|
|
2126
2170
|
let falBody;
|
|
2127
|
-
if (
|
|
2171
|
+
if (FAL_ROUTE_RE.test(pathname) && req.headers["x-fal-target-host"]) {
|
|
2128
2172
|
setCorsHeaders(res);
|
|
2129
2173
|
try {
|
|
2130
2174
|
falBody = req.method === "POST" || req.method === "PUT" ? await readBody(req) : "";
|
|
2131
2175
|
const raw = falBody;
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2176
|
+
if (falWillHandle(req, pathname)) {
|
|
2177
|
+
const chaosConfig = resolveChaosConfig(null, defaults.chaos, req.headers, defaults.logger, req.url);
|
|
2178
|
+
await awaitChaosLatency(null, defaults.chaos, req.headers, defaults.logger, req.url, res, chaosConfig);
|
|
2179
|
+
const goneAfterLatency = responseGoneReason(res);
|
|
2180
|
+
if (goneAfterLatency !== null) {
|
|
2181
|
+
defaults.logger.debug(`[chaos] ${req.method ?? "GET"} ${pathname}: ${describeUnwritableReason(goneAfterLatency)} after the latency delay — not served, not journalled`);
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
const chaosAction = evaluateChaos(null, defaults.chaos, req.headers, defaults.logger, req.url, chaosConfig);
|
|
2185
|
+
if (chaosAction) {
|
|
2186
|
+
applyChaosAction(chaosAction, res, null, journal, {
|
|
2187
|
+
method: req.method ?? "GET",
|
|
2188
|
+
path: pathname,
|
|
2189
|
+
headers: flattenHeaders(req.headers),
|
|
2190
|
+
body: null
|
|
2191
|
+
}, "internal", defaults.registry, defaults.logger);
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2144
2194
|
}
|
|
2145
2195
|
if (await handleFal(req, res, raw, pathname, fixtures, defaults, journal) === "handled") return;
|
|
2146
2196
|
} catch (err) {
|
|
2147
|
-
|
|
2148
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2149
|
-
message: msg,
|
|
2150
|
-
type: "server_error"
|
|
2151
|
-
} }));
|
|
2152
|
-
else if (!res.writableEnded) res.destroy();
|
|
2197
|
+
routeError(req, res, err, pathname);
|
|
2153
2198
|
return;
|
|
2154
2199
|
}
|
|
2155
2200
|
}
|
|
@@ -2158,12 +2203,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2158
2203
|
try {
|
|
2159
2204
|
await handleFalQueue(req, res, falBody ?? await readBody(req), pathname, fixtures, defaults, journal);
|
|
2160
2205
|
} catch (err) {
|
|
2161
|
-
|
|
2162
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2163
|
-
message: msg,
|
|
2164
|
-
type: "server_error"
|
|
2165
|
-
} }));
|
|
2166
|
-
else if (!res.writableEnded) res.destroy();
|
|
2206
|
+
routeError(req, res, err, pathname);
|
|
2167
2207
|
}
|
|
2168
2208
|
return;
|
|
2169
2209
|
}
|
|
@@ -2171,27 +2211,26 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2171
2211
|
setCorsHeaders(res);
|
|
2172
2212
|
try {
|
|
2173
2213
|
const raw = req.method === "POST" || req.method === "PUT" ? falBody ?? await readBody(req) : "{}";
|
|
2174
|
-
const
|
|
2214
|
+
const chaosConfig = resolveChaosConfig(null, defaults.chaos, req.headers, defaults.logger, req.url);
|
|
2215
|
+
await awaitChaosLatency(null, defaults.chaos, req.headers, defaults.logger, req.url, res, chaosConfig);
|
|
2216
|
+
const goneAfterLatency = responseGoneReason(res);
|
|
2217
|
+
if (goneAfterLatency !== null) {
|
|
2218
|
+
defaults.logger.debug(`[chaos] ${req.method ?? "GET"} ${pathname}: ${describeUnwritableReason(goneAfterLatency)} after the latency delay — not served, not journalled`);
|
|
2219
|
+
return;
|
|
2220
|
+
}
|
|
2221
|
+
const chaosAction = evaluateChaos(null, defaults.chaos, req.headers, defaults.logger, req.url, chaosConfig);
|
|
2175
2222
|
if (chaosAction) {
|
|
2176
2223
|
applyChaosAction(chaosAction, res, null, journal, {
|
|
2177
2224
|
method: req.method ?? "GET",
|
|
2178
2225
|
path: pathname,
|
|
2179
2226
|
headers: flattenHeaders(req.headers),
|
|
2180
|
-
body:
|
|
2181
|
-
|
|
2182
|
-
messages: []
|
|
2183
|
-
}
|
|
2184
|
-
}, "fixture", defaults.registry);
|
|
2227
|
+
body: null
|
|
2228
|
+
}, "internal", defaults.registry, defaults.logger);
|
|
2185
2229
|
return;
|
|
2186
2230
|
}
|
|
2187
2231
|
await handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
|
|
2188
2232
|
} catch (err) {
|
|
2189
|
-
|
|
2190
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2191
|
-
message: msg,
|
|
2192
|
-
type: "server_error"
|
|
2193
|
-
} }));
|
|
2194
|
-
else if (!res.writableEnded) res.destroy();
|
|
2233
|
+
routeError(req, res, err, pathname);
|
|
2195
2234
|
}
|
|
2196
2235
|
return;
|
|
2197
2236
|
}
|
|
@@ -2200,12 +2239,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2200
2239
|
try {
|
|
2201
2240
|
await handleFalQueue(req, res, falBody ?? await readBody(req), pathname, fixtures, defaults, journal);
|
|
2202
2241
|
} catch (err) {
|
|
2203
|
-
|
|
2204
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
2205
|
-
message: msg,
|
|
2206
|
-
type: "server_error"
|
|
2207
|
-
} }));
|
|
2208
|
-
else if (!res.writableEnded) res.destroy();
|
|
2242
|
+
routeError(req, res, err, pathname);
|
|
2209
2243
|
}
|
|
2210
2244
|
return;
|
|
2211
2245
|
}
|
|
@@ -2221,23 +2255,25 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2221
2255
|
try {
|
|
2222
2256
|
await handleCompletions(req, res, fixtures, journal, defaults, azureDeploymentId, completionsProvider, isOpenRouter);
|
|
2223
2257
|
} catch (err) {
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
res.end();
|
|
2235
|
-
} catch (writeErr) {
|
|
2236
|
-
logger.debug("Failed to write error recovery response:", writeErr);
|
|
2237
|
-
}
|
|
2258
|
+
routeError(req, res, err, pathname, {
|
|
2259
|
+
envelope: (m, status) => isOpenRouter ? serializeOpenRouterError(status, m) : JSON.stringify({ error: {
|
|
2260
|
+
message: m,
|
|
2261
|
+
type: errorTypeForStatus(status)
|
|
2262
|
+
} }),
|
|
2263
|
+
streamEvent: (m, status) => `data: ${isOpenRouter ? serializeOpenRouterError(status, m) : JSON.stringify({ error: {
|
|
2264
|
+
message: m,
|
|
2265
|
+
type: errorTypeForStatus(status)
|
|
2266
|
+
} })}\n\n`
|
|
2267
|
+
});
|
|
2238
2268
|
}
|
|
2239
2269
|
}
|
|
2240
2270
|
const activeConnections = /* @__PURE__ */ new Set();
|
|
2271
|
+
const liveLimits = normalizeLiveOptions(options?.live);
|
|
2272
|
+
const liveSessions = /* @__PURE__ */ new Map();
|
|
2273
|
+
const closeLiveSessions = (testId) => {
|
|
2274
|
+
for (const session of [...liveSessions.values()]) if (testId === void 0 || session.testId === testId) session.dispose();
|
|
2275
|
+
};
|
|
2276
|
+
liveClosers.set(defaults, closeLiveSessions);
|
|
2241
2277
|
server.on("upgrade", (req, socket, head) => {
|
|
2242
2278
|
handleUpgradeRequest(req, socket, head).catch((err) => {
|
|
2243
2279
|
const msg = err instanceof Error ? err.message : "Internal error";
|
|
@@ -2300,17 +2336,38 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2300
2336
|
}
|
|
2301
2337
|
}
|
|
2302
2338
|
if (!pathname.match(AZURE_DEPLOYMENT_RE)) pathname = normalizeCompatPath(pathname, logger);
|
|
2303
|
-
if (pathname !== RESPONSES_PATH && pathname !== REALTIME_PATH && pathname !== GEMINI_LIVE_PATH) {
|
|
2339
|
+
if (pathname !== RESPONSES_PATH && pathname !== REALTIME_PATH && pathname !== GEMINI_LIVE_PATH && pathname !== LIVE_PATH) {
|
|
2304
2340
|
socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
|
|
2305
2341
|
socket.destroy();
|
|
2306
2342
|
return;
|
|
2307
2343
|
}
|
|
2344
|
+
let liveId;
|
|
2345
|
+
if (pathname === LIVE_PATH) {
|
|
2346
|
+
if (req.method !== "GET" || parsedUrl.searchParams.has("model")) {
|
|
2347
|
+
socket.end("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
if (liveSessions.size >= liveLimits.maxSessions) {
|
|
2351
|
+
socket.end("HTTP/1.1 429 Too Many Requests\r\nConnection: close\r\n\r\n");
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
liveId = Symbol("live-session");
|
|
2355
|
+
const reservedId = liveId;
|
|
2356
|
+
liveSessions.set(reservedId, {
|
|
2357
|
+
testId: getTestId(req),
|
|
2358
|
+
dispose: () => {
|
|
2359
|
+
socket.destroy();
|
|
2360
|
+
liveSessions.delete(reservedId);
|
|
2361
|
+
}
|
|
2362
|
+
});
|
|
2363
|
+
}
|
|
2308
2364
|
if (head.length > 0) socket.unshift(head);
|
|
2309
2365
|
let ws;
|
|
2310
2366
|
try {
|
|
2311
|
-
ws = upgradeToWebSocket(req, socket);
|
|
2367
|
+
ws = upgradeToWebSocket(req, socket, pathname === LIVE_PATH ? liveLimits : void 0);
|
|
2312
2368
|
} catch (err) {
|
|
2313
2369
|
const msg = err instanceof Error ? err.message : "WebSocket upgrade failed";
|
|
2370
|
+
if (liveId !== void 0) liveSessions.delete(liveId);
|
|
2314
2371
|
logger.error(`WebSocket upgrade error: ${msg}`);
|
|
2315
2372
|
if (!socket.destroyed) socket.destroy();
|
|
2316
2373
|
return;
|
|
@@ -2324,7 +2381,24 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2324
2381
|
activeConnections.delete(ws);
|
|
2325
2382
|
});
|
|
2326
2383
|
const wsTestId = getTestId(req);
|
|
2327
|
-
if (pathname ===
|
|
2384
|
+
if (pathname === LIVE_PATH && liveId !== void 0) {
|
|
2385
|
+
const sessionId = liveId;
|
|
2386
|
+
const dispose = handleLiveSession(ws, fixtures, journal, {
|
|
2387
|
+
defaults,
|
|
2388
|
+
limits: liveLimits,
|
|
2389
|
+
localApiKeys: resolvedAuth.publicConfig?.apiKeys,
|
|
2390
|
+
testId: wsTestId,
|
|
2391
|
+
headers: req.headers,
|
|
2392
|
+
onDispose: () => {
|
|
2393
|
+
liveSessions.delete(sessionId);
|
|
2394
|
+
activeConnections.delete(ws);
|
|
2395
|
+
}
|
|
2396
|
+
});
|
|
2397
|
+
liveSessions.set(sessionId, {
|
|
2398
|
+
testId: wsTestId,
|
|
2399
|
+
dispose
|
|
2400
|
+
});
|
|
2401
|
+
} else if (pathname === RESPONSES_PATH) handleWebSocketResponses(ws, fixtures, journal, {
|
|
2328
2402
|
...defaults,
|
|
2329
2403
|
model: "gpt-4",
|
|
2330
2404
|
testId: wsTestId,
|
|
@@ -2349,6 +2423,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2349
2423
|
}
|
|
2350
2424
|
const originalClose = server.close.bind(server);
|
|
2351
2425
|
server.close = function(callback) {
|
|
2426
|
+
closeLiveSessions();
|
|
2352
2427
|
for (const ws of activeConnections) ws.close(1001, "Server shutting down");
|
|
2353
2428
|
activeConnections.clear();
|
|
2354
2429
|
videoStates.clear();
|
|
@@ -2372,6 +2447,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2372
2447
|
for (const { path: mountPath, handler } of mounts) if (handler.setBaseUrl) handler.setBaseUrl(url + mountPath);
|
|
2373
2448
|
}
|
|
2374
2449
|
resolve({
|
|
2450
|
+
closeLiveSessions,
|
|
2375
2451
|
server,
|
|
2376
2452
|
journal,
|
|
2377
2453
|
url,
|