mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
style: Refactor type definitions and improve error handling across multiple files
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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(", ")}`,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user