@copilotkit/aimock 1.46.0 → 1.47.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 +12 -1
- package/README.md +4 -1
- package/dist/aws-event-stream.cjs +20 -23
- package/dist/aws-event-stream.cjs.map +1 -1
- package/dist/aws-event-stream.d.cts +0 -1
- package/dist/aws-event-stream.d.ts +0 -1
- package/dist/aws-event-stream.js +20 -24
- package/dist/aws-event-stream.js.map +1 -1
- package/dist/bedrock-converse.cjs +79 -82
- 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 +80 -83
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +112 -121
- 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 +113 -122
- package/dist/bedrock.js.map +1 -1
- package/dist/cohere.cjs +58 -106
- 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 +60 -108
- 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/fixture-loader.cjs +103 -2
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +104 -4
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-interactions.cjs +63 -121
- 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 +65 -123
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +242 -155
- 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 +244 -157
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +400 -7
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +51 -1
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +51 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +391 -8
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +4 -2
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/mcp-fakes.d.cts.map +1 -1
- package/dist/messages.cjs +159 -141
- 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 +161 -143
- package/dist/messages.js.map +1 -1
- package/dist/misbehavior.cjs +62 -3
- package/dist/misbehavior.cjs.map +1 -1
- package/dist/misbehavior.d.cts.map +1 -1
- package/dist/misbehavior.d.ts.map +1 -1
- package/dist/misbehavior.js +63 -4
- package/dist/misbehavior.js.map +1 -1
- package/dist/ndjson-writer.cjs +21 -23
- package/dist/ndjson-writer.cjs.map +1 -1
- package/dist/ndjson-writer.d.cts +0 -1
- package/dist/ndjson-writer.d.cts.map +1 -1
- package/dist/ndjson-writer.d.ts +0 -1
- package/dist/ndjson-writer.d.ts.map +1 -1
- package/dist/ndjson-writer.js +21 -24
- package/dist/ndjson-writer.js.map +1 -1
- package/dist/ollama.cjs +153 -147
- 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 +155 -149
- package/dist/ollama.js.map +1 -1
- package/dist/openrouter-chat.cjs +1 -1
- package/dist/openrouter-chat.js +1 -1
- package/dist/raw-validation.cjs +177 -0
- package/dist/raw-validation.cjs.map +1 -0
- package/dist/raw-validation.js +176 -0
- package/dist/raw-validation.js.map +1 -0
- package/dist/response/deliver.cjs +144 -0
- package/dist/response/deliver.cjs.map +1 -0
- package/dist/response/deliver.js +143 -0
- package/dist/response/deliver.js.map +1 -0
- package/dist/response/frame-loop.cjs +102 -0
- package/dist/response/frame-loop.cjs.map +1 -0
- package/dist/response/frame-loop.js +100 -0
- package/dist/response/frame-loop.js.map +1 -0
- package/dist/response/lifecycle.cjs +223 -0
- package/dist/response/lifecycle.cjs.map +1 -0
- package/dist/response/lifecycle.js +211 -0
- package/dist/response/lifecycle.js.map +1 -0
- package/dist/response/proxy.cjs +71 -0
- package/dist/response/proxy.cjs.map +1 -0
- package/dist/response/proxy.js +70 -0
- package/dist/response/proxy.js.map +1 -0
- package/dist/response/raw.cjs +228 -0
- package/dist/response/raw.cjs.map +1 -0
- package/dist/response/raw.js +227 -0
- package/dist/response/raw.js.map +1 -0
- package/dist/response/timing.cjs +157 -0
- package/dist/response/timing.cjs.map +1 -0
- package/dist/response/timing.js +150 -0
- package/dist/response/timing.js.map +1 -0
- package/dist/responses.cjs +269 -148
- 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 +270 -149
- package/dist/responses.js.map +1 -1
- package/dist/router.cjs +20 -4
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts +7 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts +7 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +21 -5
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +89 -120
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +91 -122
- package/dist/server.js.map +1 -1
- package/dist/sse-writer.cjs +49 -30
- package/dist/sse-writer.cjs.map +1 -1
- package/dist/sse-writer.d.cts +0 -1
- package/dist/sse-writer.d.cts.map +1 -1
- package/dist/sse-writer.d.ts +0 -1
- package/dist/sse-writer.d.ts.map +1 -1
- package/dist/sse-writer.js +49 -31
- package/dist/sse-writer.js.map +1 -1
- package/dist/transcription.cjs +19 -33
- 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 +20 -34
- package/dist/transcription.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +173 -10
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +173 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/ws-framing.cjs +12 -6
- package/dist/ws-framing.cjs.map +1 -1
- package/dist/ws-framing.d.cts +2 -0
- package/dist/ws-framing.d.cts.map +1 -1
- package/dist/ws-framing.d.ts +2 -0
- package/dist/ws-framing.d.ts.map +1 -1
- package/dist/ws-framing.js +12 -6
- package/dist/ws-framing.js.map +1 -1
- package/dist/ws-gemini-live.cjs +120 -93
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.js +120 -93
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +80 -70
- 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 +80 -70
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +72 -53
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.d.cts.map +1 -1
- package/dist/ws-responses.d.ts.map +1 -1
- package/dist/ws-responses.js +73 -54
- package/dist/ws-responses.js.map +1 -1
- package/package.json +1 -1
- package/skills/write-fixtures/SKILL.md +34 -2
package/dist/ws-responses.cjs
CHANGED
|
@@ -2,11 +2,13 @@ const require_errors = require('./response/errors.cjs');
|
|
|
2
2
|
const require_constants = require('./constants.cjs');
|
|
3
3
|
const require_policy = require('./response/policy.cjs');
|
|
4
4
|
const require_helpers = require('./helpers.cjs');
|
|
5
|
-
const
|
|
5
|
+
const require_frame_loop = require('./response/frame-loop.cjs');
|
|
6
6
|
const require_misbehavior = require('./misbehavior.cjs');
|
|
7
7
|
const require_router = require('./router.cjs');
|
|
8
8
|
require('./journal.cjs');
|
|
9
|
-
const
|
|
9
|
+
const require_lifecycle = require('./response/lifecycle.cjs');
|
|
10
|
+
const require_timing = require('./response/timing.cjs');
|
|
11
|
+
const require_raw = require('./response/raw.cjs');
|
|
10
12
|
const require_responses = require('./responses.cjs');
|
|
11
13
|
|
|
12
14
|
//#region src/ws-responses.ts
|
|
@@ -25,6 +27,7 @@ function buildErrorEvent(message, type = "invalid_request_error", code) {
|
|
|
25
27
|
}
|
|
26
28
|
function handleWebSocketResponses(ws, fixtures, journal, defaults) {
|
|
27
29
|
const { logger } = defaults;
|
|
30
|
+
require_lifecycle.watchWsClose(ws);
|
|
28
31
|
let pending = Promise.resolve();
|
|
29
32
|
ws.on("message", (raw) => {
|
|
30
33
|
pending = pending.then(async () => {
|
|
@@ -80,7 +83,7 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
80
83
|
const contextHeader = defaults.upgradeHeaders?.["x-aimock-context"];
|
|
81
84
|
completionReq._context = typeof contextHeader === "string" ? contextHeader : Array.isArray(contextHeader) && contextHeader.length > 0 ? contextHeader[0] : void 0;
|
|
82
85
|
const testId = defaults.testId ?? require_constants.DEFAULT_TEST_ID;
|
|
83
|
-
const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
|
|
86
|
+
const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, { wire: "openai-responses" });
|
|
84
87
|
if (fixture) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
85
88
|
if (!fixture) {
|
|
86
89
|
if (require_policy.resolveRequestDecisions(defaults.strict, defaults.upgradeHeaders).fixtureMiss) {
|
|
@@ -158,8 +161,6 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
158
161
|
const latency = fixture.latency ?? defaults.latency;
|
|
159
162
|
const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
|
|
160
163
|
if (misbehavior.kind === "applied") {
|
|
161
|
-
const prepared = require_responses.prepareResponsesMisbehavior(misbehavior);
|
|
162
|
-
const output = require_responses.buildResponsesMisbehavior(prepared, completionReq, chunkSize, emitEncryptedReasoning);
|
|
163
164
|
const entry = journal.add({
|
|
164
165
|
method: "WS",
|
|
165
166
|
path: "/v1/responses",
|
|
@@ -170,28 +171,41 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
170
171
|
fixture
|
|
171
172
|
}
|
|
172
173
|
});
|
|
174
|
+
const prepared = require_responses.prepareResponsesMisbehavior(misbehavior);
|
|
175
|
+
const output = require_helpers.journalFixtureToolCallError(entry, () => require_responses.buildResponsesMisbehavior(prepared, completionReq, chunkSize, emitEncryptedReasoning));
|
|
173
176
|
require_misbehavior.recordMisbehaviorOutcome({
|
|
174
177
|
entry,
|
|
175
178
|
summary: prepared.summary,
|
|
176
179
|
defaults: misbehaviorDefaults,
|
|
177
180
|
testId
|
|
178
181
|
});
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
const interruption = require_interruption.createInterruptionSignal(fixture);
|
|
186
|
-
if (!await sendEvents(ws, output.events, latency, interruption?.signal, interruption?.tick, appliedTimings, fixture.replaySpeed ?? defaults.replaySpeed)) {
|
|
187
|
-
ws.destroy();
|
|
188
|
-
entry.response.interrupted = true;
|
|
189
|
-
entry.response.interruptReason = interruption?.reason();
|
|
190
|
-
}
|
|
191
|
-
interruption?.cleanup();
|
|
182
|
+
const appliedTimings = require_timing.extendRecordedTimings(fixture.recordedTimings, output.events.length, !!prepared.duplicateId);
|
|
183
|
+
await deliverTurn(ws, entry, fixture, output.events, {
|
|
184
|
+
latency,
|
|
185
|
+
recordedTimings: appliedTimings,
|
|
186
|
+
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
|
|
187
|
+
});
|
|
192
188
|
return;
|
|
193
189
|
}
|
|
194
190
|
const effectiveStrict = require_policy.resolveRequestDecisions(defaults.strict, defaults.upgradeHeaders).reasoningSuppression;
|
|
191
|
+
if (require_helpers.isRawResponse(response)) {
|
|
192
|
+
const message = "raw fixtures are not yet supported on the WebSocket transport";
|
|
193
|
+
const entry = journal.add({
|
|
194
|
+
method: "WS",
|
|
195
|
+
path: "/v1/responses",
|
|
196
|
+
headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
|
|
197
|
+
body: completionReq,
|
|
198
|
+
response: {
|
|
199
|
+
status: 500,
|
|
200
|
+
fixture
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
entry.response.error = message;
|
|
204
|
+
recordOutcome(entry);
|
|
205
|
+
defaults.logger.error(`WebSocket responses error: ${require_raw.RAW_MODE_UNAVAILABLE}: ${message}`);
|
|
206
|
+
ws.send(JSON.stringify(buildErrorEvent(message, "server_error", require_raw.RAW_MODE_UNAVAILABLE)));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
195
209
|
if (require_helpers.isErrorResponse(response)) {
|
|
196
210
|
const status = response.status ?? 500;
|
|
197
211
|
recordOutcome(journal.add({
|
|
@@ -229,13 +243,11 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
229
243
|
if (require_helpers.isFixtureToolCallError(error)) journalEntry.response.error = error.message;
|
|
230
244
|
throw error;
|
|
231
245
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
interruption?.cleanup();
|
|
246
|
+
await deliverTurn(ws, journalEntry, fixture, events, {
|
|
247
|
+
latency,
|
|
248
|
+
recordedTimings: fixture.recordedTimings,
|
|
249
|
+
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
|
|
250
|
+
});
|
|
239
251
|
return;
|
|
240
252
|
}
|
|
241
253
|
if (require_helpers.isTextResponse(response)) {
|
|
@@ -250,14 +262,11 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
250
262
|
}
|
|
251
263
|
});
|
|
252
264
|
recordOutcome(journalEntry);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
journalEntry.response.interruptReason = interruption?.reason();
|
|
259
|
-
}
|
|
260
|
-
interruption?.cleanup();
|
|
265
|
+
await deliverTurn(ws, journalEntry, fixture, require_helpers.journalFixtureToolCallError(journalEntry, () => require_responses.buildTextStreamEvents(response.content, completionReq.model, chunkSize, require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger), response.webSearches, require_helpers.extractOverrides(response), emitEncryptedReasoning, synthesizeSummarylessReasoning)), {
|
|
266
|
+
latency,
|
|
267
|
+
recordedTimings: fixture.recordedTimings,
|
|
268
|
+
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
|
|
269
|
+
});
|
|
261
270
|
return;
|
|
262
271
|
}
|
|
263
272
|
if (require_helpers.isToolCallResponse(response)) {
|
|
@@ -281,13 +290,11 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
281
290
|
if (require_helpers.isFixtureToolCallError(error)) journalEntry.response.error = error.message;
|
|
282
291
|
throw error;
|
|
283
292
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
interruption?.cleanup();
|
|
293
|
+
await deliverTurn(ws, journalEntry, fixture, events, {
|
|
294
|
+
latency,
|
|
295
|
+
recordedTimings: fixture.recordedTimings,
|
|
296
|
+
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
|
|
297
|
+
});
|
|
291
298
|
return;
|
|
292
299
|
}
|
|
293
300
|
recordOutcome(journal.add({
|
|
@@ -302,20 +309,32 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
302
309
|
}));
|
|
303
310
|
ws.send(JSON.stringify(buildErrorEvent("Fixture response did not match any known type", "server_error")));
|
|
304
311
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
312
|
+
/** WS Responses frames: one JSON text message per event, no head, no trailer. */
|
|
313
|
+
const wsEventCodec = {
|
|
314
|
+
headers: {},
|
|
315
|
+
encode: (event) => JSON.stringify(event),
|
|
316
|
+
checkGoneBeforeDelay: true
|
|
317
|
+
};
|
|
318
|
+
/**
|
|
319
|
+
* Send one turn's events under a WS lifecycle. The lifecycle owns the fixture
|
|
320
|
+
* interruption and also stops the turn when the client closes, so a pending
|
|
321
|
+
* delay ends at once. A stopped turn marks the entry by the shared WS rule
|
|
322
|
+
* ({@link markWsTurnStopped}) and destroys the socket.
|
|
323
|
+
* `streamingProfile` is not applied on this transport, as before.
|
|
324
|
+
*/
|
|
325
|
+
async function deliverTurn(ws, entry, fixture, events, timing) {
|
|
326
|
+
const lifecycle = require_lifecycle.wsLifecycle(ws, fixture);
|
|
327
|
+
try {
|
|
328
|
+
if (!await require_frame_loop.runFrameLoop(require_frame_loop.wsSink(ws), events, wsEventCodec, timing, {
|
|
329
|
+
lifecycle,
|
|
330
|
+
onChunkSent: lifecycle.tick
|
|
331
|
+
})) {
|
|
332
|
+
require_lifecycle.markWsTurnStopped(entry, ws, lifecycle);
|
|
333
|
+
ws.destroy();
|
|
334
|
+
}
|
|
335
|
+
} finally {
|
|
336
|
+
lifecycle.dispose();
|
|
317
337
|
}
|
|
318
|
-
return true;
|
|
319
338
|
}
|
|
320
339
|
|
|
321
340
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws-responses.cjs","names":["buildOpenAIError","isFixtureToolCallError","fixtureToolCallErrorCode","requestWantsEncryptedReasoning","requestIncludesEncryptedReasoning","responsesToCompletionRequest","DEFAULT_TEST_ID","matchFixtureDiagnostic","resolveRequestDecisions","strictNoMatchMessage","strictNoMatchLogLine","flattenHeaders","strictOverrideField","resolveResponse","withoutLegacyNamespaces","planMisbehavior","recordMisbehaviorOutcome","prepareResponsesMisbehavior","buildResponsesMisbehavior","createInterruptionSignal","isErrorResponse","isCombinedFixtureResponse","resolveServedBlockOutcome","contentWithToolCallsStreamEvents","servedToolCalls","resolveReasoningForModel","extractOverrides","isTextResponse","buildTextStreamEvents","isToolCallResponse","toolCallStreamEvents","calculateDelay","delay"],"sources":["../src/ws-responses.ts"],"sourcesContent":["/**\n * WebSocket handler for OpenAI Responses API.\n *\n * Accepts `{ type: \"response.create\", model: \"...\", input: [...] }` messages over\n * WebSocket and sends back the same Responses API SSE events as the HTTP\n * handler, but as individual WebSocket text frames.\n */\n\nimport type { Fixture, HandlerDefaults } from \"./types.js\";\nimport { planMisbehavior, recordMisbehaviorOutcome } from \"./misbehavior.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport {\n responsesToCompletionRequest,\n requestIncludesEncryptedReasoning,\n requestWantsEncryptedReasoning,\n buildTextStreamEvents,\n toolCallStreamEvents,\n contentWithToolCallsStreamEvents,\n prepareResponsesMisbehavior,\n buildResponsesMisbehavior,\n type ResponsesSSEEvent,\n type ResponsesInputItem,\n type ResponsesToolDef,\n} from \"./responses.js\";\nimport {\n fixtureToolCallErrorCode,\n isFixtureToolCallError,\n isTextResponse,\n isToolCallResponse,\n isCombinedFixtureResponse,\n isErrorResponse,\n extractOverrides,\n resolveResponse,\n resolveReasoningForModel,\n flattenHeaders,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n assertCustomToolCalls,\n resolveServedBlockOutcome,\n servedToolCalls,\n withoutLegacyNamespaces,\n} from \"./helpers.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport { delay, calculateDelay } from \"./sse-writer.js\";\nimport { DEFAULT_TEST_ID, type Journal } from \"./journal.js\";\nimport type { WebSocketConnection } from \"./ws-framing.js\";\nimport { resolveRequestDecisions, strictOverrideField } from \"./response/policy.js\";\nimport { buildOpenAIError } from \"./response/errors.js\";\n\ntype ResponsesWebSocketDefaults = Omit<HandlerDefaults, \"replaySpeed\"> & {\n replaySpeed?: number;\n model: string;\n testId?: string;\n upgradeHeaders?: import(\"node:http\").IncomingHttpHeaders;\n /** @internal Called before each message is processed (the server assigns fixture positions). */\n beforeProcessMessage?: () => void;\n};\n\ninterface ResponseCreateMessage {\n type: \"response.create\";\n model?: string;\n input?: unknown[];\n instructions?: string;\n tools?: unknown[];\n tool_choice?: string | object;\n stream?: boolean;\n temperature?: number;\n max_output_tokens?: number;\n [key: string]: unknown;\n}\n\nfunction isResponseCreateMessage(msg: unknown): msg is ResponseCreateMessage {\n return (\n typeof msg === \"object\" &&\n msg !== null &&\n (msg as ResponseCreateMessage).type === \"response.create\"\n );\n}\n\nfunction buildErrorEvent(\n message: string,\n type = \"invalid_request_error\",\n code?: string,\n): ResponsesSSEEvent {\n return {\n type: \"error\",\n error: buildOpenAIError({ message, type, code }),\n };\n}\n\nexport function handleWebSocketResponses(\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: ResponsesWebSocketDefaults,\n): void {\n const { logger } = defaults;\n // Serialize message processing to prevent event interleaving\n let pending = Promise.resolve();\n ws.on(\"message\", (raw: string) => {\n pending = pending.then(async () => {\n try {\n defaults.beforeProcessMessage?.();\n await processMessage(raw, ws, fixtures, journal, defaults);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : \"Internal error\";\n logger.error(`WebSocket responses error: ${msg}`);\n try {\n const code = isFixtureToolCallError(err) ? fixtureToolCallErrorCode(err) : undefined;\n ws.send(JSON.stringify(buildErrorEvent(msg, \"server_error\", code)));\n } catch (sendErr) {\n defaults.logger.debug(\n `Failed to send error to client: ${sendErr instanceof Error ? sendErr.message : \"unknown\"}`,\n );\n }\n }\n });\n });\n}\n\nasync function processMessage(\n raw: string,\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: ResponsesWebSocketDefaults,\n): Promise<void> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n ws.send(\n JSON.stringify(\n buildErrorEvent(`Malformed JSON: ${detail}`, \"invalid_request_error\", \"invalid_json\"),\n ),\n );\n return;\n }\n\n if (!isResponseCreateMessage(parsed)) {\n ws.send(\n JSON.stringify(\n buildErrorEvent(\n 'Expected message type \"response.create\"',\n \"invalid_request_error\",\n \"invalid_message_type\",\n ),\n ),\n );\n return;\n }\n\n const responsesReq = {\n model: parsed.model ?? defaults.model,\n // Same shapes as HTTP: namespace / custom tools, additional_tools items\n // and custom tool call items reach responsesToCompletionRequest unchanged.\n input: (parsed.input ?? []) as ResponsesInputItem[],\n instructions: parsed.instructions,\n tools: parsed.tools as ResponsesToolDef[] | undefined,\n tool_choice: parsed.tool_choice,\n stream: parsed.stream,\n temperature: parsed.temperature,\n max_output_tokens: parsed.max_output_tokens,\n include: (parsed as { include?: string[] }).include,\n store: (parsed as { store?: boolean }).store,\n };\n\n // Gate encrypted-reasoning emission identically to the HTTP transport so the\n // agent-framework#7233 stateless-replay path works over WebSocket too.\n const emitEncryptedReasoning = requestWantsEncryptedReasoning(responsesReq);\n // Synthesizing a reasoning item the fixture never declared takes the NARROWER\n // explicit-`include` gate, transport-identically to HTTP — a `store: false`\n // request with a summary-less fixture still gets no reasoning item.\n const synthesizeSummarylessReasoning = requestIncludesEncryptedReasoning(responsesReq);\n\n const extendedTools = defaults.responsesTools === \"extended\";\n const completionReq = responsesToCompletionRequest(responsesReq, {\n extended: extendedTools,\n logger: defaults.logger,\n });\n completionReq._endpointType = \"chat\";\n const contextHeader = defaults.upgradeHeaders?.[\"x-aimock-context\"];\n completionReq._context =\n typeof contextHeader === \"string\"\n ? contextHeader\n : Array.isArray(contextHeader) && contextHeader.length > 0\n ? contextHeader[0]\n : undefined;\n const testId = defaults.testId ?? DEFAULT_TEST_ID;\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (!fixture) {\n if (resolveRequestDecisions(defaults.strict, defaults.upgradeHeaders).fixtureMiss) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(\"WS\", \"/v1/responses\", skippedBySequenceOrTurn));\n journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, defaults.upgradeHeaders),\n },\n });\n ws.close(1008, strictMessage);\n return;\n }\n journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, defaults.upgradeHeaders),\n },\n });\n ws.send(\n JSON.stringify(\n buildErrorEvent(\"No fixture matched\", \"invalid_request_error\", \"no_fixture_match\"),\n ),\n );\n return;\n }\n\n // Without responsesTools \"extended\", a namespace on a toolCalls entry or a\n // toolCall block is not emitted, as in earlier releases.\n const resolvedResponse = await resolveResponse(fixture, completionReq);\n const response = extendedTools ? resolvedResponse : withoutLegacyNamespaces(resolvedResponse);\n const misbehaviorDefaults: HandlerDefaults = {\n ...defaults,\n replaySpeed: defaults.replaySpeed ?? 1,\n };\n const misbehavior = planMisbehavior({\n wire: \"openai-responses\",\n fixture,\n response,\n request: completionReq,\n // Responses WebSocket always emits streaming events, even with stream:false.\n stream: true,\n emitsToolCallIds: true,\n defaults: misbehaviorDefaults,\n // The server has already resolved the upgrade's header/query test ID.\n rawHeaders: { ...defaults.upgradeHeaders, \"x-test-id\": testId },\n url: \"/v1/responses\",\n });\n const recordOutcome = (entry: ReturnType<Journal[\"add\"]>) =>\n recordMisbehaviorOutcome({\n entry,\n summary: misbehavior.summary,\n defaults: misbehaviorDefaults,\n testId,\n });\n if (misbehavior.kind === \"error\") {\n const entry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: misbehavior.status, fixture },\n });\n recordOutcome(entry);\n if (!misbehavior.summary?.evaluations.length) {\n defaults.logger.error(\n `[misbehavior] ${misbehavior.code}: ${misbehavior.message} (testId=${testId})`,\n );\n }\n ws.send(\n JSON.stringify(\n buildErrorEvent(misbehavior.message, \"invalid_request_error\", misbehavior.code),\n ),\n );\n return;\n }\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n if (misbehavior.kind === \"applied\") {\n const prepared = prepareResponsesMisbehavior(misbehavior);\n const output = buildResponsesMisbehavior(\n prepared,\n completionReq,\n chunkSize,\n emitEncryptedReasoning,\n );\n const entry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n recordMisbehaviorOutcome({\n entry,\n summary: prepared.summary,\n defaults: misbehaviorDefaults,\n testId,\n });\n const timings = fixture.recordedTimings;\n const gaps = timings?.interChunkDelaysMs;\n // K4 can add events; extend only its replay using the last recorded gap.\n const appliedTimings =\n prepared.duplicateId && timings && gaps\n ? {\n ...timings,\n interChunkDelaysMs: Array.from(\n { length: Math.max(gaps.length, output.events.length - 1) },\n (_, index) => gaps[index] ?? gaps.at(-1) ?? 0,\n ),\n }\n : timings;\n const interruption = createInterruptionSignal(fixture);\n const completed = await sendEvents(\n ws,\n output.events,\n latency,\n interruption?.signal,\n interruption?.tick,\n appliedTimings,\n fixture.replaySpeed ?? defaults.replaySpeed,\n );\n if (!completed) {\n ws.destroy();\n entry.response.interrupted = true;\n entry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n return;\n }\n\n // The WS path has no per-request `req.headers`; strict is resolved from the\n // connection's upgrade headers (see the `!fixture` branch above). Used below\n // to gate the synthesized reasoning channel on the requested model's capability.\n const effectiveStrict = resolveRequestDecisions(\n defaults.strict,\n defaults.upgradeHeaders,\n ).reasoningSuppression;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n const entry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status, fixture },\n });\n recordOutcome(entry);\n ws.send(\n JSON.stringify(\n buildErrorEvent(response.error.message, response.error.type, response.error.code),\n ),\n );\n return;\n }\n\n // Content + tool calls response (must be checked before isTextResponse / isToolCallResponse)\n if (isCombinedFixtureResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n recordOutcome(journalEntry);\n\n let events: ResponsesSSEEvent[];\n try {\n const ordered = resolveServedBlockOutcome(response)?.ordered;\n if (!ordered) assertCustomToolCalls(response);\n events = contentWithToolCallsStreamEvents(\n response.content ?? \"\",\n servedToolCalls(response),\n completionReq.model,\n chunkSize,\n resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n defaults.logger,\n ),\n response.webSearches,\n extractOverrides(response),\n ordered,\n emitEncryptedReasoning,\n synthesizeSummarylessReasoning,\n );\n } catch (error) {\n journalEntry.response.status = 500;\n if (isFixtureToolCallError(error)) journalEntry.response.error = error.message;\n throw error;\n }\n\n const interruption = createInterruptionSignal(fixture);\n const completed = await sendEvents(\n ws,\n events,\n latency,\n interruption?.signal,\n interruption?.tick,\n fixture.recordedTimings,\n fixture.replaySpeed ?? defaults.replaySpeed,\n );\n if (!completed) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n recordOutcome(journalEntry);\n\n const events = buildTextStreamEvents(\n response.content,\n completionReq.model,\n chunkSize,\n resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n defaults.logger,\n ),\n response.webSearches,\n extractOverrides(response),\n emitEncryptedReasoning,\n synthesizeSummarylessReasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await sendEvents(\n ws,\n events,\n latency,\n interruption?.signal,\n interruption?.tick,\n fixture.recordedTimings,\n fixture.replaySpeed ?? defaults.replaySpeed,\n );\n if (!completed) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n recordOutcome(journalEntry);\n let events: ResponsesSSEEvent[];\n try {\n assertCustomToolCalls(response);\n events = toolCallStreamEvents(\n servedToolCalls(response),\n completionReq.model,\n chunkSize,\n // Gate the synthesized reasoning channel on the requested model's\n // capability, matching the WS text / content+tool branches and the HTTP\n // tool-only path so reasoning emission is transport-independent.\n resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n defaults.logger,\n ),\n response.webSearches,\n extractOverrides(response),\n emitEncryptedReasoning,\n synthesizeSummarylessReasoning,\n );\n } catch (error) {\n journalEntry.response.status = 500;\n if (isFixtureToolCallError(error)) journalEntry.response.error = error.message;\n throw error;\n }\n const interruption = createInterruptionSignal(fixture);\n const completed = await sendEvents(\n ws,\n events,\n latency,\n interruption?.signal,\n interruption?.tick,\n fixture.recordedTimings,\n fixture.replaySpeed ?? defaults.replaySpeed,\n );\n if (!completed) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n return;\n }\n\n // Unknown response type\n const entry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 500, fixture },\n });\n recordOutcome(entry);\n ws.send(\n JSON.stringify(\n buildErrorEvent(\"Fixture response did not match any known type\", \"server_error\"),\n ),\n );\n}\n\nasync function sendEvents(\n ws: WebSocketConnection,\n events: ResponsesSSEEvent[],\n latency: number,\n signal?: AbortSignal,\n onChunkSent?: () => void,\n recordedTimings?: import(\"./types.js\").RecordedTimings,\n replaySpeed?: number,\n): Promise<boolean> {\n let eventIndex = 0;\n for (const event of events) {\n if (ws.isClosed) return false;\n const chunkDelay = calculateDelay(eventIndex, undefined, latency, recordedTimings, replaySpeed);\n if (chunkDelay > 0) await delay(chunkDelay, signal);\n if (signal?.aborted) return false;\n if (ws.isClosed) return false;\n ws.send(JSON.stringify(event));\n eventIndex++;\n onChunkSent?.();\n if (signal?.aborted) return false;\n }\n return true;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ws-responses.cjs","names":["buildOpenAIError","isFixtureToolCallError","fixtureToolCallErrorCode","requestWantsEncryptedReasoning","requestIncludesEncryptedReasoning","responsesToCompletionRequest","DEFAULT_TEST_ID","matchFixtureDiagnostic","resolveRequestDecisions","strictNoMatchMessage","strictNoMatchLogLine","flattenHeaders","strictOverrideField","resolveResponse","withoutLegacyNamespaces","planMisbehavior","recordMisbehaviorOutcome","prepareResponsesMisbehavior","journalFixtureToolCallError","buildResponsesMisbehavior","extendRecordedTimings","isRawResponse","RAW_MODE_UNAVAILABLE","isErrorResponse","isCombinedFixtureResponse","resolveServedBlockOutcome","contentWithToolCallsStreamEvents","servedToolCalls","resolveReasoningForModel","extractOverrides","isTextResponse","buildTextStreamEvents","isToolCallResponse","toolCallStreamEvents","wsLifecycle","runFrameLoop","wsSink"],"sources":["../src/ws-responses.ts"],"sourcesContent":["/**\n * WebSocket handler for OpenAI Responses API.\n *\n * Accepts `{ type: \"response.create\", model: \"...\", input: [...] }` messages over\n * WebSocket and sends back the same Responses API SSE events as the HTTP\n * handler, but as individual WebSocket text frames.\n */\n\nimport type { Fixture, HandlerDefaults, JournalEntry, RecordedTimings } from \"./types.js\";\nimport { planMisbehavior, recordMisbehaviorOutcome } from \"./misbehavior.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport {\n responsesToCompletionRequest,\n requestIncludesEncryptedReasoning,\n requestWantsEncryptedReasoning,\n buildTextStreamEvents,\n toolCallStreamEvents,\n contentWithToolCallsStreamEvents,\n prepareResponsesMisbehavior,\n buildResponsesMisbehavior,\n type ResponsesSSEEvent,\n type ResponsesInputItem,\n type ResponsesToolDef,\n} from \"./responses.js\";\nimport {\n fixtureToolCallErrorCode,\n isFixtureToolCallError,\n journalFixtureToolCallError,\n isTextResponse,\n isToolCallResponse,\n isCombinedFixtureResponse,\n isErrorResponse,\n isRawResponse,\n extractOverrides,\n resolveResponse,\n resolveReasoningForModel,\n flattenHeaders,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n assertCustomToolCalls,\n resolveServedBlockOutcome,\n servedToolCalls,\n withoutLegacyNamespaces,\n} from \"./helpers.js\";\nimport { DEFAULT_TEST_ID, type Journal } from \"./journal.js\";\nimport type { WebSocketConnection } from \"./ws-framing.js\";\nimport { resolveRequestDecisions, strictOverrideField } from \"./response/policy.js\";\nimport { buildOpenAIError } from \"./response/errors.js\";\nimport { markWsTurnStopped, watchWsClose, wsLifecycle } from \"./response/lifecycle.js\";\nimport { runFrameLoop, wsSink, type FrameCodec } from \"./response/frame-loop.js\";\nimport { extendRecordedTimings } from \"./response/timing.js\";\nimport { RAW_MODE_UNAVAILABLE } from \"./response/raw.js\";\n\ntype ResponsesWebSocketDefaults = Omit<HandlerDefaults, \"replaySpeed\"> & {\n replaySpeed?: number;\n model: string;\n testId?: string;\n upgradeHeaders?: import(\"node:http\").IncomingHttpHeaders;\n /** @internal Called before each message is processed (the server assigns fixture positions). */\n beforeProcessMessage?: () => void;\n};\n\ninterface ResponseCreateMessage {\n type: \"response.create\";\n model?: string;\n input?: unknown[];\n instructions?: string;\n tools?: unknown[];\n tool_choice?: string | object;\n stream?: boolean;\n temperature?: number;\n max_output_tokens?: number;\n [key: string]: unknown;\n}\n\nfunction isResponseCreateMessage(msg: unknown): msg is ResponseCreateMessage {\n return (\n typeof msg === \"object\" &&\n msg !== null &&\n (msg as ResponseCreateMessage).type === \"response.create\"\n );\n}\n\nfunction buildErrorEvent(\n message: string,\n type = \"invalid_request_error\",\n code?: string,\n): ResponsesSSEEvent {\n return {\n type: \"error\",\n error: buildOpenAIError({ message, type, code }),\n };\n}\n\nexport function handleWebSocketResponses(\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: ResponsesWebSocketDefaults,\n): void {\n const { logger } = defaults;\n watchWsClose(ws);\n // Serialize message processing to prevent event interleaving\n let pending = Promise.resolve();\n ws.on(\"message\", (raw: string) => {\n pending = pending.then(async () => {\n try {\n defaults.beforeProcessMessage?.();\n await processMessage(raw, ws, fixtures, journal, defaults);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : \"Internal error\";\n logger.error(`WebSocket responses error: ${msg}`);\n try {\n const code = isFixtureToolCallError(err) ? fixtureToolCallErrorCode(err) : undefined;\n ws.send(JSON.stringify(buildErrorEvent(msg, \"server_error\", code)));\n } catch (sendErr) {\n defaults.logger.debug(\n `Failed to send error to client: ${sendErr instanceof Error ? sendErr.message : \"unknown\"}`,\n );\n }\n }\n });\n });\n}\n\nasync function processMessage(\n raw: string,\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: ResponsesWebSocketDefaults,\n): Promise<void> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n ws.send(\n JSON.stringify(\n buildErrorEvent(`Malformed JSON: ${detail}`, \"invalid_request_error\", \"invalid_json\"),\n ),\n );\n return;\n }\n\n if (!isResponseCreateMessage(parsed)) {\n ws.send(\n JSON.stringify(\n buildErrorEvent(\n 'Expected message type \"response.create\"',\n \"invalid_request_error\",\n \"invalid_message_type\",\n ),\n ),\n );\n return;\n }\n\n const responsesReq = {\n model: parsed.model ?? defaults.model,\n // Same shapes as HTTP: namespace / custom tools, additional_tools items\n // and custom tool call items reach responsesToCompletionRequest unchanged.\n input: (parsed.input ?? []) as ResponsesInputItem[],\n instructions: parsed.instructions,\n tools: parsed.tools as ResponsesToolDef[] | undefined,\n tool_choice: parsed.tool_choice,\n stream: parsed.stream,\n temperature: parsed.temperature,\n max_output_tokens: parsed.max_output_tokens,\n include: (parsed as { include?: string[] }).include,\n store: (parsed as { store?: boolean }).store,\n };\n\n // Gate encrypted-reasoning emission identically to the HTTP transport so the\n // agent-framework#7233 stateless-replay path works over WebSocket too.\n const emitEncryptedReasoning = requestWantsEncryptedReasoning(responsesReq);\n // Synthesizing a reasoning item the fixture never declared takes the NARROWER\n // explicit-`include` gate, transport-identically to HTTP — a `store: false`\n // request with a summary-less fixture still gets no reasoning item.\n const synthesizeSummarylessReasoning = requestIncludesEncryptedReasoning(responsesReq);\n\n const extendedTools = defaults.responsesTools === \"extended\";\n const completionReq = responsesToCompletionRequest(responsesReq, {\n extended: extendedTools,\n logger: defaults.logger,\n });\n completionReq._endpointType = \"chat\";\n const contextHeader = defaults.upgradeHeaders?.[\"x-aimock-context\"];\n completionReq._context =\n typeof contextHeader === \"string\"\n ? contextHeader\n : Array.isArray(contextHeader) && contextHeader.length > 0\n ? contextHeader[0]\n : undefined;\n const testId = defaults.testId ?? DEFAULT_TEST_ID;\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n // Wire only: recordMatchOptions here would turn on strict turnIndex and the\n // relaxed-turnIndex warning, which this transport has never had.\n { wire: \"openai-responses\" },\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (!fixture) {\n if (resolveRequestDecisions(defaults.strict, defaults.upgradeHeaders).fixtureMiss) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(\"WS\", \"/v1/responses\", skippedBySequenceOrTurn));\n journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, defaults.upgradeHeaders),\n },\n });\n ws.close(1008, strictMessage);\n return;\n }\n journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, defaults.upgradeHeaders),\n },\n });\n ws.send(\n JSON.stringify(\n buildErrorEvent(\"No fixture matched\", \"invalid_request_error\", \"no_fixture_match\"),\n ),\n );\n return;\n }\n\n // Without responsesTools \"extended\", a namespace on a toolCalls entry or a\n // toolCall block is not emitted, as in earlier releases.\n const resolvedResponse = await resolveResponse(fixture, completionReq);\n const response = extendedTools ? resolvedResponse : withoutLegacyNamespaces(resolvedResponse);\n const misbehaviorDefaults: HandlerDefaults = {\n ...defaults,\n replaySpeed: defaults.replaySpeed ?? 1,\n };\n const misbehavior = planMisbehavior({\n wire: \"openai-responses\",\n fixture,\n response,\n request: completionReq,\n // Responses WebSocket always emits streaming events, even with stream:false.\n stream: true,\n emitsToolCallIds: true,\n defaults: misbehaviorDefaults,\n // The server has already resolved the upgrade's header/query test ID.\n rawHeaders: { ...defaults.upgradeHeaders, \"x-test-id\": testId },\n url: \"/v1/responses\",\n });\n const recordOutcome = (entry: ReturnType<Journal[\"add\"]>) =>\n recordMisbehaviorOutcome({\n entry,\n summary: misbehavior.summary,\n defaults: misbehaviorDefaults,\n testId,\n });\n if (misbehavior.kind === \"error\") {\n const entry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: misbehavior.status, fixture },\n });\n recordOutcome(entry);\n if (!misbehavior.summary?.evaluations.length) {\n defaults.logger.error(\n `[misbehavior] ${misbehavior.code}: ${misbehavior.message} (testId=${testId})`,\n );\n }\n ws.send(\n JSON.stringify(\n buildErrorEvent(misbehavior.message, \"invalid_request_error\", misbehavior.code),\n ),\n );\n return;\n }\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n if (misbehavior.kind === \"applied\") {\n // Journal first, as HTTP /v1/responses does, so a fixture error thrown\n // while building leaves a 500 entry that keeps the fixture. Record the\n // fault only once the reply is built: a thrown error was not served.\n const entry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n const prepared = prepareResponsesMisbehavior(misbehavior);\n const output = journalFixtureToolCallError(entry, () =>\n buildResponsesMisbehavior(prepared, completionReq, chunkSize, emitEncryptedReasoning),\n );\n recordMisbehaviorOutcome({\n entry,\n summary: prepared.summary,\n defaults: misbehaviorDefaults,\n testId,\n });\n // K4 can add events; extend only its replay using the last recorded gap.\n const appliedTimings = extendRecordedTimings(\n fixture.recordedTimings,\n output.events.length,\n !!prepared.duplicateId,\n );\n await deliverTurn(ws, entry, fixture, output.events, {\n latency,\n recordedTimings: appliedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\n });\n return;\n }\n\n // The WS path has no per-request `req.headers`; strict is resolved from the\n // connection's upgrade headers (see the `!fixture` branch above). Used below\n // to gate the synthesized reasoning channel on the requested model's capability.\n const effectiveStrict = resolveRequestDecisions(\n defaults.strict,\n defaults.upgradeHeaders,\n ).reasoningSuppression;\n\n // Raw passthrough fixtures are served over HTTP only for now. Fail loudly\n // instead of the generic unknown-type error so the author sees why.\n if (isRawResponse(response)) {\n const message = \"raw fixtures are not yet supported on the WebSocket transport\";\n const entry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 500, fixture },\n });\n entry.response.error = message;\n recordOutcome(entry);\n defaults.logger.error(`WebSocket responses error: ${RAW_MODE_UNAVAILABLE}: ${message}`);\n ws.send(JSON.stringify(buildErrorEvent(message, \"server_error\", RAW_MODE_UNAVAILABLE)));\n return;\n }\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n const entry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status, fixture },\n });\n recordOutcome(entry);\n ws.send(\n JSON.stringify(\n buildErrorEvent(response.error.message, response.error.type, response.error.code),\n ),\n );\n return;\n }\n\n // Content + tool calls response (must be checked before isTextResponse / isToolCallResponse)\n if (isCombinedFixtureResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n recordOutcome(journalEntry);\n\n let events: ResponsesSSEEvent[];\n try {\n const ordered = resolveServedBlockOutcome(response)?.ordered;\n if (!ordered) assertCustomToolCalls(response);\n events = contentWithToolCallsStreamEvents(\n response.content ?? \"\",\n servedToolCalls(response),\n completionReq.model,\n chunkSize,\n resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n defaults.logger,\n ),\n response.webSearches,\n extractOverrides(response),\n ordered,\n emitEncryptedReasoning,\n synthesizeSummarylessReasoning,\n );\n } catch (error) {\n journalEntry.response.status = 500;\n if (isFixtureToolCallError(error)) journalEntry.response.error = error.message;\n throw error;\n }\n\n await deliverTurn(ws, journalEntry, fixture, events, {\n latency,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\n });\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n recordOutcome(journalEntry);\n\n // A fixture error (malformed citations, a non-image file) marks the entry\n // a 500 carrying the message before the error event goes out.\n const events = journalFixtureToolCallError(journalEntry, () =>\n buildTextStreamEvents(\n response.content,\n completionReq.model,\n chunkSize,\n resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n defaults.logger,\n ),\n response.webSearches,\n extractOverrides(response),\n emitEncryptedReasoning,\n synthesizeSummarylessReasoning,\n ),\n );\n await deliverTurn(ws, journalEntry, fixture, events, {\n latency,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\n });\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n recordOutcome(journalEntry);\n let events: ResponsesSSEEvent[];\n try {\n assertCustomToolCalls(response);\n events = toolCallStreamEvents(\n servedToolCalls(response),\n completionReq.model,\n chunkSize,\n // Gate the synthesized reasoning channel on the requested model's\n // capability, matching the WS text / content+tool branches and the HTTP\n // tool-only path so reasoning emission is transport-independent.\n resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n defaults.logger,\n ),\n response.webSearches,\n extractOverrides(response),\n emitEncryptedReasoning,\n synthesizeSummarylessReasoning,\n );\n } catch (error) {\n journalEntry.response.status = 500;\n if (isFixtureToolCallError(error)) journalEntry.response.error = error.message;\n throw error;\n }\n await deliverTurn(ws, journalEntry, fixture, events, {\n latency,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\n });\n return;\n }\n\n // Unknown response type\n const entry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 500, fixture },\n });\n recordOutcome(entry);\n ws.send(\n JSON.stringify(\n buildErrorEvent(\"Fixture response did not match any known type\", \"server_error\"),\n ),\n );\n}\n\n/** WS Responses frames: one JSON text message per event, no head, no trailer. */\nconst wsEventCodec: FrameCodec<ResponsesSSEEvent> = {\n headers: {},\n encode: (event) => JSON.stringify(event),\n checkGoneBeforeDelay: true,\n};\n\n/**\n * Send one turn's events under a WS lifecycle. The lifecycle owns the fixture\n * interruption and also stops the turn when the client closes, so a pending\n * delay ends at once. A stopped turn marks the entry by the shared WS rule\n * ({@link markWsTurnStopped}) and destroys the socket.\n * `streamingProfile` is not applied on this transport, as before.\n */\nasync function deliverTurn(\n ws: WebSocketConnection,\n entry: JournalEntry,\n fixture: Fixture,\n events: ResponsesSSEEvent[],\n timing: { latency: number; recordedTimings?: RecordedTimings; replaySpeed?: number },\n): Promise<void> {\n const lifecycle = wsLifecycle(ws, fixture);\n try {\n const completed = await runFrameLoop(wsSink(ws), events, wsEventCodec, timing, {\n lifecycle,\n onChunkSent: lifecycle.tick,\n });\n if (!completed) {\n markWsTurnStopped(entry, ws, lifecycle);\n ws.destroy();\n }\n } finally {\n lifecycle.dispose();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AA2EA,SAAS,wBAAwB,KAA4C;AAC3E,QACE,OAAO,QAAQ,YACf,QAAQ,QACP,IAA8B,SAAS;;AAI5C,SAAS,gBACP,SACA,OAAO,yBACP,MACmB;AACnB,QAAO;EACL,MAAM;EACN,OAAOA,gCAAiB;GAAE;GAAS;GAAM;GAAM,CAAC;EACjD;;AAGH,SAAgB,yBACd,IACA,UACA,SACA,UACM;CACN,MAAM,EAAE,WAAW;AACnB,gCAAa,GAAG;CAEhB,IAAI,UAAU,QAAQ,SAAS;AAC/B,IAAG,GAAG,YAAY,QAAgB;AAChC,YAAU,QAAQ,KAAK,YAAY;AACjC,OAAI;AACF,aAAS,wBAAwB;AACjC,UAAM,eAAe,KAAK,IAAI,UAAU,SAAS,SAAS;YACnD,KAAc;IACrB,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU;AACjD,WAAO,MAAM,8BAA8B,MAAM;AACjD,QAAI;KACF,MAAM,OAAOC,uCAAuB,IAAI,GAAGC,yCAAyB,IAAI,GAAG;AAC3E,QAAG,KAAK,KAAK,UAAU,gBAAgB,KAAK,gBAAgB,KAAK,CAAC,CAAC;aAC5D,SAAS;AAChB,cAAS,OAAO,MACd,mCAAmC,mBAAmB,QAAQ,QAAQ,UAAU,YACjF;;;IAGL;GACF;;AAGJ,eAAe,eACb,KACA,IACA,UACA,SACA,UACe;CACf,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,IAAI;UACjB,UAAU;EACjB,MAAM,SAAS,oBAAoB,QAAQ,SAAS,UAAU;AAC9D,KAAG,KACD,KAAK,UACH,gBAAgB,mBAAmB,UAAU,yBAAyB,eAAe,CACtF,CACF;AACD;;AAGF,KAAI,CAAC,wBAAwB,OAAO,EAAE;AACpC,KAAG,KACD,KAAK,UACH,gBACE,6CACA,yBACA,uBACD,CACF,CACF;AACD;;CAGF,MAAM,eAAe;EACnB,OAAO,OAAO,SAAS,SAAS;EAGhC,OAAQ,OAAO,SAAS,EAAE;EAC1B,cAAc,OAAO;EACrB,OAAO,OAAO;EACd,aAAa,OAAO;EACpB,QAAQ,OAAO;EACf,aAAa,OAAO;EACpB,mBAAmB,OAAO;EAC1B,SAAU,OAAkC;EAC5C,OAAQ,OAA+B;EACxC;CAID,MAAM,yBAAyBC,iDAA+B,aAAa;CAI3E,MAAM,iCAAiCC,oDAAkC,aAAa;CAEtF,MAAM,gBAAgB,SAAS,mBAAmB;CAClD,MAAM,gBAAgBC,+CAA6B,cAAc;EAC/D,UAAU;EACV,QAAQ,SAAS;EAClB,CAAC;AACF,eAAc,gBAAgB;CAC9B,MAAM,gBAAgB,SAAS,iBAAiB;AAChD,eAAc,WACZ,OAAO,kBAAkB,WACrB,gBACA,MAAM,QAAQ,cAAc,IAAI,cAAc,SAAS,IACrD,cAAc,KACd;CACR,MAAM,SAAS,SAAS,UAAUC;CAClC,MAAM,EAAE,SAAS,4BAA4BC,sCAC3C,UACA,eACA,QAAQ,6BAA6B,OAAO,EAC5C,SAAS,kBAGT,EAAE,MAAM,oBAAoB,CAC7B;AAED,KAAI,QACF,SAAQ,2BAA2B,SAAS,UAAU,OAAO;AAG/D,KAAI,CAAC,SAAS;AACZ,MAAIC,uCAAwB,SAAS,QAAQ,SAAS,eAAe,CAAC,aAAa;GACjF,MAAM,gBAAgBC,qCAAqB,wBAAwB;AACnE,YAAS,OAAO,MAAMC,qCAAqB,MAAM,iBAAiB,wBAAwB,CAAC;AAC3F,WAAQ,IAAI;IACV,QAAQ;IACR,MAAM;IACN,SAASC,+BAAe,SAAS,kBAAkB,EAAE,CAAC;IACtD,MAAM;IACN,UAAU;KACR,QAAQ;KACR,SAAS;KACT,GAAGC,mCAAoB,SAAS,QAAQ,SAAS,eAAe;KACjE;IACF,CAAC;AACF,MAAG,MAAM,MAAM,cAAc;AAC7B;;AAEF,UAAQ,IAAI;GACV,QAAQ;GACR,MAAM;GACN,SAASD,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IACR,QAAQ;IACR,SAAS;IACT,GAAGC,mCAAoB,SAAS,QAAQ,SAAS,eAAe;IACjE;GACF,CAAC;AACF,KAAG,KACD,KAAK,UACH,gBAAgB,sBAAsB,yBAAyB,mBAAmB,CACnF,CACF;AACD;;CAKF,MAAM,mBAAmB,MAAMC,gCAAgB,SAAS,cAAc;CACtE,MAAM,WAAW,gBAAgB,mBAAmBC,wCAAwB,iBAAiB;CAC7F,MAAM,sBAAuC;EAC3C,GAAG;EACH,aAAa,SAAS,eAAe;EACtC;CACD,MAAM,cAAcC,oCAAgB;EAClC,MAAM;EACN;EACA;EACA,SAAS;EAET,QAAQ;EACR,kBAAkB;EAClB,UAAU;EAEV,YAAY;GAAE,GAAG,SAAS;GAAgB,aAAa;GAAQ;EAC/D,KAAK;EACN,CAAC;CACF,MAAM,iBAAiB,UACrBC,6CAAyB;EACvB;EACA,SAAS,YAAY;EACrB,UAAU;EACV;EACD,CAAC;AACJ,KAAI,YAAY,SAAS,SAAS;AAQhC,gBAPc,QAAQ,IAAI;GACxB,QAAQ;GACR,MAAM;GACN,SAASL,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE,QAAQ,YAAY;IAAQ;IAAS;GAClD,CAAC,CACkB;AACpB,MAAI,CAAC,YAAY,SAAS,YAAY,OACpC,UAAS,OAAO,MACd,iBAAiB,YAAY,KAAK,IAAI,YAAY,QAAQ,WAAW,OAAO,GAC7E;AAEH,KAAG,KACD,KAAK,UACH,gBAAgB,YAAY,SAAS,yBAAyB,YAAY,KAAK,CAChF,CACF;AACD;;CAEF,MAAM,UAAU,QAAQ,WAAW,SAAS;CAC5C,MAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,aAAa,SAAS,UAAU;AAEtE,KAAI,YAAY,SAAS,WAAW;EAIlC,MAAM,QAAQ,QAAQ,IAAI;GACxB,QAAQ;GACR,MAAM;GACN,SAASA,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;EACF,MAAM,WAAWM,8CAA4B,YAAY;EACzD,MAAM,SAASC,4CAA4B,aACzCC,4CAA0B,UAAU,eAAe,WAAW,uBAAuB,CACtF;AACD,+CAAyB;GACvB;GACA,SAAS,SAAS;GAClB,UAAU;GACV;GACD,CAAC;EAEF,MAAM,iBAAiBC,qCACrB,QAAQ,iBACR,OAAO,OAAO,QACd,CAAC,CAAC,SAAS,YACZ;AACD,QAAM,YAAY,IAAI,OAAO,SAAS,OAAO,QAAQ;GACnD;GACA,iBAAiB;GACjB,aAAa,QAAQ,eAAe,SAAS;GAC9C,CAAC;AACF;;CAMF,MAAM,kBAAkBZ,uCACtB,SAAS,QACT,SAAS,eACV,CAAC;AAIF,KAAIa,8BAAc,SAAS,EAAE;EAC3B,MAAM,UAAU;EAChB,MAAM,QAAQ,QAAQ,IAAI;GACxB,QAAQ;GACR,MAAM;GACN,SAASV,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,QAAM,SAAS,QAAQ;AACvB,gBAAc,MAAM;AACpB,WAAS,OAAO,MAAM,8BAA8BW,iCAAqB,IAAI,UAAU;AACvF,KAAG,KAAK,KAAK,UAAU,gBAAgB,SAAS,gBAAgBA,iCAAqB,CAAC,CAAC;AACvF;;AAIF,KAAIC,gCAAgB,SAAS,EAAE;EAC7B,MAAM,SAAS,SAAS,UAAU;AAQlC,gBAPc,QAAQ,IAAI;GACxB,QAAQ;GACR,MAAM;GACN,SAASZ,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE;IAAQ;IAAS;GAC9B,CAAC,CACkB;AACpB,KAAG,KACD,KAAK,UACH,gBAAgB,SAAS,MAAM,SAAS,SAAS,MAAM,MAAM,SAAS,MAAM,KAAK,CAClF,CACF;AACD;;AAIF,KAAIa,0CAA0B,SAAS,EAAE;EACvC,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ;GACR,MAAM;GACN,SAASb,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,gBAAc,aAAa;EAE3B,IAAI;AACJ,MAAI;GACF,MAAM,UAAUc,0CAA0B,SAAS,EAAE;AACrD,OAAI,CAAC,QAAS,uCAAsB,SAAS;AAC7C,YAASC,mDACP,SAAS,WAAW,IACpBC,gCAAgB,SAAS,EACzB,cAAc,OACd,WACAC,yCACE,SAAS,WACT,cAAc,OACd,iBACA,SAAS,OACV,EACD,SAAS,aACTC,iCAAiB,SAAS,EAC1B,SACA,wBACA,+BACD;WACM,OAAO;AACd,gBAAa,SAAS,SAAS;AAC/B,OAAI5B,uCAAuB,MAAM,CAAE,cAAa,SAAS,QAAQ,MAAM;AACvE,SAAM;;AAGR,QAAM,YAAY,IAAI,cAAc,SAAS,QAAQ;GACnD;GACA,iBAAiB,QAAQ;GACzB,aAAa,QAAQ,eAAe,SAAS;GAC9C,CAAC;AACF;;AAIF,KAAI6B,+BAAe,SAAS,EAAE;EAC5B,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ;GACR,MAAM;GACN,SAASnB,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,gBAAc,aAAa;AAqB3B,QAAM,YAAY,IAAI,cAAc,SAjBrBO,4CAA4B,oBACzCa,wCACE,SAAS,SACT,cAAc,OACd,WACAH,yCACE,SAAS,WACT,cAAc,OACd,iBACA,SAAS,OACV,EACD,SAAS,aACTC,iCAAiB,SAAS,EAC1B,wBACA,+BACD,CACF,EACoD;GACnD;GACA,iBAAiB,QAAQ;GACzB,aAAa,QAAQ,eAAe,SAAS;GAC9C,CAAC;AACF;;AAIF,KAAIG,mCAAmB,SAAS,EAAE;EAChC,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ;GACR,MAAM;GACN,SAASrB,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,gBAAc,aAAa;EAC3B,IAAI;AACJ,MAAI;AACF,yCAAsB,SAAS;AAC/B,YAASsB,uCACPN,gCAAgB,SAAS,EACzB,cAAc,OACd,WAIAC,yCACE,SAAS,WACT,cAAc,OACd,iBACA,SAAS,OACV,EACD,SAAS,aACTC,iCAAiB,SAAS,EAC1B,wBACA,+BACD;WACM,OAAO;AACd,gBAAa,SAAS,SAAS;AAC/B,OAAI5B,uCAAuB,MAAM,CAAE,cAAa,SAAS,QAAQ,MAAM;AACvE,SAAM;;AAER,QAAM,YAAY,IAAI,cAAc,SAAS,QAAQ;GACnD;GACA,iBAAiB,QAAQ;GACzB,aAAa,QAAQ,eAAe,SAAS;GAC9C,CAAC;AACF;;AAWF,eAPc,QAAQ,IAAI;EACxB,QAAQ;EACR,MAAM;EACN,SAASU,+BAAe,SAAS,kBAAkB,EAAE,CAAC;EACtD,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK;GAAS;EACnC,CAAC,CACkB;AACpB,IAAG,KACD,KAAK,UACH,gBAAgB,iDAAiD,eAAe,CACjF,CACF;;;AAIH,MAAM,eAA8C;CAClD,SAAS,EAAE;CACX,SAAS,UAAU,KAAK,UAAU,MAAM;CACxC,sBAAsB;CACvB;;;;;;;;AASD,eAAe,YACb,IACA,OACA,SACA,QACA,QACe;CACf,MAAM,YAAYuB,8BAAY,IAAI,QAAQ;AAC1C,KAAI;AAKF,MAAI,CAJc,MAAMC,gCAAaC,0BAAO,GAAG,EAAE,QAAQ,cAAc,QAAQ;GAC7E;GACA,aAAa,UAAU;GACxB,CAAC,EACc;AACd,uCAAkB,OAAO,IAAI,UAAU;AACvC,MAAG,SAAS;;WAEN;AACR,YAAU,SAAS"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws-responses.d.cts","names":[],"sources":["../src/ws-responses.ts"],"sourcesContent":[],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"ws-responses.d.cts","names":[],"sources":["../src/ws-responses.ts"],"sourcesContent":[],"mappings":";;;;;;;KAqDK,0BAAA,GAA6B,IAIK,CAJA,eAIA,EAAA,aAAA,CAAA,GAAA;EAAmB,WAAA,CAAA,EAAA,MAAA;EAqC1C,KAAA,EAAA,MAAA;EAAwB,MAAA,CAAA,EAAA,MAAA;gBAClC,CAAA,EA1CgC,UAAA,CAIC,mBAsCjC;;sBAEK,CAAA,EAAA,GAAA,GAAA,IAAA;;AAC2B,iBAJtB,wBAAA,CAIsB,EAAA,EAHhC,mBAGgC,EAAA,QAAA,EAF1B,OAE0B,EAAA,EAAA,OAAA,EAD3B,OAC2B,EAAA,QAAA,EAA1B,0BAA0B,CAAA,EAAA,IAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws-responses.d.ts","names":[],"sources":["../src/ws-responses.ts"],"sourcesContent":[],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"ws-responses.d.ts","names":[],"sources":["../src/ws-responses.ts"],"sourcesContent":[],"mappings":";;;;;;;KAqDK,0BAAA,GAA6B,IAIK,CAJA,eAIA,EAAA,aAAA,CAAA,GAAA;EAAmB,WAAA,CAAA,EAAA,MAAA;EAqC1C,KAAA,EAAA,MAAA;EAAwB,MAAA,CAAA,EAAA,MAAA;gBAClC,CAAA,EA1CgC,UAAA,CAIC,mBAsCjC;;sBAEK,CAAA,EAAA,GAAA,GAAA,IAAA;;AAC2B,iBAJtB,wBAAA,CAIsB,EAAA,EAHhC,mBAGgC,EAAA,QAAA,EAF1B,OAE0B,EAAA,EAAA,OAAA,EAD3B,OAC2B,EAAA,QAAA,EAA1B,0BAA0B,CAAA,EAAA,IAAA"}
|
package/dist/ws-responses.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { buildOpenAIError } from "./response/errors.js";
|
|
2
2
|
import { DEFAULT_TEST_ID } from "./constants.js";
|
|
3
3
|
import { resolveRequestDecisions, strictOverrideField } from "./response/policy.js";
|
|
4
|
-
import { assertCustomToolCalls, extractOverrides, fixtureToolCallErrorCode, flattenHeaders, isCombinedFixtureResponse, isErrorResponse, isFixtureToolCallError, isTextResponse, isToolCallResponse, resolveReasoningForModel, resolveResponse, resolveServedBlockOutcome, servedToolCalls, strictNoMatchLogLine, strictNoMatchMessage, withoutLegacyNamespaces } from "./helpers.js";
|
|
5
|
-
import {
|
|
4
|
+
import { assertCustomToolCalls, extractOverrides, fixtureToolCallErrorCode, flattenHeaders, isCombinedFixtureResponse, isErrorResponse, isFixtureToolCallError, isRawResponse, isTextResponse, isToolCallResponse, journalFixtureToolCallError, resolveReasoningForModel, resolveResponse, resolveServedBlockOutcome, servedToolCalls, strictNoMatchLogLine, strictNoMatchMessage, withoutLegacyNamespaces } from "./helpers.js";
|
|
5
|
+
import { runFrameLoop, wsSink } from "./response/frame-loop.js";
|
|
6
6
|
import { planMisbehavior, recordMisbehaviorOutcome } from "./misbehavior.js";
|
|
7
7
|
import { matchFixtureDiagnostic } from "./router.js";
|
|
8
8
|
import "./journal.js";
|
|
9
|
-
import {
|
|
9
|
+
import { markWsTurnStopped, watchWsClose, wsLifecycle } from "./response/lifecycle.js";
|
|
10
|
+
import { extendRecordedTimings } from "./response/timing.js";
|
|
11
|
+
import { RAW_MODE_UNAVAILABLE } from "./response/raw.js";
|
|
10
12
|
import { buildResponsesMisbehavior, buildTextStreamEvents, contentWithToolCallsStreamEvents, prepareResponsesMisbehavior, requestIncludesEncryptedReasoning, requestWantsEncryptedReasoning, responsesToCompletionRequest, toolCallStreamEvents } from "./responses.js";
|
|
11
13
|
|
|
12
14
|
//#region src/ws-responses.ts
|
|
@@ -25,6 +27,7 @@ function buildErrorEvent(message, type = "invalid_request_error", code) {
|
|
|
25
27
|
}
|
|
26
28
|
function handleWebSocketResponses(ws, fixtures, journal, defaults) {
|
|
27
29
|
const { logger } = defaults;
|
|
30
|
+
watchWsClose(ws);
|
|
28
31
|
let pending = Promise.resolve();
|
|
29
32
|
ws.on("message", (raw) => {
|
|
30
33
|
pending = pending.then(async () => {
|
|
@@ -80,7 +83,7 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
80
83
|
const contextHeader = defaults.upgradeHeaders?.["x-aimock-context"];
|
|
81
84
|
completionReq._context = typeof contextHeader === "string" ? contextHeader : Array.isArray(contextHeader) && contextHeader.length > 0 ? contextHeader[0] : void 0;
|
|
82
85
|
const testId = defaults.testId ?? DEFAULT_TEST_ID;
|
|
83
|
-
const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
|
|
86
|
+
const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, { wire: "openai-responses" });
|
|
84
87
|
if (fixture) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
85
88
|
if (!fixture) {
|
|
86
89
|
if (resolveRequestDecisions(defaults.strict, defaults.upgradeHeaders).fixtureMiss) {
|
|
@@ -158,8 +161,6 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
158
161
|
const latency = fixture.latency ?? defaults.latency;
|
|
159
162
|
const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
|
|
160
163
|
if (misbehavior.kind === "applied") {
|
|
161
|
-
const prepared = prepareResponsesMisbehavior(misbehavior);
|
|
162
|
-
const output = buildResponsesMisbehavior(prepared, completionReq, chunkSize, emitEncryptedReasoning);
|
|
163
164
|
const entry = journal.add({
|
|
164
165
|
method: "WS",
|
|
165
166
|
path: "/v1/responses",
|
|
@@ -170,28 +171,41 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
170
171
|
fixture
|
|
171
172
|
}
|
|
172
173
|
});
|
|
174
|
+
const prepared = prepareResponsesMisbehavior(misbehavior);
|
|
175
|
+
const output = journalFixtureToolCallError(entry, () => buildResponsesMisbehavior(prepared, completionReq, chunkSize, emitEncryptedReasoning));
|
|
173
176
|
recordMisbehaviorOutcome({
|
|
174
177
|
entry,
|
|
175
178
|
summary: prepared.summary,
|
|
176
179
|
defaults: misbehaviorDefaults,
|
|
177
180
|
testId
|
|
178
181
|
});
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
const interruption = createInterruptionSignal(fixture);
|
|
186
|
-
if (!await sendEvents(ws, output.events, latency, interruption?.signal, interruption?.tick, appliedTimings, fixture.replaySpeed ?? defaults.replaySpeed)) {
|
|
187
|
-
ws.destroy();
|
|
188
|
-
entry.response.interrupted = true;
|
|
189
|
-
entry.response.interruptReason = interruption?.reason();
|
|
190
|
-
}
|
|
191
|
-
interruption?.cleanup();
|
|
182
|
+
const appliedTimings = extendRecordedTimings(fixture.recordedTimings, output.events.length, !!prepared.duplicateId);
|
|
183
|
+
await deliverTurn(ws, entry, fixture, output.events, {
|
|
184
|
+
latency,
|
|
185
|
+
recordedTimings: appliedTimings,
|
|
186
|
+
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
|
|
187
|
+
});
|
|
192
188
|
return;
|
|
193
189
|
}
|
|
194
190
|
const effectiveStrict = resolveRequestDecisions(defaults.strict, defaults.upgradeHeaders).reasoningSuppression;
|
|
191
|
+
if (isRawResponse(response)) {
|
|
192
|
+
const message = "raw fixtures are not yet supported on the WebSocket transport";
|
|
193
|
+
const entry = journal.add({
|
|
194
|
+
method: "WS",
|
|
195
|
+
path: "/v1/responses",
|
|
196
|
+
headers: flattenHeaders(defaults.upgradeHeaders ?? {}),
|
|
197
|
+
body: completionReq,
|
|
198
|
+
response: {
|
|
199
|
+
status: 500,
|
|
200
|
+
fixture
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
entry.response.error = message;
|
|
204
|
+
recordOutcome(entry);
|
|
205
|
+
defaults.logger.error(`WebSocket responses error: ${RAW_MODE_UNAVAILABLE}: ${message}`);
|
|
206
|
+
ws.send(JSON.stringify(buildErrorEvent(message, "server_error", RAW_MODE_UNAVAILABLE)));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
195
209
|
if (isErrorResponse(response)) {
|
|
196
210
|
const status = response.status ?? 500;
|
|
197
211
|
recordOutcome(journal.add({
|
|
@@ -229,13 +243,11 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
229
243
|
if (isFixtureToolCallError(error)) journalEntry.response.error = error.message;
|
|
230
244
|
throw error;
|
|
231
245
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
interruption?.cleanup();
|
|
246
|
+
await deliverTurn(ws, journalEntry, fixture, events, {
|
|
247
|
+
latency,
|
|
248
|
+
recordedTimings: fixture.recordedTimings,
|
|
249
|
+
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
|
|
250
|
+
});
|
|
239
251
|
return;
|
|
240
252
|
}
|
|
241
253
|
if (isTextResponse(response)) {
|
|
@@ -250,14 +262,11 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
250
262
|
}
|
|
251
263
|
});
|
|
252
264
|
recordOutcome(journalEntry);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
journalEntry.response.interruptReason = interruption?.reason();
|
|
259
|
-
}
|
|
260
|
-
interruption?.cleanup();
|
|
265
|
+
await deliverTurn(ws, journalEntry, fixture, journalFixtureToolCallError(journalEntry, () => buildTextStreamEvents(response.content, completionReq.model, chunkSize, resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger), response.webSearches, extractOverrides(response), emitEncryptedReasoning, synthesizeSummarylessReasoning)), {
|
|
266
|
+
latency,
|
|
267
|
+
recordedTimings: fixture.recordedTimings,
|
|
268
|
+
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
|
|
269
|
+
});
|
|
261
270
|
return;
|
|
262
271
|
}
|
|
263
272
|
if (isToolCallResponse(response)) {
|
|
@@ -281,13 +290,11 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
281
290
|
if (isFixtureToolCallError(error)) journalEntry.response.error = error.message;
|
|
282
291
|
throw error;
|
|
283
292
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
interruption?.cleanup();
|
|
293
|
+
await deliverTurn(ws, journalEntry, fixture, events, {
|
|
294
|
+
latency,
|
|
295
|
+
recordedTimings: fixture.recordedTimings,
|
|
296
|
+
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
|
|
297
|
+
});
|
|
291
298
|
return;
|
|
292
299
|
}
|
|
293
300
|
recordOutcome(journal.add({
|
|
@@ -302,20 +309,32 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
302
309
|
}));
|
|
303
310
|
ws.send(JSON.stringify(buildErrorEvent("Fixture response did not match any known type", "server_error")));
|
|
304
311
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
312
|
+
/** WS Responses frames: one JSON text message per event, no head, no trailer. */
|
|
313
|
+
const wsEventCodec = {
|
|
314
|
+
headers: {},
|
|
315
|
+
encode: (event) => JSON.stringify(event),
|
|
316
|
+
checkGoneBeforeDelay: true
|
|
317
|
+
};
|
|
318
|
+
/**
|
|
319
|
+
* Send one turn's events under a WS lifecycle. The lifecycle owns the fixture
|
|
320
|
+
* interruption and also stops the turn when the client closes, so a pending
|
|
321
|
+
* delay ends at once. A stopped turn marks the entry by the shared WS rule
|
|
322
|
+
* ({@link markWsTurnStopped}) and destroys the socket.
|
|
323
|
+
* `streamingProfile` is not applied on this transport, as before.
|
|
324
|
+
*/
|
|
325
|
+
async function deliverTurn(ws, entry, fixture, events, timing) {
|
|
326
|
+
const lifecycle = wsLifecycle(ws, fixture);
|
|
327
|
+
try {
|
|
328
|
+
if (!await runFrameLoop(wsSink(ws), events, wsEventCodec, timing, {
|
|
329
|
+
lifecycle,
|
|
330
|
+
onChunkSent: lifecycle.tick
|
|
331
|
+
})) {
|
|
332
|
+
markWsTurnStopped(entry, ws, lifecycle);
|
|
333
|
+
ws.destroy();
|
|
334
|
+
}
|
|
335
|
+
} finally {
|
|
336
|
+
lifecycle.dispose();
|
|
317
337
|
}
|
|
318
|
-
return true;
|
|
319
338
|
}
|
|
320
339
|
|
|
321
340
|
//#endregion
|