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