mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 07:09:07 +03:00
## Summary Each assistant tool's identity and contract is currently written in up to nine places that nothing ties together at compile time. This PR adds a contract/server split and migrates `update_notebook`, the tool with the most copies, as the first proof of the design. - **Contract** (`lib/ai/tools/update-notebook.ts`): isomorphic file with the input and output schemas, approval flag, minimum opt-in level, and `toModelOutput`, the single rule for what the model may see. - **Server** (`update-notebook.server.ts`): the executable tool, built with `approvalGatedToolFromContract`. - **Registry** (`registry.ts`): collects contracts, derives a typed `StudioUIMessage`, and provides `sanitizeContractPart` for history replay. - **Shared types** (`types.ts`): `ToolContract`, `ToolContext`, `ToolFactory`, plus a single `getAuthHeaders` replacing three variants. - **Approval gate** (`approval-gate.ts`): approval-gated tools get `acceptUntrustedSql` / `acceptUntrustedLogsSql` through a gate passed to `execute`, and `*.server.ts` files can't import the raw functions (ESLint). - **Opt-in level**: `update_notebook` is gated by its contract's `minOptInLevel`. `TOOL_CATEGORY_MAP` is now typed against `ToolName` and excludes migrated tools, which exposed `get_active_incidents` missing from the name enum. Removed duplicates: the `update_notebook` sanitizer in `tool-sanitizer.ts`, the client-side `stripNotebookSnapshot`, and the copied update schemas in `Message.utils.ts`. ## Behavior changes - The client no longer strips `previous_content` from `update_notebook` outputs before sending history, so requests carry the previous notebook snapshot. The server still strips it before the model sees it. - On history replay, an `update_notebook` output that doesn't match its schema is dropped instead of passed through minus `previous_content`. ## Testing - New `registry.test.ts` (typed parts, replay sanitizer) and a contract case in `tool-filter.test.ts`. - Studio typecheck, lint ratchet, knip, Prettier and the affected vitest suites pass.
39 lines
1.5 KiB
TypeScript
39 lines
1.5 KiB
TypeScript
import { z } from 'zod'
|
|
|
|
import type { ToolContract } from './types'
|
|
import { notebookOperationsSchema } from '@/data/content/notebooks/notebook-operations'
|
|
import { notebookSchema } from '@/data/content/notebooks/notebook-schema'
|
|
|
|
const inputSchema = z.object({
|
|
id: z.string().describe('The id of the notebook to update.'),
|
|
expected_updated_at: z
|
|
.string()
|
|
.describe(
|
|
'The `updated_at` you received from `get_notebook`. The update is rejected if the notebook changed since.'
|
|
),
|
|
operations: notebookOperationsSchema.describe(
|
|
'An ordered list of operations to apply to the notebook, addressing existing cells by id.'
|
|
),
|
|
})
|
|
|
|
/**
|
|
* `previous_content` lets the client re-derive the diff it already showed for approval,
|
|
* without re-fetching a notebook that's now post-update. It is UI-only: `toModelOutput` drops it.
|
|
*/
|
|
const outputSchema = z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
previous_content: notebookSchema.optional(),
|
|
})
|
|
|
|
export const contract = {
|
|
name: 'update_notebook',
|
|
description:
|
|
'Asks the user to apply an ordered list of cell operations (insert, replace, delete, move) to an existing notebook. Requires user approval before updating. Re-fetches the notebook right before applying the operations and rejects the update if it changed since expected_updated_at.',
|
|
inputSchema,
|
|
outputSchema,
|
|
minOptInLevel: 'schema',
|
|
needsApproval: true,
|
|
toModelOutput: ({ id, name }) => ({ id, name }),
|
|
} as const satisfies ToolContract
|