@copilotkit/aimock 1.40.0 → 1.42.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 +28 -1
- package/README.md +5 -3
- package/dist/a2a-mock.cjs +18 -11
- package/dist/a2a-mock.cjs.map +1 -1
- package/dist/a2a-mock.d.cts +1 -0
- package/dist/a2a-mock.d.cts.map +1 -1
- package/dist/a2a-mock.d.ts +1 -0
- package/dist/a2a-mock.d.ts.map +1 -1
- package/dist/a2a-mock.js +18 -11
- package/dist/a2a-mock.js.map +1 -1
- package/dist/agui-handler.d.cts.map +1 -1
- package/dist/agui-handler.d.ts.map +1 -1
- package/dist/agui-mock.cjs +2 -0
- 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 +2 -0
- package/dist/agui-mock.js.map +1 -1
- package/dist/bedrock-converse.cjs +36 -2
- 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 +37 -3
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +50 -13
- 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 +51 -14
- package/dist/bedrock.js.map +1 -1
- package/dist/byteplus-video.cjs +954 -0
- package/dist/byteplus-video.cjs.map +1 -0
- package/dist/byteplus-video.d.cts +176 -0
- package/dist/byteplus-video.d.cts.map +1 -0
- package/dist/byteplus-video.d.ts +176 -0
- package/dist/byteplus-video.d.ts.map +1 -0
- package/dist/byteplus-video.js +947 -0
- package/dist/byteplus-video.js.map +1 -0
- package/dist/chaos.cjs +36 -7
- package/dist/chaos.cjs.map +1 -1
- package/dist/chaos.d.cts +8 -3
- package/dist/chaos.d.cts.map +1 -1
- package/dist/chaos.d.ts +8 -3
- package/dist/chaos.d.ts.map +1 -1
- package/dist/chaos.js +36 -8
- package/dist/chaos.js.map +1 -1
- package/dist/cli.cjs +5 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +58 -5
- 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 +59 -6
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/elevenlabs-audio.cjs +36 -2
- package/dist/elevenlabs-audio.cjs.map +1 -1
- package/dist/elevenlabs-audio.d.cts.map +1 -1
- package/dist/elevenlabs-audio.d.ts.map +1 -1
- package/dist/elevenlabs-audio.js +37 -3
- package/dist/elevenlabs-audio.js.map +1 -1
- package/dist/embeddings.cjs +58 -3
- 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 +59 -4
- package/dist/embeddings.js.map +1 -1
- package/dist/fal-audio.cjs +38 -2
- package/dist/fal-audio.cjs.map +1 -1
- package/dist/fal-audio.d.cts.map +1 -1
- package/dist/fal-audio.d.ts.map +1 -1
- package/dist/fal-audio.js +39 -3
- package/dist/fal-audio.js.map +1 -1
- package/dist/fixture-loader.cjs +0 -0
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +9 -1
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +9 -1
- 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 +19 -1
- package/dist/gemini-embeddings.cjs.map +1 -1
- package/dist/gemini-embeddings.js +20 -2
- package/dist/gemini-embeddings.js.map +1 -1
- package/dist/gemini-interactions.cjs +15 -1
- 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 +16 -2
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +70 -6
- 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 +71 -7
- package/dist/gemini.js.map +1 -1
- package/dist/grok-video.cjs +2 -2
- package/dist/grok-video.cjs.map +1 -1
- package/dist/grok-video.d.cts.map +1 -1
- package/dist/grok-video.d.ts.map +1 -1
- package/dist/grok-video.js +2 -2
- package/dist/grok-video.js.map +1 -1
- package/dist/helpers.cjs +160 -5
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +152 -6
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +24 -6
- package/dist/images.cjs.map +1 -1
- package/dist/images.d.cts +11 -3
- package/dist/images.d.cts.map +1 -1
- package/dist/images.d.ts +11 -3
- package/dist/images.d.ts.map +1 -1
- package/dist/images.js +25 -7
- package/dist/images.js.map +1 -1
- package/dist/index.cjs +7 -0
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/llmock.cjs +15 -0
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +12 -0
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +12 -0
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +16 -1
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-mock.cjs +11 -0
- package/dist/mcp-mock.cjs.map +1 -1
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +11 -0
- package/dist/mcp-mock.js.map +1 -1
- package/dist/messages.cjs +18 -1
- 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 +19 -2
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +20 -0
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +19 -1
- package/dist/metrics.js.map +1 -1
- package/dist/moderation.cjs +67 -3
- package/dist/moderation.cjs.map +1 -1
- package/dist/moderation.d.cts +1 -0
- package/dist/moderation.d.cts.map +1 -1
- package/dist/moderation.d.ts +1 -0
- package/dist/moderation.d.ts.map +1 -1
- package/dist/moderation.js +68 -4
- package/dist/moderation.js.map +1 -1
- package/dist/ollama.cjs +75 -5
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts.map +1 -1
- package/dist/ollama.js +76 -6
- package/dist/ollama.js.map +1 -1
- package/dist/openrouter-video.cjs +4 -4
- package/dist/openrouter-video.cjs.map +1 -1
- package/dist/openrouter-video.d.cts.map +1 -1
- package/dist/openrouter-video.d.ts.map +1 -1
- package/dist/openrouter-video.js +4 -4
- package/dist/openrouter-video.js.map +1 -1
- package/dist/provider-auth.cjs +2 -0
- package/dist/provider-auth.cjs.map +1 -1
- package/dist/provider-auth.js +2 -0
- package/dist/provider-auth.js.map +1 -1
- package/dist/rerank.cjs +65 -2
- package/dist/rerank.cjs.map +1 -1
- package/dist/rerank.d.cts +1 -1
- package/dist/rerank.d.cts.map +1 -1
- package/dist/rerank.d.ts +1 -1
- package/dist/rerank.d.ts.map +1 -1
- package/dist/rerank.js +66 -3
- package/dist/rerank.js.map +1 -1
- package/dist/responses.cjs +18 -1
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +19 -2
- package/dist/responses.js.map +1 -1
- package/dist/router.cjs +1 -0
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +1 -0
- package/dist/router.js.map +1 -1
- package/dist/search.cjs +67 -3
- package/dist/search.cjs.map +1 -1
- package/dist/search.d.cts +1 -0
- package/dist/search.d.cts.map +1 -1
- package/dist/search.d.ts +1 -0
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +68 -4
- package/dist/search.js.map +1 -1
- package/dist/server.cjs +406 -17
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +2 -0
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +410 -21
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +18 -1
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.d.cts.map +1 -1
- package/dist/speech.d.ts.map +1 -1
- package/dist/speech.js +19 -2
- package/dist/speech.js.map +1 -1
- package/dist/stream-collapse.cjs +2 -1
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.js +2 -1
- package/dist/stream-collapse.js.map +1 -1
- package/dist/transcription.cjs +21 -2
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.d.cts.map +1 -1
- package/dist/transcription.d.ts.map +1 -1
- package/dist/transcription.js +21 -2
- package/dist/transcription.js.map +1 -1
- package/dist/types.d.cts +58 -5
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +58 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-mock.cjs +88 -0
- package/dist/vector-mock.cjs.map +1 -1
- package/dist/vector-mock.d.cts.map +1 -1
- package/dist/vector-mock.d.ts.map +1 -1
- package/dist/vector-mock.js +89 -1
- package/dist/vector-mock.js.map +1 -1
- package/dist/vector-types.d.cts.map +1 -1
- package/dist/veo-video.cjs +2 -2
- package/dist/veo-video.cjs.map +1 -1
- package/dist/veo-video.d.cts.map +1 -1
- package/dist/veo-video.d.ts.map +1 -1
- package/dist/veo-video.js +2 -2
- package/dist/veo-video.js.map +1 -1
- package/dist/video.cjs +19 -2
- package/dist/video.cjs.map +1 -1
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts.map +1 -1
- package/dist/video.js +20 -3
- package/dist/video.js.map +1 -1
- package/package.json +2 -2
package/dist/server.d.cts
CHANGED
|
@@ -7,6 +7,7 @@ import { VideoStateMap } from "./video.cjs";
|
|
|
7
7
|
import { OpenRouterVideoJobMap } from "./openrouter-video.cjs";
|
|
8
8
|
import { VeoVideoJobMap } from "./veo-video.cjs";
|
|
9
9
|
import { GrokVideoJobMap } from "./grok-video.cjs";
|
|
10
|
+
import { BytePlusVideoJobMap } from "./byteplus-video.cjs";
|
|
10
11
|
import * as http$1 from "node:http";
|
|
11
12
|
|
|
12
13
|
//#region src/server.d.ts
|
|
@@ -19,6 +20,7 @@ interface ServerInstance {
|
|
|
19
20
|
openRouterVideoJobs: OpenRouterVideoJobMap;
|
|
20
21
|
veoVideoJobs: VeoVideoJobMap;
|
|
21
22
|
grokVideoJobs: GrokVideoJobMap;
|
|
23
|
+
bytePlusVideoJobs: BytePlusVideoJobMap;
|
|
22
24
|
}
|
|
23
25
|
/**
|
|
24
26
|
* The per-server state a full reset clears. `ServerInstance` structurally
|
package/dist/server.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.cts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.cts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;UAgJiB,cAAA;EAAA,MAAA,EACP,MAAA,CAAK,MADgB;EAAA,OAAA,EAEpB,OAFoB;KACrB,EAAA,MAAK;UACJ,EAEC,eAFD;aAEC,EACG,aADH;qBACG,EACQ,qBADR;cACQ,EACP,cADO;eACP,EACC,eADD;mBACC,EACI,mBADJ;;;AAqkDjB;;;;;UAAiB,eAAA;UACP;UACA;cACI;;iBAMQ,YAAA,WACV,qBACA,4BACD;;WAA+B;sBACtB,kBACjB,QAAQ"}
|
package/dist/server.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { VideoStateMap } from "./video.js";
|
|
|
7
7
|
import { OpenRouterVideoJobMap } from "./openrouter-video.js";
|
|
8
8
|
import { VeoVideoJobMap } from "./veo-video.js";
|
|
9
9
|
import { GrokVideoJobMap } from "./grok-video.js";
|
|
10
|
+
import { BytePlusVideoJobMap } from "./byteplus-video.js";
|
|
10
11
|
import * as http$1 from "node:http";
|
|
11
12
|
|
|
12
13
|
//#region src/server.d.ts
|
|
@@ -19,6 +20,7 @@ interface ServerInstance {
|
|
|
19
20
|
openRouterVideoJobs: OpenRouterVideoJobMap;
|
|
20
21
|
veoVideoJobs: VeoVideoJobMap;
|
|
21
22
|
grokVideoJobs: GrokVideoJobMap;
|
|
23
|
+
bytePlusVideoJobs: BytePlusVideoJobMap;
|
|
22
24
|
}
|
|
23
25
|
/**
|
|
24
26
|
* The per-server state a full reset clears. `ServerInstance` structurally
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;UAgJiB,cAAA;EAAA,MAAA,EACP,MAAA,CAAK,MADgB;EAAA,OAAA,EAEpB,OAFoB;KACrB,EAAA,MAAK;UACJ,EAEC,eAFD;aAEC,EACG,aADH;qBACG,EACQ,qBADR;cACQ,EACP,cADO;eACP,EACC,eADD;mBACC,EACI,mBADJ;;;AAqkDjB;;;;;UAAiB,eAAA;UACP;UACA;cACI;;iBAMQ,YAAA,WACV,qBACA,4BACD;;WAA+B;sBACtB,kBACjB,QAAQ"}
|
package/dist/server.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import { DEFAULT_TEST_ID } from "./constants.js";
|
|
1
2
|
import { markAuthenticatedRequest, resolveInboundAuth, validateRequestApiKey, writeApiKeyHttpRejection, writeApiKeyUpgradeRejection } from "./api-key-auth.js";
|
|
2
|
-
import { buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, extractOverrides, flattenHeaders, getContext, getTestId, isAudioResponse, isContentWithToolCallsResponse, isErrorResponse, isTextResponse, isToolCallResponse, readBody, resolveFixtureBlocks, resolveReasoningForModel, resolveResponse, resolveStrictMode, resolveUsage, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.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, resolveFixtureBlocks, resolveReasoningForModel, resolveResponse, resolveStrictMode, resolveUsage, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
|
|
3
4
|
import { Logger } from "./logger.js";
|
|
4
5
|
import { Journal } from "./journal.js";
|
|
5
6
|
import { matchFixtureDiagnostic, recordMatchOptions } from "./router.js";
|
|
6
|
-
import { entryToFixture, validateFixtures } from "./fixture-loader.js";
|
|
7
|
+
import { INJECTED_STATUS_RANGE, entryToFixture, isInjectableStatus, validateFixtures } from "./fixture-loader.js";
|
|
7
8
|
import { writeErrorResponse, writeSSEStream } from "./sse-writer.js";
|
|
8
9
|
import { createInterruptionSignal } from "./interruption.js";
|
|
9
10
|
import { buildOpenRouterCandidates, handleOpenRouterCredits, handleOpenRouterKey, handleOpenRouterModels, isOpenRouterPath, resolveOpenRouterShaping, serializeOpenRouterError, shapeOpenRouterChunks, shapeOpenRouterCompletion } from "./openrouter-chat.js";
|
|
10
|
-
import { applyChaosAction, evaluateChaos } from "./chaos.js";
|
|
11
|
+
import { applyChaosAction, evaluateChaos, isChaosScope } from "./chaos.js";
|
|
11
12
|
import { proxyAndRecord } from "./recorder.js";
|
|
12
13
|
import { handleResponses } from "./responses.js";
|
|
13
14
|
import { handleMessages } from "./messages.js";
|
|
@@ -26,6 +27,7 @@ import { falQueueStates, handleFal } from "./fal.js";
|
|
|
26
27
|
import { OPENROUTER_VIDEO_DEFAULT_MAX_CONTENT_BYTES, OpenRouterVideoJobMap, handleOpenRouterVideoContent, handleOpenRouterVideoCreate, handleOpenRouterVideoModels, handleOpenRouterVideoStatus } from "./openrouter-video.js";
|
|
27
28
|
import { VeoVideoJobMap, handleVeoVideoCreate, handleVeoVideoStatus } from "./veo-video.js";
|
|
28
29
|
import { GrokVideoJobMap, handleGrokVideoCreate, handleGrokVideoStatus } from "./grok-video.js";
|
|
30
|
+
import { BytePlusVideoJobMap, handleBytePlusVideoCreate, handleBytePlusVideoStatus } from "./byteplus-video.js";
|
|
29
31
|
import { handleElevenLabsAudio, handleElevenLabsTTS } from "./elevenlabs-audio.js";
|
|
30
32
|
import { handleOllama, handleOllamaEmbeddings, handleOllamaGenerate } from "./ollama.js";
|
|
31
33
|
import { handleCohere, handleCohereEmbed } from "./cohere.js";
|
|
@@ -36,7 +38,7 @@ import { upgradeToWebSocket } from "./ws-framing.js";
|
|
|
36
38
|
import { handleWebSocketResponses } from "./ws-responses.js";
|
|
37
39
|
import { handleWebSocketRealtime } from "./ws-realtime.js";
|
|
38
40
|
import { handleWebSocketGeminiLive } from "./ws-gemini-live.js";
|
|
39
|
-
import { 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";
|
|
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";
|
|
40
42
|
import * as http$1 from "node:http";
|
|
41
43
|
|
|
42
44
|
//#region src/server.ts
|
|
@@ -124,7 +126,8 @@ const DEFAULT_MODELS = [
|
|
|
124
126
|
const CORS_HEADERS = {
|
|
125
127
|
"Access-Control-Allow-Origin": "*",
|
|
126
128
|
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
|
127
|
-
"Access-Control-Allow-Headers": "*"
|
|
129
|
+
"Access-Control-Allow-Headers": "*",
|
|
130
|
+
"Access-Control-Expose-Headers": "X-Total-Count"
|
|
128
131
|
};
|
|
129
132
|
function setCorsHeaders(res) {
|
|
130
133
|
for (const [key, value] of Object.entries(CORS_HEADERS)) res.setHeader(key, value);
|
|
@@ -142,11 +145,24 @@ function handleNotFound(res, message) {
|
|
|
142
145
|
} }));
|
|
143
146
|
}
|
|
144
147
|
const CONTROL_PREFIX = "/__aimock";
|
|
148
|
+
/** The complete `GET /__aimock/fixtures` query-param vocabulary; anything else 400s. */
|
|
149
|
+
const FIXTURES_PARAMS = new Set(["include"]);
|
|
150
|
+
/** The complete `GET /__aimock/journal` query-param vocabulary; anything else 400s. */
|
|
151
|
+
const JOURNAL_PARAMS = new Set([
|
|
152
|
+
"limit",
|
|
153
|
+
"offset",
|
|
154
|
+
"path",
|
|
155
|
+
"method",
|
|
156
|
+
"status",
|
|
157
|
+
"service",
|
|
158
|
+
"testId"
|
|
159
|
+
]);
|
|
145
160
|
/**
|
|
146
161
|
* Perform a full reset: clear the fixtures array, the journal (entries *and*
|
|
147
162
|
* per-test fixture match-counts, i.e. sequence position), the video and fal.ai
|
|
148
|
-
* job/queue state,
|
|
149
|
-
* the
|
|
163
|
+
* job/queue state, the Gemini interaction/event-id counters, and any runtime
|
|
164
|
+
* chaos override (reverting to the construction-time chaos config), then
|
|
165
|
+
* re-zero the `aimock_fixtures_loaded` gauge.
|
|
150
166
|
*
|
|
151
167
|
* `targets` is `null` when no server is running (an in-process `reset()` before
|
|
152
168
|
* `start()`). The process-global generation state is reset either way, since it
|
|
@@ -163,17 +179,112 @@ function performFullReset(fixtures, targets) {
|
|
|
163
179
|
resetEventIdCounter();
|
|
164
180
|
if (!targets) return;
|
|
165
181
|
targets.journal.clear();
|
|
182
|
+
targets.defaults.chaos = void 0;
|
|
166
183
|
targets.videoStates.clear();
|
|
167
184
|
targets.openRouterVideoJobs.clear();
|
|
168
185
|
targets.veoVideoJobs.clear();
|
|
169
186
|
targets.grokVideoJobs.clear();
|
|
187
|
+
targets.bytePlusVideoJobs.clear();
|
|
170
188
|
if (targets.defaults.registry) targets.defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
|
|
171
189
|
}
|
|
172
190
|
/**
|
|
191
|
+
* JSON-safe redaction for control-API inspection output. Functions (fixture
|
|
192
|
+
* `predicate`s, response factories) and RegExps do not survive
|
|
193
|
+
* JSON.stringify as anything useful, so they become marker strings.
|
|
194
|
+
*/
|
|
195
|
+
function redactForInspection(value) {
|
|
196
|
+
if (typeof value === "function") return "[function]";
|
|
197
|
+
if (value instanceof RegExp) return String(value);
|
|
198
|
+
if (Array.isArray(value)) return value.map(redactForInspection);
|
|
199
|
+
if (value !== null && typeof value === "object") {
|
|
200
|
+
const out = {};
|
|
201
|
+
for (const [key, entry] of Object.entries(value)) out[key] = redactForInspection(entry);
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
return value;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* One-line kind label for a fixture response used by
|
|
208
|
+
* `GET /__aimock/fixtures?include=fixtures` (e.g. "text", "error").
|
|
209
|
+
* Response factories are reported as "factory".
|
|
210
|
+
*
|
|
211
|
+
* Discriminates with the same `is*Response` guards the request handlers use,
|
|
212
|
+
* in the same order — NOT by inspecting keys. Every response shape extends
|
|
213
|
+
* `ResponseOverrides` (`id`/`created`/`model`/`usage`), so a recorded fixture's
|
|
214
|
+
* first key is usually an envelope override rather than the discriminant. The
|
|
215
|
+
* audio-before-content/toolCalls order is the ORDERING CONTRACT documented on
|
|
216
|
+
* `AudioResponse` in types.ts: those shapes structurally overlap.
|
|
217
|
+
*/
|
|
218
|
+
function fixtureResponseKind(response) {
|
|
219
|
+
if (isResponseFactory(response)) return "factory";
|
|
220
|
+
if (isErrorResponse(response)) return "error";
|
|
221
|
+
if (isAudioResponse(response)) return "audio";
|
|
222
|
+
if (isVideoResponse(response)) return "video";
|
|
223
|
+
if (isTranscriptionResponse(response)) return "transcription";
|
|
224
|
+
if (isImageResponse(response)) return "image";
|
|
225
|
+
if (isEmbeddingResponse(response)) return "embedding";
|
|
226
|
+
if (isJSONResponse(response)) return "json";
|
|
227
|
+
if (isContentWithToolCallsResponse(response)) return "contentWithToolCalls";
|
|
228
|
+
if (isToolCallResponse(response)) return "toolCalls";
|
|
229
|
+
if (isTextResponse(response)) return "text";
|
|
230
|
+
return "unknown";
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Resolve a journal entry's testId exactly the way `getTestId` resolves an
|
|
234
|
+
* incoming request's: the `X-Test-Id` header wins, then `?testId=` parsed out
|
|
235
|
+
* of the query string, then `DEFAULT_TEST_ID`. The
|
|
236
|
+
* filter and the server's own per-test isolation must never disagree — and a
|
|
237
|
+
* raw `path.includes("testId=t1")` both prefix-collides with `t10` and matches
|
|
238
|
+
* unrelated params like `notTestId`.
|
|
239
|
+
*/
|
|
240
|
+
function journalEntryTestId(entry) {
|
|
241
|
+
const header = entry.headers["x-test-id"];
|
|
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;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* The testId a chaos override is stored under, and read back with — resolved by
|
|
252
|
+
* `getTestId`, i.e. `X-Test-Id` header then `?testId=`, exactly as
|
|
253
|
+
* `resolveScopedDefaults` resolves the traffic being evaluated. A control call
|
|
254
|
+
* and the requests it is meant to affect can then never land in different
|
|
255
|
+
* scopes. `DEFAULT_TEST_ID` (neither header nor param) means the server-wide
|
|
256
|
+
* baseline.
|
|
257
|
+
*
|
|
258
|
+
* Returns `null` when `X-Test-Id` is PRESENT but blank. `String(testId ?? "")`
|
|
259
|
+
* in a harness produces that trivially, and silently treating it as "untagged"
|
|
260
|
+
* would install a server-wide baseline that fails every other test — the exact
|
|
261
|
+
* cross-test leak per-testId scoping exists to prevent. Callers must 400.
|
|
262
|
+
*/
|
|
263
|
+
function chaosScopeId(req) {
|
|
264
|
+
const headerValue = req.headers["x-test-id"];
|
|
265
|
+
const raw = Array.isArray(headerValue) ? headerValue[0] : headerValue;
|
|
266
|
+
if (typeof raw === "string" && raw.trim() === "") return null;
|
|
267
|
+
return getTestId(req);
|
|
268
|
+
}
|
|
269
|
+
/** 400 for a control call whose `X-Test-Id` is present but blank. */
|
|
270
|
+
function writeBlankTestId(res) {
|
|
271
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
272
|
+
res.end(JSON.stringify({ error: "Invalid 'X-Test-Id': header is present but empty" }));
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
/** The chaos config in effect for one testId: its override, else the baseline. */
|
|
276
|
+
function effectiveChaos(defaults, scopeId) {
|
|
277
|
+
const scoped = defaults.chaosByTestId?.get(scopeId);
|
|
278
|
+
if (scoped) return scoped;
|
|
279
|
+
const current = defaults.chaos;
|
|
280
|
+
if (!current) return {};
|
|
281
|
+
return isChaosScope(current) ? current.base ?? {} : current;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
173
284
|
* Handle requests under `/__aimock/`. Returns `true` if the request was
|
|
174
285
|
* handled, `false` if the path doesn't match the control prefix.
|
|
175
286
|
*/
|
|
176
|
-
async function handleControlAPI(req, res, pathname, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, defaults) {
|
|
287
|
+
async function handleControlAPI(req, res, pathname, searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults) {
|
|
177
288
|
if (!pathname.startsWith(CONTROL_PREFIX)) return false;
|
|
178
289
|
const subPath = pathname.slice(9);
|
|
179
290
|
setCorsHeaders(res);
|
|
@@ -183,13 +294,148 @@ async function handleControlAPI(req, res, pathname, fixtures, journal, videoStat
|
|
|
183
294
|
return true;
|
|
184
295
|
}
|
|
185
296
|
if (subPath === "/journal" && req.method === "GET") {
|
|
186
|
-
|
|
187
|
-
|
|
297
|
+
const bad = (message) => {
|
|
298
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
299
|
+
res.end(JSON.stringify({ error: message }));
|
|
300
|
+
return true;
|
|
301
|
+
};
|
|
302
|
+
for (const key of searchParams.keys()) if (!JOURNAL_PARAMS.has(key)) return bad(`Unknown query parameter: '${key}'. Supported: ${[...JOURNAL_PARAMS].join(", ")}`);
|
|
303
|
+
const parseNonNegativeInt = (name, fallback) => {
|
|
304
|
+
const raw = searchParams.get(name);
|
|
305
|
+
if (raw === null) return fallback;
|
|
306
|
+
if (!/^\d+$/.test(raw.trim())) return null;
|
|
307
|
+
return Number(raw);
|
|
308
|
+
};
|
|
309
|
+
const limit = parseNonNegativeInt("limit", -1);
|
|
310
|
+
const offset = parseNonNegativeInt("offset", 0);
|
|
311
|
+
if (limit === null) return bad("Invalid 'limit': must be an integer >= 0");
|
|
312
|
+
if (offset === null) return bad("Invalid 'offset': must be an integer >= 0");
|
|
313
|
+
let statusFilter;
|
|
314
|
+
const statusRaw = searchParams.get("status");
|
|
315
|
+
if (statusRaw !== null) {
|
|
316
|
+
if (!/^\d+$/.test(statusRaw.trim())) return bad("Invalid 'status': must be an integer");
|
|
317
|
+
statusFilter = Number(statusRaw);
|
|
318
|
+
}
|
|
319
|
+
const pathFilter = searchParams.get("path");
|
|
320
|
+
const methodFilter = searchParams.get("method");
|
|
321
|
+
const serviceFilter = searchParams.get("service");
|
|
322
|
+
const testIdFilter = searchParams.get("testId");
|
|
323
|
+
let entries = journal.getAll();
|
|
324
|
+
if (methodFilter !== null) {
|
|
325
|
+
const want = methodFilter.toUpperCase();
|
|
326
|
+
entries = entries.filter((e) => e.method.toUpperCase() === want);
|
|
327
|
+
}
|
|
328
|
+
if (pathFilter !== null) entries = entries.filter((e) => e.path.includes(pathFilter));
|
|
329
|
+
if (statusFilter !== void 0) entries = entries.filter((e) => e.response.status === statusFilter);
|
|
330
|
+
if (serviceFilter !== null) entries = entries.filter((e) => e.service === serviceFilter);
|
|
331
|
+
if (testIdFilter !== null) entries = entries.filter((e) => journalEntryTestId(e) === testIdFilter);
|
|
332
|
+
const total = entries.length;
|
|
333
|
+
if (offset > 0) entries = entries.slice(offset);
|
|
334
|
+
if (limit >= 0) entries = entries.slice(0, limit);
|
|
335
|
+
res.writeHead(200, {
|
|
336
|
+
"Content-Type": "application/json",
|
|
337
|
+
"X-Total-Count": String(total)
|
|
338
|
+
});
|
|
339
|
+
res.end(JSON.stringify(entries));
|
|
188
340
|
return true;
|
|
189
341
|
}
|
|
190
342
|
if (subPath === "/fixtures" && req.method === "GET") {
|
|
343
|
+
for (const key of searchParams.keys()) if (!FIXTURES_PARAMS.has(key)) {
|
|
344
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
345
|
+
res.end(JSON.stringify({ error: `Unknown query parameter: '${key}'. Supported: ${[...FIXTURES_PARAMS].join(", ")}` }));
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
const include = searchParams.get("include");
|
|
349
|
+
if (include === null) {
|
|
350
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
351
|
+
res.end(JSON.stringify({ count: fixtures.length }));
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
if (include !== "fixtures") {
|
|
355
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
356
|
+
res.end(JSON.stringify({ error: "Invalid 'include': expected 'fixtures'" }));
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
360
|
+
res.end(JSON.stringify({
|
|
361
|
+
count: fixtures.length,
|
|
362
|
+
fixtures: fixtures.map((fixture, index) => ({
|
|
363
|
+
index,
|
|
364
|
+
match: redactForInspection(fixture.match),
|
|
365
|
+
responseKind: fixtureResponseKind(fixture.response),
|
|
366
|
+
...fixture.latency !== void 0 ? { latency: fixture.latency } : {},
|
|
367
|
+
...fixture.chaos !== void 0 ? { chaos: fixture.chaos } : {}
|
|
368
|
+
}))
|
|
369
|
+
}));
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
if (subPath === "/chaos" && req.method === "GET") {
|
|
373
|
+
const scopeId = chaosScopeId(req);
|
|
374
|
+
if (scopeId === null) return writeBlankTestId(res);
|
|
191
375
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
192
|
-
res.end(JSON.stringify({
|
|
376
|
+
res.end(JSON.stringify({ chaos: effectiveChaos(defaults, scopeId) }));
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
if (subPath === "/chaos" && req.method === "DELETE") {
|
|
380
|
+
const scopeId = chaosScopeId(req);
|
|
381
|
+
if (scopeId === null) return writeBlankTestId(res);
|
|
382
|
+
defaults.chaosByTestId?.delete(scopeId);
|
|
383
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
384
|
+
res.end(JSON.stringify({ chaos: effectiveChaos(defaults, scopeId) }));
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
if (subPath === "/chaos" && req.method === "POST") {
|
|
388
|
+
const scopeId = chaosScopeId(req);
|
|
389
|
+
if (scopeId === null) return writeBlankTestId(res);
|
|
390
|
+
let raw;
|
|
391
|
+
try {
|
|
392
|
+
raw = await readBody(req);
|
|
393
|
+
} catch (err) {
|
|
394
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
395
|
+
defaults.logger.error(`POST /__aimock/chaos: failed to read body: ${msg}`);
|
|
396
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
397
|
+
res.end(JSON.stringify({ error: `Failed to read request body: ${msg}` }));
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
let parsed;
|
|
401
|
+
try {
|
|
402
|
+
parsed = JSON.parse(raw);
|
|
403
|
+
} catch (err) {
|
|
404
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
405
|
+
defaults.logger.error(`POST /__aimock/chaos: invalid JSON: ${msg}`);
|
|
406
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
407
|
+
res.end(JSON.stringify({ error: `Invalid JSON: ${msg}` }));
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
411
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
412
|
+
res.end(JSON.stringify({ error: "Invalid body: expected a JSON object" }));
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
const allowed = [
|
|
416
|
+
"dropRate",
|
|
417
|
+
"malformedRate",
|
|
418
|
+
"disconnectRate"
|
|
419
|
+
];
|
|
420
|
+
for (const key of Object.keys(parsed)) if (!allowed.includes(key)) {
|
|
421
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
422
|
+
res.end(JSON.stringify({ error: `Unknown chaos field: '${key}'` }));
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
const next = {};
|
|
426
|
+
for (const key of allowed) {
|
|
427
|
+
const value = parsed[key];
|
|
428
|
+
if (value === void 0) continue;
|
|
429
|
+
if (typeof value !== "number" || Number.isNaN(value) || value < 0 || value > 1) {
|
|
430
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
431
|
+
res.end(JSON.stringify({ error: `Invalid '${key}': must be a number between 0 and 1` }));
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
434
|
+
next[key] = value;
|
|
435
|
+
}
|
|
436
|
+
defaults.chaosByTestId?.set(scopeId, next);
|
|
437
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
438
|
+
res.end(JSON.stringify({ chaos: effectiveChaos(defaults, scopeId) }));
|
|
193
439
|
return true;
|
|
194
440
|
}
|
|
195
441
|
if (subPath === "/fixtures" && req.method === "POST") {
|
|
@@ -247,6 +493,7 @@ async function handleControlAPI(req, res, pathname, fixtures, journal, videoStat
|
|
|
247
493
|
openRouterVideoJobs,
|
|
248
494
|
veoVideoJobs,
|
|
249
495
|
grokVideoJobs,
|
|
496
|
+
bytePlusVideoJobs,
|
|
250
497
|
defaults
|
|
251
498
|
});
|
|
252
499
|
if (subPath === "/reset" && req.method === "POST") {
|
|
@@ -297,8 +544,37 @@ async function handleControlAPI(req, res, pathname, fixtures, journal, videoStat
|
|
|
297
544
|
res.end(JSON.stringify({ error: `Invalid JSON: ${msg}` }));
|
|
298
545
|
return true;
|
|
299
546
|
}
|
|
547
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
548
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
549
|
+
res.end(JSON.stringify({ error: "Invalid body: expected a JSON object" }));
|
|
550
|
+
return true;
|
|
551
|
+
}
|
|
300
552
|
const status = parsed.status ?? 500;
|
|
553
|
+
if (!isInjectableStatus(status)) {
|
|
554
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
555
|
+
res.end(JSON.stringify({ error: `Invalid 'status': must be ${INJECTED_STATUS_RANGE}` }));
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
301
558
|
const errorBody = parsed.body;
|
|
559
|
+
if (errorBody !== void 0 && errorBody !== null) {
|
|
560
|
+
if (typeof errorBody !== "object" || Array.isArray(errorBody)) {
|
|
561
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
562
|
+
res.end(JSON.stringify({ error: "Invalid 'body': must be an object" }));
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
for (const field of [
|
|
566
|
+
"message",
|
|
567
|
+
"type",
|
|
568
|
+
"code"
|
|
569
|
+
]) {
|
|
570
|
+
const value = errorBody[field];
|
|
571
|
+
if (value !== void 0 && value !== null && typeof value !== "string") {
|
|
572
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
573
|
+
res.end(JSON.stringify({ error: `Invalid 'body.${field}': must be a string` }));
|
|
574
|
+
return true;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
302
578
|
const errorFixture = {
|
|
303
579
|
match: { predicate: () => true },
|
|
304
580
|
response: {
|
|
@@ -358,7 +634,6 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
358
634
|
let body;
|
|
359
635
|
try {
|
|
360
636
|
body = JSON.parse(raw);
|
|
361
|
-
if (modelFallback && !body.model) body.model = modelFallback;
|
|
362
637
|
} catch (parseErr) {
|
|
363
638
|
const detail = parseErr instanceof Error ? parseErr.message : "unknown parse error";
|
|
364
639
|
journal.add({
|
|
@@ -379,6 +654,24 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
379
654
|
} }));
|
|
380
655
|
return;
|
|
381
656
|
}
|
|
657
|
+
if (!isJsonObject(body)) {
|
|
658
|
+
journal.add({
|
|
659
|
+
method: req.method ?? "POST",
|
|
660
|
+
path: req.url ?? COMPLETIONS_PATH,
|
|
661
|
+
headers: flattenHeaders(req.headers),
|
|
662
|
+
body: null,
|
|
663
|
+
response: {
|
|
664
|
+
status: 400,
|
|
665
|
+
fixture: null
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, "Request body must be a JSON object") : JSON.stringify({ error: {
|
|
669
|
+
message: "Request body must be a JSON object",
|
|
670
|
+
type: "invalid_request_error"
|
|
671
|
+
} }));
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (modelFallback && !body.model) body.model = modelFallback;
|
|
382
675
|
if (!Array.isArray(body.messages)) {
|
|
383
676
|
journal.add({
|
|
384
677
|
method: req.method ?? "POST",
|
|
@@ -470,7 +763,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
470
763
|
const snippet = typeof lastUserMsg?.content === "string" ? lastUserMsg.content.slice(0, 80) : "";
|
|
471
764
|
defaults.logger.debug(`No fixture matched for request (model=${body.model ?? "?"}, msg="${snippet}")`);
|
|
472
765
|
}
|
|
473
|
-
const chaosAction = evaluateChaos(fixture, defaults.chaos, req.headers, defaults.logger);
|
|
766
|
+
const chaosAction = evaluateChaos(fixture, defaults.chaos, req.headers, defaults.logger, req.url);
|
|
474
767
|
const chaosContext = {
|
|
475
768
|
method,
|
|
476
769
|
path,
|
|
@@ -767,13 +1060,27 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
767
1060
|
const logger = new Logger(options?.logLevel ?? "silent");
|
|
768
1061
|
const registry = options?.metrics ? createMetricsRegistry() : void 0;
|
|
769
1062
|
const serverOptions = options ?? {};
|
|
1063
|
+
const chaosByTestId = /* @__PURE__ */ new Map();
|
|
770
1064
|
const defaults = {
|
|
771
1065
|
latency: serverOptions.latency ?? 0,
|
|
772
1066
|
chunkSize: Math.max(1, serverOptions.chunkSize ?? DEFAULT_CHUNK_SIZE),
|
|
773
1067
|
replaySpeed: serverOptions.replaySpeed ?? 1,
|
|
774
1068
|
logger,
|
|
1069
|
+
chaosByTestId,
|
|
775
1070
|
get chaos() {
|
|
776
|
-
return
|
|
1071
|
+
return {
|
|
1072
|
+
base: chaosByTestId.get(DEFAULT_TEST_ID) ?? serverOptions.chaos,
|
|
1073
|
+
byTestId: chaosByTestId
|
|
1074
|
+
};
|
|
1075
|
+
},
|
|
1076
|
+
set chaos(value) {
|
|
1077
|
+
if (value === void 0) {
|
|
1078
|
+
chaosByTestId.clear();
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
const base = isChaosScope(value) ? value.base : value;
|
|
1082
|
+
if (base === void 0) chaosByTestId.delete(DEFAULT_TEST_ID);
|
|
1083
|
+
else chaosByTestId.set(DEFAULT_TEST_ID, base);
|
|
777
1084
|
},
|
|
778
1085
|
registry,
|
|
779
1086
|
get record() {
|
|
@@ -796,6 +1103,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
796
1103
|
},
|
|
797
1104
|
get grokVideo() {
|
|
798
1105
|
return serverOptions.grokVideo;
|
|
1106
|
+
},
|
|
1107
|
+
get bytePlusVideo() {
|
|
1108
|
+
return serverOptions.bytePlusVideo;
|
|
799
1109
|
}
|
|
800
1110
|
};
|
|
801
1111
|
if (options?.chaos) {
|
|
@@ -831,6 +1141,10 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
831
1141
|
{
|
|
832
1142
|
name: "grokVideo",
|
|
833
1143
|
config: options?.grokVideo
|
|
1144
|
+
},
|
|
1145
|
+
{
|
|
1146
|
+
name: "bytePlusVideo",
|
|
1147
|
+
config: options?.bytePlusVideo
|
|
834
1148
|
}
|
|
835
1149
|
]) {
|
|
836
1150
|
if (!config) continue;
|
|
@@ -845,6 +1159,16 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
845
1159
|
const cap = options?.record?.openRouterVideo?.maxContentBytes;
|
|
846
1160
|
if (cap !== void 0 && (!Number.isInteger(cap) || cap < 0)) logger.warn(`record.openRouterVideo.maxContentBytes (${cap}) is not a non-negative integer — using the default cap (${OPENROUTER_VIDEO_DEFAULT_MAX_CONTENT_BYTES})`);
|
|
847
1161
|
}
|
|
1162
|
+
{
|
|
1163
|
+
const bytePlusBase = options?.record?.providers?.byteplus;
|
|
1164
|
+
if (bytePlusBase !== void 0) {
|
|
1165
|
+
let basePath;
|
|
1166
|
+
try {
|
|
1167
|
+
basePath = new URL(bytePlusBase).pathname;
|
|
1168
|
+
} catch {}
|
|
1169
|
+
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
|
+
}
|
|
1171
|
+
}
|
|
848
1172
|
const journal = new Journal({
|
|
849
1173
|
maxEntries: options?.journalMaxEntries ?? 1e3,
|
|
850
1174
|
fixtureCountsMaxTestIds: options?.fixtureCountsMaxTestIds ?? 500
|
|
@@ -853,6 +1177,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
853
1177
|
const openRouterVideoJobs = new OpenRouterVideoJobMap();
|
|
854
1178
|
const veoVideoJobs = new VeoVideoJobMap();
|
|
855
1179
|
const grokVideoJobs = new GrokVideoJobMap();
|
|
1180
|
+
const bytePlusVideoJobs = new BytePlusVideoJobMap();
|
|
856
1181
|
if (mounts) for (const { handler } of mounts) {
|
|
857
1182
|
if (handler.setJournal) handler.setJournal(journal);
|
|
858
1183
|
if (registry && handler.setRegistry) handler.setRegistry(registry);
|
|
@@ -878,7 +1203,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
878
1203
|
const startTime = registry ? process.hrtime.bigint() : 0n;
|
|
879
1204
|
const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
880
1205
|
let pathname = parsedUrl.pathname;
|
|
881
|
-
const
|
|
1206
|
+
const originalPathname = pathname;
|
|
1207
|
+
const isOpenRouter = isOpenRouterPath(originalPathname);
|
|
1208
|
+
const isBytePlusArk = defaults.record?.providers.byteplus !== void 0 && originalPathname.startsWith("/api/v3/");
|
|
882
1209
|
if (registry) res.on("finish", () => {
|
|
883
1210
|
try {
|
|
884
1211
|
const normalizedPath = normalizePathLabel(pathname);
|
|
@@ -910,7 +1237,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
910
1237
|
markAuthenticatedRequest(req, resolvedAuth.policy);
|
|
911
1238
|
}
|
|
912
1239
|
if (pathname.startsWith(CONTROL_PREFIX)) {
|
|
913
|
-
await handleControlAPI(req, res, pathname, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, defaults);
|
|
1240
|
+
await handleControlAPI(req, res, pathname, parsedUrl.searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults);
|
|
914
1241
|
return;
|
|
915
1242
|
}
|
|
916
1243
|
if (mounts) {
|
|
@@ -1093,6 +1420,66 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1093
1420
|
}
|
|
1094
1421
|
return;
|
|
1095
1422
|
}
|
|
1423
|
+
const bytePlusVideoStatusMatch = pathname.match(BYTEPLUS_VIDEO_STATUS_RE);
|
|
1424
|
+
if (bytePlusVideoStatusMatch && req.method === "GET") {
|
|
1425
|
+
try {
|
|
1426
|
+
await handleBytePlusVideoStatus(req, res, bytePlusVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
|
|
1427
|
+
} catch (err) {
|
|
1428
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
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();
|
|
1450
|
+
}
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
if (BYTEPLUS_VIDEO_SUBMIT_RE.test(pathname) && req.method === "POST") {
|
|
1454
|
+
setCorsHeaders(res);
|
|
1455
|
+
try {
|
|
1456
|
+
await handleBytePlusVideoCreate(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
|
|
1457
|
+
} catch (err) {
|
|
1458
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
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();
|
|
1480
|
+
}
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1096
1483
|
if (pathname === "/api/v1/models" && req.method === "GET") {
|
|
1097
1484
|
handleOpenRouterModels(req, res, fixtures, journal, defaults, setCorsHeaders);
|
|
1098
1485
|
return;
|
|
@@ -1291,7 +1678,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1291
1678
|
}
|
|
1292
1679
|
if (pathname === IMAGES_PATH && req.method === "POST") {
|
|
1293
1680
|
try {
|
|
1294
|
-
await handleImages(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
1681
|
+
await handleImages(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, "openai", void 0, isBytePlusArk ? "byteplus" : void 0);
|
|
1295
1682
|
} catch (err) {
|
|
1296
1683
|
const msg = err instanceof Error ? err.message : "Internal error";
|
|
1297
1684
|
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
@@ -1742,7 +2129,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1742
2129
|
try {
|
|
1743
2130
|
falBody = req.method === "POST" || req.method === "PUT" ? await readBody(req) : "";
|
|
1744
2131
|
const raw = falBody;
|
|
1745
|
-
const chaosAction = evaluateChaos(null, defaults.chaos, req.headers, defaults.logger);
|
|
2132
|
+
const chaosAction = evaluateChaos(null, defaults.chaos, req.headers, defaults.logger, req.url);
|
|
1746
2133
|
if (chaosAction) {
|
|
1747
2134
|
applyChaosAction(chaosAction, res, null, journal, {
|
|
1748
2135
|
method: req.method ?? "GET",
|
|
@@ -1784,7 +2171,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1784
2171
|
setCorsHeaders(res);
|
|
1785
2172
|
try {
|
|
1786
2173
|
const raw = req.method === "POST" || req.method === "PUT" ? falBody ?? await readBody(req) : "{}";
|
|
1787
|
-
const chaosAction = evaluateChaos(null, defaults.chaos, req.headers, defaults.logger);
|
|
2174
|
+
const chaosAction = evaluateChaos(null, defaults.chaos, req.headers, defaults.logger, req.url);
|
|
1788
2175
|
if (chaosAction) {
|
|
1789
2176
|
applyChaosAction(chaosAction, res, null, journal, {
|
|
1790
2177
|
method: req.method ?? "GET",
|
|
@@ -1830,7 +2217,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1830
2217
|
handleNotFound(res, "Not found");
|
|
1831
2218
|
return;
|
|
1832
2219
|
}
|
|
1833
|
-
const completionsProvider = azureDeploymentId ? "azure" : isOpenRouter ? "openrouter" : "openai";
|
|
2220
|
+
const completionsProvider = azureDeploymentId ? "azure" : isOpenRouter ? "openrouter" : isBytePlusArk ? "byteplus" : "openai";
|
|
1834
2221
|
try {
|
|
1835
2222
|
await handleCompletions(req, res, fixtures, journal, defaults, azureDeploymentId, completionsProvider, isOpenRouter);
|
|
1836
2223
|
} catch (err) {
|
|
@@ -1968,6 +2355,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1968
2355
|
openRouterVideoJobs.clear();
|
|
1969
2356
|
veoVideoJobs.clear();
|
|
1970
2357
|
grokVideoJobs.clear();
|
|
2358
|
+
bytePlusVideoJobs.clear();
|
|
1971
2359
|
originalClose(callback);
|
|
1972
2360
|
return this;
|
|
1973
2361
|
};
|
|
@@ -1991,7 +2379,8 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1991
2379
|
videoStates,
|
|
1992
2380
|
openRouterVideoJobs,
|
|
1993
2381
|
veoVideoJobs,
|
|
1994
|
-
grokVideoJobs
|
|
2382
|
+
grokVideoJobs,
|
|
2383
|
+
bytePlusVideoJobs
|
|
1995
2384
|
});
|
|
1996
2385
|
});
|
|
1997
2386
|
});
|