mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
fix(assistant): SQL approval buttons do nothing (#48102)
Fixes FE-3954: clicking Skip/Run Query in the AI Assistant did nothing. ## Root cause `onFinish` synced the AI SDK `Chat` instance's live message array directly into valtio state (`chat.messages = messages`). valtio's `proxy()` mutates an object's nested properties in place instead of cloning them, so this corrupted the SDK's own array with Proxies. The next approval click hit the SDK's internal `structuredClone()` call and threw `DOMException: Proxy object could not be cloned` — an unhandled rejection before any network request, so the buttons silently did nothing. ## Fix Assign a sanitized copy of the message array instead of the SDK's live reference. ## Test plan - [x] `pnpm --filter studio exec vitest run state/ai-assistant-state.test.ts` — fails on old code with the exact DOMException, passes on the fix - [ ] Manual: approve/skip a suggested query in AI Assistant and confirm it runs/is skipped <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved AI assistant chat message synchronization to prevent message corruption. * Ensured chat messages remain safely cloneable after approval-related updates. * **Tests** * Added coverage verifying that synchronized AI assistant messages can be cloned successfully. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
3d07613c2b
commit
4864031466
2 files changed
+49
-2
No files matched your search
@@ -0,0 +1,46 @@
|
||||
import { proxy, ref } from 'valtio/vanilla'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { sanitizeForCloning } from './ai-assistant-state'
|
||||
|
||||
describe('AI assistant chat message sync', () => {
|
||||
// FE-3954: syncing the live array into valtio corrupted it with Proxies, breaking structuredClone in addToolApprovalResponse
|
||||
it('does not corrupt the live SDK message array when synced into valtio state', () => {
|
||||
const state = proxy<{ chats: Record<string, any>; chatInstances: Record<string, any> }>({
|
||||
chats: {},
|
||||
chatInstances: {},
|
||||
})
|
||||
state.chats['chat-1'] = { id: 'chat-1', messages: [] }
|
||||
|
||||
const liveSdkMessages = [
|
||||
{
|
||||
id: 'message-1',
|
||||
role: 'assistant',
|
||||
parts: [
|
||||
{ type: 'text', text: "Sure, here's a query" },
|
||||
{
|
||||
type: 'tool-execute_sql',
|
||||
toolCallId: 'tool-1',
|
||||
state: 'approval-requested',
|
||||
approval: { id: 'approval-1' },
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
state.chatInstances['chat-1'] = ref({ messages: liveSdkMessages })
|
||||
|
||||
const chat = state.chats['chat-1']
|
||||
chat.messages = liveSdkMessages.map((message) => sanitizeForCloning(message))
|
||||
|
||||
// mirrors addToolApprovalResponse's own update logic
|
||||
const lastMessage = liveSdkMessages[liveSdkMessages.length - 1]
|
||||
const updatedParts = lastMessage.parts.map((part: any) =>
|
||||
part.state === 'approval-requested' && part.approval?.id === 'approval-1'
|
||||
? { ...part, state: 'approval-responded', approval: { id: 'approval-1', approved: true } }
|
||||
: part
|
||||
)
|
||||
const replacedMessage = { ...lastMessage, parts: updatedParts }
|
||||
|
||||
expect(() => structuredClone(replacedMessage)).not.toThrow()
|
||||
})
|
||||
})
|
||||
@@ -152,7 +152,7 @@ async function clearStorage(): Promise<void> {
|
||||
|
||||
// Helper function to sanitize objects to ensure they're cloneable
|
||||
// Issue due to addToolResult
|
||||
function sanitizeForCloning(obj: any): any {
|
||||
export function sanitizeForCloning(obj: any): any {
|
||||
if (obj === null || obj === undefined) return obj
|
||||
if (typeof obj !== 'object') return obj
|
||||
return JSON.parse(JSON.stringify(obj))
|
||||
@@ -329,7 +329,8 @@ function createChatInstance(
|
||||
const messages = chatInstance.messages
|
||||
const chat = state.chats[options.id]
|
||||
if (chat) {
|
||||
chat.messages = messages
|
||||
// Clone first — valtio's proxy() mutates nested properties in place and would corrupt the SDK's live array
|
||||
chat.messages = messages.map((message) => sanitizeForCloning(message))
|
||||
chat.updatedAt = new Date()
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user