Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website, Klausur-Service, School-Service, Voice-Service, Geo-Service, BreakPilot Drive, Agent-Core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
276 lines
7.9 KiB
TypeScript
276 lines
7.9 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import * as fs from 'fs/promises'
|
|
import * as path from 'path'
|
|
|
|
/**
|
|
* Individual Agent API
|
|
*
|
|
* GET - Get agent details including SOUL content
|
|
* PUT - Update agent configuration
|
|
* DELETE - Delete agent
|
|
*/
|
|
|
|
const AGENT_CORE_PATH = process.env.AGENT_CORE_PATH || '/app/agent-core'
|
|
const SOUL_PATH = path.join(AGENT_CORE_PATH, 'soul')
|
|
|
|
interface AgentDetails {
|
|
id: string
|
|
name: string
|
|
description: string
|
|
soulFile: string
|
|
soulContent: string
|
|
color: string
|
|
icon: string
|
|
status: 'running' | 'paused' | 'stopped' | 'error'
|
|
activeSessions: number
|
|
totalProcessed: number
|
|
avgResponseTime: number
|
|
errorRate: number
|
|
lastActivity: string
|
|
version: string
|
|
createdAt: string
|
|
updatedAt: string
|
|
}
|
|
|
|
// Agent metadata (in production, store in database)
|
|
const agentMetadata: Record<string, Partial<AgentDetails>> = {
|
|
'tutor-agent': {
|
|
name: 'TutorAgent',
|
|
description: 'Lernbegleitung und Fragen beantworten',
|
|
color: '#3b82f6',
|
|
icon: 'brain'
|
|
},
|
|
'grader-agent': {
|
|
name: 'GraderAgent',
|
|
description: 'Klausur-Korrektur und Bewertung',
|
|
color: '#10b981',
|
|
icon: 'bot'
|
|
},
|
|
'quality-judge': {
|
|
name: 'QualityJudge',
|
|
description: 'BQAS Qualitaetspruefung',
|
|
color: '#f59e0b',
|
|
icon: 'settings'
|
|
},
|
|
'alert-agent': {
|
|
name: 'AlertAgent',
|
|
description: 'Monitoring und Benachrichtigungen',
|
|
color: '#ef4444',
|
|
icon: 'alert'
|
|
},
|
|
'orchestrator': {
|
|
name: 'Orchestrator',
|
|
description: 'Task-Koordination und Routing',
|
|
color: '#8b5cf6',
|
|
icon: 'message'
|
|
}
|
|
}
|
|
|
|
// Read SOUL file content
|
|
async function readSoulFile(agentId: string): Promise<string | null> {
|
|
const soulFile = `${agentId}.soul.md`
|
|
const soulFilePath = path.join(SOUL_PATH, soulFile)
|
|
|
|
try {
|
|
const content = await fs.readFile(soulFilePath, 'utf-8')
|
|
return content
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
// Get file stats
|
|
async function getFileStats(agentId: string): Promise<{ createdAt: string; updatedAt: string } | null> {
|
|
const soulFile = `${agentId}.soul.md`
|
|
const soulFilePath = path.join(SOUL_PATH, soulFile)
|
|
|
|
try {
|
|
const stats = await fs.stat(soulFilePath)
|
|
return {
|
|
createdAt: stats.birthtime.toISOString(),
|
|
updatedAt: stats.mtime.toISOString()
|
|
}
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
// Mock agent stats (in production, query from Redis/PostgreSQL)
|
|
function getMockStats(agentId: string) {
|
|
const mockStats: Record<string, { activeSessions: number; totalProcessed: number; avgResponseTime: number; errorRate: number }> = {
|
|
'tutor-agent': { activeSessions: 12, totalProcessed: 1847, avgResponseTime: 234, errorRate: 0.3 },
|
|
'grader-agent': { activeSessions: 3, totalProcessed: 456, avgResponseTime: 1205, errorRate: 0.5 },
|
|
'quality-judge': { activeSessions: 8, totalProcessed: 3291, avgResponseTime: 89, errorRate: 0.1 },
|
|
'alert-agent': { activeSessions: 1, totalProcessed: 892, avgResponseTime: 45, errorRate: 0.0 },
|
|
'orchestrator': { activeSessions: 24, totalProcessed: 8934, avgResponseTime: 12, errorRate: 0.2 }
|
|
}
|
|
|
|
return mockStats[agentId] || { activeSessions: 0, totalProcessed: 0, avgResponseTime: 0, errorRate: 0 }
|
|
}
|
|
|
|
// GET - Get agent details
|
|
export async function GET(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ agentId: string }> }
|
|
) {
|
|
try {
|
|
const { agentId } = await params
|
|
|
|
const soulContent = await readSoulFile(agentId)
|
|
if (!soulContent) {
|
|
return NextResponse.json(
|
|
{ error: 'Agent not found' },
|
|
{ status: 404 }
|
|
)
|
|
}
|
|
|
|
const fileStats = await getFileStats(agentId)
|
|
const metadata = agentMetadata[agentId] || {}
|
|
const stats = getMockStats(agentId)
|
|
|
|
const agent: AgentDetails = {
|
|
id: agentId,
|
|
name: metadata.name || agentId.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(''),
|
|
description: metadata.description || 'Custom agent',
|
|
soulFile: `${agentId}.soul.md`,
|
|
soulContent,
|
|
color: metadata.color || '#6b7280',
|
|
icon: metadata.icon || 'bot',
|
|
status: 'running',
|
|
...stats,
|
|
lastActivity: 'just now',
|
|
version: '1.0.0',
|
|
createdAt: fileStats?.createdAt || new Date().toISOString(),
|
|
updatedAt: fileStats?.updatedAt || new Date().toISOString()
|
|
}
|
|
|
|
return NextResponse.json(agent)
|
|
} catch (error) {
|
|
console.error('Error fetching agent:', error)
|
|
return NextResponse.json(
|
|
{ error: error instanceof Error ? error.message : 'Failed to fetch agent' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|
|
|
|
// PUT - Update agent
|
|
export async function PUT(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ agentId: string }> }
|
|
) {
|
|
try {
|
|
const { agentId } = await params
|
|
const body = await request.json()
|
|
const { soulContent, name, description, color, icon } = body
|
|
|
|
const soulFile = `${agentId}.soul.md`
|
|
const soulFilePath = path.join(SOUL_PATH, soulFile)
|
|
|
|
// Check if agent exists
|
|
try {
|
|
await fs.access(soulFilePath)
|
|
} catch {
|
|
return NextResponse.json(
|
|
{ error: 'Agent not found' },
|
|
{ status: 404 }
|
|
)
|
|
}
|
|
|
|
// Update SOUL file if content provided
|
|
if (soulContent) {
|
|
// Create backup before updating
|
|
const backupPath = path.join(SOUL_PATH, '.backups', `${agentId}-${Date.now()}.soul.md`)
|
|
try {
|
|
await fs.mkdir(path.dirname(backupPath), { recursive: true })
|
|
const currentContent = await fs.readFile(soulFilePath, 'utf-8')
|
|
await fs.writeFile(backupPath, currentContent, 'utf-8')
|
|
} catch {
|
|
// Backup failed, continue anyway
|
|
}
|
|
|
|
// Write new content
|
|
await fs.writeFile(soulFilePath, soulContent, 'utf-8')
|
|
}
|
|
|
|
// In production, update metadata in database
|
|
// For now, just return success
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: `Agent ${agentId} updated successfully`,
|
|
agent: {
|
|
id: agentId,
|
|
name: name || agentMetadata[agentId]?.name || agentId,
|
|
description: description || agentMetadata[agentId]?.description || '',
|
|
soulFile,
|
|
color: color || agentMetadata[agentId]?.color || '#6b7280',
|
|
icon: icon || agentMetadata[agentId]?.icon || 'bot',
|
|
updatedAt: new Date().toISOString()
|
|
}
|
|
})
|
|
} catch (error) {
|
|
console.error('Error updating agent:', error)
|
|
return NextResponse.json(
|
|
{ error: error instanceof Error ? error.message : 'Failed to update agent' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|
|
|
|
// DELETE - Delete agent
|
|
export async function DELETE(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ agentId: string }> }
|
|
) {
|
|
try {
|
|
const { agentId } = await params
|
|
|
|
// Don't allow deleting core agents
|
|
const coreAgents = ['tutor-agent', 'grader-agent', 'quality-judge', 'alert-agent', 'orchestrator']
|
|
if (coreAgents.includes(agentId)) {
|
|
return NextResponse.json(
|
|
{ error: 'Cannot delete core system agent' },
|
|
{ status: 403 }
|
|
)
|
|
}
|
|
|
|
const soulFile = `${agentId}.soul.md`
|
|
const soulFilePath = path.join(SOUL_PATH, soulFile)
|
|
|
|
// Check if agent exists
|
|
try {
|
|
await fs.access(soulFilePath)
|
|
} catch {
|
|
return NextResponse.json(
|
|
{ error: 'Agent not found' },
|
|
{ status: 404 }
|
|
)
|
|
}
|
|
|
|
// Create backup before deleting
|
|
const backupPath = path.join(SOUL_PATH, '.deleted', `${agentId}-${Date.now()}.soul.md`)
|
|
try {
|
|
await fs.mkdir(path.dirname(backupPath), { recursive: true })
|
|
const content = await fs.readFile(soulFilePath, 'utf-8')
|
|
await fs.writeFile(backupPath, content, 'utf-8')
|
|
} catch {
|
|
// Backup failed, continue anyway
|
|
}
|
|
|
|
// Delete the file
|
|
await fs.unlink(soulFilePath)
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: `Agent ${agentId} deleted successfully`
|
|
})
|
|
} catch (error) {
|
|
console.error('Error deleting agent:', error)
|
|
return NextResponse.json(
|
|
{ error: error instanceof Error ? error.message : 'Failed to delete agent' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|