@copilotkit/aimock 1.29.0 → 1.30.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 (172) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/agui-types.d.ts.map +1 -1
  3. package/dist/bedrock-converse.cjs +63 -31
  4. package/dist/bedrock-converse.cjs.map +1 -1
  5. package/dist/bedrock-converse.d.cts.map +1 -1
  6. package/dist/bedrock-converse.d.ts.map +1 -1
  7. package/dist/bedrock-converse.js +65 -33
  8. package/dist/bedrock-converse.js.map +1 -1
  9. package/dist/bedrock.cjs +95 -33
  10. package/dist/bedrock.cjs.map +1 -1
  11. package/dist/bedrock.d.cts.map +1 -1
  12. package/dist/bedrock.d.ts.map +1 -1
  13. package/dist/bedrock.js +97 -35
  14. package/dist/bedrock.js.map +1 -1
  15. package/dist/cohere.cjs +49 -15
  16. package/dist/cohere.cjs.map +1 -1
  17. package/dist/cohere.d.cts.map +1 -1
  18. package/dist/cohere.d.ts.map +1 -1
  19. package/dist/cohere.js +51 -17
  20. package/dist/cohere.js.map +1 -1
  21. package/dist/config-loader.d.ts.map +1 -1
  22. package/dist/elevenlabs-audio.cjs +8 -4
  23. package/dist/elevenlabs-audio.cjs.map +1 -1
  24. package/dist/elevenlabs-audio.d.cts.map +1 -1
  25. package/dist/elevenlabs-audio.d.ts.map +1 -1
  26. package/dist/elevenlabs-audio.js +10 -6
  27. package/dist/elevenlabs-audio.js.map +1 -1
  28. package/dist/embeddings.cjs +4 -3
  29. package/dist/embeddings.cjs.map +1 -1
  30. package/dist/embeddings.d.cts.map +1 -1
  31. package/dist/embeddings.d.ts.map +1 -1
  32. package/dist/embeddings.js +6 -5
  33. package/dist/embeddings.js.map +1 -1
  34. package/dist/fal-audio.cjs +8 -4
  35. package/dist/fal-audio.cjs.map +1 -1
  36. package/dist/fal-audio.d.cts.map +1 -1
  37. package/dist/fal-audio.d.ts.map +1 -1
  38. package/dist/fal-audio.js +10 -6
  39. package/dist/fal-audio.js.map +1 -1
  40. package/dist/fal.cjs +4 -2
  41. package/dist/fal.cjs.map +1 -1
  42. package/dist/fal.d.cts.map +1 -1
  43. package/dist/fal.d.ts.map +1 -1
  44. package/dist/fal.js +6 -4
  45. package/dist/fal.js.map +1 -1
  46. package/dist/gemini-embeddings.cjs +4 -3
  47. package/dist/gemini-embeddings.cjs.map +1 -1
  48. package/dist/gemini-embeddings.js +6 -5
  49. package/dist/gemini-embeddings.js.map +1 -1
  50. package/dist/gemini-interactions.cjs +3 -3
  51. package/dist/gemini-interactions.cjs.map +1 -1
  52. package/dist/gemini-interactions.d.cts.map +1 -1
  53. package/dist/gemini-interactions.d.ts.map +1 -1
  54. package/dist/gemini-interactions.js +5 -5
  55. package/dist/gemini-interactions.js.map +1 -1
  56. package/dist/gemini.cjs +55 -24
  57. package/dist/gemini.cjs.map +1 -1
  58. package/dist/gemini.d.cts.map +1 -1
  59. package/dist/gemini.d.ts.map +1 -1
  60. package/dist/gemini.js +57 -26
  61. package/dist/gemini.js.map +1 -1
  62. package/dist/helpers.cjs +120 -2
  63. package/dist/helpers.cjs.map +1 -1
  64. package/dist/helpers.d.cts +43 -3
  65. package/dist/helpers.d.cts.map +1 -1
  66. package/dist/helpers.d.ts +43 -3
  67. package/dist/helpers.d.ts.map +1 -1
  68. package/dist/helpers.js +117 -3
  69. package/dist/helpers.js.map +1 -1
  70. package/dist/images.cjs +12 -6
  71. package/dist/images.cjs.map +1 -1
  72. package/dist/images.d.cts.map +1 -1
  73. package/dist/images.d.ts.map +1 -1
  74. package/dist/images.js +14 -8
  75. package/dist/images.js.map +1 -1
  76. package/dist/index.cjs +3 -0
  77. package/dist/index.d.cts +3 -3
  78. package/dist/index.d.ts +3 -3
  79. package/dist/index.js +3 -3
  80. package/dist/messages.cjs +325 -85
  81. package/dist/messages.cjs.map +1 -1
  82. package/dist/messages.d.cts.map +1 -1
  83. package/dist/messages.d.ts.map +1 -1
  84. package/dist/messages.js +327 -87
  85. package/dist/messages.js.map +1 -1
  86. package/dist/model-utils.cjs +68 -0
  87. package/dist/model-utils.cjs.map +1 -1
  88. package/dist/model-utils.js +68 -1
  89. package/dist/model-utils.js.map +1 -1
  90. package/dist/ollama.cjs +58 -21
  91. package/dist/ollama.cjs.map +1 -1
  92. package/dist/ollama.d.cts.map +1 -1
  93. package/dist/ollama.d.ts.map +1 -1
  94. package/dist/ollama.js +60 -23
  95. package/dist/ollama.js.map +1 -1
  96. package/dist/recorder.cjs +49 -8
  97. package/dist/recorder.cjs.map +1 -1
  98. package/dist/recorder.js +50 -9
  99. package/dist/recorder.js.map +1 -1
  100. package/dist/responses.cjs +26 -12
  101. package/dist/responses.cjs.map +1 -1
  102. package/dist/responses.d.cts +1 -1
  103. package/dist/responses.d.cts.map +1 -1
  104. package/dist/responses.d.ts +1 -1
  105. package/dist/responses.d.ts.map +1 -1
  106. package/dist/responses.js +28 -14
  107. package/dist/responses.js.map +1 -1
  108. package/dist/router.cjs +37 -8
  109. package/dist/router.cjs.map +1 -1
  110. package/dist/router.d.cts +30 -1
  111. package/dist/router.d.cts.map +1 -1
  112. package/dist/router.d.ts +30 -1
  113. package/dist/router.d.ts.map +1 -1
  114. package/dist/router.js +37 -9
  115. package/dist/router.js.map +1 -1
  116. package/dist/server.cjs +15 -9
  117. package/dist/server.cjs.map +1 -1
  118. package/dist/server.d.cts.map +1 -1
  119. package/dist/server.d.ts.map +1 -1
  120. package/dist/server.js +17 -11
  121. package/dist/server.js.map +1 -1
  122. package/dist/speech.cjs +4 -2
  123. package/dist/speech.cjs.map +1 -1
  124. package/dist/speech.d.cts.map +1 -1
  125. package/dist/speech.d.ts.map +1 -1
  126. package/dist/speech.js +6 -4
  127. package/dist/speech.js.map +1 -1
  128. package/dist/stream-collapse.cjs +44 -1
  129. package/dist/stream-collapse.cjs.map +1 -1
  130. package/dist/stream-collapse.d.cts +28 -0
  131. package/dist/stream-collapse.d.cts.map +1 -1
  132. package/dist/stream-collapse.d.ts +28 -0
  133. package/dist/stream-collapse.d.ts.map +1 -1
  134. package/dist/stream-collapse.js +44 -2
  135. package/dist/stream-collapse.js.map +1 -1
  136. package/dist/transcription.cjs +4 -2
  137. package/dist/transcription.cjs.map +1 -1
  138. package/dist/transcription.d.cts.map +1 -1
  139. package/dist/transcription.d.ts.map +1 -1
  140. package/dist/transcription.js +6 -4
  141. package/dist/transcription.js.map +1 -1
  142. package/dist/types.d.cts +42 -0
  143. package/dist/types.d.cts.map +1 -1
  144. package/dist/types.d.ts +42 -0
  145. package/dist/types.d.ts.map +1 -1
  146. package/dist/vector-types.d.cts.map +1 -1
  147. package/dist/vector-types.d.ts.map +1 -1
  148. package/dist/video.cjs +4 -2
  149. package/dist/video.cjs.map +1 -1
  150. package/dist/video.d.cts.map +1 -1
  151. package/dist/video.d.ts.map +1 -1
  152. package/dist/video.js +6 -4
  153. package/dist/video.js.map +1 -1
  154. package/dist/ws-gemini-live.cjs +4 -3
  155. package/dist/ws-gemini-live.cjs.map +1 -1
  156. package/dist/ws-gemini-live.d.cts.map +1 -1
  157. package/dist/ws-gemini-live.d.ts.map +1 -1
  158. package/dist/ws-gemini-live.js +6 -5
  159. package/dist/ws-gemini-live.js.map +1 -1
  160. package/dist/ws-realtime.cjs +4 -3
  161. package/dist/ws-realtime.cjs.map +1 -1
  162. package/dist/ws-realtime.d.cts.map +1 -1
  163. package/dist/ws-realtime.d.ts.map +1 -1
  164. package/dist/ws-realtime.js +6 -5
  165. package/dist/ws-realtime.js.map +1 -1
  166. package/dist/ws-responses.cjs +8 -6
  167. package/dist/ws-responses.cjs.map +1 -1
  168. package/dist/ws-responses.d.cts.map +1 -1
  169. package/dist/ws-responses.d.ts.map +1 -1
  170. package/dist/ws-responses.js +10 -8
  171. package/dist/ws-responses.js.map +1 -1
  172. package/package.json +1 -1
package/dist/messages.cjs CHANGED
@@ -6,70 +6,87 @@ const require_chaos = require('./chaos.cjs');
6
6
  const require_recorder = require('./recorder.cjs');
7
7
 
8
8
  //#region src/messages.ts
9
+ /**
10
+ * Non-empty placeholder signature written into emitted `thinking` blocks.
11
+ *
12
+ * The real Anthropic signature is a cryptographic value aimock cannot
13
+ * reproduce, but extended-thinking invariant (b) requires a non-empty
14
+ * `signature` on the leading thinking block of a tool-loop continuation turn.
15
+ * Emitting "" would make a record→replay round-trip of an aimock thinking turn
16
+ * self-trip that invariant under strict mode. A non-empty placeholder keeps
17
+ * round-trips green; the invariant only checks for non-emptiness, not value.
18
+ */
19
+ const PLACEHOLDER_SIGNATURE = "aimock-placeholder-signature";
9
20
  function extractClaudeTextContent(content) {
10
21
  if (typeof content === "string") return content;
11
- return content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("");
22
+ if (!Array.isArray(content)) return "";
23
+ return content.filter((b) => b != null && typeof b === "object" && b.type === "text").map((b) => b.text ?? "").join("");
12
24
  }
13
25
  function claudeToCompletionRequest(req) {
14
26
  const messages = [];
15
27
  if (req.system) {
16
- const systemText = typeof req.system === "string" ? req.system : req.system.filter((b) => b.type === "text").map((b) => b.text ?? "").join("");
28
+ const systemText = typeof req.system === "string" ? req.system : Array.isArray(req.system) ? req.system.filter((b) => b != null && typeof b === "object" && b.type === "text").map((b) => b.text ?? "").join("") : "";
17
29
  if (systemText) messages.push({
18
30
  role: "system",
19
31
  content: systemText
20
32
  });
21
33
  }
22
- for (const msg of req.messages) if (msg.role === "user") {
23
- if (typeof msg.content !== "string" && Array.isArray(msg.content)) {
24
- const toolResults = msg.content.filter((b) => b.type === "tool_result");
25
- const textBlocks = msg.content.filter((b) => b.type === "text");
26
- if (toolResults.length > 0) {
27
- for (const tr of toolResults) {
28
- const resultContent = typeof tr.content === "string" ? tr.content : Array.isArray(tr.content) ? tr.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("") : "";
29
- messages.push({
30
- role: "tool",
31
- content: resultContent,
32
- tool_call_id: tr.tool_use_id
34
+ const reqMessages = Array.isArray(req.messages) ? req.messages : [];
35
+ for (const msg of reqMessages) {
36
+ if (!msg || typeof msg !== "object") continue;
37
+ if (msg.role === "user") {
38
+ if (typeof msg.content !== "string" && Array.isArray(msg.content)) {
39
+ const blocks = msg.content.filter((b) => b != null && typeof b === "object");
40
+ const toolResults = blocks.filter((b) => b.type === "tool_result");
41
+ const textBlocks = blocks.filter((b) => b.type === "text");
42
+ if (toolResults.length > 0) {
43
+ for (const tr of toolResults) {
44
+ const resultContent = typeof tr.content === "string" ? tr.content : Array.isArray(tr.content) ? tr.content.filter((b) => b != null && typeof b === "object" && b.type === "text").map((b) => b.text ?? "").join("") : "";
45
+ messages.push({
46
+ role: "tool",
47
+ content: resultContent,
48
+ tool_call_id: tr.tool_use_id
49
+ });
50
+ }
51
+ if (textBlocks.length > 0) messages.push({
52
+ role: "user",
53
+ content: textBlocks.map((b) => b.text ?? "").join("")
33
54
  });
55
+ continue;
34
56
  }
35
- if (textBlocks.length > 0) messages.push({
36
- role: "user",
37
- content: textBlocks.map((b) => b.text ?? "").join("")
38
- });
39
- continue;
40
57
  }
41
- }
42
- messages.push({
43
- role: "user",
44
- content: extractClaudeTextContent(msg.content)
45
- });
46
- } else if (msg.role === "assistant") if (typeof msg.content === "string") messages.push({
47
- role: "assistant",
48
- content: msg.content
49
- });
50
- else if (Array.isArray(msg.content)) {
51
- const toolUseBlocks = msg.content.filter((b) => b.type === "tool_use");
52
- const textContent = extractClaudeTextContent(msg.content);
53
- if (toolUseBlocks.length > 0) messages.push({
58
+ messages.push({
59
+ role: "user",
60
+ content: extractClaudeTextContent(msg.content)
61
+ });
62
+ } else if (msg.role === "assistant") if (typeof msg.content === "string") messages.push({
54
63
  role: "assistant",
55
- content: textContent || null,
56
- tool_calls: toolUseBlocks.map((b) => ({
57
- id: b.id ?? require_helpers.generateToolUseId(),
58
- type: "function",
59
- function: {
60
- name: b.name ?? "",
61
- arguments: typeof b.input === "string" ? b.input : JSON.stringify(b.input ?? {})
62
- }
63
- }))
64
+ content: msg.content
64
65
  });
65
- else messages.push({
66
+ else if (Array.isArray(msg.content)) {
67
+ const toolUseBlocks = msg.content.filter((b) => b != null && typeof b === "object" && b.type === "tool_use");
68
+ const textContent = extractClaudeTextContent(msg.content);
69
+ if (toolUseBlocks.length > 0) messages.push({
70
+ role: "assistant",
71
+ content: textContent || null,
72
+ tool_calls: toolUseBlocks.map((b) => ({
73
+ id: b.id ?? require_helpers.generateToolUseId(),
74
+ type: "function",
75
+ function: {
76
+ name: b.name ?? "",
77
+ arguments: typeof b.input === "string" ? b.input : JSON.stringify(b.input ?? {})
78
+ }
79
+ }))
80
+ });
81
+ else messages.push({
82
+ role: "assistant",
83
+ content: textContent || null
84
+ });
85
+ } else messages.push({
66
86
  role: "assistant",
67
- content: textContent || null
87
+ content: null
68
88
  });
69
- } else messages.push({
70
- role: "assistant",
71
- content: null
72
- });
89
+ }
73
90
  let tools;
74
91
  if (req.tools && req.tools.length > 0) tools = req.tools.map((t) => ({
75
92
  type: "function",
@@ -89,6 +106,88 @@ function claudeToCompletionRequest(req) {
89
106
  _endpointType: "chat"
90
107
  };
91
108
  }
109
+ /** True iff `req.thinking` is an object with `type === "enabled"`. */
110
+ function isThinkingEnabled(req) {
111
+ const t = req.thinking;
112
+ return typeof t === "object" && t !== null && t.type === "enabled";
113
+ }
114
+ /**
115
+ * True iff the user turn at `req.messages[userIndex]` carries a `tool_result`
116
+ * referencing one of `toolUseIds` — i.e. it answers the preceding assistant
117
+ * turn's `tool_use` block(s), making that assistant turn a tool-loop
118
+ * continuation point.
119
+ */
120
+ function userTurnAnswersToolUse(msg, toolUseIds) {
121
+ if (!msg || msg.role !== "user" || !Array.isArray(msg.content)) return false;
122
+ return msg.content.some((b) => b != null && typeof b === "object" && b.type === "tool_result" && typeof b.tool_use_id === "string" && toolUseIds.has(b.tool_use_id));
123
+ }
124
+ /**
125
+ * Validate the Anthropic extended-thinking request invariants on tool-loop
126
+ * continuation turns. Pure: returns the first detected `ThinkingViolation` or
127
+ * `null` when thinking is disabled or every in-scope turn is well-formed.
128
+ *
129
+ * Scope: an assistant turn is in-scope only when it (a) is array content
130
+ * carrying at least one `tool_use` block and (b) is followed by a matching
131
+ * `tool_result` on the next user turn. Text-only / `end_turn` turns, string or
132
+ * empty turns, and trailing unanswered `tool_use` turns are all exempt.
133
+ *
134
+ * Scope detection assumes well-formed Anthropic transcripts: `tool_use` ids are
135
+ * unique, and a tool_use turn is answered by the immediately-following user
136
+ * turn's `tool_result` (adjacency). Malformed shapes — non-adjacent answers,
137
+ * idless `tool_use` blocks, or a `tool_result` separated from its `tool_use` by
138
+ * intervening turns — are intentionally treated as out-of-scope (and therefore
139
+ * not 400'd) rather than validated, since the real Anthropic API would never
140
+ * have produced them.
141
+ */
142
+ function validateThinkingInvariants(req) {
143
+ if (!isThinkingEnabled(req)) return null;
144
+ const messages = Array.isArray(req.messages) ? req.messages : [];
145
+ for (let i = 0; i < messages.length; i++) {
146
+ const msg = messages[i];
147
+ if (!msg || typeof msg !== "object") continue;
148
+ if (msg.role !== "assistant" || !Array.isArray(msg.content) || msg.content.length === 0) continue;
149
+ const toolUseIds = /* @__PURE__ */ new Set();
150
+ for (const b of msg.content) {
151
+ if (!b || typeof b !== "object") continue;
152
+ if (b.type === "tool_use" && typeof b.id === "string") toolUseIds.add(b.id);
153
+ }
154
+ if (toolUseIds.size === 0) continue;
155
+ if (!userTurnAnswersToolUse(messages[i + 1], toolUseIds)) continue;
156
+ const first = msg.content[0];
157
+ if (!first || typeof first !== "object") return {
158
+ kind: "missing_thinking_first",
159
+ messageIndex: i,
160
+ observedFirstBlockType: void 0
161
+ };
162
+ if (first.type !== "thinking" && first.type !== "redacted_thinking") return {
163
+ kind: "missing_thinking_first",
164
+ messageIndex: i,
165
+ observedFirstBlockType: first.type
166
+ };
167
+ if (first.type === "thinking") {
168
+ if (typeof first.signature !== "string" || first.signature.length === 0) return {
169
+ kind: "missing_signature",
170
+ messageIndex: i
171
+ };
172
+ }
173
+ if (first.type === "redacted_thinking") {
174
+ if (typeof first.data !== "string" || first.data.length === 0) return {
175
+ kind: "dropped_redacted_thinking",
176
+ messageIndex: i
177
+ };
178
+ }
179
+ }
180
+ return null;
181
+ }
182
+ /** Render the Anthropic-shaped 400 error message for a thinking violation. */
183
+ function thinkingViolationMessage(v) {
184
+ const prefix = `messages.${v.messageIndex}.content.0`;
185
+ switch (v.kind) {
186
+ case "missing_thinking_first": return `${prefix}: when \`thinking\` is enabled, a tool-loop continuation assistant turn must begin with a \`thinking\` block; got \`${v.observedFirstBlockType ?? "unknown"}\`.`;
187
+ case "missing_signature": return `${prefix}: the leading \`thinking\` block is missing a non-empty \`signature\`.`;
188
+ case "dropped_redacted_thinking": return `${prefix}: the leading \`redacted_thinking\` block must preserve its \`data\`.`;
189
+ }
190
+ }
92
191
  function claudeStopReason(finishReason, defaultReason) {
93
192
  if (!finishReason) return defaultReason;
94
193
  if (finishReason === "stop") return "end_turn";
@@ -106,9 +205,44 @@ function claudeUsage(overrides) {
106
205
  output_tokens: overrides.usage.output_tokens ?? overrides.usage.completion_tokens ?? 0
107
206
  };
108
207
  }
109
- function buildClaudeTextStreamEvents(content, model, chunkSize, reasoning, overrides) {
208
+ /**
209
+ * Emit faithful Anthropic `redacted_thinking` content-block events at the head
210
+ * of a streamed turn — one `content_block_start` (carrying the opaque `data`)
211
+ * and `content_block_stop` per recorded block, with NO deltas (real Anthropic
212
+ * delivers the encrypted reasoning entirely on the start event). Returns the
213
+ * next free block index. When `redactedThinking` is empty/absent this is a
214
+ * no-op, so no events are added.
215
+ *
216
+ * Leading placement keeps the turn round-trip-safe for the extended-thinking
217
+ * leading-block invariant (a thinking / redacted_thinking block must come
218
+ * first). It does NOT reproduce real Anthropic's stream order: aimock joins all
219
+ * plaintext reasoning into one block and emits all redacted blocks ahead of it,
220
+ * whereas real Anthropic interleaves `thinking` and `redacted_thinking` in the
221
+ * order they occurred. The interleaving between the two is not preserved.
222
+ */
223
+ function pushRedactedThinkingStreamEvents(events, startIndex, redactedThinking) {
224
+ let blockIndex = startIndex;
225
+ for (const data of redactedThinking ?? []) {
226
+ events.push({
227
+ type: "content_block_start",
228
+ index: blockIndex,
229
+ content_block: {
230
+ type: "redacted_thinking",
231
+ data
232
+ }
233
+ });
234
+ events.push({
235
+ type: "content_block_stop",
236
+ index: blockIndex
237
+ });
238
+ blockIndex++;
239
+ }
240
+ return blockIndex;
241
+ }
242
+ function buildClaudeTextStreamEvents(content, model, chunkSize, reasoning, overrides, reasoningSignature, redactedThinking) {
110
243
  const msgId = overrides?.id ?? require_helpers.generateMessageId();
111
244
  const effectiveModel = overrides?.model ?? model;
245
+ const signature = reasoningSignature ?? PLACEHOLDER_SIGNATURE;
112
246
  const events = [];
113
247
  events.push({
114
248
  type: "message_start",
@@ -124,6 +258,7 @@ function buildClaudeTextStreamEvents(content, model, chunkSize, reasoning, overr
124
258
  }
125
259
  });
126
260
  let blockIndex = 0;
261
+ blockIndex = pushRedactedThinkingStreamEvents(events, blockIndex, redactedThinking);
127
262
  if (reasoning) {
128
263
  events.push({
129
264
  type: "content_block_start",
@@ -150,7 +285,7 @@ function buildClaudeTextStreamEvents(content, model, chunkSize, reasoning, overr
150
285
  index: blockIndex,
151
286
  delta: {
152
287
  type: "signature_delta",
153
- signature: ""
288
+ signature
154
289
  }
155
290
  });
156
291
  events.push({
@@ -193,9 +328,10 @@ function buildClaudeTextStreamEvents(content, model, chunkSize, reasoning, overr
193
328
  events.push({ type: "message_stop" });
194
329
  return events;
195
330
  }
196
- function buildClaudeToolCallStreamEvents(toolCalls, model, chunkSize, logger, overrides) {
331
+ function buildClaudeToolCallStreamEvents(toolCalls, model, chunkSize, logger, reasoning, overrides, reasoningSignature, redactedThinking) {
197
332
  const msgId = overrides?.id ?? require_helpers.generateMessageId();
198
333
  const effectiveModel = overrides?.model ?? model;
334
+ const signature = reasoningSignature ?? PLACEHOLDER_SIGNATURE;
199
335
  const events = [];
200
336
  events.push({
201
337
  type: "message_start",
@@ -210,8 +346,44 @@ function buildClaudeToolCallStreamEvents(toolCalls, model, chunkSize, logger, ov
210
346
  usage: claudeUsage(overrides)
211
347
  }
212
348
  });
213
- for (let idx = 0; idx < toolCalls.length; idx++) {
214
- const tc = toolCalls[idx];
349
+ let blockIndex = 0;
350
+ blockIndex = pushRedactedThinkingStreamEvents(events, blockIndex, redactedThinking);
351
+ if (reasoning) {
352
+ events.push({
353
+ type: "content_block_start",
354
+ index: blockIndex,
355
+ content_block: {
356
+ type: "thinking",
357
+ thinking: "",
358
+ signature: ""
359
+ }
360
+ });
361
+ for (let i = 0; i < reasoning.length; i += chunkSize) {
362
+ const slice = reasoning.slice(i, i + chunkSize);
363
+ events.push({
364
+ type: "content_block_delta",
365
+ index: blockIndex,
366
+ delta: {
367
+ type: "thinking_delta",
368
+ thinking: slice
369
+ }
370
+ });
371
+ }
372
+ events.push({
373
+ type: "content_block_delta",
374
+ index: blockIndex,
375
+ delta: {
376
+ type: "signature_delta",
377
+ signature
378
+ }
379
+ });
380
+ events.push({
381
+ type: "content_block_stop",
382
+ index: blockIndex
383
+ });
384
+ blockIndex++;
385
+ }
386
+ for (const tc of toolCalls) {
215
387
  const toolUseId = tc.id || require_helpers.generateToolUseId();
216
388
  let argsObj;
217
389
  try {
@@ -223,7 +395,7 @@ function buildClaudeToolCallStreamEvents(toolCalls, model, chunkSize, logger, ov
223
395
  const argsJson = JSON.stringify(argsObj);
224
396
  events.push({
225
397
  type: "content_block_start",
226
- index: idx,
398
+ index: blockIndex,
227
399
  content_block: {
228
400
  type: "tool_use",
229
401
  id: toolUseId,
@@ -235,7 +407,7 @@ function buildClaudeToolCallStreamEvents(toolCalls, model, chunkSize, logger, ov
235
407
  const slice = argsJson.slice(i, i + chunkSize);
236
408
  events.push({
237
409
  type: "content_block_delta",
238
- index: idx,
410
+ index: blockIndex,
239
411
  delta: {
240
412
  type: "input_json_delta",
241
413
  partial_json: slice
@@ -244,8 +416,9 @@ function buildClaudeToolCallStreamEvents(toolCalls, model, chunkSize, logger, ov
244
416
  }
245
417
  events.push({
246
418
  type: "content_block_stop",
247
- index: idx
419
+ index: blockIndex
248
420
  });
421
+ blockIndex++;
249
422
  }
250
423
  events.push({
251
424
  type: "message_delta",
@@ -258,12 +431,31 @@ function buildClaudeToolCallStreamEvents(toolCalls, model, chunkSize, logger, ov
258
431
  events.push({ type: "message_stop" });
259
432
  return events;
260
433
  }
261
- function buildClaudeTextResponse(content, model, reasoning, overrides) {
434
+ /**
435
+ * Push faithful Anthropic `redacted_thinking` content blocks (each a
436
+ * `{ type: "redacted_thinking", data }`) onto a non-streaming content array, in
437
+ * recorded order. No-op when `redactedThinking` is empty/absent, so no blocks
438
+ * are added.
439
+ *
440
+ * Leading placement keeps the turn round-trip-safe for the extended-thinking
441
+ * leading-block invariant. It does NOT reproduce real Anthropic's block order:
442
+ * aimock joins all plaintext reasoning into one block and emits all redacted
443
+ * blocks ahead of it, whereas real Anthropic interleaves `thinking` and
444
+ * `redacted_thinking` in occurrence order. The interleaving is not preserved.
445
+ */
446
+ function pushRedactedThinkingBlocks(contentBlocks, redactedThinking) {
447
+ for (const data of redactedThinking ?? []) contentBlocks.push({
448
+ type: "redacted_thinking",
449
+ data
450
+ });
451
+ }
452
+ function buildClaudeTextResponse(content, model, reasoning, overrides, reasoningSignature, redactedThinking) {
262
453
  const contentBlocks = [];
454
+ pushRedactedThinkingBlocks(contentBlocks, redactedThinking);
263
455
  if (reasoning) contentBlocks.push({
264
456
  type: "thinking",
265
457
  thinking: reasoning,
266
- signature: ""
458
+ signature: reasoningSignature ?? PLACEHOLDER_SIGNATURE
267
459
  });
268
460
  contentBlocks.push({
269
461
  type: "text",
@@ -280,35 +472,44 @@ function buildClaudeTextResponse(content, model, reasoning, overrides) {
280
472
  usage: claudeUsage(overrides)
281
473
  };
282
474
  }
283
- function buildClaudeToolCallResponse(toolCalls, model, logger, overrides) {
475
+ function buildClaudeToolCallResponse(toolCalls, model, logger, reasoning, overrides, reasoningSignature, redactedThinking) {
476
+ const contentBlocks = [];
477
+ pushRedactedThinkingBlocks(contentBlocks, redactedThinking);
478
+ if (reasoning) contentBlocks.push({
479
+ type: "thinking",
480
+ thinking: reasoning,
481
+ signature: reasoningSignature ?? PLACEHOLDER_SIGNATURE
482
+ });
483
+ for (const tc of toolCalls) {
484
+ let argsObj;
485
+ try {
486
+ argsObj = JSON.parse(tc.arguments || "{}");
487
+ } catch {
488
+ logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
489
+ argsObj = {};
490
+ }
491
+ contentBlocks.push({
492
+ type: "tool_use",
493
+ id: tc.id || require_helpers.generateToolUseId(),
494
+ name: tc.name,
495
+ input: argsObj
496
+ });
497
+ }
284
498
  return {
285
499
  id: overrides?.id ?? require_helpers.generateMessageId(),
286
500
  type: "message",
287
501
  role: overrides?.role ?? "assistant",
288
- content: toolCalls.map((tc) => {
289
- let argsObj;
290
- try {
291
- argsObj = JSON.parse(tc.arguments || "{}");
292
- } catch {
293
- logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
294
- argsObj = {};
295
- }
296
- return {
297
- type: "tool_use",
298
- id: tc.id || require_helpers.generateToolUseId(),
299
- name: tc.name,
300
- input: argsObj
301
- };
302
- }),
502
+ content: contentBlocks,
303
503
  model: overrides?.model ?? model,
304
504
  stop_reason: claudeStopReason(overrides?.finishReason, "tool_use"),
305
505
  stop_sequence: null,
306
506
  usage: claudeUsage(overrides)
307
507
  };
308
508
  }
309
- function buildClaudeContentWithToolCallsStreamEvents(content, toolCalls, model, chunkSize, logger, reasoning, overrides) {
509
+ function buildClaudeContentWithToolCallsStreamEvents(content, toolCalls, model, chunkSize, logger, reasoning, overrides, reasoningSignature, redactedThinking) {
310
510
  const msgId = overrides?.id ?? require_helpers.generateMessageId();
311
511
  const effectiveModel = overrides?.model ?? model;
512
+ const signature = reasoningSignature ?? PLACEHOLDER_SIGNATURE;
312
513
  const events = [];
313
514
  events.push({
314
515
  type: "message_start",
@@ -324,6 +525,7 @@ function buildClaudeContentWithToolCallsStreamEvents(content, toolCalls, model,
324
525
  }
325
526
  });
326
527
  let blockIndex = 0;
528
+ blockIndex = pushRedactedThinkingStreamEvents(events, blockIndex, redactedThinking);
327
529
  if (reasoning) {
328
530
  events.push({
329
531
  type: "content_block_start",
@@ -350,7 +552,7 @@ function buildClaudeContentWithToolCallsStreamEvents(content, toolCalls, model,
350
552
  index: blockIndex,
351
553
  delta: {
352
554
  type: "signature_delta",
353
- signature: ""
555
+ signature
354
556
  }
355
557
  });
356
558
  events.push({
@@ -431,12 +633,13 @@ function buildClaudeContentWithToolCallsStreamEvents(content, toolCalls, model,
431
633
  events.push({ type: "message_stop" });
432
634
  return events;
433
635
  }
434
- function buildClaudeContentWithToolCallsResponse(content, toolCalls, model, logger, reasoning, overrides) {
636
+ function buildClaudeContentWithToolCallsResponse(content, toolCalls, model, logger, reasoning, overrides, reasoningSignature, redactedThinking) {
435
637
  const contentBlocks = [];
638
+ pushRedactedThinkingBlocks(contentBlocks, redactedThinking);
436
639
  if (reasoning) contentBlocks.push({
437
640
  type: "thinking",
438
641
  thinking: reasoning,
439
- signature: ""
642
+ signature: reasoningSignature ?? PLACEHOLDER_SIGNATURE
440
643
  });
441
644
  contentBlocks.push({
442
645
  type: "text",
@@ -517,10 +720,38 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
517
720
  } }));
518
721
  return;
519
722
  }
723
+ const thinkingViolation = validateThinkingInvariants(claudeReq);
724
+ if (thinkingViolation) {
725
+ const effectiveStrict = require_helpers.resolveStrictMode(defaults.strict, req.headers);
726
+ const violationMessage = thinkingViolationMessage(thinkingViolation);
727
+ if (effectiveStrict) {
728
+ logger.error(`THINKING: ${violationMessage}`);
729
+ journal.add({
730
+ method: req.method ?? "POST",
731
+ path: req.url ?? "/v1/messages",
732
+ headers: require_helpers.flattenHeaders(req.headers),
733
+ body: null,
734
+ response: {
735
+ status: 400,
736
+ fixture: null,
737
+ ...require_helpers.strictOverrideField(defaults.strict, req.headers)
738
+ }
739
+ });
740
+ require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({
741
+ type: "error",
742
+ error: {
743
+ type: "invalid_request_error",
744
+ message: violationMessage
745
+ }
746
+ }));
747
+ return;
748
+ }
749
+ logger.warn(`THINKING: ${violationMessage} (strict off — replaying anyway)`);
750
+ }
520
751
  const completionReq = claudeToCompletionRequest(claudeReq);
521
752
  completionReq._context = require_helpers.getContext(req);
522
753
  const testId = require_helpers.getTestId(req);
523
- const fixture = require_router.matchFixture(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
754
+ const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
524
755
  if (fixture) {
525
756
  journal.incrementFixtureMatchCount(fixture, fixtures, testId);
526
757
  logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
@@ -538,8 +769,8 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
538
769
  if (!fixture) {
539
770
  if (require_helpers.resolveStrictMode(defaults.strict, req.headers)) {
540
771
  const strictStatus = 503;
541
- const strictMessage = "Strict mode: no fixture matched";
542
- logger.error(`STRICT: No fixture matched for ${req.method ?? "POST"} ${req.url ?? "/v1/messages"}`);
772
+ const strictMessage = require_helpers.strictNoMatchMessage(skippedBySequenceOrTurn);
773
+ logger.error(require_helpers.strictNoMatchLogLine(req.method ?? "POST", req.url ?? "/v1/messages", skippedBySequenceOrTurn));
543
774
  journal.add({
544
775
  method: req.method ?? "POST",
545
776
  path: req.url ?? "/v1/messages",
@@ -620,6 +851,9 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
620
851
  if (require_helpers.isContentWithToolCallsResponse(response)) {
621
852
  if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Claude Messages API — ignoring");
622
853
  const overrides = require_helpers.extractOverrides(response);
854
+ const effectiveStrict = require_helpers.resolveStrictMode(defaults.strict, req.headers);
855
+ const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger);
856
+ const { reasoningSignature: effReasoningSignature, redactedThinking: effRedactedThinking } = require_helpers.resolveReasoningArtifactsForModel(response.reasoningSignature, response.redactedThinking, completionReq.model, effectiveStrict, defaults.logger);
623
857
  const journalEntry = journal.add({
624
858
  method: req.method ?? "POST",
625
859
  path: req.url ?? "/v1/messages",
@@ -631,11 +865,11 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
631
865
  }
632
866
  });
633
867
  if (claudeReq.stream !== true) {
634
- const body = buildClaudeContentWithToolCallsResponse(response.content, response.toolCalls, completionReq.model, logger, response.reasoning, overrides);
868
+ const body = buildClaudeContentWithToolCallsResponse(response.content, response.toolCalls, completionReq.model, logger, effReasoning, overrides, effReasoningSignature, effRedactedThinking);
635
869
  res.writeHead(200, { "Content-Type": "application/json" });
636
870
  res.end(JSON.stringify(body));
637
871
  } else {
638
- const events = buildClaudeContentWithToolCallsStreamEvents(response.content, response.toolCalls, completionReq.model, chunkSize, logger, response.reasoning, overrides);
872
+ const events = buildClaudeContentWithToolCallsStreamEvents(response.content, response.toolCalls, completionReq.model, chunkSize, logger, effReasoning, overrides, effReasoningSignature, effRedactedThinking);
639
873
  const interruption = require_interruption.createInterruptionSignal(fixture);
640
874
  if (!await writeClaudeSSEStream(res, events, {
641
875
  latency,
@@ -656,6 +890,9 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
656
890
  if (require_helpers.isTextResponse(response)) {
657
891
  if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Claude Messages API — ignoring");
658
892
  const overrides = require_helpers.extractOverrides(response);
893
+ const effectiveStrict = require_helpers.resolveStrictMode(defaults.strict, req.headers);
894
+ const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger);
895
+ const { reasoningSignature: effReasoningSignature, redactedThinking: effRedactedThinking } = require_helpers.resolveReasoningArtifactsForModel(response.reasoningSignature, response.redactedThinking, completionReq.model, effectiveStrict, defaults.logger);
659
896
  const journalEntry = journal.add({
660
897
  method: req.method ?? "POST",
661
898
  path: req.url ?? "/v1/messages",
@@ -667,11 +904,11 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
667
904
  }
668
905
  });
669
906
  if (claudeReq.stream !== true) {
670
- const body = buildClaudeTextResponse(response.content, completionReq.model, response.reasoning, overrides);
907
+ const body = buildClaudeTextResponse(response.content, completionReq.model, effReasoning, overrides, effReasoningSignature, effRedactedThinking);
671
908
  res.writeHead(200, { "Content-Type": "application/json" });
672
909
  res.end(JSON.stringify(body));
673
910
  } else {
674
- const events = buildClaudeTextStreamEvents(response.content, completionReq.model, chunkSize, response.reasoning, overrides);
911
+ const events = buildClaudeTextStreamEvents(response.content, completionReq.model, chunkSize, effReasoning, overrides, effReasoningSignature, effRedactedThinking);
675
912
  const interruption = require_interruption.createInterruptionSignal(fixture);
676
913
  if (!await writeClaudeSSEStream(res, events, {
677
914
  latency,
@@ -692,6 +929,9 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
692
929
  if (require_helpers.isToolCallResponse(response)) {
693
930
  if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Claude Messages API — ignoring");
694
931
  const overrides = require_helpers.extractOverrides(response);
932
+ const effectiveStrict = require_helpers.resolveStrictMode(defaults.strict, req.headers);
933
+ const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger);
934
+ const { reasoningSignature: effReasoningSignature, redactedThinking: effRedactedThinking } = require_helpers.resolveReasoningArtifactsForModel(response.reasoningSignature, response.redactedThinking, completionReq.model, effectiveStrict, defaults.logger);
695
935
  const journalEntry = journal.add({
696
936
  method: req.method ?? "POST",
697
937
  path: req.url ?? "/v1/messages",
@@ -703,11 +943,11 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
703
943
  }
704
944
  });
705
945
  if (claudeReq.stream !== true) {
706
- const body = buildClaudeToolCallResponse(response.toolCalls, completionReq.model, logger, overrides);
946
+ const body = buildClaudeToolCallResponse(response.toolCalls, completionReq.model, logger, effReasoning, overrides, effReasoningSignature, effRedactedThinking);
707
947
  res.writeHead(200, { "Content-Type": "application/json" });
708
948
  res.end(JSON.stringify(body));
709
949
  } else {
710
- const events = buildClaudeToolCallStreamEvents(response.toolCalls, completionReq.model, chunkSize, logger, overrides);
950
+ const events = buildClaudeToolCallStreamEvents(response.toolCalls, completionReq.model, chunkSize, logger, effReasoning, overrides, effReasoningSignature, effRedactedThinking);
711
951
  const interruption = require_interruption.createInterruptionSignal(fixture);
712
952
  if (!await writeClaudeSSEStream(res, events, {
713
953
  latency,