diff --git a/src/app/api/combos/auto/route.ts b/src/app/api/combos/auto/route.ts index 0ec47145af..8c3b040fa8 100644 --- a/src/app/api/combos/auto/route.ts +++ b/src/app/api/combos/auto/route.ts @@ -10,6 +10,8 @@ */ import { NextRequest, NextResponse } from "next/server"; +import { createAutoComboSchema } from "@/shared/validation/schemas"; +import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; // ── In-memory auto-combo store (mirrors open-sse/services/autoCombo/engine.ts) ── @@ -45,29 +47,45 @@ interface AutoComboConfig { const autoCombos = new Map(); export async function POST(req: NextRequest) { + let rawBody: unknown; try { - const body = await req.json(); - const { id, name, candidatePool, weights, modePack, budgetCap, explorationRate } = body; + rawBody = await req.json(); + } catch { + return NextResponse.json( + { + error: { + message: "Invalid request", + details: [{ field: "body", message: "Invalid JSON body" }], + }, + }, + { status: 400 } + ); + } - if (!id || !name) { - return NextResponse.json({ error: "id and name required" }, { status: 400 }); + try { + const validation = validateBody(createAutoComboSchema, rawBody); + if (isValidationFailure(validation)) { + return NextResponse.json({ error: validation.error }, { status: 400 }); } + const { id, name, candidatePool, weights, modePack, budgetCap, explorationRate } = + validation.data; const config: AutoComboConfig = { id, name, type: "auto", - candidatePool: candidatePool || [], - weights: weights || DEFAULT_WEIGHTS, + candidatePool, + weights: weights ?? DEFAULT_WEIGHTS, modePack, budgetCap, - explorationRate: explorationRate ?? 0.05, + explorationRate, }; autoCombos.set(id, config); return NextResponse.json(config, { status: 201 }); } catch (err) { - return NextResponse.json({ error: String(err) }, { status: 500 }); + console.log("Error creating auto-combo:", err); + return NextResponse.json({ error: "Failed to create auto-combo" }, { status: 500 }); } } diff --git a/src/app/api/v1/chat/completions/route.ts b/src/app/api/v1/chat/completions/route.ts index 8c9e85159b..53b46de89a 100644 --- a/src/app/api/v1/chat/completions/route.ts +++ b/src/app/api/v1/chat/completions/route.ts @@ -1,4 +1,4 @@ -import { CORS_ORIGIN } from "@/shared/utils/cors"; +import { CORS_ORIGIN, CORS_HEADERS } from "@/shared/utils/cors"; import { callCloudWithMachineId } from "@/shared/utils/cloud"; import { handleChat } from "@/sse/handlers/chat"; import { initTranslators } from "@omniroute/open-sse/translator/index.ts"; @@ -53,7 +53,7 @@ export async function POST(request) { detections: result.detections.length, }, }), - { status: 400, headers: { "Content-Type": "application/json" } } + { status: 400, headers: { ...CORS_HEADERS, "Content-Type": "application/json" } } ); } } @@ -67,7 +67,7 @@ export async function POST(request) { code: "SECURITY_002", }, }), - { status: 503, headers: { "Content-Type": "application/json" } } + { status: 503, headers: { ...CORS_HEADERS, "Content-Type": "application/json" } } ); } diff --git a/src/shared/validation/schemas.ts b/src/shared/validation/schemas.ts index c8a0bf18d8..cd34559f47 100644 --- a/src/shared/validation/schemas.ts +++ b/src/shared/validation/schemas.ts @@ -79,6 +79,29 @@ export const createComboSchema = z.object({ config: comboConfigSchema, }); +// ──── Auto-Combo Schemas ──── + +const scoringWeightsSchema = z + .object({ + quota: z.number().min(0).max(1), + health: z.number().min(0).max(1), + costInv: z.number().min(0).max(1), + latencyInv: z.number().min(0).max(1), + taskFit: z.number().min(0).max(1), + stability: z.number().min(0).max(1), + }) + .optional(); + +export const createAutoComboSchema = z.object({ + id: z.string().trim().min(1, "id is required").max(100), + name: z.string().trim().min(1, "name is required").max(200), + candidatePool: z.array(z.string().min(1)).optional().default([]), + weights: scoringWeightsSchema, + modePack: z.string().max(100).optional(), + budgetCap: z.number().positive().optional(), + explorationRate: z.number().min(0).max(1).optional().default(0.05), +}); + // ──── Settings Schemas ──── // FASE-01: Removed .passthrough() — only explicitly listed fields are accepted