From c381c519576f52570348a11f4ac738ef9e003588 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Mon, 29 Dec 2025 14:28:52 +0000 Subject: [PATCH] style: Refactor type definitions and improve error handling across multiple files --- AIAgent/CodeAgents/OpenCodeAgent.ts | 268 +++++++++++++------------- AIAgent/Index.ts | 3 +- AIAgent/Jobs/ProcessScheduledTasks.ts | 23 ++- AIAgent/Utils/BackendAPI.ts | 32 +-- AIAgent/Utils/PullRequestCreator.ts | 4 +- AIAgent/Utils/RepositoryManager.ts | 2 +- AIAgent/Utils/WorkspaceManager.ts | 6 +- Common/Server/API/AIAgentDataAPI.ts | 3 +- 8 files changed, 178 insertions(+), 163 deletions(-) diff --git a/AIAgent/CodeAgents/OpenCodeAgent.ts b/AIAgent/CodeAgents/OpenCodeAgent.ts index e51cec0e60..c58547c7d4 100644 --- a/AIAgent/CodeAgents/OpenCodeAgent.ts +++ b/AIAgent/CodeAgents/OpenCodeAgent.ts @@ -17,7 +17,7 @@ import BadDataException from "Common/Types/Exception/BadDataException"; // OpenCode configuration file structure interface OpenCodeConfig { - provider?: object; + provider?: Record; model?: string; small_model?: string; disabled_providers?: Array; @@ -288,142 +288,144 @@ export default class OpenCodeAgent implements CodeAgent { timeoutMs: number, onOutput: (event: CodeAgentProgressEvent) => void, ): Promise { - return new Promise((resolve, reject) => { - if (!this.config) { - reject(new Error("Config not initialized")); - return; - } - - // Set environment variables for API key - const env: NodeJS.ProcessEnv = { ...process.env }; - - if (this.config.apiKey) { - switch (this.config.llmType) { - case LlmType.Anthropic: - env["ANTHROPIC_API_KEY"] = this.config.apiKey; - break; - case LlmType.OpenAI: - env["OPENAI_API_KEY"] = this.config.apiKey; - break; - case LlmType.Ollama: - if (this.config.baseUrl) { - env["OLLAMA_HOST"] = this.config.baseUrl; - } - break; - } - } - - const args: Array = ["run", prompt]; - - logger.debug( - `Running: opencode ${args - .map((a: string) => { - return a.includes(" ") ? `"${a.substring(0, 50)}..."` : a; - }) - .join(" ")}`, - ); - - const child: ChildProcess = spawn("opencode", args, { - cwd: workingDirectory, - env, - stdio: ["pipe", "pipe", "pipe"], - }); - - this.currentProcess = child; - - let stdout: string = ""; - let stderr: string = ""; - - // Set timeout - const timeout: ReturnType = setTimeout(() => { - if (child.pid) { - child.kill("SIGTERM"); - reject( - new Error( - `OpenCode execution timed out after ${timeoutMs / 1000} seconds`, - ), - ); - } - }, timeoutMs); - - child.stdout?.on("data", (data: Buffer) => { - const text: string = data.toString(); - stdout += text; - - // Stream to console immediately - const trimmedText: string = text.trim(); - if (trimmedText) { - logger.info(`[OpenCode stdout] ${trimmedText}`); - - // Stream to task logger for server-side logging - if (this.taskLogger) { - this.taskLogger - .info(`[OpenCode] ${trimmedText}`) - .catch((err: Error) => { - logger.error(`Failed to log OpenCode output: ${err.message}`); - }); - } - } - - onOutput({ - type: "stdout", - message: trimmedText, - timestamp: new Date(), - }); - }); - - child.stderr?.on("data", (data: Buffer) => { - const text: string = data.toString(); - stderr += text; - - // Stream to console immediately - const trimmedText: string = text.trim(); - if (trimmedText) { - logger.warn(`[OpenCode stderr] ${trimmedText}`); - - // Stream to task logger for server-side logging - if (this.taskLogger) { - this.taskLogger - .warning(`[OpenCode stderr] ${trimmedText}`) - .catch((err: Error) => { - logger.error(`Failed to log OpenCode stderr: ${err.message}`); - }); - } - } - - onOutput({ - type: "stderr", - message: trimmedText, - timestamp: new Date(), - }); - }); - - child.on("close", (code: number | null) => { - clearTimeout(timeout); - this.currentProcess = null; - - if (this.aborted) { - reject(new Error("Execution aborted")); + return new Promise( + (resolve: (value: string) => void, reject: (reason: Error) => void) => { + if (!this.config) { + reject(new Error("Config not initialized")); return; } - if (code === 0 || code === null) { - resolve(stdout); - } else { - reject( - new Error( - `OpenCode exited with code ${code}. stderr: ${stderr.substring(0, 500)}`, - ), - ); - } - }); + // Set environment variables for API key + const env: NodeJS.ProcessEnv = { ...process.env }; - child.on("error", (error: Error) => { - clearTimeout(timeout); - this.currentProcess = null; - reject(error); - }); - }); + if (this.config.apiKey) { + switch (this.config.llmType) { + case LlmType.Anthropic: + env["ANTHROPIC_API_KEY"] = this.config.apiKey; + break; + case LlmType.OpenAI: + env["OPENAI_API_KEY"] = this.config.apiKey; + break; + case LlmType.Ollama: + if (this.config.baseUrl) { + env["OLLAMA_HOST"] = this.config.baseUrl; + } + break; + } + } + + const args: Array = ["run", prompt]; + + logger.debug( + `Running: opencode ${args + .map((a: string) => { + return a.includes(" ") ? `"${a.substring(0, 50)}..."` : a; + }) + .join(" ")}`, + ); + + const child: ChildProcess = spawn("opencode", args, { + cwd: workingDirectory, + env, + stdio: ["pipe", "pipe", "pipe"], + }); + + this.currentProcess = child; + + let stdout: string = ""; + let stderr: string = ""; + + // Set timeout + const timeout: ReturnType = setTimeout(() => { + if (child.pid) { + child.kill("SIGTERM"); + reject( + new Error( + `OpenCode execution timed out after ${timeoutMs / 1000} seconds`, + ), + ); + } + }, timeoutMs); + + child.stdout?.on("data", (data: Buffer) => { + const text: string = data.toString(); + stdout += text; + + // Stream to console immediately + const trimmedText: string = text.trim(); + if (trimmedText) { + logger.info(`[OpenCode stdout] ${trimmedText}`); + + // Stream to task logger for server-side logging + if (this.taskLogger) { + this.taskLogger + .info(`[OpenCode] ${trimmedText}`) + .catch((err: Error) => { + logger.error(`Failed to log OpenCode output: ${err.message}`); + }); + } + } + + onOutput({ + type: "stdout", + message: trimmedText, + timestamp: new Date(), + }); + }); + + child.stderr?.on("data", (data: Buffer) => { + const text: string = data.toString(); + stderr += text; + + // Stream to console immediately + const trimmedText: string = text.trim(); + if (trimmedText) { + logger.warn(`[OpenCode stderr] ${trimmedText}`); + + // Stream to task logger for server-side logging + if (this.taskLogger) { + this.taskLogger + .warning(`[OpenCode stderr] ${trimmedText}`) + .catch((err: Error) => { + logger.error(`Failed to log OpenCode stderr: ${err.message}`); + }); + } + } + + onOutput({ + type: "stderr", + message: trimmedText, + timestamp: new Date(), + }); + }); + + child.on("close", (code: number | null) => { + clearTimeout(timeout); + this.currentProcess = null; + + if (this.aborted) { + reject(new Error("Execution aborted")); + return; + } + + if (code === 0 || code === null) { + resolve(stdout); + } else { + reject( + new Error( + `OpenCode exited with code ${code}. stderr: ${stderr.substring(0, 500)}`, + ), + ); + } + }); + + child.on("error", (error: Error) => { + clearTimeout(timeout); + this.currentProcess = null; + reject(error); + }); + }, + ); } // Get list of modified files using git diff --git a/AIAgent/Index.ts b/AIAgent/Index.ts index 8cd8bc638a..8c3c206838 100644 --- a/AIAgent/Index.ts +++ b/AIAgent/Index.ts @@ -47,7 +47,8 @@ const init: PromiseVoidFunction = async (): Promise => { // Register task handlers logger.debug("Registering task handlers..."); - const taskHandlerRegistry = getTaskHandlerRegistry(); + const taskHandlerRegistry: ReturnType = + getTaskHandlerRegistry(); taskHandlerRegistry.register(new FixExceptionTaskHandler()); logger.debug( `Registered ${taskHandlerRegistry.getHandlerCount()} task handler(s): ${taskHandlerRegistry.getRegisteredTaskTypes().join(", ")}`, diff --git a/AIAgent/Jobs/ProcessScheduledTasks.ts b/AIAgent/Jobs/ProcessScheduledTasks.ts index dd04562868..05dde9f963 100644 --- a/AIAgent/Jobs/ProcessScheduledTasks.ts +++ b/AIAgent/Jobs/ProcessScheduledTasks.ts @@ -7,9 +7,14 @@ import { getTaskHandlerRegistry, TaskContext, TaskMetadata, + TaskHandler, + TaskResult, } from "../TaskHandlers/Index"; +import TaskHandlerRegistry from "../TaskHandlers/TaskHandlerRegistry"; import URL from "Common/Types/API/URL"; import API from "Common/Utils/API"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import { JSONObject } from "Common/Types/JSON"; import logger from "Common/Server/Utils/Logger"; import AIAgentTaskStatus from "Common/Types/AI/AIAgentTaskStatus"; import AIAgentTaskType from "Common/Types/AI/AIAgentTaskType"; @@ -50,21 +55,21 @@ const executeTask: ExecuteTaskFunction = async ( const createdAt: Date = new Date(task.createdAt); // Get the task handler from the registry - const registry = getTaskHandlerRegistry(); - const handler = registry.getHandler(taskType); + const registry: TaskHandlerRegistry = getTaskHandlerRegistry(); + const handler: TaskHandler | undefined = registry.getHandler(taskType); if (!handler) { throw new Error(`No handler registered for task type: ${taskType}`); } // Create task logger - const taskLogger = new TaskLogger({ + const taskLogger: TaskLogger = new TaskLogger({ taskId: taskIdString, context: `${handler.name}`, }); // Create backend API client - const backendAPI = new BackendAPI(); + const backendAPI: BackendAPI = new BackendAPI(); // Build task context const context: TaskContext = { @@ -90,7 +95,7 @@ const executeTask: ExecuteTaskFunction = async ( } // Execute the task handler - const result = await handler.execute(context); + const result: TaskResult = await handler.execute(context); // Log result if (result.success) { @@ -139,7 +144,7 @@ const startTaskProcessingLoop: () => Promise = while (true) { try { /* Fetch one scheduled task */ - const getPendingTaskResult = await API.post({ + const getPendingTaskResult: HTTPResponse = await API.post({ url: getPendingTaskUrl, data: AIAgentAPIRequest.getDefaultRequestBody(), }); @@ -172,7 +177,7 @@ const startTaskProcessingLoop: () => Promise = try { /* Mark task as InProgress */ - const inProgressResult = await API.post({ + const inProgressResult: HTTPResponse = await API.post({ url: updateTaskStatusUrl, data: { ...AIAgentAPIRequest.getDefaultRequestBody(), @@ -195,7 +200,7 @@ const startTaskProcessingLoop: () => Promise = await executeTask(task); /* Mark task as Completed */ - const completedResult = await API.post({ + const completedResult: HTTPResponse = await API.post({ url: updateTaskStatusUrl, data: { ...AIAgentAPIRequest.getDefaultRequestBody(), @@ -216,7 +221,7 @@ const startTaskProcessingLoop: () => Promise = const errorMessage: string = error instanceof Error ? error.message : "Unknown error occurred"; - const errorResult = await API.post({ + const errorResult: HTTPResponse = await API.post({ url: updateTaskStatusUrl, data: { ...AIAgentAPIRequest.getDefaultRequestBody(), diff --git a/AIAgent/Utils/BackendAPI.ts b/AIAgent/Utils/BackendAPI.ts index 38c23fafd9..c72ebc5d09 100644 --- a/AIAgent/Utils/BackendAPI.ts +++ b/AIAgent/Utils/BackendAPI.ts @@ -2,6 +2,8 @@ import { ONEUPTIME_URL } from "../Config"; import AIAgentAPIRequest from "./AIAgentAPIRequest"; import URL from "Common/Types/API/URL"; import API from "Common/Utils/API"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import { JSONObject } from "Common/Types/JSON"; import LlmType from "Common/Types/LLM/LlmType"; import AIAgentTaskStatus from "Common/Types/AI/AIAgentTaskStatus"; import logger from "Common/Server/Utils/Logger"; @@ -143,7 +145,7 @@ export default class BackendAPI { "/api/ai-agent-data/get-llm-config", ); - const response = await API.post({ + const response: HTTPResponse = await API.post({ url, data: { ...AIAgentAPIRequest.getDefaultRequestBody(), @@ -152,7 +154,8 @@ export default class BackendAPI { }); if (!response.isSuccess()) { - const data = response.data as unknown as LLMConfigResponse; + const data: LLMConfigResponse = + response.data as unknown as LLMConfigResponse; const errorMessage: string = data?.message || "Failed to get LLM config"; throw new Error(errorMessage); } @@ -189,7 +192,7 @@ export default class BackendAPI { "/api/ai-agent-data/get-exception-details", ); - const response = await API.post({ + const response: HTTPResponse = await API.post({ url, data: { ...AIAgentAPIRequest.getDefaultRequestBody(), @@ -198,7 +201,8 @@ export default class BackendAPI { }); if (!response.isSuccess()) { - const data = response.data as unknown as ExceptionDetailsResponse; + const data: ExceptionDetailsResponse = + response.data as unknown as ExceptionDetailsResponse; const errorMessage: string = data?.message || "Failed to get exception details"; throw new Error(errorMessage); @@ -237,7 +241,7 @@ export default class BackendAPI { "/api/ai-agent-data/get-code-repositories", ); - const response = await API.post({ + const response: HTTPResponse = await API.post({ url, data: { ...AIAgentAPIRequest.getDefaultRequestBody(), @@ -246,7 +250,8 @@ export default class BackendAPI { }); if (!response.isSuccess()) { - const data = response.data as unknown as CodeRepositoriesResponse; + const data: CodeRepositoriesResponse = + response.data as unknown as CodeRepositoriesResponse; const errorMessage: string = data?.message || "Failed to get code repositories"; throw new Error(errorMessage); @@ -281,7 +286,7 @@ export default class BackendAPI { "/api/ai-agent-data/get-repository-token", ); - const response = await API.post({ + const response: HTTPResponse = await API.post({ url, data: { ...AIAgentAPIRequest.getDefaultRequestBody(), @@ -290,7 +295,8 @@ export default class BackendAPI { }); if (!response.isSuccess()) { - const data = response.data as unknown as RepositoryTokenResponse; + const data: RepositoryTokenResponse = + response.data as unknown as RepositoryTokenResponse; const errorMessage: string = data?.message || "Failed to get repository token"; throw new Error(errorMessage); @@ -320,7 +326,7 @@ export default class BackendAPI { "/api/ai-agent-data/record-pull-request", ); - const response = await API.post({ + const response: HTTPResponse = await API.post({ url, data: { ...AIAgentAPIRequest.getDefaultRequestBody(), @@ -337,7 +343,8 @@ export default class BackendAPI { }); if (!response.isSuccess()) { - const data = response.data as unknown as RecordPullRequestResponse; + const data: RecordPullRequestResponse = + response.data as unknown as RecordPullRequestResponse; const errorMessage: string = data?.message || "Failed to record pull request"; throw new Error(errorMessage); @@ -364,7 +371,7 @@ export default class BackendAPI { "/api/ai-agent-task/update-task-status", ); - const response = await API.post({ + const response: HTTPResponse = await API.post({ url, data: { ...AIAgentAPIRequest.getDefaultRequestBody(), @@ -375,7 +382,8 @@ export default class BackendAPI { }); if (!response.isSuccess()) { - const data = response.data as unknown as UpdateTaskStatusResponse; + const data: UpdateTaskStatusResponse = + response.data as unknown as UpdateTaskStatusResponse; const errorMessage: string = data?.message || "Failed to update task status"; throw new Error(errorMessage); diff --git a/AIAgent/Utils/PullRequestCreator.ts b/AIAgent/Utils/PullRequestCreator.ts index b84bd9b3b5..39a03648ba 100644 --- a/AIAgent/Utils/PullRequestCreator.ts +++ b/AIAgent/Utils/PullRequestCreator.ts @@ -136,7 +136,7 @@ export default class PullRequestCreator { const headers: Headers = this.getHeaders(token); - const response = await API.get({ + const response: HTTPResponse = await API.get({ url, headers, params: { @@ -222,7 +222,7 @@ export default class PullRequestCreator { const headers: Headers = this.getHeaders(token); - const response = await API.post({ + const response: HTTPResponse = await API.post({ url, data: { labels }, headers, diff --git a/AIAgent/Utils/RepositoryManager.ts b/AIAgent/Utils/RepositoryManager.ts index c96fc28c75..f193c1b89c 100644 --- a/AIAgent/Utils/RepositoryManager.ts +++ b/AIAgent/Utils/RepositoryManager.ts @@ -116,7 +116,7 @@ export default class RepositoryManager { // Check if branch exists locally await this.runGitCommand(repoPath, ["rev-parse", "--verify", branchName]); await this.checkoutBranch(repoPath, branchName); - } catch (error) { + } catch { // Branch doesn't exist, create it await this.createBranch(repoPath, branchName); } diff --git a/AIAgent/Utils/WorkspaceManager.ts b/AIAgent/Utils/WorkspaceManager.ts index 6d5066eab6..89c3102a2e 100644 --- a/AIAgent/Utils/WorkspaceManager.ts +++ b/AIAgent/Utils/WorkspaceManager.ts @@ -50,7 +50,7 @@ export default class WorkspaceManager { try { await LocalFile.readDirectory(workspacePath); return true; - } catch (error) { + } catch { return false; } } @@ -113,7 +113,7 @@ export default class WorkspaceManager { const filePath: string = path.join(workspacePath, relativePath); await LocalFile.read(filePath); return true; - } catch (error) { + } catch { return false; } } @@ -156,7 +156,7 @@ export default class WorkspaceManager { // Ensure base directory exists try { await LocalFile.readDirectory(this.BASE_TEMP_DIR); - } catch (error) { + } catch { // Base directory doesn't exist, nothing to clean return 0; } diff --git a/Common/Server/API/AIAgentDataAPI.ts b/Common/Server/API/AIAgentDataAPI.ts index f44587b1e1..770aa32a59 100644 --- a/Common/Server/API/AIAgentDataAPI.ts +++ b/Common/Server/API/AIAgentDataAPI.ts @@ -6,13 +6,12 @@ import ServiceCatalogCodeRepositoryService from "../Services/ServiceCatalogCodeR import CodeRepositoryService from "../Services/CodeRepositoryService"; import AIAgentTaskPullRequestService from "../Services/AIAgentTaskPullRequestService"; import AIAgentTaskService from "../Services/AIAgentTaskService"; -import { +import Express, { ExpressRequest, ExpressResponse, ExpressRouter, NextFunction, } from "../Utils/Express"; -import Express from "../Utils/Express"; import Response from "../Utils/Response"; import AIAgent from "../../Models/DatabaseModels/AIAgent"; import LlmProvider from "../../Models/DatabaseModels/LlmProvider";