diff --git a/packages/ai/src/protocols/bedrock-converse.ts b/packages/ai/src/protocols/bedrock-converse.ts index 60325a0936d..fe968bb98ba 100644 --- a/packages/ai/src/protocols/bedrock-converse.ts +++ b/packages/ai/src/protocols/bedrock-converse.ts @@ -555,7 +555,7 @@ interface ParserState { readonly hasToolCalls: boolean readonly lifecycle: Lifecycle.State readonly reasoningSignatures: Readonly> - readonly reasoningRedactedContent: Readonly>> + readonly reasoningRedactedContent: Readonly> } const encodeRedactedContent = (chunks: ReadonlyArray) => Encoding.encodeBase64(concatBytes(chunks)) @@ -605,10 +605,9 @@ const step = (state: ParserState, event: BedrockEvent) => const index = event.contentBlockDelta.contentBlockIndex const reasoning = event.contentBlockDelta.delta.reasoningContent const events: LLMEvent[] = [] - const redactedChunks = yield* (() => { + const redactedChunk = yield* (() => { if (reasoning.redactedContent === undefined) return Effect.succeed(undefined) return Effect.fromResult(Encoding.decodeBase64(reasoning.redactedContent)).pipe( - Effect.map((chunk) => [...(state.reasoningRedactedContent[index] ?? []), chunk]), Effect.mapError((cause) => ProviderShared.eventError( ADAPTER, @@ -619,17 +618,21 @@ const step = (state: ParserState, event: BedrockEvent) => ), ) })() - const redactedData = redactedChunks === undefined ? reasoning.data : encodeRedactedContent(redactedChunks) + const redactedChunks = state.reasoningRedactedContent[index] ?? [] + if (redactedChunk !== undefined) redactedChunks.push(redactedChunk) const metadata = (() => { if (reasoning.signature) return providerMetadata(state.providerMetadataKey, { signature: reasoning.signature }) - if (redactedData !== undefined) return providerMetadata(state.providerMetadataKey, { redactedData }) + if (redactedChunk === undefined && reasoning.data !== undefined) + return providerMetadata(state.providerMetadataKey, { redactedData: reasoning.data }) })() const lifecycle = (() => { - if (reasoning.text === undefined && metadata === undefined) return state.lifecycle - return Lifecycle.reasoningDelta(state.lifecycle, events, `reasoning-${index}`, reasoning.text ?? "", metadata) + if (reasoning.text !== undefined || metadata !== undefined) + return Lifecycle.reasoningDelta(state.lifecycle, events, `reasoning-${index}`, reasoning.text ?? "", metadata) + if (redactedChunk !== undefined) return Lifecycle.reasoningStart(state.lifecycle, events, `reasoning-${index}`) + return state.lifecycle })() const reasoningRedactedContent = (() => { - if (redactedChunks !== undefined) return { ...state.reasoningRedactedContent, [index]: redactedChunks } + if (redactedChunk !== undefined) return { ...state.reasoningRedactedContent, [index]: redactedChunks } if (reasoning.data === undefined) return state.reasoningRedactedContent return Object.fromEntries( Object.entries(state.reasoningRedactedContent).filter(([key]) => key !== String(index)), @@ -765,7 +768,19 @@ const onHalt = (state: ParserState): ReadonlyArray => { return state.finishReason.normalized })() const events: LLMEvent[] = [] - Lifecycle.finish(state.lifecycle, events, { + const lifecycle = Object.entries(state.reasoningRedactedContent).reduce((current, [index, chunks]) => { + const signature = state.reasoningSignatures[Number(index)] + return Lifecycle.reasoningEnd( + current, + events, + `reasoning-${index}`, + providerMetadata( + state.providerMetadataKey, + signature ? { signature } : { redactedData: encodeRedactedContent(chunks) }, + ), + ) + }, state.lifecycle) + Lifecycle.finish(lifecycle, events, { reason: { ...state.finishReason, normalized, diff --git a/packages/ai/test/provider/bedrock-converse.test.ts b/packages/ai/test/provider/bedrock-converse.test.ts index 9880f1b0fd6..f01d271ded9 100644 --- a/packages/ai/test/provider/bedrock-converse.test.ts +++ b/packages/ai/test/provider/bedrock-converse.test.ts @@ -1301,11 +1301,11 @@ describe("Bedrock Converse route", () => { ), ), ) - expect(response.events.filter((event) => event.type === "reasoning-delta" && event.text === "").at(-1)).toEqual({ - type: "reasoning-delta", + expect(response.events.filter((event) => event.type === "reasoning-delta")).toEqual([]) + expect(response.events.find((event) => event.type === "reasoning-start")).toEqual({ + type: "reasoning-start", id: "reasoning-0", - text: "", - providerMetadata: { bedrock: { redactedData } }, + providerMetadata: undefined, }) expect(response.events.find((event) => event.type === "reasoning-end")).toEqual({ type: "reasoning-end", @@ -1383,6 +1383,13 @@ describe("Bedrock Converse route", () => { ), ) + expect(response.events.filter((event) => event.type === "reasoning-delta")).toEqual([]) + expect(response.events.find((event) => event.type === "reasoning-end")).toEqual({ + type: "reasoning-end", + id: "reasoning-0", + providerMetadata: { bedrock: { redactedData: "AQID" } }, + text: undefined, + }) expect(response.message.content).toEqual([ { type: "reasoning", text: "", providerMetadata: { bedrock: { redactedData: "AQID" } } }, ])