diff --git a/src/backend/drivers/ai-chat/providers/mistral/MistralAiProvider.test.ts b/src/backend/drivers/ai-chat/providers/mistral/MistralAiProvider.test.ts index 3c38ba1f8..54da23411 100644 --- a/src/backend/drivers/ai-chat/providers/mistral/MistralAiProvider.test.ts +++ b/src/backend/drivers/ai-chat/providers/mistral/MistralAiProvider.test.ts @@ -210,6 +210,51 @@ describe('MistralAIProvider.complete request shape', () => { expect(args.temperature).toBe(0.4); }); + it('forwards custom.prompt_mode as the SDK promptMode', async () => { + const { provider } = makeProvider(); + completeMock.mockResolvedValueOnce({ + choices: [ + { + message: { role: 'assistant', content: 'ok' }, + finishReason: 'stop', + }, + ], + usage: { promptTokens: 1, completionTokens: 1 }, + }); + + await withTestActor(() => + provider.complete({ + model: 'magistral-small-latest', + messages: [{ role: 'user', content: 'think' }], + custom: { prompt_mode: 'reasoning' }, + }), + ); + + expect(completeMock.mock.calls[0]![0].promptMode).toBe('reasoning'); + }); + + it('omits promptMode when custom does not carry prompt_mode', async () => { + const { provider } = makeProvider(); + completeMock.mockResolvedValueOnce({ + choices: [ + { + message: { role: 'assistant', content: 'ok' }, + finishReason: 'stop', + }, + ], + usage: { promptTokens: 1, completionTokens: 1 }, + }); + + await withTestActor(() => + provider.complete({ + model: 'mistral-small-2603', + messages: [{ role: 'user', content: 'hi' }], + }), + ); + + expect('promptMode' in completeMock.mock.calls[0]![0]).toBe(false); + }); + it('omits the `tools` key when no tools are supplied', async () => { const { provider } = makeProvider(); completeMock.mockResolvedValueOnce(baseCompletion); diff --git a/src/backend/drivers/ai-chat/providers/mistral/MistralAiProvider.ts b/src/backend/drivers/ai-chat/providers/mistral/MistralAiProvider.ts index 96de9da5a..1618cb8be 100644 --- a/src/backend/drivers/ai-chat/providers/mistral/MistralAiProvider.ts +++ b/src/backend/drivers/ai-chat/providers/mistral/MistralAiProvider.ts @@ -152,7 +152,19 @@ export class MistralAIProvider implements IChatProvider { temperature, normalize, response, + custom, }: ICompleteArguments): Promise { + // Mistral's reasoning prompt mode: with `prompt_mode: 'reasoning'`, + // magistral models return their thinking as structured ThinkChunk + // content (which the splitter below separates into `reasoning`) + // instead of inlining it as answer prose. Opt-in passthrough rather + // than a default because the API rejects it on accounts/models where + // the mode is not enabled ('Reasoning prompt mode is not enabled for + // this model', code 3051). + const customParams = + custom && typeof custom === 'object' && !Array.isArray(custom) + ? (custom as { prompt_mode?: 'reasoning' | null }) + : {}; messages = await OpenAIUtil.process_input_messages(messages); messages = this.#coerceImageUrls(messages); for (const message of messages) { @@ -177,6 +189,9 @@ export class MistralAIProvider implements IChatProvider { ]({ model: selectedModel.id, ...(tools ? { tools: tools as any[] } : {}), + ...(customParams.prompt_mode !== undefined + ? { promptMode: customParams.prompt_mode } + : {}), messages, maxTokens: max_tokens, temperature,