diff --git a/packages/local-runtime-v2/src/service/model-system/resolution/local-model-resolver.test.ts b/packages/local-runtime-v2/src/service/model-system/resolution/local-model-resolver.test.ts index a9124e94a..b8e4c32f3 100644 --- a/packages/local-runtime-v2/src/service/model-system/resolution/local-model-resolver.test.ts +++ b/packages/local-runtime-v2/src/service/model-system/resolution/local-model-resolver.test.ts @@ -1447,6 +1447,10 @@ describe('LocalModelResolver custom provider compat overrides', () => { expect(await systemPromptRoleFor(undefined)).toBe('developer'); }); + it('keeps the system role for a thinking model on the Mistral API', async () => { + expect(await systemPromptRoleFor(undefined, 'https://api.mistral.ai/v1')).toBe('system'); + }); + it.each([ 'https://api.siliconflow.cn/v1', 'https://api.siliconflow.com/v1', @@ -1474,6 +1478,8 @@ describe('LocalModelResolver custom provider compat overrides', () => { it.each([ ['https://api.openai.com/v1', 'developer'], ['https://api.deepseek.com/v1', 'system'], + ['https://api.mistral.ai.example/v1', 'developer'], + ['https://gateway.example/api.mistral.ai/v1', 'developer'], ['https://api.siliconflow.cn.example/v1', 'developer'], ['https://gateway.example/api.siliconflow.cn/v1', 'developer'], ['https://api.siliconflow.com.example/v1', 'developer'], @@ -1493,6 +1499,12 @@ describe('LocalModelResolver custom provider compat overrides', () => { expect(await systemPromptRoleFor(undefined, baseURL)).toBe(role); }); + it('honors an explicit developer-role override on the Mistral API', async () => { + expect( + await systemPromptRoleFor({ supportsDeveloperRole: true }, 'https://api.mistral.ai/v1'), + ).toBe('developer'); + }); + it.each([ 'https://api.siliconflow.cn/v1', 'https://api.siliconflow.com/v1', diff --git a/third_party/pi-mono/MINIMAX_CHANGES.md b/third_party/pi-mono/MINIMAX_CHANGES.md index 10dd06116..c9057b542 100644 --- a/third_party/pi-mono/MINIMAX_CHANGES.md +++ b/third_party/pi-mono/MINIMAX_CHANGES.md @@ -13,6 +13,14 @@ This directory vendors `pi-mono` as source so MiniMax can patch, validate, and s No upstream source files are changed in the baseline import. +### 2026-09-19 — preserve the system role for Mistral Chat Completions + +- Reason: thinking-enabled custom OpenAI-compatible connections to `api.mistral.ai` emitted `developer`, which is absent from the [Mistral Chat Completions message contract](https://docs.mistral.ai/api/endpoint/chat). [OpenClaw's compatibility defaults](https://github.com/openclaw/openclaw/blob/e2bcb1614de060927121bd72de850cee3a08d308/packages/ai/src/transports/openai-completions-compat.ts#L184-L210) also disable this role for the Mistral public endpoint. +- Affected package: `packages/ai` (`@earendil-works/pi-ai`), OpenAI Completions compatibility detection. +- Change: default to `system` only on the exact Mistral API host. Preserve thinking, explicit compatibility overrides, all other request options, and other endpoints. Native Mistral transport is unchanged. +- Upstream PR: not opened. +- Validation: outgoing-payload regressions in `packages/local-runtime-v2/src/service/model-system/resolution/local-model-resolver.test.ts`; the endpoint regression fails before the fix. No live Mistral requests or full CLI acceptance were performed; the decision is based on the official contract and current upstream implementation. + ### 2026-09-19 — preserve the system role for SiliconFlow - Reason: thinking-enabled OpenAI-compatible requests sent the `developer` role, which is absent from SiliconFlow's documented message schema. diff --git a/third_party/pi-mono/packages/ai/src/providers/openai-completions.ts b/third_party/pi-mono/packages/ai/src/providers/openai-completions.ts index 2c08bf0cf..4d7b2d0fa 100644 --- a/third_party/pi-mono/packages/ai/src/providers/openai-completions.ts +++ b/third_party/pi-mono/packages/ai/src/providers/openai-completions.ts @@ -1095,6 +1095,7 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAIComplet /^https?:\/\/(?:dashscope(?:-intl|-us)?|(?:coding(?:-intl)?|cn-hongkong)\.dashscope)\.aliyuncs\.com(?::\d+)?(?:\/|$)/i.test(baseUrl) || /^https?:\/\/[a-z0-9-]+\.(?:cn-beijing|cn-hongkong|ap-southeast-1|ap-northeast-1|eu-central-1|us-east-1)\.maas\.aliyuncs\.com(?::\d+)?(?:\/|$)/i.test(baseUrl); const isSiliconFlow = /^https?:\/\/api\.siliconflow\.(?:cn|com)(?::\d+)?(?:\/|$)/i.test(baseUrl); + const isMistralApi = /^https?:\/\/api\.mistral\.ai(?::\d+)?(?:\/|$)/i.test(baseUrl); const isOpenRouter = provider === "openrouter" || baseUrl.includes("openrouter.ai"); const isCloudflareWorkersAI = provider === "cloudflare-workers-ai" || baseUrl.includes("api.cloudflare.com"); const isCloudflareAiGateway = provider === "cloudflare-ai-gateway" || baseUrl.includes("gateway.ai.cloudflare.com"); @@ -1130,7 +1131,11 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAIComplet return { supportsStore: !isNonStandard, supportsDeveloperRole: - !isKimiCoding && !isDashScope && !isSiliconFlow && (isOpenRouterDeveloperRoleModel || (!isNonStandard && !isOpenRouter)), + !isKimiCoding && + !isDashScope && + !isSiliconFlow && + !isMistralApi && + (isOpenRouterDeveloperRoleModel || (!isNonStandard && !isOpenRouter)), supportsReasoningEffort: !isGrok && !isZai && !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia && !isAntLing, supportsUsageInStreaming: true,