@copilotkit/aimock 1.44.0 → 1.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +31 -0
- package/README.md +6 -0
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/aimock-cli.cjs +31 -5
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.d.cts.map +1 -1
- package/dist/aimock-cli.d.ts.map +1 -1
- package/dist/aimock-cli.js +31 -5
- package/dist/aimock-cli.js.map +1 -1
- package/dist/bedrock-converse.cjs +312 -39
- package/dist/bedrock-converse.cjs.map +1 -1
- package/dist/bedrock-converse.d.cts.map +1 -1
- package/dist/bedrock-converse.d.ts.map +1 -1
- package/dist/bedrock-converse.js +313 -40
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +195 -42
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.d.cts.map +1 -1
- package/dist/bedrock.d.ts.map +1 -1
- package/dist/bedrock.js +196 -43
- package/dist/bedrock.js.map +1 -1
- package/dist/chaos.cjs +1 -0
- package/dist/chaos.js +1 -1
- package/dist/cli.cjs +152 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +155 -8
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +132 -71
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.d.cts.map +1 -1
- package/dist/cohere.d.ts.map +1 -1
- package/dist/cohere.js +133 -72
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.cjs +62 -4
- package/dist/config-loader.cjs.map +1 -1
- package/dist/config-loader.d.cts +37 -4
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts +37 -4
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +62 -4
- package/dist/config-loader.js.map +1 -1
- package/dist/fakes-plugin.cjs +131 -0
- package/dist/fakes-plugin.cjs.map +1 -0
- package/dist/fakes-plugin.d.cts +23 -0
- package/dist/fakes-plugin.d.cts.map +1 -0
- package/dist/fakes-plugin.d.ts +23 -0
- package/dist/fakes-plugin.d.ts.map +1 -0
- package/dist/fakes-plugin.js +127 -0
- package/dist/fakes-plugin.js.map +1 -0
- package/dist/fixture-loader-services.cjs +1 -1
- package/dist/fixture-loader-services.cjs.map +1 -1
- package/dist/fixture-loader-services.js +1 -1
- package/dist/fixture-loader-services.js.map +1 -1
- package/dist/fixture-loader.cjs +389 -27
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +47 -3
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +47 -3
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +384 -29
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-interactions.cjs +250 -52
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +251 -53
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +226 -37
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts.map +1 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +227 -38
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +881 -7
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +85 -4
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +85 -4
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +839 -8
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +9 -0
- package/dist/index.d.cts +7 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +4 -3
- package/dist/jest.cjs +31 -7
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +43 -3
- package/dist/jest.d.cts.map +1 -1
- package/dist/jest.d.ts +43 -3
- package/dist/jest.d.ts.map +1 -1
- package/dist/jest.js +33 -9
- package/dist/jest.js.map +1 -1
- package/dist/journal.cjs +135 -13
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts +38 -4
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts +38 -4
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +135 -13
- package/dist/journal.js.map +1 -1
- package/dist/live-sanitize.cjs +4 -32
- package/dist/live-sanitize.cjs.map +1 -1
- package/dist/live-sanitize.js +4 -32
- package/dist/live-sanitize.js.map +1 -1
- package/dist/llmock.cjs +123 -30
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +16 -1
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +16 -1
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +125 -32
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-fakes-mount.cjs +1 -0
- package/dist/mcp-fakes-mount.cjs.map +1 -1
- package/dist/mcp-fakes-mount.js +1 -0
- package/dist/mcp-fakes-mount.js.map +1 -1
- package/dist/mcp-fakes-report.cjs +111 -0
- package/dist/mcp-fakes-report.cjs.map +1 -0
- package/dist/mcp-fakes-report.d.cts +40 -0
- package/dist/mcp-fakes-report.d.cts.map +1 -0
- package/dist/mcp-fakes-report.d.ts +40 -0
- package/dist/mcp-fakes-report.d.ts.map +1 -0
- package/dist/mcp-fakes-report.js +107 -0
- package/dist/mcp-fakes-report.js.map +1 -0
- package/dist/mcp-fakes.cjs +331 -8
- package/dist/mcp-fakes.cjs.map +1 -1
- package/dist/mcp-fakes.d.cts +406 -3
- package/dist/mcp-fakes.d.cts.map +1 -1
- package/dist/mcp-fakes.d.ts +406 -3
- package/dist/mcp-fakes.d.ts.map +1 -1
- package/dist/mcp-fakes.js +331 -8
- package/dist/mcp-fakes.js.map +1 -1
- package/dist/mcp-handler.cjs +209 -16
- package/dist/mcp-handler.cjs.map +1 -1
- package/dist/mcp-handler.d.cts +49 -0
- package/dist/mcp-handler.d.cts.map +1 -0
- package/dist/mcp-handler.d.ts +49 -0
- package/dist/mcp-handler.d.ts.map +1 -0
- package/dist/mcp-handler.js +207 -17
- package/dist/mcp-handler.js.map +1 -1
- package/dist/mcp-mock.cjs +112 -1
- package/dist/mcp-mock.cjs.map +1 -1
- package/dist/mcp-mock.d.cts +33 -2
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts +33 -2
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +112 -1
- package/dist/mcp-mock.js.map +1 -1
- package/dist/mcp-recorder.cjs +1224 -0
- package/dist/mcp-recorder.cjs.map +1 -0
- package/dist/mcp-recorder.d.cts +108 -0
- package/dist/mcp-recorder.d.cts.map +1 -0
- package/dist/mcp-recorder.d.ts +108 -0
- package/dist/mcp-recorder.d.ts.map +1 -0
- package/dist/mcp-recorder.js +1215 -0
- package/dist/mcp-recorder.js.map +1 -0
- package/dist/messages.cjs +362 -56
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.d.cts.map +1 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +363 -58
- package/dist/messages.js.map +1 -1
- package/dist/misbehavior.cjs +1523 -0
- package/dist/misbehavior.cjs.map +1 -0
- package/dist/misbehavior.d.cts +43 -0
- package/dist/misbehavior.d.cts.map +1 -0
- package/dist/misbehavior.d.ts +43 -0
- package/dist/misbehavior.d.ts.map +1 -0
- package/dist/misbehavior.js +1511 -0
- package/dist/misbehavior.js.map +1 -0
- package/dist/ollama.cjs +310 -112
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts.map +1 -1
- package/dist/ollama.js +311 -113
- package/dist/ollama.js.map +1 -1
- package/dist/record-sanitize.cjs +231 -0
- package/dist/record-sanitize.cjs.map +1 -0
- package/dist/record-sanitize.js +224 -0
- package/dist/record-sanitize.js.map +1 -0
- package/dist/recorder.cjs +106 -5
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts +2 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts +2 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +107 -7
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +667 -171
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts +5 -13
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts +5 -13
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +665 -174
- package/dist/responses.js.map +1 -1
- package/dist/router.cjs +24 -2
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +25 -4
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +572 -76
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +574 -78
- package/dist/server.js.map +1 -1
- package/dist/stream-collapse.cjs +236 -34
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +39 -4
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +39 -4
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +235 -35
- package/dist/stream-collapse.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +315 -4
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +315 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/validate-cli.cjs +36 -4
- package/dist/validate-cli.cjs.map +1 -1
- package/dist/validate-cli.js +37 -5
- package/dist/validate-cli.js.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/version.cjs +45 -0
- package/dist/version.cjs.map +1 -0
- package/dist/version.js +44 -0
- package/dist/version.js.map +1 -0
- package/dist/vitest.cjs +66 -10
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +35 -4
- package/dist/vitest.d.cts.map +1 -1
- package/dist/vitest.d.ts +35 -4
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +67 -11
- package/dist/vitest.js.map +1 -1
- package/dist/watcher.cjs +6 -2
- package/dist/watcher.cjs.map +1 -1
- package/dist/watcher.js +6 -2
- package/dist/watcher.js.map +1 -1
- package/dist/ws-gemini-live.cjs +210 -101
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.d.cts +9 -3
- package/dist/ws-gemini-live.d.cts.map +1 -1
- package/dist/ws-gemini-live.d.ts +9 -3
- package/dist/ws-gemini-live.d.ts.map +1 -1
- package/dist/ws-gemini-live.js +211 -102
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +175 -62
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts +8 -3
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts +8 -3
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +176 -63
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +109 -16
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.d.cts +8 -12
- package/dist/ws-responses.d.cts.map +1 -1
- package/dist/ws-responses.d.ts +8 -12
- package/dist/ws-responses.d.ts.map +1 -1
- package/dist/ws-responses.js +111 -18
- package/dist/ws-responses.js.map +1 -1
- package/package.json +10 -2
- package/skills/write-fixtures/SKILL.md +179 -21
package/dist/gemini.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { extractOverrides, flattenHeaders, formatToMime, getContext, getTestId, isAudioResponse,
|
|
1
|
+
import { extractOverrides, flattenHeaders, formatToMime, getContext, getTestId, isAudioResponse, isCombinedFixtureResponse, isErrorResponse, isJsonObject, isTextResponse, isToolCallResponse, requireEmittedFunctionToolCalls, requireServedFunctionToolCalls, resolveFixtureBlockOutcome, resolveReasoningForModel, resolveResponse, resolveStrictMode, resolveUsage, servedToolArgs, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, validateToolsField } from "./helpers.js";
|
|
2
2
|
import { calculateDelay, delay, writeErrorResponse } from "./sse-writer.js";
|
|
3
3
|
import { applyChaosAsync } from "./chaos.js";
|
|
4
|
+
import { planMisbehavior, recordMisbehaviorOutcome } from "./misbehavior.js";
|
|
4
5
|
import { matchFixtureDiagnostic, recordMatchOptions } from "./router.js";
|
|
5
6
|
import { proxyAndRecord } from "./recorder.js";
|
|
6
7
|
import { createInterruptionSignal } from "./interruption.js";
|
|
@@ -115,7 +116,7 @@ function geminiToCompletionRequest(req, model, stream) {
|
|
|
115
116
|
function: {
|
|
116
117
|
name: d.name,
|
|
117
118
|
description: d.description,
|
|
118
|
-
parameters: d.parameters
|
|
119
|
+
parameters: d.parameters ?? d.parametersJsonSchema
|
|
119
120
|
}
|
|
120
121
|
}));
|
|
121
122
|
}
|
|
@@ -199,16 +200,10 @@ function buildGeminiTextStreamChunks(content, chunkSize, reasoning, overrides) {
|
|
|
199
200
|
return chunks;
|
|
200
201
|
}
|
|
201
202
|
function parseToolCallPart(tc, logger) {
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
argsObj = JSON.parse(tc.arguments || "{}");
|
|
205
|
-
} catch {
|
|
206
|
-
logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
|
|
207
|
-
argsObj = {};
|
|
208
|
-
}
|
|
203
|
+
const args = servedToolArgs(tc, "object", logger);
|
|
209
204
|
const functionCall = {
|
|
210
205
|
name: tc.name,
|
|
211
|
-
args:
|
|
206
|
+
args: args.value
|
|
212
207
|
};
|
|
213
208
|
if (tc.id) functionCall.id = tc.id;
|
|
214
209
|
return { functionCall };
|
|
@@ -425,24 +420,24 @@ function resolveAudioInlineData(audio) {
|
|
|
425
420
|
data: audio.audio.b64Json
|
|
426
421
|
};
|
|
427
422
|
}
|
|
428
|
-
function buildGeminiAudioParts(audio, logger, effReasoning) {
|
|
423
|
+
function buildGeminiAudioParts(audio, toolCalls, logger, effReasoning) {
|
|
429
424
|
const parts = [{ inlineData: resolveAudioInlineData(audio) }];
|
|
430
425
|
if (effReasoning) parts.push({
|
|
431
426
|
text: effReasoning,
|
|
432
427
|
thought: true
|
|
433
428
|
});
|
|
434
429
|
if (audio.content) parts.push({ text: audio.content });
|
|
435
|
-
if (
|
|
430
|
+
if (toolCalls.length) parts.push(...toolCalls.map((tc) => parseToolCallPart(tc, logger)));
|
|
436
431
|
return parts;
|
|
437
432
|
}
|
|
438
|
-
function buildGeminiAudioResponse(audio, logger, effReasoning) {
|
|
433
|
+
function buildGeminiAudioResponse(audio, toolCalls, logger, effReasoning) {
|
|
439
434
|
return {
|
|
440
435
|
candidates: [{
|
|
441
436
|
content: {
|
|
442
437
|
role: "model",
|
|
443
|
-
parts: buildGeminiAudioParts(audio, logger, effReasoning)
|
|
438
|
+
parts: buildGeminiAudioParts(audio, toolCalls, logger, effReasoning)
|
|
444
439
|
},
|
|
445
|
-
finishReason:
|
|
440
|
+
finishReason: toolCalls.length ? "FUNCTION_CALL" : "STOP",
|
|
446
441
|
index: 0
|
|
447
442
|
}],
|
|
448
443
|
usageMetadata: {
|
|
@@ -452,14 +447,14 @@ function buildGeminiAudioResponse(audio, logger, effReasoning) {
|
|
|
452
447
|
}
|
|
453
448
|
};
|
|
454
449
|
}
|
|
455
|
-
function buildGeminiAudioStreamChunks(audio, logger, effReasoning) {
|
|
450
|
+
function buildGeminiAudioStreamChunks(audio, toolCalls, logger, effReasoning) {
|
|
456
451
|
return [{
|
|
457
452
|
candidates: [{
|
|
458
453
|
content: {
|
|
459
454
|
role: "model",
|
|
460
|
-
parts: buildGeminiAudioParts(audio, logger, effReasoning)
|
|
455
|
+
parts: buildGeminiAudioParts(audio, toolCalls, logger, effReasoning)
|
|
461
456
|
},
|
|
462
|
-
finishReason:
|
|
457
|
+
finishReason: toolCalls.length ? "FUNCTION_CALL" : "STOP",
|
|
463
458
|
index: 0
|
|
464
459
|
}],
|
|
465
460
|
usageMetadata: {
|
|
@@ -494,8 +489,90 @@ async function writeGeminiSSEStream(res, chunks, optionsOrLatency) {
|
|
|
494
489
|
if (!res.writableEnded) res.end();
|
|
495
490
|
return true;
|
|
496
491
|
}
|
|
492
|
+
/** Prepare native fault output once; the journal describes these exact parts. */
|
|
493
|
+
function prepareGeminiMisbehavior(plan, request, streaming, chunkSize, logger, strict) {
|
|
494
|
+
const response = plan.response;
|
|
495
|
+
const combined = isCombinedFixtureResponse(response);
|
|
496
|
+
if (!(combined || isToolCallResponse(response) || isTextResponse(response))) throw new Error("Applied Gemini misbehavior requires a chat response");
|
|
497
|
+
const outcome = combined && response.blocks?.length ? resolveFixtureBlockOutcome(response.blocks) : void 0;
|
|
498
|
+
const content = outcome?.content ?? ("content" in response ? response.content ?? "" : "");
|
|
499
|
+
const calls = outcome?.toolCalls ?? ("toolCalls" in response ? response.toolCalls ?? [] : []);
|
|
500
|
+
const overrides = extractOverrides(response);
|
|
501
|
+
delete overrides.usage;
|
|
502
|
+
const fault = plan.summary.fault;
|
|
503
|
+
const candidate = {
|
|
504
|
+
content: {
|
|
505
|
+
role: "model",
|
|
506
|
+
parts: []
|
|
507
|
+
},
|
|
508
|
+
index: 0
|
|
509
|
+
};
|
|
510
|
+
let chunks;
|
|
511
|
+
if (fault === "tool-args-invalid-json") {
|
|
512
|
+
const target = calls[plan.target?.index ?? 0];
|
|
513
|
+
if (!target) throw new Error("Gemini malformed-call plan requires its selected tool call");
|
|
514
|
+
chunks = [{ candidates: [{
|
|
515
|
+
index: 0,
|
|
516
|
+
finishReason: "MALFORMED_FUNCTION_CALL",
|
|
517
|
+
finishMessage: `Malformed function call: ${target.name}(${target.arguments})`
|
|
518
|
+
}] }];
|
|
519
|
+
} else if (fault === "tool-unknown-name" && !request.tools?.length) {
|
|
520
|
+
candidate.finishReason = "UNEXPECTED_TOOL_CALL";
|
|
521
|
+
chunks = [{ candidates: [candidate] }];
|
|
522
|
+
} else if (fault === "stop-length-mid-tool") {
|
|
523
|
+
candidate.content.parts = content ? [{ text: content }] : [];
|
|
524
|
+
candidate.finishReason = "MAX_TOKENS";
|
|
525
|
+
chunks = [{ candidates: [candidate] }];
|
|
526
|
+
} else if (fault === "empty-response") {
|
|
527
|
+
candidate.content.parts = [{ text: "" }];
|
|
528
|
+
candidate.finishReason = "STOP";
|
|
529
|
+
chunks = [{ candidates: [candidate] }];
|
|
530
|
+
} else if (fault === "content-filter") {
|
|
531
|
+
candidate.finishReason = "SAFETY";
|
|
532
|
+
candidate.safetyRatings = [{
|
|
533
|
+
category: "HARM_CATEGORY_DANGEROUS_CONTENT",
|
|
534
|
+
probability: "HIGH",
|
|
535
|
+
blocked: true
|
|
536
|
+
}];
|
|
537
|
+
chunks = [{ candidates: [candidate] }];
|
|
538
|
+
} else if (fault === "reasoning-only") {
|
|
539
|
+
candidate.content.parts = [{
|
|
540
|
+
text: plan.reasoning ?? "",
|
|
541
|
+
thought: true
|
|
542
|
+
}];
|
|
543
|
+
candidate.finishReason = "MAX_TOKENS";
|
|
544
|
+
chunks = [{ candidates: [candidate] }];
|
|
545
|
+
} else {
|
|
546
|
+
const reasoning = resolveReasoningForModel(response.reasoning, request.model, strict, logger);
|
|
547
|
+
if (fault === "tool-unknown-name") overrides.finishReason = "stop";
|
|
548
|
+
if (combined) chunks = streaming ? buildGeminiContentWithToolCallsStreamChunks(content, calls, chunkSize, logger, reasoning, overrides, response.blocks) : [buildGeminiContentWithToolCallsResponse(content, calls, logger, reasoning, overrides, response.blocks)];
|
|
549
|
+
else if (isToolCallResponse(response)) chunks = streaming ? buildGeminiToolCallStreamChunks(calls, chunkSize, logger, reasoning, overrides) : [buildGeminiToolCallResponse(calls, logger, reasoning, overrides)];
|
|
550
|
+
else chunks = streaming ? buildGeminiTextStreamChunks(content, chunkSize, reasoning, overrides) : [buildGeminiTextResponse(content, reasoning, overrides)];
|
|
551
|
+
}
|
|
552
|
+
const parts = chunks.flatMap((chunk) => chunk.candidates.flatMap((item) => item.content?.parts ?? []));
|
|
553
|
+
const servedToolCalls = parts.flatMap((part) => part.functionCall ? [{
|
|
554
|
+
name: part.functionCall.name,
|
|
555
|
+
arguments: JSON.stringify(part.functionCall.args),
|
|
556
|
+
...part.functionCall.id ? { id: part.functionCall.id } : {}
|
|
557
|
+
}] : []);
|
|
558
|
+
const usage = resolveUsage(void 0, request.messages.map((message) => typeof message.content === "string" ? message.content : (message.content ?? []).map((part) => part.text ?? "").join("")).join(""), parts.map((part) => part.text ?? (part.functionCall ? part.functionCall.name + JSON.stringify(part.functionCall.args) : "")).join(""));
|
|
559
|
+
for (const chunk of chunks) delete chunk.usageMetadata;
|
|
560
|
+
chunks[chunks.length - 1].usageMetadata = {
|
|
561
|
+
promptTokenCount: usage.prompt_tokens,
|
|
562
|
+
candidatesTokenCount: usage.completion_tokens,
|
|
563
|
+
totalTokenCount: usage.total_tokens
|
|
564
|
+
};
|
|
565
|
+
return {
|
|
566
|
+
chunks,
|
|
567
|
+
summary: {
|
|
568
|
+
...plan.summary,
|
|
569
|
+
servedToolCalls
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
497
573
|
async function handleGemini(req, res, raw, model, streaming, fixtures, journal, defaults, setCorsHeaders, providerKey = "gemini") {
|
|
498
574
|
const { logger } = defaults;
|
|
575
|
+
const wire = providerKey === "vertexai" ? "Vertex AI" : "Gemini";
|
|
499
576
|
setCorsHeaders(res);
|
|
500
577
|
let geminiReq;
|
|
501
578
|
try {
|
|
@@ -630,21 +707,101 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
|
|
|
630
707
|
return;
|
|
631
708
|
}
|
|
632
709
|
const response = await resolveResponse(fixture, completionReq);
|
|
710
|
+
const misbehavior = planMisbehavior({
|
|
711
|
+
wire: "gemini",
|
|
712
|
+
toolCallIdMode: "authored-nonempty",
|
|
713
|
+
fixture,
|
|
714
|
+
response,
|
|
715
|
+
request: completionReq,
|
|
716
|
+
stream: streaming,
|
|
717
|
+
defaults,
|
|
718
|
+
rawHeaders: req.headers,
|
|
719
|
+
url: req.url
|
|
720
|
+
});
|
|
721
|
+
if (misbehavior.kind === "error") {
|
|
722
|
+
recordMisbehaviorOutcome({
|
|
723
|
+
entry: journal.add({
|
|
724
|
+
method: req.method ?? "POST",
|
|
725
|
+
path,
|
|
726
|
+
headers: flattenHeaders(req.headers),
|
|
727
|
+
body: completionReq,
|
|
728
|
+
response: {
|
|
729
|
+
status: misbehavior.status,
|
|
730
|
+
fixture,
|
|
731
|
+
error: misbehavior.message
|
|
732
|
+
}
|
|
733
|
+
}),
|
|
734
|
+
summary: misbehavior.summary,
|
|
735
|
+
defaults,
|
|
736
|
+
testId
|
|
737
|
+
});
|
|
738
|
+
if (misbehavior.summary?.evaluations.length === 0) logger.error(`${req.method ?? "POST"} ${path}: ${misbehavior.message}`);
|
|
739
|
+
writeErrorResponse(res, misbehavior.status, JSON.stringify({ error: {
|
|
740
|
+
code: misbehavior.status,
|
|
741
|
+
message: misbehavior.message,
|
|
742
|
+
status: misbehavior.status === 400 ? "INVALID_ARGUMENT" : "UNIMPLEMENTED"
|
|
743
|
+
} }));
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
633
746
|
const latency = fixture.latency ?? defaults.latency;
|
|
634
747
|
const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
|
|
635
748
|
const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
|
|
636
|
-
if (
|
|
637
|
-
const
|
|
638
|
-
journal.add({
|
|
749
|
+
if (misbehavior.kind === "applied") {
|
|
750
|
+
const prepared = prepareGeminiMisbehavior(misbehavior, completionReq, streaming, chunkSize, logger, resolveStrictMode(defaults.strict, req.headers));
|
|
751
|
+
const entry = journal.add({
|
|
639
752
|
method: req.method ?? "POST",
|
|
640
753
|
path,
|
|
641
754
|
headers: flattenHeaders(req.headers),
|
|
642
755
|
body: completionReq,
|
|
643
756
|
response: {
|
|
644
|
-
status,
|
|
757
|
+
status: 200,
|
|
645
758
|
fixture
|
|
646
759
|
}
|
|
647
760
|
});
|
|
761
|
+
recordMisbehaviorOutcome({
|
|
762
|
+
entry,
|
|
763
|
+
summary: prepared.summary,
|
|
764
|
+
defaults,
|
|
765
|
+
testId
|
|
766
|
+
});
|
|
767
|
+
if (!streaming) {
|
|
768
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
769
|
+
res.end(JSON.stringify(prepared.chunks[0]));
|
|
770
|
+
} else {
|
|
771
|
+
const interruption = createInterruptionSignal(fixture);
|
|
772
|
+
if (!await writeGeminiSSEStream(res, prepared.chunks, {
|
|
773
|
+
latency,
|
|
774
|
+
streamingProfile: fixture.streamingProfile,
|
|
775
|
+
recordedTimings: fixture.recordedTimings,
|
|
776
|
+
replaySpeed,
|
|
777
|
+
signal: interruption?.signal,
|
|
778
|
+
onChunkSent: interruption?.tick
|
|
779
|
+
})) {
|
|
780
|
+
if (!res.writableEnded) res.destroy();
|
|
781
|
+
entry.response.interrupted = true;
|
|
782
|
+
entry.response.interruptReason = interruption?.reason();
|
|
783
|
+
}
|
|
784
|
+
interruption?.cleanup();
|
|
785
|
+
}
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
if (isErrorResponse(response)) {
|
|
789
|
+
const status = response.status ?? 500;
|
|
790
|
+
recordMisbehaviorOutcome({
|
|
791
|
+
entry: journal.add({
|
|
792
|
+
method: req.method ?? "POST",
|
|
793
|
+
path,
|
|
794
|
+
headers: flattenHeaders(req.headers),
|
|
795
|
+
body: completionReq,
|
|
796
|
+
response: {
|
|
797
|
+
status,
|
|
798
|
+
fixture
|
|
799
|
+
}
|
|
800
|
+
}),
|
|
801
|
+
summary: misbehavior.summary,
|
|
802
|
+
defaults,
|
|
803
|
+
testId
|
|
804
|
+
});
|
|
648
805
|
const geminiError = { error: {
|
|
649
806
|
code: status,
|
|
650
807
|
message: response.error.message,
|
|
@@ -666,12 +823,19 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
|
|
|
666
823
|
fixture
|
|
667
824
|
}
|
|
668
825
|
});
|
|
826
|
+
recordMisbehaviorOutcome({
|
|
827
|
+
entry: journalEntry,
|
|
828
|
+
summary: misbehavior.summary,
|
|
829
|
+
defaults,
|
|
830
|
+
testId
|
|
831
|
+
});
|
|
832
|
+
const audioToolCalls = Array.isArray(response.toolCalls) ? requireServedFunctionToolCalls(response, wire) : [];
|
|
669
833
|
if (!streaming) {
|
|
670
|
-
const body = buildGeminiAudioResponse(response, logger, effReasoning);
|
|
834
|
+
const body = buildGeminiAudioResponse(response, audioToolCalls, logger, effReasoning);
|
|
671
835
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
672
836
|
res.end(JSON.stringify(body));
|
|
673
837
|
} else {
|
|
674
|
-
const chunks = buildGeminiAudioStreamChunks(response, logger, effReasoning);
|
|
838
|
+
const chunks = buildGeminiAudioStreamChunks(response, audioToolCalls, logger, effReasoning);
|
|
675
839
|
const interruption = createInterruptionSignal(fixture);
|
|
676
840
|
if (!await writeGeminiSSEStream(res, chunks, {
|
|
677
841
|
latency,
|
|
@@ -689,7 +853,7 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
|
|
|
689
853
|
}
|
|
690
854
|
return;
|
|
691
855
|
}
|
|
692
|
-
if (
|
|
856
|
+
if (isCombinedFixtureResponse(response)) {
|
|
693
857
|
if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Gemini API — ignoring");
|
|
694
858
|
const overrides = extractOverrides(response);
|
|
695
859
|
const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
|
|
@@ -704,12 +868,19 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
|
|
|
704
868
|
fixture
|
|
705
869
|
}
|
|
706
870
|
});
|
|
871
|
+
recordMisbehaviorOutcome({
|
|
872
|
+
entry: journalEntry,
|
|
873
|
+
summary: misbehavior.summary,
|
|
874
|
+
defaults,
|
|
875
|
+
testId
|
|
876
|
+
});
|
|
877
|
+
const functionToolCalls = requireEmittedFunctionToolCalls(response, wire);
|
|
707
878
|
if (!streaming) {
|
|
708
|
-
const body = buildGeminiContentWithToolCallsResponse(response.content ?? "",
|
|
879
|
+
const body = buildGeminiContentWithToolCallsResponse(response.content ?? "", functionToolCalls, logger, effReasoning, overrides, response.blocks);
|
|
709
880
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
710
881
|
res.end(JSON.stringify(body));
|
|
711
882
|
} else {
|
|
712
|
-
const chunks = buildGeminiContentWithToolCallsStreamChunks(response.content ?? "",
|
|
883
|
+
const chunks = buildGeminiContentWithToolCallsStreamChunks(response.content ?? "", functionToolCalls, chunkSize, logger, effReasoning, overrides, response.blocks);
|
|
713
884
|
const interruption = createInterruptionSignal(fixture);
|
|
714
885
|
if (!await writeGeminiSSEStream(res, chunks, {
|
|
715
886
|
latency,
|
|
@@ -742,6 +913,12 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
|
|
|
742
913
|
fixture
|
|
743
914
|
}
|
|
744
915
|
});
|
|
916
|
+
recordMisbehaviorOutcome({
|
|
917
|
+
entry: journalEntry,
|
|
918
|
+
summary: misbehavior.summary,
|
|
919
|
+
defaults,
|
|
920
|
+
testId
|
|
921
|
+
});
|
|
745
922
|
if (!streaming) {
|
|
746
923
|
const body = buildGeminiTextResponse(response.content, effReasoning, overrides);
|
|
747
924
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -780,12 +957,19 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
|
|
|
780
957
|
fixture
|
|
781
958
|
}
|
|
782
959
|
});
|
|
960
|
+
recordMisbehaviorOutcome({
|
|
961
|
+
entry: journalEntry,
|
|
962
|
+
summary: misbehavior.summary,
|
|
963
|
+
defaults,
|
|
964
|
+
testId
|
|
965
|
+
});
|
|
966
|
+
const functionToolCalls = requireEmittedFunctionToolCalls(response, wire);
|
|
783
967
|
if (!streaming) {
|
|
784
|
-
const body = buildGeminiToolCallResponse(
|
|
968
|
+
const body = buildGeminiToolCallResponse(functionToolCalls, logger, effReasoning, overrides);
|
|
785
969
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
786
970
|
res.end(JSON.stringify(body));
|
|
787
971
|
} else {
|
|
788
|
-
const chunks = buildGeminiToolCallStreamChunks(
|
|
972
|
+
const chunks = buildGeminiToolCallStreamChunks(functionToolCalls, chunkSize, logger, effReasoning, overrides);
|
|
789
973
|
const interruption = createInterruptionSignal(fixture);
|
|
790
974
|
if (!await writeGeminiSSEStream(res, chunks, {
|
|
791
975
|
latency,
|
|
@@ -803,15 +987,20 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
|
|
|
803
987
|
}
|
|
804
988
|
return;
|
|
805
989
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
990
|
+
recordMisbehaviorOutcome({
|
|
991
|
+
entry: journal.add({
|
|
992
|
+
method: req.method ?? "POST",
|
|
993
|
+
path,
|
|
994
|
+
headers: flattenHeaders(req.headers),
|
|
995
|
+
body: completionReq,
|
|
996
|
+
response: {
|
|
997
|
+
status: 500,
|
|
998
|
+
fixture
|
|
999
|
+
}
|
|
1000
|
+
}),
|
|
1001
|
+
summary: misbehavior.summary,
|
|
1002
|
+
defaults,
|
|
1003
|
+
testId
|
|
815
1004
|
});
|
|
816
1005
|
writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
817
1006
|
message: "Fixture response did not match any known type",
|