mirror of
https://github.com/supabase/supabase.git
synced 2026-09-07 02:20:52 +08:00
## Summary
- **Removed dead client-side refresh UI** in
`NotebookProposalRenderer.tsx` and its test — the diff preview is always
computed from live data, so the check was redundant with the tool's
server-side re-validation
- **Added typed `NotebookToolError`** in `notebook-tools.ts` with
structured metadata (`{ exposeToAssistant: boolean }`) validated by a
zod schema with a literal discriminant tag (`tag:
'notebook_tool_error'`) — tracks the two retryable failures: staleness
conflict and invalid operations (unknown cell id)
- **Encoded errors in `generate-v4.ts` onError** — the one place in the
pipeline that holds the live `Error` before it becomes a string in the
persisted message
- **Extracted and fixed message history filter** into new
`generate-assistant-response.utils.ts` — any tool-error whose
`errorText` decodes against the `NotebookToolError` schema is let
through (with `errorText` rewritten to plain prose so the model sees the
message, not JSON), while other errors stay filtered as before
Net effect: the assistant detects the specific, actionable rejection
reason and retries on its own with no dead button or human intervention
needed.
## Test plan
- Existing unit tests in `NotebookProposalRenderer.test.tsx` pass (dead
button test removed)
- New unit tests in `notebook-tools.test.ts` cover encode/decode
round-trips and error discrimination
- New unit tests in `generate-assistant-response.utils.test.ts` cover
message history filtering with all error states
- `pnpm typecheck` is clean
- `pnpm --filter studio run lint:ratchet` passes (no new ESLint
warnings)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Notebook update errors now provide clearer, structured explanations to
the AI assistant.
* Assistant responses preserve relevant notebook error details while
filtering invalid or temporary tool states.
* **Bug Fixes**
* Improved handling of stale notebook revisions and invalid notebook
update operations.
* Notebook proposal rendering proceeds without an unnecessary refresh
step.
* **Tests**
* Expanded coverage for notebook errors, message filtering,
serialization, and error handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
51 lines
2.0 KiB
TypeScript
51 lines
2.0 KiB
TypeScript
import { isToolUIPart, type DynamicToolUIPart, type ToolUIPart, type UIMessage } from 'ai'
|
|
|
|
import type { AiOptInLevel } from '@/hooks/misc/useOrgOptedIntoAi'
|
|
import { decodeNotebookToolError } from '@/lib/ai/tools/notebook-tools'
|
|
import { sanitizeMessagePart } from '@/lib/ai/tools/tool-sanitizer'
|
|
|
|
const INVALID_TOOL_STATES = [
|
|
'input-streaming',
|
|
'input-available',
|
|
'approval-requested',
|
|
'output-error',
|
|
]
|
|
|
|
/**
|
|
* Notebook tool errors are opted into model visibility explicitly (NotebookToolError,
|
|
* encoded in generate-v4.ts's onError). A successful parse against the schema — including
|
|
* its literal `tag` — is proof enough that this was a NotebookToolError; every other
|
|
* output-error stays hidden, same as before. Rewrites errorText back to the plain message
|
|
* so the model sees prose, not JSON.
|
|
*/
|
|
function exposedNotebookErrorPart(
|
|
part: ToolUIPart | DynamicToolUIPart
|
|
): ToolUIPart | DynamicToolUIPart | null {
|
|
if (part.state !== 'output-error') return null
|
|
const decoded = decodeNotebookToolError(part.errorText)
|
|
if (!decoded?.exposeToAssistant) return null
|
|
return { ...part, errorText: decoded.message }
|
|
}
|
|
|
|
/** Trims history to the last 7 messages and strips tool parts the model shouldn't see. */
|
|
export function prepareMessagesForModel(rawMessages: UIMessage[], aiOptInLevel: AiOptInLevel) {
|
|
return (rawMessages || []).slice(-7).map((msg) => {
|
|
if (msg && msg.role === 'assistant' && 'results' in msg) {
|
|
const cleanedMsg = { ...msg }
|
|
delete cleanedMsg.results
|
|
return cleanedMsg
|
|
}
|
|
if (msg && msg.role === 'assistant' && msg.parts) {
|
|
const cleanedParts = msg.parts.flatMap((part) => {
|
|
if (!isToolUIPart(part)) return [part]
|
|
if (!INVALID_TOOL_STATES.includes(part.state))
|
|
return [sanitizeMessagePart(part, aiOptInLevel)]
|
|
const exposed = exposedNotebookErrorPart(part)
|
|
return exposed ? [sanitizeMessagePart(exposed, aiOptInLevel)] : []
|
|
})
|
|
return { ...msg, parts: cleanedParts }
|
|
}
|
|
return msg
|
|
})
|
|
}
|