diff --git a/backend/src/build-system/__tests__/test-generate-doc.spec.ts b/backend/src/build-system/__tests__/test-generate-doc.spec.ts index df5ec8c7..ca84f7da 100644 --- a/backend/src/build-system/__tests__/test-generate-doc.spec.ts +++ b/backend/src/build-system/__tests__/test-generate-doc.spec.ts @@ -25,8 +25,6 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS', () => { { id: 'op:PRD', name: 'PRD Generation Node', - type: 'ANALYSIS', - subType: 'PRD', }, ], }, @@ -37,8 +35,6 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS', () => { { id: 'op:UX:SMD', name: 'UX Sitemap Document Node', - type: 'UX', - subType: 'SITEMAP', requires: ['op:PRD'], }, ], @@ -50,8 +46,6 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS', () => { { id: 'op:UX:SMS', name: 'UX Sitemap Structure Node', - type: 'UX', - subType: 'VIEWS', requires: ['op:UX:SMD'], }, ], diff --git a/backend/src/build-system/__tests__/test.backend-code-generator.spec.ts b/backend/src/build-system/__tests__/test.backend-code-generator.spec.ts index 944ddd4f..e708322b 100644 --- a/backend/src/build-system/__tests__/test.backend-code-generator.spec.ts +++ b/backend/src/build-system/__tests__/test.backend-code-generator.spec.ts @@ -27,8 +27,6 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGener { id: 'op:PRD', name: 'PRD Generation Node', - type: 'ANALYSIS', - subType: 'PRD', }, ], }, @@ -39,8 +37,6 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGener { id: 'op:UX:SMD', name: 'UX Sitemap Document Node', - type: 'UX', - subType: 'SITEMAP', requires: ['op:PRD'], }, ], @@ -52,8 +48,6 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGener { id: 'op:UX:DATAMAP:DOC', name: 'UX Data Map Document Node', - type: 'UX', - subType: 'DATAMAP', requires: ['op:UX:SMD'], }, ], @@ -65,8 +59,6 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGener { id: 'op:DATABASE_REQ', name: 'Database Requirements Node', - type: 'DATABASE', - subType: 'SCHEMAS', requires: ['op:UX:DATAMAP:DOC'], }, ], @@ -78,8 +70,6 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGener { id: 'op:DATABASE:SCHEMAS', name: 'Database Schemas Node', - type: 'DATABASE', - subType: 'SCHEMAS', requires: ['op:DATABASE_REQ'], }, ], @@ -91,7 +81,6 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGener { id: 'op:BACKEND:CODE', name: 'Backend Code Generator Node', - type: 'BACKEND', requires: ['op:DATABASE:SCHEMAS', 'op:UX:DATAMAP:DOC'], }, ], diff --git a/backend/src/build-system/__tests__/test.fullstack-gen.spec.ts b/backend/src/build-system/__tests__/test.fullstack-gen.spec.ts index b6de68b9..f6f80fa3 100644 --- a/backend/src/build-system/__tests__/test.fullstack-gen.spec.ts +++ b/backend/src/build-system/__tests__/test.fullstack-gen.spec.ts @@ -5,7 +5,6 @@ import * as fs from 'fs'; import * as path from 'path'; import { writeToFile } from './utils'; import { BuildMonitor } from '../monitor'; -import { Logger } from '@nestjs/common'; describe('Sequence: PRD -> UXSD -> UXSS -> UXDD -> DATABASE_REQ -> DBSchemas -> Frontend_File_struct -> Frontend_File_arch -> BackendCodeGenerator', () => { // Generate a unique folder with a timestamp @@ -24,85 +23,54 @@ describe('Sequence: PRD -> UXSD -> UXSS -> UXDD -> DATABASE_REQ -> DBSchemas -> steps: [ { id: 'step-1', - name: 'Generate PRD', + name: 'Initial Analysis', + parallel: false, nodes: [ { id: 'op:PRD', name: 'PRD Generation Node', - type: 'ANALYSIS', - subType: 'PRD', }, ], }, { id: 'step-2', - name: 'Generate UX Sitemap Document', + name: 'UX Base Document Generation', + parallel: false, nodes: [ { id: 'op:UX:SMD', name: 'UX Sitemap Document Node', - type: 'UX', - subType: 'SITEMAP', requires: ['op:PRD'], }, ], }, { - id: 'step-4', - name: 'Generate UX Sitemap Structure', + id: 'step-3', + name: 'Parallel UX Processing', + parallel: true, nodes: [ { id: 'op:UX:SMS', name: 'UX Sitemap Structure Node', - type: 'UX', - subType: 'VIEWS', requires: ['op:UX:SMD'], }, - ], - }, - { - id: 'step-5', - name: 'Generate UX Data Map Document', - nodes: [ { id: 'op:UX:DATAMAP:DOC', name: 'UX Data Map Document Node', - type: 'UX', - subType: 'DATAMAP', requires: ['op:UX:SMD'], }, ], }, { - id: 'step-6', - name: 'Generate Database Requirements', + id: 'step-4', + name: 'Parallel Project Structure', + parallel: true, nodes: [ { id: 'op:DATABASE_REQ', name: 'Database Requirements Node', - type: 'DATABASE', - subType: 'SCHEMAS', requires: ['op:UX:DATAMAP:DOC'], }, - ], - }, - { - id: 'step-7', - name: 'Generate Database Schemas', - nodes: [ - { - id: 'op:DATABASE:SCHEMAS', - name: 'Database Schemas Node', - type: 'DATABASE', - subType: 'SCHEMAS', - requires: ['op:DATABASE_REQ'], - }, - ], - }, - { - id: 'step-8', - name: 'file structure generation', - nodes: [ { id: 'op:FILE:STRUCT', name: 'file structure generation', @@ -114,9 +82,15 @@ describe('Sequence: PRD -> UXSD -> UXSS -> UXDD -> DATABASE_REQ -> DBSchemas -> ], }, { - id: 'step-9', - name: 'File_Arch Document', + id: 'step-5', + name: 'Parallel Implementation', + parallel: true, nodes: [ + { + id: 'op:DATABASE:SCHEMAS', + name: 'Database Schemas Node', + requires: ['op:DATABASE_REQ'], + }, { id: 'op:FILE:ARCH', name: 'File_Arch', @@ -125,29 +99,25 @@ describe('Sequence: PRD -> UXSD -> UXSS -> UXDD -> DATABASE_REQ -> DBSchemas -> ], }, { - id: 'step-10', - name: 'Generate Backend Code', + id: 'step-6', + name: 'Final Code Generation', + parallel: false, nodes: [ { id: 'op:BACKEND:CODE', name: 'Backend Code Generator Node', - type: 'BACKEND', requires: ['op:DATABASE:SCHEMAS', 'op:UX:DATAMAP:DOC'], }, ], }, ], }; - - // Initialize the BuilderContext with the defined sequence and environment const context = new BuilderContext(sequence, 'test-env'); const monitor = BuildMonitor.getInstance(); try { - console.log('Starting sequence execution...'); console.time('Total Execution Time'); - // Execute the build sequence await context.execute(); console.timeEnd('Total Execution Time'); @@ -158,8 +128,6 @@ describe('Sequence: PRD -> UXSD -> UXSS -> UXDD -> DATABASE_REQ -> DBSchemas -> monitorReport, 'utf8', ); - console.log('\nExecution Metrics Report:'); - console.log(monitorReport); const sequenceMetrics = monitor.getSequenceMetrics(sequence.id); if (sequenceMetrics) { @@ -184,15 +152,8 @@ describe('Sequence: PRD -> UXSD -> UXSS -> UXDD -> DATABASE_REQ -> DBSchemas -> console.table(metricsJson); } - console.log('\nProcessing individual node results and metrics...'); for (const step of sequence.steps) { const stepMetrics = sequenceMetrics?.stepMetrics.get(step.id); - console.log(`\nStep: ${step.name} (${step.id})`); - console.log(`Duration: ${stepMetrics?.duration}ms`); - console.log( - `Completed Nodes: ${stepMetrics?.completedNodes}/${stepMetrics?.totalNodes}`, - ); - for (const node of step.nodes) { const resultData = await context.getNodeData(node.id); const nodeMetrics = stepMetrics?.nodeMetrics.get(node.id); @@ -226,15 +187,7 @@ describe('Sequence: PRD -> UXSD -> UXSS -> UXDD -> DATABASE_REQ -> DBSchemas -> JSON.stringify(summary, null, 2), 'utf8', ); - - console.log('\nExecution Summary:'); - console.table(summary); - - console.log('\nDetailed logs and metrics stored in:', logFolderPath); } catch (error) { - console.timeEnd('Total Execution Time'); - - // 保存错误信息和当前的执行状态 const errorReport = { error: { message: error.message, @@ -258,5 +211,5 @@ describe('Sequence: PRD -> UXSD -> UXSS -> UXDD -> DATABASE_REQ -> DBSchemas -> ); throw new Error('Sequence execution failed.'); } - }, 600000); // Timeout set to 10 minutes + }, 300000); // Timeout set to 10 minutes }); diff --git a/backend/src/build-system/__tests__/utils.ts b/backend/src/build-system/__tests__/utils.ts index ff134a9a..b1b4b2d3 100644 --- a/backend/src/build-system/__tests__/utils.ts +++ b/backend/src/build-system/__tests__/utils.ts @@ -1,3 +1,4 @@ +import { Logger } from '@nestjs/common'; import * as fs from 'fs'; import * as path from 'path'; /** @@ -20,9 +21,9 @@ export const writeToFile = ( // Write the formatted content to the file fs.writeFileSync(filePath, formattedContent, 'utf8'); - console.log(`Successfully wrote data for ${handlerName} to ${filePath}`); + Logger.log(`Successfully wrote data for ${handlerName} to ${filePath}`); } catch (error) { - console.error(`Failed to write data for ${handlerName}:`, error); + Logger.error(`Failed to write data for ${handlerName}:`, error); throw error; } }; diff --git a/backend/src/build-system/context.ts b/backend/src/build-system/context.ts index 4cfb8c44..220187c3 100644 --- a/backend/src/build-system/context.ts +++ b/backend/src/build-system/context.ts @@ -140,42 +140,106 @@ export class BuilderContext { */ private async executeParallelNodes(step: BuildStep): Promise { let remainingNodes = [...step.nodes]; - let lastLength = remainingNodes.length; - let retryCount = 0; - const maxRetries = 10; + const concurrencyLimit = 3; // TODO: current is manually set to 3 for testing purposes - while (remainingNodes.length > 0 && retryCount < maxRetries) { + while (remainingNodes.length > 0) { const executableNodes = remainingNodes.filter((node) => this.canExecute(node.id), ); if (executableNodes.length > 0) { - await Promise.all( - executableNodes.map((node) => this.executeNode(node)), - ); + for (let i = 0; i < executableNodes.length; i += concurrencyLimit) { + const batch = executableNodes.slice(i, i + concurrencyLimit); + + try { + const nodeExecutionPromises = batch.map(async (node) => { + if (this.executionState.completed.has(node.id)) { + return; + } + + const currentStep = this.monitor.getCurrentStep(); + this.monitor.startNodeExecution( + node.id, + this.sequence.id, + currentStep.id, + ); + + try { + if (!this.canExecute(node.id)) { + this.logger.log( + `Waiting for dependencies of node ${node.id}: ${node.requires?.join( + ', ', + )}`, + ); + this.monitor.incrementNodeRetry( + node.id, + this.sequence.id, + currentStep.id, + ); + return; + } + + this.logger.log(`Executing node ${node.id} in parallel batch`); + await this.executeNodeById(node.id); + + this.monitor.endNodeExecution( + node.id, + this.sequence.id, + currentStep.id, + true, + ); + } catch (error) { + this.monitor.endNodeExecution( + node.id, + this.sequence.id, + currentStep.id, + false, + error instanceof Error ? error : new Error(String(error)), + ); + throw error; + } + }); + + await Promise.all(nodeExecutionPromises); + + const activeModelPromises = this.model.getAllActivePromises(); + if (activeModelPromises.length > 0) { + this.logger.debug( + `Waiting for ${activeModelPromises.length} active LLM requests to complete`, + ); + await Promise.all(activeModelPromises); + } + } catch (error) { + this.logger.error( + `Error executing parallel nodes batch: ${error}`, + error instanceof Error ? error.stack : undefined, + ); + throw error; + } + } remainingNodes = remainingNodes.filter( (node) => !this.executionState.completed.has(node.id), ); - - if (remainingNodes.length < lastLength) { - retryCount = 0; - lastLength = remainingNodes.length; - } else { - retryCount++; - } } else { await new Promise((resolve) => setTimeout(resolve, 100)); - retryCount++; + + const activeModelPromises = this.model.getAllActivePromises(); + if (activeModelPromises.length > 0) { + this.logger.debug( + `Waiting for ${activeModelPromises.length} active LLM requests during retry`, + ); + await Promise.all(activeModelPromises); + } } } - if (remainingNodes.length > 0) { - throw new Error( - `Unable to complete all nodes in step ${step.id}. Remaining: ${remainingNodes - .map((n) => n.id) - .join(', ')}`, + const finalActivePromises = this.model.getAllActivePromises(); + if (finalActivePromises.length > 0) { + this.logger.debug( + `Final wait for ${finalActivePromises.length} remaining LLM requests`, ); + await Promise.all(finalActivePromises); } } @@ -368,7 +432,6 @@ export class BuilderContext { } private async invokeNodeHandler(node: BuildNode): Promise> { - this.logger.log(`Executing node handler: ${node.id}`); const handler = this.handlerManager.getHandler(node.id); if (!handler) { throw new Error(`No handler found for node: ${node.id}`); diff --git a/backend/src/build-system/handlers/ux/datamap/index.ts b/backend/src/build-system/handlers/ux/datamap/index.ts index 0eab70d9..833628a6 100644 --- a/backend/src/build-system/handlers/ux/datamap/index.ts +++ b/backend/src/build-system/handlers/ux/datamap/index.ts @@ -2,6 +2,7 @@ import { BuildHandler, BuildResult } from 'src/build-system/types'; import { BuilderContext } from 'src/build-system/context'; import { ModelProvider } from 'src/common/model-provider'; import { prompts } from './prompt'; +import { Logger } from '@nestjs/common'; /** * Handler for generating the UX Data Map document. @@ -10,8 +11,6 @@ export class UXDatamapHandler implements BuildHandler { readonly id = 'op:UX:DATAMAP:DOC'; async run(context: BuilderContext): Promise> { - console.log('Generating UX Data Map Document...'); - // Extract relevant data from the context const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; @@ -29,6 +28,7 @@ export class UXDatamapHandler implements BuildHandler { }, 'gpt-4o-mini', ); + Logger.log('UX Data Map Content: ', uxDatamapContent); return { success: true, diff --git a/backend/src/build-system/handlers/ux/sitemap-structure/index.ts b/backend/src/build-system/handlers/ux/sitemap-structure/index.ts index 6d08cead..455cf486 100644 --- a/backend/src/build-system/handlers/ux/sitemap-structure/index.ts +++ b/backend/src/build-system/handlers/ux/sitemap-structure/index.ts @@ -29,7 +29,6 @@ export class UXSitemapStructureHandler implements BuildHandler { sitemapDoc, 'web', // TODO: Change platform dynamically if necessary ); - this.logger.log(prompt); const uxStructureContent = await context.model.chatSync( { diff --git a/backend/src/build-system/types.ts b/backend/src/build-system/types.ts index 8e1cb041..5aa5949b 100644 --- a/backend/src/build-system/types.ts +++ b/backend/src/build-system/types.ts @@ -1,23 +1,4 @@ -import { ModelProvider } from 'src/common/model-provider'; import { BuilderContext } from './context'; -import { BuildOptions } from 'typescript'; - -export type BuildNodeType = - | 'PROJECT_SETUP' - | 'ANALYSIS' - | 'DATABASE' - | 'BACKEND' - | 'UX' - | 'WEBAPP'; - -export type BuildSubType = { - ANALYSIS: 'PRD' | 'FRD' | 'DRD' | 'BRD' | 'UXSD' | 'UXDD'; - DATABASE: 'SCHEMAS' | 'POSTGRES'; - BACKEND: 'OPENAPI' | 'ASYNCAPI' | 'SERVER'; - UX: 'SITEMAP' | 'DATAMAP' | 'VIEWS'; - WEBAPP: 'STORE' | 'ROOT' | 'VIEW'; - PROJECT_SETUP: never; -}; export interface BuildBase { id: string; @@ -28,8 +9,6 @@ export interface BuildBase { } export interface BuildNode extends BuildBase { - type?: BuildNodeType; - subType?: BuildSubType[BuildNodeType]; config?: Record; } diff --git a/backend/src/common/model-provider/index.ts b/backend/src/common/model-provider/index.ts index b4f9135b..9991c96c 100644 --- a/backend/src/common/model-provider/index.ts +++ b/backend/src/common/model-provider/index.ts @@ -1,5 +1,6 @@ import { Logger } from '@nestjs/common'; import { HttpService } from '@nestjs/axios'; +import { Subject, Subscription } from 'rxjs'; export interface ModelProviderConfig { endpoint: string; @@ -13,19 +14,33 @@ export interface CustomAsyncIterableIterator extends AsyncIterator { export class ModelProvider { private readonly logger = new Logger('ModelProvider'); private isDone = false; - private responseSubscription: any; + private responseSubscription: Subscription | null = null; private chunkQueue: ChatCompletionChunk[] = []; private resolveNextChunk: | ((value: IteratorResult) => void) | null = null; + // Track active requests + private activeRequests = new Map< + string, + { + startTime: number; + subscription: Subscription; + stream: Subject; + promise: Promise; + } + >(); + + // Concurrent request management + private concurrentLimit = 3; + private currentRequests = 0; + private static instance: ModelProvider | undefined = undefined; public static getInstance() { if (this.instance) { return this.instance; } - return new ModelProvider(new HttpService(), { endpoint: 'http://localhost:3001', }); @@ -36,50 +51,75 @@ export class ModelProvider { private readonly config: ModelProviderConfig, ) {} + /** + * Synchronous chat method that returns a complete response + */ async chatSync( input: ChatInput | string, model: string, chatId?: string, ): Promise { - this.logger.debug('Starting chatSync', { model, chatId }); + while (this.currentRequests >= this.concurrentLimit) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } - this.resetState(); + const requestId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + this.currentRequests++; - try { - const chatStream = this.chat(input, model, chatId); - let content = ''; - - this.logger.debug('Starting to process chat stream'); - for await (const chunk of chatStream) { - if (chunk.status === StreamStatus.STREAMING) { - const newContent = chunk.choices - .map((choice) => choice.delta?.content || '') - .join(''); - content += newContent; + this.logger.debug( + `Starting request ${requestId}. Active: ${this.currentRequests}/${this.concurrentLimit}`, + ); + + const normalizedInput = this.normalizeChatInput(input); + + let resolvePromise: (value: string) => void; + let rejectPromise: (error: any) => void; + + const promise = new Promise((resolve, reject) => { + resolvePromise = resolve; + rejectPromise = reject; + }); + + const stream = new Subject(); + let content = ''; + let isCompleted = false; + + const subscription = stream.subscribe({ + next: (chunk) => { + if (chunk?.choices?.[0]?.delta?.content) { + content += chunk.choices[0].delta.content; } - } + }, + error: (error) => { + if (!isCompleted) { + isCompleted = true; + this.cleanupRequest(requestId); + rejectPromise(error); + } + }, + complete: () => { + if (!isCompleted) { + isCompleted = true; + this.cleanupRequest(requestId); + resolvePromise(content); + } + }, + }); - return content; - } catch (error) { - this.logger.error('Error in chatSync:', error); - throw error; - } finally { - this.cleanup(); - this.logger.debug('ChatSync cleanup completed'); - } - } + this.activeRequests.set(requestId, { + startTime: Date.now(), + subscription, + stream, + promise, + }); - private resetState() { - this.logger.debug('Resetting provider state'); - this.isDone = false; - this.chunkQueue = []; - this.resolveNextChunk = null; - if (this.responseSubscription) { - this.responseSubscription.unsubscribe(); - this.responseSubscription = null; - } + this.processRequest(normalizedInput, model, chatId, requestId, stream); + return promise; } + /** + * Stream-based chat method that returns an async iterator + */ chat( input: ChatInput | string, model: string, @@ -89,9 +129,7 @@ export class ModelProvider { const selectedModel = model || this.config.defaultModel; if (!selectedModel) { - const error = new Error('No model selected for chat request'); - this.logger.error(error.message); - throw error; + throw new Error('No model selected for chat request'); } const iterator: CustomAsyncIterableIterator = { @@ -107,15 +145,146 @@ export class ModelProvider { return iterator; } - private normalizeChatInput(input: ChatInput | string): ChatInput { - return typeof input === 'string' ? { content: input } : input; + /** + * Get all active promises for concurrent request management + */ + public getAllActivePromises(): Promise[] { + return Array.from(this.activeRequests.values()).map( + (request) => request.promise, + ); + } + + private async processRequest( + input: ChatInput, + model: string, + chatId: string | undefined, + requestId: string, + stream: Subject, + ) { + let isCompleted = false; + + try { + const response = await this.httpService + .post( + `${this.config.endpoint}/chat/completion`, + this.createRequestPayload(input, model, chatId), + { + responseType: 'stream', + headers: { 'Content-Type': 'application/json' }, + }, + ) + .toPromise(); + + let buffer = ''; + + response.data.on('data', (chunk: Buffer) => { + if (isCompleted) return; + + buffer += chunk.toString(); + + let newlineIndex; + while ((newlineIndex = buffer.indexOf('\n')) !== -1) { + const line = buffer.slice(0, newlineIndex).trim(); + buffer = buffer.slice(newlineIndex + 1); + + if (line.startsWith('data: ')) { + const jsonStr = line.slice(6); + if (jsonStr === '[DONE]') { + if (!isCompleted) { + isCompleted = true; + this.logger.debug( + `Request ${requestId} received [DONE] signal`, + ); + stream.complete(); + } + return; + } + try { + const parsed = JSON.parse(jsonStr); + if (!isCompleted) { + stream.next(parsed); + } + } catch (error) { + this.logger.error( + `Error parsing chunk for request ${requestId}:`, + error, + ); + } + } + } + }); + + response.data.on('end', () => { + if (!isCompleted) { + isCompleted = true; + stream.complete(); + } + }); + + response.data.on('error', (error) => { + if (!isCompleted) { + isCompleted = true; + stream.error(error); + } + }); + } catch (error) { + if (!isCompleted) { + isCompleted = true; + stream.error(error); + } + } + } + + private startChat(input: ChatInput, model: string, chatId?: string) { + this.resetState(); + const payload = this.createRequestPayload(input, model, chatId); + + this.responseSubscription = this.httpService + .post(`${this.config.endpoint}/chat/completion`, payload, { + responseType: 'stream', + headers: { 'Content-Type': 'application/json' }, + }) + .subscribe({ + next: (response) => { + let buffer = ''; + response.data.on('data', (chunk: Buffer) => { + buffer += chunk.toString(); + let newlineIndex; + + while ((newlineIndex = buffer.indexOf('\n')) !== -1) { + const line = buffer.slice(0, newlineIndex).trim(); + buffer = buffer.slice(newlineIndex + 1); + + if (line.startsWith('data: ')) { + const jsonStr = line.slice(6); + if (jsonStr === '[DONE]') { + this.handleStreamEnd(model); + return; + } + try { + const parsed = JSON.parse(jsonStr); + this.handleChunk(parsed); + } catch (error) { + this.logger.error('Error parsing chunk:', error); + } + } + } + }); + + response.data.on('end', () => { + this.handleStreamEnd(model); + }); + }, + error: (error) => { + this.handleStreamError(error, model); + }, + }); } private async handleNext(): Promise> { return new Promise>((resolve) => { if (this.chunkQueue.length > 0) { - const chunk = this.chunkQueue.shift()!; - resolve({ done: false, value: chunk }); + resolve({ done: false, value: this.chunkQueue.shift()! }); } else if (this.isDone) { resolve({ done: true, value: undefined }); } else { @@ -136,41 +305,6 @@ export class ModelProvider { return Promise.reject(error); } - private cleanup() { - this.logger.debug('Cleaning up provider'); - this.isDone = true; - if (this.responseSubscription) { - this.responseSubscription.unsubscribe(); - this.responseSubscription = null; - } - this.chunkQueue = []; - this.resolveNextChunk = null; - } - - private createRequestPayload( - input: ChatInput, - model: string, - chatId?: string, - ) { - return { - ...input, - model, - ...(chatId && { chatId }), - }; - } - - private createDoneChunk(model: string): ChatCompletionChunk { - return { - id: 'done', - object: 'chat.completion.chunk', - created: Date.now(), - model, - systemFingerprint: null, - choices: [], - status: StreamStatus.DONE, - }; - } - private handleChunk(chunk: any) { if (this.isValidChunk(chunk)) { const parsedChunk: ChatCompletionChunk = { @@ -184,17 +318,12 @@ export class ModelProvider { } else { this.chunkQueue.push(parsedChunk); } - } else { - this.logger.warn('Invalid chunk received:', chunk); } } private handleStreamEnd(model: string) { - this.logger.debug('Stream ended, handling completion'); - if (!this.isDone) { const doneChunk = this.createDoneChunk(model); - if (this.resolveNextChunk) { this.resolveNextChunk({ done: false, value: doneChunk }); this.resolveNextChunk = null; @@ -204,7 +333,6 @@ export class ModelProvider { } Promise.resolve().then(() => { - this.logger.debug('Setting done state'); this.isDone = true; if (this.resolveNextChunk) { this.resolveNextChunk({ done: true, value: undefined }); @@ -214,94 +342,100 @@ export class ModelProvider { } private handleStreamError(error: any, model: string) { - this.logger.error('Stream error occurred:', error); const doneChunk = this.createDoneChunk(model); - if (this.resolveNextChunk) { - this.logger.debug('Resolving waiting promise with error done chunk'); this.resolveNextChunk({ done: false, value: doneChunk }); - Promise.resolve().then(() => { - this.isDone = true; - if (this.resolveNextChunk) { - this.resolveNextChunk({ done: true, value: undefined }); - this.resolveNextChunk = null; - } - }); + this.isDone = true; } else { - this.logger.debug('Queueing error done chunk'); this.chunkQueue.push(doneChunk); - Promise.resolve().then(() => { - this.isDone = true; - }); + this.isDone = true; } } - private startChat(input: ChatInput, model: string, chatId?: string) { - this.resetState(); - - const payload = this.createRequestPayload(input, model, chatId); + private cleanupRequest(requestId: string) { + const request = this.activeRequests.get(requestId); + if (request) { + try { + const duration = Date.now() - request.startTime; + this.logger.debug( + `Completed request ${requestId}. Duration: ${duration}ms`, + ); + + if (request.subscription && !request.subscription.closed) { + request.subscription.unsubscribe(); + } - this.responseSubscription = this.httpService - .post(`${this.config.endpoint}/chat/completion`, payload, { - responseType: 'stream', - headers: { - 'Content-Type': 'application/json', - }, - }) - .subscribe({ - next: (response) => { - let buffer = ''; + this.activeRequests.delete(requestId); + this.currentRequests--; + + this.logger.debug( + `Remaining active requests: ${this.currentRequests}/${this.concurrentLimit}`, + ); + } catch (error) { + this.logger.error( + `Error during cleanup of request ${requestId}:`, + error, + ); + } + } + } - response.data.on('data', (chunk: Buffer) => { - buffer += chunk.toString(); - let newlineIndex; + private cleanup() { + this.isDone = true; + if (this.responseSubscription) { + this.responseSubscription.unsubscribe(); + this.responseSubscription = null; + } + this.chunkQueue = []; + this.resolveNextChunk = null; + } - while ((newlineIndex = buffer.indexOf('\n')) !== -1) { - const line = buffer.slice(0, newlineIndex).trim(); - buffer = buffer.slice(newlineIndex + 1); + private resetState() { + this.isDone = false; + this.chunkQueue = []; + this.resolveNextChunk = null; + if (this.responseSubscription) { + this.responseSubscription.unsubscribe(); + this.responseSubscription = null; + } + } - if (line.startsWith('data: ')) { - const jsonStr = line.slice(6); - if (jsonStr === '[DONE]') { - this.logger.debug('Received [DONE] signal'); - this.handleStreamEnd(model); - return; - } - try { - const parsed = JSON.parse(jsonStr); - this.handleChunk(parsed); - } catch (error) { - this.logger.error('Error parsing chunk:', error); - } - } - } - }); + private createRequestPayload( + input: ChatInput, + model: string, + chatId?: string, + ) { + return { + ...input, + model, + ...(chatId && { chatId }), + }; + } - response.data.on('end', () => { - this.logger.debug('Response stream ended'); - this.handleStreamEnd(model); - }); - }, - error: (error) => { - this.logger.error('Error in chat request:', error); - this.handleStreamError(error, model); - }, - }); + private createDoneChunk(model: string): ChatCompletionChunk { + return { + id: 'done', + object: 'chat.completion.chunk', + created: Date.now(), + model, + systemFingerprint: null, + choices: [], + status: StreamStatus.DONE, + }; } private isValidChunk(chunk: any): boolean { - const isValid = + return ( chunk && typeof chunk.id === 'string' && typeof chunk.object === 'string' && typeof chunk.created === 'number' && - typeof chunk.model === 'string'; - - if (!isValid) { - this.logger.warn('Invalid chunk structure', chunk); - } + typeof chunk.model === 'string' + ); + } - return isValid; + private normalizeChatInput(input: ChatInput | string): ChatInput { + return typeof input === 'string' ? { content: input } : input; } public async fetchModelsName() { diff --git a/llm-server/package.json b/llm-server/package.json index e5aa4ded..6b67f9a4 100644 --- a/llm-server/package.json +++ b/llm-server/package.json @@ -20,7 +20,8 @@ "express": "^4.21.1", "node-fetch": "^3.3.2", "node-llama-cpp": "^3.1.1", - "nodemon": "^3.1.7" + "nodemon": "^3.1.7", + "p-queue": "^8.0.1" }, "devDependencies": { "@types/express": "^4.17.13", diff --git a/llm-server/src/llm-provider.ts b/llm-server/src/llm-provider.ts index f47f999a..0982ebdf 100644 --- a/llm-server/src/llm-provider.ts +++ b/llm-server/src/llm-provider.ts @@ -1,9 +1,14 @@ -import express, { Express, Request, Response } from 'express'; -import { ModelProvider } from './model/model-provider'; +import { Response } from 'express'; import { OpenAIModelProvider } from './model/openai-model-provider'; import { LlamaModelProvider } from './model/llama-model-provider'; import { Logger } from '@nestjs/common'; -import { GenerateMessageParams } from './type/GenerateMessage'; +import { + ModelProviderType, + ModelProviderOptions, + ModelError, + GenerateMessageParams, +} from './types'; +import { ModelProvider } from './model/model-provider'; export interface ChatMessageInput { content: string; @@ -17,29 +22,131 @@ export interface ChatMessage { export class LLMProvider { private readonly logger = new Logger(LLMProvider.name); private modelProvider: ModelProvider; + private readonly options: ModelProviderOptions; + private initialized: boolean = false; - constructor(modelProviderType: 'llama' | 'openai' = 'llama') { - if (modelProviderType === 'openai') { - this.modelProvider = new OpenAIModelProvider(); - } else { - this.modelProvider = new LlamaModelProvider(); + constructor( + modelProviderType: ModelProviderType = 'llama', + options: ModelProviderOptions = {}, + ) { + this.options = { + maxConcurrentRequests: 5, + maxRetries: 3, + retryDelay: 1000, + ...options, + }; + + this.modelProvider = this.createModelProvider(modelProviderType); + } + + private createModelProvider(type: ModelProviderType): ModelProvider { + switch (type) { + case 'openai': + return new OpenAIModelProvider(this.options); + case 'llama': + // TODO: need to support concurrent requests + return new LlamaModelProvider(); + default: + throw new Error(`Unsupported model provider type: ${type}`); } } async initialize(): Promise { - this.logger.log('Initializing LLM provider...'); - await this.modelProvider.initialize(); - this.logger.log('LLM provider fully initialized and ready.'); + try { + this.logger.log('Initializing LLM provider...'); + await this.modelProvider.initialize(); + this.initialized = true; + this.logger.log('LLM provider fully initialized and ready.'); + } catch (error) { + const modelError = this.normalizeError(error); + this.logger.error('Failed to initialize LLM provider:', modelError); + throw modelError; + } } async generateStreamingResponse( params: GenerateMessageParams, res: Response, ): Promise { - await this.modelProvider.generateStreamingResponse(params, res); + this.ensureInitialized(); + + try { + await this.modelProvider.generateStreamingResponse(params, res); + } catch (error) { + const modelError = this.normalizeError(error); + this.logger.error('Error in streaming response:', modelError); + + if (!res.writableEnded) { + this.sendErrorResponse(res, modelError); + } + } } async getModelTags(res: Response): Promise { - await this.modelProvider.getModelTagsResponse(res); + this.ensureInitialized(); + + try { + await this.modelProvider.getModelTagsResponse(res); + } catch (error) { + const modelError = this.normalizeError(error); + this.logger.error('Error getting model tags:', modelError); + + if (!res.writableEnded) { + this.sendErrorResponse(res, modelError); + } + } + } + + private ensureInitialized(): void { + if (!this.initialized) { + throw new Error('LLM provider not initialized. Call initialize() first.'); + } + } + + private normalizeError(error: any): ModelError { + if (error instanceof Error) { + return { + ...error, + code: (error as any).code || 'UNKNOWN_ERROR', + retryable: (error as any).retryable || false, + }; + } + + return { + name: 'Error', + message: String(error), + code: 'UNKNOWN_ERROR', + retryable: false, + }; + } + + private sendErrorResponse(res: Response, error: ModelError): void { + const errorResponse = { + error: { + message: error.message, + code: error.code, + details: error.details, + }, + }; + + if (res.headersSent) { + res.write(`data: ${JSON.stringify(errorResponse)}\n\n`); + res.write('data: [DONE]\n\n'); + res.end(); + } else { + res.status(500).json(errorResponse); + } + } + + isInitialized(): boolean { + return this.initialized; + } + + getCurrentProvider(): string { + return this.modelProvider.constructor.name; + } + + getProviderOptions(): ModelProviderOptions { + return { ...this.options }; } } diff --git a/llm-server/src/main.ts b/llm-server/src/main.ts index fbc73914..80ec476a 100644 --- a/llm-server/src/main.ts +++ b/llm-server/src/main.ts @@ -1,7 +1,7 @@ import { Logger } from '@nestjs/common'; import { ChatMessageInput, LLMProvider } from './llm-provider'; import express, { Express, Request, Response } from 'express'; -import { GenerateMessageParams } from './type/GenerateMessage'; +import { GenerateMessageParams } from './types'; export class App { private readonly logger = new Logger(App.name); @@ -31,9 +31,9 @@ export class App { const { content, model } = req.body as ChatMessageInput & { model: string; }; - + this.logger.log(`Received chat request for model: ${model}`); const params: GenerateMessageParams = { - model: model || 'gpt-3.5-turbo', // Default to 'gpt-3.5-turbo' if model is not provided + model: model || 'gpt-3.5-turbo', message: content, role: 'user', }; diff --git a/llm-server/src/model/llama-model-provider.ts b/llm-server/src/model/llama-model-provider.ts index 2b25159e..77fa89ae 100644 --- a/llm-server/src/model/llama-model-provider.ts +++ b/llm-server/src/model/llama-model-provider.ts @@ -10,7 +10,7 @@ import { ModelProvider } from './model-provider.js'; import { Logger } from '@nestjs/common'; import { systemPrompts } from '../prompt/systemPrompt'; import { ChatCompletionMessageParam } from 'openai/resources/chat/completions'; -import { GenerateMessageParams } from '../type/GenerateMessage'; +import { GenerateMessageParams } from '../types.js'; //TODO: using protocol class export class LlamaModelProvider extends ModelProvider { diff --git a/llm-server/src/model/model-provider.ts b/llm-server/src/model/model-provider.ts index 4d823290..855319c4 100644 --- a/llm-server/src/model/model-provider.ts +++ b/llm-server/src/model/model-provider.ts @@ -1,5 +1,5 @@ import { Response } from 'express'; -import { GenerateMessageParams } from '../type/GenerateMessage'; +import { GenerateMessageParams } from '../types'; export abstract class ModelProvider { abstract initialize(): Promise; diff --git a/llm-server/src/model/openai-model-provider.ts b/llm-server/src/model/openai-model-provider.ts index 93c990c2..fc845a69 100644 --- a/llm-server/src/model/openai-model-provider.ts +++ b/llm-server/src/model/openai-model-provider.ts @@ -1,46 +1,113 @@ import { Response } from 'express'; import OpenAI from 'openai'; -import { ModelProvider } from './model-provider'; import { Logger } from '@nestjs/common'; import { systemPrompts } from '../prompt/systemPrompt'; import { ChatCompletionMessageParam } from 'openai/resources/chat/completions'; -import { GenerateMessageParams } from '../type/GenerateMessage'; +import PQueue from 'p-queue'; +import { GenerateMessageParams } from '../types'; -export class OpenAIModelProvider extends ModelProvider { +export interface OpenAIProviderOptions { + maxConcurrentRequests?: number; + maxRetries?: number; + retryDelay?: number; + queueInterval?: number; + intervalCap?: number; + apiKey?: string; + systemPromptKey?: string; +} + +interface QueuedRequest { + params: GenerateMessageParams; + res: Response; + retries: number; +} + +export class OpenAIModelProvider { private readonly logger = new Logger(OpenAIModelProvider.name); private openai: OpenAI; + private requestQueue: PQueue; + private readonly options: Required; + + constructor(options: OpenAIProviderOptions = {}) { + this.options = { + maxConcurrentRequests: 5, + maxRetries: 3, + retryDelay: 1000, + queueInterval: 1000, + intervalCap: 10, + apiKey: process.env.OPENAI_API_KEY, + systemPromptKey: 'codefox-basic', + ...options, + }; + + this.requestQueue = new PQueue({ + concurrency: this.options.maxConcurrentRequests, + interval: this.options.queueInterval, + intervalCap: this.options.intervalCap, + }); + + this.requestQueue.on('active', () => { + this.logger.debug( + `Queue size: ${this.requestQueue.size}, Pending: ${this.requestQueue.pending}`, + ); + }); + } async initialize(): Promise { this.logger.log('Initializing OpenAI model...'); + + if (!this.options.apiKey) { + throw new Error('OpenAI API key is required'); + } + this.openai = new OpenAI({ - apiKey: process.env.OPENAI_API_KEY, + apiKey: this.options.apiKey, }); - this.logger.log('OpenAI model initialized.'); + + this.logger.log( + `OpenAI model initialized with options: + - Max Concurrent Requests: ${this.options.maxConcurrentRequests} + - Max Retries: ${this.options.maxRetries} + - Queue Interval: ${this.options.queueInterval}ms + - Interval Cap: ${this.options.intervalCap} + - System Prompt Key: ${this.options.systemPromptKey}`, + ); } async generateStreamingResponse( - { model, message, role = 'user' }: GenerateMessageParams, + params: GenerateMessageParams, res: Response, ): Promise { - this.logger.log('Generating streaming response with OpenAI...'); - const startTime = Date.now(); + const request: QueuedRequest = { + params, + res, + retries: 0, + }; - // Set SSE headers - res.writeHead(200, { - 'Content-Type': 'text/event-stream', - 'Cache-Control': 'no-cache', - Connection: 'keep-alive', - }); + await this.requestQueue.add(() => this.processRequest(request)); + } - // Get the system prompt based on the model - const systemPrompt = systemPrompts['codefox-basic']?.systemPrompt || ''; + private async processRequest(request: QueuedRequest): Promise { + const { params, res, retries } = request; + const { model, message, role = 'user' } = params; - const messages: ChatCompletionMessageParam[] = [ - { role: 'system', content: systemPrompt }, - { role: role as 'user' | 'system' | 'assistant', content: message }, - ]; + this.logger.log(`Processing request (attempt ${retries + 1})`); + const startTime = Date.now(); try { + res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + }); + + const systemPrompt = + systemPrompts[this.options.systemPromptKey]?.systemPrompt || ''; + const messages: ChatCompletionMessageParam[] = [ + { role: 'system', content: systemPrompt }, + { role: role as 'user' | 'system' | 'assistant', content: message }, + ]; + const stream = await this.openai.chat.completions.create({ model, messages, @@ -52,61 +119,131 @@ export class OpenAIModelProvider extends ModelProvider { const content = chunk.choices[0]?.delta?.content || ''; if (content) { chunkCount++; - this.logger.debug(`Sending chunk #${chunkCount}: "${content}"`); res.write(`data: ${JSON.stringify(chunk)}\n\n`); } } const endTime = Date.now(); this.logger.log( - `Response generation completed. Total chunks: ${chunkCount}`, + `Response completed. Chunks: ${chunkCount}, Time: ${endTime - startTime}ms`, ); - this.logger.log(`Generation time: ${endTime - startTime}ms`); res.write(`data: [DONE]\n\n`); res.end(); - this.logger.log('Response stream ended.'); } catch (error) { - this.logger.error('Error during OpenAI response generation:', error); - res.write(`data: ${JSON.stringify({ error: 'Generation failed' })}\n\n`); - res.write(`data: [DONE]\n\n`); - res.end(); - } - } + const errorMessage = + error instanceof Error ? error.message : String(error); - async getModelTagsResponse(res: Response): Promise { - this.logger.log('Fetching available models from OpenAI...'); - res.writeHead(200, { - 'Content-Type': 'text/event-stream', - 'Cache-Control': 'no-cache', - Connection: 'keep-alive', - }); + const isServerError = errorMessage.includes('server had an error'); + const isRateLimit = errorMessage.includes('rate_limit'); + const isAuthError = errorMessage.includes('authentication'); + + if ((isServerError || isRateLimit) && retries < this.options.maxRetries) { + this.logger.warn( + `Request failed (attempt ${retries + 1}) with error: ${errorMessage}. Retrying...`, + ); + + const baseDelay = isServerError ? 5000 : this.options.retryDelay; + const delay = Math.min(baseDelay * Math.pow(2, retries), 30000); + + await new Promise(resolve => setTimeout(resolve, delay)); + + request.retries++; + await this.requestQueue.add(() => this.processRequest(request)); + return; + } - try { - const startTime = Date.now(); - const models = await this.openai.models.list(); - const response = { - models: models, - }; - const endTime = Date.now(); - this.logger.log( - `Model fetching completed. Total models: ${models.data.length}`, - ); - this.logger.log(`Fetch time: ${endTime - startTime}ms`); - res.write(JSON.stringify(response)); - res.end(); - this.logger.log('Response ModelTags ended.'); - } catch (error) { - this.logger.error('Error during OpenAI response generation:', error); const errorResponse = { error: { - message: 'Failed to fetch models', - code: 'FETCH_MODELS_ERROR', - details: error instanceof Error ? error.message : 'Unknown error', + message: errorMessage, + code: isServerError + ? 'SERVER_ERROR' + : isRateLimit + ? 'RATE_LIMIT' + : isAuthError + ? 'AUTH_ERROR' + : 'UNKNOWN_ERROR', + retryable: isServerError || isRateLimit, + retries: retries, }, }; + + this.logger.error('Error during OpenAI response generation:', { + error: errorResponse, + params: { + model, + messageLength: message.length, + role, + }, + }); + res.write(`data: ${JSON.stringify(errorResponse)}\n\n`); res.write(`data: [DONE]\n\n`); res.end(); } } + + private shouldRetry(error: any): boolean { + const retryableErrors = [ + 'rate_limit_exceeded', + 'timeout', + 'service_unavailable', + ]; + + if (error instanceof Error) { + return retryableErrors.some(e => error.message.includes(e)); + } + + return false; + } + + async getModelTagsResponse(res: Response): Promise { + await this.requestQueue.add(async () => { + this.logger.log('Fetching available models from OpenAI...'); + res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + }); + + try { + const startTime = Date.now(); + const models = await this.openai.models.list(); + const response = { + models: models, + }; + const endTime = Date.now(); + + this.logger.log( + `Models fetched: ${models.data.length}, Time: ${endTime - startTime}ms`, + ); + + res.write(JSON.stringify(response)); + res.end(); + } catch (error) { + this.logger.error('Error fetching models:', error); + const errorResponse = { + error: { + message: 'Failed to fetch models', + code: 'FETCH_MODELS_ERROR', + details: error instanceof Error ? error.message : 'Unknown error', + }, + }; + res.write(`data: ${JSON.stringify(errorResponse)}\n\n`); + res.write(`data: [DONE]\n\n`); + res.end(); + } + }); + } + + getOptions(): Readonly { + return { ...this.options }; + } + + getQueueStatus() { + return { + size: this.requestQueue.size, + pending: this.requestQueue.pending, + isPaused: this.requestQueue.isPaused, + }; + } } diff --git a/llm-server/src/type/GenerateMessage.ts b/llm-server/src/type/GenerateMessage.ts deleted file mode 100644 index c7d8f6dd..00000000 --- a/llm-server/src/type/GenerateMessage.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface GenerateMessageParams { - model: string; // Model to use, e.g., 'gpt-3.5-turbo' - message: string; // User's message or query - role?: 'user' | 'system' | 'assistant' | 'tool' | 'function'; // Optional role -} diff --git a/llm-server/src/types.ts b/llm-server/src/types.ts new file mode 100644 index 00000000..2f1bc1df --- /dev/null +++ b/llm-server/src/types.ts @@ -0,0 +1,20 @@ +export interface GenerateMessageParams { + model: string; // Model to use, e.g., 'gpt-3.5-turbo' + message: string; // User's message or query + role?: 'user' | 'system' | 'assistant' | 'tool' | 'function'; // Optional role +} + +// types.ts +export type ModelProviderType = 'llama' | 'openai'; + +export interface ModelProviderOptions { + maxConcurrentRequests?: number; + maxRetries?: number; + retryDelay?: number; +} + +export interface ModelError extends Error { + code?: string; + retryable?: boolean; + details?: any; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40b46a1b..69ec2407 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -211,7 +211,7 @@ importers: version: 3.6.3(@mdx-js/react@3.1.0)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.6.3) '@docusaurus/preset-classic': specifier: 3.6.3 - version: 3.6.3(@algolia/client-search@5.17.1)(@mdx-js/react@3.1.0)(@types/react@18.3.17)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1)(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.3) + version: 3.6.3(@algolia/client-search@5.18.0)(@mdx-js/react@3.1.0)(@types/react@18.3.17)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1)(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.3) '@mdx-js/react': specifier: ^3.0.0 version: 3.1.0(@types/react@18.3.17)(react@18.3.1) @@ -472,6 +472,9 @@ importers: nodemon: specifier: ^3.1.7 version: 3.1.9 + p-queue: + specifier: ^8.0.1 + version: 8.0.1 devDependencies: '@types/express': specifier: ^4.17.13 @@ -544,48 +547,48 @@ packages: resolution: {integrity: sha512-12WGKBQzjUAI4ayyF4IAtfw2QR/IDoqk6jTddXDhtYTJF9ASmoE1zst7cVtP0aL/F1jUJL5r+JxKXKEgHNbEUQ==} dev: true - /@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.17.1)(algoliasearch@5.17.1)(search-insights@2.17.3): + /@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3): resolution: {integrity: sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==} dependencies: - '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.17.1)(algoliasearch@5.17.1)(search-insights@2.17.3) - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.17.1)(algoliasearch@5.17.1) + '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0) transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - search-insights dev: false - /@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.17.1)(algoliasearch@5.17.1)(search-insights@2.17.3): + /@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3): resolution: {integrity: sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==} peerDependencies: search-insights: '>= 1 < 3' dependencies: - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.17.1)(algoliasearch@5.17.1) + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0) search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' - algoliasearch dev: false - /@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.17.1)(algoliasearch@5.17.1): + /@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0): resolution: {integrity: sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==} peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' dependencies: - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.17.1)(algoliasearch@5.17.1) - '@algolia/client-search': 5.17.1 - algoliasearch: 5.17.1 + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0) + '@algolia/client-search': 5.18.0 + algoliasearch: 5.18.0 dev: false - /@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.17.1)(algoliasearch@5.17.1): + /@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0): resolution: {integrity: sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==} peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' dependencies: - '@algolia/client-search': 5.17.1 - algoliasearch: 5.17.1 + '@algolia/client-search': 5.18.0 + algoliasearch: 5.18.0 dev: false /@algolia/cache-browser-local-storage@4.24.0: @@ -604,14 +607,14 @@ packages: '@algolia/cache-common': 4.24.0 dev: false - /@algolia/client-abtesting@5.17.1: - resolution: {integrity: sha512-Os/xkQbDp5A5RdGYq1yS3fF69GoBJH5FIfrkVh+fXxCSe714i1Xdl9XoXhS4xG76DGKm6EFMlUqP024qjps8cg==} + /@algolia/client-abtesting@5.18.0: + resolution: {integrity: sha512-DLIrAukjsSrdMNNDx1ZTks72o4RH/1kOn8Wx5zZm8nnqFexG+JzY4SANnCNEjnFQPJTTvC+KpgiNW/CP2lumng==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.17.1 - '@algolia/requester-browser-xhr': 5.17.1 - '@algolia/requester-fetch': 5.17.1 - '@algolia/requester-node-http': 5.17.1 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 dev: false /@algolia/client-account@4.24.0: @@ -631,14 +634,14 @@ packages: '@algolia/transporter': 4.24.0 dev: false - /@algolia/client-analytics@5.17.1: - resolution: {integrity: sha512-WKpGC+cUhmdm3wndIlTh8RJXoVabUH+4HrvZHC4hXtvCYojEXYeep8RZstatwSZ7Ocg6Y2u67bLw90NEINuYEw==} + /@algolia/client-analytics@5.18.0: + resolution: {integrity: sha512-0VpGG2uQW+h2aejxbG8VbnMCQ9ary9/ot7OASXi6OjE0SRkYQ/+pkW+q09+IScif3pmsVVYggmlMPtAsmYWHng==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.17.1 - '@algolia/requester-browser-xhr': 5.17.1 - '@algolia/requester-fetch': 5.17.1 - '@algolia/requester-node-http': 5.17.1 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 dev: false /@algolia/client-common@4.24.0: @@ -648,19 +651,19 @@ packages: '@algolia/transporter': 4.24.0 dev: false - /@algolia/client-common@5.17.1: - resolution: {integrity: sha512-5rb5+yPIie6912riAypTSyzbE23a7UM1UpESvD8GEPI4CcWQvA9DBlkRNx9qbq/nJ5pvv8VjZjUxJj7rFkzEAA==} + /@algolia/client-common@5.18.0: + resolution: {integrity: sha512-X1WMSC+1ve2qlMsemyTF5bIjwipOT+m99Ng1Tyl36ZjQKTa54oajBKE0BrmM8LD8jGdtukAgkUhFoYOaRbMcmQ==} engines: {node: '>= 14.0.0'} dev: false - /@algolia/client-insights@5.17.1: - resolution: {integrity: sha512-nb/tfwBMn209TzFv1DDTprBKt/wl5btHVKoAww9fdEVdoKK02R2KAqxe5tuXLdEzAsS+LevRyOM/YjXuLmPtjQ==} + /@algolia/client-insights@5.18.0: + resolution: {integrity: sha512-FAJRNANUOSs/FgYOJ/Njqp+YTe4TMz2GkeZtfsw1TMiA5mVNRS/nnMpxas9771aJz7KTEWvK9GwqPs0K6RMYWg==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.17.1 - '@algolia/requester-browser-xhr': 5.17.1 - '@algolia/requester-fetch': 5.17.1 - '@algolia/requester-node-http': 5.17.1 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 dev: false /@algolia/client-personalization@4.24.0: @@ -671,24 +674,24 @@ packages: '@algolia/transporter': 4.24.0 dev: false - /@algolia/client-personalization@5.17.1: - resolution: {integrity: sha512-JuNlZe1SdW9KbV0gcgdsiVkFfXt0mmPassdS3cBSGvZGbPB9JsHthD719k5Y6YOY4dGvw1JmC1i9CwCQHAS8hg==} + /@algolia/client-personalization@5.18.0: + resolution: {integrity: sha512-I2dc94Oiwic3SEbrRp8kvTZtYpJjGtg5y5XnqubgnA15AgX59YIY8frKsFG8SOH1n2rIhUClcuDkxYQNXJLg+w==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.17.1 - '@algolia/requester-browser-xhr': 5.17.1 - '@algolia/requester-fetch': 5.17.1 - '@algolia/requester-node-http': 5.17.1 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 dev: false - /@algolia/client-query-suggestions@5.17.1: - resolution: {integrity: sha512-RBIFIv1QE3IlAikJKWTOpd6pwE4d2dY6t02iXH7r/SLXWn0HzJtsAPPeFg/OKkFvWAXt0H7In2/Mp7a1/Dy2pw==} + /@algolia/client-query-suggestions@5.18.0: + resolution: {integrity: sha512-x6XKIQgKFTgK/bMasXhghoEjHhmgoP61pFPb9+TaUJ32aKOGc65b12usiGJ9A84yS73UDkXS452NjyP50Knh/g==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.17.1 - '@algolia/requester-browser-xhr': 5.17.1 - '@algolia/requester-fetch': 5.17.1 - '@algolia/requester-node-http': 5.17.1 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 dev: false /@algolia/client-search@4.24.0: @@ -699,28 +702,28 @@ packages: '@algolia/transporter': 4.24.0 dev: false - /@algolia/client-search@5.17.1: - resolution: {integrity: sha512-bd5JBUOP71kPsxwDcvOxqtqXXVo/706NFifZ/O5Rx5GB8ZNVAhg4l7aGoT6jBvEfgmrp2fqPbkdIZ6JnuOpGcw==} + /@algolia/client-search@5.18.0: + resolution: {integrity: sha512-qI3LcFsVgtvpsBGR7aNSJYxhsR+Zl46+958ODzg8aCxIcdxiK7QEVLMJMZAR57jGqW0Lg/vrjtuLFDMfSE53qA==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.17.1 - '@algolia/requester-browser-xhr': 5.17.1 - '@algolia/requester-fetch': 5.17.1 - '@algolia/requester-node-http': 5.17.1 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 dev: false /@algolia/events@4.0.1: resolution: {integrity: sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==} dev: false - /@algolia/ingestion@1.17.1: - resolution: {integrity: sha512-T18tvePi1rjRYcIKhd82oRukrPWHxG/Iy1qFGaxCplgRm9Im5z96qnYOq75MSKGOUHkFxaBKJOLmtn8xDR+Mcw==} + /@algolia/ingestion@1.18.0: + resolution: {integrity: sha512-bGvJg7HnGGm+XWYMDruZXWgMDPVt4yCbBqq8DM6EoaMBK71SYC4WMfIdJaw+ABqttjBhe6aKNRkWf/bbvYOGyw==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.17.1 - '@algolia/requester-browser-xhr': 5.17.1 - '@algolia/requester-fetch': 5.17.1 - '@algolia/requester-node-http': 5.17.1 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 dev: false /@algolia/logger-common@4.24.0: @@ -733,14 +736,14 @@ packages: '@algolia/logger-common': 4.24.0 dev: false - /@algolia/monitoring@1.17.1: - resolution: {integrity: sha512-gDtow+AUywTehRP8S1tWKx2IvhcJOxldAoqBxzN3asuQobF7er5n72auBeL++HY4ImEuzMi7PDOA/Iuwxs2IcA==} + /@algolia/monitoring@1.18.0: + resolution: {integrity: sha512-lBssglINIeGIR+8KyzH05NAgAmn1BCrm5D2T6pMtr/8kbTHvvrm1Zvcltc5dKUQEFyyx3J5+MhNc7kfi8LdjVw==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.17.1 - '@algolia/requester-browser-xhr': 5.17.1 - '@algolia/requester-fetch': 5.17.1 - '@algolia/requester-node-http': 5.17.1 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 dev: false /@algolia/recommend@4.24.0: @@ -759,14 +762,14 @@ packages: '@algolia/transporter': 4.24.0 dev: false - /@algolia/recommend@5.17.1: - resolution: {integrity: sha512-2992tTHkRe18qmf5SP57N78kN1D3e5t4PO1rt10sJncWtXBZWiNOK6K/UcvWsFbNSGAogFcIcvIMAl5mNp6RWA==} + /@algolia/recommend@5.18.0: + resolution: {integrity: sha512-uSnkm0cdAuFwdMp4pGT5vHVQ84T6AYpTZ3I0b3k/M3wg4zXDhl3aCiY8NzokEyRLezz/kHLEEcgb/tTTobOYVw==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.17.1 - '@algolia/requester-browser-xhr': 5.17.1 - '@algolia/requester-fetch': 5.17.1 - '@algolia/requester-node-http': 5.17.1 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 dev: false /@algolia/requester-browser-xhr@4.24.0: @@ -775,22 +778,22 @@ packages: '@algolia/requester-common': 4.24.0 dev: false - /@algolia/requester-browser-xhr@5.17.1: - resolution: {integrity: sha512-XpKgBfyczVesKgr7DOShNyPPu5kqlboimRRPjdqAw5grSyHhCmb8yoTIKy0TCqBABZeXRPMYT13SMruUVRXvHA==} + /@algolia/requester-browser-xhr@5.18.0: + resolution: {integrity: sha512-1XFjW0C3pV0dS/9zXbV44cKI+QM4ZIz9cpatXpsjRlq6SUCpLID3DZHsXyE6sTb8IhyPaUjk78GEJT8/3hviqg==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.17.1 + '@algolia/client-common': 5.18.0 dev: false /@algolia/requester-common@4.24.0: resolution: {integrity: sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==} dev: false - /@algolia/requester-fetch@5.17.1: - resolution: {integrity: sha512-EhUomH+DZP5vb6DnEjT0GvXaXBSwzZnuU6hPGNU1EYKRXDouRjII/bIWpVjt7ycMgL2D2oQruqDh6rAWUhQwRw==} + /@algolia/requester-fetch@5.18.0: + resolution: {integrity: sha512-0uodeNdAHz1YbzJh6C5xeQ4T6x5WGiUxUq3GOaT/R4njh5t78dq+Rb187elr7KtnjUmETVVuCvmEYaThfTHzNg==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.17.1 + '@algolia/client-common': 5.18.0 dev: false /@algolia/requester-node-http@4.24.0: @@ -799,11 +802,11 @@ packages: '@algolia/requester-common': 4.24.0 dev: false - /@algolia/requester-node-http@5.17.1: - resolution: {integrity: sha512-PSnENJtl4/wBWXlGyOODbLYm6lSiFqrtww7UpQRCJdsHXlJKF8XAP6AME8NxvbE0Qo/RJUxK0mvyEh9sQcx6bg==} + /@algolia/requester-node-http@5.18.0: + resolution: {integrity: sha512-tZCqDrqJ2YE2I5ukCQrYN8oiF6u3JIdCxrtKq+eniuLkjkO78TKRnXrVcKZTmfFJyyDK8q47SfDcHzAA3nHi6w==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-common': 5.17.1 + '@algolia/client-common': 5.18.0 dev: false /@algolia/transporter@4.24.0: @@ -3016,7 +3019,7 @@ packages: resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} dev: false - /@docsearch/react@3.8.2(@algolia/client-search@5.17.1)(@types/react@18.3.17)(react-dom@18.3.1)(react@18.3.1)(search-insights@2.17.3): + /@docsearch/react@3.8.2(@algolia/client-search@5.18.0)(@types/react@18.3.17)(react-dom@18.3.1)(react@18.3.1)(search-insights@2.17.3): resolution: {integrity: sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==} peerDependencies: '@types/react': '>= 16.8.0 < 19.0.0' @@ -3033,11 +3036,11 @@ packages: search-insights: optional: true dependencies: - '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.17.1)(algoliasearch@5.17.1)(search-insights@2.17.3) - '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.17.1)(algoliasearch@5.17.1) + '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3) + '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0) '@docsearch/css': 3.8.2 '@types/react': 18.3.17 - algoliasearch: 5.17.1 + algoliasearch: 5.18.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) search-insights: 2.17.3 @@ -3610,7 +3613,7 @@ packages: - webpack-cli dev: false - /@docusaurus/preset-classic@3.6.3(@algolia/client-search@5.17.1)(@mdx-js/react@3.1.0)(@types/react@18.3.17)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1)(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.3): + /@docusaurus/preset-classic@3.6.3(@algolia/client-search@5.18.0)(@mdx-js/react@3.1.0)(@types/react@18.3.17)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1)(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.3): resolution: {integrity: sha512-VHSYWROT3flvNNI1SrnMOtW1EsjeHNK9dhU6s9eY5hryZe79lUqnZJyze/ymDe2LXAqzyj6y5oYvyBoZZk6ErA==} engines: {node: '>=18.0'} peerDependencies: @@ -3628,7 +3631,7 @@ packages: '@docusaurus/plugin-sitemap': 3.6.3(@mdx-js/react@3.1.0)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.6.3) '@docusaurus/theme-classic': 3.6.3(@types/react@18.3.17)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.6.3) '@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3)(acorn@8.14.0)(react-dom@18.3.1)(react@18.3.1)(typescript@5.6.3) - '@docusaurus/theme-search-algolia': 3.6.3(@algolia/client-search@5.17.1)(@mdx-js/react@3.1.0)(@types/react@18.3.17)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1)(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.3) + '@docusaurus/theme-search-algolia': 3.6.3(@algolia/client-search@5.18.0)(@mdx-js/react@3.1.0)(@types/react@18.3.17)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1)(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.3) '@docusaurus/types': 3.6.3(acorn@8.14.0)(react-dom@18.3.1)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -3755,14 +3758,14 @@ packages: - webpack-cli dev: false - /@docusaurus/theme-search-algolia@3.6.3(@algolia/client-search@5.17.1)(@mdx-js/react@3.1.0)(@types/react@18.3.17)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1)(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.3): + /@docusaurus/theme-search-algolia@3.6.3(@algolia/client-search@5.18.0)(@mdx-js/react@3.1.0)(@types/react@18.3.17)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1)(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.3): resolution: {integrity: sha512-rt+MGCCpYgPyWCGXtbxlwFbTSobu15jWBTPI2LHsHNa5B0zSmOISX6FWYAPt5X1rNDOqMGM0FATnh7TBHRohVA==} engines: {node: '>=18.0'} peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 dependencies: - '@docsearch/react': 3.8.2(@algolia/client-search@5.17.1)(@types/react@18.3.17)(react-dom@18.3.1)(react@18.3.1)(search-insights@2.17.3) + '@docsearch/react': 3.8.2(@algolia/client-search@5.18.0)(@types/react@18.3.17)(react-dom@18.3.1)(react@18.3.1)(search-insights@2.17.3) '@docusaurus/core': 3.6.3(@mdx-js/react@3.1.0)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.6.3) '@docusaurus/logger': 3.6.3 '@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.1.0)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.6.3) @@ -9084,23 +9087,23 @@ packages: '@algolia/transporter': 4.24.0 dev: false - /algoliasearch@5.17.1: - resolution: {integrity: sha512-3CcbT5yTWJDIcBe9ZHgsPi184SkT1kyZi3GWlQU5EFgvq1V73X2sqHRkPCQMe0RA/uvZbB+1sFeAk73eWygeLg==} + /algoliasearch@5.18.0: + resolution: {integrity: sha512-/tfpK2A4FpS0o+S78o3YSdlqXr0MavJIDlFK3XZrlXLy7vaRXJvW5jYg3v5e/wCaF8y0IpMjkYLhoV6QqfpOgw==} engines: {node: '>= 14.0.0'} dependencies: - '@algolia/client-abtesting': 5.17.1 - '@algolia/client-analytics': 5.17.1 - '@algolia/client-common': 5.17.1 - '@algolia/client-insights': 5.17.1 - '@algolia/client-personalization': 5.17.1 - '@algolia/client-query-suggestions': 5.17.1 - '@algolia/client-search': 5.17.1 - '@algolia/ingestion': 1.17.1 - '@algolia/monitoring': 1.17.1 - '@algolia/recommend': 5.17.1 - '@algolia/requester-browser-xhr': 5.17.1 - '@algolia/requester-fetch': 5.17.1 - '@algolia/requester-node-http': 5.17.1 + '@algolia/client-abtesting': 5.18.0 + '@algolia/client-analytics': 5.18.0 + '@algolia/client-common': 5.18.0 + '@algolia/client-insights': 5.18.0 + '@algolia/client-personalization': 5.18.0 + '@algolia/client-query-suggestions': 5.18.0 + '@algolia/client-search': 5.18.0 + '@algolia/ingestion': 1.18.0 + '@algolia/monitoring': 1.18.0 + '@algolia/recommend': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 dev: false /ansi-align@3.0.1: @@ -9455,7 +9458,7 @@ packages: /babel-plugin-dynamic-import-node@2.3.3: resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==} dependencies: - object.assign: 4.1.5 + object.assign: 4.1.7 dev: false /babel-plugin-istanbul@6.1.1: @@ -9700,7 +9703,7 @@ packages: dependencies: ansi-align: 3.0.1 camelcase: 7.0.1 - chalk: 5.3.0 + chalk: 5.4.0 cli-boxes: 3.0.0 string-width: 5.1.2 type-fest: 2.19.0 @@ -9922,8 +9925,8 @@ packages: ansi-styles: 4.3.0 supports-color: 7.2.0 - /chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + /chalk@5.4.0: + resolution: {integrity: sha512-ZkD35Mx92acjB2yNJgziGqT9oKHEOxjTBTDRpOsRWtdecL/0jM3z5kM/CTzHWvHIen1GvkM85p6TuFfDGfc8/Q==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} /change-case-all@1.0.14: @@ -11499,26 +11502,26 @@ packages: is-data-view: 1.0.2 is-negative-zero: 2.0.3 is-regex: 1.2.1 - is-shared-array-buffer: 1.0.3 + is-shared-array-buffer: 1.0.4 is-string: 1.1.1 - is-typed-array: 1.1.14 + is-typed-array: 1.1.15 is-weakref: 1.1.0 math-intrinsics: 1.0.0 object-inspect: 1.13.3 object-keys: 1.1.1 - object.assign: 4.1.5 + object.assign: 4.1.7 regexp.prototype.flags: 1.5.3 safe-array-concat: 1.1.3 safe-regex-test: 1.1.0 string.prototype.trim: 1.2.10 string.prototype.trimend: 1.0.9 string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.2 + typed-array-buffer: 1.0.3 typed-array-byte-length: 1.0.3 typed-array-byte-offset: 1.0.3 typed-array-length: 1.0.7 unbox-primitive: 1.1.0 - which-typed-array: 1.1.16 + which-typed-array: 1.1.18 dev: true /es-define-property@1.0.1: @@ -12997,8 +13000,9 @@ packages: resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} dev: false - /has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + /has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} dev: true /has-flag@3.0.0: @@ -13621,7 +13625,7 @@ packages: dependencies: '@ljharb/through': 2.3.13 ansi-escapes: 4.3.2 - chalk: 5.3.0 + chalk: 5.4.0 cli-cursor: 3.1.0 cli-width: 4.1.0 external-editor: 3.1.0 @@ -13685,7 +13689,7 @@ packages: dependencies: '@tinyhttp/content-disposition': 2.2.2 async-retry: 1.3.3 - chalk: 5.3.0 + chalk: 5.4.0 ci-info: 4.1.0 cli-spinners: 2.9.2 commander: 10.0.1 @@ -13761,7 +13765,7 @@ packages: resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} engines: {node: '>= 0.4'} dependencies: - has-bigints: 1.0.2 + has-bigints: 1.1.0 dev: true /is-binary-path@2.1.0: @@ -13808,7 +13812,7 @@ packages: dependencies: call-bound: 1.0.3 get-intrinsic: 1.2.6 - is-typed-array: 1.1.14 + is-typed-array: 1.1.15 dev: true /is-date-object@1.1.0: @@ -14011,11 +14015,11 @@ packages: engines: {node: '>= 0.4'} dev: true - /is-shared-array-buffer@1.0.3: - resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + /is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bound: 1.0.3 dev: true /is-stream@2.0.1: @@ -14039,11 +14043,11 @@ packages: safe-regex-test: 1.1.0 dev: true - /is-typed-array@1.1.14: - resolution: {integrity: sha512-lQUsHzcTb7rH57dajbOuZEuMDXjs9f04ZloER4QOpjpKcaw4f98BRUrs8aiO9Z4G7i7B0Xhgarg6SCgYcYi8Nw==} + /is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} dependencies: - which-typed-array: 1.1.16 + which-typed-array: 1.1.18 dev: true /is-typedarray@1.0.0: @@ -14225,7 +14229,7 @@ packages: es-object-atoms: 1.0.0 get-intrinsic: 1.2.6 has-symbols: 1.1.0 - reflect.getprototypeof: 1.0.8 + reflect.getprototypeof: 1.0.9 set-function-name: 2.0.2 dev: true @@ -14643,7 +14647,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.10.2 + '@types/node': 20.17.10 chalk: 4.1.2 cjs-module-lexer: 1.4.1 collect-v8-coverage: 1.0.2 @@ -14965,7 +14969,7 @@ packages: dependencies: array-includes: 3.1.8 array.prototype.flat: 1.3.3 - object.assign: 4.1.5 + object.assign: 4.1.7 object.values: 1.2.0 dev: true @@ -15265,7 +15269,7 @@ packages: resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} engines: {node: '>=18'} dependencies: - chalk: 5.3.0 + chalk: 5.4.0 is-unicode-supported: 1.3.0 dev: false @@ -16556,7 +16560,7 @@ packages: '@huggingface/jinja': 0.3.2 async-retry: 1.3.3 bytes: 3.1.2 - chalk: 5.3.0 + chalk: 5.4.0 chmodrp: 1.0.2 cmake-js: 7.3.0 cross-env: 7.0.3 @@ -16720,12 +16724,14 @@ packages: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} - /object.assign@4.1.5: - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + /object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 + call-bound: 1.0.3 define-properties: 1.2.1 + es-object-atoms: 1.0.0 has-symbols: 1.1.0 object-keys: 1.1.1 @@ -16920,7 +16926,7 @@ packages: resolution: {integrity: sha512-YWielGi1XzG1UTvOaCFaNgEnuhZVMSHYkW/FQ7UX8O26PtlpdM84c0f7wLPlkvx2RfiQmnzd61d/MGxmpQeJPw==} engines: {node: '>=18'} dependencies: - chalk: 5.3.0 + chalk: 5.4.0 cli-cursor: 5.0.0 cli-spinners: 2.9.2 is-interactive: 2.0.0 @@ -17009,6 +17015,14 @@ packages: p-timeout: 3.2.0 dev: false + /p-queue@8.0.1: + resolution: {integrity: sha512-NXzu9aQJTAzbBqOt2hwsR63ea7yvxJc0PwN/zobNAudYfb1B7R08SzB4TsLeSbUCuG467NhnoT0oO6w1qRO+BA==} + engines: {node: '>=18'} + dependencies: + eventemitter3: 5.0.1 + p-timeout: 6.1.3 + dev: false + /p-retry@4.6.2: resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} engines: {node: '>=8'} @@ -17024,6 +17038,11 @@ packages: p-finally: 1.0.0 dev: false + /p-timeout@6.1.3: + resolution: {integrity: sha512-UJUyfKbwvr/uZSV6btANfb+0t/mOhKV/KXcCUTp8FcQI+v/0d+wXqH4htrW0E4rR6WiEO/EPvUFiV9D5OI4vlw==} + engines: {node: '>=14.16'} + dev: false + /p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -18826,8 +18845,8 @@ packages: /reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} - /reflect.getprototypeof@1.0.8: - resolution: {integrity: sha512-B5dj6usc5dkk8uFliwjwDHM8To5/QwdKz9JcBZ8Ic4G1f0YmeeJTtE/ZTdgRFPAfxZFiUaPhZ1Jcs4qeagItGQ==} + /reflect.getprototypeof@1.0.9: + resolution: {integrity: sha512-r0Ay04Snci87djAsI4U+WNRcSw5S4pOH7qFjd/veA5gC7TbqESR3tcj28ia95L/fYUDw11JKP7uqUKUAfVvV5Q==} engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 @@ -20858,13 +20877,13 @@ packages: media-typer: 0.3.0 mime-types: 2.1.35 - /typed-array-buffer@1.0.2: - resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + /typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bound: 1.0.3 es-errors: 1.3.0 - is-typed-array: 1.1.14 + is-typed-array: 1.1.15 dev: true /typed-array-byte-length@1.0.3: @@ -20875,7 +20894,7 @@ packages: for-each: 0.3.3 gopd: 1.2.0 has-proto: 1.2.0 - is-typed-array: 1.1.14 + is-typed-array: 1.1.15 dev: true /typed-array-byte-offset@1.0.3: @@ -20887,8 +20906,8 @@ packages: for-each: 0.3.3 gopd: 1.2.0 has-proto: 1.2.0 - is-typed-array: 1.1.14 - reflect.getprototypeof: 1.0.8 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.9 dev: true /typed-array-length@1.0.7: @@ -20898,9 +20917,9 @@ packages: call-bind: 1.0.8 for-each: 0.3.3 gopd: 1.2.0 - is-typed-array: 1.1.14 + is-typed-array: 1.1.15 possible-typed-array-names: 1.0.0 - reflect.getprototypeof: 1.0.8 + reflect.getprototypeof: 1.0.9 dev: true /typedarray-to-buffer@3.1.5: @@ -21023,7 +21042,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.3 - has-bigints: 1.0.2 + has-bigints: 1.1.0 has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 dev: true @@ -21183,7 +21202,7 @@ packages: engines: {node: '>=14.16'} dependencies: boxen: 7.1.1 - chalk: 5.3.0 + chalk: 5.4.0 configstore: 6.0.0 has-yarn: 3.0.0 import-lazy: 4.0.0 @@ -21724,7 +21743,7 @@ packages: isarray: 2.0.5 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 - which-typed-array: 1.1.16 + which-typed-array: 1.1.18 dev: true /which-collection@1.0.2: @@ -21741,12 +21760,13 @@ packages: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} dev: true - /which-typed-array@1.1.16: - resolution: {integrity: sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==} + /which-typed-array@1.1.18: + resolution: {integrity: sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==} engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.8 + call-bound: 1.0.3 for-each: 0.3.3 gopd: 1.2.0 has-tostringtag: 1.0.2