@copilotkit/aimock 1.44.0 → 1.45.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 +31 -0
- package/README.md +6 -0
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/aimock-cli.cjs +31 -5
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.d.cts.map +1 -1
- package/dist/aimock-cli.d.ts.map +1 -1
- package/dist/aimock-cli.js +31 -5
- package/dist/aimock-cli.js.map +1 -1
- package/dist/bedrock-converse.cjs +312 -39
- 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 +313 -40
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +195 -42
- 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 +196 -43
- package/dist/bedrock.js.map +1 -1
- package/dist/chaos.cjs +1 -0
- package/dist/chaos.js +1 -1
- package/dist/cli.cjs +152 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +155 -8
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +132 -71
- 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 +133 -72
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.cjs +62 -4
- package/dist/config-loader.cjs.map +1 -1
- package/dist/config-loader.d.cts +37 -4
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts +37 -4
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +62 -4
- package/dist/config-loader.js.map +1 -1
- package/dist/fakes-plugin.cjs +131 -0
- package/dist/fakes-plugin.cjs.map +1 -0
- package/dist/fakes-plugin.d.cts +23 -0
- package/dist/fakes-plugin.d.cts.map +1 -0
- package/dist/fakes-plugin.d.ts +23 -0
- package/dist/fakes-plugin.d.ts.map +1 -0
- package/dist/fakes-plugin.js +127 -0
- package/dist/fakes-plugin.js.map +1 -0
- package/dist/fixture-loader-services.cjs +1 -1
- package/dist/fixture-loader-services.cjs.map +1 -1
- package/dist/fixture-loader-services.js +1 -1
- package/dist/fixture-loader-services.js.map +1 -1
- package/dist/fixture-loader.cjs +389 -27
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +47 -3
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +47 -3
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +384 -29
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-interactions.cjs +250 -52
- 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 +251 -53
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +226 -37
- 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 +227 -38
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +881 -7
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +85 -4
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +85 -4
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +839 -8
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +9 -0
- package/dist/index.d.cts +7 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +4 -3
- package/dist/jest.cjs +31 -7
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +43 -3
- package/dist/jest.d.cts.map +1 -1
- package/dist/jest.d.ts +43 -3
- package/dist/jest.d.ts.map +1 -1
- package/dist/jest.js +33 -9
- package/dist/jest.js.map +1 -1
- package/dist/journal.cjs +135 -13
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts +38 -4
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts +38 -4
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +135 -13
- package/dist/journal.js.map +1 -1
- package/dist/live-sanitize.cjs +4 -32
- package/dist/live-sanitize.cjs.map +1 -1
- package/dist/live-sanitize.js +4 -32
- package/dist/live-sanitize.js.map +1 -1
- package/dist/llmock.cjs +123 -30
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +16 -1
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +16 -1
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +125 -32
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-fakes-mount.cjs +1 -0
- package/dist/mcp-fakes-mount.cjs.map +1 -1
- package/dist/mcp-fakes-mount.js +1 -0
- package/dist/mcp-fakes-mount.js.map +1 -1
- package/dist/mcp-fakes-report.cjs +111 -0
- package/dist/mcp-fakes-report.cjs.map +1 -0
- package/dist/mcp-fakes-report.d.cts +40 -0
- package/dist/mcp-fakes-report.d.cts.map +1 -0
- package/dist/mcp-fakes-report.d.ts +40 -0
- package/dist/mcp-fakes-report.d.ts.map +1 -0
- package/dist/mcp-fakes-report.js +107 -0
- package/dist/mcp-fakes-report.js.map +1 -0
- package/dist/mcp-fakes.cjs +331 -8
- package/dist/mcp-fakes.cjs.map +1 -1
- package/dist/mcp-fakes.d.cts +406 -3
- package/dist/mcp-fakes.d.cts.map +1 -1
- package/dist/mcp-fakes.d.ts +406 -3
- package/dist/mcp-fakes.d.ts.map +1 -1
- package/dist/mcp-fakes.js +331 -8
- package/dist/mcp-fakes.js.map +1 -1
- package/dist/mcp-handler.cjs +209 -16
- package/dist/mcp-handler.cjs.map +1 -1
- package/dist/mcp-handler.d.cts +49 -0
- package/dist/mcp-handler.d.cts.map +1 -0
- package/dist/mcp-handler.d.ts +49 -0
- package/dist/mcp-handler.d.ts.map +1 -0
- package/dist/mcp-handler.js +207 -17
- package/dist/mcp-handler.js.map +1 -1
- package/dist/mcp-mock.cjs +112 -1
- package/dist/mcp-mock.cjs.map +1 -1
- package/dist/mcp-mock.d.cts +33 -2
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts +33 -2
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +112 -1
- package/dist/mcp-mock.js.map +1 -1
- package/dist/mcp-recorder.cjs +1224 -0
- package/dist/mcp-recorder.cjs.map +1 -0
- package/dist/mcp-recorder.d.cts +108 -0
- package/dist/mcp-recorder.d.cts.map +1 -0
- package/dist/mcp-recorder.d.ts +108 -0
- package/dist/mcp-recorder.d.ts.map +1 -0
- package/dist/mcp-recorder.js +1215 -0
- package/dist/mcp-recorder.js.map +1 -0
- package/dist/messages.cjs +362 -56
- 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 +363 -58
- package/dist/messages.js.map +1 -1
- package/dist/misbehavior.cjs +1523 -0
- package/dist/misbehavior.cjs.map +1 -0
- package/dist/misbehavior.d.cts +43 -0
- package/dist/misbehavior.d.cts.map +1 -0
- package/dist/misbehavior.d.ts +43 -0
- package/dist/misbehavior.d.ts.map +1 -0
- package/dist/misbehavior.js +1511 -0
- package/dist/misbehavior.js.map +1 -0
- package/dist/ollama.cjs +310 -112
- 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 +311 -113
- package/dist/ollama.js.map +1 -1
- package/dist/record-sanitize.cjs +231 -0
- package/dist/record-sanitize.cjs.map +1 -0
- package/dist/record-sanitize.js +224 -0
- package/dist/record-sanitize.js.map +1 -0
- package/dist/recorder.cjs +106 -5
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts +2 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts +2 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +107 -7
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +667 -171
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts +5 -13
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts +5 -13
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +665 -174
- package/dist/responses.js.map +1 -1
- package/dist/router.cjs +24 -2
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +25 -4
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +572 -76
- 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 +574 -78
- package/dist/server.js.map +1 -1
- package/dist/stream-collapse.cjs +236 -34
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +39 -4
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +39 -4
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +235 -35
- package/dist/stream-collapse.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +315 -4
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +315 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/validate-cli.cjs +36 -4
- package/dist/validate-cli.cjs.map +1 -1
- package/dist/validate-cli.js +37 -5
- package/dist/validate-cli.js.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/version.cjs +45 -0
- package/dist/version.cjs.map +1 -0
- package/dist/version.js +44 -0
- package/dist/version.js.map +1 -0
- package/dist/vitest.cjs +66 -10
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +35 -4
- package/dist/vitest.d.cts.map +1 -1
- package/dist/vitest.d.ts +35 -4
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +67 -11
- package/dist/vitest.js.map +1 -1
- package/dist/watcher.cjs +6 -2
- package/dist/watcher.cjs.map +1 -1
- package/dist/watcher.js +6 -2
- package/dist/watcher.js.map +1 -1
- package/dist/ws-gemini-live.cjs +210 -101
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.d.cts +9 -3
- package/dist/ws-gemini-live.d.cts.map +1 -1
- package/dist/ws-gemini-live.d.ts +9 -3
- package/dist/ws-gemini-live.d.ts.map +1 -1
- package/dist/ws-gemini-live.js +211 -102
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +175 -62
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts +8 -3
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts +8 -3
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +176 -63
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +109 -16
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.d.cts +8 -12
- package/dist/ws-responses.d.cts.map +1 -1
- package/dist/ws-responses.d.ts +8 -12
- package/dist/ws-responses.d.ts.map +1 -1
- package/dist/ws-responses.js +111 -18
- package/dist/ws-responses.js.map +1 -1
- package/package.json +10 -2
- package/skills/write-fixtures/SKILL.md +179 -21
package/dist/ws-responses.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const require_constants = require('./constants.cjs');
|
|
2
2
|
const require_helpers = require('./helpers.cjs');
|
|
3
3
|
const require_sse_writer = require('./sse-writer.cjs');
|
|
4
|
+
const require_misbehavior = require('./misbehavior.cjs');
|
|
4
5
|
const require_router = require('./router.cjs');
|
|
5
6
|
require('./journal.cjs');
|
|
6
7
|
const require_interruption = require('./interruption.cjs');
|
|
@@ -24,15 +25,21 @@ function handleWebSocketResponses(ws, fixtures, journal, defaults) {
|
|
|
24
25
|
const { logger } = defaults;
|
|
25
26
|
let pending = Promise.resolve();
|
|
26
27
|
ws.on("message", (raw) => {
|
|
27
|
-
pending = pending.then(
|
|
28
|
-
const msg = err instanceof Error ? err.message : "Internal error";
|
|
29
|
-
logger.error(`WebSocket responses error: ${msg}`);
|
|
28
|
+
pending = pending.then(async () => {
|
|
30
29
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
defaults.beforeProcessMessage?.();
|
|
31
|
+
await processMessage(raw, ws, fixtures, journal, defaults);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
34
|
+
logger.error(`WebSocket responses error: ${msg}`);
|
|
35
|
+
try {
|
|
36
|
+
const code = require_helpers.isFixtureToolCallError(err) ? require_helpers.fixtureToolCallErrorCode(err) : void 0;
|
|
37
|
+
ws.send(JSON.stringify(buildErrorEvent(msg, "server_error", code)));
|
|
38
|
+
} catch (sendErr) {
|
|
39
|
+
defaults.logger.debug(`Failed to send error to client: ${sendErr instanceof Error ? sendErr.message : "unknown"}`);
|
|
40
|
+
}
|
|
34
41
|
}
|
|
35
|
-
})
|
|
42
|
+
});
|
|
36
43
|
});
|
|
37
44
|
}
|
|
38
45
|
async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
@@ -62,7 +69,11 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
62
69
|
};
|
|
63
70
|
const emitEncryptedReasoning = require_responses.requestWantsEncryptedReasoning(responsesReq);
|
|
64
71
|
const synthesizeSummarylessReasoning = require_responses.requestIncludesEncryptedReasoning(responsesReq);
|
|
65
|
-
const
|
|
72
|
+
const extendedTools = defaults.responsesTools === "extended";
|
|
73
|
+
const completionReq = require_responses.responsesToCompletionRequest(responsesReq, {
|
|
74
|
+
extended: extendedTools,
|
|
75
|
+
logger: defaults.logger
|
|
76
|
+
});
|
|
66
77
|
completionReq._endpointType = "chat";
|
|
67
78
|
const contextHeader = defaults.upgradeHeaders?.["x-aimock-context"];
|
|
68
79
|
completionReq._context = typeof contextHeader === "string" ? contextHeader : Array.isArray(contextHeader) && contextHeader.length > 0 ? contextHeader[0] : void 0;
|
|
@@ -101,13 +112,87 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
101
112
|
ws.send(JSON.stringify(buildErrorEvent("No fixture matched", "invalid_request_error", "no_fixture_match")));
|
|
102
113
|
return;
|
|
103
114
|
}
|
|
104
|
-
const
|
|
115
|
+
const resolvedResponse = await require_helpers.resolveResponse(fixture, completionReq);
|
|
116
|
+
const response = extendedTools ? resolvedResponse : require_helpers.withoutLegacyNamespaces(resolvedResponse);
|
|
117
|
+
const misbehaviorDefaults = {
|
|
118
|
+
...defaults,
|
|
119
|
+
replaySpeed: defaults.replaySpeed ?? 1
|
|
120
|
+
};
|
|
121
|
+
const misbehavior = require_misbehavior.planMisbehavior({
|
|
122
|
+
wire: "openai-responses",
|
|
123
|
+
fixture,
|
|
124
|
+
response,
|
|
125
|
+
request: completionReq,
|
|
126
|
+
stream: true,
|
|
127
|
+
emitsToolCallIds: true,
|
|
128
|
+
defaults: misbehaviorDefaults,
|
|
129
|
+
rawHeaders: {
|
|
130
|
+
...defaults.upgradeHeaders,
|
|
131
|
+
"x-test-id": testId
|
|
132
|
+
},
|
|
133
|
+
url: "/v1/responses"
|
|
134
|
+
});
|
|
135
|
+
const recordOutcome = (entry) => require_misbehavior.recordMisbehaviorOutcome({
|
|
136
|
+
entry,
|
|
137
|
+
summary: misbehavior.summary,
|
|
138
|
+
defaults: misbehaviorDefaults,
|
|
139
|
+
testId
|
|
140
|
+
});
|
|
141
|
+
if (misbehavior.kind === "error") {
|
|
142
|
+
recordOutcome(journal.add({
|
|
143
|
+
method: "WS",
|
|
144
|
+
path: "/v1/responses",
|
|
145
|
+
headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
|
|
146
|
+
body: completionReq,
|
|
147
|
+
response: {
|
|
148
|
+
status: misbehavior.status,
|
|
149
|
+
fixture
|
|
150
|
+
}
|
|
151
|
+
}));
|
|
152
|
+
if (!misbehavior.summary?.evaluations.length) defaults.logger.error(`[misbehavior] ${misbehavior.code}: ${misbehavior.message} (testId=${testId})`);
|
|
153
|
+
ws.send(JSON.stringify(buildErrorEvent(misbehavior.message, "invalid_request_error", misbehavior.code)));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
105
156
|
const latency = fixture.latency ?? defaults.latency;
|
|
106
157
|
const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
|
|
158
|
+
if (misbehavior.kind === "applied") {
|
|
159
|
+
const prepared = require_responses.prepareResponsesMisbehavior(misbehavior);
|
|
160
|
+
const output = require_responses.buildResponsesMisbehavior(prepared, completionReq, chunkSize, emitEncryptedReasoning);
|
|
161
|
+
const entry = journal.add({
|
|
162
|
+
method: "WS",
|
|
163
|
+
path: "/v1/responses",
|
|
164
|
+
headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
|
|
165
|
+
body: completionReq,
|
|
166
|
+
response: {
|
|
167
|
+
status: 200,
|
|
168
|
+
fixture
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
require_misbehavior.recordMisbehaviorOutcome({
|
|
172
|
+
entry,
|
|
173
|
+
summary: prepared.summary,
|
|
174
|
+
defaults: misbehaviorDefaults,
|
|
175
|
+
testId
|
|
176
|
+
});
|
|
177
|
+
const timings = fixture.recordedTimings;
|
|
178
|
+
const gaps = timings?.interChunkDelaysMs;
|
|
179
|
+
const appliedTimings = prepared.duplicateId && timings && gaps ? {
|
|
180
|
+
...timings,
|
|
181
|
+
interChunkDelaysMs: Array.from({ length: Math.max(gaps.length, output.events.length - 1) }, (_, index) => gaps[index] ?? gaps.at(-1) ?? 0)
|
|
182
|
+
} : timings;
|
|
183
|
+
const interruption = require_interruption.createInterruptionSignal(fixture);
|
|
184
|
+
if (!await sendEvents(ws, output.events, latency, interruption?.signal, interruption?.tick, appliedTimings, fixture.replaySpeed ?? defaults.replaySpeed)) {
|
|
185
|
+
ws.destroy();
|
|
186
|
+
entry.response.interrupted = true;
|
|
187
|
+
entry.response.interruptReason = interruption?.reason();
|
|
188
|
+
}
|
|
189
|
+
interruption?.cleanup();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
107
192
|
const effectiveStrict = require_helpers.resolveStrictMode(defaults.strict, defaults.upgradeHeaders);
|
|
108
193
|
if (require_helpers.isErrorResponse(response)) {
|
|
109
194
|
const status = response.status ?? 500;
|
|
110
|
-
journal.add({
|
|
195
|
+
recordOutcome(journal.add({
|
|
111
196
|
method: "WS",
|
|
112
197
|
path: "/v1/responses",
|
|
113
198
|
headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
|
|
@@ -116,11 +201,11 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
116
201
|
status,
|
|
117
202
|
fixture
|
|
118
203
|
}
|
|
119
|
-
});
|
|
204
|
+
}));
|
|
120
205
|
ws.send(JSON.stringify(buildErrorEvent(response.error.message, response.error.type, response.error.code)));
|
|
121
206
|
return;
|
|
122
207
|
}
|
|
123
|
-
if (require_helpers.
|
|
208
|
+
if (require_helpers.isCombinedFixtureResponse(response)) {
|
|
124
209
|
const journalEntry = journal.add({
|
|
125
210
|
method: "WS",
|
|
126
211
|
path: "/v1/responses",
|
|
@@ -131,11 +216,15 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
131
216
|
fixture
|
|
132
217
|
}
|
|
133
218
|
});
|
|
219
|
+
recordOutcome(journalEntry);
|
|
134
220
|
let events;
|
|
135
221
|
try {
|
|
136
|
-
|
|
222
|
+
const ordered = require_helpers.resolveServedBlockOutcome(response)?.ordered;
|
|
223
|
+
if (!ordered) require_helpers.assertCustomToolCalls(response);
|
|
224
|
+
events = require_responses.contentWithToolCallsStreamEvents(response.content ?? "", require_helpers.servedToolCalls(response), completionReq.model, chunkSize, require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger), response.webSearches, require_helpers.extractOverrides(response), ordered, emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
137
225
|
} catch (error) {
|
|
138
226
|
journalEntry.response.status = 500;
|
|
227
|
+
if (require_helpers.isFixtureToolCallError(error)) journalEntry.response.error = error.message;
|
|
139
228
|
throw error;
|
|
140
229
|
}
|
|
141
230
|
const interruption = require_interruption.createInterruptionSignal(fixture);
|
|
@@ -158,6 +247,7 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
158
247
|
fixture
|
|
159
248
|
}
|
|
160
249
|
});
|
|
250
|
+
recordOutcome(journalEntry);
|
|
161
251
|
const events = 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);
|
|
162
252
|
const interruption = require_interruption.createInterruptionSignal(fixture);
|
|
163
253
|
if (!await sendEvents(ws, events, latency, interruption?.signal, interruption?.tick, fixture.recordedTimings, fixture.replaySpeed ?? defaults.replaySpeed)) {
|
|
@@ -179,11 +269,14 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
179
269
|
fixture
|
|
180
270
|
}
|
|
181
271
|
});
|
|
272
|
+
recordOutcome(journalEntry);
|
|
182
273
|
let events;
|
|
183
274
|
try {
|
|
184
|
-
|
|
275
|
+
require_helpers.assertCustomToolCalls(response);
|
|
276
|
+
events = require_responses.toolCallStreamEvents(require_helpers.servedToolCalls(response), completionReq.model, chunkSize, require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger), response.webSearches, require_helpers.extractOverrides(response), emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
185
277
|
} catch (error) {
|
|
186
278
|
journalEntry.response.status = 500;
|
|
279
|
+
if (require_helpers.isFixtureToolCallError(error)) journalEntry.response.error = error.message;
|
|
187
280
|
throw error;
|
|
188
281
|
}
|
|
189
282
|
const interruption = require_interruption.createInterruptionSignal(fixture);
|
|
@@ -195,7 +288,7 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
195
288
|
interruption?.cleanup();
|
|
196
289
|
return;
|
|
197
290
|
}
|
|
198
|
-
journal.add({
|
|
291
|
+
recordOutcome(journal.add({
|
|
199
292
|
method: "WS",
|
|
200
293
|
path: "/v1/responses",
|
|
201
294
|
headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
|
|
@@ -204,7 +297,7 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
204
297
|
status: 500,
|
|
205
298
|
fixture
|
|
206
299
|
}
|
|
207
|
-
});
|
|
300
|
+
}));
|
|
208
301
|
ws.send(JSON.stringify(buildErrorEvent("Fixture response did not match any known type", "server_error")));
|
|
209
302
|
}
|
|
210
303
|
async function sendEvents(ws, events, latency, signal, onChunkSent, recordedTimings, replaySpeed) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws-responses.cjs","names":["requestWantsEncryptedReasoning","requestIncludesEncryptedReasoning","responsesToCompletionRequest","DEFAULT_TEST_ID","matchFixtureDiagnostic","resolveStrictMode","strictNoMatchMessage","strictNoMatchLogLine","flattenHeaders","strictOverrideField","resolveResponse","isErrorResponse","isContentWithToolCallsResponse","buildContentWithToolCallsStreamEvents","resolveReasoningForModel","extractOverrides","createInterruptionSignal","isTextResponse","buildTextStreamEvents","isToolCallResponse","buildToolCallStreamEvents","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 { ChatCompletionRequest, Fixture } from \"./types.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport {\n responsesToCompletionRequest,\n requestIncludesEncryptedReasoning,\n requestWantsEncryptedReasoning,\n buildTextStreamEvents,\n buildToolCallStreamEvents,\n buildContentWithToolCallsStreamEvents,\n type ResponsesSSEEvent,\n} from \"./responses.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n extractOverrides,\n resolveResponse,\n resolveStrictMode,\n resolveReasoningForModel,\n strictOverrideField,\n flattenHeaders,\n strictNoMatchMessage,\n strictNoMatchLogLine,\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 { Logger } from \"./logger.js\";\nimport type { WebSocketConnection } from \"./ws-framing.js\";\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: { message, type, code },\n };\n}\n\nexport function handleWebSocketResponses(\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n replaySpeed?: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n upgradeHeaders?: import(\"node:http\").IncomingHttpHeaders;\n },\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(() =>\n processMessage(raw, ws, fixtures, journal, defaults).catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : \"Internal error\";\n logger.error(`WebSocket responses error: ${msg}`);\n try {\n ws.send(JSON.stringify(buildErrorEvent(msg, \"server_error\")));\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: {\n latency: number;\n chunkSize: number;\n replaySpeed?: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n upgradeHeaders?: import(\"node:http\").IncomingHttpHeaders;\n },\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 input: (parsed.input ?? []) as {\n role?: string;\n type?: string;\n content?: string | { type: string; text?: string }[];\n call_id?: string;\n name?: string;\n arguments?: string;\n output?: string;\n id?: string;\n }[],\n instructions: parsed.instructions,\n tools: parsed.tools as\n | {\n type: \"function\";\n name: string;\n description?: string;\n parameters?: object;\n strict?: boolean;\n }[]\n | 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 completionReq = responsesToCompletionRequest(responsesReq);\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 (resolveStrictMode(defaults.strict, defaults.upgradeHeaders)) {\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 const response = await resolveResponse(fixture, completionReq);\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\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 = resolveStrictMode(defaults.strict, defaults.upgradeHeaders);\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status, fixture },\n });\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 (isContentWithToolCallsResponse(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\n let events: ResponsesSSEEvent[];\n try {\n events = buildContentWithToolCallsStreamEvents(\n response.content ?? \"\",\n response.toolCalls ?? [],\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 response.blocks,\n emitEncryptedReasoning,\n synthesizeSummarylessReasoning,\n );\n } catch (error) {\n journalEntry.response.status = 500;\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\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 let events: ResponsesSSEEvent[];\n try {\n events = buildToolCallStreamEvents(\n response.toolCalls,\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 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 journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 500, fixture },\n });\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":";;;;;;;;;AAoDA,SAAS,wBAAwB,KAA4C;AAC3E,QACE,OAAO,QAAQ,YACf,QAAQ,QACP,IAA8B,SAAS;;AAI5C,SAAS,gBACP,SACA,OAAO,yBACP,MACmB;AACnB,QAAO;EACL,MAAM;EACN,OAAO;GAAE;GAAS;GAAM;GAAM;EAC/B;;AAGH,SAAgB,yBACd,IACA,UACA,SACA,UAWM;CACN,MAAM,EAAE,WAAW;CAEnB,IAAI,UAAU,QAAQ,SAAS;AAC/B,IAAG,GAAG,YAAY,QAAgB;AAChC,YAAU,QAAQ,WAChB,eAAe,KAAK,IAAI,UAAU,SAAS,SAAS,CAAC,OAAO,QAAiB;GAC3E,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU;AACjD,UAAO,MAAM,8BAA8B,MAAM;AACjD,OAAI;AACF,OAAG,KAAK,KAAK,UAAU,gBAAgB,KAAK,eAAe,CAAC,CAAC;YACtD,SAAS;AAChB,aAAS,OAAO,MACd,mCAAmC,mBAAmB,QAAQ,QAAQ,UAAU,YACjF;;IAEH,CACH;GACD;;AAGJ,eAAe,eACb,KACA,IACA,UACA,SACA,UAWe;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;EAChC,OAAQ,OAAO,SAAS,EAAE;EAU1B,cAAc,OAAO;EACrB,OAAO,OAAO;EASd,aAAa,OAAO;EACpB,QAAQ,OAAO;EACf,aAAa,OAAO;EACpB,mBAAmB,OAAO;EAC1B,SAAU,OAAkC;EAC5C,OAAQ,OAA+B;EACxC;CAID,MAAM,yBAAyBA,iDAA+B,aAAa;CAI3E,MAAM,iCAAiCC,oDAAkC,aAAa;CAEtF,MAAM,gBAAgBC,+CAA6B,aAAa;AAChE,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,iBACV;AAED,KAAI,QACF,SAAQ,2BAA2B,SAAS,UAAU,OAAO;AAG/D,KAAI,CAAC,SAAS;AACZ,MAAIC,kCAAkB,SAAS,QAAQ,SAAS,eAAe,EAAE;GAC/D,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,oCAAoB,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,oCAAoB,SAAS,QAAQ,SAAS,eAAe;IACjE;GACF,CAAC;AACF,KAAG,KACD,KAAK,UACH,gBAAgB,sBAAsB,yBAAyB,mBAAmB,CACnF,CACF;AACD;;CAGF,MAAM,WAAW,MAAMC,gCAAgB,SAAS,cAAc;CAC9D,MAAM,UAAU,QAAQ,WAAW,SAAS;CAC5C,MAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,aAAa,SAAS,UAAU;CAKtE,MAAM,kBAAkBL,kCAAkB,SAAS,QAAQ,SAAS,eAAe;AAGnF,KAAIM,gCAAgB,SAAS,EAAE;EAC7B,MAAM,SAAS,SAAS,UAAU;AAClC,UAAQ,IAAI;GACV,QAAQ;GACR,MAAM;GACN,SAASH,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE;IAAQ;IAAS;GAC9B,CAAC;AACF,KAAG,KACD,KAAK,UACH,gBAAgB,SAAS,MAAM,SAAS,SAAS,MAAM,MAAM,SAAS,MAAM,KAAK,CAClF,CACF;AACD;;AAIF,KAAII,+CAA+B,SAAS,EAAE;EAC5C,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ;GACR,MAAM;GACN,SAASJ,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;EAEF,IAAI;AACJ,MAAI;AACF,YAASK,wDACP,SAAS,WAAW,IACpB,SAAS,aAAa,EAAE,EACxB,cAAc,OACd,WACAC,yCACE,SAAS,WACT,cAAc,OACd,iBACA,SAAS,OACV,EACD,SAAS,aACTC,iCAAiB,SAAS,EAC1B,SAAS,QACT,wBACA,+BACD;WACM,OAAO;AACd,gBAAa,SAAS,SAAS;AAC/B,SAAM;;EAGR,MAAM,eAAeC,8CAAyB,QAAQ;AAUtD,MAAI,CATc,MAAM,WACtB,IACA,QACA,SACA,cAAc,QACd,cAAc,MACd,QAAQ,iBACR,QAAQ,eAAe,SAAS,YACjC,EACe;AACd,MAAG,SAAS;AACZ,gBAAa,SAAS,cAAc;AACpC,gBAAa,SAAS,kBAAkB,cAAc,QAAQ;;AAEhE,gBAAc,SAAS;AACvB;;AAIF,KAAIC,+BAAe,SAAS,EAAE;EAC5B,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ;GACR,MAAM;GACN,SAAST,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;EAEF,MAAM,SAASU,wCACb,SAAS,SACT,cAAc,OACd,WACAJ,yCACE,SAAS,WACT,cAAc,OACd,iBACA,SAAS,OACV,EACD,SAAS,aACTC,iCAAiB,SAAS,EAC1B,wBACA,+BACD;EACD,MAAM,eAAeC,8CAAyB,QAAQ;AAUtD,MAAI,CATc,MAAM,WACtB,IACA,QACA,SACA,cAAc,QACd,cAAc,MACd,QAAQ,iBACR,QAAQ,eAAe,SAAS,YACjC,EACe;AACd,MAAG,SAAS;AACZ,gBAAa,SAAS,cAAc;AACpC,gBAAa,SAAS,kBAAkB,cAAc,QAAQ;;AAEhE,gBAAc,SAAS;AACvB;;AAIF,KAAIG,mCAAmB,SAAS,EAAE;EAChC,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ;GACR,MAAM;GACN,SAASX,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;EACF,IAAI;AACJ,MAAI;AACF,YAASY,4CACP,SAAS,WACT,cAAc,OACd,WAIAN,yCACE,SAAS,WACT,cAAc,OACd,iBACA,SAAS,OACV,EACD,SAAS,aACTC,iCAAiB,SAAS,EAC1B,wBACA,+BACD;WACM,OAAO;AACd,gBAAa,SAAS,SAAS;AAC/B,SAAM;;EAER,MAAM,eAAeC,8CAAyB,QAAQ;AAUtD,MAAI,CATc,MAAM,WACtB,IACA,QACA,SACA,cAAc,QACd,cAAc,MACd,QAAQ,iBACR,QAAQ,eAAe,SAAS,YACjC,EACe;AACd,MAAG,SAAS;AACZ,gBAAa,SAAS,cAAc;AACpC,gBAAa,SAAS,kBAAkB,cAAc,QAAQ;;AAEhE,gBAAc,SAAS;AACvB;;AAIF,SAAQ,IAAI;EACV,QAAQ;EACR,MAAM;EACN,SAASR,+BAAe,SAAS,kBAAkB,EAAE,CAAC;EACtD,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK;GAAS;EACnC,CAAC;AACF,IAAG,KACD,KAAK,UACH,gBAAgB,iDAAiD,eAAe,CACjF,CACF;;AAGH,eAAe,WACb,IACA,QACA,SACA,QACA,aACA,iBACA,aACkB;CAClB,IAAI,aAAa;AACjB,MAAK,MAAM,SAAS,QAAQ;AAC1B,MAAI,GAAG,SAAU,QAAO;EACxB,MAAM,aAAaa,kCAAe,YAAY,QAAW,SAAS,iBAAiB,YAAY;AAC/F,MAAI,aAAa,EAAG,OAAMC,yBAAM,YAAY,OAAO;AACnD,MAAI,QAAQ,QAAS,QAAO;AAC5B,MAAI,GAAG,SAAU,QAAO;AACxB,KAAG,KAAK,KAAK,UAAU,MAAM,CAAC;AAC9B;AACA,iBAAe;AACf,MAAI,QAAQ,QAAS,QAAO;;AAE9B,QAAO"}
|
|
1
|
+
{"version":3,"file":"ws-responses.cjs","names":["isFixtureToolCallError","fixtureToolCallErrorCode","requestWantsEncryptedReasoning","requestIncludesEncryptedReasoning","responsesToCompletionRequest","DEFAULT_TEST_ID","matchFixtureDiagnostic","resolveStrictMode","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 resolveStrictMode,\n resolveReasoningForModel,\n strictOverrideField,\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\";\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: { 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 (resolveStrictMode(defaults.strict, defaults.upgradeHeaders)) {\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 = resolveStrictMode(defaults.strict, defaults.upgradeHeaders);\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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"}
|
package/dist/ws-responses.d.cts
CHANGED
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
import { Journal } from "./journal.cjs";
|
|
2
|
-
import {
|
|
3
|
-
import { ChatCompletionRequest, Fixture } from "./types.cjs";
|
|
2
|
+
import { Fixture, HandlerDefaults } from "./types.cjs";
|
|
4
3
|
import { WebSocketConnection } from "./ws-framing.cjs";
|
|
5
|
-
import * as
|
|
4
|
+
import * as node_http1 from "node:http";
|
|
6
5
|
|
|
7
6
|
//#region src/ws-responses.d.ts
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
latency: number;
|
|
11
|
-
chunkSize: number;
|
|
8
|
+
type ResponsesWebSocketDefaults = Omit<HandlerDefaults, "replaySpeed"> & {
|
|
12
9
|
replaySpeed?: number;
|
|
13
10
|
model: string;
|
|
14
|
-
logger: Logger;
|
|
15
|
-
strict?: boolean;
|
|
16
|
-
requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;
|
|
17
11
|
testId?: string;
|
|
18
|
-
upgradeHeaders?:
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
upgradeHeaders?: node_http1.IncomingHttpHeaders;
|
|
13
|
+
/** @internal Called before each message is processed (the server assigns fixture positions). */
|
|
14
|
+
beforeProcessMessage?: () => void;
|
|
15
|
+
};
|
|
16
|
+
declare function handleWebSocketResponses(ws: WebSocketConnection, fixtures: Fixture[], journal: Journal, defaults: ResponsesWebSocketDefaults): void;
|
|
21
17
|
//#endregion
|
|
22
18
|
export { handleWebSocketResponses };
|
|
23
19
|
//# sourceMappingURL=ws-responses.d.cts.map
|
|
@@ -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":";;;;;;;KAiDK,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.d.ts
CHANGED
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
import { Journal } from "./journal.js";
|
|
2
|
-
import {
|
|
3
|
-
import { ChatCompletionRequest, Fixture } from "./types.js";
|
|
2
|
+
import { Fixture, HandlerDefaults } from "./types.js";
|
|
4
3
|
import { WebSocketConnection } from "./ws-framing.js";
|
|
5
|
-
import * as
|
|
4
|
+
import * as node_http1 from "node:http";
|
|
6
5
|
|
|
7
6
|
//#region src/ws-responses.d.ts
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
latency: number;
|
|
11
|
-
chunkSize: number;
|
|
8
|
+
type ResponsesWebSocketDefaults = Omit<HandlerDefaults, "replaySpeed"> & {
|
|
12
9
|
replaySpeed?: number;
|
|
13
10
|
model: string;
|
|
14
|
-
logger: Logger;
|
|
15
|
-
strict?: boolean;
|
|
16
|
-
requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;
|
|
17
11
|
testId?: string;
|
|
18
|
-
upgradeHeaders?:
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
upgradeHeaders?: node_http1.IncomingHttpHeaders;
|
|
13
|
+
/** @internal Called before each message is processed (the server assigns fixture positions). */
|
|
14
|
+
beforeProcessMessage?: () => void;
|
|
15
|
+
};
|
|
16
|
+
declare function handleWebSocketResponses(ws: WebSocketConnection, fixtures: Fixture[], journal: Journal, defaults: ResponsesWebSocketDefaults): void;
|
|
21
17
|
//#endregion
|
|
22
18
|
export { handleWebSocketResponses };
|
|
23
19
|
//# sourceMappingURL=ws-responses.d.ts.map
|
|
@@ -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":";;;;;;;KAiDK,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,10 +1,11 @@
|
|
|
1
1
|
import { DEFAULT_TEST_ID } from "./constants.js";
|
|
2
|
-
import { extractOverrides, flattenHeaders,
|
|
2
|
+
import { assertCustomToolCalls, extractOverrides, fixtureToolCallErrorCode, flattenHeaders, isCombinedFixtureResponse, isErrorResponse, isFixtureToolCallError, isTextResponse, isToolCallResponse, resolveReasoningForModel, resolveResponse, resolveServedBlockOutcome, resolveStrictMode, servedToolCalls, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, withoutLegacyNamespaces } from "./helpers.js";
|
|
3
3
|
import { calculateDelay, delay } from "./sse-writer.js";
|
|
4
|
+
import { planMisbehavior, recordMisbehaviorOutcome } from "./misbehavior.js";
|
|
4
5
|
import { matchFixtureDiagnostic } from "./router.js";
|
|
5
6
|
import "./journal.js";
|
|
6
7
|
import { createInterruptionSignal } from "./interruption.js";
|
|
7
|
-
import {
|
|
8
|
+
import { buildResponsesMisbehavior, buildTextStreamEvents, contentWithToolCallsStreamEvents, prepareResponsesMisbehavior, requestIncludesEncryptedReasoning, requestWantsEncryptedReasoning, responsesToCompletionRequest, toolCallStreamEvents } from "./responses.js";
|
|
8
9
|
|
|
9
10
|
//#region src/ws-responses.ts
|
|
10
11
|
function isResponseCreateMessage(msg) {
|
|
@@ -24,15 +25,21 @@ function handleWebSocketResponses(ws, fixtures, journal, defaults) {
|
|
|
24
25
|
const { logger } = defaults;
|
|
25
26
|
let pending = Promise.resolve();
|
|
26
27
|
ws.on("message", (raw) => {
|
|
27
|
-
pending = pending.then(
|
|
28
|
-
const msg = err instanceof Error ? err.message : "Internal error";
|
|
29
|
-
logger.error(`WebSocket responses error: ${msg}`);
|
|
28
|
+
pending = pending.then(async () => {
|
|
30
29
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
defaults.beforeProcessMessage?.();
|
|
31
|
+
await processMessage(raw, ws, fixtures, journal, defaults);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
34
|
+
logger.error(`WebSocket responses error: ${msg}`);
|
|
35
|
+
try {
|
|
36
|
+
const code = isFixtureToolCallError(err) ? fixtureToolCallErrorCode(err) : void 0;
|
|
37
|
+
ws.send(JSON.stringify(buildErrorEvent(msg, "server_error", code)));
|
|
38
|
+
} catch (sendErr) {
|
|
39
|
+
defaults.logger.debug(`Failed to send error to client: ${sendErr instanceof Error ? sendErr.message : "unknown"}`);
|
|
40
|
+
}
|
|
34
41
|
}
|
|
35
|
-
})
|
|
42
|
+
});
|
|
36
43
|
});
|
|
37
44
|
}
|
|
38
45
|
async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
@@ -62,7 +69,11 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
62
69
|
};
|
|
63
70
|
const emitEncryptedReasoning = requestWantsEncryptedReasoning(responsesReq);
|
|
64
71
|
const synthesizeSummarylessReasoning = requestIncludesEncryptedReasoning(responsesReq);
|
|
65
|
-
const
|
|
72
|
+
const extendedTools = defaults.responsesTools === "extended";
|
|
73
|
+
const completionReq = responsesToCompletionRequest(responsesReq, {
|
|
74
|
+
extended: extendedTools,
|
|
75
|
+
logger: defaults.logger
|
|
76
|
+
});
|
|
66
77
|
completionReq._endpointType = "chat";
|
|
67
78
|
const contextHeader = defaults.upgradeHeaders?.["x-aimock-context"];
|
|
68
79
|
completionReq._context = typeof contextHeader === "string" ? contextHeader : Array.isArray(contextHeader) && contextHeader.length > 0 ? contextHeader[0] : void 0;
|
|
@@ -101,13 +112,87 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
101
112
|
ws.send(JSON.stringify(buildErrorEvent("No fixture matched", "invalid_request_error", "no_fixture_match")));
|
|
102
113
|
return;
|
|
103
114
|
}
|
|
104
|
-
const
|
|
115
|
+
const resolvedResponse = await resolveResponse(fixture, completionReq);
|
|
116
|
+
const response = extendedTools ? resolvedResponse : withoutLegacyNamespaces(resolvedResponse);
|
|
117
|
+
const misbehaviorDefaults = {
|
|
118
|
+
...defaults,
|
|
119
|
+
replaySpeed: defaults.replaySpeed ?? 1
|
|
120
|
+
};
|
|
121
|
+
const misbehavior = planMisbehavior({
|
|
122
|
+
wire: "openai-responses",
|
|
123
|
+
fixture,
|
|
124
|
+
response,
|
|
125
|
+
request: completionReq,
|
|
126
|
+
stream: true,
|
|
127
|
+
emitsToolCallIds: true,
|
|
128
|
+
defaults: misbehaviorDefaults,
|
|
129
|
+
rawHeaders: {
|
|
130
|
+
...defaults.upgradeHeaders,
|
|
131
|
+
"x-test-id": testId
|
|
132
|
+
},
|
|
133
|
+
url: "/v1/responses"
|
|
134
|
+
});
|
|
135
|
+
const recordOutcome = (entry) => recordMisbehaviorOutcome({
|
|
136
|
+
entry,
|
|
137
|
+
summary: misbehavior.summary,
|
|
138
|
+
defaults: misbehaviorDefaults,
|
|
139
|
+
testId
|
|
140
|
+
});
|
|
141
|
+
if (misbehavior.kind === "error") {
|
|
142
|
+
recordOutcome(journal.add({
|
|
143
|
+
method: "WS",
|
|
144
|
+
path: "/v1/responses",
|
|
145
|
+
headers: flattenHeaders(defaults.upgradeHeaders ?? {}),
|
|
146
|
+
body: completionReq,
|
|
147
|
+
response: {
|
|
148
|
+
status: misbehavior.status,
|
|
149
|
+
fixture
|
|
150
|
+
}
|
|
151
|
+
}));
|
|
152
|
+
if (!misbehavior.summary?.evaluations.length) defaults.logger.error(`[misbehavior] ${misbehavior.code}: ${misbehavior.message} (testId=${testId})`);
|
|
153
|
+
ws.send(JSON.stringify(buildErrorEvent(misbehavior.message, "invalid_request_error", misbehavior.code)));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
105
156
|
const latency = fixture.latency ?? defaults.latency;
|
|
106
157
|
const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
|
|
158
|
+
if (misbehavior.kind === "applied") {
|
|
159
|
+
const prepared = prepareResponsesMisbehavior(misbehavior);
|
|
160
|
+
const output = buildResponsesMisbehavior(prepared, completionReq, chunkSize, emitEncryptedReasoning);
|
|
161
|
+
const entry = journal.add({
|
|
162
|
+
method: "WS",
|
|
163
|
+
path: "/v1/responses",
|
|
164
|
+
headers: flattenHeaders(defaults.upgradeHeaders ?? {}),
|
|
165
|
+
body: completionReq,
|
|
166
|
+
response: {
|
|
167
|
+
status: 200,
|
|
168
|
+
fixture
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
recordMisbehaviorOutcome({
|
|
172
|
+
entry,
|
|
173
|
+
summary: prepared.summary,
|
|
174
|
+
defaults: misbehaviorDefaults,
|
|
175
|
+
testId
|
|
176
|
+
});
|
|
177
|
+
const timings = fixture.recordedTimings;
|
|
178
|
+
const gaps = timings?.interChunkDelaysMs;
|
|
179
|
+
const appliedTimings = prepared.duplicateId && timings && gaps ? {
|
|
180
|
+
...timings,
|
|
181
|
+
interChunkDelaysMs: Array.from({ length: Math.max(gaps.length, output.events.length - 1) }, (_, index) => gaps[index] ?? gaps.at(-1) ?? 0)
|
|
182
|
+
} : timings;
|
|
183
|
+
const interruption = createInterruptionSignal(fixture);
|
|
184
|
+
if (!await sendEvents(ws, output.events, latency, interruption?.signal, interruption?.tick, appliedTimings, fixture.replaySpeed ?? defaults.replaySpeed)) {
|
|
185
|
+
ws.destroy();
|
|
186
|
+
entry.response.interrupted = true;
|
|
187
|
+
entry.response.interruptReason = interruption?.reason();
|
|
188
|
+
}
|
|
189
|
+
interruption?.cleanup();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
107
192
|
const effectiveStrict = resolveStrictMode(defaults.strict, defaults.upgradeHeaders);
|
|
108
193
|
if (isErrorResponse(response)) {
|
|
109
194
|
const status = response.status ?? 500;
|
|
110
|
-
journal.add({
|
|
195
|
+
recordOutcome(journal.add({
|
|
111
196
|
method: "WS",
|
|
112
197
|
path: "/v1/responses",
|
|
113
198
|
headers: flattenHeaders(defaults.upgradeHeaders ?? {}),
|
|
@@ -116,11 +201,11 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
116
201
|
status,
|
|
117
202
|
fixture
|
|
118
203
|
}
|
|
119
|
-
});
|
|
204
|
+
}));
|
|
120
205
|
ws.send(JSON.stringify(buildErrorEvent(response.error.message, response.error.type, response.error.code)));
|
|
121
206
|
return;
|
|
122
207
|
}
|
|
123
|
-
if (
|
|
208
|
+
if (isCombinedFixtureResponse(response)) {
|
|
124
209
|
const journalEntry = journal.add({
|
|
125
210
|
method: "WS",
|
|
126
211
|
path: "/v1/responses",
|
|
@@ -131,11 +216,15 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
131
216
|
fixture
|
|
132
217
|
}
|
|
133
218
|
});
|
|
219
|
+
recordOutcome(journalEntry);
|
|
134
220
|
let events;
|
|
135
221
|
try {
|
|
136
|
-
|
|
222
|
+
const ordered = resolveServedBlockOutcome(response)?.ordered;
|
|
223
|
+
if (!ordered) assertCustomToolCalls(response);
|
|
224
|
+
events = contentWithToolCallsStreamEvents(response.content ?? "", servedToolCalls(response), completionReq.model, chunkSize, resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger), response.webSearches, extractOverrides(response), ordered, emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
137
225
|
} catch (error) {
|
|
138
226
|
journalEntry.response.status = 500;
|
|
227
|
+
if (isFixtureToolCallError(error)) journalEntry.response.error = error.message;
|
|
139
228
|
throw error;
|
|
140
229
|
}
|
|
141
230
|
const interruption = createInterruptionSignal(fixture);
|
|
@@ -158,6 +247,7 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
158
247
|
fixture
|
|
159
248
|
}
|
|
160
249
|
});
|
|
250
|
+
recordOutcome(journalEntry);
|
|
161
251
|
const events = buildTextStreamEvents(response.content, completionReq.model, chunkSize, resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger), response.webSearches, extractOverrides(response), emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
162
252
|
const interruption = createInterruptionSignal(fixture);
|
|
163
253
|
if (!await sendEvents(ws, events, latency, interruption?.signal, interruption?.tick, fixture.recordedTimings, fixture.replaySpeed ?? defaults.replaySpeed)) {
|
|
@@ -179,11 +269,14 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
179
269
|
fixture
|
|
180
270
|
}
|
|
181
271
|
});
|
|
272
|
+
recordOutcome(journalEntry);
|
|
182
273
|
let events;
|
|
183
274
|
try {
|
|
184
|
-
|
|
275
|
+
assertCustomToolCalls(response);
|
|
276
|
+
events = toolCallStreamEvents(servedToolCalls(response), completionReq.model, chunkSize, resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger), response.webSearches, extractOverrides(response), emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
185
277
|
} catch (error) {
|
|
186
278
|
journalEntry.response.status = 500;
|
|
279
|
+
if (isFixtureToolCallError(error)) journalEntry.response.error = error.message;
|
|
187
280
|
throw error;
|
|
188
281
|
}
|
|
189
282
|
const interruption = createInterruptionSignal(fixture);
|
|
@@ -195,7 +288,7 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
195
288
|
interruption?.cleanup();
|
|
196
289
|
return;
|
|
197
290
|
}
|
|
198
|
-
journal.add({
|
|
291
|
+
recordOutcome(journal.add({
|
|
199
292
|
method: "WS",
|
|
200
293
|
path: "/v1/responses",
|
|
201
294
|
headers: flattenHeaders(defaults.upgradeHeaders ?? {}),
|
|
@@ -204,7 +297,7 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
|
|
|
204
297
|
status: 500,
|
|
205
298
|
fixture
|
|
206
299
|
}
|
|
207
|
-
});
|
|
300
|
+
}));
|
|
208
301
|
ws.send(JSON.stringify(buildErrorEvent("Fixture response did not match any known type", "server_error")));
|
|
209
302
|
}
|
|
210
303
|
async function sendEvents(ws, events, latency, signal, onChunkSent, recordedTimings, replaySpeed) {
|