@copilotkit/aimock 1.43.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 +46 -0
- package/README.md +14 -2
- package/dist/agui-handler.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 +344 -43
- 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 +345 -44
- 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 +9 -5
- package/dist/chaos.cjs.map +1 -1
- package/dist/chaos.d.cts.map +1 -1
- package/dist/chaos.d.ts.map +1 -1
- package/dist/chaos.js +10 -7
- package/dist/chaos.js.map +1 -1
- package/dist/cli.cjs +238 -9
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +242 -13
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +159 -72
- 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 +160 -73
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.cjs +63 -5
- 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 +63 -5
- package/dist/config-loader.js.map +1 -1
- package/dist/constants.cjs +8 -0
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +6 -2
- package/dist/constants.d.ts +6 -2
- package/dist/constants.js +8 -1
- package/dist/constants.js.map +1 -1
- package/dist/echo-text.cjs +65 -0
- package/dist/echo-text.cjs.map +1 -0
- package/dist/echo-text.d.cts +22 -0
- package/dist/echo-text.d.cts.map +1 -0
- package/dist/echo-text.d.ts +22 -0
- package/dist/echo-text.d.ts.map +1 -0
- package/dist/echo-text.js +65 -0
- package/dist/echo-text.js.map +1 -0
- package/dist/embeddings.cjs +18 -1
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.d.cts +1 -1
- package/dist/embeddings.d.cts.map +1 -1
- package/dist/embeddings.d.ts +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +18 -1
- package/dist/embeddings.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/files.cjs +9 -11
- package/dist/files.cjs.map +1 -1
- package/dist/files.d.cts.map +1 -1
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +10 -13
- package/dist/files.js.map +1 -1
- package/dist/fine-tuning.cjs +1 -19
- package/dist/fine-tuning.cjs.map +1 -1
- package/dist/fine-tuning.d.cts.map +1 -1
- package/dist/fine-tuning.d.ts.map +1 -1
- package/dist/fine-tuning.js +2 -20
- package/dist/fine-tuning.js.map +1 -1
- package/dist/fixture-loader-services.cjs +116 -0
- package/dist/fixture-loader-services.cjs.map +1 -0
- package/dist/fixture-loader-services.d.cts +48 -0
- package/dist/fixture-loader-services.d.cts.map +1 -0
- package/dist/fixture-loader-services.d.ts +48 -0
- package/dist/fixture-loader-services.d.ts.map +1 -0
- package/dist/fixture-loader-services.js +114 -0
- package/dist/fixture-loader-services.js.map +1 -0
- package/dist/fixture-loader.cjs +552 -29
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +140 -2
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +140 -2
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +542 -31
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-embeddings.cjs +42 -2
- package/dist/gemini-embeddings.cjs.map +1 -1
- package/dist/gemini-embeddings.js +43 -3
- package/dist/gemini-embeddings.js.map +1 -1
- package/dist/gemini-interactions.cjs +288 -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 +289 -53
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +234 -38
- 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 +235 -39
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +1157 -12
- 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 +1113 -14
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +3 -2
- package/dist/images.cjs.map +1 -1
- package/dist/images.js +3 -2
- package/dist/images.js.map +1 -1
- package/dist/index.cjs +39 -1
- package/dist/index.d.cts +11 -4
- package/dist/index.d.ts +11 -4
- package/dist/index.js +11 -4
- package/dist/jest.cjs +43 -14
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +47 -5
- package/dist/jest.d.cts.map +1 -1
- package/dist/jest.d.ts +47 -5
- package/dist/jest.d.ts.map +1 -1
- package/dist/jest.js +45 -16
- package/dist/jest.js.map +1 -1
- package/dist/journal.cjs +155 -13
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts +54 -4
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts +54 -4
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +155 -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 +204 -39
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +51 -1
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +51 -1
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +206 -41
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-fakes-mount.cjs +217 -0
- package/dist/mcp-fakes-mount.cjs.map +1 -0
- package/dist/mcp-fakes-mount.js +211 -0
- package/dist/mcp-fakes-mount.js.map +1 -0
- 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 +1942 -0
- package/dist/mcp-fakes.cjs.map +1 -0
- package/dist/mcp-fakes.d.cts +504 -0
- package/dist/mcp-fakes.d.cts.map +1 -0
- package/dist/mcp-fakes.d.ts +504 -0
- package/dist/mcp-fakes.d.ts.map +1 -0
- package/dist/mcp-fakes.js +1937 -0
- package/dist/mcp-fakes.js.map +1 -0
- package/dist/mcp-handler.cjs +534 -27
- 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 +530 -28
- package/dist/mcp-handler.js.map +1 -1
- package/dist/mcp-mock.cjs +326 -47
- package/dist/mcp-mock.cjs.map +1 -1
- package/dist/mcp-mock.d.cts +97 -1
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts +97 -1
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +327 -48
- 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/mcp-stub.cjs +5 -1
- package/dist/mcp-stub.d.cts +3 -1
- package/dist/mcp-stub.d.ts +3 -1
- package/dist/mcp-stub.js +3 -1
- package/dist/mcp-types.d.cts +6 -0
- package/dist/mcp-types.d.ts +6 -0
- package/dist/message-text.cjs +192 -0
- package/dist/message-text.cjs.map +1 -0
- package/dist/message-text.d.cts +31 -0
- package/dist/message-text.d.cts.map +1 -0
- package/dist/message-text.d.ts +31 -0
- package/dist/message-text.d.ts.map +1 -0
- package/dist/message-text.js +184 -0
- package/dist/message-text.js.map +1 -0
- package/dist/messages.cjs +400 -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 +401 -58
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +46 -66
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts +14 -6
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts +14 -6
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +32 -40
- package/dist/metrics.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 +315 -113
- 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 +316 -114
- package/dist/ollama.js.map +1 -1
- package/dist/openapi.cjs +3405 -0
- package/dist/openapi.cjs.map +1 -0
- package/dist/openapi.d.cts +30 -0
- package/dist/openapi.d.cts.map +1 -0
- package/dist/openapi.d.ts +30 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +3404 -0
- package/dist/openapi.js.map +1 -0
- package/dist/openrouter-video.cjs +4 -4
- 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 +4 -4
- 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 +115 -6
- 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 +116 -8
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +701 -166
- 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 +699 -169
- package/dist/responses.js.map +1 -1
- package/dist/route-registry.cjs +951 -0
- package/dist/route-registry.cjs.map +1 -0
- package/dist/route-registry.d.cts +33 -0
- package/dist/route-registry.d.cts.map +1 -0
- package/dist/route-registry.d.ts +33 -0
- package/dist/route-registry.d.ts.map +1 -0
- package/dist/route-registry.js +866 -0
- package/dist/route-registry.js.map +1 -0
- package/dist/router.cjs +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 +1159 -264
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +4 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +4 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1067 -172
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +4 -2
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.js +4 -2
- package/dist/speech.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/suite.cjs +1 -1
- package/dist/suite.js +1 -1
- package/dist/transcription.cjs +2 -2
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.d.cts.map +1 -1
- package/dist/transcription.d.ts.map +1 -1
- package/dist/transcription.js +2 -2
- package/dist/transcription.js.map +1 -1
- package/dist/types.cjs +18 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +516 -5
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +516 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/dist/validate-cli.cjs +123 -33
- package/dist/validate-cli.cjs.map +1 -1
- package/dist/validate-cli.js +125 -35
- package/dist/validate-cli.js.map +1 -1
- package/dist/vector-stores.cjs +1180 -0
- package/dist/vector-stores.cjs.map +1 -0
- package/dist/vector-stores.d.cts +106 -0
- package/dist/vector-stores.d.cts.map +1 -0
- package/dist/vector-stores.d.ts +106 -0
- package/dist/vector-stores.d.ts.map +1 -0
- package/dist/vector-stores.js +1164 -0
- package/dist/vector-stores.js.map +1 -0
- package/dist/vector-types.d.cts.map +1 -1
- package/dist/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/video.cjs +19 -0
- package/dist/video.cjs.map +1 -1
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts.map +1 -1
- package/dist/video.js +19 -0
- package/dist/video.js.map +1 -1
- package/dist/vitest.cjs +77 -16
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +38 -5
- package/dist/vitest.d.cts.map +1 -1
- package/dist/vitest.d.ts +38 -5
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +78 -17
- 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 +121 -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 +123 -18
- package/dist/ws-responses.js.map +1 -1
- package/package.json +13 -2
- package/skills/write-fixtures/SKILL.md +179 -21
package/dist/helpers.cjs
CHANGED
|
@@ -1,10 +1,259 @@
|
|
|
1
1
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
2
|
const require_constants = require('./constants.cjs');
|
|
3
|
+
const require_echo_text = require('./echo-text.cjs');
|
|
4
|
+
const require_message_text = require('./message-text.cjs');
|
|
3
5
|
const require_model_utils = require('./model-utils.cjs');
|
|
4
6
|
const require_api_key_auth = require('./api-key-auth.cjs');
|
|
7
|
+
let node_async_hooks = require("node:async_hooks");
|
|
5
8
|
let node_crypto = require("node:crypto");
|
|
6
9
|
|
|
7
10
|
//#region src/helpers.ts
|
|
11
|
+
/** Preserve authored text for string wires without silently repairing invalid JSON. */
|
|
12
|
+
function toolArgsForWire(tc) {
|
|
13
|
+
const raw = tc.arguments || "{}";
|
|
14
|
+
try {
|
|
15
|
+
const value = JSON.parse(raw);
|
|
16
|
+
return {
|
|
17
|
+
kind: "parsed",
|
|
18
|
+
value,
|
|
19
|
+
text: JSON.stringify(value),
|
|
20
|
+
raw
|
|
21
|
+
};
|
|
22
|
+
} catch {
|
|
23
|
+
return {
|
|
24
|
+
kind: "verbatim",
|
|
25
|
+
raw
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Object wires reject the verbatim branch; string wires never need this error. */
|
|
30
|
+
var InvalidToolArgumentsError = class extends Error {
|
|
31
|
+
toolName;
|
|
32
|
+
parseDiagnostic;
|
|
33
|
+
constructor(tc) {
|
|
34
|
+
let parseDiagnostic = "";
|
|
35
|
+
try {
|
|
36
|
+
JSON.parse(tc.arguments || "{}");
|
|
37
|
+
} catch (error) {
|
|
38
|
+
parseDiagnostic = error instanceof Error ? error.message : String(error);
|
|
39
|
+
}
|
|
40
|
+
super(`aimock: fixture tool call "${tc.name}" has invalid JSON arguments; this wire carries arguments as an object. Use a wire that carries tool arguments as a string to test malformed JSON. (${parseDiagnostic})`);
|
|
41
|
+
this.name = "InvalidToolArgumentsError";
|
|
42
|
+
this.toolName = tc.name;
|
|
43
|
+
this.parseDiagnostic = parseDiagnostic;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const toolArgumentsScope = new node_async_hooks.AsyncLocalStorage();
|
|
47
|
+
/** Run `fn` with the `strictToolArguments` setting and logger of one request. */
|
|
48
|
+
function runWithToolArgumentsScope(scope, fn) {
|
|
49
|
+
return toolArgumentsScope.run(scope, fn);
|
|
50
|
+
}
|
|
51
|
+
/** Whether `strictToolArguments` is on for the request being served (default off). */
|
|
52
|
+
function strictToolArgumentsEnabled() {
|
|
53
|
+
return toolArgumentsScope.getStore()?.strict === true;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Serve the rest of this request's tool arguments as authored (the
|
|
57
|
+
* `strictToolArguments` behavior). An applied misbehavior fault calls this so
|
|
58
|
+
* that the malformed arguments it injects are not replaced with `{}`; the
|
|
59
|
+
* planner only applies a fault to a fixture whose own arguments are valid
|
|
60
|
+
* wherever the default would replace them.
|
|
61
|
+
*/
|
|
62
|
+
function keepAuthoredToolArguments() {
|
|
63
|
+
const scope = toolArgumentsScope.getStore();
|
|
64
|
+
if (scope) scope.strict = true;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The fixture tool-call arguments a wire serves on its normal (no misbehavior)
|
|
68
|
+
* path. Valid JSON gives the parsed value, its re-serialized text, and the
|
|
69
|
+
* authored text. Invalid JSON logs one warning and, by default, is served as
|
|
70
|
+
* `{}` / `"{}"`, as in 1.44.0. With `strictToolArguments`, an object wire
|
|
71
|
+
* (`"object"`) throws {@link InvalidToolArgumentsError} and a string wire
|
|
72
|
+
* (`"string"`) serves the authored text unchanged.
|
|
73
|
+
*/
|
|
74
|
+
function servedToolArgs(tc, wire, logger) {
|
|
75
|
+
const args = toolArgsForWire(tc);
|
|
76
|
+
if (args.kind === "parsed") return args;
|
|
77
|
+
const scope = toolArgumentsScope.getStore();
|
|
78
|
+
(logger ?? scope?.logger)?.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
|
|
79
|
+
if (scope?.strict !== true) return {
|
|
80
|
+
value: {},
|
|
81
|
+
text: "{}",
|
|
82
|
+
raw: "{}"
|
|
83
|
+
};
|
|
84
|
+
if (wire === "object") throw new InvalidToolArgumentsError(tc);
|
|
85
|
+
return {
|
|
86
|
+
value: void 0,
|
|
87
|
+
text: args.raw,
|
|
88
|
+
raw: args.raw
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A custom (freeform) tool call reached a wire that has no faithful projection
|
|
93
|
+
* for it. Only the OpenAI Responses API (HTTP and WebSocket) emits custom tool
|
|
94
|
+
* calls, from a fixture's `customToolCalls` or a `customToolCall` block in its
|
|
95
|
+
* `responsesBlocks`. The handlers of the other wires raise this through
|
|
96
|
+
* {@link rejectResponsesOnlyToolCalls} (usually via
|
|
97
|
+
* {@link requireEmittedFunctionToolCalls}) before they write any content.
|
|
98
|
+
* Ollama `/api/generate` is the exception: it rejects every tool-call fixture
|
|
99
|
+
* with a 400 before this check.
|
|
100
|
+
*/
|
|
101
|
+
var UnsupportedToolCallError = class extends Error {
|
|
102
|
+
toolName;
|
|
103
|
+
wire;
|
|
104
|
+
constructor(toolName, wire) {
|
|
105
|
+
super(`aimock: fixture tool call "${toolName}" is a custom tool call, which only the OpenAI Responses API supports; this request arrived on ${wire}`);
|
|
106
|
+
this.name = "UnsupportedToolCallError";
|
|
107
|
+
this.toolName = toolName;
|
|
108
|
+
this.wire = wire;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* A fixture tool call is malformed: a `toolCalls` entry that is not an object,
|
|
113
|
+
* a malformed `customToolCalls` entry, or a malformed tool block in
|
|
114
|
+
* `responsesBlocks`. Programmatic and factory fixtures skip load validation,
|
|
115
|
+
* so this request-time guard is their validation for the rules
|
|
116
|
+
* {@link assertResponsesToolCalls} and {@link resolveResponsesBlocks} check.
|
|
117
|
+
* An HTTP request then fails with a 500 that carries this message and `code`;
|
|
118
|
+
* a WebSocket turn gets the transport's error message with the code. A
|
|
119
|
+
* malformed legacy `blocks` entry keeps the plain Error of
|
|
120
|
+
* {@link resolveFixtureBlocks}.
|
|
121
|
+
*/
|
|
122
|
+
var InvalidFixtureToolCallError = class extends Error {
|
|
123
|
+
code = "aimock_invalid_fixture_tool_call";
|
|
124
|
+
constructor(index, problem, carrier = "toolCalls") {
|
|
125
|
+
super(carrier === "blocks" ? `Invalid fixture block at index ${index}: ${problem}` : `Invalid fixture tool call: ${problem} (${carrier}[${index}])`);
|
|
126
|
+
this.name = "InvalidFixtureToolCallError";
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Request-time guard for one custom tool call (a `customToolCalls` entry).
|
|
131
|
+
* Programmatic and factory fixtures skip load validation, so this is their
|
|
132
|
+
* validation: an object with a non-empty string `name`, a string `input`, no
|
|
133
|
+
* `arguments`, a string `id` when present and a non-empty string `namespace`
|
|
134
|
+
* when present.
|
|
135
|
+
*/
|
|
136
|
+
function assertCustomToolCall(call, index) {
|
|
137
|
+
const fail = (problem) => {
|
|
138
|
+
throw new InvalidFixtureToolCallError(index, problem, "customToolCalls");
|
|
139
|
+
};
|
|
140
|
+
if (!isPlainObject(call)) fail("expected an object");
|
|
141
|
+
const tc = call;
|
|
142
|
+
if (tc.namespace !== void 0 && (typeof tc.namespace !== "string" || tc.namespace === "")) fail("\"namespace\" must be a non-empty string when present");
|
|
143
|
+
if (typeof tc.name !== "string" || tc.name === "") fail("custom tool call requires a non-empty string \"name\" field");
|
|
144
|
+
if (typeof tc.input !== "string") fail("\"input\" must be a string for a custom tool call");
|
|
145
|
+
if (tc.arguments !== void 0) fail("custom tool call takes \"input\", not \"arguments\"");
|
|
146
|
+
if (tc.id !== void 0 && typeof tc.id !== "string") fail("custom tool call \"id\" must be a string when present");
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Validate a response's `customToolCalls` (OpenAI Responses only) before it is
|
|
150
|
+
* served. Throws {@link InvalidFixtureToolCallError} on the first malformed
|
|
151
|
+
* entry. The key is new, so no fixture of an earlier release reaches this.
|
|
152
|
+
*/
|
|
153
|
+
function assertCustomToolCalls(response) {
|
|
154
|
+
const list = response.customToolCalls;
|
|
155
|
+
if (list === void 0) return;
|
|
156
|
+
if (!Array.isArray(list)) throw new InvalidFixtureToolCallError(0, "customToolCalls must be an array", "customToolCalls");
|
|
157
|
+
list.forEach((call, i) => assertCustomToolCall(call, i));
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Validate the custom calls among the tool calls the OpenAI Responses API is
|
|
161
|
+
* about to serve (see {@link servedToolCalls}). Function calls are not
|
|
162
|
+
* checked here: they keep the handling of earlier releases (a non-string
|
|
163
|
+
* `arguments` fails with the uncoded error of the Responses builders).
|
|
164
|
+
*/
|
|
165
|
+
function assertResponsesToolCalls(calls) {
|
|
166
|
+
let customIndex = 0;
|
|
167
|
+
for (const tc of calls) if (isPlainObject(tc) && tc.type === "custom") assertCustomToolCall(tc, customIndex++);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* A fixture's `toolCalls` for a wire that cannot carry custom tool calls.
|
|
171
|
+
* Every entry is a function call and passes through unchanged, exactly as in
|
|
172
|
+
* earlier releases (the builders read its `name`, `arguments` and `id`; its
|
|
173
|
+
* Responses-only `namespace` is not emitted). Custom calls live in
|
|
174
|
+
* `customToolCalls`, which {@link rejectResponsesOnlyToolCalls} rejects.
|
|
175
|
+
*/
|
|
176
|
+
function requireFunctionToolCalls(calls) {
|
|
177
|
+
return calls;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Reject the Responses-only custom tool calls on a wire that cannot carry
|
|
181
|
+
* them: a non-empty `customToolCalls`, or a `customToolCall` block in
|
|
182
|
+
* `responsesBlocks`. Throws {@link UnsupportedToolCallError} naming `wire`.
|
|
183
|
+
* Both keys are new, so no fixture of an earlier release reaches this error.
|
|
184
|
+
*/
|
|
185
|
+
function rejectResponsesOnlyToolCalls(response, wire) {
|
|
186
|
+
const r = response;
|
|
187
|
+
if (Array.isArray(r.customToolCalls) && r.customToolCalls.length > 0) {
|
|
188
|
+
const first = r.customToolCalls[0];
|
|
189
|
+
throw new UnsupportedToolCallError(isPlainObject(first) && typeof first.name === "string" ? first.name : "", wire);
|
|
190
|
+
}
|
|
191
|
+
if (Array.isArray(r.responsesBlocks)) {
|
|
192
|
+
for (const block of r.responsesBlocks) if (isPlainObject(block) && block.type === "customToolCall") throw new UnsupportedToolCallError(typeof block.name === "string" ? block.name : "", wire);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* {@link rejectResponsesOnlyToolCalls}, then {@link requireFunctionToolCalls}
|
|
197
|
+
* on the response's `toolCalls` (ignoring any `blocks`).
|
|
198
|
+
*/
|
|
199
|
+
function requireServedFunctionToolCalls(response, wire) {
|
|
200
|
+
rejectResponsesOnlyToolCalls(response, wire);
|
|
201
|
+
return requireFunctionToolCalls(response.toolCalls ?? []);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The function calls a non-Responses wire emits for this response, after
|
|
205
|
+
* {@link rejectResponsesOnlyToolCalls}. Non-empty ordered `blocks` are
|
|
206
|
+
* authoritative: the builders stream them (and validate them through
|
|
207
|
+
* {@link resolveFixtureBlocks}) and never emit the legacy `toolCalls`, so the
|
|
208
|
+
* result is empty. Without blocks, `toolCalls` is checked by
|
|
209
|
+
* {@link requireFunctionToolCalls}.
|
|
210
|
+
*/
|
|
211
|
+
function requireEmittedFunctionToolCalls(response, wire) {
|
|
212
|
+
rejectResponsesOnlyToolCalls(response, wire);
|
|
213
|
+
if (Array.isArray(response.blocks) && response.blocks.length > 0) return [];
|
|
214
|
+
return requireFunctionToolCalls(response.toolCalls ?? []);
|
|
215
|
+
}
|
|
216
|
+
/** True for the fixture tool-call errors a handler surfaces as a coded 500. */
|
|
217
|
+
function isFixtureToolCallError(error) {
|
|
218
|
+
return error instanceof InvalidToolArgumentsError || error instanceof UnsupportedToolCallError || error instanceof InvalidFixtureToolCallError;
|
|
219
|
+
}
|
|
220
|
+
/** The aimock error code carried in every provider envelope for a fixture tool-call error. */
|
|
221
|
+
function fixtureToolCallErrorCode(error) {
|
|
222
|
+
if (error instanceof UnsupportedToolCallError) return "aimock_unsupported_tool_call";
|
|
223
|
+
if (error instanceof InvalidFixtureToolCallError) return error.code;
|
|
224
|
+
return "aimock_invalid_tool_arguments";
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Google RPC error `details` carrying an aimock fixture tool-call code, for the
|
|
228
|
+
* Gemini, Vertex AI and Gemini Live envelopes (`google.rpc.ErrorInfo`: an
|
|
229
|
+
* UPPER_SNAKE `reason`, a `domain`, and string `metadata`).
|
|
230
|
+
*/
|
|
231
|
+
function googleFixtureToolCallErrorDetails(code) {
|
|
232
|
+
return [{
|
|
233
|
+
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
|
|
234
|
+
reason: code.toUpperCase(),
|
|
235
|
+
domain: "aimock",
|
|
236
|
+
metadata: { code }
|
|
237
|
+
}];
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Run a WebSocket preflight `check` that may raise a fixture tool-call error
|
|
241
|
+
* ({@link InvalidToolArgumentsError}, {@link UnsupportedToolCallError} or
|
|
242
|
+
* {@link InvalidFixtureToolCallError}).
|
|
243
|
+
* Such an error marks the already-journaled entry as a 500 carrying the
|
|
244
|
+
* message, then propagates to the transport's error handler.
|
|
245
|
+
*/
|
|
246
|
+
function journalFixtureToolCallError(journalEntry, check) {
|
|
247
|
+
try {
|
|
248
|
+
return check();
|
|
249
|
+
} catch (error) {
|
|
250
|
+
if (isFixtureToolCallError(error)) {
|
|
251
|
+
journalEntry.response.status = 500;
|
|
252
|
+
journalEntry.response.error = error.message;
|
|
253
|
+
}
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
8
257
|
/**
|
|
9
258
|
* Resolve effective strict mode from per-request header and server default.
|
|
10
259
|
* Header values override the server default — same precedence pattern as chaos
|
|
@@ -26,6 +275,25 @@ function resolveStrictMode(serverDefault, rawHeaders) {
|
|
|
26
275
|
}
|
|
27
276
|
return serverDefault ?? false;
|
|
28
277
|
}
|
|
278
|
+
const STRICT_INTEGER_RE = /^\d+$/;
|
|
279
|
+
/**
|
|
280
|
+
* Strict decimal-integer grammar shared by the query/list parsers that grew
|
|
281
|
+
* their own local copy on separate branches (files `limit`, fine-tuning
|
|
282
|
+
* `limit`, chaos integer fields): an unsigned digit run and nothing else — no
|
|
283
|
+
* sign, no exponent, no radix prefix, no surrounding whitespace (callers that
|
|
284
|
+
* accept padded input trim before calling).
|
|
285
|
+
*
|
|
286
|
+
* Deliberately narrower than `Number()`, which reads `0x10` as 16, `1e3` as
|
|
287
|
+
* 1000 and strips whitespace and a leading `+`. Returns the value, or `null`
|
|
288
|
+
* when the text is not a safe integer literal. Range checks and error
|
|
289
|
+
* messages stay with the callers — this is the grammar gate only, so sharing
|
|
290
|
+
* it cannot change any surface's accepted range or its 400 text.
|
|
291
|
+
*/
|
|
292
|
+
function parseStrictIntegerText(text) {
|
|
293
|
+
if (!STRICT_INTEGER_RE.test(text)) return null;
|
|
294
|
+
const n = Number(text);
|
|
295
|
+
return Number.isSafeInteger(n) ? n : null;
|
|
296
|
+
}
|
|
29
297
|
/**
|
|
30
298
|
* Would a fixture miss on this request have been forwarded upstream? This is
|
|
31
299
|
* the ONE rule behind the no-fixture chaos journal `source` label: "proxy"
|
|
@@ -162,14 +430,192 @@ function isLiveResponse(value) {
|
|
|
162
430
|
function isResponseFactory(r) {
|
|
163
431
|
return typeof r === "function";
|
|
164
432
|
}
|
|
433
|
+
const serverFixtureLists = /* @__PURE__ */ new WeakMap();
|
|
434
|
+
/**
|
|
435
|
+
* Array methods that read elements and write them back. On a server list
|
|
436
|
+
* they run on the caller's array, so the extended view of an element is
|
|
437
|
+
* never written into it.
|
|
438
|
+
*/
|
|
439
|
+
const ELEMENT_MOVING_METHODS = new Set([
|
|
440
|
+
"copyWithin",
|
|
441
|
+
"fill",
|
|
442
|
+
"pop",
|
|
443
|
+
"push",
|
|
444
|
+
"reverse",
|
|
445
|
+
"shift",
|
|
446
|
+
"sort",
|
|
447
|
+
"splice",
|
|
448
|
+
"unshift"
|
|
449
|
+
]);
|
|
450
|
+
/**
|
|
451
|
+
* The fixture list one server reads and writes: a live view of the caller's
|
|
452
|
+
* `fixtures` array, with its own identity. The matcher reads the server's
|
|
453
|
+
* `responsesTools` mode from the list it is given, so every handler on every
|
|
454
|
+
* wire sees that server's mode without extra parameters, and two servers that
|
|
455
|
+
* share one array each keep their own mode. With `"extended"`, reading an
|
|
456
|
+
* element gives `extendedView(element)` (the fixture with its held keys
|
|
457
|
+
* applied); the caller's array and its fixtures are never changed for it.
|
|
458
|
+
* Writes (push, splice, `length = 0`, ...) reach the caller's array.
|
|
459
|
+
*/
|
|
460
|
+
function createServerFixtureList(fixtures, mode, extendedView) {
|
|
461
|
+
const boundMethods = /* @__PURE__ */ new Map();
|
|
462
|
+
const list = new Proxy(fixtures, { get(target, key) {
|
|
463
|
+
const value = Reflect.get(target, key);
|
|
464
|
+
if (typeof value === "function") {
|
|
465
|
+
if (!ELEMENT_MOVING_METHODS.has(key)) return value;
|
|
466
|
+
let bound = boundMethods.get(key);
|
|
467
|
+
if (bound === void 0) {
|
|
468
|
+
bound = value.bind(target);
|
|
469
|
+
boundMethods.set(key, bound);
|
|
470
|
+
}
|
|
471
|
+
return bound;
|
|
472
|
+
}
|
|
473
|
+
if (typeof key !== "string" || mode() !== "extended") return value;
|
|
474
|
+
const first = key.charCodeAt(0);
|
|
475
|
+
if (first < 48 || first > 57 || value === null || typeof value !== "object") return value;
|
|
476
|
+
return extendedView(value);
|
|
477
|
+
} });
|
|
478
|
+
serverFixtureLists.set(list, {
|
|
479
|
+
target: fixtures,
|
|
480
|
+
mode
|
|
481
|
+
});
|
|
482
|
+
return list;
|
|
483
|
+
}
|
|
484
|
+
/** Fixture arrays that only their own server reads: see {@link markServerOwnedFixtures}. */
|
|
485
|
+
const serverOwnedFixtureArrays = /* @__PURE__ */ new WeakSet();
|
|
486
|
+
/**
|
|
487
|
+
* @internal Mark `fixtures` as an array no other server reads (LLMock's own
|
|
488
|
+
* array). A server may change the fixtures it makes itself (the control API)
|
|
489
|
+
* in place only in such an array; in a caller's array, which another server
|
|
490
|
+
* may share, they stay as made and an extended server reads them through its
|
|
491
|
+
* view.
|
|
492
|
+
*/
|
|
493
|
+
function markServerOwnedFixtures(fixtures) {
|
|
494
|
+
serverOwnedFixtureArrays.add(fixtures);
|
|
495
|
+
}
|
|
496
|
+
/** Whether the array behind `fixtures` (a server list or an array) is server-owned. */
|
|
497
|
+
function isServerOwnedFixtures(fixtures) {
|
|
498
|
+
return serverOwnedFixtureArrays.has(fixtureListTarget(fixtures));
|
|
499
|
+
}
|
|
500
|
+
/** Whether `fixtures` is the list of a server running with `responsesTools: "extended"`. */
|
|
501
|
+
function isExtendedResponsesToolsList(fixtures) {
|
|
502
|
+
return serverFixtureLists.get(fixtures)?.mode() === "extended";
|
|
503
|
+
}
|
|
504
|
+
/** The caller's array behind a server list; any other array is itself. */
|
|
505
|
+
function fixtureListTarget(fixtures) {
|
|
506
|
+
return serverFixtureLists.get(fixtures)?.target ?? fixtures;
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Requests matched against an extended fixture list. {@link resolveResponse}
|
|
510
|
+
* reads it, so the response a handler serves follows the same mode as the
|
|
511
|
+
* match that chose its fixture.
|
|
512
|
+
*/
|
|
513
|
+
const extendedResponsesToolsRequests = /* @__PURE__ */ new WeakSet();
|
|
514
|
+
/** Mark `request` as matched with `responsesTools: "extended"`. */
|
|
515
|
+
function markExtendedResponsesToolsRequest(request) {
|
|
516
|
+
extendedResponsesToolsRequests.add(request);
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Drop `customToolCalls` and `responsesBlocks`, for responses served without
|
|
520
|
+
* `responsesTools: "extended"`. Earlier releases ignored both keys, so a
|
|
521
|
+
* fixture that carries them serves exactly as before. Returns the same object
|
|
522
|
+
* when neither key is set.
|
|
523
|
+
*/
|
|
524
|
+
function withoutResponsesToolKeys(response) {
|
|
525
|
+
if (!isPlainObject(response)) return response;
|
|
526
|
+
if (!("customToolCalls" in response) && !("responsesBlocks" in response)) return response;
|
|
527
|
+
const copy = { ...response };
|
|
528
|
+
delete copy.customToolCalls;
|
|
529
|
+
delete copy.responsesBlocks;
|
|
530
|
+
return copy;
|
|
531
|
+
}
|
|
165
532
|
async function resolveResponse(fixture, request) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
533
|
+
const served = (response) => sanitizeFixtureResponse(extendedResponsesToolsRequests.has(request) ? response : withoutResponsesToolKeys(response));
|
|
534
|
+
if (typeof fixture.response === "function") {
|
|
535
|
+
let normalized;
|
|
536
|
+
try {
|
|
537
|
+
normalized = normalizeFactoryResponse(await fixture.response(request));
|
|
538
|
+
} catch (err) {
|
|
539
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
540
|
+
throw new Error(`Response factory threw: ${msg}`, { cause: err });
|
|
541
|
+
}
|
|
542
|
+
return served(normalized);
|
|
171
543
|
}
|
|
172
|
-
return fixture.response;
|
|
544
|
+
return served(fixture.response);
|
|
545
|
+
}
|
|
546
|
+
function isPlainObject(value) {
|
|
547
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
548
|
+
}
|
|
549
|
+
/** A non-empty string, the only valid value of a tool call's `namespace`. */
|
|
550
|
+
function isValidNamespace(value) {
|
|
551
|
+
return typeof value === "string" && value !== "";
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Drop the keys a served `toolCalls` entry or `toolCall` block ignores, so
|
|
555
|
+
* no handler reads them: on a `toolCalls` entry, `type` and `input` (a
|
|
556
|
+
* `toolCalls` entry is always a function call; custom calls live in
|
|
557
|
+
* `customToolCalls`) and an invalid `namespace` (not a non-empty string); on a
|
|
558
|
+
* `toolCall` block, `input` and an invalid `namespace`. Earlier releases
|
|
559
|
+
* ignored all of these, so fixtures that carry them serve exactly as before.
|
|
560
|
+
* Returns the same object when nothing needs dropping. `customToolCalls` and
|
|
561
|
+
* `responsesBlocks` are new keys and are validated, not sanitized.
|
|
562
|
+
*/
|
|
563
|
+
function sanitizeFixtureResponse(response) {
|
|
564
|
+
if (!isPlainObject(response)) return response;
|
|
565
|
+
const r = response;
|
|
566
|
+
const dropKeys = (entry, keys) => {
|
|
567
|
+
if (!isPlainObject(entry)) return entry;
|
|
568
|
+
const drop = keys.filter((key) => key in entry && (key !== "namespace" || !isValidNamespace(entry.namespace)));
|
|
569
|
+
if (drop.length === 0) return entry;
|
|
570
|
+
const copy = { ...entry };
|
|
571
|
+
for (const key of drop) delete copy[key];
|
|
572
|
+
return copy;
|
|
573
|
+
};
|
|
574
|
+
const sanitizeList = (list, pick) => {
|
|
575
|
+
if (!Array.isArray(list)) return list;
|
|
576
|
+
const next = list.map(pick);
|
|
577
|
+
return next.some((entry, k) => entry !== list[k]) ? next : list;
|
|
578
|
+
};
|
|
579
|
+
const toolCalls = sanitizeList(r.toolCalls, (entry) => dropKeys(entry, [
|
|
580
|
+
"type",
|
|
581
|
+
"input",
|
|
582
|
+
"namespace"
|
|
583
|
+
]));
|
|
584
|
+
let blocks = sanitizeList(r.blocks, (entry) => isPlainObject(entry) && entry.type === "toolCall" ? dropKeys(entry, ["input", "namespace"]) : entry);
|
|
585
|
+
if (!(Array.isArray(r.blocks) && r.blocks.length > 0) && Array.isArray(r.responsesBlocks) && r.responsesBlocks.length > 0 && !r.responsesBlocks.some((entry) => isPlainObject(entry) && entry.type === "customToolCall")) blocks = r.responsesBlocks;
|
|
586
|
+
if (toolCalls === r.toolCalls && blocks === r.blocks) return response;
|
|
587
|
+
const copy = { ...r };
|
|
588
|
+
if (toolCalls !== r.toolCalls) copy.toolCalls = toolCalls;
|
|
589
|
+
if (blocks !== r.blocks) copy.blocks = blocks;
|
|
590
|
+
return copy;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Drop `namespace` from every `toolCalls` entry and `toolCall` block, for
|
|
594
|
+
* OpenAI Responses requests served without `responsesTools: "extended"`:
|
|
595
|
+
* earlier releases never emitted it. (Without that mode, resolveResponse has
|
|
596
|
+
* already dropped `customToolCalls` and `responsesBlocks`.) Returns the same
|
|
597
|
+
* object when no namespace is set.
|
|
598
|
+
*/
|
|
599
|
+
function withoutLegacyNamespaces(response) {
|
|
600
|
+
if (!isPlainObject(response)) return response;
|
|
601
|
+
const r = response;
|
|
602
|
+
const strip = (list, isTarget) => {
|
|
603
|
+
if (!Array.isArray(list)) return list;
|
|
604
|
+
if (!list.some((entry) => isPlainObject(entry) && isTarget(entry) && "namespace" in entry)) return list;
|
|
605
|
+
return list.map((entry) => {
|
|
606
|
+
if (!isPlainObject(entry) || !isTarget(entry) || !("namespace" in entry)) return entry;
|
|
607
|
+
const copy = { ...entry };
|
|
608
|
+
delete copy.namespace;
|
|
609
|
+
return copy;
|
|
610
|
+
});
|
|
611
|
+
};
|
|
612
|
+
const toolCalls = strip(r.toolCalls, () => true);
|
|
613
|
+
const blocks = strip(r.blocks, (entry) => entry.type === "toolCall");
|
|
614
|
+
if (toolCalls === r.toolCalls && blocks === r.blocks) return response;
|
|
615
|
+
const copy = { ...r };
|
|
616
|
+
if (toolCalls !== r.toolCalls) copy.toolCalls = toolCalls;
|
|
617
|
+
if (blocks !== r.blocks) copy.blocks = blocks;
|
|
618
|
+
return copy;
|
|
173
619
|
}
|
|
174
620
|
function normalizeFactoryResponse(raw) {
|
|
175
621
|
const r = { ...raw };
|
|
@@ -188,6 +634,10 @@ function normalizeFactoryResponse(raw) {
|
|
|
188
634
|
};
|
|
189
635
|
return { ...block };
|
|
190
636
|
});
|
|
637
|
+
if (Array.isArray(r.responsesBlocks)) r.responsesBlocks = r.responsesBlocks.map((block) => isPlainObject(block) && block.type === "toolCall" && typeof block.arguments === "object" && block.arguments !== null ? {
|
|
638
|
+
...block,
|
|
639
|
+
arguments: JSON.stringify(block.arguments)
|
|
640
|
+
} : block);
|
|
191
641
|
return r;
|
|
192
642
|
}
|
|
193
643
|
function generateId(prefix = "chatcmpl") {
|
|
@@ -259,6 +709,12 @@ function isTextResponse(r) {
|
|
|
259
709
|
function isToolCallResponse(r) {
|
|
260
710
|
return "toolCalls" in r && Array.isArray(r.toolCalls) && !("content" in r && typeof r.content === "string");
|
|
261
711
|
}
|
|
712
|
+
/**
|
|
713
|
+
* Whether `r` is a combined content + tool calls response: a string `content`
|
|
714
|
+
* with a `toolCalls` array, or a non-empty `blocks` array. Public, with the
|
|
715
|
+
* 1.44.0 result for every input; `responsesBlocks` does not count. aimock's
|
|
716
|
+
* own handlers use {@link isCombinedFixtureResponse}, which also counts it.
|
|
717
|
+
*/
|
|
262
718
|
function isContentWithToolCallsResponse(r) {
|
|
263
719
|
const o = r;
|
|
264
720
|
const hasContentAndToolCalls = "content" in r && typeof o.content === "string" && "toolCalls" in r && Array.isArray(o.toolCalls);
|
|
@@ -266,6 +722,19 @@ function isContentWithToolCallsResponse(r) {
|
|
|
266
722
|
return hasContentAndToolCalls || hasNonEmptyBlocks;
|
|
267
723
|
}
|
|
268
724
|
/**
|
|
725
|
+
* @internal The combined-response guard aimock's handlers, validation and
|
|
726
|
+
* misbehavior use: {@link isContentWithToolCallsResponse} plus a non-empty
|
|
727
|
+
* `responsesBlocks`. In legacy mode those keys are dropped before serving, so
|
|
728
|
+
* only `responsesTools: "extended"` sees the extra clause.
|
|
729
|
+
*/
|
|
730
|
+
function isCombinedFixtureResponse(r) {
|
|
731
|
+
const o = r;
|
|
732
|
+
const hasContentAndToolCalls = "content" in r && typeof o.content === "string" && "toolCalls" in r && Array.isArray(o.toolCalls);
|
|
733
|
+
const hasNonEmptyBlocks = Array.isArray(o.blocks) && o.blocks.length > 0;
|
|
734
|
+
const hasNonEmptyResponsesBlocks = Array.isArray(o.responsesBlocks) && o.responsesBlocks.length > 0;
|
|
735
|
+
return hasContentAndToolCalls || hasNonEmptyBlocks || hasNonEmptyResponsesBlocks;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
269
738
|
* Validate and pass through the ordered `blocks` field of a combined
|
|
270
739
|
* content+toolCalls fixture. Used ONLY on the new block-iteration path (when a
|
|
271
740
|
* fixture explicitly sets `blocks`); it is NOT a legacy-order reconstructor —
|
|
@@ -292,7 +761,9 @@ function isContentWithToolCallsResponse(r) {
|
|
|
292
761
|
* optional string `id`. The return type guarantees `arguments: string` — every
|
|
293
762
|
* caller relies on that. Throws on a malformed array or entry — same fail-fast
|
|
294
763
|
* idiom as the other fixture validators in this module (see e.g. the factory
|
|
295
|
-
* guard at {@link resolveResponse}).
|
|
764
|
+
* guard at {@link resolveResponse}). A `customToolCall` block is not a
|
|
765
|
+
* `blocks` entry (it belongs in `responsesBlocks`, see
|
|
766
|
+
* {@link resolveResponsesBlocks}) and fails as an unknown type.
|
|
296
767
|
*/
|
|
297
768
|
function resolveFixtureBlocks(blocks) {
|
|
298
769
|
if (!Array.isArray(blocks)) throw new Error(`Invalid fixture blocks: expected an array, got ${typeof blocks}`);
|
|
@@ -322,6 +793,130 @@ function resolveFixtureBlocks(blocks) {
|
|
|
322
793
|
} else throw new Error(`Invalid fixture block at index ${i}: unknown type ${JSON.stringify(b.type)} (expected "text" or "toolCall")`);
|
|
323
794
|
});
|
|
324
795
|
}
|
|
796
|
+
/**
|
|
797
|
+
* Validate and normalize a fixture's `responsesBlocks` (OpenAI Responses
|
|
798
|
+
* only). Same rules and normalized copy as {@link resolveFixtureBlocks}, plus:
|
|
799
|
+
* a `customToolCall` block with a non-empty string `name`, a string `input`
|
|
800
|
+
* (never parsed or stringified), no `arguments` and an optional string `id`;
|
|
801
|
+
* on either tool block, `namespace` must be a non-empty string when present,
|
|
802
|
+
* and a `toolCall` block carries no `input`. The key is new, so a malformed
|
|
803
|
+
* tool block throws the coded {@link InvalidFixtureToolCallError}; a malformed
|
|
804
|
+
* text block or an unknown type throws a plain Error.
|
|
805
|
+
*/
|
|
806
|
+
function resolveResponsesBlocks(blocks) {
|
|
807
|
+
if (!Array.isArray(blocks)) throw new Error(`Invalid fixture responsesBlocks: expected an array, got ${typeof blocks}`);
|
|
808
|
+
return blocks.map((block, i) => {
|
|
809
|
+
if (!isPlainObject(block)) throw new Error(`Invalid fixture block at index ${i}: expected an object`);
|
|
810
|
+
const b = block;
|
|
811
|
+
const failToolCall = (problem) => {
|
|
812
|
+
throw new InvalidFixtureToolCallError(i, problem, "blocks");
|
|
813
|
+
};
|
|
814
|
+
if ((b.type === "toolCall" || b.type === "customToolCall") && b.namespace !== void 0 && (typeof b.namespace !== "string" || b.namespace === "")) failToolCall("\"namespace\" must be a non-empty string when present");
|
|
815
|
+
const namespace = typeof b.namespace === "string" ? { namespace: b.namespace } : {};
|
|
816
|
+
const id = typeof b.id === "string" ? { id: b.id } : {};
|
|
817
|
+
if (b.type === "text") {
|
|
818
|
+
if (typeof b.text !== "string") throw new Error(`Invalid fixture block at index ${i}: "text" block requires a string "text" field`);
|
|
819
|
+
return {
|
|
820
|
+
type: "text",
|
|
821
|
+
text: b.text
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
if (b.type === "toolCall") {
|
|
825
|
+
if (b.input !== void 0) failToolCall("\"input\" is only valid on a \"customToolCall\" block");
|
|
826
|
+
if (typeof b.name !== "string") failToolCall("\"toolCall\" block requires a string \"name\" field");
|
|
827
|
+
if (b.id !== void 0 && typeof b.id !== "string") failToolCall("\"toolCall\" block \"id\" must be a string when present");
|
|
828
|
+
const args = typeof b.arguments === "object" && b.arguments !== null ? JSON.stringify(b.arguments) : b.arguments;
|
|
829
|
+
if (typeof args !== "string") failToolCall("\"toolCall\" block requires a string or object \"arguments\" field");
|
|
830
|
+
return {
|
|
831
|
+
type: "toolCall",
|
|
832
|
+
name: b.name,
|
|
833
|
+
arguments: args,
|
|
834
|
+
...id,
|
|
835
|
+
...namespace
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
if (b.type === "customToolCall") {
|
|
839
|
+
if (typeof b.name !== "string" || b.name === "") failToolCall("\"customToolCall\" block requires a non-empty string \"name\" field");
|
|
840
|
+
if (typeof b.input !== "string") failToolCall("\"customToolCall\" block requires a string \"input\" field");
|
|
841
|
+
if (b.arguments !== void 0) failToolCall("\"customToolCall\" block takes \"input\", not \"arguments\"");
|
|
842
|
+
if (b.id !== void 0 && typeof b.id !== "string") failToolCall("\"customToolCall\" block \"id\" must be a string when present");
|
|
843
|
+
return {
|
|
844
|
+
type: "customToolCall",
|
|
845
|
+
name: b.name,
|
|
846
|
+
input: b.input,
|
|
847
|
+
...id,
|
|
848
|
+
...namespace
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
throw new Error(`Invalid fixture block at index ${i}: unknown type ${JSON.stringify(b.type)} (expected "text", "toolCall" or "customToolCall")`);
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
/** Allocate the exact OpenAI call identities once for both wire output and observations. */
|
|
855
|
+
function prepareOpenAIChatMisbehavior(plan) {
|
|
856
|
+
const response = plan.response;
|
|
857
|
+
const combined = isCombinedFixtureResponse(response);
|
|
858
|
+
const outcome = combined ? resolveServedBlockOutcome(response) : void 0;
|
|
859
|
+
const calls = outcome?.toolCalls ?? (combined || isToolCallResponse(response) ? servedToolCalls(response) : []);
|
|
860
|
+
const duplicate = plan.duplicateId;
|
|
861
|
+
const toolCalls = calls.map((call, index) => ({
|
|
862
|
+
...call,
|
|
863
|
+
...index === duplicate?.destinationIndex ? {} : { id: call.id || generateToolCallId() }
|
|
864
|
+
}));
|
|
865
|
+
if (duplicate) toolCalls[duplicate.destinationIndex].id = toolCalls[duplicate.sourceIndex].id;
|
|
866
|
+
let preparedResponse = { ...response };
|
|
867
|
+
if ("usage" in preparedResponse) delete preparedResponse.usage;
|
|
868
|
+
if (plan.stop === "length") preparedResponse = {
|
|
869
|
+
...preparedResponse,
|
|
870
|
+
finishReason: "length"
|
|
871
|
+
};
|
|
872
|
+
if (outcome) preparedResponse = withServedToolCalls({
|
|
873
|
+
...preparedResponse,
|
|
874
|
+
content: outcome.content
|
|
875
|
+
}, toolCalls, rebuildOrderedBlocks(outcome.ordered, toolCalls));
|
|
876
|
+
else if (combined || isToolCallResponse(response)) preparedResponse = withServedToolCalls(preparedResponse, toolCalls);
|
|
877
|
+
return {
|
|
878
|
+
...plan,
|
|
879
|
+
response: preparedResponse,
|
|
880
|
+
summary: {
|
|
881
|
+
...plan.summary,
|
|
882
|
+
servedToolCalls: toolCalls.map(servedMisbehaviorToolCall)
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* The journaled form of a served misbehavior tool call: `{ name, arguments,
|
|
888
|
+
* id }`. A Responses custom tool call adds `type: "custom"` and carries its
|
|
889
|
+
* free-text `input` in `arguments`; `namespace` is recorded only when set.
|
|
890
|
+
*/
|
|
891
|
+
function servedMisbehaviorToolCall(call) {
|
|
892
|
+
const namespace = call.namespace !== void 0 ? { namespace: call.namespace } : {};
|
|
893
|
+
return call.type === "custom" ? {
|
|
894
|
+
type: "custom",
|
|
895
|
+
name: call.name,
|
|
896
|
+
arguments: call.input,
|
|
897
|
+
id: call.id,
|
|
898
|
+
...namespace
|
|
899
|
+
} : {
|
|
900
|
+
name: call.name,
|
|
901
|
+
arguments: call.arguments,
|
|
902
|
+
id: call.id,
|
|
903
|
+
...namespace
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
/** The text a tool call contributes to an estimated completion: name plus arguments or input. */
|
|
907
|
+
function toolCallUsageText(call) {
|
|
908
|
+
return call.name + (call.type === "custom" ? call.input : call.arguments);
|
|
909
|
+
}
|
|
910
|
+
/** Estimate once from the full applied output, before any delivery interruption. */
|
|
911
|
+
function resolveOpenAIChatMisbehaviorUsage(plan, request, exposeReasoning = true) {
|
|
912
|
+
const response = plan.response;
|
|
913
|
+
const combined = isCombinedFixtureResponse(response);
|
|
914
|
+
const outcome = combined ? resolveServedBlockOutcome(response) : void 0;
|
|
915
|
+
const calls = outcome?.toolCalls ?? (combined || isToolCallResponse(response) ? servedToolCalls(response) : []);
|
|
916
|
+
const content = outcome?.content ?? ("content" in response && typeof response.content === "string" ? response.content : "");
|
|
917
|
+
const reasoning = plan.reasoning ?? ("reasoning" in response && typeof response.reasoning === "string" ? response.reasoning : "");
|
|
918
|
+
return resolveUsage(void 0, request.messages.map((message) => typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content.map((part) => part.text ?? "").join("") : "").join(""), content + calls.map(toolCallUsageText).join("") + (plan.refusal ?? "") + (exposeReasoning ? reasoning : ""));
|
|
919
|
+
}
|
|
325
920
|
/** Project normalized ordered blocks without changing the fixture or generating ids. */
|
|
326
921
|
function resolveFixtureBlockOutcome(blocks) {
|
|
327
922
|
const ordered = resolveFixtureBlocks(blocks);
|
|
@@ -340,6 +935,118 @@ function resolveFixtureBlockOutcome(blocks) {
|
|
|
340
935
|
hasToolCalls: toolCalls.length > 0
|
|
341
936
|
};
|
|
342
937
|
}
|
|
938
|
+
/**
|
|
939
|
+
* Every tool call a response serves, in order: the function calls of
|
|
940
|
+
* `toolCalls`, then the custom calls of `customToolCalls` (each tagged
|
|
941
|
+
* `type: "custom"`). Only the OpenAI Responses API may serve the custom ones;
|
|
942
|
+
* the other wires reject a non-empty `customToolCalls` first
|
|
943
|
+
* ({@link rejectResponsesOnlyToolCalls}).
|
|
944
|
+
*/
|
|
945
|
+
function servedToolCalls(response) {
|
|
946
|
+
const calls = [...response.toolCalls ?? []];
|
|
947
|
+
for (const call of Array.isArray(response.customToolCalls) ? response.customToolCalls : []) calls.push(isPlainObject(call) ? {
|
|
948
|
+
...call,
|
|
949
|
+
type: "custom"
|
|
950
|
+
} : call);
|
|
951
|
+
return calls;
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Resolve the ordered blocks a response serves, with each tool block projected
|
|
955
|
+
* to its {@link FixtureToolCall}: a non-empty `responsesBlocks` (OpenAI
|
|
956
|
+
* Responses only, through {@link resolveResponsesBlocks}), else a non-empty
|
|
957
|
+
* legacy `blocks` (through {@link resolveFixtureBlocks}, which rejects a
|
|
958
|
+
* `customToolCall` block with the error of earlier releases). Undefined when
|
|
959
|
+
* the response has neither.
|
|
960
|
+
*/
|
|
961
|
+
function resolveServedBlockOutcome(response) {
|
|
962
|
+
const r = response;
|
|
963
|
+
let ordered;
|
|
964
|
+
let source;
|
|
965
|
+
if (Array.isArray(r.responsesBlocks) && r.responsesBlocks.length > 0) {
|
|
966
|
+
ordered = resolveResponsesBlocks(r.responsesBlocks);
|
|
967
|
+
source = "responsesBlocks";
|
|
968
|
+
} else if (Array.isArray(r.blocks) && r.blocks.length > 0) {
|
|
969
|
+
ordered = resolveFixtureBlocks(r.blocks);
|
|
970
|
+
source = "blocks";
|
|
971
|
+
} else return void 0;
|
|
972
|
+
let content = "";
|
|
973
|
+
const toolCalls = [];
|
|
974
|
+
for (const block of ordered) if (block.type === "text") content += block.text;
|
|
975
|
+
else toolCalls.push(fixtureBlockToolCall(block));
|
|
976
|
+
return {
|
|
977
|
+
ordered,
|
|
978
|
+
content,
|
|
979
|
+
toolCalls,
|
|
980
|
+
hasToolCalls: toolCalls.length > 0,
|
|
981
|
+
source
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Write rewritten served tool calls (and, with `ordered`, rebuilt ordered
|
|
986
|
+
* blocks) back into a copy of `response`, split by carrier: function calls go
|
|
987
|
+
* to `toolCalls`, custom calls to `customToolCalls`, and the blocks to
|
|
988
|
+
* `responsesBlocks` when the response served those, else to `blocks`.
|
|
989
|
+
*/
|
|
990
|
+
function withServedToolCalls(response, toolCalls, ordered) {
|
|
991
|
+
const functionCalls = [];
|
|
992
|
+
const customCalls = [];
|
|
993
|
+
for (const call of toolCalls) if (call.type === "custom") customCalls.push(call);
|
|
994
|
+
else functionCalls.push(call);
|
|
995
|
+
const out = {
|
|
996
|
+
...response,
|
|
997
|
+
toolCalls: functionCalls
|
|
998
|
+
};
|
|
999
|
+
if (customCalls.length > 0 || "customToolCalls" in response) out.customToolCalls = customCalls;
|
|
1000
|
+
if (ordered) {
|
|
1001
|
+
const r = response;
|
|
1002
|
+
if (Array.isArray(r.responsesBlocks) && r.responsesBlocks.length > 0) out.responsesBlocks = ordered;
|
|
1003
|
+
else out.blocks = ordered;
|
|
1004
|
+
}
|
|
1005
|
+
return out;
|
|
1006
|
+
}
|
|
1007
|
+
/** Project one normalized tool block to the matching served tool call. */
|
|
1008
|
+
function fixtureBlockToolCall(block) {
|
|
1009
|
+
const id = block.id !== void 0 ? { id: block.id } : {};
|
|
1010
|
+
const namespace = block.namespace !== void 0 ? { namespace: block.namespace } : {};
|
|
1011
|
+
return block.type === "customToolCall" ? {
|
|
1012
|
+
type: "custom",
|
|
1013
|
+
name: block.name,
|
|
1014
|
+
input: block.input,
|
|
1015
|
+
...id,
|
|
1016
|
+
...namespace
|
|
1017
|
+
} : {
|
|
1018
|
+
name: block.name,
|
|
1019
|
+
arguments: block.arguments,
|
|
1020
|
+
...id,
|
|
1021
|
+
...namespace
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
/** Project one served tool call back to its ordered tool block. */
|
|
1025
|
+
function toolCallFixtureBlock(call) {
|
|
1026
|
+
const id = call.id !== void 0 ? { id: call.id } : {};
|
|
1027
|
+
const namespace = call.namespace !== void 0 ? { namespace: call.namespace } : {};
|
|
1028
|
+
return call.type === "custom" ? {
|
|
1029
|
+
type: "customToolCall",
|
|
1030
|
+
name: call.name,
|
|
1031
|
+
input: call.input,
|
|
1032
|
+
...id,
|
|
1033
|
+
...namespace
|
|
1034
|
+
} : {
|
|
1035
|
+
type: "toolCall",
|
|
1036
|
+
name: call.name,
|
|
1037
|
+
arguments: call.arguments,
|
|
1038
|
+
...id,
|
|
1039
|
+
...namespace
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Rebuild ordered blocks after their tool calls were rewritten one-for-one:
|
|
1044
|
+
* text blocks are copied and the Nth tool block becomes `toolCalls[N]`.
|
|
1045
|
+
*/
|
|
1046
|
+
function rebuildOrderedBlocks(ordered, toolCalls) {
|
|
1047
|
+
let callIndex = 0;
|
|
1048
|
+
return ordered.map((block) => block.type === "text" ? { ...block } : toolCallFixtureBlock(toolCalls[callIndex++]));
|
|
1049
|
+
}
|
|
343
1050
|
function isErrorResponse(r) {
|
|
344
1051
|
return "error" in r && r.error !== null && typeof r.error === "object" && "message" in r.error && typeof r.error.message === "string";
|
|
345
1052
|
}
|
|
@@ -417,6 +1124,17 @@ function estimateTokens(text) {
|
|
|
417
1124
|
return Math.max(1, Math.ceil(text.length / 4));
|
|
418
1125
|
}
|
|
419
1126
|
/**
|
|
1127
|
+
* Estimate prompt tokens from a request's messages array.
|
|
1128
|
+
*/
|
|
1129
|
+
function estimatePromptTokens(messages) {
|
|
1130
|
+
let totalChars = 0;
|
|
1131
|
+
for (const msg of messages) if (typeof msg.content === "string") totalChars += msg.content.length;
|
|
1132
|
+
else if (Array.isArray(msg.content)) {
|
|
1133
|
+
for (const part of msg.content) if (part.text) totalChars += part.text.length;
|
|
1134
|
+
}
|
|
1135
|
+
return Math.max(1, Math.ceil(totalChars / 4));
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
420
1138
|
* Build usage object: use explicit overrides if provided, otherwise estimate.
|
|
421
1139
|
*
|
|
422
1140
|
* Shared by BOTH the non-streaming completion builders (below) and the
|
|
@@ -531,6 +1249,140 @@ function buildTextChunks(content, model, chunkSize, reasoning, overrides) {
|
|
|
531
1249
|
});
|
|
532
1250
|
return chunks;
|
|
533
1251
|
}
|
|
1252
|
+
/** K9 exposure is selected by the provider path, never the echoed response model. */
|
|
1253
|
+
function buildOpenAIReasoningChunks(reasoning, model, chunkSize, exposeReasoning, overrides) {
|
|
1254
|
+
return buildTextChunks(exposeReasoning ? reasoning : "", model, chunkSize, void 0, {
|
|
1255
|
+
...overrides,
|
|
1256
|
+
role: "assistant",
|
|
1257
|
+
finishReason: "length"
|
|
1258
|
+
}).map((chunk) => ({
|
|
1259
|
+
...chunk,
|
|
1260
|
+
choices: chunk.choices.map((choice) => ({
|
|
1261
|
+
...choice,
|
|
1262
|
+
delta: choice.delta.role !== void 0 ? {
|
|
1263
|
+
role: "assistant",
|
|
1264
|
+
content: null
|
|
1265
|
+
} : typeof choice.delta.content === "string" ? {
|
|
1266
|
+
role: "assistant",
|
|
1267
|
+
content: "",
|
|
1268
|
+
reasoning: choice.delta.content,
|
|
1269
|
+
reasoning_details: [{
|
|
1270
|
+
type: "reasoning.text",
|
|
1271
|
+
text: choice.delta.content,
|
|
1272
|
+
format: "unknown",
|
|
1273
|
+
index: 0
|
|
1274
|
+
}]
|
|
1275
|
+
} : {}
|
|
1276
|
+
}))
|
|
1277
|
+
}));
|
|
1278
|
+
}
|
|
1279
|
+
/** Render exposed reasoning or native OpenAI's empty length completion. */
|
|
1280
|
+
function buildOpenAIReasoningCompletion(reasoning, model, exposeReasoning, overrides, requestMessages) {
|
|
1281
|
+
const visible = exposeReasoning ? reasoning : "";
|
|
1282
|
+
const completion = buildTextCompletion(visible, model, void 0, {
|
|
1283
|
+
...overrides,
|
|
1284
|
+
role: "assistant",
|
|
1285
|
+
finishReason: "length",
|
|
1286
|
+
usage: void 0
|
|
1287
|
+
}, requestMessages);
|
|
1288
|
+
return {
|
|
1289
|
+
...completion,
|
|
1290
|
+
choices: completion.choices.map((choice) => ({
|
|
1291
|
+
...choice,
|
|
1292
|
+
message: {
|
|
1293
|
+
role: "assistant",
|
|
1294
|
+
content: null,
|
|
1295
|
+
refusal: null,
|
|
1296
|
+
...exposeReasoning ? {
|
|
1297
|
+
reasoning: visible,
|
|
1298
|
+
reasoning_details: [{
|
|
1299
|
+
type: "reasoning.text",
|
|
1300
|
+
text: visible,
|
|
1301
|
+
format: "unknown",
|
|
1302
|
+
index: 0
|
|
1303
|
+
}]
|
|
1304
|
+
} : {}
|
|
1305
|
+
}
|
|
1306
|
+
}))
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
/** Render withheld output with only the role and native content-filter terminal. */
|
|
1310
|
+
function buildOpenAIContentFilterChunks(model, overrides) {
|
|
1311
|
+
return buildTextChunks("", model, 1, void 0, {
|
|
1312
|
+
...overrides,
|
|
1313
|
+
role: "assistant",
|
|
1314
|
+
finishReason: "content_filter"
|
|
1315
|
+
}).map((chunk) => ({
|
|
1316
|
+
...chunk,
|
|
1317
|
+
choices: chunk.choices.map((choice) => ({
|
|
1318
|
+
...choice,
|
|
1319
|
+
delta: choice.delta.role !== void 0 ? {
|
|
1320
|
+
role: "assistant",
|
|
1321
|
+
content: null
|
|
1322
|
+
} : {}
|
|
1323
|
+
}))
|
|
1324
|
+
}));
|
|
1325
|
+
}
|
|
1326
|
+
/** Non-streaming filtered output contains no answer, refusal text, reasoning, or calls. */
|
|
1327
|
+
function buildOpenAIContentFilterCompletion(model, overrides, requestMessages) {
|
|
1328
|
+
const completion = buildTextCompletion("", model, void 0, {
|
|
1329
|
+
...overrides,
|
|
1330
|
+
role: "assistant",
|
|
1331
|
+
finishReason: "content_filter",
|
|
1332
|
+
usage: void 0
|
|
1333
|
+
}, requestMessages);
|
|
1334
|
+
return {
|
|
1335
|
+
...completion,
|
|
1336
|
+
choices: completion.choices.map((choice) => ({
|
|
1337
|
+
...choice,
|
|
1338
|
+
message: {
|
|
1339
|
+
role: "assistant",
|
|
1340
|
+
content: null,
|
|
1341
|
+
refusal: null
|
|
1342
|
+
}
|
|
1343
|
+
}))
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
/** Render an applied refusal on its own channel without ordinary content or reasoning. */
|
|
1347
|
+
function buildOpenAIRefusalChunks(refusal, model, chunkSize, overrides) {
|
|
1348
|
+
return buildTextChunks(refusal, model, chunkSize, void 0, {
|
|
1349
|
+
...overrides,
|
|
1350
|
+
role: "assistant",
|
|
1351
|
+
finishReason: "stop"
|
|
1352
|
+
}).map((chunk) => ({
|
|
1353
|
+
...chunk,
|
|
1354
|
+
choices: chunk.choices.map((choice) => {
|
|
1355
|
+
const delta = choice.delta.role !== void 0 ? {
|
|
1356
|
+
role: "assistant",
|
|
1357
|
+
content: null
|
|
1358
|
+
} : typeof choice.delta.content === "string" ? { refusal: choice.delta.content } : {};
|
|
1359
|
+
return {
|
|
1360
|
+
...choice,
|
|
1361
|
+
delta
|
|
1362
|
+
};
|
|
1363
|
+
})
|
|
1364
|
+
}));
|
|
1365
|
+
}
|
|
1366
|
+
/** Refusal-only counterpart of the ordinary text completion builder. */
|
|
1367
|
+
function buildOpenAIRefusalCompletion(refusal, model, overrides, requestMessages) {
|
|
1368
|
+
const completion = buildTextCompletion(refusal, model, void 0, {
|
|
1369
|
+
...overrides,
|
|
1370
|
+
role: "assistant",
|
|
1371
|
+
finishReason: "stop",
|
|
1372
|
+
usage: void 0
|
|
1373
|
+
}, requestMessages);
|
|
1374
|
+
return {
|
|
1375
|
+
...completion,
|
|
1376
|
+
choices: completion.choices.map((choice) => ({
|
|
1377
|
+
...choice,
|
|
1378
|
+
message: {
|
|
1379
|
+
role: "assistant",
|
|
1380
|
+
content: null,
|
|
1381
|
+
refusal
|
|
1382
|
+
}
|
|
1383
|
+
}))
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
534
1386
|
function buildToolCallChunks(toolCalls, model, chunkSize, reasoning, overrides) {
|
|
535
1387
|
const id = overrides?.id ?? generateId();
|
|
536
1388
|
const created = overrides?.created ?? Math.floor(Date.now() / 1e3);
|
|
@@ -1071,11 +1923,14 @@ function matchesPattern(text, pattern) {
|
|
|
1071
1923
|
return result;
|
|
1072
1924
|
}
|
|
1073
1925
|
/**
|
|
1074
|
-
*
|
|
1075
|
-
*
|
|
1076
|
-
* `
|
|
1077
|
-
*
|
|
1078
|
-
*
|
|
1926
|
+
* Which test LLM and other HTTP-route traffic belongs to: the `X-Test-Id`
|
|
1927
|
+
* header (raw, not percent-decoded) wins, then `?testId=` in the query string
|
|
1928
|
+
* (read through `URLSearchParams`, so percent-decoded and with `+` as a
|
|
1929
|
+
* space), then `DEFAULT_TEST_ID`. Every per-test axis of those routes
|
|
1930
|
+
* (fixture match-counts, the journal filter, chaos scoping) resolves through
|
|
1931
|
+
* this. MCP mounts resolve the test id with {@link resolveMcpIdentity}
|
|
1932
|
+
* instead, which percent-decodes the header, so `X-Test-Id: a%20b` is `a%20b`
|
|
1933
|
+
* here and `a b` there. Both read a `+` in the query as a space.
|
|
1079
1934
|
*/
|
|
1080
1935
|
function resolveTestId(headers, url) {
|
|
1081
1936
|
const headerValue = headers["x-test-id"];
|
|
@@ -1099,6 +1954,237 @@ function getContext(req) {
|
|
|
1099
1954
|
} else if (typeof headerValue === "string" && headerValue) return headerValue;
|
|
1100
1955
|
}
|
|
1101
1956
|
/**
|
|
1957
|
+
* Decode one MCP identity value: an `X-Test-Id` / `X-AIMock-Context` header
|
|
1958
|
+
* value, and also each query name and (through {@link decodeMcpQueryValue})
|
|
1959
|
+
* each `?testId=` / `?context=` / `?undeclared=` query value. Clients send `encodeURIComponent(id)` because
|
|
1960
|
+
* `fetch` rejects characters such as `›` in a header. A value that is not
|
|
1961
|
+
* valid percent-encoding (a raw `applies 50% discount`) is used as-is, with
|
|
1962
|
+
* `fellBack: true` so the caller can log the fallback. Only that `URIError`
|
|
1963
|
+
* is caught; any other throw propagates. MCP requests only: LLM routes read
|
|
1964
|
+
* the header raw and the query through `URLSearchParams` (see
|
|
1965
|
+
* {@link resolveTestId}).
|
|
1966
|
+
*/
|
|
1967
|
+
function decodeMcpHeaderValue(raw) {
|
|
1968
|
+
try {
|
|
1969
|
+
return {
|
|
1970
|
+
value: decodeURIComponent(raw),
|
|
1971
|
+
fellBack: false
|
|
1972
|
+
};
|
|
1973
|
+
} catch (err) {
|
|
1974
|
+
if (!(err instanceof URIError)) throw err;
|
|
1975
|
+
return {
|
|
1976
|
+
value: raw,
|
|
1977
|
+
fellBack: true
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* Decode one MCP query value (`?testId=`, `?context=`, `?undeclared=`) the way
|
|
1983
|
+
* `URLSearchParams` reads it (spec I1): a `+` is a space, then the value is
|
|
1984
|
+
* percent-decoded by the {@link decodeMcpHeaderValue} rule. So a query built
|
|
1985
|
+
* with `URL.searchParams.set(...)` (a space sent as `+`) and one built with
|
|
1986
|
+
* `encodeURIComponent` both resolve to the id itself. A value that is not
|
|
1987
|
+
* valid percent-encoding is used as-is (its `+` still a space), with
|
|
1988
|
+
* `fellBack: true`.
|
|
1989
|
+
*/
|
|
1990
|
+
function decodeMcpQueryValue(raw) {
|
|
1991
|
+
return decodeMcpHeaderValue(raw.replace(/\+/g, " "));
|
|
1992
|
+
}
|
|
1993
|
+
/**
|
|
1994
|
+
* Every value of the header `name`, one per header line: `[]` when absent.
|
|
1995
|
+
* Node joins a repeated header into one comma-joined `req.headers` string, so
|
|
1996
|
+
* the values come from `req.headersDistinct` (one element per line) when the
|
|
1997
|
+
* request has it. A caller without it (a hand-built request) is read from
|
|
1998
|
+
* `req.headers`: one element for a string, each element for an array.
|
|
1999
|
+
*/
|
|
2000
|
+
function headerValues(req, name) {
|
|
2001
|
+
const distinct = req.headersDistinct?.[name];
|
|
2002
|
+
if (distinct !== void 0) return distinct;
|
|
2003
|
+
const value = req.headers[name];
|
|
2004
|
+
if (value === void 0) return [];
|
|
2005
|
+
return Array.isArray(value) ? value : [value];
|
|
2006
|
+
}
|
|
2007
|
+
/**
|
|
2008
|
+
* Every pair of a query string, in order, each name decoded by the
|
|
2009
|
+
* `decodeMcpHeaderValue` rule. A bare `name` with no `=` counts as an
|
|
2010
|
+
* occurrence with an empty value, so `?testId&testId=t1` has two. Each value
|
|
2011
|
+
* is left raw for the caller to decode with {@link decodeMcpQueryValue}.
|
|
2012
|
+
* A name that is not valid percent-encoding (`test%Id`) gets `name: null`: it
|
|
2013
|
+
* matches no field, and the caller counts it.
|
|
2014
|
+
*/
|
|
2015
|
+
function queryPairs(query) {
|
|
2016
|
+
if (query === "") return [];
|
|
2017
|
+
return query.split("&").map((pair) => {
|
|
2018
|
+
const eq = pair.indexOf("=");
|
|
2019
|
+
const key = decodeMcpHeaderValue(eq === -1 ? pair : pair.slice(0, eq));
|
|
2020
|
+
return {
|
|
2021
|
+
name: key.fellBack ? null : key.value,
|
|
2022
|
+
value: eq === -1 ? "" : pair.slice(eq + 1)
|
|
2023
|
+
};
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* A request value as a message part, the way the message builder quotes every
|
|
2028
|
+
* user string: JSON-quoted (quotes and control characters escaped), then cut
|
|
2029
|
+
* once as JSON text, so the whole echo, quotes, escapes and the
|
|
2030
|
+
* `… (<n> more chars)` count included, fits in `MCP_FAKES_ECHO_LIMIT` and the
|
|
2031
|
+
* cut never splits a JSON escape or a surrogate pair.
|
|
2032
|
+
*/
|
|
2033
|
+
function echoIdentityValue(value) {
|
|
2034
|
+
return require_message_text.quote(value, require_constants.MCP_FAKES_ECHO_LIMIT);
|
|
2035
|
+
}
|
|
2036
|
+
const MCP_IDENTITY_INPUTS = {
|
|
2037
|
+
testId: {
|
|
2038
|
+
header: "x-test-id",
|
|
2039
|
+
query: "testId",
|
|
2040
|
+
label: "X-Test-Id"
|
|
2041
|
+
},
|
|
2042
|
+
context: {
|
|
2043
|
+
header: "x-aimock-context",
|
|
2044
|
+
query: "context",
|
|
2045
|
+
label: "X-AIMock-Context"
|
|
2046
|
+
},
|
|
2047
|
+
undeclared: {
|
|
2048
|
+
header: "x-aimock-mcp-undeclared",
|
|
2049
|
+
query: "undeclared",
|
|
2050
|
+
label: "X-AIMock-MCP-Undeclared"
|
|
2051
|
+
}
|
|
2052
|
+
};
|
|
2053
|
+
/**
|
|
2054
|
+
* Resolve the identity (test id, context, undeclared override) of one MCP request.
|
|
2055
|
+
* Each field resolves on its own, first of: the header, the query parameter,
|
|
2056
|
+
* the session value bound at `initialize`. A field sent more than once on
|
|
2057
|
+
* either path (more than one header line, read from `headersDistinct` because
|
|
2058
|
+
* Node joins repeated lines into one `headers` string; a header array from a
|
|
2059
|
+
* hand-built request; a repeated query name) is an error
|
|
2060
|
+
* (`MCP_DUPLICATE_IDENTITY`), even when the other path also carries it. For
|
|
2061
|
+
* the test id and the context, a header value is decoded with
|
|
2062
|
+
* `decodeURIComponent` (raw value on failure, with `fellBack` set) and a query
|
|
2063
|
+
* value the way `URLSearchParams` reads it (spec I1): a `+` is a space, then
|
|
2064
|
+
* the same `decodeURIComponent` rule. So `encodeURIComponent` output (what
|
|
2065
|
+
* clients send) and a query built with `URL.searchParams` both decode to the
|
|
2066
|
+
* id itself, while a raw `+` is a `+` in the header and a space in the query.
|
|
2067
|
+
* When both paths carry a test id or a context, the header is used and the
|
|
2068
|
+
* query value is ignored, even if the two differ, but an ignored query value
|
|
2069
|
+
* that does not decode still sets `fellBack`. The undeclared header is not
|
|
2070
|
+
* percent-decoded; its query value is, by the same query rule. Both are
|
|
2071
|
+
* trimmed and compared case-insensitively. `supplied` says whether the
|
|
2072
|
+
* request itself carried a non-empty header or query value for the field,
|
|
2073
|
+
* never `resolveTestId`'s result (which returns `"__default__"` when nothing
|
|
2074
|
+
* is sent). Blank values: an empty value is not supplied and falls through;
|
|
2075
|
+
* it is never rejected. A whitespace-only value is not supplied for the
|
|
2076
|
+
* undeclared override (it is trimmed), but is a real value for the test id
|
|
2077
|
+
* and the context, which match exactly and are never trimmed (Node trims a
|
|
2078
|
+
* header value, so only the query or a hand-built request can carry one). A
|
|
2079
|
+
* lone query name with no `=` carries no value. An empty session value counts
|
|
2080
|
+
* as none, and a missing `req.url` as `/`. A field with no value anywhere is
|
|
2081
|
+
* `null`. An unrecognized undeclared override on the header or the query is
|
|
2082
|
+
* an error (`ok: false`), not a fall-through: both are checked before the
|
|
2083
|
+
* header one is used, so a valid header does not hide a mistyped query value.
|
|
2084
|
+
* Bad percent-encoding is never silent: a test id or context value that does
|
|
2085
|
+
* not decode sets `fellBack`, on either path, used or ignored; an undeclared
|
|
2086
|
+
* query value that does not decode is that error, with the message saying
|
|
2087
|
+
* so; and a query name that does not decode matches no field and is counted
|
|
2088
|
+
* in `undecodedQueryNames`. `resolveTestId` / `getContext` stay unchanged for
|
|
2089
|
+
* LLM routes.
|
|
2090
|
+
*/
|
|
2091
|
+
function resolveMcpIdentity(req, session) {
|
|
2092
|
+
const url = req.url ?? "/";
|
|
2093
|
+
const qIdx = url.indexOf("?");
|
|
2094
|
+
const pairs = queryPairs(qIdx === -1 ? "" : url.slice(qIdx + 1));
|
|
2095
|
+
const raw = {};
|
|
2096
|
+
for (const field of [
|
|
2097
|
+
"testId",
|
|
2098
|
+
"context",
|
|
2099
|
+
"undeclared"
|
|
2100
|
+
]) {
|
|
2101
|
+
const input = MCP_IDENTITY_INPUTS[field];
|
|
2102
|
+
const sent = [["header", headerValues(req, input.header)], ["query", pairs.filter((p) => p.name === input.query).map((p) => p.value)]];
|
|
2103
|
+
for (const [source, values] of sent) if (values.length > 1) {
|
|
2104
|
+
const label = source === "header" ? require_message_text.msg`${require_message_text.fixed(input.label)} header` : require_message_text.msg`?${require_message_text.fixed(input.query)}= query parameter`;
|
|
2105
|
+
return {
|
|
2106
|
+
ok: false,
|
|
2107
|
+
error: {
|
|
2108
|
+
code: "MCP_DUPLICATE_IDENTITY",
|
|
2109
|
+
status: 400,
|
|
2110
|
+
field,
|
|
2111
|
+
source,
|
|
2112
|
+
count: values.length,
|
|
2113
|
+
message: require_message_text.build(require_message_text.msg`Duplicate ${label}: ${values.length} values sent, expected one`)
|
|
2114
|
+
}
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
raw[field] = {
|
|
2118
|
+
header: sent[0][1][0],
|
|
2119
|
+
query: sent[1][1][0]
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
const fromRequest = (field) => {
|
|
2123
|
+
const { header, query: q } = raw[field];
|
|
2124
|
+
const fromQuery = q ? decodeMcpQueryValue(q) : null;
|
|
2125
|
+
if (header) {
|
|
2126
|
+
const used = decodeMcpHeaderValue(header);
|
|
2127
|
+
return {
|
|
2128
|
+
value: used.value,
|
|
2129
|
+
fellBack: used.fellBack || (fromQuery?.fellBack ?? false)
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
return fromQuery ?? {
|
|
2133
|
+
value: null,
|
|
2134
|
+
fellBack: false
|
|
2135
|
+
};
|
|
2136
|
+
};
|
|
2137
|
+
const testId = fromRequest("testId");
|
|
2138
|
+
const context = fromRequest("context");
|
|
2139
|
+
let undeclared = null;
|
|
2140
|
+
for (const source of ["header", "query"]) {
|
|
2141
|
+
const sent = raw.undeclared[source];
|
|
2142
|
+
if (sent === void 0) continue;
|
|
2143
|
+
const decoded = source === "header" ? {
|
|
2144
|
+
value: sent,
|
|
2145
|
+
fellBack: false
|
|
2146
|
+
} : decodeMcpQueryValue(sent);
|
|
2147
|
+
if (decoded.value.trim() === "") continue;
|
|
2148
|
+
const parsed = parseMcpUndeclared(decoded.value);
|
|
2149
|
+
if (parsed === null) {
|
|
2150
|
+
const echoed = echoIdentityValue(sent);
|
|
2151
|
+
const label = source === "header" ? require_message_text.msg`X-AIMock-MCP-Undeclared header` : require_message_text.msg`?undeclared= query parameter`;
|
|
2152
|
+
const note = decoded.fellBack ? require_message_text.msg` (not valid percent-encoding)` : require_message_text.msg``;
|
|
2153
|
+
return {
|
|
2154
|
+
ok: false,
|
|
2155
|
+
error: {
|
|
2156
|
+
code: "MCP_INVALID_UNDECLARED",
|
|
2157
|
+
status: 400,
|
|
2158
|
+
field: "undeclared",
|
|
2159
|
+
source,
|
|
2160
|
+
raw: require_echo_text.cutText(sent),
|
|
2161
|
+
message: require_message_text.build(require_message_text.msg`Invalid ${label} value ${echoed}${note}: expected allow or deny`)
|
|
2162
|
+
}
|
|
2163
|
+
};
|
|
2164
|
+
}
|
|
2165
|
+
undeclared ??= parsed;
|
|
2166
|
+
}
|
|
2167
|
+
const undecodedQueryNames = pairs.filter((p) => p.name === null).length;
|
|
2168
|
+
return {
|
|
2169
|
+
ok: true,
|
|
2170
|
+
identity: {
|
|
2171
|
+
testId: testId.value ?? (session?.testId || null),
|
|
2172
|
+
context: context.value ?? (session?.context || null),
|
|
2173
|
+
undeclared: undeclared ?? session?.undeclared ?? null
|
|
2174
|
+
},
|
|
2175
|
+
supplied: {
|
|
2176
|
+
testId: testId.value !== null,
|
|
2177
|
+
context: context.value !== null,
|
|
2178
|
+
undeclared: undeclared !== null
|
|
2179
|
+
},
|
|
2180
|
+
fellBack: {
|
|
2181
|
+
testId: testId.fellBack,
|
|
2182
|
+
context: context.fellBack
|
|
2183
|
+
},
|
|
2184
|
+
...undecodedQueryNames > 0 ? { undecodedQueryNames } : {}
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
1102
2188
|
* Convert a test ID (e.g. Playwright titlePath) into a filesystem-safe slug
|
|
1103
2189
|
* suitable for use as a directory name in snapshot-style recording.
|
|
1104
2190
|
*/
|
|
@@ -1329,21 +2415,51 @@ function describeMatch(match, index) {
|
|
|
1329
2415
|
const cut = head.lastIndexOf(", ");
|
|
1330
2416
|
return `${cut > 0 ? head.slice(0, cut) : head}, … }`;
|
|
1331
2417
|
}
|
|
2418
|
+
/**
|
|
2419
|
+
* Parse an `X-AIMock-MCP-Undeclared` header or `?undeclared=` value.
|
|
2420
|
+
* Modeled on {@link resolveStrictMode}: case-insensitive, surrounding
|
|
2421
|
+
* whitespace trimmed. Returns `null` when unrecognised; unlike the strict
|
|
2422
|
+
* header, {@link resolveMcpIdentity} turns that into an error rather than a
|
|
2423
|
+
* fall-through, because falling through would turn a mistyped `deny` into
|
|
2424
|
+
* the permissive default.
|
|
2425
|
+
*/
|
|
2426
|
+
function parseMcpUndeclared(raw) {
|
|
2427
|
+
const val = raw.trim().toLowerCase();
|
|
2428
|
+
if (val === "allow" || val === "deny") return val;
|
|
2429
|
+
return null;
|
|
2430
|
+
}
|
|
1332
2431
|
|
|
1333
2432
|
//#endregion
|
|
1334
2433
|
exports.FORMAT_TO_CONTENT_TYPE = FORMAT_TO_CONTENT_TYPE;
|
|
2434
|
+
exports.InvalidFixtureToolCallError = InvalidFixtureToolCallError;
|
|
2435
|
+
exports.InvalidToolArgumentsError = InvalidToolArgumentsError;
|
|
1335
2436
|
exports.MAX_EMBEDDING_DIMENSIONS = MAX_EMBEDDING_DIMENSIONS;
|
|
1336
2437
|
exports.RequestBodyTooLargeError = RequestBodyTooLargeError;
|
|
2438
|
+
exports.UnsupportedToolCallError = UnsupportedToolCallError;
|
|
2439
|
+
exports.assertCustomToolCalls = assertCustomToolCalls;
|
|
2440
|
+
exports.assertResponsesToolCalls = assertResponsesToolCalls;
|
|
1337
2441
|
exports.buildContentWithToolCallsChunks = buildContentWithToolCallsChunks;
|
|
1338
2442
|
exports.buildContentWithToolCallsCompletion = buildContentWithToolCallsCompletion;
|
|
1339
2443
|
exports.buildEmbeddingResponse = buildEmbeddingResponse;
|
|
2444
|
+
exports.buildOpenAIContentFilterChunks = buildOpenAIContentFilterChunks;
|
|
2445
|
+
exports.buildOpenAIContentFilterCompletion = buildOpenAIContentFilterCompletion;
|
|
2446
|
+
exports.buildOpenAIReasoningChunks = buildOpenAIReasoningChunks;
|
|
2447
|
+
exports.buildOpenAIReasoningCompletion = buildOpenAIReasoningCompletion;
|
|
2448
|
+
exports.buildOpenAIRefusalChunks = buildOpenAIRefusalChunks;
|
|
2449
|
+
exports.buildOpenAIRefusalCompletion = buildOpenAIRefusalCompletion;
|
|
1340
2450
|
exports.buildTextChunks = buildTextChunks;
|
|
1341
2451
|
exports.buildTextCompletion = buildTextCompletion;
|
|
1342
2452
|
exports.buildToolCallChunks = buildToolCallChunks;
|
|
1343
2453
|
exports.buildToolCallCompletion = buildToolCallCompletion;
|
|
1344
2454
|
exports.buildUsageChunk = buildUsageChunk;
|
|
2455
|
+
exports.createServerFixtureList = createServerFixtureList;
|
|
2456
|
+
exports.decodeMcpHeaderValue = decodeMcpHeaderValue;
|
|
1345
2457
|
exports.describeMatch = describeMatch;
|
|
2458
|
+
exports.estimatePromptTokens = estimatePromptTokens;
|
|
2459
|
+
exports.estimateTokens = estimateTokens;
|
|
1346
2460
|
exports.extractOverrides = extractOverrides;
|
|
2461
|
+
exports.fixtureListTarget = fixtureListTarget;
|
|
2462
|
+
exports.fixtureToolCallErrorCode = fixtureToolCallErrorCode;
|
|
1347
2463
|
exports.flattenHeaders = flattenHeaders;
|
|
1348
2464
|
exports.formatToMime = formatToMime;
|
|
1349
2465
|
exports.generateDeterministicEmbedding = generateDeterministicEmbedding;
|
|
@@ -1353,43 +2469,72 @@ exports.generateToolCallId = generateToolCallId;
|
|
|
1353
2469
|
exports.generateToolUseId = generateToolUseId;
|
|
1354
2470
|
exports.getContext = getContext;
|
|
1355
2471
|
exports.getTestId = getTestId;
|
|
2472
|
+
exports.googleFixtureToolCallErrorDetails = googleFixtureToolCallErrorDetails;
|
|
1356
2473
|
exports.hasMintedRequestId = hasMintedRequestId;
|
|
1357
2474
|
exports.isAudioResponse = isAudioResponse;
|
|
2475
|
+
exports.isCombinedFixtureResponse = isCombinedFixtureResponse;
|
|
1358
2476
|
exports.isContentWithToolCallsResponse = isContentWithToolCallsResponse;
|
|
1359
2477
|
exports.isEmbeddingResponse = isEmbeddingResponse;
|
|
1360
2478
|
exports.isErrorResponse = isErrorResponse;
|
|
2479
|
+
exports.isExtendedResponsesToolsList = isExtendedResponsesToolsList;
|
|
2480
|
+
exports.isFixtureToolCallError = isFixtureToolCallError;
|
|
1361
2481
|
exports.isImageResponse = isImageResponse;
|
|
1362
2482
|
exports.isJSONResponse = isJSONResponse;
|
|
1363
2483
|
exports.isJsonObject = isJsonObject;
|
|
1364
2484
|
exports.isLiveResponse = isLiveResponse;
|
|
1365
2485
|
exports.isResponseFactory = isResponseFactory;
|
|
2486
|
+
exports.isServerOwnedFixtures = isServerOwnedFixtures;
|
|
1366
2487
|
exports.isTextResponse = isTextResponse;
|
|
1367
2488
|
exports.isToolCallResponse = isToolCallResponse;
|
|
1368
2489
|
exports.isTranscriptionResponse = isTranscriptionResponse;
|
|
1369
2490
|
exports.isVideoResponse = isVideoResponse;
|
|
2491
|
+
exports.journalFixtureToolCallError = journalFixtureToolCallError;
|
|
2492
|
+
exports.keepAuthoredToolArguments = keepAuthoredToolArguments;
|
|
2493
|
+
exports.markExtendedResponsesToolsRequest = markExtendedResponsesToolsRequest;
|
|
1370
2494
|
exports.markMintedRequestId = markMintedRequestId;
|
|
2495
|
+
exports.markServerOwnedFixtures = markServerOwnedFixtures;
|
|
1371
2496
|
exports.matchesPattern = matchesPattern;
|
|
1372
2497
|
exports.normalizeEmbeddingInput = normalizeEmbeddingInput;
|
|
1373
2498
|
exports.normalizeTextInput = normalizeTextInput;
|
|
2499
|
+
exports.parseStrictIntegerText = parseStrictIntegerText;
|
|
2500
|
+
exports.prepareOpenAIChatMisbehavior = prepareOpenAIChatMisbehavior;
|
|
1374
2501
|
exports.readBody = readBody;
|
|
1375
2502
|
exports.readBodyBufferBounded = readBodyBufferBounded;
|
|
2503
|
+
exports.rebuildOrderedBlocks = rebuildOrderedBlocks;
|
|
2504
|
+
exports.rejectResponsesOnlyToolCalls = rejectResponsesOnlyToolCalls;
|
|
2505
|
+
exports.requireEmittedFunctionToolCalls = requireEmittedFunctionToolCalls;
|
|
2506
|
+
exports.requireFunctionToolCalls = requireFunctionToolCalls;
|
|
2507
|
+
exports.requireServedFunctionToolCalls = requireServedFunctionToolCalls;
|
|
1376
2508
|
exports.resolveFixtureBlockOutcome = resolveFixtureBlockOutcome;
|
|
1377
2509
|
exports.resolveFixtureBlocks = resolveFixtureBlocks;
|
|
2510
|
+
exports.resolveMcpIdentity = resolveMcpIdentity;
|
|
2511
|
+
exports.resolveOpenAIChatMisbehaviorUsage = resolveOpenAIChatMisbehaviorUsage;
|
|
1378
2512
|
exports.resolveReasoningArtifactsForModel = resolveReasoningArtifactsForModel;
|
|
1379
2513
|
exports.resolveReasoningForModel = resolveReasoningForModel;
|
|
1380
2514
|
exports.resolveRequestId = resolveRequestId;
|
|
1381
2515
|
exports.resolveResponse = resolveResponse;
|
|
2516
|
+
exports.resolveResponsesBlocks = resolveResponsesBlocks;
|
|
2517
|
+
exports.resolveServedBlockOutcome = resolveServedBlockOutcome;
|
|
1382
2518
|
exports.resolveStrictMode = resolveStrictMode;
|
|
1383
2519
|
exports.resolveTestId = resolveTestId;
|
|
1384
2520
|
exports.resolveUsage = resolveUsage;
|
|
2521
|
+
exports.runWithToolArgumentsScope = runWithToolArgumentsScope;
|
|
1385
2522
|
exports.serializeErrorResponse = serializeErrorResponse;
|
|
2523
|
+
exports.servedToolArgs = servedToolArgs;
|
|
2524
|
+
exports.servedToolCalls = servedToolCalls;
|
|
1386
2525
|
exports.slugifyContext = slugifyContext;
|
|
1387
2526
|
exports.slugifyTestId = slugifyTestId;
|
|
1388
2527
|
exports.strictNoMatchLogLine = strictNoMatchLogLine;
|
|
1389
2528
|
exports.strictNoMatchMessage = strictNoMatchMessage;
|
|
1390
2529
|
exports.strictOverrideField = strictOverrideField;
|
|
2530
|
+
exports.strictToolArgumentsEnabled = strictToolArgumentsEnabled;
|
|
2531
|
+
exports.toolArgsForWire = toolArgsForWire;
|
|
2532
|
+
exports.toolCallFixtureBlock = toolCallFixtureBlock;
|
|
1391
2533
|
exports.validateChatMessages = validateChatMessages;
|
|
1392
2534
|
exports.validateEmbeddingDimensions = validateEmbeddingDimensions;
|
|
1393
2535
|
exports.validateToolsField = validateToolsField;
|
|
2536
|
+
exports.withServedToolCalls = withServedToolCalls;
|
|
2537
|
+
exports.withoutLegacyNamespaces = withoutLegacyNamespaces;
|
|
2538
|
+
exports.withoutResponsesToolKeys = withoutResponsesToolKeys;
|
|
1394
2539
|
exports.wouldProxyMiss = wouldProxyMiss;
|
|
1395
2540
|
//# sourceMappingURL=helpers.cjs.map
|