import { getSessionUser } from '$lib/server/auth/session'; import { requirePermission } from '$lib/server/auth/authorization'; import { recordIsacAudit } from '$lib/server/audit'; import { AiMechanicConfigurationError, AiMechanicGenerationError, generateAiMechanicDraft } from '$lib/server/ai/mechanic-authoring-service'; import { AI_MECHANIC_PROMPT_VERSION } from '$lib/server/ai/mechanic-authoring-prompt'; import { AiMechanicRequestError, parseAiMechanicAuthoringRequest } from '$lib/server/ai/mechanic-authoring-schema'; import { AiMechanicRateLimitError, reserveAiMechanicGeneration } from '$lib/server/ai/mechanic-authoring-rate-limit'; import { isAllowedShdBuildRequestOrigin } from '$lib/server/request-origin'; import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; const MAX_REQUEST_BYTES = 100_000; export const POST: RequestHandler = async ({ cookies, request, url }) => { const operator = await requirePermission( await getSessionUser(cookies), 'ai.mechanic.generate' ); const origin = request.headers.get('origin'); if (!isAllowedShdBuildRequestOrigin(origin, url.origin)) { return json( { message: 'Cross-origin mechanic authoring requests are not allowed.' }, { status: 403 } ); } const contentLength = Number(request.headers.get('content-length') ?? 0); if (Number.isFinite(contentLength) && contentLength > MAX_REQUEST_BYTES) { return json({ message: 'The mechanic authoring request is too large.' }, { status: 413 }); } let payload: unknown; try { const body = await request.text(); if (Buffer.byteLength(body, 'utf8') > MAX_REQUEST_BYTES) { return json({ message: 'The mechanic authoring request is too large.' }, { status: 413 }); } payload = JSON.parse(body); } catch { return json({ message: 'The mechanic authoring request must be valid JSON.' }, { status: 400 }); } try { const authoringRequest = parseAiMechanicAuthoringRequest(payload); const release = reserveAiMechanicGeneration(operator.id); let response; try { response = await generateAiMechanicDraft(authoringRequest, operator.id); } finally { release(); } await recordIsacAudit({ actorId: operator.id, category: 'ai_assistance', action: 'mechanic_ai.generated', entityId: `${authoringRequest.context.sourceKind}:${response.meta.requestId}`, after: { sourceKind: authoringRequest.context.sourceKind, promptVersion: AI_MECHANIC_PROMPT_VERSION, responseStatus: response.status, model: response.meta.model, requestId: response.meta.requestId, usage: { inputTokens: response.meta.inputTokens, outputTokens: response.meta.outputTokens, totalTokens: response.meta.totalTokens }, variantKeys: authoringRequest.valueColumns.map((column) => column.variantKey), clarificationCount: response.status === 'clarification_required' ? response.questions.length : 0, capabilityGapCount: response.status === 'capability_gap' ? response.gaps.length : 0, draftValidated: response.status === 'draft_ready' } }); return json(response); } catch (caught) { if (caught instanceof AiMechanicRequestError) { return json({ message: caught.message }, { status: caught.status }); } if (caught instanceof AiMechanicConfigurationError) { return json({ message: caught.message }, { status: 503 }); } if (caught instanceof AiMechanicRateLimitError) { return json( { message: caught.message }, { status: 429, headers: { 'retry-after': String(caught.retryAfterSeconds) } } ); } if (caught instanceof AiMechanicGenerationError) { return json({ message: caught.message }, { status: 422 }); } console.error('Mechanic authoring assistant failed.', caught); return json( { message: 'The AI mechanic authoring service is temporarily unavailable.' }, { status: 502 } ); } };