@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
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
//#region src/live-replay.ts
|
|
4
|
+
function boundedJson(root, limit) {
|
|
5
|
+
let budget = limit;
|
|
6
|
+
function visit(value, depth) {
|
|
7
|
+
if (depth > 32 || --budget < 0) return false;
|
|
8
|
+
if (typeof value === "string") {
|
|
9
|
+
budget -= Buffer.byteLength(value);
|
|
10
|
+
return budget >= 0;
|
|
11
|
+
}
|
|
12
|
+
if (value === null || typeof value === "boolean") return true;
|
|
13
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
14
|
+
if (typeof value !== "object") return false;
|
|
15
|
+
const prototype = Object.getPrototypeOf(value);
|
|
16
|
+
if (Array.isArray(value) ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) return false;
|
|
17
|
+
for (const key of Object.keys(value)) {
|
|
18
|
+
if ([
|
|
19
|
+
"__proto__",
|
|
20
|
+
"constructor",
|
|
21
|
+
"prototype"
|
|
22
|
+
].includes(key)) return false;
|
|
23
|
+
budget -= Buffer.byteLength(key);
|
|
24
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
25
|
+
if (!descriptor || !("value" in descriptor) || !visit(descriptor.value, depth + 1)) return false;
|
|
26
|
+
}
|
|
27
|
+
return budget >= 0;
|
|
28
|
+
}
|
|
29
|
+
return visit(root, 0);
|
|
30
|
+
}
|
|
31
|
+
function canonical(value) {
|
|
32
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
33
|
+
if (value !== null && typeof value === "object") return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
|
|
34
|
+
return JSON.stringify(value);
|
|
35
|
+
}
|
|
36
|
+
function field(event, pointer) {
|
|
37
|
+
const parts = pointer.slice(1).split("/").map((p) => p.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
38
|
+
const key = parts.pop();
|
|
39
|
+
let parent = event;
|
|
40
|
+
for (const part of parts) {
|
|
41
|
+
if (parent === null || typeof parent !== "object") throw new Error("missing identifier");
|
|
42
|
+
parent = Array.isArray(parent) ? parent[Number(part)] : parent[part];
|
|
43
|
+
}
|
|
44
|
+
if (parent === null || typeof parent !== "object" || !Object.hasOwn(parent, key)) throw new Error("missing identifier");
|
|
45
|
+
return {
|
|
46
|
+
parent,
|
|
47
|
+
key
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function read(event, pointer) {
|
|
51
|
+
const { parent, key } = field(event, pointer);
|
|
52
|
+
return Array.isArray(parent) ? parent[Number(key)] : parent[key];
|
|
53
|
+
}
|
|
54
|
+
function replace(event, pointer, value) {
|
|
55
|
+
const { parent, key } = field(event, pointer);
|
|
56
|
+
if (Array.isArray(parent)) parent[Number(key)] = value;
|
|
57
|
+
else parent[key] = value;
|
|
58
|
+
}
|
|
59
|
+
function namespace(binding) {
|
|
60
|
+
const p = binding.pointer;
|
|
61
|
+
if (p.endsWith("client_event_id")) return "client-event";
|
|
62
|
+
if (p === "/event_id") return `${binding.owner}-event`;
|
|
63
|
+
if (p === "/session/id") return "session";
|
|
64
|
+
if (p === "/delegation/id" || p === "/delegation_id") return "delegation";
|
|
65
|
+
if (p.endsWith("/call_id")) return "tool-call";
|
|
66
|
+
if (p === "/event/item/id" || p === "/event/item_id") return "response-item";
|
|
67
|
+
return "response";
|
|
68
|
+
}
|
|
69
|
+
function decode(value, remaining) {
|
|
70
|
+
if (typeof value !== "string" || value.length % 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) throw new Error("invalid audio");
|
|
71
|
+
const size = value.length / 4 * 3 - (value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0);
|
|
72
|
+
if (size % 2 || size > remaining) throw new Error("invalid audio size");
|
|
73
|
+
const bytes = Buffer.from(value, "base64");
|
|
74
|
+
if (bytes.toString("base64") !== value) throw new Error("invalid audio encoding");
|
|
75
|
+
return bytes;
|
|
76
|
+
}
|
|
77
|
+
/** Accepts synchronously; the single output pump alone waits for transport pressure.
|
|
78
|
+
* The caller supplies a validated, immutable transcript and owns cancellation of send(). */
|
|
79
|
+
function createLiveReplay(transcript, options) {
|
|
80
|
+
if (!Number.isFinite(options.replaySpeed) || options.replaySpeed <= 0) throw new Error("replaySpeed must be positive");
|
|
81
|
+
const { limits } = options;
|
|
82
|
+
const annotations = /* @__PURE__ */ new Map();
|
|
83
|
+
for (const binding of transcript.bindings) {
|
|
84
|
+
const bucket = annotations.get(binding.entry) ?? [];
|
|
85
|
+
bucket.push(binding);
|
|
86
|
+
annotations.set(binding.entry, bucket);
|
|
87
|
+
}
|
|
88
|
+
for (const bucket of annotations.values()) bucket.sort((a, b) => Number(a.action === "reference") - Number(b.action === "reference"));
|
|
89
|
+
const tape = [];
|
|
90
|
+
const servers = [];
|
|
91
|
+
const audioChunks = /* @__PURE__ */ new Map();
|
|
92
|
+
let fixtureBytes = 0;
|
|
93
|
+
for (const [index, entry] of transcript.entries.entries()) {
|
|
94
|
+
if (entry.direction === "server") {
|
|
95
|
+
servers.push(index);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (entry.event.type !== "session.input_audio.append") {
|
|
99
|
+
tape.push({
|
|
100
|
+
kind: "control",
|
|
101
|
+
index
|
|
102
|
+
});
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const bytes = decode(entry.event.audio, limits.maxDecodedAudioBytes - fixtureBytes);
|
|
106
|
+
fixtureBytes += bytes.length;
|
|
107
|
+
const metadata = { ...entry.event };
|
|
108
|
+
delete metadata.audio;
|
|
109
|
+
const previous = tape.at(-1);
|
|
110
|
+
if (annotations.has(index)) {
|
|
111
|
+
tape.push({
|
|
112
|
+
kind: "control",
|
|
113
|
+
index
|
|
114
|
+
});
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (previous?.kind === "audio" && canonical(previous.metadata) === canonical(metadata)) audioChunks.get(previous).push(bytes);
|
|
118
|
+
else {
|
|
119
|
+
const run = {
|
|
120
|
+
kind: "audio",
|
|
121
|
+
bytes,
|
|
122
|
+
metadata
|
|
123
|
+
};
|
|
124
|
+
tape.push(run);
|
|
125
|
+
audioChunks.set(run, [bytes]);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
for (const [run, chunks] of audioChunks) run.bytes = Buffer.concat(chunks);
|
|
129
|
+
audioChunks.clear();
|
|
130
|
+
const values = /* @__PURE__ */ new Map();
|
|
131
|
+
const reverse = /* @__PURE__ */ new Map();
|
|
132
|
+
let cursor = 0, offset = 0, command = 0, audioBytes = 0, totalAudio = 0, next = 0;
|
|
133
|
+
let stopped = false, sending = false, start;
|
|
134
|
+
let timer;
|
|
135
|
+
const created = Date.now();
|
|
136
|
+
let lastActivity = created, missingSince = created;
|
|
137
|
+
function close() {
|
|
138
|
+
if (stopped) return;
|
|
139
|
+
stopped = true;
|
|
140
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
141
|
+
timer = void 0;
|
|
142
|
+
options.signal.removeEventListener("abort", close);
|
|
143
|
+
}
|
|
144
|
+
function fail(category, detail) {
|
|
145
|
+
if (!stopped) {
|
|
146
|
+
close();
|
|
147
|
+
options.fail(category, detail);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function bind(binding, value) {
|
|
151
|
+
const existing = values.get(binding.name);
|
|
152
|
+
if (binding.action === "reference") {
|
|
153
|
+
if (existing !== value) throw new Error("identifier reference mismatch");
|
|
154
|
+
} else {
|
|
155
|
+
const key = JSON.stringify([namespace(binding), value]);
|
|
156
|
+
if (existing !== void 0 || reverse.has(key)) throw new Error("identifier bijection mismatch");
|
|
157
|
+
values.set(binding.name, value);
|
|
158
|
+
reverse.set(key, binding.name);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function eligible() {
|
|
162
|
+
const barrier = transcript.entries[servers[next]]?.barrier;
|
|
163
|
+
return barrier !== void 0 && (command > barrier.command || command === barrier.command && audioBytes >= barrier.audioBytes);
|
|
164
|
+
}
|
|
165
|
+
function due() {
|
|
166
|
+
return options.timing === "immediate" ? 0 : transcript.entries[servers[next]].atMs / options.replaySpeed;
|
|
167
|
+
}
|
|
168
|
+
function schedule() {
|
|
169
|
+
if (stopped) return;
|
|
170
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
171
|
+
const now = Date.now();
|
|
172
|
+
const deadlines = [(start ?? created) + limits.maxDurationMs, lastActivity + limits.idleTimeoutMs];
|
|
173
|
+
if (cursor < tape.length && (next >= servers.length || !eligible())) deadlines.push(missingSince + limits.mismatchTimeoutMs);
|
|
174
|
+
if (!sending && start !== void 0 && next < servers.length && eligible()) deadlines.push(start + due());
|
|
175
|
+
timer = setTimeout(tick, Math.max(0, Math.min(2147483647, Math.min(...deadlines) - now)));
|
|
176
|
+
}
|
|
177
|
+
function tick() {
|
|
178
|
+
timer = void 0;
|
|
179
|
+
if (stopped) return;
|
|
180
|
+
const now = Date.now();
|
|
181
|
+
if (now >= (start ?? created) + limits.maxDurationMs || now >= lastActivity + limits.idleTimeoutMs) {
|
|
182
|
+
fail("timeout", "live replay deadline exceeded");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (cursor < tape.length && (next >= servers.length || !eligible()) && now >= missingSince + limits.mismatchTimeoutMs) {
|
|
186
|
+
fail("fixture-mismatch", "expected client input deadline exceeded");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (!sending && start !== void 0 && next < servers.length && eligible() && now >= start + due()) pump();
|
|
190
|
+
schedule();
|
|
191
|
+
}
|
|
192
|
+
async function pump() {
|
|
193
|
+
sending = true;
|
|
194
|
+
try {
|
|
195
|
+
while (!stopped && start !== void 0 && next < servers.length && eligible() && Date.now() >= start + due()) {
|
|
196
|
+
const index = servers[next];
|
|
197
|
+
const event = structuredClone(transcript.entries[index].event);
|
|
198
|
+
for (const binding of annotations.get(index) ?? []) {
|
|
199
|
+
if (binding.action === "define") bind(binding, `aimock-${randomUUID()}`);
|
|
200
|
+
const value = values.get(binding.name);
|
|
201
|
+
if (value === void 0) throw new Error("unavailable identifier");
|
|
202
|
+
replace(event, binding.pointer, value);
|
|
203
|
+
}
|
|
204
|
+
if (Buffer.byteLength(JSON.stringify(event)) > limits.maxWriteBytes) {
|
|
205
|
+
fail("resource-limit", "live output exceeds write limit");
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
await options.send(event);
|
|
209
|
+
if (stopped) break;
|
|
210
|
+
next++;
|
|
211
|
+
lastActivity = Date.now();
|
|
212
|
+
missingSince = Date.now();
|
|
213
|
+
if (next === servers.length) close();
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
fail("protocol-divergence", "live output failed");
|
|
217
|
+
} finally {
|
|
218
|
+
sending = false;
|
|
219
|
+
schedule();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function accept(event) {
|
|
223
|
+
if (stopped) return;
|
|
224
|
+
try {
|
|
225
|
+
if (!boundedJson(event, limits.maxMessageBytes)) {
|
|
226
|
+
fail("invalid-client", "live input must be bounded JSON");
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (Buffer.byteLength(JSON.stringify(event)) > limits.maxMessageBytes) {
|
|
230
|
+
fail("resource-limit", "live input exceeds message limit");
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
let expected = tape[cursor];
|
|
234
|
+
const metadata = { ...event };
|
|
235
|
+
delete metadata.audio;
|
|
236
|
+
while (expected?.kind === "audio" && offset === expected.bytes.length && !(event.audio === "" && canonical(metadata) === canonical(expected.metadata))) {
|
|
237
|
+
expected = tape[++cursor];
|
|
238
|
+
offset = 0;
|
|
239
|
+
}
|
|
240
|
+
if (!expected) throw new Error("unexpected client input");
|
|
241
|
+
if (expected.kind === "audio") {
|
|
242
|
+
const bytes = decode(event.audio, Math.min(limits.maxDecodedAudioBytes - totalAudio, expected.bytes.length - offset));
|
|
243
|
+
if (canonical(metadata) !== canonical(expected.metadata) || !bytes.equals(expected.bytes.subarray(offset, offset + bytes.length))) throw new Error("audio mismatch");
|
|
244
|
+
offset += bytes.length;
|
|
245
|
+
audioBytes += bytes.length;
|
|
246
|
+
totalAudio += bytes.length;
|
|
247
|
+
} else {
|
|
248
|
+
const template = structuredClone(transcript.entries[expected.index].event);
|
|
249
|
+
for (const binding of annotations.get(expected.index) ?? []) {
|
|
250
|
+
const value = read(event, binding.pointer);
|
|
251
|
+
if (typeof value !== "string" || !value.length) throw new Error("missing identifier");
|
|
252
|
+
bind(binding, value);
|
|
253
|
+
replace(template, binding.pointer, value);
|
|
254
|
+
}
|
|
255
|
+
if (canonical(template) !== canonical(event)) throw new Error("command mismatch");
|
|
256
|
+
if (event.type === "session.input_audio.append") {
|
|
257
|
+
const bytes = decode(event.audio, limits.maxDecodedAudioBytes - totalAudio);
|
|
258
|
+
audioBytes += bytes.length;
|
|
259
|
+
totalAudio += bytes.length;
|
|
260
|
+
} else {
|
|
261
|
+
command++;
|
|
262
|
+
audioBytes = 0;
|
|
263
|
+
}
|
|
264
|
+
cursor++;
|
|
265
|
+
}
|
|
266
|
+
start ??= Date.now();
|
|
267
|
+
lastActivity = Date.now();
|
|
268
|
+
missingSince = Date.now();
|
|
269
|
+
schedule();
|
|
270
|
+
} catch {
|
|
271
|
+
fail("fixture-mismatch", "client input does not match live fixture");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
options.signal.addEventListener("abort", close, { once: true });
|
|
275
|
+
if (options.signal.aborted) close();
|
|
276
|
+
else schedule();
|
|
277
|
+
return {
|
|
278
|
+
accept,
|
|
279
|
+
close
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
//#endregion
|
|
284
|
+
export { createLiveReplay };
|
|
285
|
+
//# sourceMappingURL=live-replay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-replay.js","names":[],"sources":["../src/live-replay.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type {\n LiveBinding,\n LiveFailureCategory,\n LiveJson,\n LiveObject,\n LiveOptions,\n LiveTranscript,\n} from \"./live-types.js\";\n\nexport interface LiveReplayOptions {\n send: (event: LiveObject) => Promise<void>;\n fail: (category: LiveFailureCategory, detail: string) => void;\n signal: AbortSignal;\n replaySpeed: number;\n timing: \"recorded\" | \"immediate\";\n limits: Required<LiveOptions>;\n}\n// Inspect descriptors before serialization: in-process callers must not smuggle\n// getters, cycles, prototypes, or unbounded nesting into the receive path.\nfunction boundedJson(root: LiveObject, limit: number): boolean {\n let budget = limit;\n function visit(value: LiveJson, depth: number): boolean {\n if (depth > 32 || --budget < 0) return false;\n if (typeof value === \"string\") {\n budget -= Buffer.byteLength(value);\n return budget >= 0;\n }\n if (value === null || typeof value === \"boolean\") return true;\n if (typeof value === \"number\") return Number.isFinite(value);\n if (typeof value !== \"object\") return false;\n const prototype = Object.getPrototypeOf(value);\n if (\n Array.isArray(value)\n ? prototype !== Array.prototype\n : prototype !== Object.prototype && prototype !== null\n )\n return false;\n for (const key of Object.keys(value)) {\n if ([\"__proto__\", \"constructor\", \"prototype\"].includes(key)) return false;\n budget -= Buffer.byteLength(key);\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n if (!descriptor || !(\"value\" in descriptor) || !visit(descriptor.value, depth + 1))\n return false;\n }\n return budget >= 0;\n }\n return visit(root, 0);\n}\nfunction canonical(value: LiveJson): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(\",\")}]`;\n if (value !== null && typeof value === \"object\")\n return `{${Object.keys(value)\n .sort()\n .map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)\n .join(\",\")}}`;\n return JSON.stringify(value);\n}\nfunction field(\n event: LiveObject,\n pointer: string,\n): { parent: LiveObject | LiveJson[]; key: string } {\n const parts = pointer\n .slice(1)\n .split(\"/\")\n .map((p) => p.replace(/~1/g, \"/\").replace(/~0/g, \"~\"));\n const key = parts.pop()!;\n let parent: LiveJson = event;\n for (const part of parts) {\n if (parent === null || typeof parent !== \"object\") throw new Error(\"missing identifier\");\n parent = Array.isArray(parent) ? parent[Number(part)] : parent[part];\n }\n if (parent === null || typeof parent !== \"object\" || !Object.hasOwn(parent, key))\n throw new Error(\"missing identifier\");\n return { parent, key };\n}\nfunction read(event: LiveObject, pointer: string): LiveJson {\n const { parent, key } = field(event, pointer);\n return Array.isArray(parent) ? parent[Number(key)] : parent[key];\n}\nfunction replace(event: LiveObject, pointer: string, value: string): void {\n const { parent, key } = field(event, pointer);\n if (Array.isArray(parent)) parent[Number(key)] = value;\n else parent[key] = value;\n}\n// Namespace comes from the reviewed operand, never the author's arbitrary binding name.\nfunction namespace(binding: LiveBinding): string {\n const p = binding.pointer;\n if (p.endsWith(\"client_event_id\")) return \"client-event\";\n if (p === \"/event_id\") return `${binding.owner}-event`;\n if (p === \"/session/id\") return \"session\";\n if (p === \"/delegation/id\" || p === \"/delegation_id\") return \"delegation\";\n if (p.endsWith(\"/call_id\")) return \"tool-call\";\n if (p === \"/event/item/id\" || p === \"/event/item_id\") return \"response-item\";\n return \"response\";\n}\nfunction decode(value: LiveJson, remaining: number): Buffer {\n if (\n typeof value !== \"string\" ||\n value.length % 4 ||\n !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)\n )\n throw new Error(\"invalid audio\");\n const size = (value.length / 4) * 3 - (value.endsWith(\"==\") ? 2 : value.endsWith(\"=\") ? 1 : 0);\n if (size % 2 || size > remaining) throw new Error(\"invalid audio size\");\n const bytes = Buffer.from(value, \"base64\");\n if (bytes.toString(\"base64\") !== value) throw new Error(\"invalid audio encoding\");\n return bytes;\n}\ninterface Control {\n kind: \"control\";\n index: number;\n}\ninterface Audio {\n kind: \"audio\";\n bytes: Buffer;\n metadata: LiveObject;\n}\n\n/** Accepts synchronously; the single output pump alone waits for transport pressure.\n * The caller supplies a validated, immutable transcript and owns cancellation of send(). */\nexport function createLiveReplay(\n transcript: LiveTranscript,\n options: LiveReplayOptions,\n): { accept(event: LiveObject): void; close(): void } {\n if (!Number.isFinite(options.replaySpeed) || options.replaySpeed <= 0)\n throw new Error(\"replaySpeed must be positive\");\n const { limits } = options;\n const annotations = new Map<number, LiveBinding[]>();\n for (const binding of transcript.bindings) {\n const bucket = annotations.get(binding.entry) ?? [];\n bucket.push(binding);\n annotations.set(binding.entry, bucket);\n }\n for (const bucket of annotations.values())\n bucket.sort((a, b) => Number(a.action === \"reference\") - Number(b.action === \"reference\"));\n const tape: (Control | Audio)[] = [];\n const servers: number[] = [];\n const audioChunks = new Map<Audio, Buffer[]>();\n let fixtureBytes = 0;\n for (const [index, entry] of transcript.entries.entries()) {\n if (entry.direction === \"server\") {\n servers.push(index);\n continue;\n }\n if (entry.event.type !== \"session.input_audio.append\") {\n tape.push({ kind: \"control\", index });\n continue;\n }\n const bytes = decode(entry.event.audio, limits.maxDecodedAudioBytes - fixtureBytes);\n fixtureBytes += bytes.length;\n const metadata = { ...entry.event };\n delete metadata.audio;\n const previous = tape.at(-1);\n // Annotated audio commands retain their original frame boundary. Plain PCM runs\n // coalesce across intervening server output, never across a client control.\n if (annotations.has(index)) {\n tape.push({ kind: \"control\", index });\n continue;\n }\n if (previous?.kind === \"audio\" && canonical(previous.metadata) === canonical(metadata))\n audioChunks.get(previous)!.push(bytes);\n else {\n const run: Audio = { kind: \"audio\", bytes, metadata };\n tape.push(run);\n audioChunks.set(run, [bytes]);\n }\n }\n for (const [run, chunks] of audioChunks) run.bytes = Buffer.concat(chunks);\n audioChunks.clear();\n const values = new Map<string, string>();\n const reverse = new Map<string, string>();\n let cursor = 0,\n offset = 0,\n command = 0,\n audioBytes = 0,\n totalAudio = 0,\n next = 0;\n let stopped = false,\n sending = false,\n start: number | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const created = Date.now();\n let lastActivity = created,\n missingSince = created;\n function close() {\n if (stopped) return;\n stopped = true;\n if (timer !== undefined) clearTimeout(timer);\n timer = undefined;\n options.signal.removeEventListener(\"abort\", close);\n }\n function fail(category: LiveFailureCategory, detail: string) {\n if (!stopped) {\n close();\n options.fail(category, detail);\n }\n }\n function bind(binding: LiveBinding, value: string) {\n const existing = values.get(binding.name);\n if (binding.action === \"reference\") {\n if (existing !== value) throw new Error(\"identifier reference mismatch\");\n } else {\n const key = JSON.stringify([namespace(binding), value]);\n if (existing !== undefined || reverse.has(key))\n throw new Error(\"identifier bijection mismatch\");\n values.set(binding.name, value);\n reverse.set(key, binding.name);\n }\n }\n function eligible() {\n const barrier = transcript.entries[servers[next]]?.barrier;\n return (\n barrier !== undefined &&\n (command > barrier.command ||\n (command === barrier.command && audioBytes >= barrier.audioBytes))\n );\n }\n function due() {\n return options.timing === \"immediate\"\n ? 0\n : transcript.entries[servers[next]].atMs / options.replaySpeed;\n }\n function schedule() {\n if (stopped) return;\n if (timer !== undefined) clearTimeout(timer);\n const now = Date.now();\n const deadlines = [\n (start ?? created) + limits.maxDurationMs,\n lastActivity + limits.idleTimeoutMs,\n ];\n if (cursor < tape.length && (next >= servers.length || !eligible()))\n deadlines.push(missingSince + limits.mismatchTimeoutMs);\n if (!sending && start !== undefined && next < servers.length && eligible())\n deadlines.push(start + due());\n timer = setTimeout(tick, Math.max(0, Math.min(2_147_483_647, Math.min(...deadlines) - now)));\n }\n function tick() {\n timer = undefined;\n if (stopped) return;\n const now = Date.now();\n if (\n now >= (start ?? created) + limits.maxDurationMs ||\n now >= lastActivity + limits.idleTimeoutMs\n ) {\n fail(\"timeout\", \"live replay deadline exceeded\");\n return;\n }\n if (\n cursor < tape.length &&\n (next >= servers.length || !eligible()) &&\n now >= missingSince + limits.mismatchTimeoutMs\n ) {\n fail(\"fixture-mismatch\", \"expected client input deadline exceeded\");\n return;\n }\n if (\n !sending &&\n start !== undefined &&\n next < servers.length &&\n eligible() &&\n now >= start + due()\n )\n void pump();\n schedule();\n }\n async function pump() {\n sending = true;\n try {\n while (\n !stopped &&\n start !== undefined &&\n next < servers.length &&\n eligible() &&\n Date.now() >= start + due()\n ) {\n const index = servers[next];\n const event = structuredClone(transcript.entries[index].event);\n for (const binding of annotations.get(index) ?? []) {\n if (binding.action === \"define\") bind(binding, `aimock-${randomUUID()}`);\n const value = values.get(binding.name);\n if (value === undefined) throw new Error(\"unavailable identifier\");\n replace(event, binding.pointer, value);\n }\n if (Buffer.byteLength(JSON.stringify(event)) > limits.maxWriteBytes) {\n fail(\"resource-limit\", \"live output exceeds write limit\");\n break;\n }\n await options.send(event);\n if (stopped) break;\n next++;\n lastActivity = Date.now();\n missingSince = Date.now();\n if (next === servers.length) close();\n }\n } catch {\n fail(\"protocol-divergence\", \"live output failed\");\n } finally {\n sending = false;\n schedule();\n }\n }\n function accept(event: LiveObject): void {\n if (stopped) return;\n try {\n if (!boundedJson(event, limits.maxMessageBytes)) {\n fail(\"invalid-client\", \"live input must be bounded JSON\");\n return;\n }\n if (Buffer.byteLength(JSON.stringify(event)) > limits.maxMessageBytes) {\n fail(\"resource-limit\", \"live input exceeds message limit\");\n return;\n }\n let expected = tape[cursor];\n const metadata = { ...event };\n delete metadata.audio;\n // Empty frames do not advance PCM position, even at the end of a run.\n // Move past exhausted runs when the next input changes metadata or supplies\n // more bytes/a control, without crossing an unfinished run or control.\n while (\n expected?.kind === \"audio\" &&\n offset === expected.bytes.length &&\n !(event.audio === \"\" && canonical(metadata) === canonical(expected.metadata))\n ) {\n expected = tape[++cursor];\n offset = 0;\n }\n if (!expected) throw new Error(\"unexpected client input\");\n if (expected.kind === \"audio\") {\n const bytes = decode(\n event.audio,\n Math.min(limits.maxDecodedAudioBytes - totalAudio, expected.bytes.length - offset),\n );\n if (\n canonical(metadata) !== canonical(expected.metadata) ||\n !bytes.equals(expected.bytes.subarray(offset, offset + bytes.length))\n )\n throw new Error(\"audio mismatch\");\n offset += bytes.length;\n audioBytes += bytes.length;\n totalAudio += bytes.length;\n } else {\n const template = structuredClone(transcript.entries[expected.index].event);\n for (const binding of annotations.get(expected.index) ?? []) {\n const value = read(event, binding.pointer);\n if (typeof value !== \"string\" || !value.length) throw new Error(\"missing identifier\");\n bind(binding, value);\n replace(template, binding.pointer, value);\n }\n if (canonical(template) !== canonical(event)) throw new Error(\"command mismatch\");\n if (event.type === \"session.input_audio.append\") {\n const bytes = decode(event.audio, limits.maxDecodedAudioBytes - totalAudio);\n audioBytes += bytes.length;\n totalAudio += bytes.length;\n } else {\n command++;\n audioBytes = 0;\n }\n cursor++;\n }\n start ??= Date.now();\n lastActivity = Date.now();\n missingSince = Date.now();\n schedule();\n } catch {\n fail(\"fixture-mismatch\", \"client input does not match live fixture\");\n }\n }\n options.signal.addEventListener(\"abort\", close, { once: true });\n if (options.signal.aborted) close();\n else schedule();\n return { accept, close };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
const require_live_fixture = require('./live-fixture.cjs');
|
|
2
|
+
|
|
3
|
+
//#region src/live-sanitize.ts
|
|
4
|
+
var LiveUnsafeExportError = class extends Error {
|
|
5
|
+
category = "unsafe-export";
|
|
6
|
+
constructor() {
|
|
7
|
+
super("unsafe-export: Live transcript cannot be retained safely");
|
|
8
|
+
this.name = "LiveUnsafeExportError";
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
const credentialKey = /^(?:authorization|proxy-authorization|cookie|set-cookie|x-api-key|api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|password|secret|token|sec-websocket-protocol)$/i;
|
|
12
|
+
const semanticKey = new Set([
|
|
13
|
+
"instructions",
|
|
14
|
+
"content",
|
|
15
|
+
"delta",
|
|
16
|
+
"audio",
|
|
17
|
+
"arguments",
|
|
18
|
+
"output",
|
|
19
|
+
"input",
|
|
20
|
+
"tools",
|
|
21
|
+
"parameters",
|
|
22
|
+
"client",
|
|
23
|
+
"text",
|
|
24
|
+
"thinking"
|
|
25
|
+
]);
|
|
26
|
+
const metadataKey = new Set([
|
|
27
|
+
"metadata",
|
|
28
|
+
"headers",
|
|
29
|
+
"diagnostics"
|
|
30
|
+
]);
|
|
31
|
+
const unsafeKey = new Set([
|
|
32
|
+
"__proto__",
|
|
33
|
+
"constructor",
|
|
34
|
+
"prototype"
|
|
35
|
+
]);
|
|
36
|
+
const redacted = "[REDACTED]";
|
|
37
|
+
function fail() {
|
|
38
|
+
throw new LiveUnsafeExportError();
|
|
39
|
+
}
|
|
40
|
+
function decodeUrlComponent(value) {
|
|
41
|
+
return new URLSearchParams(`value=${value.replace(/\+/g, "%2B").replace(/&/g, "%26")}`).get("value");
|
|
42
|
+
}
|
|
43
|
+
function escapePointer(key) {
|
|
44
|
+
return key.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
45
|
+
}
|
|
46
|
+
function parts(pointer) {
|
|
47
|
+
if (!pointer.startsWith("/") || /~(?![01])/u.test(pointer)) fail();
|
|
48
|
+
const keys = pointer.slice(1).split("/").map((key) => key.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
49
|
+
if (keys.some((key) => unsafeKey.has(key))) fail();
|
|
50
|
+
return keys;
|
|
51
|
+
}
|
|
52
|
+
function child(value, key) {
|
|
53
|
+
if (!value || typeof value !== "object" || !Object.hasOwn(value, key)) fail();
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
if (!/^(0|[1-9]\d*)$/.test(key)) fail();
|
|
56
|
+
return value[Number(key)];
|
|
57
|
+
}
|
|
58
|
+
return value[key];
|
|
59
|
+
}
|
|
60
|
+
function setIdentifier(event, pointer, value) {
|
|
61
|
+
const keys = parts(pointer);
|
|
62
|
+
const last = keys.pop();
|
|
63
|
+
if (last === void 0) fail();
|
|
64
|
+
let parent = event;
|
|
65
|
+
for (const key of keys) parent = child(parent, key);
|
|
66
|
+
if (typeof child(parent, last) !== "string" || !parent || typeof parent !== "object") fail();
|
|
67
|
+
if (Array.isArray(parent)) parent[Number(last)] = value;
|
|
68
|
+
else parent[last] = value;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Sanitize a separately retained successful capture; never mutate the relay.
|
|
72
|
+
* Validation supplies the reviewed identifier descriptors and causal checks.
|
|
73
|
+
* Credential descriptors supplement the built-in policy, not permission to
|
|
74
|
+
* delete arbitrary protocol operands. Ambiguous secret-bearing data fails closed.
|
|
75
|
+
*/
|
|
76
|
+
function sanitizeLiveTranscript(transcript, secretValues = [], credentialFields = [], validationOptions = {}) {
|
|
77
|
+
try {
|
|
78
|
+
const result = require_live_fixture.validateLiveTranscript(transcript, validationOptions);
|
|
79
|
+
if (!Array.isArray(secretValues) || secretValues.some((s) => typeof s !== "string")) fail();
|
|
80
|
+
const secrets = [...new Set(secretValues.filter((s) => s.length))].sort((a, b) => b.length - a.length);
|
|
81
|
+
const hasSecret = (value) => secrets.some((secret) => value.includes(secret));
|
|
82
|
+
const replaceSecrets = (value) => {
|
|
83
|
+
for (const secret of secrets) value = value.split(secret).join(redacted);
|
|
84
|
+
return value;
|
|
85
|
+
};
|
|
86
|
+
const descriptors = new Set(credentialFields.map((field) => {
|
|
87
|
+
parts(field.pointer);
|
|
88
|
+
return field.pointer;
|
|
89
|
+
}));
|
|
90
|
+
const handled = /* @__PURE__ */ new Set();
|
|
91
|
+
const originalIds = /* @__PURE__ */ new Set();
|
|
92
|
+
for (const binding of result.bindings) {
|
|
93
|
+
let value = result.entries[binding.entry].event;
|
|
94
|
+
for (const key of parts(binding.pointer)) value = child(value, key);
|
|
95
|
+
if (typeof value !== "string") fail();
|
|
96
|
+
originalIds.add(value);
|
|
97
|
+
}
|
|
98
|
+
let nextId = 1;
|
|
99
|
+
const identities = /* @__PURE__ */ new Map();
|
|
100
|
+
for (const binding of result.bindings) {
|
|
101
|
+
let identity = identities.get(binding.name);
|
|
102
|
+
if (!identity) {
|
|
103
|
+
const index = identities.size + 1;
|
|
104
|
+
while (originalIds.has(`live-id-${nextId}`)) nextId++;
|
|
105
|
+
identity = {
|
|
106
|
+
name: `binding-${index}`,
|
|
107
|
+
value: `live-id-${nextId++}`
|
|
108
|
+
};
|
|
109
|
+
identities.set(binding.name, identity);
|
|
110
|
+
}
|
|
111
|
+
setIdentifier(result.entries[binding.entry].event, binding.pointer, identity.value);
|
|
112
|
+
binding.name = identity.name;
|
|
113
|
+
}
|
|
114
|
+
function sanitizeUrl(value) {
|
|
115
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(value)) return value;
|
|
116
|
+
let url;
|
|
117
|
+
try {
|
|
118
|
+
url = new URL(value);
|
|
119
|
+
} catch {
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
if (hasSecret(decodeUrlComponent(url.pathname))) fail();
|
|
123
|
+
let changed = false;
|
|
124
|
+
if (url.username || url.password) {
|
|
125
|
+
url.username = "";
|
|
126
|
+
url.password = "";
|
|
127
|
+
changed = true;
|
|
128
|
+
}
|
|
129
|
+
for (const [key, val] of [...url.searchParams]) if (credentialKey.test(key) || hasSecret(key) || hasSecret(val)) {
|
|
130
|
+
url.searchParams.delete(key);
|
|
131
|
+
changed = true;
|
|
132
|
+
}
|
|
133
|
+
const decodedHash = decodeUrlComponent(url.hash);
|
|
134
|
+
if (url.hash && (hasSecret(decodedHash) || /(?:token|secret|password|api[_-]?key)=/i.test(decodedHash))) {
|
|
135
|
+
url.hash = "";
|
|
136
|
+
changed = true;
|
|
137
|
+
}
|
|
138
|
+
return changed ? url.href : value;
|
|
139
|
+
}
|
|
140
|
+
function providerMetadata(pointer) {
|
|
141
|
+
const match = /^\/entries\/(\d+)\/event\/event\/(?:response\/(?:user|prompt_cache_key|safety_identifier)|item\/encrypted_content)$/.exec(pointer);
|
|
142
|
+
if (!match) return false;
|
|
143
|
+
const event = result.entries[Number(match[1])].event;
|
|
144
|
+
const nested = event.event;
|
|
145
|
+
if (event.type !== "response.event" || !nested || typeof nested !== "object" || Array.isArray(nested)) return false;
|
|
146
|
+
if (!pointer.endsWith("/item/encrypted_content")) return [
|
|
147
|
+
"response.created",
|
|
148
|
+
"response.in_progress",
|
|
149
|
+
"response.completed"
|
|
150
|
+
].includes(String(nested.type));
|
|
151
|
+
const item = nested.item;
|
|
152
|
+
return ["response.output_item.added", "response.output_item.done"].includes(String(nested.type)) && !!item && typeof item === "object" && !Array.isArray(item) && item.type === "reasoning";
|
|
153
|
+
}
|
|
154
|
+
function visit(value, path, metadata = false, semantic = false) {
|
|
155
|
+
if (typeof value === "string") {
|
|
156
|
+
const url = sanitizeUrl(value);
|
|
157
|
+
if ((hasSecret(value) || url !== value) && (!metadata || semantic)) fail();
|
|
158
|
+
return metadata && !semantic ? replaceSecrets(url) : value;
|
|
159
|
+
}
|
|
160
|
+
if (Array.isArray(value)) return value.map((item, i) => visit(item, `${path}/${i}`, metadata, semantic));
|
|
161
|
+
if (!value || typeof value !== "object") return value;
|
|
162
|
+
const output = {};
|
|
163
|
+
for (const [key, item] of Object.entries(value)) {
|
|
164
|
+
const pointer = `${path}/${escapePointer(key)}`;
|
|
165
|
+
if (providerMetadata(pointer) && (typeof item === "string" || item === null)) {
|
|
166
|
+
if (descriptors.has(pointer)) handled.add(pointer);
|
|
167
|
+
else output[key] = typeof item === "string" ? redacted : item;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const protectedBranch = semantic || semanticKey.has(key);
|
|
171
|
+
const removable = !protectedBranch && (metadata || credentialKey.test(key));
|
|
172
|
+
if (descriptors.has(pointer)) {
|
|
173
|
+
if (!removable) fail();
|
|
174
|
+
handled.add(pointer);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (!semantic && credentialKey.test(key)) continue;
|
|
178
|
+
if (hasSecret(key)) {
|
|
179
|
+
if (!metadata || protectedBranch) fail();
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
output[key] = visit(item, pointer, metadata || metadataKey.has(key), protectedBranch);
|
|
183
|
+
}
|
|
184
|
+
return output;
|
|
185
|
+
}
|
|
186
|
+
const cleaned = visit(result, "");
|
|
187
|
+
if (handled.size !== descriptors.size || hasSecret(JSON.stringify(cleaned))) fail();
|
|
188
|
+
return require_live_fixture.validateLiveTranscript(cleaned, validationOptions);
|
|
189
|
+
} catch {
|
|
190
|
+
fail();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
//#endregion
|
|
195
|
+
exports.sanitizeLiveTranscript = sanitizeLiveTranscript;
|
|
196
|
+
//# sourceMappingURL=live-sanitize.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-sanitize.cjs","names":["validateLiveTranscript"],"sources":["../src/live-sanitize.ts"],"sourcesContent":["import { validateLiveTranscript } from \"./live-fixture.js\";\nimport type { LiveJson, LiveObject, LiveOptions, LiveTranscript } from \"./live-types.js\";\n\n/** Exact transcript-rooted pointer to an observed removable credential metadata field. */\nexport interface LiveCredentialField {\n readonly pointer: string;\n}\n\nexport class LiveUnsafeExportError extends Error {\n readonly category = \"unsafe-export\";\n constructor() {\n // Never reflect untrusted values, keys, pointers, or validation errors.\n super(\"unsafe-export: Live transcript cannot be retained safely\");\n this.name = \"LiveUnsafeExportError\";\n }\n}\n\nconst credentialKey =\n /^(?:authorization|proxy-authorization|cookie|set-cookie|x-api-key|api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|password|secret|token|sec-websocket-protocol)$/i;\n// These branches carry conversation, tool schema, or PCM semantics. A key named\n// \"authorization\" inside tool parameters is a schema property, not a header.\nconst semanticKey = new Set([\n \"instructions\",\n \"content\",\n \"delta\",\n \"audio\",\n \"arguments\",\n \"output\",\n \"input\",\n \"tools\",\n \"parameters\",\n \"client\",\n \"text\",\n \"thinking\",\n]);\nconst metadataKey = new Set([\"metadata\", \"headers\", \"diagnostics\"]);\nconst unsafeKey = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\nconst redacted = \"[REDACTED]\";\nfunction fail(): never {\n throw new LiveUnsafeExportError();\n}\nfunction decodeUrlComponent(value: string): string {\n // Tolerate malformed escapes while exposing encoded secrets. Protect literal\n // form separators so the entire component remains one value.\n return new URLSearchParams(`value=${value.replace(/\\+/g, \"%2B\").replace(/&/g, \"%26\")}`).get(\n \"value\",\n )!;\n}\nfunction escapePointer(key: string): string {\n return key.replace(/~/g, \"~0\").replace(/\\//g, \"~1\");\n}\nfunction parts(pointer: string): string[] {\n if (!pointer.startsWith(\"/\") || /~(?![01])/u.test(pointer)) fail();\n const keys = pointer\n .slice(1)\n .split(\"/\")\n .map((key) => key.replace(/~1/g, \"/\").replace(/~0/g, \"~\"));\n if (keys.some((key) => unsafeKey.has(key))) fail();\n return keys;\n}\nfunction child(value: LiveJson, key: string): LiveJson {\n if (!value || typeof value !== \"object\" || !Object.hasOwn(value, key)) fail();\n if (Array.isArray(value)) {\n if (!/^(0|[1-9]\\d*)$/.test(key)) fail();\n return value[Number(key)];\n }\n return value[key];\n}\nfunction setIdentifier(event: LiveObject, pointer: string, value: string): void {\n const keys = parts(pointer);\n const last = keys.pop();\n if (last === undefined) fail();\n let parent: LiveJson = event;\n for (const key of keys) parent = child(parent, key);\n if (typeof child(parent, last) !== \"string\" || !parent || typeof parent !== \"object\") fail();\n if (Array.isArray(parent)) parent[Number(last)] = value;\n else parent[last] = value;\n}\n\n/**\n * Sanitize a separately retained successful capture; never mutate the relay.\n * Validation supplies the reviewed identifier descriptors and causal checks.\n * Credential descriptors supplement the built-in policy, not permission to\n * delete arbitrary protocol operands. Ambiguous secret-bearing data fails closed.\n */\nexport function sanitizeLiveTranscript(\n transcript: LiveTranscript,\n secretValues: readonly string[] = [],\n credentialFields: readonly LiveCredentialField[] = [],\n validationOptions: Omit<LiveOptions, \"secretValues\"> = {},\n): LiveTranscript {\n try {\n const result = validateLiveTranscript(transcript, validationOptions);\n if (!Array.isArray(secretValues) || secretValues.some((s) => typeof s !== \"string\")) fail();\n const secrets = [...new Set(secretValues.filter((s) => s.length))].sort(\n (a, b) => b.length - a.length,\n );\n const hasSecret = (value: string) => secrets.some((secret) => value.includes(secret));\n const replaceSecrets = (value: string) => {\n for (const secret of secrets) value = value.split(secret).join(redacted);\n return value;\n };\n const descriptors = new Set(\n credentialFields.map((field) => {\n parts(field.pointer);\n return field.pointer;\n }),\n );\n const handled = new Set<string>();\n // Names themselves may have been derived from provider IDs. Replace both\n // the annotation names and only their explicitly annotated operand values.\n const originalIds = new Set<string>();\n for (const binding of result.bindings) {\n let value: LiveJson = result.entries[binding.entry].event;\n for (const key of parts(binding.pointer)) value = child(value, key);\n if (typeof value !== \"string\") fail();\n originalIds.add(value);\n }\n let nextId = 1;\n const identities = new Map<string, { name: string; value: string }>();\n for (const binding of result.bindings) {\n let identity = identities.get(binding.name);\n if (!identity) {\n const index = identities.size + 1;\n while (originalIds.has(`live-id-${nextId}`)) nextId++;\n identity = { name: `binding-${index}`, value: `live-id-${nextId++}` };\n identities.set(binding.name, identity);\n }\n setIdentifier(result.entries[binding.entry].event, binding.pointer, identity.value);\n binding.name = identity.name;\n }\n function sanitizeUrl(value: string): string {\n // Leave non-URLs and clean URLs byte-for-byte intact.\n if (!/^[a-z][a-z0-9+.-]*:\\/\\//i.test(value)) return value;\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return value;\n }\n // A path identifies the resource; changing it could change replay semantics.\n if (hasSecret(decodeUrlComponent(url.pathname))) fail();\n let changed = false;\n if (url.username || url.password) {\n url.username = \"\";\n url.password = \"\";\n changed = true;\n }\n for (const [key, val] of [...url.searchParams]) {\n if (credentialKey.test(key) || hasSecret(key) || hasSecret(val)) {\n url.searchParams.delete(key);\n changed = true;\n }\n }\n const decodedHash = decodeUrlComponent(url.hash);\n if (\n url.hash &&\n (hasSecret(decodedHash) || /(?:token|secret|password|api[_-]?key)=/i.test(decodedHash))\n ) {\n url.hash = \"\";\n changed = true;\n }\n return changed ? url.href : value;\n }\n function providerMetadata(pointer: string): boolean {\n const match =\n /^\\/entries\\/(\\d+)\\/event\\/event\\/(?:response\\/(?:user|prompt_cache_key|safety_identifier)|item\\/encrypted_content)$/.exec(\n pointer,\n );\n if (!match) return false;\n const event = result.entries[Number(match[1])].event;\n const nested = event.event;\n if (\n event.type !== \"response.event\" ||\n !nested ||\n typeof nested !== \"object\" ||\n Array.isArray(nested)\n )\n return false;\n if (!pointer.endsWith(\"/item/encrypted_content\")) {\n return [\"response.created\", \"response.in_progress\", \"response.completed\"].includes(\n String(nested.type),\n );\n }\n const item = nested.item;\n return (\n [\"response.output_item.added\", \"response.output_item.done\"].includes(String(nested.type)) &&\n !!item &&\n typeof item === \"object\" &&\n !Array.isArray(item) &&\n item.type === \"reasoning\"\n );\n }\n function visit(value: LiveJson, path: string, metadata = false, semantic = false): LiveJson {\n if (typeof value === \"string\") {\n const url = sanitizeUrl(value);\n if ((hasSecret(value) || url !== value) && (!metadata || semantic)) fail();\n return metadata && !semantic ? replaceSecrets(url) : value;\n }\n if (Array.isArray(value))\n return value.map((item, i) => visit(item, `${path}/${i}`, metadata, semantic));\n if (!value || typeof value !== \"object\") return value;\n const output: LiveObject = {};\n for (const [key, item] of Object.entries(value)) {\n const pointer = `${path}/${escapePointer(key)}`;\n // Exact retained metadata sites from the reviewed v3 sanitation\n // evidence. Never infer these by equal values or similar field names.\n if (providerMetadata(pointer) && (typeof item === \"string\" || item === null)) {\n if (descriptors.has(pointer)) handled.add(pointer);\n else output[key] = typeof item === \"string\" ? redacted : item;\n continue;\n }\n const protectedBranch = semantic || semanticKey.has(key);\n const removable = !protectedBranch && (metadata || credentialKey.test(key));\n if (descriptors.has(pointer)) {\n if (!removable) fail();\n handled.add(pointer);\n continue;\n }\n if (!semantic && credentialKey.test(key)) continue;\n if (hasSecret(key)) {\n if (!metadata || protectedBranch) fail();\n continue;\n }\n output[key] = visit(item, pointer, metadata || metadataKey.has(key), protectedBranch);\n }\n return output;\n }\n // Validation clones all bounded JSON, including unknown metadata. Preserve\n // those fields, then validate again to catch any semantic deletion.\n const cleaned = visit(result as LiveTranscript & LiveObject, \"\");\n if (handled.size !== descriptors.size || hasSecret(JSON.stringify(cleaned))) fail();\n return validateLiveTranscript(cleaned, validationOptions);\n } catch {\n fail();\n }\n}\n"],"mappings":"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"}
|