| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- import { acceptUntrustedSql, untrustedSql } from '@supabase/pg-meta'
- import { tool } from 'ai'
- import { z } from 'zod'
- import { deployEdgeFunction } from '@/data/edge-functions/edge-functions-deploy-mutation'
- import { executeSql } from '@/data/sql/execute-sql-query'
- import type { AiOptInLevel } from '@/hooks/misc/useOrgOptedIntoAi'
- import {
- EDGE_FUNCTION_PROMPT,
- PG_BEST_PRACTICES,
- REALTIME_PROMPT,
- RLS_PROMPT,
- } from '@/lib/ai/prompts'
- import { NO_DATA_PERMISSIONS } from '@/lib/ai/tools/tool-sanitizer'
- import { fixSqlBackslashEscapes } from '@/lib/ai/util'
- const KNOWLEDGE = {
- pg_best_practices: PG_BEST_PRACTICES,
- rls: RLS_PROMPT,
- edge_functions: EDGE_FUNCTION_PROMPT,
- realtime: REALTIME_PROMPT,
- } as const
- type KnowledgeName = keyof typeof KNOWLEDGE
- export const executeSqlInputSchema = z.object({
- // Transform at parse time so the corrected SQL is what gets stored in
- // toolCall.input — ensuring evals and logs reflect what actually runs.
- sql: z.string().describe('The SQL statement to execute.').transform(fixSqlBackslashEscapes),
- label: z.string().describe('A short 2-4 word label for the SQL statement.'),
- chartConfig: z
- .object({
- view: z.enum(['table', 'chart']).describe('How to render the results after execution'),
- xAxis: z.string().optional().describe('The column to use for the x-axis of the chart.'),
- yAxis: z.string().optional().describe('The column to use for the y-axis of the chart.'),
- })
- .describe('Chart configuration for rendering the results'),
- isWriteQuery: z
- .boolean()
- .default(false)
- .describe(
- 'Whether the SQL statement performs a write operation or has side effects. Set true for INSERT/UPDATE/DELETE/DDL and for SELECT statements that call side-effecting functions, such as select cron.schedule(...), cron.unschedule(...), or functions that create, modify, schedule, enqueue, notify, or trigger work.'
- ),
- })
- export const loadKnowledgeInputSchema = z.object({
- name: z
- .enum(Object.keys(KNOWLEDGE) as [KnowledgeName, ...KnowledgeName[]])
- .describe('The knowledge to load'),
- })
- export type StudioToolsContext = {
- projectRef?: string
- connectionString?: string
- authorization?: string
- aiOptInLevel?: AiOptInLevel
- }
- export const getStudioTools = (ctx: StudioToolsContext = {}) => {
- const { projectRef, connectionString, authorization, aiOptInLevel = 'schema' } = ctx
- const authHeaders = authorization
- ? { 'Content-Type': 'application/json', Authorization: authorization }
- : undefined
- return {
- execute_sql: tool({
- description:
- 'Asks the user to execute a SQL statement and return the results. Requires user approval before executing.',
- inputSchema: executeSqlInputSchema,
- needsApproval: true,
- execute: async ({ sql }) => {
- // The `needsApproval: true` gate on this tool means the user has
- // explicitly approved this AI-generated SQL before execute runs —
- // that approval is the user gesture that promotes untrusted to safe.
- const { result } = await executeSql(
- { projectRef, connectionString, sql: acceptUntrustedSql(untrustedSql(sql)) },
- undefined,
- authHeaders
- )
- return result
- },
- toModelOutput: ({ output }) => {
- return aiOptInLevel === 'schema_and_log_and_data'
- ? { type: 'json', value: output }
- : { type: 'text', value: NO_DATA_PERMISSIONS }
- },
- }),
- deploy_edge_function: tool({
- description:
- 'Asks the user to deploy a Briven Edge Function from provided code. Requires user approval before deploying.',
- inputSchema: z.object({
- name: z.string().describe('The URL-friendly name/slug of the Edge Function.'),
- code: z.string().describe('The TypeScript code for the Edge Function.'),
- }),
- needsApproval: true,
- execute: async ({ name, code }) => {
- await deployEdgeFunction({
- projectRef: projectRef ?? '',
- slug: name,
- metadata: {
- entrypoint_path: 'index.ts',
- name,
- verify_jwt: true,
- },
- files: [{ name: 'index.ts', content: code }],
- authorization,
- })
- return { success: true }
- },
- }),
- rename_chat: tool({
- description: `Rename the current chat session when the current chat name doesn't describe the conversation topic.`,
- inputSchema: z.object({
- newName: z.string().describe('The new name for the chat session. Five words or less.'),
- }),
- execute: async () => {
- return { status: 'Chat request sent to client' }
- },
- }),
- load_knowledge: tool({
- description:
- 'Load detailed knowledge about a Briven topic before answering questions about it.',
- inputSchema: loadKnowledgeInputSchema,
- execute: ({ name }) => KNOWLEDGE[name],
- }),
- }
- }
|