feat(agents): SSE-Endpoint + Agent-Test-Tab (5-URL parallel)
CI / detect-changes (push) Successful in 7s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Failing after 4s
CI / validate-canonical-controls (push) Successful in 12s
CI / loc-budget (push) Successful in 14s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m24s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 29s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped

Backend:
- specialist_agent_routes.py: GET /agents, POST /test/start (run_id),
  GET /test/stream/{run_id} (SSE), GET /run/{run_id}/result,
  GET /run/{run_id}/artifacts, GET /run/{run_id}/artifact/{path},
  DELETE /run/{run_id}, GET /runs.
- Per-URL async orchestrator: text fetch via consent-tester
  dsi-discovery → agent.evaluate() → vault.put_json + stream events.
- Tests: 7/7 grün.

Frontend:
- /api/sdk/v1/specialist-agent proxy mit SSE-passthrough.
- AgentTestTab.tsx: Agent-Wähler + 5 URL-Slots + Live-Events +
  Speedometer (OK/N-A/HIGH/MEDIUM/LOW) + Findings + Recommendations +
  Eskalations-Log + Artefakt-Link pro Slot.
- Neuer Tab "Agent-Test" in /sdk/agent.

User-Wunsch 2026-06-08: pro Agent isoliert testen, 5 URLs gleichzeitig,
Live-Updates statt Polling-Wartespiel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-08 17:47:05 +02:00
parent f4357a2e9b
commit 3ae4e60c9d
6 changed files with 1064 additions and 1 deletions
@@ -0,0 +1,112 @@
/**
* Specialist-Agent API Proxy
* Proxies /api/sdk/v1/specialist-agent/* → backend-compliance:8002/api/v1/specialist-agent/*
*
* Streaming routes (SSE /test/stream/{run_id}) pass through unmodified.
*/
import { NextRequest, NextResponse } from 'next/server'
const BACKEND_URL = process.env.BACKEND_URL || 'http://backend-compliance:8002'
async function proxyRequest(
request: NextRequest,
pathSegments: string[] | undefined,
method: string,
) {
const pathStr = pathSegments?.join('/') || ''
const searchParams = request.nextUrl.searchParams.toString()
const basePath = `${BACKEND_URL}/api/v1/specialist-agent`
const url = pathStr
? `${basePath}/${pathStr}${searchParams ? `?${searchParams}` : ''}`
: `${basePath}${searchParams ? `?${searchParams}` : ''}`
const isSSE = pathStr.startsWith('test/stream/')
try {
const headers: HeadersInit = {}
if (!isSSE) headers['Content-Type'] = 'application/json'
const fetchOptions: RequestInit = {
method,
headers,
signal: AbortSignal.timeout(isSSE ? 600000 : 60000),
}
if (method === 'POST' || method === 'PUT' || method === 'PATCH' ||
method === 'DELETE') {
const body = await request.text()
if (body) fetchOptions.body = body
}
const response = await fetch(url, fetchOptions)
if (isSSE) {
return new NextResponse(response.body, {
status: response.status,
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no',
},
})
}
if (!response.ok) {
const errText = await response.text()
let errJson
try { errJson = JSON.parse(errText) }
catch { errJson = { error: errText } }
return NextResponse.json(
{ error: `Backend Error: ${response.status}`, ...errJson },
{ status: response.status },
)
}
const ct = response.headers.get('content-type') || ''
if (ct.includes('application/json')) {
const data = await response.json()
return NextResponse.json(data)
}
// Binary asset (image/video/csv etc.)
const blob = await response.blob()
return new NextResponse(blob, {
status: response.status,
headers: {
'Content-Type': ct || 'application/octet-stream',
'Content-Disposition':
response.headers.get('content-disposition') || '',
},
})
} catch (e) {
console.error('specialist-agent proxy error:', e)
return NextResponse.json(
{ error: 'Verbindung zum Backend fehlgeschlagen' },
{ status: 503 },
)
}
}
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ path?: string[] }> },
) {
const { path } = await params
return proxyRequest(request, path, 'GET')
}
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ path?: string[] }> },
) {
const { path } = await params
return proxyRequest(request, path, 'POST')
}
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ path?: string[] }> },
) {
const { path } = await params
return proxyRequest(request, path, 'DELETE')
}