@copilotkit/aimock 1.42.0 → 1.43.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 +56 -0
- package/README.md +102 -16
- 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 +12 -8
- 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 +14 -10
- 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 +622 -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 +613 -74
- package/dist/chaos.js.map +1 -1
- package/dist/cli.cjs +42 -23
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +42 -23
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +10 -8
- 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 +12 -10
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts.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/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 +8 -4
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.d.cts.map +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +9 -5
- 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 +1071 -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 +1059 -0
- package/dist/files.js.map +1 -0
- package/dist/fine-tuning.cjs +1051 -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 +1044 -0
- package/dist/fine-tuning.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 +37 -5
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +37 -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 +2 -2
- package/dist/gemini-embeddings.cjs.map +1 -1
- package/dist/gemini-embeddings.js +3 -3
- package/dist/gemini-embeddings.js.map +1 -1
- package/dist/gemini-interactions.cjs +13 -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 +15 -9
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +11 -7
- 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 +13 -9
- 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 +229 -7
- 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 +221 -8
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +54 -123
- 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 +55 -124
- package/dist/images.js.map +1 -1
- package/dist/index.cjs +41 -8
- package/dist/index.d.cts +12 -7
- package/dist/index.d.ts +12 -7
- package/dist/index.js +14 -10
- package/dist/jest.cjs +14 -7
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +3 -2
- package/dist/jest.d.cts.map +1 -1
- package/dist/jest.d.ts +3 -2
- package/dist/jest.d.ts.map +1 -1
- package/dist/jest.js +14 -7
- package/dist/jest.js.map +1 -1
- package/dist/journal.cjs +41 -4
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts +44 -3
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts +44 -3
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +41 -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 +52 -41
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +16 -5
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +16 -5
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +53 -42
- package/dist/llmock.js.map +1 -1
- package/dist/messages.cjs +11 -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 +13 -9
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +258 -5
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts +3 -1
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts +3 -1
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +255 -6
- 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 +5 -5
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.js +6 -6
- package/dist/ollama.js.map +1 -1
- package/dist/openrouter-video.cjs +5 -5
- package/dist/openrouter-video.cjs.map +1 -1
- package/dist/openrouter-video.js +6 -6
- package/dist/openrouter-video.js.map +1 -1
- package/dist/recorder.cjs +45 -9
- 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 +43 -11
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +4 -3
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.js +5 -5
- package/dist/responses.js.map +1 -1
- package/dist/router.cjs +20 -33
- 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 +20 -34
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +701 -625
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +1 -0
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +704 -628
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +2 -2
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.js +3 -3
- package/dist/speech.js.map +1 -1
- package/dist/transcription.cjs +4 -4
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.js +5 -5
- package/dist/transcription.js.map +1 -1
- package/dist/types.d.cts +119 -16
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +119 -16
- package/dist/types.d.ts.map +1 -1
- package/dist/validate-cli.cjs +925 -0
- package/dist/validate-cli.cjs.map +1 -0
- package/dist/validate-cli.js +924 -0
- package/dist/validate-cli.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 +4 -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 +5 -5
- package/dist/video.js.map +1 -1
- package/dist/vitest.cjs +13 -6
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +2 -1
- package/dist/vitest.d.cts.map +1 -1
- package/dist/vitest.d.ts +2 -1
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +14 -7
- 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.d.ts +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 +2 -2
- 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 +2 -2
- package/dist/ws-responses.js +2 -2
- 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 +1 -1
package/dist/ws-live.js
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { flattenHeaders, isLiveResponse, resolveResponse, resolveStrictMode, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, wouldProxyMiss } from "./helpers.js";
|
|
2
|
+
import { normalizeLiveFixture, validateLiveClientEvent } from "./live-fixture.js";
|
|
3
|
+
import { matchFixtureDiagnostic } from "./router.js";
|
|
4
|
+
import { resolveUpstreamUrl } from "./url.js";
|
|
5
|
+
import { applyProviderAuth } from "./provider-auth.js";
|
|
6
|
+
import { DEFAULT_MAX_PROXY_BUFFER_BYTES, DEFAULT_MAX_PROXY_BUFFER_FRAMES, clampTimeout } from "./recorder.js";
|
|
7
|
+
import { LiveRecorderError, createLiveRecorder } from "./live-recorder.js";
|
|
8
|
+
import { connectUpstreamWebSocket } from "./ws-upstream.js";
|
|
9
|
+
import { createLiveReplay } from "./live-replay.js";
|
|
10
|
+
import { performance } from "node:perf_hooks";
|
|
11
|
+
|
|
12
|
+
//#region src/ws-live.ts
|
|
13
|
+
function object(value) {
|
|
14
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
15
|
+
}
|
|
16
|
+
/** Node otherwise turns an overflowing delay into a one-millisecond timeout. */
|
|
17
|
+
function scheduleLiveTimeout(callback, timeoutMs) {
|
|
18
|
+
const started = performance.now();
|
|
19
|
+
const onTimeout = () => {
|
|
20
|
+
const remaining = timeoutMs - (performance.now() - started);
|
|
21
|
+
if (remaining > 0) timer = setTimeout(onTimeout, Math.min(remaining, 2147483647));
|
|
22
|
+
else callback();
|
|
23
|
+
};
|
|
24
|
+
let timer = setTimeout(onTimeout, Math.min(timeoutMs, 2147483647));
|
|
25
|
+
return () => clearTimeout(timer);
|
|
26
|
+
}
|
|
27
|
+
/** Diagnostic copies never retain executable members or mutate replay operands. */
|
|
28
|
+
function liveJournalSnapshot(entry, secretValues) {
|
|
29
|
+
const secrets = secretValues.filter((secret) => secret.length > 0);
|
|
30
|
+
if (!secrets.length) return entry;
|
|
31
|
+
const containsSecret = (value) => secrets.some((secret) => value.includes(secret));
|
|
32
|
+
const marker = containsSecret("[REDACTED]") ? "" : "[REDACTED]";
|
|
33
|
+
const redact = (value) => containsSecret(value) ? marker : value;
|
|
34
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
35
|
+
const copy = (value) => {
|
|
36
|
+
if (typeof value === "string") return redact(value);
|
|
37
|
+
if (typeof value === "function" || typeof value === "symbol") return void 0;
|
|
38
|
+
if (value === null || typeof value !== "object") return value;
|
|
39
|
+
if (ancestors.has(value)) return void 0;
|
|
40
|
+
ancestors.add(value);
|
|
41
|
+
try {
|
|
42
|
+
if (Array.isArray(value)) return value.map(copy);
|
|
43
|
+
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return void 0;
|
|
44
|
+
return Object.fromEntries(Object.entries(Object.getOwnPropertyDescriptors(value)).filter(([, descriptor]) => descriptor.enumerable && "value" in descriptor).map(([key, descriptor]) => [redact(key), copy(descriptor.value)]));
|
|
45
|
+
} finally {
|
|
46
|
+
ancestors.delete(value);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
...entry,
|
|
51
|
+
headers: copy(entry.headers),
|
|
52
|
+
body: copy(entry.body),
|
|
53
|
+
response: {
|
|
54
|
+
...entry.response,
|
|
55
|
+
fixture: copy(entry.response.fixture)
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Owns a single transport, replay cursor and every cancellation deadline. */
|
|
60
|
+
function handleLiveSession(ws, fixtures, journal, options) {
|
|
61
|
+
const { defaults, limits, testId } = options;
|
|
62
|
+
const normalizeRecordTimeout = (value) => Math.min(Number.MAX_SAFE_INTEGER, Math.ceil(clampTimeout(value, 3e4)));
|
|
63
|
+
const upstreamTimeoutMs = normalizeRecordTimeout(defaults.record?.upstreamTimeoutMs);
|
|
64
|
+
const bodyTimeoutMs = normalizeRecordTimeout(defaults.record?.bodyTimeoutMs);
|
|
65
|
+
const controller = new AbortController();
|
|
66
|
+
let state = "awaiting-start";
|
|
67
|
+
let mode;
|
|
68
|
+
let starting = false;
|
|
69
|
+
const pending = [];
|
|
70
|
+
let pendingBytes = 0;
|
|
71
|
+
const maxPendingBytes = Math.min(limits.maxBufferedBytes, defaults.record?.maxProxyBufferBytes ?? DEFAULT_MAX_PROXY_BUFFER_BYTES);
|
|
72
|
+
const maxPendingFrames = defaults.record?.maxProxyBufferFrames ?? DEFAULT_MAX_PROXY_BUFFER_FRAMES;
|
|
73
|
+
let upstream;
|
|
74
|
+
let recorder;
|
|
75
|
+
let recordingSecrets = [];
|
|
76
|
+
let terminalReceived = false;
|
|
77
|
+
let captureStarted;
|
|
78
|
+
let recordingJournaled = false;
|
|
79
|
+
let upstreamIdle;
|
|
80
|
+
let replay;
|
|
81
|
+
const deadline = scheduleLiveTimeout(() => fail("timeout", "Live session duration exceeded"), limits.maxDurationMs);
|
|
82
|
+
const startup = scheduleLiveTimeout(() => fail("timeout", "Live startup deadline exceeded"), defaults.record ? upstreamTimeoutMs : limits.idleTimeoutMs);
|
|
83
|
+
function recordJournal(category) {
|
|
84
|
+
if (recordingJournaled || !recorder) return;
|
|
85
|
+
recordingJournaled = true;
|
|
86
|
+
journal.add(liveJournalSnapshot({
|
|
87
|
+
method: "WS",
|
|
88
|
+
path: "/v1/live/sessions",
|
|
89
|
+
headers: { "x-test-id": testId },
|
|
90
|
+
body: {
|
|
91
|
+
model: "gpt-live-1",
|
|
92
|
+
messages: []
|
|
93
|
+
},
|
|
94
|
+
response: {
|
|
95
|
+
status: category ? 502 : 200,
|
|
96
|
+
fixture: null,
|
|
97
|
+
source: "proxy",
|
|
98
|
+
...strictOverrideField(defaults.strict, options.headers),
|
|
99
|
+
...category ? { error: category } : {}
|
|
100
|
+
}
|
|
101
|
+
}, [...limits.secretValues, ...recordingSecrets]));
|
|
102
|
+
}
|
|
103
|
+
function cleanup() {
|
|
104
|
+
if (state === "closed") return;
|
|
105
|
+
state = "closed";
|
|
106
|
+
deadline();
|
|
107
|
+
startup();
|
|
108
|
+
upstreamIdle?.();
|
|
109
|
+
recordJournal("incomplete-capture");
|
|
110
|
+
recorder?.abort();
|
|
111
|
+
upstream?.destroy();
|
|
112
|
+
pending.length = 0;
|
|
113
|
+
pendingBytes = 0;
|
|
114
|
+
replay?.close();
|
|
115
|
+
controller.abort();
|
|
116
|
+
ws.removeListener("message", receive);
|
|
117
|
+
ws.removeListener("close", cleanup);
|
|
118
|
+
ws.removeListener("error", cleanup);
|
|
119
|
+
options.onDispose();
|
|
120
|
+
}
|
|
121
|
+
function dispose() {
|
|
122
|
+
if (state === "closed") return;
|
|
123
|
+
ws.close(1001, "Live session disposed");
|
|
124
|
+
cleanup();
|
|
125
|
+
}
|
|
126
|
+
function fail(category, message) {
|
|
127
|
+
if (state === "closed" || state === "closing") return;
|
|
128
|
+
state = "closing";
|
|
129
|
+
recorder?.abort(category);
|
|
130
|
+
recordJournal(category);
|
|
131
|
+
ws.send(JSON.stringify({
|
|
132
|
+
type: "aimock.error",
|
|
133
|
+
error: {
|
|
134
|
+
category,
|
|
135
|
+
message
|
|
136
|
+
}
|
|
137
|
+
}));
|
|
138
|
+
ws.close(category === "resource-limit" ? 1009 : 1008, "Live session failed");
|
|
139
|
+
cleanup();
|
|
140
|
+
}
|
|
141
|
+
function receive(data) {
|
|
142
|
+
if (state === "closed" || state === "closing") return;
|
|
143
|
+
let event;
|
|
144
|
+
try {
|
|
145
|
+
event = validateLiveClientEvent(JSON.parse(data), limits);
|
|
146
|
+
} catch {
|
|
147
|
+
fail("invalid-client", "Invalid Live client command");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (starting) {
|
|
151
|
+
if (event.type === "session.start") {
|
|
152
|
+
fail("invalid-client", "Live session has already started");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const bytes = Buffer.byteLength(data);
|
|
156
|
+
if (bytes > maxPendingBytes - pendingBytes || pending.length >= maxPendingFrames) {
|
|
157
|
+
fail("resource-limit", "Live startup input queue exceeded");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
pendingBytes += bytes;
|
|
161
|
+
pending.push(event);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
accept(event);
|
|
165
|
+
}
|
|
166
|
+
function recordingError(error) {
|
|
167
|
+
fail(error instanceof LiveRecorderError ? error.category : error instanceof Error && error.message === "WebSocket write limit exceeded" ? "resource-limit" : "upstream-connect", "Live recording failed");
|
|
168
|
+
}
|
|
169
|
+
function touchUpstream() {
|
|
170
|
+
upstreamIdle?.();
|
|
171
|
+
upstreamIdle = scheduleLiveTimeout(() => fail("timeout", "Live upstream idle deadline exceeded"), bodyTimeoutMs);
|
|
172
|
+
}
|
|
173
|
+
function forward(event) {
|
|
174
|
+
if (!upstream || !recorder || terminalReceived) return;
|
|
175
|
+
try {
|
|
176
|
+
const raw = JSON.stringify(event);
|
|
177
|
+
const now = performance.now();
|
|
178
|
+
captureStarted ??= now;
|
|
179
|
+
recorder.acceptClient(raw, Math.floor(now - captureStarted));
|
|
180
|
+
upstream.sendAsync(raw, controller.signal).catch(recordingError);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
recordingError(error);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async function startRecording(first) {
|
|
186
|
+
const record = defaults.record;
|
|
187
|
+
if (!record?.providers.openai) {
|
|
188
|
+
fail("fixture-mismatch", "No matching Live fixture");
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
starting = true;
|
|
192
|
+
try {
|
|
193
|
+
const base = new URL(record.providers.openai);
|
|
194
|
+
if (base.username || base.password || base.search || base.hash) throw new Error("Invalid URL");
|
|
195
|
+
base.pathname = base.pathname.replace(/\/v1\/?$/, "/");
|
|
196
|
+
const target = resolveUpstreamUrl(base.href, "/v1/live/sessions");
|
|
197
|
+
const headers = {};
|
|
198
|
+
const incoming = options.headers.authorization;
|
|
199
|
+
const credential = incoming?.match(/^\s*(?:Bearer|Key)\s+(.+)$/i)?.[1].trim();
|
|
200
|
+
if (incoming && !options.localApiKeys?.includes(credential ?? "")) headers.Authorization = incoming;
|
|
201
|
+
applyProviderAuth(headers, target, "openai", record.providerKeys?.openai);
|
|
202
|
+
const selected = headers.Authorization?.replace(/^\s*(?:Bearer|Key)\s+/i, "").trim();
|
|
203
|
+
recordingSecrets = [...selected ? [selected] : [], ...options.localApiKeys ?? []];
|
|
204
|
+
recorder = createLiveRecorder({
|
|
205
|
+
record,
|
|
206
|
+
fixtures,
|
|
207
|
+
logger: defaults.logger,
|
|
208
|
+
testId,
|
|
209
|
+
live: limits,
|
|
210
|
+
secretValues: recordingSecrets,
|
|
211
|
+
provenance: {
|
|
212
|
+
host: target.host,
|
|
213
|
+
path: "/v1/live/sessions",
|
|
214
|
+
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
215
|
+
referenceVersion: "gpt-live-1-observed-v3",
|
|
216
|
+
sanitationVersion: 1
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
upstream = await connectUpstreamWebSocket(target, headers, limits, controller.signal, upstreamTimeoutMs);
|
|
220
|
+
if (controller.signal.aborted) {
|
|
221
|
+
upstream.destroy();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
upstream.on("activity", touchUpstream);
|
|
225
|
+
upstream.on("message", (raw) => {
|
|
226
|
+
if (controller.signal.aborted) return;
|
|
227
|
+
touchUpstream();
|
|
228
|
+
try {
|
|
229
|
+
recorder.acceptServer(raw, captureStarted === void 0 ? 0 : Math.floor(performance.now() - captureStarted));
|
|
230
|
+
const output = JSON.parse(raw);
|
|
231
|
+
if (!output || typeof output !== "object" || !("type" in output)) throw new Error("Invalid event");
|
|
232
|
+
if (output.type === "session.started") {
|
|
233
|
+
startup();
|
|
234
|
+
starting = false;
|
|
235
|
+
state = "active";
|
|
236
|
+
const queued = pending.splice(0);
|
|
237
|
+
pendingBytes = 0;
|
|
238
|
+
for (const event of queued) {
|
|
239
|
+
if (controller.signal.aborted) break;
|
|
240
|
+
accept(event);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const terminal = output.type === "session.closed";
|
|
244
|
+
if (terminal) terminalReceived = true;
|
|
245
|
+
ws.sendAsync(raw, controller.signal).then(() => {
|
|
246
|
+
if (!terminal || controller.signal.aborted) return;
|
|
247
|
+
const result = recorder.finish();
|
|
248
|
+
if (result.kind === "failed") {
|
|
249
|
+
fail(result.category, "Live capture export failed");
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
recordJournal();
|
|
253
|
+
ws.close(1e3, "Live session complete");
|
|
254
|
+
cleanup();
|
|
255
|
+
}, recordingError);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
recordingError(error);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
upstream.on("error", () => fail("upstream-connect", "Live upstream connection failed"));
|
|
261
|
+
upstream.on("close", (code) => {
|
|
262
|
+
if (!terminalReceived) fail(code === 1009 ? "resource-limit" : "incomplete-capture", "Live upstream closed before completion");
|
|
263
|
+
});
|
|
264
|
+
touchUpstream();
|
|
265
|
+
forward(first);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
const message = error instanceof Error ? error.message : "";
|
|
268
|
+
if (/HTTP 401/.test(message)) fail("upstream-auth", "Live upstream authentication refused");
|
|
269
|
+
else if (/HTTP (403|404|429)/.test(message)) fail("upstream-access", "Live upstream access refused");
|
|
270
|
+
else recordingError(error);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function startReplay(fixture, request, first, raw) {
|
|
274
|
+
if (state === "closed" || state === "closing") return;
|
|
275
|
+
try {
|
|
276
|
+
if (!isLiveResponse(raw)) throw new Error("not a Live fixture");
|
|
277
|
+
const response = normalizeLiveFixture(raw, limits);
|
|
278
|
+
replay = createLiveReplay(response.live, {
|
|
279
|
+
limits,
|
|
280
|
+
signal: controller.signal,
|
|
281
|
+
timing: response.liveTiming ?? "recorded",
|
|
282
|
+
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
|
|
283
|
+
fail,
|
|
284
|
+
send: async (output) => {
|
|
285
|
+
await ws.sendAsync(JSON.stringify(output), controller.signal);
|
|
286
|
+
if (output.type === "session.closed" && !controller.signal.aborted) {
|
|
287
|
+
ws.close(1e3, "Live session complete");
|
|
288
|
+
cleanup();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
} catch {
|
|
293
|
+
fail("protocol-divergence", "Invalid Live replay configuration");
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
journal.add(liveJournalSnapshot({
|
|
297
|
+
method: "WS",
|
|
298
|
+
path: "/v1/live/sessions",
|
|
299
|
+
headers: {
|
|
300
|
+
...flattenHeaders(options.headers),
|
|
301
|
+
"x-test-id": testId
|
|
302
|
+
},
|
|
303
|
+
body: request,
|
|
304
|
+
response: {
|
|
305
|
+
status: 200,
|
|
306
|
+
fixture
|
|
307
|
+
}
|
|
308
|
+
}, limits.secretValues), fixture);
|
|
309
|
+
startup();
|
|
310
|
+
starting = false;
|
|
311
|
+
state = "active";
|
|
312
|
+
replay.accept(first);
|
|
313
|
+
const queued = pending.splice(0);
|
|
314
|
+
pendingBytes = 0;
|
|
315
|
+
for (const event of queued) {
|
|
316
|
+
if (controller.signal.aborted) break;
|
|
317
|
+
accept(event);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function accept(event) {
|
|
321
|
+
if (state === "closed" || state === "closing") return;
|
|
322
|
+
if (state === "awaiting-start") {
|
|
323
|
+
const session = object(event.session);
|
|
324
|
+
const format = object(object(session?.audio)?.format);
|
|
325
|
+
const delegation = object(session?.delegation);
|
|
326
|
+
if (event.type !== "session.start" || session?.model !== "gpt-live-1" || format?.type !== "audio/pcm" || format.rate !== 24e3 || delegation?.type !== "client" && delegation?.type !== "responses") {
|
|
327
|
+
fail("invalid-client", "Live requires session.start with explicit model, PCM24 audio and delegation");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
mode = delegation.type === "client" ? "client" : "managed";
|
|
331
|
+
const request = {
|
|
332
|
+
model: session.model,
|
|
333
|
+
messages: [],
|
|
334
|
+
_endpointType: "openai-live",
|
|
335
|
+
session: structuredClone(session)
|
|
336
|
+
};
|
|
337
|
+
let fixture;
|
|
338
|
+
let skippedBySequenceOrTurn;
|
|
339
|
+
try {
|
|
340
|
+
({fixture, skippedBySequenceOrTurn} = matchFixtureDiagnostic(fixtures, request, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform));
|
|
341
|
+
} catch {
|
|
342
|
+
fail("protocol-divergence", "Live fixture selection failed");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (!fixture) {
|
|
346
|
+
const strict = resolveStrictMode(defaults.strict, options.headers);
|
|
347
|
+
if (strict) {
|
|
348
|
+
const message = strictNoMatchMessage(skippedBySequenceOrTurn);
|
|
349
|
+
defaults.logger.error(strictNoMatchLogLine("WS", "/v1/live/sessions", skippedBySequenceOrTurn));
|
|
350
|
+
journal.add(liveJournalSnapshot({
|
|
351
|
+
method: "WS",
|
|
352
|
+
path: "/v1/live/sessions",
|
|
353
|
+
headers: { "x-test-id": testId },
|
|
354
|
+
body: {
|
|
355
|
+
model: request.model,
|
|
356
|
+
messages: []
|
|
357
|
+
},
|
|
358
|
+
response: {
|
|
359
|
+
status: 503,
|
|
360
|
+
fixture: null,
|
|
361
|
+
...strictOverrideField(defaults.strict, options.headers)
|
|
362
|
+
}
|
|
363
|
+
}, limits.secretValues));
|
|
364
|
+
fail("fixture-mismatch", message);
|
|
365
|
+
} else if (wouldProxyMiss(strict, defaults.record, "openai")) startRecording(event);
|
|
366
|
+
else fail("fixture-mismatch", "No matching Live fixture");
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
370
|
+
if (typeof fixture.response === "function") {
|
|
371
|
+
starting = true;
|
|
372
|
+
const selected = fixture;
|
|
373
|
+
resolveResponse(selected, request).then((response) => startReplay(selected, request, event, response), () => fail("protocol-divergence", "Live response factory failed"));
|
|
374
|
+
} else startReplay(fixture, request, event, fixture.response);
|
|
375
|
+
return;
|
|
376
|
+
} else if (event.type === "session.start") {
|
|
377
|
+
fail("invalid-client", "Live session has already started");
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (event.type === "session.update") {
|
|
381
|
+
const patch = object(event.session);
|
|
382
|
+
const delegation = object(patch?.delegation);
|
|
383
|
+
const backend = object(delegation?.responses);
|
|
384
|
+
if (mode !== "managed" || !patch || !delegation || !backend || Object.keys(patch).some((key) => key !== "delegation") || Object.keys(delegation).some((key) => key !== "type" && key !== "responses") || delegation.type !== void 0 && delegation.type !== "responses" || Object.keys(backend).some((key) => key !== "instructions") || !Object.hasOwn(backend, "instructions")) {
|
|
385
|
+
fail("invalid-client", "Only managed backend instructions can be updated");
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (upstream) forward(event);
|
|
390
|
+
else replay?.accept(event);
|
|
391
|
+
}
|
|
392
|
+
ws.on("message", receive);
|
|
393
|
+
ws.on("close", cleanup);
|
|
394
|
+
ws.on("error", cleanup);
|
|
395
|
+
return dispose;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
//#endregion
|
|
399
|
+
export { handleLiveSession };
|
|
400
|
+
//# sourceMappingURL=ws-live.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws-live.js","names":[],"sources":["../src/ws-live.ts"],"sourcesContent":["import { performance } from \"node:perf_hooks\";\nimport type { IncomingHttpHeaders } from \"node:http\";\nimport {\n flattenHeaders,\n isLiveResponse,\n resolveResponse,\n resolveStrictMode,\n strictNoMatchLogLine,\n strictNoMatchMessage,\n strictOverrideField,\n wouldProxyMiss,\n} from \"./helpers.js\";\nimport { normalizeLiveFixture, validateLiveClientEvent } from \"./live-fixture.js\";\nimport { createLiveRecorder, LiveRecorderError } from \"./live-recorder.js\";\nimport { applyProviderAuth } from \"./provider-auth.js\";\nimport { connectUpstreamWebSocket } from \"./ws-upstream.js\";\nimport { resolveUpstreamUrl } from \"./url.js\";\nimport {\n clampTimeout,\n DEFAULT_MAX_PROXY_BUFFER_BYTES,\n DEFAULT_MAX_PROXY_BUFFER_FRAMES,\n} from \"./recorder.js\";\nimport { createLiveReplay } from \"./live-replay.js\";\nimport type {\n LiveFailureCategory,\n LiveJson,\n LiveMode,\n LiveObject,\n LiveOptions,\n} from \"./live-types.js\";\nimport type { Journal } from \"./journal.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n FixtureResponse,\n HandlerDefaults,\n JournalEntry,\n} from \"./types.js\";\nimport type { WebSocketConnection } from \"./ws-framing.js\";\n\nexport interface LiveSessionOptions {\n defaults: HandlerDefaults;\n limits: Required<LiveOptions>;\n testId: string;\n headers: IncomingHttpHeaders;\n localApiKeys?: readonly string[];\n onDispose: () => void;\n}\nfunction object(value: LiveJson | undefined): LiveObject | undefined {\n return value !== null && typeof value === \"object\" && !Array.isArray(value) ? value : undefined;\n}\n\n/** Node otherwise turns an overflowing delay into a one-millisecond timeout. */\nfunction scheduleLiveTimeout(callback: () => void, timeoutMs: number): () => void {\n const started = performance.now();\n const onTimeout = () => {\n const remaining = timeoutMs - (performance.now() - started);\n if (remaining > 0) timer = setTimeout(onTimeout, Math.min(remaining, 2_147_483_647));\n else callback();\n };\n let timer = setTimeout(onTimeout, Math.min(timeoutMs, 2_147_483_647));\n return () => clearTimeout(timer);\n}\n\n/** Diagnostic copies never retain executable members or mutate replay operands. */\nfunction liveJournalSnapshot(\n entry: Omit<JournalEntry, \"id\" | \"timestamp\">,\n secretValues: readonly string[],\n): Omit<JournalEntry, \"id\" | \"timestamp\"> {\n const secrets = secretValues.filter((secret) => secret.length > 0);\n if (!secrets.length) return entry;\n const containsSecret = (value: string) => secrets.some((secret) => value.includes(secret));\n // Whole-field redaction avoids creating another secret by joining substrings.\n const marker = containsSecret(\"[REDACTED]\") ? \"\" : \"[REDACTED]\";\n const redact = (value: string) => (containsSecret(value) ? marker : value);\n const ancestors = new Set<object>();\n const copy = (value: unknown): unknown => {\n if (typeof value === \"string\") return redact(value);\n if (typeof value === \"function\" || typeof value === \"symbol\") return undefined;\n if (value === null || typeof value !== \"object\") return value;\n if (ancestors.has(value)) return undefined;\n ancestors.add(value);\n try {\n if (Array.isArray(value)) return value.map(copy);\n // Diagnostics contain data only; omit getters and non-JSON objects.\n if (\n Object.getPrototypeOf(value) !== Object.prototype &&\n Object.getPrototypeOf(value) !== null\n )\n return undefined;\n return Object.fromEntries(\n Object.entries(Object.getOwnPropertyDescriptors(value))\n .filter(([, descriptor]) => descriptor.enumerable && \"value\" in descriptor)\n .map(([key, descriptor]) => [redact(key), copy(descriptor.value)]),\n );\n } finally {\n ancestors.delete(value);\n }\n };\n // Preserve the journal schema; copied operands are diagnostics, never replay inputs.\n return {\n ...entry,\n headers: copy(entry.headers) as JournalEntry[\"headers\"],\n body: copy(entry.body) as JournalEntry[\"body\"],\n response: { ...entry.response, fixture: copy(entry.response.fixture) as Fixture | null },\n };\n}\n\n/** Owns a single transport, replay cursor and every cancellation deadline. */\nexport function handleLiveSession(\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n options: LiveSessionOptions,\n): () => void {\n const { defaults, limits, testId } = options;\n // HTTP accepts positive finite milliseconds; the connector requires whole safe milliseconds.\n const normalizeRecordTimeout = (value: number | undefined) =>\n Math.min(Number.MAX_SAFE_INTEGER, Math.ceil(clampTimeout(value, 30_000)));\n const upstreamTimeoutMs = normalizeRecordTimeout(defaults.record?.upstreamTimeoutMs);\n const bodyTimeoutMs = normalizeRecordTimeout(defaults.record?.bodyTimeoutMs);\n const controller = new AbortController();\n let state: \"awaiting-start\" | \"active\" | \"closing\" | \"closed\" = \"awaiting-start\";\n let mode: LiveMode | undefined;\n let starting = false;\n const pending: LiveObject[] = [];\n let pendingBytes = 0;\n const maxPendingBytes = Math.min(\n limits.maxBufferedBytes,\n defaults.record?.maxProxyBufferBytes ?? DEFAULT_MAX_PROXY_BUFFER_BYTES,\n );\n const maxPendingFrames = defaults.record?.maxProxyBufferFrames ?? DEFAULT_MAX_PROXY_BUFFER_FRAMES;\n let upstream: WebSocketConnection | undefined;\n let recorder: ReturnType<typeof createLiveRecorder> | undefined;\n let recordingSecrets: string[] = [];\n let terminalReceived = false;\n let captureStarted: number | undefined;\n let recordingJournaled = false;\n let upstreamIdle: (() => void) | undefined;\n let replay: ReturnType<typeof createLiveReplay> | undefined;\n const deadline = scheduleLiveTimeout(\n () => fail(\"timeout\", \"Live session duration exceeded\"),\n limits.maxDurationMs,\n );\n const startup = scheduleLiveTimeout(\n () => fail(\"timeout\", \"Live startup deadline exceeded\"),\n defaults.record ? upstreamTimeoutMs : limits.idleTimeoutMs,\n );\n\n function recordJournal(category?: LiveFailureCategory) {\n if (recordingJournaled || !recorder) return;\n recordingJournaled = true;\n journal.add(\n liveJournalSnapshot(\n {\n method: \"WS\",\n path: \"/v1/live/sessions\",\n headers: { \"x-test-id\": testId },\n body: { model: \"gpt-live-1\", messages: [] },\n response: {\n status: category ? 502 : 200,\n fixture: null,\n source: \"proxy\",\n ...strictOverrideField(defaults.strict, options.headers),\n ...(category ? { error: category } : {}),\n },\n },\n [...limits.secretValues, ...recordingSecrets],\n ),\n );\n }\n function cleanup() {\n if (state === \"closed\") return;\n state = \"closed\";\n deadline();\n startup();\n upstreamIdle?.();\n recordJournal(\"incomplete-capture\");\n recorder?.abort();\n upstream?.destroy();\n pending.length = 0;\n pendingBytes = 0;\n replay?.close();\n controller.abort();\n ws.removeListener(\"message\", receive);\n ws.removeListener(\"close\", cleanup);\n ws.removeListener(\"error\", cleanup);\n options.onDispose();\n }\n function dispose() {\n if (state === \"closed\") return;\n ws.close(1001, \"Live session disposed\");\n cleanup();\n }\n function fail(category: LiveFailureCategory, message: string) {\n if (state === \"closed\" || state === \"closing\") return;\n state = \"closing\";\n recorder?.abort(category);\n recordJournal(category);\n // The safe diagnostic contains no caller payload, fixture data or credential.\n ws.send(JSON.stringify({ type: \"aimock.error\", error: { category, message } }));\n ws.close(category === \"resource-limit\" ? 1009 : 1008, \"Live session failed\");\n cleanup();\n }\n function receive(data: string) {\n if (state === \"closed\" || state === \"closing\") return;\n let event: LiveObject;\n try {\n event = validateLiveClientEvent(JSON.parse(data), limits);\n } catch {\n fail(\"invalid-client\", \"Invalid Live client command\");\n return;\n }\n if (starting) {\n if (event.type === \"session.start\") {\n fail(\"invalid-client\", \"Live session has already started\");\n return;\n }\n const bytes = Buffer.byteLength(data);\n if (bytes > maxPendingBytes - pendingBytes || pending.length >= maxPendingFrames) {\n fail(\"resource-limit\", \"Live startup input queue exceeded\");\n return;\n }\n pendingBytes += bytes;\n pending.push(event);\n return;\n }\n accept(event);\n }\n function recordingError(error: unknown) {\n fail(\n error instanceof LiveRecorderError\n ? error.category\n : error instanceof Error && error.message === \"WebSocket write limit exceeded\"\n ? \"resource-limit\"\n : \"upstream-connect\",\n \"Live recording failed\",\n );\n }\n function touchUpstream() {\n upstreamIdle?.();\n upstreamIdle = scheduleLiveTimeout(\n () => fail(\"timeout\", \"Live upstream idle deadline exceeded\"),\n bodyTimeoutMs,\n );\n }\n function forward(event: LiveObject) {\n if (!upstream || !recorder || terminalReceived) return;\n try {\n const raw = JSON.stringify(event);\n const now = performance.now();\n captureStarted ??= now;\n recorder.acceptClient(raw, Math.floor(now - captureStarted));\n void upstream.sendAsync(raw, controller.signal).catch(recordingError);\n } catch (error) {\n recordingError(error);\n }\n }\n async function startRecording(first: LiveObject) {\n const record = defaults.record;\n if (!record?.providers.openai) {\n fail(\"fixture-mismatch\", \"No matching Live fixture\");\n return;\n }\n starting = true;\n try {\n const base = new URL(record.providers.openai);\n if (base.username || base.password || base.search || base.hash)\n throw new Error(\"Invalid URL\");\n base.pathname = base.pathname.replace(/\\/v1\\/?$/, \"/\");\n const target = resolveUpstreamUrl(base.href, \"/v1/live/sessions\");\n const headers: Record<string, string> = {};\n const incoming = options.headers.authorization;\n const credential = incoming?.match(/^\\s*(?:Bearer|Key)\\s+(.+)$/i)?.[1].trim();\n if (incoming && !options.localApiKeys?.includes(credential ?? \"\"))\n headers.Authorization = incoming;\n applyProviderAuth(headers, target, \"openai\", record.providerKeys?.openai);\n const selected = headers.Authorization?.replace(/^\\s*(?:Bearer|Key)\\s+/i, \"\").trim();\n recordingSecrets = [...(selected ? [selected] : []), ...(options.localApiKeys ?? [])];\n recorder = createLiveRecorder({\n record,\n fixtures,\n logger: defaults.logger,\n testId,\n live: limits,\n secretValues: recordingSecrets,\n provenance: {\n host: target.host,\n path: \"/v1/live/sessions\",\n observedAt: new Date().toISOString(),\n referenceVersion: \"gpt-live-1-observed-v3\",\n sanitationVersion: 1,\n },\n });\n upstream = await connectUpstreamWebSocket(\n target,\n headers,\n limits,\n controller.signal,\n upstreamTimeoutMs,\n );\n if (controller.signal.aborted) {\n upstream.destroy();\n return;\n }\n upstream.on(\"activity\", touchUpstream);\n upstream.on(\"message\", (raw: string) => {\n if (controller.signal.aborted) return;\n touchUpstream();\n try {\n // Capture at provider arrival, before downstream backpressure settles.\n recorder!.acceptServer(\n raw,\n captureStarted === undefined ? 0 : Math.floor(performance.now() - captureStarted),\n );\n const output: unknown = JSON.parse(raw);\n if (!output || typeof output !== \"object\" || !(\"type\" in output))\n throw new Error(\"Invalid event\");\n if (output.type === \"session.started\") {\n startup();\n starting = false;\n state = \"active\";\n const queued = pending.splice(0);\n pendingBytes = 0;\n for (const event of queued) {\n if (controller.signal.aborted) break;\n accept(event);\n }\n }\n const terminal = output.type === \"session.closed\";\n if (terminal) terminalReceived = true;\n void ws.sendAsync(raw, controller.signal).then(() => {\n if (!terminal || controller.signal.aborted) return;\n const result = recorder!.finish();\n if (result.kind === \"failed\") {\n fail(result.category, \"Live capture export failed\");\n return;\n }\n recordJournal();\n ws.close(1000, \"Live session complete\");\n cleanup();\n }, recordingError);\n } catch (error) {\n recordingError(error);\n }\n });\n upstream.on(\"error\", () => fail(\"upstream-connect\", \"Live upstream connection failed\"));\n upstream.on(\"close\", (code: number) => {\n if (!terminalReceived)\n fail(\n code === 1009 ? \"resource-limit\" : \"incomplete-capture\",\n \"Live upstream closed before completion\",\n );\n });\n touchUpstream();\n // Only startup is forwarded until the provider acknowledges the session.\n // The bounded queue retains commands that the caller pipelined meanwhile.\n forward(first);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"\";\n if (/HTTP 401/.test(message)) fail(\"upstream-auth\", \"Live upstream authentication refused\");\n else if (/HTTP (403|404|429)/.test(message))\n fail(\"upstream-access\", \"Live upstream access refused\");\n else recordingError(error);\n }\n }\n function startReplay(\n fixture: Fixture,\n request: ChatCompletionRequest,\n first: LiveObject,\n raw: FixtureResponse,\n ) {\n if (state === \"closed\" || state === \"closing\") return;\n try {\n if (!isLiveResponse(raw)) throw new Error(\"not a Live fixture\");\n const response = normalizeLiveFixture(raw, limits);\n replay = createLiveReplay(response.live, {\n limits,\n signal: controller.signal,\n timing: response.liveTiming ?? \"recorded\",\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\n fail,\n send: async (output) => {\n await ws.sendAsync(JSON.stringify(output), controller.signal);\n if (output.type === \"session.closed\" && !controller.signal.aborted) {\n ws.close(1000, \"Live session complete\");\n cleanup();\n }\n },\n });\n } catch {\n fail(\"protocol-divergence\", \"Invalid Live replay configuration\");\n return;\n }\n journal.add(\n liveJournalSnapshot(\n {\n method: \"WS\",\n path: \"/v1/live/sessions\",\n headers: { ...flattenHeaders(options.headers), \"x-test-id\": testId },\n body: request,\n response: { status: 200, fixture },\n },\n limits.secretValues,\n ),\n fixture,\n );\n startup();\n starting = false;\n state = \"active\";\n replay.accept(first);\n const queued = pending.splice(0);\n pendingBytes = 0;\n for (const event of queued) {\n if (controller.signal.aborted) break;\n accept(event);\n }\n }\n function accept(event: LiveObject) {\n if (state === \"closed\" || state === \"closing\") return;\n if (state === \"awaiting-start\") {\n const session = object(event.session);\n const audio = object(session?.audio);\n const format = object(audio?.format);\n const delegation = object(session?.delegation);\n if (\n event.type !== \"session.start\" ||\n session?.model !== \"gpt-live-1\" ||\n format?.type !== \"audio/pcm\" ||\n format.rate !== 24000 ||\n (delegation?.type !== \"client\" && delegation?.type !== \"responses\")\n ) {\n fail(\n \"invalid-client\",\n \"Live requires session.start with explicit model, PCM24 audio and delegation\",\n );\n return;\n }\n mode = delegation.type === \"client\" ? \"client\" : \"managed\";\n const request: ChatCompletionRequest = {\n model: session.model,\n messages: [],\n _endpointType: \"openai-live\",\n session: structuredClone(session),\n };\n let fixture: Fixture | null;\n let skippedBySequenceOrTurn: number;\n try {\n ({ fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n request,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n ));\n } catch {\n fail(\"protocol-divergence\", \"Live fixture selection failed\");\n return;\n }\n if (!fixture) {\n const strict = resolveStrictMode(defaults.strict, options.headers);\n if (strict) {\n const message = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(\n strictNoMatchLogLine(\"WS\", \"/v1/live/sessions\", skippedBySequenceOrTurn),\n );\n // A rejected startup is diagnostic only: retain test ownership, never\n // caller authorization or session content, and do not create a recorder.\n journal.add(\n liveJournalSnapshot(\n {\n method: \"WS\",\n path: \"/v1/live/sessions\",\n headers: { \"x-test-id\": testId },\n body: { model: request.model, messages: [] },\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, options.headers),\n },\n },\n limits.secretValues,\n ),\n );\n fail(\"fixture-mismatch\", message);\n } else if (wouldProxyMiss(strict, defaults.record, \"openai\")) {\n void startRecording(event);\n } else {\n fail(\"fixture-mismatch\", \"No matching Live fixture\");\n }\n return;\n }\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n if (typeof fixture.response === \"function\") {\n starting = true;\n const selected = fixture;\n void resolveResponse(selected, request).then(\n (response) => startReplay(selected, request, event, response),\n () => fail(\"protocol-divergence\", \"Live response factory failed\"),\n );\n } else startReplay(fixture, request, event, fixture.response);\n return;\n } else if (event.type === \"session.start\") {\n fail(\"invalid-client\", \"Live session has already started\");\n return;\n }\n if (event.type === \"session.update\") {\n const patch = object(event.session);\n const delegation = object(patch?.delegation);\n const backend = object(delegation?.responses);\n if (\n mode !== \"managed\" ||\n !patch ||\n !delegation ||\n !backend ||\n Object.keys(patch).some((key) => key !== \"delegation\") ||\n Object.keys(delegation).some((key) => key !== \"type\" && key !== \"responses\") ||\n (delegation.type !== undefined && delegation.type !== \"responses\") ||\n Object.keys(backend).some((key) => key !== \"instructions\") ||\n !Object.hasOwn(backend, \"instructions\")\n ) {\n fail(\"invalid-client\", \"Only managed backend instructions can be updated\");\n return;\n }\n }\n // Input acceptance is synchronous; output pressure never serializes duplex input.\n if (upstream) forward(event);\n else replay?.accept(event);\n }\n ws.on(\"message\", receive);\n ws.on(\"close\", cleanup);\n ws.on(\"error\", cleanup);\n return dispose;\n}\n"],"mappings":"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"}
|
package/dist/ws-realtime.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
2
|
const require_constants = require('./constants.cjs');
|
|
3
3
|
const require_helpers = require('./helpers.cjs');
|
|
4
|
-
require('./journal.cjs');
|
|
5
|
-
const require_router = require('./router.cjs');
|
|
6
4
|
const require_sse_writer = require('./sse-writer.cjs');
|
|
5
|
+
const require_router = require('./router.cjs');
|
|
6
|
+
require('./journal.cjs');
|
|
7
7
|
const require_interruption = require('./interruption.cjs');
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
|
|
@@ -301,10 +301,54 @@ function buildErrorRealtimeEvent(ws, message, isBeta, type = "invalid_request_er
|
|
|
301
301
|
}
|
|
302
302
|
}, isBeta);
|
|
303
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* OpenAI's Realtime *Beta* shape ("OpenAI-Beta: realtime=v1") no longer serves.
|
|
306
|
+
* Per aimock's deprecation policy (https://aimock.copilotkit.dev/deprecation-policy/)
|
|
307
|
+
* a removed upstream surface is mocked as REMOVED: a client that negotiates it
|
|
308
|
+
* gets the sunset rejection the real API returns, so the removal surfaces in the
|
|
309
|
+
* caller's tests instead of in production.
|
|
310
|
+
*
|
|
311
|
+
* Wire behaviour observed firsthand against wss://api.openai.com/v1/realtime on
|
|
312
|
+
* 2026-09-15 (authenticated, `OpenAI-Beta: realtime=v1`, GA control run in the
|
|
313
|
+
* same session still returned session.created):
|
|
314
|
+
*
|
|
315
|
+
* HTTP/1.1 101 Switching Protocols <- the upgrade SUCCEEDS
|
|
316
|
+
* TEXT {"type":"error","event_id":"event_…","error":{"type":"invalid_request_error",
|
|
317
|
+
* "code":"beta_api_shape_disabled","message":"The Realtime Beta API is no
|
|
318
|
+
* longer supported. Please use /v1/realtime for the GA API.","param":null,
|
|
319
|
+
* "event_id":null}}
|
|
320
|
+
* CLOSE code=4000 reason="invalid_request_error.beta_api_shape_disabled"
|
|
321
|
+
*
|
|
322
|
+
* No session.created is sent. Every value below is copied from that capture —
|
|
323
|
+
* none of it is inferred.
|
|
324
|
+
*/
|
|
325
|
+
const BETA_SUNSET_ERROR_CODE = "beta_api_shape_disabled";
|
|
326
|
+
const BETA_SUNSET_MESSAGE = "The Realtime Beta API is no longer supported. Please use /v1/realtime for the GA API.";
|
|
327
|
+
const BETA_SUNSET_CLOSE_CODE = 4e3;
|
|
328
|
+
const BETA_SUNSET_CLOSE_REASON = `invalid_request_error.${BETA_SUNSET_ERROR_CODE}`;
|
|
329
|
+
function rejectRetiredBetaShape(ws, logger) {
|
|
330
|
+
logger.warn(`WS /v1/realtime rejected the retired Realtime Beta shape (OpenAI-Beta: realtime=v1) — ${BETA_SUNSET_MESSAGE}`);
|
|
331
|
+
ws.send(JSON.stringify({
|
|
332
|
+
type: "error",
|
|
333
|
+
event_id: realtimeId("event"),
|
|
334
|
+
error: {
|
|
335
|
+
type: "invalid_request_error",
|
|
336
|
+
code: BETA_SUNSET_ERROR_CODE,
|
|
337
|
+
message: BETA_SUNSET_MESSAGE,
|
|
338
|
+
param: null,
|
|
339
|
+
event_id: null
|
|
340
|
+
}
|
|
341
|
+
}));
|
|
342
|
+
ws.close(BETA_SUNSET_CLOSE_CODE, BETA_SUNSET_CLOSE_REASON);
|
|
343
|
+
}
|
|
304
344
|
function handleWebSocketRealtime(ws, fixtures, journal, defaults) {
|
|
305
345
|
const { logger } = defaults;
|
|
306
346
|
const sessionId = realtimeId("sess");
|
|
307
347
|
const isBeta = defaults.upgradeHeaders?.["openai-beta"] ? String(defaults.upgradeHeaders["openai-beta"]).includes("realtime=v1") : false;
|
|
348
|
+
if (isBeta) {
|
|
349
|
+
rejectRetiredBetaShape(ws, logger);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
308
352
|
const session = {
|
|
309
353
|
model: defaults.model,
|
|
310
354
|
modalities: ["text"],
|