diff --git a/src/api/providers/__tests__/mistral.spec.ts b/src/api/providers/__tests__/mistral.spec.ts index 73861ecdc0..747e962b54 100644 --- a/src/api/providers/__tests__/mistral.spec.ts +++ b/src/api/providers/__tests__/mistral.spec.ts @@ -118,6 +118,86 @@ describe("MistralHandler", () => { expect(results[0].text).toBe("Test response") }) + it("should handle thinking type in content array", async () => { + // Override the mock to return content with thinking type + mockCreate.mockImplementationOnce(async (_options) => { + const stream = { + [Symbol.asyncIterator]: async function* () { + yield { + data: { + choices: [ + { + delta: { + content: [ + { type: "thinking", text: "Let me think about this..." }, + { type: "text", text: "Here is my response" }, + ], + }, + index: 0, + }, + ], + }, + } + }, + } + return stream + }) + + const iterator = handler.createMessage(systemPrompt, messages) + const results: ApiStreamTextChunk[] = [] + + for await (const chunk of iterator) { + if ("text" in chunk) { + results.push(chunk as ApiStreamTextChunk) + } + } + + // Should only include the text type content, not thinking + expect(results.length).toBe(1) + expect(results[0].text).toBe("Here is my response") + }) + + it("should handle mixed content types gracefully", async () => { + // Override the mock to return various content types + mockCreate.mockImplementationOnce(async (_options) => { + const stream = { + [Symbol.asyncIterator]: async function* () { + yield { + data: { + choices: [ + { + delta: { + content: [ + { type: "thinking", text: "Processing..." }, + { type: "text", text: "First part" }, + { type: "unknown", data: "some data" }, + { type: "text", text: " Second part" }, + ], + }, + index: 0, + }, + ], + }, + } + }, + } + return stream + }) + + const iterator = handler.createMessage(systemPrompt, messages) + const results: ApiStreamTextChunk[] = [] + + for await (const chunk of iterator) { + if ("text" in chunk) { + results.push(chunk as ApiStreamTextChunk) + } + } + + // Should concatenate only text type content + expect(results.length).toBe(1) + expect(results[0].text).toBe("First part Second part") + }) + it("should handle errors gracefully", async () => { mockCreate.mockRejectedValueOnce(new Error("API Error")) await expect(handler.createMessage(systemPrompt, messages).next()).rejects.toThrow("API Error") diff --git a/src/api/providers/mistral.ts b/src/api/providers/mistral.ts index 7d48b9ef01..2ec74c166a 100644 --- a/src/api/providers/mistral.ts +++ b/src/api/providers/mistral.ts @@ -57,10 +57,17 @@ export class MistralHandler extends BaseProvider implements SingleCompletionHand if (typeof delta.content === "string") { content = delta.content } else if (Array.isArray(delta.content)) { - content = delta.content.map((c) => (c.type === "text" ? c.text : "")).join("") + // Handle array content, filtering out "thinking" type and other non-text types + content = delta.content + .filter((c: any) => c.type === "text") + .map((c: any) => c.text || "") + .join("") } - yield { type: "text", text: content } + // Only yield if we have actual content to send + if (content) { + yield { type: "text", text: content } + } } if (chunk.data.usage) { @@ -97,7 +104,11 @@ export class MistralHandler extends BaseProvider implements SingleCompletionHand const content = response.choices?.[0]?.message.content if (Array.isArray(content)) { - return content.map((c) => (c.type === "text" ? c.text : "")).join("") + // Handle array content, filtering out "thinking" type and other non-text types + return content + .filter((c: any) => c.type === "text") + .map((c: any) => c.text || "") + .join("") } return content || ""