fix: guard empty MiniMax Anthropic messages (#74731)

Fixes #74589. Thanks @neeravmakwana and @DerekEXS.
This commit is contained in:
Neerav Makwana
2026-05-11 11:02:03 -04:00
committed by GitHub
parent bc33a2049d
commit 7bf4458bbe
3 changed files with 67 additions and 1 deletions

View File

@@ -60,6 +60,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Bonjour/Gateway: treat active ciao probing and fresh name-conflict renames as in-progress so the mDNS watchdog waits for probe settlement before retrying, preventing rapid re-advertise loops on Windows, WSL, and other multicast-hostile hosts. (#74778) Refs #74242. Thanks @fuller-stack-dev.
- Providers/MiniMax: send a minimal Anthropic-compatible user fallback when message conversion filters a turn to an empty payload, so MiniMax M2.7 no longer returns `chat content is empty` after tool-heavy sessions. Fixes #74589. Thanks @neeravmakwana and @DerekEXS.
- Cron: keep long manual cron runs active in the task registry until completion, preventing transient `lost` markers before durable recovery reconciles. Fixes #78233. (#78243) Thanks @Feelw00.
- Doctor/GitHub CLI: surface a `GH_CONFIG_DIR` hint when the GitHub skill is usable but `gh` auth lives under a different operator HOME than the agent process, without warning for disabled or filtered skills. Fixes #78063. (#78095) Thanks @tmimmanuel.
- Gateway: dedupe concurrent `send`, `poll`, and `message.action` requests while delivery is still in flight, preventing duplicate outbound work for the same idempotency key. (#68341) Thanks @thesomewhatyou.

View File

@@ -718,6 +718,61 @@ describe("anthropic transport stream", () => {
expect(toolUse.input).toEqual({});
});
it.each([
{
name: "empty history",
context: { messages: [] } as AnthropicStreamContext,
},
{
name: "blank user content",
context: {
messages: [
{
role: "user",
content: " \n\t ",
timestamp: 0,
},
],
} as AnthropicStreamContext,
},
])(
"sends a minimal user fallback when Anthropic message conversion has no content: $name",
async ({ context }) => {
await runTransportStream(
makeAnthropicTransportModel({
id: "MiniMax-M2.7",
name: "MiniMax M2.7",
provider: "minimax",
baseUrl: "https://api.minimax.io/anthropic",
}),
context,
{
apiKey: "sk-minimax-test",
} as AnthropicStreamOptions,
);
expect(latestAnthropicRequest().payload).toMatchObject({
model: "MiniMax-M2.7",
messages: [
{
role: "user",
content: [
{
type: "text",
text: ".",
cache_control: { type: "ephemeral" },
},
],
},
],
});
expect(guardedFetchMock).toHaveBeenCalledWith(
"https://api.minimax.io/anthropic/v1/messages",
expect.objectContaining({ method: "POST" }),
);
},
);
it.each([
["empty", ""],
["whitespace-only", " \n\t "],

View File

@@ -108,6 +108,8 @@ type MutableAssistantOutput = {
errorMessage?: string;
};
const EMPTY_ANTHROPIC_MESSAGES_FALLBACK_TEXT = ".";
function isClaudeOpus47Model(modelId: string): boolean {
return modelId.includes("opus-4-7") || modelId.includes("opus-4.7");
}
@@ -425,6 +427,12 @@ function convertAnthropicMessages(
return params;
}
function ensureNonEmptyAnthropicMessages(messages: Array<Record<string, unknown>>) {
return messages.length > 0
? messages
: [{ role: "user", content: EMPTY_ANTHROPIC_MESSAGES_FALLBACK_TEXT }];
}
function convertAnthropicTools(tools: Context["tools"], isOAuthToken: boolean) {
if (!tools) {
return [];
@@ -754,7 +762,9 @@ function buildAnthropicParams(
});
const params: Record<string, unknown> = {
model: model.id,
messages: convertAnthropicMessages(context.messages, model, isOAuthToken),
messages: ensureNonEmptyAnthropicMessages(
convertAnthropicMessages(context.messages, model, isOAuthToken),
),
max_tokens: maxTokens,
stream: true,
};