Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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)
SendResponse.success(res, 'Agent run created successfully', run, StatusConstant.CREATED)
} catch (err: any) {
SendResponse.error(res, err.message, StatusConstant.BAD_REQUEST, err)
}
}

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 },
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) => {
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)
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)
run.status = 'failed'
await run.save()
}
}
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