Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions LocalMind-Backend/src/api/v1/Agent/agent.constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const AgentRoles = ['planner', 'researcher', 'executor', 'validator', 'custom'] as const
export const RunStatuses = ['pending', 'running', 'blocked', 'completed', 'failed'] as const
export const StepStatuses = ['success', 'error'] as const

export const AgentConstants = {
MAX_AGENTS_PER_RUN: 7,
DEFAULT_PRIORITY: 1,
}
61 changes: 61 additions & 0 deletions LocalMind-Backend/src/api/v1/Agent/agent.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Request, Response } from 'express'
import { createRunSchema } from './agent.validator'
import { createAgentRun, executeAgentRun, getAgentRun, getAgentRunLogs } from './agent.service'
import { SendResponse } from '../../../utils/SendResponse.utils'
import { StatusConstant } from '../../../constant/Status.constant'

class AgentController {
constructor() {
this.createRun = this.createRun.bind(this)
this.executeRun = this.executeRun.bind(this)
this.getRunStatus = this.getRunStatus.bind(this)
this.getRunLogs = this.getRunLogs.bind(this)
}

async createRun(req: Request, res: Response): Promise<void> {
try {
const validatedData = await createRunSchema.parseAsync(req.body)
// Cast the agents to any to bypass strict type checking if needed vs Validator types
const run = await createAgentRun(validatedData.runName, validatedData.agents as any)
Comment thread
motalib-code marked this conversation as resolved.
SendResponse.success(res, 'Agent run created successfully', run, StatusConstant.CREATED)
} catch (err: any) {
SendResponse.error(res, err.message, StatusConstant.BAD_REQUEST, err)
}
Comment thread
motalib-code marked this conversation as resolved.
}

async executeRun(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params
const run = await executeAgentRun(id)
SendResponse.success(res, 'Agent run execution started', run, StatusConstant.OK)
} catch (err: any) {
SendResponse.error(res, err.message, StatusConstant.INTERNAL_SERVER_ERROR, err)
}
}

async getRunStatus(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params
const run = await getAgentRun(id)
if (!run) {
SendResponse.error(res, 'Run not found', StatusConstant.NOT_FOUND)
return
}
SendResponse.success(res, 'Agent run status fetched', run, StatusConstant.OK)
} catch (err: any) {
SendResponse.error(res, err.message, StatusConstant.INTERNAL_SERVER_ERROR, err)
}
}

async getRunLogs(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params
const logs = await getAgentRunLogs(id)
SendResponse.success(res, 'Agent run logs fetched', logs, StatusConstant.OK)
} catch (err: any) {
SendResponse.error(res, err.message, StatusConstant.INTERNAL_SERVER_ERROR, err)
}
}
}

export default new AgentController()
34 changes: 34 additions & 0 deletions LocalMind-Backend/src/api/v1/Agent/agent.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import mongoose, { Schema } from 'mongoose'
import { IAgentRun, IAgentStepLog } from './agent.type'
import { AgentRoles, RunStatuses, StepStatuses } from './agent.constant'

const agentConfigSchema = new Schema({
agentId: { type: String, required: true },
name: { type: String, required: true },
type: { type: String, enum: AgentRoles, required: true },
systemPrompt: { type: String, required: true },
tools: [{ type: String }],
priority: { type: Number, default: 1 },
isActive: { type: Boolean, default: true }
}, { _id: false })

const agentRunSchema = new Schema<IAgentRun>({
runName: { type: String, required: true },
status: { type: String, enum: RunStatuses, default: 'pending' },
agents: [agentConfigSchema],
currentAgent: { type: String },
startedAt: { type: Date },
completedAt: { type: Date }
}, { timestamps: true })

const agentStepLogSchema = new Schema<IAgentStepLog>({
runId: { type: 'ObjectId', ref: 'AgentRun', required: true },
Comment thread
motalib-code marked this conversation as resolved.
agentId: { type: String, required: true },
inputPrompt: { type: String, required: true },
output: { type: String, required: true },
status: { type: String, enum: StepStatuses, required: true },
executionTimeMs: { type: Number, required: true }
}, { timestamps: true })

export const AgentRunModel = mongoose.model<IAgentRun>('AgentRun', agentRunSchema)
export const AgentStepLogModel = mongoose.model<IAgentStepLog>('AgentStepLog', agentStepLogSchema)
11 changes: 11 additions & 0 deletions LocalMind-Backend/src/api/v1/Agent/agent.routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Router } from 'express'
import AgentController from './agent.controller'

const router = Router()

router.post('/v1/agent-runs', AgentController.createRun)
router.post('/v1/agent-runs/:id/execute', AgentController.executeRun)
router.get('/v1/agent-runs/:id', AgentController.getRunStatus)
router.get('/v1/agent-runs/:id/logs', AgentController.getRunLogs)

export { router as agentRoutes }
88 changes: 88 additions & 0 deletions LocalMind-Backend/src/api/v1/Agent/agent.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { AgentRunModel, AgentStepLogModel } from './agent.model'
import { IAgentConfig, IAgentRun, IAgentStepLog } from './agent.type'

export const createAgentRun = async (
runName: string,
agents: IAgentConfig[]
): Promise<IAgentRun> => {
const run = new AgentRunModel({
runName,
agents,
status: 'pending',
})
return await run.save()
}

export const getAgentRun = async (runId: string): Promise<IAgentRun | null> => {
return await AgentRunModel.findById(runId).exec()
}

export const getAgentRunLogs = async (runId: string): Promise<IAgentStepLog[]> => {
return await AgentStepLogModel.find({ runId }).sort({ createdAt: 1 }).exec()
}

export const executeAgentRun = async (runId: string) => {
const run = await AgentRunModel.findById(runId)
if (!run) throw new Error('Run not found')

if (run.status === 'running' || run.status === 'completed') {
return run
}

run.status = 'running'
run.startedAt = new Date()
await run.save()

// Start execution in background
processRun(run)

return run
}

const processRun = async (run: any) => {
Comment thread
motalib-code marked this conversation as resolved.
try {
// Sort agents by priority (lower number = higher priority, or vice versa?
// Usually priority 1 is high. But if it's a sequence, maybe 1, 2, 3...
// The requirement says "Sort agents by priority". I'll assume ascending order of priority field.
const agents = run.agents.sort((a: IAgentConfig, b: IAgentConfig) => a.priority - b.priority)
Comment thread
motalib-code marked this conversation as resolved.
let previousOutput = ''

for (const agent of agents) {
if (!agent.isActive) continue

run.currentAgent = agent.agentId
await run.save()

const start = Date.now()

// Simulate AI processing delay
await new Promise((resolve) => setTimeout(resolve, 1000))

// Placeholder for AI invocation
const input = previousOutput || 'Start of Run'
const executionOutput = `[${agent.name}]: Executed task based on prompt. Input len: ${input.length}`

const log = new AgentStepLogModel({
runId: run._id,
agentId: agent.agentId,
inputPrompt: input,
output: executionOutput,
status: 'success',
executionTimeMs: Date.now() - start
})
await log.save()

previousOutput = executionOutput
}

run.status = 'completed'
run.completedAt = new Date()
run.currentAgent = undefined
await run.save()

} catch (error) {
console.error('Run execution error:', error)
Comment thread
motalib-code marked this conversation as resolved.
run.status = 'failed'
await run.save()
Comment thread
motalib-code marked this conversation as resolved.
}
}
38 changes: 38 additions & 0 deletions LocalMind-Backend/src/api/v1/Agent/agent.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { AgentRoles, RunStatuses, StepStatuses } from './agent.constant'

export type AgentRole = (typeof AgentRoles)[number]
export type RunStatus = (typeof RunStatuses)[number]
export type StepStatus = (typeof StepStatuses)[number]

export interface IAgentConfig {
agentId: string
name: string
type: AgentRole
systemPrompt: string
tools: string[]
priority: number
isActive: boolean
}

export interface IAgentRun {
_id?: string
runName: string
status: RunStatus
agents: IAgentConfig[]
currentAgent?: string
startedAt?: Date
completedAt?: Date
createdAt?: Date
updatedAt?: Date
}

export interface IAgentStepLog {
_id?: string
runId: string
agentId: string
inputPrompt: string
output: string
status: StepStatus
executionTimeMs: number
createdAt?: Date
}
17 changes: 17 additions & 0 deletions LocalMind-Backend/src/api/v1/Agent/agent.validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { z } from 'zod'
import { AgentRoles, AgentConstants } from './agent.constant'

export const agentConfigSchema = z.object({
agentId: z.string().min(1),
name: z.string().min(1),
type: z.enum(AgentRoles),
systemPrompt: z.string().min(1),
tools: z.array(z.string()),
priority: z.number().default(AgentConstants.DEFAULT_PRIORITY),
isActive: z.boolean().default(true)
})

export const createRunSchema = z.object({
runName: z.string().min(1),
agents: z.array(agentConfigSchema).max(AgentConstants.MAX_AGENTS_PER_RUN)
})
5 changes: 3 additions & 2 deletions LocalMind-Backend/src/routes/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DataSetRoutes } from '../api/v1/DataSet/v1/DataSet.routes'
import { userRoutes } from '../api/v1/user/user.routes'
import { OllamaRouter } from '../api/v1/Ai-model/Ollama/Ollama.routes'
import { GroqRouter } from '../api/v1/Ai-model/Groq/Groq.routes'
import { agentRoutes } from '../api/v1/Agent/agent.routes'


logger.token('time', () => new Date().toLocaleString())
Expand All @@ -19,13 +20,13 @@ app.use(express.json())
app.use(express.urlencoded({ extended: true }))

// API routes
app.use('/api', GoogleRoutes, userRoutes, DataSetRoutes, OllamaRouter, GroqRouter)
app.use('/api', GoogleRoutes, userRoutes, DataSetRoutes, OllamaRouter, GroqRouter, agentRoutes)

// Serve static files from public directory (for frontend in production)
const publicPath = path.join(__dirname, '../../public')
if (fs.existsSync(publicPath)) {
app.use(express.static(publicPath))

// SPA fallback: serve index.html for all non-API routes
app.get('*', (req, res) => {
if (!req.path.startsWith('/api')) {
Expand Down