@copilotkit/aimock 1.42.1 → 1.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +65 -0
- package/README.md +108 -16
- package/dist/agui-handler.d.ts.map +1 -1
- package/dist/agui-mock.cjs +1 -1
- package/dist/agui-mock.cjs.map +1 -1
- package/dist/agui-mock.d.cts.map +1 -1
- package/dist/agui-mock.d.ts.map +1 -1
- package/dist/agui-mock.js +1 -1
- package/dist/agui-mock.js.map +1 -1
- package/dist/agui-recorder.cjs +33 -11
- package/dist/agui-recorder.cjs.map +1 -1
- package/dist/agui-recorder.d.cts +10 -1
- package/dist/agui-recorder.d.cts.map +1 -1
- package/dist/agui-recorder.d.ts +10 -1
- package/dist/agui-recorder.d.ts.map +1 -1
- package/dist/agui-recorder.js +33 -11
- package/dist/agui-recorder.js.map +1 -1
- package/dist/agui-types.d.cts +1 -0
- package/dist/agui-types.d.cts.map +1 -1
- package/dist/agui-types.d.ts +1 -0
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/aimock-cli.cjs +38 -10
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.d.cts +5 -0
- package/dist/aimock-cli.d.cts.map +1 -1
- package/dist/aimock-cli.d.ts +5 -0
- package/dist/aimock-cli.d.ts.map +1 -1
- package/dist/aimock-cli.js +38 -10
- package/dist/aimock-cli.js.map +1 -1
- package/dist/batches.cjs +458 -0
- package/dist/batches.cjs.map +1 -0
- package/dist/batches.d.cts +57 -0
- package/dist/batches.d.cts.map +1 -0
- package/dist/batches.d.ts +57 -0
- package/dist/batches.d.ts.map +1 -0
- package/dist/batches.js +453 -0
- package/dist/batches.js.map +1 -0
- package/dist/bedrock-converse.cjs +44 -12
- package/dist/bedrock-converse.cjs.map +1 -1
- package/dist/bedrock-converse.d.cts.map +1 -1
- package/dist/bedrock-converse.d.ts.map +1 -1
- package/dist/bedrock-converse.js +46 -14
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +10 -8
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.d.cts.map +1 -1
- package/dist/bedrock.d.ts.map +1 -1
- package/dist/bedrock.js +12 -10
- package/dist/bedrock.js.map +1 -1
- package/dist/byteplus-video.cjs +3 -3
- package/dist/byteplus-video.cjs.map +1 -1
- package/dist/byteplus-video.js +4 -4
- package/dist/byteplus-video.js.map +1 -1
- package/dist/chaos.cjs +625 -72
- package/dist/chaos.cjs.map +1 -1
- package/dist/chaos.d.cts +164 -7
- package/dist/chaos.d.cts.map +1 -1
- package/dist/chaos.d.ts +164 -7
- package/dist/chaos.d.ts.map +1 -1
- package/dist/chaos.js +616 -74
- package/dist/chaos.js.map +1 -1
- package/dist/cli.cjs +131 -30
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +132 -31
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +37 -9
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.d.cts.map +1 -1
- package/dist/cohere.d.ts.map +1 -1
- package/dist/cohere.js +39 -11
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.cjs +1 -1
- package/dist/config-loader.js +1 -1
- package/dist/constants.cjs +8 -0
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +6 -2
- package/dist/constants.d.ts +6 -2
- package/dist/constants.js +8 -1
- package/dist/constants.js.map +1 -1
- package/dist/convert.cjs +1 -1
- package/dist/convert.cjs.map +1 -1
- package/dist/convert.js +1 -1
- package/dist/convert.js.map +1 -1
- package/dist/echo-text.cjs +65 -0
- package/dist/echo-text.cjs.map +1 -0
- package/dist/echo-text.d.cts +22 -0
- package/dist/echo-text.d.cts.map +1 -0
- package/dist/echo-text.d.ts +22 -0
- package/dist/echo-text.d.ts.map +1 -0
- package/dist/echo-text.js +65 -0
- package/dist/echo-text.js.map +1 -0
- package/dist/elevenlabs-audio.cjs +3 -3
- package/dist/elevenlabs-audio.cjs.map +1 -1
- package/dist/elevenlabs-audio.js +4 -4
- package/dist/elevenlabs-audio.js.map +1 -1
- package/dist/elevenlabs-voice.cjs +1053 -0
- package/dist/elevenlabs-voice.cjs.map +1 -0
- package/dist/elevenlabs-voice.d.cts +72 -0
- package/dist/elevenlabs-voice.d.cts.map +1 -0
- package/dist/elevenlabs-voice.d.ts +72 -0
- package/dist/elevenlabs-voice.d.ts.map +1 -0
- package/dist/elevenlabs-voice.js +1048 -0
- package/dist/elevenlabs-voice.js.map +1 -0
- package/dist/embeddings.cjs +26 -5
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.d.cts +1 -1
- package/dist/embeddings.d.cts.map +1 -1
- package/dist/embeddings.d.ts +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +27 -6
- package/dist/embeddings.js.map +1 -1
- package/dist/fal-audio.cjs +3 -3
- package/dist/fal-audio.cjs.map +1 -1
- package/dist/fal-audio.js +4 -4
- package/dist/fal-audio.js.map +1 -1
- package/dist/fal.cjs +28 -4
- package/dist/fal.cjs.map +1 -1
- package/dist/fal.d.cts +16 -1
- package/dist/fal.d.cts.map +1 -1
- package/dist/fal.d.ts +16 -1
- package/dist/fal.d.ts.map +1 -1
- package/dist/fal.js +28 -5
- package/dist/fal.js.map +1 -1
- package/dist/files.cjs +1069 -0
- package/dist/files.cjs.map +1 -0
- package/dist/files.d.cts +68 -0
- package/dist/files.d.cts.map +1 -0
- package/dist/files.d.ts +68 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +1056 -0
- package/dist/files.js.map +1 -0
- package/dist/fine-tuning.cjs +1033 -0
- package/dist/fine-tuning.cjs.map +1 -0
- package/dist/fine-tuning.d.cts +96 -0
- package/dist/fine-tuning.d.cts.map +1 -0
- package/dist/fine-tuning.d.ts +96 -0
- package/dist/fine-tuning.d.ts.map +1 -0
- package/dist/fine-tuning.js +1026 -0
- package/dist/fine-tuning.js.map +1 -0
- package/dist/fixture-loader-services.cjs +116 -0
- package/dist/fixture-loader-services.cjs.map +1 -0
- package/dist/fixture-loader-services.d.cts +48 -0
- package/dist/fixture-loader-services.d.cts.map +1 -0
- package/dist/fixture-loader-services.d.ts +48 -0
- package/dist/fixture-loader-services.d.ts.map +1 -0
- package/dist/fixture-loader-services.js +114 -0
- package/dist/fixture-loader-services.js.map +1 -0
- package/dist/fixture-loader.cjs +0 -0
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +131 -5
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +131 -5
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +0 -0
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-embeddings.cjs +44 -4
- package/dist/gemini-embeddings.cjs.map +1 -1
- package/dist/gemini-embeddings.js +46 -6
- package/dist/gemini-embeddings.js.map +1 -1
- package/dist/gemini-interactions.cjs +51 -7
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +53 -9
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +19 -8
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts.map +1 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +21 -10
- package/dist/gemini.js.map +1 -1
- package/dist/grok-video.cjs +14 -14
- package/dist/grok-video.cjs.map +1 -1
- package/dist/grok-video.js +15 -15
- package/dist/grok-video.js.map +1 -1
- package/dist/helpers.cjs +497 -10
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +38 -2
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +38 -2
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +487 -12
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +57 -125
- package/dist/images.cjs.map +1 -1
- package/dist/images.d.cts.map +1 -1
- package/dist/images.d.ts.map +1 -1
- package/dist/images.js +58 -126
- package/dist/images.js.map +1 -1
- package/dist/index.cjs +71 -9
- package/dist/index.d.cts +17 -7
- package/dist/index.d.ts +17 -7
- package/dist/index.js +21 -11
- package/dist/jest.cjs +24 -12
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +6 -3
- package/dist/jest.d.cts.map +1 -1
- package/dist/jest.d.ts +6 -3
- package/dist/jest.d.ts.map +1 -1
- package/dist/jest.js +25 -13
- package/dist/jest.js.map +1 -1
- package/dist/journal.cjs +61 -4
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts +60 -3
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts +60 -3
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +61 -5
- package/dist/journal.js.map +1 -1
- package/dist/live-fixture.cjs +1192 -0
- package/dist/live-fixture.cjs.map +1 -0
- package/dist/live-fixture.js +1187 -0
- package/dist/live-fixture.js.map +1 -0
- package/dist/live-recorder.cjs +256 -0
- package/dist/live-recorder.cjs.map +1 -0
- package/dist/live-recorder.js +254 -0
- package/dist/live-recorder.js.map +1 -0
- package/dist/live-replay.cjs +286 -0
- package/dist/live-replay.cjs.map +1 -0
- package/dist/live-replay.js +285 -0
- package/dist/live-replay.js.map +1 -0
- package/dist/live-sanitize.cjs +196 -0
- package/dist/live-sanitize.cjs.map +1 -0
- package/dist/live-sanitize.js +196 -0
- package/dist/live-sanitize.js.map +1 -0
- package/dist/live-types.d.cts +71 -0
- package/dist/live-types.d.cts.map +1 -0
- package/dist/live-types.d.ts +71 -0
- package/dist/live-types.d.ts.map +1 -0
- package/dist/llmock.cjs +132 -49
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +51 -5
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +51 -5
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +133 -50
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-fakes-mount.cjs +216 -0
- package/dist/mcp-fakes-mount.cjs.map +1 -0
- package/dist/mcp-fakes-mount.js +210 -0
- package/dist/mcp-fakes-mount.js.map +1 -0
- package/dist/mcp-fakes.cjs +1619 -0
- package/dist/mcp-fakes.cjs.map +1 -0
- package/dist/mcp-fakes.d.cts +101 -0
- package/dist/mcp-fakes.d.cts.map +1 -0
- package/dist/mcp-fakes.d.ts +101 -0
- package/dist/mcp-fakes.d.ts.map +1 -0
- package/dist/mcp-fakes.js +1614 -0
- package/dist/mcp-fakes.js.map +1 -0
- package/dist/mcp-handler.cjs +331 -17
- package/dist/mcp-handler.cjs.map +1 -1
- package/dist/mcp-handler.js +330 -18
- package/dist/mcp-handler.js.map +1 -1
- package/dist/mcp-mock.cjs +217 -49
- package/dist/mcp-mock.cjs.map +1 -1
- package/dist/mcp-mock.d.cts +66 -1
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts +66 -1
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +218 -50
- package/dist/mcp-mock.js.map +1 -1
- package/dist/mcp-stub.cjs +5 -1
- package/dist/mcp-stub.d.cts +3 -1
- package/dist/mcp-stub.d.ts +3 -1
- package/dist/mcp-stub.js +3 -1
- package/dist/mcp-types.d.cts +6 -0
- package/dist/mcp-types.d.ts +6 -0
- package/dist/message-text.cjs +192 -0
- package/dist/message-text.cjs.map +1 -0
- package/dist/message-text.d.cts +31 -0
- package/dist/message-text.d.cts.map +1 -0
- package/dist/message-text.d.ts +31 -0
- package/dist/message-text.d.ts.map +1 -0
- package/dist/message-text.js +184 -0
- package/dist/message-text.js.map +1 -0
- package/dist/messages.cjs +49 -7
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.d.cts.map +1 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +51 -9
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +278 -45
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts +17 -7
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts +17 -7
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +270 -29
- package/dist/metrics.js.map +1 -1
- package/dist/moderation.cjs +10 -1
- package/dist/moderation.cjs.map +1 -1
- package/dist/moderation.d.cts.map +1 -1
- package/dist/moderation.d.ts.map +1 -1
- package/dist/moderation.js +10 -1
- package/dist/moderation.js.map +1 -1
- package/dist/ollama.cjs +10 -6
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts.map +1 -1
- package/dist/ollama.js +11 -7
- package/dist/ollama.js.map +1 -1
- package/dist/openapi.cjs +3405 -0
- package/dist/openapi.cjs.map +1 -0
- package/dist/openapi.d.cts +30 -0
- package/dist/openapi.d.cts.map +1 -0
- package/dist/openapi.d.ts +30 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +3404 -0
- package/dist/openapi.js.map +1 -0
- package/dist/openrouter-video.cjs +9 -9
- package/dist/openrouter-video.cjs.map +1 -1
- package/dist/openrouter-video.d.cts.map +1 -1
- package/dist/openrouter-video.d.ts.map +1 -1
- package/dist/openrouter-video.js +10 -10
- package/dist/openrouter-video.js.map +1 -1
- package/dist/recorder.cjs +54 -10
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +52 -12
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +47 -7
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +48 -9
- package/dist/responses.js.map +1 -1
- package/dist/route-registry.cjs +951 -0
- package/dist/route-registry.cjs.map +1 -0
- package/dist/route-registry.d.cts +33 -0
- package/dist/route-registry.d.cts.map +1 -0
- package/dist/route-registry.d.ts +33 -0
- package/dist/route-registry.d.ts.map +1 -0
- package/dist/route-registry.js +866 -0
- package/dist/route-registry.js.map +1 -0
- package/dist/router.cjs +21 -34
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts +4 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts +4 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +21 -35
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +1252 -777
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +5 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +5 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1185 -710
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +6 -4
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.js +7 -5
- package/dist/speech.js.map +1 -1
- package/dist/suite.cjs +1 -1
- package/dist/suite.js +1 -1
- package/dist/transcription.cjs +6 -6
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.d.cts.map +1 -1
- package/dist/transcription.d.ts.map +1 -1
- package/dist/transcription.js +7 -7
- package/dist/transcription.js.map +1 -1
- package/dist/types.cjs +18 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +320 -17
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +320 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/dist/validate-cli.cjs +983 -0
- package/dist/validate-cli.cjs.map +1 -0
- package/dist/validate-cli.js +982 -0
- package/dist/validate-cli.js.map +1 -0
- package/dist/vector-stores.cjs +1180 -0
- package/dist/vector-stores.cjs.map +1 -0
- package/dist/vector-stores.d.cts +106 -0
- package/dist/vector-stores.d.cts.map +1 -0
- package/dist/vector-stores.d.ts +106 -0
- package/dist/vector-stores.d.ts.map +1 -0
- package/dist/vector-stores.js +1164 -0
- package/dist/vector-stores.js.map +1 -0
- package/dist/vector-types.d.cts.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/veo-video.cjs +13 -13
- package/dist/veo-video.cjs.map +1 -1
- package/dist/veo-video.js +14 -14
- package/dist/veo-video.js.map +1 -1
- package/dist/video.cjs +23 -4
- package/dist/video.cjs.map +1 -1
- package/dist/video.d.cts +1 -1
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts +1 -1
- package/dist/video.d.ts.map +1 -1
- package/dist/video.js +24 -5
- package/dist/video.js.map +1 -1
- package/dist/vitest.cjs +23 -11
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +5 -2
- package/dist/vitest.d.cts.map +1 -1
- package/dist/vitest.d.ts +5 -2
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +25 -13
- package/dist/vitest.js.map +1 -1
- package/dist/watcher.cjs +2 -1
- package/dist/watcher.cjs.map +1 -1
- package/dist/watcher.js +2 -1
- package/dist/watcher.js.map +1 -1
- package/dist/ws-framing.cjs +150 -35
- package/dist/ws-framing.cjs.map +1 -1
- package/dist/ws-framing.d.cts +18 -3
- package/dist/ws-framing.d.cts.map +1 -1
- package/dist/ws-framing.d.ts +18 -3
- package/dist/ws-framing.d.ts.map +1 -1
- package/dist/ws-framing.js +151 -36
- package/dist/ws-framing.js.map +1 -1
- package/dist/ws-gemini-live.cjs +2 -2
- package/dist/ws-gemini-live.js +2 -2
- package/dist/ws-live.cjs +401 -0
- package/dist/ws-live.cjs.map +1 -0
- package/dist/ws-live.js +400 -0
- package/dist/ws-live.js.map +1 -0
- package/dist/ws-realtime.cjs +46 -2
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +46 -2
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +16 -4
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.js +16 -4
- package/dist/ws-responses.js.map +1 -1
- package/dist/ws-upstream.cjs +118 -0
- package/dist/ws-upstream.cjs.map +1 -0
- package/dist/ws-upstream.js +115 -0
- package/dist/ws-upstream.js.map +1 -0
- package/fixtures/openai-live-client.json +390 -0
- package/fixtures/openai-live-managed.json +709 -0
- package/package.json +4 -1
package/dist/helpers.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LiveFixtureResponse } from "./live-types.js";
|
|
1
2
|
import { AudioResponse, ChatCompletion, ChatCompletionRequest, ContentWithToolCallsResponse, EmbeddingResponse, ErrorResponse, FixtureBlock, FixtureFileBlock, FixtureResponse, ImageResponse, ResponseOverrides, SSEChunk, TextResponse, ToolCall, ToolCallResponse, TranscriptionResponse, VideoResponse } from "./types.js";
|
|
2
3
|
import * as http$1 from "node:http";
|
|
3
4
|
|
|
@@ -44,7 +45,32 @@ declare function strictNoMatchLogLine(method: string, url: string, skippedBySequ
|
|
|
44
45
|
*/
|
|
45
46
|
|
|
46
47
|
declare function flattenHeaders(headers: http$1.IncomingHttpHeaders): Record<string, string>;
|
|
48
|
+
/** Structural discriminator; registration performs full transcript validation. */
|
|
49
|
+
declare function isLiveResponse(value: unknown): value is LiveFixtureResponse;
|
|
47
50
|
declare function generateId(prefix?: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the request id for this HTTP request.
|
|
53
|
+
*
|
|
54
|
+
* - When the caller sends a well-formed `X-Request-Id` (1-128 chars of
|
|
55
|
+
* `A-Za-z0-9-_.:`), it is echoed verbatim so distributed traces correlate.
|
|
56
|
+
* - Otherwise (absent, empty, too long, or illegal characters) a fresh
|
|
57
|
+
* `req-…` id is generated — never trust an attacker-controlled correlation
|
|
58
|
+
* value to be a valid log key.
|
|
59
|
+
*
|
|
60
|
+
* Returns `{ id, generated }`. The server normalizes
|
|
61
|
+
* `req.headers["x-request-id"]` to the resolved value, so every downstream
|
|
62
|
+
* `flattenHeaders` journal snapshot carries it with zero per-handler edits —
|
|
63
|
+
* which also means a MINTED id would otherwise ride the egress header set to
|
|
64
|
+
* a real provider. The server therefore passes `generated` to
|
|
65
|
+
* `markMintedRequestId` so `buildForwardHeaders` can drop it; a caller's own
|
|
66
|
+
* id still forwards verbatim.
|
|
67
|
+
*/
|
|
68
|
+
declare function resolveRequestId(rawHeaders: http$1.IncomingHttpHeaders): {
|
|
69
|
+
id: string;
|
|
70
|
+
generated: boolean;
|
|
71
|
+
};
|
|
72
|
+
/** @internal Record that this request's `x-request-id` is aimock's own. */
|
|
73
|
+
|
|
48
74
|
declare function generateToolCallId(): string;
|
|
49
75
|
declare function generateMessageId(): string;
|
|
50
76
|
declare function generateToolUseId(): string;
|
|
@@ -81,6 +107,8 @@ declare function isContentWithToolCallsResponse(r: FixtureResponse): r is Conten
|
|
|
81
107
|
* guard at {@link resolveResponse}).
|
|
82
108
|
*/
|
|
83
109
|
declare function resolveFixtureBlocks(blocks: FixtureFileBlock[]): FixtureBlock[];
|
|
110
|
+
/** Project normalized ordered blocks without changing the fixture or generating ids. */
|
|
111
|
+
|
|
84
112
|
declare function isErrorResponse(r: FixtureResponse): r is ErrorResponse;
|
|
85
113
|
/**
|
|
86
114
|
* Serialize an ErrorResponse to JSON, stripping the internal-only `status`
|
|
@@ -115,6 +143,15 @@ declare function buildTextCompletion(content: string, model: string, reasoning?:
|
|
|
115
143
|
declare function buildToolCallCompletion(toolCalls: ToolCall[], model: string, reasoning?: string, overrides?: ResponseOverrides, requestMessages?: ChatCompletionRequest["messages"]): ChatCompletion;
|
|
116
144
|
declare function buildContentWithToolCallsChunks(content: string, toolCalls: ToolCall[], model: string, chunkSize: number, reasoning?: string, overrides?: ResponseOverrides, blocks?: FixtureBlock[]): SSEChunk[];
|
|
117
145
|
declare function buildContentWithToolCallsCompletion(content: string, toolCalls: ToolCall[], model: string, reasoning?: string, overrides?: ResponseOverrides, requestMessages?: ChatCompletionRequest["messages"]): ChatCompletion;
|
|
146
|
+
/**
|
|
147
|
+
* A body read that stopped because the CLIENT sent more bytes than the route
|
|
148
|
+
* allows. The socket is already destroyed when this rejects, so no status line
|
|
149
|
+
* ever reaches the caller — but the fault is the caller's, and error arms that
|
|
150
|
+
* journal and log the failure need to say so. Carrying the classification on
|
|
151
|
+
* the error type (rather than re-matching the message text) is what lets them:
|
|
152
|
+
* the message is unchanged from the plain `Error` this replaced.
|
|
153
|
+
*/
|
|
154
|
+
|
|
118
155
|
/**
|
|
119
156
|
* Generate a deterministic embedding vector from input text.
|
|
120
157
|
* Hashes the input with SHA-256 and spreads the hash bytes across
|
|
@@ -141,7 +178,6 @@ declare function buildEmbeddingResponse(embeddings: number[][], model: string, u
|
|
|
141
178
|
prompt_tokens?: number;
|
|
142
179
|
total_tokens?: number;
|
|
143
180
|
}): EmbeddingAPIResponse;
|
|
144
|
-
//# sourceMappingURL=helpers.d.ts.map
|
|
145
181
|
//#endregion
|
|
146
|
-
export { EmbeddingAPIResponse, FORMAT_TO_CONTENT_TYPE, buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildEmbeddingResponse, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, extractOverrides, flattenHeaders, formatToMime, generateDeterministicEmbedding, generateId, generateMessageId, generateToolCallId, generateToolUseId, isAudioResponse, isContentWithToolCallsResponse, isEmbeddingResponse, isErrorResponse, isImageResponse, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, resolveFixtureBlocks, strictNoMatchLogLine, strictNoMatchMessage };
|
|
182
|
+
export { EmbeddingAPIResponse, FORMAT_TO_CONTENT_TYPE, buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildEmbeddingResponse, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, extractOverrides, flattenHeaders, formatToMime, generateDeterministicEmbedding, generateId, generateMessageId, generateToolCallId, generateToolUseId, isAudioResponse, isContentWithToolCallsResponse, isEmbeddingResponse, isErrorResponse, isImageResponse, isLiveResponse, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, resolveFixtureBlocks, resolveRequestId, strictNoMatchLogLine, strictNoMatchMessage };
|
|
147
183
|
//# sourceMappingURL=helpers.d.ts.map
|
package/dist/helpers.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","names":[],"sources":["../src/helpers.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","names":[],"sources":["../src/helpers.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AAmjBA;;;;;AAUA;AAaA;AAIA;;;;AAAuF,iBAtcvE,oBAAA,CAscuE,uBAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AAQvF;;;;AAAuE,iBAncvD,oBAAA,CAmcuD,MAAA,EAAA,MAAA,EAAA,GAAA,EAAA,MAAA,EAAA,uBAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AAYvE;;;;;;;AAqHA;;;;;AA2EA;;;;;;;iBA7iBgB,cAAA,UAAwB,MAAA,CAAK,sBAAsB;;iBAcnD,cAAA,2BAAyC;iBA0DzC,UAAA;;;;;;;;;;;;;;;;;;iBAqBA,gBAAA,aAA6B,MAAA,CAAK;;;;;;iBAqClC,kBAAA,CAAA;iBAIA,iBAAA,CAAA;iBAIA,iBAAA,CAAA;iBAIA,cAAA,IAAkB,uBAAuB;iBAIzC,kBAAA,IAAsB,uBAAuB;iBAQ7C,8BAAA,IACX,uBACG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmDQ,oBAAA,SAA6B,qBAAqB;;;iBA0ElD,eAAA,IAAmB,uBAAuB;;;;;;;iBA0B1C,mBAAA,IAAuB,uBAAuB;iBAI9C,eAAA,IAAmB,uBAAuB;iBAO1C,eAAA,IAAmB,uBAAuB;;;;;cAU7C,wBAAwB;;;;;iBAarB,YAAA;iBAIA,uBAAA,IAA2B,uBAAuB;iBAQlD,eAAA,IAAmB,uBAAuB;iBAY1C,gBAAA,WACJ,eAAe,mBAAmB,+BAC3C;;;;;;iBAmHa,eAAA,oFAKF,oBACX;iBAqEa,mBAAA,YACH,8EAIC,oBACX;iBAsHa,mBAAA,iEAIF,qCACM,oCACjB;iBAqCa,uBAAA,YACH,2DAGC,qCACM,oCACjB;iBA2Ca,+BAAA,6BAEH,8EAIC,4BACH,iBACR;iBAmSa,mCAAA,6BAEH,2DAGC,qCACM,oCACjB;;;;;;;;;;;;;;;iBA+wBa,8BAAA;UAgBC,oBAAA;;;;;;;;;;;;;;;;iBAUD,sBAAA;;;IAIb"}
|
package/dist/helpers.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { DEFAULT_TEST_ID } from "./constants.js";
|
|
1
|
+
import { DEFAULT_TEST_ID, MCP_FAKES_ECHO_LIMIT } from "./constants.js";
|
|
2
|
+
import { cutText } from "./echo-text.js";
|
|
3
|
+
import { build, fixed, msg, quote } from "./message-text.js";
|
|
2
4
|
import { isReasoningModel } from "./model-utils.js";
|
|
3
5
|
import { isRecognizedApiKeyHeader } from "./api-key-auth.js";
|
|
4
6
|
import { createHash, randomBytes } from "node:crypto";
|
|
@@ -25,6 +27,37 @@ function resolveStrictMode(serverDefault, rawHeaders) {
|
|
|
25
27
|
}
|
|
26
28
|
return serverDefault ?? false;
|
|
27
29
|
}
|
|
30
|
+
const STRICT_INTEGER_RE = /^\d+$/;
|
|
31
|
+
/**
|
|
32
|
+
* Strict decimal-integer grammar shared by the query/list parsers that grew
|
|
33
|
+
* their own local copy on separate branches (files `limit`, fine-tuning
|
|
34
|
+
* `limit`, chaos integer fields): an unsigned digit run and nothing else — no
|
|
35
|
+
* sign, no exponent, no radix prefix, no surrounding whitespace (callers that
|
|
36
|
+
* accept padded input trim before calling).
|
|
37
|
+
*
|
|
38
|
+
* Deliberately narrower than `Number()`, which reads `0x10` as 16, `1e3` as
|
|
39
|
+
* 1000 and strips whitespace and a leading `+`. Returns the value, or `null`
|
|
40
|
+
* when the text is not a safe integer literal. Range checks and error
|
|
41
|
+
* messages stay with the callers — this is the grammar gate only, so sharing
|
|
42
|
+
* it cannot change any surface's accepted range or its 400 text.
|
|
43
|
+
*/
|
|
44
|
+
function parseStrictIntegerText(text) {
|
|
45
|
+
if (!STRICT_INTEGER_RE.test(text)) return null;
|
|
46
|
+
const n = Number(text);
|
|
47
|
+
return Number.isSafeInteger(n) ? n : null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Would a fixture miss on this request have been forwarded upstream? This is
|
|
51
|
+
* the ONE rule behind the no-fixture chaos journal `source` label: "proxy"
|
|
52
|
+
* only when record mode has an upstream for THIS provider and strict mode is
|
|
53
|
+
* not refusing the miss first. `effectiveStrict` is the header-resolved value
|
|
54
|
+
* from {@link resolveStrictMode}. `record.providers` is read guarded — a JS
|
|
55
|
+
* caller can hand over `record: {}`.
|
|
56
|
+
*/
|
|
57
|
+
function wouldProxyMiss(effectiveStrict, record, providerKey) {
|
|
58
|
+
if (effectiveStrict || providerKey === void 0) return false;
|
|
59
|
+
return Boolean(record?.providers?.[providerKey]);
|
|
60
|
+
}
|
|
28
61
|
/**
|
|
29
62
|
* Returns `true` or `false` when the X-AIMock-Strict header overrides the
|
|
30
63
|
* server default, or `undefined` when it doesn't. Designed to be spread
|
|
@@ -142,6 +175,10 @@ function flattenHeaders(headers) {
|
|
|
142
175
|
}
|
|
143
176
|
return flat;
|
|
144
177
|
}
|
|
178
|
+
/** Structural discriminator; registration performs full transcript validation. */
|
|
179
|
+
function isLiveResponse(value) {
|
|
180
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && "live" in value;
|
|
181
|
+
}
|
|
145
182
|
function isResponseFactory(r) {
|
|
146
183
|
return typeof r === "function";
|
|
147
184
|
}
|
|
@@ -176,6 +213,57 @@ function normalizeFactoryResponse(raw) {
|
|
|
176
213
|
function generateId(prefix = "chatcmpl") {
|
|
177
214
|
return `${prefix}-${randomBytes(12).toString("base64url")}`;
|
|
178
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Resolve the request id for this HTTP request.
|
|
218
|
+
*
|
|
219
|
+
* - When the caller sends a well-formed `X-Request-Id` (1-128 chars of
|
|
220
|
+
* `A-Za-z0-9-_.:`), it is echoed verbatim so distributed traces correlate.
|
|
221
|
+
* - Otherwise (absent, empty, too long, or illegal characters) a fresh
|
|
222
|
+
* `req-…` id is generated — never trust an attacker-controlled correlation
|
|
223
|
+
* value to be a valid log key.
|
|
224
|
+
*
|
|
225
|
+
* Returns `{ id, generated }`. The server normalizes
|
|
226
|
+
* `req.headers["x-request-id"]` to the resolved value, so every downstream
|
|
227
|
+
* `flattenHeaders` journal snapshot carries it with zero per-handler edits —
|
|
228
|
+
* which also means a MINTED id would otherwise ride the egress header set to
|
|
229
|
+
* a real provider. The server therefore passes `generated` to
|
|
230
|
+
* `markMintedRequestId` so `buildForwardHeaders` can drop it; a caller's own
|
|
231
|
+
* id still forwards verbatim.
|
|
232
|
+
*/
|
|
233
|
+
function resolveRequestId(rawHeaders) {
|
|
234
|
+
const raw = rawHeaders["x-request-id"];
|
|
235
|
+
const first = Array.isArray(raw) ? raw[0] : raw;
|
|
236
|
+
if (typeof first === "string") {
|
|
237
|
+
const trimmed = first.trim();
|
|
238
|
+
if (trimmed.length >= 1 && trimmed.length <= 128 && /^[A-Za-z0-9\-_.:]+$/.test(trimmed)) return {
|
|
239
|
+
id: trimmed,
|
|
240
|
+
generated: false
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
id: generateId("req"),
|
|
245
|
+
generated: true
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Requests whose `x-request-id` aimock MINTED (the caller sent none, or sent
|
|
250
|
+
* an unusable one). The normalized header is indistinguishable from a
|
|
251
|
+
* caller-supplied one by inspection, so the provenance is tracked out-of-band
|
|
252
|
+
* and keyed on the request object, exactly like the egress auth marker.
|
|
253
|
+
*/
|
|
254
|
+
const mintedRequestIds = /* @__PURE__ */ new WeakSet();
|
|
255
|
+
/** @internal Record that this request's `x-request-id` is aimock's own. */
|
|
256
|
+
function markMintedRequestId(req, generated) {
|
|
257
|
+
if (generated) mintedRequestIds.add(req);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* @internal True when `x-request-id` on this request was minted by aimock.
|
|
261
|
+
* Egress paths use it to avoid transmitting an invented correlation id to a
|
|
262
|
+
* real provider.
|
|
263
|
+
*/
|
|
264
|
+
function hasMintedRequestId(req) {
|
|
265
|
+
return mintedRequestIds.has(req);
|
|
266
|
+
}
|
|
179
267
|
function generateToolCallId() {
|
|
180
268
|
return `call_${randomBytes(12).toString("base64url")}`;
|
|
181
269
|
}
|
|
@@ -254,6 +342,24 @@ function resolveFixtureBlocks(blocks) {
|
|
|
254
342
|
} else throw new Error(`Invalid fixture block at index ${i}: unknown type ${JSON.stringify(b.type)} (expected "text" or "toolCall")`);
|
|
255
343
|
});
|
|
256
344
|
}
|
|
345
|
+
/** Project normalized ordered blocks without changing the fixture or generating ids. */
|
|
346
|
+
function resolveFixtureBlockOutcome(blocks) {
|
|
347
|
+
const ordered = resolveFixtureBlocks(blocks);
|
|
348
|
+
let content = "";
|
|
349
|
+
const toolCalls = [];
|
|
350
|
+
for (const block of ordered) if (block.type === "text") content += block.text;
|
|
351
|
+
else toolCalls.push({
|
|
352
|
+
name: block.name,
|
|
353
|
+
arguments: block.arguments,
|
|
354
|
+
...block.id !== void 0 ? { id: block.id } : {}
|
|
355
|
+
});
|
|
356
|
+
return {
|
|
357
|
+
ordered,
|
|
358
|
+
content,
|
|
359
|
+
toolCalls,
|
|
360
|
+
hasToolCalls: toolCalls.length > 0
|
|
361
|
+
};
|
|
362
|
+
}
|
|
257
363
|
function isErrorResponse(r) {
|
|
258
364
|
return "error" in r && r.error !== null && typeof r.error === "object" && "message" in r.error && typeof r.error.message === "string";
|
|
259
365
|
}
|
|
@@ -603,7 +709,8 @@ function buildContentWithToolCallsChunks(content, toolCalls, model, chunkSize, r
|
|
|
603
709
|
const chunks = [];
|
|
604
710
|
const fingerprint = overrides?.systemFingerprint;
|
|
605
711
|
if (blocks && blocks.length > 0) {
|
|
606
|
-
const
|
|
712
|
+
const outcome = resolveFixtureBlockOutcome(blocks);
|
|
713
|
+
const ordered = outcome.ordered;
|
|
607
714
|
if (reasoning) for (let i = 0; i < reasoning.length; i += chunkSize) {
|
|
608
715
|
const slice = reasoning.slice(i, i + chunkSize);
|
|
609
716
|
chunks.push({
|
|
@@ -710,7 +817,7 @@ function buildContentWithToolCallsChunks(content, toolCalls, model, chunkSize, r
|
|
|
710
817
|
index: 0,
|
|
711
818
|
delta: {},
|
|
712
819
|
logprobs: null,
|
|
713
|
-
finish_reason: overrides?.finishReason ?? "tool_calls"
|
|
820
|
+
finish_reason: overrides?.finishReason ?? (outcome.hasToolCalls ? "tool_calls" : "stop")
|
|
714
821
|
}],
|
|
715
822
|
...fingerprint !== void 0 && { system_fingerprint: fingerprint }
|
|
716
823
|
});
|
|
@@ -859,7 +966,31 @@ function buildContentWithToolCallsCompletion(content, toolCalls, model, reasonin
|
|
|
859
966
|
};
|
|
860
967
|
}
|
|
861
968
|
const DEFAULT_MAX_BODY_BYTES = 10 * 1024 * 1024;
|
|
862
|
-
|
|
969
|
+
/**
|
|
970
|
+
* A body read that stopped because the CLIENT sent more bytes than the route
|
|
971
|
+
* allows. The socket is already destroyed when this rejects, so no status line
|
|
972
|
+
* ever reaches the caller — but the fault is the caller's, and error arms that
|
|
973
|
+
* journal and log the failure need to say so. Carrying the classification on
|
|
974
|
+
* the error type (rather than re-matching the message text) is what lets them:
|
|
975
|
+
* the message is unchanged from the plain `Error` this replaced.
|
|
976
|
+
*/
|
|
977
|
+
var RequestBodyTooLargeError = class extends Error {
|
|
978
|
+
constructor(maxBytes) {
|
|
979
|
+
super(`Request body exceeded size limit of ${maxBytes} bytes`);
|
|
980
|
+
this.maxBytes = maxBytes;
|
|
981
|
+
this.name = "RequestBodyTooLargeError";
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
/**
|
|
985
|
+
* Read a request body as raw bytes, preserving every octet.
|
|
986
|
+
*
|
|
987
|
+
* This is the byte-level primitive {@link readBody} is built on: routes that
|
|
988
|
+
* must not lose bytes (binary file uploads) take the Buffer, everything else
|
|
989
|
+
* keeps taking the decoded string. Splitting it this way leaves the text path
|
|
990
|
+
* byte-identical — `readBody` performs exactly the same `Buffer.concat(...)`
|
|
991
|
+
* + default (utf8) `toString()` it always did, just one call later.
|
|
992
|
+
*/
|
|
993
|
+
function readBodyBuffer(req, maxBytes = DEFAULT_MAX_BODY_BYTES) {
|
|
863
994
|
return new Promise((resolve, reject) => {
|
|
864
995
|
const chunks = [];
|
|
865
996
|
let totalBytes = 0;
|
|
@@ -870,7 +1001,7 @@ function readBody(req, maxBytes = DEFAULT_MAX_BODY_BYTES) {
|
|
|
870
1001
|
if (totalBytes > maxBytes) {
|
|
871
1002
|
settled = true;
|
|
872
1003
|
req.destroy();
|
|
873
|
-
reject(
|
|
1004
|
+
reject(new RequestBodyTooLargeError(maxBytes));
|
|
874
1005
|
return;
|
|
875
1006
|
}
|
|
876
1007
|
chunks.push(chunk);
|
|
@@ -878,8 +1009,58 @@ function readBody(req, maxBytes = DEFAULT_MAX_BODY_BYTES) {
|
|
|
878
1009
|
req.on("end", () => {
|
|
879
1010
|
if (!settled) {
|
|
880
1011
|
settled = true;
|
|
881
|
-
resolve(Buffer.concat(chunks)
|
|
1012
|
+
resolve(Buffer.concat(chunks));
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
req.on("error", (err) => {
|
|
1016
|
+
if (!settled) {
|
|
1017
|
+
settled = true;
|
|
1018
|
+
reject(err);
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Read a request body that may legitimately be over-size, without either
|
|
1025
|
+
* buffering it all or dropping the socket.
|
|
1026
|
+
*
|
|
1027
|
+
* {@link readBodyBuffer} answers an over-size body with `req.destroy()`: no
|
|
1028
|
+
* status line, no body, no CORS headers. That is the right default for routes
|
|
1029
|
+
* whose limit is a pure DoS bound, but it is the wrong answer for a route that
|
|
1030
|
+
* owes the caller a `400` describing what was wrong — the caller cannot read an
|
|
1031
|
+
* error it never receives. So this variant splits the two concerns:
|
|
1032
|
+
*
|
|
1033
|
+
* - `maxBytes` bounds MEMORY. Past it the buffered chunks are dropped and the
|
|
1034
|
+
* body is only counted, so peak retention never exceeds `maxBytes` no matter
|
|
1035
|
+
* how large the body is. The caller gets `buffer: null` and answers itself.
|
|
1036
|
+
* - `drainMaxBytes` bounds WORK, and is the only socket drop here: a body that
|
|
1037
|
+
* keeps coming past it is not worth draining and rejects as `readBodyBuffer`
|
|
1038
|
+
* would.
|
|
1039
|
+
*/
|
|
1040
|
+
function readBodyBufferBounded(req, maxBytes, drainMaxBytes) {
|
|
1041
|
+
return new Promise((resolve, reject) => {
|
|
1042
|
+
let chunks = [];
|
|
1043
|
+
let totalBytes = 0;
|
|
1044
|
+
let settled = false;
|
|
1045
|
+
req.on("data", (chunk) => {
|
|
1046
|
+
if (settled) return;
|
|
1047
|
+
totalBytes += chunk.length;
|
|
1048
|
+
if (chunks !== null && totalBytes > maxBytes) chunks = null;
|
|
1049
|
+
if (totalBytes > drainMaxBytes) {
|
|
1050
|
+
settled = true;
|
|
1051
|
+
req.destroy();
|
|
1052
|
+
reject(new RequestBodyTooLargeError(drainMaxBytes));
|
|
1053
|
+
return;
|
|
882
1054
|
}
|
|
1055
|
+
if (chunks !== null) chunks.push(chunk);
|
|
1056
|
+
});
|
|
1057
|
+
req.on("end", () => {
|
|
1058
|
+
if (settled) return;
|
|
1059
|
+
settled = true;
|
|
1060
|
+
resolve({
|
|
1061
|
+
buffer: chunks === null ? null : Buffer.concat(chunks),
|
|
1062
|
+
bytesRead: totalBytes
|
|
1063
|
+
});
|
|
883
1064
|
});
|
|
884
1065
|
req.on("error", (err) => {
|
|
885
1066
|
if (!settled) {
|
|
@@ -889,6 +1070,9 @@ function readBody(req, maxBytes = DEFAULT_MAX_BODY_BYTES) {
|
|
|
889
1070
|
});
|
|
890
1071
|
});
|
|
891
1072
|
}
|
|
1073
|
+
function readBody(req, maxBytes = DEFAULT_MAX_BODY_BYTES) {
|
|
1074
|
+
return readBodyBuffer(req, maxBytes).then((buf) => buf.toString());
|
|
1075
|
+
}
|
|
892
1076
|
/**
|
|
893
1077
|
* Case-insensitive substring/regex match used for search, rerank, and
|
|
894
1078
|
* moderation endpoints where exact casing rarely matters. String patterns
|
|
@@ -907,11 +1091,14 @@ function matchesPattern(text, pattern) {
|
|
|
907
1091
|
return result;
|
|
908
1092
|
}
|
|
909
1093
|
/**
|
|
910
|
-
*
|
|
911
|
-
*
|
|
912
|
-
* `
|
|
913
|
-
*
|
|
914
|
-
*
|
|
1094
|
+
* Which test LLM and other HTTP-route traffic belongs to: the `X-Test-Id`
|
|
1095
|
+
* header (raw, not percent-decoded) wins, then `?testId=` in the query string
|
|
1096
|
+
* (read through `URLSearchParams`, so percent-decoded and with `+` as a
|
|
1097
|
+
* space), then `DEFAULT_TEST_ID`. Every per-test axis of those routes
|
|
1098
|
+
* (fixture match-counts, the journal filter, chaos scoping) resolves through
|
|
1099
|
+
* this. MCP mounts resolve the test id with {@link resolveMcpIdentity}
|
|
1100
|
+
* instead, which percent-decodes the header, so `X-Test-Id: a%20b` is `a%20b`
|
|
1101
|
+
* here and `a b` there. Both read a `+` in the query as a space.
|
|
915
1102
|
*/
|
|
916
1103
|
function resolveTestId(headers, url) {
|
|
917
1104
|
const headerValue = headers["x-test-id"];
|
|
@@ -935,6 +1122,237 @@ function getContext(req) {
|
|
|
935
1122
|
} else if (typeof headerValue === "string" && headerValue) return headerValue;
|
|
936
1123
|
}
|
|
937
1124
|
/**
|
|
1125
|
+
* Decode one MCP identity value: an `X-Test-Id` / `X-AIMock-Context` header
|
|
1126
|
+
* value, and also each query name and (through {@link decodeMcpQueryValue})
|
|
1127
|
+
* each `?testId=` / `?context=` / `?undeclared=` query value. Clients send `encodeURIComponent(id)` because
|
|
1128
|
+
* `fetch` rejects characters such as `›` in a header. A value that is not
|
|
1129
|
+
* valid percent-encoding (a raw `applies 50% discount`) is used as-is, with
|
|
1130
|
+
* `fellBack: true` so the caller can log the fallback. Only that `URIError`
|
|
1131
|
+
* is caught; any other throw propagates. MCP requests only: LLM routes read
|
|
1132
|
+
* the header raw and the query through `URLSearchParams` (see
|
|
1133
|
+
* {@link resolveTestId}).
|
|
1134
|
+
*/
|
|
1135
|
+
function decodeMcpHeaderValue(raw) {
|
|
1136
|
+
try {
|
|
1137
|
+
return {
|
|
1138
|
+
value: decodeURIComponent(raw),
|
|
1139
|
+
fellBack: false
|
|
1140
|
+
};
|
|
1141
|
+
} catch (err) {
|
|
1142
|
+
if (!(err instanceof URIError)) throw err;
|
|
1143
|
+
return {
|
|
1144
|
+
value: raw,
|
|
1145
|
+
fellBack: true
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Decode one MCP query value (`?testId=`, `?context=`, `?undeclared=`) the way
|
|
1151
|
+
* `URLSearchParams` reads it (spec I1): a `+` is a space, then the value is
|
|
1152
|
+
* percent-decoded by the {@link decodeMcpHeaderValue} rule. So a query built
|
|
1153
|
+
* with `URL.searchParams.set(...)` (a space sent as `+`) and one built with
|
|
1154
|
+
* `encodeURIComponent` both resolve to the id itself. A value that is not
|
|
1155
|
+
* valid percent-encoding is used as-is (its `+` still a space), with
|
|
1156
|
+
* `fellBack: true`.
|
|
1157
|
+
*/
|
|
1158
|
+
function decodeMcpQueryValue(raw) {
|
|
1159
|
+
return decodeMcpHeaderValue(raw.replace(/\+/g, " "));
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Every value of the header `name`, one per header line: `[]` when absent.
|
|
1163
|
+
* Node joins a repeated header into one comma-joined `req.headers` string, so
|
|
1164
|
+
* the values come from `req.headersDistinct` (one element per line) when the
|
|
1165
|
+
* request has it. A caller without it (a hand-built request) is read from
|
|
1166
|
+
* `req.headers`: one element for a string, each element for an array.
|
|
1167
|
+
*/
|
|
1168
|
+
function headerValues(req, name) {
|
|
1169
|
+
const distinct = req.headersDistinct?.[name];
|
|
1170
|
+
if (distinct !== void 0) return distinct;
|
|
1171
|
+
const value = req.headers[name];
|
|
1172
|
+
if (value === void 0) return [];
|
|
1173
|
+
return Array.isArray(value) ? value : [value];
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Every pair of a query string, in order, each name decoded by the
|
|
1177
|
+
* `decodeMcpHeaderValue` rule. A bare `name` with no `=` counts as an
|
|
1178
|
+
* occurrence with an empty value, so `?testId&testId=t1` has two. Each value
|
|
1179
|
+
* is left raw for the caller to decode with {@link decodeMcpQueryValue}.
|
|
1180
|
+
* A name that is not valid percent-encoding (`test%Id`) gets `name: null`: it
|
|
1181
|
+
* matches no field, and the caller counts it.
|
|
1182
|
+
*/
|
|
1183
|
+
function queryPairs(query) {
|
|
1184
|
+
if (query === "") return [];
|
|
1185
|
+
return query.split("&").map((pair) => {
|
|
1186
|
+
const eq = pair.indexOf("=");
|
|
1187
|
+
const key = decodeMcpHeaderValue(eq === -1 ? pair : pair.slice(0, eq));
|
|
1188
|
+
return {
|
|
1189
|
+
name: key.fellBack ? null : key.value,
|
|
1190
|
+
value: eq === -1 ? "" : pair.slice(eq + 1)
|
|
1191
|
+
};
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
/**
|
|
1195
|
+
* A request value as a message part, the way the message builder quotes every
|
|
1196
|
+
* user string: JSON-quoted (quotes and control characters escaped), then cut
|
|
1197
|
+
* once as JSON text, so the whole echo, quotes, escapes and the
|
|
1198
|
+
* `… (<n> more chars)` count included, fits in `MCP_FAKES_ECHO_LIMIT` and the
|
|
1199
|
+
* cut never splits a JSON escape or a surrogate pair.
|
|
1200
|
+
*/
|
|
1201
|
+
function echoIdentityValue(value) {
|
|
1202
|
+
return quote(value, MCP_FAKES_ECHO_LIMIT);
|
|
1203
|
+
}
|
|
1204
|
+
const MCP_IDENTITY_INPUTS = {
|
|
1205
|
+
testId: {
|
|
1206
|
+
header: "x-test-id",
|
|
1207
|
+
query: "testId",
|
|
1208
|
+
label: "X-Test-Id"
|
|
1209
|
+
},
|
|
1210
|
+
context: {
|
|
1211
|
+
header: "x-aimock-context",
|
|
1212
|
+
query: "context",
|
|
1213
|
+
label: "X-AIMock-Context"
|
|
1214
|
+
},
|
|
1215
|
+
undeclared: {
|
|
1216
|
+
header: "x-aimock-mcp-undeclared",
|
|
1217
|
+
query: "undeclared",
|
|
1218
|
+
label: "X-AIMock-MCP-Undeclared"
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
/**
|
|
1222
|
+
* Resolve the identity (test id, context, undeclared override) of one MCP request.
|
|
1223
|
+
* Each field resolves on its own, first of: the header, the query parameter,
|
|
1224
|
+
* the session value bound at `initialize`. A field sent more than once on
|
|
1225
|
+
* either path (more than one header line, read from `headersDistinct` because
|
|
1226
|
+
* Node joins repeated lines into one `headers` string; a header array from a
|
|
1227
|
+
* hand-built request; a repeated query name) is an error
|
|
1228
|
+
* (`MCP_DUPLICATE_IDENTITY`), even when the other path also carries it. For
|
|
1229
|
+
* the test id and the context, a header value is decoded with
|
|
1230
|
+
* `decodeURIComponent` (raw value on failure, with `fellBack` set) and a query
|
|
1231
|
+
* value the way `URLSearchParams` reads it (spec I1): a `+` is a space, then
|
|
1232
|
+
* the same `decodeURIComponent` rule. So `encodeURIComponent` output (what
|
|
1233
|
+
* clients send) and a query built with `URL.searchParams` both decode to the
|
|
1234
|
+
* id itself, while a raw `+` is a `+` in the header and a space in the query.
|
|
1235
|
+
* When both paths carry a test id or a context, the header is used and the
|
|
1236
|
+
* query value is ignored, even if the two differ, but an ignored query value
|
|
1237
|
+
* that does not decode still sets `fellBack`. The undeclared header is not
|
|
1238
|
+
* percent-decoded; its query value is, by the same query rule. Both are
|
|
1239
|
+
* trimmed and compared case-insensitively. `supplied` says whether the
|
|
1240
|
+
* request itself carried a non-empty header or query value for the field,
|
|
1241
|
+
* never `resolveTestId`'s result (which returns `"__default__"` when nothing
|
|
1242
|
+
* is sent). Blank values: an empty value is not supplied and falls through;
|
|
1243
|
+
* it is never rejected. A whitespace-only value is not supplied for the
|
|
1244
|
+
* undeclared override (it is trimmed), but is a real value for the test id
|
|
1245
|
+
* and the context, which match exactly and are never trimmed (Node trims a
|
|
1246
|
+
* header value, so only the query or a hand-built request can carry one). A
|
|
1247
|
+
* lone query name with no `=` carries no value. An empty session value counts
|
|
1248
|
+
* as none, and a missing `req.url` as `/`. A field with no value anywhere is
|
|
1249
|
+
* `null`. An unrecognized undeclared override on the header or the query is
|
|
1250
|
+
* an error (`ok: false`), not a fall-through: both are checked before the
|
|
1251
|
+
* header one is used, so a valid header does not hide a mistyped query value.
|
|
1252
|
+
* Bad percent-encoding is never silent: a test id or context value that does
|
|
1253
|
+
* not decode sets `fellBack`, on either path, used or ignored; an undeclared
|
|
1254
|
+
* query value that does not decode is that error, with the message saying
|
|
1255
|
+
* so; and a query name that does not decode matches no field and is counted
|
|
1256
|
+
* in `undecodedQueryNames`. `resolveTestId` / `getContext` stay unchanged for
|
|
1257
|
+
* LLM routes.
|
|
1258
|
+
*/
|
|
1259
|
+
function resolveMcpIdentity(req, session) {
|
|
1260
|
+
const url = req.url ?? "/";
|
|
1261
|
+
const qIdx = url.indexOf("?");
|
|
1262
|
+
const pairs = queryPairs(qIdx === -1 ? "" : url.slice(qIdx + 1));
|
|
1263
|
+
const raw = {};
|
|
1264
|
+
for (const field of [
|
|
1265
|
+
"testId",
|
|
1266
|
+
"context",
|
|
1267
|
+
"undeclared"
|
|
1268
|
+
]) {
|
|
1269
|
+
const input = MCP_IDENTITY_INPUTS[field];
|
|
1270
|
+
const sent = [["header", headerValues(req, input.header)], ["query", pairs.filter((p) => p.name === input.query).map((p) => p.value)]];
|
|
1271
|
+
for (const [source, values] of sent) if (values.length > 1) {
|
|
1272
|
+
const label = source === "header" ? msg`${fixed(input.label)} header` : msg`?${fixed(input.query)}= query parameter`;
|
|
1273
|
+
return {
|
|
1274
|
+
ok: false,
|
|
1275
|
+
error: {
|
|
1276
|
+
code: "MCP_DUPLICATE_IDENTITY",
|
|
1277
|
+
status: 400,
|
|
1278
|
+
field,
|
|
1279
|
+
source,
|
|
1280
|
+
count: values.length,
|
|
1281
|
+
message: build(msg`Duplicate ${label}: ${values.length} values sent, expected one`)
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
raw[field] = {
|
|
1286
|
+
header: sent[0][1][0],
|
|
1287
|
+
query: sent[1][1][0]
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
const fromRequest = (field) => {
|
|
1291
|
+
const { header, query: q } = raw[field];
|
|
1292
|
+
const fromQuery = q ? decodeMcpQueryValue(q) : null;
|
|
1293
|
+
if (header) {
|
|
1294
|
+
const used = decodeMcpHeaderValue(header);
|
|
1295
|
+
return {
|
|
1296
|
+
value: used.value,
|
|
1297
|
+
fellBack: used.fellBack || (fromQuery?.fellBack ?? false)
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
return fromQuery ?? {
|
|
1301
|
+
value: null,
|
|
1302
|
+
fellBack: false
|
|
1303
|
+
};
|
|
1304
|
+
};
|
|
1305
|
+
const testId = fromRequest("testId");
|
|
1306
|
+
const context = fromRequest("context");
|
|
1307
|
+
let undeclared = null;
|
|
1308
|
+
for (const source of ["header", "query"]) {
|
|
1309
|
+
const sent = raw.undeclared[source];
|
|
1310
|
+
if (sent === void 0) continue;
|
|
1311
|
+
const decoded = source === "header" ? {
|
|
1312
|
+
value: sent,
|
|
1313
|
+
fellBack: false
|
|
1314
|
+
} : decodeMcpQueryValue(sent);
|
|
1315
|
+
if (decoded.value.trim() === "") continue;
|
|
1316
|
+
const parsed = parseMcpUndeclared(decoded.value);
|
|
1317
|
+
if (parsed === null) {
|
|
1318
|
+
const echoed = echoIdentityValue(sent);
|
|
1319
|
+
const label = source === "header" ? msg`X-AIMock-MCP-Undeclared header` : msg`?undeclared= query parameter`;
|
|
1320
|
+
const note = decoded.fellBack ? msg` (not valid percent-encoding)` : msg``;
|
|
1321
|
+
return {
|
|
1322
|
+
ok: false,
|
|
1323
|
+
error: {
|
|
1324
|
+
code: "MCP_INVALID_UNDECLARED",
|
|
1325
|
+
status: 400,
|
|
1326
|
+
field: "undeclared",
|
|
1327
|
+
source,
|
|
1328
|
+
raw: cutText(sent),
|
|
1329
|
+
message: build(msg`Invalid ${label} value ${echoed}${note}: expected allow or deny`)
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
undeclared ??= parsed;
|
|
1334
|
+
}
|
|
1335
|
+
const undecodedQueryNames = pairs.filter((p) => p.name === null).length;
|
|
1336
|
+
return {
|
|
1337
|
+
ok: true,
|
|
1338
|
+
identity: {
|
|
1339
|
+
testId: testId.value ?? (session?.testId || null),
|
|
1340
|
+
context: context.value ?? (session?.context || null),
|
|
1341
|
+
undeclared: undeclared ?? session?.undeclared ?? null
|
|
1342
|
+
},
|
|
1343
|
+
supplied: {
|
|
1344
|
+
testId: testId.value !== null,
|
|
1345
|
+
context: context.value !== null,
|
|
1346
|
+
undeclared: undeclared !== null
|
|
1347
|
+
},
|
|
1348
|
+
fellBack: {
|
|
1349
|
+
testId: testId.fellBack,
|
|
1350
|
+
context: context.fellBack
|
|
1351
|
+
},
|
|
1352
|
+
...undecodedQueryNames > 0 ? { undecodedQueryNames } : {}
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
938
1356
|
* Convert a test ID (e.g. Playwright titlePath) into a filesystem-safe slug
|
|
939
1357
|
* suitable for use as a directory name in snapshot-style recording.
|
|
940
1358
|
*/
|
|
@@ -1121,7 +1539,64 @@ function buildEmbeddingResponse(embeddings, model, usage) {
|
|
|
1121
1539
|
}
|
|
1122
1540
|
};
|
|
1123
1541
|
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Build a stable, human-readable identifier for a fixture's match shape, for
|
|
1544
|
+
* any log line that has to name WHICH fixture it means. The `Fixture` type
|
|
1545
|
+
* carries no `id`/`name`, so the matchers are the only handle a reader has.
|
|
1546
|
+
*
|
|
1547
|
+
* Used by the relaxed-turnIndex warning (`router.ts`) and by the chaos
|
|
1548
|
+
* rejected-value warning (`chaos.ts`); one implementation so the two name the
|
|
1549
|
+
* same fixture the same way. The obvious `JSON.stringify(match)` is unfit: it
|
|
1550
|
+
* DROPS `predicate` functions (non-serialisable) and serialises any RegExp
|
|
1551
|
+
* matcher to `{}`, so a predicate- or regex-gated fixture's warning collapsed to
|
|
1552
|
+
* an uninformative "served fixture {}" / `{"userMessage":{}}` blob.
|
|
1553
|
+
*
|
|
1554
|
+
* Instead we list the PRESENT matcher keys in declaration order, annotating each
|
|
1555
|
+
* by VALUE KIND so predicates and regexes survive: `predicate(fn)`,
|
|
1556
|
+
* `userMessage(regex)`, `userMessage("hello")`, `turnIndex=0`, etc. The
|
|
1557
|
+
* fixture's array `index` is prefixed as a stable positional identifier when
|
|
1558
|
+
* the caller knows it (i.e. `>= 0`); callers holding only the fixture object
|
|
1559
|
+
* pass `-1` and get the matcher summary alone. String/number values are shown
|
|
1560
|
+
* inline (truncated) so a content match remains recognisable; the whole string
|
|
1561
|
+
* is capped to keep the log line bounded.
|
|
1562
|
+
*/
|
|
1563
|
+
const DESCRIBE_MATCH_MAX = 160;
|
|
1564
|
+
function describeMatch(match, index) {
|
|
1565
|
+
const parts = [];
|
|
1566
|
+
for (const [key, value] of Object.entries(match)) {
|
|
1567
|
+
if (value === void 0) continue;
|
|
1568
|
+
if (typeof value === "function") parts.push(key === "predicate" ? "[predicate]" : `${key}[fn]`);
|
|
1569
|
+
else if (value instanceof RegExp) parts.push(`${key}(${value})`);
|
|
1570
|
+
else if (typeof value === "string") {
|
|
1571
|
+
const v = value.length > 40 ? `${value.slice(0, 40)}…` : value;
|
|
1572
|
+
parts.push(`${key}(${JSON.stringify(v)})`);
|
|
1573
|
+
} else if (Array.isArray(value)) parts.push(`${key}(${value.length} item${value.length === 1 ? "" : "s"})`);
|
|
1574
|
+
else if (typeof value === "object" && value !== null) {
|
|
1575
|
+
const json = JSON.stringify(value);
|
|
1576
|
+
parts.push(`${key}(${json.length > 40 ? `${json.slice(0, 40)}…` : json})`);
|
|
1577
|
+
} else parts.push(`${key}=${String(value)}`);
|
|
1578
|
+
}
|
|
1579
|
+
const keys = parts.length > 0 ? parts.join(", ") : "no matchers";
|
|
1580
|
+
const full = `${index >= 0 ? `#${index} ` : ""}{ ${keys} }`;
|
|
1581
|
+
if (full.length <= DESCRIBE_MATCH_MAX) return full;
|
|
1582
|
+
const head = full.slice(0, DESCRIBE_MATCH_MAX - 4);
|
|
1583
|
+
const cut = head.lastIndexOf(", ");
|
|
1584
|
+
return `${cut > 0 ? head.slice(0, cut) : head}, … }`;
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Parse an `X-AIMock-MCP-Undeclared` header or `?undeclared=` value.
|
|
1588
|
+
* Modeled on {@link resolveStrictMode}: case-insensitive, surrounding
|
|
1589
|
+
* whitespace trimmed. Returns `null` when unrecognised; unlike the strict
|
|
1590
|
+
* header, {@link resolveMcpIdentity} turns that into an error rather than a
|
|
1591
|
+
* fall-through, because falling through would turn a mistyped `deny` into
|
|
1592
|
+
* the permissive default.
|
|
1593
|
+
*/
|
|
1594
|
+
function parseMcpUndeclared(raw) {
|
|
1595
|
+
const val = raw.trim().toLowerCase();
|
|
1596
|
+
if (val === "allow" || val === "deny") return val;
|
|
1597
|
+
return null;
|
|
1598
|
+
}
|
|
1124
1599
|
|
|
1125
1600
|
//#endregion
|
|
1126
|
-
export { FORMAT_TO_CONTENT_TYPE, MAX_EMBEDDING_DIMENSIONS, buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildEmbeddingResponse, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, extractOverrides, flattenHeaders, formatToMime, generateDeterministicEmbedding, generateId, generateMessageId, generateToolCallId, generateToolUseId, getContext, getTestId, isAudioResponse, isContentWithToolCallsResponse, isEmbeddingResponse, isErrorResponse, isImageResponse, isJSONResponse, isJsonObject, isResponseFactory, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, matchesPattern, normalizeEmbeddingInput, normalizeTextInput, readBody, resolveFixtureBlocks, resolveReasoningArtifactsForModel, resolveReasoningForModel, resolveResponse, resolveStrictMode, resolveTestId, resolveUsage, serializeErrorResponse, slugifyContext, slugifyTestId, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, validateChatMessages, validateEmbeddingDimensions, validateToolsField };
|
|
1601
|
+
export { FORMAT_TO_CONTENT_TYPE, MAX_EMBEDDING_DIMENSIONS, RequestBodyTooLargeError, buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildEmbeddingResponse, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, decodeMcpHeaderValue, describeMatch, extractOverrides, flattenHeaders, formatToMime, generateDeterministicEmbedding, generateId, generateMessageId, generateToolCallId, generateToolUseId, getContext, getTestId, hasMintedRequestId, isAudioResponse, isContentWithToolCallsResponse, isEmbeddingResponse, isErrorResponse, isImageResponse, isJSONResponse, isJsonObject, isLiveResponse, isResponseFactory, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, markMintedRequestId, matchesPattern, normalizeEmbeddingInput, normalizeTextInput, parseStrictIntegerText, readBody, readBodyBufferBounded, resolveFixtureBlockOutcome, resolveFixtureBlocks, resolveMcpIdentity, resolveReasoningArtifactsForModel, resolveReasoningForModel, resolveRequestId, resolveResponse, resolveStrictMode, resolveTestId, resolveUsage, serializeErrorResponse, slugifyContext, slugifyTestId, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, validateChatMessages, validateEmbeddingDimensions, validateToolsField, wouldProxyMiss };
|
|
1127
1602
|
//# sourceMappingURL=helpers.js.map
|