diff --git a/.gitignore b/.gitignore index a85b41ae..f5c6219c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ models/ */**/models/ */**/database.sqlite -./backend/src/database.sqlite \ No newline at end of file +./backend/src/database.sqlite +.codefox \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index 77a9675b..aa62e5a2 100644 --- a/backend/package.json +++ b/backend/package.json @@ -41,6 +41,7 @@ "@types/fs-extra": "^11.0.4", "@types/normalize-path": "^3.0.2", "@types/toposort": "^2.0.7", + "toposort": "^2.0.2", "axios": "^1.7.7", "bcrypt": "^5.1.1", "class-validator": "^0.14.1", 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 2e818301..ca84f7da 100644 --- a/backend/src/build-system/__tests__/test-generate-doc.spec.ts +++ b/backend/src/build-system/__tests__/test-generate-doc.spec.ts @@ -1,7 +1,6 @@ /* eslint-disable no-console */ import { BuilderContext } from 'src/build-system/context'; import { BuildSequence } from '../types'; -import { BuildSequenceExecutor } from '../executor'; import * as fs from 'fs'; import * as path from 'path'; import { writeToFile } from './utils'; @@ -26,8 +25,6 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS', () => { { id: 'op:PRD', name: 'PRD Generation Node', - type: 'ANALYSIS', - subType: 'PRD', }, ], }, @@ -38,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'], }, ], @@ -51,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'], }, ], @@ -108,7 +101,7 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS', () => { context.setGlobalContext('platform', 'web'); try { - await BuildSequenceExecutor.executeSequence(sequence, context); + await context.execute(); for (const step of sequence.steps) { for (const node of step.nodes) { 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 5b80ed41..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 @@ -1,7 +1,6 @@ /* eslint-disable no-console */ import { BuilderContext } from 'src/build-system/context'; import { BuildSequence } from '../types'; -import { BuildSequenceExecutor } from '../executor'; import * as fs from 'fs'; import * as path from 'path'; import { writeToFile } from './utils'; @@ -28,8 +27,6 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGener { id: 'op:PRD', name: 'PRD Generation Node', - type: 'ANALYSIS', - subType: 'PRD', }, ], }, @@ -40,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'], }, ], @@ -53,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'], }, ], @@ -66,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'], }, ], @@ -79,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'], }, ], @@ -92,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'], }, ], @@ -105,7 +93,7 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGener try { // Execute the build sequence - await BuildSequenceExecutor.executeSequence(sequence, context); + await context.execute(); // Iterate through each step and node to retrieve and log results for (const step of sequence.steps) { diff --git a/backend/src/build-system/__tests__/test.Fullstack.spec.ts b/backend/src/build-system/__tests__/test.fullstack-gen.spec.ts similarity index 50% rename from backend/src/build-system/__tests__/test.Fullstack.spec.ts rename to backend/src/build-system/__tests__/test.fullstack-gen.spec.ts index 4d810158..f6f80fa3 100644 --- a/backend/src/build-system/__tests__/test.Fullstack.spec.ts +++ b/backend/src/build-system/__tests__/test.fullstack-gen.spec.ts @@ -1,10 +1,10 @@ /* eslint-disable no-console */ import { BuilderContext } from 'src/build-system/context'; import { BuildSequence } from '../types'; -import { BuildSequenceExecutor } from '../executor'; import * as fs from 'fs'; import * as path from 'path'; import { writeToFile } from './utils'; +import { BuildMonitor } from '../monitor'; describe('Sequence: PRD -> UXSD -> UXSS -> UXDD -> DATABASE_REQ -> DBSchemas -> Frontend_File_struct -> Frontend_File_arch -> BackendCodeGenerator', () => { // Generate a unique folder with a timestamp @@ -23,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', @@ -113,71 +82,134 @@ 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', - requires: [ - 'op:FILE:STRUCT', - //TODO: here use datamap doc rather than datamap struct, we have to change this - 'op:UX:DATAMAP:DOC', - ], + requires: ['op:FILE:STRUCT', 'op:UX:DATAMAP:DOC'], }, ], }, { - 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 { - // Execute the build sequence - await BuildSequenceExecutor.executeSequence(sequence, context); + console.time('Total Execution Time'); + + await context.execute(); + + console.timeEnd('Total Execution Time'); + + const monitorReport = monitor.generateTextReport(sequence.id); + fs.writeFileSync( + path.join(logFolderPath, 'execution-metrics.txt'), + monitorReport, + 'utf8', + ); + + const sequenceMetrics = monitor.getSequenceMetrics(sequence.id); + if (sequenceMetrics) { + const metricsJson = { + totalDuration: `${sequenceMetrics.duration}ms`, + successRate: `${sequenceMetrics.successRate.toFixed(2)}%`, + totalSteps: sequenceMetrics.totalSteps, + completedSteps: sequenceMetrics.completedSteps, + failedSteps: sequenceMetrics.failedSteps, + totalNodes: sequenceMetrics.totalNodes, + startTime: new Date(sequenceMetrics.startTime).toISOString(), + endTime: new Date(sequenceMetrics.endTime).toISOString(), + }; + + fs.writeFileSync( + path.join(logFolderPath, 'metrics.json'), + JSON.stringify(metricsJson, null, 2), + 'utf8', + ); + + console.log('\nSequence Metrics:'); + console.table(metricsJson); + } - // Iterate through each step and node to retrieve and log results for (const step of sequence.steps) { + const stepMetrics = sequenceMetrics?.stepMetrics.get(step.id); for (const node of step.nodes) { const resultData = await context.getNodeData(node.id); - console.log(`Result for ${node.name}:`, resultData); - + const nodeMetrics = stepMetrics?.nodeMetrics.get(node.id); if (resultData) { - writeToFile(logFolderPath, node.name, resultData); + writeToFile(logFolderPath, `${node.name}`, resultData); } else { console.error( - `Handler ${node.name} failed with error:`, - resultData.error, + ` Error: Handler ${node.name} failed to produce result data`, ); + writeToFile(logFolderPath, `${node.name}-error`, { + error: 'No result data', + metrics: nodeMetrics, + }); } } } - console.log( - 'Sequence executed successfully. Logs stored in:', - logFolderPath, + const summary = { + timestamp: new Date().toISOString(), + sequenceId: sequence.id, + sequenceName: sequence.name, + totalExecutionTime: `${sequenceMetrics?.duration}ms`, + successRate: `${sequenceMetrics?.successRate.toFixed(2)}%`, + nodesExecuted: sequenceMetrics?.totalNodes, + completedNodes: sequenceMetrics?.stepMetrics.size, + logFolder: logFolderPath, + }; + + fs.writeFileSync( + path.join(logFolderPath, 'execution-summary.json'), + JSON.stringify(summary, null, 2), + 'utf8', ); } catch (error) { - console.error('Error during sequence execution:', error); + const errorReport = { + error: { + message: error.message, + stack: error.stack, + }, + metrics: monitor.getSequenceMetrics(sequence.id), + timestamp: new Date().toISOString(), + }; + fs.writeFileSync( - path.join(logFolderPath, 'error.txt'), - `Error: ${error.message}\n${error.stack}`, + path.join(logFolderPath, 'error-with-metrics.json'), + JSON.stringify(errorReport, null, 2), 'utf8', ); + + console.error('\nError during sequence execution:'); + console.error(error); + console.error( + '\nError report saved to:', + path.join(logFolderPath, 'error-with-metrics.json'), + ); 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__/test.spec.ts b/backend/src/build-system/__tests__/test.spec.ts index 7e132c6a..5524fe33 100644 --- a/backend/src/build-system/__tests__/test.spec.ts +++ b/backend/src/build-system/__tests__/test.spec.ts @@ -1,12 +1,10 @@ // src/build-system/__tests__/project-init-sequence.spec.ts import { BuilderContext } from '../context'; -import { BuildSequenceExecutor } from '../executor'; import { BuildHandlerManager } from '../hanlder-manager'; import { ProjectInitHandler } from '../handlers/project-init'; import { BuildSequence } from '../types'; describe('Project Init Handler Test', () => { let context: BuilderContext; - let executor: BuildSequenceExecutor; let handlerManager: BuildHandlerManager; const testSequence: BuildSequence = { @@ -35,7 +33,6 @@ describe('Project Init Handler Test', () => { handlerManager.clear(); context = new BuilderContext(testSequence, 'id'); - executor = new BuildSequenceExecutor(context); }); describe('Handler Registration', () => { 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 5d1ab5b4..220187c3 100644 --- a/backend/src/build-system/context.ts +++ b/backend/src/build-system/context.ts @@ -1,20 +1,22 @@ -import { BuildHandlerManager } from './hanlder-manager'; import { BuildExecutionState, BuildNode, BuildResult, BuildSequence, + BuildStep, NodeOutputMap, } from './types'; import { Logger } from '@nestjs/common'; import { VirtualDirectory } from './virtual-dir'; import { ModelProvider } from 'src/common/model-provider'; +import { v4 as uuidv4 } from 'uuid'; +import { BuildMonitor } from './monitor'; +import { BuildHandlerManager } from './hanlder-manager'; /** - * Predefined global keys for context. + * Global data keys used throughout the build process + * @type GlobalDataKeys */ -import { v4 as uuidv4 } from 'uuid'; - export type GlobalDataKeys = | 'projectName' | 'description' @@ -23,16 +25,20 @@ export type GlobalDataKeys = | 'projectUUID'; /** - * ContextData type, allowing dynamic keys and predefined keys. + * Generic context data type mapping keys to any value + * @type ContextData */ type ContextData = Record; /** - * BuilderContext manages: - * - Execution state of nodes (completed, pending, failed, waiting) - * - Global and arbitrary data (projectName, description, etc.) - * - Node output data, stored after successful execution - * - References to model provider, handler manager, virtual directory + * Core build context class that manages the execution of build sequences + * @class BuilderContext + * @description Responsible for: + * - Managing build execution state + * - Handling node dependencies and execution order + * - Managing global and node-specific context data + * - Coordinating with build handlers and monitors + * - Managing virtual directory operations */ export class BuilderContext { private executionState: BuildExecutionState = { @@ -43,10 +49,11 @@ export class BuilderContext { }; private logger: Logger; - private globalContext: Map = new Map(); // Stores global context data - private nodeData: Map = new Map(); // Stores node outputs + private globalContext: Map = new Map(); + private nodeData: Map = new Map(); private handlerManager: BuildHandlerManager; + private monitor: BuildMonitor; public model: ModelProvider; public virtualDirectory: VirtualDirectory; @@ -56,6 +63,7 @@ export class BuilderContext { ) { this.handlerManager = BuildHandlerManager.getInstance(); this.model = ModelProvider.getInstance(); + this.monitor = BuildMonitor.getInstance(); this.logger = new Logger(`builder-context-${id}`); this.virtualDirectory = new VirtualDirectory(); @@ -64,13 +72,255 @@ export class BuilderContext { this.globalContext.set('description', sequence.description || ''); this.globalContext.set('platform', 'web'); this.globalContext.set('databaseType', sequence.databaseType || 'SQLite'); - const projectUUID = uuidv4(); - this.globalContext.set('projectUUID', projectUUID); + this.globalContext.set('projectUUID', uuidv4()); + } + + async execute(): Promise { + this.logger.log(`Starting build sequence: ${this.sequence.id}`); + this.monitor.startSequenceExecution(this.sequence); + + try { + for (const step of this.sequence.steps) { + await this.executeStep(step); + + const incompletedNodes = step.nodes.filter( + (node) => !this.executionState.completed.has(node.id), + ); + + if (incompletedNodes.length > 0) { + this.logger.warn( + `Step ${step.id} failed to complete nodes: ${incompletedNodes + .map((n) => n.id) + .join(', ')}`, + ); + return; + } + } + + this.logger.log(`Build sequence completed: ${this.sequence.id}`); + this.logger.log('Final execution state:', this.executionState); + } finally { + this.monitor.endSequenceExecution( + this.sequence.id, + this.globalContext.get('projectUUID'), + ); + } + } + + /** + * Executes a build step, handling both parallel and sequential node execution + * @param step The build step to execute + * @private + */ + private async executeStep(step: BuildStep): Promise { + this.logger.log(`Executing build step: ${step.id}`); + this.monitor.setCurrentStep(step); + this.monitor.startStepExecution( + step.id, + this.sequence.id, + step.parallel, + step.nodes.length, + ); + + try { + if (step.parallel) { + await this.executeParallelNodes(step); + } else { + await this.executeSequentialNodes(step); + } + } finally { + this.monitor.endStepExecution(step.id, this.sequence.id); + } + } + + /** + * Executes nodes in parallel within a build step + * @param step The build step containing nodes to execute in parallel + * @private + */ + private async executeParallelNodes(step: BuildStep): Promise { + let remainingNodes = [...step.nodes]; + const concurrencyLimit = 3; // TODO: current is manually set to 3 for testing purposes + + while (remainingNodes.length > 0) { + const executableNodes = remainingNodes.filter((node) => + this.canExecute(node.id), + ); + + if (executableNodes.length > 0) { + 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), + ); + } else { + await new Promise((resolve) => setTimeout(resolve, 100)); + + 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); + } + } + } + + const finalActivePromises = this.model.getAllActivePromises(); + if (finalActivePromises.length > 0) { + this.logger.debug( + `Final wait for ${finalActivePromises.length} remaining LLM requests`, + ); + await Promise.all(finalActivePromises); + } } /** - * Checks if a node can be executed. - * @param nodeId The ID of the node. + * Executes nodes sequentially within a build step + * @param step The build step containing nodes to execute sequentially + * @private + */ + private async executeSequentialNodes(step: BuildStep): Promise { + for (const node of step.nodes) { + let retryCount = 0; + const maxRetries = 10; + + while ( + !this.executionState.completed.has(node.id) && + retryCount < maxRetries + ) { + await this.executeNode(node); + + if (!this.executionState.completed.has(node.id)) { + await new Promise((resolve) => setTimeout(resolve, 100)); + retryCount++; + } + } + + if (!this.executionState.completed.has(node.id)) { + this.logger.warn( + `Failed to execute node ${node.id} after ${maxRetries} attempts`, + ); + } + } + } + + private async executeNode(node: BuildNode): Promise { + 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}`); + 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; + } + } + + /** + * Checks if a node can be executed based on its dependencies + * @param nodeId The ID of the node to check + * @returns boolean indicating if the node can be executed */ canExecute(nodeId: string): boolean { const node = this.findNode(nodeId); @@ -90,11 +340,9 @@ export class BuilderContext { ); } - /** - * Executes a node by its ID. Upon success, stores node output data. - * @param nodeId The ID of the node to execute. - */ - async executeNodeById(nodeId: string): Promise> { + private async executeNodeById( + nodeId: string, + ): Promise> { const node = this.findNode(nodeId); if (!node) { throw new Error(`Node not found: ${nodeId}`); @@ -119,17 +367,14 @@ export class BuilderContext { } } - /** - * Returns the current execution state of the build sequence. - */ getExecutionState(): BuildExecutionState { return { ...this.executionState }; } /** - * Store global context data. - * @param key The key to store. - * @param value The value to store. + * Sets data in the global context + * @param key The key to set + * @param value The value to set */ setGlobalContext( key: Key, @@ -139,8 +384,9 @@ export class BuilderContext { } /** - * Retrieve global context data. - * @param key The key to retrieve. + * Gets data from the global context + * @param key The key to retrieve + * @returns The value associated with the key, or undefined */ getGlobalContext( key: Key, @@ -149,22 +395,23 @@ export class BuilderContext { } /** - * Retrieve the stored output data for a given node (typed if defined in NodeOutputMap). - * Overload 1: If nodeId is a key of NodeOutputMap, return strong-typed data. + * Retrieves node-specific data + * @param nodeId The ID of the node + * @returns The data associated with the node */ getNodeData( nodeId: NodeId, ): NodeOutputMap[NodeId]; - - /** - * Overload 2: If nodeId is not in NodeOutputMap, return any. - */ getNodeData(nodeId: string): any; - getNodeData(nodeId: string) { return this.nodeData.get(nodeId); } + /** + * Sets node-specific data + * @param nodeId The ID of the node + * @param data The data to associate with the node + */ setNodeData( nodeId: NodeId, data: any, @@ -172,17 +419,10 @@ export class BuilderContext { this.nodeData.set(nodeId, data); } - /** - * Builds the virtual directory from a given JSON content. - */ buildVirtualDirectory(jsonContent: string): boolean { return this.virtualDirectory.parseJsonStructure(jsonContent); } - /** - * Finds a node in the sequence by its ID. - * @param nodeId Node ID to find - */ private findNode(nodeId: string): BuildNode | null { for (const step of this.sequence.steps) { const node = step.nodes.find((n) => n.id === nodeId); @@ -191,12 +431,7 @@ export class BuilderContext { return null; } - /** - * Invokes the node's handler and returns its BuildResult. - * @param node The node to execute. - */ 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/executor.ts b/backend/src/build-system/executor.ts deleted file mode 100644 index 2398a893..00000000 --- a/backend/src/build-system/executor.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { Logger } from '@nestjs/common'; -import { BuilderContext } from './context'; -import { BuildNode, BuildSequence, BuildStep } from './types'; -import { v4 as uuidv4 } from 'uuid'; - -export class BuildSequenceExecutor { - private static logger: Logger = new Logger( - `BuildSequenceExecutor-${uuidv4()}`, - ); - - /** - * Execute a single node. - * If the node is completed, do nothing. - * If dependencies aren't ready, wait and retry. - */ - static async executeNode( - node: BuildNode, - context: BuilderContext, - ): Promise { - try { - // Check if node is already completed - if (context.getExecutionState().completed.has(node.id)) { - return; - } - - // If dependencies are not met, wait and retry - if (!context.canExecute(node.id)) { - this.logger.log( - `Waiting for dependencies of node ${node.id}: ${node.requires?.join(', ')}`, - ); - await new Promise((resolve) => setTimeout(resolve, 100)); - return; - } - - this.logger.log(`Executing node ${node.id}`); - // Execute the node using the updated context method - await context.executeNodeById(node.id); - } catch (error) { - this.logger.error(`Error executing node ${node.id}:`, error); - throw error; - } - } - - /** - * Execute a single step. - * If the step is parallel, attempt to run all nodes concurrently (respecting dependencies). - * If not parallel, run nodes in sequence. - */ - static async executeStep( - step: BuildStep, - context: BuilderContext, - ): Promise { - this.logger.log(`Executing build step: ${step.id}`); - - if (step.parallel) { - let remainingNodes = [...step.nodes]; - let lastLength = remainingNodes.length; - let retryCount = 0; - const maxRetries = 10; - - while (remainingNodes.length > 0 && retryCount < maxRetries) { - // Identify nodes that can be executed now - const executableNodes = remainingNodes.filter((node) => - context.canExecute(node.id), - ); - - if (executableNodes.length > 0) { - // Execute all currently executable nodes in parallel - await Promise.all( - executableNodes.map((node) => this.executeNode(node, context)), - ); - - // Filter out completed nodes - remainingNodes = remainingNodes.filter( - (node) => !context.getExecutionState().completed.has(node.id), - ); - - // If progress is made, reset retryCount - if (remainingNodes.length < lastLength) { - retryCount = 0; - lastLength = remainingNodes.length; - } else { - retryCount++; - } - } else { - // No executable nodes currently, wait and retry - await new Promise((resolve) => setTimeout(resolve, 100)); - retryCount++; - } - } - - // If after max retries some nodes are still not completed, throw an error - if (remainingNodes.length > 0) { - throw new Error( - `Unable to complete all nodes in step ${step.id}. Remaining: ${remainingNodes - .map((n) => n.id) - .join(', ')}`, - ); - } - } else { - // Sequential execution - for (const node of step.nodes) { - let retryCount = 0; - const maxRetries = 10; - - while ( - !context.getExecutionState().completed.has(node.id) && - retryCount < maxRetries - ) { - await this.executeNode(node, context); - - if (!context.getExecutionState().completed.has(node.id)) { - await new Promise((resolve) => setTimeout(resolve, 100)); - retryCount++; - } - } - - if (!context.getExecutionState().completed.has(node.id)) { - this.logger.warn( - `Failed to execute node ${node.id} after ${maxRetries} attempts`, - ); - } - } - } - } - - /** - * Execute the entire build sequence. - * Iterates through each step, executing them in order. - * If a step fails to complete all its nodes, logs a warning and returns. - */ - static async executeSequence( - sequence: BuildSequence, - context: BuilderContext, - ): Promise { - this.logger.log(`Starting build sequence: ${sequence.id}`); - - for (const step of sequence.steps) { - await this.executeStep(step, context); - - const incompletedNodes = step.nodes.filter( - (node) => !context.getExecutionState().completed.has(node.id), - ); - - if (incompletedNodes.length > 0) { - this.logger.warn( - `Step ${step.id} failed to complete nodes: ${incompletedNodes - .map((n) => n.id) - .join(', ')}`, - ); - return; - } - } - - this.logger.log(`Build sequence completed: ${sequence.id}`); - this.logger.log('Final execution state:', context.getExecutionState()); - } -} diff --git a/backend/src/build-system/handlers/file-manager/file-generate/index.ts b/backend/src/build-system/handlers/file-manager/file-generate/index.ts index ad0b3a01..29f261c2 100644 --- a/backend/src/build-system/handlers/file-manager/file-generate/index.ts +++ b/backend/src/build-system/handlers/file-manager/file-generate/index.ts @@ -1,13 +1,13 @@ import * as fs from 'fs/promises'; import * as path from 'path'; import { Logger } from '@nestjs/common'; -import * as toposort from 'toposort'; import { VirtualDirectory } from '../../../virtual-dir'; import { BuilderContext } from 'src/build-system/context'; import { BuildHandler, BuildResult } from 'src/build-system/types'; import { extractJsonFromMarkdown } from 'src/build-system/utils/strings'; import { getProjectPath } from 'src/config/common-path'; import normalizePath from 'normalize-path'; +import toposort from 'toposort'; export class FileGeneratorHandler implements BuildHandler { readonly id = 'op:FILE:GENERATE'; diff --git a/backend/src/build-system/handlers/file-manager/file-structure/index.ts b/backend/src/build-system/handlers/file-manager/file-structure/index.ts index 98b4e5e8..c92d87a8 100644 --- a/backend/src/build-system/handlers/file-manager/file-structure/index.ts +++ b/backend/src/build-system/handlers/file-manager/file-structure/index.ts @@ -32,8 +32,10 @@ export class FileStructureHandler implements BuildHandler { const datamapDoc = context.getNodeData('op:UX:DATAMAP:DOC'); // TODO: make sure passing this parameter is correct const projectPart = opts.projectPart ?? 'frontend'; - const framework = context.getGlobalContext('framework'); - + const framework = context.getGlobalContext('framework') ?? 'react'; + this.logger.warn( + "there is no default framework setup, using 'react', plz fix it ASAP", + ); // Validate required arguments if (!sitemapDoc || typeof sitemapDoc !== 'string') { throw new Error( 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/hanlder-manager.ts b/backend/src/build-system/hanlder-manager.ts index 5e1617f8..4b2ef340 100644 --- a/backend/src/build-system/hanlder-manager.ts +++ b/backend/src/build-system/hanlder-manager.ts @@ -11,6 +11,15 @@ import { DBSchemaHandler } from './handlers/database/schemas/schemas'; import { DatabaseRequirementHandler } from './handlers/database/requirements-document'; import { FileGeneratorHandler } from './handlers/file-manager/file-generate'; +/** + * Manages the registration and retrieval of build handlers in the system + * @class BuildHandlerManager + * @description Singleton class responsible for: + * - Maintaining a registry of all build handlers + * - Providing access to specific handlers by ID + * - Managing the lifecycle of built-in handlers + * - Implementing the singleton pattern for global handler management + */ export class BuildHandlerManager { private static instance: BuildHandlerManager; private handlers: Map = new Map(); diff --git a/backend/src/build-system/logger.ts b/backend/src/build-system/logger.ts new file mode 100644 index 00000000..0e871067 --- /dev/null +++ b/backend/src/build-system/logger.ts @@ -0,0 +1,76 @@ +import { Logger } from '@nestjs/common'; +import * as fs from 'fs'; +import * as path from 'path'; +import { PROJECT_EVENT_PATH } from 'src/config/common-path'; + +/** + * Interface representing a project event with associated metadata + * @interface ProjectEvent + * @property {string} timestamp - ISO timestamp of when the event occurred + * @property {string} projectId - Unique identifier for the project + * @property {string} eventId - Unique identifier for the event + * @property {'BUILD_START' | 'BUILD_END' | 'BUILD_ERROR' | 'BUILD_METRICS'} type - Type of build event + * @property {any} data - Additional event-specific data + */ +export interface ProjectEvent { + timestamp: string; + projectId: string; + eventId: string; + type: 'BUILD_START' | 'BUILD_END' | 'BUILD_ERROR' | 'BUILD_METRICS'; + data: any; +} + +/** + * Singleton class responsible for logging project build events to JSON files + * @class ProjectEventLogger + * @description Handles creation, reading and writing of project event logs organized by date + */ +export class ProjectEventLogger { + private static instance: ProjectEventLogger; + private logger: Logger; + private constructor() { + this.logger = new Logger('ProjectEventLogger'); + this.ensureDirectoryExists(); + } + + static getInstance(): ProjectEventLogger { + if (!ProjectEventLogger.instance) { + ProjectEventLogger.instance = new ProjectEventLogger(); + } + return ProjectEventLogger.instance; + } + + private ensureDirectoryExists(): void { + if (!fs.existsSync(PROJECT_EVENT_PATH)) { + fs.mkdirSync(PROJECT_EVENT_PATH, { recursive: true }); + } + } + + private getLogFilePath(): string { + // get current server date as standard log date format + const today = new Date().toISOString().split('T')[0]; + return path.join(PROJECT_EVENT_PATH, `${today}.json`); + } + + private async readExistingEvents(): Promise { + const filePath = this.getLogFilePath(); + if (!fs.existsSync(filePath)) { + return []; + } + const content = await fs.promises.readFile(filePath, 'utf8'); + return content ? JSON.parse(content) : []; + } + + async logEvent(event: ProjectEvent): Promise { + try { + const filePath = this.getLogFilePath(); + const events = await this.readExistingEvents(); + events.push(event); + await fs.promises.writeFile(filePath, JSON.stringify(events, null, 2)); + this.logger.log(`Event logged: ${event.type}`); + } catch (error) { + this.logger.error('Failed to log event:', error); + throw error; + } + } +} diff --git a/backend/src/build-system/monitor.ts b/backend/src/build-system/monitor.ts new file mode 100644 index 00000000..130334d4 --- /dev/null +++ b/backend/src/build-system/monitor.ts @@ -0,0 +1,401 @@ +import { Logger } from '@nestjs/common'; +import { BuildNode, BuildStep, BuildSequence } from './types'; +import { ProjectEventLogger } from './logger'; + +/** + * Metrics for sequence, step, and node execution + */ +export interface BuildReport { + metadata: { + projectId: string; + sequenceId: string; + timestamp: string; + duration: number; + }; + summary: { + totalSteps: number; + completedSteps: number; + failedSteps: number; + totalNodes: number; + completedNodes: number; + failedNodes: number; + successRate: number; + }; + steps: Array<{ + id: string; + name: string; + duration: number; + parallel: boolean; + status: 'completed' | 'failed'; + nodesTotal: number; + nodesCompleted: number; + nodesFailed: number; + nodes: Array<{ + id: string; + name: string; + duration: number; + status: 'completed' | 'failed' | 'pending'; + retryCount: number; + error?: { + message: string; + stack?: string; + }; + }>; + }>; +} + +export interface NodeMetrics { + nodeId: string; + startTime: number; + endTime: number; + duration: number; + status: 'completed' | 'failed' | 'pending'; + memory?: number; + tokensUsed?: number; + retryCount: number; + error?: Error; +} + +export interface StepMetrics { + stepId: string; + startTime: number; + endTime: number; + duration: number; + nodeMetrics: Map; + parallel: boolean; + totalNodes: number; + completedNodes: number; + failedNodes: number; +} + +export interface SequenceMetrics { + sequenceId: string; + startTime: number; + endTime: number; + duration: number; + stepMetrics: Map; + totalSteps: number; + completedSteps: number; + failedSteps: number; + totalNodes: number; + successRate: number; +} + +export class BuildMonitor { + private static instance: BuildMonitor; + private logger: Logger; // TODO: adding more logger + private sequenceMetrics: Map = new Map(); + + private constructor() { + this.logger = new Logger('BuildMonitor'); + } + + static getInstance(): BuildMonitor { + if (!BuildMonitor.instance) { + BuildMonitor.instance = new BuildMonitor(); + } + return BuildMonitor.instance; + } + + // Node-level monitoring + startNodeExecution(nodeId: string, sequenceId: string, stepId: string): void { + const metrics = this.getOrCreateNodeMetrics(nodeId, sequenceId, stepId); + metrics.startTime = Date.now(); + metrics.status = 'pending'; + } + + endNodeExecution( + nodeId: string, + sequenceId: string, + stepId: string, + success: boolean, + error?: Error, + ): void { + const metrics = this.getOrCreateNodeMetrics(nodeId, sequenceId, stepId); + metrics.endTime = Date.now(); + metrics.duration = metrics.endTime - metrics.startTime; + metrics.status = success ? 'completed' : 'failed'; + if (error) { + metrics.error = error; + } + } + + incrementNodeRetry(nodeId: string, sequenceId: string, stepId: string): void { + const metrics = this.getOrCreateNodeMetrics(nodeId, sequenceId, stepId); + metrics.retryCount++; + } + + // Step-level monitoring + startStepExecution( + stepId: string, + sequenceId: string, + parallel: boolean, + totalNodes: number, + ): void { + const metrics = this.getOrCreateStepMetrics(stepId, sequenceId); + metrics.startTime = Date.now(); + metrics.parallel = parallel; + metrics.totalNodes = totalNodes; + } + + endStepExecution(stepId: string, sequenceId: string): void { + const metrics = this.getStepMetrics(sequenceId, stepId); + if (metrics) { + metrics.endTime = Date.now(); + metrics.duration = metrics.endTime - metrics.startTime; + + // Calculate completion statistics + metrics.completedNodes = Array.from(metrics.nodeMetrics.values()).filter( + (n) => n.status === 'completed', + ).length; + metrics.failedNodes = Array.from(metrics.nodeMetrics.values()).filter( + (n) => n.status === 'failed', + ).length; + } + } + + // Sequence-level monitoring + startSequenceExecution(sequence: BuildSequence): void { + const metrics: SequenceMetrics = { + sequenceId: sequence.id, + startTime: Date.now(), + endTime: 0, + duration: 0, + stepMetrics: new Map(), + totalSteps: sequence.steps.length, + completedSteps: 0, + failedSteps: 0, + totalNodes: sequence.steps.reduce( + (sum, step) => sum + step.nodes.length, + 0, + ), + successRate: 0, + }; + this.sequenceMetrics.set(sequence.id, metrics); + } + + async endSequenceExecution( + sequenceId: string, + projectUUID: string, + ): Promise { + const metrics = this.sequenceMetrics.get(sequenceId); + if (metrics) { + metrics.endTime = Date.now(); + metrics.duration = metrics.endTime - metrics.startTime; + + // Calculate final statistics + let completedNodes = 0; + let totalNodes = 0; + + metrics.stepMetrics.forEach((stepMetric) => { + completedNodes += stepMetric.completedNodes; + totalNodes += stepMetric.totalNodes; + }); + + metrics.successRate = (completedNodes / totalNodes) * 100; + + const report = await this.generateStructuredReport( + sequenceId, + projectUUID, + ); + // log the event + await ProjectEventLogger.getInstance().logEvent({ + timestamp: report.metadata.timestamp, + projectId: report.metadata.projectId, + eventId: `build-${report.metadata.sequenceId}`, + type: 'BUILD_METRICS', + data: report, + }); + } + } + + // Utility methods + private getOrCreateNodeMetrics( + nodeId: string, + sequenceId: string, + stepId: string, + ): NodeMetrics { + const stepMetrics = this.getOrCreateStepMetrics(stepId, sequenceId); + if (!stepMetrics.nodeMetrics.has(nodeId)) { + stepMetrics.nodeMetrics.set(nodeId, { + nodeId, + startTime: 0, + endTime: 0, + duration: 0, + status: 'pending', + retryCount: 0, + }); + } + return stepMetrics.nodeMetrics.get(nodeId)!; + } + + private getOrCreateStepMetrics( + stepId: string, + sequenceId: string, + ): StepMetrics { + const sequenceMetrics = this.sequenceMetrics.get(sequenceId); + if (!sequenceMetrics) { + throw new Error(`No metrics found for sequence ${sequenceId}`); + } + + if (!sequenceMetrics.stepMetrics.has(stepId)) { + sequenceMetrics.stepMetrics.set(stepId, { + stepId, + startTime: 0, + endTime: 0, + duration: 0, + nodeMetrics: new Map(), + parallel: false, + totalNodes: 0, + completedNodes: 0, + failedNodes: 0, + }); + } + return sequenceMetrics.stepMetrics.get(stepId)!; + } + + private getStepMetrics( + sequenceId: string, + stepId: string, + ): StepMetrics | undefined { + return this.sequenceMetrics.get(sequenceId)?.stepMetrics.get(stepId); + } + + // Reporting methods + getSequenceMetrics(sequenceId: string): SequenceMetrics | undefined { + return this.sequenceMetrics.get(sequenceId); + } + + /** + * Return a structured report for a sequence + * @param sequenceId sequenceId + * @param projectUUID unique identifier for the project + * @returns BuildReport + */ + async generateStructuredReport( + sequenceId: string, + projectUUID: string, + ): Promise { + const metrics = this.getSequenceMetrics(sequenceId); + if (!metrics) { + throw new Error(`No metrics found for sequence ${sequenceId}`); + } + + let totalCompletedNodes = 0; + let totalFailedNodes = 0; + + const steps = Array.from(metrics.stepMetrics.entries()).map( + ([stepId, stepMetric]) => { + const nodes = Array.from(stepMetric.nodeMetrics.entries()).map( + ([nodeId, nodeMetric]) => ({ + id: nodeId, + name: nodeId, + duration: nodeMetric.duration, + status: nodeMetric.status, + retryCount: nodeMetric.retryCount, + error: nodeMetric.error + ? { + message: nodeMetric.error.message, + stack: nodeMetric.error.stack, + } + : undefined, + }), + ); + + const completed = nodes.filter((n) => n.status === 'completed').length; + const failed = nodes.filter((n) => n.status === 'failed').length; + + totalCompletedNodes += completed; + totalFailedNodes += failed; + + return { + id: stepId, + name: stepId, + duration: stepMetric.duration, + parallel: stepMetric.parallel, + status: (failed > 0 ? 'failed' : 'completed') as + | 'completed' + | 'failed', + nodesTotal: stepMetric.totalNodes, + nodesCompleted: completed, + nodesFailed: failed, + nodes, + }; + }, + ); + + const report: BuildReport = { + metadata: { + projectId: projectUUID, + sequenceId: metrics.sequenceId, + timestamp: new Date().toISOString(), + duration: metrics.duration, + }, + summary: { + totalSteps: metrics.totalSteps, + completedSteps: steps.filter((s) => s.status === 'completed').length, + failedSteps: steps.filter((s) => s.status === 'failed').length, + totalNodes: metrics.totalNodes, + completedNodes: totalCompletedNodes, + failedNodes: totalFailedNodes, + successRate: (totalCompletedNodes / metrics.totalNodes) * 100, + }, + steps, + }; + + return report; + } + + /** + * Get Report for a sequence as string, using for test + * @param sequenceId sequenceId + * @returns string report + */ + public generateTextReport(sequenceId: string): string { + const metrics = this.getSequenceMetrics(sequenceId); + if (!metrics) { + return `No metrics found for sequence ${sequenceId}`; + } + + let report = `Build Sequence Report: ${sequenceId}\n`; + report += `====================================\n`; + report += `Total Duration: ${metrics.duration}ms\n`; + report += `Success Rate: ${metrics.successRate.toFixed(2)}%\n`; + report += `Total Steps: ${metrics.totalSteps}\n`; + report += `Total Nodes: ${metrics.totalNodes}\n\n`; + + metrics.stepMetrics.forEach((stepMetric, stepId) => { + report += `Step: ${stepId}\n`; + report += ` Duration: ${stepMetric.duration}ms\n`; + report += ` Parallel: ${stepMetric.parallel}\n`; + report += ` Completed Nodes: ${stepMetric.completedNodes}/${stepMetric.totalNodes}\n`; + report += ` Failed Nodes: ${stepMetric.failedNodes}\n\n`; + + stepMetric.nodeMetrics.forEach((nodeMetric, nodeId) => { + report += ` Node: ${nodeId}\n`; + report += ` Status: ${nodeMetric.status}\n`; + report += ` Duration: ${nodeMetric.duration}ms\n`; + report += ` Retries: ${nodeMetric.retryCount}\n`; + if (nodeMetric.error) { + report += ` Error: ${nodeMetric.error.message}\n`; + } + report += '\n'; + }); + }); + + return report; + } + + private currentStep: BuildStep | null = null; + + setCurrentStep(step: BuildStep): void { + this.currentStep = step; + } + + getCurrentStep(): BuildStep { + if (!this.currentStep) { + throw new Error('No current step set'); + } + return this.currentStep; + } +} 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/backend/src/config/common-path.ts b/backend/src/config/common-path.ts index 6de9809b..6f40ab2a 100644 --- a/backend/src/config/common-path.ts +++ b/backend/src/config/common-path.ts @@ -8,6 +8,8 @@ const WORKSPACE_ROOT = path.resolve(__dirname, '../../../'); const ROOT_DIR = path.join(WORKSPACE_ROOT, `.${APP_NAME}`); export const TEMPLATE_PATH = path.join(WORKSPACE_ROOT, 'backend/template'); +export const PROJECT_EVENT_PATH = path.join(ROOT_DIR, 'project-events'); + export const getTemplatePath = (templateName: string): string => path.join(TEMPLATE_PATH, templateName); // Utility function to ensure a directory exists 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 564f002f..69ec2407 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -123,6 +123,9 @@ importers: subscriptions-transport-ws: specifier: ^0.11.0 version: 0.11.0(graphql@16.10.0) + toposort: + specifier: ^2.0.2 + version: 2.0.2 typeorm: specifier: ^0.3.20 version: 0.3.20(sqlite3@5.1.7)(ts-node@10.9.2) @@ -208,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) @@ -254,10 +257,10 @@ importers: version: 3.9.1(react-hook-form@7.54.1) '@langchain/community': specifier: ^0.3.1 - version: 0.3.19(@browserbasehq/stagehand@1.6.0)(@ibm-cloud/watsonx-ai@1.3.0)(@langchain/core@0.3.24)(axios@1.7.4)(ibm-cloud-sdk-core@5.1.0)(openai@4.76.3)(ws@8.18.0) + version: 0.3.19(@browserbasehq/stagehand@1.7.0)(@ibm-cloud/watsonx-ai@1.3.0)(@langchain/core@0.3.24)(axios@1.7.4)(ibm-cloud-sdk-core@5.1.0)(openai@4.77.0)(ws@8.18.0) '@langchain/core': specifier: ^0.3.3 - version: 0.3.24(openai@4.76.3) + version: 0.3.24(openai@4.77.0) '@nestjs/common': specifier: ^10.4.6 version: 10.4.15(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -365,7 +368,7 @@ importers: version: 2.5.5 tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.16) + version: 1.0.7(tailwindcss@3.4.17) uuid: specifier: ^10.0.0 version: 10.0.0 @@ -441,7 +444,7 @@ importers: version: 8.4.49 tailwindcss: specifier: ^3.4.12 - version: 3.4.16(ts-node@10.9.2) + version: 3.4.17(ts-node@10.9.2) ts-jest: specifier: ^29.2.5 version: 29.2.5(@babel/core@7.26.0)(jest@29.7.0)(typescript@5.6.3) @@ -469,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 @@ -493,7 +499,7 @@ importers: version: 5.2.1(eslint-config-prettier@9.1.0)(eslint@8.57.1)(prettier@3.4.2) openai: specifier: ^4.68.1 - version: 4.76.3(zod@3.24.1) + version: 4.77.0(zod@3.24.1) prettier: specifier: ^3.0.0 version: 3.4.2 @@ -541,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: @@ -601,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: @@ -628,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: @@ -645,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: @@ -668,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: @@ -696,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: @@ -730,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: @@ -756,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: @@ -772,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: @@ -796,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: @@ -2505,8 +2511,8 @@ packages: - encoding dev: false - /@browserbasehq/stagehand@1.6.0(@playwright/test@1.49.1)(deepmerge@4.3.1)(dotenv@16.4.7)(openai@4.76.3)(zod@3.24.1): - resolution: {integrity: sha512-seDmCtokkCf+5xaJ2IOjxcNAGm5PrOR8m+hgtDuMxfByE9o1pQdFYtNkjDFfI9oBbIB2EF3RQJTxkQHT9s6QRA==} + /@browserbasehq/stagehand@1.7.0(@playwright/test@1.49.1)(deepmerge@4.3.1)(dotenv@16.4.7)(openai@4.77.0)(zod@3.24.1): + resolution: {integrity: sha512-QsobXC+E8yid7ZKpvTleXPCCotwxJRb7uhvtP3H6V9RGe31t0Il2FZyA2hsLdvvgkXDYa47F2rAuWMOpc6Ea0g==} peerDependencies: '@playwright/test': ^1.42.1 deepmerge: ^4.3.1 @@ -2519,7 +2525,7 @@ packages: '@playwright/test': 1.49.1 deepmerge: 4.3.1 dotenv: 16.4.7 - openai: 4.76.3(zod@3.24.1) + openai: 4.77.0(zod@3.24.1) sharp: 0.33.5 zod: 3.24.1 zod-to-json-schema: 3.24.1(zod@3.24.1) @@ -3009,12 +3015,12 @@ packages: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} - /@docsearch/css@3.8.1: - resolution: {integrity: sha512-XiPhKT+ghUi4pEi/ACE9iDmwWsLA6d6xSwtR5ab48iB63OtYWFLZHUKdH7jHKTmwOs0Eg22TX4Kb3H5liFm5bQ==} + /@docsearch/css@3.8.2: + resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} dev: false - /@docsearch/react@3.8.1(@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): - resolution: {integrity: sha512-7vgQuktQNBQdNWO1jbkiwgIrTZ0r5nPIHqcO3Z2neAWgkdUuldvvMfEOEaPXT5lqcezEv7i0h+tC285nD3jpZg==} + /@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' react: '>= 16.8.0 < 19.0.0' @@ -3030,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) - '@docsearch/css': 3.8.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 @@ -3607,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: @@ -3625,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) @@ -3752,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.1(@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) @@ -3894,7 +3900,7 @@ packages: github-slugger: 1.5.0 globby: 11.1.0 gray-matter: 4.0.3 - jiti: 1.21.6 + jiti: 1.21.7 js-yaml: 4.1.0 lodash: 4.17.21 micromatch: 4.0.8 @@ -4095,7 +4101,7 @@ packages: graphql-config: 5.1.3(@types/node@22.10.2)(graphql@16.10.0)(typescript@5.6.3) inquirer: 8.2.6 is-glob: 4.0.3 - jiti: 1.21.6 + jiti: 1.21.7 json-to-pretty-yaml: 1.2.2 listr2: 4.0.5 log-symbols: 4.1.0 @@ -5398,7 +5404,7 @@ packages: resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} dev: false - /@langchain/community@0.3.19(@browserbasehq/stagehand@1.6.0)(@ibm-cloud/watsonx-ai@1.3.0)(@langchain/core@0.3.24)(axios@1.7.4)(ibm-cloud-sdk-core@5.1.0)(openai@4.76.3)(ws@8.18.0): + /@langchain/community@0.3.19(@browserbasehq/stagehand@1.7.0)(@ibm-cloud/watsonx-ai@1.3.0)(@langchain/core@0.3.24)(axios@1.7.4)(ibm-cloud-sdk-core@5.1.0)(openai@4.77.0)(ws@8.18.0): resolution: {integrity: sha512-7ygPPC9eaIq6Bkv7Z3Vz8PxKjCEi0FCyOuK3e5fafm7ahqX0JXDxnBADObDJ7euFWTKXYFiXDYdoJI/BGlm1PQ==} engines: {node: '>=18'} peerDependencies: @@ -5769,18 +5775,18 @@ packages: youtubei.js: optional: true dependencies: - '@browserbasehq/stagehand': 1.6.0(@playwright/test@1.49.1)(deepmerge@4.3.1)(dotenv@16.4.7)(openai@4.76.3)(zod@3.24.1) + '@browserbasehq/stagehand': 1.7.0(@playwright/test@1.49.1)(deepmerge@4.3.1)(dotenv@16.4.7)(openai@4.77.0)(zod@3.24.1) '@ibm-cloud/watsonx-ai': 1.3.0 - '@langchain/core': 0.3.24(openai@4.76.3) + '@langchain/core': 0.3.24(openai@4.77.0) '@langchain/openai': 0.3.14(@langchain/core@0.3.24) binary-extensions: 2.3.0 expr-eval: 2.0.2 flat: 5.0.2 ibm-cloud-sdk-core: 5.1.0 js-yaml: 4.1.0 - langchain: 0.3.7(@langchain/core@0.3.24)(axios@1.7.4)(openai@4.76.3) - langsmith: 0.2.13(openai@4.76.3) - openai: 4.76.3(zod@3.24.1) + langchain: 0.3.7(@langchain/core@0.3.24)(axios@1.7.4)(openai@4.77.0) + langsmith: 0.2.13(openai@4.77.0) + openai: 4.77.0(zod@3.24.1) uuid: 10.0.0 ws: 8.18.0 zod: 3.24.1 @@ -5800,7 +5806,7 @@ packages: - peggy dev: false - /@langchain/core@0.3.24(openai@4.76.3): + /@langchain/core@0.3.24(openai@4.77.0): resolution: {integrity: sha512-xd7+VSJCwFNwt57poYjl18SbAb51mLWvq7OvQhkUQXv20LdnrO8Y5e2NhVKpNcYE306fFfAu+ty9ncPyKCpMZA==} engines: {node: '>=18'} dependencies: @@ -5809,7 +5815,7 @@ packages: camelcase: 6.3.0 decamelize: 1.2.0 js-tiktoken: 1.0.15 - langsmith: 0.2.13(openai@4.76.3) + langsmith: 0.2.13(openai@4.77.0) mustache: 4.2.0 p-queue: 6.6.2 p-retry: 4.6.2 @@ -5826,9 +5832,9 @@ packages: peerDependencies: '@langchain/core': '>=0.2.26 <0.4.0' dependencies: - '@langchain/core': 0.3.24(openai@4.76.3) + '@langchain/core': 0.3.24(openai@4.77.0) js-tiktoken: 1.0.15 - openai: 4.76.3(zod@3.24.1) + openai: 4.77.0(zod@3.24.1) zod: 3.24.1 zod-to-json-schema: 3.24.1(zod@3.24.1) transitivePeerDependencies: @@ -5841,7 +5847,7 @@ packages: peerDependencies: '@langchain/core': '>=0.2.21 <0.4.0' dependencies: - '@langchain/core': 0.3.24(openai@4.76.3) + '@langchain/core': 0.3.24(openai@4.77.0) js-tiktoken: 1.0.15 dev: false @@ -7050,7 +7056,7 @@ packages: aria-hidden: 1.2.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.6.1(@types/react@18.3.17)(react@18.3.1) + react-remove-scroll: 2.6.2(@types/react@18.3.17)(react@18.3.1) dev: false /@radix-ui/react-direction@1.1.0(@types/react@18.3.17)(react@18.3.1): @@ -7227,7 +7233,7 @@ packages: aria-hidden: 1.2.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.6.1(@types/react@18.3.17)(react@18.3.1) + react-remove-scroll: 2.6.2(@types/react@18.3.17)(react@18.3.1) dev: false /@radix-ui/react-popover@1.1.4(@types/react-dom@18.3.5)(@types/react@18.3.17)(react-dom@18.3.1)(react@18.3.1): @@ -7261,7 +7267,7 @@ packages: aria-hidden: 1.2.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.6.1(@types/react@18.3.17)(react@18.3.1) + react-remove-scroll: 2.6.2(@types/react@18.3.17)(react@18.3.1) dev: false /@radix-ui/react-popper@1.2.1(@types/react-dom@18.3.5)(@types/react@18.3.17)(react-dom@18.3.1)(react@18.3.1): @@ -7448,7 +7454,7 @@ packages: aria-hidden: 1.2.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.6.1(@types/react@18.3.17)(react@18.3.1) + react-remove-scroll: 2.6.2(@types/react@18.3.17)(react@18.3.1) dev: false /@radix-ui/react-separator@1.1.1(@types/react-dom@18.3.5)(@types/react@18.3.17)(react-dom@18.3.1)(react@18.3.1): @@ -9081,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: @@ -9452,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: @@ -9697,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 @@ -9919,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: @@ -10086,7 +10092,7 @@ packages: resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==} dependencies: '@types/validator': 13.12.2 - libphonenumber-js: 1.11.16 + libphonenumber-js: 1.11.17 validator: 13.12.0 /class-variance-authority@0.7.1: @@ -11483,7 +11489,7 @@ packages: es-to-primitive: 1.3.0 function.prototype.name: 1.1.7 get-intrinsic: 1.2.6 - get-symbol-description: 1.0.2 + get-symbol-description: 1.1.0 globalthis: 1.0.4 gopd: 1.2.0 has-property-descriptors: 1.0.2 @@ -11496,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.13 + 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-byte-length: 1.0.1 + 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: @@ -12726,11 +12732,11 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - /get-symbol-description@1.0.2: - resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} + /get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bound: 1.0.3 es-errors: 1.3.0 get-intrinsic: 1.2.6 dev: true @@ -12915,7 +12921,7 @@ packages: '@graphql-tools/utils': 10.6.4(graphql@16.10.0) cosmiconfig: 8.3.6(typescript@5.6.3) graphql: 16.10.0 - jiti: 2.4.1 + jiti: 2.4.2 minimatch: 9.0.5 string-env-interpolation: 1.0.1 tslib: 2.8.1 @@ -12994,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: @@ -13618,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 @@ -13682,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 @@ -13758,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: @@ -13805,7 +13812,7 @@ packages: dependencies: call-bound: 1.0.3 get-intrinsic: 1.2.6 - is-typed-array: 1.1.13 + is-typed-array: 1.1.15 dev: true /is-date-object@1.1.0: @@ -14008,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: @@ -14036,11 +14043,11 @@ packages: safe-regex-test: 1.1.0 dev: true - /is-typed-array@1.1.13: - resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + /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: @@ -14222,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 @@ -14640,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 @@ -14783,12 +14790,12 @@ packages: - ts-node dev: true - /jiti@1.21.6: - resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} + /jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true - /jiti@2.4.1: - resolution: {integrity: sha512-yPBThwecp1wS9DmoA4x4KR2h3QoslacnDR8ypuFM962kI4/456Iy1oHx2RAgh4jfZNdn0bctsdadceiBUgpU1g==} + /jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true dev: true @@ -14962,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 @@ -14994,7 +15001,7 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - /langchain@0.3.7(@langchain/core@0.3.24)(axios@1.7.4)(openai@4.76.3): + /langchain@0.3.7(@langchain/core@0.3.24)(axios@1.7.4)(openai@4.77.0): resolution: {integrity: sha512-6/Gkk9Zez3HkbsETFxZVo1iKLmaK3OzkDseC5MYFKVmYFDXFAOyJR3srJ9P61xF8heVdsPixqYIsejBn7/9dXg==} engines: {node: '>=18'} peerDependencies: @@ -15040,14 +15047,14 @@ packages: typeorm: optional: true dependencies: - '@langchain/core': 0.3.24(openai@4.76.3) + '@langchain/core': 0.3.24(openai@4.77.0) '@langchain/openai': 0.3.14(@langchain/core@0.3.24) '@langchain/textsplitters': 0.1.0(@langchain/core@0.3.24) axios: 1.7.4(debug@4.4.0) js-tiktoken: 1.0.15 js-yaml: 4.1.0 jsonpointer: 5.0.1 - langsmith: 0.2.13(openai@4.76.3) + langsmith: 0.2.13(openai@4.77.0) openapi-types: 12.1.3 p-retry: 4.6.2 uuid: 10.0.0 @@ -15059,7 +15066,7 @@ packages: - openai dev: false - /langsmith@0.2.13(openai@4.76.3): + /langsmith@0.2.13(openai@4.77.0): resolution: {integrity: sha512-16EOM5nhU6GlMCKGm5sgBIAKOKzS2d30qcDZmF21kSLZJiUhUNTROwvYdqgZLrGfIIzmSMJHCKA7RFd5qf50uw==} peerDependencies: openai: '*' @@ -15069,7 +15076,7 @@ packages: dependencies: '@types/uuid': 10.0.0 commander: 10.0.1 - openai: 4.76.3(zod@3.24.1) + openai: 4.77.0(zod@3.24.1) p-queue: 6.6.2 p-retry: 4.6.2 semver: 7.6.3 @@ -15112,8 +15119,8 @@ packages: prelude-ls: 1.2.1 type-check: 0.4.0 - /libphonenumber-js@1.11.16: - resolution: {integrity: sha512-Noyazmt0yOvnG0OeRY45Cd1ur8G7Z0HWVkuCuKe+yysGNxPQwBAODBQQ40j0AIagi9ZWurfmmZWNlpg4h4W+XQ==} + /libphonenumber-js@1.11.17: + resolution: {integrity: sha512-Jr6v8thd5qRlOlc6CslSTzGzzQW03uiscab7KHQZX1Dfo4R6n6FDhZ0Hri6/X7edLIDv9gl4VMZXhxTjLnl0VQ==} /lifecycle-utils@1.7.0: resolution: {integrity: sha512-suNHxB8zsWrvsWxsmy9PsOcHuThRsCzvUhtGwxfvYAl8mbeWv7lt+wNT3q9KgILWmNe9zEVZ6PXo1gsvpYIdvw==} @@ -15262,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 @@ -16553,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 @@ -16717,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 @@ -16849,8 +16858,8 @@ packages: is-wsl: 2.2.0 dev: false - /openai@4.76.3(zod@3.24.1): - resolution: {integrity: sha512-BISkI90m8zT7BAMljK0j00TzOoLvmc7AulPxv6EARa++3+hhIK5G6z4xkITurEaA9bvDhQ09kSNKA3DL+rDMwA==} + /openai@4.77.0(zod@3.24.1): + resolution: {integrity: sha512-WWacavtns/7pCUkOWvQIjyOfcdr9X+9n9Vvb0zFeKVDAqwCMDHB+iSr24SVaBAhplvSG6JrRXFpcNM9gWhOGIw==} hasBin: true peerDependencies: zod: ^3.23.8 @@ -16917,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 @@ -17006,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'} @@ -17021,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'} @@ -17596,7 +17618,7 @@ packages: webpack: ^5.0.0 dependencies: cosmiconfig: 8.3.6(typescript@5.6.3) - jiti: 1.21.6 + jiti: 1.21.7 postcss: 8.4.49 semver: 7.6.3 webpack: 5.97.1(webpack-cli@5.1.4) @@ -18590,8 +18612,8 @@ packages: tslib: 2.8.1 dev: false - /react-remove-scroll@2.6.1(@types/react@18.3.17)(react@18.3.1): - resolution: {integrity: sha512-jWEvWQidZ/C/FnFlUIB1mDLpY3r7uEb22WZ3uVeKj520caKDiaBsNDEB9J1gHJgpiLo+eTdPl2MVi0JitFTiFg==} + /react-remove-scroll@2.6.2(@types/react@18.3.17)(react@18.3.1): + resolution: {integrity: sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==} engines: {node: '>=10'} peerDependencies: '@types/react': '*' @@ -18605,7 +18627,7 @@ packages: react-remove-scroll-bar: 2.3.8(@types/react@18.3.17)(react@18.3.1) react-style-singleton: 2.2.3(@types/react@18.3.17)(react@18.3.1) tslib: 2.8.1 - use-callback-ref: 1.3.2(@types/react@18.3.17)(react@18.3.1) + use-callback-ref: 1.3.3(@types/react@18.3.17)(react@18.3.1) use-sidecar: 1.1.3(@types/react@18.3.17)(react@18.3.1) dev: false @@ -18823,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 @@ -20263,16 +20285,16 @@ packages: resolution: {integrity: sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==} dev: false - /tailwindcss-animate@1.0.7(tailwindcss@3.4.16): + /tailwindcss-animate@1.0.7(tailwindcss@3.4.17): resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} peerDependencies: tailwindcss: '>=3.0.0 || insiders' dependencies: - tailwindcss: 3.4.16(ts-node@10.9.2) + tailwindcss: 3.4.17(ts-node@10.9.2) dev: false - /tailwindcss@3.4.16(ts-node@10.9.2): - resolution: {integrity: sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==} + /tailwindcss@3.4.17(ts-node@10.9.2): + resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} engines: {node: '>=14.0.0'} hasBin: true dependencies: @@ -20284,7 +20306,7 @@ packages: fast-glob: 3.3.2 glob-parent: 6.0.2 is-glob: 4.0.3 - jiti: 1.21.6 + jiti: 1.21.7 lilconfig: 3.1.3 micromatch: 4.0.8 normalize-path: 3.0.0 @@ -20466,6 +20488,10 @@ packages: ieee754: 1.2.1 dev: false + /toposort@2.0.2: + resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} + dev: false + /totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -20851,24 +20877,24 @@ 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.13 + is-typed-array: 1.1.15 dev: true - /typed-array-byte-length@1.0.1: - resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} + /typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 for-each: 0.3.3 gopd: 1.2.0 has-proto: 1.2.0 - is-typed-array: 1.1.13 + is-typed-array: 1.1.15 dev: true /typed-array-byte-offset@1.0.3: @@ -20880,8 +20906,8 @@ packages: for-each: 0.3.3 gopd: 1.2.0 has-proto: 1.2.0 - is-typed-array: 1.1.13 - reflect.getprototypeof: 1.0.8 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.9 dev: true /typed-array-length@1.0.7: @@ -20891,9 +20917,9 @@ packages: call-bind: 1.0.8 for-each: 0.3.3 gopd: 1.2.0 - is-typed-array: 1.1.13 + 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: @@ -21016,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 @@ -21176,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 @@ -21239,12 +21265,12 @@ packages: resolution: {integrity: sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==} dev: true - /use-callback-ref@1.3.2(@types/react@18.3.17)(react@18.3.1): - resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} + /use-callback-ref@1.3.3(@types/react@18.3.17)(react@18.3.1): + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true @@ -21717,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: @@ -21734,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