Files
supabase/apps/studio/pages/api/ai/code/complete.ts
T
d60aceb562 Prompt and tool refactoring (#37500)
* try a really long context window to maximize caching

* update examples

* attempt to update packages and useChat

* update endpoints

* update zod

* zod

* update to v5

* message update

* Revert "zod"

This reverts commit ec39bac6b6.

* revert zod

* zod i

* fix complete endpoints

* remove async

* change to content

* type cleanup

* Revert the package bumps to rebuild them.

* Bump zod to 2.25.76 in all packages.

* Bump openai in all packages.

* Bump ai and ai-related packages.

* Remove unneeded files.

* Fix the rest of the migration stuff.

* Prettier fixes.

* add policy list tool

* refactor

* ai sdk 5 fixes

* refactor complete endpoint

* edge function prompt

* remove example

* slight prompt change

* Minor clean up

* More clean up

---------

Co-authored-by: Jordi Enric <jordi.err@gmail.com>
Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2025-08-08 15:25:57 +07:00

171 lines
5.4 KiB
TypeScript

import pgMeta from '@supabase/pg-meta'
import { ModelMessage, stepCountIs, streamText } from 'ai'
import { IS_PLATFORM } from 'common'
import { source } from 'common-tags'
import { executeSql } from 'data/sql/execute-sql-query'
import { AiOptInLevel } from 'hooks/misc/useOrgOptedIntoAi'
import { getModel } from 'lib/ai/model'
import { getOrgAIDetails } from 'lib/ai/org-ai-details'
import {
EDGE_FUNCTION_PROMPT,
GENERAL_PROMPT,
OUTPUT_ONLY_PROMPT,
PG_BEST_PRACTICES,
RLS_PROMPT,
SECURITY_PROMPT,
} from 'lib/ai/prompts'
import { getTools } from 'lib/ai/tools'
import apiWrapper from 'lib/api/apiWrapper'
import { queryPgMetaSelfHosted } from 'lib/self-hosted'
import { NextApiRequest, NextApiResponse } from 'next'
export const maxDuration = 60
async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return new Response(
JSON.stringify({ data: null, error: { message: `Method ${req.method} Not Allowed` } }),
{
status: 405,
headers: { 'Content-Type': 'application/json', Allow: 'POST' },
}
)
}
try {
const { completionMetadata, projectRef, connectionString, orgSlug, language } = req.body
const { textBeforeCursor, textAfterCursor, prompt, selection } = completionMetadata
if (!projectRef) {
return res.status(400).json({
error: 'Missing project_ref in request body',
})
}
const authorization = req.headers.authorization
const accessToken = authorization?.replace('Bearer ', '')
let aiOptInLevel: AiOptInLevel = 'disabled'
if (!IS_PLATFORM) {
aiOptInLevel = 'schema'
}
if (IS_PLATFORM && orgSlug && authorization && projectRef) {
// Get organizations and compute opt in level server-side
const { aiOptInLevel: orgAIOptInLevel } = await getOrgAIDetails({
orgSlug,
authorization,
projectRef,
})
aiOptInLevel = orgAIOptInLevel
}
const { model, error: modelError } = await getModel(projectRef)
if (modelError) {
return res.status(500).json({ error: modelError.message })
}
// Get a list of all schemas to add to context
const pgMetaSchemasList = pgMeta.schemas.list()
const { result: schemas } =
aiOptInLevel !== 'disabled'
? await executeSql(
{
projectRef,
connectionString,
sql: pgMetaSchemasList.sql,
},
undefined,
{
'Content-Type': 'application/json',
...(authorization && { Authorization: authorization }),
},
IS_PLATFORM ? undefined : queryPgMetaSelfHosted
)
: { result: [] }
const schemasString =
schemas?.length > 0
? `The available database schema names are: ${JSON.stringify(schemas)}`
: "You don't have access to any schemas."
// Important: do not use dynamic content in the system prompt or Bedrock will not cache it
const system = source`
${GENERAL_PROMPT}
${OUTPUT_ONLY_PROMPT}
${language === 'sql' ? PG_BEST_PRACTICES : EDGE_FUNCTION_PROMPT}
${language === 'sql' && RLS_PROMPT}
${SECURITY_PROMPT}
`
// Note: these must be of type `CoreMessage` to prevent AI SDK from stripping `providerOptions`
// https://github.com/vercel/ai/blob/81ef2511311e8af34d75e37fc8204a82e775e8c3/packages/ai/core/prompt/standardize-prompt.ts#L83-L88
const coreMessages: ModelMessage[] = [
{
role: 'system',
content: system,
providerOptions: {
bedrock: {
// Always cache the system prompt (must not contain dynamic content)
cachePoint: { type: 'default' },
},
},
},
{
role: 'assistant',
// Add any dynamic context here
content: `
You are helping me edit some code.
Here is the context:
${textBeforeCursor}<selection>${selection}</selection>${textAfterCursor}
The available database schema names are: ${schemasString}
Instructions:
1. Only modify the selected text based on this prompt: ${prompt}
2. Your response should be ONLY the modified selection text, nothing else. Remove selected text if needed.
3. Do not wrap in code blocks or markdown
4. You can respond with one word or multiple words
5. Ensure the modified text flows naturally within the current line
6. Avoid duplicating code when considering the full statement
7. If there is no surrounding context (before or after), make sure your response is a complete valid SQL statement that can be run and resolves the prompt.
Modify the selected text now:
`,
},
]
// Get tools
const tools = await getTools({
projectRef,
connectionString,
authorization,
aiOptInLevel,
accessToken,
})
const result = streamText({
model,
stopWhen: stepCountIs(5),
messages: coreMessages,
tools,
})
return result.pipeUIMessageStreamToResponse(res)
} catch (error) {
console.error('Completion error:', error)
return res.status(500).json({
error: 'Failed to generate completion',
})
}
}
const wrapper = (req: NextApiRequest, res: NextApiResponse) =>
apiWrapper(req, res, handler, { withAuth: true })
export default wrapper