@copilotkit/aimock 1.24.1 → 1.26.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.
Files changed (206) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +38 -0
  4. package/README.md +21 -11
  5. package/dist/agui-types.d.cts.map +1 -1
  6. package/dist/agui-types.d.ts.map +1 -1
  7. package/dist/aws-event-stream.cjs +2 -1
  8. package/dist/aws-event-stream.cjs.map +1 -1
  9. package/dist/aws-event-stream.d.cts +3 -1
  10. package/dist/aws-event-stream.d.cts.map +1 -1
  11. package/dist/aws-event-stream.d.ts +3 -1
  12. package/dist/aws-event-stream.d.ts.map +1 -1
  13. package/dist/aws-event-stream.js +2 -1
  14. package/dist/aws-event-stream.js.map +1 -1
  15. package/dist/bedrock-converse.cjs +8 -2
  16. package/dist/bedrock-converse.cjs.map +1 -1
  17. package/dist/bedrock-converse.d.cts.map +1 -1
  18. package/dist/bedrock-converse.d.ts.map +1 -1
  19. package/dist/bedrock-converse.js +8 -2
  20. package/dist/bedrock-converse.js.map +1 -1
  21. package/dist/bedrock.cjs +8 -2
  22. package/dist/bedrock.cjs.map +1 -1
  23. package/dist/bedrock.d.cts.map +1 -1
  24. package/dist/bedrock.d.ts.map +1 -1
  25. package/dist/bedrock.js +8 -2
  26. package/dist/bedrock.js.map +1 -1
  27. package/dist/cli.cjs +36 -1
  28. package/dist/cli.cjs.map +1 -1
  29. package/dist/cli.js +36 -1
  30. package/dist/cli.js.map +1 -1
  31. package/dist/cohere.cjs +206 -2
  32. package/dist/cohere.cjs.map +1 -1
  33. package/dist/cohere.d.cts.map +1 -1
  34. package/dist/cohere.d.ts.map +1 -1
  35. package/dist/cohere.js +207 -4
  36. package/dist/cohere.js.map +1 -1
  37. package/dist/config-loader.d.ts.map +1 -1
  38. package/dist/elevenlabs-audio.cjs +173 -1
  39. package/dist/elevenlabs-audio.cjs.map +1 -1
  40. package/dist/elevenlabs-audio.d.cts.map +1 -1
  41. package/dist/elevenlabs-audio.d.ts.map +1 -1
  42. package/dist/elevenlabs-audio.js +173 -2
  43. package/dist/elevenlabs-audio.js.map +1 -1
  44. package/dist/embeddings.cjs +1 -1
  45. package/dist/embeddings.cjs.map +1 -1
  46. package/dist/embeddings.js +1 -1
  47. package/dist/embeddings.js.map +1 -1
  48. package/dist/fal-audio.cjs +2 -4
  49. package/dist/fal-audio.cjs.map +1 -1
  50. package/dist/fal-audio.js +2 -4
  51. package/dist/fal-audio.js.map +1 -1
  52. package/dist/fal.cjs +2 -2
  53. package/dist/fal.cjs.map +1 -1
  54. package/dist/fal.d.cts.map +1 -1
  55. package/dist/fal.d.ts.map +1 -1
  56. package/dist/fal.js +2 -2
  57. package/dist/fal.js.map +1 -1
  58. package/dist/fixture-loader.cjs +16 -3
  59. package/dist/fixture-loader.cjs.map +1 -1
  60. package/dist/fixture-loader.d.cts.map +1 -1
  61. package/dist/fixture-loader.d.ts.map +1 -1
  62. package/dist/fixture-loader.js +16 -3
  63. package/dist/fixture-loader.js.map +1 -1
  64. package/dist/gemini-embeddings.cjs +166 -0
  65. package/dist/gemini-embeddings.cjs.map +1 -0
  66. package/dist/gemini-embeddings.js +166 -0
  67. package/dist/gemini-embeddings.js.map +1 -0
  68. package/dist/gemini-interactions.cjs +10 -2
  69. package/dist/gemini-interactions.cjs.map +1 -1
  70. package/dist/gemini-interactions.d.cts.map +1 -1
  71. package/dist/gemini-interactions.d.ts.map +1 -1
  72. package/dist/gemini-interactions.js +10 -2
  73. package/dist/gemini-interactions.js.map +1 -1
  74. package/dist/gemini.cjs +12 -2
  75. package/dist/gemini.cjs.map +1 -1
  76. package/dist/gemini.d.cts.map +1 -1
  77. package/dist/gemini.d.ts.map +1 -1
  78. package/dist/gemini.js +12 -2
  79. package/dist/gemini.js.map +1 -1
  80. package/dist/helpers.cjs +70 -33
  81. package/dist/helpers.cjs.map +1 -1
  82. package/dist/helpers.d.cts +9 -5
  83. package/dist/helpers.d.cts.map +1 -1
  84. package/dist/helpers.d.ts +9 -5
  85. package/dist/helpers.d.ts.map +1 -1
  86. package/dist/helpers.js +68 -34
  87. package/dist/helpers.js.map +1 -1
  88. package/dist/images.cjs +295 -13
  89. package/dist/images.cjs.map +1 -1
  90. package/dist/images.d.cts +9 -1
  91. package/dist/images.d.cts.map +1 -1
  92. package/dist/images.d.ts +9 -1
  93. package/dist/images.d.ts.map +1 -1
  94. package/dist/images.js +294 -14
  95. package/dist/images.js.map +1 -1
  96. package/dist/index.cjs +1 -1
  97. package/dist/index.d.cts +2 -2
  98. package/dist/index.d.ts +2 -2
  99. package/dist/index.js +1 -1
  100. package/dist/llmock.cjs +16 -1
  101. package/dist/llmock.cjs.map +1 -1
  102. package/dist/llmock.d.cts +2 -0
  103. package/dist/llmock.d.cts.map +1 -1
  104. package/dist/llmock.d.ts +2 -0
  105. package/dist/llmock.d.ts.map +1 -1
  106. package/dist/llmock.js +16 -1
  107. package/dist/llmock.js.map +1 -1
  108. package/dist/messages.cjs +9 -2
  109. package/dist/messages.cjs.map +1 -1
  110. package/dist/messages.d.cts.map +1 -1
  111. package/dist/messages.d.ts.map +1 -1
  112. package/dist/messages.js +9 -2
  113. package/dist/messages.js.map +1 -1
  114. package/dist/metrics.cjs +2 -0
  115. package/dist/metrics.cjs.map +1 -1
  116. package/dist/metrics.d.cts.map +1 -1
  117. package/dist/metrics.d.ts.map +1 -1
  118. package/dist/metrics.js +2 -0
  119. package/dist/metrics.js.map +1 -1
  120. package/dist/ndjson-writer.cjs +2 -1
  121. package/dist/ndjson-writer.cjs.map +1 -1
  122. package/dist/ndjson-writer.d.cts +3 -2
  123. package/dist/ndjson-writer.d.cts.map +1 -1
  124. package/dist/ndjson-writer.d.ts +3 -2
  125. package/dist/ndjson-writer.d.ts.map +1 -1
  126. package/dist/ndjson-writer.js +2 -1
  127. package/dist/ndjson-writer.js.map +1 -1
  128. package/dist/ollama.cjs +197 -2
  129. package/dist/ollama.cjs.map +1 -1
  130. package/dist/ollama.d.cts.map +1 -1
  131. package/dist/ollama.d.ts.map +1 -1
  132. package/dist/ollama.js +198 -4
  133. package/dist/ollama.js.map +1 -1
  134. package/dist/recorder.cjs +49 -5
  135. package/dist/recorder.cjs.map +1 -1
  136. package/dist/recorder.d.cts.map +1 -1
  137. package/dist/recorder.d.ts.map +1 -1
  138. package/dist/recorder.js +49 -5
  139. package/dist/recorder.js.map +1 -1
  140. package/dist/responses.cjs +11 -2
  141. package/dist/responses.cjs.map +1 -1
  142. package/dist/responses.d.cts.map +1 -1
  143. package/dist/responses.d.ts.map +1 -1
  144. package/dist/responses.js +11 -2
  145. package/dist/responses.js.map +1 -1
  146. package/dist/server.cjs +196 -49
  147. package/dist/server.cjs.map +1 -1
  148. package/dist/server.d.cts.map +1 -1
  149. package/dist/server.d.ts.map +1 -1
  150. package/dist/server.js +201 -54
  151. package/dist/server.js.map +1 -1
  152. package/dist/speech.cjs +1 -1
  153. package/dist/speech.cjs.map +1 -1
  154. package/dist/speech.js +1 -1
  155. package/dist/speech.js.map +1 -1
  156. package/dist/sse-writer.cjs +48 -10
  157. package/dist/sse-writer.cjs.map +1 -1
  158. package/dist/sse-writer.d.cts +12 -4
  159. package/dist/sse-writer.d.cts.map +1 -1
  160. package/dist/sse-writer.d.ts +12 -4
  161. package/dist/sse-writer.d.ts.map +1 -1
  162. package/dist/sse-writer.js +48 -10
  163. package/dist/sse-writer.js.map +1 -1
  164. package/dist/transcription.cjs +9 -6
  165. package/dist/transcription.cjs.map +1 -1
  166. package/dist/transcription.d.cts +2 -2
  167. package/dist/transcription.d.cts.map +1 -1
  168. package/dist/transcription.d.ts +2 -2
  169. package/dist/transcription.d.ts.map +1 -1
  170. package/dist/transcription.js +8 -7
  171. package/dist/transcription.js.map +1 -1
  172. package/dist/types.d.cts +45 -3
  173. package/dist/types.d.cts.map +1 -1
  174. package/dist/types.d.ts +45 -3
  175. package/dist/types.d.ts.map +1 -1
  176. package/dist/video.cjs +1 -1
  177. package/dist/video.cjs.map +1 -1
  178. package/dist/video.d.cts.map +1 -1
  179. package/dist/video.d.ts.map +1 -1
  180. package/dist/video.js +1 -1
  181. package/dist/video.js.map +1 -1
  182. package/dist/ws-gemini-live.cjs +14 -4
  183. package/dist/ws-gemini-live.cjs.map +1 -1
  184. package/dist/ws-gemini-live.d.cts +1 -0
  185. package/dist/ws-gemini-live.d.cts.map +1 -1
  186. package/dist/ws-gemini-live.d.ts +1 -0
  187. package/dist/ws-gemini-live.d.ts.map +1 -1
  188. package/dist/ws-gemini-live.js +15 -5
  189. package/dist/ws-gemini-live.js.map +1 -1
  190. package/dist/ws-realtime.cjs +21 -4
  191. package/dist/ws-realtime.cjs.map +1 -1
  192. package/dist/ws-realtime.d.cts +1 -0
  193. package/dist/ws-realtime.d.cts.map +1 -1
  194. package/dist/ws-realtime.d.ts +1 -0
  195. package/dist/ws-realtime.d.ts.map +1 -1
  196. package/dist/ws-realtime.js +22 -5
  197. package/dist/ws-realtime.js.map +1 -1
  198. package/dist/ws-responses.cjs +8 -5
  199. package/dist/ws-responses.cjs.map +1 -1
  200. package/dist/ws-responses.d.cts +1 -0
  201. package/dist/ws-responses.d.cts.map +1 -1
  202. package/dist/ws-responses.d.ts +1 -0
  203. package/dist/ws-responses.d.ts.map +1 -1
  204. package/dist/ws-responses.js +9 -6
  205. package/dist/ws-responses.js.map +1 -1
  206. package/package.json +2 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini-embeddings.cjs","names":["flattenHeaders","getTestId","matchFixture","applyChaos","resolveResponse","isErrorResponse","isEmbeddingResponse","resolveStrictMode","strictOverrideField","proxyAndRecord","generateDeterministicEmbedding"],"sources":["../src/gemini-embeddings.ts"],"sourcesContent":["/**\n * Google Gemini embedContent API support.\n *\n * Handles POST /v1beta/models/{model}:embedContent requests. Translates\n * incoming Gemini embedding requests into the ChatCompletionRequest format\n * used by the fixture router (with _endpointType: \"embedding\"), and converts\n * fixture responses back into the Gemini embedContent response format.\n *\n * Falls back to generating a deterministic embedding from the input text hash\n * when no fixture matches — same strategy as the OpenAI embeddings handler.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RecordProviderKey,\n} from \"./types.js\";\nimport {\n isEmbeddingResponse,\n isErrorResponse,\n generateDeterministicEmbedding,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Gemini embedContent request types ────────────────────────────────────\n\ninterface GeminiEmbedContentPart {\n text?: string;\n}\n\ninterface GeminiEmbedContentRequest {\n content?: { parts?: GeminiEmbedContentPart[] };\n model?: string;\n taskType?: string;\n title?: string;\n outputDimensionality?: number;\n [key: string]: unknown;\n}\n\n// ─── Gemini embedContent response type ────────────────────────────────────\n\ninterface GeminiEmbedContentResponse {\n embedding: {\n values: number[];\n };\n}\n\n// ─── Default embedding dimensions for Gemini ──────────────────────────────\n\nconst DEFAULT_GEMINI_EMBEDDING_DIMENSIONS = 768;\n\n// ─── Request handler ───────────────────────────────────────────────────────\n\nexport async function handleGeminiEmbedContent(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n model: string,\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 embedReq: GeminiEmbedContentRequest;\n try {\n embedReq = JSON.parse(raw) as GeminiEmbedContentRequest;\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}:embedContent`,\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 // Extract text from content.parts\n const parts = embedReq.content?.parts ?? [];\n const inputText = parts\n .filter((p) => p.text !== undefined)\n .map((p) => p.text!)\n .join(\" \");\n\n // Build a synthetic ChatCompletionRequest for the fixture router.\n // Uses _endpointType: \"embedding\" and embeddingInput to share fixtures\n // with OpenAI embeddings.\n const syntheticReq: ChatCompletionRequest = {\n model: embedReq.model ?? model,\n messages: [],\n embeddingInput: inputText,\n _endpointType: \"embedding\",\n };\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n const path = req.url ?? `/v1beta/models/${model}:embedContent`;\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\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 (\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: syntheticReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (fixture) {\n const response = await resolveResponse(fixture, syntheticReq);\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: syntheticReq,\n response: { status, fixture },\n });\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 retryAfter: response.retryAfter,\n });\n return;\n }\n\n // Embedding response — use the fixture's embedding values\n if (isEmbeddingResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n const body: GeminiEmbedContentResponse = {\n embedding: {\n values: [...response.embedding],\n },\n };\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Fixture matched but response type is not compatible with embeddings\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message:\n \"Fixture response did not match any known embedding type (must have embedding or error)\",\n code: 500,\n status: \"INTERNAL\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — check strict mode first, then try proxy\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: syntheticReq,\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 syntheticReq,\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: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n // No fixture match — generate deterministic embedding from input text\n const dimensions = embedReq.outputDimensionality ?? DEFAULT_GEMINI_EMBEDDING_DIMENSIONS;\n const embedding = generateDeterministicEmbedding(inputText, dimensions);\n\n logger.warn(\n `No embedding fixture matched for \"${inputText.slice(0, 80)}\" — returning deterministic fallback`,\n );\n\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n\n const body: GeminiEmbedContentResponse = {\n embedding: {\n values: embedding,\n },\n };\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":"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"}
@@ -0,0 +1,166 @@
1
+ import { flattenHeaders, generateDeterministicEmbedding, getTestId, isEmbeddingResponse, isErrorResponse, resolveResponse, resolveStrictMode, strictOverrideField } from "./helpers.js";
2
+ import { matchFixture } from "./router.js";
3
+ import { writeErrorResponse } from "./sse-writer.js";
4
+ import { applyChaos } from "./chaos.js";
5
+ import { proxyAndRecord } from "./recorder.js";
6
+
7
+ //#region src/gemini-embeddings.ts
8
+ const DEFAULT_GEMINI_EMBEDDING_DIMENSIONS = 768;
9
+ async function handleGeminiEmbedContent(req, res, raw, model, fixtures, journal, defaults, setCorsHeaders, providerKey = "gemini") {
10
+ const { logger } = defaults;
11
+ setCorsHeaders(res);
12
+ let embedReq;
13
+ try {
14
+ embedReq = JSON.parse(raw);
15
+ } catch (parseErr) {
16
+ const detail = parseErr instanceof Error ? parseErr.message : "unknown";
17
+ journal.add({
18
+ method: req.method ?? "POST",
19
+ path: req.url ?? `/v1beta/models/${model}:embedContent`,
20
+ headers: flattenHeaders(req.headers),
21
+ body: null,
22
+ response: {
23
+ status: 400,
24
+ fixture: null
25
+ }
26
+ });
27
+ writeErrorResponse(res, 400, JSON.stringify({ error: {
28
+ message: `Malformed JSON body: ${detail}`,
29
+ code: 400,
30
+ status: "INVALID_ARGUMENT"
31
+ } }));
32
+ return;
33
+ }
34
+ const inputText = (embedReq.content?.parts ?? []).filter((p) => p.text !== void 0).map((p) => p.text).join(" ");
35
+ const syntheticReq = {
36
+ model: embedReq.model ?? model,
37
+ messages: [],
38
+ embeddingInput: inputText,
39
+ _endpointType: "embedding"
40
+ };
41
+ const testId = getTestId(req);
42
+ const fixture = matchFixture(fixtures, syntheticReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
43
+ const path = req.url ?? `/v1beta/models/${model}:embedContent`;
44
+ if (fixture) {
45
+ journal.incrementFixtureMatchCount(fixture, fixtures, testId);
46
+ logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
47
+ } else logger.debug(`No fixture matched for request`);
48
+ if (applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
49
+ method: req.method ?? "POST",
50
+ path,
51
+ headers: flattenHeaders(req.headers),
52
+ body: syntheticReq
53
+ }, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
54
+ if (fixture) {
55
+ const response = await resolveResponse(fixture, syntheticReq);
56
+ if (isErrorResponse(response)) {
57
+ const status = response.status ?? 500;
58
+ journal.add({
59
+ method: req.method ?? "POST",
60
+ path,
61
+ headers: flattenHeaders(req.headers),
62
+ body: syntheticReq,
63
+ response: {
64
+ status,
65
+ fixture
66
+ }
67
+ });
68
+ const geminiError = { error: {
69
+ code: status,
70
+ message: response.error.message,
71
+ status: response.error.type ?? "ERROR"
72
+ } };
73
+ writeErrorResponse(res, status, JSON.stringify(geminiError), { retryAfter: response.retryAfter });
74
+ return;
75
+ }
76
+ if (isEmbeddingResponse(response)) {
77
+ journal.add({
78
+ method: req.method ?? "POST",
79
+ path,
80
+ headers: flattenHeaders(req.headers),
81
+ body: syntheticReq,
82
+ response: {
83
+ status: 200,
84
+ fixture
85
+ }
86
+ });
87
+ const body = { embedding: { values: [...response.embedding] } };
88
+ res.writeHead(200, { "Content-Type": "application/json" });
89
+ res.end(JSON.stringify(body));
90
+ return;
91
+ }
92
+ journal.add({
93
+ method: req.method ?? "POST",
94
+ path,
95
+ headers: flattenHeaders(req.headers),
96
+ body: syntheticReq,
97
+ response: {
98
+ status: 500,
99
+ fixture
100
+ }
101
+ });
102
+ writeErrorResponse(res, 500, JSON.stringify({ error: {
103
+ message: "Fixture response did not match any known embedding type (must have embedding or error)",
104
+ code: 500,
105
+ status: "INTERNAL"
106
+ } }));
107
+ return;
108
+ }
109
+ if (resolveStrictMode(defaults.strict, req.headers)) {
110
+ logger.error(`STRICT: No fixture matched for ${req.method ?? "POST"} ${path}`);
111
+ journal.add({
112
+ method: req.method ?? "POST",
113
+ path,
114
+ headers: flattenHeaders(req.headers),
115
+ body: syntheticReq,
116
+ response: {
117
+ status: 503,
118
+ fixture: null,
119
+ ...strictOverrideField(defaults.strict, req.headers)
120
+ }
121
+ });
122
+ writeErrorResponse(res, 503, JSON.stringify({ error: {
123
+ message: "Strict mode: no fixture matched",
124
+ code: 503,
125
+ status: "UNAVAILABLE"
126
+ } }));
127
+ return;
128
+ }
129
+ if (defaults.record) {
130
+ const outcome = await proxyAndRecord(req, res, syntheticReq, providerKey, path, fixtures, defaults, raw);
131
+ if (outcome === "handled_by_hook") return;
132
+ if (outcome !== "not_configured") {
133
+ journal.add({
134
+ method: req.method ?? "POST",
135
+ path,
136
+ headers: flattenHeaders(req.headers),
137
+ body: syntheticReq,
138
+ response: {
139
+ status: res.statusCode ?? 200,
140
+ fixture: null,
141
+ source: "proxy"
142
+ }
143
+ });
144
+ return;
145
+ }
146
+ }
147
+ const embedding = generateDeterministicEmbedding(inputText, embedReq.outputDimensionality ?? DEFAULT_GEMINI_EMBEDDING_DIMENSIONS);
148
+ logger.warn(`No embedding fixture matched for "${inputText.slice(0, 80)}" — returning deterministic fallback`);
149
+ journal.add({
150
+ method: req.method ?? "POST",
151
+ path,
152
+ headers: flattenHeaders(req.headers),
153
+ body: syntheticReq,
154
+ response: {
155
+ status: 200,
156
+ fixture: null
157
+ }
158
+ });
159
+ const body = { embedding: { values: embedding } };
160
+ res.writeHead(200, { "Content-Type": "application/json" });
161
+ res.end(JSON.stringify(body));
162
+ }
163
+
164
+ //#endregion
165
+ export { handleGeminiEmbedContent };
166
+ //# sourceMappingURL=gemini-embeddings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini-embeddings.js","names":[],"sources":["../src/gemini-embeddings.ts"],"sourcesContent":["/**\n * Google Gemini embedContent API support.\n *\n * Handles POST /v1beta/models/{model}:embedContent requests. Translates\n * incoming Gemini embedding requests into the ChatCompletionRequest format\n * used by the fixture router (with _endpointType: \"embedding\"), and converts\n * fixture responses back into the Gemini embedContent response format.\n *\n * Falls back to generating a deterministic embedding from the input text hash\n * when no fixture matches — same strategy as the OpenAI embeddings handler.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RecordProviderKey,\n} from \"./types.js\";\nimport {\n isEmbeddingResponse,\n isErrorResponse,\n generateDeterministicEmbedding,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Gemini embedContent request types ────────────────────────────────────\n\ninterface GeminiEmbedContentPart {\n text?: string;\n}\n\ninterface GeminiEmbedContentRequest {\n content?: { parts?: GeminiEmbedContentPart[] };\n model?: string;\n taskType?: string;\n title?: string;\n outputDimensionality?: number;\n [key: string]: unknown;\n}\n\n// ─── Gemini embedContent response type ────────────────────────────────────\n\ninterface GeminiEmbedContentResponse {\n embedding: {\n values: number[];\n };\n}\n\n// ─── Default embedding dimensions for Gemini ──────────────────────────────\n\nconst DEFAULT_GEMINI_EMBEDDING_DIMENSIONS = 768;\n\n// ─── Request handler ───────────────────────────────────────────────────────\n\nexport async function handleGeminiEmbedContent(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n model: string,\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 embedReq: GeminiEmbedContentRequest;\n try {\n embedReq = JSON.parse(raw) as GeminiEmbedContentRequest;\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}:embedContent`,\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 // Extract text from content.parts\n const parts = embedReq.content?.parts ?? [];\n const inputText = parts\n .filter((p) => p.text !== undefined)\n .map((p) => p.text!)\n .join(\" \");\n\n // Build a synthetic ChatCompletionRequest for the fixture router.\n // Uses _endpointType: \"embedding\" and embeddingInput to share fixtures\n // with OpenAI embeddings.\n const syntheticReq: ChatCompletionRequest = {\n model: embedReq.model ?? model,\n messages: [],\n embeddingInput: inputText,\n _endpointType: \"embedding\",\n };\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n const path = req.url ?? `/v1beta/models/${model}:embedContent`;\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\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 (\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: syntheticReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (fixture) {\n const response = await resolveResponse(fixture, syntheticReq);\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: syntheticReq,\n response: { status, fixture },\n });\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 retryAfter: response.retryAfter,\n });\n return;\n }\n\n // Embedding response — use the fixture's embedding values\n if (isEmbeddingResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n const body: GeminiEmbedContentResponse = {\n embedding: {\n values: [...response.embedding],\n },\n };\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Fixture matched but response type is not compatible with embeddings\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message:\n \"Fixture response did not match any known embedding type (must have embedding or error)\",\n code: 500,\n status: \"INTERNAL\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — check strict mode first, then try proxy\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: syntheticReq,\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 syntheticReq,\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: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n // No fixture match — generate deterministic embedding from input text\n const dimensions = embedReq.outputDimensionality ?? DEFAULT_GEMINI_EMBEDDING_DIMENSIONS;\n const embedding = generateDeterministicEmbedding(inputText, dimensions);\n\n logger.warn(\n `No embedding fixture matched for \"${inputText.slice(0, 80)}\" — returning deterministic fallback`,\n );\n\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n\n const body: GeminiEmbedContentResponse = {\n embedding: {\n values: embedding,\n },\n };\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":"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"}
@@ -397,6 +397,7 @@ async function writeGeminiInteractionsSSEStream(res, events, optionsOrLatency) {
397
397
  const opts = typeof optionsOrLatency === "number" ? { latency: optionsOrLatency } : optionsOrLatency ?? {};
398
398
  const latency = opts.latency ?? 0;
399
399
  const profile = opts.streamingProfile;
400
+ const { recordedTimings, replaySpeed } = opts;
400
401
  const signal = opts.signal;
401
402
  const onChunkSent = opts.onChunkSent;
402
403
  if (res.writableEnded) return true;
@@ -405,7 +406,7 @@ async function writeGeminiInteractionsSSEStream(res, events, optionsOrLatency) {
405
406
  res.setHeader("Connection", "keep-alive");
406
407
  let chunkIndex = 0;
407
408
  for (const event of events) {
408
- const chunkDelay = require_sse_writer.calculateDelay(chunkIndex, profile, latency);
409
+ const chunkDelay = require_sse_writer.calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);
409
410
  if (chunkDelay > 0) await require_sse_writer.delay(chunkDelay, signal);
410
411
  if (signal?.aborted) return false;
411
412
  if (res.writableEnded) return true;
@@ -506,6 +507,7 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
506
507
  const response = await require_helpers.resolveResponse(fixture, completionReq);
507
508
  const latency = fixture.latency ?? defaults.latency;
508
509
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
510
+ const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
509
511
  if (require_helpers.isErrorResponse(response)) {
510
512
  const status = response.status ?? 500;
511
513
  journal.add({
@@ -518,7 +520,7 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
518
520
  fixture
519
521
  }
520
522
  });
521
- require_sse_writer.writeErrorResponse(res, status, JSON.stringify(buildInteractionsErrorResponse(response.error.message, response.error.type ?? "ERROR")));
523
+ require_sse_writer.writeErrorResponse(res, status, JSON.stringify(buildInteractionsErrorResponse(response.error.message, response.error.type ?? "ERROR")), { retryAfter: response.retryAfter });
522
524
  return;
523
525
  }
524
526
  const interactionId = nextInteractionId();
@@ -545,6 +547,8 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
545
547
  if (!await writeGeminiInteractionsSSEStream(res, events, {
546
548
  latency,
547
549
  streamingProfile: fixture.streamingProfile,
550
+ recordedTimings: fixture.recordedTimings,
551
+ replaySpeed,
548
552
  signal: interruption?.signal,
549
553
  onChunkSent: interruption?.tick
550
554
  })) {
@@ -579,6 +583,8 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
579
583
  if (!await writeGeminiInteractionsSSEStream(res, events, {
580
584
  latency,
581
585
  streamingProfile: fixture.streamingProfile,
586
+ recordedTimings: fixture.recordedTimings,
587
+ replaySpeed,
582
588
  signal: interruption?.signal,
583
589
  onChunkSent: interruption?.tick
584
590
  })) {
@@ -612,6 +618,8 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
612
618
  if (!await writeGeminiInteractionsSSEStream(res, events, {
613
619
  latency,
614
620
  streamingProfile: fixture.streamingProfile,
621
+ recordedTimings: fixture.recordedTimings,
622
+ replaySpeed,
615
623
  signal: interruption?.signal,
616
624
  onChunkSent: interruption?.tick
617
625
  })) {
@@ -1 +1 @@
1
- {"version":3,"file":"gemini-interactions.cjs","names":["generateToolCallId","calculateDelay","delay","flattenHeaders","getTestId","matchFixture","applyChaos","resolveStrictMode","strictOverrideField","proxyAndRecord","resolveResponse","isErrorResponse","isContentWithToolCallsResponse","extractOverrides","createInterruptionSignal","isTextResponse","isToolCallResponse"],"sources":["../src/gemini-interactions.ts"],"sourcesContent":["/**\n * Google Gemini Interactions API support.\n *\n * Translates incoming Interactions requests into the ChatCompletionRequest\n * format used by the fixture router, and converts fixture responses back\n * into the Gemini Interactions format — either a single JSON response or\n * an SSE stream with event_type-based framing.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n ResponseOverrides,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n extractOverrides,\n generateToolCallId,\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// ─── Interactions request types ────────────────────────────────────────────\n\ninterface InteractionsContentBlock {\n type: string;\n text?: string;\n name?: string;\n call_id?: string;\n id?: string;\n arguments?: Record<string, unknown>;\n output?: unknown;\n result?: unknown;\n}\n\ninterface InteractionsTurn {\n role: string;\n content?: InteractionsContentBlock[];\n parts?: InteractionsContentBlock[];\n}\n\ninterface InteractionsFunctionTool {\n type: \"function\";\n name: string;\n description?: string;\n parameters?: object;\n}\n\ninterface InteractionsRequest {\n model?: string;\n input?: string | InteractionsTurn[] | InteractionsContentBlock[];\n system_instruction?: string;\n tools?: InteractionsFunctionTool[];\n generation_config?: {\n temperature?: number;\n max_output_tokens?: number;\n [key: string]: unknown;\n };\n stream?: boolean;\n previous_interaction_id?: string;\n [key: string]: unknown;\n}\n\n// ─── Input conversion: Interactions → ChatCompletionRequest ───────────────\n\nexport function geminiInteractionsToCompletionRequest(\n req: InteractionsRequest,\n): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n const model = req.model ?? \"gemini-2.5-flash\";\n\n // system_instruction → system message\n if (req.system_instruction) {\n messages.push({ role: \"system\", content: req.system_instruction });\n }\n\n // Parse input\n if (req.input !== undefined) {\n if (typeof req.input === \"string\") {\n // Simple string input → single user message\n messages.push({ role: \"user\", content: req.input });\n } else if (Array.isArray(req.input)) {\n // Could be Turn[] or Content[]\n const firstItem = req.input[0];\n if (firstItem && \"role\" in firstItem) {\n // Turn[] format\n for (const turn of req.input as InteractionsTurn[]) {\n const role = turn.role === \"model\" ? \"assistant\" : turn.role;\n const blocks = turn.content ?? turn.parts;\n if (!blocks || blocks.length === 0) {\n if (role === \"user\" || role === \"assistant\") {\n messages.push({ role: role as \"user\" | \"assistant\", content: \"\" });\n }\n continue;\n }\n\n // Check for function_call or function_result parts\n const funcCallParts = blocks.filter((p) => p.type === \"function_call\");\n const funcResultParts = blocks.filter((p) => p.type === \"function_result\");\n const textParts = blocks.filter((p) => p.type === \"text\");\n\n if (funcCallParts.length > 0) {\n // Assistant tool call message\n const textContent = textParts.map((p) => p.text ?? \"\").join(\"\");\n messages.push({\n role: \"assistant\",\n content: textContent || null,\n tool_calls: funcCallParts.map((p) => ({\n id: p.id ?? p.call_id ?? generateToolCallId(),\n type: \"function\" as const,\n function: {\n name: p.name ?? \"\",\n arguments: JSON.stringify(p.arguments ?? {}),\n },\n })),\n });\n } else if (funcResultParts.length > 0) {\n // Tool response messages\n for (const part of funcResultParts) {\n const resultValue = part.result ?? part.output;\n messages.push({\n role: \"tool\",\n content:\n typeof resultValue === \"string\" ? resultValue : JSON.stringify(resultValue ?? \"\"),\n tool_call_id: part.call_id ?? part.id ?? \"\",\n });\n }\n // Any text parts alongside → separate user message\n if (textParts.length > 0) {\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n if (text) {\n messages.push({ role: \"user\", content: text });\n }\n }\n } else {\n // Text-only turn\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n if (role === \"user\" || role === \"assistant\" || role === \"system\") {\n messages.push({\n role: role as \"user\" | \"assistant\" | \"system\",\n content: text,\n });\n }\n }\n }\n } else {\n // Content[] format — single user message with content blocks\n const textParts = (req.input as InteractionsContentBlock[]).filter(\n (p) => p.type === \"text\",\n );\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n messages.push({ role: \"user\", content: text || \"\" });\n }\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n const funcTools = req.tools.filter((t) => t.type === \"function\");\n if (funcTools.length > 0) {\n tools = funcTools.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n },\n }));\n }\n }\n\n return {\n model,\n messages,\n stream: req.stream !== false, // default true\n temperature: req.generation_config?.temperature,\n max_tokens: req.generation_config?.max_output_tokens,\n tools,\n };\n}\n\n// ─── Interaction ID generation ────────────────────────────────────────────\n\nlet interactionCounter = 0;\n\nexport function resetInteractionCounter(): void {\n interactionCounter = 0;\n}\n\nfunction nextInteractionId(): string {\n return `aimock-int-${interactionCounter++}`;\n}\n\n// ─── Usage helpers ────────────────────────────────────────────────────────\n\nfunction interactionsUsage(overrides?: ResponseOverrides): {\n total_input_tokens: number;\n total_output_tokens: number;\n total_tokens: number;\n} {\n if (!overrides?.usage) return { total_input_tokens: 0, total_output_tokens: 0, total_tokens: 0 };\n const input = overrides.usage.input_tokens ?? overrides.usage.prompt_tokens ?? 0;\n const output = overrides.usage.output_tokens ?? overrides.usage.completion_tokens ?? 0;\n return {\n total_input_tokens: input,\n total_output_tokens: output,\n total_tokens: input + output,\n };\n}\n\n// ─── Response building: fixture → Interactions format ─────────────────────\n\nexport function buildInteractionsTextResponse(\n content: string,\n model: string,\n interactionId: string,\n overrides?: ResponseOverrides,\n): object {\n return {\n id: interactionId,\n status: \"completed\",\n model: overrides?.model ?? model,\n role: \"model\",\n outputs: [{ type: \"text\", text: content }],\n usage: interactionsUsage(overrides),\n };\n}\n\nexport function buildInteractionsToolCallResponse(\n toolCalls: ToolCall[],\n model: string,\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): object {\n return {\n id: interactionId,\n status: \"requires_action\",\n model: overrides?.model ?? model,\n role: \"model\",\n outputs: toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n };\n }),\n usage: interactionsUsage(overrides),\n };\n}\n\nexport function buildInteractionsContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): object {\n const outputs: object[] = [{ type: \"text\", text: content }];\n for (const tc of toolCalls) {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n outputs.push({\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n });\n }\n\n return {\n id: interactionId,\n status: \"requires_action\",\n model: overrides?.model ?? model,\n role: \"model\",\n outputs,\n usage: interactionsUsage(overrides),\n };\n}\n\nfunction buildInteractionsErrorResponse(message: string, code?: string): object {\n return {\n error: {\n code: code ?? \"INVALID_ARGUMENT\",\n message,\n },\n };\n}\n\n// ─── SSE event builders ──────────────────────────────────────────────────\n\ninterface InteractionsSSEEvent {\n event_type: string;\n [key: string]: unknown;\n}\n\nlet eventIdCounter = 0;\n\nexport function resetEventIdCounter(): void {\n eventIdCounter = 0;\n}\n\nfunction nextEventId(): string {\n return `evt_${++eventIdCounter}`;\n}\n\nexport function buildInteractionsTextSSEEvents(\n content: string,\n interactionId: string,\n chunkSize: number,\n overrides?: ResponseOverrides,\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.start\n events.push({\n event_type: \"interaction.start\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n // content.start\n events.push({\n event_type: \"content.start\",\n index: 0,\n content: { type: \"text\" },\n event_id: nextEventId(),\n });\n\n // content.delta(s)\n if (content.length === 0) {\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: \"\" },\n event_id: nextEventId(),\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: slice },\n event_id: nextEventId(),\n });\n }\n }\n\n // content.stop\n events.push({\n event_type: \"content.stop\",\n index: 0,\n event_id: nextEventId(),\n });\n\n // interaction.complete\n events.push({\n event_type: \"interaction.complete\",\n interaction: {\n id: interactionId,\n status: \"completed\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\nexport function buildInteractionsToolCallSSEEvents(\n toolCalls: ToolCall[],\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.start\n events.push({\n event_type: \"interaction.start\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n // Each tool call gets its own content.start/delta/stop bracket\n for (let idx = 0; idx < toolCalls.length; idx++) {\n const tc = toolCalls[idx];\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n\n events.push({\n event_type: \"content.start\",\n index: idx,\n content: { type: \"function_call\" },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.delta\",\n index: idx,\n delta: {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.stop\",\n index: idx,\n event_id: nextEventId(),\n });\n }\n\n // interaction.complete\n events.push({\n event_type: \"interaction.complete\",\n interaction: {\n id: interactionId,\n status: \"requires_action\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\nexport function buildInteractionsContentWithToolCallsSSEEvents(\n content: string,\n toolCalls: ToolCall[],\n interactionId: string,\n chunkSize: number,\n logger: Logger,\n overrides?: ResponseOverrides,\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.start\n events.push({\n event_type: \"interaction.start\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n // Text content at index 0\n events.push({\n event_type: \"content.start\",\n index: 0,\n content: { type: \"text\" },\n event_id: nextEventId(),\n });\n\n if (content.length === 0) {\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: \"\" },\n event_id: nextEventId(),\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: slice },\n event_id: nextEventId(),\n });\n }\n }\n\n events.push({\n event_type: \"content.stop\",\n index: 0,\n event_id: nextEventId(),\n });\n\n // Tool calls at index 1+\n for (let i = 0; i < toolCalls.length; i++) {\n const tc = toolCalls[i];\n const idx = i + 1; // offset by 1 because text is index 0\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n\n events.push({\n event_type: \"content.start\",\n index: idx,\n content: { type: \"function_call\" },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.delta\",\n index: idx,\n delta: {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.stop\",\n index: idx,\n event_id: nextEventId(),\n });\n }\n\n // interaction.complete\n events.push({\n event_type: \"interaction.complete\",\n interaction: {\n id: interactionId,\n status: \"requires_action\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\n// ─── SSE writer for Interactions streaming ────────────────────────────────\n\ninterface InteractionsStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nexport async function writeGeminiInteractionsSSEStream(\n res: http.ServerResponse,\n events: InteractionsSSEEvent[],\n optionsOrLatency?: number | InteractionsStreamOptions,\n): Promise<boolean> {\n const opts: InteractionsStreamOptions =\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 event of events) {\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 // Data-only SSE (no event: prefix, no [DONE])\n res.write(`data: ${JSON.stringify(event)}\\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 handleGeminiInteractions(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n const urlPath = req.url ?? \"/v1beta/interactions\";\n\n let interactionsReq: InteractionsRequest;\n try {\n interactionsReq = JSON.parse(raw) as InteractionsRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\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 buildInteractionsErrorResponse(`Malformed JSON body: ${detail}`, \"INVALID_ARGUMENT\"),\n ),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = geminiInteractionsToCompletionRequest(interactionsReq);\n completionReq._endpointType = \"chat\";\n\n const streaming = interactionsReq.stream !== false; // default true\n const model = completionReq.model;\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\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: urlPath,\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 const strictStatus = 503;\n const strictMessage = \"Strict mode: no fixture matched\";\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${urlPath}`);\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: strictStatus,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify(buildInteractionsErrorResponse(strictMessage, \"UNAVAILABLE\")),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"gemini-interactions\",\n urlPath,\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: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: res.statusCode ?? 200,\n fixture: null,\n source: \"proxy\",\n },\n });\n return;\n }\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\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(buildInteractionsErrorResponse(\"No fixture matched\", \"NOT_FOUND\")),\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: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n writeErrorResponse(\n res,\n status,\n JSON.stringify(\n buildInteractionsErrorResponse(response.error.message, response.error.type ?? \"ERROR\"),\n ),\n );\n return;\n }\n\n const interactionId = nextInteractionId();\n\n // Content + tool calls response\n if (isContentWithToolCallsResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\n \"webSearches in fixture response are not supported for Gemini Interactions API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildInteractionsContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n model,\n interactionId,\n logger,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsContentWithToolCallsSSEEvents(\n response.content,\n response.toolCalls,\n interactionId,\n chunkSize,\n logger,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\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(\n \"webSearches in fixture response are not supported for Gemini Interactions API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildInteractionsTextResponse(response.content, model, interactionId, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsTextSSEEvents(\n response.content,\n interactionId,\n chunkSize,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\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: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildInteractionsToolCallResponse(\n response.toolCalls,\n model,\n interactionId,\n logger,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsToolCallSSEEvents(\n response.toolCalls,\n interactionId,\n logger,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\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: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify(\n buildInteractionsErrorResponse(\"Fixture response did not match any known type\", \"INTERNAL\"),\n ),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"gemini-interactions.cjs","names":["generateToolCallId","calculateDelay","delay","flattenHeaders","getTestId","matchFixture","applyChaos","resolveStrictMode","strictOverrideField","proxyAndRecord","resolveResponse","isErrorResponse","isContentWithToolCallsResponse","extractOverrides","createInterruptionSignal","isTextResponse","isToolCallResponse"],"sources":["../src/gemini-interactions.ts"],"sourcesContent":["/**\n * Google Gemini Interactions API support.\n *\n * Translates incoming Interactions requests into the ChatCompletionRequest\n * format used by the fixture router, and converts fixture responses back\n * into the Gemini Interactions format — either a single JSON response or\n * an SSE stream with event_type-based framing.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n RecordedTimings,\n ResponseOverrides,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n extractOverrides,\n generateToolCallId,\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// ─── Interactions request types ────────────────────────────────────────────\n\ninterface InteractionsContentBlock {\n type: string;\n text?: string;\n name?: string;\n call_id?: string;\n id?: string;\n arguments?: Record<string, unknown>;\n output?: unknown;\n result?: unknown;\n}\n\ninterface InteractionsTurn {\n role: string;\n content?: InteractionsContentBlock[];\n parts?: InteractionsContentBlock[];\n}\n\ninterface InteractionsFunctionTool {\n type: \"function\";\n name: string;\n description?: string;\n parameters?: object;\n}\n\ninterface InteractionsRequest {\n model?: string;\n input?: string | InteractionsTurn[] | InteractionsContentBlock[];\n system_instruction?: string;\n tools?: InteractionsFunctionTool[];\n generation_config?: {\n temperature?: number;\n max_output_tokens?: number;\n [key: string]: unknown;\n };\n stream?: boolean;\n previous_interaction_id?: string;\n [key: string]: unknown;\n}\n\n// ─── Input conversion: Interactions → ChatCompletionRequest ───────────────\n\nexport function geminiInteractionsToCompletionRequest(\n req: InteractionsRequest,\n): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n const model = req.model ?? \"gemini-2.5-flash\";\n\n // system_instruction → system message\n if (req.system_instruction) {\n messages.push({ role: \"system\", content: req.system_instruction });\n }\n\n // Parse input\n if (req.input !== undefined) {\n if (typeof req.input === \"string\") {\n // Simple string input → single user message\n messages.push({ role: \"user\", content: req.input });\n } else if (Array.isArray(req.input)) {\n // Could be Turn[] or Content[]\n const firstItem = req.input[0];\n if (firstItem && \"role\" in firstItem) {\n // Turn[] format\n for (const turn of req.input as InteractionsTurn[]) {\n const role = turn.role === \"model\" ? \"assistant\" : turn.role;\n const blocks = turn.content ?? turn.parts;\n if (!blocks || blocks.length === 0) {\n if (role === \"user\" || role === \"assistant\") {\n messages.push({ role: role as \"user\" | \"assistant\", content: \"\" });\n }\n continue;\n }\n\n // Check for function_call or function_result parts\n const funcCallParts = blocks.filter((p) => p.type === \"function_call\");\n const funcResultParts = blocks.filter((p) => p.type === \"function_result\");\n const textParts = blocks.filter((p) => p.type === \"text\");\n\n if (funcCallParts.length > 0) {\n // Assistant tool call message\n const textContent = textParts.map((p) => p.text ?? \"\").join(\"\");\n messages.push({\n role: \"assistant\",\n content: textContent || null,\n tool_calls: funcCallParts.map((p) => ({\n id: p.id ?? p.call_id ?? generateToolCallId(),\n type: \"function\" as const,\n function: {\n name: p.name ?? \"\",\n arguments: JSON.stringify(p.arguments ?? {}),\n },\n })),\n });\n } else if (funcResultParts.length > 0) {\n // Tool response messages\n for (const part of funcResultParts) {\n const resultValue = part.result ?? part.output;\n messages.push({\n role: \"tool\",\n content:\n typeof resultValue === \"string\" ? resultValue : JSON.stringify(resultValue ?? \"\"),\n tool_call_id: part.call_id ?? part.id ?? \"\",\n });\n }\n // Any text parts alongside → separate user message\n if (textParts.length > 0) {\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n if (text) {\n messages.push({ role: \"user\", content: text });\n }\n }\n } else {\n // Text-only turn\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n if (role === \"user\" || role === \"assistant\" || role === \"system\") {\n messages.push({\n role: role as \"user\" | \"assistant\" | \"system\",\n content: text,\n });\n }\n }\n }\n } else {\n // Content[] format — single user message with content blocks\n const textParts = (req.input as InteractionsContentBlock[]).filter(\n (p) => p.type === \"text\",\n );\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n messages.push({ role: \"user\", content: text || \"\" });\n }\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n const funcTools = req.tools.filter((t) => t.type === \"function\");\n if (funcTools.length > 0) {\n tools = funcTools.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n },\n }));\n }\n }\n\n return {\n model,\n messages,\n stream: req.stream !== false, // default true\n temperature: req.generation_config?.temperature,\n max_tokens: req.generation_config?.max_output_tokens,\n tools,\n };\n}\n\n// ─── Interaction ID generation ────────────────────────────────────────────\n\nlet interactionCounter = 0;\n\nexport function resetInteractionCounter(): void {\n interactionCounter = 0;\n}\n\nfunction nextInteractionId(): string {\n return `aimock-int-${interactionCounter++}`;\n}\n\n// ─── Usage helpers ────────────────────────────────────────────────────────\n\nfunction interactionsUsage(overrides?: ResponseOverrides): {\n total_input_tokens: number;\n total_output_tokens: number;\n total_tokens: number;\n} {\n if (!overrides?.usage) return { total_input_tokens: 0, total_output_tokens: 0, total_tokens: 0 };\n const input = overrides.usage.input_tokens ?? overrides.usage.prompt_tokens ?? 0;\n const output = overrides.usage.output_tokens ?? overrides.usage.completion_tokens ?? 0;\n return {\n total_input_tokens: input,\n total_output_tokens: output,\n total_tokens: input + output,\n };\n}\n\n// ─── Response building: fixture → Interactions format ─────────────────────\n\nexport function buildInteractionsTextResponse(\n content: string,\n model: string,\n interactionId: string,\n overrides?: ResponseOverrides,\n): object {\n return {\n id: interactionId,\n status: \"completed\",\n model: overrides?.model ?? model,\n role: \"model\",\n outputs: [{ type: \"text\", text: content }],\n usage: interactionsUsage(overrides),\n };\n}\n\nexport function buildInteractionsToolCallResponse(\n toolCalls: ToolCall[],\n model: string,\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): object {\n return {\n id: interactionId,\n status: \"requires_action\",\n model: overrides?.model ?? model,\n role: \"model\",\n outputs: toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n };\n }),\n usage: interactionsUsage(overrides),\n };\n}\n\nexport function buildInteractionsContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): object {\n const outputs: object[] = [{ type: \"text\", text: content }];\n for (const tc of toolCalls) {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n outputs.push({\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n });\n }\n\n return {\n id: interactionId,\n status: \"requires_action\",\n model: overrides?.model ?? model,\n role: \"model\",\n outputs,\n usage: interactionsUsage(overrides),\n };\n}\n\nfunction buildInteractionsErrorResponse(message: string, code?: string): object {\n return {\n error: {\n code: code ?? \"INVALID_ARGUMENT\",\n message,\n },\n };\n}\n\n// ─── SSE event builders ──────────────────────────────────────────────────\n\ninterface InteractionsSSEEvent {\n event_type: string;\n [key: string]: unknown;\n}\n\nlet eventIdCounter = 0;\n\nexport function resetEventIdCounter(): void {\n eventIdCounter = 0;\n}\n\nfunction nextEventId(): string {\n return `evt_${++eventIdCounter}`;\n}\n\nexport function buildInteractionsTextSSEEvents(\n content: string,\n interactionId: string,\n chunkSize: number,\n overrides?: ResponseOverrides,\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.start\n events.push({\n event_type: \"interaction.start\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n // content.start\n events.push({\n event_type: \"content.start\",\n index: 0,\n content: { type: \"text\" },\n event_id: nextEventId(),\n });\n\n // content.delta(s)\n if (content.length === 0) {\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: \"\" },\n event_id: nextEventId(),\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: slice },\n event_id: nextEventId(),\n });\n }\n }\n\n // content.stop\n events.push({\n event_type: \"content.stop\",\n index: 0,\n event_id: nextEventId(),\n });\n\n // interaction.complete\n events.push({\n event_type: \"interaction.complete\",\n interaction: {\n id: interactionId,\n status: \"completed\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\nexport function buildInteractionsToolCallSSEEvents(\n toolCalls: ToolCall[],\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.start\n events.push({\n event_type: \"interaction.start\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n // Each tool call gets its own content.start/delta/stop bracket\n for (let idx = 0; idx < toolCalls.length; idx++) {\n const tc = toolCalls[idx];\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n\n events.push({\n event_type: \"content.start\",\n index: idx,\n content: { type: \"function_call\" },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.delta\",\n index: idx,\n delta: {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.stop\",\n index: idx,\n event_id: nextEventId(),\n });\n }\n\n // interaction.complete\n events.push({\n event_type: \"interaction.complete\",\n interaction: {\n id: interactionId,\n status: \"requires_action\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\nexport function buildInteractionsContentWithToolCallsSSEEvents(\n content: string,\n toolCalls: ToolCall[],\n interactionId: string,\n chunkSize: number,\n logger: Logger,\n overrides?: ResponseOverrides,\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.start\n events.push({\n event_type: \"interaction.start\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n // Text content at index 0\n events.push({\n event_type: \"content.start\",\n index: 0,\n content: { type: \"text\" },\n event_id: nextEventId(),\n });\n\n if (content.length === 0) {\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: \"\" },\n event_id: nextEventId(),\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: slice },\n event_id: nextEventId(),\n });\n }\n }\n\n events.push({\n event_type: \"content.stop\",\n index: 0,\n event_id: nextEventId(),\n });\n\n // Tool calls at index 1+\n for (let i = 0; i < toolCalls.length; i++) {\n const tc = toolCalls[i];\n const idx = i + 1; // offset by 1 because text is index 0\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n\n events.push({\n event_type: \"content.start\",\n index: idx,\n content: { type: \"function_call\" },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.delta\",\n index: idx,\n delta: {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.stop\",\n index: idx,\n event_id: nextEventId(),\n });\n }\n\n // interaction.complete\n events.push({\n event_type: \"interaction.complete\",\n interaction: {\n id: interactionId,\n status: \"requires_action\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\n// ─── SSE writer for Interactions streaming ────────────────────────────────\n\ninterface InteractionsStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nexport async function writeGeminiInteractionsSSEStream(\n res: http.ServerResponse,\n events: InteractionsSSEEvent[],\n optionsOrLatency?: number | InteractionsStreamOptions,\n): Promise<boolean> {\n const opts: InteractionsStreamOptions =\n typeof optionsOrLatency === \"number\" ? { latency: optionsOrLatency } : (optionsOrLatency ?? {});\n const latency = opts.latency ?? 0;\n const profile = opts.streamingProfile;\n const { recordedTimings, replaySpeed } = opts;\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 event of events) {\n const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);\n if (chunkDelay > 0) await delay(chunkDelay, signal);\n if (signal?.aborted) return false;\n if (res.writableEnded) return true;\n // Data-only SSE (no event: prefix, no [DONE])\n res.write(`data: ${JSON.stringify(event)}\\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 handleGeminiInteractions(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n const urlPath = req.url ?? \"/v1beta/interactions\";\n\n let interactionsReq: InteractionsRequest;\n try {\n interactionsReq = JSON.parse(raw) as InteractionsRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\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 buildInteractionsErrorResponse(`Malformed JSON body: ${detail}`, \"INVALID_ARGUMENT\"),\n ),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = geminiInteractionsToCompletionRequest(interactionsReq);\n completionReq._endpointType = \"chat\";\n\n const streaming = interactionsReq.stream !== false; // default true\n const model = completionReq.model;\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\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: urlPath,\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 const strictStatus = 503;\n const strictMessage = \"Strict mode: no fixture matched\";\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${urlPath}`);\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: strictStatus,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify(buildInteractionsErrorResponse(strictMessage, \"UNAVAILABLE\")),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"gemini-interactions\",\n urlPath,\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: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: res.statusCode ?? 200,\n fixture: null,\n source: \"proxy\",\n },\n });\n return;\n }\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\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(buildInteractionsErrorResponse(\"No fixture matched\", \"NOT_FOUND\")),\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 const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n writeErrorResponse(\n res,\n status,\n JSON.stringify(\n buildInteractionsErrorResponse(response.error.message, response.error.type ?? \"ERROR\"),\n ),\n { retryAfter: response.retryAfter },\n );\n return;\n }\n\n const interactionId = nextInteractionId();\n\n // Content + tool calls response\n if (isContentWithToolCallsResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\n \"webSearches in fixture response are not supported for Gemini Interactions API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildInteractionsContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n model,\n interactionId,\n logger,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsContentWithToolCallsSSEEvents(\n response.content,\n response.toolCalls,\n interactionId,\n chunkSize,\n logger,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed,\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(\n \"webSearches in fixture response are not supported for Gemini Interactions API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildInteractionsTextResponse(response.content, model, interactionId, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsTextSSEEvents(\n response.content,\n interactionId,\n chunkSize,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed,\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: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildInteractionsToolCallResponse(\n response.toolCalls,\n model,\n interactionId,\n logger,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsToolCallSSEEvents(\n response.toolCalls,\n interactionId,\n logger,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed,\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: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify(\n buildInteractionsErrorResponse(\"Fixture response did not match any known type\", \"INTERNAL\"),\n ),\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"gemini-interactions.d.cts","names":[],"sources":["../src/gemini-interactions.ts"],"sourcesContent":[],"mappings":";;;;;;AA4DkC,UAjBxB,wBAAA,CAwBmB;EAAA,IAAA,EAAA,MAAA;MAEV,CAAA,EAAA,MAAA;MAAqB,CAAA,EAAA,MAAA;SAE9B,CAAA,EAAA,MAAA;EAAwB,EAAA,CAAA,EAAA,MAAA;EAalB,SAAA,CAAA,EAnCF,MAmCE,CAAA,MAAA,EAAA,OAAA,CAAA;EAAqC,MAAA,CAAA,EAAA,OAAA;QAC9C,CAAA,EAAA,OAAA;;UA/BG,gBAAA,CAgCc;EA6hBF,IAAA,EAAA,MAAA;EAAwB,OAAA,CAAA,EA3jBlC,wBA2jBkC,EAAA;OACvC,CAAA,EA3jBG,wBA2jBE,EAAA;;UAxjBF,wBAAA,CA2jBE;MACD,EAAA,UAAA;MACC,EAAA,MAAA;aACiB,CAAA,EAAA,MAAA;YAC1B,CAAA,EAAA,MAAA;;UAxjBO,mBAAA;;mBAES,qBAAqB;;UAE9B;;;;;;;;;;iBAaM,qCAAA,MACT,sBACJ;iBA6hBmB,wBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B"}
1
+ {"version":3,"file":"gemini-interactions.d.cts","names":[],"sources":["../src/gemini-interactions.ts"],"sourcesContent":[],"mappings":";;;;;;AA6DkC,UAjBxB,wBAAA,CAwBmB;EAAA,IAAA,EAAA,MAAA;MAEV,CAAA,EAAA,MAAA;MAAqB,CAAA,EAAA,MAAA;SAE9B,CAAA,EAAA,MAAA;EAAwB,EAAA,CAAA,EAAA,MAAA;EAalB,SAAA,CAAA,EAnCF,MAmCE,CAAA,MAAA,EAAA,OAAA,CAAA;EAAqC,MAAA,CAAA,EAAA,OAAA;QAC9C,CAAA,EAAA,OAAA;;UA/BG,gBAAA,CAgCc;EAgiBF,IAAA,EAAA,MAAA;EAAwB,OAAA,CAAA,EA9jBlC,wBA8jBkC,EAAA;OACvC,CAAA,EA9jBG,wBA8jBE,EAAA;;UA3jBF,wBAAA,CA8jBE;MACD,EAAA,UAAA;MACC,EAAA,MAAA;aACiB,CAAA,EAAA,MAAA;YAC1B,CAAA,EAAA,MAAA;;UA3jBO,mBAAA;;mBAES,qBAAqB;;UAE9B;;;;;;;;;;iBAaM,qCAAA,MACT,sBACJ;iBAgiBmB,wBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B"}
@@ -1 +1 @@
1
- {"version":3,"file":"gemini-interactions.d.ts","names":[],"sources":["../src/gemini-interactions.ts"],"sourcesContent":[],"mappings":";;;;;;AA4DkC,UAjBxB,wBAAA,CAwBmB;EAAA,IAAA,EAAA,MAAA;MAEV,CAAA,EAAA,MAAA;MAAqB,CAAA,EAAA,MAAA;SAE9B,CAAA,EAAA,MAAA;EAAwB,EAAA,CAAA,EAAA,MAAA;EAalB,SAAA,CAAA,EAnCF,MAmCE,CAAA,MAAA,EAAA,OAAA,CAAA;EAAqC,MAAA,CAAA,EAAA,OAAA;QAC9C,CAAA,EAAA,OAAA;;UA/BG,gBAAA,CAgCc;EA6hBF,IAAA,EAAA,MAAA;EAAwB,OAAA,CAAA,EA3jBlC,wBA2jBkC,EAAA;OACvC,CAAA,EA3jBG,wBA2jBE,EAAA;;UAxjBF,wBAAA,CA2jBE;MACD,EAAA,UAAA;MACC,EAAA,MAAA;aACiB,CAAA,EAAA,MAAA;YAC1B,CAAA,EAAA,MAAA;;UAxjBO,mBAAA;;mBAES,qBAAqB;;UAE9B;;;;;;;;;;iBAaM,qCAAA,MACT,sBACJ;iBA6hBmB,wBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B"}
1
+ {"version":3,"file":"gemini-interactions.d.ts","names":[],"sources":["../src/gemini-interactions.ts"],"sourcesContent":[],"mappings":";;;;;;AA6DkC,UAjBxB,wBAAA,CAwBmB;EAAA,IAAA,EAAA,MAAA;MAEV,CAAA,EAAA,MAAA;MAAqB,CAAA,EAAA,MAAA;SAE9B,CAAA,EAAA,MAAA;EAAwB,EAAA,CAAA,EAAA,MAAA;EAalB,SAAA,CAAA,EAnCF,MAmCE,CAAA,MAAA,EAAA,OAAA,CAAA;EAAqC,MAAA,CAAA,EAAA,OAAA;QAC9C,CAAA,EAAA,OAAA;;UA/BG,gBAAA,CAgCc;EAgiBF,IAAA,EAAA,MAAA;EAAwB,OAAA,CAAA,EA9jBlC,wBA8jBkC,EAAA;OACvC,CAAA,EA9jBG,wBA8jBE,EAAA;;UA3jBF,wBAAA,CA8jBE;MACD,EAAA,UAAA;MACC,EAAA,MAAA;aACiB,CAAA,EAAA,MAAA;YAC1B,CAAA,EAAA,MAAA;;UA3jBO,mBAAA;;mBAES,qBAAqB;;UAE9B;;;;;;;;;;iBAaM,qCAAA,MACT,sBACJ;iBAgiBmB,wBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B"}
@@ -397,6 +397,7 @@ async function writeGeminiInteractionsSSEStream(res, events, optionsOrLatency) {
397
397
  const opts = typeof optionsOrLatency === "number" ? { latency: optionsOrLatency } : optionsOrLatency ?? {};
398
398
  const latency = opts.latency ?? 0;
399
399
  const profile = opts.streamingProfile;
400
+ const { recordedTimings, replaySpeed } = opts;
400
401
  const signal = opts.signal;
401
402
  const onChunkSent = opts.onChunkSent;
402
403
  if (res.writableEnded) return true;
@@ -405,7 +406,7 @@ async function writeGeminiInteractionsSSEStream(res, events, optionsOrLatency) {
405
406
  res.setHeader("Connection", "keep-alive");
406
407
  let chunkIndex = 0;
407
408
  for (const event of events) {
408
- const chunkDelay = calculateDelay(chunkIndex, profile, latency);
409
+ const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);
409
410
  if (chunkDelay > 0) await delay(chunkDelay, signal);
410
411
  if (signal?.aborted) return false;
411
412
  if (res.writableEnded) return true;
@@ -506,6 +507,7 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
506
507
  const response = await resolveResponse(fixture, completionReq);
507
508
  const latency = fixture.latency ?? defaults.latency;
508
509
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
510
+ const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
509
511
  if (isErrorResponse(response)) {
510
512
  const status = response.status ?? 500;
511
513
  journal.add({
@@ -518,7 +520,7 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
518
520
  fixture
519
521
  }
520
522
  });
521
- writeErrorResponse(res, status, JSON.stringify(buildInteractionsErrorResponse(response.error.message, response.error.type ?? "ERROR")));
523
+ writeErrorResponse(res, status, JSON.stringify(buildInteractionsErrorResponse(response.error.message, response.error.type ?? "ERROR")), { retryAfter: response.retryAfter });
522
524
  return;
523
525
  }
524
526
  const interactionId = nextInteractionId();
@@ -545,6 +547,8 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
545
547
  if (!await writeGeminiInteractionsSSEStream(res, events, {
546
548
  latency,
547
549
  streamingProfile: fixture.streamingProfile,
550
+ recordedTimings: fixture.recordedTimings,
551
+ replaySpeed,
548
552
  signal: interruption?.signal,
549
553
  onChunkSent: interruption?.tick
550
554
  })) {
@@ -579,6 +583,8 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
579
583
  if (!await writeGeminiInteractionsSSEStream(res, events, {
580
584
  latency,
581
585
  streamingProfile: fixture.streamingProfile,
586
+ recordedTimings: fixture.recordedTimings,
587
+ replaySpeed,
582
588
  signal: interruption?.signal,
583
589
  onChunkSent: interruption?.tick
584
590
  })) {
@@ -612,6 +618,8 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
612
618
  if (!await writeGeminiInteractionsSSEStream(res, events, {
613
619
  latency,
614
620
  streamingProfile: fixture.streamingProfile,
621
+ recordedTimings: fixture.recordedTimings,
622
+ replaySpeed,
615
623
  signal: interruption?.signal,
616
624
  onChunkSent: interruption?.tick
617
625
  })) {