mirror of
https://github.com/supabase/supabase.git
synced 2026-11-06 12:24:31 +08:00
## Summary
Migrates four more assistant tools to the contract + `.server.ts`
pattern used by the notebook tools. Each tool now declares its schemas,
opt-in level, approval and model-visible output once.
## Changes
* Add contracts and server factories for `execute_sql`,
`deploy_edge_function`, `rename_chat`, `load_knowledge`;
`getStudioTools` now composes them.
* Add `execute-sql-output.ts` (output schema, `toModelRows`,
`NO_DATA_PERMISSIONS`) and `knowledge-topics.ts` (topic names + UI
labels, replacing the copy in `Message.Parts.utils.ts`).
* Delete `executeSqlSanitizer` and `ALL_TOOL_SANITIZERS`; history replay
goes through `sanitizeContractPart` only.
* Drop the four tools from `TOOL_CATEGORY_MAP`; the UI and evals import
schemas from the contracts; `rename_chat` input casts now use the
contract type.
## Deviations worth reviewing
* `execute_sql` replay: a bare-array legacy output is stamped `schema`
via a union transform (still hidden from the model, still shown in the
UI). A malformed non-array output is now dropped, where before it passed
through at the data level.
* The "no data permission" message now reaches the model as a JSON
string value instead of a text part. The text the model sees is
unchanged.
* `deploy_edge_function` output is now strictly `{ success: boolean }`
in the UI parse (was optional/passthrough).
60 lines
2.6 KiB
TypeScript
60 lines
2.6 KiB
TypeScript
import type { JSONValue } from 'ai'
|
|
import { z } from 'zod'
|
|
|
|
import { lowerOptInLevel, optInLevelSchema } from '../opt-in-level'
|
|
import { DEFAULT_CELL_ROW_LIMIT } from '@/components/interfaces/Explorer/QueryCell/QueryCell.utils'
|
|
import type { AiOptInLevel } from '@/hooks/misc/useOrgOptedIntoAi'
|
|
|
|
export const NO_DATA_PERMISSIONS =
|
|
'The query was executed and the user has viewed the results but decided not to share in the conversation due to permission levels. Continue with your plan unless instructed to interpret the result. If you need the rows to answer and `update_opt_in_level` is available, call it with `schema_and_log_and_data`, unless the user already skipped that request in this chat.'
|
|
|
|
// the rows.length default covers chats saved before execute_sql sliced its rows
|
|
export const toModelRows = <T>(
|
|
rows: T[],
|
|
{
|
|
totalRows = rows.length,
|
|
hasMoreRows = false,
|
|
}: { totalRows?: number; hasMoreRows?: boolean } = {}
|
|
) => {
|
|
if (!hasMoreRows && totalRows <= DEFAULT_CELL_ROW_LIMIT) return rows
|
|
|
|
return {
|
|
rows: rows.slice(0, DEFAULT_CELL_ROW_LIMIT),
|
|
note: hasMoreRows
|
|
? `The query matches more than ${DEFAULT_CELL_ROW_LIMIT} rows. Only the first ${DEFAULT_CELL_ROW_LIMIT} are included because results are capped to protect database performance, so the exact total is unknown. Tell the user it matched more than ${DEFAULT_CELL_ROW_LIMIT} rows and only the first ${DEFAULT_CELL_ROW_LIMIT} are shown. Do not state a specific total.`
|
|
: `Only the first ${DEFAULT_CELL_ROW_LIMIT} of ${totalRows} rows are included.`,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* `optInLevel` is the level the query ran under. Rows are shared only at the lower of that
|
|
* and the current level, so rows from `schema` stay hidden after an upgrade to data.
|
|
*/
|
|
const stampedOutputSchema = z.object({
|
|
rows: z.array(z.unknown()),
|
|
totalRows: z.number().optional(),
|
|
hasMoreRows: z.boolean().optional(),
|
|
optInLevel: optInLevelSchema,
|
|
})
|
|
|
|
/**
|
|
* Output saved before the stamp existed is a bare array. It's stamped `schema` so its rows
|
|
* stay hidden from the model, and the UI can still show them.
|
|
*/
|
|
export const executeSqlOutputSchema = z.union([
|
|
stampedOutputSchema,
|
|
z.array(z.unknown()).transform((rows) => ({ rows, optInLevel: 'schema' as const })),
|
|
])
|
|
|
|
export type ExecuteSqlOutput = z.infer<typeof stampedOutputSchema>
|
|
|
|
export function toModelExecuteSqlOutput(
|
|
output: ExecuteSqlOutput,
|
|
optInLevel: AiOptInLevel
|
|
): JSONValue {
|
|
const level = lowerOptInLevel(output.optInLevel, optInLevel)
|
|
return level === 'schema_and_log_and_data'
|
|
? (toModelRows(output.rows, output) as JSONValue)
|
|
: NO_DATA_PERMISSIONS
|
|
}
|