From a6f1313490c898624947f6a995bf6db950564f08 Mon Sep 17 00:00:00 2001 From: Greg Richardson Date: Mon, 18 Dec 2023 11:23:59 -0700 Subject: [PATCH] AI SQL: Update model + add test suite (#19644) * refactor: ai sql logic to common package * feat: ai sql tests * feat: test rls policy ai chat assistant * refactor: jest env loading * feat(ai): improve sql title and description quality * fix: ai message role type * Move the new files to a separate package. * Remove a forgotten console.log. * Migrate the tests to use snapshots and commit the snapshots. * Separate the functions which require edge runtime to be exported via /edge subpath. * Bust the turbo cache when one of the deps has been rebuilt. * chore: fix package main/type references * fix: package lock out of sync * fix: ai sql debugging to fix typos --------- Co-authored-by: Ivan Vasilov --- .../Policies/AIPolicyEditorPanel/Message.tsx | 2 +- apps/studio/package.json | 2 + apps/studio/pages/api/ai/sql/debug.ts | 176 +--- apps/studio/pages/api/ai/sql/edit.ts | 174 +--- apps/studio/pages/api/ai/sql/generate.ts | 149 +--- apps/studio/pages/api/ai/sql/suggest.ts | 70 +- apps/studio/pages/api/ai/sql/title.ts | 120 +-- package-lock.json | 761 +++++++++++------- packages/ai-commands/README.md | 12 + packages/ai-commands/babel.config.js | 4 + packages/ai-commands/edge.ts | 2 + packages/ai-commands/index.ts | 2 + packages/ai-commands/jest.config.js | 14 + packages/ai-commands/package.json | 34 + .../src/__snapshots__/sql.test.ts.snap | 51 ++ packages/ai-commands/src/errors.ts | 23 + packages/ai-commands/src/sql.edge.ts | 90 +++ packages/ai-commands/src/sql.test.ts | 128 +++ packages/ai-commands/src/sql.ts | 409 ++++++++++ packages/ai-commands/test/setup.ts | 8 + packages/ai-commands/test/util.ts | 47 ++ packages/ai-commands/tsconfig.json | 5 + turbo.json | 1 + 23 files changed, 1427 insertions(+), 857 deletions(-) create mode 100644 packages/ai-commands/README.md create mode 100644 packages/ai-commands/babel.config.js create mode 100644 packages/ai-commands/edge.ts create mode 100644 packages/ai-commands/index.ts create mode 100644 packages/ai-commands/jest.config.js create mode 100644 packages/ai-commands/package.json create mode 100644 packages/ai-commands/src/__snapshots__/sql.test.ts.snap create mode 100644 packages/ai-commands/src/errors.ts create mode 100644 packages/ai-commands/src/sql.edge.ts create mode 100644 packages/ai-commands/src/sql.test.ts create mode 100644 packages/ai-commands/src/sql.ts create mode 100644 packages/ai-commands/test/setup.ts create mode 100644 packages/ai-commands/test/util.ts create mode 100644 packages/ai-commands/tsconfig.json diff --git a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/Message.tsx b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/Message.tsx index e54bddfd60..99afc52868 100644 --- a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/Message.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/Message.tsx @@ -11,7 +11,7 @@ import { AIPolicyPre } from './AIPolicyPre' interface MessageProps { name?: string - role: 'function' | 'user' | 'assistant' | 'system' + role: 'function' | 'user' | 'assistant' | 'system' | 'data' content?: string createdAt?: number isDebug?: boolean diff --git a/apps/studio/package.json b/apps/studio/package.json index ccf1c72bf5..e83037d379 100644 --- a/apps/studio/package.json +++ b/apps/studio/package.json @@ -39,6 +39,8 @@ "@uidotdev/usehooks": "^2.4.1", "@zip.js/zip.js": "^2.7.29", "ai": "^2.2.26", + "ai-commands": "*", + "ajv": "^8.6.3", "awesome-debounce-promise": "^2.1.0", "blueimp-md5": "^2.19.0", "clsx": "^1.2.1", diff --git a/apps/studio/pages/api/ai/sql/debug.ts b/apps/studio/pages/api/ai/sql/debug.ts index c1f78d6430..965bf49f14 100644 --- a/apps/studio/pages/api/ai/sql/debug.ts +++ b/apps/studio/pages/api/ai/sql/debug.ts @@ -1,44 +1,10 @@ -import { SchemaBuilder } from '@serafin/schema-builder' -import { codeBlock, stripIndent } from 'common-tags' -import { isError } from 'data/utils/error-check' -import { jsonrepair } from 'jsonrepair' +import { ContextLengthError, EmptySqlError, debugSql } from 'ai-commands' import apiWrapper from 'lib/api/apiWrapper' import { NextApiRequest, NextApiResponse } from 'next' import { OpenAI } from 'openai' const openAiKey = process.env.OPENAI_KEY - -const debugSqlSchema = SchemaBuilder.emptySchema() - .addString('solution', { - description: 'A short suggested solution for the error (as concise as possible).', - }) - .addString('sql', { - description: 'The SQL rewritten to apply the solution. Includes all the original SQL.', - }) - -type DebugSqlResult = typeof debugSqlSchema.T - -const completionFunctions: Record< - string, - OpenAI.Chat.Completions.ChatCompletionCreateParams.Function -> = { - debugSql: { - name: 'debugSql', - description: stripIndent` - Debugs a Postgres SQL error and modifies the SQL to fix it. - - Create extensions if they are missing (only for valid extensions) - - Suggest creating tables if they are missing - - Include all of the original SQL - - For primary keys, always use "id bigint primary key generated always as identity" (not serial) - - When creating tables, always add foreign key references inline - - Prefer 'text' over 'varchar' - - Prefer 'timestamp with time zone' over 'date' - - Use vector(384) data type for any embedding/vector related query - - Always use double apostrophe in SQL strings (eg. 'Night''s watch') - `, - parameters: debugSqlSchema.schema as Record, - }, -} +const openai = new OpenAI({ apiKey: openAiKey }) async function handler(req: NextApiRequest, res: NextApiResponse) { if (!openAiKey) { @@ -59,128 +25,54 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } export async function handlePost(req: NextApiRequest, res: NextApiResponse) { - const openAI = new OpenAI({ apiKey: openAiKey }) const { body: { errorMessage, sql, entityDefinitions }, } = req - const model = 'gpt-3.5-turbo-0613' - const maxCompletionTokenCount = 2048 - const hasEntityDefinitions = entityDefinitions !== undefined && entityDefinitions.length > 0 - - const completionMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [] - - if (hasEntityDefinitions) { - completionMessages.push({ - role: 'user', - content: codeBlock` - Here is my database schema for reference: - ${entityDefinitions.join('\n\n')} - `, - }) - } - - completionMessages.push( - { - role: 'user', - content: stripIndent` - Here is my current SQL: - ${sql} - `, - }, - { - role: 'user', - content: stripIndent` - Here is the error I am getting: - ${errorMessage} - `, - } - ) - - const completionOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = { - model, - messages: completionMessages, - max_tokens: maxCompletionTokenCount, - temperature: 0, - function_call: { - name: completionFunctions.debugSql.name, - }, - functions: [completionFunctions.debugSql], - stream: false, - } - - let completionResponse: OpenAI.Chat.Completions.ChatCompletion try { - completionResponse = await openAI.chat.completions.create(completionOptions) - } catch (error: any) { - console.error(`AI SQL debugging failed: ${error.message}`) + const result = await debugSql(openai, errorMessage, sql, entityDefinitions) + return res.json(result) + } catch (error) { + if (error instanceof Error) { + console.error(`AI SQL debugging failed: ${error.message}`) - if ('code' in error && error.code === 'context_length_exceeded') { - if (hasEntityDefinitions) { - const definitionsLength = entityDefinitions.reduce( - (sum: number, def: string) => sum + def.length, - 0 - ) + const hasEntityDefinitions = entityDefinitions !== undefined && entityDefinitions.length > 0 - if (definitionsLength > sql.length) { - return res.status(400).json({ - error: - 'Your database metadata is too large for Supabase AI to ingest. Try disabling database metadata in AI settings.', - }) + if (error instanceof ContextLengthError) { + // If there are more entity definitions than the SQL provided, attribute the + // error to the database metadata + if (hasEntityDefinitions) { + const definitionsLength = entityDefinitions.reduce( + (sum: number, def: string) => sum + def.length, + 0 + ) + if (definitionsLength > sql.length) { + return res.status(400).json({ + error: + 'Your database metadata is too large for Supabase AI to ingest. Try disabling database metadata in AI settings.', + }) + } } + // Otherwise attribute the error to the SQL being too large + return res.status(400).json({ + error: + 'Your SQL query is too large for Supabase AI to ingest. Try splitting it into smaller queries.', + }) } - return res.status(400).json({ - error: - 'Your SQL query is too large for Supabase AI to ingest. Try splitting it into smaller queries.', - }) + if (error instanceof EmptySqlError) { + res.status(400).json({ + error: 'Unable to debug SQL. No fix identified for the error.', + }) + } + } else { + console.log(`Unknown error: ${error}`) } return res.status(500).json({ error: 'There was an unknown error debugging the SQL snippet. Please try again.', }) } - - const [firstChoice] = completionResponse.choices - - const sqlResponseString = firstChoice.message?.function_call?.arguments - - if (!sqlResponseString) { - console.error( - `AI SQL debugging failed: OpenAI response succeeded, but response format was incorrect` - ) - - return res.status(500).json({ - error: 'There was an unknown error debugging the SQL snippet. Please try again.', - }) - } - - try { - // Attempt to repair broken JSON from OpenAI (eg. multiline strings) - const repairedJsonString = jsonrepair(sqlResponseString) - - const debugSqlResult: DebugSqlResult = JSON.parse(repairedJsonString) - - if (!debugSqlResult.sql) { - console.error(`AI SQL debugging failed: Unable to debug SQL for the given error message`) - - return res.status(400).json({ - error: 'Unable to debug SQL', - }) - } - - return res.json(debugSqlResult) - } catch (error) { - console.error( - `AI SQL editing failed: ${ - isError(error) ? error.message : 'An unknown error occurred' - }, sqlResponseString: ${sqlResponseString}` - ) - - return res.status(500).json({ - error: 'There was an unknown error editing the SQL snippet. Please try again.', - }) - } } const wrapper = (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) diff --git a/apps/studio/pages/api/ai/sql/edit.ts b/apps/studio/pages/api/ai/sql/edit.ts index 869ac6211d..3e564c1918 100644 --- a/apps/studio/pages/api/ai/sql/edit.ts +++ b/apps/studio/pages/api/ai/sql/edit.ts @@ -1,39 +1,10 @@ -import { SchemaBuilder } from '@serafin/schema-builder' -import { codeBlock, stripIndent } from 'common-tags' -import { isError } from 'data/utils/error-check' -import { jsonrepair } from 'jsonrepair' +import { ContextLengthError, EmptySqlError, editSql } from 'ai-commands' import apiWrapper from 'lib/api/apiWrapper' import { NextApiRequest, NextApiResponse } from 'next' import { OpenAI } from 'openai' const openAiKey = process.env.OPENAI_KEY - -const editSqlSchema = SchemaBuilder.emptySchema().addString('sql', { - description: stripIndent` - The modified SQL (must be valid SQL). - - Assume the query hasn't been executed yet - - For primary keys, always use "id bigint primary key generated always as identity" (not serial) - - When creating tables, always add foreign key references inline - - Prefer 'text' over 'varchar' - - Prefer 'timestamp with time zone' over 'date' - - Use vector(384) data type for any embedding/vector related query - - Always use double apostrophe in SQL strings (eg. 'Night''s watch') - - Use real examples when possible - `, -}) - -type EditSqlResult = typeof editSqlSchema.T - -const completionFunctions: Record< - string, - OpenAI.Chat.Completions.ChatCompletionCreateParams.Function -> = { - editSql: { - name: 'editSql', - description: "Edits a Postgres SQL query based on the user's instructions", - parameters: editSqlSchema.schema as Record, - }, -} +const openai = new OpenAI({ apiKey: openAiKey }) async function handler(req: NextApiRequest, res: NextApiResponse) { if (!openAiKey) { @@ -58,112 +29,45 @@ export async function handlePost(req: NextApiRequest, res: NextApiResponse) { body: { prompt, sql, entityDefinitions }, } = req - const openAI = new OpenAI({ apiKey: openAiKey }) - const model = 'gpt-3.5-turbo-0613' - const maxCompletionTokenCount = 2048 - const hasEntityDefinitions = entityDefinitions !== undefined && entityDefinitions.length > 0 - - const completionMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [] - - if (hasEntityDefinitions) { - completionMessages.push({ - role: 'user', - content: codeBlock` - Here is my database schema for reference: - ${entityDefinitions.join('\n\n')} - `, - }) - } - - completionMessages.push( - { - role: 'user', - content: stripIndent` - Here is my current SQL: - ${sql} - `, - }, - { - role: 'user', - content: prompt, - } - ) - - const completionOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = { - model, - messages: completionMessages, - max_tokens: maxCompletionTokenCount, - temperature: 0, - function_call: { - name: completionFunctions.editSql.name, - }, - functions: [completionFunctions.editSql], - stream: false, - } - - let completionResponse: OpenAI.Chat.Completions.ChatCompletion try { - completionResponse = await openAI.chat.completions.create(completionOptions) - } catch (error: any) { - console.error(`AI SQL editing failed: ${error.message}`) - if ('code' in error && error.code === 'context_length_exceeded') { - if (hasEntityDefinitions) { - const definitionsLength = entityDefinitions.reduce( - (sum: number, def: string) => sum + def.length, - 0 - ) - if (definitionsLength > sql.length) { - return res.status(400).json({ - error: - 'Your database metadata is too large for Supabase AI to ingest. Try disabling database metadata in AI settings.', - }) - } - } - return res.status(400).json({ - error: - 'Your SQL query is too large for Supabase AI to ingest. Try splitting it into smaller queries.', - }) - } - return res.status(500).json({ - error: 'There was an unknown error editing the SQL snippet. Please try again.', - }) - } - - const [firstChoice] = completionResponse.choices - - const sqlResponseString = firstChoice.message?.function_call?.arguments - - if (!sqlResponseString) { - console.error( - `AI SQL editing failed: OpenAI response succeeded, but response format was incorrect` - ) - - return res.status(500).json({ - error: 'There was an unknown error editing the SQL snippet. Please try again.', - }) - } - - try { - // Attempt to repair broken JSON from OpenAI (eg. multiline strings) - const repairedJsonString = jsonrepair(sqlResponseString) - - const editSqlResult: EditSqlResult = JSON.parse(repairedJsonString) - - if (!editSqlResult.sql) { - console.error(`AI SQL editing failed: Unable to edit SQL for the given prompt`) - - return res.status(400).json({ - error: 'Unable to edit SQL. Try adding more details to your prompt.', - }) - } - - return res.json(editSqlResult) + const result = await editSql(openai, prompt, sql, entityDefinitions) + return res.json(result) } catch (error) { - console.error( - `AI SQL editing failed: ${ - isError(error) ? error.message : 'An unknown error occurred' - }, sqlResponseString: ${sqlResponseString}` - ) + if (error instanceof Error) { + console.error(`AI SQL editing failed: ${error.message}`) + + const hasEntityDefinitions = entityDefinitions !== undefined && entityDefinitions.length > 0 + + if (error instanceof ContextLengthError) { + // If there are more entity definitions than the SQL provided, attribute the + // error to the database metadata + if (hasEntityDefinitions) { + const definitionsLength = entityDefinitions.reduce( + (sum: number, def: string) => sum + def.length, + 0 + ) + if (definitionsLength > sql.length) { + return res.status(400).json({ + error: + 'Your database metadata is too large for Supabase AI to ingest. Try disabling database metadata in AI settings.', + }) + } + } + // Otherwise attribute the error to the SQL being too large + return res.status(400).json({ + error: + 'Your SQL query is too large for Supabase AI to ingest. Try splitting it into smaller queries.', + }) + } + + if (error instanceof EmptySqlError) { + res.status(400).json({ + error: 'Unable to edit SQL. Try adding more details to your prompt.', + }) + } + } else { + console.log(`Unknown error: ${error}`) + } return res.status(500).json({ error: 'There was an unknown error editing the SQL snippet. Please try again.', diff --git a/apps/studio/pages/api/ai/sql/generate.ts b/apps/studio/pages/api/ai/sql/generate.ts index 71642983ea..1dc73696ac 100644 --- a/apps/studio/pages/api/ai/sql/generate.ts +++ b/apps/studio/pages/api/ai/sql/generate.ts @@ -1,44 +1,10 @@ -import { SchemaBuilder } from '@serafin/schema-builder' -import { codeBlock, stripIndent } from 'common-tags' -import { isError } from 'data/utils/error-check' -import { jsonrepair } from 'jsonrepair' +import { ContextLengthError, EmptySqlError, generateSql } from 'ai-commands' import apiWrapper from 'lib/api/apiWrapper' import { NextApiRequest, NextApiResponse } from 'next' import { OpenAI } from 'openai' const openAiKey = process.env.OPENAI_KEY - -const generateSqlSchema = SchemaBuilder.emptySchema() - .addString('sql', { - description: stripIndent` - The generated SQL (must be valid SQL). - - For primary keys, always use "id bigint primary key generated always as identity" (not serial) - - Prefer creating foreign key references in the create statement - - Prefer 'text' over 'varchar' - - Prefer 'timestamp with time zone' over 'date' - - Use vector(384) data type for any embedding/vector related query - - Always use double apostrophe in SQL strings (eg. 'Night''s watch') - `, - }) - .addString('title', { - description: stripIndent` - The title of the SQL. - - Omit words like 'SQL', 'Postgres', or 'Query' - `, - }) - -type GenerateSqlResult = typeof generateSqlSchema.T - -const completionFunctions: Record< - string, - OpenAI.Chat.Completions.ChatCompletionCreateParams.Function -> = { - generateSql: { - name: 'generateSql', - description: 'Generates Postgres SQL based on a natural language prompt', - parameters: generateSqlSchema.schema as Record, - }, -} +const openai = new OpenAI({ apiKey: openAiKey }) async function handler(req: NextApiRequest, res: NextApiResponse) { if (!openAiKey) { @@ -59,102 +25,37 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } export async function handlePost(req: NextApiRequest, res: NextApiResponse) { - const openAI = new OpenAI({ apiKey: openAiKey }) const { body: { prompt, entityDefinitions }, } = req - const model = 'gpt-3.5-turbo-0613' - const maxCompletionTokenCount = 1024 - const hasEntityDefinitions = entityDefinitions !== undefined && entityDefinitions.length > 0 - - const completionMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [] - - if (hasEntityDefinitions) { - completionMessages.push({ - role: 'user', - content: codeBlock` - Here is my database schema for reference: - ${entityDefinitions.join('\n\n')} - `, - }) - } - - completionMessages.push({ - role: 'user', - content: prompt, - }) - - const completionOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = { - model, - messages: completionMessages, - max_tokens: maxCompletionTokenCount, - temperature: 0, - function_call: { - name: completionFunctions.generateSql.name, - }, - functions: [completionFunctions.generateSql], - stream: false, - } - - let completionResponse: OpenAI.Chat.Completions.ChatCompletion try { - completionResponse = await openAI.chat.completions.create(completionOptions) - } catch (error: any) { - console.error(`AI SQL generation failed: ${error.message}`) - - if ('code' in error && error.code === 'context_length_exceeded' && hasEntityDefinitions) { - return res.status(400).json({ - error: - 'Your database metadata is too large for Supabase AI to ingest. Try disabling database metadata in AI settings.', - }) - } - - return res.status(500).json({ - error: 'There was an unknown error generating the SQL snippet. Please try again.', - }) - } - - const [firstChoice] = completionResponse.choices - - const sqlResponseString = firstChoice.message?.function_call?.arguments - - if (!sqlResponseString) { - console.error( - `AI SQL generation failed: OpenAI response succeeded, but response format was incorrect` - ) - - return res.status(500).json({ - error: 'There was an unknown error generating the SQL snippet. Please try again.', - }) - } - - try { - // Attempt to repair broken JSON from OpenAI (eg. multiline strings) - const repairedJsonString = jsonrepair(sqlResponseString) - - const generateSqlResult: GenerateSqlResult = JSON.parse(repairedJsonString) - - if (!generateSqlResult.sql) { - console.error(`AI SQL generation failed: Unable to generate SQL for the given prompt`) - - res.status(400).json({ - error: 'Unable to generate SQL. Try adding more details to your prompt.', - }) - - return - } - - return res.json(generateSqlResult) + const result = await generateSql(openai, prompt, entityDefinitions) + return res.json(result) } catch (error) { - console.error( - `AI SQL editing failed: ${ - isError(error) ? error.message : 'An unknown error occurred' - }, sqlResponseString: ${sqlResponseString}` - ) + if (error instanceof Error) { + console.error(`AI SQL generation failed: ${error.message}`) + + const hasEntityDefinitions = entityDefinitions !== undefined && entityDefinitions.length > 0 + + if (error instanceof ContextLengthError && hasEntityDefinitions) { + return res.status(400).json({ + error: + 'Your database metadata is too large for Supabase AI to ingest. Try disabling database metadata in AI settings.', + }) + } + + if (error instanceof EmptySqlError) { + res.status(400).json({ + error: 'Unable to generate SQL. Try adding more details to your prompt.', + }) + } + } else { + console.log(`Unknown error: ${error}`) + } return res.status(500).json({ - error: 'There was an unknown error editing the SQL snippet. Please try again.', + error: 'There was an unknown error generating the SQL snippet. Please try again.', }) } } diff --git a/apps/studio/pages/api/ai/sql/suggest.ts b/apps/studio/pages/api/ai/sql/suggest.ts index 70d15dc26a..3f60da2f8f 100644 --- a/apps/studio/pages/api/ai/sql/suggest.ts +++ b/apps/studio/pages/api/ai/sql/suggest.ts @@ -1,5 +1,5 @@ -import { OpenAIStream, StreamingTextResponse } from 'ai' -import { codeBlock, oneLine, stripIndent } from 'common-tags' +import { StreamingTextResponse } from 'ai' +import { chatRlsPolicy } from 'ai-commands/edge' import { NextRequest } from 'next/server' import OpenAI from 'openai' @@ -47,72 +47,10 @@ async function handlePost(request: NextRequest) { const { messages, entityDefinitions, policyDefinition } = body - const initMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [ - { - role: 'system', - content: stripIndent` - You're an Postgres expert in writing row level security policies. Your purpose is to - generate a policy with the constraints given by the user. You will be provided a schema - on which the policy should be applied. - - The output should use the following instructions: - - The generated SQL must be valid SQL. - - Always use double apostrophe in SQL strings (eg. 'Night''s watch') - - You can use only CREATE POLICY or ALTER POLICY queries, no other queries are allowed. - - You can add short explanations to your messages. - - The result should be a valid markdown. The SQL code should be wrapped in \`\`\`. - - Always use "auth.uid()" instead of "current_user". - - You can't use "USING" expression on INSERT policies. - - Only use "WITH CHECK" expression on INSERT or UPDATE policies. - - The policy name should be short text explaining the policy, enclosed in double quotes. - - Always put explanations as separate text. Never use inline SQL comments. - - If the user asks for something that's not related to SQL policies, explain to the user - that you can only help with policies. - - The output should look like this: - "CREATE POLICY user_policy ON users FOR INSERT USING (user_name = current_user) WITH (true);" - `, - }, - ] - - if (entityDefinitions) { - const definitions = codeBlock`${entityDefinitions.join('\n\n')}` - initMessages.push({ - role: 'user', - content: oneLine`Here is my database schema for reference: ${definitions}`, - }) - } - - if (policyDefinition !== undefined) { - const definitionBlock = codeBlock`${policyDefinition}` - initMessages.push({ - role: 'user', - content: codeBlock` - Here is my policy definition for reference: - ${definitionBlock} - `.trim(), - }) - } - - if (messages) { - initMessages.push(...messages) - } - - const completionOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { - model: 'gpt-3.5-turbo-1106', - messages: initMessages, - max_tokens: 1024, - temperature: 0, - stream: true, - } - try { - const response = await openai.chat.completions.create(completionOptions) - // Proxy the streamed SSE response from OpenAI - const stream = OpenAIStream(response) - + const stream = await chatRlsPolicy(openai, messages, entityDefinitions, policyDefinition) return new StreamingTextResponse(stream) - } catch (error: any) { + } catch (error) { console.error(error) return new Response( diff --git a/apps/studio/pages/api/ai/sql/title.ts b/apps/studio/pages/api/ai/sql/title.ts index b53170f900..c5d1477faf 100644 --- a/apps/studio/pages/api/ai/sql/title.ts +++ b/apps/studio/pages/api/ai/sql/title.ts @@ -1,36 +1,10 @@ -import { SchemaBuilder } from '@serafin/schema-builder' -import { stripIndent } from 'common-tags' -import { isError } from 'data/utils/error-check' -import { jsonrepair } from 'jsonrepair' +import { ContextLengthError, titleSql } from 'ai-commands' import apiWrapper from 'lib/api/apiWrapper' import { NextApiRequest, NextApiResponse } from 'next' import { OpenAI } from 'openai' const openAiKey = process.env.OPENAI_KEY - -const generateTitleSchema = SchemaBuilder.emptySchema() - .addString('title', { - description: stripIndent` - The generated title for the SQL snippet (short and concise). - - Omit these words: 'SQL', 'Postgres', 'Query', 'Database' - `, - }) - .addString('description', { - description: stripIndent` - The generated description for the SQL snippet (longer and more detailed than title). - - Read the SQL line by line and summarize it - `, - }) - -type GenerateTitleResult = typeof generateTitleSchema.T - -const completionFunctions: Record = { - generateTitle: { - name: 'generateTitle', - description: 'Generates a short title and detailed description for a Postgres SQL snippet', - parameters: generateTitleSchema.schema as Record, - }, -} +const openai = new OpenAI({ apiKey: openAiKey }) async function handler(req: NextApiRequest, res: NextApiResponse) { if (!openAiKey) { @@ -51,89 +25,29 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } export async function handlePost(req: NextApiRequest, res: NextApiResponse) { - const openAI = new OpenAI({ apiKey: openAiKey }) const { body: { sql }, } = req - const model = 'gpt-3.5-turbo-0613' - const maxCompletionTokenCount = 1024 - - const completionMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [ - { - role: 'user', - content: sql, - }, - ] - - const completionOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = { - model, - messages: completionMessages, - max_tokens: maxCompletionTokenCount, - temperature: 0, - function_call: { - name: completionFunctions.generateTitle.name, - }, - functions: [completionFunctions.generateTitle], - stream: false, - } - - let completionResponse: OpenAI.Chat.Completions.ChatCompletion try { - completionResponse = await openAI.chat.completions.create(completionOptions) - } catch (error: any) { - console.error(`AI title generation failed: ${error.message}`) - - if ('code' in error && error.code === 'context_length_exceeded') { - return res.status(400).json({ - error: - 'Your SQL query is too large for Supabase AI to ingest. Try splitting it into smaller queries.', - }) - } - - return res.status(500).json({ - error: 'There was an unknown error generating the snippet title. Please try again.', - }) - } - - const [firstChoice] = completionResponse.choices - - const titleResponseString = firstChoice.message?.function_call?.arguments - - if (!titleResponseString) { - console.error( - `AI title generation failed: OpenAI response succeeded, but response format was incorrect` - ) - - return res.status(500).json({ - error: 'There was an unknown error generating the snippet title. Please try again.', - }) - } - - try { - // Attempt to repair broken JSON from OpenAI (eg. multiline strings) - const repairedJsonString = jsonrepair(titleResponseString) - - const generateTitleResult: GenerateTitleResult = JSON.parse(repairedJsonString) - - if (!generateTitleResult.title) { - console.error(`AI title generation failed: Unable to generate title for the given SQL`) - - res.status(400).json({ - error: 'Unable to generate title', - }) - } - - return res.json(generateTitleResult) + const result = await titleSql(openai, sql) + return res.json(result) } catch (error) { - console.error( - `AI SQL editing failed: ${ - isError(error) ? error.message : 'An unknown error occurred' - }, titleResponseString: ${titleResponseString}` - ) + if (error instanceof Error) { + console.error(`AI title generation failed: ${error.message}`) + + if (error instanceof ContextLengthError) { + return res.status(400).json({ + error: + 'Your SQL query is too large for Supabase AI to ingest. Try splitting it into smaller queries.', + }) + } + } else { + console.log(`Unknown error: ${error}`) + } return res.status(500).json({ - error: 'There was an unknown error editing the SQL snippet. Please try again.', + error: 'There was an unknown error generating the snippet title. Please try again.', }) } } diff --git a/package-lock.json b/package-lock.json index 5d7baf6a3e..9367b462ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -727,6 +727,8 @@ "@uidotdev/usehooks": "^2.4.1", "@zip.js/zip.js": "^2.7.29", "ai": "^2.2.26", + "ai-commands": "*", + "ajv": "^8.6.3", "awesome-debounce-promise": "^2.1.0", "blueimp-md5": "^2.19.0", "clsx": "^1.2.1", @@ -850,6 +852,21 @@ "glob": "7.1.7" } }, + "apps/studio/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "apps/studio/node_modules/argparse": { "version": "2.0.1", "license": "Python-2.0" @@ -962,6 +979,11 @@ "url": "https://github.com/sponsors/isaacs" } }, + "apps/studio/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "apps/studio/node_modules/resolve": { "version": "2.0.0-next.5", "dev": true, @@ -1847,11 +1869,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/highlight": "^7.22.13", + "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" }, "engines": { @@ -1859,28 +1882,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.22.20", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.0", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz", + "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", "dev": true, - "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helpers": "^7.23.0", - "@babel/parser": "^7.23.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.6", + "@babel/parser": "^7.23.6", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.0", - "@babel/types": "^7.23.0", + "@babel/traverse": "^7.23.6", + "@babel/types": "^7.23.6", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -1896,11 +1921,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.0", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.23.0", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -1922,8 +1948,9 @@ }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", "dev": true, - "license": "MIT", "dependencies": { "@babel/types": "^7.22.15" }, @@ -1932,13 +1959,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.15", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -1985,9 +2013,10 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.2", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", + "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", @@ -2053,9 +2082,10 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.0", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", @@ -2091,8 +2121,9 @@ }, "node_modules/@babel/helper-remap-async-to-generator": { "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.20", @@ -2155,9 +2186,10 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -2171,17 +2203,19 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.15", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-function-name": "^7.22.5", "@babel/template": "^7.22.15", @@ -2192,22 +2226,24 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.1", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", + "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", "dev": true, - "license": "MIT", "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.0", - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.23.6", + "@babel/types": "^7.23.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.20", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", @@ -2218,9 +2254,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz", - "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", "bin": { "parser": "bin/babel-parser.js" }, @@ -2229,9 +2265,10 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.22.15", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", + "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -2243,13 +2280,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.22.15", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", + "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.15" + "@babel/plugin-transform-optional-chaining": "^7.23.3" }, "engines": { "node": ">=6.9.0" @@ -2258,6 +2296,22 @@ "@babel/core": "^7.13.0" } }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", + "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-proposal-class-properties": { "version": "7.18.6", "dev": true, @@ -2350,8 +2404,9 @@ }, "node_modules/@babel/plugin-syntax-class-static-block": { "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -2364,8 +2419,9 @@ }, "node_modules/@babel/plugin-syntax-dynamic-import": { "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -2375,8 +2431,9 @@ }, "node_modules/@babel/plugin-syntax-export-namespace-from": { "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.3" }, @@ -2399,9 +2456,10 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", + "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -2413,9 +2471,10 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", + "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -2530,8 +2589,9 @@ }, "node_modules/@babel/plugin-syntax-private-property-in-object": { "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -2586,9 +2646,10 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", + "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -2600,13 +2661,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.22.15", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz", + "integrity": "sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.9", + "@babel/helper-remap-async-to-generator": "^7.22.20", "@babel/plugin-syntax-async-generators": "^7.8.4" }, "engines": { @@ -2617,13 +2679,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5" + "@babel/helper-remap-async-to-generator": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -2633,9 +2696,10 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", + "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -2647,9 +2711,10 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.23.0", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", + "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -2661,11 +2726,12 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", + "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -2676,11 +2742,12 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.22.11", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", + "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.11", + "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, @@ -2692,17 +2759,18 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.22.15", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", + "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-replace-supers": "^7.22.20", "@babel/helper-split-export-declaration": "^7.22.6", "globals": "^11.1.0" }, @@ -2714,12 +2782,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", + "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.5" + "@babel/template": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -2729,9 +2798,10 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.23.0", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", + "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -2743,11 +2813,12 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", + "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-create-regexp-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -2758,9 +2829,10 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", + "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -2772,9 +2844,10 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.22.11", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", + "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3" @@ -2787,11 +2860,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", + "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -2802,9 +2876,10 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.22.11", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", + "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" @@ -2832,11 +2907,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.22.15", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", + "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -2846,12 +2923,13 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", + "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -2862,9 +2940,10 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.22.11", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", + "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-json-strings": "^7.8.3" @@ -2877,9 +2956,10 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", + "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -2891,9 +2971,10 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.22.11", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", + "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" @@ -2906,9 +2987,10 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", + "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -2920,11 +3002,12 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.23.0", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", + "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -2935,11 +3018,12 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.23.0", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", + "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-simple-access": "^7.22.5" }, @@ -2951,12 +3035,13 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.23.0", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", + "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.20" }, @@ -2968,11 +3053,12 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", + "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -2998,9 +3084,10 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", + "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -3012,9 +3099,10 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.22.11", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", + "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" @@ -3027,9 +3115,10 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.22.11", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", + "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-numeric-separator": "^7.10.4" @@ -3042,15 +3131,16 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.22.15", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", + "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.9", + "@babel/compat-data": "^7.23.3", "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.22.15" + "@babel/plugin-transform-parameters": "^7.23.3" }, "engines": { "node": ">=6.9.0" @@ -3060,12 +3150,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", + "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5" + "@babel/helper-replace-supers": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -3075,9 +3166,10 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.22.11", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", + "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" @@ -3090,9 +3182,10 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.23.0", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", + "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", @@ -3106,9 +3199,10 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.22.15", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", + "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -3120,11 +3214,12 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", + "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -3135,12 +3230,13 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.22.11", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", + "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.11", + "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, @@ -3152,9 +3248,10 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", + "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -3227,9 +3324,10 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.22.10", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", + "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "regenerator-transform": "^0.15.2" @@ -3242,9 +3340,10 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", + "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -3256,9 +3355,10 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", + "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -3270,9 +3370,10 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", + "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" @@ -3285,9 +3386,10 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", + "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -3299,9 +3401,10 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", + "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -3313,9 +3416,10 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", + "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -3344,9 +3448,10 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.22.10", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", + "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -3358,11 +3463,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", + "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-create-regexp-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -3373,11 +3479,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", + "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-create-regexp-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -3388,11 +3495,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.22.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", + "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-create-regexp-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -3403,24 +3511,26 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.22.20", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.6.tgz", + "integrity": "sha512-2XPn/BqKkZCpzYhUUNZ1ssXw7DcXfKQEjv/uXZUXgaebCMYmkEsfZ2yY+vv+xtXv50WmL5SGhyB6/xsWxIvvOQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.20", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.15", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.15", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.22.5", - "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", @@ -3432,59 +3542,58 @@ "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.22.5", - "@babel/plugin-transform-async-generator-functions": "^7.22.15", - "@babel/plugin-transform-async-to-generator": "^7.22.5", - "@babel/plugin-transform-block-scoped-functions": "^7.22.5", - "@babel/plugin-transform-block-scoping": "^7.22.15", - "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-class-static-block": "^7.22.11", - "@babel/plugin-transform-classes": "^7.22.15", - "@babel/plugin-transform-computed-properties": "^7.22.5", - "@babel/plugin-transform-destructuring": "^7.22.15", - "@babel/plugin-transform-dotall-regex": "^7.22.5", - "@babel/plugin-transform-duplicate-keys": "^7.22.5", - "@babel/plugin-transform-dynamic-import": "^7.22.11", - "@babel/plugin-transform-exponentiation-operator": "^7.22.5", - "@babel/plugin-transform-export-namespace-from": "^7.22.11", - "@babel/plugin-transform-for-of": "^7.22.15", - "@babel/plugin-transform-function-name": "^7.22.5", - "@babel/plugin-transform-json-strings": "^7.22.11", - "@babel/plugin-transform-literals": "^7.22.5", - "@babel/plugin-transform-logical-assignment-operators": "^7.22.11", - "@babel/plugin-transform-member-expression-literals": "^7.22.5", - "@babel/plugin-transform-modules-amd": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.15", - "@babel/plugin-transform-modules-systemjs": "^7.22.11", - "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.4", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.5", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", + "@babel/plugin-transform-for-of": "^7.23.6", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.3", + "@babel/plugin-transform-modules-umd": "^7.23.3", "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.22.5", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.11", - "@babel/plugin-transform-numeric-separator": "^7.22.11", - "@babel/plugin-transform-object-rest-spread": "^7.22.15", - "@babel/plugin-transform-object-super": "^7.22.5", - "@babel/plugin-transform-optional-catch-binding": "^7.22.11", - "@babel/plugin-transform-optional-chaining": "^7.22.15", - "@babel/plugin-transform-parameters": "^7.22.15", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.11", - "@babel/plugin-transform-property-literals": "^7.22.5", - "@babel/plugin-transform-regenerator": "^7.22.10", - "@babel/plugin-transform-reserved-words": "^7.22.5", - "@babel/plugin-transform-shorthand-properties": "^7.22.5", - "@babel/plugin-transform-spread": "^7.22.5", - "@babel/plugin-transform-sticky-regex": "^7.22.5", - "@babel/plugin-transform-template-literals": "^7.22.5", - "@babel/plugin-transform-typeof-symbol": "^7.22.5", - "@babel/plugin-transform-unicode-escapes": "^7.22.10", - "@babel/plugin-transform-unicode-property-regex": "^7.22.5", - "@babel/plugin-transform-unicode-regex": "^7.22.5", - "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.23.4", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", "@babel/preset-modules": "0.1.6-no-external-plugins", - "@babel/types": "^7.22.19", - "babel-plugin-polyfill-corejs2": "^0.4.5", - "babel-plugin-polyfill-corejs3": "^0.8.3", - "babel-plugin-polyfill-regenerator": "^0.5.2", + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", "core-js-compat": "^3.31.0", "semver": "^6.3.1" }, @@ -3725,19 +3834,20 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.2", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", + "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -3745,11 +3855,12 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, @@ -10837,9 +10948,10 @@ } }, "node_modules/@types/common-tags": { - "version": "1.8.2", - "dev": true, - "license": "MIT" + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.4.tgz", + "integrity": "sha512-S+1hLDJPjWNDhcGxsxEbepzaxWqURP/o+3cP4aa2w7yBXgdcmKGQtZzP8JbyfOd0m+33nh+8+kvxYE2UJtBDkg==", + "dev": true }, "node_modules/@types/connect": { "version": "3.4.36", @@ -12437,9 +12549,9 @@ } }, "node_modules/ai": { - "version": "2.2.26", - "resolved": "https://registry.npmjs.org/ai/-/ai-2.2.26.tgz", - "integrity": "sha512-ANvG9sTT/pwOykEj1Huos8BuiYD4azUVoMXxDV3uhWO1KdZP5iN1GnSnnh08+ZDXKhedZnXZqvVTG2rSEgCWPQ==", + "version": "2.2.29", + "resolved": "https://registry.npmjs.org/ai/-/ai-2.2.29.tgz", + "integrity": "sha512-/zzSTTKF5LxMGQuNVUnNjs7X6PWYfb6M88Zn74gCUnM3KCYgh0CiAWhLyhKP6UtK0H5mHSmXgt0ZkZYUecRp0w==", "dependencies": { "eventsource-parser": "1.0.0", "nanoid": "3.3.6", @@ -12473,6 +12585,10 @@ } } }, + "node_modules/ai-commands": { + "resolved": "packages/ai-commands", + "link": true + }, "node_modules/ajv": { "version": "6.12.6", "license": "MIT", @@ -13301,12 +13417,13 @@ "license": "MIT" }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.5", + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz", + "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.2", + "@babel/helper-define-polyfill-provider": "^0.4.4", "semver": "^6.3.1" }, "peerDependencies": { @@ -13314,23 +13431,25 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.4", + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", + "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.2", - "core-js-compat": "^3.32.2" + "@babel/helper-define-polyfill-provider": "^0.4.4", + "core-js-compat": "^3.33.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.2", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz", + "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.2" + "@babel/helper-define-polyfill-provider": "^0.4.4" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -13357,6 +13476,19 @@ "node": ">=4" } }, + "node_modules/babel-plugin-transform-import-meta": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-import-meta/-/babel-plugin-transform-import-meta-2.2.1.tgz", + "integrity": "sha512-AxNh27Pcg8Kt112RGa3Vod2QS2YXKKJ6+nSvRtv7qQTJAdx0MZa4UHZ4lnxHUWA2MNbLuZQv5FVab4P1CoLOWw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.4.4", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@babel/core": "^7.10.0" + } + }, "node_modules/babel-preset-current-node-syntax": { "version": "1.0.1", "dev": true, @@ -13674,7 +13806,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.1", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", "funding": [ { "type": "opencollective", @@ -13689,11 +13823,10 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, "bin": { @@ -13961,7 +14094,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001542", + "version": "1.0.30001570", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz", + "integrity": "sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw==", "funding": [ { "type": "opencollective", @@ -13975,8 +14110,7 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0" + ] }, "node_modules/capital-case": { "version": "1.0.4", @@ -14991,11 +15125,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.33.0", + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.34.0.tgz", + "integrity": "sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA==", "dev": true, - "license": "MIT", "dependencies": { - "browserslist": "^4.22.1" + "browserslist": "^4.22.2" }, "funding": { "type": "opencollective", @@ -16432,8 +16567,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.539", - "license": "ISC" + "version": "1.4.613", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.613.tgz", + "integrity": "sha512-r4x5+FowKG6q+/Wj0W9nidx7QO31BJwmR2uEo+Qh3YLGQ8SbBAFuDFpTxzly/I2gsbrFwBuIjrMp423L3O5U3w==" }, "node_modules/elkjs": { "version": "0.8.2", @@ -22685,8 +22821,9 @@ } }, "node_modules/jsonrepair": { - "version": "3.4.1", - "license": "ISC", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.5.0.tgz", + "integrity": "sha512-SavvDsUP9Xnqo2MoC6Wl6zNyX3f+I5199hRbXBtAITyP2NTPyAgyx5xM0bgcIljRjzsIvOBANbgfWe8XXlyeLA==", "bin": { "jsonrepair": "bin/cli.js" } @@ -23021,8 +23158,9 @@ }, "node_modules/lodash.debounce": { "version": "4.0.8", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true }, "node_modules/lodash.includes": { "version": "4.3.0", @@ -23194,8 +23332,9 @@ }, "node_modules/lru-cache": { "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "license": "ISC", "dependencies": { "yallist": "^3.0.2" } @@ -26400,8 +26539,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.13", - "license": "MIT" + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/node-sql-parser": { "version": "4.11.0", @@ -29551,8 +29691,9 @@ }, "node_modules/regenerator-transform": { "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.4" } @@ -35042,8 +35183,9 @@ }, "node_modules/yallist": { "version": "3.1.1", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true }, "node_modules/yaml": { "version": "2.3.2", @@ -35184,6 +35326,53 @@ "version": "4.4.2", "license": "MIT" }, + "packages/ai-commands": { + "version": "0.0.0", + "license": "MIT", + "dependencies": { + "@serafin/schema-builder": "^0.18.5", + "ai": "^2.2.29", + "common-tags": "^1.8.2", + "config": "*", + "jsonrepair": "^3.5.0", + "openai": "^4.20.1" + }, + "devDependencies": { + "@babel/core": "^7.23.6", + "@babel/preset-env": "^7.23.6", + "@jest/globals": "^29.7.0", + "@types/common-tags": "^1.8.4", + "babel-jest": "^29.7.0", + "babel-plugin-transform-import-meta": "^2.2.1", + "dotenv": "^16.3.1", + "jest": "^29.7.0", + "mdast-util-from-markdown": "^2.0.0", + "sql-formatter": "^15.0.2", + "ts-jest": "^29.1.1", + "tsconfig": "*", + "typescript": "^5.2.2" + } + }, + "packages/ai-commands/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "packages/ai-commands/node_modules/sql-formatter": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.0.2.tgz", + "integrity": "sha512-B8FTRc1dhb36lfuwSdiLhwrhkvT3PU/3es7YDPPQBOhbGHdXKlweAXTRS+QfCWk06ufAh118yFja6NcukBS4gg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "get-stdin": "=8.0.0", + "nearley": "^2.20.1" + }, + "bin": { + "sql-formatter": "bin/sql-formatter-cli.cjs" + } + }, "packages/common": { "version": "0.0.0", "license": "MIT", diff --git a/packages/ai-commands/README.md b/packages/ai-commands/README.md new file mode 100644 index 0000000000..5068e2ba40 --- /dev/null +++ b/packages/ai-commands/README.md @@ -0,0 +1,12 @@ +# ai-commands + +## Main purpose + +This package contains all features involving OpenAI API. Technically, each feature is implemented as a function which +can be easily tested for regressions. + +The streaming functions only work on Edge runtime so they can only be imported via a special `edge` subpath like so: + +``` +import { chatRlsPolicy } from 'ai-commands/edge' +``` diff --git a/packages/ai-commands/babel.config.js b/packages/ai-commands/babel.config.js new file mode 100644 index 0000000000..7a52c7fe86 --- /dev/null +++ b/packages/ai-commands/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@babel/preset-env', { targets: { node: 'current' } }]], + plugins: ['babel-plugin-transform-import-meta'], +} diff --git a/packages/ai-commands/edge.ts b/packages/ai-commands/edge.ts new file mode 100644 index 0000000000..269714fb33 --- /dev/null +++ b/packages/ai-commands/edge.ts @@ -0,0 +1,2 @@ +export * from './src/errors' +export * from './src/sql.edge' diff --git a/packages/ai-commands/index.ts b/packages/ai-commands/index.ts new file mode 100644 index 0000000000..dc525721fc --- /dev/null +++ b/packages/ai-commands/index.ts @@ -0,0 +1,2 @@ +export * from './src/errors' +export * from './src/sql' diff --git a/packages/ai-commands/jest.config.js b/packages/ai-commands/jest.config.js new file mode 100644 index 0000000000..e5f05acb26 --- /dev/null +++ b/packages/ai-commands/jest.config.js @@ -0,0 +1,14 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + transform: { + '^.+\\.ts?$': 'ts-jest', + '^.+\\.(js|jsx)$': 'babel-jest', + }, + setupFiles: ['./test/setup.ts'], + testTimeout: 15000, + transformIgnorePatterns: [ + 'node_modules/(?!(mdast-.*|micromark|micromark-.*|unist-.*|decode-named-character-reference|character-entities)/)', + ], +} diff --git a/packages/ai-commands/package.json b/packages/ai-commands/package.json new file mode 100644 index 0000000000..4a5e25a1b1 --- /dev/null +++ b/packages/ai-commands/package.json @@ -0,0 +1,34 @@ +{ + "name": "ai-commands", + "version": "0.0.0", + "main": "./index.ts", + "types": "./index.ts", + "license": "MIT", + "scripts": { + "typecheck": "tsc --noEmit", + "test": "jest" + }, + "dependencies": { + "@serafin/schema-builder": "^0.18.5", + "ai": "^2.2.29", + "common-tags": "^1.8.2", + "config": "*", + "jsonrepair": "^3.5.0", + "openai": "^4.20.1" + }, + "devDependencies": { + "@babel/core": "^7.23.6", + "@babel/preset-env": "^7.23.6", + "@jest/globals": "^29.7.0", + "@types/common-tags": "^1.8.4", + "babel-jest": "^29.7.0", + "babel-plugin-transform-import-meta": "^2.2.1", + "dotenv": "^16.3.1", + "jest": "^29.7.0", + "mdast-util-from-markdown": "^2.0.0", + "sql-formatter": "^15.0.2", + "ts-jest": "^29.1.1", + "tsconfig": "*", + "typescript": "^5.2.2" + } +} diff --git a/packages/ai-commands/src/__snapshots__/sql.test.ts.snap b/packages/ai-commands/src/__snapshots__/sql.test.ts.snap new file mode 100644 index 0000000000..8317480bf1 --- /dev/null +++ b/packages/ai-commands/src/__snapshots__/sql.test.ts.snap @@ -0,0 +1,51 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`debug fix order of operations 1`] = ` +"create table departments ( + id bigint primary key generated always as identity, + name text +); + +create table employees ( + id bigint primary key generated always as identity, + name text, + email text, + department_id bigint references departments (id) +);" +`; + +exports[`debug fix typos 1`] = ` +"select + * +from + employees;" +`; + +exports[`edit add length constraint 1`] = ` +"create table employees ( + id bigint primary key generated always as identity, + name text check (length(name) >= 4), + email text +);" +`; + +exports[`generate single table with specified columns 1`] = ` +"create table employees ( + id bigint primary key generated always as identity, + name text, + email text, + position text +);" +`; + +exports[`generate single table with specified columns 2`] = `"Employee Tracking Table"`; + +exports[`rls chat select policy using table definition 1`] = ` +"create policy select_todo_policy on todos for +select + using (user_id = auth.uid ());" +`; + +exports[`title title matches content 1`] = `"Employee and Department Tables"`; + +exports[`title title matches content 2`] = `"Tables to track employees and their respective departments"`; diff --git a/packages/ai-commands/src/errors.ts b/packages/ai-commands/src/errors.ts new file mode 100644 index 0000000000..52c2d13b79 --- /dev/null +++ b/packages/ai-commands/src/errors.ts @@ -0,0 +1,23 @@ +export class ApplicationError extends Error { + constructor(message: string, public data: Record = {}) { + super(message) + } +} + +export class ContextLengthError extends ApplicationError { + constructor() { + super('LLM context length exceeded') + } +} + +export class EmptyResponseError extends ApplicationError { + constructor() { + super('LLM API response succeeded but returned nothing') + } +} + +export class EmptySqlError extends ApplicationError { + constructor() { + super('LLM did not generate any SQL') + } +} diff --git a/packages/ai-commands/src/sql.edge.ts b/packages/ai-commands/src/sql.edge.ts new file mode 100644 index 0000000000..b6c04ce878 --- /dev/null +++ b/packages/ai-commands/src/sql.edge.ts @@ -0,0 +1,90 @@ +import { OpenAIStream } from 'ai' +import { codeBlock, oneLine, stripIndent } from 'common-tags' +import OpenAI from 'openai' +import { ContextLengthError } from './errors' + +export type AiAssistantMessage = { + content: string + role: 'user' | 'assistant' +} + +/** + * Responds to a conversation about building an RLS policy. + * + * @returns A `ReadableStream` containing the response text and SQL. + */ +export async function chatRlsPolicy( + openai: OpenAI, + messages: AiAssistantMessage[], + entityDefinitions?: string[], + policyDefinition?: string +): Promise> { + const initMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [ + { + role: 'system', + content: stripIndent` + You're an Postgres expert in writing row level security policies. Your purpose is to + generate a policy with the constraints given by the user. You will be provided a schema + on which the policy should be applied. + + The output should use the following instructions: + - The generated SQL must be valid SQL. + - Always use double apostrophe in SQL strings (eg. 'Night''s watch') + - You can use only CREATE POLICY or ALTER POLICY queries, no other queries are allowed. + - You can add short explanations to your messages. + - The result should be a valid markdown. The SQL code should be wrapped in \`\`\`. + - Always use "auth.uid()" instead of "current_user". + - You can't use "USING" expression on INSERT policies. + - Only use "WITH CHECK" expression on INSERT or UPDATE policies. + - The policy name should be short text explaining the policy, enclosed in double quotes. + - Always put explanations as separate text. Never use inline SQL comments. + - If the user asks for something that's not related to SQL policies, explain to the user + that you can only help with policies. + + The output should look like this: + "CREATE POLICY user_policy ON users FOR INSERT USING (user_name = current_user) WITH (true);" + `, + }, + ] + + if (entityDefinitions) { + const definitions = codeBlock`${entityDefinitions.join('\n\n')}` + initMessages.push({ + role: 'user', + content: oneLine`Here is my database schema for reference: ${definitions}`, + }) + } + + if (policyDefinition !== undefined) { + const definitionBlock = codeBlock`${policyDefinition}` + initMessages.push({ + role: 'user', + content: codeBlock` + Here is my policy definition for reference: + ${definitionBlock} + `.trim(), + }) + } + + if (messages) { + initMessages.push(...messages) + } + + try { + const response = await openai.chat.completions.create({ + model: 'gpt-3.5-turbo-1106', + messages: initMessages, + max_tokens: 1024, + temperature: 0, + stream: true, + }) + + // Transform the streamed SSE response from OpenAI to a ReadableStream + return OpenAIStream(response) + } catch (error) { + if (error instanceof Error && 'code' in error && error.code === 'context_length_exceeded') { + throw new ContextLengthError() + } + throw error + } +} diff --git a/packages/ai-commands/src/sql.test.ts b/packages/ai-commands/src/sql.test.ts new file mode 100644 index 0000000000..8982f7be77 --- /dev/null +++ b/packages/ai-commands/src/sql.test.ts @@ -0,0 +1,128 @@ +import { describe, expect, test } from '@jest/globals' +import { codeBlock } from 'common-tags' +import OpenAI from 'openai' +import { collectStream, extractMarkdownSql, formatSql } from '../test/util' +import { debugSql, editSql, generateSql, titleSql } from './sql' +import { chatRlsPolicy } from './sql.edge' + +const openAiKey = process.env.OPENAI_KEY +const openai = new OpenAI({ apiKey: openAiKey }) + +describe('generate', () => { + test('single table with specified columns', async () => { + const { sql, title } = await generateSql( + openai, + 'create a table to track employees with name, email, and position' + ) + + expect(formatSql(sql)).toMatchSnapshot() + expect(title).toMatchSnapshot() + }) +}) + +describe('edit', () => { + test('add length constraint', async () => { + const { sql } = await editSql( + openai, + 'force name to be at least 4 characters', + codeBlock` + create table employees ( + id bigint primary key generated always as identity, + name text, + email text + ); + ` + ) + + expect(formatSql(sql)).toMatchSnapshot() + }) +}) + +describe('debug', () => { + test('fix order of operations', async () => { + const { sql } = await debugSql( + openai, + 'relation "departments" does not exist', + codeBlock` + create table employees ( + id bigint primary key generated always as identity, + name text, + email text, + department_id bigint references departments (id) + ); + + create table departments ( + id bigint primary key generated always as identity, + name text + ); + ` + ) + + expect(formatSql(sql)).toMatchSnapshot() + }) + + test('fix typos', async () => { + const { sql, solution } = await debugSql( + openai, + 'syntax error at or near "fromm"', + codeBlock` + select * fromm employees; + ` + ) + + expect(solution).toBeDefined() + expect(formatSql(sql)).toMatchSnapshot() + }) +}) + +describe('title', () => { + test('title matches content', async () => { + const { title, description } = await titleSql( + openai, + codeBlock` + create table employees ( + id bigint primary key generated always as identity, + name text, + email text, + department_id bigint references departments (id) + ); + + create table departments ( + id bigint primary key generated always as identity, + name text + ); + ` + ) + + expect(title).toMatchSnapshot() + expect(description).toMatchSnapshot() + }) +}) + +describe('rls chat', () => { + test('select policy using table definition', async () => { + const responseStream = await chatRlsPolicy( + openai, + [ + { + role: 'user', + content: 'Users can only select their own todos', + }, + ], + [ + codeBlock` + create table todos ( + id bigint primary key generated always as identity, + task text, + email text, + user_id uuid references auth.users (id) + ); + `, + ] + ) + const responseText = await collectStream(responseStream) + const [sql] = extractMarkdownSql(responseText) + + expect(formatSql(sql)).toMatchSnapshot() + }) +}) diff --git a/packages/ai-commands/src/sql.ts b/packages/ai-commands/src/sql.ts new file mode 100644 index 0000000000..99966ed9dd --- /dev/null +++ b/packages/ai-commands/src/sql.ts @@ -0,0 +1,409 @@ +import { SchemaBuilder } from '@serafin/schema-builder' +import { codeBlock, stripIndent } from 'common-tags' +import { jsonrepair } from 'jsonrepair' +import OpenAI from 'openai' +import { ContextLengthError, EmptyResponseError, EmptySqlError } from './errors' + +// Declare JSON schema for each function that the LLM can call +const generateSqlSchema = SchemaBuilder.emptySchema() + .addString('sql', { + description: stripIndent` + The generated SQL (must be valid SQL). + - For primary keys, always use "id bigint primary key generated always as identity" (not serial) + - Prefer creating foreign key references in the create statement + - Prefer 'text' over 'varchar' + - Prefer 'timestamp with time zone' over 'date' + - Use vector(384) data type for any embedding/vector related query + - Always use double apostrophe in SQL strings (eg. 'Night''s watch') + `, + }) + .addString('title', { + description: stripIndent` + The title of the SQL. + - Omit words like 'SQL', 'Postgres', or 'Query' + `, + }) + +const editSqlSchema = SchemaBuilder.emptySchema().addString('sql', { + description: stripIndent` + The modified SQL (must be valid SQL). + - Assume the query hasn't been executed yet + - For primary keys, always use "id bigint primary key generated always as identity" (not serial) + - When creating tables, always add foreign key references inline + - Prefer 'text' over 'varchar' + - Prefer 'timestamp with time zone' over 'date' + - Use vector(384) data type for any embedding/vector related query + - Always use double apostrophe in SQL strings (eg. 'Night''s watch') + - Use real examples when possible + - Add constraints if requested + `, +}) + +const debugSqlSchema = SchemaBuilder.emptySchema() + .addString('solution', { + description: 'A short suggested solution for the error (as concise as possible).', + }) + .addString('sql', { + description: 'The SQL rewritten to apply the solution. Includes all the original logic, but modified to fix the issue.', + }) + +const generateTitleSchema = SchemaBuilder.emptySchema() + .addString('title', { + description: stripIndent` + The generated title for the SQL snippet (short and concise). + - Omit these words: 'SQL', 'Postgres', 'Query', 'Database' + `, + }) + .addString('description', { + description: stripIndent` + The generated description for the SQL snippet. + `, + }) + +// Reference auto-generated types for each JSON schema +export type GenerateSqlResult = typeof generateSqlSchema.T +export type EditSqlResult = typeof editSqlSchema.T +export type DebugSqlResult = typeof debugSqlSchema.T +export type GenerateTitleResult = typeof generateTitleSchema.T + +// Combine the completion functions +const completionFunctions = { + generateSql: { + name: 'generateSql', + description: 'Generates Postgres SQL based on a natural language prompt', + parameters: generateSqlSchema.schema as Record, + }, + editSql: { + name: 'editSql', + description: "Edits a Postgres SQL query based on the user's instructions", + parameters: editSqlSchema.schema as Record, + }, + debugSql: { + name: 'debugSql', + description: stripIndent` + Debugs a Postgres SQL error. Returns the fixed SQL and a solution explaining it. + - Create extensions if they are missing (only for valid extensions) + - Suggest creating tables if they are missing + - Include all of the original SQL + - For primary keys, always use "id bigint primary key generated always as identity" (not serial) + - When creating tables, always add foreign key references inline + - Prefer 'text' over 'varchar' + - Prefer 'timestamp with time zone' over 'date' + - Use vector(384) data type for any embedding/vector related query + - Always use double apostrophe in SQL strings (eg. 'Night''s watch') + `, + parameters: debugSqlSchema.schema as Record, + }, + generateTitle: { + name: 'generateTitle', + description: stripIndent` + Generates a short title and summarized description for a Postgres SQL snippet. + + The description should describe why this table was created (eg. "Table to track todos") + `, + parameters: generateTitleSchema.schema as Record, + }, +} satisfies Record + +/** + * Generates a SQL snippet based on the provided prompt. + * + * @returns The generated SQL along with a title for it. + */ +export async function generateSql(openai: OpenAI, prompt: string, entityDefinitions?: string[]) { + const hasEntityDefinitions = entityDefinitions !== undefined && entityDefinitions.length > 0 + + const completionMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [] + + if (hasEntityDefinitions) { + completionMessages.push({ + role: 'user', + content: codeBlock` + Here is my database schema for reference: + ${entityDefinitions.join('\n\n')} + `, + }) + } + + completionMessages.push({ + role: 'user', + content: prompt, + }) + + try { + const completionResponse = await openai.chat.completions.create({ + model: 'gpt-3.5-turbo-1106', + messages: completionMessages, + max_tokens: 1024, + temperature: 0, + tool_choice: { + type: 'function', + function: { + name: completionFunctions.generateSql.name, + }, + }, + tools: [ + { + type: 'function', + function: completionFunctions.generateSql, + }, + ], + stream: false, + }) + + const [firstChoice] = completionResponse.choices + const [firstTool] = firstChoice.message?.tool_calls ?? [] + + const sqlResponseString = firstTool?.function.arguments + + if (!sqlResponseString) { + throw new EmptyResponseError() + } + + const repairedJsonString = jsonrepair(sqlResponseString) + + const result: GenerateSqlResult = JSON.parse(repairedJsonString) + + if (!result.sql) { + throw new EmptySqlError() + } + + return result + } catch (error) { + if (error instanceof Error && 'code' in error && error.code === 'context_length_exceeded') { + throw new ContextLengthError() + } + throw error + } +} + +/** + * Modifies a SQL snippet based on the provided prompt. + * + * @returns The modified SQL. + */ +export async function editSql( + openai: OpenAI, + prompt: string, + sql: string, + entityDefinitions?: string[] +) { + const hasEntityDefinitions = entityDefinitions !== undefined && entityDefinitions.length > 0 + + const completionMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [] + + if (hasEntityDefinitions) { + completionMessages.push({ + role: 'user', + content: codeBlock` + Here is my database schema for reference: + ${entityDefinitions.join('\n\n')} + `, + }) + } + + completionMessages.push( + { + role: 'user', + content: stripIndent` + Here is my current SQL: + ${sql} + `, + }, + { + role: 'user', + content: prompt, + } + ) + + try { + const completionResponse = await openai.chat.completions.create({ + model: 'gpt-3.5-turbo-1106', + messages: completionMessages, + max_tokens: 2048, + temperature: 0, + tool_choice: { + type: 'function', + function: { + name: completionFunctions.editSql.name, + }, + }, + tools: [ + { + type: 'function', + function: completionFunctions.editSql, + }, + ], + stream: false, + }) + + const [firstChoice] = completionResponse.choices + const [firstTool] = firstChoice.message?.tool_calls ?? [] + + const sqlResponseString = firstTool?.function.arguments + + if (!sqlResponseString) { + throw new EmptyResponseError() + } + + const repairedJsonString = jsonrepair(sqlResponseString) + + const result: EditSqlResult = JSON.parse(repairedJsonString) + + if (!result.sql) { + throw new EmptySqlError() + } + + return result + } catch (error) { + if (error instanceof Error && 'code' in error && error.code === 'context_length_exceeded') { + throw new ContextLengthError() + } + throw error + } +} + +/** + * Debugs SQL errors. + * + * @returns A suggested SQL fix along with a solution explanation. + */ +export async function debugSql( + openai: OpenAI, + errorMessage: string, + sql: string, + entityDefinitions?: string[] +) { + const hasEntityDefinitions = entityDefinitions !== undefined && entityDefinitions.length > 0 + + const completionMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [] + + if (hasEntityDefinitions) { + completionMessages.push({ + role: 'user', + content: codeBlock` + Here is my database schema for reference: + ${entityDefinitions.join('\n\n')} + `, + }) + } + + completionMessages.push( + { + role: 'user', + content: stripIndent` + Here is my current SQL: + ${sql} + `, + }, + { + role: 'user', + content: stripIndent` + Here is the error I am getting: + ${errorMessage} + `, + } + ) + + try { + const completionResponse = await openai.chat.completions.create({ + model: 'gpt-3.5-turbo-1106', + messages: completionMessages, + max_tokens: 2048, + temperature: 0, + tool_choice: { + type: 'function', + function: { + name: completionFunctions.debugSql.name, + }, + }, + tools: [ + { + type: 'function', + function: completionFunctions.debugSql, + }, + ], + stream: false, + }) + + const [firstChoice] = completionResponse.choices + const [firstTool] = firstChoice.message?.tool_calls ?? [] + + const sqlResponseString = firstTool?.function.arguments + + if (!sqlResponseString) { + throw new EmptyResponseError() + } + + const repairedJsonString = jsonrepair(sqlResponseString) + + const result: DebugSqlResult = JSON.parse(repairedJsonString) + + if (!result.sql) { + throw new EmptySqlError() + } + + return result + } catch (error) { + if (error instanceof Error && 'code' in error && error.code === 'context_length_exceeded') { + throw new ContextLengthError() + } + throw error + } +} + +/** + * Generates a snippet title based on the provided SQL. + * + * @returns A title and description for the SQL snippet. + */ +export async function titleSql(openai: OpenAI, sql: string) { + const completionMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [ + { + role: 'user', + content: sql, + }, + ] + + try { + const completionResponse = await openai.chat.completions.create({ + model: 'gpt-3.5-turbo-1106', + messages: completionMessages, + max_tokens: 1024, + temperature: 0, + tool_choice: { + type: 'function', + function: { + name: completionFunctions.generateTitle.name, + }, + }, + tools: [ + { + type: 'function', + function: completionFunctions.generateTitle, + }, + ], + stream: false, + }) + + const [firstChoice] = completionResponse.choices + const [firstTool] = firstChoice.message?.tool_calls ?? [] + + const sqlResponseString = firstTool?.function.arguments + + if (!sqlResponseString) { + throw new EmptyResponseError() + } + + const repairedJsonString = jsonrepair(sqlResponseString) + + const result: GenerateTitleResult = JSON.parse(repairedJsonString) + + return result + } catch (error) { + if (error instanceof Error && 'code' in error && error.code === 'context_length_exceeded') { + throw new ContextLengthError() + } + throw error + } +} diff --git a/packages/ai-commands/test/setup.ts b/packages/ai-commands/test/setup.ts new file mode 100644 index 0000000000..a963f60147 --- /dev/null +++ b/packages/ai-commands/test/setup.ts @@ -0,0 +1,8 @@ +import { config } from 'dotenv' +import { statSync } from 'fs' + +// Use studio .env.local for now +const envPath = '../../apps/studio/.env.local' + +statSync(envPath) +config({ path: envPath }) diff --git a/packages/ai-commands/test/util.ts b/packages/ai-commands/test/util.ts new file mode 100644 index 0000000000..b5f00b696c --- /dev/null +++ b/packages/ai-commands/test/util.ts @@ -0,0 +1,47 @@ +import { fromMarkdown } from 'mdast-util-from-markdown' +import type { Code } from 'mdast-util-from-markdown/lib' +import { format } from 'sql-formatter' + +declare global { + interface ReadableStream { + [Symbol.asyncIterator](): AsyncIterableIterator + } +} + +/** + * Formats Postgres SQL into a consistent format. + * + * @returns The formatted SQL. + */ +export const formatSql = (sql: string) => + format(sql, { language: 'postgresql', keywordCase: 'lower' }) + +/** + * Collects an `ArrayBuffer` stream into a single decoded string. + * + * @returns A single string combining all the decoded stream chunks. + */ +export async function collectStream(stream: ReadableStream) { + const textDecoderStream = new TextDecoderStream() + + let content = '' + + for await (const chunk of stream.pipeThrough(textDecoderStream)) { + content += chunk + } + + return content +} + +/** + * Parses markdown and extracts all SQL code blocks. + * + * @returns An array of string content from each SQL code block. + */ +export function extractMarkdownSql(markdown: string) { + const mdTree = fromMarkdown(markdown) + + return mdTree.children + .filter((node): node is Code => node.type === 'code' && node.lang === 'sql') + .map(({ value }) => value) +} diff --git a/packages/ai-commands/tsconfig.json b/packages/ai-commands/tsconfig.json new file mode 100644 index 0000000000..cd6c94d6e8 --- /dev/null +++ b/packages/ai-commands/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "tsconfig/react-library.json", + "include": ["."], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/turbo.json b/turbo.json index 91a5d7a34f..ae965e2771 100644 --- a/turbo.json +++ b/turbo.json @@ -54,6 +54,7 @@ "cache": false }, "typecheck": { + "dependsOn": ["^typecheck"], "outputs": ["**/node_modules/.cache/tsbuildinfo.json"] } }