When I stop the chat and submit a new message, I get errors like this":
‘‘fc_689b494b97b88196beca7f5359fc59bb0e21d6210d7020ab’ of type ‘function_call’ was provided without its required ‘reasoning’ item: ‘rs_689b49479dd48196bd47a3d1ffcadd930e21d6210d7020ab’."
To workaround, we have to use this conditional spaghetti like so, wondering if there a better way to do this.
function pruneProblematicParts(messages: UIMessage): UIMessage {
if (!Array.isArray(messages)) return messages;
const isIncompleteToolInput = (part: unknown): boolean => {
const p = part as { type?: string; state?: string };
if (typeof p?.type !== ‘string’) return false;
const isToolType = p.type === ‘dynamic-tool’ || p.type.startsWith(‘tool-’);
return isToolType && p.state === ‘input-streaming’;
};
const isReasoningPart = (part: unknown): boolean => {
return (part as { type?: string }).type === ‘reasoning’;
};
return messages
.map((*message*) => {
if (message.role !== ‘assistant’) return message;
// Drop incomplete tool input parts and any reasoning parts (reasoning is ephemeral and
// can be orphaned when a stream is stopped mid-flight)
const filteredParts = message.parts
.filter((*part*, *index*, *arr*) => {
if (isIncompleteToolInput(part)) return false;
// Only drop reasoning parts that are trailing (no following item)
if (isReasoningPart(part)) {
const isLast = index === arr.length - 1;
return !isLast;
}
return true;
})
// Strip provider-specific metadata that can create invalid provider item dependencies
.map((*part*) => {
// eslint-disable-next-line @typescript-eslint*/no-explicit-any*
const p: any = part as any;
if (p && typeof p === ‘object’) {
if (‘callProviderMetadata’ in p) {
// Remove function_call ids linking to missing reasoning items
delete p.callProviderMetadata;
}
if (‘providerMetadata’ in p) {
// Remove message/item ids to avoid strict item ordering requirements on resend
delete p.providerMetadata;
}
}
return p as typeof part;
});
// If no parts remain, drop the message entirely to avoid empty assistant messages
if (filteredParts.length === 0) {
return { …message, parts: } as UIMessage; // will be filtered out in the next step
}
return { …message, parts: filteredParts } as UIMessage;
})
.filter((*m*) => !(*m*.role === 'assistant' && *m*.parts.length === 0));
}