Don't output newline-only reasoning (#8990)

Co-authored-by: Roo Code <roomote@roocode.com>
This commit is contained in:
Matt Rubens 2025-11-02 23:17:59 -05:00 committed by GitHub
parent 613255c09a
commit 5b753f5855
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 105 additions and 2 deletions

View file

@ -228,6 +228,106 @@ describe("BaseOpenAiCompatibleProvider", () => {
})
})
describe("reasoning_content field", () => {
it("should filter out whitespace-only reasoning_content", async () => {
mockCreate.mockImplementationOnce(() => {
return {
[Symbol.asyncIterator]: () => ({
next: vi
.fn()
.mockResolvedValueOnce({
done: false,
value: { choices: [{ delta: { reasoning_content: "\n" } }] },
})
.mockResolvedValueOnce({
done: false,
value: { choices: [{ delta: { reasoning_content: " " } }] },
})
.mockResolvedValueOnce({
done: false,
value: { choices: [{ delta: { reasoning_content: "\t\n " } }] },
})
.mockResolvedValueOnce({
done: false,
value: { choices: [{ delta: { content: "Regular content" } }] },
})
.mockResolvedValueOnce({ done: true }),
}),
}
})
const stream = handler.createMessage("system prompt", [])
const chunks = []
for await (const chunk of stream) {
chunks.push(chunk)
}
// Should only have the regular content, not the whitespace-only reasoning
expect(chunks).toEqual([{ type: "text", text: "Regular content" }])
})
it("should yield non-empty reasoning_content", async () => {
mockCreate.mockImplementationOnce(() => {
return {
[Symbol.asyncIterator]: () => ({
next: vi
.fn()
.mockResolvedValueOnce({
done: false,
value: { choices: [{ delta: { reasoning_content: "Thinking step 1" } }] },
})
.mockResolvedValueOnce({
done: false,
value: { choices: [{ delta: { reasoning_content: "\n" } }] },
})
.mockResolvedValueOnce({
done: false,
value: { choices: [{ delta: { reasoning_content: "Thinking step 2" } }] },
})
.mockResolvedValueOnce({ done: true }),
}),
}
})
const stream = handler.createMessage("system prompt", [])
const chunks = []
for await (const chunk of stream) {
chunks.push(chunk)
}
// Should only yield the non-empty reasoning content
expect(chunks).toEqual([
{ type: "reasoning", text: "Thinking step 1" },
{ type: "reasoning", text: "Thinking step 2" },
])
})
it("should handle reasoning_content with leading/trailing whitespace", async () => {
mockCreate.mockImplementationOnce(() => {
return {
[Symbol.asyncIterator]: () => ({
next: vi
.fn()
.mockResolvedValueOnce({
done: false,
value: { choices: [{ delta: { reasoning_content: " content with spaces " } }] },
})
.mockResolvedValueOnce({ done: true }),
}),
}
})
const stream = handler.createMessage("system prompt", [])
const chunks = []
for await (const chunk of stream) {
chunks.push(chunk)
}
// Should yield reasoning with spaces (only pure whitespace is filtered)
expect(chunks).toEqual([{ type: "reasoning", text: " content with spaces " }])
})
})
describe("Basic functionality", () => {
it("should create stream with correct parameters", async () => {
mockCreate.mockImplementationOnce(() => {

View file

@ -124,8 +124,11 @@ export abstract class BaseOpenAiCompatibleProvider<ModelName extends string>
}
}
if (delta && "reasoning_content" in delta && delta.reasoning_content) {
yield { type: "reasoning", text: (delta.reasoning_content as string | undefined) || "" }
if (delta && "reasoning_content" in delta) {
const reasoning_content = (delta.reasoning_content as string | undefined) || ""
if (reasoning_content?.trim()) {
yield { type: "reasoning", text: reasoning_content }
}
}
if (chunk.usage) {