@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/server.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { DEFAULT_TEST_ID } from "./constants.js";
|
|
2
2
|
import { build, msg, quote } from "./message-text.js";
|
|
3
3
|
import { markAuthenticatedRequest, resolveInboundAuth, validateRequestApiKey, writeApiKeyHttpRejection, writeApiKeyUpgradeRejection } from "./api-key-auth.js";
|
|
4
|
-
import { RequestBodyTooLargeError, buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, describeMatch, extractOverrides, flattenHeaders, getContext, getTestId, isAudioResponse,
|
|
4
|
+
import { RequestBodyTooLargeError, buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildOpenAIContentFilterChunks, buildOpenAIContentFilterCompletion, buildOpenAIReasoningChunks, buildOpenAIReasoningCompletion, buildOpenAIRefusalChunks, buildOpenAIRefusalCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, createServerFixtureList, describeMatch, extractOverrides, fixtureToolCallErrorCode, flattenHeaders, getContext, getTestId, googleFixtureToolCallErrorDetails, isAudioResponse, isCombinedFixtureResponse, isEmbeddingResponse, isErrorResponse, isExtendedResponsesToolsList, isFixtureToolCallError, isImageResponse, isJSONResponse, isJsonObject, isResponseFactory, isServerOwnedFixtures, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, markMintedRequestId, prepareOpenAIChatMisbehavior, readBody, readBodyBufferBounded, requireEmittedFunctionToolCalls, requireServedFunctionToolCalls, resolveFixtureBlockOutcome, resolveOpenAIChatMisbehaviorUsage, resolveReasoningForModel, resolveRequestId, resolveResponse, resolveStrictMode, resolveTestId, resolveUsage, runWithToolArgumentsScope, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, validateToolsField, withoutResponsesToolKeys, wouldProxyMiss } from "./helpers.js";
|
|
5
5
|
import { normalizeLiveOptions } from "./live-fixture.js";
|
|
6
6
|
import { writeErrorResponse, writeSSEStream } from "./sse-writer.js";
|
|
7
7
|
import { CHAOS_FIELDS, CHAOS_FIELD_NAMES, applyChaosAction, awaitChaosLatency, describeUnwritableReason, evaluateChaos, isChaosScope, parseChaosField, resetChaosWarnings, resolveChaosConfig, responseGoneReason } from "./chaos.js";
|
|
8
|
-
import {
|
|
8
|
+
import { MISBEHAVIOR_CATALOG, WIRE_SUPPORT, getFixtureMisbehaviorPosition, parseMisbehavior, parseMisbehaviorHeader, planMisbehavior, recordMisbehaviorOutcome, resolveMisbehaviorSeed, resolveMisbehaviorShortCircuit, setFixtureMisbehaviorPosition } from "./misbehavior.js";
|
|
9
|
+
import { FixtureLoadError, INJECTED_STATUS_RANGE, MisbehaviorConfigError, claimOneShotError, clearFixtureQueue, enableHeldFixtureMisbehavior, entryToFixture, isInjectableStatus, isOneShotError, markFixtureResponsesToolsExtended, queueOneShotError, releaseOneShotError, responsesToolsExtendedView, responsesToolsViewSource, validateFixtures } from "./fixture-loader.js";
|
|
9
10
|
import { matchFixtureDiagnostic, recordMatchOptions } from "./router.js";
|
|
10
11
|
import { proxyAndRecord } from "./recorder.js";
|
|
11
12
|
import { Logger } from "./logger.js";
|
|
@@ -52,6 +53,7 @@ import { DESTROYED_STATUS_LABEL, createMetricsRegistry, normalizePathLabel } fro
|
|
|
52
53
|
import { MCP_FAKES_DEFAULT_MOUNT, McpFakeStore, McpFakesAddError, blockIdOf } from "./mcp-fakes.js";
|
|
53
54
|
import { MCPMock } from "./mcp-mock.js";
|
|
54
55
|
import { ensureFakeMount, findFakeMount, handOffMcpFakes, isFakeMount, mountConflictDetail } from "./mcp-fakes-mount.js";
|
|
56
|
+
import { handleFakesReportRoute } from "./mcp-fakes-report.js";
|
|
55
57
|
import * as http$1 from "node:http";
|
|
56
58
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
57
59
|
|
|
@@ -159,6 +161,7 @@ function performFullReset(fixtures, targets) {
|
|
|
159
161
|
targets.journal.clear();
|
|
160
162
|
resetChaosWarnings(targets.defaults.logger);
|
|
161
163
|
targets.defaults.chaos = void 0;
|
|
164
|
+
targets.defaults.misbehavior?.byTestId.clear();
|
|
162
165
|
targets.videoStates.clear();
|
|
163
166
|
targets.openRouterVideoJobs.clear();
|
|
164
167
|
targets.veoVideoJobs.clear();
|
|
@@ -204,7 +207,7 @@ function fixtureResponseKind(response) {
|
|
|
204
207
|
if (isImageResponse(response)) return "image";
|
|
205
208
|
if (isEmbeddingResponse(response)) return "embedding";
|
|
206
209
|
if (isJSONResponse(response)) return "json";
|
|
207
|
-
if (
|
|
210
|
+
if (isCombinedFixtureResponse(response)) return "contentWithToolCalls";
|
|
208
211
|
if (isToolCallResponse(response)) return "toolCalls";
|
|
209
212
|
if (isTextResponse(response)) return "text";
|
|
210
213
|
return "unknown";
|
|
@@ -368,7 +371,8 @@ function planControlApiFakes(raw, mounts) {
|
|
|
368
371
|
const target = ensureFakeMount(mounts, group.path, {
|
|
369
372
|
journal: wiring.journal,
|
|
370
373
|
registry: wiring.defaults.registry,
|
|
371
|
-
logger: wiring.defaults.logger
|
|
374
|
+
logger: wiring.defaults.logger,
|
|
375
|
+
replaySpeed: wiring.defaults.replaySpeed
|
|
372
376
|
}, wiring.defaults.logger);
|
|
373
377
|
if ("conflict" in target) continue;
|
|
374
378
|
const result = target.handler.addMcpFakes?.(group.sources, { kind: "control-api" });
|
|
@@ -419,6 +423,7 @@ function listControlApiFakes(mounts, testId, context, mount) {
|
|
|
419
423
|
}] : []);
|
|
420
424
|
return mount !== null && listed.length === 0 ? null : listed;
|
|
421
425
|
}
|
|
426
|
+
const controlFixtureAdditions = /* @__PURE__ */ new WeakMap();
|
|
422
427
|
/**
|
|
423
428
|
* Handle requests under `/__aimock/`. Returns `true` if the request was
|
|
424
429
|
* handled, `false` if the path doesn't match the control prefix.
|
|
@@ -490,7 +495,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
490
495
|
"Content-Type": "application/json",
|
|
491
496
|
"X-Total-Count": String(total)
|
|
492
497
|
});
|
|
493
|
-
res.end(
|
|
498
|
+
res.end(journalJson(entries, journal));
|
|
494
499
|
return true;
|
|
495
500
|
}
|
|
496
501
|
if (subPath === "/fixtures" && req.method === "GET") {
|
|
@@ -510,19 +515,70 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
510
515
|
res.end(JSON.stringify({ error: "Invalid 'include': expected 'fixtures'" }));
|
|
511
516
|
return true;
|
|
512
517
|
}
|
|
518
|
+
const extendedTools = isExtendedResponsesToolsList(fixtures);
|
|
513
519
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
514
520
|
res.end(JSON.stringify({
|
|
515
521
|
count: fixtures.length,
|
|
516
522
|
fixtures: fixtures.map((fixture, index) => ({
|
|
517
523
|
index,
|
|
518
524
|
match: redactForInspection(fixture.match),
|
|
519
|
-
responseKind: isOneShotError(fixture) ? "error" : fixtureResponseKind(fixture.response),
|
|
525
|
+
responseKind: isOneShotError(fixture) ? "error" : fixtureResponseKind(extendedTools || typeof fixture.response === "function" ? fixture.response : withoutResponsesToolKeys(fixture.response)),
|
|
520
526
|
...fixture.latency !== void 0 ? { latency: fixture.latency } : {},
|
|
521
527
|
...fixture.chaos !== void 0 ? { chaos: fixture.chaos } : {}
|
|
522
528
|
}))
|
|
523
529
|
}));
|
|
524
530
|
return true;
|
|
525
531
|
}
|
|
532
|
+
if (subPath === "/misbehavior/catalog" && req.method === "GET") {
|
|
533
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
534
|
+
res.end(JSON.stringify({
|
|
535
|
+
catalog: MISBEHAVIOR_CATALOG,
|
|
536
|
+
WIRE_SUPPORT
|
|
537
|
+
}));
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
if (subPath === "/misbehavior" && (req.method === "GET" || req.method === "POST" || req.method === "DELETE")) {
|
|
541
|
+
const scopeId = chaosScopeId(req);
|
|
542
|
+
if (scopeId === null) return writeBlankTestId(res);
|
|
543
|
+
const reply = (status, body) => {
|
|
544
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
545
|
+
res.end(JSON.stringify(body));
|
|
546
|
+
return true;
|
|
547
|
+
};
|
|
548
|
+
const scope = defaults.misbehavior;
|
|
549
|
+
if (!scope && req.method !== "GET") return reply(409, { error: "misbehavior is not enabled: start aimock with enableMisbehavior: true (CLI: --misbehavior)" });
|
|
550
|
+
if (req.method === "POST") {
|
|
551
|
+
let raw;
|
|
552
|
+
try {
|
|
553
|
+
raw = await readBody(req);
|
|
554
|
+
} catch (err) {
|
|
555
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
556
|
+
defaults.logger.error(`POST /__aimock/misbehavior: failed to read body: ${msg}`);
|
|
557
|
+
return reply(400, { error: `Failed to read request body: ${msg}` });
|
|
558
|
+
}
|
|
559
|
+
let input;
|
|
560
|
+
try {
|
|
561
|
+
input = JSON.parse(raw);
|
|
562
|
+
} catch (err) {
|
|
563
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
564
|
+
defaults.logger.error(`POST /__aimock/misbehavior: invalid JSON: ${msg}`);
|
|
565
|
+
return reply(400, { error: `Invalid JSON: ${msg}` });
|
|
566
|
+
}
|
|
567
|
+
if (isJsonObject(input) && Object.keys(input).length === 0) input = { faults: [] };
|
|
568
|
+
const parsed = parseMisbehavior(input);
|
|
569
|
+
if (!parsed.ok) return reply(400, {
|
|
570
|
+
error: "Validation failed",
|
|
571
|
+
rule: parsed.issue.rule,
|
|
572
|
+
message: parsed.issue.message
|
|
573
|
+
});
|
|
574
|
+
const config = parsed.config.seed === "random" ? {
|
|
575
|
+
...parsed.config,
|
|
576
|
+
seed: resolveMisbehaviorSeed(parsed.config.seed, defaults.logger)
|
|
577
|
+
} : parsed.config;
|
|
578
|
+
scope?.byTestId.set(scopeId, config);
|
|
579
|
+
} else if (req.method === "DELETE") scope?.byTestId.delete(scopeId);
|
|
580
|
+
return reply(200, { misbehavior: scope?.byTestId.get(scopeId) ?? scope?.baseline ?? { faults: [] } });
|
|
581
|
+
}
|
|
526
582
|
if (subPath === "/chaos" && req.method === "GET") {
|
|
527
583
|
const scopeId = chaosScopeId(req);
|
|
528
584
|
if (scopeId === null) return writeBlankTestId(res);
|
|
@@ -613,6 +669,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
613
669
|
mounts: listed
|
|
614
670
|
});
|
|
615
671
|
}
|
|
672
|
+
if (subPath === "/mcp/fakes/report" && req.method === "GET") return handleFakesReportRoute(res, searchParams, mounts);
|
|
616
673
|
if (subPath === "/fixtures" && req.method === "POST") {
|
|
617
674
|
let raw;
|
|
618
675
|
try {
|
|
@@ -653,8 +710,25 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
653
710
|
res.end(JSON.stringify({ error: `Fixture at index ${invalidMatchIndex}: match must be an object` }));
|
|
654
711
|
return true;
|
|
655
712
|
}
|
|
656
|
-
const
|
|
657
|
-
const
|
|
713
|
+
const extendedResponsesTools = defaults.responsesTools === "extended";
|
|
714
|
+
const markInPlace = extendedResponsesTools && isServerOwnedFixtures(fixtures);
|
|
715
|
+
const addition = controlFixtureAdditions.get(defaults) ?? 0;
|
|
716
|
+
controlFixtureAdditions.set(defaults, addition + 1);
|
|
717
|
+
const converted = [];
|
|
718
|
+
const loadErrors = [];
|
|
719
|
+
for (const [index, entry] of entries.entries()) try {
|
|
720
|
+
const fixture = entryToFixture(entry, void 0, void 0, {
|
|
721
|
+
file: `control-api#${addition}`,
|
|
722
|
+
index
|
|
723
|
+
});
|
|
724
|
+
if (defaults.misbehavior) enableHeldFixtureMisbehavior(fixture);
|
|
725
|
+
if (markInPlace) markFixtureResponsesToolsExtended(fixture);
|
|
726
|
+
converted.push(fixture);
|
|
727
|
+
} catch (error) {
|
|
728
|
+
if (!(error instanceof FixtureLoadError || error instanceof MisbehaviorConfigError)) throw error;
|
|
729
|
+
loadErrors.push(error);
|
|
730
|
+
}
|
|
731
|
+
const errors = (loadErrors.length === 0 ? validateFixtures(extendedResponsesTools ? converted.map(responsesToolsExtendedView) : converted) : []).filter((i) => i.severity === "error");
|
|
658
732
|
const fakes = withFakes ? planControlApiFakes(parsed.mcpFakes, mounts) : null;
|
|
659
733
|
const fakeErrors = fakes?.errors ?? [];
|
|
660
734
|
const validationFailed = (details) => {
|
|
@@ -665,7 +739,11 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
665
739
|
}));
|
|
666
740
|
return true;
|
|
667
741
|
};
|
|
668
|
-
if (errors.length > 0 || fakeErrors.length > 0) return validationFailed([
|
|
742
|
+
if (loadErrors.length > 0 || errors.length > 0 || fakeErrors.length > 0) return validationFailed([
|
|
743
|
+
...loadErrors.map((e) => e.toJSON()),
|
|
744
|
+
...errors,
|
|
745
|
+
...fakeErrors.map((e) => e.toJSON())
|
|
746
|
+
]);
|
|
669
747
|
let mcpFakesAdded = 0;
|
|
670
748
|
if (fakes) try {
|
|
671
749
|
mcpFakesAdded = fakes.apply({
|
|
@@ -687,6 +765,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
|
|
|
687
765
|
}
|
|
688
766
|
if (subPath === "/fixtures" && req.method === "DELETE") {
|
|
689
767
|
clearFixtureQueue(fixtures);
|
|
768
|
+
journal.clearMisbehaviorCounters();
|
|
690
769
|
for (const { handler } of mounts) handler.clearMcpFakes?.();
|
|
691
770
|
if (defaults.registry) defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
|
|
692
771
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -803,8 +882,9 @@ function clientAbortedMidBody(req, res, err) {
|
|
|
803
882
|
if (err instanceof RequestBodyTooLargeError) return false;
|
|
804
883
|
return res.destroyed && !req.complete;
|
|
805
884
|
}
|
|
806
|
-
async function handleCompletions(req, res, fixtures, journal, defaults, modelFallback, providerKey, openRouter = false) {
|
|
885
|
+
async function handleCompletions(req, res, fixtures, journal, defaults, getRequestEntry, modelFallback, providerKey, openRouter = false, beforeFixtureSelection, bytePlusPath = false) {
|
|
807
886
|
setCorsHeaders(res);
|
|
887
|
+
const wire = providerKey === "azure" ? "Azure OpenAI Chat Completions" : providerKey === "openrouter" ? "OpenRouter Chat Completions" : providerKey === "byteplus" || bytePlusPath ? "BytePlus ModelArk Chat Completions" : "OpenAI Chat Completions";
|
|
808
888
|
let raw;
|
|
809
889
|
try {
|
|
810
890
|
raw = await readBody(req);
|
|
@@ -970,6 +1050,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
970
1050
|
const openRouterFallback = openRouter && Array.isArray(body.models) && body.models.length > 0;
|
|
971
1051
|
const selectFixture = (probe, counts) => {
|
|
972
1052
|
for (;;) {
|
|
1053
|
+
beforeFixtureSelection?.();
|
|
973
1054
|
const attempt = matchFixtureDiagnostic(fixtures, probe, counts, defaults.requestTransform, matchOptions);
|
|
974
1055
|
if (attempt.fixture && isOneShotError(attempt.fixture) && !claimOneShotError(fixtures, attempt.fixture)) continue;
|
|
975
1056
|
return attempt;
|
|
@@ -1053,13 +1134,32 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1053
1134
|
const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
|
|
1054
1135
|
const missWouldProxy = wouldProxyMiss(effectiveStrict, defaults.record, providerKey);
|
|
1055
1136
|
const noFixtureSource = missWouldProxy ? "proxy" : "internal";
|
|
1137
|
+
const recordBypass = (reason) => {
|
|
1138
|
+
const entry = getRequestEntry();
|
|
1139
|
+
if (!entry) return;
|
|
1140
|
+
recordMisbehaviorOutcome({
|
|
1141
|
+
entry,
|
|
1142
|
+
summary: resolveMisbehaviorShortCircuit({
|
|
1143
|
+
wire: "openai-chat",
|
|
1144
|
+
fixture: fixture ?? void 0,
|
|
1145
|
+
defaults,
|
|
1146
|
+
rawHeaders: req.headers,
|
|
1147
|
+
url: req.url,
|
|
1148
|
+
reason
|
|
1149
|
+
}),
|
|
1150
|
+
defaults,
|
|
1151
|
+
testId
|
|
1152
|
+
});
|
|
1153
|
+
};
|
|
1056
1154
|
if (chaosAction === "drop" || chaosAction === "disconnect" || chaosAction === "rateLimit") {
|
|
1057
1155
|
applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : noFixtureSource, defaults.registry, defaults.logger);
|
|
1156
|
+
recordBypass("chaos-fired");
|
|
1058
1157
|
releaseOneShotError(fixtures, fixture);
|
|
1059
1158
|
return;
|
|
1060
1159
|
}
|
|
1061
1160
|
if (chaosAction === "malformed" && (fixture || !missWouldProxy)) {
|
|
1062
1161
|
applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : "internal", defaults.registry, defaults.logger);
|
|
1162
|
+
recordBypass("chaos-fired");
|
|
1063
1163
|
releaseOneShotError(fixtures, fixture);
|
|
1064
1164
|
return;
|
|
1065
1165
|
}
|
|
@@ -1091,6 +1191,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1091
1191
|
const hookOptions = chaosAction === "malformed" ? {
|
|
1092
1192
|
beforeWriteResponse: () => {
|
|
1093
1193
|
applyChaosAction(chaosAction, res, null, journal, chaosContext, "proxy", defaults.registry, defaults.logger);
|
|
1194
|
+
recordBypass("chaos-fired");
|
|
1094
1195
|
return true;
|
|
1095
1196
|
},
|
|
1096
1197
|
onHookBypassed: (reason) => {
|
|
@@ -1133,10 +1234,12 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1133
1234
|
} : {}
|
|
1134
1235
|
}
|
|
1135
1236
|
});
|
|
1237
|
+
recordBypass("proxied");
|
|
1136
1238
|
return;
|
|
1137
1239
|
}
|
|
1138
1240
|
if (chaosAction === "malformed") {
|
|
1139
1241
|
applyChaosAction(chaosAction, res, null, journal, chaosContext, "internal", defaults.registry, defaults.logger);
|
|
1242
|
+
recordBypass("chaos-fired");
|
|
1140
1243
|
return;
|
|
1141
1244
|
}
|
|
1142
1245
|
}
|
|
@@ -1159,13 +1262,71 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1159
1262
|
} }));
|
|
1160
1263
|
return;
|
|
1161
1264
|
}
|
|
1162
|
-
const
|
|
1265
|
+
const resolvedResponse = preResolvedResponse ?? await resolveResponse(fixture, body);
|
|
1266
|
+
const evaluation = planMisbehavior({
|
|
1267
|
+
wire: "openai-chat",
|
|
1268
|
+
fixture,
|
|
1269
|
+
response: resolvedResponse,
|
|
1270
|
+
request: body,
|
|
1271
|
+
stream: body.stream === true,
|
|
1272
|
+
defaults,
|
|
1273
|
+
rawHeaders: req.headers,
|
|
1274
|
+
url: req.url
|
|
1275
|
+
});
|
|
1276
|
+
if (evaluation.kind === "error") {
|
|
1277
|
+
recordMisbehaviorOutcome({
|
|
1278
|
+
entry: journal.add({
|
|
1279
|
+
method,
|
|
1280
|
+
path,
|
|
1281
|
+
headers: flatHeaders,
|
|
1282
|
+
body,
|
|
1283
|
+
response: {
|
|
1284
|
+
status: evaluation.status,
|
|
1285
|
+
fixture
|
|
1286
|
+
}
|
|
1287
|
+
}),
|
|
1288
|
+
summary: evaluation.summary,
|
|
1289
|
+
defaults,
|
|
1290
|
+
testId
|
|
1291
|
+
});
|
|
1292
|
+
if (!evaluation.summary?.evaluations.some((row) => row.outcome === "error")) defaults.logger.error(`${evaluation.code}: ${evaluation.message}`);
|
|
1293
|
+
writeErrorResponse(res, evaluation.status, openRouter ? serializeOpenRouterError(evaluation.status, `${evaluation.code}: ${evaluation.message}`) : JSON.stringify({ error: {
|
|
1294
|
+
message: evaluation.message,
|
|
1295
|
+
type: "invalid_request_error",
|
|
1296
|
+
param: null,
|
|
1297
|
+
code: evaluation.code
|
|
1298
|
+
} }));
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
const appliedPlan = evaluation.kind === "applied" ? prepareOpenAIChatMisbehavior(evaluation) : void 0;
|
|
1302
|
+
const response = appliedPlan?.response ?? resolvedResponse;
|
|
1303
|
+
const summary = appliedPlan?.summary ?? evaluation.summary;
|
|
1304
|
+
const recordOutcome = (entry) => {
|
|
1305
|
+
recordMisbehaviorOutcome({
|
|
1306
|
+
entry,
|
|
1307
|
+
summary,
|
|
1308
|
+
defaults,
|
|
1309
|
+
testId
|
|
1310
|
+
});
|
|
1311
|
+
};
|
|
1312
|
+
const appliedReasoning = appliedPlan && appliedPlan.reasoning === void 0 && (isTextResponse(response) || isToolCallResponse(response) || isCombinedFixtureResponse(response)) ? resolveReasoningForModel(response.reasoning, responseModel, resolveStrictMode(defaults.strict, req.headers), defaults.logger) : void 0;
|
|
1313
|
+
const appliedUsage = appliedPlan ? resolveOpenAIChatMisbehaviorUsage(appliedPlan, body, appliedPlan.reasoning === void 0 ? appliedReasoning !== void 0 : openRouter) : void 0;
|
|
1163
1314
|
const latency = fixture.latency ?? defaults.latency;
|
|
1164
1315
|
const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
|
|
1165
1316
|
const emitStreamingUsage = body.stream === true && body.stream_options?.include_usage === true || openRouter;
|
|
1166
1317
|
const streamingPromptText = body.messages.map((m) => typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((p) => p.text ?? "").join("") : "").join("");
|
|
1167
|
-
const resolveStreamingUsageTokens = (overrides, completionText) => resolveUsage(overrides, streamingPromptText, completionText);
|
|
1318
|
+
const resolveStreamingUsageTokens = (overrides, completionText) => appliedUsage ?? resolveUsage(overrides, streamingPromptText, completionText);
|
|
1319
|
+
const resolveRecordedTimings = (chunks) => {
|
|
1320
|
+
const timings = fixture.recordedTimings;
|
|
1321
|
+
if (!appliedPlan?.duplicateId || !timings) return timings;
|
|
1322
|
+
const gaps = timings.interChunkDelaysMs;
|
|
1323
|
+
return {
|
|
1324
|
+
...timings,
|
|
1325
|
+
interChunkDelaysMs: Array.from({ length: Math.max(gaps.length, chunks.length - 1) }, (_, index) => gaps[index] ?? gaps.at(-1) ?? 0)
|
|
1326
|
+
};
|
|
1327
|
+
};
|
|
1168
1328
|
const shapeORCompletion = (completion, overrides) => {
|
|
1329
|
+
if (appliedUsage) completion.usage = appliedUsage;
|
|
1169
1330
|
if (!openRouter) return completion;
|
|
1170
1331
|
return shapeOpenRouterCompletion(completion, resolveOpenRouterShaping(overrides, overrides?.model ?? responseModel), overrides?.id !== void 0);
|
|
1171
1332
|
};
|
|
@@ -1178,7 +1339,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1178
1339
|
const openRouterProcessing = !!(openRouter && fixture.openRouterProcessing);
|
|
1179
1340
|
if (isErrorResponse(response)) {
|
|
1180
1341
|
const status = response.status ?? 500;
|
|
1181
|
-
journal.add({
|
|
1342
|
+
recordOutcome(journal.add({
|
|
1182
1343
|
method: req.method ?? "POST",
|
|
1183
1344
|
path: req.url ?? COMPLETIONS_PATH,
|
|
1184
1345
|
headers: flattenHeaders(req.headers),
|
|
@@ -1187,12 +1348,12 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1187
1348
|
status,
|
|
1188
1349
|
fixture
|
|
1189
1350
|
}
|
|
1190
|
-
});
|
|
1351
|
+
}));
|
|
1191
1352
|
writeErrorResponse(res, status, openRouter ? serializeOpenRouterError(status, response.error.message, response.error.metadata) : serializeErrorResponse(response), { retryAfter: response.retryAfter });
|
|
1192
1353
|
return;
|
|
1193
1354
|
}
|
|
1194
1355
|
if (isAudioResponse(response)) {
|
|
1195
|
-
journal.add({
|
|
1356
|
+
recordOutcome(journal.add({
|
|
1196
1357
|
method: req.method ?? "POST",
|
|
1197
1358
|
path: req.url ?? COMPLETIONS_PATH,
|
|
1198
1359
|
headers: flattenHeaders(req.headers),
|
|
@@ -1201,18 +1362,58 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1201
1362
|
status: 422,
|
|
1202
1363
|
fixture
|
|
1203
1364
|
}
|
|
1204
|
-
});
|
|
1365
|
+
}));
|
|
1205
1366
|
writeErrorResponse(res, 422, openRouter ? serializeOpenRouterError(422, "Audio responses are not supported on the chat completions endpoint. Use Gemini generateContent or a dedicated audio endpoint.") : JSON.stringify({ error: {
|
|
1206
1367
|
message: "Audio responses are not supported on the chat completions endpoint. Use Gemini generateContent or a dedicated audio endpoint.",
|
|
1207
1368
|
type: "invalid_request_error"
|
|
1208
1369
|
} }));
|
|
1209
1370
|
return;
|
|
1210
1371
|
}
|
|
1211
|
-
if (
|
|
1372
|
+
if (appliedPlan && isTextResponse(response) && (appliedPlan.stop === "refusal" || appliedPlan.stop === "content_filter" || appliedPlan.reasoning !== void 0)) {
|
|
1373
|
+
const overrides = extractOverrides(response);
|
|
1374
|
+
const entry = journal.add({
|
|
1375
|
+
method,
|
|
1376
|
+
path,
|
|
1377
|
+
headers: flatHeaders,
|
|
1378
|
+
body,
|
|
1379
|
+
response: {
|
|
1380
|
+
status: 200,
|
|
1381
|
+
fixture
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
recordOutcome(entry);
|
|
1385
|
+
if (body.stream !== true) {
|
|
1386
|
+
const completion = appliedPlan.stop === "refusal" ? buildOpenAIRefusalCompletion(appliedPlan.refusal ?? "", responseModel, overrides, body.messages) : appliedPlan.stop === "content_filter" ? buildOpenAIContentFilterCompletion(responseModel, overrides, body.messages) : buildOpenAIReasoningCompletion(appliedPlan.reasoning ?? "", responseModel, openRouter, overrides, body.messages);
|
|
1387
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1388
|
+
res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
|
|
1389
|
+
} else {
|
|
1390
|
+
const chunks = appliedPlan.stop === "refusal" ? buildOpenAIRefusalChunks(appliedPlan.refusal ?? "", responseModel, chunkSize, overrides) : appliedPlan.stop === "content_filter" ? buildOpenAIContentFilterChunks(responseModel, overrides) : buildOpenAIReasoningChunks(appliedPlan.reasoning ?? "", responseModel, chunkSize, openRouter, overrides);
|
|
1391
|
+
const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0].id, overrides?.model ?? responseModel, chunks[0].created, resolveStreamingUsageTokens(overrides, ""), overrides?.systemFingerprint) : void 0;
|
|
1392
|
+
shapeORChunks(chunks, usageChunk, overrides);
|
|
1393
|
+
const interruption = createInterruptionSignal(fixture);
|
|
1394
|
+
if (!await writeSSEStream(res, chunks, {
|
|
1395
|
+
latency,
|
|
1396
|
+
streamingProfile: fixture.streamingProfile,
|
|
1397
|
+
signal: interruption?.signal,
|
|
1398
|
+
onChunkSent: interruption?.tick,
|
|
1399
|
+
usageChunk,
|
|
1400
|
+
recordedTimings: resolveRecordedTimings(chunks),
|
|
1401
|
+
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
|
|
1402
|
+
openRouterProcessing
|
|
1403
|
+
})) {
|
|
1404
|
+
if (!res.writableEnded) res.destroy();
|
|
1405
|
+
entry.response.interrupted = true;
|
|
1406
|
+
entry.response.interruptReason = interruption?.reason();
|
|
1407
|
+
}
|
|
1408
|
+
interruption?.cleanup();
|
|
1409
|
+
}
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
if (isCombinedFixtureResponse(response)) {
|
|
1212
1413
|
if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
|
|
1213
1414
|
const overrides = extractOverrides(response);
|
|
1214
1415
|
const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
|
|
1215
|
-
const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
1416
|
+
const effReasoning = appliedPlan ? appliedReasoning : resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
1216
1417
|
const streaming = body.stream === true;
|
|
1217
1418
|
const blockOutcome = response.blocks && response.blocks.length > 0 ? resolveFixtureBlockOutcome(response.blocks) : void 0;
|
|
1218
1419
|
const journalEntry = journal.add({
|
|
@@ -1225,16 +1426,18 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1225
1426
|
fixture
|
|
1226
1427
|
}
|
|
1227
1428
|
});
|
|
1429
|
+
recordOutcome(journalEntry);
|
|
1430
|
+
const toolCalls = requireEmittedFunctionToolCalls(response, wire);
|
|
1228
1431
|
if (!streaming) {
|
|
1229
|
-
const completion = buildContentWithToolCallsCompletion(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ??
|
|
1432
|
+
const completion = buildContentWithToolCallsCompletion(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? toolCalls, responseModel, effReasoning, blockOutcome ? {
|
|
1230
1433
|
...overrides,
|
|
1231
1434
|
finishReason: overrides?.finishReason ?? (blockOutcome.hasToolCalls ? "tool_calls" : "stop")
|
|
1232
1435
|
} : overrides, body.messages);
|
|
1233
1436
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1234
1437
|
res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
|
|
1235
1438
|
} else {
|
|
1236
|
-
const chunks = buildContentWithToolCallsChunks(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ??
|
|
1237
|
-
const completionText = blockOutcome ? blockOutcome.content + blockOutcome.toolCalls.map((tc) => tc.name + tc.arguments).join("") : (response.content ?? "") +
|
|
1439
|
+
const chunks = buildContentWithToolCallsChunks(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? toolCalls, responseModel, chunkSize, effReasoning, overrides, blockOutcome?.ordered);
|
|
1440
|
+
const completionText = blockOutcome ? blockOutcome.content + blockOutcome.toolCalls.map((tc) => tc.name + tc.arguments).join("") : (response.content ?? "") + toolCalls.map((tc) => tc.name + tc.arguments).join("");
|
|
1238
1441
|
const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? responseModel, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), resolveStreamingUsageTokens(overrides, completionText), overrides?.systemFingerprint) : void 0;
|
|
1239
1442
|
shapeORChunks(chunks, usageChunk, overrides);
|
|
1240
1443
|
const interruption = createInterruptionSignal(fixture);
|
|
@@ -1244,7 +1447,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1244
1447
|
signal: interruption?.signal,
|
|
1245
1448
|
onChunkSent: interruption?.tick,
|
|
1246
1449
|
usageChunk,
|
|
1247
|
-
recordedTimings:
|
|
1450
|
+
recordedTimings: resolveRecordedTimings(chunks),
|
|
1248
1451
|
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
|
|
1249
1452
|
openRouterProcessing
|
|
1250
1453
|
})) {
|
|
@@ -1260,7 +1463,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1260
1463
|
if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
|
|
1261
1464
|
const overrides = extractOverrides(response);
|
|
1262
1465
|
const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
|
|
1263
|
-
const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
1466
|
+
const effReasoning = appliedPlan ? appliedReasoning : resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
1264
1467
|
const journalEntry = journal.add({
|
|
1265
1468
|
method: req.method ?? "POST",
|
|
1266
1469
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -1271,6 +1474,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1271
1474
|
fixture
|
|
1272
1475
|
}
|
|
1273
1476
|
});
|
|
1477
|
+
recordOutcome(journalEntry);
|
|
1274
1478
|
if (body.stream !== true) {
|
|
1275
1479
|
const completion = buildTextCompletion(response.content, responseModel, effReasoning, overrides, body.messages);
|
|
1276
1480
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -1286,7 +1490,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1286
1490
|
signal: interruption?.signal,
|
|
1287
1491
|
onChunkSent: interruption?.tick,
|
|
1288
1492
|
usageChunk,
|
|
1289
|
-
recordedTimings:
|
|
1493
|
+
recordedTimings: resolveRecordedTimings(chunks),
|
|
1290
1494
|
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
|
|
1291
1495
|
openRouterProcessing
|
|
1292
1496
|
})) {
|
|
@@ -1302,7 +1506,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1302
1506
|
if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
|
|
1303
1507
|
const overrides = extractOverrides(response);
|
|
1304
1508
|
const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
|
|
1305
|
-
const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
1509
|
+
const effReasoning = appliedPlan ? appliedReasoning : resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
1306
1510
|
const journalEntry = journal.add({
|
|
1307
1511
|
method: req.method ?? "POST",
|
|
1308
1512
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -1313,13 +1517,15 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1313
1517
|
fixture
|
|
1314
1518
|
}
|
|
1315
1519
|
});
|
|
1520
|
+
recordOutcome(journalEntry);
|
|
1521
|
+
const toolCalls = requireServedFunctionToolCalls(response, wire);
|
|
1316
1522
|
if (body.stream !== true) {
|
|
1317
|
-
const completion = buildToolCallCompletion(
|
|
1523
|
+
const completion = buildToolCallCompletion(toolCalls, responseModel, effReasoning, overrides, body.messages);
|
|
1318
1524
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1319
1525
|
res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
|
|
1320
1526
|
} else {
|
|
1321
|
-
const chunks = buildToolCallChunks(
|
|
1322
|
-
const completionText =
|
|
1527
|
+
const chunks = buildToolCallChunks(toolCalls, responseModel, chunkSize, effReasoning, overrides);
|
|
1528
|
+
const completionText = toolCalls.map((tc) => tc.name + tc.arguments).join("");
|
|
1323
1529
|
const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? responseModel, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), resolveStreamingUsageTokens(overrides, completionText), overrides?.systemFingerprint) : void 0;
|
|
1324
1530
|
shapeORChunks(chunks, usageChunk, overrides);
|
|
1325
1531
|
const interruption = createInterruptionSignal(fixture);
|
|
@@ -1329,7 +1535,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1329
1535
|
signal: interruption?.signal,
|
|
1330
1536
|
onChunkSent: interruption?.tick,
|
|
1331
1537
|
usageChunk,
|
|
1332
|
-
recordedTimings:
|
|
1538
|
+
recordedTimings: resolveRecordedTimings(chunks),
|
|
1333
1539
|
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
|
|
1334
1540
|
openRouterProcessing
|
|
1335
1541
|
})) {
|
|
@@ -1341,7 +1547,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1341
1547
|
}
|
|
1342
1548
|
return;
|
|
1343
1549
|
}
|
|
1344
|
-
journal.add({
|
|
1550
|
+
recordOutcome(journal.add({
|
|
1345
1551
|
method: req.method ?? "POST",
|
|
1346
1552
|
path: req.url ?? COMPLETIONS_PATH,
|
|
1347
1553
|
headers: flattenHeaders(req.headers),
|
|
@@ -1350,7 +1556,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
1350
1556
|
status: 500,
|
|
1351
1557
|
fixture
|
|
1352
1558
|
}
|
|
1353
|
-
});
|
|
1559
|
+
}));
|
|
1354
1560
|
writeErrorResponse(res, 500, openRouter ? serializeOpenRouterError(500, "Fixture response did not match any known type") : JSON.stringify({ error: {
|
|
1355
1561
|
message: "Fixture response did not match any known type",
|
|
1356
1562
|
type: "server_error"
|
|
@@ -1375,22 +1581,90 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
|
|
|
1375
1581
|
}
|
|
1376
1582
|
}
|
|
1377
1583
|
/**
|
|
1584
|
+
* The journal as JSON. An entry names the caller's fixture; the JSON shows
|
|
1585
|
+
* the fixture the request was served from (`Journal.servedFixture`): an
|
|
1586
|
+
* extended server's view, with the held keys applied, or LLMock's
|
|
1587
|
+
* per-addition copy with misbehavior enabled. The journal therefore reads as
|
|
1588
|
+
* it did when the entries held those objects.
|
|
1589
|
+
*/
|
|
1590
|
+
function journalJson(entries, journal) {
|
|
1591
|
+
if (!entries.some((entry) => journal.servedFixture(entry) !== entry.response.fixture)) return JSON.stringify(entries);
|
|
1592
|
+
return JSON.stringify(entries.map((entry) => {
|
|
1593
|
+
const fixture = entry.response.fixture;
|
|
1594
|
+
let served = journal.servedFixture(entry);
|
|
1595
|
+
if (served === fixture || !served) return entry;
|
|
1596
|
+
const viewSource = responsesToolsViewSource(served);
|
|
1597
|
+
if (viewSource !== served) served = responsesToolsExtendedView(viewSource);
|
|
1598
|
+
return {
|
|
1599
|
+
...entry,
|
|
1600
|
+
response: {
|
|
1601
|
+
...entry.response,
|
|
1602
|
+
fixture: served
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1605
|
+
}));
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1378
1608
|
* The server of `createServerWithResolvedAuth`, after the MCP fakes hand-off.
|
|
1379
1609
|
* `commitHandOff` runs once the server listens; if it throws, the server is
|
|
1380
1610
|
* closed and start rejects.
|
|
1381
1611
|
*/
|
|
1382
1612
|
async function startServer(fixtures, options, resolvedAuth, mountList, logger, serviceFixtures, commitHandOff) {
|
|
1613
|
+
const callerFixtures = fixtures;
|
|
1614
|
+
let rawFixtureAddition = 0;
|
|
1615
|
+
function ensureRawFixturePositions() {
|
|
1616
|
+
const existingPositions = new Set(callerFixtures.map(getFixtureMisbehaviorPosition).filter((position) => position !== void 0));
|
|
1617
|
+
for (const fixture of callerFixtures) {
|
|
1618
|
+
if (getFixtureMisbehaviorPosition(fixture) !== void 0) continue;
|
|
1619
|
+
let position;
|
|
1620
|
+
do
|
|
1621
|
+
position = `createServer#${rawFixtureAddition++}`;
|
|
1622
|
+
while (existingPositions.has(position));
|
|
1623
|
+
setFixtureMisbehaviorPosition(fixture, position);
|
|
1624
|
+
existingPositions.add(position);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
ensureRawFixturePositions();
|
|
1383
1628
|
const host = options?.host ?? "127.0.0.1";
|
|
1384
1629
|
const port = options?.port ?? 0;
|
|
1385
1630
|
const registry = options?.metrics ? createMetricsRegistry() : void 0;
|
|
1386
1631
|
const serverOptions = options ?? {};
|
|
1632
|
+
const validResponsesTools = (mode) => mode === "legacy" || mode === "extended";
|
|
1633
|
+
if (serverOptions.responsesTools !== void 0 && !validResponsesTools(serverOptions.responsesTools)) new Logger("warn").warn(`Ignoring responsesTools because it must be "legacy" or "extended", got ${JSON.stringify(serverOptions.responsesTools)}. Using "legacy".`);
|
|
1634
|
+
fixtures = createServerFixtureList(callerFixtures, () => serverOptions.responsesTools, responsesToolsExtendedView);
|
|
1387
1635
|
const chaosByTestId = /* @__PURE__ */ new Map();
|
|
1636
|
+
let misbehaviorBaseline;
|
|
1637
|
+
const misbehavior = {
|
|
1638
|
+
byTestId: /* @__PURE__ */ new Map(),
|
|
1639
|
+
get baseline() {
|
|
1640
|
+
return this.byTestId.get(DEFAULT_TEST_ID) ?? misbehaviorBaseline;
|
|
1641
|
+
},
|
|
1642
|
+
set baseline(config) {
|
|
1643
|
+
misbehaviorBaseline = config;
|
|
1644
|
+
if (config === void 0) this.byTestId.delete(DEFAULT_TEST_ID);
|
|
1645
|
+
else if (this.byTestId.has(DEFAULT_TEST_ID)) this.byTestId.set(DEFAULT_TEST_ID, config);
|
|
1646
|
+
}
|
|
1647
|
+
};
|
|
1648
|
+
const misbehaviorEnabled = serverOptions.enableMisbehavior === true;
|
|
1649
|
+
if (misbehaviorEnabled) callerFixtures.forEach(enableHeldFixtureMisbehavior);
|
|
1650
|
+
if (misbehaviorEnabled && serverOptions.misbehavior !== void 0) {
|
|
1651
|
+
const parsed = parseMisbehavior(serverOptions.misbehavior);
|
|
1652
|
+
if (!parsed.ok) throw new TypeError(`${parsed.issue.rule}: ${parsed.issue.message}`);
|
|
1653
|
+
misbehavior.baseline = parsed.config.seed === "random" ? {
|
|
1654
|
+
...parsed.config,
|
|
1655
|
+
seed: resolveMisbehaviorSeed(parsed.config.seed, logger)
|
|
1656
|
+
} : parsed.config;
|
|
1657
|
+
}
|
|
1388
1658
|
const defaults = {
|
|
1389
1659
|
latency: serverOptions.latency ?? 0,
|
|
1390
1660
|
chunkSize: Math.max(1, serverOptions.chunkSize ?? DEFAULT_CHUNK_SIZE),
|
|
1391
1661
|
replaySpeed: serverOptions.replaySpeed ?? 1,
|
|
1392
1662
|
logger,
|
|
1393
1663
|
chaosByTestId,
|
|
1664
|
+
misbehavior: misbehaviorEnabled ? misbehavior : void 0,
|
|
1665
|
+
get misbehaviorCounters() {
|
|
1666
|
+
return journal;
|
|
1667
|
+
},
|
|
1394
1668
|
get chaos() {
|
|
1395
1669
|
return {
|
|
1396
1670
|
base: chaosByTestId.get(DEFAULT_TEST_ID) ?? serverOptions.chaos,
|
|
@@ -1413,6 +1687,13 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
1413
1687
|
get strict() {
|
|
1414
1688
|
return serverOptions.strict;
|
|
1415
1689
|
},
|
|
1690
|
+
get strictToolArguments() {
|
|
1691
|
+
return serverOptions.strictToolArguments;
|
|
1692
|
+
},
|
|
1693
|
+
get responsesTools() {
|
|
1694
|
+
const mode = serverOptions.responsesTools;
|
|
1695
|
+
return validResponsesTools(mode) ? mode : void 0;
|
|
1696
|
+
},
|
|
1416
1697
|
get requestTransform() {
|
|
1417
1698
|
return serverOptions.requestTransform;
|
|
1418
1699
|
},
|
|
@@ -1503,6 +1784,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
1503
1784
|
if (req) ownJournalEntry.set(req, entry);
|
|
1504
1785
|
}
|
|
1505
1786
|
});
|
|
1787
|
+
if (serverOptions.responsesTools === "extended") journal.configureFixtureViewSource(responsesToolsViewSource);
|
|
1506
1788
|
const videoStates = new VideoStateMap();
|
|
1507
1789
|
const openRouterVideoJobs = new OpenRouterVideoJobMap();
|
|
1508
1790
|
const veoVideoJobs = new VeoVideoJobMap();
|
|
@@ -1517,6 +1799,85 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
1517
1799
|
return status >= 400 && status < 500 ? "invalid_request_error" : "server_error";
|
|
1518
1800
|
}
|
|
1519
1801
|
/**
|
|
1802
|
+
* Envelope for a fixture tool-call error (see isFixtureToolCallError: invalid
|
|
1803
|
+
* JSON arguments on an object wire, a custom tool call on a non-Responses
|
|
1804
|
+
* wire, or a malformed fixture tool call). It is the provider envelope aimock
|
|
1805
|
+
* emits for that route, with the aimock `code` in it: Google's
|
|
1806
|
+
* `google.rpc.ErrorInfo` detail on Gemini and Vertex AI, a string
|
|
1807
|
+
* `error.code` on Gemini Interactions and Anthropic Messages, a `reason`
|
|
1808
|
+
* member on Bedrock (a `code` member would replace `__type` as the AWS SDK's
|
|
1809
|
+
* error name), and a sibling `code` on Ollama's bare string error. Every
|
|
1810
|
+
* other route, Cohere included, gets the OpenAI-style
|
|
1811
|
+
* `{error: {message, type, code}}`. OpenRouter routes pass their own
|
|
1812
|
+
* `toolCallEnvelope` instead of using this one.
|
|
1813
|
+
*/
|
|
1814
|
+
function invalidToolArgumentsEnvelope(pathname, message, code) {
|
|
1815
|
+
if (pathname === OLLAMA_CHAT_PATH) return JSON.stringify({
|
|
1816
|
+
error: message,
|
|
1817
|
+
code
|
|
1818
|
+
});
|
|
1819
|
+
if (pathname === GEMINI_INTERACTIONS_PATH) return JSON.stringify({ error: {
|
|
1820
|
+
code,
|
|
1821
|
+
message
|
|
1822
|
+
} });
|
|
1823
|
+
if (GEMINI_PATH_RE.test(pathname) || VERTEX_AI_RE.test(pathname)) return JSON.stringify({ error: {
|
|
1824
|
+
code: 500,
|
|
1825
|
+
message,
|
|
1826
|
+
status: "INTERNAL",
|
|
1827
|
+
details: googleFixtureToolCallErrorDetails(code)
|
|
1828
|
+
} });
|
|
1829
|
+
if (pathname === MESSAGES_PATH) return JSON.stringify({
|
|
1830
|
+
type: "error",
|
|
1831
|
+
error: {
|
|
1832
|
+
type: "api_error",
|
|
1833
|
+
code,
|
|
1834
|
+
message
|
|
1835
|
+
}
|
|
1836
|
+
});
|
|
1837
|
+
if (BEDROCK_INVOKE_RE.test(pathname) || BEDROCK_STREAM_RE.test(pathname) || BEDROCK_CONVERSE_RE.test(pathname) || BEDROCK_CONVERSE_STREAM_RE.test(pathname)) return JSON.stringify({
|
|
1838
|
+
__type: "InternalServerException",
|
|
1839
|
+
message,
|
|
1840
|
+
reason: code
|
|
1841
|
+
});
|
|
1842
|
+
return JSON.stringify({ error: {
|
|
1843
|
+
message,
|
|
1844
|
+
type: "server_error",
|
|
1845
|
+
code
|
|
1846
|
+
} });
|
|
1847
|
+
}
|
|
1848
|
+
function invalidMisbehaviorHeaderEnvelope(pathname, detail) {
|
|
1849
|
+
const code = "aimock_misbehavior_invalid";
|
|
1850
|
+
const message = `${code}: ${detail}`;
|
|
1851
|
+
if (pathname === OLLAMA_CHAT_PATH || pathname === OLLAMA_GENERATE_PATH) return JSON.stringify({ error: message });
|
|
1852
|
+
if (pathname === COHERE_CHAT_PATH) return JSON.stringify({ message });
|
|
1853
|
+
if (pathname === GEMINI_INTERACTIONS_PATH) return JSON.stringify({ error: {
|
|
1854
|
+
code,
|
|
1855
|
+
message
|
|
1856
|
+
} });
|
|
1857
|
+
if (GEMINI_PATH_RE.test(pathname) || VERTEX_AI_RE.test(pathname)) return JSON.stringify({ error: {
|
|
1858
|
+
code: 400,
|
|
1859
|
+
message,
|
|
1860
|
+
status: "INVALID_ARGUMENT"
|
|
1861
|
+
} });
|
|
1862
|
+
if (pathname === MESSAGES_PATH) return JSON.stringify({
|
|
1863
|
+
type: "error",
|
|
1864
|
+
error: {
|
|
1865
|
+
type: "invalid_request_error",
|
|
1866
|
+
code,
|
|
1867
|
+
message
|
|
1868
|
+
}
|
|
1869
|
+
});
|
|
1870
|
+
if (BEDROCK_INVOKE_RE.test(pathname) || BEDROCK_STREAM_RE.test(pathname) || BEDROCK_CONVERSE_RE.test(pathname) || BEDROCK_CONVERSE_STREAM_RE.test(pathname)) return JSON.stringify({
|
|
1871
|
+
__type: "ValidationException",
|
|
1872
|
+
message
|
|
1873
|
+
});
|
|
1874
|
+
return JSON.stringify({ error: {
|
|
1875
|
+
message,
|
|
1876
|
+
type: "invalid_request_error",
|
|
1877
|
+
code
|
|
1878
|
+
} });
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1520
1881
|
* The newest journal entry this request already produced, or null.
|
|
1521
1882
|
*
|
|
1522
1883
|
* Attribution is by the identity of the `IncomingMessage` the entry was
|
|
@@ -1567,6 +1928,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
1567
1928
|
function routeError(req, res, err, pathname, opts) {
|
|
1568
1929
|
const route = `${req.method ?? "?"} ${pathname}`;
|
|
1569
1930
|
const msg = err instanceof Error ? err.message : "Internal error";
|
|
1931
|
+
const invalidToolArguments = isFixtureToolCallError(err);
|
|
1570
1932
|
const clientFault = err instanceof RequestBodyTooLargeError;
|
|
1571
1933
|
const clientAbort = clientAbortedMidBody(req, res, err);
|
|
1572
1934
|
const status = clientFault ? 400 : 500;
|
|
@@ -1607,8 +1969,10 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
1607
1969
|
return;
|
|
1608
1970
|
}
|
|
1609
1971
|
if (!res.headersSent) {
|
|
1610
|
-
if (existing)
|
|
1611
|
-
|
|
1972
|
+
if (existing) {
|
|
1973
|
+
existing.response.status = status;
|
|
1974
|
+
if (invalidToolArguments) existing.response.error = msg;
|
|
1975
|
+
} else try {
|
|
1612
1976
|
journal.add({
|
|
1613
1977
|
method: req.method ?? "?",
|
|
1614
1978
|
path: req.url ?? "?",
|
|
@@ -1618,14 +1982,15 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
1618
1982
|
response: {
|
|
1619
1983
|
status,
|
|
1620
1984
|
fixture: null,
|
|
1621
|
-
source: "internal"
|
|
1985
|
+
source: "internal",
|
|
1986
|
+
...invalidToolArguments ? { error: msg } : {}
|
|
1622
1987
|
}
|
|
1623
1988
|
});
|
|
1624
1989
|
} catch (jErr) {
|
|
1625
1990
|
logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
1626
1991
|
}
|
|
1627
1992
|
setCorsHeaders(res);
|
|
1628
|
-
writeErrorResponse(res, status, opts?.envelope ? opts.envelope(msg, status) : JSON.stringify({ error: {
|
|
1993
|
+
writeErrorResponse(res, status, invalidToolArguments ? (opts?.toolCallEnvelope ?? ((m, c) => invalidToolArgumentsEnvelope(pathname, m, c)))(msg, fixtureToolCallErrorCode(err)) : opts?.envelope ? opts.envelope(msg, status) : JSON.stringify({ error: {
|
|
1629
1994
|
message: msg,
|
|
1630
1995
|
type: errorTypeForStatus(status)
|
|
1631
1996
|
} }));
|
|
@@ -1674,13 +2039,17 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
1674
2039
|
if (handler.setJournal) handler.setJournal(journal);
|
|
1675
2040
|
if (registry && handler.setRegistry) handler.setRegistry(registry);
|
|
1676
2041
|
if (handler.setLogger) handler.setLogger(logger);
|
|
2042
|
+
if (handler instanceof MCPMock) handler.setReplaySpeed(defaults.replaySpeed);
|
|
1677
2043
|
}
|
|
1678
2044
|
if (registry) registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
|
|
1679
2045
|
const server = http$1.createServer((req, res) => {
|
|
1680
2046
|
requestScope.run(req, () => {
|
|
1681
|
-
|
|
2047
|
+
runWithToolArgumentsScope({
|
|
2048
|
+
strict: defaults.strictToolArguments === true,
|
|
2049
|
+
logger
|
|
2050
|
+
}, () => handleHttpRequest(req, res).catch((err) => {
|
|
1682
2051
|
routeError(req, res, err, req.url ?? "?");
|
|
1683
|
-
});
|
|
2052
|
+
}));
|
|
1684
2053
|
});
|
|
1685
2054
|
});
|
|
1686
2055
|
async function handleHttpRequest(req, res) {
|
|
@@ -1763,16 +2132,40 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
1763
2132
|
markAuthenticatedRequest(req, resolvedAuth.policy);
|
|
1764
2133
|
}
|
|
1765
2134
|
if (pathname.startsWith(CONTROL_PREFIX)) {
|
|
2135
|
+
ensureRawFixturePositions();
|
|
1766
2136
|
await handleControlAPI(req, res, pathname, parsedUrl.searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults, mountList);
|
|
1767
2137
|
return;
|
|
1768
2138
|
}
|
|
2139
|
+
const misbehaviorHeader = defaults.misbehavior ? parseMisbehaviorHeader(req.headers["x-aimock-misbehavior"]) : void 0;
|
|
2140
|
+
if (misbehaviorHeader && !misbehaviorHeader.ok) {
|
|
2141
|
+
const detail = misbehaviorHeader.issue.message;
|
|
2142
|
+
const message = `aimock_misbehavior_invalid: ${detail}`;
|
|
2143
|
+
logger.error(`${req.method ?? "?"} ${pathname}: ${message}`);
|
|
2144
|
+
journal.add({
|
|
2145
|
+
method: req.method ?? "?",
|
|
2146
|
+
path: req.url ?? pathname,
|
|
2147
|
+
headers: flattenHeaders(req.headers),
|
|
2148
|
+
body: null,
|
|
2149
|
+
response: {
|
|
2150
|
+
status: 400,
|
|
2151
|
+
fixture: null,
|
|
2152
|
+
source: "internal",
|
|
2153
|
+
error: message
|
|
2154
|
+
}
|
|
2155
|
+
});
|
|
2156
|
+
setCorsHeaders(res);
|
|
2157
|
+
writeErrorResponse(res, 400, invalidMisbehaviorHeaderEnvelope(normalizeCompatPath(pathname), detail));
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
1769
2160
|
for (const { path: mountPath, handler } of mountList) if (pathname === mountPath || pathname.startsWith(mountPath + "/")) {
|
|
1770
2161
|
const subPath = pathname.slice(mountPath.length) || "/";
|
|
1771
2162
|
if (await handler.handleRequest(req, res, subPath)) return;
|
|
1772
2163
|
}
|
|
1773
2164
|
if (pathname === OLLAMA_CHAT_PATH && req.method === "POST") {
|
|
1774
2165
|
try {
|
|
1775
|
-
|
|
2166
|
+
const raw = await readBody(req);
|
|
2167
|
+
ensureRawFixturePositions();
|
|
2168
|
+
await handleOllama(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
1776
2169
|
} catch (err) {
|
|
1777
2170
|
routeError(req, res, err, pathname);
|
|
1778
2171
|
}
|
|
@@ -1780,7 +2173,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
1780
2173
|
}
|
|
1781
2174
|
if (pathname === OLLAMA_GENERATE_PATH && req.method === "POST") {
|
|
1782
2175
|
try {
|
|
1783
|
-
|
|
2176
|
+
const raw = await readBody(req);
|
|
2177
|
+
ensureRawFixturePositions();
|
|
2178
|
+
await handleOllamaGenerate(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
1784
2179
|
} catch (err) {
|
|
1785
2180
|
routeError(req, res, err, pathname);
|
|
1786
2181
|
}
|
|
@@ -1788,7 +2183,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
1788
2183
|
}
|
|
1789
2184
|
if ((pathname === OLLAMA_EMBEDDINGS_PATH || pathname === OLLAMA_EMBED_PATH) && req.method === "POST") {
|
|
1790
2185
|
try {
|
|
1791
|
-
|
|
2186
|
+
const raw = await readBody(req);
|
|
2187
|
+
ensureRawFixturePositions();
|
|
2188
|
+
await handleOllamaEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
1792
2189
|
} catch (err) {
|
|
1793
2190
|
routeError(req, res, err, pathname);
|
|
1794
2191
|
}
|
|
@@ -1821,6 +2218,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
1821
2218
|
}
|
|
1822
2219
|
if (pathname === OPENROUTER_VIDEO_MODELS_PATH && req.method === "GET") {
|
|
1823
2220
|
try {
|
|
2221
|
+
ensureRawFixturePositions();
|
|
1824
2222
|
await handleOpenRouterVideoModels(req, res, fixtures, journal, defaults, setCorsHeaders);
|
|
1825
2223
|
} catch (err) {
|
|
1826
2224
|
routeError(req, res, err, pathname);
|
|
@@ -1830,6 +2228,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
1830
2228
|
const openRouterVideoStatusMatch = pathname.match(OPENROUTER_VIDEO_STATUS_RE);
|
|
1831
2229
|
if (openRouterVideoStatusMatch && req.method === "GET") {
|
|
1832
2230
|
try {
|
|
2231
|
+
ensureRawFixturePositions();
|
|
1833
2232
|
await handleOpenRouterVideoStatus(req, res, openRouterVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
|
|
1834
2233
|
} catch (err) {
|
|
1835
2234
|
routeError(req, res, err, pathname);
|
|
@@ -1839,7 +2238,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
1839
2238
|
if (pathname === OPENROUTER_VIDEOS_PATH && req.method === "POST") {
|
|
1840
2239
|
setCorsHeaders(res);
|
|
1841
2240
|
try {
|
|
1842
|
-
|
|
2241
|
+
const raw = await readBody(req);
|
|
2242
|
+
ensureRawFixturePositions();
|
|
2243
|
+
await handleOpenRouterVideoCreate(req, res, raw, fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
|
|
1843
2244
|
} catch (err) {
|
|
1844
2245
|
routeError(req, res, err, pathname);
|
|
1845
2246
|
}
|
|
@@ -1848,6 +2249,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
1848
2249
|
const bytePlusVideoStatusMatch = pathname.match(BYTEPLUS_VIDEO_STATUS_RE);
|
|
1849
2250
|
if (bytePlusVideoStatusMatch && req.method === "GET") {
|
|
1850
2251
|
try {
|
|
2252
|
+
ensureRawFixturePositions();
|
|
1851
2253
|
await handleBytePlusVideoStatus(req, res, bytePlusVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
|
|
1852
2254
|
} catch (err) {
|
|
1853
2255
|
routeError(req, res, err, pathname);
|
|
@@ -1857,13 +2259,16 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
1857
2259
|
if (BYTEPLUS_VIDEO_SUBMIT_RE.test(pathname) && req.method === "POST") {
|
|
1858
2260
|
setCorsHeaders(res);
|
|
1859
2261
|
try {
|
|
1860
|
-
|
|
2262
|
+
const raw = await readBody(req);
|
|
2263
|
+
ensureRawFixturePositions();
|
|
2264
|
+
await handleBytePlusVideoCreate(req, res, raw, fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
|
|
1861
2265
|
} catch (err) {
|
|
1862
2266
|
routeError(req, res, err, pathname);
|
|
1863
2267
|
}
|
|
1864
2268
|
return;
|
|
1865
2269
|
}
|
|
1866
2270
|
if (pathname === OPENROUTER_MODELS_PATH && req.method === "GET") {
|
|
2271
|
+
ensureRawFixturePositions();
|
|
1867
2272
|
handleOpenRouterModels(req, res, fixtures, journal, defaults, setCorsHeaders);
|
|
1868
2273
|
return;
|
|
1869
2274
|
}
|
|
@@ -2202,7 +2607,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2202
2607
|
}
|
|
2203
2608
|
const entries = journal.getAll(opts);
|
|
2204
2609
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2205
|
-
res.end(
|
|
2610
|
+
res.end(journalJson(entries, journal));
|
|
2206
2611
|
return;
|
|
2207
2612
|
}
|
|
2208
2613
|
if (req.method === "DELETE") {
|
|
@@ -2216,7 +2621,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2216
2621
|
}
|
|
2217
2622
|
if (pathname === RESPONSES_PATH && req.method === "POST") {
|
|
2218
2623
|
try {
|
|
2219
|
-
|
|
2624
|
+
const raw = await readBody(req);
|
|
2625
|
+
ensureRawFixturePositions();
|
|
2626
|
+
await handleResponses(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
2220
2627
|
} catch (err) {
|
|
2221
2628
|
routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
2222
2629
|
}
|
|
@@ -2224,7 +2631,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2224
2631
|
}
|
|
2225
2632
|
if (pathname === MESSAGES_PATH && req.method === "POST") {
|
|
2226
2633
|
try {
|
|
2227
|
-
|
|
2634
|
+
const raw = await readBody(req);
|
|
2635
|
+
ensureRawFixturePositions();
|
|
2636
|
+
await handleMessages(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
2228
2637
|
} catch (err) {
|
|
2229
2638
|
routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
2230
2639
|
}
|
|
@@ -2232,7 +2641,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2232
2641
|
}
|
|
2233
2642
|
if (pathname === COHERE_CHAT_PATH && req.method === "POST") {
|
|
2234
2643
|
try {
|
|
2235
|
-
|
|
2644
|
+
const raw = await readBody(req);
|
|
2645
|
+
ensureRawFixturePositions();
|
|
2646
|
+
await handleCohere(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
2236
2647
|
} catch (err) {
|
|
2237
2648
|
routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
2238
2649
|
}
|
|
@@ -2240,7 +2651,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2240
2651
|
}
|
|
2241
2652
|
if (pathname === COHERE_EMBED_PATH && req.method === "POST") {
|
|
2242
2653
|
try {
|
|
2243
|
-
|
|
2654
|
+
const raw = await readBody(req);
|
|
2655
|
+
ensureRawFixturePositions();
|
|
2656
|
+
await handleCohereEmbed(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
2244
2657
|
} catch (err) {
|
|
2245
2658
|
routeError(req, res, err, pathname);
|
|
2246
2659
|
}
|
|
@@ -2260,6 +2673,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2260
2673
|
} catch (err) {
|
|
2261
2674
|
if (!(err instanceof SyntaxError)) defaults.logger.error(`Unexpected error in Azure model injection: ${err instanceof Error ? err.message : String(err)}`);
|
|
2262
2675
|
}
|
|
2676
|
+
ensureRawFixturePositions();
|
|
2263
2677
|
await handleEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders, embeddingsProvider, originalPathname === EMBEDDINGS_PATH);
|
|
2264
2678
|
} catch (err) {
|
|
2265
2679
|
routeError(req, res, err, pathname);
|
|
@@ -2268,7 +2682,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2268
2682
|
}
|
|
2269
2683
|
if (pathname === IMAGES_PATH && req.method === "POST") {
|
|
2270
2684
|
try {
|
|
2271
|
-
|
|
2685
|
+
const raw = await readBody(req);
|
|
2686
|
+
ensureRawFixturePositions();
|
|
2687
|
+
await handleImages(req, res, raw, fixtures, journal, defaults, setCorsHeaders, "openai", void 0, isBytePlusArk ? "byteplus" : void 0);
|
|
2272
2688
|
} catch (err) {
|
|
2273
2689
|
routeError(req, res, err, pathname);
|
|
2274
2690
|
}
|
|
@@ -2276,7 +2692,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2276
2692
|
}
|
|
2277
2693
|
if (pathname === IMAGES_EDIT_PATH && req.method === "POST") {
|
|
2278
2694
|
try {
|
|
2279
|
-
|
|
2695
|
+
const raw = await readBody(req);
|
|
2696
|
+
ensureRawFixturePositions();
|
|
2697
|
+
await handleImageEdit(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
2280
2698
|
} catch (err) {
|
|
2281
2699
|
routeError(req, res, err, pathname);
|
|
2282
2700
|
}
|
|
@@ -2284,7 +2702,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2284
2702
|
}
|
|
2285
2703
|
if (pathname === IMAGES_VARIATIONS_PATH && req.method === "POST") {
|
|
2286
2704
|
try {
|
|
2287
|
-
|
|
2705
|
+
const raw = await readBody(req);
|
|
2706
|
+
ensureRawFixturePositions();
|
|
2707
|
+
await handleImageVariations(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
2288
2708
|
} catch (err) {
|
|
2289
2709
|
routeError(req, res, err, pathname);
|
|
2290
2710
|
}
|
|
@@ -2292,7 +2712,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2292
2712
|
}
|
|
2293
2713
|
if (pathname === SPEECH_PATH && req.method === "POST") {
|
|
2294
2714
|
try {
|
|
2295
|
-
|
|
2715
|
+
const raw = await readBody(req);
|
|
2716
|
+
ensureRawFixturePositions();
|
|
2717
|
+
await handleSpeech(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
2296
2718
|
} catch (err) {
|
|
2297
2719
|
routeError(req, res, err, pathname);
|
|
2298
2720
|
}
|
|
@@ -2300,7 +2722,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2300
2722
|
}
|
|
2301
2723
|
if (pathname === TRANSCRIPTIONS_PATH && req.method === "POST") {
|
|
2302
2724
|
try {
|
|
2303
|
-
|
|
2725
|
+
const raw = await readBody(req);
|
|
2726
|
+
ensureRawFixturePositions();
|
|
2727
|
+
await handleTranscription(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
2304
2728
|
} catch (err) {
|
|
2305
2729
|
routeError(req, res, err, pathname);
|
|
2306
2730
|
}
|
|
@@ -2308,7 +2732,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2308
2732
|
}
|
|
2309
2733
|
if (pathname === TRANSLATIONS_PATH && req.method === "POST") {
|
|
2310
2734
|
try {
|
|
2311
|
-
|
|
2735
|
+
const raw = await readBody(req);
|
|
2736
|
+
ensureRawFixturePositions();
|
|
2737
|
+
await handleTranscription(req, res, raw, fixtures, journal, defaults, setCorsHeaders, "translation");
|
|
2312
2738
|
} catch (err) {
|
|
2313
2739
|
routeError(req, res, err, pathname);
|
|
2314
2740
|
}
|
|
@@ -2317,7 +2743,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2317
2743
|
if (pathname === GROK_VIDEO_SUBMIT_PATH && req.method === "POST") {
|
|
2318
2744
|
setCorsHeaders(res);
|
|
2319
2745
|
try {
|
|
2320
|
-
|
|
2746
|
+
const raw = await readBody(req);
|
|
2747
|
+
ensureRawFixturePositions();
|
|
2748
|
+
await handleGrokVideoCreate(req, res, raw, fixtures, journal, defaults, setCorsHeaders, grokVideoJobs);
|
|
2321
2749
|
} catch (err) {
|
|
2322
2750
|
routeError(req, res, err, pathname);
|
|
2323
2751
|
}
|
|
@@ -2325,7 +2753,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2325
2753
|
}
|
|
2326
2754
|
if (pathname === VIDEOS_PATH && req.method === "POST") {
|
|
2327
2755
|
try {
|
|
2328
|
-
|
|
2756
|
+
const raw = await readBody(req);
|
|
2757
|
+
ensureRawFixturePositions();
|
|
2758
|
+
await handleVideoCreate(req, res, raw, fixtures, journal, defaults, setCorsHeaders, videoStates);
|
|
2329
2759
|
} catch (err) {
|
|
2330
2760
|
routeError(req, res, err, pathname);
|
|
2331
2761
|
}
|
|
@@ -2334,6 +2764,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2334
2764
|
const grokVideoStatusMatch = pathname.match(GROK_VIDEO_STATUS_RE);
|
|
2335
2765
|
if (grokVideoStatusMatch && grokVideoStatusMatch[1] !== "generations" && req.method === "GET") {
|
|
2336
2766
|
try {
|
|
2767
|
+
ensureRawFixturePositions();
|
|
2337
2768
|
await handleGrokVideoStatus(req, res, grokVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, grokVideoJobs, videoStates);
|
|
2338
2769
|
} catch (err) {
|
|
2339
2770
|
routeError(req, res, err, pathname);
|
|
@@ -2344,7 +2775,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2344
2775
|
if (veoPredictMatch && req.method === "POST") {
|
|
2345
2776
|
setCorsHeaders(res);
|
|
2346
2777
|
try {
|
|
2347
|
-
|
|
2778
|
+
const raw = await readBody(req);
|
|
2779
|
+
ensureRawFixturePositions();
|
|
2780
|
+
await handleVeoVideoCreate(req, res, raw, veoPredictMatch[1], fixtures, journal, defaults, setCorsHeaders, veoVideoJobs);
|
|
2348
2781
|
} catch (err) {
|
|
2349
2782
|
routeError(req, res, err, pathname);
|
|
2350
2783
|
}
|
|
@@ -2353,6 +2786,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2353
2786
|
const veoOperationMatch = pathname.match(VEO_OPERATION_RE);
|
|
2354
2787
|
if (veoOperationMatch && req.method === "GET") {
|
|
2355
2788
|
try {
|
|
2789
|
+
ensureRawFixturePositions();
|
|
2356
2790
|
await handleVeoVideoStatus(req, res, veoOperationMatch[1], fixtures, journal, defaults, setCorsHeaders, veoVideoJobs);
|
|
2357
2791
|
} catch (err) {
|
|
2358
2792
|
routeError(req, res, err, pathname);
|
|
@@ -2363,7 +2797,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2363
2797
|
if (geminiPredictMatch && req.method === "POST") {
|
|
2364
2798
|
const predictModel = geminiPredictMatch[1];
|
|
2365
2799
|
try {
|
|
2366
|
-
|
|
2800
|
+
const raw = await readBody(req);
|
|
2801
|
+
ensureRawFixturePositions();
|
|
2802
|
+
await handleImages(req, res, raw, fixtures, journal, defaults, setCorsHeaders, "gemini", predictModel);
|
|
2367
2803
|
} catch (err) {
|
|
2368
2804
|
routeError(req, res, err, pathname);
|
|
2369
2805
|
}
|
|
@@ -2371,7 +2807,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2371
2807
|
}
|
|
2372
2808
|
if (pathname === GEMINI_INTERACTIONS_PATH && req.method === "POST") {
|
|
2373
2809
|
try {
|
|
2374
|
-
|
|
2810
|
+
const raw = await readBody(req);
|
|
2811
|
+
ensureRawFixturePositions();
|
|
2812
|
+
await handleGeminiInteractions(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
|
|
2375
2813
|
} catch (err) {
|
|
2376
2814
|
routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
2377
2815
|
}
|
|
@@ -2381,7 +2819,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2381
2819
|
if (geminiEmbedMatch && req.method === "POST") {
|
|
2382
2820
|
const embedModel = geminiEmbedMatch[1];
|
|
2383
2821
|
try {
|
|
2384
|
-
|
|
2822
|
+
const raw = await readBody(req);
|
|
2823
|
+
ensureRawFixturePositions();
|
|
2824
|
+
await handleGeminiEmbedContent(req, res, raw, embedModel, fixtures, journal, defaults, setCorsHeaders);
|
|
2385
2825
|
} catch (err) {
|
|
2386
2826
|
routeError(req, res, err, pathname);
|
|
2387
2827
|
}
|
|
@@ -2392,7 +2832,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2392
2832
|
const geminiModel = geminiMatch[1];
|
|
2393
2833
|
const streaming = geminiMatch[2] === "streamGenerateContent";
|
|
2394
2834
|
try {
|
|
2395
|
-
|
|
2835
|
+
const raw = await readBody(req);
|
|
2836
|
+
ensureRawFixturePositions();
|
|
2837
|
+
await handleGemini(req, res, raw, geminiModel, streaming, fixtures, journal, defaults, setCorsHeaders);
|
|
2396
2838
|
} catch (err) {
|
|
2397
2839
|
routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
2398
2840
|
}
|
|
@@ -2403,7 +2845,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2403
2845
|
const vertexModel = vertexMatch[1];
|
|
2404
2846
|
const streaming = vertexMatch[2] === "streamGenerateContent";
|
|
2405
2847
|
try {
|
|
2406
|
-
|
|
2848
|
+
const raw = await readBody(req);
|
|
2849
|
+
ensureRawFixturePositions();
|
|
2850
|
+
await handleGemini(req, res, raw, vertexModel, streaming, fixtures, journal, defaults, setCorsHeaders, "vertexai");
|
|
2407
2851
|
} catch (err) {
|
|
2408
2852
|
routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
|
|
2409
2853
|
}
|
|
@@ -2413,7 +2857,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2413
2857
|
if (bedrockMatch && req.method === "POST") {
|
|
2414
2858
|
const bedrockModelId = bedrockMatch[1];
|
|
2415
2859
|
try {
|
|
2416
|
-
|
|
2860
|
+
const raw = await readBody(req);
|
|
2861
|
+
ensureRawFixturePositions();
|
|
2862
|
+
await handleBedrock(req, res, raw, bedrockModelId, fixtures, journal, defaults, setCorsHeaders);
|
|
2417
2863
|
} catch (err) {
|
|
2418
2864
|
routeError(req, res, err, pathname);
|
|
2419
2865
|
}
|
|
@@ -2423,7 +2869,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2423
2869
|
if (bedrockStreamMatch && req.method === "POST") {
|
|
2424
2870
|
const bedrockModelId = bedrockStreamMatch[1];
|
|
2425
2871
|
try {
|
|
2426
|
-
|
|
2872
|
+
const raw = await readBody(req);
|
|
2873
|
+
ensureRawFixturePositions();
|
|
2874
|
+
await handleBedrockStream(req, res, raw, bedrockModelId, fixtures, journal, defaults, setCorsHeaders);
|
|
2427
2875
|
} catch (err) {
|
|
2428
2876
|
routeError(req, res, err, pathname);
|
|
2429
2877
|
}
|
|
@@ -2433,7 +2881,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2433
2881
|
if (converseMatch && req.method === "POST") {
|
|
2434
2882
|
const converseModelId = converseMatch[1];
|
|
2435
2883
|
try {
|
|
2436
|
-
|
|
2884
|
+
const raw = await readBody(req);
|
|
2885
|
+
ensureRawFixturePositions();
|
|
2886
|
+
await handleConverse(req, res, raw, converseModelId, fixtures, journal, defaults, setCorsHeaders);
|
|
2437
2887
|
} catch (err) {
|
|
2438
2888
|
routeError(req, res, err, pathname);
|
|
2439
2889
|
}
|
|
@@ -2443,7 +2893,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2443
2893
|
if (converseStreamMatch && req.method === "POST") {
|
|
2444
2894
|
const converseStreamModelId = converseStreamMatch[1];
|
|
2445
2895
|
try {
|
|
2446
|
-
|
|
2896
|
+
const raw = await readBody(req);
|
|
2897
|
+
ensureRawFixturePositions();
|
|
2898
|
+
await handleConverseStream(req, res, raw, converseStreamModelId, fixtures, journal, defaults, setCorsHeaders);
|
|
2447
2899
|
} catch (err) {
|
|
2448
2900
|
routeError(req, res, err, pathname);
|
|
2449
2901
|
}
|
|
@@ -2476,7 +2928,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2476
2928
|
if (pathname === ELEVENLABS_SOUND_GENERATION_PATH && req.method === "POST") {
|
|
2477
2929
|
setCorsHeaders(res);
|
|
2478
2930
|
try {
|
|
2479
|
-
|
|
2931
|
+
const raw = await readBody(req);
|
|
2932
|
+
ensureRawFixturePositions();
|
|
2933
|
+
await handleElevenLabsAudio(req, res, raw, fixtures, defaults, journal, "sound-generation");
|
|
2480
2934
|
} catch (err) {
|
|
2481
2935
|
routeError(req, res, err, pathname);
|
|
2482
2936
|
}
|
|
@@ -2485,7 +2939,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2485
2939
|
if (pathname === ELEVENLABS_VOICE_DESIGN_PATH && req.method === "POST") {
|
|
2486
2940
|
setCorsHeaders(res);
|
|
2487
2941
|
try {
|
|
2488
|
-
|
|
2942
|
+
const raw = await readBody(req);
|
|
2943
|
+
ensureRawFixturePositions();
|
|
2944
|
+
await handleElevenLabsVoiceDesign(req, res, raw, fixtures, defaults, journal);
|
|
2489
2945
|
} catch (err) {
|
|
2490
2946
|
routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
|
|
2491
2947
|
}
|
|
@@ -2494,7 +2950,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2494
2950
|
if (pathname === ELEVENLABS_VOICE_CREATE_PATH && req.method === "POST") {
|
|
2495
2951
|
setCorsHeaders(res);
|
|
2496
2952
|
try {
|
|
2497
|
-
|
|
2953
|
+
const raw = await readBody(req);
|
|
2954
|
+
ensureRawFixturePositions();
|
|
2955
|
+
await handleElevenLabsVoiceCreate(req, res, raw, fixtures, defaults, journal);
|
|
2498
2956
|
} catch (err) {
|
|
2499
2957
|
routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
|
|
2500
2958
|
}
|
|
@@ -2525,8 +2983,13 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2525
2983
|
return;
|
|
2526
2984
|
}
|
|
2527
2985
|
try {
|
|
2528
|
-
if (req.method === "GET")
|
|
2529
|
-
|
|
2986
|
+
if (req.method === "GET") {
|
|
2987
|
+
ensureRawFixturePositions();
|
|
2988
|
+
await handleElevenLabsVoiceGet(req, res, voiceId, fixtures, defaults, journal);
|
|
2989
|
+
} else {
|
|
2990
|
+
ensureRawFixturePositions();
|
|
2991
|
+
await handleElevenLabsVoiceDelete(req, res, voiceId, fixtures, defaults, journal);
|
|
2992
|
+
}
|
|
2530
2993
|
} catch (err) {
|
|
2531
2994
|
routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
|
|
2532
2995
|
}
|
|
@@ -2537,7 +3000,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2537
3000
|
setCorsHeaders(res);
|
|
2538
3001
|
const voiceId = elevenLabsTTSMatch[1];
|
|
2539
3002
|
try {
|
|
2540
|
-
|
|
3003
|
+
const raw = await readBody(req);
|
|
3004
|
+
ensureRawFixturePositions();
|
|
3005
|
+
await handleElevenLabsTTS(req, res, raw, fixtures, defaults, journal, voiceId);
|
|
2541
3006
|
} catch (err) {
|
|
2542
3007
|
routeError(req, res, err, pathname);
|
|
2543
3008
|
}
|
|
@@ -2548,7 +3013,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2548
3013
|
setCorsHeaders(res);
|
|
2549
3014
|
const musicSubType = musicMatch[1] ?? "music";
|
|
2550
3015
|
try {
|
|
2551
|
-
|
|
3016
|
+
const raw = await readBody(req);
|
|
3017
|
+
ensureRawFixturePositions();
|
|
3018
|
+
await handleElevenLabsAudio(req, res, raw, fixtures, defaults, journal, musicSubType);
|
|
2552
3019
|
} catch (err) {
|
|
2553
3020
|
routeError(req, res, err, pathname);
|
|
2554
3021
|
}
|
|
@@ -2579,6 +3046,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2579
3046
|
return;
|
|
2580
3047
|
}
|
|
2581
3048
|
}
|
|
3049
|
+
ensureRawFixturePositions();
|
|
2582
3050
|
if (await handleFal(req, res, raw, pathname, fixtures, defaults, journal) === "handled") return;
|
|
2583
3051
|
} catch (err) {
|
|
2584
3052
|
routeError(req, res, err, pathname);
|
|
@@ -2588,7 +3056,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2588
3056
|
if (pathname.match(FAL_QUEUE_SUBMIT_RE) && req.method === "POST") {
|
|
2589
3057
|
setCorsHeaders(res);
|
|
2590
3058
|
try {
|
|
2591
|
-
|
|
3059
|
+
const raw = falBody ?? await readBody(req);
|
|
3060
|
+
ensureRawFixturePositions();
|
|
3061
|
+
await handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
|
|
2592
3062
|
} catch (err) {
|
|
2593
3063
|
routeError(req, res, err, pathname);
|
|
2594
3064
|
}
|
|
@@ -2615,6 +3085,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2615
3085
|
}, "internal", defaults.registry, defaults.logger);
|
|
2616
3086
|
return;
|
|
2617
3087
|
}
|
|
3088
|
+
ensureRawFixturePositions();
|
|
2618
3089
|
await handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
|
|
2619
3090
|
} catch (err) {
|
|
2620
3091
|
routeError(req, res, err, pathname);
|
|
@@ -2624,7 +3095,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2624
3095
|
if (pathname.match(FAL_RUN_RE) && req.method === "POST") {
|
|
2625
3096
|
setCorsHeaders(res);
|
|
2626
3097
|
try {
|
|
2627
|
-
|
|
3098
|
+
const raw = falBody ?? await readBody(req);
|
|
3099
|
+
ensureRawFixturePositions();
|
|
3100
|
+
await handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
|
|
2628
3101
|
} catch (err) {
|
|
2629
3102
|
routeError(req, res, err, pathname);
|
|
2630
3103
|
}
|
|
@@ -2642,13 +3115,14 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2642
3115
|
}
|
|
2643
3116
|
const completionsProvider = azureDeploymentId ? "azure" : isOpenRouter ? "openrouter" : isBytePlusArk ? "byteplus" : "openai";
|
|
2644
3117
|
try {
|
|
2645
|
-
await handleCompletions(req, res, fixtures, journal, defaults, azureDeploymentId, completionsProvider, isOpenRouter);
|
|
3118
|
+
await handleCompletions(req, res, fixtures, journal, defaults, () => lastJournalEntryFor(req), azureDeploymentId, completionsProvider, isOpenRouter, ensureRawFixturePositions, originalPathname.startsWith("/api/v3/"));
|
|
2646
3119
|
} catch (err) {
|
|
2647
3120
|
routeError(req, res, err, pathname, {
|
|
2648
3121
|
envelope: (m, status) => isOpenRouter ? serializeOpenRouterError(status, m) : JSON.stringify({ error: {
|
|
2649
3122
|
message: m,
|
|
2650
3123
|
type: errorTypeForStatus(status)
|
|
2651
3124
|
} }),
|
|
3125
|
+
...isOpenRouter ? { toolCallEnvelope: (m, code) => serializeOpenRouterError(500, m, { reason: code }) } : {},
|
|
2652
3126
|
streamEvent: (m, status) => `data: ${isOpenRouter ? serializeOpenRouterError(status, m) : JSON.stringify({ error: {
|
|
2653
3127
|
message: m,
|
|
2654
3128
|
type: errorTypeForStatus(status)
|
|
@@ -2736,6 +3210,25 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2736
3210
|
socket.destroy();
|
|
2737
3211
|
return;
|
|
2738
3212
|
}
|
|
3213
|
+
if (defaults.misbehavior && pathname !== LIVE_PATH && req.headers["x-aimock-misbehavior"] !== void 0) {
|
|
3214
|
+
const code = "aimock_misbehavior_invalid";
|
|
3215
|
+
const message = "use the runtime scope: POST /__aimock/misbehavior with X-Test-Id";
|
|
3216
|
+
const body = JSON.stringify({ error: pathname === GEMINI_LIVE_PATH ? {
|
|
3217
|
+
code: 400,
|
|
3218
|
+
status: "INVALID_ARGUMENT",
|
|
3219
|
+
message: `${code}: ${message}`
|
|
3220
|
+
} : {
|
|
3221
|
+
type: "invalid_request_error",
|
|
3222
|
+
code,
|
|
3223
|
+
message
|
|
3224
|
+
} });
|
|
3225
|
+
socket.end(`HTTP/1.1 400 Bad Request\r
|
|
3226
|
+
Content-Type: application/json\r
|
|
3227
|
+
Content-Length: ${Buffer.byteLength(body)}\r\nConnection: close\r
|
|
3228
|
+
\r
|
|
3229
|
+
` + body);
|
|
3230
|
+
return;
|
|
3231
|
+
}
|
|
2739
3232
|
let liveId;
|
|
2740
3233
|
if (pathname === LIVE_PATH) {
|
|
2741
3234
|
if (req.method !== "GET" || parsedUrl.searchParams.has("model")) {
|
|
@@ -2797,7 +3290,8 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2797
3290
|
...defaults,
|
|
2798
3291
|
model: "gpt-4",
|
|
2799
3292
|
testId: wsTestId,
|
|
2800
|
-
upgradeHeaders: req.headers
|
|
3293
|
+
upgradeHeaders: req.headers,
|
|
3294
|
+
beforeProcessMessage: ensureRawFixturePositions
|
|
2801
3295
|
});
|
|
2802
3296
|
else if (pathname === REALTIME_PATH) {
|
|
2803
3297
|
const transcriptionIntent = parsedUrl.searchParams.get("intent") === "transcription";
|
|
@@ -2807,13 +3301,15 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
|
|
|
2807
3301
|
model,
|
|
2808
3302
|
transcriptionIntent,
|
|
2809
3303
|
testId: wsTestId,
|
|
2810
|
-
upgradeHeaders: req.headers
|
|
3304
|
+
upgradeHeaders: req.headers,
|
|
3305
|
+
beforeProcessMessage: ensureRawFixturePositions
|
|
2811
3306
|
});
|
|
2812
3307
|
} else if (pathname === GEMINI_LIVE_PATH) handleWebSocketGeminiLive(ws, fixtures, journal, {
|
|
2813
3308
|
...defaults,
|
|
2814
3309
|
model: "gemini-2.0-flash",
|
|
2815
3310
|
testId: wsTestId,
|
|
2816
|
-
upgradeHeaders: req.headers
|
|
3311
|
+
upgradeHeaders: req.headers,
|
|
3312
|
+
beforeProcessMessage: ensureRawFixturePositions
|
|
2817
3313
|
});
|
|
2818
3314
|
}
|
|
2819
3315
|
const originalClose = server.close.bind(server);
|