@copilotkit/aimock 1.21.0 → 1.22.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.
Files changed (230) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +56 -0
  4. package/README.md +1 -0
  5. package/dist/a2a-mock.cjs +1 -1
  6. package/dist/a2a-mock.cjs.map +1 -1
  7. package/dist/a2a-mock.d.cts.map +1 -1
  8. package/dist/a2a-mock.d.ts.map +1 -1
  9. package/dist/a2a-mock.js +1 -1
  10. package/dist/a2a-mock.js.map +1 -1
  11. package/dist/agui-recorder.cjs +25 -12
  12. package/dist/agui-recorder.cjs.map +1 -1
  13. package/dist/agui-recorder.js +25 -12
  14. package/dist/agui-recorder.js.map +1 -1
  15. package/dist/agui-types.d.cts.map +1 -1
  16. package/dist/aimock-cli.cjs +0 -0
  17. package/dist/aimock-cli.js +0 -0
  18. package/dist/bedrock-converse.cjs +72 -26
  19. package/dist/bedrock-converse.cjs.map +1 -1
  20. package/dist/bedrock-converse.d.cts.map +1 -1
  21. package/dist/bedrock-converse.d.ts.map +1 -1
  22. package/dist/bedrock-converse.js +73 -27
  23. package/dist/bedrock-converse.js.map +1 -1
  24. package/dist/bedrock.cjs +69 -24
  25. package/dist/bedrock.cjs.map +1 -1
  26. package/dist/bedrock.d.cts.map +1 -1
  27. package/dist/bedrock.d.ts.map +1 -1
  28. package/dist/bedrock.js +70 -25
  29. package/dist/bedrock.js.map +1 -1
  30. package/dist/cli.cjs +2 -2
  31. package/dist/cli.cjs.map +1 -1
  32. package/dist/cli.js +2 -2
  33. package/dist/cli.js.map +1 -1
  34. package/dist/cohere.cjs +34 -11
  35. package/dist/cohere.cjs.map +1 -1
  36. package/dist/cohere.d.cts.map +1 -1
  37. package/dist/cohere.d.ts.map +1 -1
  38. package/dist/cohere.js +35 -12
  39. package/dist/cohere.js.map +1 -1
  40. package/dist/config-loader.d.ts.map +1 -1
  41. package/dist/constants.cjs +8 -0
  42. package/dist/constants.cjs.map +1 -0
  43. package/dist/constants.d.cts +8 -0
  44. package/dist/constants.d.cts.map +1 -0
  45. package/dist/constants.d.ts +8 -0
  46. package/dist/constants.d.ts.map +1 -0
  47. package/dist/constants.js +7 -0
  48. package/dist/constants.js.map +1 -0
  49. package/dist/elevenlabs-audio.cjs +46 -20
  50. package/dist/elevenlabs-audio.cjs.map +1 -1
  51. package/dist/elevenlabs-audio.d.cts.map +1 -1
  52. package/dist/elevenlabs-audio.d.ts.map +1 -1
  53. package/dist/elevenlabs-audio.js +47 -21
  54. package/dist/elevenlabs-audio.js.map +1 -1
  55. package/dist/embeddings.cjs +25 -21
  56. package/dist/embeddings.cjs.map +1 -1
  57. package/dist/embeddings.d.cts.map +1 -1
  58. package/dist/embeddings.d.ts.map +1 -1
  59. package/dist/embeddings.js +26 -22
  60. package/dist/embeddings.js.map +1 -1
  61. package/dist/fal-audio.cjs +138 -43
  62. package/dist/fal-audio.cjs.map +1 -1
  63. package/dist/fal-audio.d.cts.map +1 -1
  64. package/dist/fal-audio.d.ts.map +1 -1
  65. package/dist/fal-audio.js +139 -44
  66. package/dist/fal-audio.js.map +1 -1
  67. package/dist/fal.cjs +27 -8
  68. package/dist/fal.cjs.map +1 -1
  69. package/dist/fal.d.cts.map +1 -1
  70. package/dist/fal.d.ts.map +1 -1
  71. package/dist/fal.js +28 -9
  72. package/dist/fal.js.map +1 -1
  73. package/dist/fixture-loader.cjs +9 -1
  74. package/dist/fixture-loader.cjs.map +1 -1
  75. package/dist/fixture-loader.js +9 -1
  76. package/dist/fixture-loader.js.map +1 -1
  77. package/dist/gemini-interactions.cjs +34 -9
  78. package/dist/gemini-interactions.cjs.map +1 -1
  79. package/dist/gemini-interactions.d.cts.map +1 -1
  80. package/dist/gemini-interactions.d.ts.map +1 -1
  81. package/dist/gemini-interactions.js +34 -11
  82. package/dist/gemini-interactions.js.map +1 -1
  83. package/dist/gemini.cjs +50 -21
  84. package/dist/gemini.cjs.map +1 -1
  85. package/dist/gemini.d.cts.map +1 -1
  86. package/dist/gemini.d.ts.map +1 -1
  87. package/dist/gemini.js +51 -22
  88. package/dist/gemini.js.map +1 -1
  89. package/dist/helpers.cjs +82 -8
  90. package/dist/helpers.cjs.map +1 -1
  91. package/dist/helpers.d.cts +7 -0
  92. package/dist/helpers.d.cts.map +1 -1
  93. package/dist/helpers.d.ts +7 -0
  94. package/dist/helpers.d.ts.map +1 -1
  95. package/dist/helpers.js +80 -9
  96. package/dist/helpers.js.map +1 -1
  97. package/dist/images.cjs +31 -10
  98. package/dist/images.cjs.map +1 -1
  99. package/dist/images.d.cts.map +1 -1
  100. package/dist/images.d.ts.map +1 -1
  101. package/dist/images.js +32 -11
  102. package/dist/images.js.map +1 -1
  103. package/dist/index.cjs +2 -1
  104. package/dist/index.d.cts +2 -1
  105. package/dist/index.d.ts +2 -1
  106. package/dist/index.js +2 -1
  107. package/dist/journal.cjs +17 -7
  108. package/dist/journal.cjs.map +1 -1
  109. package/dist/journal.d.cts +2 -3
  110. package/dist/journal.d.cts.map +1 -1
  111. package/dist/journal.d.ts +2 -3
  112. package/dist/journal.d.ts.map +1 -1
  113. package/dist/journal.js +15 -4
  114. package/dist/journal.js.map +1 -1
  115. package/dist/mcp-mock.cjs +1 -1
  116. package/dist/mcp-mock.cjs.map +1 -1
  117. package/dist/mcp-mock.d.cts.map +1 -1
  118. package/dist/mcp-mock.d.ts.map +1 -1
  119. package/dist/mcp-mock.js +1 -1
  120. package/dist/mcp-mock.js.map +1 -1
  121. package/dist/messages.cjs +38 -14
  122. package/dist/messages.cjs.map +1 -1
  123. package/dist/messages.d.cts.map +1 -1
  124. package/dist/messages.d.ts.map +1 -1
  125. package/dist/messages.js +39 -15
  126. package/dist/messages.js.map +1 -1
  127. package/dist/moderation.cjs +3 -2
  128. package/dist/moderation.cjs.map +1 -1
  129. package/dist/moderation.js +3 -2
  130. package/dist/moderation.js.map +1 -1
  131. package/dist/ollama.cjs +69 -22
  132. package/dist/ollama.cjs.map +1 -1
  133. package/dist/ollama.d.cts.map +1 -1
  134. package/dist/ollama.d.ts.map +1 -1
  135. package/dist/ollama.js +70 -23
  136. package/dist/ollama.js.map +1 -1
  137. package/dist/recorder.cjs +89 -41
  138. package/dist/recorder.cjs.map +1 -1
  139. package/dist/recorder.d.cts +3 -2
  140. package/dist/recorder.d.cts.map +1 -1
  141. package/dist/recorder.d.ts +3 -2
  142. package/dist/recorder.d.ts.map +1 -1
  143. package/dist/recorder.js +89 -41
  144. package/dist/recorder.js.map +1 -1
  145. package/dist/rerank.cjs +3 -2
  146. package/dist/rerank.cjs.map +1 -1
  147. package/dist/rerank.js +3 -2
  148. package/dist/rerank.js.map +1 -1
  149. package/dist/responses.cjs +66 -54
  150. package/dist/responses.cjs.map +1 -1
  151. package/dist/responses.d.cts +1 -1
  152. package/dist/responses.d.cts.map +1 -1
  153. package/dist/responses.d.ts +1 -1
  154. package/dist/responses.d.ts.map +1 -1
  155. package/dist/responses.js +67 -55
  156. package/dist/responses.js.map +1 -1
  157. package/dist/search.cjs +3 -2
  158. package/dist/search.cjs.map +1 -1
  159. package/dist/search.js +3 -2
  160. package/dist/search.js.map +1 -1
  161. package/dist/server.cjs +117 -171
  162. package/dist/server.cjs.map +1 -1
  163. package/dist/server.d.cts.map +1 -1
  164. package/dist/server.d.ts.map +1 -1
  165. package/dist/server.js +95 -149
  166. package/dist/server.js.map +1 -1
  167. package/dist/speech.cjs +31 -10
  168. package/dist/speech.cjs.map +1 -1
  169. package/dist/speech.d.cts.map +1 -1
  170. package/dist/speech.d.ts.map +1 -1
  171. package/dist/speech.js +32 -11
  172. package/dist/speech.js.map +1 -1
  173. package/dist/stream-collapse.cjs +51 -21
  174. package/dist/stream-collapse.cjs.map +1 -1
  175. package/dist/stream-collapse.d.cts +1 -0
  176. package/dist/stream-collapse.d.cts.map +1 -1
  177. package/dist/stream-collapse.d.ts +1 -0
  178. package/dist/stream-collapse.d.ts.map +1 -1
  179. package/dist/stream-collapse.js +51 -21
  180. package/dist/stream-collapse.js.map +1 -1
  181. package/dist/transcription.cjs +59 -19
  182. package/dist/transcription.cjs.map +1 -1
  183. package/dist/transcription.d.cts.map +1 -1
  184. package/dist/transcription.d.ts.map +1 -1
  185. package/dist/transcription.js +60 -20
  186. package/dist/transcription.js.map +1 -1
  187. package/dist/types.d.cts +4 -0
  188. package/dist/types.d.cts.map +1 -1
  189. package/dist/types.d.ts +4 -0
  190. package/dist/types.d.ts.map +1 -1
  191. package/dist/vector-mock.cjs +10 -8
  192. package/dist/vector-mock.cjs.map +1 -1
  193. package/dist/vector-mock.d.cts.map +1 -1
  194. package/dist/vector-mock.d.ts.map +1 -1
  195. package/dist/vector-mock.js +10 -8
  196. package/dist/vector-mock.js.map +1 -1
  197. package/dist/vector-types.d.ts.map +1 -1
  198. package/dist/video.cjs +55 -16
  199. package/dist/video.cjs.map +1 -1
  200. package/dist/video.d.cts +8 -1
  201. package/dist/video.d.cts.map +1 -1
  202. package/dist/video.d.ts +8 -1
  203. package/dist/video.d.ts.map +1 -1
  204. package/dist/video.js +56 -17
  205. package/dist/video.js.map +1 -1
  206. package/dist/ws-gemini-live.cjs +40 -31
  207. package/dist/ws-gemini-live.cjs.map +1 -1
  208. package/dist/ws-gemini-live.d.cts +2 -0
  209. package/dist/ws-gemini-live.d.cts.map +1 -1
  210. package/dist/ws-gemini-live.d.ts +2 -0
  211. package/dist/ws-gemini-live.d.ts.map +1 -1
  212. package/dist/ws-gemini-live.js +40 -31
  213. package/dist/ws-gemini-live.js.map +1 -1
  214. package/dist/ws-realtime.cjs +257 -16
  215. package/dist/ws-realtime.cjs.map +1 -1
  216. package/dist/ws-realtime.d.cts +2 -0
  217. package/dist/ws-realtime.d.cts.map +1 -1
  218. package/dist/ws-realtime.d.ts +2 -0
  219. package/dist/ws-realtime.d.ts.map +1 -1
  220. package/dist/ws-realtime.js +257 -16
  221. package/dist/ws-realtime.js.map +1 -1
  222. package/dist/ws-responses.cjs +54 -16
  223. package/dist/ws-responses.cjs.map +1 -1
  224. package/dist/ws-responses.d.cts +2 -0
  225. package/dist/ws-responses.d.cts.map +1 -1
  226. package/dist/ws-responses.d.ts +2 -0
  227. package/dist/ws-responses.d.ts.map +1 -1
  228. package/dist/ws-responses.js +55 -17
  229. package/dist/ws-responses.js.map +1 -1
  230. package/package.json +2 -2
package/dist/gemini.js CHANGED
@@ -1,4 +1,4 @@
1
- import { extractOverrides, flattenHeaders, formatToMime, getTestId, isAudioResponse, isContentWithToolCallsResponse, isErrorResponse, isTextResponse, isToolCallResponse, resolveResponse } from "./helpers.js";
1
+ import { extractOverrides, flattenHeaders, formatToMime, getTestId, isAudioResponse, isContentWithToolCallsResponse, isErrorResponse, isTextResponse, isToolCallResponse, resolveResponse, resolveStrictMode, strictOverrideField } from "./helpers.js";
2
2
  import { matchFixture } from "./router.js";
3
3
  import { calculateDelay, delay, writeErrorResponse } from "./sse-writer.js";
4
4
  import { createInterruptionSignal } from "./interruption.js";
@@ -23,11 +23,18 @@ function geminiToCompletionRequest(req, model, stream) {
23
23
  const funcResponses = content.parts.filter((p) => p.functionResponse);
24
24
  const textParts = content.parts.filter((p) => p.text !== void 0 && !p.thought);
25
25
  if (funcResponses.length > 0) {
26
- for (const part of funcResponses) messages.push({
27
- role: "tool",
28
- content: typeof part.functionResponse.response === "string" ? part.functionResponse.response : JSON.stringify(part.functionResponse.response),
29
- tool_call_id: `call_gemini_${part.functionResponse.name}_${callCounter++}`
30
- });
26
+ const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant" && m.tool_calls);
27
+ const matchedToolCallIds = /* @__PURE__ */ new Set();
28
+ for (const part of funcResponses) {
29
+ const matchingCall = lastAssistant?.tool_calls?.find((tc) => tc.function.name === part.functionResponse.name && !matchedToolCallIds.has(tc.id));
30
+ if (matchingCall) matchedToolCallIds.add(matchingCall.id);
31
+ const toolCallId = matchingCall?.id ?? `call_gemini_${part.functionResponse.name}_${callCounter++}`;
32
+ messages.push({
33
+ role: "tool",
34
+ content: typeof part.functionResponse.response === "string" ? part.functionResponse.response : JSON.stringify(part.functionResponse.response),
35
+ tool_call_id: toolCallId
36
+ });
37
+ }
31
38
  if (textParts.length > 0) messages.push({
32
39
  role: "user",
33
40
  content: textParts.map((p) => p.text).join("")
@@ -47,8 +54,8 @@ function geminiToCompletionRequest(req, model, stream) {
47
54
  messages.push({
48
55
  role: "assistant",
49
56
  content: text || null,
50
- tool_calls: funcCalls.map((fc) => ({
51
- id: `call_gemini_${fc.functionCall.name}_${callCounter++}`,
57
+ tool_calls: funcCalls.map((fc, i) => ({
58
+ id: `call_gemini_${fc.functionCall.name}_${i}`,
52
59
  type: "function",
53
60
  function: {
54
61
  name: fc.functionCall.name,
@@ -103,8 +110,8 @@ function geminiUsageMetadata(overrides) {
103
110
  candidatesTokenCount: 0,
104
111
  totalTokenCount: 0
105
112
  };
106
- const prompt = overrides.usage.promptTokenCount ?? 0;
107
- const candidates = overrides.usage.candidatesTokenCount ?? 0;
113
+ const prompt = overrides.usage.promptTokenCount ?? overrides.usage.prompt_tokens ?? overrides.usage.input_tokens ?? 0;
114
+ const candidates = overrides.usage.candidatesTokenCount ?? overrides.usage.completion_tokens ?? overrides.usage.output_tokens ?? 0;
108
115
  return {
109
116
  promptTokenCount: prompt,
110
117
  candidatesTokenCount: candidates,
@@ -355,7 +362,8 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
355
362
  let geminiReq;
356
363
  try {
357
364
  geminiReq = JSON.parse(raw);
358
- } catch {
365
+ } catch (parseErr) {
366
+ const detail = parseErr instanceof Error ? parseErr.message : "unknown";
359
367
  journal.add({
360
368
  method: req.method ?? "POST",
361
369
  path: req.url ?? `/v1beta/models/${model}:generateContent`,
@@ -367,7 +375,7 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
367
375
  }
368
376
  });
369
377
  writeErrorResponse(res, 400, JSON.stringify({ error: {
370
- message: "Malformed JSON",
378
+ message: `Malformed JSON body: ${detail}`,
371
379
  code: 400,
372
380
  status: "INVALID_ARGUMENT"
373
381
  } }));
@@ -388,8 +396,30 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
388
396
  body: completionReq
389
397
  }, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
390
398
  if (!fixture) {
399
+ if (resolveStrictMode(defaults.strict, req.headers)) {
400
+ logger.error(`STRICT: No fixture matched for ${req.method ?? "POST"} ${path}`);
401
+ journal.add({
402
+ method: req.method ?? "POST",
403
+ path,
404
+ headers: flattenHeaders(req.headers),
405
+ body: completionReq,
406
+ response: {
407
+ status: 503,
408
+ fixture: null,
409
+ ...strictOverrideField(defaults.strict, req.headers)
410
+ }
411
+ });
412
+ writeErrorResponse(res, 503, JSON.stringify({ error: {
413
+ message: "Strict mode: no fixture matched",
414
+ code: 503,
415
+ status: "UNAVAILABLE"
416
+ } }));
417
+ return;
418
+ }
391
419
  if (defaults.record) {
392
- if (await proxyAndRecord(req, res, completionReq, providerKey, path, fixtures, defaults, raw) !== "not_configured") {
420
+ const outcome = await proxyAndRecord(req, res, completionReq, providerKey, path, fixtures, defaults, raw);
421
+ if (outcome === "handled_by_hook") return;
422
+ if (outcome !== "not_configured") {
393
423
  journal.add({
394
424
  method: req.method ?? "POST",
395
425
  path,
@@ -404,23 +434,21 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
404
434
  return;
405
435
  }
406
436
  }
407
- const strictStatus = defaults.strict ? 503 : 404;
408
- const strictMessage = defaults.strict ? "Strict mode: no fixture matched" : "No fixture matched";
409
- if (defaults.strict) logger.error(`STRICT: No fixture matched for ${req.method ?? "POST"} ${path}`);
410
437
  journal.add({
411
438
  method: req.method ?? "POST",
412
439
  path,
413
440
  headers: flattenHeaders(req.headers),
414
441
  body: completionReq,
415
442
  response: {
416
- status: strictStatus,
417
- fixture: null
443
+ status: 404,
444
+ fixture: null,
445
+ ...strictOverrideField(defaults.strict, req.headers)
418
446
  }
419
447
  });
420
- writeErrorResponse(res, strictStatus, JSON.stringify({ error: {
421
- message: strictMessage,
422
- code: strictStatus,
423
- status: defaults.strict ? "UNAVAILABLE" : "NOT_FOUND"
448
+ writeErrorResponse(res, 404, JSON.stringify({ error: {
449
+ message: "No fixture matched",
450
+ code: 404,
451
+ status: "NOT_FOUND"
424
452
  } }));
425
453
  return;
426
454
  }
@@ -548,6 +576,7 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
548
576
  return;
549
577
  }
550
578
  if (isToolCallResponse(response)) {
579
+ if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Gemini API — ignoring");
551
580
  const overrides = extractOverrides(response);
552
581
  const journalEntry = journal.add({
553
582
  method: req.method ?? "POST",
@@ -1 +1 @@
1
- {"version":3,"file":"gemini.js","names":[],"sources":["../src/gemini.ts"],"sourcesContent":["/**\n * Google Gemini GenerateContent API support.\n *\n * Translates incoming Gemini requests into the ChatCompletionRequest format\n * used by the fixture router, and converts fixture responses back into the\n * Gemini GenerateContent streaming (or non-streaming) format.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n AudioResponse,\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n RecordProviderKey,\n ResponseOverrides,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n isAudioResponse,\n extractOverrides,\n formatToMime,\n flattenHeaders,\n getTestId,\n resolveResponse,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse, delay, calculateDelay } from \"./sse-writer.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Gemini request types ───────────────────────────────────────────────────\n\ninterface GeminiPart {\n text?: string;\n thought?: boolean;\n functionCall?: { name: string; args: Record<string, unknown> };\n functionResponse?: { name: string; response: unknown };\n inlineData?: { mimeType: string; data: string };\n}\n\ninterface GeminiContent {\n role?: string;\n parts: GeminiPart[];\n}\n\ninterface GeminiFunctionDeclaration {\n name: string;\n description?: string;\n parameters?: object;\n}\n\ninterface GeminiToolDef {\n functionDeclarations?: GeminiFunctionDeclaration[];\n}\n\ninterface GeminiRequest {\n contents?: GeminiContent[];\n systemInstruction?: GeminiContent;\n tools?: GeminiToolDef[];\n generationConfig?: {\n temperature?: number;\n maxOutputTokens?: number;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}\n\n// ─── Input conversion: Gemini → ChatCompletions messages ────────────────────\n\nexport function geminiToCompletionRequest(\n req: GeminiRequest,\n model: string,\n stream: boolean,\n): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n // systemInstruction → system message\n if (req.systemInstruction) {\n const text = req.systemInstruction.parts\n .filter((p) => p.text !== undefined)\n .map((p) => p.text!)\n .join(\"\");\n if (text) {\n messages.push({ role: \"system\", content: text });\n }\n }\n\n if (req.contents) {\n let callCounter = 0;\n for (const content of req.contents) {\n const role = content.role ?? \"user\";\n\n if (role === \"user\") {\n // Check for functionResponse parts\n const funcResponses = content.parts.filter((p) => p.functionResponse);\n const textParts = content.parts.filter((p) => p.text !== undefined && !p.thought);\n\n if (funcResponses.length > 0) {\n // functionResponse → tool message\n for (const part of funcResponses) {\n messages.push({\n role: \"tool\",\n content:\n typeof part.functionResponse!.response === \"string\"\n ? part.functionResponse!.response\n : JSON.stringify(part.functionResponse!.response),\n tool_call_id: `call_gemini_${part.functionResponse!.name}_${callCounter++}`,\n });\n }\n // Any text parts alongside → user message\n if (textParts.length > 0) {\n messages.push({\n role: \"user\",\n content: textParts.map((p) => p.text!).join(\"\"),\n });\n }\n } else {\n // Regular user text\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({ role: \"user\", content: text });\n }\n } else if (role === \"model\") {\n // Check for functionCall parts\n const funcCalls = content.parts.filter((p) => p.functionCall);\n const textParts = content.parts.filter((p) => p.text !== undefined && !p.thought);\n\n if (funcCalls.length > 0) {\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({\n role: \"assistant\",\n content: text || null,\n tool_calls: funcCalls.map((fc) => ({\n id: `call_gemini_${fc.functionCall!.name}_${callCounter++}`,\n type: \"function\" as const,\n function: {\n name: fc.functionCall!.name,\n arguments: JSON.stringify(fc.functionCall!.args ?? {}),\n },\n })),\n });\n } else {\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({ role: \"assistant\", content: text });\n }\n }\n // Unrecognized roles (not \"user\" or \"model\") are silently dropped.\n // Gemini only defines \"user\" and \"model\"; any other value indicates\n // a malformed request or an unsupported future role.\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n const decls = req.tools.flatMap((t) => t.functionDeclarations ?? []);\n if (decls.length > 0) {\n tools = decls.map((d) => ({\n type: \"function\" as const,\n function: {\n name: d.name,\n description: d.description,\n parameters: d.parameters,\n },\n }));\n }\n }\n\n return {\n model,\n messages,\n stream,\n temperature: req.generationConfig?.temperature,\n max_tokens: req.generationConfig?.maxOutputTokens,\n top_p: req.generationConfig?.topP as number | undefined,\n top_k: req.generationConfig?.topK as number | undefined,\n tools,\n };\n}\n\n// ─── Response building: fixture → Gemini format ─────────────────────────────\n\nfunction geminiFinishReason(finishReason: string | undefined, defaultReason: string): string {\n if (!finishReason) return defaultReason;\n if (finishReason === \"stop\") return \"STOP\";\n if (finishReason === \"tool_calls\") return \"FUNCTION_CALL\";\n if (finishReason === \"length\") return \"MAX_TOKENS\";\n if (finishReason === \"content_filter\") return \"SAFETY\";\n // Pass through unrecognized values as-is\n return finishReason;\n}\n\nfunction geminiUsageMetadata(overrides?: ResponseOverrides): {\n promptTokenCount: number;\n candidatesTokenCount: number;\n totalTokenCount: number;\n} {\n if (!overrides?.usage)\n return { promptTokenCount: 0, candidatesTokenCount: 0, totalTokenCount: 0 };\n const prompt = overrides.usage.promptTokenCount ?? 0;\n const candidates = overrides.usage.candidatesTokenCount ?? 0;\n const total = overrides.usage.totalTokenCount ?? prompt + candidates;\n return {\n promptTokenCount: prompt,\n candidatesTokenCount: candidates,\n totalTokenCount: total,\n };\n}\n\ninterface GeminiResponseChunk {\n candidates: {\n content: { role: string; parts: GeminiPart[] };\n finishReason?: string;\n index: number;\n }[];\n usageMetadata?: {\n promptTokenCount: number;\n candidatesTokenCount: number;\n totalTokenCount: number;\n };\n}\n\nfunction buildGeminiTextStreamChunks(\n content: string,\n chunkSize: number,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk[] {\n const chunks: GeminiResponseChunk[] = [];\n const effectiveFinish = geminiFinishReason(overrides?.finishReason, \"STOP\");\n const usage = geminiUsageMetadata(overrides);\n\n // Reasoning chunks (thought: true)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice, thought: true }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n // Content chunks\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n const isLast = i + chunkSize >= content.length;\n const chunk: GeminiResponseChunk = {\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice }] },\n index: 0,\n ...(isLast ? { finishReason: effectiveFinish } : {}),\n },\n ],\n ...(isLast ? { usageMetadata: usage } : {}),\n };\n chunks.push(chunk);\n }\n\n // Handle empty content\n if (content.length === 0) {\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: \"\" }] },\n finishReason: effectiveFinish,\n index: 0,\n },\n ],\n usageMetadata: usage,\n });\n }\n\n return chunks;\n}\n\nfunction parseToolCallPart(tc: ToolCall, logger: Logger): GeminiPart {\n let argsObj: Record<string, unknown>;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\") as Record<string, unknown>;\n } catch {\n logger.warn(`Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`);\n argsObj = {};\n }\n return { functionCall: { name: tc.name, args: argsObj } };\n}\n\nfunction buildGeminiToolCallStreamChunks(\n toolCalls: ToolCall[],\n logger: Logger,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk[] {\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n // Gemini sends all tool calls in a single response chunk\n return [\n {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n },\n ];\n}\n\n// Non-streaming response builders\n\nfunction buildGeminiTextResponse(\n content: string,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk {\n const parts: GeminiPart[] = [];\n if (reasoning) {\n parts.push({ text: reasoning, thought: true });\n }\n parts.push({ text: content });\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"STOP\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n };\n}\n\nfunction buildGeminiToolCallResponse(\n toolCalls: ToolCall[],\n logger: Logger,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk {\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n };\n}\n\nfunction buildGeminiContentWithToolCallsStreamChunks(\n content: string,\n toolCalls: ToolCall[],\n chunkSize: number,\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk[] {\n const chunks: GeminiResponseChunk[] = [];\n\n // Reasoning chunks (thought: true)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice, thought: true }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n if (content.length === 0) {\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: \"\" }] },\n index: 0,\n },\n ],\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n });\n\n return chunks;\n}\n\nfunction buildGeminiContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk {\n const parts: GeminiPart[] = [];\n if (reasoning) {\n parts.push({ text: reasoning, thought: true });\n }\n parts.push({ text: content });\n parts.push(...toolCalls.map((tc) => parseToolCallPart(tc, logger)));\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n };\n}\n\n// ─── Audio response builders ────────────────────────────────────────────────\n\nfunction resolveAudioInlineData(audio: AudioResponse): { mimeType: string; data: string } {\n if (typeof audio.audio === \"string\") {\n return { mimeType: formatToMime(audio.format ?? \"mp3\"), data: audio.audio };\n }\n return {\n mimeType: audio.audio.contentType ?? \"audio/mpeg\",\n data: audio.audio.b64Json,\n };\n}\n\nfunction buildGeminiAudioResponse(audio: AudioResponse): GeminiResponseChunk {\n const inlineData = resolveAudioInlineData(audio);\n return {\n candidates: [\n {\n content: { role: \"model\", parts: [{ inlineData }] },\n finishReason: \"STOP\",\n index: 0,\n },\n ],\n usageMetadata: { promptTokenCount: 0, candidatesTokenCount: 0, totalTokenCount: 0 },\n };\n}\n\nfunction buildGeminiAudioStreamChunks(audio: AudioResponse): GeminiResponseChunk[] {\n const inlineData = resolveAudioInlineData(audio);\n return [\n {\n candidates: [\n {\n content: { role: \"model\", parts: [{ inlineData }] },\n finishReason: \"STOP\",\n index: 0,\n },\n ],\n usageMetadata: { promptTokenCount: 0, candidatesTokenCount: 0, totalTokenCount: 0 },\n },\n ];\n}\n\n// ─── SSE writer for Gemini streaming ────────────────────────────────────────\n\ninterface GeminiStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nasync function writeGeminiSSEStream(\n res: http.ServerResponse,\n chunks: GeminiResponseChunk[],\n optionsOrLatency?: number | GeminiStreamOptions,\n): Promise<boolean> {\n const opts: GeminiStreamOptions =\n typeof optionsOrLatency === \"number\" ? { latency: optionsOrLatency } : (optionsOrLatency ?? {});\n const latency = opts.latency ?? 0;\n const profile = opts.streamingProfile;\n const signal = opts.signal;\n const onChunkSent = opts.onChunkSent;\n\n if (res.writableEnded) return true;\n res.setHeader(\"Content-Type\", \"text/event-stream\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"keep-alive\");\n\n let chunkIndex = 0;\n for (const chunk of chunks) {\n const chunkDelay = calculateDelay(chunkIndex, profile, latency);\n if (chunkDelay > 0) await delay(chunkDelay, signal);\n if (signal?.aborted) return false;\n if (res.writableEnded) return true;\n // Gemini uses data-only SSE (no event: prefix, no [DONE])\n res.write(`data: ${JSON.stringify(chunk)}\\n\\n`);\n onChunkSent?.();\n if (signal?.aborted) return false;\n chunkIndex++;\n }\n\n if (!res.writableEnded) {\n res.end();\n }\n return true;\n}\n\n// ─── Request handler ────────────────────────────────────────────────────────\n\nexport async function handleGemini(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n model: string,\n streaming: boolean,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n providerKey: RecordProviderKey = \"gemini\",\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let geminiReq: GeminiRequest;\n try {\n geminiReq = JSON.parse(raw) as GeminiRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? `/v1beta/models/${model}:generateContent`,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n code: 400,\n status: \"INVALID_ARGUMENT\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = geminiToCompletionRequest(geminiReq, model, streaming);\n completionReq._endpointType = \"chat\";\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n const path = req.url ?? `/v1beta/models/${model}:generateContent`;\n\n if (fixture) {\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n logger.debug(`No fixture matched for request`);\n }\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n providerKey,\n path,\n fixtures,\n defaults,\n raw,\n );\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n if (defaults.strict) {\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${path}`);\n }\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n code: strictStatus,\n status: defaults.strict ? \"UNAVAILABLE\" : \"NOT_FOUND\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, completionReq);\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n // Gemini-style error format: { error: { code, message, status } }\n const geminiError = {\n error: {\n code: status,\n message: response.error.message,\n status: response.error.type ?? \"ERROR\",\n },\n };\n writeErrorResponse(res, status, JSON.stringify(geminiError));\n return;\n }\n\n // Audio response\n if (isAudioResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiAudioResponse(response);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiAudioStreamChunks(response);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Content + tool calls response (must be checked before isTextResponse / isToolCallResponse)\n if (isContentWithToolCallsResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Gemini API — ignoring\");\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n logger,\n response.reasoning,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiContentWithToolCallsStreamChunks(\n response.content,\n response.toolCalls,\n chunkSize,\n logger,\n response.reasoning,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Gemini API — ignoring\");\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiTextResponse(response.content, response.reasoning, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiTextStreamChunks(\n response.content,\n chunkSize,\n response.reasoning,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiToolCallResponse(response.toolCalls, logger, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiToolCallStreamChunks(response.toolCalls, logger, overrides);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n code: 500,\n status: \"INTERNAL\",\n },\n }),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"gemini.js","names":[],"sources":["../src/gemini.ts"],"sourcesContent":["/**\n * Google Gemini GenerateContent API support.\n *\n * Translates incoming Gemini requests into the ChatCompletionRequest format\n * used by the fixture router, and converts fixture responses back into the\n * Gemini GenerateContent streaming (or non-streaming) format.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n AudioResponse,\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n RecordProviderKey,\n ResponseOverrides,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n isAudioResponse,\n extractOverrides,\n formatToMime,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse, delay, calculateDelay } from \"./sse-writer.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Gemini request types ───────────────────────────────────────────────────\n\ninterface GeminiPart {\n text?: string;\n thought?: boolean;\n functionCall?: { name: string; args: Record<string, unknown> };\n functionResponse?: { name: string; response: unknown };\n inlineData?: { mimeType: string; data: string };\n}\n\ninterface GeminiContent {\n role?: string;\n parts: GeminiPart[];\n}\n\ninterface GeminiFunctionDeclaration {\n name: string;\n description?: string;\n parameters?: object;\n}\n\ninterface GeminiToolDef {\n functionDeclarations?: GeminiFunctionDeclaration[];\n}\n\ninterface GeminiRequest {\n contents?: GeminiContent[];\n systemInstruction?: GeminiContent;\n tools?: GeminiToolDef[];\n generationConfig?: {\n temperature?: number;\n maxOutputTokens?: number;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}\n\n// ─── Input conversion: Gemini → ChatCompletions messages ────────────────────\n\nexport function geminiToCompletionRequest(\n req: GeminiRequest,\n model: string,\n stream: boolean,\n): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n // systemInstruction → system message\n if (req.systemInstruction) {\n const text = req.systemInstruction.parts\n .filter((p) => p.text !== undefined)\n .map((p) => p.text!)\n .join(\"\");\n if (text) {\n messages.push({ role: \"system\", content: text });\n }\n }\n\n if (req.contents) {\n let callCounter = 0;\n for (const content of req.contents) {\n const role = content.role ?? \"user\";\n\n if (role === \"user\") {\n // Check for functionResponse parts\n const funcResponses = content.parts.filter((p) => p.functionResponse);\n const textParts = content.parts.filter((p) => p.text !== undefined && !p.thought);\n\n if (funcResponses.length > 0) {\n // functionResponse → tool message; match IDs from the preceding assistant's tool_calls\n const lastAssistant = [...messages]\n .reverse()\n .find((m) => m.role === \"assistant\" && m.tool_calls);\n const matchedToolCallIds = new Set<string>();\n for (const part of funcResponses) {\n const matchingCall = lastAssistant?.tool_calls?.find(\n (tc) =>\n tc.function.name === part.functionResponse!.name && !matchedToolCallIds.has(tc.id),\n );\n if (matchingCall) matchedToolCallIds.add(matchingCall.id);\n const toolCallId =\n matchingCall?.id ?? `call_gemini_${part.functionResponse!.name}_${callCounter++}`;\n messages.push({\n role: \"tool\",\n content:\n typeof part.functionResponse!.response === \"string\"\n ? part.functionResponse!.response\n : JSON.stringify(part.functionResponse!.response),\n tool_call_id: toolCallId,\n });\n }\n // Any text parts alongside → user message\n if (textParts.length > 0) {\n messages.push({\n role: \"user\",\n content: textParts.map((p) => p.text!).join(\"\"),\n });\n }\n } else {\n // Regular user text\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({ role: \"user\", content: text });\n }\n } else if (role === \"model\") {\n // Check for functionCall parts\n const funcCalls = content.parts.filter((p) => p.functionCall);\n const textParts = content.parts.filter((p) => p.text !== undefined && !p.thought);\n\n if (funcCalls.length > 0) {\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({\n role: \"assistant\",\n content: text || null,\n tool_calls: funcCalls.map((fc, i) => ({\n id: `call_gemini_${fc.functionCall!.name}_${i}`,\n type: \"function\" as const,\n function: {\n name: fc.functionCall!.name,\n arguments: JSON.stringify(fc.functionCall!.args ?? {}),\n },\n })),\n });\n } else {\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({ role: \"assistant\", content: text });\n }\n }\n // Unrecognized roles (not \"user\" or \"model\") are silently dropped.\n // Gemini only defines \"user\" and \"model\"; any other value indicates\n // a malformed request or an unsupported future role.\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n const decls = req.tools.flatMap((t) => t.functionDeclarations ?? []);\n if (decls.length > 0) {\n tools = decls.map((d) => ({\n type: \"function\" as const,\n function: {\n name: d.name,\n description: d.description,\n parameters: d.parameters,\n },\n }));\n }\n }\n\n return {\n model,\n messages,\n stream,\n temperature: req.generationConfig?.temperature,\n max_tokens: req.generationConfig?.maxOutputTokens,\n top_p: req.generationConfig?.topP as number | undefined,\n top_k: req.generationConfig?.topK as number | undefined,\n tools,\n };\n}\n\n// ─── Response building: fixture → Gemini format ─────────────────────────────\n\nfunction geminiFinishReason(finishReason: string | undefined, defaultReason: string): string {\n if (!finishReason) return defaultReason;\n if (finishReason === \"stop\") return \"STOP\";\n if (finishReason === \"tool_calls\") return \"FUNCTION_CALL\";\n if (finishReason === \"length\") return \"MAX_TOKENS\";\n if (finishReason === \"content_filter\") return \"SAFETY\";\n // Pass through unrecognized values as-is\n return finishReason;\n}\n\nfunction geminiUsageMetadata(overrides?: ResponseOverrides): {\n promptTokenCount: number;\n candidatesTokenCount: number;\n totalTokenCount: number;\n} {\n if (!overrides?.usage)\n return { promptTokenCount: 0, candidatesTokenCount: 0, totalTokenCount: 0 };\n const prompt =\n overrides.usage.promptTokenCount ??\n overrides.usage.prompt_tokens ??\n overrides.usage.input_tokens ??\n 0;\n const candidates =\n overrides.usage.candidatesTokenCount ??\n overrides.usage.completion_tokens ??\n overrides.usage.output_tokens ??\n 0;\n const total = overrides.usage.totalTokenCount ?? prompt + candidates;\n return {\n promptTokenCount: prompt,\n candidatesTokenCount: candidates,\n totalTokenCount: total,\n };\n}\n\ninterface GeminiResponseChunk {\n candidates: {\n content: { role: string; parts: GeminiPart[] };\n finishReason?: string;\n index: number;\n }[];\n usageMetadata?: {\n promptTokenCount: number;\n candidatesTokenCount: number;\n totalTokenCount: number;\n };\n}\n\nfunction buildGeminiTextStreamChunks(\n content: string,\n chunkSize: number,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk[] {\n const chunks: GeminiResponseChunk[] = [];\n const effectiveFinish = geminiFinishReason(overrides?.finishReason, \"STOP\");\n const usage = geminiUsageMetadata(overrides);\n\n // Reasoning chunks (thought: true)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice, thought: true }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n // Content chunks\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n const isLast = i + chunkSize >= content.length;\n const chunk: GeminiResponseChunk = {\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice }] },\n index: 0,\n ...(isLast ? { finishReason: effectiveFinish } : {}),\n },\n ],\n ...(isLast ? { usageMetadata: usage } : {}),\n };\n chunks.push(chunk);\n }\n\n // Handle empty content\n if (content.length === 0) {\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: \"\" }] },\n finishReason: effectiveFinish,\n index: 0,\n },\n ],\n usageMetadata: usage,\n });\n }\n\n return chunks;\n}\n\nfunction parseToolCallPart(tc: ToolCall, logger: Logger): GeminiPart {\n let argsObj: Record<string, unknown>;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\") as Record<string, unknown>;\n } catch {\n logger.warn(`Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`);\n argsObj = {};\n }\n return { functionCall: { name: tc.name, args: argsObj } };\n}\n\nfunction buildGeminiToolCallStreamChunks(\n toolCalls: ToolCall[],\n logger: Logger,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk[] {\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n // Gemini sends all tool calls in a single response chunk\n return [\n {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n },\n ];\n}\n\n// Non-streaming response builders\n\nfunction buildGeminiTextResponse(\n content: string,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk {\n const parts: GeminiPart[] = [];\n if (reasoning) {\n parts.push({ text: reasoning, thought: true });\n }\n parts.push({ text: content });\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"STOP\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n };\n}\n\nfunction buildGeminiToolCallResponse(\n toolCalls: ToolCall[],\n logger: Logger,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk {\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n };\n}\n\nfunction buildGeminiContentWithToolCallsStreamChunks(\n content: string,\n toolCalls: ToolCall[],\n chunkSize: number,\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk[] {\n const chunks: GeminiResponseChunk[] = [];\n\n // Reasoning chunks (thought: true)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice, thought: true }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n if (content.length === 0) {\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: \"\" }] },\n index: 0,\n },\n ],\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n });\n\n return chunks;\n}\n\nfunction buildGeminiContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk {\n const parts: GeminiPart[] = [];\n if (reasoning) {\n parts.push({ text: reasoning, thought: true });\n }\n parts.push({ text: content });\n parts.push(...toolCalls.map((tc) => parseToolCallPart(tc, logger)));\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n };\n}\n\n// ─── Audio response builders ────────────────────────────────────────────────\n\nfunction resolveAudioInlineData(audio: AudioResponse): { mimeType: string; data: string } {\n if (typeof audio.audio === \"string\") {\n return { mimeType: formatToMime(audio.format ?? \"mp3\"), data: audio.audio };\n }\n return {\n mimeType: audio.audio.contentType ?? \"audio/mpeg\",\n data: audio.audio.b64Json,\n };\n}\n\nfunction buildGeminiAudioResponse(audio: AudioResponse): GeminiResponseChunk {\n const inlineData = resolveAudioInlineData(audio);\n return {\n candidates: [\n {\n content: { role: \"model\", parts: [{ inlineData }] },\n finishReason: \"STOP\",\n index: 0,\n },\n ],\n usageMetadata: { promptTokenCount: 0, candidatesTokenCount: 0, totalTokenCount: 0 },\n };\n}\n\nfunction buildGeminiAudioStreamChunks(audio: AudioResponse): GeminiResponseChunk[] {\n const inlineData = resolveAudioInlineData(audio);\n return [\n {\n candidates: [\n {\n content: { role: \"model\", parts: [{ inlineData }] },\n finishReason: \"STOP\",\n index: 0,\n },\n ],\n usageMetadata: { promptTokenCount: 0, candidatesTokenCount: 0, totalTokenCount: 0 },\n },\n ];\n}\n\n// ─── SSE writer for Gemini streaming ────────────────────────────────────────\n\ninterface GeminiStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nasync function writeGeminiSSEStream(\n res: http.ServerResponse,\n chunks: GeminiResponseChunk[],\n optionsOrLatency?: number | GeminiStreamOptions,\n): Promise<boolean> {\n const opts: GeminiStreamOptions =\n typeof optionsOrLatency === \"number\" ? { latency: optionsOrLatency } : (optionsOrLatency ?? {});\n const latency = opts.latency ?? 0;\n const profile = opts.streamingProfile;\n const signal = opts.signal;\n const onChunkSent = opts.onChunkSent;\n\n if (res.writableEnded) return true;\n res.setHeader(\"Content-Type\", \"text/event-stream\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"keep-alive\");\n\n let chunkIndex = 0;\n for (const chunk of chunks) {\n const chunkDelay = calculateDelay(chunkIndex, profile, latency);\n if (chunkDelay > 0) await delay(chunkDelay, signal);\n if (signal?.aborted) return false;\n if (res.writableEnded) return true;\n // Gemini uses data-only SSE (no event: prefix, no [DONE])\n res.write(`data: ${JSON.stringify(chunk)}\\n\\n`);\n onChunkSent?.();\n if (signal?.aborted) return false;\n chunkIndex++;\n }\n\n if (!res.writableEnded) {\n res.end();\n }\n return true;\n}\n\n// ─── Request handler ────────────────────────────────────────────────────────\n\nexport async function handleGemini(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n model: string,\n streaming: boolean,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n providerKey: RecordProviderKey = \"gemini\",\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let geminiReq: GeminiRequest;\n try {\n geminiReq = JSON.parse(raw) as GeminiRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? `/v1beta/models/${model}:generateContent`,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: `Malformed JSON body: ${detail}`,\n code: 400,\n status: \"INVALID_ARGUMENT\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = geminiToCompletionRequest(geminiReq, model, streaming);\n completionReq._endpointType = \"chat\";\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n const path = req.url ?? `/v1beta/models/${model}:generateContent`;\n\n if (fixture) {\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n logger.debug(`No fixture matched for request`);\n }\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${path}`);\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: \"Strict mode: no fixture matched\",\n code: 503,\n status: \"UNAVAILABLE\",\n },\n }),\n );\n return;\n }\n\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n providerKey,\n path,\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n code: 404,\n status: \"NOT_FOUND\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, completionReq);\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n // Gemini-style error format: { error: { code, message, status } }\n const geminiError = {\n error: {\n code: status,\n message: response.error.message,\n status: response.error.type ?? \"ERROR\",\n },\n };\n writeErrorResponse(res, status, JSON.stringify(geminiError));\n return;\n }\n\n // Audio response\n if (isAudioResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiAudioResponse(response);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiAudioStreamChunks(response);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Content + tool calls response (must be checked before isTextResponse / isToolCallResponse)\n if (isContentWithToolCallsResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Gemini API — ignoring\");\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n logger,\n response.reasoning,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiContentWithToolCallsStreamChunks(\n response.content,\n response.toolCalls,\n chunkSize,\n logger,\n response.reasoning,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Gemini API — ignoring\");\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiTextResponse(response.content, response.reasoning, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiTextStreamChunks(\n response.content,\n chunkSize,\n response.reasoning,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Gemini API — ignoring\");\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiToolCallResponse(response.toolCalls, logger, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiToolCallStreamChunks(response.toolCalls, logger, overrides);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n code: 500,\n status: \"INTERNAL\",\n },\n }),\n );\n}\n"],"mappings":"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"}
package/dist/helpers.cjs CHANGED
@@ -1,4 +1,5 @@
1
1
  const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
2
+ const require_constants = require('./constants.cjs');
2
3
  let node_crypto = require("node:crypto");
3
4
 
4
5
  //#region src/helpers.ts
@@ -7,6 +8,35 @@ const REDACTED_HEADERS = new Set([
7
8
  "x-api-key",
8
9
  "api-key"
9
10
  ]);
11
+ /**
12
+ * Resolve effective strict mode from per-request header and server default.
13
+ * Header values override the server default — same precedence pattern as chaos
14
+ * config headers (see resolveChaosConfig in chaos.ts).
15
+ *
16
+ * Header: `X-AIMock-Strict` — "true"/"1" → strict on, "false"/"0" → strict off.
17
+ * When absent or unrecognised, falls back to the server-level default.
18
+ */
19
+ function resolveStrictMode(serverDefault, rawHeaders) {
20
+ if (rawHeaders) {
21
+ const header = rawHeaders["x-aimock-strict"];
22
+ const val = typeof header === "string" ? header : Array.isArray(header) ? header[0] : void 0;
23
+ if (val === "true" || val === "1") return true;
24
+ if (val === "false" || val === "0") return false;
25
+ }
26
+ return serverDefault ?? false;
27
+ }
28
+ /**
29
+ * Returns `true` or `false` when the X-AIMock-Strict header overrides the
30
+ * server default, or `undefined` when it doesn't. Designed to be spread
31
+ * directly into a journal entry's `response` object:
32
+ *
33
+ * response: { status, fixture, ...strictOverrideField(defaults.strict, req.headers) }
34
+ */
35
+ function strictOverrideField(serverDefault, rawHeaders) {
36
+ const effective = resolveStrictMode(serverDefault, rawHeaders);
37
+ if (effective !== (serverDefault ?? false)) return { strictOverride: effective };
38
+ return {};
39
+ }
10
40
  function flattenHeaders(headers) {
11
41
  const flat = {};
12
42
  for (const [key, value] of Object.entries(headers)) {
@@ -17,7 +47,12 @@ function flattenHeaders(headers) {
17
47
  return flat;
18
48
  }
19
49
  async function resolveResponse(fixture, request) {
20
- if (typeof fixture.response === "function") return normalizeFactoryResponse(await fixture.response(request));
50
+ if (typeof fixture.response === "function") try {
51
+ return normalizeFactoryResponse(await fixture.response(request));
52
+ } catch (err) {
53
+ const msg = err instanceof Error ? err.message : String(err);
54
+ throw new Error(`Response factory threw: ${msg}`);
55
+ }
21
56
  return fixture.response;
22
57
  }
23
58
  function normalizeFactoryResponse(raw) {
@@ -28,7 +63,7 @@ function normalizeFactoryResponse(raw) {
28
63
  ...tc,
29
64
  arguments: JSON.stringify(tc.arguments)
30
65
  };
31
- return tc;
66
+ return { ...tc };
32
67
  });
33
68
  return r;
34
69
  }
@@ -54,7 +89,19 @@ function isContentWithToolCallsResponse(r) {
54
89
  return "content" in r && typeof r.content === "string" && "toolCalls" in r && Array.isArray(r.toolCalls);
55
90
  }
56
91
  function isErrorResponse(r) {
57
- return "error" in r && r.error !== null && typeof r.error === "object";
92
+ return "error" in r && r.error !== null && typeof r.error === "object" && "message" in r.error && typeof r.error.message === "string";
93
+ }
94
+ /**
95
+ * Serialize an ErrorResponse to JSON, stripping the internal-only `status`
96
+ * field that controls the HTTP status code but should never appear in the
97
+ * response body. Real LLM APIs don't include it.
98
+ */
99
+ function serializeErrorResponse(response) {
100
+ return JSON.stringify({ error: {
101
+ message: response.error.message,
102
+ type: response.error.type ?? "server_error",
103
+ code: response.error.code ?? null
104
+ } });
58
105
  }
59
106
  function isEmbeddingResponse(r) {
60
107
  return "embedding" in r && Array.isArray(r.embedding);
@@ -477,12 +524,35 @@ function buildContentWithToolCallsCompletion(content, toolCalls, model, reasonin
477
524
  ...overrides?.systemFingerprint !== void 0 && { system_fingerprint: overrides.systemFingerprint }
478
525
  };
479
526
  }
480
- function readBody(req) {
527
+ const DEFAULT_MAX_BODY_BYTES = 10 * 1024 * 1024;
528
+ function readBody(req, maxBytes = DEFAULT_MAX_BODY_BYTES) {
481
529
  return new Promise((resolve, reject) => {
482
530
  const chunks = [];
483
- req.on("data", (chunk) => chunks.push(chunk));
484
- req.on("end", () => resolve(Buffer.concat(chunks).toString()));
485
- req.on("error", reject);
531
+ let totalBytes = 0;
532
+ let settled = false;
533
+ req.on("data", (chunk) => {
534
+ if (settled) return;
535
+ totalBytes += chunk.length;
536
+ if (totalBytes > maxBytes) {
537
+ settled = true;
538
+ req.destroy();
539
+ reject(/* @__PURE__ */ new Error(`Request body exceeded size limit of ${maxBytes} bytes`));
540
+ return;
541
+ }
542
+ chunks.push(chunk);
543
+ });
544
+ req.on("end", () => {
545
+ if (!settled) {
546
+ settled = true;
547
+ resolve(Buffer.concat(chunks).toString());
548
+ }
549
+ });
550
+ req.on("error", (err) => {
551
+ if (!settled) {
552
+ settled = true;
553
+ reject(err);
554
+ }
555
+ });
486
556
  });
487
557
  }
488
558
  /**
@@ -496,6 +566,7 @@ function readBody(req) {
496
566
  */
497
567
  function matchesPattern(text, pattern) {
498
568
  if (typeof pattern === "string") return text.toLowerCase().includes(pattern.toLowerCase());
569
+ pattern.lastIndex = 0;
499
570
  return pattern.test(text);
500
571
  }
501
572
  function getTestId(req) {
@@ -509,7 +580,7 @@ function getTestId(req) {
509
580
  const queryValue = new URLSearchParams(url.slice(qIdx + 1)).get("testId");
510
581
  if (queryValue) return queryValue;
511
582
  }
512
- return "__default__";
583
+ return require_constants.DEFAULT_TEST_ID;
513
584
  }
514
585
  /**
515
586
  * Convert a test ID (e.g. Playwright titlePath) into a filesystem-safe slug
@@ -583,5 +654,8 @@ exports.isVideoResponse = isVideoResponse;
583
654
  exports.matchesPattern = matchesPattern;
584
655
  exports.readBody = readBody;
585
656
  exports.resolveResponse = resolveResponse;
657
+ exports.resolveStrictMode = resolveStrictMode;
658
+ exports.serializeErrorResponse = serializeErrorResponse;
586
659
  exports.slugifyTestId = slugifyTestId;
660
+ exports.strictOverrideField = strictOverrideField;
587
661
  //# sourceMappingURL=helpers.cjs.map