diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts index 606f070..d749cb7 100644 --- a/src/app/api/chat/route.ts +++ b/src/app/api/chat/route.ts @@ -17,11 +17,78 @@ import { getCustomOpenaiModelName, } from '@/lib/config'; import { searchHandlers } from '@/lib/search'; -import { ChatApiBody as Body, Message, safeValidateBody } from './validation'; +import { z } from 'zod'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; +// Message schema +const messageSchema = z.object({ + messageId: z.string().min(1, 'Message ID is required'), + chatId: z.string().min(1, 'Chat ID is required'), + content: z.string().min(1, 'Message content is required'), +}); + +// ChatModel schema +const chatModelSchema = z.object({ + provider: z.string().optional(), + name: z.string().optional(), +}); + +// EmbeddingModel schema +const embeddingModelSchema = z.object({ + provider: z.string().optional(), + name: z.string().optional(), +}); + +// Main Body schema +const bodySchema = z.object({ + message: messageSchema, + optimizationMode: z.enum(['speed', 'balanced', 'quality'], { + errorMap: () => ({ + message: 'Optimization mode must be one of: speed, balanced, quality', + }), + }), + focusMode: z.string().min(1, 'Focus mode is required'), + history: z + .array( + z.tuple([z.string(), z.string()], { + errorMap: () => ({ + message: 'History items must be tuples of two strings', + }), + }), + ) + .optional() + .default([]), + files: z.array(z.string()).optional().default([]), + chatModel: chatModelSchema.optional().default({}), + embeddingModel: embeddingModelSchema.optional().default({}), + systemInstructions: z.string().nullable().optional().default(''), +}); + +type Message = z.infer; +type Body = z.infer; + +// Safe validation that returns success/error +function safeValidateBody(data: unknown) { + const result = bodySchema.safeParse(data); + + if (!result.success) { + return { + success: false, + error: result.error.errors.map((e) => ({ + path: e.path.join('.'), + message: e.message, + })), + }; + } + + return { + success: true, + data: result.data, + }; +} + const handleEmitterEvents = async ( stream: EventEmitter, writer: WritableStreamDefaultWriter, @@ -267,7 +334,7 @@ export const POST = async (req: Request) => { embedding, body.optimizationMode, body.files, - body.systemInstructions, + body.systemInstructions as string, ); const responseStream = new TransformStream(); diff --git a/src/app/api/chat/validation.ts b/src/app/api/chat/validation.ts deleted file mode 100644 index 8bed73f..0000000 --- a/src/app/api/chat/validation.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { z } from 'zod'; - -// Message schema -const messageSchema = z.object({ - messageId: z.string().min(1, 'Message ID is required'), - chatId: z.string().min(1, 'Chat ID is required'), - content: z.string().min(1, 'Message content is required'), -}); - -// ChatModel schema -const chatModelSchema = z.object({ - provider: z.string().optional(), - name: z.string().optional(), -}); - -// EmbeddingModel schema -const embeddingModelSchema = z.object({ - provider: z.string().optional(), - name: z.string().optional(), -}); - -// Main Body schema -export const bodySchema = z.object({ - message: messageSchema, - optimizationMode: z.enum(['speed', 'balanced', 'quality'], { - errorMap: () => ({ - message: 'Optimization mode must be one of: speed, balanced, quality', - }), - }), - focusMode: z.string().min(1, 'Focus mode is required'), - history: z - .array( - z.tuple([z.string(), z.string()], { - errorMap: () => ({ - message: 'History items must be tuples of two strings', - }), - }), - ) - .optional() - .default([]), - files: z.array(z.string()).optional().default([]), - chatModel: chatModelSchema.optional().default({}), - embeddingModel: embeddingModelSchema.optional().default({}), - systemInstructions: z.string().optional().default(''), -}); - -export type Message = z.infer; -export type ChatModel = z.infer; -export type EmbeddingModel = z.infer; -export type ChatApiBody = z.infer; - -// Safe validation that returns success/error -export function safeValidateBody(data: unknown) { - const result = bodySchema.safeParse(data); - - if (!result.success) { - return { - success: false, - error: result.error.errors.map((e) => ({ - path: e.path.join('.'), - message: e.message, - })), - }; - } - - return { - success: true, - data: result.data, - }; -}