@@ -75,6 +75,14 @@ function converseStopReason(
7575 return overrideFinishReason ;
7676}
7777
78+ /**
79+ * Build Converse-format usage from fixture overrides.
80+ *
81+ * When no overrides are provided (the common case for mocks), all token
82+ * counts default to zero. This is intentional — aimock is a mock server
83+ * and does not perform real tokenisation. Callers that need non-zero
84+ * usage should supply explicit `usage` overrides in their fixture.
85+ */
7886function converseUsage ( overrides ?: ResponseOverrides ) : {
7987 inputTokens : number ;
8088 outputTokens : number ;
@@ -113,15 +121,13 @@ function buildBedrockStreamTextEvents(
113121 events . push ( {
114122 eventType : "contentBlockStart" ,
115123 payload : {
116- contentBlockIndex : blockIndex ,
117124 contentBlockStart : { contentBlockIndex : blockIndex , start : { type : "thinking" } } ,
118125 } ,
119126 } ) ;
120127 for ( let i = 0 ; i < reasoning . length ; i += chunkSize ) {
121128 events . push ( {
122129 eventType : "contentBlockDelta" ,
123130 payload : {
124- contentBlockIndex : blockIndex ,
125131 contentBlockDelta : {
126132 contentBlockIndex : blockIndex ,
127133 delta : { type : "thinking_delta" , thinking : reasoning . slice ( i , i + chunkSize ) } ,
@@ -136,15 +142,13 @@ function buildBedrockStreamTextEvents(
136142 events . push ( {
137143 eventType : "contentBlockStart" ,
138144 payload : {
139- contentBlockIndex : textBlockIndex ,
140145 contentBlockStart : { contentBlockIndex : textBlockIndex , start : { type : "text" } } ,
141146 } ,
142147 } ) ;
143148 for ( let i = 0 ; i < content . length ; i += chunkSize ) {
144149 events . push ( {
145150 eventType : "contentBlockDelta" ,
146151 payload : {
147- contentBlockIndex : textBlockIndex ,
148152 contentBlockDelta : {
149153 contentBlockIndex : textBlockIndex ,
150154 delta : { type : "text_delta" , text : content . slice ( i , i + chunkSize ) } ,
@@ -157,6 +161,11 @@ function buildBedrockStreamTextEvents(
157161 eventType : "messageStop" ,
158162 payload : { stopReason : converseStopReason ( overrides ?. finishReason , "end_turn" ) } ,
159163 } ) ;
164+ const usage = converseUsage ( overrides ) ;
165+ events . push ( {
166+ eventType : "metadata" ,
167+ payload : { metadata : { usage, metrics : { latencyMs : 0 } } } ,
168+ } ) ;
160169 return events ;
161170}
162171
@@ -172,15 +181,16 @@ function buildBedrockStreamContentWithToolCallsEvents(
172181 ...overrides ,
173182 finishReason : "stop" ,
174183 } ) ;
175- events . pop ( ) ;
184+ // Remove trailing metadata + messageStop events — we re-emit them after tool blocks
185+ events . pop ( ) ; // metadata
186+ events . pop ( ) ; // messageStop
176187 let blockIndex = reasoning ? 2 : 1 ;
177188
178189 for ( const tc of toolCalls ) {
179190 const toolUseId = tc . id || generateToolUseId ( ) ;
180191 events . push ( {
181192 eventType : "contentBlockStart" ,
182193 payload : {
183- contentBlockIndex : blockIndex ,
184194 contentBlockStart : {
185195 contentBlockIndex : blockIndex ,
186196 start : { toolUse : { toolUseId, name : tc . name } } ,
@@ -192,7 +202,6 @@ function buildBedrockStreamContentWithToolCallsEvents(
192202 events . push ( {
193203 eventType : "contentBlockDelta" ,
194204 payload : {
195- contentBlockIndex : blockIndex ,
196205 contentBlockDelta : {
197206 contentBlockIndex : blockIndex ,
198207 delta : { toolUse : { input : argsStr . slice ( i , i + chunkSize ) } } ,
@@ -207,6 +216,11 @@ function buildBedrockStreamContentWithToolCallsEvents(
207216 eventType : "messageStop" ,
208217 payload : { stopReason : converseStopReason ( overrides ?. finishReason , "tool_use" ) } ,
209218 } ) ;
219+ const usage = converseUsage ( overrides ) ;
220+ events . push ( {
221+ eventType : "metadata" ,
222+ payload : { metadata : { usage, metrics : { latencyMs : 0 } } } ,
223+ } ) ;
210224 return events ;
211225}
212226
@@ -226,7 +240,6 @@ function buildBedrockStreamToolCallEvents(
226240 events . push ( {
227241 eventType : "contentBlockStart" ,
228242 payload : {
229- contentBlockIndex : tcIdx ,
230243 contentBlockStart : {
231244 contentBlockIndex : tcIdx ,
232245 start : { toolUse : { toolUseId, name : tc . name } } ,
@@ -238,7 +251,6 @@ function buildBedrockStreamToolCallEvents(
238251 events . push ( {
239252 eventType : "contentBlockDelta" ,
240253 payload : {
241- contentBlockIndex : tcIdx ,
242254 contentBlockDelta : {
243255 contentBlockIndex : tcIdx ,
244256 delta : { toolUse : { input : argsStr . slice ( i , i + chunkSize ) } } ,
@@ -252,6 +264,11 @@ function buildBedrockStreamToolCallEvents(
252264 eventType : "messageStop" ,
253265 payload : { stopReason : converseStopReason ( overrides ?. finishReason , "tool_use" ) } ,
254266 } ) ;
267+ const usage = converseUsage ( overrides ) ;
268+ events . push ( {
269+ eventType : "metadata" ,
270+ payload : { metadata : { usage, metrics : { latencyMs : 0 } } } ,
271+ } ) ;
255272 return events ;
256273}
257274
@@ -260,6 +277,7 @@ function buildBedrockStreamToolCallEvents(
260277export function converseToCompletionRequest (
261278 req : ConverseRequest ,
262279 modelId : string ,
280+ logger ?: Logger ,
263281) : ChatCompletionRequest {
264282 const messages : ChatMessage [ ] = [ ] ;
265283
@@ -275,7 +293,9 @@ export function converseToCompletionRequest(
275293 if ( msg . role === "user" ) {
276294 // Check for toolResult blocks
277295 const toolResults = msg . content . filter ( ( b ) => b . toolResult ) ;
278- const textBlocks = msg . content . filter ( ( b ) => b . text !== undefined && ! b . toolResult ) ;
296+ const textBlocks = msg . content . filter (
297+ ( b ) => b . text !== undefined && b . text !== "" && ! b . toolResult ,
298+ ) ;
279299
280300 if ( toolResults . length > 0 ) {
281301 for ( const block of toolResults ) {
@@ -298,21 +318,21 @@ export function converseToCompletionRequest(
298318
299319 // Plain user message
300320 const text = msg . content
301- . filter ( ( b ) => b . text !== undefined )
321+ . filter ( ( b ) => b . text !== undefined && b . text !== "" )
302322 . map ( ( b ) => b . text ?? "" )
303323 . join ( "" ) ;
304324 messages . push ( { role : "user" , content : text } ) ;
305325 } else if ( msg . role === "assistant" ) {
306326 const toolUseBlocks = msg . content . filter ( ( b ) => b . toolUse ) ;
307327 const textContent = msg . content
308- . filter ( ( b ) => b . text !== undefined )
328+ . filter ( ( b ) => b . text !== undefined && b . text !== "" )
309329 . map ( ( b ) => b . text ?? "" )
310330 . join ( "" ) ;
311331
312332 if ( toolUseBlocks . length > 0 ) {
313333 messages . push ( {
314334 role : "assistant" ,
315- content : textContent || null ,
335+ content : textContent ?? null ,
316336 tool_calls : toolUseBlocks . map ( ( b ) => ( {
317337 id : b . toolUse ! . toolUseId ,
318338 type : "function" as const ,
@@ -323,7 +343,12 @@ export function converseToCompletionRequest(
323343 } ) ) ,
324344 } ) ;
325345 } else {
326- messages . push ( { role : "assistant" , content : textContent || null } ) ;
346+ messages . push ( { role : "assistant" , content : textContent ?? null } ) ;
347+ }
348+ } else {
349+ const warnMsg = `Unexpected message role "${ msg . role } " in Converse request — skipping` ;
350+ if ( logger ) {
351+ logger . warn ( warnMsg ) ;
327352 }
328353 }
329354 }
@@ -518,7 +543,7 @@ export async function handleConverse(
518543 return ;
519544 }
520545
521- const completionReq = converseToCompletionRequest ( converseReq , modelId ) ;
546+ const completionReq = converseToCompletionRequest ( converseReq , modelId , logger ) ;
522547 completionReq . _endpointType = "chat" ;
523548
524549 const testId = getTestId ( req ) ;
@@ -623,7 +648,7 @@ export async function handleConverse(
623648 const errBody = {
624649 type : "error" ,
625650 error : {
626- type : response . error . type || "invalid_request_error" ,
651+ type : response . error . type ?? "invalid_request_error" ,
627652 message : response . error . message ,
628653 } ,
629654 } ;
@@ -681,6 +706,11 @@ export async function handleConverse(
681706
682707 // Tool call response
683708 if ( isToolCallResponse ( response ) ) {
709+ if ( "webSearches" in response ) {
710+ logger . warn (
711+ "webSearches in fixture response are not supported for Bedrock Converse API — ignoring" ,
712+ ) ;
713+ }
684714 const overrides = extractOverrides ( response ) ;
685715 journal . add ( {
686716 method : req . method ?? "POST" ,
@@ -775,7 +805,8 @@ export async function handleConverseStream(
775805 return ;
776806 }
777807
778- const completionReq = converseToCompletionRequest ( converseReq , modelId ) ;
808+ const completionReq = converseToCompletionRequest ( converseReq , modelId , logger ) ;
809+ completionReq . stream = true ;
779810 completionReq . _endpointType = "chat" ;
780811
781812 const testId = getTestId ( req ) ;
@@ -882,7 +913,7 @@ export async function handleConverseStream(
882913 const errBody = {
883914 type : "error" ,
884915 error : {
885- type : response . error . type || "invalid_request_error" ,
916+ type : response . error . type ?? "invalid_request_error" ,
886917 message : response . error . message ,
887918 } ,
888919 } ;
@@ -968,6 +999,11 @@ export async function handleConverseStream(
968999
9691000 // Tool call response — stream as Event Stream
9701001 if ( isToolCallResponse ( response ) ) {
1002+ if ( "webSearches" in response ) {
1003+ logger . warn (
1004+ "webSearches in fixture response are not supported for Bedrock Converse API — ignoring" ,
1005+ ) ;
1006+ }
9711007 const overrides = extractOverrides ( response ) ;
9721008 const journalEntry = journal . add ( {
9731009 method : req . method ?? "POST" ,
0 commit comments