style: Refactor type definitions and improve error handling across multiple files

This commit is contained in:
Nawaz Dhandala
2025-12-29 14:28:52 +00:00
parent 8ab088ace0
commit c381c51957
8 changed files with 178 additions and 163 deletions

View File

@@ -17,7 +17,7 @@ import BadDataException from "Common/Types/Exception/BadDataException";
// OpenCode configuration file structure
interface OpenCodeConfig {
provider?: object;
provider?: Record<string, unknown>;
model?: string;
small_model?: string;
disabled_providers?: Array<string>;
@@ -288,142 +288,144 @@ export default class OpenCodeAgent implements CodeAgent {
timeoutMs: number,
onOutput: (event: CodeAgentProgressEvent) => void,
): Promise<string> {
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<string> = ["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<typeof setTimeout> = 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<string> = ["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<typeof setTimeout> = 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

View File

@@ -47,7 +47,8 @@ const init: PromiseVoidFunction = async (): Promise<void> => {
// Register task handlers
logger.debug("Registering task handlers...");
const taskHandlerRegistry = getTaskHandlerRegistry();
const taskHandlerRegistry: ReturnType<typeof getTaskHandlerRegistry> =
getTaskHandlerRegistry();
taskHandlerRegistry.register(new FixExceptionTaskHandler());
logger.debug(
`Registered ${taskHandlerRegistry.getHandlerCount()} task handler(s): ${taskHandlerRegistry.getRegisteredTaskTypes().join(", ")}`,

View File

@@ -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<void> =
while (true) {
try {
/* Fetch one scheduled task */
const getPendingTaskResult = await API.post({
const getPendingTaskResult: HTTPResponse<JSONObject> = await API.post({
url: getPendingTaskUrl,
data: AIAgentAPIRequest.getDefaultRequestBody(),
});
@@ -172,7 +177,7 @@ const startTaskProcessingLoop: () => Promise<void> =
try {
/* Mark task as InProgress */
const inProgressResult = await API.post({
const inProgressResult: HTTPResponse<JSONObject> = await API.post({
url: updateTaskStatusUrl,
data: {
...AIAgentAPIRequest.getDefaultRequestBody(),
@@ -195,7 +200,7 @@ const startTaskProcessingLoop: () => Promise<void> =
await executeTask(task);
/* Mark task as Completed */
const completedResult = await API.post({
const completedResult: HTTPResponse<JSONObject> = await API.post({
url: updateTaskStatusUrl,
data: {
...AIAgentAPIRequest.getDefaultRequestBody(),
@@ -216,7 +221,7 @@ const startTaskProcessingLoop: () => Promise<void> =
const errorMessage: string =
error instanceof Error ? error.message : "Unknown error occurred";
const errorResult = await API.post({
const errorResult: HTTPResponse<JSONObject> = await API.post({
url: updateTaskStatusUrl,
data: {
...AIAgentAPIRequest.getDefaultRequestBody(),

View File

@@ -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<JSONObject> = 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<JSONObject> = 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<JSONObject> = 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<JSONObject> = 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<JSONObject> = 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<JSONObject> = 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);

View File

@@ -136,7 +136,7 @@ export default class PullRequestCreator {
const headers: Headers = this.getHeaders(token);
const response = await API.get({
const response: HTTPResponse<JSONArray> = 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<JSONObject> = await API.post({
url,
data: { labels },
headers,

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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";