mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 18:05:11 +03:00
Addresses the issue of parallel tool approvals freezing Assistant and causing UX ambiguity around what happens if only part of multiple dependent invocations is approved. Two layers at which this is addressed: 1. Tightens prompt to clarify approval tools must be issued one per step, not in parallel. 2. In case something slips past the prompt, this also auto-denies all but the first `approval-required` tool call when the model issues multiple in the same step, so the model is forced to reissue them sequentially. **Demo** The following chats demo me explicitly asking Assistant to run those approval-gated tools in parallel, and the Assistant correctly invokes them sequentially instead. | Parallel `execute_sql` request | Parallel `deploy_edge_function` request | |--------|--------| | <img width="1820" height="4240" alt="CleanShot 2026-06-16 at 16 20 23@2x" src="https://github.com/user-attachments/assets/3ddad61d-24d6-4e5b-8572-c261755f3a03" /> | <img width="1738" height="2726" alt="CleanShot 2026-06-16 at 16 22 59@2x" src="https://github.com/user-attachments/assets/77a0aa20-e3cb-4061-b270-ab2a96d1e64a" /> | As shown in [this trace](https://www.braintrust.dev/app/supabase.io/p/Assistant/trace?object_type=project_logs&object_id=5a8d02e5-b3b6-40cc-ba76-ecee286478f4&r=a3a37857-95df-4a95-a5a8-818ea305b2a5&s=a3a37857-95df-4a95-a5a8-818ea305b2a5), parallel tool calls are still allowed for context gathering tools that don't require approval: <img width="1051" height="517" alt="CleanShot 2026-06-16 at 16 25 42@2x" src="https://github.com/user-attachments/assets/00d208b1-6131-4b8e-910e-e92fd2a79a5b" /> Closes AI-803 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Improvements** * Approval-required operations are now processed sequentially rather than in parallel within the AI Assistant. * **Tests** * Added comprehensive test coverage for the parallel approval prevention logic. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
40 lines
1.5 KiB
TypeScript
40 lines
1.5 KiB
TypeScript
import { isToolUIPart, type UIMessage } from 'ai'
|
|
|
|
/**
|
|
* Prepares messages for API transmission by cleaning and limiting history
|
|
*/
|
|
export function prepareMessagesForAPI(messages: UIMessage[]): UIMessage[] {
|
|
// [Joshen] Specifically limiting the chat history that get's sent to reduce the
|
|
// size of the context that goes into the model. This should always be an odd number
|
|
// as much as possible so that the first message is always the user's
|
|
const MAX_CHAT_HISTORY = 7
|
|
|
|
const slicedMessages = messages.slice(-MAX_CHAT_HISTORY)
|
|
|
|
// Filter out results from messages before sending to the model
|
|
const cleanedMessages = slicedMessages.map((_message) => {
|
|
const message = _message as UIMessage & { results?: unknown }
|
|
const cleanedMessage = { ...message } as UIMessage & { results?: unknown }
|
|
if (message.role === 'assistant' && message.results) {
|
|
delete cleanedMessage.results
|
|
}
|
|
return cleanedMessage as UIMessage
|
|
})
|
|
|
|
return cleanedMessages
|
|
}
|
|
|
|
/**
|
|
* Returns approval IDs to auto-deny when the model issues multiple approval-required
|
|
* tool calls in the same turn — all but the first, so the model reissues them sequentially.
|
|
*/
|
|
export function getParallelApprovalIdsToReject(messages: UIMessage[]): string[] {
|
|
const lastMessage = messages.findLast((m) => m.role === 'assistant')
|
|
if (!lastMessage) return []
|
|
|
|
const pendingIds = (lastMessage.parts ?? []).flatMap((part) =>
|
|
isToolUIPart(part) && part.state === 'approval-requested' ? [part.approval.id] : []
|
|
)
|
|
return pendingIds.slice(1)
|
|
}
|