@copilotkit/aimock 1.44.0 → 1.45.1
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 +42 -0
- package/README.md +6 -0
- package/dist/agui-types.d.cts.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 +216 -17
- 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 +214 -18
- 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/openrouter-video.cjs +30 -2
- package/dist/openrouter-video.cjs.map +1 -1
- package/dist/openrouter-video.d.cts.map +1 -1
- package/dist/openrouter-video.d.ts.map +1 -1
- package/dist/openrouter-video.js +30 -2
- package/dist/openrouter-video.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/responses.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { isReasoningModel } from "./model-utils.js";
|
|
2
|
-
import { extractOverrides, flattenHeaders, generateId, generateToolCallId, getContext, getTestId,
|
|
2
|
+
import { assertCustomToolCalls, assertResponsesToolCalls, extractOverrides, flattenHeaders, generateId, generateToolCallId, getContext, getTestId, isCombinedFixtureResponse, isErrorResponse, isJsonObject, isTextResponse, isToolCallResponse, prepareOpenAIChatMisbehavior, resolveFixtureBlocks, resolveOpenAIChatMisbehaviorUsage, resolveReasoningForModel, resolveResponse, resolveServedBlockOutcome, resolveStrictMode, serializeErrorResponse, servedToolCalls, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, toolCallFixtureBlock, withoutLegacyNamespaces } from "./helpers.js";
|
|
3
3
|
import { calculateDelay, delay, writeErrorResponse } from "./sse-writer.js";
|
|
4
4
|
import { applyChaosAsync } from "./chaos.js";
|
|
5
|
-
import {
|
|
5
|
+
import { planMisbehavior, recordMisbehaviorOutcome } from "./misbehavior.js";
|
|
6
|
+
import { matchFixtureDiagnostic, recordMatchOptions, setResponsesOfferedTools } from "./router.js";
|
|
6
7
|
import { proxyAndRecord } from "./recorder.js";
|
|
7
8
|
import { createInterruptionSignal } from "./interruption.js";
|
|
9
|
+
import { randomBytes } from "node:crypto";
|
|
8
10
|
|
|
9
11
|
//#region src/responses.ts
|
|
10
12
|
function extractTextContent(content) {
|
|
@@ -12,7 +14,36 @@ function extractTextContent(content) {
|
|
|
12
14
|
if (typeof content === "string") return content;
|
|
13
15
|
return content.filter((p) => p.type === "input_text" || p.type === "output_text").map((p) => p.text ?? "").join("");
|
|
14
16
|
}
|
|
15
|
-
|
|
17
|
+
/**
|
|
18
|
+
* A `custom_tool_call_output.output` is a string or a list of content parts
|
|
19
|
+
* (`input_text` / `input_image` / `input_file`). Flatten a list to its
|
|
20
|
+
* concatenated `input_text` text so text matchers such as
|
|
21
|
+
* `toolResultContains` see it.
|
|
22
|
+
*
|
|
23
|
+
* The value comes straight from the request body, so it is read as `unknown`.
|
|
24
|
+
* Like `function_call_output`, a malformed output never fails the request:
|
|
25
|
+
* any other value (null, an object, a number) yields empty text, and list
|
|
26
|
+
* entries that are not `input_text` parts with string text are skipped.
|
|
27
|
+
*/
|
|
28
|
+
function customToolOutputText(output) {
|
|
29
|
+
if (typeof output === "string") return output;
|
|
30
|
+
if (!Array.isArray(output)) return "";
|
|
31
|
+
return output.map((p) => {
|
|
32
|
+
if (p === null || typeof p !== "object") return "";
|
|
33
|
+
const part = p;
|
|
34
|
+
return part.type === "input_text" && typeof part.text === "string" ? part.text : "";
|
|
35
|
+
}).join("");
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Convert Responses input items into chat messages for fixture matching. The
|
|
39
|
+
* default is the conversion of earlier releases: `custom_tool_call` and
|
|
40
|
+
* `custom_tool_call_output` items are skipped, and history namespaces are not
|
|
41
|
+
* kept. With `extended`, a custom tool call becomes an assistant message with
|
|
42
|
+
* `custom_tool_calls` (so turn counting sees it), its output becomes a `tool`
|
|
43
|
+
* message (so `hasToolResult` and `toolCallId` see it), and a `function_call`
|
|
44
|
+
* keeps its `namespace`.
|
|
45
|
+
*/
|
|
46
|
+
function responsesInputToMessages(req, extended = false) {
|
|
16
47
|
const messages = [];
|
|
17
48
|
const itemReferencePlaceholders = /* @__PURE__ */ new WeakSet();
|
|
18
49
|
if (req.instructions) messages.push({
|
|
@@ -26,6 +57,25 @@ function responsesInputToMessages(req) {
|
|
|
26
57
|
});
|
|
27
58
|
return messages;
|
|
28
59
|
}
|
|
60
|
+
/** A synthesized call for an output item that has no matching call. */
|
|
61
|
+
const attachSynthesizedCall = (target, item) => {
|
|
62
|
+
const id = item.call_id ?? generateToolCallId();
|
|
63
|
+
if (item.type === "custom_tool_call_output") (target.custom_tool_calls ??= []).push({
|
|
64
|
+
id,
|
|
65
|
+
type: "custom",
|
|
66
|
+
name: "",
|
|
67
|
+
input: ""
|
|
68
|
+
});
|
|
69
|
+
else (target.tool_calls ??= []).push({
|
|
70
|
+
id,
|
|
71
|
+
type: "function",
|
|
72
|
+
function: {
|
|
73
|
+
name: "",
|
|
74
|
+
arguments: ""
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
const hasCalls = (m) => m.tool_calls !== void 0 || m.custom_tool_calls !== void 0;
|
|
29
79
|
for (const item of req.input) if (item.role === "system" || item.role === "developer") messages.push({
|
|
30
80
|
role: "system",
|
|
31
81
|
content: extractTextContent(item.content)
|
|
@@ -44,61 +94,55 @@ function responsesInputToMessages(req) {
|
|
|
44
94
|
tool_calls: [{
|
|
45
95
|
id: item.call_id ?? generateToolCallId(),
|
|
46
96
|
type: "function",
|
|
97
|
+
...extended && typeof item.namespace === "string" ? { namespace: item.namespace } : {},
|
|
47
98
|
function: {
|
|
48
99
|
name: item.name ?? "",
|
|
49
100
|
arguments: item.arguments ?? ""
|
|
50
101
|
}
|
|
51
102
|
}]
|
|
52
103
|
});
|
|
53
|
-
else if (item.type === "
|
|
54
|
-
|
|
104
|
+
else if (extended && item.type === "custom_tool_call") messages.push({
|
|
105
|
+
role: "assistant",
|
|
106
|
+
content: null,
|
|
107
|
+
custom_tool_calls: [{
|
|
108
|
+
id: item.call_id ?? generateToolCallId(),
|
|
109
|
+
type: "custom",
|
|
110
|
+
name: item.name ?? "",
|
|
111
|
+
input: typeof item.input === "string" ? item.input : "",
|
|
112
|
+
...typeof item.namespace === "string" ? { namespace: item.namespace } : {}
|
|
113
|
+
}]
|
|
114
|
+
});
|
|
115
|
+
else if (item.type === "function_call_output" || extended && item.type === "custom_tool_call_output") {
|
|
116
|
+
if (!messages.some((m) => m.role === "assistant" && (m.tool_calls?.some((tc) => tc.id === item.call_id) || m.custom_tool_calls?.some((tc) => tc.id === item.call_id)))) {
|
|
55
117
|
const lastMsg = messages[messages.length - 1];
|
|
56
|
-
if (lastMsg && lastMsg.role === "assistant" && itemReferencePlaceholders.has(lastMsg) && !lastMsg
|
|
118
|
+
if (lastMsg && lastMsg.role === "assistant" && itemReferencePlaceholders.has(lastMsg) && !hasCalls(lastMsg)) {
|
|
57
119
|
lastMsg.content = null;
|
|
58
|
-
lastMsg
|
|
59
|
-
id: item.call_id ?? generateToolCallId(),
|
|
60
|
-
type: "function",
|
|
61
|
-
function: {
|
|
62
|
-
name: "",
|
|
63
|
-
arguments: ""
|
|
64
|
-
}
|
|
65
|
-
}];
|
|
120
|
+
attachSynthesizedCall(lastMsg, item);
|
|
66
121
|
itemReferencePlaceholders.delete(lastMsg);
|
|
67
122
|
} else {
|
|
68
123
|
let appended = false;
|
|
69
124
|
for (let k = messages.length - 1; k >= 0; k--) {
|
|
70
125
|
const m = messages[k];
|
|
71
|
-
if (m.role === "assistant" && m
|
|
72
|
-
m
|
|
73
|
-
id: item.call_id ?? generateToolCallId(),
|
|
74
|
-
type: "function",
|
|
75
|
-
function: {
|
|
76
|
-
name: "",
|
|
77
|
-
arguments: ""
|
|
78
|
-
}
|
|
79
|
-
});
|
|
126
|
+
if (m.role === "assistant" && hasCalls(m)) {
|
|
127
|
+
attachSynthesizedCall(m, item);
|
|
80
128
|
appended = true;
|
|
81
129
|
break;
|
|
82
130
|
}
|
|
83
131
|
if (m.role === "user") break;
|
|
84
132
|
}
|
|
85
|
-
if (!appended)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
arguments: ""
|
|
94
|
-
}
|
|
95
|
-
}]
|
|
96
|
-
});
|
|
133
|
+
if (!appended) {
|
|
134
|
+
const synthesized = {
|
|
135
|
+
role: "assistant",
|
|
136
|
+
content: null
|
|
137
|
+
};
|
|
138
|
+
attachSynthesizedCall(synthesized, item);
|
|
139
|
+
messages.push(synthesized);
|
|
140
|
+
}
|
|
97
141
|
}
|
|
98
142
|
}
|
|
99
143
|
messages.push({
|
|
100
144
|
role: "tool",
|
|
101
|
-
content: item.output ?? "",
|
|
145
|
+
content: item.type === "custom_tool_call_output" ? customToolOutputText(item.output) : item.output ?? "",
|
|
102
146
|
tool_call_id: item.call_id
|
|
103
147
|
});
|
|
104
148
|
} else if (item.type === "item_reference") {
|
|
@@ -111,6 +155,7 @@ function responsesInputToMessages(req) {
|
|
|
111
155
|
}
|
|
112
156
|
return messages;
|
|
113
157
|
}
|
|
158
|
+
/** The request tools of earlier releases: top-level `function` tools only. */
|
|
114
159
|
function responsesToolsToCompletionsTools(tools) {
|
|
115
160
|
if (!tools || tools.length === 0) return void 0;
|
|
116
161
|
return tools.filter((t) => t.type === "function").map((t) => ({
|
|
@@ -122,17 +167,146 @@ function responsesToolsToCompletionsTools(tools) {
|
|
|
122
167
|
}
|
|
123
168
|
}));
|
|
124
169
|
}
|
|
125
|
-
function
|
|
170
|
+
/** One Responses `function` or `custom` tool in aimock's normalized form. */
|
|
171
|
+
function normalizeResponsesTool(tool, namespace) {
|
|
172
|
+
if (tool === null || typeof tool !== "object") return void 0;
|
|
173
|
+
const t = tool;
|
|
174
|
+
const ns = namespace !== void 0 ? { namespace } : {};
|
|
175
|
+
if (t.type === "function") {
|
|
176
|
+
const fn = t;
|
|
177
|
+
return {
|
|
178
|
+
type: "function",
|
|
179
|
+
...ns,
|
|
180
|
+
function: {
|
|
181
|
+
name: fn.name,
|
|
182
|
+
description: fn.description,
|
|
183
|
+
parameters: fn.parameters
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (t.type === "custom") {
|
|
188
|
+
const custom = t;
|
|
189
|
+
return {
|
|
190
|
+
type: "custom",
|
|
191
|
+
name: custom.name,
|
|
192
|
+
...custom.description !== void 0 ? { description: custom.description } : {},
|
|
193
|
+
...ns,
|
|
194
|
+
...custom.format !== void 0 ? { format: custom.format } : {}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Input items whose `tools` add to the request's tools:
|
|
200
|
+
* `additional_tools` (Codex responses-lite) and `tool_search_output` (tools
|
|
201
|
+
* loaded by tool search, e.g. Codex's deferred MCP namespaces). Both carry the
|
|
202
|
+
* same function / custom / namespace tool shapes as `req.tools`.
|
|
203
|
+
*/
|
|
204
|
+
function isToolCarryingItemType(type) {
|
|
205
|
+
return type === "additional_tools" || type === "tool_search_output";
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Why a `namespace` tool cannot be flattened, or undefined when it can: its
|
|
209
|
+
* name is not a non-empty string (OpenAI's `NamespaceToolParam` requires
|
|
210
|
+
* `name` with `minLength: 1`), or its `tools` is not an array.
|
|
211
|
+
*/
|
|
212
|
+
function namespaceToolProblem(tool) {
|
|
213
|
+
if (typeof tool.name !== "string" || tool.name === "") return "name is not a non-empty string";
|
|
214
|
+
if (!Array.isArray(tool.tools)) return "tools is not an array";
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Flatten every Responses tool a request offers, for extended matching and for
|
|
218
|
+
* `toolNamespace`. Sources, in order: `req.tools`, then the `tools` of every
|
|
219
|
+
* `additional_tools` or `tool_search_output` input item, in input order.
|
|
220
|
+
* `namespace` tools contribute one entry per inner function/custom tool, each
|
|
221
|
+
* carrying the namespace; other tool types are ignored.
|
|
222
|
+
*
|
|
223
|
+
* Malformed items never fail the request (earlier releases ignored them): a
|
|
224
|
+
* `namespace` tool without a non-empty string name or with a non-array
|
|
225
|
+
* `tools`, a `null` entry, and a tool-carrying item whose `tools` is not an
|
|
226
|
+
* array are dropped, with a warning when `warn` is given.
|
|
227
|
+
*/
|
|
228
|
+
function flattenResponsesTools(req, warn) {
|
|
229
|
+
const tools = [];
|
|
230
|
+
const customTools = [];
|
|
231
|
+
const add = (normalized) => {
|
|
232
|
+
if (!normalized) return;
|
|
233
|
+
if (normalized.type === "custom") customTools.push(normalized);
|
|
234
|
+
else tools.push(normalized);
|
|
235
|
+
};
|
|
236
|
+
const sources = [];
|
|
237
|
+
if (Array.isArray(req.tools)) req.tools.forEach((tool, i) => sources.push({
|
|
238
|
+
tool,
|
|
239
|
+
path: `tools[${i}]`
|
|
240
|
+
}));
|
|
241
|
+
if (Array.isArray(req.input)) req.input.forEach((item, i) => {
|
|
242
|
+
if (!isToolCarryingItemType(item?.type) || item?.tools === void 0) return;
|
|
243
|
+
if (!Array.isArray(item.tools)) {
|
|
244
|
+
warn?.(`Ignoring input[${i}].tools: not an array`);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
item.tools.forEach((tool, j) => sources.push({
|
|
248
|
+
tool,
|
|
249
|
+
path: `input[${i}].tools[${j}]`
|
|
250
|
+
}));
|
|
251
|
+
});
|
|
252
|
+
for (const { tool, path } of sources) {
|
|
253
|
+
const t = tool;
|
|
254
|
+
if (t === null) {
|
|
255
|
+
warn?.(`Ignoring ${path}: null tool`);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (t?.type === "namespace") {
|
|
259
|
+
const problem = namespaceToolProblem(t);
|
|
260
|
+
if (problem) {
|
|
261
|
+
warn?.(`Ignoring namespace tool ${path}: ${problem}`);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
t.tools.forEach((inner, k) => {
|
|
265
|
+
if (inner === null) warn?.(`Ignoring ${path}.tools[${k}]: null tool`);
|
|
266
|
+
else add(normalizeResponsesTool(inner, t.name));
|
|
267
|
+
});
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
add(normalizeResponsesTool(tool));
|
|
271
|
+
}
|
|
126
272
|
return {
|
|
273
|
+
tools,
|
|
274
|
+
customTools,
|
|
275
|
+
sourceCount: sources.length
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function responsesToCompletionRequest(req, options = {}) {
|
|
279
|
+
const extended = options.extended === true;
|
|
280
|
+
const all = flattenResponsesTools(req, extended && options.logger ? (message) => options.logger?.warn(`Responses request: ${message}`) : void 0);
|
|
281
|
+
const completionReq = {
|
|
127
282
|
model: req.model,
|
|
128
|
-
messages: responsesInputToMessages(req),
|
|
283
|
+
messages: responsesInputToMessages(req, extended),
|
|
129
284
|
stream: req.stream,
|
|
130
285
|
temperature: req.temperature,
|
|
131
286
|
max_tokens: req.max_output_tokens,
|
|
132
|
-
tools: responsesToolsToCompletionsTools(req.tools),
|
|
287
|
+
tools: extended ? all.sourceCount > 0 ? all.tools : void 0 : responsesToolsToCompletionsTools(req.tools),
|
|
288
|
+
...extended && all.customTools.length > 0 ? { customTools: all.customTools } : {},
|
|
133
289
|
tool_choice: req.tool_choice,
|
|
134
290
|
response_format: req.response_format
|
|
135
291
|
};
|
|
292
|
+
setResponsesOfferedTools(completionReq, {
|
|
293
|
+
tools: all.tools,
|
|
294
|
+
customTools: all.customTools
|
|
295
|
+
});
|
|
296
|
+
return completionReq;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* The HTTP request check of earlier releases for `tools`: keep the
|
|
300
|
+
* converter's empty/falsy bypass, and reject only a collection the legacy
|
|
301
|
+
* converter would throw on (not an array, or a `null` entry). Malformed
|
|
302
|
+
* nested tools (inside a `namespace` tool or a tool-carrying input item) are
|
|
303
|
+
* dropped by the conversion instead.
|
|
304
|
+
*/
|
|
305
|
+
function validateResponsesTools(tools) {
|
|
306
|
+
if (tools && tools.length !== 0) {
|
|
307
|
+
if (!Array.isArray(tools)) return "tools must be an array";
|
|
308
|
+
if (tools.some((tool) => tool === null)) return "tools entries must not be null";
|
|
309
|
+
}
|
|
136
310
|
}
|
|
137
311
|
function responsesStatus(finishReason, defaultStatus) {
|
|
138
312
|
if (!finishReason) return defaultStatus;
|
|
@@ -184,56 +358,101 @@ function requireFixtureToolArguments(value) {
|
|
|
184
358
|
if (typeof value !== "string") throw new Error("Invalid fixture tool call: \"arguments\" must be a string after normalization");
|
|
185
359
|
return value;
|
|
186
360
|
}
|
|
361
|
+
function requireFixtureToolInput(value) {
|
|
362
|
+
if (typeof value !== "string") throw new Error("Invalid fixture tool call: \"input\" must be a string for a custom tool call");
|
|
363
|
+
return value;
|
|
364
|
+
}
|
|
365
|
+
function isCustomEmittable(call) {
|
|
366
|
+
if (call === null || typeof call !== "object") return false;
|
|
367
|
+
return call.type === "custom" || call.type === "customToolCall";
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* A Responses `function_call` item. `namespace` is added only when the fixture
|
|
371
|
+
* sets it, so a call without one keeps the keys and key order it had before
|
|
372
|
+
* namespaces existed (existing fixtures stay byte-identical on the wire).
|
|
373
|
+
*/
|
|
374
|
+
function functionCallItem(call, fcId, callId, args, status) {
|
|
375
|
+
return {
|
|
376
|
+
type: "function_call",
|
|
377
|
+
id: fcId,
|
|
378
|
+
call_id: callId,
|
|
379
|
+
...call.namespace !== void 0 ? { namespace: call.namespace } : {},
|
|
380
|
+
name: call.name,
|
|
381
|
+
arguments: args,
|
|
382
|
+
status
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* `ctc_…` item id, the prefix the OpenAI guide and Codex's item-id map use for
|
|
387
|
+
* custom tool calls. The underscore separator is deliberate: it matches the
|
|
388
|
+
* real id format, so this does not use `generateId` (which would give
|
|
389
|
+
* `ctc-…`, like aimock's own `fc-…` ids).
|
|
390
|
+
*/
|
|
391
|
+
function customToolCallItemId() {
|
|
392
|
+
return `ctc_${randomBytes(12).toString("base64url")}`;
|
|
393
|
+
}
|
|
394
|
+
/** A Responses `custom_tool_call` item; `namespace` is added only when set. */
|
|
395
|
+
function customToolCallItem(call, ctcId, callId, input, status) {
|
|
396
|
+
return {
|
|
397
|
+
type: "custom_tool_call",
|
|
398
|
+
id: ctcId,
|
|
399
|
+
call_id: callId,
|
|
400
|
+
...call.namespace !== void 0 ? { namespace: call.namespace } : {},
|
|
401
|
+
name: call.name,
|
|
402
|
+
input,
|
|
403
|
+
status
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* A `toolCalls` entry as earlier releases served it: a function call whose
|
|
408
|
+
* `type`, `input` and `namespace` are ignored. The exported builders take
|
|
409
|
+
* fixtures in that form; the handler passes `namespace` (extended mode) and
|
|
410
|
+
* custom calls to the internal builders instead.
|
|
411
|
+
*/
|
|
412
|
+
function legacyFunctionCall(call) {
|
|
413
|
+
if (call === null || typeof call !== "object") return call;
|
|
414
|
+
const { name, arguments: args, id } = call;
|
|
415
|
+
return id !== void 0 ? {
|
|
416
|
+
name,
|
|
417
|
+
arguments: args,
|
|
418
|
+
id
|
|
419
|
+
} : {
|
|
420
|
+
name,
|
|
421
|
+
arguments: args
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* The exported builders' `toolCalls` as legacy function calls. It walks
|
|
426
|
+
* `toolCalls` by index up to `toolCalls.length`, as earlier releases did, so an
|
|
427
|
+
* untyped caller's non-array value gives the same calls: none for an object
|
|
428
|
+
* without `length`, and entries by index for a string or array-like.
|
|
429
|
+
*/
|
|
430
|
+
function legacyFunctionCalls(toolCalls) {
|
|
431
|
+
const calls = [];
|
|
432
|
+
for (let idx = 0; idx < toolCalls.length; idx++) calls.push(legacyFunctionCall(toolCalls[idx]));
|
|
433
|
+
return calls;
|
|
434
|
+
}
|
|
435
|
+
/** Resolve exported-builder `blocks` as earlier releases did (no namespace emitted). */
|
|
436
|
+
function legacyOrderedBlocks(blocks) {
|
|
437
|
+
if (!blocks || blocks.length === 0) return void 0;
|
|
438
|
+
return resolveFixtureBlocks(blocks).map((block) => {
|
|
439
|
+
if (block.type === "text" || block.namespace === void 0) return block;
|
|
440
|
+
const copy = { ...block };
|
|
441
|
+
delete copy.namespace;
|
|
442
|
+
return copy;
|
|
443
|
+
});
|
|
444
|
+
}
|
|
187
445
|
function buildToolCallStreamEvents(toolCalls, model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
|
|
446
|
+
return toolCallStreamEvents(legacyFunctionCalls(toolCalls), model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
447
|
+
}
|
|
448
|
+
function toolCallStreamEvents(toolCalls, model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
|
|
449
|
+
assertResponsesToolCalls(toolCalls);
|
|
188
450
|
const { respId, created, events, prefixOutputItems, nextOutputIndex } = buildResponsePreamble(model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
189
451
|
const fcOutputItems = [];
|
|
190
452
|
for (let idx = 0; idx < toolCalls.length; idx++) {
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const outputIndex = nextOutputIndex + idx;
|
|
195
|
-
events.push({
|
|
196
|
-
type: "response.output_item.added",
|
|
197
|
-
output_index: outputIndex,
|
|
198
|
-
item: {
|
|
199
|
-
type: "function_call",
|
|
200
|
-
id: fcId,
|
|
201
|
-
call_id: callId,
|
|
202
|
-
name: tc.name,
|
|
203
|
-
arguments: "",
|
|
204
|
-
status: "in_progress"
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
const args = requireFixtureToolArguments(tc.arguments);
|
|
208
|
-
for (let i = 0; i < args.length; i += chunkSize) {
|
|
209
|
-
const slice = args.slice(i, i + chunkSize);
|
|
210
|
-
events.push({
|
|
211
|
-
type: "response.function_call_arguments.delta",
|
|
212
|
-
item_id: fcId,
|
|
213
|
-
output_index: outputIndex,
|
|
214
|
-
delta: slice
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
events.push({
|
|
218
|
-
type: "response.function_call_arguments.done",
|
|
219
|
-
item_id: fcId,
|
|
220
|
-
output_index: outputIndex,
|
|
221
|
-
arguments: args
|
|
222
|
-
});
|
|
223
|
-
const doneItem = {
|
|
224
|
-
type: "function_call",
|
|
225
|
-
id: fcId,
|
|
226
|
-
call_id: callId,
|
|
227
|
-
name: tc.name,
|
|
228
|
-
arguments: args,
|
|
229
|
-
status: "completed"
|
|
230
|
-
};
|
|
231
|
-
events.push({
|
|
232
|
-
type: "response.output_item.done",
|
|
233
|
-
output_index: outputIndex,
|
|
234
|
-
item: doneItem
|
|
235
|
-
});
|
|
236
|
-
fcOutputItems.push(doneItem);
|
|
453
|
+
const { events: callEvents, item } = buildToolCallOutputEvents(toolCalls[idx], chunkSize, nextOutputIndex + idx);
|
|
454
|
+
events.push(...callEvents);
|
|
455
|
+
fcOutputItems.push(item);
|
|
237
456
|
}
|
|
238
457
|
events.push({
|
|
239
458
|
type: "response.completed",
|
|
@@ -464,7 +683,7 @@ function buildWebSearchStreamEvents(queries, startOutputIndex) {
|
|
|
464
683
|
}
|
|
465
684
|
return events;
|
|
466
685
|
}
|
|
467
|
-
function buildResponsePreamble(model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
|
|
686
|
+
function buildResponsePreamble(model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false, forceReasoning = false) {
|
|
468
687
|
const respId = overrides?.id ?? responseId();
|
|
469
688
|
const created = overrides?.created ?? Math.floor(Date.now() / 1e3);
|
|
470
689
|
const effectiveModel = overrides?.model ?? model;
|
|
@@ -493,7 +712,7 @@ function buildResponsePreamble(model, chunkSize, reasoning, webSearches, overrid
|
|
|
493
712
|
output: []
|
|
494
713
|
}
|
|
495
714
|
});
|
|
496
|
-
if (reasoning || shouldSynthesizeBlobOnlyReasoning(reasoning, model, synthesizeSummarylessReasoning)) {
|
|
715
|
+
if (reasoning || forceReasoning || shouldSynthesizeBlobOnlyReasoning(reasoning, model, synthesizeSummarylessReasoning)) {
|
|
497
716
|
const reasoningEvents = buildReasoningStreamEvents(reasoning, chunkSize, emitEncryptedReasoning);
|
|
498
717
|
events.push(...reasoningEvents);
|
|
499
718
|
const doneEvent = reasoningEvents.find((e) => e.type === "response.output_item.done" && e.item?.type === "reasoning");
|
|
@@ -589,10 +808,10 @@ function buildMessageOutputEvents(content, chunkSize, outputIndex) {
|
|
|
589
808
|
/**
|
|
590
809
|
* Emit the output_item.added → arguments deltas → arguments.done →
|
|
591
810
|
* output_item.done events for a single function_call at `outputIndex`,
|
|
592
|
-
* returning the completed item for the final `output` array.
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
811
|
+
* returning the completed item for the final `output` array. Every Responses
|
|
812
|
+
* streaming builder (tool-only, legacy content+toolCalls and ordered blocks)
|
|
813
|
+
* reaches this through {@link buildToolCallOutputEvents}, so a given
|
|
814
|
+
* (tool, outputIndex) has the same wire output on every path.
|
|
596
815
|
*/
|
|
597
816
|
function buildFunctionCallOutputEvents(toolCall, chunkSize, outputIndex) {
|
|
598
817
|
const callId = toolCall.id || generateToolCallId();
|
|
@@ -602,14 +821,7 @@ function buildFunctionCallOutputEvents(toolCall, chunkSize, outputIndex) {
|
|
|
602
821
|
events.push({
|
|
603
822
|
type: "response.output_item.added",
|
|
604
823
|
output_index: outputIndex,
|
|
605
|
-
item:
|
|
606
|
-
type: "function_call",
|
|
607
|
-
id: fcId,
|
|
608
|
-
call_id: callId,
|
|
609
|
-
name: toolCall.name,
|
|
610
|
-
arguments: "",
|
|
611
|
-
status: "in_progress"
|
|
612
|
-
}
|
|
824
|
+
item: functionCallItem(toolCall, fcId, callId, "", "in_progress")
|
|
613
825
|
});
|
|
614
826
|
for (let i = 0; i < args.length; i += chunkSize) events.push({
|
|
615
827
|
type: "response.function_call_arguments.delta",
|
|
@@ -623,24 +835,78 @@ function buildFunctionCallOutputEvents(toolCall, chunkSize, outputIndex) {
|
|
|
623
835
|
output_index: outputIndex,
|
|
624
836
|
arguments: args
|
|
625
837
|
});
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
838
|
+
const item = functionCallItem(toolCall, fcId, callId, args, "completed");
|
|
839
|
+
events.push({
|
|
840
|
+
type: "response.output_item.done",
|
|
841
|
+
output_index: outputIndex,
|
|
842
|
+
item
|
|
843
|
+
});
|
|
844
|
+
return {
|
|
845
|
+
events,
|
|
846
|
+
item
|
|
633
847
|
};
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* `text` cut into slices of at most `chunkSize` UTF-16 units, never between
|
|
851
|
+
* the two halves of a surrogate pair: a cut that would land inside a pair
|
|
852
|
+
* moves back before it, or, when that would leave the slice empty
|
|
853
|
+
* (`chunkSize` 1), forward past it. The slices rejoin to `text` exactly.
|
|
854
|
+
*/
|
|
855
|
+
function surrogateSafeChunks(text, chunkSize) {
|
|
856
|
+
const chunks = [];
|
|
857
|
+
for (let i = 0; i < text.length;) {
|
|
858
|
+
let end = Math.min(i + chunkSize, text.length);
|
|
859
|
+
const high = text.charCodeAt(end - 1);
|
|
860
|
+
const low = text.charCodeAt(end);
|
|
861
|
+
if (high >= 55296 && high <= 56319 && low >= 56320 && low <= 57343) end = end - 1 > i ? end - 1 : end + 1;
|
|
862
|
+
chunks.push(text.slice(i, end));
|
|
863
|
+
i = end;
|
|
864
|
+
}
|
|
865
|
+
return chunks;
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Emit output_item.added → custom_tool_call_input.delta×N →
|
|
869
|
+
* custom_tool_call_input.done → output_item.done for one `custom_tool_call` at
|
|
870
|
+
* `outputIndex`, returning the completed item. An empty `input`
|
|
871
|
+
* yields no delta event.
|
|
872
|
+
*/
|
|
873
|
+
function buildCustomToolCallOutputEvents(call, chunkSize, outputIndex) {
|
|
874
|
+
const callId = call.id || generateToolCallId();
|
|
875
|
+
const ctcId = customToolCallItemId();
|
|
876
|
+
const input = requireFixtureToolInput(call.input);
|
|
877
|
+
const events = [];
|
|
878
|
+
events.push({
|
|
879
|
+
type: "response.output_item.added",
|
|
880
|
+
output_index: outputIndex,
|
|
881
|
+
item: customToolCallItem(call, ctcId, callId, "", "in_progress")
|
|
882
|
+
});
|
|
883
|
+
for (const delta of surrogateSafeChunks(input, chunkSize)) events.push({
|
|
884
|
+
type: "response.custom_tool_call_input.delta",
|
|
885
|
+
item_id: ctcId,
|
|
886
|
+
output_index: outputIndex,
|
|
887
|
+
delta
|
|
888
|
+
});
|
|
889
|
+
events.push({
|
|
890
|
+
type: "response.custom_tool_call_input.done",
|
|
891
|
+
item_id: ctcId,
|
|
892
|
+
output_index: outputIndex,
|
|
893
|
+
input
|
|
894
|
+
});
|
|
895
|
+
const item = customToolCallItem(call, ctcId, callId, input, "completed");
|
|
634
896
|
events.push({
|
|
635
897
|
type: "response.output_item.done",
|
|
636
898
|
output_index: outputIndex,
|
|
637
|
-
item
|
|
899
|
+
item
|
|
638
900
|
});
|
|
639
901
|
return {
|
|
640
902
|
events,
|
|
641
|
-
|
|
903
|
+
item
|
|
642
904
|
};
|
|
643
905
|
}
|
|
906
|
+
/** Streaming dispatcher: one function or custom tool call at `outputIndex`. */
|
|
907
|
+
function buildToolCallOutputEvents(call, chunkSize, outputIndex) {
|
|
908
|
+
return isCustomEmittable(call) ? buildCustomToolCallOutputEvents(call, chunkSize, outputIndex) : buildFunctionCallOutputEvents(call, chunkSize, outputIndex);
|
|
909
|
+
}
|
|
644
910
|
function buildOutputPrefix(content, model, reasoning, webSearches, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
|
|
645
911
|
const output = [];
|
|
646
912
|
if (reasoning || shouldSynthesizeBlobOnlyReasoning(reasoning, model, synthesizeSummarylessReasoning)) output.push(buildReasoningOutputItem(generateId("rs"), {
|
|
@@ -684,6 +950,7 @@ function buildTextResponse(content, model, reasoning, webSearches, overrides, em
|
|
|
684
950
|
return buildResponseEnvelope(model, buildOutputPrefix(content, model, reasoning, webSearches, emitEncryptedReasoning, synthesizeSummarylessReasoning), overrides);
|
|
685
951
|
}
|
|
686
952
|
function buildToolCallResponse(toolCalls, model, reasoning, webSearches, overrides, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
|
|
953
|
+
assertResponsesToolCalls(toolCalls);
|
|
687
954
|
const output = [];
|
|
688
955
|
if (reasoning || shouldSynthesizeBlobOnlyReasoning(reasoning, model, synthesizeSummarylessReasoning)) output.push(buildReasoningOutputItem(generateId("rs"), {
|
|
689
956
|
summaryText: reasoning,
|
|
@@ -698,21 +965,22 @@ function buildToolCallResponse(toolCalls, model, reasoning, webSearches, overrid
|
|
|
698
965
|
query
|
|
699
966
|
}
|
|
700
967
|
});
|
|
701
|
-
for (const tc of toolCalls) output.push(
|
|
702
|
-
type: "function_call",
|
|
703
|
-
id: generateId("fc"),
|
|
704
|
-
call_id: tc.id || generateToolCallId(),
|
|
705
|
-
name: tc.name,
|
|
706
|
-
arguments: requireFixtureToolArguments(tc.arguments),
|
|
707
|
-
status: "completed"
|
|
708
|
-
});
|
|
968
|
+
for (const tc of toolCalls) output.push(buildToolCallOutputItem(tc));
|
|
709
969
|
return buildResponseEnvelope(model, output, overrides);
|
|
710
970
|
}
|
|
711
971
|
function buildContentWithToolCallsStreamEvents(content, toolCalls, model, chunkSize, reasoning, webSearches, overrides, blocks, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
|
|
972
|
+
const ordered = legacyOrderedBlocks(blocks);
|
|
973
|
+
return contentWithToolCallsStreamEvents(content, ordered && ordered.length > 0 ? [] : legacyFunctionCalls(toolCalls), model, chunkSize, reasoning, webSearches, overrides, ordered, emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Internal content+tool-calls builder. `ordered` is already resolved (see
|
|
977
|
+
* {@link resolveServedBlockOutcome}); when non-empty it is streamed in array
|
|
978
|
+
* order, otherwise the legacy message-first path runs over `toolCalls`.
|
|
979
|
+
*/
|
|
980
|
+
function contentWithToolCallsStreamEvents(content, toolCalls, model, chunkSize, reasoning, webSearches, overrides, ordered, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
|
|
712
981
|
const { respId, created, events, prefixOutputItems, nextOutputIndex } = buildResponsePreamble(model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
713
982
|
const orderedOutputItems = [];
|
|
714
|
-
if (
|
|
715
|
-
const ordered = resolveFixtureBlocks(blocks);
|
|
983
|
+
if (ordered && ordered.length > 0) {
|
|
716
984
|
let outputIndex = nextOutputIndex;
|
|
717
985
|
for (const block of ordered) {
|
|
718
986
|
if (block.type === "text") {
|
|
@@ -720,25 +988,21 @@ function buildContentWithToolCallsStreamEvents(content, toolCalls, model, chunkS
|
|
|
720
988
|
events.push(...msgEvents);
|
|
721
989
|
orderedOutputItems.push(msgItem);
|
|
722
990
|
} else {
|
|
723
|
-
const { events:
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
id: block.id
|
|
727
|
-
}, chunkSize, outputIndex);
|
|
728
|
-
events.push(...fcEvents);
|
|
729
|
-
orderedOutputItems.push(fcItem);
|
|
991
|
+
const { events: callEvents, item } = buildToolCallOutputEvents(block, chunkSize, outputIndex);
|
|
992
|
+
events.push(...callEvents);
|
|
993
|
+
orderedOutputItems.push(item);
|
|
730
994
|
}
|
|
731
995
|
outputIndex += 1;
|
|
732
996
|
}
|
|
733
997
|
} else {
|
|
998
|
+
assertResponsesToolCalls(toolCalls);
|
|
734
999
|
const { events: msgEvents, msgItem } = buildMessageOutputEvents(content, chunkSize, nextOutputIndex);
|
|
735
1000
|
events.push(...msgEvents);
|
|
736
1001
|
orderedOutputItems.push(msgItem);
|
|
737
1002
|
for (let idx = 0; idx < toolCalls.length; idx++) {
|
|
738
|
-
const
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
orderedOutputItems.push(fcItem);
|
|
1003
|
+
const { events: callEvents, item } = buildToolCallOutputEvents(toolCalls[idx], chunkSize, nextOutputIndex + 1 + idx);
|
|
1004
|
+
events.push(...callEvents);
|
|
1005
|
+
orderedOutputItems.push(item);
|
|
742
1006
|
}
|
|
743
1007
|
}
|
|
744
1008
|
events.push({
|
|
@@ -755,15 +1019,10 @@ function buildContentWithToolCallsStreamEvents(content, toolCalls, model, chunkS
|
|
|
755
1019
|
});
|
|
756
1020
|
return events;
|
|
757
1021
|
}
|
|
758
|
-
function
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
call_id: tc.id || generateToolCallId(),
|
|
763
|
-
name: tc.name,
|
|
764
|
-
arguments: requireFixtureToolArguments(tc.arguments),
|
|
765
|
-
status: "completed"
|
|
766
|
-
};
|
|
1022
|
+
/** Non-streaming dispatcher: the completed function or custom tool call item. */
|
|
1023
|
+
function buildToolCallOutputItem(tc) {
|
|
1024
|
+
if (isCustomEmittable(tc)) return customToolCallItem(tc, customToolCallItemId(), tc.id || generateToolCallId(), requireFixtureToolInput(tc.input), "completed");
|
|
1025
|
+
return functionCallItem(tc, generateId("fc"), tc.id || generateToolCallId(), requireFixtureToolArguments(tc.arguments), "completed");
|
|
767
1026
|
}
|
|
768
1027
|
function buildMessageOutputItem(content) {
|
|
769
1028
|
return {
|
|
@@ -778,9 +1037,8 @@ function buildMessageOutputItem(content) {
|
|
|
778
1037
|
}]
|
|
779
1038
|
};
|
|
780
1039
|
}
|
|
781
|
-
function buildContentWithToolCallsResponse(content, toolCalls, model, reasoning, webSearches, overrides,
|
|
782
|
-
if (
|
|
783
|
-
const ordered = resolveFixtureBlocks(blocks);
|
|
1040
|
+
function buildContentWithToolCallsResponse(content, toolCalls, model, reasoning, webSearches, overrides, ordered, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
|
|
1041
|
+
if (ordered && ordered.length > 0) {
|
|
784
1042
|
const output = [];
|
|
785
1043
|
if (reasoning || shouldSynthesizeBlobOnlyReasoning(reasoning, model, synthesizeSummarylessReasoning)) output.push(buildReasoningOutputItem(generateId("rs"), {
|
|
786
1044
|
summaryText: reasoning,
|
|
@@ -796,15 +1054,12 @@ function buildContentWithToolCallsResponse(content, toolCalls, model, reasoning,
|
|
|
796
1054
|
}
|
|
797
1055
|
});
|
|
798
1056
|
for (const block of ordered) if (block.type === "text") output.push(buildMessageOutputItem(block.text));
|
|
799
|
-
else output.push(
|
|
800
|
-
name: block.name,
|
|
801
|
-
arguments: block.arguments,
|
|
802
|
-
id: block.id
|
|
803
|
-
}));
|
|
1057
|
+
else output.push(buildToolCallOutputItem(block));
|
|
804
1058
|
return buildResponseEnvelope(model, output, overrides);
|
|
805
1059
|
}
|
|
1060
|
+
assertResponsesToolCalls(toolCalls);
|
|
806
1061
|
const output = buildOutputPrefix(content, model, reasoning, webSearches, emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
807
|
-
for (const tc of toolCalls) output.push(
|
|
1062
|
+
for (const tc of toolCalls) output.push(buildToolCallOutputItem(tc));
|
|
808
1063
|
return buildResponseEnvelope(model, output, overrides);
|
|
809
1064
|
}
|
|
810
1065
|
async function writeResponsesSSEStream(res, events, optionsOrLatency) {
|
|
@@ -885,12 +1140,7 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
|
|
|
885
1140
|
break;
|
|
886
1141
|
}
|
|
887
1142
|
}
|
|
888
|
-
|
|
889
|
-
if (responsesReq.tools && responsesReq.tools.length !== 0) {
|
|
890
|
-
if (!Array.isArray(responsesReq.tools)) toolsError = "tools must be an array";
|
|
891
|
-
else if (responsesReq.tools.some((tool) => tool === null)) toolsError = "tools entries must not be null";
|
|
892
|
-
}
|
|
893
|
-
const validationError = inputError ?? toolsError;
|
|
1143
|
+
const validationError = inputError ?? validateResponsesTools(responsesReq.tools);
|
|
894
1144
|
if (validationError) {
|
|
895
1145
|
journal.add({
|
|
896
1146
|
method: req.method ?? "POST",
|
|
@@ -908,7 +1158,11 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
|
|
|
908
1158
|
} }));
|
|
909
1159
|
return;
|
|
910
1160
|
}
|
|
911
|
-
const
|
|
1161
|
+
const extendedTools = defaults.responsesTools === "extended";
|
|
1162
|
+
const completionReq = responsesToCompletionRequest(responsesReq, {
|
|
1163
|
+
extended: extendedTools,
|
|
1164
|
+
logger: defaults.logger
|
|
1165
|
+
});
|
|
912
1166
|
completionReq._endpointType = "chat";
|
|
913
1167
|
completionReq._context = getContext(req);
|
|
914
1168
|
const emitEncryptedReasoning = requestWantsEncryptedReasoning(responsesReq);
|
|
@@ -984,27 +1238,116 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
|
|
|
984
1238
|
} }));
|
|
985
1239
|
return;
|
|
986
1240
|
}
|
|
987
|
-
const
|
|
1241
|
+
const resolvedResponse = await resolveResponse(fixture, completionReq);
|
|
1242
|
+
const response = extendedTools ? resolvedResponse : withoutLegacyNamespaces(resolvedResponse);
|
|
1243
|
+
const misbehavior = planMisbehavior({
|
|
1244
|
+
wire: "openai-responses",
|
|
1245
|
+
fixture,
|
|
1246
|
+
response,
|
|
1247
|
+
request: completionReq,
|
|
1248
|
+
stream: responsesReq.stream === true,
|
|
1249
|
+
emitsToolCallIds: true,
|
|
1250
|
+
defaults,
|
|
1251
|
+
rawHeaders: req.headers,
|
|
1252
|
+
url: req.url
|
|
1253
|
+
});
|
|
1254
|
+
if (misbehavior.kind === "error") {
|
|
1255
|
+
recordMisbehaviorOutcome({
|
|
1256
|
+
entry: journal.add({
|
|
1257
|
+
method: req.method ?? "POST",
|
|
1258
|
+
path: req.url ?? "/v1/responses",
|
|
1259
|
+
headers: flattenHeaders(req.headers),
|
|
1260
|
+
body: completionReq,
|
|
1261
|
+
response: {
|
|
1262
|
+
status: misbehavior.status,
|
|
1263
|
+
fixture
|
|
1264
|
+
}
|
|
1265
|
+
}),
|
|
1266
|
+
summary: misbehavior.summary,
|
|
1267
|
+
defaults,
|
|
1268
|
+
testId
|
|
1269
|
+
});
|
|
1270
|
+
if (!misbehavior.summary?.evaluations.some((evaluation) => evaluation.outcome === "error")) defaults.logger.error(misbehavior.message);
|
|
1271
|
+
writeErrorResponse(res, misbehavior.status, JSON.stringify({ error: {
|
|
1272
|
+
message: misbehavior.message,
|
|
1273
|
+
type: "invalid_request_error",
|
|
1274
|
+
code: misbehavior.code
|
|
1275
|
+
} }));
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
988
1278
|
const latency = fixture.latency ?? defaults.latency;
|
|
989
1279
|
const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
|
|
990
1280
|
const fixtureTimings = fixture.recordedTimings;
|
|
991
1281
|
const effectiveReplaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
|
|
992
|
-
if (
|
|
993
|
-
const
|
|
994
|
-
|
|
1282
|
+
if (misbehavior.kind === "applied") {
|
|
1283
|
+
const prepared = prepareResponsesMisbehavior(misbehavior);
|
|
1284
|
+
const output = buildResponsesMisbehavior(prepared, completionReq, chunkSize, emitEncryptedReasoning);
|
|
1285
|
+
const entry = journal.add({
|
|
995
1286
|
method: req.method ?? "POST",
|
|
996
1287
|
path: req.url ?? "/v1/responses",
|
|
997
1288
|
headers: flattenHeaders(req.headers),
|
|
998
1289
|
body: completionReq,
|
|
999
1290
|
response: {
|
|
1000
|
-
status,
|
|
1291
|
+
status: 200,
|
|
1001
1292
|
fixture
|
|
1002
1293
|
}
|
|
1003
1294
|
});
|
|
1295
|
+
recordMisbehaviorOutcome({
|
|
1296
|
+
entry,
|
|
1297
|
+
summary: prepared.summary,
|
|
1298
|
+
defaults,
|
|
1299
|
+
testId
|
|
1300
|
+
});
|
|
1301
|
+
if (responsesReq.stream !== true) {
|
|
1302
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1303
|
+
res.end(JSON.stringify(output.response));
|
|
1304
|
+
} else {
|
|
1305
|
+
const appliedTimings = prepared.duplicateId && fixtureTimings ? {
|
|
1306
|
+
...fixtureTimings,
|
|
1307
|
+
interChunkDelaysMs: [...fixtureTimings.interChunkDelaysMs]
|
|
1308
|
+
} : fixtureTimings;
|
|
1309
|
+
if (prepared.duplicateId && appliedTimings) {
|
|
1310
|
+
const lastGap = appliedTimings.interChunkDelaysMs.at(-1) ?? 0;
|
|
1311
|
+
while (appliedTimings.interChunkDelaysMs.length < output.events.length - 1) appliedTimings.interChunkDelaysMs.push(lastGap);
|
|
1312
|
+
}
|
|
1313
|
+
const interruption = createInterruptionSignal(fixture);
|
|
1314
|
+
if (!await writeResponsesSSEStream(res, output.events, {
|
|
1315
|
+
latency,
|
|
1316
|
+
streamingProfile: fixture.streamingProfile,
|
|
1317
|
+
recordedTimings: appliedTimings,
|
|
1318
|
+
replaySpeed: effectiveReplaySpeed,
|
|
1319
|
+
signal: interruption?.signal,
|
|
1320
|
+
onChunkSent: interruption?.tick
|
|
1321
|
+
})) {
|
|
1322
|
+
if (!res.writableEnded) res.destroy();
|
|
1323
|
+
entry.response.interrupted = true;
|
|
1324
|
+
entry.response.interruptReason = interruption?.reason();
|
|
1325
|
+
}
|
|
1326
|
+
interruption?.cleanup();
|
|
1327
|
+
}
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
if (isErrorResponse(response)) {
|
|
1331
|
+
const status = response.status ?? 500;
|
|
1332
|
+
recordMisbehaviorOutcome({
|
|
1333
|
+
entry: journal.add({
|
|
1334
|
+
method: req.method ?? "POST",
|
|
1335
|
+
path: req.url ?? "/v1/responses",
|
|
1336
|
+
headers: flattenHeaders(req.headers),
|
|
1337
|
+
body: completionReq,
|
|
1338
|
+
response: {
|
|
1339
|
+
status,
|
|
1340
|
+
fixture
|
|
1341
|
+
}
|
|
1342
|
+
}),
|
|
1343
|
+
summary: misbehavior.summary,
|
|
1344
|
+
defaults,
|
|
1345
|
+
testId
|
|
1346
|
+
});
|
|
1004
1347
|
writeErrorResponse(res, status, serializeErrorResponse(response), { retryAfter: response.retryAfter });
|
|
1005
1348
|
return;
|
|
1006
1349
|
}
|
|
1007
|
-
if (
|
|
1350
|
+
if (isCombinedFixtureResponse(response)) {
|
|
1008
1351
|
const overrides = extractOverrides(response);
|
|
1009
1352
|
const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
|
|
1010
1353
|
const effReasoning = resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger);
|
|
@@ -1018,12 +1361,21 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
|
|
|
1018
1361
|
fixture
|
|
1019
1362
|
}
|
|
1020
1363
|
});
|
|
1364
|
+
recordMisbehaviorOutcome({
|
|
1365
|
+
entry: journalEntry,
|
|
1366
|
+
summary: misbehavior.summary,
|
|
1367
|
+
defaults,
|
|
1368
|
+
testId
|
|
1369
|
+
});
|
|
1370
|
+
const ordered = resolveServedBlockOutcome(response)?.ordered;
|
|
1371
|
+
if (!ordered) assertCustomToolCalls(response);
|
|
1372
|
+
const toolCalls = servedToolCalls(response);
|
|
1021
1373
|
if (responsesReq.stream !== true) {
|
|
1022
|
-
const body = buildContentWithToolCallsResponse(response.content ?? "",
|
|
1374
|
+
const body = buildContentWithToolCallsResponse(response.content ?? "", toolCalls, completionReq.model, effReasoning, response.webSearches, overrides, ordered, emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
1023
1375
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1024
1376
|
res.end(JSON.stringify(body));
|
|
1025
1377
|
} else {
|
|
1026
|
-
const events =
|
|
1378
|
+
const events = contentWithToolCallsStreamEvents(response.content ?? "", toolCalls, completionReq.model, chunkSize, effReasoning, response.webSearches, overrides, ordered, emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
1027
1379
|
const interruption = createInterruptionSignal(fixture);
|
|
1028
1380
|
if (!await writeResponsesSSEStream(res, events, {
|
|
1029
1381
|
latency,
|
|
@@ -1055,6 +1407,12 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
|
|
|
1055
1407
|
fixture
|
|
1056
1408
|
}
|
|
1057
1409
|
});
|
|
1410
|
+
recordMisbehaviorOutcome({
|
|
1411
|
+
entry: journalEntry,
|
|
1412
|
+
summary: misbehavior.summary,
|
|
1413
|
+
defaults,
|
|
1414
|
+
testId
|
|
1415
|
+
});
|
|
1058
1416
|
if (responsesReq.stream !== true) {
|
|
1059
1417
|
const body = buildTextResponse(response.content, completionReq.model, effReasoning, response.webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
1060
1418
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -1092,12 +1450,19 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
|
|
|
1092
1450
|
fixture
|
|
1093
1451
|
}
|
|
1094
1452
|
});
|
|
1453
|
+
recordMisbehaviorOutcome({
|
|
1454
|
+
entry: journalEntry,
|
|
1455
|
+
summary: misbehavior.summary,
|
|
1456
|
+
defaults,
|
|
1457
|
+
testId
|
|
1458
|
+
});
|
|
1459
|
+
assertCustomToolCalls(response);
|
|
1095
1460
|
if (responsesReq.stream !== true) {
|
|
1096
|
-
const body = buildToolCallResponse(response
|
|
1461
|
+
const body = buildToolCallResponse(servedToolCalls(response), completionReq.model, effReasoning, response.webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
1097
1462
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1098
1463
|
res.end(JSON.stringify(body));
|
|
1099
1464
|
} else {
|
|
1100
|
-
const events =
|
|
1465
|
+
const events = toolCallStreamEvents(servedToolCalls(response), completionReq.model, chunkSize, effReasoning, response.webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
|
|
1101
1466
|
const interruption = createInterruptionSignal(fixture);
|
|
1102
1467
|
if (!await writeResponsesSSEStream(res, events, {
|
|
1103
1468
|
latency,
|
|
@@ -1115,22 +1480,148 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
|
|
|
1115
1480
|
}
|
|
1116
1481
|
return;
|
|
1117
1482
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1483
|
+
recordMisbehaviorOutcome({
|
|
1484
|
+
entry: journal.add({
|
|
1485
|
+
method: req.method ?? "POST",
|
|
1486
|
+
path: req.url ?? "/v1/responses",
|
|
1487
|
+
headers: flattenHeaders(req.headers),
|
|
1488
|
+
body: completionReq,
|
|
1489
|
+
response: {
|
|
1490
|
+
status: 500,
|
|
1491
|
+
fixture
|
|
1492
|
+
}
|
|
1493
|
+
}),
|
|
1494
|
+
summary: misbehavior.summary,
|
|
1495
|
+
defaults,
|
|
1496
|
+
testId
|
|
1127
1497
|
});
|
|
1128
1498
|
writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1129
1499
|
message: "Fixture response did not match any known type",
|
|
1130
1500
|
type: "server_error"
|
|
1131
1501
|
} }));
|
|
1132
1502
|
}
|
|
1503
|
+
/** Prepare call IDs once, retaining the engine's effective blocks and exact served-call metadata. */
|
|
1504
|
+
function prepareResponsesMisbehavior(plan) {
|
|
1505
|
+
return prepareOpenAIChatMisbehavior(plan);
|
|
1506
|
+
}
|
|
1507
|
+
/** Build one faulted payload/event sequence for both HTTP and Responses WebSocket consumers. */
|
|
1508
|
+
function buildResponsesMisbehavior(plan, request, chunkSize, emitEncryptedReasoning = false) {
|
|
1509
|
+
const response = plan.response;
|
|
1510
|
+
if (!(isCombinedFixtureResponse(response) || isTextResponse(response) || isToolCallResponse(response))) throw new Error("Responses misbehavior requires a prepared chat response");
|
|
1511
|
+
const overrides = extractOverrides(response);
|
|
1512
|
+
const reasoning = plan.reasoning ?? ("reasoning" in response ? response.reasoning : void 0);
|
|
1513
|
+
const webSearches = "webSearches" in response ? response.webSearches : void 0;
|
|
1514
|
+
const { respId, created, events, prefixOutputItems, nextOutputIndex } = buildResponsePreamble(request.model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning, false, plan.reasoning !== void 0);
|
|
1515
|
+
const output = [...prefixOutputItems];
|
|
1516
|
+
let outputIndex = nextOutputIndex;
|
|
1517
|
+
const incomplete = plan.stop === "length" || plan.stop === "content_filter";
|
|
1518
|
+
if (plan.refusal !== void 0) {
|
|
1519
|
+
const id = itemId();
|
|
1520
|
+
const part = {
|
|
1521
|
+
type: "refusal",
|
|
1522
|
+
refusal: plan.refusal
|
|
1523
|
+
};
|
|
1524
|
+
const item = {
|
|
1525
|
+
id,
|
|
1526
|
+
type: "message",
|
|
1527
|
+
role: "assistant",
|
|
1528
|
+
status: "completed",
|
|
1529
|
+
content: [part]
|
|
1530
|
+
};
|
|
1531
|
+
const position = {
|
|
1532
|
+
item_id: id,
|
|
1533
|
+
output_index: outputIndex,
|
|
1534
|
+
content_index: 0
|
|
1535
|
+
};
|
|
1536
|
+
events.push({
|
|
1537
|
+
type: "response.output_item.added",
|
|
1538
|
+
output_index: outputIndex,
|
|
1539
|
+
item: {
|
|
1540
|
+
...item,
|
|
1541
|
+
status: "in_progress",
|
|
1542
|
+
content: []
|
|
1543
|
+
}
|
|
1544
|
+
}, {
|
|
1545
|
+
type: "response.content_part.added",
|
|
1546
|
+
...position,
|
|
1547
|
+
part: {
|
|
1548
|
+
type: "refusal",
|
|
1549
|
+
refusal: ""
|
|
1550
|
+
}
|
|
1551
|
+
});
|
|
1552
|
+
for (let i = 0; i < plan.refusal.length; i += chunkSize) events.push({
|
|
1553
|
+
type: "response.refusal.delta",
|
|
1554
|
+
...position,
|
|
1555
|
+
delta: plan.refusal.slice(i, i + chunkSize)
|
|
1556
|
+
});
|
|
1557
|
+
events.push({
|
|
1558
|
+
type: "response.refusal.done",
|
|
1559
|
+
...position,
|
|
1560
|
+
refusal: plan.refusal
|
|
1561
|
+
}, {
|
|
1562
|
+
type: "response.content_part.done",
|
|
1563
|
+
...position,
|
|
1564
|
+
part
|
|
1565
|
+
}, {
|
|
1566
|
+
type: "response.output_item.done",
|
|
1567
|
+
output_index: outputIndex,
|
|
1568
|
+
item
|
|
1569
|
+
});
|
|
1570
|
+
output.push(item);
|
|
1571
|
+
} else if (plan.summary.fault !== "empty-response" && plan.stop !== "content_filter" && plan.reasoning === void 0) {
|
|
1572
|
+
const combined = isCombinedFixtureResponse(response);
|
|
1573
|
+
const blocks = (combined ? resolveServedBlockOutcome(response) : void 0)?.ordered ?? [..."content" in response && typeof response.content === "string" ? [{
|
|
1574
|
+
type: "text",
|
|
1575
|
+
text: response.content
|
|
1576
|
+
}] : [], ...combined || isToolCallResponse(response) ? servedToolCalls(response).map(toolCallFixtureBlock) : []];
|
|
1577
|
+
let callIndex = 0;
|
|
1578
|
+
for (const block of blocks) {
|
|
1579
|
+
if (block.type === "text") {
|
|
1580
|
+
const message = buildMessageOutputEvents(block.text, chunkSize, outputIndex);
|
|
1581
|
+
events.push(...message.events);
|
|
1582
|
+
output.push(message.msgItem);
|
|
1583
|
+
} else {
|
|
1584
|
+
const call = block.type === "customToolCall" ? buildCustomToolCallOutputEvents(block, chunkSize, outputIndex) : buildFunctionCallOutputEvents(block, chunkSize, outputIndex);
|
|
1585
|
+
const cut = plan.summary.fault === "stop-length-mid-tool" && callIndex === plan.target?.index;
|
|
1586
|
+
const item = cut ? {
|
|
1587
|
+
...call.item,
|
|
1588
|
+
status: "incomplete"
|
|
1589
|
+
} : call.item;
|
|
1590
|
+
events.push(...call.events.map((event) => cut && event.type === "response.output_item.done" ? {
|
|
1591
|
+
...event,
|
|
1592
|
+
item
|
|
1593
|
+
} : event));
|
|
1594
|
+
output.push(item);
|
|
1595
|
+
callIndex++;
|
|
1596
|
+
}
|
|
1597
|
+
outputIndex++;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
const usage = resolveOpenAIChatMisbehaviorUsage(plan, request);
|
|
1601
|
+
const body = {
|
|
1602
|
+
id: respId,
|
|
1603
|
+
object: "response",
|
|
1604
|
+
created_at: created,
|
|
1605
|
+
model: overrides?.model ?? request.model,
|
|
1606
|
+
status: incomplete ? "incomplete" : responsesStatus(overrides?.finishReason, "completed"),
|
|
1607
|
+
...incomplete ? { incomplete_details: { reason: plan.stop === "content_filter" ? "content_filter" : "max_output_tokens" } } : {},
|
|
1608
|
+
output,
|
|
1609
|
+
usage: {
|
|
1610
|
+
input_tokens: usage.prompt_tokens,
|
|
1611
|
+
output_tokens: usage.completion_tokens,
|
|
1612
|
+
total_tokens: usage.total_tokens
|
|
1613
|
+
}
|
|
1614
|
+
};
|
|
1615
|
+
events.push({
|
|
1616
|
+
type: incomplete ? "response.incomplete" : "response.completed",
|
|
1617
|
+
response: body
|
|
1618
|
+
});
|
|
1619
|
+
return {
|
|
1620
|
+
response: body,
|
|
1621
|
+
events
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1133
1624
|
|
|
1134
1625
|
//#endregion
|
|
1135
|
-
export { buildContentWithToolCallsStreamEvents, buildTextResponse, buildTextStreamEvents, buildToolCallStreamEvents, handleResponses, requestIncludesEncryptedReasoning, requestWantsEncryptedReasoning, responsesToCompletionRequest };
|
|
1626
|
+
export { buildContentWithToolCallsStreamEvents, buildResponsesMisbehavior, buildTextResponse, buildTextStreamEvents, buildToolCallStreamEvents, contentWithToolCallsStreamEvents, handleResponses, prepareResponsesMisbehavior, requestIncludesEncryptedReasoning, requestWantsEncryptedReasoning, responsesToCompletionRequest, toolCallStreamEvents };
|
|
1136
1627
|
//# sourceMappingURL=responses.js.map
|