@copilotkit/aimock 1.15.0 → 1.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/CHANGELOG.md +26 -16
  2. package/dist/bedrock-converse.cjs +133 -25
  3. package/dist/bedrock-converse.cjs.map +1 -1
  4. package/dist/bedrock-converse.d.cts.map +1 -1
  5. package/dist/bedrock-converse.d.ts.map +1 -1
  6. package/dist/bedrock-converse.js +135 -27
  7. package/dist/bedrock-converse.js.map +1 -1
  8. package/dist/bedrock.cjs +262 -48
  9. package/dist/bedrock.cjs.map +1 -1
  10. package/dist/bedrock.d.cts.map +1 -1
  11. package/dist/bedrock.d.ts.map +1 -1
  12. package/dist/bedrock.js +263 -50
  13. package/dist/bedrock.js.map +1 -1
  14. package/dist/chaos.cjs +9 -35
  15. package/dist/chaos.cjs.map +1 -1
  16. package/dist/chaos.d.cts +2 -17
  17. package/dist/chaos.d.cts.map +1 -1
  18. package/dist/chaos.d.ts +2 -17
  19. package/dist/chaos.d.ts.map +1 -1
  20. package/dist/chaos.js +10 -35
  21. package/dist/chaos.js.map +1 -1
  22. package/dist/cohere.cjs +289 -33
  23. package/dist/cohere.cjs.map +1 -1
  24. package/dist/cohere.d.cts +9 -0
  25. package/dist/cohere.d.cts.map +1 -1
  26. package/dist/cohere.d.ts +9 -0
  27. package/dist/cohere.d.ts.map +1 -1
  28. package/dist/cohere.js +290 -34
  29. package/dist/cohere.js.map +1 -1
  30. package/dist/config-loader.d.ts.map +1 -1
  31. package/dist/embeddings.cjs +22 -4
  32. package/dist/embeddings.cjs.map +1 -1
  33. package/dist/embeddings.d.cts +2 -2
  34. package/dist/embeddings.d.cts.map +1 -1
  35. package/dist/embeddings.d.ts +2 -2
  36. package/dist/embeddings.d.ts.map +1 -1
  37. package/dist/embeddings.js +22 -4
  38. package/dist/embeddings.js.map +1 -1
  39. package/dist/gemini.cjs +48 -45
  40. package/dist/gemini.cjs.map +1 -1
  41. package/dist/gemini.d.cts.map +1 -1
  42. package/dist/gemini.d.ts.map +1 -1
  43. package/dist/gemini.js +48 -45
  44. package/dist/gemini.js.map +1 -1
  45. package/dist/helpers.cjs +9 -0
  46. package/dist/helpers.cjs.map +1 -1
  47. package/dist/helpers.d.cts.map +1 -1
  48. package/dist/helpers.d.ts.map +1 -1
  49. package/dist/helpers.js +9 -0
  50. package/dist/helpers.js.map +1 -1
  51. package/dist/images.cjs +21 -3
  52. package/dist/images.cjs.map +1 -1
  53. package/dist/images.js +21 -3
  54. package/dist/images.js.map +1 -1
  55. package/dist/index.cjs +2 -0
  56. package/dist/index.d.cts +2 -2
  57. package/dist/index.d.ts +2 -2
  58. package/dist/index.js +3 -3
  59. package/dist/jest.cjs +10 -3
  60. package/dist/jest.cjs.map +1 -1
  61. package/dist/jest.js +10 -3
  62. package/dist/jest.js.map +1 -1
  63. package/dist/journal.cjs +1 -1
  64. package/dist/journal.cjs.map +1 -1
  65. package/dist/journal.d.cts.map +1 -1
  66. package/dist/journal.d.ts.map +1 -1
  67. package/dist/journal.js +1 -1
  68. package/dist/journal.js.map +1 -1
  69. package/dist/messages.cjs +5 -4
  70. package/dist/messages.cjs.map +1 -1
  71. package/dist/messages.js +5 -4
  72. package/dist/messages.js.map +1 -1
  73. package/dist/ollama.cjs +129 -8
  74. package/dist/ollama.cjs.map +1 -1
  75. package/dist/ollama.d.cts.map +1 -1
  76. package/dist/ollama.d.ts.map +1 -1
  77. package/dist/ollama.js +130 -9
  78. package/dist/ollama.js.map +1 -1
  79. package/dist/recorder.cjs +234 -69
  80. package/dist/recorder.cjs.map +1 -1
  81. package/dist/recorder.d.cts +5 -50
  82. package/dist/recorder.d.cts.map +1 -1
  83. package/dist/recorder.d.ts +5 -50
  84. package/dist/recorder.d.ts.map +1 -1
  85. package/dist/recorder.js +234 -69
  86. package/dist/recorder.js.map +1 -1
  87. package/dist/responses.cjs +12 -3
  88. package/dist/responses.cjs.map +1 -1
  89. package/dist/responses.d.cts +2 -1
  90. package/dist/responses.d.cts.map +1 -1
  91. package/dist/responses.d.ts +2 -1
  92. package/dist/responses.d.ts.map +1 -1
  93. package/dist/responses.js +12 -4
  94. package/dist/responses.js.map +1 -1
  95. package/dist/router.cjs +13 -6
  96. package/dist/router.cjs.map +1 -1
  97. package/dist/router.js +13 -6
  98. package/dist/router.js.map +1 -1
  99. package/dist/server.cjs +150 -94
  100. package/dist/server.cjs.map +1 -1
  101. package/dist/server.d.cts.map +1 -1
  102. package/dist/server.d.ts.map +1 -1
  103. package/dist/server.js +152 -96
  104. package/dist/server.js.map +1 -1
  105. package/dist/speech.cjs +21 -3
  106. package/dist/speech.cjs.map +1 -1
  107. package/dist/speech.js +21 -3
  108. package/dist/speech.js.map +1 -1
  109. package/dist/transcription.cjs +10 -6
  110. package/dist/transcription.cjs.map +1 -1
  111. package/dist/transcription.d.cts.map +1 -1
  112. package/dist/transcription.d.ts.map +1 -1
  113. package/dist/transcription.js +10 -6
  114. package/dist/transcription.js.map +1 -1
  115. package/dist/types.d.cts +1 -16
  116. package/dist/types.d.cts.map +1 -1
  117. package/dist/types.d.ts +1 -16
  118. package/dist/types.d.ts.map +1 -1
  119. package/dist/video.cjs +66 -10
  120. package/dist/video.cjs.map +1 -1
  121. package/dist/video.d.cts +16 -3
  122. package/dist/video.d.cts.map +1 -1
  123. package/dist/video.d.ts +16 -3
  124. package/dist/video.d.ts.map +1 -1
  125. package/dist/video.js +66 -11
  126. package/dist/video.js.map +1 -1
  127. package/dist/vitest.cjs +10 -3
  128. package/dist/vitest.cjs.map +1 -1
  129. package/dist/vitest.js +10 -3
  130. package/dist/vitest.js.map +1 -1
  131. package/package.json +1 -1
package/dist/recorder.cjs CHANGED
@@ -34,14 +34,17 @@ const STRIP_HEADERS = new Set([
34
34
  * Proxy an unmatched request to the real upstream provider, record the
35
35
  * response as a fixture on disk and in memory, then relay the response
36
36
  * back to the original client.
37
+ *
38
+ * Returns `true` if the request was proxied (provider configured),
39
+ * `false` if no upstream URL is configured for the given provider key.
37
40
  */
38
- async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures, defaults, rawBody, options) {
41
+ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures, defaults, rawBody) {
39
42
  const record = defaults.record;
40
- if (!record) return "not_configured";
43
+ if (!record) return false;
41
44
  const upstreamUrl = record.providers[providerKey];
42
45
  if (!upstreamUrl) {
43
46
  defaults.logger.warn(`No upstream URL configured for provider "${providerKey}" — cannot proxy`);
44
- return "not_configured";
47
+ return false;
45
48
  }
46
49
  const fixturePath = record.fixturePath ?? "./fixtures/recorded";
47
50
  let target;
@@ -53,7 +56,7 @@ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures
53
56
  message: `Invalid upstream URL: ${upstreamUrl}`,
54
57
  type: "proxy_error"
55
58
  } }));
56
- return "relayed";
59
+ return true;
57
60
  }
58
61
  defaults.logger.warn(`NO FIXTURE MATCH — proxying to ${upstreamUrl}${pathname}`);
59
62
  const forwardHeaders = {};
@@ -64,6 +67,7 @@ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures
64
67
  let upstreamBody;
65
68
  let rawBuffer;
66
69
  let streamedToClient = false;
70
+ let clientDisconnected = false;
67
71
  try {
68
72
  const result = await makeUpstreamRequest(target, forwardHeaders, requestBody, res);
69
73
  upstreamStatus = result.status;
@@ -71,15 +75,18 @@ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures
71
75
  upstreamBody = result.body;
72
76
  rawBuffer = result.rawBuffer;
73
77
  streamedToClient = result.streamedToClient;
78
+ clientDisconnected = result.clientDisconnected;
74
79
  } catch (err) {
75
80
  const msg = err instanceof Error ? err.message : "Unknown proxy error";
76
81
  defaults.logger.error(`Proxy request failed: ${msg}`);
77
- res.writeHead(502, { "Content-Type": "application/json" });
78
- res.end(JSON.stringify({ error: {
79
- message: `Proxy to upstream failed: ${msg}`,
80
- type: "proxy_error"
81
- } }));
82
- return "relayed";
82
+ if (!res.headersSent) {
83
+ res.writeHead(502, { "Content-Type": "application/json" });
84
+ res.end(JSON.stringify({ error: {
85
+ message: `Proxy to upstream failed: ${msg}`,
86
+ type: "proxy_error"
87
+ } }));
88
+ } else res.end();
89
+ return true;
83
90
  }
84
91
  const contentType = upstreamHeaders["content-type"];
85
92
  const ctString = Array.isArray(contentType) ? contentType.join(", ") : contentType ?? "";
@@ -97,10 +104,20 @@ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures
97
104
  if (collapsed.truncated) defaults.logger.warn("Bedrock EventStream: CRC mismatch — response may be truncated");
98
105
  if (collapsed.droppedChunks && collapsed.droppedChunks > 0) defaults.logger.warn(`${collapsed.droppedChunks} chunk(s) dropped during stream collapse`);
99
106
  if (collapsed.content === "" && (!collapsed.toolCalls || collapsed.toolCalls.length === 0)) defaults.logger.warn("Stream collapse produced empty content — fixture may be incomplete");
100
- if (collapsed.toolCalls && collapsed.toolCalls.length > 0) {
101
- if (collapsed.content) defaults.logger.warn("Collapsed response has both content and toolCalls — preferring toolCalls");
102
- fixtureResponse = { toolCalls: collapsed.toolCalls };
103
- } else fixtureResponse = { content: collapsed.content ?? "" };
107
+ const reasoningSpread = collapsed.reasoning ? { reasoning: collapsed.reasoning } : {};
108
+ if (collapsed.toolCalls && collapsed.toolCalls.length > 0) if (collapsed.content) fixtureResponse = {
109
+ content: collapsed.content,
110
+ toolCalls: collapsed.toolCalls,
111
+ ...reasoningSpread
112
+ };
113
+ else fixtureResponse = {
114
+ toolCalls: collapsed.toolCalls,
115
+ ...reasoningSpread
116
+ };
117
+ else fixtureResponse = {
118
+ content: collapsed.content ?? "",
119
+ ...reasoningSpread
120
+ };
104
121
  } else {
105
122
  let parsedResponse = null;
106
123
  try {
@@ -111,9 +128,15 @@ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures
111
128
  let encodingFormat;
112
129
  try {
113
130
  encodingFormat = rawBody ? JSON.parse(rawBody).encoding_format : void 0;
114
- } catch {}
131
+ } catch (err) {
132
+ defaults.logger.debug(`Could not parse encoding_format from raw body: ${err instanceof Error ? err.message : "unknown error"}`);
133
+ }
115
134
  fixtureResponse = buildFixtureResponse(parsedResponse, upstreamStatus, encodingFormat);
116
135
  }
136
+ if (clientDisconnected) {
137
+ defaults.logger.warn("Client disconnected mid-stream — skipping fixture save to avoid truncated data");
138
+ return true;
139
+ }
117
140
  const fixtureMatch = buildFixtureMatch(defaults.requestTransform ? defaults.requestTransform(request) : request);
118
141
  const fixture = {
119
142
  match: fixtureMatch,
@@ -138,29 +161,22 @@ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures
138
161
  } catch (err) {
139
162
  const msg = err instanceof Error ? err.message : "Unknown filesystem error";
140
163
  defaults.logger.error(`Failed to save fixture to disk: ${msg}`);
141
- res.setHeader("X-LLMock-Record-Error", msg);
164
+ if (!res.headersSent) res.setHeader("X-LLMock-Record-Error", msg);
165
+ else defaults.logger.warn(`Cannot set X-LLMock-Record-Error header — headers already sent`);
142
166
  }
143
167
  if (writtenToDisk) {
144
168
  if (!isEmptyMatch) fixtures.push(fixture);
145
169
  defaults.logger.warn(`Response recorded → ${filepath}`);
146
170
  } else defaults.logger.warn(`Response relayed but NOT saved to disk — see error above`);
147
171
  } else defaults.logger.info(`Proxied ${providerKey} request (proxy-only mode)`);
148
- if (streamedToClient) {
149
- if (options?.beforeWriteResponse && options.onHookBypassed) options.onHookBypassed("sse_streamed");
150
- } else {
151
- if (options?.beforeWriteResponse) {
152
- if (await options.beforeWriteResponse({
153
- status: upstreamStatus,
154
- contentType: ctString,
155
- body: rawBuffer
156
- })) return "handled_by_hook";
157
- }
172
+ if (!streamedToClient) {
158
173
  const relayHeaders = {};
159
174
  if (ctString) relayHeaders["Content-Type"] = ctString;
160
175
  res.writeHead(upstreamStatus, relayHeaders);
161
- res.end(isBinaryStream ? rawBuffer : upstreamBody);
176
+ const isAudioRelay = ctString.toLowerCase().startsWith("audio/");
177
+ res.end(isBinaryStream || isAudioRelay ? rawBuffer : upstreamBody);
162
178
  }
163
- return "relayed";
179
+ return true;
164
180
  }
165
181
  function makeUpstreamRequest(target, headers, body, clientRes) {
166
182
  return new Promise((resolve, reject) => {
@@ -182,28 +198,34 @@ function makeUpstreamRequest(target, headers, body, clientRes) {
182
198
  const ctStr = Array.isArray(ct) ? ct.join(", ") : ct ?? "";
183
199
  const isSSE = ctStr.toLowerCase().includes("text/event-stream");
184
200
  let streamedToClient = false;
201
+ let clientDisconnected = false;
185
202
  if (isSSE && clientRes && !clientRes.headersSent) {
186
203
  const relayHeaders = {};
187
204
  if (ctStr) relayHeaders["Content-Type"] = ctStr;
188
205
  clientRes.writeHead(res.statusCode ?? 200, relayHeaders);
189
206
  if (typeof clientRes.flushHeaders === "function") clientRes.flushHeaders();
190
207
  streamedToClient = true;
208
+ clientRes.on("close", () => {
209
+ clientDisconnected = true;
210
+ req.destroy();
211
+ });
191
212
  }
192
213
  const chunks = [];
193
214
  res.on("data", (chunk) => {
194
215
  chunks.push(chunk);
195
- if (streamedToClient) clientRes.write(chunk);
216
+ if (streamedToClient && clientRes && !clientDisconnected && !clientRes.destroyed && !clientRes.writableEnded) clientRes.write(chunk);
196
217
  });
197
218
  res.on("error", reject);
198
219
  res.on("end", () => {
199
220
  const rawBuffer = Buffer.concat(chunks);
200
- if (streamedToClient) clientRes.end();
221
+ if (streamedToClient && clientRes && !clientDisconnected && !clientRes.destroyed && !clientRes.writableEnded) clientRes.end();
201
222
  resolve({
202
223
  status: res.statusCode ?? 500,
203
224
  headers: res.headers,
204
225
  body: rawBuffer.toString(),
205
226
  rawBuffer,
206
- streamedToClient
227
+ streamedToClient,
228
+ clientDisconnected
207
229
  });
208
230
  });
209
231
  });
@@ -228,7 +250,7 @@ function buildFixtureResponse(parsed, status, encodingFormat) {
228
250
  status
229
251
  };
230
252
  const obj = parsed;
231
- if (obj.error) {
253
+ if (typeof obj.error === "object" && obj.error !== null && typeof obj.error.message === "string") {
232
254
  const err = obj.error;
233
255
  return {
234
256
  error: {
@@ -242,11 +264,12 @@ function buildFixtureResponse(parsed, status, encodingFormat) {
242
264
  if (Array.isArray(obj.data) && obj.data.length > 0) {
243
265
  const first = obj.data[0];
244
266
  if (Array.isArray(first.embedding)) return { embedding: first.embedding };
245
- if (typeof first.embedding === "string" && encodingFormat === "base64") try {
267
+ if (typeof first.embedding === "string" && encodingFormat === "base64") {
246
268
  const buf = Buffer.from(first.embedding, "base64");
247
- const floats = new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
269
+ const aligned = new Uint8Array(buf).buffer;
270
+ const floats = new Float32Array(aligned, 0, buf.byteLength / 4);
248
271
  return { embedding: Array.from(floats) };
249
- } catch {}
272
+ }
250
273
  if (first.url || first.b64_json) {
251
274
  const images = obj.data.map((item) => ({
252
275
  ...item.url ? { url: String(item.url) } : {},
@@ -272,7 +295,7 @@ function buildFixtureResponse(parsed, status, encodingFormat) {
272
295
  ...Array.isArray(obj.words) ? { words: obj.words } : {},
273
296
  ...Array.isArray(obj.segments) ? { segments: obj.segments } : {}
274
297
  } };
275
- if (typeof obj.id === "string" && typeof obj.status === "string" && (obj.status === "completed" || obj.status === "in_progress" || obj.status === "failed")) {
298
+ if (typeof obj.id === "string" && typeof obj.status === "string" && (obj.status === "completed" || obj.status === "in_progress" || obj.status === "failed") && !("choices" in obj) && !("content" in obj) && !("candidates" in obj) && !("message" in obj) && !("data" in obj) && !("object" in obj)) {
276
299
  if (obj.status === "completed" && obj.url) return { video: {
277
300
  id: String(obj.id),
278
301
  status: "completed",
@@ -287,40 +310,109 @@ function buildFixtureResponse(parsed, status, encodingFormat) {
287
310
  if (Array.isArray(obj.choices) && obj.choices.length > 0) {
288
311
  const message = obj.choices[0].message;
289
312
  if (message) {
290
- if (Array.isArray(message.tool_calls) && message.tool_calls.length > 0) return { toolCalls: message.tool_calls.map((tc) => {
291
- const fn = tc.function;
313
+ const hasToolCalls = Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
314
+ const hasContent = typeof message.content === "string" && message.content.length > 0;
315
+ const openaiReasoning = typeof message.reasoning_content === "string" && message.reasoning_content.length > 0 ? message.reasoning_content : void 0;
316
+ if (hasToolCalls) {
317
+ const toolCalls = message.tool_calls.map((tc) => {
318
+ const fn = tc.function;
319
+ return {
320
+ name: String(fn.name),
321
+ arguments: String(fn.arguments),
322
+ ...tc.id ? { id: String(tc.id) } : {}
323
+ };
324
+ });
325
+ if (hasContent) return {
326
+ content: message.content,
327
+ toolCalls,
328
+ ...openaiReasoning ? { reasoning: openaiReasoning } : {}
329
+ };
292
330
  return {
293
- name: String(fn.name),
294
- arguments: String(fn.arguments)
331
+ toolCalls,
332
+ ...openaiReasoning ? { reasoning: openaiReasoning } : {}
295
333
  };
296
- }) };
297
- if (typeof message.content === "string") return { content: message.content };
334
+ }
335
+ if (hasContent) return {
336
+ content: message.content,
337
+ ...openaiReasoning ? { reasoning: openaiReasoning } : {}
338
+ };
339
+ return {
340
+ content: "",
341
+ ...openaiReasoning ? { reasoning: openaiReasoning } : {}
342
+ };
298
343
  }
299
344
  }
300
345
  if (Array.isArray(obj.content) && obj.content.length > 0) {
301
346
  const blocks = obj.content;
302
347
  const toolUseBlocks = blocks.filter((b) => b.type === "tool_use");
303
- if (toolUseBlocks.length > 0) return { toolCalls: toolUseBlocks.map((b) => ({
304
- name: String(b.name),
305
- arguments: typeof b.input === "string" ? b.input : JSON.stringify(b.input)
306
- })) };
307
- const textBlock = blocks.find((b) => b.type === "text");
308
- if (textBlock && typeof textBlock.text === "string") return { content: textBlock.text };
348
+ const textBlocks = blocks.filter((b) => b.type === "text" && typeof b.text === "string");
349
+ const thinkingBlocks = blocks.filter((b) => b.type === "thinking");
350
+ const hasToolCalls = toolUseBlocks.length > 0;
351
+ const joinedText = textBlocks.map((b) => String(b.text ?? "")).join("");
352
+ const hasContent = joinedText.length > 0;
353
+ const anthropicReasoning = thinkingBlocks.length > 0 ? thinkingBlocks.map((b) => String(b.thinking ?? "")).join("") : void 0;
354
+ if (hasToolCalls) {
355
+ const toolCalls = toolUseBlocks.map((b) => ({
356
+ name: String(b.name),
357
+ arguments: typeof b.input === "string" ? b.input : JSON.stringify(b.input),
358
+ ...b.id ? { id: String(b.id) } : {}
359
+ }));
360
+ if (hasContent) return {
361
+ content: joinedText,
362
+ toolCalls,
363
+ ...anthropicReasoning ? { reasoning: anthropicReasoning } : {}
364
+ };
365
+ return {
366
+ toolCalls,
367
+ ...anthropicReasoning ? { reasoning: anthropicReasoning } : {}
368
+ };
369
+ }
370
+ if (hasContent) return {
371
+ content: joinedText,
372
+ ...anthropicReasoning ? { reasoning: anthropicReasoning } : {}
373
+ };
374
+ if (anthropicReasoning) return {
375
+ content: "",
376
+ reasoning: anthropicReasoning
377
+ };
309
378
  }
310
379
  if (Array.isArray(obj.candidates) && obj.candidates.length > 0) {
311
380
  const content = obj.candidates[0].content;
312
381
  if (content && Array.isArray(content.parts)) {
313
382
  const parts = content.parts;
314
383
  const fnCallParts = parts.filter((p) => p.functionCall);
315
- if (fnCallParts.length > 0) return { toolCalls: fnCallParts.map((p) => {
316
- const fc = p.functionCall;
384
+ const textParts = parts.filter((p) => typeof p.text === "string" && !p.thought);
385
+ const thoughtParts = parts.filter((p) => p.thought === true && typeof p.text === "string");
386
+ const hasToolCalls = fnCallParts.length > 0;
387
+ const joinedText = textParts.map((p) => String(p.text ?? "")).join("");
388
+ const hasContent = joinedText.length > 0;
389
+ const geminiReasoning = thoughtParts.length > 0 ? thoughtParts.map((p) => String(p.text ?? "")).join("") : void 0;
390
+ if (hasToolCalls) {
391
+ const toolCalls = fnCallParts.map((p) => {
392
+ const fc = p.functionCall;
393
+ return {
394
+ name: String(fc.name),
395
+ arguments: typeof fc.args === "string" ? fc.args : JSON.stringify(fc.args)
396
+ };
397
+ });
398
+ if (hasContent) return {
399
+ content: joinedText,
400
+ toolCalls,
401
+ ...geminiReasoning ? { reasoning: geminiReasoning } : {}
402
+ };
317
403
  return {
318
- name: String(fc.name),
319
- arguments: typeof fc.args === "string" ? fc.args : JSON.stringify(fc.args)
404
+ toolCalls,
405
+ ...geminiReasoning ? { reasoning: geminiReasoning } : {}
320
406
  };
321
- }) };
322
- const textPart = parts.find((p) => typeof p.text === "string");
323
- if (textPart && typeof textPart.text === "string") return { content: textPart.text };
407
+ }
408
+ if (hasContent) return {
409
+ content: joinedText,
410
+ ...geminiReasoning ? { reasoning: geminiReasoning } : {}
411
+ };
412
+ return {
413
+ content: "",
414
+ ...geminiReasoning ? { reasoning: geminiReasoning } : {}
415
+ };
324
416
  }
325
417
  }
326
418
  if (obj.output && typeof obj.output === "object") {
@@ -328,32 +420,105 @@ function buildFixtureResponse(parsed, status, encodingFormat) {
328
420
  if (msg && Array.isArray(msg.content)) {
329
421
  const blocks = msg.content;
330
422
  const toolUseBlocks = blocks.filter((b) => b.toolUse);
331
- if (toolUseBlocks.length > 0) return { toolCalls: toolUseBlocks.map((b) => {
332
- const tu = b.toolUse;
423
+ const textBlocks = blocks.filter((b) => typeof b.text === "string");
424
+ const reasoningBlocks = blocks.filter((b) => b.reasoningContent);
425
+ const hasToolCalls = toolUseBlocks.length > 0;
426
+ const joinedText = textBlocks.map((b) => String(b.text ?? "")).join("");
427
+ const hasContent = joinedText.length > 0;
428
+ const bedrockReasoning = reasoningBlocks.length > 0 ? reasoningBlocks.map((b) => {
429
+ const rt = b.reasoningContent?.reasoningText;
430
+ return String(rt?.text ?? "");
431
+ }).join("") : void 0;
432
+ if (hasToolCalls) {
433
+ const toolCalls = toolUseBlocks.map((b) => {
434
+ const tu = b.toolUse;
435
+ return {
436
+ name: String(tu.name ?? ""),
437
+ arguments: typeof tu.input === "string" ? tu.input : JSON.stringify(tu.input),
438
+ ...tu.toolUseId ? { id: String(tu.toolUseId) } : {}
439
+ };
440
+ });
441
+ if (hasContent) return {
442
+ content: joinedText,
443
+ toolCalls,
444
+ ...bedrockReasoning ? { reasoning: bedrockReasoning } : {}
445
+ };
333
446
  return {
334
- name: String(tu.name ?? ""),
335
- arguments: typeof tu.input === "string" ? tu.input : JSON.stringify(tu.input)
447
+ toolCalls,
448
+ ...bedrockReasoning ? { reasoning: bedrockReasoning } : {}
336
449
  };
337
- }) };
338
- const textBlock = blocks.find((b) => typeof b.text === "string");
339
- if (textBlock && typeof textBlock.text === "string") return { content: textBlock.text };
450
+ }
451
+ if (hasContent) return {
452
+ content: joinedText,
453
+ ...bedrockReasoning ? { reasoning: bedrockReasoning } : {}
454
+ };
455
+ return {
456
+ content: "",
457
+ ...bedrockReasoning ? { reasoning: bedrockReasoning } : {}
458
+ };
340
459
  }
341
460
  }
461
+ if (typeof obj.finish_reason === "string" && obj.message && typeof obj.message === "object" && Array.isArray(obj.message.content)) {
462
+ const msg = obj.message;
463
+ const contentBlocks = msg.content;
464
+ const textBlock = contentBlocks.find((b) => b.type === "text" && typeof b.text === "string");
465
+ const hasContent = textBlock && typeof textBlock.text === "string" && textBlock.text.length > 0;
466
+ const toolCallBlocks = contentBlocks.filter((b) => b.type === "tool_call");
467
+ const msgToolCalls = Array.isArray(msg.tool_calls) ? msg.tool_calls : [];
468
+ if (toolCallBlocks.length > 0) {
469
+ const toolCalls = toolCallBlocks.map((b) => ({
470
+ name: String(b.name ?? b.function?.name ?? ""),
471
+ arguments: typeof b.parameters === "string" ? b.parameters : typeof b.parameters === "object" ? JSON.stringify(b.parameters) : typeof b.function?.arguments === "string" ? String(b.function.arguments) : JSON.stringify(b.function?.arguments),
472
+ ...b.id ? { id: String(b.id) } : {}
473
+ }));
474
+ if (hasContent) return {
475
+ content: textBlock.text,
476
+ toolCalls
477
+ };
478
+ return { toolCalls };
479
+ }
480
+ if (msgToolCalls.length > 0) {
481
+ const toolCalls = msgToolCalls.map((tc) => {
482
+ const fn = tc.function;
483
+ return {
484
+ name: String(tc.name ?? fn?.name ?? ""),
485
+ arguments: typeof tc.parameters === "string" ? tc.parameters : typeof tc.parameters === "object" ? JSON.stringify(tc.parameters) : typeof fn?.arguments === "string" ? String(fn.arguments) : JSON.stringify(fn?.arguments),
486
+ ...tc.id ? { id: String(tc.id) } : {}
487
+ };
488
+ });
489
+ if (hasContent) return {
490
+ content: textBlock.text,
491
+ toolCalls
492
+ };
493
+ return { toolCalls };
494
+ }
495
+ if (hasContent) return { content: textBlock.text };
496
+ }
342
497
  if (obj.message && typeof obj.message === "object") {
343
498
  const msg = obj.message;
344
- if (Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) return { toolCalls: msg.tool_calls.filter((tc) => tc.function != null).map((tc) => {
345
- const fn = tc.function;
346
- return {
347
- name: String(fn.name ?? ""),
348
- arguments: typeof fn.arguments === "string" ? fn.arguments : JSON.stringify(fn.arguments)
499
+ const hasOllamaToolCalls = Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0;
500
+ const hasOllamaContent = typeof msg.content === "string" && msg.content.length > 0;
501
+ if (hasOllamaToolCalls) {
502
+ const toolCalls = msg.tool_calls.filter((tc) => tc.function != null).map((tc) => {
503
+ const fn = tc.function;
504
+ return {
505
+ name: String(fn.name ?? ""),
506
+ arguments: typeof fn.arguments === "string" ? fn.arguments : JSON.stringify(fn.arguments)
507
+ };
508
+ });
509
+ if (hasOllamaContent) return {
510
+ content: msg.content,
511
+ toolCalls
349
512
  };
350
- }) };
351
- if (typeof msg.content === "string" && msg.content.length > 0) return { content: msg.content };
513
+ return { toolCalls };
514
+ }
515
+ if (hasOllamaContent) return { content: msg.content };
352
516
  if (Array.isArray(msg.content) && msg.content.length > 0) {
353
517
  const first = msg.content[0];
354
518
  if (typeof first.text === "string") return { content: first.text };
355
519
  }
356
520
  }
521
+ if (typeof obj.response === "string" && "done" in obj) return { content: obj.response };
357
522
  return {
358
523
  error: {
359
524
  message: "Could not detect response format from upstream",