Skip to content

Commit dc385a4

Browse files
committed
fix(ai-sdk): convert message-level reasoning_content to reasoning part
1 parent d29274c commit dc385a4

File tree

2 files changed

+58
-1
lines changed

2 files changed

+58
-1
lines changed

src/api/transform/__tests__/ai-sdk.spec.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,51 @@ describe("AI SDK conversion utilities", () => {
354354
],
355355
})
356356
})
357+
358+
it("converts assistant message-level reasoning_content to reasoning part", () => {
359+
const messages: Anthropic.Messages.MessageParam[] = [
360+
{
361+
role: "assistant",
362+
content: [{ type: "text", text: "Answer" }],
363+
reasoning_content: "Thinking...",
364+
} as any,
365+
]
366+
367+
const result = convertToAiSdkMessages(messages)
368+
369+
expect(result).toHaveLength(1)
370+
expect(result[0]).toEqual({
371+
role: "assistant",
372+
content: [
373+
{ type: "reasoning", text: "Thinking..." },
374+
{ type: "text", text: "Answer" },
375+
],
376+
})
377+
})
378+
379+
it("prefers message-level reasoning_content over reasoning blocks", () => {
380+
const messages: Anthropic.Messages.MessageParam[] = [
381+
{
382+
role: "assistant",
383+
content: [
384+
{ type: "reasoning" as any, text: "BLOCK" },
385+
{ type: "text", text: "Answer" },
386+
],
387+
reasoning_content: "MSG",
388+
} as any,
389+
]
390+
391+
const result = convertToAiSdkMessages(messages)
392+
393+
expect(result).toHaveLength(1)
394+
expect(result[0]).toEqual({
395+
role: "assistant",
396+
content: [
397+
{ type: "reasoning", text: "MSG" },
398+
{ type: "text", text: "Answer" },
399+
],
400+
})
401+
})
357402
})
358403

359404
describe("convertToolsForAiSdk", () => {

src/api/transform/ai-sdk.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ export function convertToAiSdkMessages(
127127
} else if (message.role === "assistant") {
128128
const textParts: string[] = []
129129
const reasoningParts: string[] = []
130+
const reasoningContent = (() => {
131+
const maybe = (message as unknown as { reasoning_content?: unknown }).reasoning_content
132+
return typeof maybe === "string" && maybe.length > 0 ? maybe : undefined
133+
})()
130134
const toolCalls: Array<{
131135
type: "tool-call"
132136
toolCallId: string
@@ -154,6 +158,10 @@ export function convertToAiSdkMessages(
154158
// Task stores reasoning as a content block (type: "reasoning") and Anthropic extended
155159
// thinking as (type: "thinking"). Convert both to AI SDK's reasoning part.
156160
if ((part as unknown as { type?: string }).type === "reasoning") {
161+
// If message-level reasoning_content is present, treat it as canonical and
162+
// avoid mixing it with content-block reasoning (which can cause duplication).
163+
if (reasoningContent) continue
164+
157165
const text = (part as unknown as { text?: string }).text
158166
if (typeof text === "string" && text.length > 0) {
159167
reasoningParts.push(text)
@@ -162,6 +170,8 @@ export function convertToAiSdkMessages(
162170
}
163171

164172
if ((part as unknown as { type?: string }).type === "thinking") {
173+
if (reasoningContent) continue
174+
165175
const thinking = (part as unknown as { thinking?: string }).thinking
166176
if (typeof thinking === "string" && thinking.length > 0) {
167177
reasoningParts.push(thinking)
@@ -176,7 +186,9 @@ export function convertToAiSdkMessages(
176186
| { type: "tool-call"; toolCallId: string; toolName: string; input: unknown }
177187
> = []
178188

179-
if (reasoningParts.length > 0) {
189+
if (reasoningContent) {
190+
content.push({ type: "reasoning", text: reasoningContent })
191+
} else if (reasoningParts.length > 0) {
180192
content.push({ type: "reasoning", text: reasoningParts.join("") })
181193
}
182194

0 commit comments

Comments
 (0)