@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/video.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { flattenHeaders, getContext, getTestId, isErrorResponse, isJsonObject, isVideoResponse, resolveResponse, resolveStrictMode, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
|
|
2
|
-
import { matchFixtureDiagnostic } from "./router.js";
|
|
3
2
|
import { writeErrorResponse } from "./sse-writer.js";
|
|
4
|
-
import {
|
|
3
|
+
import { applyChaosAsync } from "./chaos.js";
|
|
4
|
+
import { matchFixtureDiagnostic } from "./router.js";
|
|
5
5
|
import { proxyAndRecord } from "./recorder.js";
|
|
6
6
|
import { extractBoundary, extractFormField } from "./transcription.js";
|
|
7
7
|
|
|
@@ -142,6 +142,25 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
142
142
|
} }));
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
145
|
+
const prompt = videoReq.prompt;
|
|
146
|
+
const validParts = Array.isArray(prompt) && prompt.length > 0 && prompt.every((part) => isJsonObject(part) && typeof part.type === "string" && (!("text" in part) || typeof part.text === "string"));
|
|
147
|
+
if (typeof prompt !== "string" && !validParts) {
|
|
148
|
+
journal.add({
|
|
149
|
+
method,
|
|
150
|
+
path,
|
|
151
|
+
headers: flattenHeaders(req.headers),
|
|
152
|
+
body: null,
|
|
153
|
+
response: {
|
|
154
|
+
status: 400,
|
|
155
|
+
fixture: null
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
writeErrorResponse(res, 400, JSON.stringify({ error: {
|
|
159
|
+
message: "Invalid parameter: 'prompt' must be a string or a non-empty array of content parts",
|
|
160
|
+
type: "invalid_request_error"
|
|
161
|
+
} }));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
145
164
|
const syntheticReq = {
|
|
146
165
|
model: videoReq.model ?? "sora-2",
|
|
147
166
|
messages: [{
|
|
@@ -157,7 +176,7 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
157
176
|
journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
158
177
|
defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
|
|
159
178
|
} else defaults.logger.debug(`No fixture matched for request`);
|
|
160
|
-
if (
|
|
179
|
+
if (await applyChaosAsync(res, fixture, defaults.chaos, req.headers, req.url, journal, {
|
|
161
180
|
method,
|
|
162
181
|
path,
|
|
163
182
|
headers: flattenHeaders(req.headers),
|
|
@@ -285,11 +304,11 @@ async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, set
|
|
|
285
304
|
}));
|
|
286
305
|
}
|
|
287
306
|
}
|
|
288
|
-
function handleVideoStatus(req, res, videoId, journal, defaults, setCorsHeaders, videoStates) {
|
|
307
|
+
async function handleVideoStatus(req, res, videoId, journal, defaults, setCorsHeaders, videoStates) {
|
|
289
308
|
setCorsHeaders(res);
|
|
290
309
|
const path = req.url ?? `/v1/videos/${videoId}`;
|
|
291
310
|
const method = req.method ?? "GET";
|
|
292
|
-
if (
|
|
311
|
+
if (await applyChaosAsync(res, null, defaults.chaos, req.headers, req.url, journal, {
|
|
293
312
|
method,
|
|
294
313
|
path,
|
|
295
314
|
headers: flattenHeaders(req.headers),
|
package/dist/video.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video.js","names":[],"sources":["../src/video.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport type { ChatCompletionRequest, Fixture, HandlerDefaults, VideoResponse } from \"./types.js\";\nimport {\n isVideoResponse,\n isErrorResponse,\n isJsonObject,\n serializeErrorResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\nimport { extractBoundary, extractFormField } from \"./transcription.js\";\n\ninterface VideoRequest {\n model?: string;\n prompt: string;\n [key: string]: unknown;\n}\n\n// ─── VideoStateMap with TTL and size bound ────────────────────────────────\n\nconst VIDEO_STATE_MAX_ENTRIES = 10_000;\nconst VIDEO_STATE_TTL_MS = 3_600_000; // 1 hour\n\ninterface VideoStateEntry {\n video: VideoResponse[\"video\"];\n createdAt: number;\n}\n\n/**\n * A Map wrapper for video state that enforces a maximum size and per-entry TTL.\n * Entries older than VIDEO_STATE_TTL_MS are lazily evicted on `get`.\n * When the map exceeds VIDEO_STATE_MAX_ENTRIES on `set`, the oldest entries\n * are removed to stay within bounds.\n */\nexport class VideoStateMap {\n private readonly entries = new Map<string, VideoStateEntry>();\n private readonly sweepTimer: ReturnType<typeof setInterval>;\n\n constructor() {\n // Proactive sweep every 60 seconds to evict expired entries\n this.sweepTimer = setInterval(() => {\n const now = Date.now();\n for (const [key, entry] of this.entries) {\n if (now - entry.createdAt > VIDEO_STATE_TTL_MS) {\n this.entries.delete(key);\n }\n }\n }, 60_000);\n // Allow the process to exit even if the timer is still running\n if (this.sweepTimer.unref) {\n this.sweepTimer.unref();\n }\n }\n\n getEntry(key: string): { video: VideoResponse[\"video\"]; createdAtUnix: number } | undefined {\n const entry = this.entries.get(key);\n if (!entry) return undefined;\n if (Date.now() - entry.createdAt > VIDEO_STATE_TTL_MS) {\n this.entries.delete(key);\n return undefined;\n }\n return { video: entry.video, createdAtUnix: Math.floor(entry.createdAt / 1000) };\n }\n\n getCreatedAtUnix(key: string): number | undefined {\n const e = this.getEntry(key);\n return e?.createdAtUnix;\n }\n\n set(key: string, video: VideoResponse[\"video\"]): void {\n this.entries.set(key, { video, createdAt: Date.now() });\n // Evict oldest entries if over capacity\n if (this.entries.size > VIDEO_STATE_MAX_ENTRIES) {\n const excess = this.entries.size - VIDEO_STATE_MAX_ENTRIES;\n const iter = this.entries.keys();\n for (let i = 0; i < excess; i++) {\n const next = iter.next();\n if (!next.done) this.entries.delete(next.value);\n }\n }\n }\n\n delete(key: string): boolean {\n return this.entries.delete(key);\n }\n\n clear(): void {\n this.entries.clear();\n }\n\n destroy(): void {\n clearInterval(this.sweepTimer);\n this.entries.clear();\n }\n\n get size(): number {\n return this.entries.size;\n }\n}\n\nexport async function handleVideoCreate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n videoStates: VideoStateMap,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? \"/v1/videos\";\n const method = req.method ?? \"POST\";\n\n const contentType = Array.isArray(req.headers[\"content-type\"])\n ? req.headers[\"content-type\"][0]\n : req.headers[\"content-type\"];\n const isMultipart = (contentType ?? \"\").toLowerCase().includes(\"multipart/form-data\");\n\n let videoReq: VideoRequest;\n if (isMultipart) {\n // The OpenAI SDK (6.28.0+) sends POST /v1/videos as multipart/form-data;\n // older SDKs sent JSON for a File-less body. Parse the form fields into the\n // same shape the JSON path produces, reusing the transcription multipart\n // helpers. Numeric fields (e.g. `seconds`) arrive as strings and are\n // coerced to numbers to match the JSON body's types.\n const boundary = extractBoundary(contentType);\n const prompt = extractFormField(raw, \"prompt\", boundary);\n const model = extractFormField(raw, \"model\", boundary);\n const size = extractFormField(raw, \"size\", boundary);\n const secondsRaw = extractFormField(raw, \"seconds\", boundary);\n videoReq = { prompt: prompt ?? \"\" };\n if (model !== undefined) videoReq.model = model;\n if (size !== undefined) videoReq.size = size;\n if (secondsRaw !== undefined) {\n const secondsNum = Number(secondsRaw);\n videoReq.seconds = Number.isNaN(secondsNum) ? secondsRaw : secondsNum;\n }\n } else {\n try {\n videoReq = JSON.parse(raw) as VideoRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: `Malformed JSON: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n }\n\n // Reject bodies that parsed but are not a JSON object (e.g. `null`) before\n // touching fields — otherwise `videoReq.prompt` throws a TypeError that\n // surfaces as a 500 instead of a 400. The multipart branch above builds its\n // own object and needs no guard.\n if (!isJsonObject(videoReq)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Request body must be a JSON object\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!videoReq.prompt) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: { message: \"Missing required parameter: 'prompt'\", type: \"invalid_request_error\" },\n }),\n );\n return;\n }\n\n const syntheticReq: ChatCompletionRequest = {\n model: videoReq.model ?? \"sora-2\",\n messages: [{ role: \"user\", content: videoReq.prompt }],\n _endpointType: \"video\",\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"openai\",\n req.url ?? \"/v1/videos\",\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isVideoResponse(response)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: { message: \"Fixture response is not a video type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const video = response.video;\n\n // Store for GET status checks\n const stateKey = `${testId}:${video.id}`;\n videoStates.set(stateKey, video);\n const created_at = videoStates.getCreatedAtUnix(stateKey)!;\n\n if (video.status === \"completed\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ id: video.id, status: video.status, url: video.url, created_at }));\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ id: video.id, status: video.status, created_at }));\n }\n}\n\nexport function handleVideoStatus(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n videoId: string,\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n videoStates: VideoStateMap,\n): void {\n setCorsHeaders(res);\n const path = req.url ?? `/v1/videos/${videoId}`;\n const method = req.method ?? \"GET\";\n\n if (\n applyChaos(\n res,\n null,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: null },\n \"internal\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n const testId = getTestId(req);\n const stateKey = `${testId}:${videoId}`;\n const entry = videoStates.getEntry(stateKey);\n\n if (!entry) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 404, fixture: null },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({ error: { message: `Video ${videoId} not found`, type: \"not_found\" } }),\n );\n return;\n }\n\n const { video, createdAtUnix: created_at } = entry;\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 200, fixture: null },\n });\n\n const body: Record<string, unknown> = {\n id: video.id,\n status: video.status,\n created_at,\n };\n if (video.url) body.url = video.url;\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"video.js","names":[],"sources":["../src/video.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport type { ChatCompletionRequest, Fixture, HandlerDefaults, VideoResponse } from \"./types.js\";\nimport {\n isVideoResponse,\n isErrorResponse,\n isJsonObject,\n serializeErrorResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaosAsync } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\nimport { extractBoundary, extractFormField } from \"./transcription.js\";\n\ninterface VideoRequest {\n model?: string;\n prompt: string;\n [key: string]: unknown;\n}\n\n// ─── VideoStateMap with TTL and size bound ────────────────────────────────\n\nconst VIDEO_STATE_MAX_ENTRIES = 10_000;\nconst VIDEO_STATE_TTL_MS = 3_600_000; // 1 hour\n\ninterface VideoStateEntry {\n video: VideoResponse[\"video\"];\n createdAt: number;\n}\n\n/**\n * A Map wrapper for video state that enforces a maximum size and per-entry TTL.\n * Entries older than VIDEO_STATE_TTL_MS are lazily evicted on `get`.\n * When the map exceeds VIDEO_STATE_MAX_ENTRIES on `set`, the oldest entries\n * are removed to stay within bounds.\n */\nexport class VideoStateMap {\n private readonly entries = new Map<string, VideoStateEntry>();\n private readonly sweepTimer: ReturnType<typeof setInterval>;\n\n constructor() {\n // Proactive sweep every 60 seconds to evict expired entries\n this.sweepTimer = setInterval(() => {\n const now = Date.now();\n for (const [key, entry] of this.entries) {\n if (now - entry.createdAt > VIDEO_STATE_TTL_MS) {\n this.entries.delete(key);\n }\n }\n }, 60_000);\n // Allow the process to exit even if the timer is still running\n if (this.sweepTimer.unref) {\n this.sweepTimer.unref();\n }\n }\n\n getEntry(key: string): { video: VideoResponse[\"video\"]; createdAtUnix: number } | undefined {\n const entry = this.entries.get(key);\n if (!entry) return undefined;\n if (Date.now() - entry.createdAt > VIDEO_STATE_TTL_MS) {\n this.entries.delete(key);\n return undefined;\n }\n return { video: entry.video, createdAtUnix: Math.floor(entry.createdAt / 1000) };\n }\n\n getCreatedAtUnix(key: string): number | undefined {\n const e = this.getEntry(key);\n return e?.createdAtUnix;\n }\n\n set(key: string, video: VideoResponse[\"video\"]): void {\n this.entries.set(key, { video, createdAt: Date.now() });\n // Evict oldest entries if over capacity\n if (this.entries.size > VIDEO_STATE_MAX_ENTRIES) {\n const excess = this.entries.size - VIDEO_STATE_MAX_ENTRIES;\n const iter = this.entries.keys();\n for (let i = 0; i < excess; i++) {\n const next = iter.next();\n if (!next.done) this.entries.delete(next.value);\n }\n }\n }\n\n delete(key: string): boolean {\n return this.entries.delete(key);\n }\n\n clear(): void {\n this.entries.clear();\n }\n\n destroy(): void {\n clearInterval(this.sweepTimer);\n this.entries.clear();\n }\n\n get size(): number {\n return this.entries.size;\n }\n}\n\nexport async function handleVideoCreate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n videoStates: VideoStateMap,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? \"/v1/videos\";\n const method = req.method ?? \"POST\";\n\n const contentType = Array.isArray(req.headers[\"content-type\"])\n ? req.headers[\"content-type\"][0]\n : req.headers[\"content-type\"];\n const isMultipart = (contentType ?? \"\").toLowerCase().includes(\"multipart/form-data\");\n\n let videoReq: VideoRequest;\n if (isMultipart) {\n // The OpenAI SDK (6.28.0+) sends POST /v1/videos as multipart/form-data;\n // older SDKs sent JSON for a File-less body. Parse the form fields into the\n // same shape the JSON path produces, reusing the transcription multipart\n // helpers. Numeric fields (e.g. `seconds`) arrive as strings and are\n // coerced to numbers to match the JSON body's types.\n const boundary = extractBoundary(contentType);\n const prompt = extractFormField(raw, \"prompt\", boundary);\n const model = extractFormField(raw, \"model\", boundary);\n const size = extractFormField(raw, \"size\", boundary);\n const secondsRaw = extractFormField(raw, \"seconds\", boundary);\n videoReq = { prompt: prompt ?? \"\" };\n if (model !== undefined) videoReq.model = model;\n if (size !== undefined) videoReq.size = size;\n if (secondsRaw !== undefined) {\n const secondsNum = Number(secondsRaw);\n videoReq.seconds = Number.isNaN(secondsNum) ? secondsRaw : secondsNum;\n }\n } else {\n try {\n videoReq = JSON.parse(raw) as VideoRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: `Malformed JSON: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n }\n\n // Reject bodies that parsed but are not a JSON object (e.g. `null`) before\n // touching fields — otherwise `videoReq.prompt` throws a TypeError that\n // surfaces as a 500 instead of a 400. The multipart branch above builds its\n // own object and needs no guard.\n if (!isJsonObject(videoReq)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Request body must be a JSON object\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!videoReq.prompt) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: { message: \"Missing required parameter: 'prompt'\", type: \"invalid_request_error\" },\n }),\n );\n return;\n }\n\n const prompt = videoReq.prompt;\n const validParts =\n Array.isArray(prompt) &&\n prompt.length > 0 &&\n prompt.every(\n (part: unknown) =>\n isJsonObject(part) &&\n typeof part.type === \"string\" &&\n (!(\"text\" in part) || typeof part.text === \"string\"),\n );\n if (typeof prompt !== \"string\" && !validParts) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message:\n \"Invalid parameter: 'prompt' must be a string or a non-empty array of content parts\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const syntheticReq: ChatCompletionRequest = {\n model: videoReq.model ?? \"sora-2\",\n messages: [{ role: \"user\", content: videoReq.prompt }],\n _endpointType: \"video\",\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"openai\",\n req.url ?? \"/v1/videos\",\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isVideoResponse(response)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: { message: \"Fixture response is not a video type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const video = response.video;\n\n // Store for GET status checks\n const stateKey = `${testId}:${video.id}`;\n videoStates.set(stateKey, video);\n const created_at = videoStates.getCreatedAtUnix(stateKey)!;\n\n if (video.status === \"completed\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ id: video.id, status: video.status, url: video.url, created_at }));\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ id: video.id, status: video.status, created_at }));\n }\n}\n\nexport async function handleVideoStatus(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n videoId: string,\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n videoStates: VideoStateMap,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? `/v1/videos/${videoId}`;\n const method = req.method ?? \"GET\";\n\n if (\n await applyChaosAsync(\n res,\n null,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: null },\n \"internal\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n const testId = getTestId(req);\n const stateKey = `${testId}:${videoId}`;\n const entry = videoStates.getEntry(stateKey);\n\n if (!entry) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 404, fixture: null },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({ error: { message: `Video ${videoId} not found`, type: \"not_found\" } }),\n );\n return;\n }\n\n const { video, createdAtUnix: created_at } = entry;\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 200, fixture: null },\n });\n\n const body: Record<string, unknown> = {\n id: video.id,\n status: video.status,\n created_at,\n };\n if (video.url) body.url = video.url;\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":"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"}
|
package/dist/vitest.cjs
CHANGED
|
@@ -22,8 +22,11 @@ let vitest = require("vitest");
|
|
|
22
22
|
/**
|
|
23
23
|
* Start an aimock server for the duration of the test suite.
|
|
24
24
|
*
|
|
25
|
-
* - `beforeAll`: starts the server and optionally loads fixtures
|
|
26
|
-
*
|
|
25
|
+
* - `beforeAll`: starts the server and optionally loads fixtures (LLM fixtures and
|
|
26
|
+
* `mcpFakes` blocks; a bad `mcpFakes` block fails `beforeAll`)
|
|
27
|
+
* - `beforeEach`: closes Live sessions and resets fixture match counts and MCP fake
|
|
28
|
+
* consumption (not fixtures or fakes)
|
|
29
|
+
* - `afterEach`: closes Live sessions owned by this helper's server
|
|
27
30
|
* - `afterAll`: stops the server
|
|
28
31
|
*
|
|
29
32
|
* Returns a getter function — call it inside tests to access the handle.
|
|
@@ -35,10 +38,7 @@ function useAimock(options = {}) {
|
|
|
35
38
|
(0, vitest.beforeAll)(async () => {
|
|
36
39
|
const { fixtures: fixturePath, patchEnv, ...serverOpts } = options;
|
|
37
40
|
const llm = new require_llmock.LLMock(serverOpts);
|
|
38
|
-
if (fixturePath)
|
|
39
|
-
const loadedFixtures = loadFixtures((0, node_path.resolve)(fixturePath));
|
|
40
|
-
for (const f of loadedFixtures) llm.addFixture(f);
|
|
41
|
-
}
|
|
41
|
+
if (fixturePath) loadFixtures(llm, (0, node_path.resolve)(fixturePath));
|
|
42
42
|
const url = await llm.start();
|
|
43
43
|
if (patchEnv !== false) {
|
|
44
44
|
origOpenaiUrl = process.env.OPENAI_BASE_URL;
|
|
@@ -52,7 +52,13 @@ function useAimock(options = {}) {
|
|
|
52
52
|
};
|
|
53
53
|
});
|
|
54
54
|
(0, vitest.beforeEach)(() => {
|
|
55
|
-
if (handle)
|
|
55
|
+
if (handle) {
|
|
56
|
+
handle.llm.closeLiveSessions();
|
|
57
|
+
handle.llm.resetMatchCounts();
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
(0, vitest.afterEach)(() => {
|
|
61
|
+
handle?.llm.closeLiveSessions();
|
|
56
62
|
});
|
|
57
63
|
(0, vitest.afterAll)(async () => {
|
|
58
64
|
if (handle) {
|
|
@@ -71,13 +77,19 @@ function useAimock(options = {}) {
|
|
|
71
77
|
return handle;
|
|
72
78
|
};
|
|
73
79
|
}
|
|
74
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Load a fixture file or directory into `llm`: its LLM fixtures and its
|
|
82
|
+
* `mcpFakes` blocks, which the LLMock buffers and auto-mounts at start (F12).
|
|
83
|
+
* A `FixtureLoadError` (a bad `mcpFakes` block) propagates and fails
|
|
84
|
+
* `beforeAll`; any other load failure is only warned about, as before.
|
|
85
|
+
*/
|
|
86
|
+
function loadFixtures(llm, fixturePath) {
|
|
75
87
|
try {
|
|
76
|
-
if ((0, node_fs.statSync)(fixturePath).isDirectory())
|
|
77
|
-
|
|
88
|
+
if ((0, node_fs.statSync)(fixturePath).isDirectory()) llm.loadFixtureDir(fixturePath);
|
|
89
|
+
else llm.loadFixtureFile(fixturePath);
|
|
78
90
|
} catch (err) {
|
|
91
|
+
if (err instanceof require_fixture_loader.FixtureLoadError) throw err;
|
|
79
92
|
console.warn(`[aimock] Failed to load fixtures from ${fixturePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
80
|
-
return [];
|
|
81
93
|
}
|
|
82
94
|
}
|
|
83
95
|
|
package/dist/vitest.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest.cjs","names":["LLMock","
|
|
1
|
+
{"version":3,"file":"vitest.cjs","names":["LLMock","FixtureLoadError"],"sources":["../src/vitest.ts"],"sourcesContent":["/**\n * Vitest integration for aimock.\n *\n * Usage:\n * import { useAimock } from \"@copilotkit/aimock/vitest\";\n *\n * const mock = useAimock({ fixtures: \"./fixtures\" });\n *\n * it(\"responds\", async () => {\n * const res = await fetch(`${mock().url}/v1/chat/completions`, { ... });\n * });\n */\n\nimport { beforeAll, afterAll, beforeEach, afterEach } from \"vitest\";\nimport { LLMock } from \"./llmock.js\";\nimport { FixtureLoadError } from \"./fixture-loader.js\";\nimport type { MockServerOptions } from \"./types.js\";\nimport { statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nexport interface UseAimockOptions extends MockServerOptions {\n /** Path to fixture file or directory. Loaded automatically on start. */\n fixtures?: string;\n /** If true, sets process.env.OPENAI_BASE_URL to the mock URL + /v1. */\n patchEnv?: boolean;\n}\n\nexport interface AimockHandle {\n /** The LLMock instance. */\n readonly llm: LLMock;\n /** The server URL (e.g., http://127.0.0.1:4010). */\n readonly url: string;\n}\n\n/**\n * Start an aimock server for the duration of the test suite.\n *\n * - `beforeAll`: starts the server and optionally loads fixtures (LLM fixtures and\n * `mcpFakes` blocks; a bad `mcpFakes` block fails `beforeAll`)\n * - `beforeEach`: closes Live sessions and resets fixture match counts and MCP fake\n * consumption (not fixtures or fakes)\n * - `afterEach`: closes Live sessions owned by this helper's server\n * - `afterAll`: stops the server\n *\n * Returns a getter function — call it inside tests to access the handle.\n */\nexport function useAimock(options: UseAimockOptions = {}): () => AimockHandle {\n let handle: AimockHandle | null = null;\n let origOpenaiUrl: string | undefined;\n let origAnthropicUrl: string | undefined;\n\n beforeAll(async () => {\n const { fixtures: fixturePath, patchEnv, ...serverOpts } = options;\n const llm = new LLMock(serverOpts);\n\n if (fixturePath) {\n loadFixtures(llm, resolve(fixturePath));\n }\n\n const url = await llm.start();\n\n if (patchEnv !== false) {\n origOpenaiUrl = process.env.OPENAI_BASE_URL;\n origAnthropicUrl = process.env.ANTHROPIC_BASE_URL;\n process.env.OPENAI_BASE_URL = `${url}/v1`;\n process.env.ANTHROPIC_BASE_URL = `${url}/v1`;\n }\n\n handle = { llm, url };\n });\n\n beforeEach(() => {\n if (handle) {\n handle.llm.closeLiveSessions();\n handle.llm.resetMatchCounts();\n }\n });\n\n afterEach(() => {\n handle?.llm.closeLiveSessions();\n });\n\n afterAll(async () => {\n if (handle) {\n if (options.patchEnv !== false) {\n if (origOpenaiUrl !== undefined) process.env.OPENAI_BASE_URL = origOpenaiUrl;\n else delete process.env.OPENAI_BASE_URL;\n if (origAnthropicUrl !== undefined) process.env.ANTHROPIC_BASE_URL = origAnthropicUrl;\n else delete process.env.ANTHROPIC_BASE_URL;\n }\n await handle.llm.stop();\n handle = null;\n }\n });\n\n return () => {\n if (!handle) {\n throw new Error(\"useAimock(): server not started — are you calling this inside a test?\");\n }\n return handle;\n };\n}\n\n/**\n * Load a fixture file or directory into `llm`: its LLM fixtures and its\n * `mcpFakes` blocks, which the LLMock buffers and auto-mounts at start (F12).\n * A `FixtureLoadError` (a bad `mcpFakes` block) propagates and fails\n * `beforeAll`; any other load failure is only warned about, as before.\n */\nfunction loadFixtures(llm: LLMock, fixturePath: string): void {\n try {\n const stat = statSync(fixturePath);\n if (stat.isDirectory()) {\n llm.loadFixtureDir(fixturePath);\n } else {\n llm.loadFixtureFile(fixturePath);\n }\n } catch (err) {\n if (err instanceof FixtureLoadError) throw err;\n console.warn(\n `[aimock] Failed to load fixtures from ${fixturePath}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\nexport { LLMock } from \"./llmock.js\";\nexport type { MockServerOptions, Fixture } from \"./types.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,SAAgB,UAAU,UAA4B,EAAE,EAAsB;CAC5E,IAAI,SAA8B;CAClC,IAAI;CACJ,IAAI;AAEJ,uBAAU,YAAY;EACpB,MAAM,EAAE,UAAU,aAAa,UAAU,GAAG,eAAe;EAC3D,MAAM,MAAM,IAAIA,sBAAO,WAAW;AAElC,MAAI,YACF,cAAa,4BAAa,YAAY,CAAC;EAGzC,MAAM,MAAM,MAAM,IAAI,OAAO;AAE7B,MAAI,aAAa,OAAO;AACtB,mBAAgB,QAAQ,IAAI;AAC5B,sBAAmB,QAAQ,IAAI;AAC/B,WAAQ,IAAI,kBAAkB,GAAG,IAAI;AACrC,WAAQ,IAAI,qBAAqB,GAAG,IAAI;;AAG1C,WAAS;GAAE;GAAK;GAAK;GACrB;AAEF,8BAAiB;AACf,MAAI,QAAQ;AACV,UAAO,IAAI,mBAAmB;AAC9B,UAAO,IAAI,kBAAkB;;GAE/B;AAEF,6BAAgB;AACd,UAAQ,IAAI,mBAAmB;GAC/B;AAEF,sBAAS,YAAY;AACnB,MAAI,QAAQ;AACV,OAAI,QAAQ,aAAa,OAAO;AAC9B,QAAI,kBAAkB,OAAW,SAAQ,IAAI,kBAAkB;QAC1D,QAAO,QAAQ,IAAI;AACxB,QAAI,qBAAqB,OAAW,SAAQ,IAAI,qBAAqB;QAChE,QAAO,QAAQ,IAAI;;AAE1B,SAAM,OAAO,IAAI,MAAM;AACvB,YAAS;;GAEX;AAEF,cAAa;AACX,MAAI,CAAC,OACH,OAAM,IAAI,MAAM,wEAAwE;AAE1F,SAAO;;;;;;;;;AAUX,SAAS,aAAa,KAAa,aAA2B;AAC5D,KAAI;AAEF,4BADsB,YAAY,CACzB,aAAa,CACpB,KAAI,eAAe,YAAY;MAE/B,KAAI,gBAAgB,YAAY;UAE3B,KAAK;AACZ,MAAI,eAAeC,wCAAkB,OAAM;AAC3C,UAAQ,KACN,yCAAyC,YAAY,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAC1G"}
|
package/dist/vitest.d.cts
CHANGED
|
@@ -18,8 +18,11 @@ interface AimockHandle {
|
|
|
18
18
|
/**
|
|
19
19
|
* Start an aimock server for the duration of the test suite.
|
|
20
20
|
*
|
|
21
|
-
* - `beforeAll`: starts the server and optionally loads fixtures
|
|
22
|
-
*
|
|
21
|
+
* - `beforeAll`: starts the server and optionally loads fixtures (LLM fixtures and
|
|
22
|
+
* `mcpFakes` blocks; a bad `mcpFakes` block fails `beforeAll`)
|
|
23
|
+
* - `beforeEach`: closes Live sessions and resets fixture match counts and MCP fake
|
|
24
|
+
* consumption (not fixtures or fakes)
|
|
25
|
+
* - `afterEach`: closes Live sessions owned by this helper's server
|
|
23
26
|
* - `afterAll`: stops the server
|
|
24
27
|
*
|
|
25
28
|
* Returns a getter function — call it inside tests to access the handle.
|
package/dist/vitest.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest.d.cts","names":[],"sources":["../src/vitest.ts"],"sourcesContent":[],"mappings":";;;;;UAoBiB,gBAAA,SAAyB;;;;;;UAOzB,YAAA;;gBAED
|
|
1
|
+
{"version":3,"file":"vitest.d.cts","names":[],"sources":["../src/vitest.ts"],"sourcesContent":[],"mappings":";;;;;UAoBiB,gBAAA,SAAyB;;;;;;UAOzB,YAAA;;gBAED;;;;;;;;;;;;;;;;iBAiBA,SAAA,WAAmB,yBAA8B"}
|
package/dist/vitest.d.ts
CHANGED
|
@@ -18,8 +18,11 @@ interface AimockHandle {
|
|
|
18
18
|
/**
|
|
19
19
|
* Start an aimock server for the duration of the test suite.
|
|
20
20
|
*
|
|
21
|
-
* - `beforeAll`: starts the server and optionally loads fixtures
|
|
22
|
-
*
|
|
21
|
+
* - `beforeAll`: starts the server and optionally loads fixtures (LLM fixtures and
|
|
22
|
+
* `mcpFakes` blocks; a bad `mcpFakes` block fails `beforeAll`)
|
|
23
|
+
* - `beforeEach`: closes Live sessions and resets fixture match counts and MCP fake
|
|
24
|
+
* consumption (not fixtures or fakes)
|
|
25
|
+
* - `afterEach`: closes Live sessions owned by this helper's server
|
|
23
26
|
* - `afterAll`: stops the server
|
|
24
27
|
*
|
|
25
28
|
* Returns a getter function — call it inside tests to access the handle.
|
package/dist/vitest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest.d.ts","names":[],"sources":["../src/vitest.ts"],"sourcesContent":[],"mappings":";;;;;UAoBiB,gBAAA,SAAyB;;;;;;UAOzB,YAAA;;gBAED
|
|
1
|
+
{"version":3,"file":"vitest.d.ts","names":[],"sources":["../src/vitest.ts"],"sourcesContent":[],"mappings":";;;;;UAoBiB,gBAAA,SAAyB;;;;;;UAOzB,YAAA;;gBAED;;;;;;;;;;;;;;;;iBAiBA,SAAA,WAAmB,yBAA8B"}
|
package/dist/vitest.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FixtureLoadError } from "./fixture-loader.js";
|
|
2
2
|
import { LLMock } from "./llmock.js";
|
|
3
3
|
import { statSync } from "node:fs";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
|
-
import { afterAll, beforeAll, beforeEach } from "vitest";
|
|
5
|
+
import { afterAll, afterEach, beforeAll, beforeEach } from "vitest";
|
|
6
6
|
|
|
7
7
|
//#region src/vitest.ts
|
|
8
8
|
/**
|
|
@@ -20,8 +20,11 @@ import { afterAll, beforeAll, beforeEach } from "vitest";
|
|
|
20
20
|
/**
|
|
21
21
|
* Start an aimock server for the duration of the test suite.
|
|
22
22
|
*
|
|
23
|
-
* - `beforeAll`: starts the server and optionally loads fixtures
|
|
24
|
-
*
|
|
23
|
+
* - `beforeAll`: starts the server and optionally loads fixtures (LLM fixtures and
|
|
24
|
+
* `mcpFakes` blocks; a bad `mcpFakes` block fails `beforeAll`)
|
|
25
|
+
* - `beforeEach`: closes Live sessions and resets fixture match counts and MCP fake
|
|
26
|
+
* consumption (not fixtures or fakes)
|
|
27
|
+
* - `afterEach`: closes Live sessions owned by this helper's server
|
|
25
28
|
* - `afterAll`: stops the server
|
|
26
29
|
*
|
|
27
30
|
* Returns a getter function — call it inside tests to access the handle.
|
|
@@ -33,10 +36,7 @@ function useAimock(options = {}) {
|
|
|
33
36
|
beforeAll(async () => {
|
|
34
37
|
const { fixtures: fixturePath, patchEnv, ...serverOpts } = options;
|
|
35
38
|
const llm = new LLMock(serverOpts);
|
|
36
|
-
if (fixturePath)
|
|
37
|
-
const loadedFixtures = loadFixtures(resolve(fixturePath));
|
|
38
|
-
for (const f of loadedFixtures) llm.addFixture(f);
|
|
39
|
-
}
|
|
39
|
+
if (fixturePath) loadFixtures(llm, resolve(fixturePath));
|
|
40
40
|
const url = await llm.start();
|
|
41
41
|
if (patchEnv !== false) {
|
|
42
42
|
origOpenaiUrl = process.env.OPENAI_BASE_URL;
|
|
@@ -50,7 +50,13 @@ function useAimock(options = {}) {
|
|
|
50
50
|
};
|
|
51
51
|
});
|
|
52
52
|
beforeEach(() => {
|
|
53
|
-
if (handle)
|
|
53
|
+
if (handle) {
|
|
54
|
+
handle.llm.closeLiveSessions();
|
|
55
|
+
handle.llm.resetMatchCounts();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
handle?.llm.closeLiveSessions();
|
|
54
60
|
});
|
|
55
61
|
afterAll(async () => {
|
|
56
62
|
if (handle) {
|
|
@@ -69,13 +75,19 @@ function useAimock(options = {}) {
|
|
|
69
75
|
return handle;
|
|
70
76
|
};
|
|
71
77
|
}
|
|
72
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Load a fixture file or directory into `llm`: its LLM fixtures and its
|
|
80
|
+
* `mcpFakes` blocks, which the LLMock buffers and auto-mounts at start (F12).
|
|
81
|
+
* A `FixtureLoadError` (a bad `mcpFakes` block) propagates and fails
|
|
82
|
+
* `beforeAll`; any other load failure is only warned about, as before.
|
|
83
|
+
*/
|
|
84
|
+
function loadFixtures(llm, fixturePath) {
|
|
73
85
|
try {
|
|
74
|
-
if (statSync(fixturePath).isDirectory())
|
|
75
|
-
|
|
86
|
+
if (statSync(fixturePath).isDirectory()) llm.loadFixtureDir(fixturePath);
|
|
87
|
+
else llm.loadFixtureFile(fixturePath);
|
|
76
88
|
} catch (err) {
|
|
89
|
+
if (err instanceof FixtureLoadError) throw err;
|
|
77
90
|
console.warn(`[aimock] Failed to load fixtures from ${fixturePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
78
|
-
return [];
|
|
79
91
|
}
|
|
80
92
|
}
|
|
81
93
|
|
package/dist/vitest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest.js","names":[],"sources":["../src/vitest.ts"],"sourcesContent":["/**\n * Vitest integration for aimock.\n *\n * Usage:\n * import { useAimock } from \"@copilotkit/aimock/vitest\";\n *\n * const mock = useAimock({ fixtures: \"./fixtures\" });\n *\n * it(\"responds\", async () => {\n * const res = await fetch(`${mock().url}/v1/chat/completions`, { ... });\n * });\n */\n\nimport { beforeAll, afterAll, beforeEach } from \"vitest\";\nimport { LLMock } from \"./llmock.js\";\nimport {
|
|
1
|
+
{"version":3,"file":"vitest.js","names":[],"sources":["../src/vitest.ts"],"sourcesContent":["/**\n * Vitest integration for aimock.\n *\n * Usage:\n * import { useAimock } from \"@copilotkit/aimock/vitest\";\n *\n * const mock = useAimock({ fixtures: \"./fixtures\" });\n *\n * it(\"responds\", async () => {\n * const res = await fetch(`${mock().url}/v1/chat/completions`, { ... });\n * });\n */\n\nimport { beforeAll, afterAll, beforeEach, afterEach } from \"vitest\";\nimport { LLMock } from \"./llmock.js\";\nimport { FixtureLoadError } from \"./fixture-loader.js\";\nimport type { MockServerOptions } from \"./types.js\";\nimport { statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nexport interface UseAimockOptions extends MockServerOptions {\n /** Path to fixture file or directory. Loaded automatically on start. */\n fixtures?: string;\n /** If true, sets process.env.OPENAI_BASE_URL to the mock URL + /v1. */\n patchEnv?: boolean;\n}\n\nexport interface AimockHandle {\n /** The LLMock instance. */\n readonly llm: LLMock;\n /** The server URL (e.g., http://127.0.0.1:4010). */\n readonly url: string;\n}\n\n/**\n * Start an aimock server for the duration of the test suite.\n *\n * - `beforeAll`: starts the server and optionally loads fixtures (LLM fixtures and\n * `mcpFakes` blocks; a bad `mcpFakes` block fails `beforeAll`)\n * - `beforeEach`: closes Live sessions and resets fixture match counts and MCP fake\n * consumption (not fixtures or fakes)\n * - `afterEach`: closes Live sessions owned by this helper's server\n * - `afterAll`: stops the server\n *\n * Returns a getter function — call it inside tests to access the handle.\n */\nexport function useAimock(options: UseAimockOptions = {}): () => AimockHandle {\n let handle: AimockHandle | null = null;\n let origOpenaiUrl: string | undefined;\n let origAnthropicUrl: string | undefined;\n\n beforeAll(async () => {\n const { fixtures: fixturePath, patchEnv, ...serverOpts } = options;\n const llm = new LLMock(serverOpts);\n\n if (fixturePath) {\n loadFixtures(llm, resolve(fixturePath));\n }\n\n const url = await llm.start();\n\n if (patchEnv !== false) {\n origOpenaiUrl = process.env.OPENAI_BASE_URL;\n origAnthropicUrl = process.env.ANTHROPIC_BASE_URL;\n process.env.OPENAI_BASE_URL = `${url}/v1`;\n process.env.ANTHROPIC_BASE_URL = `${url}/v1`;\n }\n\n handle = { llm, url };\n });\n\n beforeEach(() => {\n if (handle) {\n handle.llm.closeLiveSessions();\n handle.llm.resetMatchCounts();\n }\n });\n\n afterEach(() => {\n handle?.llm.closeLiveSessions();\n });\n\n afterAll(async () => {\n if (handle) {\n if (options.patchEnv !== false) {\n if (origOpenaiUrl !== undefined) process.env.OPENAI_BASE_URL = origOpenaiUrl;\n else delete process.env.OPENAI_BASE_URL;\n if (origAnthropicUrl !== undefined) process.env.ANTHROPIC_BASE_URL = origAnthropicUrl;\n else delete process.env.ANTHROPIC_BASE_URL;\n }\n await handle.llm.stop();\n handle = null;\n }\n });\n\n return () => {\n if (!handle) {\n throw new Error(\"useAimock(): server not started — are you calling this inside a test?\");\n }\n return handle;\n };\n}\n\n/**\n * Load a fixture file or directory into `llm`: its LLM fixtures and its\n * `mcpFakes` blocks, which the LLMock buffers and auto-mounts at start (F12).\n * A `FixtureLoadError` (a bad `mcpFakes` block) propagates and fails\n * `beforeAll`; any other load failure is only warned about, as before.\n */\nfunction loadFixtures(llm: LLMock, fixturePath: string): void {\n try {\n const stat = statSync(fixturePath);\n if (stat.isDirectory()) {\n llm.loadFixtureDir(fixturePath);\n } else {\n llm.loadFixtureFile(fixturePath);\n }\n } catch (err) {\n if (err instanceof FixtureLoadError) throw err;\n console.warn(\n `[aimock] Failed to load fixtures from ${fixturePath}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\nexport { LLMock } from \"./llmock.js\";\nexport type { MockServerOptions, Fixture } from \"./types.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,SAAgB,UAAU,UAA4B,EAAE,EAAsB;CAC5E,IAAI,SAA8B;CAClC,IAAI;CACJ,IAAI;AAEJ,WAAU,YAAY;EACpB,MAAM,EAAE,UAAU,aAAa,UAAU,GAAG,eAAe;EAC3D,MAAM,MAAM,IAAI,OAAO,WAAW;AAElC,MAAI,YACF,cAAa,KAAK,QAAQ,YAAY,CAAC;EAGzC,MAAM,MAAM,MAAM,IAAI,OAAO;AAE7B,MAAI,aAAa,OAAO;AACtB,mBAAgB,QAAQ,IAAI;AAC5B,sBAAmB,QAAQ,IAAI;AAC/B,WAAQ,IAAI,kBAAkB,GAAG,IAAI;AACrC,WAAQ,IAAI,qBAAqB,GAAG,IAAI;;AAG1C,WAAS;GAAE;GAAK;GAAK;GACrB;AAEF,kBAAiB;AACf,MAAI,QAAQ;AACV,UAAO,IAAI,mBAAmB;AAC9B,UAAO,IAAI,kBAAkB;;GAE/B;AAEF,iBAAgB;AACd,UAAQ,IAAI,mBAAmB;GAC/B;AAEF,UAAS,YAAY;AACnB,MAAI,QAAQ;AACV,OAAI,QAAQ,aAAa,OAAO;AAC9B,QAAI,kBAAkB,OAAW,SAAQ,IAAI,kBAAkB;QAC1D,QAAO,QAAQ,IAAI;AACxB,QAAI,qBAAqB,OAAW,SAAQ,IAAI,qBAAqB;QAChE,QAAO,QAAQ,IAAI;;AAE1B,SAAM,OAAO,IAAI,MAAM;AACvB,YAAS;;GAEX;AAEF,cAAa;AACX,MAAI,CAAC,OACH,OAAM,IAAI,MAAM,wEAAwE;AAE1F,SAAO;;;;;;;;;AAUX,SAAS,aAAa,KAAa,aAA2B;AAC5D,KAAI;AAEF,MADa,SAAS,YAAY,CACzB,aAAa,CACpB,KAAI,eAAe,YAAY;MAE/B,KAAI,gBAAgB,YAAY;UAE3B,KAAK;AACZ,MAAI,eAAe,iBAAkB,OAAM;AAC3C,UAAQ,KACN,yCAAyC,YAAY,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAC1G"}
|
package/dist/watcher.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
|
+
const require_fixture_loader = require('./fixture-loader.cjs');
|
|
2
3
|
let node_fs = require("node:fs");
|
|
3
4
|
|
|
4
5
|
//#region src/watcher.ts
|
|
@@ -31,7 +32,7 @@ function watchFixtures(fixturePath, fixtures, loadFn, opts) {
|
|
|
31
32
|
return;
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
|
-
fixtures
|
|
35
|
+
require_fixture_loader.clearFixtureQueue(fixtures);
|
|
35
36
|
fixtures.push(...newFixtures);
|
|
36
37
|
logger.info(`Reloaded ${newFixtures.length} fixture(s)`);
|
|
37
38
|
}
|
package/dist/watcher.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watcher.cjs","names":[],"sources":["../src/watcher.ts"],"sourcesContent":["import { watch, type FSWatcher } from \"node:fs\";\nimport type { Fixture } from \"./types.js\";\nimport type { Logger } from \"./logger.js\";\nimport type
|
|
1
|
+
{"version":3,"file":"watcher.cjs","names":[],"sources":["../src/watcher.ts"],"sourcesContent":["import { watch, type FSWatcher } from \"node:fs\";\nimport type { Fixture } from \"./types.js\";\nimport type { Logger } from \"./logger.js\";\nimport { clearFixtureQueue, type ValidationResult } from \"./fixture-loader.js\";\n\nconst DEBOUNCE_MS = 500;\n\nexport function watchFixtures(\n fixturePath: string,\n fixtures: Fixture[],\n loadFn: () => Fixture[],\n opts: {\n logger: Logger;\n validate?: boolean;\n validateFn?: (fixtures: Fixture[]) => ValidationResult[];\n },\n): { close: () => void } {\n const { logger, validate, validateFn } = opts;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n function reload() {\n logger.info(`File changed — reloading fixtures from ${fixturePath}...`);\n\n let newFixtures: Fixture[];\n try {\n newFixtures = loadFn();\n } catch (err) {\n logger.error(\"Failed to reload fixtures:\", err);\n logger.error(\"Previous fixtures remain active. Fix the error and save again to retry.\");\n return;\n }\n\n if (newFixtures.length === 0 && fixtures.length > 0) {\n logger.warn(\n \"Reload produced 0 fixtures — keeping previous fixtures. Check fixture file for errors.\",\n );\n return;\n }\n\n if (validate && validateFn) {\n const results = validateFn(newFixtures);\n const errors = results.filter((r) => r.severity === \"error\");\n const warnings = results.filter((r) => r.severity === \"warning\");\n\n for (const w of warnings) {\n logger.warn(`Fixture ${w.fixtureIndex}: ${w.message}`);\n }\n\n if (errors.length > 0) {\n for (const e of errors) {\n logger.error(`Fixture ${e.fixtureIndex}: ${e.message}`);\n }\n logger.error(`${errors.length} validation error(s) — keeping previous fixtures`);\n return;\n }\n }\n\n // Replace in-place to preserve array reference identity. Same door as\n // reset: a one-shot claim parked across the reload must not re-arm into it.\n clearFixtureQueue(fixtures);\n fixtures.push(...newFixtures);\n logger.info(`Reloaded ${newFixtures.length} fixture(s)`);\n }\n\n const watcher: FSWatcher = watch(fixturePath, { recursive: true }, () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(reload, DEBOUNCE_MS);\n });\n\n watcher.on(\"error\", (err: Error) => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = null;\n try {\n watcher.close();\n } catch {\n /* already dead */\n }\n logger.error(`File watcher error on ${fixturePath}: ${err.message}`);\n logger.error(\"Fixture auto-reload is no longer active. Restart the server to resume watching.\");\n });\n\n return {\n close() {\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n },\n };\n}\n"],"mappings":";;;;;AAKA,MAAM,cAAc;AAEpB,SAAgB,cACd,aACA,UACA,QACA,MAKuB;CACvB,MAAM,EAAE,QAAQ,UAAU,eAAe;CACzC,IAAI,gBAAsD;CAE1D,SAAS,SAAS;AAChB,SAAO,KAAK,0CAA0C,YAAY,KAAK;EAEvE,IAAI;AACJ,MAAI;AACF,iBAAc,QAAQ;WACf,KAAK;AACZ,UAAO,MAAM,8BAA8B,IAAI;AAC/C,UAAO,MAAM,0EAA0E;AACvF;;AAGF,MAAI,YAAY,WAAW,KAAK,SAAS,SAAS,GAAG;AACnD,UAAO,KACL,yFACD;AACD;;AAGF,MAAI,YAAY,YAAY;GAC1B,MAAM,UAAU,WAAW,YAAY;GACvC,MAAM,SAAS,QAAQ,QAAQ,MAAM,EAAE,aAAa,QAAQ;GAC5D,MAAM,WAAW,QAAQ,QAAQ,MAAM,EAAE,aAAa,UAAU;AAEhE,QAAK,MAAM,KAAK,SACd,QAAO,KAAK,WAAW,EAAE,aAAa,IAAI,EAAE,UAAU;AAGxD,OAAI,OAAO,SAAS,GAAG;AACrB,SAAK,MAAM,KAAK,OACd,QAAO,MAAM,WAAW,EAAE,aAAa,IAAI,EAAE,UAAU;AAEzD,WAAO,MAAM,GAAG,OAAO,OAAO,kDAAkD;AAChF;;;AAMJ,2CAAkB,SAAS;AAC3B,WAAS,KAAK,GAAG,YAAY;AAC7B,SAAO,KAAK,YAAY,YAAY,OAAO,aAAa;;CAG1D,MAAM,6BAA2B,aAAa,EAAE,WAAW,MAAM,QAAQ;AACvE,MAAI,cAAe,cAAa,cAAc;AAC9C,kBAAgB,WAAW,QAAQ,YAAY;GAC/C;AAEF,SAAQ,GAAG,UAAU,QAAe;AAClC,MAAI,cAAe,cAAa,cAAc;AAC9C,kBAAgB;AAChB,MAAI;AACF,WAAQ,OAAO;UACT;AAGR,SAAO,MAAM,yBAAyB,YAAY,IAAI,IAAI,UAAU;AACpE,SAAO,MAAM,kFAAkF;GAC/F;AAEF,QAAO,EACL,QAAQ;AACN,MAAI,cAAe,cAAa,cAAc;AAC9C,UAAQ,OAAO;IAElB"}
|
package/dist/watcher.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clearFixtureQueue } from "./fixture-loader.js";
|
|
1
2
|
import { watch } from "node:fs";
|
|
2
3
|
|
|
3
4
|
//#region src/watcher.ts
|
|
@@ -30,7 +31,7 @@ function watchFixtures(fixturePath, fixtures, loadFn, opts) {
|
|
|
30
31
|
return;
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
|
-
fixtures
|
|
34
|
+
clearFixtureQueue(fixtures);
|
|
34
35
|
fixtures.push(...newFixtures);
|
|
35
36
|
logger.info(`Reloaded ${newFixtures.length} fixture(s)`);
|
|
36
37
|
}
|
package/dist/watcher.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watcher.js","names":[],"sources":["../src/watcher.ts"],"sourcesContent":["import { watch, type FSWatcher } from \"node:fs\";\nimport type { Fixture } from \"./types.js\";\nimport type { Logger } from \"./logger.js\";\nimport type
|
|
1
|
+
{"version":3,"file":"watcher.js","names":[],"sources":["../src/watcher.ts"],"sourcesContent":["import { watch, type FSWatcher } from \"node:fs\";\nimport type { Fixture } from \"./types.js\";\nimport type { Logger } from \"./logger.js\";\nimport { clearFixtureQueue, type ValidationResult } from \"./fixture-loader.js\";\n\nconst DEBOUNCE_MS = 500;\n\nexport function watchFixtures(\n fixturePath: string,\n fixtures: Fixture[],\n loadFn: () => Fixture[],\n opts: {\n logger: Logger;\n validate?: boolean;\n validateFn?: (fixtures: Fixture[]) => ValidationResult[];\n },\n): { close: () => void } {\n const { logger, validate, validateFn } = opts;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n function reload() {\n logger.info(`File changed — reloading fixtures from ${fixturePath}...`);\n\n let newFixtures: Fixture[];\n try {\n newFixtures = loadFn();\n } catch (err) {\n logger.error(\"Failed to reload fixtures:\", err);\n logger.error(\"Previous fixtures remain active. Fix the error and save again to retry.\");\n return;\n }\n\n if (newFixtures.length === 0 && fixtures.length > 0) {\n logger.warn(\n \"Reload produced 0 fixtures — keeping previous fixtures. Check fixture file for errors.\",\n );\n return;\n }\n\n if (validate && validateFn) {\n const results = validateFn(newFixtures);\n const errors = results.filter((r) => r.severity === \"error\");\n const warnings = results.filter((r) => r.severity === \"warning\");\n\n for (const w of warnings) {\n logger.warn(`Fixture ${w.fixtureIndex}: ${w.message}`);\n }\n\n if (errors.length > 0) {\n for (const e of errors) {\n logger.error(`Fixture ${e.fixtureIndex}: ${e.message}`);\n }\n logger.error(`${errors.length} validation error(s) — keeping previous fixtures`);\n return;\n }\n }\n\n // Replace in-place to preserve array reference identity. Same door as\n // reset: a one-shot claim parked across the reload must not re-arm into it.\n clearFixtureQueue(fixtures);\n fixtures.push(...newFixtures);\n logger.info(`Reloaded ${newFixtures.length} fixture(s)`);\n }\n\n const watcher: FSWatcher = watch(fixturePath, { recursive: true }, () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(reload, DEBOUNCE_MS);\n });\n\n watcher.on(\"error\", (err: Error) => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = null;\n try {\n watcher.close();\n } catch {\n /* already dead */\n }\n logger.error(`File watcher error on ${fixturePath}: ${err.message}`);\n logger.error(\"Fixture auto-reload is no longer active. Restart the server to resume watching.\");\n });\n\n return {\n close() {\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n },\n };\n}\n"],"mappings":";;;;AAKA,MAAM,cAAc;AAEpB,SAAgB,cACd,aACA,UACA,QACA,MAKuB;CACvB,MAAM,EAAE,QAAQ,UAAU,eAAe;CACzC,IAAI,gBAAsD;CAE1D,SAAS,SAAS;AAChB,SAAO,KAAK,0CAA0C,YAAY,KAAK;EAEvE,IAAI;AACJ,MAAI;AACF,iBAAc,QAAQ;WACf,KAAK;AACZ,UAAO,MAAM,8BAA8B,IAAI;AAC/C,UAAO,MAAM,0EAA0E;AACvF;;AAGF,MAAI,YAAY,WAAW,KAAK,SAAS,SAAS,GAAG;AACnD,UAAO,KACL,yFACD;AACD;;AAGF,MAAI,YAAY,YAAY;GAC1B,MAAM,UAAU,WAAW,YAAY;GACvC,MAAM,SAAS,QAAQ,QAAQ,MAAM,EAAE,aAAa,QAAQ;GAC5D,MAAM,WAAW,QAAQ,QAAQ,MAAM,EAAE,aAAa,UAAU;AAEhE,QAAK,MAAM,KAAK,SACd,QAAO,KAAK,WAAW,EAAE,aAAa,IAAI,EAAE,UAAU;AAGxD,OAAI,OAAO,SAAS,GAAG;AACrB,SAAK,MAAM,KAAK,OACd,QAAO,MAAM,WAAW,EAAE,aAAa,IAAI,EAAE,UAAU;AAEzD,WAAO,MAAM,GAAG,OAAO,OAAO,kDAAkD;AAChF;;;AAMJ,oBAAkB,SAAS;AAC3B,WAAS,KAAK,GAAG,YAAY;AAC7B,SAAO,KAAK,YAAY,YAAY,OAAO,aAAa;;CAG1D,MAAM,UAAqB,MAAM,aAAa,EAAE,WAAW,MAAM,QAAQ;AACvE,MAAI,cAAe,cAAa,cAAc;AAC9C,kBAAgB,WAAW,QAAQ,YAAY;GAC/C;AAEF,SAAQ,GAAG,UAAU,QAAe;AAClC,MAAI,cAAe,cAAa,cAAc;AAC9C,kBAAgB;AAChB,MAAI;AACF,WAAQ,OAAO;UACT;AAGR,SAAO,MAAM,yBAAyB,YAAY,IAAI,IAAI,UAAU;AACpE,SAAO,MAAM,kFAAkF;GAC/F;AAEF,QAAO,EACL,QAAQ;AACN,MAAI,cAAe,cAAa,cAAc;AAC9C,UAAQ,OAAO;IAElB"}
|