ruvector/studio/pages/api/ai/code/complete.ts
rUv 814f595995 feat(studio): Add complete RuVector Studio application
Major additions:
- Complete Next.js studio application with 1600+ components
- Docker support (Dockerfile.combined, docker-compose.yml)
- GCP deployment documentation and benchmarks
- SQL benchmark scripts for performance testing
- Sentry integration for monitoring
- Comprehensive test suite and mocks

Studio features:
- Dashboard and admin interfaces
- Data visualization components
- Authentication and user management
- API integration with RuVector backend
- Static data and public assets

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-06 23:04:48 +00:00

175 lines
5.5 KiB
TypeScript

import pgMeta from '@supabase/pg-meta'
import { generateText, ModelMessage, stepCountIs } 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 { executeQuery } from 'lib/api/self-hosted/query'
import { NextApiRequest, NextApiResponse } from 'next'
import z from 'zod'
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
}
// For code completion, we always use the limited model
const {
model,
error: modelError,
promptProviderOptions,
} = await getModel({
provider: 'openai',
routingKey: 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()
type Schemas = z.infer<(typeof pgMetaSchemasList)['zod']>
const { result: schemas } =
aiOptInLevel !== 'disabled'
? await executeSql<Schemas>(
{
projectRef,
connectionString,
sql: pgMetaSchemasList.sql,
},
undefined,
{
'Content-Type': 'application/json',
...(authorization && { Authorization: authorization }),
},
IS_PLATFORM ? undefined : executeQuery
)
: { 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,
...(promptProviderOptions && { providerOptions: promptProviderOptions }),
},
{
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 { text } = await generateText({
model,
stopWhen: stepCountIs(5),
messages: coreMessages,
tools,
})
return res.status(200).json(text)
} 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