@copilotkit/aimock 1.43.0 → 1.45.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 +46 -0
- package/README.md +14 -2
- package/dist/agui-handler.d.ts.map +1 -1
- package/dist/aimock-cli.cjs +31 -5
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.d.cts.map +1 -1
- package/dist/aimock-cli.d.ts.map +1 -1
- package/dist/aimock-cli.js +31 -5
- package/dist/aimock-cli.js.map +1 -1
- package/dist/bedrock-converse.cjs +344 -43
- 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 +345 -44
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +195 -42
- 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 +196 -43
- package/dist/bedrock.js.map +1 -1
- package/dist/chaos.cjs +9 -5
- package/dist/chaos.cjs.map +1 -1
- package/dist/chaos.d.cts.map +1 -1
- package/dist/chaos.d.ts.map +1 -1
- package/dist/chaos.js +10 -7
- package/dist/chaos.js.map +1 -1
- package/dist/cli.cjs +238 -9
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +242 -13
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +159 -72
- 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 +160 -73
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.cjs +63 -5
- package/dist/config-loader.cjs.map +1 -1
- package/dist/config-loader.d.cts +37 -4
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts +37 -4
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +63 -5
- package/dist/config-loader.js.map +1 -1
- package/dist/constants.cjs +8 -0
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +6 -2
- package/dist/constants.d.ts +6 -2
- package/dist/constants.js +8 -1
- package/dist/constants.js.map +1 -1
- package/dist/echo-text.cjs +65 -0
- package/dist/echo-text.cjs.map +1 -0
- package/dist/echo-text.d.cts +22 -0
- package/dist/echo-text.d.cts.map +1 -0
- package/dist/echo-text.d.ts +22 -0
- package/dist/echo-text.d.ts.map +1 -0
- package/dist/echo-text.js +65 -0
- package/dist/echo-text.js.map +1 -0
- package/dist/embeddings.cjs +18 -1
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.d.cts +1 -1
- package/dist/embeddings.d.cts.map +1 -1
- package/dist/embeddings.d.ts +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +18 -1
- package/dist/embeddings.js.map +1 -1
- package/dist/fakes-plugin.cjs +131 -0
- package/dist/fakes-plugin.cjs.map +1 -0
- package/dist/fakes-plugin.d.cts +23 -0
- package/dist/fakes-plugin.d.cts.map +1 -0
- package/dist/fakes-plugin.d.ts +23 -0
- package/dist/fakes-plugin.d.ts.map +1 -0
- package/dist/fakes-plugin.js +127 -0
- package/dist/fakes-plugin.js.map +1 -0
- package/dist/files.cjs +9 -11
- package/dist/files.cjs.map +1 -1
- package/dist/files.d.cts.map +1 -1
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +10 -13
- package/dist/files.js.map +1 -1
- package/dist/fine-tuning.cjs +1 -19
- package/dist/fine-tuning.cjs.map +1 -1
- package/dist/fine-tuning.d.cts.map +1 -1
- package/dist/fine-tuning.d.ts.map +1 -1
- package/dist/fine-tuning.js +2 -20
- package/dist/fine-tuning.js.map +1 -1
- package/dist/fixture-loader-services.cjs +116 -0
- package/dist/fixture-loader-services.cjs.map +1 -0
- package/dist/fixture-loader-services.d.cts +48 -0
- package/dist/fixture-loader-services.d.cts.map +1 -0
- package/dist/fixture-loader-services.d.ts +48 -0
- package/dist/fixture-loader-services.d.ts.map +1 -0
- package/dist/fixture-loader-services.js +114 -0
- package/dist/fixture-loader-services.js.map +1 -0
- package/dist/fixture-loader.cjs +552 -29
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +140 -2
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +140 -2
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +542 -31
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-embeddings.cjs +42 -2
- package/dist/gemini-embeddings.cjs.map +1 -1
- package/dist/gemini-embeddings.js +43 -3
- package/dist/gemini-embeddings.js.map +1 -1
- package/dist/gemini-interactions.cjs +288 -52
- 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 +289 -53
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +234 -38
- 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 +235 -39
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +1157 -12
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +85 -4
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +85 -4
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +1113 -14
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +3 -2
- package/dist/images.cjs.map +1 -1
- package/dist/images.js +3 -2
- package/dist/images.js.map +1 -1
- package/dist/index.cjs +39 -1
- package/dist/index.d.cts +11 -4
- package/dist/index.d.ts +11 -4
- package/dist/index.js +11 -4
- package/dist/jest.cjs +43 -14
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +47 -5
- package/dist/jest.d.cts.map +1 -1
- package/dist/jest.d.ts +47 -5
- package/dist/jest.d.ts.map +1 -1
- package/dist/jest.js +45 -16
- package/dist/jest.js.map +1 -1
- package/dist/journal.cjs +155 -13
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts +54 -4
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts +54 -4
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +155 -13
- package/dist/journal.js.map +1 -1
- package/dist/live-sanitize.cjs +4 -32
- package/dist/live-sanitize.cjs.map +1 -1
- package/dist/live-sanitize.js +4 -32
- package/dist/live-sanitize.js.map +1 -1
- package/dist/llmock.cjs +204 -39
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +51 -1
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +51 -1
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +206 -41
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-fakes-mount.cjs +217 -0
- package/dist/mcp-fakes-mount.cjs.map +1 -0
- package/dist/mcp-fakes-mount.js +211 -0
- package/dist/mcp-fakes-mount.js.map +1 -0
- package/dist/mcp-fakes-report.cjs +111 -0
- package/dist/mcp-fakes-report.cjs.map +1 -0
- package/dist/mcp-fakes-report.d.cts +40 -0
- package/dist/mcp-fakes-report.d.cts.map +1 -0
- package/dist/mcp-fakes-report.d.ts +40 -0
- package/dist/mcp-fakes-report.d.ts.map +1 -0
- package/dist/mcp-fakes-report.js +107 -0
- package/dist/mcp-fakes-report.js.map +1 -0
- package/dist/mcp-fakes.cjs +1942 -0
- package/dist/mcp-fakes.cjs.map +1 -0
- package/dist/mcp-fakes.d.cts +504 -0
- package/dist/mcp-fakes.d.cts.map +1 -0
- package/dist/mcp-fakes.d.ts +504 -0
- package/dist/mcp-fakes.d.ts.map +1 -0
- package/dist/mcp-fakes.js +1937 -0
- package/dist/mcp-fakes.js.map +1 -0
- package/dist/mcp-handler.cjs +534 -27
- package/dist/mcp-handler.cjs.map +1 -1
- package/dist/mcp-handler.d.cts +49 -0
- package/dist/mcp-handler.d.cts.map +1 -0
- package/dist/mcp-handler.d.ts +49 -0
- package/dist/mcp-handler.d.ts.map +1 -0
- package/dist/mcp-handler.js +530 -28
- package/dist/mcp-handler.js.map +1 -1
- package/dist/mcp-mock.cjs +326 -47
- package/dist/mcp-mock.cjs.map +1 -1
- package/dist/mcp-mock.d.cts +97 -1
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts +97 -1
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +327 -48
- package/dist/mcp-mock.js.map +1 -1
- package/dist/mcp-recorder.cjs +1224 -0
- package/dist/mcp-recorder.cjs.map +1 -0
- package/dist/mcp-recorder.d.cts +108 -0
- package/dist/mcp-recorder.d.cts.map +1 -0
- package/dist/mcp-recorder.d.ts +108 -0
- package/dist/mcp-recorder.d.ts.map +1 -0
- package/dist/mcp-recorder.js +1215 -0
- package/dist/mcp-recorder.js.map +1 -0
- package/dist/mcp-stub.cjs +5 -1
- package/dist/mcp-stub.d.cts +3 -1
- package/dist/mcp-stub.d.ts +3 -1
- package/dist/mcp-stub.js +3 -1
- package/dist/mcp-types.d.cts +6 -0
- package/dist/mcp-types.d.ts +6 -0
- package/dist/message-text.cjs +192 -0
- package/dist/message-text.cjs.map +1 -0
- package/dist/message-text.d.cts +31 -0
- package/dist/message-text.d.cts.map +1 -0
- package/dist/message-text.d.ts +31 -0
- package/dist/message-text.d.ts.map +1 -0
- package/dist/message-text.js +184 -0
- package/dist/message-text.js.map +1 -0
- package/dist/messages.cjs +400 -56
- 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 +401 -58
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +46 -66
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts +14 -6
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts +14 -6
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +32 -40
- package/dist/metrics.js.map +1 -1
- package/dist/misbehavior.cjs +1523 -0
- package/dist/misbehavior.cjs.map +1 -0
- package/dist/misbehavior.d.cts +43 -0
- package/dist/misbehavior.d.cts.map +1 -0
- package/dist/misbehavior.d.ts +43 -0
- package/dist/misbehavior.d.ts.map +1 -0
- package/dist/misbehavior.js +1511 -0
- package/dist/misbehavior.js.map +1 -0
- package/dist/ollama.cjs +315 -113
- 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 +316 -114
- package/dist/ollama.js.map +1 -1
- package/dist/openapi.cjs +3405 -0
- package/dist/openapi.cjs.map +1 -0
- package/dist/openapi.d.cts +30 -0
- package/dist/openapi.d.cts.map +1 -0
- package/dist/openapi.d.ts +30 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +3404 -0
- package/dist/openapi.js.map +1 -0
- package/dist/openrouter-video.cjs +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/record-sanitize.cjs +231 -0
- package/dist/record-sanitize.cjs.map +1 -0
- package/dist/record-sanitize.js +224 -0
- package/dist/record-sanitize.js.map +1 -0
- package/dist/recorder.cjs +115 -6
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts +2 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts +2 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +116 -8
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +701 -166
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts +5 -13
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts +5 -13
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +699 -169
- package/dist/responses.js.map +1 -1
- package/dist/route-registry.cjs +951 -0
- package/dist/route-registry.cjs.map +1 -0
- package/dist/route-registry.d.cts +33 -0
- package/dist/route-registry.d.cts.map +1 -0
- package/dist/route-registry.d.ts +33 -0
- package/dist/route-registry.d.ts.map +1 -0
- package/dist/route-registry.js +866 -0
- package/dist/route-registry.js.map +1 -0
- package/dist/router.cjs +24 -2
- 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 +25 -4
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +1159 -264
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +4 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +4 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1067 -172
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +4 -2
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.js +4 -2
- package/dist/speech.js.map +1 -1
- package/dist/stream-collapse.cjs +236 -34
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +39 -4
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +39 -4
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +235 -35
- package/dist/stream-collapse.js.map +1 -1
- package/dist/suite.cjs +1 -1
- package/dist/suite.js +1 -1
- package/dist/transcription.cjs +2 -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 +2 -2
- package/dist/transcription.js.map +1 -1
- package/dist/types.cjs +18 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +516 -5
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +516 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/dist/validate-cli.cjs +123 -33
- package/dist/validate-cli.cjs.map +1 -1
- package/dist/validate-cli.js +125 -35
- package/dist/validate-cli.js.map +1 -1
- package/dist/vector-stores.cjs +1180 -0
- package/dist/vector-stores.cjs.map +1 -0
- package/dist/vector-stores.d.cts +106 -0
- package/dist/vector-stores.d.cts.map +1 -0
- package/dist/vector-stores.d.ts +106 -0
- package/dist/vector-stores.d.ts.map +1 -0
- package/dist/vector-stores.js +1164 -0
- package/dist/vector-stores.js.map +1 -0
- package/dist/vector-types.d.cts.map +1 -1
- package/dist/version.cjs +45 -0
- package/dist/version.cjs.map +1 -0
- package/dist/version.js +44 -0
- package/dist/version.js.map +1 -0
- package/dist/video.cjs +19 -0
- 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 +19 -0
- package/dist/video.js.map +1 -1
- package/dist/vitest.cjs +77 -16
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +38 -5
- package/dist/vitest.d.cts.map +1 -1
- package/dist/vitest.d.ts +38 -5
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +78 -17
- package/dist/vitest.js.map +1 -1
- package/dist/watcher.cjs +6 -2
- package/dist/watcher.cjs.map +1 -1
- package/dist/watcher.js +6 -2
- package/dist/watcher.js.map +1 -1
- package/dist/ws-gemini-live.cjs +210 -101
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.d.cts +9 -3
- package/dist/ws-gemini-live.d.cts.map +1 -1
- package/dist/ws-gemini-live.d.ts +9 -3
- package/dist/ws-gemini-live.d.ts.map +1 -1
- package/dist/ws-gemini-live.js +211 -102
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +175 -62
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts +8 -3
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts +8 -3
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +176 -63
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +121 -16
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.d.cts +8 -12
- package/dist/ws-responses.d.cts.map +1 -1
- package/dist/ws-responses.d.ts +8 -12
- package/dist/ws-responses.d.ts.map +1 -1
- package/dist/ws-responses.js +123 -18
- package/dist/ws-responses.js.map +1 -1
- package/package.json +13 -2
- package/skills/write-fixtures/SKILL.md +179 -21
package/dist/server.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { DEFAULT_TEST_ID } from "./constants.js";
|
|
2
|
+
import { build, msg, quote } from "./message-text.js";
|
|
2
3
|
import { markAuthenticatedRequest, resolveInboundAuth, validateRequestApiKey, writeApiKeyHttpRejection, writeApiKeyUpgradeRejection } from "./api-key-auth.js";
|
|
3
|
-
import { RequestBodyTooLargeError, buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, describeMatch, extractOverrides, flattenHeaders, getContext, getTestId, isAudioResponse,
|
|
4
|
+
import { RequestBodyTooLargeError, buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildOpenAIContentFilterChunks, buildOpenAIContentFilterCompletion, buildOpenAIReasoningChunks, buildOpenAIReasoningCompletion, buildOpenAIRefusalChunks, buildOpenAIRefusalCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, createServerFixtureList, describeMatch, extractOverrides, fixtureToolCallErrorCode, flattenHeaders, getContext, getTestId, googleFixtureToolCallErrorDetails, isAudioResponse, isCombinedFixtureResponse, isEmbeddingResponse, isErrorResponse, isExtendedResponsesToolsList, isFixtureToolCallError, isImageResponse, isJSONResponse, isJsonObject, isResponseFactory, isServerOwnedFixtures, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, markMintedRequestId, prepareOpenAIChatMisbehavior, readBody, readBodyBufferBounded, requireEmittedFunctionToolCalls, requireServedFunctionToolCalls, resolveFixtureBlockOutcome, resolveOpenAIChatMisbehaviorUsage, resolveReasoningForModel, resolveRequestId, resolveResponse, resolveStrictMode, resolveTestId, resolveUsage, runWithToolArgumentsScope, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, validateToolsField, withoutResponsesToolKeys, wouldProxyMiss } from "./helpers.js";
|
|
4
5
|
import { normalizeLiveOptions } from "./live-fixture.js";
|
|
5
6
|
import { writeErrorResponse, writeSSEStream } from "./sse-writer.js";
|
|
6
7
|
import { CHAOS_FIELDS, CHAOS_FIELD_NAMES, applyChaosAction, awaitChaosLatency, describeUnwritableReason, evaluateChaos, isChaosScope, parseChaosField, resetChaosWarnings, resolveChaosConfig, responseGoneReason } from "./chaos.js";
|
|
7
|
-
import {
|
|
8
|
+
import { MISBEHAVIOR_CATALOG, WIRE_SUPPORT, getFixtureMisbehaviorPosition, parseMisbehavior, parseMisbehaviorHeader, planMisbehavior, recordMisbehaviorOutcome, resolveMisbehaviorSeed, resolveMisbehaviorShortCircuit, setFixtureMisbehaviorPosition } from "./misbehavior.js";
|
|
9
|
+
import { FixtureLoadError, INJECTED_STATUS_RANGE, MisbehaviorConfigError, claimOneShotError, clearFixtureQueue, enableHeldFixtureMisbehavior, entryToFixture, isInjectableStatus, isOneShotError, markFixtureResponsesToolsExtended, queueOneShotError, releaseOneShotError, responsesToolsExtendedView, responsesToolsViewSource, validateFixtures } from "./fixture-loader.js";
|
|
8
10
|
import { matchFixtureDiagnostic, recordMatchOptions } from "./router.js";
|
|
9
11
|
import { proxyAndRecord } from "./recorder.js";
|
|
10
12
|
import { Logger } from "./logger.js";
|
|
@@ -37,6 +39,7 @@ import { handleSearch } from "./search.js";
|
|
|
37
39
|
import { handleRerank } from "./rerank.js";
|
|
38
40
|
import { handleModeration } from "./moderation.js";
|
|
39
41
|
import { clearFineTuningStore, handleFineTuningCancel, handleFineTuningCreate, handleFineTuningEvents, handleFineTuningList, handleFineTuningRetrieve } from "./fine-tuning.js";
|
|
42
|
+
import { clearVectorStoreStore, handleVectorFileBatchesCancel, handleVectorFileBatchesCreate, handleVectorFileBatchesFiles, handleVectorFileBatchesRetrieve, handleVectorStoreFilesCreate, handleVectorStoreFilesDelete, handleVectorStoreFilesList, handleVectorStoreFilesRetrieve, handleVectorStoresCreate, handleVectorStoresDelete, handleVectorStoresList, handleVectorStoresModify, handleVectorStoresRetrieve, handleVectorStoresSearch } from "./vector-stores.js";
|
|
40
43
|
import { FILES_BODY_DRAIN_MAX_BYTES, FILES_BODY_MAX_BYTES, FILES_BODY_OVERSIZED, clearFileStore, handleFilesContent, handleFilesCreate, handleFilesDelete, handleFilesList, handleFilesRetrieve } from "./files.js";
|
|
41
44
|
import { clearBatchStore, handleBatchesCancel, handleBatchesCreate, handleBatchesList, handleBatchesRetrieve } from "./batches.js";
|
|
42
45
|
import { upgradeToWebSocket } from "./ws-framing.js";
|
|
@@ -44,41 +47,18 @@ import { handleLiveSession } from "./ws-live.js";
|
|
|
44
47
|
import { handleWebSocketResponses } from "./ws-responses.js";
|
|
45
48
|
import { handleWebSocketRealtime } from "./ws-realtime.js";
|
|
46
49
|
import { handleWebSocketGeminiLive } from "./ws-gemini-live.js";
|
|
47
|
-
import { BYTEPLUS_VIDEO_STATUS_RE, BYTEPLUS_VIDEO_SUBMIT_RE,
|
|
50
|
+
import { AZURE_DEPLOYMENT_RE, BATCHES_CANCEL_RE, BATCHES_ID_RE, BATCHES_PATH, BEDROCK_CONVERSE_RE, BEDROCK_CONVERSE_STREAM_RE, BEDROCK_INVOKE_RE, BEDROCK_STREAM_RE, BYTEPLUS_VIDEO_STATUS_RE, BYTEPLUS_VIDEO_SUBMIT_RE, COHERE_CHAT_PATH, COHERE_EMBED_PATH, COMPLETIONS_PATH, CONTROL_PREFIX, ELEVENLABS_MUSIC_RE, ELEVENLABS_SOUND_GENERATION_PATH, ELEVENLABS_TTS_RE, ELEVENLABS_VOICE_CREATE_PATH, ELEVENLABS_VOICE_DESIGN_PATH, ELEVENLABS_VOICE_RE, EMBEDDINGS_PATH, FAL_QUEUE_REQUESTS_RE, FAL_QUEUE_SUBMIT_RE, FAL_ROUTE_RE, FAL_RUN_RE, FILES_CONTENT_RE, FILES_ID_RE, FILES_PATH, FINE_TUNING_CANCEL_RE, FINE_TUNING_EVENTS_RE, FINE_TUNING_ID_RE, FINE_TUNING_JOBS_PATH, GEMINI_EMBED_RE, GEMINI_INTERACTIONS_PATH, GEMINI_LIVE_PATH, GEMINI_PATH_RE, GEMINI_PREDICT_RE, GROK_VIDEO_STATUS_RE, GROK_VIDEO_SUBMIT_PATH, HEALTH_PATH, IMAGES_EDIT_PATH, IMAGES_PATH, IMAGES_VARIATIONS_PATH, LIVE_PATH, MESSAGES_PATH, METRICS_PATH, MODELS_PATH, MODERATIONS_PATH, OLLAMA_CHAT_PATH, OLLAMA_EMBEDDINGS_PATH, OLLAMA_EMBED_PATH, OLLAMA_GENERATE_PATH, OLLAMA_TAGS_PATH, OPENROUTER_CREDITS_PATH, OPENROUTER_KEY_PATH, OPENROUTER_MODELS_PATH, OPENROUTER_VIDEOS_PATH, OPENROUTER_VIDEO_CONTENT_RE, OPENROUTER_VIDEO_MODELS_PATH, OPENROUTER_VIDEO_STATUS_RE, READY_PATH, REALTIME_PATH, REQUESTS_PATH, RERANK_PATH, RESPONSES_PATH, SEARCH_PATH, SPEECH_PATH, TRANSCRIPTIONS_PATH, TRANSLATIONS_PATH, VECTOR_STORES_BATCH_CANCEL_RE, VECTOR_STORES_BATCH_FILES_RE, VECTOR_STORES_BATCH_RE, VECTOR_STORES_FILES_RE, VECTOR_STORES_FILE_BATCHES_RE, VECTOR_STORES_FILE_RE, VECTOR_STORES_ID_RE, VECTOR_STORES_PATH, VECTOR_STORES_SEARCH_RE, VEO_OPERATION_RE, VEO_PREDICT_LRO_RE, VERTEX_AI_RE, VIDEOS_PATH, matchRouteDefinition } from "./route-registry.js";
|
|
51
|
+
import { CATALOG_ROUTES, buildOpenApiDocument } from "./openapi.js";
|
|
52
|
+
import { DESTROYED_STATUS_LABEL, createMetricsRegistry, normalizePathLabel } from "./metrics.js";
|
|
53
|
+
import { MCP_FAKES_DEFAULT_MOUNT, McpFakeStore, McpFakesAddError, blockIdOf } from "./mcp-fakes.js";
|
|
54
|
+
import { MCPMock } from "./mcp-mock.js";
|
|
55
|
+
import { ensureFakeMount, findFakeMount, handOffMcpFakes, isFakeMount, mountConflictDetail } from "./mcp-fakes-mount.js";
|
|
56
|
+
import { handleFakesReportRoute } from "./mcp-fakes-report.js";
|
|
48
57
|
import * as http$1 from "node:http";
|
|
49
58
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
50
59
|
|
|
51
60
|
//#region src/server.ts
|
|
52
61
|
const liveClosers = /* @__PURE__ */ new WeakMap();
|
|
53
|
-
const COMPLETIONS_PATH = "/v1/chat/completions";
|
|
54
|
-
const RESPONSES_PATH = "/v1/responses";
|
|
55
|
-
const LIVE_PATH = "/v1/live/sessions";
|
|
56
|
-
const REALTIME_PATH = "/v1/realtime";
|
|
57
|
-
const GEMINI_LIVE_PATH = "/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent";
|
|
58
|
-
const MESSAGES_PATH = "/v1/messages";
|
|
59
|
-
const EMBEDDINGS_PATH = "/v1/embeddings";
|
|
60
|
-
const COHERE_CHAT_PATH = "/v2/chat";
|
|
61
|
-
const COHERE_EMBED_PATH = "/v2/embed";
|
|
62
|
-
const SEARCH_PATH = "/search";
|
|
63
|
-
const RERANK_PATH = "/v2/rerank";
|
|
64
|
-
const MODERATIONS_PATH = "/v1/moderations";
|
|
65
|
-
const IMAGES_PATH = "/v1/images/generations";
|
|
66
|
-
const IMAGES_EDIT_PATH = "/v1/images/edits";
|
|
67
|
-
const IMAGES_VARIATIONS_PATH = "/v1/images/variations";
|
|
68
|
-
const SPEECH_PATH = "/v1/audio/speech";
|
|
69
|
-
const TRANSCRIPTIONS_PATH = "/v1/audio/transcriptions";
|
|
70
|
-
const TRANSLATIONS_PATH = "/v1/audio/translations";
|
|
71
|
-
const VIDEOS_PATH = "/v1/videos";
|
|
72
|
-
const GEMINI_PREDICT_RE = /^\/v1beta\/models\/([^:]+):predict$/;
|
|
73
|
-
const ELEVENLABS_SOUND_GENERATION_PATH = "/v1/sound-generation";
|
|
74
|
-
const ELEVENLABS_TTS_RE = /^\/v1\/text-to-speech\/([^/]+)$/;
|
|
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\/([^/]+)$/;
|
|
79
|
-
const FAL_QUEUE_SUBMIT_RE = /^\/fal\/queue\/submit\/(.+)$/;
|
|
80
|
-
const FAL_QUEUE_REQUESTS_RE = /^\/fal\/queue\/requests\/(.+)$/;
|
|
81
|
-
const FAL_RUN_RE = /^\/fal\/run\/(.+)$/;
|
|
82
62
|
const DEFAULT_CHUNK_SIZE = 20;
|
|
83
63
|
const COMPAT_SUFFIXES = [
|
|
84
64
|
"/chat/completions",
|
|
@@ -107,34 +87,6 @@ function normalizeCompatPath(pathname, logger) {
|
|
|
107
87
|
}
|
|
108
88
|
return pathname;
|
|
109
89
|
}
|
|
110
|
-
const GEMINI_INTERACTIONS_PATH = "/v1beta/interactions";
|
|
111
|
-
const GEMINI_PATH_RE = /^\/v1beta\/models\/([^:]+):(generateContent|streamGenerateContent)$/;
|
|
112
|
-
const GEMINI_EMBED_RE = /^\/v1beta\/models\/([^:]+):embedContent$/;
|
|
113
|
-
const AZURE_DEPLOYMENT_RE = /^\/openai\/deployments\/([^/]+)\/(chat\/completions|embeddings)$/;
|
|
114
|
-
const BEDROCK_INVOKE_RE = /^\/model\/([^/]+)\/invoke$/;
|
|
115
|
-
const BEDROCK_STREAM_RE = /^\/model\/([^/]+)\/invoke-with-response-stream$/;
|
|
116
|
-
const BEDROCK_CONVERSE_RE = /^\/model\/([^/]+)\/converse$/;
|
|
117
|
-
const BEDROCK_CONVERSE_STREAM_RE = /^\/model\/([^/]+)\/converse-stream$/;
|
|
118
|
-
const VERTEX_AI_RE = /^\/v1\/projects\/[^/]+\/locations\/[^/]+\/publishers\/google\/models\/([^/:]+):(generateContent|streamGenerateContent)$/;
|
|
119
|
-
const OLLAMA_CHAT_PATH = "/api/chat";
|
|
120
|
-
const OLLAMA_GENERATE_PATH = "/api/generate";
|
|
121
|
-
const OLLAMA_EMBEDDINGS_PATH = "/api/embeddings";
|
|
122
|
-
const OLLAMA_EMBED_PATH = "/api/embed";
|
|
123
|
-
const OLLAMA_TAGS_PATH = "/api/tags";
|
|
124
|
-
const OPENROUTER_VIDEOS_PATH = "/api/v1/videos";
|
|
125
|
-
const OPENROUTER_VIDEO_MODELS_PATH = "/api/v1/videos/models";
|
|
126
|
-
const HEALTH_PATH = "/health";
|
|
127
|
-
const READY_PATH = "/ready";
|
|
128
|
-
const MODELS_PATH = "/v1/models";
|
|
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$/;
|
|
138
90
|
const DEFAULT_MODELS = [
|
|
139
91
|
"gpt-4",
|
|
140
92
|
"gpt-4o",
|
|
@@ -144,7 +96,7 @@ const DEFAULT_MODELS = [
|
|
|
144
96
|
];
|
|
145
97
|
const CORS_HEADERS = {
|
|
146
98
|
"Access-Control-Allow-Origin": "*",
|
|
147
|
-
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
|
99
|
+
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
148
100
|
"Access-Control-Allow-Headers": "*",
|
|
149
101
|
"Access-Control-Expose-Headers": "X-Total-Count, X-Request-Id"
|
|
150
102
|
};
|
|
@@ -163,7 +115,6 @@ function handleNotFound(res, message) {
|
|
|
163
115
|
type: "not_found"
|
|
164
116
|
} }));
|
|
165
117
|
}
|
|
166
|
-
const CONTROL_PREFIX = "/__aimock";
|
|
167
118
|
/** The complete `GET /__aimock/fixtures` query-param vocabulary; anything else 400s. */
|
|
168
119
|
const FIXTURES_PARAMS = new Set(["include"]);
|
|
169
120
|
/** The complete `GET /__aimock/journal` query-param vocabulary; anything else 400s. */
|
|
@@ -202,6 +153,7 @@ function performFullReset(fixtures, targets) {
|
|
|
202
153
|
clearElevenLabsVoices();
|
|
203
154
|
clearFileStore();
|
|
204
155
|
clearFineTuningStore();
|
|
156
|
+
clearVectorStoreStore();
|
|
205
157
|
resetInteractionCounter();
|
|
206
158
|
resetEventIdCounter();
|
|
207
159
|
if (!targets) return;
|
|
@@ -209,11 +161,13 @@ function performFullReset(fixtures, targets) {
|
|
|
209
161
|
targets.journal.clear();
|
|
210
162
|
resetChaosWarnings(targets.defaults.logger);
|
|
211
163
|
targets.defaults.chaos = void 0;
|
|
164
|
+
targets.defaults.misbehavior?.byTestId.clear();
|
|
212
165
|
targets.videoStates.clear();
|
|
213
166
|
targets.openRouterVideoJobs.clear();
|
|
214
167
|
targets.veoVideoJobs.clear();
|
|
215
168
|
targets.grokVideoJobs.clear();
|
|
216
169
|
targets.bytePlusVideoJobs.clear();
|
|
170
|
+
for (const { handler } of targets.mounts ?? []) handler.clearMcpFakes?.();
|
|
217
171
|
if (targets.defaults.registry) targets.defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
|
|
218
172
|
}
|
|
219
173
|
/**
|
|
@@ -253,7 +207,7 @@ function fixtureResponseKind(response) {
|
|
|
253
207
|
if (isImageResponse(response)) return "image";
|
|
254
208
|
if (isEmbeddingResponse(response)) return "embedding";
|
|
255
209
|
if (isJSONResponse(response)) return "json";
|
|
256
|
-
if (
|
|
210
|
+
if (isCombinedFixtureResponse(response)) return "contentWithToolCalls";
|
|
257
211
|
if (isToolCallResponse(response)) return "toolCalls";
|
|
258
212
|
if (isTextResponse(response)) return "text";
|
|
259
213
|
return "unknown";
|
|
@@ -268,9 +222,14 @@ function fixtureResponseKind(response) {
|
|
|
268
222
|
* scoping path does not also do. (Splitting on "," additionally broke every
|
|
269
223
|
* legitimately comma-bearing id.) A raw `path.includes("testId=t1")` both
|
|
270
224
|
* prefix-collides with `t10` and matches unrelated params like `notTestId`.
|
|
225
|
+
*
|
|
226
|
+
* An MCP mount records the test id it resolved on the entry (I9, B3): a
|
|
227
|
+
* decoded header or query value, or the one bound to the session at
|
|
228
|
+
* `initialize`. That value wins; LLM entries carry none and are attributed as
|
|
229
|
+
* before.
|
|
271
230
|
*/
|
|
272
231
|
function journalEntryTestId(entry) {
|
|
273
|
-
return resolveTestId(entry.headers, entry.path);
|
|
232
|
+
return entry.testId ?? resolveTestId(entry.headers, entry.path);
|
|
274
233
|
}
|
|
275
234
|
/**
|
|
276
235
|
* The testId a chaos override is stored under, and read back with — resolved by
|
|
@@ -309,19 +268,188 @@ function effectiveChaos(defaults, scopeId) {
|
|
|
309
268
|
if (!current) return {};
|
|
310
269
|
return isChaosScope(current) ? current.base ?? {} : current;
|
|
311
270
|
}
|
|
271
|
+
/** The `GET /__aimock/mcp/fakes` query-param vocabulary; anything else 400s. */
|
|
272
|
+
const MCP_FAKES_PARAMS = new Set([
|
|
273
|
+
"testId",
|
|
274
|
+
"context",
|
|
275
|
+
"mount"
|
|
276
|
+
]);
|
|
277
|
+
/** A block's `mount` path, read without running getters; the add validates it. */
|
|
278
|
+
function controlFakeMountOf(raw) {
|
|
279
|
+
if (typeof raw !== "object" || raw === null) return MCP_FAKES_DEFAULT_MOUNT;
|
|
280
|
+
const value = Object.getOwnPropertyDescriptor(raw, "mount")?.value;
|
|
281
|
+
return typeof value === "string" ? value : MCP_FAKES_DEFAULT_MOUNT;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Check one `POST /__aimock/fixtures` `mcpFakes` value against the live
|
|
285
|
+
* mounts (F10, W6) without changing anything. Returns every error in input
|
|
286
|
+
* order (an empty list when the value can be added), and `apply`, which adds
|
|
287
|
+
* the blocks (auto-mounting an MCPMock where no mount serves a block's path)
|
|
288
|
+
* and returns the number of blocks added.
|
|
289
|
+
*
|
|
290
|
+
* The whole value is checked before any mount is touched, and the check
|
|
291
|
+
* never adds to a real mount: each group is added to a throwaway store,
|
|
292
|
+
* whose errors are the ones reported (it numbers the add `control-api#1`, as
|
|
293
|
+
* a new mount would; for an existing MCPMock the rejected group is re-run on
|
|
294
|
+
* its atomic add, so the errors name that mount's next `<n>`, I7). A group
|
|
295
|
+
* whose path no request to it can reach (a mount that is not an MCPMock
|
|
296
|
+
* serves it, an MCPMock answers it as its root, or it is under the control
|
|
297
|
+
* prefix) is a mount conflict (one error per block).
|
|
298
|
+
*/
|
|
299
|
+
function planControlApiFakes(raw, mounts) {
|
|
300
|
+
const items = Array.isArray(raw) ? raw.map((block, position) => ({
|
|
301
|
+
position,
|
|
302
|
+
source: {
|
|
303
|
+
source: "control-api",
|
|
304
|
+
blockIndex: position,
|
|
305
|
+
raw: block
|
|
306
|
+
}
|
|
307
|
+
})) : [{
|
|
308
|
+
position: 0,
|
|
309
|
+
source: {
|
|
310
|
+
source: "control-api",
|
|
311
|
+
blockIndex: null,
|
|
312
|
+
raw
|
|
313
|
+
}
|
|
314
|
+
}];
|
|
315
|
+
const groups = /* @__PURE__ */ new Map();
|
|
316
|
+
if (items.length === 0) groups.set(MCP_FAKES_DEFAULT_MOUNT, {
|
|
317
|
+
path: MCP_FAKES_DEFAULT_MOUNT,
|
|
318
|
+
positions: [],
|
|
319
|
+
sources: []
|
|
320
|
+
});
|
|
321
|
+
for (const { position, source } of items) {
|
|
322
|
+
const path = controlFakeMountOf(source.raw);
|
|
323
|
+
const group = groups.get(path) ?? {
|
|
324
|
+
path,
|
|
325
|
+
positions: [],
|
|
326
|
+
sources: []
|
|
327
|
+
};
|
|
328
|
+
group.positions.push(position);
|
|
329
|
+
group.sources.push(source);
|
|
330
|
+
groups.set(path, group);
|
|
331
|
+
}
|
|
332
|
+
const found = [];
|
|
333
|
+
/** The paths `apply` auto-mounts, in order. */
|
|
334
|
+
const pending = [];
|
|
335
|
+
for (const group of groups.values()) {
|
|
336
|
+
const target = findFakeMount(mounts, group.path, pending);
|
|
337
|
+
if (target.kind === "conflict") {
|
|
338
|
+
group.positions.forEach((position) => {
|
|
339
|
+
const where = Array.isArray(raw) ? msg`mcpFakes[${position}]` : msg`mcpFakes`;
|
|
340
|
+
found.push({
|
|
341
|
+
position,
|
|
342
|
+
error: new FixtureLoadError({
|
|
343
|
+
rule: "mcp-fakes/mount-conflict",
|
|
344
|
+
file: null,
|
|
345
|
+
blockId: null,
|
|
346
|
+
entryId: null,
|
|
347
|
+
detail: msg`${where}: ${mountConflictDetail(group.path, target)}`
|
|
348
|
+
})
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
let failure = addFailure(() => new McpFakeStore().add(group.sources, { kind: "control-api" }));
|
|
354
|
+
if (target.kind === "none") pending.push(group.path);
|
|
355
|
+
if (!failure) continue;
|
|
356
|
+
if (target.kind === "mcp" && target.handler.addMcpFakes === MCPMock.prototype.addMcpFakes) {
|
|
357
|
+
const handler = target.handler;
|
|
358
|
+
failure = addFailure(() => handler.addMcpFakes?.(group.sources, { kind: "control-api" })) ?? failure;
|
|
359
|
+
}
|
|
360
|
+
for (const error of failure.errors) found.push({
|
|
361
|
+
position: positionOf(error, group),
|
|
362
|
+
error
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
found.sort((a, b) => a.position - b.position);
|
|
366
|
+
return {
|
|
367
|
+
errors: found.map((f) => f.error),
|
|
368
|
+
apply(wiring) {
|
|
369
|
+
let added = 0;
|
|
370
|
+
for (const group of groups.values()) {
|
|
371
|
+
const target = ensureFakeMount(mounts, group.path, {
|
|
372
|
+
journal: wiring.journal,
|
|
373
|
+
registry: wiring.defaults.registry,
|
|
374
|
+
logger: wiring.defaults.logger,
|
|
375
|
+
replaySpeed: wiring.defaults.replaySpeed
|
|
376
|
+
}, wiring.defaults.logger);
|
|
377
|
+
if ("conflict" in target) continue;
|
|
378
|
+
const result = target.handler.addMcpFakes?.(group.sources, { kind: "control-api" });
|
|
379
|
+
for (const warning of result?.warnings ?? []) wiring.defaults.logger.warn(warning.message);
|
|
380
|
+
added += group.sources.length;
|
|
381
|
+
}
|
|
382
|
+
return added;
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/** The `McpFakesAddError` that `add` throws, or `null` when it adds. */
|
|
387
|
+
function addFailure(add) {
|
|
388
|
+
try {
|
|
389
|
+
add();
|
|
390
|
+
return null;
|
|
391
|
+
} catch (err) {
|
|
392
|
+
if (err instanceof McpFakesAddError) return err;
|
|
393
|
+
throw err;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* The request position of the block an add error names. The add numbers a
|
|
398
|
+
* run-time group's blocks by their place in the group (`[<j>]`), or gives no
|
|
399
|
+
* index to a single-object value; an error tied to no block sorts with the
|
|
400
|
+
* group's first block.
|
|
401
|
+
*/
|
|
402
|
+
function positionOf(error, group) {
|
|
403
|
+
const first = group.positions[0] ?? 0;
|
|
404
|
+
if (error.blockId == null || error.file === null) return first;
|
|
405
|
+
const at = group.positions.findIndex((_, j) => error.blockId === blockIdOf(error.file ?? "", "", j));
|
|
406
|
+
return at === -1 ? first : group.positions[at];
|
|
407
|
+
}
|
|
408
|
+
function hasFakesSnapshot(handler) {
|
|
409
|
+
return "fakesSnapshot" in handler && typeof handler.fakesSnapshot === "function";
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* `GET /__aimock/mcp/fakes` (spec 6.5, I9): for each MCP mount (or the one
|
|
413
|
+
* named by `mount`), the blocks that apply to the requested test id and
|
|
414
|
+
* context, with every entry id and its consumed state for that test id.
|
|
415
|
+
* A mount is listed when it takes fakes (it implements `addMcpFakes`, the
|
|
416
|
+
* check every other fakes path uses); one with no `fakesSnapshot` lists no
|
|
417
|
+
* blocks.
|
|
418
|
+
*/
|
|
419
|
+
function listControlApiFakes(mounts, testId, context, mount) {
|
|
420
|
+
const listed = mounts.flatMap(({ path, handler }) => isFakeMount(handler) && (mount === null || path === mount) ? [{
|
|
421
|
+
mount: path,
|
|
422
|
+
blocks: hasFakesSnapshot(handler) ? handler.fakesSnapshot(testId, context) : []
|
|
423
|
+
}] : []);
|
|
424
|
+
return mount !== null && listed.length === 0 ? null : listed;
|
|
425
|
+
}
|
|
426
|
+
const controlFixtureAdditions = /* @__PURE__ */ new WeakMap();
|
|
312
427
|
/**
|
|
313
428
|
* Handle requests under `/__aimock/`. Returns `true` if the request was
|
|
314
429
|
* handled, `false` if the path doesn't match the control prefix.
|
|
315
430
|
*/
|
|
316
|
-
async function handleControlAPI(req, res, pathname, searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults) {
|
|
431
|
+
async function handleControlAPI(req, res, pathname, searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults, mounts) {
|
|
317
432
|
if (!pathname.startsWith(CONTROL_PREFIX)) return false;
|
|
318
|
-
const subPath = pathname.slice(
|
|
433
|
+
const subPath = pathname.slice(CONTROL_PREFIX.length);
|
|
319
434
|
setCorsHeaders(res);
|
|
320
435
|
if (subPath === "/health" && req.method === "GET") {
|
|
321
436
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
322
437
|
res.end(JSON.stringify({ status: "ok" }));
|
|
323
438
|
return true;
|
|
324
439
|
}
|
|
440
|
+
if (subPath === "/openapi.json" && req.method === "GET") {
|
|
441
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
442
|
+
res.end(JSON.stringify(buildOpenApiDocument()));
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
if (subPath === "/routes" && req.method === "GET") {
|
|
446
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
447
|
+
res.end(JSON.stringify({
|
|
448
|
+
count: CATALOG_ROUTES.length,
|
|
449
|
+
routes: CATALOG_ROUTES
|
|
450
|
+
}));
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
325
453
|
if (subPath === "/journal" && req.method === "GET") {
|
|
326
454
|
const bad = (message) => {
|
|
327
455
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
@@ -367,7 +495,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
367
495
|
"Content-Type": "application/json",
|
|
368
496
|
"X-Total-Count": String(total)
|
|
369
497
|
});
|
|
370
|
-
res.end(
|
|
498
|
+
res.end(journalJson(entries, journal));
|
|
371
499
|
return true;
|
|
372
500
|
}
|
|
373
501
|
if (subPath === "/fixtures" && req.method === "GET") {
|
|
@@ -387,19 +515,70 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
387
515
|
res.end(JSON.stringify({ error: "Invalid 'include': expected 'fixtures'" }));
|
|
388
516
|
return true;
|
|
389
517
|
}
|
|
518
|
+
const extendedTools = isExtendedResponsesToolsList(fixtures);
|
|
390
519
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
391
520
|
res.end(JSON.stringify({
|
|
392
521
|
count: fixtures.length,
|
|
393
522
|
fixtures: fixtures.map((fixture, index) => ({
|
|
394
523
|
index,
|
|
395
524
|
match: redactForInspection(fixture.match),
|
|
396
|
-
responseKind: isOneShotError(fixture) ? "error" : fixtureResponseKind(fixture.response),
|
|
525
|
+
responseKind: isOneShotError(fixture) ? "error" : fixtureResponseKind(extendedTools || typeof fixture.response === "function" ? fixture.response : withoutResponsesToolKeys(fixture.response)),
|
|
397
526
|
...fixture.latency !== void 0 ? { latency: fixture.latency } : {},
|
|
398
527
|
...fixture.chaos !== void 0 ? { chaos: fixture.chaos } : {}
|
|
399
528
|
}))
|
|
400
529
|
}));
|
|
401
530
|
return true;
|
|
402
531
|
}
|
|
532
|
+
if (subPath === "/misbehavior/catalog" && req.method === "GET") {
|
|
533
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
534
|
+
res.end(JSON.stringify({
|
|
535
|
+
catalog: MISBEHAVIOR_CATALOG,
|
|
536
|
+
WIRE_SUPPORT
|
|
537
|
+
}));
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
if (subPath === "/misbehavior" && (req.method === "GET" || req.method === "POST" || req.method === "DELETE")) {
|
|
541
|
+
const scopeId = chaosScopeId(req);
|
|
542
|
+
if (scopeId === null) return writeBlankTestId(res);
|
|
543
|
+
const reply = (status, body) => {
|
|
544
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
545
|
+
res.end(JSON.stringify(body));
|
|
546
|
+
return true;
|
|
547
|
+
};
|
|
548
|
+
const scope = defaults.misbehavior;
|
|
549
|
+
if (!scope && req.method !== "GET") return reply(409, { error: "misbehavior is not enabled: start aimock with enableMisbehavior: true (CLI: --misbehavior)" });
|
|
550
|
+
if (req.method === "POST") {
|
|
551
|
+
let raw;
|
|
552
|
+
try {
|
|
553
|
+
raw = await readBody(req);
|
|
554
|
+
} catch (err) {
|
|
555
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
556
|
+
defaults.logger.error(`POST /__aimock/misbehavior: failed to read body: ${msg}`);
|
|
557
|
+
return reply(400, { error: `Failed to read request body: ${msg}` });
|
|
558
|
+
}
|
|
559
|
+
let input;
|
|
560
|
+
try {
|
|
561
|
+
input = JSON.parse(raw);
|
|
562
|
+
} catch (err) {
|
|
563
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
564
|
+
defaults.logger.error(`POST /__aimock/misbehavior: invalid JSON: ${msg}`);
|
|
565
|
+
return reply(400, { error: `Invalid JSON: ${msg}` });
|
|
566
|
+
}
|
|
567
|
+
if (isJsonObject(input) && Object.keys(input).length === 0) input = { faults: [] };
|
|
568
|
+
const parsed = parseMisbehavior(input);
|
|
569
|
+
if (!parsed.ok) return reply(400, {
|
|
570
|
+
error: "Validation failed",
|
|
571
|
+
rule: parsed.issue.rule,
|
|
572
|
+
message: parsed.issue.message
|
|
573
|
+
});
|
|
574
|
+
const config = parsed.config.seed === "random" ? {
|
|
575
|
+
...parsed.config,
|
|
576
|
+
seed: resolveMisbehaviorSeed(parsed.config.seed, defaults.logger)
|
|
577
|
+
} : parsed.config;
|
|
578
|
+
scope?.byTestId.set(scopeId, config);
|
|
579
|
+
} else if (req.method === "DELETE") scope?.byTestId.delete(scopeId);
|
|
580
|
+
return reply(200, { misbehavior: scope?.byTestId.get(scopeId) ?? scope?.baseline ?? { faults: [] } });
|
|
581
|
+
}
|
|
403
582
|
if (subPath === "/chaos" && req.method === "GET") {
|
|
404
583
|
const scopeId = chaosScopeId(req);
|
|
405
584
|
if (scopeId === null) return writeBlankTestId(res);
|
|
@@ -472,6 +651,25 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
472
651
|
res.end(JSON.stringify({ chaos: effectiveChaos(defaults, scopeId) }));
|
|
473
652
|
return true;
|
|
474
653
|
}
|
|
654
|
+
if (subPath === "/mcp/fakes" && req.method === "GET") {
|
|
655
|
+
const reply = (status, body) => {
|
|
656
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
657
|
+
res.end(JSON.stringify(body));
|
|
658
|
+
return true;
|
|
659
|
+
};
|
|
660
|
+
for (const key of searchParams.keys()) if (!MCP_FAKES_PARAMS.has(key)) return reply(400, { error: `Unknown query parameter: '${key}'. Supported: ${[...MCP_FAKES_PARAMS].join(", ")}` });
|
|
661
|
+
const testId = searchParams.get("testId") || null;
|
|
662
|
+
const context = searchParams.get("context") || null;
|
|
663
|
+
const mount = searchParams.get("mount") || null;
|
|
664
|
+
const listed = listControlApiFakes(mounts, testId, context, mount);
|
|
665
|
+
if (listed === null) return reply(404, { error: build(msg`No MCP mount at ${quote(mount ?? "")}`) });
|
|
666
|
+
return reply(200, {
|
|
667
|
+
testId,
|
|
668
|
+
context,
|
|
669
|
+
mounts: listed
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
if (subPath === "/mcp/fakes/report" && req.method === "GET") return handleFakesReportRoute(res, searchParams, mounts);
|
|
475
673
|
if (subPath === "/fixtures" && req.method === "POST") {
|
|
476
674
|
let raw;
|
|
477
675
|
try {
|
|
@@ -493,29 +691,82 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
493
691
|
res.end(JSON.stringify({ error: `Invalid JSON: ${msg}` }));
|
|
494
692
|
return true;
|
|
495
693
|
}
|
|
496
|
-
|
|
694
|
+
const withFakes = typeof parsed === "object" && parsed !== null && Object.hasOwn(parsed, "mcpFakes");
|
|
695
|
+
if (parsed === null || !Array.isArray(parsed.fixtures) && !(withFakes && parsed.fixtures === void 0)) {
|
|
497
696
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
498
697
|
res.end(JSON.stringify({ error: "Missing or invalid \"fixtures\" array" }));
|
|
499
698
|
return true;
|
|
500
699
|
}
|
|
501
|
-
const
|
|
502
|
-
const
|
|
503
|
-
if (
|
|
700
|
+
const entries = Array.isArray(parsed.fixtures) ? parsed.fixtures : [];
|
|
701
|
+
const missingMatchIndex = entries.findIndex((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && entry.match == null);
|
|
702
|
+
if (missingMatchIndex !== -1) {
|
|
703
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
704
|
+
res.end(JSON.stringify({ error: `Fixture at index ${missingMatchIndex} is missing match` }));
|
|
705
|
+
return true;
|
|
706
|
+
}
|
|
707
|
+
const invalidMatchIndex = entries.findIndex((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && entry.match != null && (typeof entry.match !== "object" || Array.isArray(entry.match)));
|
|
708
|
+
if (invalidMatchIndex !== -1) {
|
|
709
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
710
|
+
res.end(JSON.stringify({ error: `Fixture at index ${invalidMatchIndex}: match must be an object` }));
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
const extendedResponsesTools = defaults.responsesTools === "extended";
|
|
714
|
+
const markInPlace = extendedResponsesTools && isServerOwnedFixtures(fixtures);
|
|
715
|
+
const addition = controlFixtureAdditions.get(defaults) ?? 0;
|
|
716
|
+
controlFixtureAdditions.set(defaults, addition + 1);
|
|
717
|
+
const converted = [];
|
|
718
|
+
const loadErrors = [];
|
|
719
|
+
for (const [index, entry] of entries.entries()) try {
|
|
720
|
+
const fixture = entryToFixture(entry, void 0, void 0, {
|
|
721
|
+
file: `control-api#${addition}`,
|
|
722
|
+
index
|
|
723
|
+
});
|
|
724
|
+
if (defaults.misbehavior) enableHeldFixtureMisbehavior(fixture);
|
|
725
|
+
if (markInPlace) markFixtureResponsesToolsExtended(fixture);
|
|
726
|
+
converted.push(fixture);
|
|
727
|
+
} catch (error) {
|
|
728
|
+
if (!(error instanceof FixtureLoadError || error instanceof MisbehaviorConfigError)) throw error;
|
|
729
|
+
loadErrors.push(error);
|
|
730
|
+
}
|
|
731
|
+
const errors = (loadErrors.length === 0 ? validateFixtures(extendedResponsesTools ? converted.map(responsesToolsExtendedView) : converted) : []).filter((i) => i.severity === "error");
|
|
732
|
+
const fakes = withFakes ? planControlApiFakes(parsed.mcpFakes, mounts) : null;
|
|
733
|
+
const fakeErrors = fakes?.errors ?? [];
|
|
734
|
+
const validationFailed = (details) => {
|
|
504
735
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
505
736
|
res.end(JSON.stringify({
|
|
506
737
|
error: "Validation failed",
|
|
507
|
-
details
|
|
738
|
+
details
|
|
508
739
|
}));
|
|
509
740
|
return true;
|
|
741
|
+
};
|
|
742
|
+
if (loadErrors.length > 0 || errors.length > 0 || fakeErrors.length > 0) return validationFailed([
|
|
743
|
+
...loadErrors.map((e) => e.toJSON()),
|
|
744
|
+
...errors,
|
|
745
|
+
...fakeErrors.map((e) => e.toJSON())
|
|
746
|
+
]);
|
|
747
|
+
let mcpFakesAdded = 0;
|
|
748
|
+
if (fakes) try {
|
|
749
|
+
mcpFakesAdded = fakes.apply({
|
|
750
|
+
journal,
|
|
751
|
+
defaults
|
|
752
|
+
});
|
|
753
|
+
} catch (err) {
|
|
754
|
+
if (!(err instanceof McpFakesAddError)) throw err;
|
|
755
|
+
return validationFailed(err.errors.map((e) => e.toJSON()));
|
|
510
756
|
}
|
|
511
757
|
fixtures.push(...converted);
|
|
512
758
|
if (defaults.registry) defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
|
|
513
759
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
514
|
-
res.end(JSON.stringify(
|
|
760
|
+
res.end(JSON.stringify(withFakes ? {
|
|
761
|
+
added: converted.length,
|
|
762
|
+
mcpFakesAdded
|
|
763
|
+
} : { added: converted.length }));
|
|
515
764
|
return true;
|
|
516
765
|
}
|
|
517
766
|
if (subPath === "/fixtures" && req.method === "DELETE") {
|
|
518
767
|
clearFixtureQueue(fixtures);
|
|
768
|
+
journal.clearMisbehaviorCounters();
|
|
769
|
+
for (const { handler } of mounts) handler.clearMcpFakes?.();
|
|
519
770
|
if (defaults.registry) defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
|
|
520
771
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
521
772
|
res.end(JSON.stringify({ cleared: true }));
|
|
@@ -528,7 +779,8 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
528
779
|
veoVideoJobs,
|
|
529
780
|
grokVideoJobs,
|
|
530
781
|
bytePlusVideoJobs,
|
|
531
|
-
defaults
|
|
782
|
+
defaults,
|
|
783
|
+
mounts
|
|
532
784
|
});
|
|
533
785
|
if (subPath === "/reset" && req.method === "POST") {
|
|
534
786
|
performFullReset(fixtures, resetTargets());
|
|
@@ -630,8 +882,9 @@ function clientAbortedMidBody(req, res, err) {
|
|
|
630
882
|
if (err instanceof RequestBodyTooLargeError) return false;
|
|
631
883
|
return res.destroyed && !req.complete;
|
|
632
884
|
}
|
|
633
|
-
async function handleCompletions(req, res, fixtures, journal, defaults, modelFallback, providerKey, openRouter = false) {
|
|
885
|
+
async function handleCompletions(req, res, fixtures, journal, defaults, getRequestEntry, modelFallback, providerKey, openRouter = false, beforeFixtureSelection, bytePlusPath = false) {
|
|
634
886
|
setCorsHeaders(res);
|
|
887
|
+
const wire = providerKey === "azure" ? "Azure OpenAI Chat Completions" : providerKey === "openrouter" ? "OpenRouter Chat Completions" : providerKey === "byteplus" || bytePlusPath ? "BytePlus ModelArk Chat Completions" : "OpenAI Chat Completions";
|
|
635
888
|
let raw;
|
|
636
889
|
try {
|
|
637
890
|
raw = await readBody(req);
|
|
@@ -737,6 +990,51 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
737
990
|
} }));
|
|
738
991
|
return;
|
|
739
992
|
}
|
|
993
|
+
const nullMessageIndex = body.messages.findIndex((message) => message === null);
|
|
994
|
+
if (nullMessageIndex !== -1) {
|
|
995
|
+
const message = `Invalid request: messages[${nullMessageIndex}] must be an object`;
|
|
996
|
+
journal.add({
|
|
997
|
+
method: req.method ?? "POST",
|
|
998
|
+
path: req.url ?? COMPLETIONS_PATH,
|
|
999
|
+
headers: flattenHeaders(req.headers),
|
|
1000
|
+
body: null,
|
|
1001
|
+
response: {
|
|
1002
|
+
status: 400,
|
|
1003
|
+
fixture: null
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, message) : JSON.stringify({ error: {
|
|
1007
|
+
message,
|
|
1008
|
+
type: "invalid_request_error",
|
|
1009
|
+
param: null,
|
|
1010
|
+
code: null
|
|
1011
|
+
} }));
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
if (new URL(req.url ?? COMPLETIONS_PATH, "http://localhost").pathname === COMPLETIONS_PATH) {
|
|
1015
|
+
let toolsError = validateToolsField(body.tools);
|
|
1016
|
+
if (!toolsError && Array.isArray(body.tools)) {
|
|
1017
|
+
const nullFunctionIndex = body.tools.findIndex((tool) => tool?.function === null);
|
|
1018
|
+
if (nullFunctionIndex !== -1) toolsError = `tools[${nullFunctionIndex}].function must not be null`;
|
|
1019
|
+
}
|
|
1020
|
+
if (toolsError) {
|
|
1021
|
+
journal.add({
|
|
1022
|
+
method: req.method ?? "POST",
|
|
1023
|
+
path: req.url ?? COMPLETIONS_PATH,
|
|
1024
|
+
headers: flattenHeaders(req.headers),
|
|
1025
|
+
body: null,
|
|
1026
|
+
response: {
|
|
1027
|
+
status: 400,
|
|
1028
|
+
fixture: null
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
writeErrorResponse(res, 400, JSON.stringify({ error: {
|
|
1032
|
+
message: toolsError,
|
|
1033
|
+
type: "invalid_request_error"
|
|
1034
|
+
} }));
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
740
1038
|
const method = req.method ?? "POST";
|
|
741
1039
|
const path = req.url ?? COMPLETIONS_PATH;
|
|
742
1040
|
const flatHeaders = flattenHeaders(req.headers);
|
|
@@ -752,6 +1050,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
752
1050
|
const openRouterFallback = openRouter && Array.isArray(body.models) && body.models.length > 0;
|
|
753
1051
|
const selectFixture = (probe, counts) => {
|
|
754
1052
|
for (;;) {
|
|
1053
|
+
beforeFixtureSelection?.();
|
|
755
1054
|
const attempt = matchFixtureDiagnostic(fixtures, probe, counts, defaults.requestTransform, matchOptions);
|
|
756
1055
|
if (attempt.fixture && isOneShotError(attempt.fixture) && !claimOneShotError(fixtures, attempt.fixture)) continue;
|
|
757
1056
|
return attempt;
|
|
@@ -835,13 +1134,32 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
835
1134
|
const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
|
|
836
1135
|
const missWouldProxy = wouldProxyMiss(effectiveStrict, defaults.record, providerKey);
|
|
837
1136
|
const noFixtureSource = missWouldProxy ? "proxy" : "internal";
|
|
1137
|
+
const recordBypass = (reason) => {
|
|
1138
|
+
const entry = getRequestEntry();
|
|
1139
|
+
if (!entry) return;
|
|
1140
|
+
recordMisbehaviorOutcome({
|
|
1141
|
+
entry,
|
|
1142
|
+
summary: resolveMisbehaviorShortCircuit({
|
|
1143
|
+
wire: "openai-chat",
|
|
1144
|
+
fixture: fixture ?? void 0,
|
|
1145
|
+
defaults,
|
|
1146
|
+
rawHeaders: req.headers,
|
|
1147
|
+
url: req.url,
|
|
1148
|
+
reason
|
|
1149
|
+
}),
|
|
1150
|
+
defaults,
|
|
1151
|
+
testId
|
|
1152
|
+
});
|
|
1153
|
+
};
|
|
838
1154
|
if (chaosAction === "drop" || chaosAction === "disconnect" || chaosAction === "rateLimit") {
|
|
839
1155
|
applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : noFixtureSource, defaults.registry, defaults.logger);
|
|
1156
|
+
recordBypass("chaos-fired");
|
|
840
1157
|
releaseOneShotError(fixtures, fixture);
|
|
841
1158
|
return;
|
|
842
1159
|
}
|
|
843
1160
|
if (chaosAction === "malformed" && (fixture || !missWouldProxy)) {
|
|
844
1161
|
applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : "internal", defaults.registry, defaults.logger);
|
|
1162
|
+
recordBypass("chaos-fired");
|
|
845
1163
|
releaseOneShotError(fixtures, fixture);
|
|
846
1164
|
return;
|
|
847
1165
|
}
|
|
@@ -873,6 +1191,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
873
1191
|
const hookOptions = chaosAction === "malformed" ? {
|
|
874
1192
|
beforeWriteResponse: () => {
|
|
875
1193
|
applyChaosAction(chaosAction, res, null, journal, chaosContext, "proxy", defaults.registry, defaults.logger);
|
|
1194
|
+
recordBypass("chaos-fired");
|
|
876
1195
|
return true;
|
|
877
1196
|
},
|
|
878
1197
|
onHookBypassed: (reason) => {
|
|
@@ -915,10 +1234,12 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
915
1234
|
} : {}
|
|
916
1235
|
}
|
|
917
1236
|
});
|
|
1237
|
+
recordBypass("proxied");
|
|
918
1238
|
return;
|
|
919
1239
|
}
|
|
920
1240
|
if (chaosAction === "malformed") {
|
|
921
1241
|
applyChaosAction(chaosAction, res, null, journal, chaosContext, "internal", defaults.registry, defaults.logger);
|
|
1242
|
+
recordBypass("chaos-fired");
|
|
922
1243
|
return;
|
|
923
1244
|
}
|
|
924
1245
|
}
|
|
@@ -941,13 +1262,71 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
941
1262
|
} }));
|
|
942
1263
|
return;
|
|
943
1264
|
}
|
|
944
|
-
const
|
|
1265
|
+
const resolvedResponse = preResolvedResponse ?? await resolveResponse(fixture, body);
|
|
1266
|
+
const evaluation = planMisbehavior({
|
|
1267
|
+
wire: "openai-chat",
|
|
1268
|
+
fixture,
|
|
1269
|
+
response: resolvedResponse,
|
|
1270
|
+
request: body,
|
|
1271
|
+
stream: body.stream === true,
|
|
1272
|
+
defaults,
|
|
1273
|
+
rawHeaders: req.headers,
|
|
1274
|
+
url: req.url
|
|
1275
|
+
});
|
|
1276
|
+
if (evaluation.kind === "error") {
|
|
1277
|
+
recordMisbehaviorOutcome({
|
|
1278
|
+
entry: journal.add({
|
|
1279
|
+
method,
|
|
1280
|
+
path,
|
|
1281
|
+
headers: flatHeaders,
|
|
1282
|
+
body,
|
|
1283
|
+
response: {
|
|
1284
|
+
status: evaluation.status,
|
|
1285
|
+
fixture
|
|
1286
|
+
}
|
|
1287
|
+
}),
|
|
1288
|
+
summary: evaluation.summary,
|
|
1289
|
+
defaults,
|
|
1290
|
+
testId
|
|
1291
|
+
});
|
|
1292
|
+
if (!evaluation.summary?.evaluations.some((row) => row.outcome === "error")) defaults.logger.error(`${evaluation.code}: ${evaluation.message}`);
|
|
1293
|
+
writeErrorResponse(res, evaluation.status, openRouter ? serializeOpenRouterError(evaluation.status, `${evaluation.code}: ${evaluation.message}`) : JSON.stringify({ error: {
|
|
1294
|
+
message: evaluation.message,
|
|
1295
|
+
type: "invalid_request_error",
|
|
1296
|
+
param: null,
|
|
1297
|
+
code: evaluation.code
|
|
1298
|
+
} }));
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
const appliedPlan = evaluation.kind === "applied" ? prepareOpenAIChatMisbehavior(evaluation) : void 0;
|
|
1302
|
+
const response = appliedPlan?.response ?? resolvedResponse;
|
|
1303
|
+
const summary = appliedPlan?.summary ?? evaluation.summary;
|
|
1304
|
+
const recordOutcome = (entry) => {
|
|
1305
|
+
recordMisbehaviorOutcome({
|
|
1306
|
+
entry,
|
|
1307
|
+
summary,
|
|
1308
|
+
defaults,
|
|
1309
|
+
testId
|
|
1310
|
+
});
|
|
1311
|
+
};
|
|
1312
|
+
const appliedReasoning = appliedPlan && appliedPlan.reasoning === void 0 && (isTextResponse(response) || isToolCallResponse(response) || isCombinedFixtureResponse(response)) ? resolveReasoningForModel(response.reasoning, responseModel, resolveStrictMode(defaults.strict, req.headers), defaults.logger) : void 0;
|
|
1313
|
+
const appliedUsage = appliedPlan ? resolveOpenAIChatMisbehaviorUsage(appliedPlan, body, appliedPlan.reasoning === void 0 ? appliedReasoning !== void 0 : openRouter) : void 0;
|
|
945
1314
|
const latency = fixture.latency ?? defaults.latency;
|
|
946
1315
|
const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
|
|
947
1316
|
const emitStreamingUsage = body.stream === true && body.stream_options?.include_usage === true || openRouter;
|
|
948
1317
|
const streamingPromptText = body.messages.map((m) => typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((p) => p.text ?? "").join("") : "").join("");
|
|
949
|
-
const resolveStreamingUsageTokens = (overrides, completionText) => resolveUsage(overrides, streamingPromptText, completionText);
|
|
1318
|
+
const resolveStreamingUsageTokens = (overrides, completionText) => appliedUsage ?? resolveUsage(overrides, streamingPromptText, completionText);
|
|
1319
|
+
const resolveRecordedTimings = (chunks) => {
|
|
1320
|
+
const timings = fixture.recordedTimings;
|
|
1321
|
+
if (!appliedPlan?.duplicateId || !timings) return timings;
|
|
1322
|
+
const gaps = timings.interChunkDelaysMs;
|
|
1323
|
+
return {
|
|
1324
|
+
...timings,
|
|
1325
|
+
interChunkDelaysMs: Array.from({ length: Math.max(gaps.length, chunks.length - 1) }, (_, index) => gaps[index] ?? gaps.at(-1) ?? 0)
|
|
1326
|
+
};
|
|
1327
|
+
};
|
|
950
1328
|
const shapeORCompletion = (completion, overrides) => {
|
|
1329
|
+
if (appliedUsage) completion.usage = appliedUsage;
|
|
951
1330
|
if (!openRouter) return completion;
|
|
952
1331
|
return shapeOpenRouterCompletion(completion, resolveOpenRouterShaping(overrides, overrides?.model ?? responseModel), overrides?.id !== void 0);
|
|
953
1332
|
};
|
|
@@ -960,7 +1339,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
960
1339
|
const openRouterProcessing = !!(openRouter && fixture.openRouterProcessing);
|
|
961
1340
|
if (isErrorResponse(response)) {
|
|
962
1341
|
const status = response.status ?? 500;
|
|
963
|
-
journal.add({
|
|
1342
|
+
recordOutcome(journal.add({
|
|
964
1343
|
method: req.method ?? "POST",
|
|
965
1344
|
path: req.url ?? COMPLETIONS_PATH,
|
|
966
1345
|
headers: flattenHeaders(req.headers),
|
|
@@ -969,12 +1348,12 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
969
1348
|
status,
|
|
970
1349
|
fixture
|
|
971
1350
|
}
|
|
972
|
-
});
|
|
1351
|
+
}));
|
|
973
1352
|
writeErrorResponse(res, status, openRouter ? serializeOpenRouterError(status, response.error.message, response.error.metadata) : serializeErrorResponse(response), { retryAfter: response.retryAfter });
|
|
974
1353
|
return;
|
|
975
1354
|
}
|
|
976
1355
|
if (isAudioResponse(response)) {
|
|
977
|
-
journal.add({
|
|
1356
|
+
recordOutcome(journal.add({
|
|
978
1357
|
method: req.method ?? "POST",
|
|
979
1358
|
path: req.url ?? COMPLETIONS_PATH,
|
|
980
1359
|
headers: flattenHeaders(req.headers),
|
|
@@ -983,18 +1362,58 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
983
1362
|
status: 422,
|
|
984
1363
|
fixture
|
|
985
1364
|
}
|
|
986
|
-
});
|
|
1365
|
+
}));
|
|
987
1366
|
writeErrorResponse(res, 422, openRouter ? serializeOpenRouterError(422, "Audio responses are not supported on the chat completions endpoint. Use Gemini generateContent or a dedicated audio endpoint.") : JSON.stringify({ error: {
|
|
988
1367
|
message: "Audio responses are not supported on the chat completions endpoint. Use Gemini generateContent or a dedicated audio endpoint.",
|
|
989
1368
|
type: "invalid_request_error"
|
|
990
1369
|
} }));
|
|
991
1370
|
return;
|
|
992
1371
|
}
|
|
993
|
-
if (
|
|
1372
|
+
if (appliedPlan && isTextResponse(response) && (appliedPlan.stop === "refusal" || appliedPlan.stop === "content_filter" || appliedPlan.reasoning !== void 0)) {
|
|
1373
|
+
const overrides = extractOverrides(response);
|
|
1374
|
+
const entry = journal.add({
|
|
1375
|
+
method,
|
|
1376
|
+
path,
|
|
1377
|
+
headers: flatHeaders,
|
|
1378
|
+
body,
|
|
1379
|
+
response: {
|
|
1380
|
+
status: 200,
|
|
1381
|
+
fixture
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
recordOutcome(entry);
|
|
1385
|
+
if (body.stream !== true) {
|
|
1386
|
+
const completion = appliedPlan.stop === "refusal" ? buildOpenAIRefusalCompletion(appliedPlan.refusal ?? "", responseModel, overrides, body.messages) : appliedPlan.stop === "content_filter" ? buildOpenAIContentFilterCompletion(responseModel, overrides, body.messages) : buildOpenAIReasoningCompletion(appliedPlan.reasoning ?? "", responseModel, openRouter, overrides, body.messages);
|
|
1387
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1388
|
+
res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
|
|
1389
|
+
} else {
|
|
1390
|
+
const chunks = appliedPlan.stop === "refusal" ? buildOpenAIRefusalChunks(appliedPlan.refusal ?? "", responseModel, chunkSize, overrides) : appliedPlan.stop === "content_filter" ? buildOpenAIContentFilterChunks(responseModel, overrides) : buildOpenAIReasoningChunks(appliedPlan.reasoning ?? "", responseModel, chunkSize, openRouter, overrides);
|
|
1391
|
+
const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0].id, overrides?.model ?? responseModel, chunks[0].created, resolveStreamingUsageTokens(overrides, ""), overrides?.systemFingerprint) : void 0;
|
|
1392
|
+
shapeORChunks(chunks, usageChunk, overrides);
|
|
1393
|
+
const interruption = createInterruptionSignal(fixture);
|
|
1394
|
+
if (!await writeSSEStream(res, chunks, {
|
|
1395
|
+
latency,
|
|
1396
|
+
streamingProfile: fixture.streamingProfile,
|
|
1397
|
+
signal: interruption?.signal,
|
|
1398
|
+
onChunkSent: interruption?.tick,
|
|
1399
|
+
usageChunk,
|
|
1400
|
+
recordedTimings: resolveRecordedTimings(chunks),
|
|
1401
|
+
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
|
|
1402
|
+
openRouterProcessing
|
|
1403
|
+
})) {
|
|
1404
|
+
if (!res.writableEnded) res.destroy();
|
|
1405
|
+
entry.response.interrupted = true;
|
|
1406
|
+
entry.response.interruptReason = interruption?.reason();
|
|
1407
|
+
}
|
|
1408
|
+
interruption?.cleanup();
|
|
1409
|
+
}
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
if (isCombinedFixtureResponse(response)) {
|
|
994
1413
|
if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
|
|
995
1414
|
const overrides = extractOverrides(response);
|
|
996
1415
|
const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
|
|
997
|
-
const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
1416
|
+
const effReasoning = appliedPlan ? appliedReasoning : resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
998
1417
|
const streaming = body.stream === true;
|
|
999
1418
|
const blockOutcome = response.blocks && response.blocks.length > 0 ? resolveFixtureBlockOutcome(response.blocks) : void 0;
|
|
1000
1419
|
const journalEntry = journal.add({
|
|
@@ -1007,16 +1426,18 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1007
1426
|
fixture
|
|
1008
1427
|
}
|
|
1009
1428
|
});
|
|
1429
|
+
recordOutcome(journalEntry);
|
|
1430
|
+
const toolCalls = requireEmittedFunctionToolCalls(response, wire);
|
|
1010
1431
|
if (!streaming) {
|
|
1011
|
-
const completion = buildContentWithToolCallsCompletion(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ??
|
|
1432
|
+
const completion = buildContentWithToolCallsCompletion(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? toolCalls, responseModel, effReasoning, blockOutcome ? {
|
|
1012
1433
|
...overrides,
|
|
1013
1434
|
finishReason: overrides?.finishReason ?? (blockOutcome.hasToolCalls ? "tool_calls" : "stop")
|
|
1014
1435
|
} : overrides, body.messages);
|
|
1015
1436
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1016
1437
|
res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
|
|
1017
1438
|
} else {
|
|
1018
|
-
const chunks = buildContentWithToolCallsChunks(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ??
|
|
1019
|
-
const completionText = blockOutcome ? blockOutcome.content + blockOutcome.toolCalls.map((tc) => tc.name + tc.arguments).join("") : (response.content ?? "") +
|
|
1439
|
+
const chunks = buildContentWithToolCallsChunks(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? toolCalls, responseModel, chunkSize, effReasoning, overrides, blockOutcome?.ordered);
|
|
1440
|
+
const completionText = blockOutcome ? blockOutcome.content + blockOutcome.toolCalls.map((tc) => tc.name + tc.arguments).join("") : (response.content ?? "") + toolCalls.map((tc) => tc.name + tc.arguments).join("");
|
|
1020
1441
|
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;
|
|
1021
1442
|
shapeORChunks(chunks, usageChunk, overrides);
|
|
1022
1443
|
const interruption = createInterruptionSignal(fixture);
|
|
@@ -1026,7 +1447,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1026
1447
|
signal: interruption?.signal,
|
|
1027
1448
|
onChunkSent: interruption?.tick,
|
|
1028
1449
|
usageChunk,
|
|
1029
|
-
recordedTimings:
|
|
1450
|
+
recordedTimings: resolveRecordedTimings(chunks),
|
|
1030
1451
|
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
|
|
1031
1452
|
openRouterProcessing
|
|
1032
1453
|
})) {
|
|
@@ -1042,7 +1463,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1042
1463
|
if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
|
|
1043
1464
|
const overrides = extractOverrides(response);
|
|
1044
1465
|
const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
|
|
1045
|
-
const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
1466
|
+
const effReasoning = appliedPlan ? appliedReasoning : resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
1046
1467
|
const journalEntry = journal.add({
|
|
1047
1468
|
method: req.method ?? "POST",
|
|
1048
1469
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -1053,6 +1474,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1053
1474
|
fixture
|
|
1054
1475
|
}
|
|
1055
1476
|
});
|
|
1477
|
+
recordOutcome(journalEntry);
|
|
1056
1478
|
if (body.stream !== true) {
|
|
1057
1479
|
const completion = buildTextCompletion(response.content, responseModel, effReasoning, overrides, body.messages);
|
|
1058
1480
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -1068,7 +1490,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1068
1490
|
signal: interruption?.signal,
|
|
1069
1491
|
onChunkSent: interruption?.tick,
|
|
1070
1492
|
usageChunk,
|
|
1071
|
-
recordedTimings:
|
|
1493
|
+
recordedTimings: resolveRecordedTimings(chunks),
|
|
1072
1494
|
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
|
|
1073
1495
|
openRouterProcessing
|
|
1074
1496
|
})) {
|
|
@@ -1084,7 +1506,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1084
1506
|
if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
|
|
1085
1507
|
const overrides = extractOverrides(response);
|
|
1086
1508
|
const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
|
|
1087
|
-
const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
1509
|
+
const effReasoning = appliedPlan ? appliedReasoning : resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
1088
1510
|
const journalEntry = journal.add({
|
|
1089
1511
|
method: req.method ?? "POST",
|
|
1090
1512
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -1095,13 +1517,15 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1095
1517
|
fixture
|
|
1096
1518
|
}
|
|
1097
1519
|
});
|
|
1520
|
+
recordOutcome(journalEntry);
|
|
1521
|
+
const toolCalls = requireServedFunctionToolCalls(response, wire);
|
|
1098
1522
|
if (body.stream !== true) {
|
|
1099
|
-
const completion = buildToolCallCompletion(
|
|
1523
|
+
const completion = buildToolCallCompletion(toolCalls, responseModel, effReasoning, overrides, body.messages);
|
|
1100
1524
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1101
1525
|
res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
|
|
1102
1526
|
} else {
|
|
1103
|
-
const chunks = buildToolCallChunks(
|
|
1104
|
-
const completionText =
|
|
1527
|
+
const chunks = buildToolCallChunks(toolCalls, responseModel, chunkSize, effReasoning, overrides);
|
|
1528
|
+
const completionText = toolCalls.map((tc) => tc.name + tc.arguments).join("");
|
|
1105
1529
|
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;
|
|
1106
1530
|
shapeORChunks(chunks, usageChunk, overrides);
|
|
1107
1531
|
const interruption = createInterruptionSignal(fixture);
|
|
@@ -1111,7 +1535,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1111
1535
|
signal: interruption?.signal,
|
|
1112
1536
|
onChunkSent: interruption?.tick,
|
|
1113
1537
|
usageChunk,
|
|
1114
|
-
recordedTimings:
|
|
1538
|
+
recordedTimings: resolveRecordedTimings(chunks),
|
|
1115
1539
|
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
|
|
1116
1540
|
openRouterProcessing
|
|
1117
1541
|
})) {
|
|
@@ -1123,7 +1547,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1123
1547
|
}
|
|
1124
1548
|
return;
|
|
1125
1549
|
}
|
|
1126
|
-
journal.add({
|
|
1550
|
+
recordOutcome(journal.add({
|
|
1127
1551
|
method: req.method ?? "POST",
|
|
1128
1552
|
path: req.url ?? COMPLETIONS_PATH,
|
|
1129
1553
|
headers: flattenHeaders(req.headers),
|
|
@@ -1132,7 +1556,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1132
1556
|
status: 500,
|
|
1133
1557
|
fixture
|
|
1134
1558
|
}
|
|
1135
|
-
});
|
|
1559
|
+
}));
|
|
1136
1560
|
writeErrorResponse(res, 500, openRouter ? serializeOpenRouterError(500, "Fixture response did not match any known type") : JSON.stringify({ error: {
|
|
1137
1561
|
message: "Fixture response did not match any known type",
|
|
1138
1562
|
type: "server_error"
|
|
@@ -1146,18 +1570,101 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
1146
1570
|
}
|
|
1147
1571
|
/** @internal Config startup uses this to avoid parsing a selected auth source twice. */
|
|
1148
1572
|
async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mounts, serviceFixtures) {
|
|
1573
|
+
const logger = new Logger(options?.logLevel ?? "silent");
|
|
1574
|
+
const mountList = mounts ?? [];
|
|
1575
|
+
const handOff = serviceFixtures?.mcpFakes && serviceFixtures.mcpFakes.length > 0 ? handOffMcpFakes(mountList, serviceFixtures.mcpFakes, logger) : null;
|
|
1576
|
+
try {
|
|
1577
|
+
return await startServer(fixtures, options, resolvedAuth, mountList, logger, serviceFixtures, () => handOff?.commit());
|
|
1578
|
+
} catch (err) {
|
|
1579
|
+
handOff?.undo();
|
|
1580
|
+
throw err;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
/**
|
|
1584
|
+
* The journal as JSON. An entry names the caller's fixture; the JSON shows
|
|
1585
|
+
* the fixture the request was served from (`Journal.servedFixture`): an
|
|
1586
|
+
* extended server's view, with the held keys applied, or LLMock's
|
|
1587
|
+
* per-addition copy with misbehavior enabled. The journal therefore reads as
|
|
1588
|
+
* it did when the entries held those objects.
|
|
1589
|
+
*/
|
|
1590
|
+
function journalJson(entries, journal) {
|
|
1591
|
+
if (!entries.some((entry) => journal.servedFixture(entry) !== entry.response.fixture)) return JSON.stringify(entries);
|
|
1592
|
+
return JSON.stringify(entries.map((entry) => {
|
|
1593
|
+
const fixture = entry.response.fixture;
|
|
1594
|
+
let served = journal.servedFixture(entry);
|
|
1595
|
+
if (served === fixture || !served) return entry;
|
|
1596
|
+
const viewSource = responsesToolsViewSource(served);
|
|
1597
|
+
if (viewSource !== served) served = responsesToolsExtendedView(viewSource);
|
|
1598
|
+
return {
|
|
1599
|
+
...entry,
|
|
1600
|
+
response: {
|
|
1601
|
+
...entry.response,
|
|
1602
|
+
fixture: served
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1605
|
+
}));
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1608
|
+
* The server of `createServerWithResolvedAuth`, after the MCP fakes hand-off.
|
|
1609
|
+
* `commitHandOff` runs once the server listens; if it throws, the server is
|
|
1610
|
+
* closed and start rejects.
|
|
1611
|
+
*/
|
|
1612
|
+
async function startServer(fixtures, options, resolvedAuth, mountList, logger, serviceFixtures, commitHandOff) {
|
|
1613
|
+
const callerFixtures = fixtures;
|
|
1614
|
+
let rawFixtureAddition = 0;
|
|
1615
|
+
function ensureRawFixturePositions() {
|
|
1616
|
+
const existingPositions = new Set(callerFixtures.map(getFixtureMisbehaviorPosition).filter((position) => position !== void 0));
|
|
1617
|
+
for (const fixture of callerFixtures) {
|
|
1618
|
+
if (getFixtureMisbehaviorPosition(fixture) !== void 0) continue;
|
|
1619
|
+
let position;
|
|
1620
|
+
do
|
|
1621
|
+
position = `createServer#${rawFixtureAddition++}`;
|
|
1622
|
+
while (existingPositions.has(position));
|
|
1623
|
+
setFixtureMisbehaviorPosition(fixture, position);
|
|
1624
|
+
existingPositions.add(position);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
ensureRawFixturePositions();
|
|
1149
1628
|
const host = options?.host ?? "127.0.0.1";
|
|
1150
1629
|
const port = options?.port ?? 0;
|
|
1151
|
-
const logger = new Logger(options?.logLevel ?? "silent");
|
|
1152
1630
|
const registry = options?.metrics ? createMetricsRegistry() : void 0;
|
|
1153
1631
|
const serverOptions = options ?? {};
|
|
1632
|
+
const validResponsesTools = (mode) => mode === "legacy" || mode === "extended";
|
|
1633
|
+
if (serverOptions.responsesTools !== void 0 && !validResponsesTools(serverOptions.responsesTools)) new Logger("warn").warn(`Ignoring responsesTools because it must be "legacy" or "extended", got ${JSON.stringify(serverOptions.responsesTools)}. Using "legacy".`);
|
|
1634
|
+
fixtures = createServerFixtureList(callerFixtures, () => serverOptions.responsesTools, responsesToolsExtendedView);
|
|
1154
1635
|
const chaosByTestId = /* @__PURE__ */ new Map();
|
|
1636
|
+
let misbehaviorBaseline;
|
|
1637
|
+
const misbehavior = {
|
|
1638
|
+
byTestId: /* @__PURE__ */ new Map(),
|
|
1639
|
+
get baseline() {
|
|
1640
|
+
return this.byTestId.get(DEFAULT_TEST_ID) ?? misbehaviorBaseline;
|
|
1641
|
+
},
|
|
1642
|
+
set baseline(config) {
|
|
1643
|
+
misbehaviorBaseline = config;
|
|
1644
|
+
if (config === void 0) this.byTestId.delete(DEFAULT_TEST_ID);
|
|
1645
|
+
else if (this.byTestId.has(DEFAULT_TEST_ID)) this.byTestId.set(DEFAULT_TEST_ID, config);
|
|
1646
|
+
}
|
|
1647
|
+
};
|
|
1648
|
+
const misbehaviorEnabled = serverOptions.enableMisbehavior === true;
|
|
1649
|
+
if (misbehaviorEnabled) callerFixtures.forEach(enableHeldFixtureMisbehavior);
|
|
1650
|
+
if (misbehaviorEnabled && serverOptions.misbehavior !== void 0) {
|
|
1651
|
+
const parsed = parseMisbehavior(serverOptions.misbehavior);
|
|
1652
|
+
if (!parsed.ok) throw new TypeError(`${parsed.issue.rule}: ${parsed.issue.message}`);
|
|
1653
|
+
misbehavior.baseline = parsed.config.seed === "random" ? {
|
|
1654
|
+
...parsed.config,
|
|
1655
|
+
seed: resolveMisbehaviorSeed(parsed.config.seed, logger)
|
|
1656
|
+
} : parsed.config;
|
|
1657
|
+
}
|
|
1155
1658
|
const defaults = {
|
|
1156
1659
|
latency: serverOptions.latency ?? 0,
|
|
1157
1660
|
chunkSize: Math.max(1, serverOptions.chunkSize ?? DEFAULT_CHUNK_SIZE),
|
|
1158
1661
|
replaySpeed: serverOptions.replaySpeed ?? 1,
|
|
1159
1662
|
logger,
|
|
1160
1663
|
chaosByTestId,
|
|
1664
|
+
misbehavior: misbehaviorEnabled ? misbehavior : void 0,
|
|
1665
|
+
get misbehaviorCounters() {
|
|
1666
|
+
return journal;
|
|
1667
|
+
},
|
|
1161
1668
|
get chaos() {
|
|
1162
1669
|
return {
|
|
1163
1670
|
base: chaosByTestId.get(DEFAULT_TEST_ID) ?? serverOptions.chaos,
|
|
@@ -1180,6 +1687,13 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1180
1687
|
get strict() {
|
|
1181
1688
|
return serverOptions.strict;
|
|
1182
1689
|
},
|
|
1690
|
+
get strictToolArguments() {
|
|
1691
|
+
return serverOptions.strictToolArguments;
|
|
1692
|
+
},
|
|
1693
|
+
get responsesTools() {
|
|
1694
|
+
const mode = serverOptions.responsesTools;
|
|
1695
|
+
return validResponsesTools(mode) ? mode : void 0;
|
|
1696
|
+
},
|
|
1183
1697
|
get requestTransform() {
|
|
1184
1698
|
return serverOptions.requestTransform;
|
|
1185
1699
|
},
|
|
@@ -1270,6 +1784,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1270
1784
|
if (req) ownJournalEntry.set(req, entry);
|
|
1271
1785
|
}
|
|
1272
1786
|
});
|
|
1787
|
+
if (serverOptions.responsesTools === "extended") journal.configureFixtureViewSource(responsesToolsViewSource);
|
|
1273
1788
|
const videoStates = new VideoStateMap();
|
|
1274
1789
|
const openRouterVideoJobs = new OpenRouterVideoJobMap();
|
|
1275
1790
|
const veoVideoJobs = new VeoVideoJobMap();
|
|
@@ -1284,6 +1799,85 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1284
1799
|
return status >= 400 && status < 500 ? "invalid_request_error" : "server_error";
|
|
1285
1800
|
}
|
|
1286
1801
|
/**
|
|
1802
|
+
* Envelope for a fixture tool-call error (see isFixtureToolCallError: invalid
|
|
1803
|
+
* JSON arguments on an object wire, a custom tool call on a non-Responses
|
|
1804
|
+
* wire, or a malformed fixture tool call). It is the provider envelope aimock
|
|
1805
|
+
* emits for that route, with the aimock `code` in it: Google's
|
|
1806
|
+
* `google.rpc.ErrorInfo` detail on Gemini and Vertex AI, a string
|
|
1807
|
+
* `error.code` on Gemini Interactions and Anthropic Messages, a `reason`
|
|
1808
|
+
* member on Bedrock (a `code` member would replace `__type` as the AWS SDK's
|
|
1809
|
+
* error name), and a sibling `code` on Ollama's bare string error. Every
|
|
1810
|
+
* other route, Cohere included, gets the OpenAI-style
|
|
1811
|
+
* `{error: {message, type, code}}`. OpenRouter routes pass their own
|
|
1812
|
+
* `toolCallEnvelope` instead of using this one.
|
|
1813
|
+
*/
|
|
1814
|
+
function invalidToolArgumentsEnvelope(pathname, message, code) {
|
|
1815
|
+
if (pathname === OLLAMA_CHAT_PATH) return JSON.stringify({
|
|
1816
|
+
error: message,
|
|
1817
|
+
code
|
|
1818
|
+
});
|
|
1819
|
+
if (pathname === GEMINI_INTERACTIONS_PATH) return JSON.stringify({ error: {
|
|
1820
|
+
code,
|
|
1821
|
+
message
|
|
1822
|
+
} });
|
|
1823
|
+
if (GEMINI_PATH_RE.test(pathname) || VERTEX_AI_RE.test(pathname)) return JSON.stringify({ error: {
|
|
1824
|
+
code: 500,
|
|
1825
|
+
message,
|
|
1826
|
+
status: "INTERNAL",
|
|
1827
|
+
details: googleFixtureToolCallErrorDetails(code)
|
|
1828
|
+
} });
|
|
1829
|
+
if (pathname === MESSAGES_PATH) return JSON.stringify({
|
|
1830
|
+
type: "error",
|
|
1831
|
+
error: {
|
|
1832
|
+
type: "api_error",
|
|
1833
|
+
code,
|
|
1834
|
+
message
|
|
1835
|
+
}
|
|
1836
|
+
});
|
|
1837
|
+
if (BEDROCK_INVOKE_RE.test(pathname) || BEDROCK_STREAM_RE.test(pathname) || BEDROCK_CONVERSE_RE.test(pathname) || BEDROCK_CONVERSE_STREAM_RE.test(pathname)) return JSON.stringify({
|
|
1838
|
+
__type: "InternalServerException",
|
|
1839
|
+
message,
|
|
1840
|
+
reason: code
|
|
1841
|
+
});
|
|
1842
|
+
return JSON.stringify({ error: {
|
|
1843
|
+
message,
|
|
1844
|
+
type: "server_error",
|
|
1845
|
+
code
|
|
1846
|
+
} });
|
|
1847
|
+
}
|
|
1848
|
+
function invalidMisbehaviorHeaderEnvelope(pathname, detail) {
|
|
1849
|
+
const code = "aimock_misbehavior_invalid";
|
|
1850
|
+
const message = `${code}: ${detail}`;
|
|
1851
|
+
if (pathname === OLLAMA_CHAT_PATH || pathname === OLLAMA_GENERATE_PATH) return JSON.stringify({ error: message });
|
|
1852
|
+
if (pathname === COHERE_CHAT_PATH) return JSON.stringify({ message });
|
|
1853
|
+
if (pathname === GEMINI_INTERACTIONS_PATH) return JSON.stringify({ error: {
|
|
1854
|
+
code,
|
|
1855
|
+
message
|
|
1856
|
+
} });
|
|
1857
|
+
if (GEMINI_PATH_RE.test(pathname) || VERTEX_AI_RE.test(pathname)) return JSON.stringify({ error: {
|
|
1858
|
+
code: 400,
|
|
1859
|
+
message,
|
|
1860
|
+
status: "INVALID_ARGUMENT"
|
|
1861
|
+
} });
|
|
1862
|
+
if (pathname === MESSAGES_PATH) return JSON.stringify({
|
|
1863
|
+
type: "error",
|
|
1864
|
+
error: {
|
|
1865
|
+
type: "invalid_request_error",
|
|
1866
|
+
code,
|
|
1867
|
+
message
|
|
1868
|
+
}
|
|
1869
|
+
});
|
|
1870
|
+
if (BEDROCK_INVOKE_RE.test(pathname) || BEDROCK_STREAM_RE.test(pathname) || BEDROCK_CONVERSE_RE.test(pathname) || BEDROCK_CONVERSE_STREAM_RE.test(pathname)) return JSON.stringify({
|
|
1871
|
+
__type: "ValidationException",
|
|
1872
|
+
message
|
|
1873
|
+
});
|
|
1874
|
+
return JSON.stringify({ error: {
|
|
1875
|
+
message,
|
|
1876
|
+
type: "invalid_request_error",
|
|
1877
|
+
code
|
|
1878
|
+
} });
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1287
1881
|
* The newest journal entry this request already produced, or null.
|
|
1288
1882
|
*
|
|
1289
1883
|
* Attribution is by the identity of the `IncomingMessage` the entry was
|
|
@@ -1334,6 +1928,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1334
1928
|
function routeError(req, res, err, pathname, opts) {
|
|
1335
1929
|
const route = `${req.method ?? "?"} ${pathname}`;
|
|
1336
1930
|
const msg = err instanceof Error ? err.message : "Internal error";
|
|
1931
|
+
const invalidToolArguments = isFixtureToolCallError(err);
|
|
1337
1932
|
const clientFault = err instanceof RequestBodyTooLargeError;
|
|
1338
1933
|
const clientAbort = clientAbortedMidBody(req, res, err);
|
|
1339
1934
|
const status = clientFault ? 400 : 500;
|
|
@@ -1374,8 +1969,10 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1374
1969
|
return;
|
|
1375
1970
|
}
|
|
1376
1971
|
if (!res.headersSent) {
|
|
1377
|
-
if (existing)
|
|
1378
|
-
|
|
1972
|
+
if (existing) {
|
|
1973
|
+
existing.response.status = status;
|
|
1974
|
+
if (invalidToolArguments) existing.response.error = msg;
|
|
1975
|
+
} else try {
|
|
1379
1976
|
journal.add({
|
|
1380
1977
|
method: req.method ?? "?",
|
|
1381
1978
|
path: req.url ?? "?",
|
|
@@ -1385,14 +1982,15 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1385
1982
|
response: {
|
|
1386
1983
|
status,
|
|
1387
1984
|
fixture: null,
|
|
1388
|
-
source: "internal"
|
|
1985
|
+
source: "internal",
|
|
1986
|
+
...invalidToolArguments ? { error: msg } : {}
|
|
1389
1987
|
}
|
|
1390
1988
|
});
|
|
1391
1989
|
} catch (jErr) {
|
|
1392
1990
|
logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1393
1991
|
}
|
|
1394
1992
|
setCorsHeaders(res);
|
|
1395
|
-
writeErrorResponse(res, status, opts?.envelope ? opts.envelope(msg, status) : JSON.stringify({ error: {
|
|
1993
|
+
writeErrorResponse(res, status, invalidToolArguments ? (opts?.toolCallEnvelope ?? ((m, c) => invalidToolArgumentsEnvelope(pathname, m, c)))(msg, fixtureToolCallErrorCode(err)) : opts?.envelope ? opts.envelope(msg, status) : JSON.stringify({ error: {
|
|
1396
1994
|
message: msg,
|
|
1397
1995
|
type: errorTypeForStatus(status)
|
|
1398
1996
|
} }));
|
|
@@ -1437,16 +2035,21 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1437
2035
|
else res.destroy();
|
|
1438
2036
|
}
|
|
1439
2037
|
}
|
|
1440
|
-
|
|
2038
|
+
for (const { handler } of mountList) {
|
|
1441
2039
|
if (handler.setJournal) handler.setJournal(journal);
|
|
1442
2040
|
if (registry && handler.setRegistry) handler.setRegistry(registry);
|
|
2041
|
+
if (handler.setLogger) handler.setLogger(logger);
|
|
2042
|
+
if (handler instanceof MCPMock) handler.setReplaySpeed(defaults.replaySpeed);
|
|
1443
2043
|
}
|
|
1444
2044
|
if (registry) registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
|
|
1445
2045
|
const server = http$1.createServer((req, res) => {
|
|
1446
2046
|
requestScope.run(req, () => {
|
|
1447
|
-
|
|
2047
|
+
runWithToolArgumentsScope({
|
|
2048
|
+
strict: defaults.strictToolArguments === true,
|
|
2049
|
+
logger
|
|
2050
|
+
}, () => handleHttpRequest(req, res).catch((err) => {
|
|
1448
2051
|
routeError(req, res, err, req.url ?? "?");
|
|
1449
|
-
});
|
|
2052
|
+
}));
|
|
1450
2053
|
});
|
|
1451
2054
|
});
|
|
1452
2055
|
async function handleHttpRequest(req, res) {
|
|
@@ -1462,7 +2065,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1462
2065
|
if (recorded) return;
|
|
1463
2066
|
recorded = true;
|
|
1464
2067
|
try {
|
|
1465
|
-
const normalizedPath = normalizePathLabel(pathname,
|
|
2068
|
+
const normalizedPath = normalizePathLabel(pathname, mountList.map((m) => m.path));
|
|
1466
2069
|
const method = req.method ?? "UNKNOWN";
|
|
1467
2070
|
registry.incrementCounter("aimock_requests_total", {
|
|
1468
2071
|
method,
|
|
@@ -1483,7 +2086,36 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1483
2086
|
if (!res.writableFinished) recordOutcome(DESTROYED_STATUS_LABEL);
|
|
1484
2087
|
});
|
|
1485
2088
|
}
|
|
1486
|
-
|
|
2089
|
+
let parsedUrl;
|
|
2090
|
+
try {
|
|
2091
|
+
parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
2092
|
+
} catch (err) {
|
|
2093
|
+
const route = `${req.method ?? "?"} ${req.url ?? "?"}`;
|
|
2094
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
2095
|
+
logger.error(`${route}: ${msg}`);
|
|
2096
|
+
if (err instanceof Error && err.stack) logger.debug(err.stack);
|
|
2097
|
+
try {
|
|
2098
|
+
journal.add({
|
|
2099
|
+
method: req.method ?? "?",
|
|
2100
|
+
path: req.url ?? "?",
|
|
2101
|
+
headers: flattenHeaders(req.headers),
|
|
2102
|
+
body: null,
|
|
2103
|
+
response: {
|
|
2104
|
+
status: 400,
|
|
2105
|
+
fixture: null,
|
|
2106
|
+
source: "internal"
|
|
2107
|
+
}
|
|
2108
|
+
});
|
|
2109
|
+
} catch (jErr) {
|
|
2110
|
+
logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
2111
|
+
}
|
|
2112
|
+
setCorsHeaders(res);
|
|
2113
|
+
writeErrorResponse(res, 400, JSON.stringify({ error: {
|
|
2114
|
+
message: "Invalid request target",
|
|
2115
|
+
type: "invalid_request_error"
|
|
2116
|
+
} }));
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
1487
2119
|
pathname = parsedUrl.pathname;
|
|
1488
2120
|
const originalPathname = pathname;
|
|
1489
2121
|
const isOpenRouter = isOpenRouterPath(originalPathname);
|
|
@@ -1492,7 +2124,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1492
2124
|
handleOptions(res);
|
|
1493
2125
|
return;
|
|
1494
2126
|
}
|
|
1495
|
-
if (!(req.method === "GET" && (pathname === HEALTH_PATH || pathname === READY_PATH || pathname ===
|
|
2127
|
+
if (!(req.method === "GET" && (pathname === HEALTH_PATH || pathname === READY_PATH || pathname === METRICS_PATH))) {
|
|
1496
2128
|
if (!validateRequestApiKey(req, resolvedAuth.policy).ok) {
|
|
1497
2129
|
writeApiKeyHttpRejection(res, setCorsHeaders);
|
|
1498
2130
|
return;
|
|
@@ -1500,18 +2132,40 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1500
2132
|
markAuthenticatedRequest(req, resolvedAuth.policy);
|
|
1501
2133
|
}
|
|
1502
2134
|
if (pathname.startsWith(CONTROL_PREFIX)) {
|
|
1503
|
-
|
|
2135
|
+
ensureRawFixturePositions();
|
|
2136
|
+
await handleControlAPI(req, res, pathname, parsedUrl.searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults, mountList);
|
|
1504
2137
|
return;
|
|
1505
2138
|
}
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
}
|
|
2139
|
+
const misbehaviorHeader = defaults.misbehavior ? parseMisbehaviorHeader(req.headers["x-aimock-misbehavior"]) : void 0;
|
|
2140
|
+
if (misbehaviorHeader && !misbehaviorHeader.ok) {
|
|
2141
|
+
const detail = misbehaviorHeader.issue.message;
|
|
2142
|
+
const message = `aimock_misbehavior_invalid: ${detail}`;
|
|
2143
|
+
logger.error(`${req.method ?? "?"} ${pathname}: ${message}`);
|
|
2144
|
+
journal.add({
|
|
2145
|
+
method: req.method ?? "?",
|
|
2146
|
+
path: req.url ?? pathname,
|
|
2147
|
+
headers: flattenHeaders(req.headers),
|
|
2148
|
+
body: null,
|
|
2149
|
+
response: {
|
|
2150
|
+
status: 400,
|
|
2151
|
+
fixture: null,
|
|
2152
|
+
source: "internal",
|
|
2153
|
+
error: message
|
|
2154
|
+
}
|
|
2155
|
+
});
|
|
2156
|
+
setCorsHeaders(res);
|
|
2157
|
+
writeErrorResponse(res, 400, invalidMisbehaviorHeaderEnvelope(normalizeCompatPath(pathname), detail));
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
for (const { path: mountPath, handler } of mountList) if (pathname === mountPath || pathname.startsWith(mountPath + "/")) {
|
|
2161
|
+
const subPath = pathname.slice(mountPath.length) || "/";
|
|
2162
|
+
if (await handler.handleRequest(req, res, subPath)) return;
|
|
1511
2163
|
}
|
|
1512
2164
|
if (pathname === OLLAMA_CHAT_PATH && req.method === "POST") {
|
|
1513
2165
|
try {
|
|
1514
|
-
|
|
2166
|
+
const raw = await readBody(req);
|
|
2167
|
+
ensureRawFixturePositions();
|
|
2168
|
+
await handleOllama(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
1515
2169
|
} catch (err) {
|
|
1516
2170
|
routeError(req, res, err, pathname);
|
|
1517
2171
|
}
|
|
@@ -1519,7 +2173,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1519
2173
|
}
|
|
1520
2174
|
if (pathname === OLLAMA_GENERATE_PATH && req.method === "POST") {
|
|
1521
2175
|
try {
|
|
1522
|
-
|
|
2176
|
+
const raw = await readBody(req);
|
|
2177
|
+
ensureRawFixturePositions();
|
|
2178
|
+
await handleOllamaGenerate(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
1523
2179
|
} catch (err) {
|
|
1524
2180
|
routeError(req, res, err, pathname);
|
|
1525
2181
|
}
|
|
@@ -1527,7 +2183,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1527
2183
|
}
|
|
1528
2184
|
if ((pathname === OLLAMA_EMBEDDINGS_PATH || pathname === OLLAMA_EMBED_PATH) && req.method === "POST") {
|
|
1529
2185
|
try {
|
|
1530
|
-
|
|
2186
|
+
const raw = await readBody(req);
|
|
2187
|
+
ensureRawFixturePositions();
|
|
2188
|
+
await handleOllamaEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
1531
2189
|
} catch (err) {
|
|
1532
2190
|
routeError(req, res, err, pathname);
|
|
1533
2191
|
}
|
|
@@ -1560,6 +2218,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1560
2218
|
}
|
|
1561
2219
|
if (pathname === OPENROUTER_VIDEO_MODELS_PATH && req.method === "GET") {
|
|
1562
2220
|
try {
|
|
2221
|
+
ensureRawFixturePositions();
|
|
1563
2222
|
await handleOpenRouterVideoModels(req, res, fixtures, journal, defaults, setCorsHeaders);
|
|
1564
2223
|
} catch (err) {
|
|
1565
2224
|
routeError(req, res, err, pathname);
|
|
@@ -1569,6 +2228,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1569
2228
|
const openRouterVideoStatusMatch = pathname.match(OPENROUTER_VIDEO_STATUS_RE);
|
|
1570
2229
|
if (openRouterVideoStatusMatch && req.method === "GET") {
|
|
1571
2230
|
try {
|
|
2231
|
+
ensureRawFixturePositions();
|
|
1572
2232
|
await handleOpenRouterVideoStatus(req, res, openRouterVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
|
|
1573
2233
|
} catch (err) {
|
|
1574
2234
|
routeError(req, res, err, pathname);
|
|
@@ -1578,7 +2238,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1578
2238
|
if (pathname === OPENROUTER_VIDEOS_PATH && req.method === "POST") {
|
|
1579
2239
|
setCorsHeaders(res);
|
|
1580
2240
|
try {
|
|
1581
|
-
|
|
2241
|
+
const raw = await readBody(req);
|
|
2242
|
+
ensureRawFixturePositions();
|
|
2243
|
+
await handleOpenRouterVideoCreate(req, res, raw, fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
|
|
1582
2244
|
} catch (err) {
|
|
1583
2245
|
routeError(req, res, err, pathname);
|
|
1584
2246
|
}
|
|
@@ -1587,6 +2249,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1587
2249
|
const bytePlusVideoStatusMatch = pathname.match(BYTEPLUS_VIDEO_STATUS_RE);
|
|
1588
2250
|
if (bytePlusVideoStatusMatch && req.method === "GET") {
|
|
1589
2251
|
try {
|
|
2252
|
+
ensureRawFixturePositions();
|
|
1590
2253
|
await handleBytePlusVideoStatus(req, res, bytePlusVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
|
|
1591
2254
|
} catch (err) {
|
|
1592
2255
|
routeError(req, res, err, pathname);
|
|
@@ -1596,21 +2259,24 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1596
2259
|
if (BYTEPLUS_VIDEO_SUBMIT_RE.test(pathname) && req.method === "POST") {
|
|
1597
2260
|
setCorsHeaders(res);
|
|
1598
2261
|
try {
|
|
1599
|
-
|
|
2262
|
+
const raw = await readBody(req);
|
|
2263
|
+
ensureRawFixturePositions();
|
|
2264
|
+
await handleBytePlusVideoCreate(req, res, raw, fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
|
|
1600
2265
|
} catch (err) {
|
|
1601
2266
|
routeError(req, res, err, pathname);
|
|
1602
2267
|
}
|
|
1603
2268
|
return;
|
|
1604
2269
|
}
|
|
1605
|
-
if (pathname ===
|
|
2270
|
+
if (pathname === OPENROUTER_MODELS_PATH && req.method === "GET") {
|
|
2271
|
+
ensureRawFixturePositions();
|
|
1606
2272
|
handleOpenRouterModels(req, res, fixtures, journal, defaults, setCorsHeaders);
|
|
1607
2273
|
return;
|
|
1608
2274
|
}
|
|
1609
|
-
if (pathname ===
|
|
2275
|
+
if (pathname === OPENROUTER_KEY_PATH && req.method === "GET") {
|
|
1610
2276
|
handleOpenRouterKey(req, res, journal, setCorsHeaders);
|
|
1611
2277
|
return;
|
|
1612
2278
|
}
|
|
1613
|
-
if (pathname ===
|
|
2279
|
+
if (pathname === OPENROUTER_CREDITS_PATH && req.method === "GET") {
|
|
1614
2280
|
handleOpenRouterCredits(req, res, journal, setCorsHeaders);
|
|
1615
2281
|
return;
|
|
1616
2282
|
}
|
|
@@ -1623,12 +2289,12 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1623
2289
|
if (!azureDeploymentId) pathname = normalizeCompatPath(pathname, logger);
|
|
1624
2290
|
if (pathname === HEALTH_PATH && req.method === "GET") {
|
|
1625
2291
|
setCorsHeaders(res);
|
|
1626
|
-
if (
|
|
2292
|
+
if (mountList.length > 0) {
|
|
1627
2293
|
const services = { llm: {
|
|
1628
2294
|
status: "ok",
|
|
1629
2295
|
fixtures: fixtures.length
|
|
1630
2296
|
} };
|
|
1631
|
-
for (const { path: mountPath, handler } of
|
|
2297
|
+
for (const { path: mountPath, handler } of mountList) if (handler.health) {
|
|
1632
2298
|
const name = mountPath.replace(/^\//, "");
|
|
1633
2299
|
services[name] = handler.health();
|
|
1634
2300
|
}
|
|
@@ -1649,7 +2315,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1649
2315
|
res.end(JSON.stringify({ status: "ready" }));
|
|
1650
2316
|
return;
|
|
1651
2317
|
}
|
|
1652
|
-
if (pathname ===
|
|
2318
|
+
if (pathname === METRICS_PATH && req.method === "GET") {
|
|
1653
2319
|
if (!registry) {
|
|
1654
2320
|
handleNotFound(res, "Not found");
|
|
1655
2321
|
return;
|
|
@@ -1797,14 +2463,140 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1797
2463
|
}
|
|
1798
2464
|
return;
|
|
1799
2465
|
}
|
|
2466
|
+
const vsSearchMatch = pathname.match(VECTOR_STORES_SEARCH_RE);
|
|
2467
|
+
if (vsSearchMatch && req.method === "POST") {
|
|
2468
|
+
try {
|
|
2469
|
+
const raw = await readBody(req);
|
|
2470
|
+
await handleVectorStoresSearch(req, res, vsSearchMatch[1], raw, journal, defaults, setCorsHeaders);
|
|
2471
|
+
} catch (err) {
|
|
2472
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2473
|
+
}
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
const vsBatchFilesMatch = pathname.match(VECTOR_STORES_BATCH_FILES_RE);
|
|
2477
|
+
if (vsBatchFilesMatch && req.method === "GET") {
|
|
2478
|
+
try {
|
|
2479
|
+
await handleVectorFileBatchesFiles(req, res, vsBatchFilesMatch[1], vsBatchFilesMatch[2], journal, defaults, setCorsHeaders);
|
|
2480
|
+
} catch (err) {
|
|
2481
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2482
|
+
}
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
const vsBatchCancelMatch = pathname.match(VECTOR_STORES_BATCH_CANCEL_RE);
|
|
2486
|
+
if (vsBatchCancelMatch && req.method === "POST") {
|
|
2487
|
+
try {
|
|
2488
|
+
await readBody(req);
|
|
2489
|
+
await handleVectorFileBatchesCancel(req, res, vsBatchCancelMatch[1], vsBatchCancelMatch[2], journal, defaults, setCorsHeaders);
|
|
2490
|
+
} catch (err) {
|
|
2491
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2492
|
+
}
|
|
2493
|
+
return;
|
|
2494
|
+
}
|
|
2495
|
+
const vsBatchMatch = pathname.match(VECTOR_STORES_BATCH_RE);
|
|
2496
|
+
if (vsBatchMatch && req.method === "GET") {
|
|
2497
|
+
try {
|
|
2498
|
+
await handleVectorFileBatchesRetrieve(req, res, vsBatchMatch[1], vsBatchMatch[2], journal, defaults, setCorsHeaders);
|
|
2499
|
+
} catch (err) {
|
|
2500
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2501
|
+
}
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2504
|
+
const vsFileMatch = pathname.match(VECTOR_STORES_FILE_RE);
|
|
2505
|
+
if (vsFileMatch && req.method === "GET") {
|
|
2506
|
+
try {
|
|
2507
|
+
await handleVectorStoreFilesRetrieve(req, res, vsFileMatch[1], vsFileMatch[2], journal, defaults, setCorsHeaders);
|
|
2508
|
+
} catch (err) {
|
|
2509
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2510
|
+
}
|
|
2511
|
+
return;
|
|
2512
|
+
}
|
|
2513
|
+
if (vsFileMatch && req.method === "DELETE") {
|
|
2514
|
+
try {
|
|
2515
|
+
await handleVectorStoreFilesDelete(req, res, vsFileMatch[1], vsFileMatch[2], journal, defaults, setCorsHeaders);
|
|
2516
|
+
} catch (err) {
|
|
2517
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2518
|
+
}
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
const vsIdMatch = pathname.match(VECTOR_STORES_ID_RE);
|
|
2522
|
+
if (vsIdMatch && req.method === "GET") {
|
|
2523
|
+
try {
|
|
2524
|
+
await handleVectorStoresRetrieve(req, res, vsIdMatch[1], journal, defaults, setCorsHeaders);
|
|
2525
|
+
} catch (err) {
|
|
2526
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2527
|
+
}
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
if (vsIdMatch && req.method === "POST") {
|
|
2531
|
+
try {
|
|
2532
|
+
const raw = await readBody(req);
|
|
2533
|
+
await handleVectorStoresModify(req, res, vsIdMatch[1], raw, journal, defaults, setCorsHeaders);
|
|
2534
|
+
} catch (err) {
|
|
2535
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2536
|
+
}
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
if (vsIdMatch && req.method === "DELETE") {
|
|
2540
|
+
try {
|
|
2541
|
+
await handleVectorStoresDelete(req, res, vsIdMatch[1], journal, defaults, setCorsHeaders);
|
|
2542
|
+
} catch (err) {
|
|
2543
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2544
|
+
}
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
if (VECTOR_STORES_FILES_RE.test(pathname) && req.method === "POST") {
|
|
2548
|
+
try {
|
|
2549
|
+
const raw = await readBody(req);
|
|
2550
|
+
const storeId = pathname.split("/")[3];
|
|
2551
|
+
await handleVectorStoreFilesCreate(req, res, storeId, raw, journal, defaults, setCorsHeaders);
|
|
2552
|
+
} catch (err) {
|
|
2553
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2554
|
+
}
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
if (VECTOR_STORES_FILES_RE.test(pathname) && req.method === "GET") {
|
|
2558
|
+
try {
|
|
2559
|
+
const storeId = pathname.split("/")[3];
|
|
2560
|
+
await handleVectorStoreFilesList(req, res, storeId, journal, defaults, setCorsHeaders);
|
|
2561
|
+
} catch (err) {
|
|
2562
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2563
|
+
}
|
|
2564
|
+
return;
|
|
2565
|
+
}
|
|
2566
|
+
if (VECTOR_STORES_FILE_BATCHES_RE.test(pathname) && req.method === "POST") {
|
|
2567
|
+
try {
|
|
2568
|
+
const raw = await readBody(req);
|
|
2569
|
+
const storeId = pathname.split("/")[3];
|
|
2570
|
+
await handleVectorFileBatchesCreate(req, res, storeId, raw, journal, defaults, setCorsHeaders);
|
|
2571
|
+
} catch (err) {
|
|
2572
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2573
|
+
}
|
|
2574
|
+
return;
|
|
2575
|
+
}
|
|
2576
|
+
if (pathname === VECTOR_STORES_PATH && req.method === "GET") {
|
|
2577
|
+
try {
|
|
2578
|
+
await handleVectorStoresList(req, res, journal, defaults, setCorsHeaders);
|
|
2579
|
+
} catch (err) {
|
|
2580
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2581
|
+
}
|
|
2582
|
+
return;
|
|
2583
|
+
}
|
|
2584
|
+
if (pathname === VECTOR_STORES_PATH && req.method === "POST") {
|
|
2585
|
+
try {
|
|
2586
|
+
await handleVectorStoresCreate(req, res, await readBody(req), journal, defaults, setCorsHeaders);
|
|
2587
|
+
} catch (err) {
|
|
2588
|
+
routeError(req, res, err, pathname, { service: "vector-stores" });
|
|
2589
|
+
}
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
1800
2592
|
if (pathname === REQUESTS_PATH) {
|
|
1801
2593
|
setCorsHeaders(res);
|
|
1802
2594
|
if (req.method === "GET") {
|
|
1803
2595
|
const limitParam = parsedUrl.searchParams.get("limit");
|
|
1804
2596
|
let opts;
|
|
1805
2597
|
if (limitParam) {
|
|
1806
|
-
const limit =
|
|
1807
|
-
if (Number.
|
|
2598
|
+
const limit = Number(limitParam);
|
|
2599
|
+
if (!/^\+?\d+$/.test(limitParam.trim()) || !Number.isSafeInteger(limit) || limit <= 0) {
|
|
1808
2600
|
writeErrorResponse(res, 400, JSON.stringify({ error: {
|
|
1809
2601
|
message: `Invalid limit parameter: "${limitParam}"`,
|
|
1810
2602
|
type: "invalid_request_error"
|
|
@@ -1815,7 +2607,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1815
2607
|
}
|
|
1816
2608
|
const entries = journal.getAll(opts);
|
|
1817
2609
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1818
|
-
res.end(
|
|
2610
|
+
res.end(journalJson(entries, journal));
|
|
1819
2611
|
return;
|
|
1820
2612
|
}
|
|
1821
2613
|
if (req.method === "DELETE") {
|
|
@@ -1829,7 +2621,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1829
2621
|
}
|
|
1830
2622
|
if (pathname === RESPONSES_PATH && req.method === "POST") {
|
|
1831
2623
|
try {
|
|
1832
|
-
|
|
2624
|
+
const raw = await readBody(req);
|
|
2625
|
+
ensureRawFixturePositions();
|
|
2626
|
+
await handleResponses(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
1833
2627
|
} catch (err) {
|
|
1834
2628
|
routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
1835
2629
|
}
|
|
@@ -1837,7 +2631,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1837
2631
|
}
|
|
1838
2632
|
if (pathname === MESSAGES_PATH && req.method === "POST") {
|
|
1839
2633
|
try {
|
|
1840
|
-
|
|
2634
|
+
const raw = await readBody(req);
|
|
2635
|
+
ensureRawFixturePositions();
|
|
2636
|
+
await handleMessages(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
1841
2637
|
} catch (err) {
|
|
1842
2638
|
routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
1843
2639
|
}
|
|
@@ -1845,7 +2641,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1845
2641
|
}
|
|
1846
2642
|
if (pathname === COHERE_CHAT_PATH && req.method === "POST") {
|
|
1847
2643
|
try {
|
|
1848
|
-
|
|
2644
|
+
const raw = await readBody(req);
|
|
2645
|
+
ensureRawFixturePositions();
|
|
2646
|
+
await handleCohere(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
1849
2647
|
} catch (err) {
|
|
1850
2648
|
routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
1851
2649
|
}
|
|
@@ -1853,7 +2651,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1853
2651
|
}
|
|
1854
2652
|
if (pathname === COHERE_EMBED_PATH && req.method === "POST") {
|
|
1855
2653
|
try {
|
|
1856
|
-
|
|
2654
|
+
const raw = await readBody(req);
|
|
2655
|
+
ensureRawFixturePositions();
|
|
2656
|
+
await handleCohereEmbed(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
1857
2657
|
} catch (err) {
|
|
1858
2658
|
routeError(req, res, err, pathname);
|
|
1859
2659
|
}
|
|
@@ -1873,7 +2673,8 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1873
2673
|
} catch (err) {
|
|
1874
2674
|
if (!(err instanceof SyntaxError)) defaults.logger.error(`Unexpected error in Azure model injection: ${err instanceof Error ? err.message : String(err)}`);
|
|
1875
2675
|
}
|
|
1876
|
-
|
|
2676
|
+
ensureRawFixturePositions();
|
|
2677
|
+
await handleEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders, embeddingsProvider, originalPathname === EMBEDDINGS_PATH);
|
|
1877
2678
|
} catch (err) {
|
|
1878
2679
|
routeError(req, res, err, pathname);
|
|
1879
2680
|
}
|
|
@@ -1881,7 +2682,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1881
2682
|
}
|
|
1882
2683
|
if (pathname === IMAGES_PATH && req.method === "POST") {
|
|
1883
2684
|
try {
|
|
1884
|
-
|
|
2685
|
+
const raw = await readBody(req);
|
|
2686
|
+
ensureRawFixturePositions();
|
|
2687
|
+
await handleImages(req, res, raw, fixtures, journal, defaults, setCorsHeaders, "openai", void 0, isBytePlusArk ? "byteplus" : void 0);
|
|
1885
2688
|
} catch (err) {
|
|
1886
2689
|
routeError(req, res, err, pathname);
|
|
1887
2690
|
}
|
|
@@ -1889,7 +2692,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1889
2692
|
}
|
|
1890
2693
|
if (pathname === IMAGES_EDIT_PATH && req.method === "POST") {
|
|
1891
2694
|
try {
|
|
1892
|
-
|
|
2695
|
+
const raw = await readBody(req);
|
|
2696
|
+
ensureRawFixturePositions();
|
|
2697
|
+
await handleImageEdit(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
1893
2698
|
} catch (err) {
|
|
1894
2699
|
routeError(req, res, err, pathname);
|
|
1895
2700
|
}
|
|
@@ -1897,7 +2702,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1897
2702
|
}
|
|
1898
2703
|
if (pathname === IMAGES_VARIATIONS_PATH && req.method === "POST") {
|
|
1899
2704
|
try {
|
|
1900
|
-
|
|
2705
|
+
const raw = await readBody(req);
|
|
2706
|
+
ensureRawFixturePositions();
|
|
2707
|
+
await handleImageVariations(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
1901
2708
|
} catch (err) {
|
|
1902
2709
|
routeError(req, res, err, pathname);
|
|
1903
2710
|
}
|
|
@@ -1905,7 +2712,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1905
2712
|
}
|
|
1906
2713
|
if (pathname === SPEECH_PATH && req.method === "POST") {
|
|
1907
2714
|
try {
|
|
1908
|
-
|
|
2715
|
+
const raw = await readBody(req);
|
|
2716
|
+
ensureRawFixturePositions();
|
|
2717
|
+
await handleSpeech(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
1909
2718
|
} catch (err) {
|
|
1910
2719
|
routeError(req, res, err, pathname);
|
|
1911
2720
|
}
|
|
@@ -1913,7 +2722,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1913
2722
|
}
|
|
1914
2723
|
if (pathname === TRANSCRIPTIONS_PATH && req.method === "POST") {
|
|
1915
2724
|
try {
|
|
1916
|
-
|
|
2725
|
+
const raw = await readBody(req);
|
|
2726
|
+
ensureRawFixturePositions();
|
|
2727
|
+
await handleTranscription(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
1917
2728
|
} catch (err) {
|
|
1918
2729
|
routeError(req, res, err, pathname);
|
|
1919
2730
|
}
|
|
@@ -1921,7 +2732,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1921
2732
|
}
|
|
1922
2733
|
if (pathname === TRANSLATIONS_PATH && req.method === "POST") {
|
|
1923
2734
|
try {
|
|
1924
|
-
|
|
2735
|
+
const raw = await readBody(req);
|
|
2736
|
+
ensureRawFixturePositions();
|
|
2737
|
+
await handleTranscription(req, res, raw, fixtures, journal, defaults, setCorsHeaders, "translation");
|
|
1925
2738
|
} catch (err) {
|
|
1926
2739
|
routeError(req, res, err, pathname);
|
|
1927
2740
|
}
|
|
@@ -1930,7 +2743,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1930
2743
|
if (pathname === GROK_VIDEO_SUBMIT_PATH && req.method === "POST") {
|
|
1931
2744
|
setCorsHeaders(res);
|
|
1932
2745
|
try {
|
|
1933
|
-
|
|
2746
|
+
const raw = await readBody(req);
|
|
2747
|
+
ensureRawFixturePositions();
|
|
2748
|
+
await handleGrokVideoCreate(req, res, raw, fixtures, journal, defaults, setCorsHeaders, grokVideoJobs);
|
|
1934
2749
|
} catch (err) {
|
|
1935
2750
|
routeError(req, res, err, pathname);
|
|
1936
2751
|
}
|
|
@@ -1938,7 +2753,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1938
2753
|
}
|
|
1939
2754
|
if (pathname === VIDEOS_PATH && req.method === "POST") {
|
|
1940
2755
|
try {
|
|
1941
|
-
|
|
2756
|
+
const raw = await readBody(req);
|
|
2757
|
+
ensureRawFixturePositions();
|
|
2758
|
+
await handleVideoCreate(req, res, raw, fixtures, journal, defaults, setCorsHeaders, videoStates);
|
|
1942
2759
|
} catch (err) {
|
|
1943
2760
|
routeError(req, res, err, pathname);
|
|
1944
2761
|
}
|
|
@@ -1947,6 +2764,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1947
2764
|
const grokVideoStatusMatch = pathname.match(GROK_VIDEO_STATUS_RE);
|
|
1948
2765
|
if (grokVideoStatusMatch && grokVideoStatusMatch[1] !== "generations" && req.method === "GET") {
|
|
1949
2766
|
try {
|
|
2767
|
+
ensureRawFixturePositions();
|
|
1950
2768
|
await handleGrokVideoStatus(req, res, grokVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, grokVideoJobs, videoStates);
|
|
1951
2769
|
} catch (err) {
|
|
1952
2770
|
routeError(req, res, err, pathname);
|
|
@@ -1957,7 +2775,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1957
2775
|
if (veoPredictMatch && req.method === "POST") {
|
|
1958
2776
|
setCorsHeaders(res);
|
|
1959
2777
|
try {
|
|
1960
|
-
|
|
2778
|
+
const raw = await readBody(req);
|
|
2779
|
+
ensureRawFixturePositions();
|
|
2780
|
+
await handleVeoVideoCreate(req, res, raw, veoPredictMatch[1], fixtures, journal, defaults, setCorsHeaders, veoVideoJobs);
|
|
1961
2781
|
} catch (err) {
|
|
1962
2782
|
routeError(req, res, err, pathname);
|
|
1963
2783
|
}
|
|
@@ -1966,6 +2786,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1966
2786
|
const veoOperationMatch = pathname.match(VEO_OPERATION_RE);
|
|
1967
2787
|
if (veoOperationMatch && req.method === "GET") {
|
|
1968
2788
|
try {
|
|
2789
|
+
ensureRawFixturePositions();
|
|
1969
2790
|
await handleVeoVideoStatus(req, res, veoOperationMatch[1], fixtures, journal, defaults, setCorsHeaders, veoVideoJobs);
|
|
1970
2791
|
} catch (err) {
|
|
1971
2792
|
routeError(req, res, err, pathname);
|
|
@@ -1976,7 +2797,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1976
2797
|
if (geminiPredictMatch && req.method === "POST") {
|
|
1977
2798
|
const predictModel = geminiPredictMatch[1];
|
|
1978
2799
|
try {
|
|
1979
|
-
|
|
2800
|
+
const raw = await readBody(req);
|
|
2801
|
+
ensureRawFixturePositions();
|
|
2802
|
+
await handleImages(req, res, raw, fixtures, journal, defaults, setCorsHeaders, "gemini", predictModel);
|
|
1980
2803
|
} catch (err) {
|
|
1981
2804
|
routeError(req, res, err, pathname);
|
|
1982
2805
|
}
|
|
@@ -1984,7 +2807,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1984
2807
|
}
|
|
1985
2808
|
if (pathname === GEMINI_INTERACTIONS_PATH && req.method === "POST") {
|
|
1986
2809
|
try {
|
|
1987
|
-
|
|
2810
|
+
const raw = await readBody(req);
|
|
2811
|
+
ensureRawFixturePositions();
|
|
2812
|
+
await handleGeminiInteractions(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
1988
2813
|
} catch (err) {
|
|
1989
2814
|
routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
1990
2815
|
}
|
|
@@ -1994,7 +2819,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1994
2819
|
if (geminiEmbedMatch && req.method === "POST") {
|
|
1995
2820
|
const embedModel = geminiEmbedMatch[1];
|
|
1996
2821
|
try {
|
|
1997
|
-
|
|
2822
|
+
const raw = await readBody(req);
|
|
2823
|
+
ensureRawFixturePositions();
|
|
2824
|
+
await handleGeminiEmbedContent(req, res, raw, embedModel, fixtures, journal, defaults, setCorsHeaders);
|
|
1998
2825
|
} catch (err) {
|
|
1999
2826
|
routeError(req, res, err, pathname);
|
|
2000
2827
|
}
|
|
@@ -2005,7 +2832,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2005
2832
|
const geminiModel = geminiMatch[1];
|
|
2006
2833
|
const streaming = geminiMatch[2] === "streamGenerateContent";
|
|
2007
2834
|
try {
|
|
2008
|
-
|
|
2835
|
+
const raw = await readBody(req);
|
|
2836
|
+
ensureRawFixturePositions();
|
|
2837
|
+
await handleGemini(req, res, raw, geminiModel, streaming, fixtures, journal, defaults, setCorsHeaders);
|
|
2009
2838
|
} catch (err) {
|
|
2010
2839
|
routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
2011
2840
|
}
|
|
@@ -2016,7 +2845,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2016
2845
|
const vertexModel = vertexMatch[1];
|
|
2017
2846
|
const streaming = vertexMatch[2] === "streamGenerateContent";
|
|
2018
2847
|
try {
|
|
2019
|
-
|
|
2848
|
+
const raw = await readBody(req);
|
|
2849
|
+
ensureRawFixturePositions();
|
|
2850
|
+
await handleGemini(req, res, raw, vertexModel, streaming, fixtures, journal, defaults, setCorsHeaders, "vertexai");
|
|
2020
2851
|
} catch (err) {
|
|
2021
2852
|
routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
2022
2853
|
}
|
|
@@ -2026,7 +2857,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2026
2857
|
if (bedrockMatch && req.method === "POST") {
|
|
2027
2858
|
const bedrockModelId = bedrockMatch[1];
|
|
2028
2859
|
try {
|
|
2029
|
-
|
|
2860
|
+
const raw = await readBody(req);
|
|
2861
|
+
ensureRawFixturePositions();
|
|
2862
|
+
await handleBedrock(req, res, raw, bedrockModelId, fixtures, journal, defaults, setCorsHeaders);
|
|
2030
2863
|
} catch (err) {
|
|
2031
2864
|
routeError(req, res, err, pathname);
|
|
2032
2865
|
}
|
|
@@ -2036,7 +2869,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2036
2869
|
if (bedrockStreamMatch && req.method === "POST") {
|
|
2037
2870
|
const bedrockModelId = bedrockStreamMatch[1];
|
|
2038
2871
|
try {
|
|
2039
|
-
|
|
2872
|
+
const raw = await readBody(req);
|
|
2873
|
+
ensureRawFixturePositions();
|
|
2874
|
+
await handleBedrockStream(req, res, raw, bedrockModelId, fixtures, journal, defaults, setCorsHeaders);
|
|
2040
2875
|
} catch (err) {
|
|
2041
2876
|
routeError(req, res, err, pathname);
|
|
2042
2877
|
}
|
|
@@ -2046,7 +2881,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2046
2881
|
if (converseMatch && req.method === "POST") {
|
|
2047
2882
|
const converseModelId = converseMatch[1];
|
|
2048
2883
|
try {
|
|
2049
|
-
|
|
2884
|
+
const raw = await readBody(req);
|
|
2885
|
+
ensureRawFixturePositions();
|
|
2886
|
+
await handleConverse(req, res, raw, converseModelId, fixtures, journal, defaults, setCorsHeaders);
|
|
2050
2887
|
} catch (err) {
|
|
2051
2888
|
routeError(req, res, err, pathname);
|
|
2052
2889
|
}
|
|
@@ -2056,7 +2893,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2056
2893
|
if (converseStreamMatch && req.method === "POST") {
|
|
2057
2894
|
const converseStreamModelId = converseStreamMatch[1];
|
|
2058
2895
|
try {
|
|
2059
|
-
|
|
2896
|
+
const raw = await readBody(req);
|
|
2897
|
+
ensureRawFixturePositions();
|
|
2898
|
+
await handleConverseStream(req, res, raw, converseStreamModelId, fixtures, journal, defaults, setCorsHeaders);
|
|
2060
2899
|
} catch (err) {
|
|
2061
2900
|
routeError(req, res, err, pathname);
|
|
2062
2901
|
}
|
|
@@ -2089,7 +2928,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2089
2928
|
if (pathname === ELEVENLABS_SOUND_GENERATION_PATH && req.method === "POST") {
|
|
2090
2929
|
setCorsHeaders(res);
|
|
2091
2930
|
try {
|
|
2092
|
-
|
|
2931
|
+
const raw = await readBody(req);
|
|
2932
|
+
ensureRawFixturePositions();
|
|
2933
|
+
await handleElevenLabsAudio(req, res, raw, fixtures, defaults, journal, "sound-generation");
|
|
2093
2934
|
} catch (err) {
|
|
2094
2935
|
routeError(req, res, err, pathname);
|
|
2095
2936
|
}
|
|
@@ -2098,7 +2939,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2098
2939
|
if (pathname === ELEVENLABS_VOICE_DESIGN_PATH && req.method === "POST") {
|
|
2099
2940
|
setCorsHeaders(res);
|
|
2100
2941
|
try {
|
|
2101
|
-
|
|
2942
|
+
const raw = await readBody(req);
|
|
2943
|
+
ensureRawFixturePositions();
|
|
2944
|
+
await handleElevenLabsVoiceDesign(req, res, raw, fixtures, defaults, journal);
|
|
2102
2945
|
} catch (err) {
|
|
2103
2946
|
routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
|
|
2104
2947
|
}
|
|
@@ -2107,7 +2950,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2107
2950
|
if (pathname === ELEVENLABS_VOICE_CREATE_PATH && req.method === "POST") {
|
|
2108
2951
|
setCorsHeaders(res);
|
|
2109
2952
|
try {
|
|
2110
|
-
|
|
2953
|
+
const raw = await readBody(req);
|
|
2954
|
+
ensureRawFixturePositions();
|
|
2955
|
+
await handleElevenLabsVoiceCreate(req, res, raw, fixtures, defaults, journal);
|
|
2111
2956
|
} catch (err) {
|
|
2112
2957
|
routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
|
|
2113
2958
|
}
|
|
@@ -2138,8 +2983,13 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2138
2983
|
return;
|
|
2139
2984
|
}
|
|
2140
2985
|
try {
|
|
2141
|
-
if (req.method === "GET")
|
|
2142
|
-
|
|
2986
|
+
if (req.method === "GET") {
|
|
2987
|
+
ensureRawFixturePositions();
|
|
2988
|
+
await handleElevenLabsVoiceGet(req, res, voiceId, fixtures, defaults, journal);
|
|
2989
|
+
} else {
|
|
2990
|
+
ensureRawFixturePositions();
|
|
2991
|
+
await handleElevenLabsVoiceDelete(req, res, voiceId, fixtures, defaults, journal);
|
|
2992
|
+
}
|
|
2143
2993
|
} catch (err) {
|
|
2144
2994
|
routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
|
|
2145
2995
|
}
|
|
@@ -2150,7 +3000,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2150
3000
|
setCorsHeaders(res);
|
|
2151
3001
|
const voiceId = elevenLabsTTSMatch[1];
|
|
2152
3002
|
try {
|
|
2153
|
-
|
|
3003
|
+
const raw = await readBody(req);
|
|
3004
|
+
ensureRawFixturePositions();
|
|
3005
|
+
await handleElevenLabsTTS(req, res, raw, fixtures, defaults, journal, voiceId);
|
|
2154
3006
|
} catch (err) {
|
|
2155
3007
|
routeError(req, res, err, pathname);
|
|
2156
3008
|
}
|
|
@@ -2161,7 +3013,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2161
3013
|
setCorsHeaders(res);
|
|
2162
3014
|
const musicSubType = musicMatch[1] ?? "music";
|
|
2163
3015
|
try {
|
|
2164
|
-
|
|
3016
|
+
const raw = await readBody(req);
|
|
3017
|
+
ensureRawFixturePositions();
|
|
3018
|
+
await handleElevenLabsAudio(req, res, raw, fixtures, defaults, journal, musicSubType);
|
|
2165
3019
|
} catch (err) {
|
|
2166
3020
|
routeError(req, res, err, pathname);
|
|
2167
3021
|
}
|
|
@@ -2192,6 +3046,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2192
3046
|
return;
|
|
2193
3047
|
}
|
|
2194
3048
|
}
|
|
3049
|
+
ensureRawFixturePositions();
|
|
2195
3050
|
if (await handleFal(req, res, raw, pathname, fixtures, defaults, journal) === "handled") return;
|
|
2196
3051
|
} catch (err) {
|
|
2197
3052
|
routeError(req, res, err, pathname);
|
|
@@ -2201,7 +3056,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2201
3056
|
if (pathname.match(FAL_QUEUE_SUBMIT_RE) && req.method === "POST") {
|
|
2202
3057
|
setCorsHeaders(res);
|
|
2203
3058
|
try {
|
|
2204
|
-
|
|
3059
|
+
const raw = falBody ?? await readBody(req);
|
|
3060
|
+
ensureRawFixturePositions();
|
|
3061
|
+
await handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
|
|
2205
3062
|
} catch (err) {
|
|
2206
3063
|
routeError(req, res, err, pathname);
|
|
2207
3064
|
}
|
|
@@ -2228,6 +3085,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2228
3085
|
}, "internal", defaults.registry, defaults.logger);
|
|
2229
3086
|
return;
|
|
2230
3087
|
}
|
|
3088
|
+
ensureRawFixturePositions();
|
|
2231
3089
|
await handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
|
|
2232
3090
|
} catch (err) {
|
|
2233
3091
|
routeError(req, res, err, pathname);
|
|
@@ -2237,13 +3095,17 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2237
3095
|
if (pathname.match(FAL_RUN_RE) && req.method === "POST") {
|
|
2238
3096
|
setCorsHeaders(res);
|
|
2239
3097
|
try {
|
|
2240
|
-
|
|
3098
|
+
const raw = falBody ?? await readBody(req);
|
|
3099
|
+
ensureRawFixturePositions();
|
|
3100
|
+
await handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
|
|
2241
3101
|
} catch (err) {
|
|
2242
3102
|
routeError(req, res, err, pathname);
|
|
2243
3103
|
}
|
|
2244
3104
|
return;
|
|
2245
3105
|
}
|
|
2246
3106
|
if (pathname !== COMPLETIONS_PATH) {
|
|
3107
|
+
const catalogMatch = matchRouteDefinition(req.method ?? "", pathname);
|
|
3108
|
+
if (catalogMatch && !(pathname === METRICS_PATH && !registry)) defaults.logger.warn(`Route drift: ${req.method ?? "?"} ${pathname} matches the route catalog (${catalogMatch.method} ${catalogMatch.path} — ${catalogMatch.description}) but no dispatcher branch served it. The catalog entry or the dispatcher is stale — see route-registry.ts.`);
|
|
2247
3109
|
handleNotFound(res, "Not found");
|
|
2248
3110
|
return;
|
|
2249
3111
|
}
|
|
@@ -2253,13 +3115,14 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2253
3115
|
}
|
|
2254
3116
|
const completionsProvider = azureDeploymentId ? "azure" : isOpenRouter ? "openrouter" : isBytePlusArk ? "byteplus" : "openai";
|
|
2255
3117
|
try {
|
|
2256
|
-
await handleCompletions(req, res, fixtures, journal, defaults, azureDeploymentId, completionsProvider, isOpenRouter);
|
|
3118
|
+
await handleCompletions(req, res, fixtures, journal, defaults, () => lastJournalEntryFor(req), azureDeploymentId, completionsProvider, isOpenRouter, ensureRawFixturePositions, originalPathname.startsWith("/api/v3/"));
|
|
2257
3119
|
} catch (err) {
|
|
2258
3120
|
routeError(req, res, err, pathname, {
|
|
2259
3121
|
envelope: (m, status) => isOpenRouter ? serializeOpenRouterError(status, m) : JSON.stringify({ error: {
|
|
2260
3122
|
message: m,
|
|
2261
3123
|
type: errorTypeForStatus(status)
|
|
2262
3124
|
} }),
|
|
3125
|
+
...isOpenRouter ? { toolCallEnvelope: (m, code) => serializeOpenRouterError(500, m, { reason: code }) } : {},
|
|
2263
3126
|
streamEvent: (m, status) => `data: ${isOpenRouter ? serializeOpenRouterError(status, m) : JSON.stringify({ error: {
|
|
2264
3127
|
message: m,
|
|
2265
3128
|
type: errorTypeForStatus(status)
|
|
@@ -2323,17 +3186,23 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2323
3186
|
server.emit("connection", socket);
|
|
2324
3187
|
return;
|
|
2325
3188
|
}
|
|
2326
|
-
|
|
3189
|
+
let parsedUrl;
|
|
3190
|
+
try {
|
|
3191
|
+
parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
3192
|
+
} catch (err) {
|
|
3193
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
3194
|
+
defaults.logger.warn(`Unhandled upgrade error: ${msg}`);
|
|
3195
|
+
socket.end("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
|
|
3196
|
+
return;
|
|
3197
|
+
}
|
|
2327
3198
|
let pathname = parsedUrl.pathname;
|
|
2328
3199
|
if (!validateRequestApiKey(req, resolvedAuth.policy).ok) {
|
|
2329
3200
|
writeApiKeyUpgradeRejection(socket);
|
|
2330
3201
|
return;
|
|
2331
3202
|
}
|
|
2332
|
-
if (
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
if (await handler.handleUpgrade(socket, head, subPath)) return;
|
|
2336
|
-
}
|
|
3203
|
+
for (const { path: mountPath, handler } of mountList) if ((pathname === mountPath || pathname.startsWith(mountPath + "/")) && handler.handleUpgrade) {
|
|
3204
|
+
const subPath = pathname.slice(mountPath.length) || "/";
|
|
3205
|
+
if (await handler.handleUpgrade(socket, head, subPath)) return;
|
|
2337
3206
|
}
|
|
2338
3207
|
if (!pathname.match(AZURE_DEPLOYMENT_RE)) pathname = normalizeCompatPath(pathname, logger);
|
|
2339
3208
|
if (pathname !== RESPONSES_PATH && pathname !== REALTIME_PATH && pathname !== GEMINI_LIVE_PATH && pathname !== LIVE_PATH) {
|
|
@@ -2341,6 +3210,25 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2341
3210
|
socket.destroy();
|
|
2342
3211
|
return;
|
|
2343
3212
|
}
|
|
3213
|
+
if (defaults.misbehavior && pathname !== LIVE_PATH && req.headers["x-aimock-misbehavior"] !== void 0) {
|
|
3214
|
+
const code = "aimock_misbehavior_invalid";
|
|
3215
|
+
const message = "use the runtime scope: POST /__aimock/misbehavior with X-Test-Id";
|
|
3216
|
+
const body = JSON.stringify({ error: pathname === GEMINI_LIVE_PATH ? {
|
|
3217
|
+
code: 400,
|
|
3218
|
+
status: "INVALID_ARGUMENT",
|
|
3219
|
+
message: `${code}: ${message}`
|
|
3220
|
+
} : {
|
|
3221
|
+
type: "invalid_request_error",
|
|
3222
|
+
code,
|
|
3223
|
+
message
|
|
3224
|
+
} });
|
|
3225
|
+
socket.end(`HTTP/1.1 400 Bad Request\r
|
|
3226
|
+
Content-Type: application/json\r
|
|
3227
|
+
Content-Length: ${Buffer.byteLength(body)}\r\nConnection: close\r
|
|
3228
|
+
\r
|
|
3229
|
+
` + body);
|
|
3230
|
+
return;
|
|
3231
|
+
}
|
|
2344
3232
|
let liveId;
|
|
2345
3233
|
if (pathname === LIVE_PATH) {
|
|
2346
3234
|
if (req.method !== "GET" || parsedUrl.searchParams.has("model")) {
|
|
@@ -2402,7 +3290,8 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2402
3290
|
...defaults,
|
|
2403
3291
|
model: "gpt-4",
|
|
2404
3292
|
testId: wsTestId,
|
|
2405
|
-
upgradeHeaders: req.headers
|
|
3293
|
+
upgradeHeaders: req.headers,
|
|
3294
|
+
beforeProcessMessage: ensureRawFixturePositions
|
|
2406
3295
|
});
|
|
2407
3296
|
else if (pathname === REALTIME_PATH) {
|
|
2408
3297
|
const transcriptionIntent = parsedUrl.searchParams.get("intent") === "transcription";
|
|
@@ -2412,13 +3301,15 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2412
3301
|
model,
|
|
2413
3302
|
transcriptionIntent,
|
|
2414
3303
|
testId: wsTestId,
|
|
2415
|
-
upgradeHeaders: req.headers
|
|
3304
|
+
upgradeHeaders: req.headers,
|
|
3305
|
+
beforeProcessMessage: ensureRawFixturePositions
|
|
2416
3306
|
});
|
|
2417
3307
|
} else if (pathname === GEMINI_LIVE_PATH) handleWebSocketGeminiLive(ws, fixtures, journal, {
|
|
2418
3308
|
...defaults,
|
|
2419
3309
|
model: "gemini-2.0-flash",
|
|
2420
3310
|
testId: wsTestId,
|
|
2421
|
-
upgradeHeaders: req.headers
|
|
3311
|
+
upgradeHeaders: req.headers,
|
|
3312
|
+
beforeProcessMessage: ensureRawFixturePositions
|
|
2422
3313
|
});
|
|
2423
3314
|
}
|
|
2424
3315
|
const originalClose = server.close.bind(server);
|
|
@@ -2443,9 +3334,13 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
2443
3334
|
return;
|
|
2444
3335
|
}
|
|
2445
3336
|
const url = `http://${addr.address}:${addr.port}`;
|
|
2446
|
-
|
|
2447
|
-
|
|
3337
|
+
try {
|
|
3338
|
+
commitHandOff();
|
|
3339
|
+
} catch (err) {
|
|
3340
|
+
server.close(() => reject(err));
|
|
3341
|
+
return;
|
|
2448
3342
|
}
|
|
3343
|
+
for (const { path: mountPath, handler } of mountList) if (handler.setBaseUrl) handler.setBaseUrl(url + mountPath);
|
|
2449
3344
|
resolve({
|
|
2450
3345
|
closeLiveSessions,
|
|
2451
3346
|
server,
|