@copilotkit/aimock 1.16.4 → 1.17.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 (269) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +24 -0
  4. package/README.md +10 -10
  5. package/dist/a2a-mock.d.cts +2 -2
  6. package/dist/a2a-mock.d.cts.map +1 -1
  7. package/dist/a2a-mock.d.ts +2 -2
  8. package/dist/a2a-mock.d.ts.map +1 -1
  9. package/dist/a2a-mock.js +2 -2
  10. package/dist/a2a-mock.js.map +1 -1
  11. package/dist/agui-handler.cjs +120 -5
  12. package/dist/agui-handler.cjs.map +1 -1
  13. package/dist/agui-handler.d.cts +41 -5
  14. package/dist/agui-handler.d.cts.map +1 -1
  15. package/dist/agui-handler.d.ts +41 -5
  16. package/dist/agui-handler.d.ts.map +1 -1
  17. package/dist/agui-handler.js +114 -6
  18. package/dist/agui-handler.js.map +1 -1
  19. package/dist/agui-mock.cjs +18 -7
  20. package/dist/agui-mock.cjs.map +1 -1
  21. package/dist/agui-mock.d.cts +2 -2
  22. package/dist/agui-mock.d.cts.map +1 -1
  23. package/dist/agui-mock.d.ts +2 -2
  24. package/dist/agui-mock.d.ts.map +1 -1
  25. package/dist/agui-mock.js +20 -9
  26. package/dist/agui-mock.js.map +1 -1
  27. package/dist/agui-recorder.cjs +43 -22
  28. package/dist/agui-recorder.cjs.map +1 -1
  29. package/dist/agui-recorder.d.cts +4 -3
  30. package/dist/agui-recorder.d.cts.map +1 -1
  31. package/dist/agui-recorder.d.ts +4 -3
  32. package/dist/agui-recorder.d.ts.map +1 -1
  33. package/dist/agui-recorder.js +45 -24
  34. package/dist/agui-recorder.js.map +1 -1
  35. package/dist/agui-stub.cjs +28 -0
  36. package/dist/agui-stub.d.cts +5 -0
  37. package/dist/agui-stub.d.ts +5 -0
  38. package/dist/agui-stub.js +5 -0
  39. package/dist/agui-types.d.cts +33 -6
  40. package/dist/agui-types.d.cts.map +1 -1
  41. package/dist/agui-types.d.ts +33 -6
  42. package/dist/agui-types.d.ts.map +1 -1
  43. package/dist/aimock-cli.cjs +1 -1
  44. package/dist/aimock-cli.js +1 -1
  45. package/dist/aws-event-stream.d.cts +2 -2
  46. package/dist/aws-event-stream.d.cts.map +1 -1
  47. package/dist/aws-event-stream.d.ts +2 -2
  48. package/dist/aws-event-stream.d.ts.map +1 -1
  49. package/dist/bedrock-converse.d.cts +3 -3
  50. package/dist/bedrock-converse.d.cts.map +1 -1
  51. package/dist/bedrock-converse.d.ts +3 -3
  52. package/dist/bedrock-converse.d.ts.map +1 -1
  53. package/dist/bedrock.d.cts +3 -3
  54. package/dist/bedrock.d.cts.map +1 -1
  55. package/dist/bedrock.d.ts +3 -3
  56. package/dist/bedrock.d.ts.map +1 -1
  57. package/dist/chaos.d.cts +3 -3
  58. package/dist/chaos.d.cts.map +1 -1
  59. package/dist/chaos.d.ts +3 -3
  60. package/dist/chaos.d.ts.map +1 -1
  61. package/dist/cli.cjs +6 -5
  62. package/dist/cli.cjs.map +1 -1
  63. package/dist/cli.js +6 -5
  64. package/dist/cli.js.map +1 -1
  65. package/dist/cohere.d.cts +2 -2
  66. package/dist/cohere.d.cts.map +1 -1
  67. package/dist/cohere.d.ts +2 -2
  68. package/dist/cohere.d.ts.map +1 -1
  69. package/dist/config-loader.cjs +3 -3
  70. package/dist/config-loader.d.cts +1 -1
  71. package/dist/config-loader.d.cts.map +1 -1
  72. package/dist/config-loader.d.ts +1 -1
  73. package/dist/config-loader.d.ts.map +1 -1
  74. package/dist/config-loader.js +2 -2
  75. package/dist/convert-vidaimock.cjs +1 -1
  76. package/dist/convert-vidaimock.js +1 -1
  77. package/dist/convert.cjs +1 -1
  78. package/dist/convert.js +1 -1
  79. package/dist/elevenlabs-audio.cjs +209 -0
  80. package/dist/elevenlabs-audio.cjs.map +1 -0
  81. package/dist/elevenlabs-audio.d.cts +11 -0
  82. package/dist/elevenlabs-audio.d.cts.map +1 -0
  83. package/dist/elevenlabs-audio.d.ts +11 -0
  84. package/dist/elevenlabs-audio.d.ts.map +1 -0
  85. package/dist/elevenlabs-audio.js +209 -0
  86. package/dist/elevenlabs-audio.js.map +1 -0
  87. package/dist/embeddings.d.cts +2 -2
  88. package/dist/embeddings.d.cts.map +1 -1
  89. package/dist/embeddings.d.ts +2 -2
  90. package/dist/embeddings.d.ts.map +1 -1
  91. package/dist/fal-audio.cjs +477 -0
  92. package/dist/fal-audio.cjs.map +1 -0
  93. package/dist/fal-audio.d.cts +10 -0
  94. package/dist/fal-audio.d.cts.map +1 -0
  95. package/dist/fal-audio.d.ts +10 -0
  96. package/dist/fal-audio.d.ts.map +1 -0
  97. package/dist/fal-audio.js +474 -0
  98. package/dist/fal-audio.js.map +1 -0
  99. package/dist/fixture-loader.cjs +14 -1
  100. package/dist/fixture-loader.cjs.map +1 -1
  101. package/dist/fixture-loader.js +14 -1
  102. package/dist/fixture-loader.js.map +1 -1
  103. package/dist/fixtures-remote.cjs +1 -1
  104. package/dist/fixtures-remote.js +1 -1
  105. package/dist/gemini-interactions.cjs +617 -0
  106. package/dist/gemini-interactions.cjs.map +1 -0
  107. package/dist/gemini-interactions.d.cts +46 -0
  108. package/dist/gemini-interactions.d.cts.map +1 -0
  109. package/dist/gemini-interactions.d.ts +46 -0
  110. package/dist/gemini-interactions.d.ts.map +1 -0
  111. package/dist/gemini-interactions.js +616 -0
  112. package/dist/gemini-interactions.js.map +1 -0
  113. package/dist/gemini.cjs +76 -0
  114. package/dist/gemini.cjs.map +1 -1
  115. package/dist/gemini.d.cts +2 -2
  116. package/dist/gemini.d.cts.map +1 -1
  117. package/dist/gemini.d.ts +2 -2
  118. package/dist/gemini.d.ts.map +1 -1
  119. package/dist/gemini.js +77 -1
  120. package/dist/gemini.js.map +1 -1
  121. package/dist/helpers.cjs +24 -1
  122. package/dist/helpers.cjs.map +1 -1
  123. package/dist/helpers.d.cts +13 -3
  124. package/dist/helpers.d.cts.map +1 -1
  125. package/dist/helpers.d.ts +13 -3
  126. package/dist/helpers.d.ts.map +1 -1
  127. package/dist/helpers.js +23 -2
  128. package/dist/helpers.js.map +1 -1
  129. package/dist/images.d.cts +2 -2
  130. package/dist/images.d.cts.map +1 -1
  131. package/dist/images.d.ts +2 -2
  132. package/dist/images.d.ts.map +1 -1
  133. package/dist/index.cjs +21 -4
  134. package/dist/index.d.cts +10 -7
  135. package/dist/index.d.ts +10 -7
  136. package/dist/index.js +10 -7
  137. package/dist/jest.cjs +1 -1
  138. package/dist/jest.js +1 -1
  139. package/dist/jsonrpc.d.cts +3 -3
  140. package/dist/jsonrpc.d.cts.map +1 -1
  141. package/dist/jsonrpc.d.ts +3 -3
  142. package/dist/jsonrpc.d.ts.map +1 -1
  143. package/dist/llmock.cjs +38 -2
  144. package/dist/llmock.cjs.map +1 -1
  145. package/dist/llmock.d.cts +4 -0
  146. package/dist/llmock.d.cts.map +1 -1
  147. package/dist/llmock.d.ts +4 -0
  148. package/dist/llmock.d.ts.map +1 -1
  149. package/dist/llmock.js +38 -2
  150. package/dist/llmock.js.map +1 -1
  151. package/dist/logger.cjs +5 -4
  152. package/dist/logger.cjs.map +1 -1
  153. package/dist/logger.d.cts +1 -1
  154. package/dist/logger.d.cts.map +1 -1
  155. package/dist/logger.d.ts +1 -1
  156. package/dist/logger.d.ts.map +1 -1
  157. package/dist/logger.js +5 -4
  158. package/dist/logger.js.map +1 -1
  159. package/dist/mcp-mock.d.cts +2 -2
  160. package/dist/mcp-mock.d.cts.map +1 -1
  161. package/dist/mcp-mock.d.ts +2 -2
  162. package/dist/mcp-mock.d.ts.map +1 -1
  163. package/dist/mcp-mock.js +2 -2
  164. package/dist/mcp-mock.js.map +1 -1
  165. package/dist/messages.d.cts +2 -2
  166. package/dist/messages.d.cts.map +1 -1
  167. package/dist/messages.d.ts +2 -2
  168. package/dist/messages.d.ts.map +1 -1
  169. package/dist/moderation.d.cts +3 -3
  170. package/dist/moderation.d.cts.map +1 -1
  171. package/dist/moderation.d.ts +3 -3
  172. package/dist/moderation.d.ts.map +1 -1
  173. package/dist/ndjson-writer.d.cts +2 -2
  174. package/dist/ndjson-writer.d.cts.map +1 -1
  175. package/dist/ndjson-writer.d.ts +2 -2
  176. package/dist/ndjson-writer.d.ts.map +1 -1
  177. package/dist/ollama.d.cts +3 -3
  178. package/dist/ollama.d.cts.map +1 -1
  179. package/dist/ollama.d.ts +3 -3
  180. package/dist/ollama.d.ts.map +1 -1
  181. package/dist/recorder.cjs +64 -21
  182. package/dist/recorder.cjs.map +1 -1
  183. package/dist/recorder.d.cts +2 -2
  184. package/dist/recorder.d.cts.map +1 -1
  185. package/dist/recorder.d.ts +2 -2
  186. package/dist/recorder.d.ts.map +1 -1
  187. package/dist/recorder.js +66 -23
  188. package/dist/recorder.js.map +1 -1
  189. package/dist/rerank.d.cts +3 -3
  190. package/dist/rerank.d.cts.map +1 -1
  191. package/dist/rerank.d.ts +3 -3
  192. package/dist/rerank.d.ts.map +1 -1
  193. package/dist/responses.d.cts +2 -2
  194. package/dist/responses.d.cts.map +1 -1
  195. package/dist/responses.d.ts +2 -2
  196. package/dist/responses.d.ts.map +1 -1
  197. package/dist/router.cjs +1 -1
  198. package/dist/router.cjs.map +1 -1
  199. package/dist/router.js +1 -1
  200. package/dist/router.js.map +1 -1
  201. package/dist/search.d.cts +3 -3
  202. package/dist/search.d.cts.map +1 -1
  203. package/dist/search.d.ts +3 -3
  204. package/dist/search.d.ts.map +1 -1
  205. package/dist/server.cjs +170 -1
  206. package/dist/server.cjs.map +1 -1
  207. package/dist/server.d.cts +2 -2
  208. package/dist/server.d.cts.map +1 -1
  209. package/dist/server.d.ts +2 -2
  210. package/dist/server.d.ts.map +1 -1
  211. package/dist/server.js +173 -4
  212. package/dist/server.js.map +1 -1
  213. package/dist/speech.cjs +18 -9
  214. package/dist/speech.cjs.map +1 -1
  215. package/dist/speech.d.cts +2 -2
  216. package/dist/speech.d.cts.map +1 -1
  217. package/dist/speech.d.ts +2 -2
  218. package/dist/speech.d.ts.map +1 -1
  219. package/dist/speech.js +18 -9
  220. package/dist/speech.js.map +1 -1
  221. package/dist/sse-writer.d.cts +3 -3
  222. package/dist/sse-writer.d.cts.map +1 -1
  223. package/dist/sse-writer.d.ts +3 -3
  224. package/dist/sse-writer.d.ts.map +1 -1
  225. package/dist/stream-collapse.cjs +80 -9
  226. package/dist/stream-collapse.cjs.map +1 -1
  227. package/dist/stream-collapse.d.cts +11 -1
  228. package/dist/stream-collapse.d.cts.map +1 -1
  229. package/dist/stream-collapse.d.ts +11 -1
  230. package/dist/stream-collapse.d.ts.map +1 -1
  231. package/dist/stream-collapse.js +80 -10
  232. package/dist/stream-collapse.js.map +1 -1
  233. package/dist/suite.cjs +1 -1
  234. package/dist/suite.d.cts +2 -2
  235. package/dist/suite.d.ts +2 -2
  236. package/dist/suite.js +1 -1
  237. package/dist/transcription.d.cts +2 -2
  238. package/dist/transcription.d.cts.map +1 -1
  239. package/dist/transcription.d.ts +2 -2
  240. package/dist/transcription.d.ts.map +1 -1
  241. package/dist/types.d.cts +10 -7
  242. package/dist/types.d.cts.map +1 -1
  243. package/dist/types.d.ts +10 -7
  244. package/dist/types.d.ts.map +1 -1
  245. package/dist/vector-mock.d.cts +2 -2
  246. package/dist/vector-mock.d.cts.map +1 -1
  247. package/dist/vector-mock.d.ts +2 -2
  248. package/dist/vector-mock.d.ts.map +1 -1
  249. package/dist/vector-mock.js +2 -2
  250. package/dist/vector-mock.js.map +1 -1
  251. package/dist/vector-types.d.ts.map +1 -1
  252. package/dist/video.d.cts +3 -3
  253. package/dist/video.d.cts.map +1 -1
  254. package/dist/video.d.ts +3 -3
  255. package/dist/video.d.ts.map +1 -1
  256. package/dist/vitest.cjs +1 -1
  257. package/dist/vitest.js +1 -1
  258. package/dist/ws-framing.d.cts +2 -2
  259. package/dist/ws-framing.d.cts.map +1 -1
  260. package/dist/ws-framing.d.ts +2 -2
  261. package/dist/ws-framing.d.ts.map +1 -1
  262. package/dist/ws-gemini-live.cjs +145 -2
  263. package/dist/ws-gemini-live.cjs.map +1 -1
  264. package/dist/ws-gemini-live.d.cts.map +1 -1
  265. package/dist/ws-gemini-live.d.ts.map +1 -1
  266. package/dist/ws-gemini-live.js +146 -3
  267. package/dist/ws-gemini-live.js.map +1 -1
  268. package/package.json +16 -2
  269. package/skills/write-fixtures/SKILL.md +10 -10
@@ -0,0 +1,616 @@
1
+ import { extractOverrides, flattenHeaders, generateToolCallId, getTestId, isContentWithToolCallsResponse, isErrorResponse, isTextResponse, isToolCallResponse } from "./helpers.js";
2
+ import { matchFixture } from "./router.js";
3
+ import { calculateDelay, delay, writeErrorResponse } from "./sse-writer.js";
4
+ import { createInterruptionSignal } from "./interruption.js";
5
+ import { applyChaos } from "./chaos.js";
6
+ import { proxyAndRecord } from "./recorder.js";
7
+
8
+ //#region src/gemini-interactions.ts
9
+ function geminiInteractionsToCompletionRequest(req) {
10
+ const messages = [];
11
+ const model = req.model ?? "gemini-2.5-flash";
12
+ if (req.system_instruction) messages.push({
13
+ role: "system",
14
+ content: req.system_instruction
15
+ });
16
+ if (req.input !== void 0) {
17
+ if (typeof req.input === "string") messages.push({
18
+ role: "user",
19
+ content: req.input
20
+ });
21
+ else if (Array.isArray(req.input)) {
22
+ const firstItem = req.input[0];
23
+ if (firstItem && "role" in firstItem) for (const turn of req.input) {
24
+ const role = turn.role === "model" ? "assistant" : turn.role;
25
+ const blocks = turn.content ?? turn.parts;
26
+ if (!blocks || blocks.length === 0) {
27
+ if (role === "user" || role === "assistant") messages.push({
28
+ role,
29
+ content: ""
30
+ });
31
+ continue;
32
+ }
33
+ const funcCallParts = blocks.filter((p) => p.type === "function_call");
34
+ const funcResultParts = blocks.filter((p) => p.type === "function_result");
35
+ const textParts = blocks.filter((p) => p.type === "text");
36
+ if (funcCallParts.length > 0) {
37
+ const textContent = textParts.map((p) => p.text ?? "").join("");
38
+ messages.push({
39
+ role: "assistant",
40
+ content: textContent || null,
41
+ tool_calls: funcCallParts.map((p) => ({
42
+ id: p.id ?? p.call_id ?? generateToolCallId(),
43
+ type: "function",
44
+ function: {
45
+ name: p.name ?? "",
46
+ arguments: JSON.stringify(p.arguments ?? {})
47
+ }
48
+ }))
49
+ });
50
+ } else if (funcResultParts.length > 0) {
51
+ for (const part of funcResultParts) {
52
+ const resultValue = part.result ?? part.output;
53
+ messages.push({
54
+ role: "tool",
55
+ content: typeof resultValue === "string" ? resultValue : JSON.stringify(resultValue ?? ""),
56
+ tool_call_id: part.call_id ?? part.id ?? ""
57
+ });
58
+ }
59
+ if (textParts.length > 0) {
60
+ const text = textParts.map((p) => p.text ?? "").join("");
61
+ if (text) messages.push({
62
+ role: "user",
63
+ content: text
64
+ });
65
+ }
66
+ } else {
67
+ const text = textParts.map((p) => p.text ?? "").join("");
68
+ if (role === "user" || role === "assistant" || role === "system") messages.push({
69
+ role,
70
+ content: text
71
+ });
72
+ }
73
+ }
74
+ else {
75
+ const text = req.input.filter((p) => p.type === "text").map((p) => p.text ?? "").join("");
76
+ messages.push({
77
+ role: "user",
78
+ content: text || ""
79
+ });
80
+ }
81
+ }
82
+ }
83
+ let tools;
84
+ if (req.tools && req.tools.length > 0) {
85
+ const funcTools = req.tools.filter((t) => t.type === "function");
86
+ if (funcTools.length > 0) tools = funcTools.map((t) => ({
87
+ type: "function",
88
+ function: {
89
+ name: t.name,
90
+ description: t.description,
91
+ parameters: t.parameters
92
+ }
93
+ }));
94
+ }
95
+ return {
96
+ model,
97
+ messages,
98
+ stream: req.stream !== false,
99
+ temperature: req.generation_config?.temperature,
100
+ max_tokens: req.generation_config?.max_output_tokens,
101
+ tools
102
+ };
103
+ }
104
+ let interactionCounter = 0;
105
+ function nextInteractionId() {
106
+ return `aimock-int-${interactionCounter++}`;
107
+ }
108
+ function interactionsUsage(overrides) {
109
+ if (!overrides?.usage) return {
110
+ total_input_tokens: 0,
111
+ total_output_tokens: 0,
112
+ total_tokens: 0
113
+ };
114
+ const input = overrides.usage.input_tokens ?? overrides.usage.prompt_tokens ?? 0;
115
+ const output = overrides.usage.output_tokens ?? overrides.usage.completion_tokens ?? 0;
116
+ return {
117
+ total_input_tokens: input,
118
+ total_output_tokens: output,
119
+ total_tokens: input + output
120
+ };
121
+ }
122
+ function buildInteractionsTextResponse(content, model, interactionId, overrides) {
123
+ return {
124
+ id: interactionId,
125
+ status: "completed",
126
+ model: overrides?.model ?? model,
127
+ role: "model",
128
+ outputs: [{
129
+ type: "text",
130
+ text: content
131
+ }],
132
+ usage: interactionsUsage(overrides)
133
+ };
134
+ }
135
+ function buildInteractionsToolCallResponse(toolCalls, model, interactionId, logger, overrides) {
136
+ return {
137
+ id: interactionId,
138
+ status: "requires_action",
139
+ model: overrides?.model ?? model,
140
+ role: "model",
141
+ outputs: toolCalls.map((tc) => {
142
+ let argsObj;
143
+ try {
144
+ argsObj = JSON.parse(tc.arguments || "{}");
145
+ } catch {
146
+ logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
147
+ argsObj = {};
148
+ }
149
+ return {
150
+ type: "function_call",
151
+ id: tc.id || generateToolCallId(),
152
+ name: tc.name,
153
+ arguments: argsObj
154
+ };
155
+ }),
156
+ usage: interactionsUsage(overrides)
157
+ };
158
+ }
159
+ function buildInteractionsContentWithToolCallsResponse(content, toolCalls, model, interactionId, logger, overrides) {
160
+ const outputs = [{
161
+ type: "text",
162
+ text: content
163
+ }];
164
+ for (const tc of toolCalls) {
165
+ let argsObj;
166
+ try {
167
+ argsObj = JSON.parse(tc.arguments || "{}");
168
+ } catch {
169
+ logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
170
+ argsObj = {};
171
+ }
172
+ outputs.push({
173
+ type: "function_call",
174
+ id: tc.id || generateToolCallId(),
175
+ name: tc.name,
176
+ arguments: argsObj
177
+ });
178
+ }
179
+ return {
180
+ id: interactionId,
181
+ status: "requires_action",
182
+ model: overrides?.model ?? model,
183
+ role: "model",
184
+ outputs,
185
+ usage: interactionsUsage(overrides)
186
+ };
187
+ }
188
+ function buildInteractionsErrorResponse(message, code) {
189
+ return { error: {
190
+ code: code ?? "INVALID_ARGUMENT",
191
+ message
192
+ } };
193
+ }
194
+ let eventIdCounter = 0;
195
+ function nextEventId() {
196
+ return `evt_${++eventIdCounter}`;
197
+ }
198
+ function buildInteractionsTextSSEEvents(content, interactionId, chunkSize, overrides) {
199
+ const events = [];
200
+ events.push({
201
+ event_type: "interaction.start",
202
+ interaction: {
203
+ id: interactionId,
204
+ status: "in_progress"
205
+ },
206
+ event_id: nextEventId()
207
+ });
208
+ events.push({
209
+ event_type: "content.start",
210
+ index: 0,
211
+ content: { type: "text" },
212
+ event_id: nextEventId()
213
+ });
214
+ if (content.length === 0) events.push({
215
+ event_type: "content.delta",
216
+ index: 0,
217
+ delta: {
218
+ type: "text",
219
+ text: ""
220
+ },
221
+ event_id: nextEventId()
222
+ });
223
+ else for (let i = 0; i < content.length; i += chunkSize) {
224
+ const slice = content.slice(i, i + chunkSize);
225
+ events.push({
226
+ event_type: "content.delta",
227
+ index: 0,
228
+ delta: {
229
+ type: "text",
230
+ text: slice
231
+ },
232
+ event_id: nextEventId()
233
+ });
234
+ }
235
+ events.push({
236
+ event_type: "content.stop",
237
+ index: 0,
238
+ event_id: nextEventId()
239
+ });
240
+ events.push({
241
+ event_type: "interaction.complete",
242
+ interaction: {
243
+ id: interactionId,
244
+ status: "completed",
245
+ usage: interactionsUsage(overrides)
246
+ },
247
+ event_id: nextEventId()
248
+ });
249
+ return events;
250
+ }
251
+ function buildInteractionsToolCallSSEEvents(toolCalls, interactionId, logger, overrides) {
252
+ const events = [];
253
+ events.push({
254
+ event_type: "interaction.start",
255
+ interaction: {
256
+ id: interactionId,
257
+ status: "in_progress"
258
+ },
259
+ event_id: nextEventId()
260
+ });
261
+ for (let idx = 0; idx < toolCalls.length; idx++) {
262
+ const tc = toolCalls[idx];
263
+ let argsObj;
264
+ try {
265
+ argsObj = JSON.parse(tc.arguments || "{}");
266
+ } catch {
267
+ logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
268
+ argsObj = {};
269
+ }
270
+ events.push({
271
+ event_type: "content.start",
272
+ index: idx,
273
+ content: { type: "function_call" },
274
+ event_id: nextEventId()
275
+ });
276
+ events.push({
277
+ event_type: "content.delta",
278
+ index: idx,
279
+ delta: {
280
+ type: "function_call",
281
+ id: tc.id || generateToolCallId(),
282
+ name: tc.name,
283
+ arguments: argsObj
284
+ },
285
+ event_id: nextEventId()
286
+ });
287
+ events.push({
288
+ event_type: "content.stop",
289
+ index: idx,
290
+ event_id: nextEventId()
291
+ });
292
+ }
293
+ events.push({
294
+ event_type: "interaction.complete",
295
+ interaction: {
296
+ id: interactionId,
297
+ status: "requires_action",
298
+ usage: interactionsUsage(overrides)
299
+ },
300
+ event_id: nextEventId()
301
+ });
302
+ return events;
303
+ }
304
+ function buildInteractionsContentWithToolCallsSSEEvents(content, toolCalls, interactionId, chunkSize, logger, overrides) {
305
+ const events = [];
306
+ events.push({
307
+ event_type: "interaction.start",
308
+ interaction: {
309
+ id: interactionId,
310
+ status: "in_progress"
311
+ },
312
+ event_id: nextEventId()
313
+ });
314
+ events.push({
315
+ event_type: "content.start",
316
+ index: 0,
317
+ content: { type: "text" },
318
+ event_id: nextEventId()
319
+ });
320
+ if (content.length === 0) events.push({
321
+ event_type: "content.delta",
322
+ index: 0,
323
+ delta: {
324
+ type: "text",
325
+ text: ""
326
+ },
327
+ event_id: nextEventId()
328
+ });
329
+ else for (let i = 0; i < content.length; i += chunkSize) {
330
+ const slice = content.slice(i, i + chunkSize);
331
+ events.push({
332
+ event_type: "content.delta",
333
+ index: 0,
334
+ delta: {
335
+ type: "text",
336
+ text: slice
337
+ },
338
+ event_id: nextEventId()
339
+ });
340
+ }
341
+ events.push({
342
+ event_type: "content.stop",
343
+ index: 0,
344
+ event_id: nextEventId()
345
+ });
346
+ for (let i = 0; i < toolCalls.length; i++) {
347
+ const tc = toolCalls[i];
348
+ const idx = i + 1;
349
+ let argsObj;
350
+ try {
351
+ argsObj = JSON.parse(tc.arguments || "{}");
352
+ } catch {
353
+ logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
354
+ argsObj = {};
355
+ }
356
+ events.push({
357
+ event_type: "content.start",
358
+ index: idx,
359
+ content: { type: "function_call" },
360
+ event_id: nextEventId()
361
+ });
362
+ events.push({
363
+ event_type: "content.delta",
364
+ index: idx,
365
+ delta: {
366
+ type: "function_call",
367
+ id: tc.id || generateToolCallId(),
368
+ name: tc.name,
369
+ arguments: argsObj
370
+ },
371
+ event_id: nextEventId()
372
+ });
373
+ events.push({
374
+ event_type: "content.stop",
375
+ index: idx,
376
+ event_id: nextEventId()
377
+ });
378
+ }
379
+ events.push({
380
+ event_type: "interaction.complete",
381
+ interaction: {
382
+ id: interactionId,
383
+ status: "requires_action",
384
+ usage: interactionsUsage(overrides)
385
+ },
386
+ event_id: nextEventId()
387
+ });
388
+ return events;
389
+ }
390
+ async function writeGeminiInteractionsSSEStream(res, events, optionsOrLatency) {
391
+ const opts = typeof optionsOrLatency === "number" ? { latency: optionsOrLatency } : optionsOrLatency ?? {};
392
+ const latency = opts.latency ?? 0;
393
+ const profile = opts.streamingProfile;
394
+ const signal = opts.signal;
395
+ const onChunkSent = opts.onChunkSent;
396
+ if (res.writableEnded) return true;
397
+ res.setHeader("Content-Type", "text/event-stream");
398
+ res.setHeader("Cache-Control", "no-cache");
399
+ res.setHeader("Connection", "keep-alive");
400
+ let chunkIndex = 0;
401
+ for (const event of events) {
402
+ const chunkDelay = calculateDelay(chunkIndex, profile, latency);
403
+ if (chunkDelay > 0) await delay(chunkDelay, signal);
404
+ if (signal?.aborted) return false;
405
+ if (res.writableEnded) return true;
406
+ res.write(`data: ${JSON.stringify(event)}\n\n`);
407
+ onChunkSent?.();
408
+ if (signal?.aborted) return false;
409
+ chunkIndex++;
410
+ }
411
+ if (!res.writableEnded) res.end();
412
+ return true;
413
+ }
414
+ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaults, setCorsHeaders) {
415
+ const { logger } = defaults;
416
+ setCorsHeaders(res);
417
+ const urlPath = req.url ?? "/v1beta/interactions";
418
+ let interactionsReq;
419
+ try {
420
+ interactionsReq = JSON.parse(raw);
421
+ } catch {
422
+ journal.add({
423
+ method: req.method ?? "POST",
424
+ path: urlPath,
425
+ headers: flattenHeaders(req.headers),
426
+ body: null,
427
+ response: {
428
+ status: 400,
429
+ fixture: null
430
+ }
431
+ });
432
+ writeErrorResponse(res, 400, JSON.stringify(buildInteractionsErrorResponse("Malformed JSON", "INVALID_ARGUMENT")));
433
+ return;
434
+ }
435
+ const completionReq = geminiInteractionsToCompletionRequest(interactionsReq);
436
+ completionReq._endpointType = "chat";
437
+ const streaming = interactionsReq.stream !== false;
438
+ const model = completionReq.model;
439
+ const testId = getTestId(req);
440
+ const fixture = matchFixture(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
441
+ if (fixture) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
442
+ if (applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
443
+ method: req.method ?? "POST",
444
+ path: urlPath,
445
+ headers: flattenHeaders(req.headers),
446
+ body: completionReq
447
+ }, defaults.registry, defaults.logger)) return;
448
+ if (!fixture) {
449
+ if (defaults.record) {
450
+ if (await proxyAndRecord(req, res, completionReq, "gemini-interactions", urlPath, fixtures, defaults, raw)) {
451
+ journal.add({
452
+ method: req.method ?? "POST",
453
+ path: urlPath,
454
+ headers: flattenHeaders(req.headers),
455
+ body: completionReq,
456
+ response: {
457
+ status: res.statusCode ?? 200,
458
+ fixture: null,
459
+ source: "proxy"
460
+ }
461
+ });
462
+ return;
463
+ }
464
+ }
465
+ const strictStatus = defaults.strict ? 503 : 404;
466
+ const strictMessage = defaults.strict ? "Strict mode: no fixture matched" : "No fixture matched";
467
+ if (defaults.strict) logger.error(`STRICT: No fixture matched for ${req.method ?? "POST"} ${urlPath}`);
468
+ journal.add({
469
+ method: req.method ?? "POST",
470
+ path: urlPath,
471
+ headers: flattenHeaders(req.headers),
472
+ body: completionReq,
473
+ response: {
474
+ status: strictStatus,
475
+ fixture: null
476
+ }
477
+ });
478
+ writeErrorResponse(res, strictStatus, JSON.stringify(buildInteractionsErrorResponse(strictMessage, defaults.strict ? "UNAVAILABLE" : "NOT_FOUND")));
479
+ return;
480
+ }
481
+ const response = fixture.response;
482
+ const latency = fixture.latency ?? defaults.latency;
483
+ const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
484
+ if (isErrorResponse(response)) {
485
+ const status = response.status ?? 500;
486
+ journal.add({
487
+ method: req.method ?? "POST",
488
+ path: urlPath,
489
+ headers: flattenHeaders(req.headers),
490
+ body: completionReq,
491
+ response: {
492
+ status,
493
+ fixture
494
+ }
495
+ });
496
+ writeErrorResponse(res, status, JSON.stringify(buildInteractionsErrorResponse(response.error.message, response.error.type ?? "ERROR")));
497
+ return;
498
+ }
499
+ const interactionId = nextInteractionId();
500
+ if (isContentWithToolCallsResponse(response)) {
501
+ if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Gemini Interactions API — ignoring");
502
+ const overrides = extractOverrides(response);
503
+ const journalEntry = journal.add({
504
+ method: req.method ?? "POST",
505
+ path: urlPath,
506
+ headers: flattenHeaders(req.headers),
507
+ body: completionReq,
508
+ response: {
509
+ status: 200,
510
+ fixture
511
+ }
512
+ });
513
+ if (!streaming) {
514
+ const body = buildInteractionsContentWithToolCallsResponse(response.content, response.toolCalls, model, interactionId, logger, overrides);
515
+ res.writeHead(200, { "Content-Type": "application/json" });
516
+ res.end(JSON.stringify(body));
517
+ } else {
518
+ const events = buildInteractionsContentWithToolCallsSSEEvents(response.content, response.toolCalls, interactionId, chunkSize, logger, overrides);
519
+ const interruption = createInterruptionSignal(fixture);
520
+ if (!await writeGeminiInteractionsSSEStream(res, events, {
521
+ latency,
522
+ streamingProfile: fixture.streamingProfile,
523
+ signal: interruption?.signal,
524
+ onChunkSent: interruption?.tick
525
+ })) {
526
+ if (!res.writableEnded) res.destroy();
527
+ journalEntry.response.interrupted = true;
528
+ journalEntry.response.interruptReason = interruption?.reason();
529
+ }
530
+ interruption?.cleanup();
531
+ }
532
+ return;
533
+ }
534
+ if (isTextResponse(response)) {
535
+ if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Gemini Interactions API — ignoring");
536
+ const overrides = extractOverrides(response);
537
+ const journalEntry = journal.add({
538
+ method: req.method ?? "POST",
539
+ path: urlPath,
540
+ headers: flattenHeaders(req.headers),
541
+ body: completionReq,
542
+ response: {
543
+ status: 200,
544
+ fixture
545
+ }
546
+ });
547
+ if (!streaming) {
548
+ const body = buildInteractionsTextResponse(response.content, model, interactionId, overrides);
549
+ res.writeHead(200, { "Content-Type": "application/json" });
550
+ res.end(JSON.stringify(body));
551
+ } else {
552
+ const events = buildInteractionsTextSSEEvents(response.content, interactionId, chunkSize, overrides);
553
+ const interruption = createInterruptionSignal(fixture);
554
+ if (!await writeGeminiInteractionsSSEStream(res, events, {
555
+ latency,
556
+ streamingProfile: fixture.streamingProfile,
557
+ signal: interruption?.signal,
558
+ onChunkSent: interruption?.tick
559
+ })) {
560
+ if (!res.writableEnded) res.destroy();
561
+ journalEntry.response.interrupted = true;
562
+ journalEntry.response.interruptReason = interruption?.reason();
563
+ }
564
+ interruption?.cleanup();
565
+ }
566
+ return;
567
+ }
568
+ if (isToolCallResponse(response)) {
569
+ const overrides = extractOverrides(response);
570
+ const journalEntry = journal.add({
571
+ method: req.method ?? "POST",
572
+ path: urlPath,
573
+ headers: flattenHeaders(req.headers),
574
+ body: completionReq,
575
+ response: {
576
+ status: 200,
577
+ fixture
578
+ }
579
+ });
580
+ if (!streaming) {
581
+ const body = buildInteractionsToolCallResponse(response.toolCalls, model, interactionId, logger, overrides);
582
+ res.writeHead(200, { "Content-Type": "application/json" });
583
+ res.end(JSON.stringify(body));
584
+ } else {
585
+ const events = buildInteractionsToolCallSSEEvents(response.toolCalls, interactionId, logger, overrides);
586
+ const interruption = createInterruptionSignal(fixture);
587
+ if (!await writeGeminiInteractionsSSEStream(res, events, {
588
+ latency,
589
+ streamingProfile: fixture.streamingProfile,
590
+ signal: interruption?.signal,
591
+ onChunkSent: interruption?.tick
592
+ })) {
593
+ if (!res.writableEnded) res.destroy();
594
+ journalEntry.response.interrupted = true;
595
+ journalEntry.response.interruptReason = interruption?.reason();
596
+ }
597
+ interruption?.cleanup();
598
+ }
599
+ return;
600
+ }
601
+ journal.add({
602
+ method: req.method ?? "POST",
603
+ path: urlPath,
604
+ headers: flattenHeaders(req.headers),
605
+ body: completionReq,
606
+ response: {
607
+ status: 500,
608
+ fixture
609
+ }
610
+ });
611
+ writeErrorResponse(res, 500, JSON.stringify(buildInteractionsErrorResponse("Fixture response did not match any known type", "INTERNAL")));
612
+ }
613
+
614
+ //#endregion
615
+ export { geminiInteractionsToCompletionRequest, handleGeminiInteractions };
616
+ //# sourceMappingURL=gemini-interactions.js.map