Skip to content

Commit a40c25f

Browse files
committed
Handle thinking
1 parent 7a860f6 commit a40c25f

File tree

2 files changed

+80
-0
lines changed

2 files changed

+80
-0
lines changed

src/api/providers/__tests__/minimax.spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,43 @@ describe("MiniMaxHandler", () => {
178178
expect(firstChunk.value).toEqual({ type: "text", text: testContent })
179179
})
180180

181+
it("should handle reasoning tags (<think>) from stream", async () => {
182+
mockCreate.mockImplementationOnce(() => {
183+
return {
184+
[Symbol.asyncIterator]: () => ({
185+
next: vitest
186+
.fn()
187+
.mockResolvedValueOnce({
188+
done: false,
189+
value: { choices: [{ delta: { content: "<think>Let me think" } }] },
190+
})
191+
.mockResolvedValueOnce({
192+
done: false,
193+
value: { choices: [{ delta: { content: " about this</think>" } }] },
194+
})
195+
.mockResolvedValueOnce({
196+
done: false,
197+
value: { choices: [{ delta: { content: "The answer is 42" } }] },
198+
})
199+
.mockResolvedValueOnce({ done: true }),
200+
}),
201+
}
202+
})
203+
204+
const stream = handler.createMessage("system prompt", [])
205+
const chunks = []
206+
for await (const chunk of stream) {
207+
chunks.push(chunk)
208+
}
209+
210+
// XmlMatcher yields chunks as they're processed
211+
expect(chunks).toEqual([
212+
{ type: "reasoning", text: "Let me think" },
213+
{ type: "reasoning", text: " about this" },
214+
{ type: "text", text: "The answer is 42" },
215+
])
216+
})
217+
181218
it("createMessage should yield usage data from stream", async () => {
182219
mockCreate.mockImplementationOnce(() => {
183220
return {

src/api/providers/minimax.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
import { Anthropic } from "@anthropic-ai/sdk"
12
import { type MinimaxModelId, minimaxDefaultModelId, minimaxModels } from "@roo-code/types"
23

34
import type { ApiHandlerOptions } from "../../shared/api"
5+
import { XmlMatcher } from "../../utils/xml-matcher"
6+
import { ApiStream } from "../transform/stream"
7+
import type { ApiHandlerCreateMessageMetadata } from "../index"
48

59
import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
610

@@ -16,4 +20,43 @@ export class MiniMaxHandler extends BaseOpenAiCompatibleProvider<MinimaxModelId>
1620
defaultTemperature: 1.0,
1721
})
1822
}
23+
24+
override async *createMessage(
25+
systemPrompt: string,
26+
messages: Anthropic.Messages.MessageParam[],
27+
metadata?: ApiHandlerCreateMessageMetadata,
28+
): ApiStream {
29+
const stream = await this.createStream(systemPrompt, messages, metadata)
30+
31+
const matcher = new XmlMatcher(
32+
"think",
33+
(chunk) =>
34+
({
35+
type: chunk.matched ? "reasoning" : "text",
36+
text: chunk.data,
37+
}) as const,
38+
)
39+
40+
for await (const chunk of stream) {
41+
const delta = chunk.choices[0]?.delta
42+
43+
if (delta?.content) {
44+
for (const matcherChunk of matcher.update(delta.content)) {
45+
yield matcherChunk
46+
}
47+
}
48+
49+
if (chunk.usage) {
50+
yield {
51+
type: "usage",
52+
inputTokens: chunk.usage.prompt_tokens || 0,
53+
outputTokens: chunk.usage.completion_tokens || 0,
54+
}
55+
}
56+
}
57+
58+
for (const chunk of matcher.final()) {
59+
yield chunk
60+
}
61+
}
1962
}

0 commit comments

Comments
 (0)