| import { |
| convertToModelMessages, |
| createUIMessageStream, |
| JsonToSseTransformStream, |
| smoothStream, |
| stepCountIs, |
| streamText, |
| } from 'ai'; |
| import { auth, type UserType } from '@/app/(auth)/auth'; |
| import { type RequestHints, systemPrompt } from '@/lib/ai/prompts'; |
| import { |
| createStreamId, |
| deleteChatById, |
| getChatById, |
| getMessageCountByUserId, |
| getMessagesByChatId, |
| saveChat, |
| saveMessages, |
| } from '@/lib/db/queries'; |
| import { convertToUIMessages, generateUUID } from '@/lib/utils'; |
| import { generateTitleFromUserMessage } from '../../actions'; |
| import { createDocument } from '@/lib/ai/tools/create-document'; |
| import { updateDocument } from '@/lib/ai/tools/update-document'; |
| import { requestSuggestions } from '@/lib/ai/tools/request-suggestions'; |
| import { getWeather } from '@/lib/ai/tools/get-weather'; |
| import { isProductionEnvironment } from '@/lib/constants'; |
| import { myProvider } from '@/lib/ai/providers'; |
| import { entitlementsByUserType } from '@/lib/ai/entitlements'; |
| import { postRequestBodySchema, type PostRequestBody } from './schema'; |
| import { geolocation } from '@vercel/functions'; |
| import { |
| createResumableStreamContext, |
| type ResumableStreamContext, |
| } from 'resumable-stream'; |
| import { after } from 'next/server'; |
| import { ChatSDKError } from '@/lib/errors'; |
| import type { ChatMessage } from '@/lib/types'; |
| import type { ChatModel } from '@/lib/ai/models'; |
| import type { VisibilityType } from '@/components/visibility-selector'; |
|
|
| export const maxDuration = 60; |
|
|
| let globalStreamContext: ResumableStreamContext | null = null; |
|
|
| export function getStreamContext() { |
| if (!globalStreamContext) { |
| try { |
| globalStreamContext = createResumableStreamContext({ |
| waitUntil: after, |
| }); |
| } catch (error: any) { |
| if (error.message.includes('REDIS_URL')) { |
| console.log( |
| ' > Resumable streams are disabled due to missing REDIS_URL', |
| ); |
| } else { |
| console.error(error); |
| } |
| } |
| } |
|
|
| return globalStreamContext; |
| } |
|
|
| export async function POST(request: Request) { |
| let requestBody: PostRequestBody; |
|
|
| try { |
| const json = await request.json(); |
| requestBody = postRequestBodySchema.parse(json); |
| } catch (_) { |
| return new ChatSDKError('bad_request:api').toResponse(); |
| } |
|
|
| try { |
| const { |
| id, |
| message, |
| selectedChatModel, |
| selectedVisibilityType, |
| }: { |
| id: string; |
| message: ChatMessage; |
| selectedChatModel: ChatModel['id']; |
| selectedVisibilityType: VisibilityType; |
| } = requestBody; |
|
|
| const session = await auth(); |
|
|
| if (!session?.user) { |
| return new ChatSDKError('unauthorized:chat').toResponse(); |
| } |
|
|
| const userType: UserType = session.user.type; |
|
|
| const messageCount = await getMessageCountByUserId({ |
| id: session.user.id, |
| differenceInHours: 24, |
| }); |
|
|
| if (messageCount > entitlementsByUserType[userType].maxMessagesPerDay) { |
| return new ChatSDKError('rate_limit:chat').toResponse(); |
| } |
|
|
| const chat = await getChatById({ id }); |
|
|
| if (!chat) { |
| const title = await generateTitleFromUserMessage({ |
| message, |
| }); |
|
|
| await saveChat({ |
| id, |
| userId: session.user.id, |
| title, |
| visibility: selectedVisibilityType, |
| }); |
| } else { |
| if (chat.userId !== session.user.id) { |
| return new ChatSDKError('forbidden:chat').toResponse(); |
| } |
| } |
|
|
| const messagesFromDb = await getMessagesByChatId({ id }); |
| const uiMessages = [...convertToUIMessages(messagesFromDb), message]; |
|
|
| const { longitude, latitude, city, country } = geolocation(request); |
|
|
| const requestHints: RequestHints = { |
| longitude, |
| latitude, |
| city, |
| country, |
| }; |
|
|
| await saveMessages({ |
| messages: [ |
| { |
| chatId: id, |
| id: message.id, |
| role: 'user', |
| parts: message.parts, |
| attachments: [], |
| createdAt: new Date(), |
| }, |
| ], |
| }); |
|
|
| const streamId = generateUUID(); |
| await createStreamId({ streamId, chatId: id }); |
|
|
| const stream = createUIMessageStream({ |
| execute: ({ writer: dataStream }) => { |
| const result = streamText({ |
| model: myProvider.languageModel(selectedChatModel), |
| system: systemPrompt({ selectedChatModel, requestHints }), |
| messages: convertToModelMessages(uiMessages), |
| stopWhen: stepCountIs(5), |
| experimental_activeTools: |
| selectedChatModel === 'chat-model-reasoning' |
| ? [] |
| : [ |
| 'getWeather', |
| 'createDocument', |
| 'updateDocument', |
| 'requestSuggestions', |
| ], |
| experimental_transform: smoothStream({ chunking: 'word' }), |
| tools: { |
| getWeather, |
| createDocument: createDocument({ session, dataStream }), |
| updateDocument: updateDocument({ session, dataStream }), |
| requestSuggestions: requestSuggestions({ |
| session, |
| dataStream, |
| }), |
| }, |
| experimental_telemetry: { |
| isEnabled: isProductionEnvironment, |
| functionId: 'stream-text', |
| }, |
| }); |
|
|
| result.consumeStream(); |
|
|
| dataStream.merge( |
| result.toUIMessageStream({ |
| sendReasoning: true, |
| }), |
| ); |
| }, |
| generateId: generateUUID, |
| onFinish: async ({ messages }) => { |
| await saveMessages({ |
| messages: messages.map((message) => ({ |
| id: message.id, |
| role: message.role, |
| parts: message.parts, |
| createdAt: new Date(), |
| attachments: [], |
| chatId: id, |
| })), |
| }); |
| }, |
| onError: () => { |
| return 'Oops, an error occurred!'; |
| }, |
| }); |
|
|
| const streamContext = getStreamContext(); |
|
|
| if (streamContext) { |
| return new Response( |
| await streamContext.resumableStream(streamId, () => |
| stream.pipeThrough(new JsonToSseTransformStream()), |
| ), |
| ); |
| } else { |
| return new Response(stream.pipeThrough(new JsonToSseTransformStream())); |
| } |
| } catch (error) { |
| if (error instanceof ChatSDKError) { |
| return error.toResponse(); |
| } |
| } |
| } |
|
|
| export async function DELETE(request: Request) { |
| const { searchParams } = new URL(request.url); |
| const id = searchParams.get('id'); |
|
|
| if (!id) { |
| return new ChatSDKError('bad_request:api').toResponse(); |
| } |
|
|
| const session = await auth(); |
|
|
| if (!session?.user) { |
| return new ChatSDKError('unauthorized:chat').toResponse(); |
| } |
|
|
| const chat = await getChatById({ id }); |
|
|
| if (chat.userId !== session.user.id) { |
| return new ChatSDKError('forbidden:chat').toResponse(); |
| } |
|
|
| const deletedChat = await deleteChatById({ id }); |
|
|
| return Response.json(deletedChat, { status: 200 }); |
| } |
|
|