Der Agent nahm aus `pvecm nodes` blind die dritte Spalte als Knotennamen. Mit einem QDevice steht dort dessen Zustand (A,V,NMW oder NR), und der Name rutscht eine Spalte weiter - gemeldet wurden damit die Zustandskuerzel und das QDevice als zusaetzlicher vierter Knoten. Die Namensspalte wird jetzt aus der Kopfzeile bestimmt, die Zeile des QDevice (Nodeid 0) uebersprungen. Beide Ausgabeformen sind als Testfall hinterlegt. Agent-Version 0.2.2. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
66 lines
2.3 KiB
TypeScript
66 lines
2.3 KiB
TypeScript
import type { ZodType } from "zod";
|
|
import { agentRateLimit, clientIp, enrollRateLimit, type RateLimit } from "@/lib/ratelimit";
|
|
import { tokenAusHeader } from "@/lib/tokens";
|
|
import { AgentApiFehler } from "./service";
|
|
|
|
/** Version des Agent-Skripts, die das Dashboard ausliefert (siehe #005). */
|
|
export const AGENT_VERSION = "0.2.2";
|
|
|
|
export function fehlerAntwort(status: number, code: string, nachricht: string, extra?: HeadersInit) {
|
|
return Response.json({ ok: false, code, fehler: nachricht }, { status, headers: extra });
|
|
}
|
|
|
|
/** Wandelt geworfene Fehler in eine JSON-Antwort, ohne interne Details preiszugeben. */
|
|
export function fehlerBehandeln(fehler: unknown) {
|
|
if (fehler instanceof AgentApiFehler) {
|
|
return fehlerAntwort(fehler.status, fehler.code, fehler.message);
|
|
}
|
|
console.error("Agent-API: unerwarteter Fehler", fehler);
|
|
return fehlerAntwort(500, "interner_fehler", "Interner Fehler.");
|
|
}
|
|
|
|
/** Prüft das Rate-Limit für IP und (falls vorhanden) Token. */
|
|
export function rateLimitPruefen(limit: RateLimit, headers: Headers, token: string | null): Response | null {
|
|
const schluessel = token ? `token:${token.slice(0, 16)}` : `ip:${clientIp(headers)}`;
|
|
const ergebnis = limit.pruefe(schluessel);
|
|
if (ergebnis.erlaubt) return null;
|
|
return fehlerAntwort(429, "zu_viele_anfragen", "Zu viele Anfragen.", {
|
|
"Retry-After": String(ergebnis.wiederholenNach),
|
|
});
|
|
}
|
|
|
|
export { agentRateLimit, enrollRateLimit };
|
|
|
|
export function bearerToken(request: Request): string | null {
|
|
return tokenAusHeader(request.headers.get("authorization"));
|
|
}
|
|
|
|
/** Liest und validiert den JSON-Rumpf; liefert bei Fehlern direkt eine Antwort. */
|
|
export async function jsonEingabe<T>(
|
|
request: Request,
|
|
schema: ZodType<T>,
|
|
): Promise<{ daten: T } | { antwort: Response }> {
|
|
let rohdaten: unknown;
|
|
try {
|
|
rohdaten = await request.json();
|
|
} catch {
|
|
return { antwort: fehlerAntwort(400, "kein_json", "Der Anfragekörper ist kein gültiges JSON.") };
|
|
}
|
|
|
|
const ergebnis = schema.safeParse(rohdaten);
|
|
if (!ergebnis.success) {
|
|
const felder = ergebnis.error.issues.map((problem) => ({
|
|
feld: problem.path.join("."),
|
|
meldung: problem.message,
|
|
}));
|
|
return {
|
|
antwort: Response.json(
|
|
{ ok: false, code: "validierung", fehler: "Eingabe ist ungültig.", felder },
|
|
{ status: 422 },
|
|
),
|
|
};
|
|
}
|
|
|
|
return { daten: ergebnis.data };
|
|
}
|