diff --git a/src/lib/api-client.ts b/src/lib/api-client.ts index 75b03412..1d226155 100644 --- a/src/lib/api-client.ts +++ b/src/lib/api-client.ts @@ -187,6 +187,19 @@ class ApiClient { return 'to continue'; } + /** + * Check if endpoint should trigger auth modal on 401 + * Auth checking endpoints should not auto-trigger modals + */ + private shouldTriggerAuthModal(endpoint: string): boolean { + // Don't trigger modal for auth state checking endpoints + if (endpoint === '/api/auth/profile') return false; + if (endpoint === '/api/auth/providers') return false; + if (endpoint === '/api/auth/sessions') return false; + + return true; + } + /** * Make HTTP request with proper error handling and type safety */ @@ -219,8 +232,8 @@ class ApiClient { const data = await response.json(); if (!response.ok) { - // Intercept 401 responses and trigger auth modal - if (response.status === 401 && globalAuthModalTrigger) { + // Intercept 401 responses and trigger auth modal (but not for auth checking endpoints) + if (response.status === 401 && globalAuthModalTrigger && this.shouldTriggerAuthModal(endpoint)) { // Determine context based on endpoint const authContext = this.getAuthContextForEndpoint(endpoint); globalAuthModalTrigger(authContext); diff --git a/vite.config.ts b/vite.config.ts index 77eb58cc..f447a0ad 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -10,7 +10,8 @@ import tailwindcss from '@tailwindcss/vite'; export default defineConfig({ optimizeDeps: { exclude: ['format', 'editor.all'], - include: ['monaco-editor/esm/vs/editor/editor.api'] + include: ['monaco-editor/esm/vs/editor/editor.api'], + force: true // Force re-optimization on every start }, // build: { // rollupOptions: { @@ -64,4 +65,6 @@ export default defineConfig({ server: { allowedHosts: true }, + // Clear cache more aggressively + cacheDir: 'node_modules/.vite' }); diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index 5df6e8d7..aa5eb463 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -1,11 +1,11 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: 1c1d7051b80a70b551f16bfc274a9872) +// Generated by Wrangler by running `wrangler types` (hash: a12a0aff0cea8699250810648e5a7084) // Runtime types generated with workerd@1.20250816.0 2025-08-10 nodejs_compat declare namespace Cloudflare { interface Env { INSTANCE_REGISTRY: KVNamespace; TEMPLATES_REPOSITORY: "https://github.com/AshishKumar4/cloudflare-build-templates"; - CUSTOM_DOMAIN: "build.cloudflare.dev"; + CLOUDFLARE_AI_GATEWAY: "orange-build-gateway"; ANTHROPIC_API_KEY: string; OPENAI_API_KEY: string; GOOGLE_AI_STUDIO_API_KEY: string; @@ -19,7 +19,6 @@ declare namespace Cloudflare { CLOUDFLARE_API_TOKEN: string; CLOUDFLARE_ACCOUNT_ID: string; CLOUDFLARE_AI_GATEWAY_URL: string; - CLOUDFLARE_AI_GATEWAY: string; CLOUDFLARE_AI_GATEWAY_TOKEN: string; SERPAPI_KEY: string; GOOGLE_CLIENT_SECRET: string; @@ -33,6 +32,7 @@ declare namespace Cloudflare { ENVIRONMENT: string; MAX_SANDBOX_INSTANCES: string; SANDBOX_INSTANCE_TYPE: string; + CUSTOM_DOMAIN: string; CodeGenObject: DurableObjectNamespace; Sandbox: DurableObjectNamespace; DeployerServiceObject: DurableObjectNamespace; @@ -50,7 +50,7 @@ type StringifyValues> = { [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; }; declare namespace NodeJS { - interface ProcessEnv extends StringifyValues> {} + interface ProcessEnv extends StringifyValues> {} } // Begin runtime types diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index 5c7b460f..8e2c2e8c 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -13,10 +13,9 @@ import { CodeGenState, CurrentDevState, MAX_PHASES, FileState } from './state'; import { AllIssues, AgentSummary, ScreenshotData, AgentInitArgs } from './types'; import { WebSocketMessageResponses } from '../constants'; import { broadcastToConnections, handleWebSocketClose, handleWebSocketMessage } from './websocket'; -import { createObjectLogger } from '../../logger'; +import { createObjectLogger, StructuredLogger } from '../../logger'; import { ProjectSetupAssistant } from '../assistants/projectsetup'; import { UserConversationProcessor } from '../operations/UserConversationProcessor'; -import { executeAction } from './actions'; import { FileManager } from '../services/implementations/FileManager'; import { StateManager } from '../services/implementations/StateManager'; // import { WebSocketBroadcaster } from '../services/implementations/WebSocketBroadcaster'; @@ -115,7 +114,22 @@ export class SimpleCodeGeneratorAgent extends Agent { // Deployment queue management to prevent concurrent deployments private currentDeploymentPromise: Promise | null = null; - public logger = createObjectLogger(this, 'CodeGeneratorAgent'); + public _logger: StructuredLogger | undefined; + + logger(): StructuredLogger { + if (!this._logger) { + this._logger = createObjectLogger(this, 'CodeGeneratorAgent'); + this._logger.setObjectId(this.state.sessionId); + this._logger.setFields({ + sessionId: this.state.sessionId, + agentId: this.state.inferenceContext.agentId, + userId: this.state.inferenceContext.userId, + }); + } + return this._logger; + } + + // logger: StructuredLogger = createObjectLogger(this, 'CodeGeneratorAgent'); initialState: CodeGenState = { blueprint: {} as Blueprint, @@ -150,8 +164,6 @@ export class SimpleCodeGeneratorAgent extends Agent { initArgs: AgentInitArgs, ..._args: unknown[] ): Promise { - this.logger = createObjectLogger(this, 'CodeGeneratorAgent'); - this.logger.setObjectId(initArgs.inferenceContext.agentId); const { query, language, frameworks, hostname, inferenceContext } = initArgs; // Fetch available templates @@ -170,23 +182,23 @@ export class SimpleCodeGeneratorAgent extends Agent { getSandboxService(inferenceContext.agentId, hostname) ]); - this.logger.info('Selected template', { selectedTemplate: analyzeQueryResponse }); + this.logger().info('Selected template', { selectedTemplate: analyzeQueryResponse }); // Find the selected template by name in the available templates if (!analyzeQueryResponse.selectedTemplateName) { - this.logger.error('No suitable template found for code generation'); + this.logger().error('No suitable template found for code generation'); throw new Error('No suitable template found for code generation'); } const selectedTemplate = templatesResponse.templates.find(template => template.name === analyzeQueryResponse.selectedTemplateName); if (!selectedTemplate) { - this.logger.error('Selected template not found'); + this.logger().error('Selected template not found'); throw new Error('Selected template not found'); } // Now fetch all the files from the instance const templateDetailsResponse = await sandboxClient.getTemplateDetails(selectedTemplate.name); if (!templateDetailsResponse.success || !templateDetailsResponse.templateDetails) { - this.logger.error('Failed to fetch files', { templateDetailsResponse }); + this.logger().error('Failed to fetch files', { templateDetailsResponse }); throw new Error('Failed to fetch files'); } @@ -194,8 +206,8 @@ export class SimpleCodeGeneratorAgent extends Agent { initArgs.onTemplateGenerated(templateDetails); // Generate a blueprint - this.logger.info('Generating blueprint', { query, queryLength: query.length }); - this.logger.info(`Using language: ${language}, frameworks: ${frameworks ? frameworks.join(", ") : "none"}`); + this.logger().info('Generating blueprint', { query, queryLength: query.length }); + this.logger().info(`Using language: ${language}, frameworks: ${frameworks ? frameworks.join(", ") : "none"}`); const blueprint = await generateBlueprint({ env: this.env, @@ -232,17 +244,17 @@ export class SimpleCodeGeneratorAgent extends Agent { // Deploy to sandbox service and generate initial setup commands in parallel Promise.all([this.deployToSandbox(), this.getProjectSetupAssistant().generateSetupCommands(), this.generateReadme()]).then(async ([, setupCommands, _readme]) => { - this.logger.info("Deployment to sandbox service and initial commands predictions completed successfully"); + this.logger().info("Deployment to sandbox service and initial commands predictions completed successfully"); await this.executeCommands(setupCommands.commands); - this.logger.info("Initial commands executed successfully"); + this.logger().info("Initial commands executed successfully"); }).catch(error => { - this.logger.error("Error during deployment:", error); + this.logger().error("Error during deployment:", error); this.broadcast(WebSocketMessageResponses.ERROR, { error: `Error during deployment: ${error instanceof Error ? error.message : String(error)}` }); }); - this.logger.info(`Agent ${this.state.sessionId} initialized successfully`); + this.logger().info(`Agent ${this.state.sessionId} initialized successfully`); return this.state; } @@ -253,14 +265,14 @@ export class SimpleCodeGeneratorAgent extends Agent { onStateUpdate(_state: CodeGenState, _source: "server" | Connection) { // You can leave this empty to disable logging // Or, you can log a more specific message, for example: - this.logger.info("State was updated."); + this.logger().info("State was updated."); } setState(state: CodeGenState): void { try { super.setState(state); } catch (error) { - this.logger.error("Error setting state:", error); + this.logger().error("Error setting state:", error); this.broadcast(WebSocketMessageResponses.ERROR, { error: `Error setting state: ${error instanceof Error ? error.message : String(error)}; Original state: ${JSON.stringify(this.state, null, 2)}; New state: ${JSON.stringify(state, null, 2)}` }); @@ -287,7 +299,7 @@ export class SimpleCodeGeneratorAgent extends Agent { getSandboxServiceClient(): BaseSandboxService { if (this.sandboxServiceClient === undefined) { - this.logger.info('Initializing sandbox service client'); + this.logger().info('Initializing sandbox service client'); this.sandboxServiceClient = getSandboxService(this.state.sessionId, this.state.hostname); } return this.sandboxServiceClient; @@ -320,10 +332,10 @@ export class SimpleCodeGeneratorAgent extends Agent { } async generateReadme() { - this.logger.info('Generating README.md'); + this.logger().info('Generating README.md'); // Only generate if it doesn't exist if (this.fileManager.fileExists('README.md')) { - this.logger.info('README.md already exists'); + this.logger().info('README.md already exists'); return; } @@ -336,8 +348,8 @@ export class SimpleCodeGeneratorAgent extends Agent { const readme = await this.operations.implementPhase.generateReadme({ agentId: this.state.sessionId, env: this.env, - logger: this.logger, - context:GenerationContext.from(this.state, this.logger), + logger: this.logger(), + context:GenerationContext.from(this.state, this.logger()), inferenceContext: this.state.inferenceContext, }); @@ -347,7 +359,7 @@ export class SimpleCodeGeneratorAgent extends Agent { message: 'README.md generated successfully', file: readme }); - this.logger.info('README.md generated successfully'); + this.logger().info('README.md generated successfully'); } /** @@ -356,11 +368,11 @@ export class SimpleCodeGeneratorAgent extends Agent { */ async generateAllFiles(reviewCycles: number = 5): Promise { if (this.state.mvpGenerated && this.state.pendingUserInputs.length === 0) { - this.logger.info("Code generation already completed and no user inputs pending"); + this.logger().info("Code generation already completed and no user inputs pending"); return; } if (this.isGenerating) { - this.logger.info("Code generation already in progress"); + this.logger().info("Code generation already in progress"); return; } this.isGenerating = true; @@ -398,7 +410,7 @@ export class SimpleCodeGeneratorAgent extends Agent { let executionResults: {currentDevState: CurrentDevState, staticAnalysis?: StaticAnalysisResponse, result?: PhaseConceptType}; // State machine loop - continues until IDLE state while (currentDevState !== CurrentDevState.IDLE) { - this.logger.info(`[generateAllFiles] Executing state: ${currentDevState}`); + this.logger().info(`[generateAllFiles] Executing state: ${currentDevState}`); switch (currentDevState) { case CurrentDevState.PHASE_GENERATING: executionResults = await this.executePhaseGeneration(); @@ -422,9 +434,9 @@ export class SimpleCodeGeneratorAgent extends Agent { } } - this.logger.info("State machine completed successfully"); + this.logger().info("State machine completed successfully"); } catch (error) { - this.logger.error("Error in state machine:", error); + this.logger().error("Error in state machine:", error); const errorMessage = error instanceof Error ? error.message : String(error); this.broadcast(WebSocketMessageResponses.ERROR, { error: `Error during generation: ${errorMessage}` @@ -443,7 +455,7 @@ export class SimpleCodeGeneratorAgent extends Agent { * Execute phase generation state - generate next phase with user suggestions */ async executePhaseGeneration(): Promise<{currentDevState: CurrentDevState, result?: PhaseConceptType, staticAnalysis?: StaticAnalysisResponse}> { - this.logger.info("Executing PHASE_GENERATING state"); + this.logger().info("Executing PHASE_GENERATING state"); try { const currentIssues = await this.fetchAllIssues(); @@ -452,7 +464,7 @@ export class SimpleCodeGeneratorAgent extends Agent { const nextPhase = await this.generateNextPhase(currentIssues, userSuggestions); if (!nextPhase) { - this.logger.info("No more phases to implement, transitioning to FINALIZING"); + this.logger().info("No more phases to implement, transitioning to FINALIZING"); return { currentDevState: CurrentDevState.FINALIZING, }; @@ -470,7 +482,7 @@ export class SimpleCodeGeneratorAgent extends Agent { staticAnalysis: currentIssues.staticAnalysis }; } catch (error) { - this.logger.error("Error generating phase", error); + this.logger().error("Error generating phase", error); this.broadcast(WebSocketMessageResponses.ERROR, { message: "Error generating phase", error: error @@ -486,16 +498,16 @@ export class SimpleCodeGeneratorAgent extends Agent { */ async executePhaseImplementation(phaseConcept?: PhaseConceptType, staticAnalysis?: StaticAnalysisResponse): Promise<{currentDevState: CurrentDevState, staticAnalysis?: StaticAnalysisResponse}> { try { - this.logger.info("Executing PHASE_IMPLEMENTING state"); + this.logger().info("Executing PHASE_IMPLEMENTING state"); if (phaseConcept === undefined) { phaseConcept = this.state.currentPhase; if (phaseConcept === undefined) { - this.logger.error("No phase concept provided to implement, will call phase generation"); + this.logger().error("No phase concept provided to implement, will call phase generation"); const results = await this.executePhaseGeneration(); phaseConcept = results.result; if (phaseConcept === undefined) { - this.logger.error("No phase concept provided to implement, will return"); + this.logger().error("No phase concept provided to implement, will return"); return {currentDevState: CurrentDevState.FINALIZING}; } } @@ -522,14 +534,14 @@ export class SimpleCodeGeneratorAgent extends Agent { // Implement the phase await this.implementPhase(phaseConcept, currentIssues); - this.logger.info(`Phase ${phaseConcept.name} completed, generating next phase`); + this.logger().info(`Phase ${phaseConcept.name} completed, generating next phase`); const phasesCounter = this.decrementPhasesCounter(); if (phaseConcept.lastPhase || phasesCounter <= 0) return {currentDevState: CurrentDevState.FINALIZING, staticAnalysis: staticAnalysis}; return {currentDevState: CurrentDevState.PHASE_GENERATING, staticAnalysis: staticAnalysis}; } catch (error) { - this.logger.error("Error implementing phase", error); + this.logger().error("Error implementing phase", error); return {currentDevState: CurrentDevState.IDLE}; } } @@ -538,33 +550,33 @@ export class SimpleCodeGeneratorAgent extends Agent { * Execute review cycle state - run code review and regeneration cycles */ async executeReviewCycle(): Promise { - this.logger.info("Executing REVIEWING state"); + this.logger().info("Executing REVIEWING state"); const reviewCycles = 3; try { - this.logger.info("Starting code review and improvement cycle..."); + this.logger().info("Starting code review and improvement cycle..."); for (let i = 0; i < reviewCycles; i++) { // Check if user input came during review - if so, go back to phase generation if (this.state.pendingUserInputs.length > 0) { - this.logger.info("User input received during review, transitioning back to PHASE_GENERATING"); + this.logger().info("User input received during review, transitioning back to PHASE_GENERATING"); return CurrentDevState.PHASE_GENERATING; } - this.logger.info(`Starting code review cycle ${i + 1}...`); + this.logger().info(`Starting code review cycle ${i + 1}...`); const reviewResult = await this.reviewCode(); if (!reviewResult) { - this.logger.warn("Code review failed. Skipping fix cycle."); + this.logger().warn("Code review failed. Skipping fix cycle."); break; } const issuesFound = reviewResult.issuesFound; if (issuesFound) { - this.logger.info(`Issues found in review cycle ${i + 1}`); + this.logger().info(`Issues found in review cycle ${i + 1}`); const promises = []; for (const fileToFix of reviewResult.filesToFix) { @@ -572,7 +584,7 @@ export class SimpleCodeGeneratorAgent extends Agent { const fileToRegenerate = this.fileManager.getGeneratedFile(fileToFix.filePath); if (!fileToRegenerate) { - this.logger.warn(`File to fix not found in generated files: ${fileToFix.filePath}`); + this.logger().warn(`File to fix not found in generated files: ${fileToFix.filePath}`); continue; } @@ -590,24 +602,24 @@ export class SimpleCodeGeneratorAgent extends Agent { // await this.applyDeterministicCodeFixes(); - this.logger.info("Completed regeneration for review cycle"); + this.logger().info("Completed regeneration for review cycle"); } else { - this.logger.info("Code review found no issues. Review cycles complete."); + this.logger().info("Code review found no issues. Review cycles complete."); break; } } // Check again for user input before finalizing if (this.state.pendingUserInputs.length > 0) { - this.logger.info("User input received after review, transitioning back to PHASE_GENERATING"); + this.logger().info("User input received after review, transitioning back to PHASE_GENERATING"); return CurrentDevState.PHASE_GENERATING; } else { - this.logger.info("Review cycles complete, transitioning to IDLE"); + this.logger().info("Review cycles complete, transitioning to IDLE"); return CurrentDevState.IDLE; } } catch (error) { - this.logger.error("Error during review cycle:", error); + this.logger().error("Error during review cycle:", error); return CurrentDevState.IDLE; } } @@ -616,11 +628,11 @@ export class SimpleCodeGeneratorAgent extends Agent { * Execute finalizing state - final review and cleanup (runs only once) */ async executeFinalizing(): Promise { - this.logger.info("Executing FINALIZING state - final review and cleanup"); + this.logger().info("Executing FINALIZING state - final review and cleanup"); // Only do finalizing stage if it wasn't done before if (this.state.mvpGenerated) { - this.logger.info("Finalizing stage already done"); + this.logger().info("Finalizing stage already done"); return CurrentDevState.REVIEWING; } this.setState({ @@ -653,7 +665,7 @@ export class SimpleCodeGeneratorAgent extends Agent { await this.implementPhase(phaseConcept, currentIssues); const numFilesGenerated = this.fileManager.getGeneratedFilePaths().length; - this.logger.info(`Finalization complete. Generated ${numFilesGenerated}/${this.getTotalFiles()} files.`); + this.logger().info(`Finalization complete. Generated ${numFilesGenerated}/${this.getTotalFiles()} files.`); // Transition to IDLE - generation complete return CurrentDevState.REVIEWING; @@ -663,7 +675,7 @@ export class SimpleCodeGeneratorAgent extends Agent { * Generate next phase with raw user suggestions */ async generateNextPhase(currentIssues: AllIssues, userSuggestions?: string[]): Promise { - const context = GenerationContext.from(this.state, this.logger); + const context = GenerationContext.from(this.state, this.logger()); const issues = IssueReport.from(currentIssues); // Notify phase generation start this.broadcast(WebSocketMessageResponses.PHASE_GENERATING, { @@ -679,7 +691,7 @@ export class SimpleCodeGeneratorAgent extends Agent { { env: this.env, agentId: this.state.sessionId, - logger: this.logger, + logger: this.logger(), context, inferenceContext: this.state.inferenceContext, } @@ -690,7 +702,7 @@ export class SimpleCodeGeneratorAgent extends Agent { } if (result.files.length === 0) { - this.logger.info("No files generated for next phase"); + this.logger().info("No files generated for next phase"); // Notify phase generation complete this.broadcast(WebSocketMessageResponses.PHASE_GENERATED, { message: `No files generated for next phase`, @@ -724,7 +736,7 @@ export class SimpleCodeGeneratorAgent extends Agent { * Streams file generation with real-time updates and incorporates technical instructions */ async implementPhase(phase: PhaseConceptType, currentIssues: AllIssues, streamChunks: boolean = true): Promise { - const context = GenerationContext.from(this.state, this.logger); + const context = GenerationContext.from(this.state, this.logger()); const issues = IssueReport.from(currentIssues); this.broadcast(WebSocketMessageResponses.PHASE_IMPLEMENTING, { @@ -765,7 +777,7 @@ export class SimpleCodeGeneratorAgent extends Agent { { env: this.env, agentId: this.state.sessionId, - logger: this.logger, + logger: this.logger(), context, inferenceContext: this.state.inferenceContext, } @@ -790,11 +802,11 @@ export class SimpleCodeGeneratorAgent extends Agent { // Update state with completed phase this.fileManager.saveGeneratedFiles(finalFiles); - this.logger.info("Files generated for phase:", phase.name, finalFiles.map(f => f.filePath)); + this.logger().info("Files generated for phase:", phase.name, finalFiles.map(f => f.filePath)); // Execute commands if provided if (result.commands && result.commands.length > 0) { - this.logger.info("Phase implementation suggested install commands:", result.commands); + this.logger().info("Phase implementation suggested install commands:", result.commands); await this.executeCommands(result.commands); } @@ -811,9 +823,9 @@ export class SimpleCodeGeneratorAgent extends Agent { phase: phase }); - this.logger.info("Files generated for phase:", phase.name, finalFiles.map(f => f.filePath)); + this.logger().info("Files generated for phase:", phase.name, finalFiles.map(f => f.filePath)); - this.logger.info(`Validation complete for phase: ${phase.name}`); + this.logger().info(`Validation complete for phase: ${phase.name}`); // Notify phase completion this.broadcast(WebSocketMessageResponses.PHASE_IMPLEMENTED, { @@ -837,7 +849,7 @@ export class SimpleCodeGeneratorAgent extends Agent { generatedPhases: updatedPhases }); - this.logger.info("Completed phases:", JSON.stringify(updatedPhases, null, 2)); + this.logger().info("Completed phases:", JSON.stringify(updatedPhases, null, 2)); return { files: finalFiles, @@ -904,7 +916,7 @@ export class SimpleCodeGeneratorAgent extends Agent { defaultConfigs }; } catch (error) { - this.logger.error('Error fetching model configs info:', error); + this.logger().error('Error fetching model configs info:', error); throw error; } } @@ -914,7 +926,7 @@ export class SimpleCodeGeneratorAgent extends Agent { * Analyzes for runtime errors, static issues, and best practices */ async reviewCode() { - const context = GenerationContext.from(this.state, this.logger); + const context = GenerationContext.from(this.state, this.logger()); const issues = await this.fetchAllIssues(); this.resetIssues(); const issueReport = IssueReport.from(issues); @@ -932,7 +944,7 @@ export class SimpleCodeGeneratorAgent extends Agent { { env: this.env, agentId: this.state.sessionId, - logger: this.logger, + logger: this.logger(), context, inferenceContext: this.state.inferenceContext, } @@ -940,7 +952,7 @@ export class SimpleCodeGeneratorAgent extends Agent { // Execute commands if any if (reviewResult.commands && reviewResult.commands.length > 0) { - this.executeCommands(reviewResult.commands); + await this.executeCommands(reviewResult.commands); } // Notify review completion this.broadcast(WebSocketMessageResponses.CODE_REVIEWED, { @@ -956,7 +968,7 @@ export class SimpleCodeGeneratorAgent extends Agent { * Retries up to 3 times before giving up */ async regenerateFile(file: FileOutputType, issues: string[], retryIndex: number = 0) { - const context = GenerationContext.from(this.state, this.logger); + const context = GenerationContext.from(this.state, this.logger()); this.broadcast(WebSocketMessageResponses.FILE_REGENERATING, { message: `Regenerating file: ${file.filePath}`, filePath: file.filePath, @@ -968,7 +980,7 @@ export class SimpleCodeGeneratorAgent extends Agent { { env: this.env, agentId: this.state.sessionId, - logger: this.logger, + logger: this.logger(), context, inferenceContext: this.state.inferenceContext, } @@ -1122,7 +1134,7 @@ export class SimpleCodeGeneratorAgent extends Agent { } } - this.logger.info('Conversation cleanup analysis', { + this.logger().info('Conversation cleanup analysis', { totalUniqueMessages: uniqueMessages.length, realConversations: realConversations.length, internalMemos: internalMemos.length, @@ -1137,7 +1149,7 @@ export class SimpleCodeGeneratorAgent extends Agent { } if (migratedConversationMessages.length !== originalCount) { - this.logger.info('Fixed conversation message exponential bloat', { + this.logger().info('Fixed conversation message exponential bloat', { originalCount, deduplicatedCount: uniqueMessages.length, finalCount: migratedConversationMessages.length, @@ -1167,7 +1179,7 @@ export class SimpleCodeGeneratorAgent extends Agent { // Apply migration if needed if (needsMigration) { - this.logger.info('Migrating state: schema format, conversation cleanup, and security fixes', { + this.logger().info('Migrating state: schema format, conversation cleanup, and security fixes', { generatedFilesCount: Object.keys(migratedFilesMap).length, templateFilesCount: migratedTemplateDetails?.files?.length || 0, finalConversationCount: migratedConversationMessages?.length || 0, @@ -1203,14 +1215,14 @@ export class SimpleCodeGeneratorAgent extends Agent { async fetchRuntimeErrors(clear: boolean = true) { if (!this.state.sandboxInstanceId || !this.fileManager) { - this.logger.warn("No sandbox instance ID available to fetch errors from."); + this.logger().warn("No sandbox instance ID available to fetch errors from."); return []; } try { const resp = await this.getSandboxServiceClient().getInstanceErrors(this.state.sandboxInstanceId); if (!resp || !resp.success) { - this.logger.error(`Failed to fetch runtime errors: ${resp?.error || 'Unknown error'}, Will initiate redeploy`); + this.logger().error(`Failed to fetch runtime errors: ${resp?.error || 'Unknown error'}, Will initiate redeploy`); // Initiate redeploy this.deployToSandbox([], true); return []; @@ -1219,14 +1231,14 @@ export class SimpleCodeGeneratorAgent extends Agent { const errors = resp?.errors || []; if (errors.filter(error => error.message.includes('Unterminated string in JSON at position')).length > 0) { - this.logger.error('Unterminated string in JSON at position, will initiate redeploy'); + this.logger().error('Unterminated string in JSON at position, will initiate redeploy'); // Initiate redeploy this.deployToSandbox([], true); return []; } if (errors.length > 0) { - this.logger.info(`Found ${errors.length} runtime errors: ${errors.map(e => e.message).join(', ')}`); + this.logger().info(`Found ${errors.length} runtime errors: ${errors.map(e => e.message).join(', ')}`); this.broadcast(WebSocketMessageResponses.RUNTIME_ERROR_FOUND, { errors, message: "Runtime errors found", @@ -1240,7 +1252,7 @@ export class SimpleCodeGeneratorAgent extends Agent { return errors; } catch (error) { - this.logger.error("Exception fetching runtime errors:", error); + this.logger().error("Exception fetching runtime errors:", error); return []; } } @@ -1253,11 +1265,11 @@ export class SimpleCodeGeneratorAgent extends Agent { const { sandboxInstanceId } = this.state; if (!sandboxInstanceId) { - this.logger.warn("No sandbox instance ID available to lint code."); + this.logger().warn("No sandbox instance ID available to lint code."); return { success: false, lint: { issues: [], }, typecheck: { issues: [], } }; } - this.logger.info(`Linting code in sandbox instance ${sandboxInstanceId}`); + this.logger().info(`Linting code in sandbox instance ${sandboxInstanceId}`); const files = this.fileManager.getGeneratedFilePaths(); @@ -1266,7 +1278,7 @@ export class SimpleCodeGeneratorAgent extends Agent { if (!analysisResponse || analysisResponse.error) { const errorMsg = `Code linting failed: ${analysisResponse?.error || 'Unknown error'}, full response: ${JSON.stringify(analysisResponse)}`; - this.logger.error(errorMsg); + this.logger().error(errorMsg); this.broadcast(WebSocketMessageResponses.ERROR, { error: errorMsg, analysisResponse }); throw new Error(errorMsg); } @@ -1274,14 +1286,14 @@ export class SimpleCodeGeneratorAgent extends Agent { const { lint, typecheck } = analysisResponse; const { issues: lintIssues, summary: lintSummary } = lint; - this.logger.info(`Linting found ${lintIssues.length} issues: ` + + this.logger().info(`Linting found ${lintIssues.length} issues: ` + `${lintSummary?.errorCount || 0} errors, ` + `${lintSummary?.warningCount || 0} warnings, ` + `${lintSummary?.infoCount || 0} info`); const { issues: typeCheckIssues, summary: typeCheckSummary } = typecheck; - this.logger.info(`Typecheck found ${typeCheckIssues.length} issues: ` + + this.logger().info(`Typecheck found ${typeCheckIssues.length} issues: ` + `${typeCheckSummary?.errorCount || 0} errors, ` + `${typeCheckSummary?.warningCount || 0} warnings, ` + `${typeCheckSummary?.infoCount || 0} info`); @@ -1293,7 +1305,7 @@ export class SimpleCodeGeneratorAgent extends Agent { return analysisResponse; } catch (error) { - this.logger.error("Error linting code:", error); + this.logger().error("Error linting code:", error); const errorMessage = error instanceof Error ? error.message : String(error); this.broadcast(WebSocketMessageResponses.ERROR, { error: `Failed to lint code: ${errorMessage}` }); // throw new Error(`Failed to lint code: ${errorMessage}`); @@ -1304,16 +1316,16 @@ export class SimpleCodeGeneratorAgent extends Agent { // private async applyFastSmartCodeFixes() : Promise { // try { // const startTime = Date.now(); - // this.logger.info("Applying fast smart code fixes"); + // this.logger().info("Applying fast smart code fixes"); // // Get static analysis and do deterministic fixes // const staticAnalysis = await this.runStaticAnalysisCode(); // if (staticAnalysis.typecheck.issues.length + staticAnalysis.lint.issues.length == 0) { - // this.logger.info("No issues found, skipping fast smart code fixes"); + // this.logger().info("No issues found, skipping fast smart code fixes"); // return; // } // const issues = staticAnalysis.typecheck.issues.concat(staticAnalysis.lint.issues); // const allFiles = this.fileManager.getAllFiles(); - // const context = GenerationContext.from(this.state, this.logger); + // const context = GenerationContext.from(this.state, this.logger()); // const fastCodeFixer = await this.operations.fastCodeFixer.execute({ // query: this.state.query, @@ -1323,17 +1335,17 @@ export class SimpleCodeGeneratorAgent extends Agent { // env: this.env, // agentId: this.state.sessionId, // context, - // logger: this.logger + // logger: this.logger() // }); // if (fastCodeFixer.length > 0) { // this.fileManager.saveGeneratedFiles(fastCodeFixer); // await this.deployToSandbox(fastCodeFixer); - // this.logger.info("Fast smart code fixes applied successfully"); + // this.logger().info("Fast smart code fixes applied successfully"); // } - // this.logger.info(`Fast smart code fixes applied in ${Date.now() - startTime}ms`); + // this.logger().info(`Fast smart code fixes applied in ${Date.now() - startTime}ms`); // } catch (error) { - // this.logger.error("Error applying fast smart code fixes:", error); + // this.logger().error("Error applying fast smart code fixes:", error); // const errorMessage = error instanceof Error ? error.message : String(error); // this.broadcast(WebSocketMessageResponses.ERROR, { error: `Failed to apply fast smart code fixes: ${errorMessage}` }); // return; @@ -1348,7 +1360,7 @@ export class SimpleCodeGeneratorAgent extends Agent { // Get static analysis and do deterministic fixes const staticAnalysis = await this.runStaticAnalysisCode(); if (staticAnalysis.typecheck.issues.length == 0) { - this.logger.info("No typecheck issues found, skipping deterministic fixes"); + this.logger().info("No typecheck issues found, skipping deterministic fixes"); return staticAnalysis; // So that static analysis is not repeated again } const typeCheckIssues = staticAnalysis.typecheck.issues; @@ -1357,7 +1369,7 @@ export class SimpleCodeGeneratorAgent extends Agent { issues: typeCheckIssues }); - this.logger.info(`Attempting to fix ${typeCheckIssues.length} TypeScript issues using deterministic code fixer`); + this.logger().info(`Attempting to fix ${typeCheckIssues.length} TypeScript issues using deterministic code fixer`); const allFiles = this.fileManager.getAllFiles(); // Create file fetcher callback @@ -1366,17 +1378,17 @@ export class SimpleCodeGeneratorAgent extends Agent { try { const result = await this.getSandboxServiceClient().getFiles(this.state.sandboxInstanceId!, [filePath]); if (result.success && result.files.length > 0) { - this.logger.info(`Successfully fetched file: ${filePath}`); + this.logger().info(`Successfully fetched file: ${filePath}`); return { filePath: filePath, fileContents: result.files[0].fileContents, filePurpose: `Fetched file: ${filePath}` }; } else { - this.logger.debug(`File not found: ${filePath}`); + this.logger().debug(`File not found: ${filePath}`); } } catch (error) { - this.logger.debug(`Failed to fetch file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`); + this.logger().debug(`Failed to fetch file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`); } return null; }; @@ -1397,8 +1409,18 @@ export class SimpleCodeGeneratorAgent extends Agent { }); if (fixResult) { + // If there are unfixable issues but of type TS2307, Extract the module that wasnt found and maybe try installing it + if (fixResult.unfixableIssues.length > 0) { + const modulesNotFound = fixResult.unfixableIssues.filter(issue => issue.issueCode === 'TS2307'); + // Reason would be of type `External package \"xyz\" should be handled by package manager`, extract via regex + const moduleNames = modulesNotFound.map(issue => issue.reason.match(/External package "(.+)"/)?.[1]); + + // Execute command + await this.executeCommands(moduleNames.filter(moduleName => moduleName !== undefined).map(moduleName => `bun install ${moduleName}`)); + this.logger().info(`Deterministic code fixer installed missing modules: ${moduleNames.join(', ')}`); + } if (fixResult.modifiedFiles.length > 0) { - this.logger.info("Applying deterministic fixes to files, Fixes: ", JSON.stringify(fixResult, null, 2)); + this.logger().info("Applying deterministic fixes to files, Fixes: ", JSON.stringify(fixResult, null, 2)); const fixedFiles = fixResult.modifiedFiles.map(file => ({ filePath: file.filePath, filePurpose: allFiles.find(f => f.filePath === file.filePath)?.filePurpose || '', @@ -1407,23 +1429,12 @@ export class SimpleCodeGeneratorAgent extends Agent { this.fileManager.saveGeneratedFiles(fixedFiles); await this.deployToSandbox(fixedFiles, false, "fix: applied deterministic fixes"); - this.logger.info("Deployed deterministic fixes to sandbox"); - } - - // If there are unfixable issues but of type TS2307, Extract the module that wasnt found and maybe try installing it - if (fixResult.unfixableIssues.length > 0) { - const modulesNotFound = fixResult.unfixableIssues.filter(issue => issue.issueCode === 'TS2307'); - // Reason would be of type `External package \"xyz\" should be handled by package manager`, extract via regex - const moduleNames = modulesNotFound.map(issue => issue.reason.match(/External package "(.+)"/)?.[1]); - - // Execute command - await this.executeCommands(moduleNames.filter(moduleName => moduleName !== undefined).map(moduleName => `bun install ${moduleName}`)); - this.logger.info(`Deterministic code fixer installed missing modules: ${moduleNames.join(', ')}`); + this.logger().info("Deployed deterministic fixes to sandbox"); } } - this.logger.info(`Applied deterministic code fixes: ${JSON.stringify(fixResult, null, 2)}`); + this.logger().info(`Applied deterministic code fixes: ${JSON.stringify(fixResult, null, 2)}`); } catch (error) { - this.logger.error('Error applying deterministic code fixes:', error); + this.logger().error('Error applying deterministic code fixes:', error); this.broadcast(WebSocketMessageResponses.ERROR, { error: `Deterministic code fixer failed: ${error instanceof Error ? error.message : String(error)}` }); @@ -1431,7 +1442,6 @@ export class SimpleCodeGeneratorAgent extends Agent { // return undefined; } - async fetchAllIssues(): Promise { const [runtimeErrors, staticAnalysis] = await Promise.all([ this.fetchRuntimeErrors(false), @@ -1439,13 +1449,13 @@ export class SimpleCodeGeneratorAgent extends Agent { ]); const clientErrors = this.state.clientReportedErrors; - this.logger.info("Fetched all issues:", JSON.stringify({ runtimeErrors, staticAnalysis, clientErrors })); + this.logger().info("Fetched all issues:", JSON.stringify({ runtimeErrors, staticAnalysis, clientErrors })); return { runtimeErrors, staticAnalysis, clientErrors }; } async resetIssues() { - this.logger.info("Resetting issues"); + this.logger().info("Resetting issues"); await this.getSandboxServiceClient().clearInstanceErrors(this.state.sandboxInstanceId!); this.setState({ ...this.state, @@ -1456,11 +1466,11 @@ export class SimpleCodeGeneratorAgent extends Agent { async deployToSandbox(files: FileOutputType[] = [], redeploy: boolean = false, commitMessage?: string): Promise { // If there's already a deployment in progress, wait for it to complete if (this.currentDeploymentPromise) { - this.logger.info('Deployment already in progress, waiting for completion before starting new deployment'); + this.logger().info('Deployment already in progress, waiting for completion before starting new deployment'); try { await this.currentDeploymentPromise; } catch (error) { - this.logger.warn('Previous deployment failed, proceeding with new deployment:', error); + this.logger().warn('Previous deployment failed, proceeding with new deployment:', error); } } @@ -1501,7 +1511,7 @@ export class SimpleCodeGeneratorAgent extends Agent { try { baseUrl = await this.env.AI.gateway(this.env.CLOUDFLARE_AI_GATEWAY).getUrl() } catch (error) { - this.logger.error(`Error getting AI gateway URL: ${error}`); + this.logger().error(`Error getting AI gateway URL: ${error}`); // throw error; } const localEnvVars = { @@ -1514,7 +1524,7 @@ export class SimpleCodeGeneratorAgent extends Agent { throw new Error(`Failed to create sandbox instance: ${createResponse?.error || 'Unknown error'}`); } - this.logger.info(`Received createInstance response: ${JSON.stringify(createResponse, null, 2)}`) + this.logger().info(`Received createInstance response: ${JSON.stringify(createResponse, null, 2)}`) if (createResponse.runId && createResponse.previewURL) { this.previewUrlCache = createResponse.previewURL; @@ -1531,7 +1541,7 @@ export class SimpleCodeGeneratorAgent extends Agent { let tunnelURL: string | undefined; if (!templateDetails) { - this.logger.error("Template details not available for deployment."); + this.logger().error("Template details not available for deployment."); this.broadcast(WebSocketMessageResponses.ERROR, { error: "Template details not configured." }); return null; } @@ -1543,16 +1553,16 @@ export class SimpleCodeGeneratorAgent extends Agent { })) }); - this.logger.info("Deploying code to sandbox service"); + this.logger().info("Deploying code to sandbox service"); // Check if the instance is running if (sandboxInstanceId) { const status = await this.getSandboxServiceClient().getInstanceStatus(sandboxInstanceId); if (!status || !status.success) { - this.logger.error(`DEPLOYMENT CHECK FAILED: Failed to get status for instance ${sandboxInstanceId}, redeploying...`); + this.logger().error(`DEPLOYMENT CHECK FAILED: Failed to get status for instance ${sandboxInstanceId}, redeploying...`); sandboxInstanceId = undefined; } else { - this.logger.info(`DEPLOYMENT CHECK PASSED: Instance ${sandboxInstanceId} is running, previewURL: ${status.previewURL}, tunnelURL: ${status.tunnelURL}`); + this.logger().info(`DEPLOYMENT CHECK PASSED: Instance ${sandboxInstanceId} is running, previewURL: ${status.previewURL}, tunnelURL: ${status.tunnelURL}`); previewURL = status.previewURL; tunnelURL = status.tunnelURL; } @@ -1577,13 +1587,13 @@ export class SimpleCodeGeneratorAgent extends Agent { }); // Run all commands in background - this.executeCommands(this.state.commandsHistory || []); + this.executeCommands(this.state.commandsHistory || [], 20); // Launch a set interval to check the health of the deployment. If it fails, redeploy const checkHealthInterval = setInterval(async () => { const status = await this.getSandboxServiceClient().getInstanceStatus(sandboxInstanceId!); if (!status || !status.success) { - this.logger.error(`DEPLOYMENT CHECK FAILED: Failed to get status for instance ${sandboxInstanceId}, redeploying...`); + this.logger().error(`DEPLOYMENT CHECK FAILED: Failed to get status for instance ${sandboxInstanceId}, redeploying...`); clearInterval(checkHealthInterval); await this.executeDeployment([], true); } @@ -1607,7 +1617,7 @@ export class SimpleCodeGeneratorAgent extends Agent { if (filesToWrite.length > 0) { const writeResponse = await this.getSandboxServiceClient().writeFiles(sandboxInstanceId, filesToWrite, commitMessage); if (!writeResponse || !writeResponse.success) { - this.logger.warn(`File writing failed. Error: ${writeResponse?.error}`); + this.logger().warn(`File writing failed. Error: ${writeResponse?.error}`); } } @@ -1624,7 +1634,7 @@ export class SimpleCodeGeneratorAgent extends Agent { return preview; } catch (error) { - this.logger.error("Error deploying to sandbox service:", error); + this.logger().error("Error deploying to sandbox service:", error); this.setState({ ...this.state, sandboxInstanceId: undefined, @@ -1638,7 +1648,7 @@ export class SimpleCodeGeneratorAgent extends Agent { */ async deployToCloudflare(): Promise<{ deploymentUrl?: string; workersUrl?: string } | null> { try { - this.logger.info('Starting Cloudflare deployment'); + this.logger().info('Starting Cloudflare deployment'); this.broadcast(WebSocketMessageResponses.CLOUDFLARE_DEPLOYMENT_STARTED, { message: 'Starting deployment to Cloudflare Workers...', instanceId: this.state.sandboxInstanceId, @@ -1646,7 +1656,7 @@ export class SimpleCodeGeneratorAgent extends Agent { // Check if we have generated files if (!this.state.generatedFilesMap || Object.keys(this.state.generatedFilesMap).length === 0) { - this.logger.error('No generated files available for deployment'); + this.logger().error('No generated files available for deployment'); this.broadcast(WebSocketMessageResponses.CLOUDFLARE_DEPLOYMENT_ERROR, { message: 'Deployment failed: No generated code available', error: 'No files have been generated yet' @@ -1656,12 +1666,12 @@ export class SimpleCodeGeneratorAgent extends Agent { // Check if we have a sandbox instance ID if (!this.state.sandboxInstanceId) { - this.logger.info('[DeployToCloudflare] No sandbox instance ID available, will initiate deployment'); + this.logger().info('[DeployToCloudflare] No sandbox instance ID available, will initiate deployment'); // Need to redeploy await this.deployToSandbox(); if (!this.state.sandboxInstanceId) { - this.logger.error('[DeployToCloudflare] Failed to deploy to sandbox service'); + this.logger().error('[DeployToCloudflare] Failed to deploy to sandbox service'); this.broadcast(WebSocketMessageResponses.CLOUDFLARE_DEPLOYMENT_ERROR, { message: 'Deployment failed: Failed to deploy to sandbox service', error: 'Sandbox service unavailable' @@ -1670,7 +1680,7 @@ export class SimpleCodeGeneratorAgent extends Agent { } } - this.logger.info('[DeployToCloudflare] Prerequisites met, initiating deployment', { + this.logger().info('[DeployToCloudflare] Prerequisites met, initiating deployment', { sandboxInstanceId: this.state.sandboxInstanceId, fileCount: Object.keys(this.state.generatedFilesMap).length }); @@ -1682,9 +1692,9 @@ export class SimpleCodeGeneratorAgent extends Agent { }; // TODO: Remove this before production const deploymentResult = await this.getSandboxServiceClient().deployToCloudflareWorkers(this.state.sandboxInstanceId, defaultCredentials); - this.logger.info('[DeployToCloudflare] Deployment result:', deploymentResult); + this.logger().info('[DeployToCloudflare] Deployment result:', deploymentResult); if (!deploymentResult) { - this.logger.error('[DeployToCloudflare] Deployment API call failed'); + this.logger().error('[DeployToCloudflare] Deployment API call failed'); this.broadcast(WebSocketMessageResponses.CLOUDFLARE_DEPLOYMENT_ERROR, { message: 'Deployment failed: API call returned null', error: 'Deployment service unavailable' @@ -1693,7 +1703,7 @@ export class SimpleCodeGeneratorAgent extends Agent { } if (!deploymentResult.success) { - this.logger.error('Deployment failed', { + this.logger().error('Deployment failed', { message: deploymentResult.message, error: deploymentResult.error }); @@ -1706,7 +1716,7 @@ export class SimpleCodeGeneratorAgent extends Agent { const deploymentUrl = deploymentResult.deployedUrl; - this.logger.info('[DeployToCloudflare] Cloudflare deployment completed successfully', { + this.logger().info('[DeployToCloudflare] Cloudflare deployment completed successfully', { deploymentUrl, deploymentId: deploymentResult.deploymentId, sandboxInstanceId: this.state.sandboxInstanceId, @@ -1717,7 +1727,7 @@ export class SimpleCodeGeneratorAgent extends Agent { await DatabaseOperations.updateDeploymentUrl( this.env, this.state.sessionId, - this.logger, + this.logger(), deploymentUrl || '' ); @@ -1731,7 +1741,7 @@ export class SimpleCodeGeneratorAgent extends Agent { } catch (error) { return ErrorHandler.handleOperationError( - this.logger, + this.logger(), this, 'Cloudflare deployment', error, @@ -1743,18 +1753,18 @@ export class SimpleCodeGeneratorAgent extends Agent { // async analyzeScreenshot(): Promise { // const screenshotData = this.state.latestScreenshot; // if (!screenshotData) { - // this.logger.warn('No screenshot available for analysis'); + // this.logger().warn('No screenshot available for analysis'); // return null; // } - // const context = GenerationContext.from(this.state, this.logger); + // const context = GenerationContext.from(this.state, this.logger()); // const result = await this.operations.analyzeScreenshot.execute( // {screenshotData}, // { // env: this.env, // agentId: this.state.sessionId, // context, - // logger: this.logger, + // logger: this.logger(), // inferenceContext: this.state.inferenceContext, // } // ); @@ -1766,12 +1776,12 @@ export class SimpleCodeGeneratorAgent extends Agent { if (this.state.generationPromise) { try { await this.state.generationPromise; - this.logger.info("Code generation completed successfully"); + this.logger().info("Code generation completed successfully"); } catch (error) { - this.logger.error("Error during code generation:", error); + this.logger().error("Error during code generation:", error); } } else { - this.logger.error("No generation process found"); + this.logger().error("No generation process found"); } } @@ -1779,11 +1789,6 @@ export class SimpleCodeGeneratorAgent extends Agent { return { action: 'No action', data: {} }; } - async executeAction(action: AgentActionType) { - this.logger.info(`Executing action: ${action.action}`); - return await executeAction(this, action); - } - async onMessage(connection: Connection, message: string): Promise { handleWebSocketMessage(this, connection, message); } @@ -1801,7 +1806,7 @@ export class SimpleCodeGeneratorAgent extends Agent { ): void { // Send the event to the conversational assistant if its a relevant event if (this.operations.processUserMessage.isProjectUpdateType(typeOrMsg)) { - const messages = this.operations.processUserMessage.processProjectUpdates(typeOrMsg, dataOrWithout as WebSocketMessageData, this.logger); + const messages = this.operations.processUserMessage.processProjectUpdates(typeOrMsg, dataOrWithout as WebSocketMessageData, this.logger()); this.setState({ ...this.state, conversationMessages: [...this.state.conversationMessages, ...messages] @@ -1847,7 +1852,7 @@ export class SimpleCodeGeneratorAgent extends Agent { const pathParts = url.pathname.split('/'); const eventType = pathParts[pathParts.length - 1]; - this.logger.info('Received webhook from sandbox service', { + this.logger().info('Received webhook from sandbox service', { eventType, agentId: this.state.sessionId }); @@ -1868,7 +1873,7 @@ export class SimpleCodeGeneratorAgent extends Agent { }); } catch (error) { - this.logger.error('Error handling webhook', error); + this.logger().error('Error handling webhook', error); return new Response('Internal server error', { status: 500 }); } } @@ -1883,10 +1888,10 @@ export class SimpleCodeGeneratorAgent extends Agent { await this.handleRuntimeErrorWebhook(event, context); break; default: - this.logger.warn('Unhandled webhook event type', { eventType: event.eventType }); + this.logger().warn('Unhandled webhook event type', { eventType: event.eventType }); } } catch (error) { - this.logger.error('Error processing webhook event', error); + this.logger().error('Error processing webhook event', error); } } @@ -1895,10 +1900,10 @@ export class SimpleCodeGeneratorAgent extends Agent { */ private async handleRuntimeErrorWebhook(event: WebhookPayload['event'], _context: WebhookPayload['context']): Promise { if (!event.payload.error) { - this.logger.error('Invalid runtime error event: No error provided'); + this.logger().error('Invalid runtime error event: No error provided'); return; } - this.logger.info('Processing runtime error webhook', { + this.logger().info('Processing runtime error webhook', { errorMessage: event.payload.error.message, runId: event.payload.runId, instanceId: event.instanceId @@ -1925,7 +1930,7 @@ export class SimpleCodeGeneratorAgent extends Agent { // if (state.lastPackageJson) { // const parsedPackageJson = JSON.parse(state.lastPackageJson); // Object.assign(deps, parsedPackageJson.dependencies as Record); - // this.logger.info(`Adding dependencies from last package.json: ${Object.keys(parsedPackageJson.dependencies).join(', ')}`); + // this.logger().info(`Adding dependencies from last package.json: ${Object.keys(parsedPackageJson.dependencies).join(', ')}`); // } // return deps; // } @@ -1934,25 +1939,24 @@ export class SimpleCodeGeneratorAgent extends Agent { * Execute commands with retry logic * Chunks commands and retries failed ones with AI assistance */ - private async executeCommands(commands: string[]): Promise { + private async executeCommands(commands: string[], chunkSize: number = 5): Promise { const state = this.state; if (!state.sandboxInstanceId) { - this.logger.warn('No sandbox instance available for executing commands'); + this.logger().warn('No sandbox instance available for executing commands'); return; } // Sanitize and prepare commands commands = commands.join('\n').split('\n').filter(cmd => cmd.trim() !== '').filter(cmd => looksLikeCommand(cmd) && !cmd.includes(' undefined')); if (commands.length === 0) { - this.logger.warn("No commands to execute"); + this.logger().warn("No commands to execute"); return; } commands = commands.map(cmd => cmd.trim().replace(/^\s*-\s*/, '').replace(/^npm/, 'bun')); - this.logger.info(`AI suggested ${commands.length} commands to run: ${commands.join(", ")}`); + this.logger().info(`AI suggested ${commands.length} commands to run: ${commands.join(", ")}`); // Execute in chunks of 5 for better reliability - const chunkSize = 5; const commandChunks = []; for (let i = 0; i < commands.length; i += chunkSize) { commandChunks.push(commands.slice(i, i + chunkSize)); @@ -1974,7 +1978,7 @@ export class SimpleCodeGeneratorAgent extends Agent { currentChunk ); if (!resp || !resp.results) { - this.logger.error('Failed to execute commands'); + this.logger().error('Failed to execute commands'); return; } @@ -1983,26 +1987,26 @@ export class SimpleCodeGeneratorAgent extends Agent { const failures = resp.results.filter(r => !r.success); if (successful.length > 0) { - this.logger.info(`Commands executed successfully: ${currentChunk.join(", ")}`); + this.logger().info(`Commands executed successfully: ${currentChunk.join(", ")}`); successfulCommands.push(...successful.map(r => r.command)); if (successful.length === currentChunk.length) { - this.logger.info(`All commands executed successfully in this chunk: ${currentChunk.join(", ")}`); + this.logger().info(`All commands executed successfully in this chunk: ${currentChunk.join(", ")}`); break; } } if (failures.length > 0) { - this.logger.warn(`Some commands failed to execute: ${failures.map(r => r.command).join(", ")}, will retry`); + this.logger().warn(`Some commands failed to execute: ${failures.map(r => r.command).join(", ")}, will retry`); } else { - this.logger.error(`This should never happen, while executing commands ${currentChunk.join(", ")}, response: ${JSON.stringify(resp)}`); + this.logger().error(`This should never happen, while executing commands ${currentChunk.join(", ")}, response: ${JSON.stringify(resp)}`); } // Use AI to regenerate failed commands const newCommands = await this.getProjectSetupAssistant().generateSetupCommands( `The following failures were reported: ${failures.length > 0 ? JSON.stringify(failures, null, 2) : currentChunk.join(", ")}. The following commands were successful: ${successful.map(r => r.command).join(", ")}` ); if (newCommands?.commands) { - this.logger.info(`Generated new commands: ${newCommands.commands.join(", ")}`); + this.logger().info(`Generated new commands: ${newCommands.commands.join(", ")}`); this.broadcast(WebSocketMessageResponses.COMMAND_EXECUTING, { message: "Executing regenerated commands", commands: newCommands.commands @@ -2017,7 +2021,7 @@ export class SimpleCodeGeneratorAgent extends Agent { failures }); } catch (error) { - this.logger.error('Error executing commands:', error); + this.logger().error('Error executing commands:', error); } } } @@ -2026,12 +2030,12 @@ export class SimpleCodeGeneratorAgent extends Agent { const failedCommands = commands.filter(cmd => !successfulCommands.includes(cmd)); if (failedCommands.length > 0) { - this.logger.warn(`Failed to execute commands: ${failedCommands.join(", ")}`); + this.logger().warn(`Failed to execute commands: ${failedCommands.join(", ")}`); this.broadcast(WebSocketMessageResponses.ERROR, { error: `Failed to execute commands: ${failedCommands.join(", ")}` }); } else { - this.logger.info(`All commands executed successfully: ${successfulCommands.join(", ")}`); + this.logger().info(`All commands executed successfully: ${successfulCommands.join(", ")}`); } // Add commands to history @@ -2054,7 +2058,7 @@ export class SimpleCodeGeneratorAgent extends Agent { */ async exportToGithub(options: GitHubExportOptions): Promise { try { - this.logger.info('Starting GitHub export', { + this.logger().info('Starting GitHub export', { repositoryName: options.repositoryName, isPrivate: options.isPrivate, fileCount: Object.keys(this.state.generatedFilesMap).length @@ -2063,7 +2067,7 @@ export class SimpleCodeGeneratorAgent extends Agent { // Check if we have generated files if (!this.state.generatedFilesMap || Object.keys(this.state.generatedFilesMap).length === 0) { return ErrorHandler.handleGitHubExportError( - this.logger, + this.logger(), this, 'Export failed: No generated code available', 'No generated files available for export' @@ -2073,12 +2077,12 @@ export class SimpleCodeGeneratorAgent extends Agent { // Check if we have a sandbox instance if (!ErrorHandler.validateRunnerInstance( this.state.sandboxInstanceId, - this.logger, + this.logger(), this, 'GitHub export' )) { return ErrorHandler.handleGitHubExportError( - this.logger, + this.logger(), this, 'Export failed: Runner service not available', 'No sandbox instance available for GitHub export' @@ -2103,7 +2107,7 @@ export class SimpleCodeGeneratorAgent extends Agent { const githubIntegration = await this.getGitHubIntegration(options.userId); if (!githubIntegration) { return ErrorHandler.handleGitHubExportError( - this.logger, + this.logger(), this, 'GitHub integration not found', 'User must connect GitHub account first' @@ -2120,7 +2124,7 @@ export class SimpleCodeGeneratorAgent extends Agent { commitMessage: `Export ready\n\n🤖 Generated with Orange Build\n${this.state.blueprint?.title ? `Blueprint: ${this.state.blueprint.title}` : ''}` }; - this.logger.info('Exporting to GitHub repository', { exportRequest }); + this.logger().info('Exporting to GitHub repository', { exportRequest }); // Update progress for creating repository this.broadcast(WebSocketMessageResponses.GITHUB_EXPORT_PROGRESS, { @@ -2134,7 +2138,7 @@ export class SimpleCodeGeneratorAgent extends Agent { if (!exportResult?.success) { return ErrorHandler.handleGitHubExportError( - this.logger, + this.logger(), this, 'Failed to export to GitHub repository', exportResult?.error || 'Failed to export to GitHub repository' @@ -2144,13 +2148,13 @@ export class SimpleCodeGeneratorAgent extends Agent { const repositoryUrl = exportResult.repositoryUrl; if (!repositoryUrl) { return ErrorHandler.handleGitHubExportError( - this.logger, + this.logger(), this, 'Failed to export to GitHub repository', 'Repository URL not found' ); } - this.logger.info('GitHub export completed successfully', { repositoryUrl, commitSha: exportResult.commitSha }); + this.logger().info('GitHub export completed successfully', { repositoryUrl, commitSha: exportResult.commitSha }); // Commit the readme // First prepare the readme by replacing [cloudflarebutton] placeholder with actual thing @@ -2162,12 +2166,12 @@ export class SimpleCodeGeneratorAgent extends Agent { await this.deployToSandbox([readmeFile], false, "feat: README updated with cloudflare deploy button"); // Export again await this.getSandboxServiceClient().exportToGitHub(this.state.sandboxInstanceId!, exportRequest); - this.logger.info('Readme committed successfully'); + this.logger().info('Readme committed successfully'); } catch (error) { - this.logger.error('Failed to commit readme', error); + this.logger().error('Failed to commit readme', error); } } else { - this.logger.info('Readme not found, skipping commit'); + this.logger().info('Readme not found, skipping commit'); } // Step 3: Finalize @@ -2181,7 +2185,7 @@ export class SimpleCodeGeneratorAgent extends Agent { await DatabaseOperations.updateGitHubRepository( this.env, this.state.sessionId || '', - this.logger, + this.logger(), repositoryUrl || '', options.isPrivate ? 'private' : 'public' ); @@ -2192,12 +2196,12 @@ export class SimpleCodeGeneratorAgent extends Agent { repositoryUrl }); - this.logger.info('GitHub export completed successfully', { repositoryUrl }); + this.logger().info('GitHub export completed successfully', { repositoryUrl }); return { success: true, repositoryUrl }; } catch (error) { return ErrorHandler.handleGitHubExportError( - this.logger, + this.logger(), this, 'GitHub export failed due to an unexpected error', error instanceof Error ? error.message : String(error) @@ -2210,7 +2214,7 @@ export class SimpleCodeGeneratorAgent extends Agent { */ private async getGitHubIntegration(userId?: string): Promise<{ accessToken: string; username: string; email: string } | null> { if (!this.env.DB || !userId) { - this.logger.warn('No database or userId provided for GitHub integration lookup'); + this.logger().warn('No database or userId provided for GitHub integration lookup'); return null; } @@ -2230,12 +2234,12 @@ export class SimpleCodeGeneratorAgent extends Agent { .limit(1); if (!result[0]) { - this.logger.warn('No GitHub integration found for user', { userId }); + this.logger().warn('No GitHub integration found for user', { userId }); return null; } const integration = result[0]; - this.logger.info('Retrieved GitHub integration with real email', { + this.logger().info('Retrieved GitHub integration with real email', { userId, username: integration.githubUsername, email: integration.userEmail @@ -2249,7 +2253,7 @@ export class SimpleCodeGeneratorAgent extends Agent { email: integration.userEmail // Real user email from users table }; } catch (error) { - this.logger.error('Error fetching GitHub integration', error); + this.logger().error('Error fetching GitHub integration', error); return null; } } @@ -2264,12 +2268,12 @@ export class SimpleCodeGeneratorAgent extends Agent { */ async handleUserInput(userMessage: string): Promise { try { - this.logger.info('Processing user input message', { + this.logger().info('Processing user input message', { messageLength: userMessage.length, pendingInputsCount: this.state.pendingUserInputs.length }); - const context = GenerationContext.from(this.state, this.logger); + const context = GenerationContext.from(this.state, this.logger()); // Process the user message using conversational assistant const conversationalResponse = await this.operations.processUserMessage.execute( @@ -2284,7 +2288,7 @@ export class SimpleCodeGeneratorAgent extends Agent { }); } }, - { env: this.env, agentId: this.state.sessionId, context, logger: this.logger, inferenceContext: this.state.inferenceContext } + { env: this.env, agentId: this.state.sessionId, context, logger: this.logger(), inferenceContext: this.state.inferenceContext } ); const { conversationResponse, messages } = conversationalResponse; @@ -2310,19 +2314,19 @@ export class SimpleCodeGeneratorAgent extends Agent { if (!this.isGenerating) { // If idle, start generation process - this.logger.info('User input during IDLE state, starting generation'); + this.logger().info('User input during IDLE state, starting generation'); this.generateAllFiles().catch(error => { - this.logger.error('Error starting generation from user input:', error); + this.logger().error('Error starting generation from user input:', error); }); } - this.logger.info('User input processed successfully', { + this.logger().info('User input processed successfully', { responseLength: conversationResponse.userResponse.length, enhancedRequestLength: conversationResponse.enhancedUserRequest.length, }); } catch (error) { - this.logger.error('Error handling user input:', error); + this.logger().error('Error handling user input:', error); this.broadcast(WebSocketMessageResponses.ERROR, { error: `Error processing user input: ${error instanceof Error ? error.message : String(error)}` }); @@ -2341,7 +2345,7 @@ export class SimpleCodeGeneratorAgent extends Agent { try { if (data.status === 'completed') { - await DatabaseOperations.updateApp(this.env, this.state.sessionId, this.logger, { + await DatabaseOperations.updateApp(this.env, this.state.sessionId, this.logger(), { status: 'completed', // deploymentUrl: state.previewURL }); @@ -2349,12 +2353,12 @@ export class SimpleCodeGeneratorAgent extends Agent { await DatabaseOperations.updateDeploymentUrl( this.env, this.state.sessionId, - this.logger, + this.logger(), data.deploymentUrl ); } } catch (error) { - this.logger.error('Failed to update database:', error); + this.logger().error('Failed to update database:', error); } } @@ -2376,7 +2380,7 @@ export class SimpleCodeGeneratorAgent extends Agent { throw new Error(error); } - this.logger.info('Capturing screenshot via REST API', { url, viewport }); + this.logger().info('Capturing screenshot via REST API', { url, viewport }); // Notify start of screenshot capture this.broadcast(WebSocketMessageResponses.SCREENSHOT_CAPTURE_STARTED, { @@ -2449,7 +2453,7 @@ export class SimpleCodeGeneratorAgent extends Agent { await DatabaseOperations.updateAppScreenshot( this.env, this.state.sessionId, - this.logger, + this.logger(), `data:image/png;base64,${base64Screenshot}` ); } catch (dbError) { @@ -2464,7 +2468,7 @@ export class SimpleCodeGeneratorAgent extends Agent { throw new Error(error); } - this.logger.info('Screenshot captured successfully via REST API', { + this.logger().info('Screenshot captured successfully via REST API', { url, screenshotSize: base64Screenshot.length }); @@ -2482,7 +2486,7 @@ export class SimpleCodeGeneratorAgent extends Agent { return `data:image/png;base64,${base64Screenshot}`; } catch (error) { - this.logger.error('Failed to capture screenshot via REST API:', error); + this.logger().error('Failed to capture screenshot via REST API:', error); // Only broadcast if error wasn't already broadcast above const errorMessage = error instanceof Error ? error.message : 'Unknown error'; @@ -2505,7 +2509,7 @@ export class SimpleCodeGeneratorAgent extends Agent { public async saveScreenshotToDatabase(screenshotData: ScreenshotData): Promise { if (!this.env.DB || !this.state.sessionId) { const error = 'Cannot capture screenshot: DB or sessionId not available'; - this.logger.warn(error); + this.logger().warn(error); this.broadcast(WebSocketMessageResponses.SCREENSHOT_CAPTURE_ERROR, { error, url: screenshotData.url, @@ -2522,7 +2526,7 @@ export class SimpleCodeGeneratorAgent extends Agent { screenshotData.viewport ); - this.logger.info('Screenshot captured and saved successfully', { + this.logger().info('Screenshot captured and saved successfully', { url: screenshotData.url, viewport: screenshotData.viewport, timestamp: screenshotData.timestamp, @@ -2539,7 +2543,7 @@ export class SimpleCodeGeneratorAgent extends Agent { // }); } catch (error) { - this.logger.error('Failed to capture and save screenshot:', error); + this.logger().error('Failed to capture and save screenshot:', error); // Error was already broadcast by captureScreenshot method // Don't throw - we don't want screenshot failures to break the generation flow } @@ -2551,7 +2555,7 @@ export class SimpleCodeGeneratorAgent extends Agent { */ async executeTerminalCommand(command: string, connection?: Connection): Promise { try { - this.logger.info('Executing terminal command', { command }); + this.logger().info('Executing terminal command', { command }); // Send server log const serverLogMessage = { @@ -2639,7 +2643,7 @@ export class SimpleCodeGeneratorAgent extends Agent { } } catch (error) { - this.logger.error('Error executing terminal command:', error); + this.logger().error('Error executing terminal command:', error); const errorMessage = { output: `Error: ${error instanceof Error ? error.message : String(error)}`, @@ -2679,7 +2683,7 @@ export class SimpleCodeGeneratorAgent extends Agent { const message = { type, ...data }; connection.send(JSON.stringify(message)); } catch (error) { - this.logger.error('Error sending message to connection:', error); + this.logger().error('Error sending message to connection:', error); } } } diff --git a/worker/agents/core/smartGeneratorAgent.ts b/worker/agents/core/smartGeneratorAgent.ts index 5c7d4629..5358426a 100644 --- a/worker/agents/core/smartGeneratorAgent.ts +++ b/worker/agents/core/smartGeneratorAgent.ts @@ -22,7 +22,7 @@ export class SmartCodeGeneratorAgent extends SimpleCodeGeneratorAgent { initArgs: AgentInitArgs, agentMode: 'deterministic' | 'smart' ): Promise { - this.logger.info('🧠 Initializing SmartCodeGeneratorAgent with enhanced AI orchestration', { + this.logger().info('🧠 Initializing SmartCodeGeneratorAgent with enhanced AI orchestration', { queryLength: initArgs.query.length, agentType: agentMode }); diff --git a/worker/agents/inferutils/config.ts b/worker/agents/inferutils/config.ts index a905e5e2..ce5e66b2 100644 --- a/worker/agents/inferutils/config.ts +++ b/worker/agents/inferutils/config.ts @@ -72,6 +72,7 @@ export const AGENT_CONFIG: AgentConfig = { name: AIModels.GEMINI_2_5_FLASH_LITE, max_tokens: 2000, fallbackModel: AIModels.GEMINI_2_5_FLASH, + temperature: 0.8, }, blueprint: { name: AIModels.GEMINI_2_5_PRO, @@ -84,7 +85,7 @@ export const AGENT_CONFIG: AgentConfig = { name: AIModels.GEMINI_2_5_PRO, reasoning_effort: 'low', max_tokens: 10000, - temperature: 0, + temperature: 0.2, fallbackModel: AIModels.GEMINI_2_5_PRO, }, phaseGeneration: { diff --git a/worker/agents/operations/Guardrail.md b/worker/agents/operations/Guardrail.md new file mode 100644 index 00000000..f4f62a25 --- /dev/null +++ b/worker/agents/operations/Guardrail.md @@ -0,0 +1,53 @@ +import { GuardRailsOutputType, GuardRailsOutput } from '../schemas'; +import { GenerationContext } from '../domain/values/GenerationContext'; +import { IssueReport } from '../domain/values/IssueReport'; +import { createSystemMessage, createUserMessage } from '../inferutils/common'; +import { executeInference } from '../inferutils/infer'; +import { generalSystemPromptBuilder, issuesPromptFormatter, PROMPT_UTILS } from '../prompts'; +import { TemplateRegistry } from '../inferutils/schemaFormatters'; +import { z } from 'zod'; +import { AgentOperation, OperationOptions } from './common'; + +export interface GuardRailsInput { + userInput: string; +} + +const SYSTEM_PROMPT = ``; + +const USER_PROMPT = ``; + +const userPromptFormatter = (issues: IssueReport, context: string) => { + const prompt = USER_PROMPT + .replaceAll('{{issues}}', issuesPromptFormatter(issues)) + .replaceAll('{{context}}', context); + return PROMPT_UTILS.verifyPrompt(prompt); +} + +export class GuardRailsOperation extends AgentOperation { + async execute( + inputs: GuardRailsInput, + options: OperationOptions + ): Promise { + const { userInput } = inputs; + const { env, logger, context } = options; + try { + const { object: reviewResult } = await executeInference({ + env: env, + messages, + schema: CodeReviewOutput, + agentActionName: "codeReview", + context: options.inferenceContext, + reasoning_effort: issues.runtimeErrors.length || issues.staticAnalysis.lint.issues.length || issues.staticAnalysis.typecheck.issues.length > 0 ? undefined : 'low', + // format: 'markdown' + }); + + if (!reviewResult) { + throw new Error("Failed to get code review result"); + } + return reviewResult; + } catch (error) { + logger.error("Error during code review:", error); + throw error; + } + } +} \ No newline at end of file diff --git a/worker/agents/planning/blueprint.ts b/worker/agents/planning/blueprint.ts index f97d6a1e..fd82fe09 100644 --- a/worker/agents/planning/blueprint.ts +++ b/worker/agents/planning/blueprint.ts @@ -268,7 +268,7 @@ export async function generateBlueprint({ env, inferenceContext, query, language ]; // Log messages to console for debugging - logger.info('Blueprint messages:', JSON.stringify(messages, null, 2)); + // logger.info('Blueprint messages:', JSON.stringify(messages, null, 2)); // let reasoningEffort: "high" | "medium" | "low" | undefined = "medium" as const; // if (templateMetaInfo?.complexity === 'simple' || templateMetaInfo?.complexity === 'moderate') { diff --git a/worker/agents/planning/templateSelector.ts b/worker/agents/planning/templateSelector.ts index b9417a3b..8d9da1f0 100644 --- a/worker/agents/planning/templateSelector.ts +++ b/worker/agents/planning/templateSelector.ts @@ -55,18 +55,21 @@ export async function selectTemplate({ env, query, availableTemplates, inference User: "Build a 2D puzzle game with scoring" Templates: ["react-dashboard", "react-game-starter", "vue-blog"] Selection: "react-game-starter" +complexity: "simple" Reasoning: "Game starter template provides canvas setup, state management, and scoring systems" **Example 2 - Business Dashboard:** User: "Create an analytics dashboard with charts" Templates: ["react-dashboard", "nextjs-blog", "vanilla-js"] Selection: "react-dashboard" +complexity: "simple" // Because single page application Reasoning: "Dashboard template includes chart components, grid layouts, and data visualization setup" **Example 3 - No Perfect Match:** User: "Build a recipe sharing app" Templates: ["react-social", "vue-blog", "angular-todo"] Selection: "react-social" +complexity: "simple" // Because single page application Reasoning: "Social template provides user interactions, content sharing, and community features closest to recipe sharing needs" ## SELECTION CRITERIA: @@ -96,7 +99,7 @@ ${templateDescriptions} **Task:** Select the most suitable template and provide: 1. Template name (exact match from list) 2. Clear reasoning for why it fits the user's needs -3. Appropriate style for the project type +3. Appropriate style for the project type. Try to come up with unique styles that might look nice and unique 4. Descriptive project name Analyze each template's features, frameworks, and architecture to make the best match.`; diff --git a/worker/logger.ts b/worker/logger.ts index 1e0fb68b..abc543ee 100644 --- a/worker/logger.ts +++ b/worker/logger.ts @@ -1,19 +1,17 @@ /** - * Advanced Structured Logging System - Legacy Compatibility Layer - * This file provides backward compatibility while using the new structured logging system + * Simplified Structured Logging System - Compatibility Layer + * Clean, type-safe replacement for complex legacy logging system */ -// Re-export new logging system -export { createLogger, createObjectLogger, Logger, Context, Trace, LogMethod, WithLogger } from './logger/index'; -export type { LogContext, ObjectContext, LoggerConfig } from './logger/index'; +// Re-export new simplified logging system +export { createLogger, createObjectLogger, Logger, StructuredLogger, LoggerFactory } from './logger/index'; +export { Context, Trace, LogMethod, WithLogger } from './logger/index'; +export type { LoggerConfig, ObjectContext, LogEntry } from './logger/index'; -// Legacy compatibility - old LogLevel enum (deprecated) +// Legacy compatibility - old LogLevel enum (deprecated but maintained for compatibility) export enum LogLevel { - DEBUG = 'debug', - INFO = 'info', - WARN = 'warn', - ERROR = 'error' -} - -// For backward compatibility, also export the StructuredLogger class directly -export { StructuredLogger } from './logger/core'; \ No newline at end of file + DEBUG = 'debug', + INFO = 'info', + WARN = 'warn', + ERROR = 'error' +} \ No newline at end of file diff --git a/worker/logger/context.ts b/worker/logger/context.ts deleted file mode 100644 index d912853f..00000000 --- a/worker/logger/context.ts +++ /dev/null @@ -1,390 +0,0 @@ -/** - * Context Management for Structured Logging - * Handles automatic context injection and tracing - */ - -import { type LogContext, type ObjectContext } from './types'; - -/** - * Request-scoped context store for proper request isolation in Cloudflare Workers - * Each request gets its own isolated context to prevent contamination - */ -class ContextStore { - private contextStack: LogContext[] = []; - private currentContext: LogContext = {}; - private readonly requestId: string; - - constructor(requestId: string) { - this.requestId = requestId; - this.currentContext = { - requestId, - timestamp: new Date().toISOString() - }; - } - - /** - * Get the request ID for this context store - */ - getRequestId(): string { - return this.requestId; - } - - /** - * Get request-scoped context store (no more singleton!) - */ - static getRequestStore(requestId: string): ContextStore { - return new ContextStore(requestId); - } - - /** - * Set the current log context - */ - setContext(context: Partial): void { - this.currentContext = { ...this.currentContext, ...context }; - } - - /** - * Get the current log context - */ - getContext(): LogContext { - return { ...this.currentContext }; - } - - /** - * Push a new context layer (for nested operations) - */ - pushContext(context: Partial): void { - this.contextStack.push({ ...this.currentContext }); - this.setContext(context); - } - - /** - * Pop the most recent context layer - */ - popContext(): void { - const previous = this.contextStack.pop(); - if (previous) { - this.currentContext = previous; - } - } - - /** - * Execute a function with a specific context - */ - withContext(context: Partial, fn: () => T): T { - this.pushContext(context); - try { - return fn(); - } finally { - this.popContext(); - } - } - - /** - * Execute an async function with a specific context - */ - async withContextAsync(context: Partial, fn: () => Promise): Promise { - this.pushContext(context); - try { - return await fn(); - } finally { - this.popContext(); - } - } - - /** - * Clear all context - */ - clearContext(): void { - this.currentContext = {}; - this.contextStack = []; - } -} - -// Request-scoped context store implementation -// This will be replaced with proper request-scoped stores -let currentContextStore: ContextStore | null = null; - -export const contextStore = { - setContext: (context: Partial) => { - if (currentContextStore) { - currentContextStore.setContext(context); - } - }, - getContext: (): LogContext => { - return currentContextStore ? currentContextStore.getContext() : {}; - }, - pushContext: (context: Partial) => { - if (currentContextStore) { - currentContextStore.pushContext(context); - } - }, - popContext: () => { - if (currentContextStore) { - currentContextStore.popContext(); - } - }, - initializeForRequest: (requestId: string) => { - currentContextStore = ContextStore.getRequestStore(requestId); - } -}; - -/** - * Request-Scoped Context Manager for Production-Grade Distributed Tracing - * Handles automatic trace propagation across all objects touched during request flow - */ -export class RequestContextManager { - private traceId: string; - private spanCounter = 0; - private objectRegistry = new Map(); - - constructor(requestId?: string) { - this.traceId = requestId || `trace-${Date.now()}-${this.generateId()}`; - } - - /** - * Start a new request context - called at API endpoint entry - * Initialize request-scoped context store for distributed tracing - */ - static startRequest(requestId?: string, metadata?: Record): RequestContextManager { - const manager = new RequestContextManager(requestId); - const rootSpanId = manager.generateSpanId(); - - // Initialize request-scoped context store - const actualRequestId = requestId || manager.traceId; - contextStore.initializeForRequest(actualRequestId); - - const context: LogContext = { - traceId: manager.traceId, - spanId: rootSpanId, - parentSpanId: undefined, - requestId: actualRequestId, - metadata: metadata || {}, - timestamp: new Date().toISOString(), - operation: 'request_start' - }; - - contextStore.setContext(context); - return manager; - } - - /** - * Register an object in the current request context with full ancestry tracking - */ - registerObject(obj: any, parent?: ObjectContext): ObjectContext { - const objectContext = ObjectContextBuilder.fromObject(obj, parent); - - // Generate unique object ID if not present - if (!objectContext.id) { - const type = objectContext.type || 'unknown'; - objectContext.id = `${type.toLowerCase()}-${Date.now()}${Math.random().toString(36).substr(2, 6)}`; - } - - // Store in registry for ancestry tracking - this.objectRegistry.set(objectContext.id, objectContext); - - return objectContext; - } - - /** - * Start a new span within the current trace - */ - startSpan(operation: string, objectContext?: ObjectContext): string { - const spanId = this.generateSpanId(); - const currentContext = contextStore.getContext(); - - const spanContext: Partial = { - spanId, - parentSpanId: currentContext.spanId, - operation, - objectContext - }; - - contextStore.pushContext(spanContext); - return spanId; - } - - /** - * End the current span - */ - endSpan(): void { - contextStore.popContext(); - } - - /** - * Execute a function within a span with automatic object context - */ - withSpan(operation: string, obj: any, fn: () => T): T { - const objectContext = this.registerObject(obj); - this.startSpan(operation, objectContext); - try { - return fn(); - } finally { - this.endSpan(); - } - } - - /** - * Execute an async function within a span with automatic object context - */ - async withSpanAsync(operation: string, obj: any, fn: () => Promise): Promise { - const objectContext = this.registerObject(obj); - this.startSpan(operation, objectContext); - try { - return await fn(); - } finally { - this.endSpan(); - } - } - - /** - * Get current trace ID - */ - getCurrentTraceId(): string { - return this.traceId; - } - - // /** - // * Build complete ancestry chain for an object (parent -> grandparent -> great-grandparent) - // */ - // private buildAncestryChain(objectContext: ObjectContext): ObjectContext[] { - // const chain: ObjectContext[] = [objectContext]; - // let current = objectContext.parent; - - // while (current) { - // chain.unshift(current); - // current = current.parent; - // } - - // return chain; - // } - - private generateSpanId(): string { - this.spanCounter++; - return `span-${this.traceId}-${this.spanCounter.toString(36).padStart(7, '0')}`; - } - - private generateId(): string { - return Math.random().toString(36).substr(2, 9); - } -} - -/** - * Object context builder for automatic metadata injection - */ -export class ObjectContextBuilder { - /** - * Create object context from an object instance - SAFE for Cloudflare Workers - */ - static fromObject(obj: any, parent?: ObjectContext): ObjectContext { - const context: ObjectContext = { - type: obj?.constructor?.name || typeof obj - }; - - // Try to extract ID from common properties - SAFE access - try { - if (obj.id) context.id = String(obj.id); - else if (obj._id) context.id = String(obj._id); - else if (obj.uuid) context.id = String(obj.uuid); - else if (obj.guid) context.id = String(obj.guid); - // Don't automatically generate ID - leave it undefined if not found - } catch (e) { - // If ID extraction fails, leave context.id undefined - // This will be handled by the logger when needed - } - - // Add parent reference - if (parent) { - context.parent = parent; - } - - // Extract additional metadata - SAFE access with try/catch - const meta: Record = {}; - - try { - // Safe property access with error handling - // if (obj.hasOwnProperty('name') && obj.name !== undefined) { - // meta.name = obj.name; - // } - if (obj.hasOwnProperty('version') && obj.version !== undefined) { - meta.version = obj.version; - } - if (obj.hasOwnProperty('status') && obj.status !== undefined) { - meta.status = obj.status; - } - if (obj.hasOwnProperty('state') && obj.state !== undefined) { - meta.state = obj.state; - } - - // Add object type information - meta.objectType = context.type; - meta.created = new Date().toISOString(); - - } catch (e) { - // If metadata extraction fails, just use basic info - meta.objectType = context.type; - meta.created = new Date().toISOString(); - meta.error = 'metadata_extraction_failed'; - } - - if (Object.keys(meta).length > 0) { - context.meta = meta; - } - - return context; - } - - /** - * Create object context manually - */ - static create(id: string, type: string, meta?: Record, parent?: ObjectContext): ObjectContext { - return { - id, - type, - meta, - parent - }; - } -} - -/** - * Utility functions for context management - */ -export const ContextUtils = { - /** - * Start a new request context with automatic trace propagation - */ - startRequest(requestId?: string, userId?: string, sessionId?: string): RequestContextManager { - const manager = RequestContextManager.startRequest(requestId, { - userId, - sessionId, - requestStart: new Date().toISOString() - }); - - return manager; - }, - - /** - * Set component context - */ - setComponent(component: string, operation?: string): void { - contextStore.setContext({ component, operation }); - }, - - /** - * Add metadata to current context - */ - addMetadata(metadata: Record): void { - const current = contextStore.getContext(); - contextStore.setContext({ - metadata: { ...current.metadata, ...metadata } - }); - }, - - /** - * Get current context - */ - getContext(): LogContext { - return contextStore.getContext(); - } -}; diff --git a/worker/logger/core.ts b/worker/logger/core.ts index 9d57a082..78d123f5 100644 --- a/worker/logger/core.ts +++ b/worker/logger/core.ts @@ -1,128 +1,115 @@ /** - * Production-Grade Structured Logging System - * Clean, modular, high-performance logger for Cloudflare Workers - * - * Features: - * - Automatic object context injection with full inheritance chains - * - Distributed tracing with correlation IDs - * - Performance monitoring with built-in timing - * - Hierarchical parent-child-grandparent relationships - * - Zero external dependencies - * - Universal compatibility (Workers, Node.js, browsers) + * Simplified Structured Logger for Cloudflare Workers + * Type-safe, efficient logging with JSON output for optimal indexing */ -import type { LoggerConfig, ObjectContext } from './types'; -import { contextStore, ObjectContextBuilder } from './context'; +import type { LoggerConfig, ObjectContext, LogEntry, LogLevel } from './types'; -/** - * Default configuration optimized for production - */ const DEFAULT_CONFIG: LoggerConfig = { level: 'info', - prettyPrint: false, - enableTiming: false, // Disabled by default as requested - enableTracing: true, - serializers: {}, - formatters: {} + prettyPrint: false +}; + +const LOG_LEVELS: Record = { + debug: 0, + info: 1, + warn: 2, + error: 3 }; /** - * High-performance timing system for operation measurement + * Simplified StructuredLogger - Clean, efficient, type-safe */ -class TimingManager { - private activeTimers = new Map(); - private completedOperations = new Map(); +export class StructuredLogger { + private readonly component: string; + private objectContext?: ObjectContext; + private readonly config: LoggerConfig; + private additionalFields: Record = {}; + + constructor(component: string, objectContext?: ObjectContext, config?: LoggerConfig) { + this.component = component; + this.objectContext = objectContext ? { ...objectContext } : undefined; + this.config = { ...DEFAULT_CONFIG, ...config }; + } /** - * Start timing an operation + * Set the object ID dynamically */ - startTiming(operation: string): void { - this.activeTimers.set(operation, Date.now()); + setObjectId(id: string): void { + if (!this.objectContext) { + this.objectContext = { type: this.component, id }; + } else { + this.objectContext.id = id; + } } /** - * End timing and return duration + * Set additional fields that will be included in all log entries */ - endTiming(operation: string): number | null { - const startTime = this.activeTimers.get(operation); - if (!startTime) return null; - - const duration = Date.now() - startTime; - this.activeTimers.delete(operation); - this.completedOperations.set(operation, duration); - return duration; + setFields(fields: Record): void { + this.additionalFields = { ...this.additionalFields, ...fields }; } /** - * Get all active timing operations + * Set a single field */ - getActiveTiming(): Record { - const result: Record = {}; - const now = Date.now(); - - for (const [operation, startTime] of this.activeTimers.entries()) { - result[operation] = now - startTime; - } + setField(key: string, value: unknown): void { + this.additionalFields[key] = value; + } + + /** + * Clear all additional fields + */ + clearFields(): void { + this.additionalFields = {}; + } + + /** + * Create a child logger with extended context + */ + child(childContext: Partial, component?: string): StructuredLogger { + const newComponent = component || this.component; + const mergedContext: ObjectContext = { + type: childContext.type || 'ChildLogger', + id: childContext.id || `child-${Date.now()}`, + ...childContext + }; - return result; + const childLogger = new StructuredLogger(newComponent, mergedContext, this.config); + childLogger.setFields(this.additionalFields); + return childLogger; } /** - * Get timing context for logs + * Check if message should be logged based on level */ - getTimingContext() { - const activeOperations = this.getActiveTiming(); - return Object.keys(activeOperations).length > 0 ? { activeOperations } : null; + private shouldLog(level: LogLevel): boolean { + const configLevel = LOG_LEVELS[this.config.level || 'info']; + const messageLevel = LOG_LEVELS[level]; + return messageLevel >= configLevel; } -} -/** - * Context enrichment system for automatic metadata injection - */ -class ContextEnricher { /** - * Build complete log context with all available metadata + * Core logging method - builds structured JSON and outputs via console */ - static enrichLogEntry( - level: string, - message: string, - component: string, - objectContext?: ObjectContext, - data?: Record, - error?: Error, - timingContext?: any - ): Record { - const timestamp = new Date().toISOString(); - const traceContext = contextStore.getContext(); - - // Base log entry - const logEntry: Record = { + private log(level: LogLevel, message: string, data?: Record, error?: Error): void { + if (!this.shouldLog(level)) return; + + const logEntry: LogEntry = { level, - time: timestamp, - component, + time: new Date().toISOString(), + component: this.component, msg: message }; - // Add object context with full inheritance chain - if (objectContext) { - logEntry.object = this.buildObjectChain(objectContext); - } - - // Add distributed tracing context - if (traceContext && (traceContext.traceId || traceContext.requestId)) { - logEntry.context = { - ...(traceContext.traceId && { traceId: traceContext.traceId }), - ...(traceContext.spanId && { spanId: traceContext.spanId }), - ...(traceContext.parentSpanId && { parentSpanId: traceContext.parentSpanId }), - ...(traceContext.requestId && { requestId: traceContext.requestId }), - ...(traceContext.sessionId && { sessionId: traceContext.sessionId }), - ...(traceContext.userId && { userId: traceContext.userId }) - }; + // Add object context if available + if (this.objectContext) { + logEntry.object = { ...this.objectContext }; } - // Add timing information - if (timingContext) { - logEntry.timing = timingContext; + // Add additional fields + if (Object.keys(this.additionalFields).length > 0) { + Object.assign(logEntry, this.additionalFields); } // Add structured data @@ -130,364 +117,81 @@ class ContextEnricher { Object.assign(logEntry, data); } - // Add error details with full stack trace - if (error && error instanceof Error) { + // Add error details + if (error instanceof Error) { logEntry.error = { name: error.name, message: error.message, stack: error.stack }; - - // Add cause if available (safe spread) - if (error.cause) { - logEntry.error.cause = error.cause; - } } - return logEntry; + // Output using appropriate method + this.output(level, logEntry); } /** - * Build complete parent-child-grandparent object relationship chain + * Output log entry using console methods */ - private static buildObjectChain(objectContext: ObjectContext | undefined): any { - if (!objectContext) { - return { type: 'Unknown', id: 'unknown' }; - } - const chain: any = { - type: objectContext.type, - id: objectContext.id - }; + private output(level: LogLevel, logEntry: LogEntry): void { + const consoleMethod = this.getConsoleMethod(level); - // Add metadata if available - if (objectContext.meta && Object.keys(objectContext.meta).length > 0) { - chain.meta = objectContext.meta; - } - - // Build complete parent chain (parent -> grandparent -> great-grandparent...) - if (objectContext.parent) { - const parents: any[] = []; - let currentParent: ObjectContext | undefined = objectContext.parent; + if (this.config.prettyPrint) { + // Pretty formatted output for development + const objectInfo = logEntry.object ? + `[${logEntry.object.type}${logEntry.object.id ? `#${logEntry.object.id}` : ''}]` : ''; + const prefix = `${logEntry.time} ${level.toUpperCase()} ${logEntry.component}${objectInfo}`; - while (currentParent) { - parents.push({ - type: currentParent.type, - id: currentParent.id, - ...(currentParent.meta && { meta: currentParent.meta }) - }); - currentParent = currentParent.parent; // TypeScript now knows this can be undefined - } + // Extract additional data excluding base fields + const { level: _, time, component, msg, object, error, ...additionalData } = logEntry; + const hasAdditionalData = Object.keys(additionalData).length > 0; - if (parents.length > 0) { - chain.parents = parents; - chain.parentChain = parents.map(p => `${p.type}#${p.id}`).join(' -> '); - } - } - - return chain; - } -} - -/** - * Output manager for different environments and formats - */ -class OutputManager { - /** - * Output log entry using appropriate method for environment - */ - static output(level: string, logEntry: Record, config: LoggerConfig): void { - const isDevelopment = config.prettyPrint || - (typeof process !== 'undefined' && process.env?.NODE_ENV === 'development'); - - if (isDevelopment) { - this.prettyOutput(level, logEntry); - } else { - this.jsonOutput(level, logEntry); - } - } - - /** - * Pretty formatted output for development - */ - private static prettyOutput(level: string, logEntry: Record): void { - const timestamp = logEntry.time; - const component = logEntry.component; - const objectInfo = logEntry.object ? - `[${logEntry.object.type}#${logEntry.object.id}${logEntry.object.parentChain ? ` <- ${logEntry.object.parentChain}` : ''}]` : ''; - const traceInfo = logEntry.context?.traceId ? - `trace:${logEntry.context.traceId.slice(-8)}` : ''; - - const prefix = `${timestamp} ${level.toUpperCase()} ${component}${objectInfo} ${traceInfo}`.trim(); - const message = logEntry.msg; - - // Choose appropriate console method - const consoleMethod = level === 'error' ? 'error' : - level === 'warn' ? 'warn' : - level === 'debug' ? 'debug' : 'log'; - - // Extract additional structured data - const { level: _, time, component: __, msg, object, context, error, timing, ...additionalData } = logEntry; - - const hasAdditionalData = Object.keys(additionalData).length > 0; - const hasError = !!error; - const hasTiming = !!timing; - - if (hasAdditionalData || hasError || hasTiming) { - const extraInfo: any = {}; - if (hasAdditionalData) Object.assign(extraInfo, additionalData); - if (hasTiming) extraInfo.timing = timing; - if (hasError) extraInfo.error = error; - - console[consoleMethod](`${prefix}: ${message}`, extraInfo); - } else { - console[consoleMethod](`${prefix}: ${message}`); - } - } - - /** - * Structured JSON output for production - */ - private static jsonOutput(level: string, logEntry: Record): void { - const consoleMethod = level === 'error' ? 'error' : - level === 'warn' ? 'warn' : - level === 'debug' ? 'debug' : 'log'; - - console[consoleMethod](JSON.stringify(logEntry)); - } -} - -/** - * Main StructuredLogger class - Production-grade logging with all requested features - */ -export class StructuredLogger { - private readonly component: string; - private objectContext?: ObjectContext; // Made mutable for dynamic field setting - private readonly config: LoggerConfig; - private readonly timingManager: TimingManager; - - constructor(component: string, objectContext?: ObjectContext, config?: LoggerConfig) { - this.component = component; - this.objectContext = objectContext; - this.config = { ...DEFAULT_CONFIG, ...config }; - this.timingManager = new TimingManager(); - - // Automatically start tracing if not already active - this.ensureTraceContext(); - } - - /** - * Ensure trace context exists - automatic tracing setup - * Generate unique spans per component while preserving request-level trace context - */ - private ensureTraceContext(): void { - const currentContext = contextStore.getContext(); - - if (this.config.enableTracing) { - if (!currentContext.traceId) { - // New request: generate fresh trace context - const traceId = this.generateTraceId(); - const spanId = this.generateSpanId(); - - contextStore.setContext({ - traceId, - spanId, - component: this.component, - operation: 'request_start', - metadata: { - timestamp: new Date().toISOString(), - requestStart: true - } - }); + if (hasAdditionalData || error) { + const extraInfo: Record = {}; + if (hasAdditionalData) Object.assign(extraInfo, additionalData); + if (error) extraInfo.error = error; + console[consoleMethod](`${prefix}: ${msg}`, extraInfo); } else { - // Existing request: create new span within current trace - const newSpanId = this.generateSpanId(); - - contextStore.setContext({ - ...currentContext, - spanId: newSpanId, - parentSpanId: currentContext.spanId, - component: this.component, - operation: `${this.component}_operation`, - metadata: { - ...currentContext.metadata, - timestamp: new Date().toISOString(), - componentStart: true - } - }); + console[consoleMethod](`${prefix}: ${msg}`); } + } else { + // Structured JSON output for production (optimal for Cloudflare Workers Logs) + console[consoleMethod](JSON.stringify(logEntry)); } } /** - * Generate unique trace ID - */ - private generateTraceId(): string { - return `trace-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`; - } - - /** - * Generate unique span ID with microsecond precision and component info - */ - private generateSpanId(): string { - const timestamp = Date.now(); - const microseconds = performance.now().toString().replace('.', ''); - const randomId = Math.random().toString(36).substring(2, 9); - const componentId = this.component.toLowerCase().substring(0, 3); - return `span-${timestamp}-${microseconds}-${componentId}-${randomId}`; - } - - /** - * Core logging method with complete context enrichment - */ - private log(level: string, message: string, data?: Record, error?: Error): void { - // Check log level filtering - if (!this.shouldLog(level)) return; - - // Get timing context if enabled - const timingContext = this.config.enableTiming ? - this.timingManager.getTimingContext() : null; - - // Build enriched log entry with all context - const logEntry = ContextEnricher.enrichLogEntry( - level, - message, - this.component, - this.objectContext, - data, - error, - timingContext - ); - - // Output using appropriate formatter - OutputManager.output(level, logEntry, this.config); - } - - /** - * Check if message should be logged based on level - */ - private shouldLog(level: string): boolean { - const levels = { trace: 0, debug: 1, info: 2, warn: 3, error: 4, fatal: 5 }; - const configLevel = levels[this.config.level as keyof typeof levels] ?? 2; - const messageLevel = levels[level as keyof typeof levels] ?? 2; - return messageLevel >= configLevel; - } - - // ======================================================================== - // PUBLIC LOGGING METHODS - // ======================================================================== - - // ======================================================================== - // DYNAMIC FIELD MANAGEMENT - // ======================================================================== - - /** - * Set additional fields that will be included in all log entries - */ - setFields(fields: Record): void { - if (!this.objectContext) { - this.objectContext = { type: 'DynamicLogger', id: 'dynamic-' + Date.now() }; - } - this.objectContext.meta = { ...this.objectContext.meta, ...fields }; - } - - /** - * Set a single field - */ - setField(key: string, value: any): void { - this.setFields({ [key]: value }); - } - - /** - * Clear all dynamic fields - */ - clearFields(): void { - if (this.objectContext?.meta) { - this.objectContext.meta = {}; - } - } - - /** - * Set the object context (used internally for safe initialization) + * Get appropriate console method for log level */ - setObjectContext(context: ObjectContext): void { - this.objectContext = context; - } - - /** - * Set or update the object ID in the context - */ - setObjectId(id: string): void { - if (this.objectContext) { - this.objectContext.id = id; + private getConsoleMethod(level: LogLevel): 'debug' | 'log' | 'warn' | 'error' { + switch (level) { + case 'debug': return 'debug'; + case 'info': return 'log'; + case 'warn': return 'warn'; + case 'error': return 'error'; + default: return 'log'; } } - // ======================================================================== - // FLEXIBLE LOGGING METHODS - Accept any argument types naturally - // ======================================================================== - - /** - * Trace level logging - accepts any arguments - */ - trace(message: string, ...args: any[]): void { - this.log('trace', message, this.processArgs(args)); - } - - /** - * Debug level logging - accepts any arguments - */ - debug(message: string, ...args: any[]): void { - this.log('debug', message, this.processArgs(args)); - } - /** - * Info level logging - accepts any arguments + * Process variable arguments into structured data */ - info(message: string, ...args: any[]): void { - this.log('info', message, this.processArgs(args)); - } - - /** - * Warning level logging - accepts any arguments - */ - warn(message: string, ...args: any[]): void { - this.log('warn', message, this.processArgs(args)); - } - - /** - * Error level logging - accepts any arguments naturally - */ - error(message: string, ...args: any[]): void { - const { data, error } = this.processArgsWithError(args); - this.log('error', message, data, error); - } - - /** - * Fatal level logging - accepts any arguments - */ - fatal(message: string, ...args: any[]): void { - const { data, error } = this.processArgsWithError(args); - this.log('fatal', message, data, error); - } - - /** - * Process arguments intelligently for flexible API - */ - private processArgs(args: any[]): Record { + private processArgs(args: unknown[]): Record { if (args.length === 0) return {}; + if (args.length === 1) { const arg = args[0]; - if (typeof arg === 'object' && arg !== null && !Array.isArray(arg) && !(arg instanceof Error)) { - return arg; + if (arg && typeof arg === 'object' && !Array.isArray(arg) && !(arg instanceof Error)) { + return arg as Record; } return { data: arg }; } - // Multiple arguments - convert to structured data - const result: Record = {}; + // Multiple arguments + const result: Record = {}; args.forEach((arg, index) => { - if (typeof arg === 'object' && arg !== null && !Array.isArray(arg) && !(arg instanceof Error)) { - Object.assign(result, arg); + if (arg && typeof arg === 'object' && !Array.isArray(arg) && !(arg instanceof Error)) { + Object.assign(result, arg as Record); } else { result[`arg${index}`] = arg; } @@ -496,13 +200,12 @@ export class StructuredLogger { } /** - * Process arguments with special error handling + * Process arguments with error handling */ - private processArgsWithError(args: any[]): { data: Record; error?: Error } { + private processArgsWithError(args: unknown[]): { data: Record; error?: Error } { let error: Error | undefined; - const otherArgs: any[] = []; + const otherArgs: unknown[] = []; - // Separate Error objects from other arguments args.forEach(arg => { if (arg instanceof Error) { error = arg; @@ -517,121 +220,34 @@ export class StructuredLogger { }; } - // ======================================================================== - // OPTIONAL PERFORMANCE TIMING METHODS (disabled by default) - // ======================================================================== - - /** - * Start timing an operation (only if timing enabled) - */ - time(operation: string, metadata?: Record): void { - if (this.config.enableTiming) { - this.timingManager.startTiming(operation); - if (metadata) { - this.debug(`Starting operation: ${operation}`, metadata); - } - } - } - - /** - * End timing an operation and log the duration (only if timing enabled) - */ - timeEnd(operation: string, data?: Record): void { - if (this.config.enableTiming) { - const duration = this.timingManager.endTiming(operation); - if (duration !== null) { - this.info(`Operation completed: ${operation}`, { - duration: `${duration}ms`, - durationMs: duration, - ...data - }); - } - } + // Public logging methods + debug(message: string, ...args: unknown[]): void { + this.log('debug', message, this.processArgs(args)); } - /** - * Measure synchronous function execution time (only if timing enabled) - */ - measure(operation: string, fn: () => T, metadata?: Record): T { - if (this.config.enableTiming) { - this.time(operation, metadata); - try { - const result = fn(); - this.timeEnd(operation, { success: true }); - return result; - } catch (error) { - this.timeEnd(operation, { success: false }); - throw error; - } - } else { - return fn(); - } + info(message: string, ...args: unknown[]): void { + this.log('info', message, this.processArgs(args)); } - /** - * Measure asynchronous function execution time (only if timing enabled) - */ - async measureAsync(operation: string, fn: () => Promise, metadata?: Record): Promise { - if (this.config.enableTiming) { - this.time(operation, metadata); - try { - const result = await fn(); - this.timeEnd(operation, { success: true }); - return result; - } catch (error) { - this.timeEnd(operation, { success: false }); - throw error; - } - } else { - return await fn(); - } + warn(message: string, ...args: unknown[]): void { + this.log('warn', message, this.processArgs(args)); } - // ======================================================================== - // HIERARCHICAL LOGGER CREATION - // ======================================================================== - - /** - * Create child logger with extended context - */ - child(childContext: Partial, component?: string): StructuredLogger { - const newComponent = component || this.component; - - // Merge contexts while preserving parent chain - const mergedContext: ObjectContext = { - type: childContext.type || 'ChildLogger', - id: childContext.id || 'child-' + Date.now(), - ...childContext, - parent: this.objectContext // Maintain parent chain - }; - - return new StructuredLogger(newComponent, mergedContext, this.config); + error(message: string, ...args: unknown[]): void { + const { data, error } = this.processArgsWithError(args); + this.log('error', message, data, error); } - /** - * Create logger for specific object with automatic context extraction - */ - forObject(obj: any, component?: string): StructuredLogger { - const objectContext = ObjectContextBuilder.fromObject(obj, this.objectContext); - return new StructuredLogger( - component || this.component, - objectContext, - this.config - ); + // Legacy compatibility methods + trace(message: string, ...args: unknown[]): void { + this.debug(message, ...args); } - /** - * Get underlying logger instance (compatibility method) - */ - getPinoLogger(): StructuredLogger { - return this; + fatal(message: string, ...args: unknown[]): void { + this.error(message, ...args); } } -// ============================================================================ -// FACTORY FUNCTIONS -// ============================================================================ - /** * Create a basic structured logger */ @@ -640,75 +256,64 @@ export function createLogger(component: string, config?: LoggerConfig): Structur } /** - * Create logger with safe object context injection for Cloudflare Workers - * Handles uninitialized objects gracefully + * Create logger with object context */ export function createObjectLogger( - obj: any, + obj: unknown, component?: string, config?: LoggerConfig ): StructuredLogger { - // Create logger first, then set object context safely - const componentName = component || safeGetConstructorName(obj) || 'UnknownComponent'; - const logger = new StructuredLogger(componentName, undefined, config); + const componentName = component || getObjectType(obj) || 'UnknownComponent'; - // Set object context safely after creation WITHOUT automatic parent relationships - try { - // Build object context without automatically inheriting parent from trace context - // This prevents incorrect parent-child relationships between unrelated objects - const objectContext = ObjectContextBuilder.fromObject(obj, undefined); - - logger.setObjectContext(objectContext); - - } catch (error) { - // If object isn't fully initialized, set basic context - const basicContext = { - type: componentName, - id: `${componentName.toLowerCase()}-${Date.now()}`, - meta: { - initialized: false, - error: 'object_context_creation_failed' - } - }; - logger.setObjectContext(basicContext); + // Create basic object context without complex probing + const objectContext: ObjectContext = { + type: componentName + }; + + // Try to get ID safely + if (obj && typeof obj === 'object') { + const objWithId = obj as Record; + if (objWithId.id && (typeof objWithId.id === 'string' || typeof objWithId.id === 'number')) { + objectContext.id = String(objWithId.id); + } } - - return logger; + + return new StructuredLogger(componentName, objectContext, config); } /** - * Safely get constructor name without triggering Cloudflare Workers errors + * Safely get object type */ -function safeGetConstructorName(obj: any): string | undefined { +function getObjectType(obj: unknown): string | undefined { try { - return obj?.constructor?.name; + if (obj && typeof obj === 'object') { + return (obj as Record).constructor?.name; + } + return typeof obj; } catch { return undefined; } } /** - * Logger factory for creating loggers with consistent configuration + * Logger factory for global configuration */ export class LoggerFactory { - private static globalConfig: LoggerConfig = { - level: process.env.LOG_LEVEL as any || 'info', - prettyPrint: process.env.NODE_ENV !== 'production', - enableTiming: true, - enableTracing: true - }; + private static globalConfig: LoggerConfig = DEFAULT_CONFIG; - /** - * Set global logger configuration - */ static configure(config: Partial): void { this.globalConfig = { ...this.globalConfig, ...config }; } - /** - * Get current global configuration - */ static getConfig(): LoggerConfig { return { ...this.globalConfig }; } -} + + static create(component: string): StructuredLogger { + return new StructuredLogger(component, undefined, this.globalConfig); + } + + static createForObject(obj: unknown, component?: string): StructuredLogger { + return createObjectLogger(obj, component, this.globalConfig); + } +} \ No newline at end of file diff --git a/worker/logger/index.ts b/worker/logger/index.ts index a8994c5e..30cb4d3e 100644 --- a/worker/logger/index.ts +++ b/worker/logger/index.ts @@ -1,25 +1,21 @@ /** - * Advanced Structured Logging System - Main Export - * Production-grade logging with automatic context injection and tracing + * Simplified Structured Logging System - Main Export + * Clean, type-safe logging for Cloudflare Workers with optimal JSON output */ export * from './types'; -export * from './context'; export * from './core'; import { createLogger, createObjectLogger, LoggerFactory } from './core'; -import { ContextUtils, RequestContextManager } from './context'; -// Configure logger based on environment +// Configure logger for Cloudflare Workers environment LoggerFactory.configure({ - level: 'debug', - prettyPrint: true, - enableTiming: true, - enableTracing: true + level: 'info', + prettyPrint: false // JSON output for optimal Cloudflare Workers Logs indexing }); /** - * Logging utilities for common patterns + * Main Logger utilities - simplified API */ export const Logger = { /** @@ -44,83 +40,63 @@ export const Logger = { }; /** - * Context management utilities + * Legacy compatibility exports - maintain API compatibility */ export const Context = { /** - * Start a new request context (call this at the beginning of request handling) + * Placeholder for compatibility - no longer needed with simplified logger */ - startRequest: ContextUtils.startRequest, - - /** - * Set component context - */ - setComponent: ContextUtils.setComponent, - - /** - * Add metadata to current context - */ - addMetadata: ContextUtils.addMetadata + startRequest: (requestId?: string) => ({ requestId }), + setComponent: () => {}, // No-op + addMetadata: () => {} // No-op }; -/** - * Request-scoped tracing utilities for production-grade distributed tracing - * Automatically handles trace propagation across all objects touched during request flow - */ export const Trace = { /** - * Start a new request context (use at API endpoint entry) - */ - startRequest: RequestContextManager.startRequest, - - /** - * Execute function within a span with automatic object context (DEPRECATED - use instance methods) - */ - withSpan: (_operation: string, _obj: any, fn: () => T): T => { - console.warn('Trace.withSpan is deprecated. Use RequestContextManager instance methods for proper trace propagation.'); - return fn(); - }, - - /** - * Execute async function within a span with automatic object context (DEPRECATED - use instance methods) - */ - withSpanAsync: async (_operation: string, _obj: any, fn: () => Promise): Promise => { - console.warn('Trace.withSpanAsync is deprecated. Use RequestContextManager instance methods for proper trace propagation.'); - return await fn(); - }, - - /** - * Get current trace ID from context + * Placeholder for compatibility - no longer needed with simplified logger */ - getCurrentId: (): string | null => { - return ContextUtils.getContext().traceId || null; - } + startRequest: (requestId?: string) => ({ requestId }), + withSpan: (_operation: string, _obj: unknown, fn: () => T): T => fn(), + withSpanAsync: async (_operation: string, _obj: unknown, fn: () => Promise): Promise => fn(), + getCurrentId: (): string | null => null }; /** - * Decorators for automatic logging (TypeScript/ES6) + * Method decorator for automatic logging (simplified) */ export function LogMethod(component?: string) { - return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { + return function (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; - const className = target.constructor.name; + const className = (target as Record)?.constructor?.name || 'UnknownClass'; const methodName = propertyKey; const loggerComponent = component || className; - descriptor.value = function (...args: any[]) { + descriptor.value = function (this: unknown, ...args: unknown[]) { const logger = createObjectLogger(this, loggerComponent); - return logger.measureAsync(`${methodName}`, async () => { - logger.debug(`Entering method: ${methodName}`, { args: args.length }); - try { - const result = await originalMethod.apply(this, args); + logger.debug(`Entering method: ${methodName}`, { argsCount: args.length }); + try { + const result = originalMethod.apply(this, args); + if (result && typeof result === 'object' && 'then' in result) { + // Handle async methods + return (result as Promise).then( + (res) => { + logger.debug(`Exiting method: ${methodName}`, { success: true }); + return res; + }, + (error) => { + logger.error(`Method ${methodName} failed`, error); + throw error; + } + ); + } else { logger.debug(`Exiting method: ${methodName}`, { success: true }); return result; - } catch (error) { - logger.error(`Method ${methodName} failed`, error); - throw error; } - }); + } catch (error) { + logger.error(`Method ${methodName} failed`, error); + throw error; + } }; return descriptor; @@ -131,12 +107,15 @@ export function LogMethod(component?: string) { * Class decorator for automatic logger injection */ export function WithLogger(component?: string) { - return function (constructor: T) { + return function {}>(constructor: T) { return class extends constructor { protected logger = createObjectLogger(this, component || constructor.name); + constructor(...args: any[]) { + super(...args); + } }; }; } // Export default logger instance for quick usage -export default Logger; +export default Logger; \ No newline at end of file diff --git a/worker/logger/types.ts b/worker/logger/types.ts index 272c0ede..97b2362c 100644 --- a/worker/logger/types.ts +++ b/worker/logger/types.ts @@ -1,32 +1,15 @@ /** - * Advanced Structured Logging System - Type Definitions + * Simplified Logger Types for Cloudflare Workers + * Type-safe structured logging with minimal complexity */ -export interface LogContext { - /** Distributed trace ID */ - traceId?: string; - /** Current span ID */ - spanId?: string; - /** Parent span ID */ - parentSpanId?: string; - /** Request ID */ - requestId?: string; - /** User ID */ - userId?: string; - /** Session ID */ - sessionId?: string; - /** Component name */ - component?: string; - /** Operation or method name */ - operation?: string; - /** Object context for the current operation */ - objectContext?: ObjectContext; - /** Complete ancestry chain (parent -> grandparent -> great-grandparent) */ - ancestry?: ObjectContext[]; - /** Timestamp for this context */ - timestamp?: string; - /** Additional metadata */ - metadata?: Record; +export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; + +export interface LoggerConfig { + /** Base log level - messages below this level won't be logged */ + level?: LogLevel; + /** Pretty print for development (uses console formatting vs JSON) */ + prettyPrint?: boolean; } export interface ObjectContext { @@ -34,53 +17,27 @@ export interface ObjectContext { id?: string; /** Type/class name of the object */ type?: string; - /** Parent object context if this is a child */ - parent?: ObjectContext; - /** Complete ancestry chain (parent -> grandparent -> great-grandparent) */ - ancestry?: ObjectContext[]; /** Additional object-specific metadata */ meta?: Record; } export interface LogEntry { /** Log level */ - level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'; + level: LogLevel; + /** Timestamp in ISO format */ + time: string; + /** Component name */ + component: string; /** Primary log message */ msg: string; - /** Timestamp */ - time: string; - /** Execution context */ - context?: LogContext; - /** Object context */ + /** Object context if applicable */ object?: ObjectContext; - /** Additional structured data */ - data?: Record; /** Error object if applicable */ - err?: Error; - /** Performance timing data */ - timing?: { - duration?: number; - startTime?: number; - endTime?: number; + error?: { + name: string; + message: string; + stack?: string; }; -} - -export interface LoggerConfig { - /** Base log level */ - level?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'; - /** Pretty print for development */ - prettyPrint?: boolean; - /** Enable performance timing */ - enableTiming?: boolean; - /** Enable distributed tracing */ - enableTracing?: boolean; - /** Custom serializers */ - serializers?: Record unknown>; - /** Custom formatters */ - formatters?: Record unknown>; -} - -export interface TimingContext { - /** Active operations with their current durations */ - activeOperations: Record; -} + /** Additional structured data */ + [key: string]: unknown; +} \ No newline at end of file diff --git a/worker/services/sandbox/sandboxSdkClient.ts b/worker/services/sandbox/sandboxSdkClient.ts index 4518c956..d757b78f 100644 --- a/worker/services/sandbox/sandboxSdkClient.ts +++ b/worker/services/sandbox/sandboxSdkClient.ts @@ -70,7 +70,6 @@ export interface StreamEvent { timestamp: Date; } -const NUM_CONTAINER_POOLS = 200; function getAutoAllocatedSandbox(sessionId: string): string { // We have N containers and we can have M sessionIds at once. M >> N // So we algorithmically assign sessionId to containerId @@ -86,9 +85,14 @@ function getAutoAllocatedSandbox(sessionId: string): string { // Make hash positive hash = Math.abs(hash); + + let max_instances = 10; + if (env.MAX_SANDBOX_INSTANCES) { + max_instances = Number(env.MAX_SANDBOX_INSTANCES); + } // Consistently map to one of N containers - const containerIndex = hash % NUM_CONTAINER_POOLS; + const containerIndex = hash % (max_instances); // Create a deterministic container ID based on the index const containerId = `container-pool-${containerIndex}`; @@ -1098,9 +1102,12 @@ export class SandboxSdkClient extends BaseSandboxService { const sandbox = this.getSandbox(); const results = []; + + const writePromises = files.map(file => sandbox.writeFile(`${instanceId}/${file.filePath}`, file.fileContents)); - for (const file of files) { - const writeResult = await sandbox.writeFile(`${instanceId}/${file.filePath}`, file.fileContents); + const writeResults = await Promise.all(writePromises); + + for (const writeResult of writeResults) { if (writeResult.success) { results.push({ file: writeResult.path, @@ -1120,6 +1127,11 @@ export class SandboxSdkClient extends BaseSandboxService { const successCount = results.filter(r => r.success).length; + // If code files were modified, touch vite.config.ts to trigger a rebuild + if (successCount > 0 && files.some(file => file.filePath.endsWith('.ts') || file.filePath.endsWith('.tsx'))) { + await sandbox.exec(`touch ${instanceId}/vite.config.ts`); + } + // Try to commit try { const commitResult = await this.createLatestCommit(instanceId, commitMessage || 'Initial commit');