diff --git a/apps/api/package.json b/apps/api/package.json index 07cbff3f..92861ad6 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -50,6 +50,7 @@ "lru-cache": "^10.2.2", "mssql": "^11.0.1", "node-fetch": "^3.3.2", + "openai": "^4.77.3", "p-all": "^5.0.0", "p-queue": "^8.0.1", "parse-duration": "^1.1.0", diff --git a/apps/api/src/datasources/structure.ts b/apps/api/src/datasources/structure.ts index 3a1a024d..3ff5f234 100644 --- a/apps/api/src/datasources/structure.ts +++ b/apps/api/src/datasources/structure.ts @@ -3,6 +3,8 @@ import prisma, { DataSource, DataSourceSchema as DBDataSourceSchema, decrypt, + getWorkspaceById, + getWorkspaceWithSecrets, } from '@briefer/database' import { IOServer } from '../websocket/index.js' import { @@ -37,6 +39,7 @@ import { PythonExecutionError } from '../python/index.js' import { getSqlServerSchema } from './sqlserver.js' import { z } from 'zod' import { splitEvery } from 'ramda' +import { createEmbedding } from '../embedding.js' function decryptDBData( dataSourceId: string, @@ -519,6 +522,15 @@ async function _refreshDataSourceStructure( socketServer: IOServer, dataSource: APIDataSource ) { + const workspace = await getWorkspaceWithSecrets( + dataSource.config.data.workspaceId + ) + if (!workspace) { + throw new Error( + `Failed to find Workspace(${dataSource.config.data.workspaceId}) for DataSource(${dataSource.config.data.id})` + ) + } + const updateQueue = new PQueue({ concurrency: 1 }) const tables: { schema: string; table: string }[] = [] let defaultSchema = '' @@ -526,12 +538,38 @@ async function _refreshDataSourceStructure( defaultSchema = defaultSchema || schema tables.push({ schema, table: tableName }) updateQueue.add(async () => { + const openAiApiKey = workspace.secrets?.openAiApiKey + let embedding: number[] | null = null + if (openAiApiKey) { + let ddl = `CREATE TABLE ${schema}.${tableName} (\n` + for (const c of table.columns) { + ddl += ` ${c.name} ${c.type}\n` + } + try { + embedding = await createEmbedding( + ddl, + decrypt(openAiApiKey, config().WORKSPACE_SECRETS_ENCRYPTION_KEY) + ) + } catch (err) { + logger().error( + { + err, + dataSourceId: dataSource.config.data.id, + dataSourceType: dataSource.config.type, + schemaName: schema, + tableName, + }, + 'Failed to create embedding' + ) + } + } + await prisma().dataSourceSchema.update({ where: { id: dataSource.structure.id }, data: { defaultSchema }, }) - await prisma().dataSourceSchemaTable.upsert({ + const dbSchemaTable = await prisma().dataSourceSchemaTable.upsert({ where: { dataSourceSchemaId_schema_name: { dataSourceSchemaId: dataSource.structure.id, @@ -550,6 +588,11 @@ async function _refreshDataSourceStructure( }, }) + if (embedding) { + await prisma() + .$queryRaw`UPDATE "DataSourceSchemaTable" SET embedding = ${embedding} WHERE id = ${dbSchemaTable.id}::uuid` + } + broadcastDataSource(socketServer, dataSource) broadcastDataSourceSchemaTableUpdate( socketServer, diff --git a/apps/api/src/embedding.ts b/apps/api/src/embedding.ts new file mode 100644 index 00000000..06589a4f --- /dev/null +++ b/apps/api/src/embedding.ts @@ -0,0 +1,55 @@ +import { createHash } from 'crypto' +import prisma from '@briefer/database' +import { OpenAI } from 'openai' +import { head } from 'ramda' +import { z } from 'zod' +import { logger } from './logger.js' +import { jsonString } from '@briefer/types' + +export async function createEmbedding(input: string, openAiApiKey: string) { + const model = 'text-embedding-3-small' + const inputChecksum = createHash('sha256').update(input).digest('hex') + const rawExistingEmbeddings = await prisma() + .$queryRaw`SELECT embedding::text FROM "EmbeddingCache" WHERE "inputChecksum" = ${inputChecksum} AND model = ${model}` + + const parsedExistingEmbeddings = z + .array(z.object({ embedding: jsonString.pipe(z.array(z.number())) })) + .safeParse(rawExistingEmbeddings) + if (parsedExistingEmbeddings.success) { + const embedding = head(parsedExistingEmbeddings.data)?.embedding + if (embedding) { + return embedding + } + } else { + logger().error( + { + err: parsedExistingEmbeddings.error, + }, + 'Failed to parse existing embeddings' + ) + } + + const openai = new OpenAI({ apiKey: openAiApiKey }) + const embeddingResponse = await openai.embeddings.create({ + model, + input, + }) + const embedding = head(embeddingResponse.data)?.embedding + if (!embedding) { + throw new Error('OpenAI did not return any embeddings') + } + + try { + await prisma() + .$queryRaw`INSERT INTO "EmbeddingCache" ("inputChecksum", model, embedding) VALUES (${inputChecksum}, ${model}, ${embedding}::vector) ON CONFLICT DO NOTHING` + } catch (err) { + logger().error( + { + err, + }, + 'Failed to insert embedding into cache' + ) + } + + return embedding +} diff --git a/apps/api/src/yjs/v2/executor/ai/sql.ts b/apps/api/src/yjs/v2/executor/ai/sql.ts index 77915719..354a6316 100644 --- a/apps/api/src/yjs/v2/executor/ai/sql.ts +++ b/apps/api/src/yjs/v2/executor/ai/sql.ts @@ -10,10 +10,11 @@ import { updateSQLAISuggestions, } from '@briefer/editor' import * as Y from 'yjs' -import { +import prisma, { listDataSources, getWorkspaceWithSecrets, DataSource, + decrypt, } from '@briefer/database' import { logger } from '../../../../logger.js' import { sqlEditStreamed } from '../../../../ai-api.js' @@ -24,7 +25,10 @@ import { fetchDataSourceStructureFromCache, listSchemaTables, } from '../../../../datasources/structure.js' -import { DataSourceStructureStateV3 } from '@briefer/types' +import { DataSourceStructureStateV3, uuidSchema } from '@briefer/types' +import { createEmbedding } from '../../../../embedding.js' +import { config } from '../../../../config/index.js' +import { z } from 'zod' async function editWithAI( workspaceId: string, @@ -70,7 +74,12 @@ async function editWithAI( dataSource.data.id, dataSource.type ) - const tableInfo = await tableInfoFromStructure(dataSource, structure) + const tableInfo = await retrieveTableInfoForQuestion( + dataSource, + structure, + instructions, + workspace?.secrets?.openAiApiKey ?? null + ) event(assistantModelId) @@ -110,6 +119,47 @@ async function editWithAI( ) } +async function retrieveTableInfoForQuestion( + datasource: DataSource, + structure: DataSourceStructureStateV3 | null, + question: string, + openAiApiKey: string | null +): Promise { + if (!structure || !openAiApiKey) { + return tableInfoFromStructure(datasource, structure) + } + + const questionEmbedding = await createEmbedding( + question, + decrypt(openAiApiKey, config().WORKSPACE_SECRETS_ENCRYPTION_KEY) + ) + + const raw = await prisma() + .$queryRaw`SELECT t.id, t.embedding <=> ${questionEmbedding}::vector AS distance FROM "DataSourceSchemaTable" t INNER JOIN "DataSourceSchema" s ON s.id = t."dataSourceSchemaId" WHERE s.id = ${structure.id}::uuid ORDER BY distance LIMIT 30` + + const result = z.array(z.object({ id: uuidSchema })).parse(raw) + + const tables = await prisma().dataSourceSchemaTable.findMany({ + where: { + id: { in: result.map((r) => r.id) }, + }, + }) + + let tableInfo = '' + for (const table of tables) { + tableInfo += `${table.schema}.${table.name}\n` + const columns = z + .array(z.object({ name: z.string(), type: z.string() })) + .parse(table.columns) + for (const column of columns) { + tableInfo += `${column.name} ${column.type}\n` + } + tableInfo += '\n' + } + + return tableInfo.trim() +} + async function tableInfoFromStructure( config: DataSource, structure: DataSourceStructureStateV3 | null @@ -121,8 +171,8 @@ async function tableInfoFromStructure( let result = '' for await (const schemaTable of listSchemaTables([{ config, structure }])) { result += `${schemaTable.schemaName}.${schemaTable.tableName}\n` - for (const columns of schemaTable.table.columns) { - result += `${columns.name} ${columns.type}\n` + for (const column of schemaTable.table.columns) { + result += `${column.name} ${column.type}\n` } result += '\n' } diff --git a/dev/postgresql/Dockerfile b/dev/postgresql/Dockerfile new file mode 100644 index 00000000..84a10779 --- /dev/null +++ b/dev/postgresql/Dockerfile @@ -0,0 +1,7 @@ +FROM postgres:16 + +# Install pgvector +RUN apt-get update && apt-get install -y postgresql-16-pgvector + +# Clean up +RUN apt-get clean && rm -rf /var/lib/apt/lists/* diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml index 1ee4041b..5bf45318 100644 --- a/docker-compose.dev.yaml +++ b/docker-compose.dev.yaml @@ -1,6 +1,8 @@ services: postgres: - image: postgres + build: + context: dev/postgresql + dockerfile: Dockerfile ports: - '5432:5432' environment: diff --git a/docker-compose.yaml b/docker-compose.yaml index 9592c173..a71aee2e 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,6 +1,6 @@ services: postgres: - image: postgres + image: pgvector/pgvector:pg16 environment: POSTGRES_DB: 'briefer' POSTGRES_USER: ${POSTGRES_USERNAME:?error} diff --git a/docker/Dockerfile b/docker/Dockerfile index 4801e866..a13221f9 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,6 +6,7 @@ RUN apt-get update && apt-get install -y \ python3-venv \ python3-dev \ postgresql \ + postgresql-common \ postgresql-contrib \ nginx \ sudo \ @@ -28,6 +29,14 @@ RUN apt-get update && apt-get install -y \ alien && \ apt-get clean && rm -rf /var/lib/apt/lists/* +# Set environment variable to skip the user prompt +ENV DEBIAN_FRONTEND=noninteractive +# Enable the PostgreSQL APT repository +RUN /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y +# Install the pgvector extension for PostgreSQL +RUN apt-get update && apt-get install -y postgresql-15-pgvector && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + #### ORACLE INSTANT CLIENT #### ARG TARGETARCH diff --git a/docker/init_db.sh b/docker/init_db.sh index 3f557109..a40717d9 100644 --- a/docker/init_db.sh +++ b/docker/init_db.sh @@ -13,3 +13,5 @@ if [ ! -f /var/lib/postgresql/data/.init ]; then psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE briefer TO briefer;" touch /var/lib/postgresql/data/.init fi + +psql -U postgres -d briefer -c "CREATE EXTENSION IF NOT EXISTS vector;" diff --git a/docker/setup/setup.py b/docker/setup/setup.py index 5365d143..29083b00 100644 --- a/docker/setup/setup.py +++ b/docker/setup/setup.py @@ -107,8 +107,10 @@ def generate_jupyter_config(): } with open(fpath, "w") as f: json.dump(cfg, f, indent=4) - os.chown(fpath, pwd.getpwnam("jupyteruser").pw_uid, grp.getgrnam("jupyteruser").gr_gid) - os.chmod(fpath, 0o700) + + # recursively chown jupyteruser home directory + os.system(f"chown -R jupyteruser:jupyteruser /home/jupyteruser") + os.system(f"chmod -R 700 /home/jupyteruser") def setup_jupyter(): generate_jupyter_config() diff --git a/docker/supervisord.conf b/docker/supervisord.conf index f074c3f5..e382e7a5 100644 --- a/docker/supervisord.conf +++ b/docker/supervisord.conf @@ -42,7 +42,7 @@ directory=/app/setup/ user=jupyteruser environment=HOME="/home/jupyteruser",USER="jupyteruser" autostart=true -autorestart=false +autorestart=true startsecs=0 priority=4 stdout_logfile=/dev/stdout diff --git a/packages/database/prisma/migrations/20250105002014_add_embedding_to_datasource_schema_table/migration.sql b/packages/database/prisma/migrations/20250105002014_add_embedding_to_datasource_schema_table/migration.sql new file mode 100644 index 00000000..499998d6 --- /dev/null +++ b/packages/database/prisma/migrations/20250105002014_add_embedding_to_datasource_schema_table/migration.sql @@ -0,0 +1,4 @@ +CREATE EXTENSION IF NOT EXISTS vector; + +-- AlterTable +ALTER TABLE "DataSourceSchemaTable" ADD COLUMN "embedding" vector; diff --git a/packages/database/prisma/migrations/20250105011201_introduce_embedding_cache_table/migration.sql b/packages/database/prisma/migrations/20250105011201_introduce_embedding_cache_table/migration.sql new file mode 100644 index 00000000..5cb04144 --- /dev/null +++ b/packages/database/prisma/migrations/20250105011201_introduce_embedding_cache_table/migration.sql @@ -0,0 +1,14 @@ +-- CreateTable +CREATE TABLE "EmbeddingCache" ( + "id" UUID NOT NULL DEFAULT gen_random_uuid(), + "inputChecksum" TEXT NOT NULL, + "model" TEXT NOT NULL, + "embedding" vector NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "EmbeddingCache_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "unique_inputChecksum_model" ON "EmbeddingCache"("inputChecksum", "model"); diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma index 6f481acf..cc5da2ad 100644 --- a/packages/database/prisma/schema.prisma +++ b/packages/database/prisma/schema.prisma @@ -367,6 +367,7 @@ model DataSourceSchemaTable { schema String columns Json dataSourceSchemaId String @db.Uuid + embedding Unsupported("vector")? createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt @@ -671,3 +672,15 @@ model OnboardingTutorial { @@unique([workspaceId]) } + +model EmbeddingCache { + id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + inputChecksum String + model String + embedding Unsupported("vector") + + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + + @@unique([inputChecksum, model], map: "unique_inputChecksum_model") +} diff --git a/yarn.lock b/yarn.lock index ed3e1979..33b1199d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3562,6 +3562,14 @@ dependencies: "@types/express" "*" +"@types/node-fetch@^2.6.4": + version "2.6.12" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.12.tgz#8ab5c3ef8330f13100a7479e2cd56d3386830a03" + integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA== + dependencies: + "@types/node" "*" + form-data "^4.0.0" + "@types/node-fetch@^2.6.9": version "2.6.11" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.11.tgz#9b39b78665dae0e82a08f02f4967d62c66f95d24" @@ -3582,6 +3590,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.0.tgz#719498898d5defab83c3560f45d8498f58d11938" integrity sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ== +"@types/node@^18.11.18": + version "18.19.69" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.69.tgz#748d301818ba4b238854c53d290257a70aae7d01" + integrity sha512-ECPdY1nlaiO/Y6GUnwgtAAhLNaQ53AyIVz+eILxpEo5OvuqE6yWkqWBIb5dU0DqhKQtMeny+FBD3PK6lm7L5xQ== + dependencies: + undici-types "~5.26.4" + "@types/node@^20", "@types/node@^20.1.1", "@types/node@^20.9.0": version "20.16.11" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.16.11.tgz#9b544c3e716b1577ac12e70f9145193f32750b33" @@ -4053,6 +4068,13 @@ agent-base@^7.0.2, agent-base@^7.1.0, agent-base@^7.1.1: dependencies: debug "^4.3.4" +agentkeepalive@^4.2.1: + version "4.6.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" + integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== + dependencies: + humanize-ms "^1.2.1" + aggregate-error@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-5.0.0.tgz#ffe15045d7521c51c9d618e3d7f37c13f29b3fd3" @@ -7243,6 +7265,11 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== +form-data-encoder@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" + integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== + form-data@^2.5.0: version "2.5.2" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.2.tgz#dc653743d1de2fcc340ceea38079daf6e9069fd2" @@ -7271,6 +7298,14 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +formdata-node@^4.3.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2" + integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== + dependencies: + node-domexception "1.0.0" + web-streams-polyfill "4.0.0-beta.3" + formdata-polyfill@^4.0.10: version "4.0.10" resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" @@ -7992,6 +8027,13 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + iconv-lite@0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -9913,7 +9955,7 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.3, ms@^2.1.1, ms@^2.1.3: +ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -10033,7 +10075,7 @@ node-addon-api@^5.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== -node-domexception@^1.0.0: +node-domexception@1.0.0, node-domexception@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== @@ -10311,6 +10353,19 @@ open@^8.0.0, open@^8.4.2: is-docker "^2.1.1" is-wsl "^2.2.0" +openai@^4.77.3: + version "4.77.3" + resolved "https://registry.yarnpkg.com/openai/-/openai-4.77.3.tgz#10f6906f2f737a98b656b745a6b710e595ba2e4d" + integrity sha512-wLDy4+KWHz31HRFMW2+9KQuVuT2QWhs0z94w1Gm1h2Ut9vIHr9/rHZggbykZEfyiaJRVgw8ZS9K6AylDWzvPYw== + dependencies: + "@types/node" "^18.11.18" + "@types/node-fetch" "^2.6.4" + abort-controller "^3.0.0" + agentkeepalive "^4.2.1" + form-data-encoder "1.7.2" + formdata-node "^4.3.2" + node-fetch "^2.6.7" + openid-client@^5.3.0, openid-client@^5.4.2: version "5.7.0" resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-5.7.0.tgz#61dbea7251f561e82342278063ce37c5c05347f2" @@ -12589,7 +12644,16 @@ string-split-by@^1.0.0: dependencies: parenthesis "^3.1.5" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -12689,7 +12753,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -13463,6 +13534,11 @@ undefsafe@^2.0.5: resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + undici-types@~6.19.2: version "6.19.8" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" @@ -14058,6 +14134,11 @@ weak-map@^1.0.5: resolved "https://registry.yarnpkg.com/weak-map/-/weak-map-1.0.8.tgz#394c18a9e8262e790544ed8b55c6a4ddad1cb1a3" integrity sha512-lNR9aAefbGPpHO7AEnY0hCFjz1eTkWCXYvkTRrTHs9qv8zJp+SkVYpzfLIFXQQiG3tVvbNFQgVg2bQS8YGgxyw== +web-streams-polyfill@4.0.0-beta.3: + version "4.0.0-beta.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" + integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== + web-streams-polyfill@^3.0.3: version "3.3.3" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" @@ -14227,7 +14308,16 @@ world-calendars@^1.0.3: dependencies: object-assign "^4.1.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==