2026-03-02 14:00:40 +00:00
import { geolocation , ipAddress } from "@vercel/functions" ;
2024-10-30 16:01:24 +05:30
import {
2025-07-03 02:26:34 -07:00
convertToModelMessages ,
createUIMessageStream ,
2026-01-15 16:06:42 +00:00
createUIMessageStreamResponse ,
generateId ,
2025-07-03 02:26:34 -07:00
stepCountIs ,
2024-10-30 16:01:24 +05:30
streamText ,
2025-09-21 11:02:31 -07:00
} from "ai" ;
2026-03-13 13:12:33 -07:00
import { checkBotId } from "botid/server" ;
2025-09-21 11:02:31 -07:00
import { after } from "next/server" ;
2026-01-15 16:06:42 +00:00
import { createResumableStreamContext } from "resumable-stream" ;
2026-03-20 09:37:02 +00:00
import { auth , type UserType } from "@/app/(auth)/auth" ;
2025-09-21 11:02:31 -07:00
import { entitlementsByUserType } from "@/lib/ai/entitlements" ;
2026-03-20 09:37:02 +00:00
import {
allowedModelIds ,
chatModels ,
DEFAULT_CHAT_MODEL ,
getCapabilities ,
} from "@/lib/ai/models" ;
2026-03-13 13:12:33 -07:00
import { type RequestHints , systemPrompt } from "@/lib/ai/prompts" ;
2025-12-14 21:26:49 +00:00
import { getLanguageModel } from "@/lib/ai/providers" ;
2025-09-21 11:02:31 -07:00
import { createDocument } from "@/lib/ai/tools/create-document" ;
2026-03-20 09:37:02 +00:00
import { editDocument } from "@/lib/ai/tools/edit-document" ;
2025-09-21 11:02:31 -07:00
import { getWeather } from "@/lib/ai/tools/get-weather" ;
import { requestSuggestions } from "@/lib/ai/tools/request-suggestions" ;
import { updateDocument } from "@/lib/ai/tools/update-document" ;
import { isProductionEnvironment } from "@/lib/constants" ;
2024-10-30 16:01:24 +05:30
import {
2025-05-01 12:36:52 -07:00
createStreamId ,
2024-10-30 16:01:24 +05:30
deleteChatById ,
getChatById ,
2025-04-25 23:40:15 -07:00
getMessageCountByUserId ,
2025-04-26 01:09:01 -07:00
getMessagesByChatId ,
2024-10-30 16:01:24 +05:30
saveChat ,
2024-11-05 17:15:51 +03:00
saveMessages ,
2025-12-15 16:56:10 +00:00
updateChatTitleById ,
2025-12-19 23:24:24 +00:00
updateMessage ,
2025-09-21 11:02:31 -07:00
} from "@/lib/db/queries" ;
2025-11-01 01:18:05 +01:00
import type { DBMessage } from "@/lib/db/schema" ;
2026-02-23 17:24:17 -08:00
import { ChatbotError } from "@/lib/errors" ;
2026-03-02 14:00:40 +00:00
import { checkIpRateLimit } from "@/lib/ratelimit" ;
2025-09-21 11:02:31 -07:00
import type { ChatMessage } from "@/lib/types" ;
import { convertToUIMessages , generateUUID } from "@/lib/utils" ;
import { generateTitleFromUserMessage } from "../../actions" ;
import { type PostRequestBody , postRequestBodySchema } from "./schema" ;
2024-10-30 16:01:24 +05:30
export const maxDuration = 60 ;
2026-01-15 16:06:42 +00:00
function getStreamContext() {
try {
return createResumableStreamContext ( { waitUntil : after } ) ;
} catch ( _ ) {
return null ;
2025-05-03 00:32:46 -07:00
}
}
2025-05-01 12:36:52 -07:00
2026-01-15 16:06:42 +00:00
export { getStreamContext } ;
2024-10-11 18:00:22 +05:30
export async function POST ( request : Request ) {
2025-04-26 01:09:01 -07:00
let requestBody : PostRequestBody ;
try {
const json = await request . json ( ) ;
requestBody = postRequestBodySchema . parse ( json ) ;
} catch ( _ ) {
2026-02-23 17:24:17 -08:00
return new ChatbotError ( "bad_request:api" ) . toResponse ( ) ;
2025-04-26 01:09:01 -07:00
}
2025-03-04 17:25:46 -08:00
try {
2025-12-19 23:24:24 +00:00
const { id , message , messages , selectedChatModel , selectedVisibilityType } =
requestBody ;
2025-03-04 17:25:46 -08:00
2026-03-04 19:20:57 +00:00
const [ , session ] = await Promise . all ( [
checkBotId ( ) . catch ( ( ) = > null ) ,
2026-03-20 09:37:02 +00:00
auth ( ) ,
2026-03-04 19:20:57 +00:00
] ) ;
2025-03-04 17:25:46 -08:00
2025-04-26 01:09:01 -07:00
if ( ! session ? . user ) {
2026-02-23 17:24:17 -08:00
return new ChatbotError ( "unauthorized:chat" ) . toResponse ( ) ;
2025-03-04 17:25:46 -08:00
}
2024-11-05 17:15:51 +03:00
2026-03-20 09:37:02 +00:00
const chatModel = allowedModelIds . has ( selectedChatModel )
? selectedChatModel
: DEFAULT_CHAT_MODEL ;
2026-03-03 15:56:05 +00:00
2026-03-02 14:00:40 +00:00
await checkIpRateLimit ( ipAddress ( request ) ) ;
2026-03-20 09:37:02 +00:00
const userType : UserType = session . user . type ;
2025-04-25 23:40:15 -07:00
const messageCount = await getMessageCountByUserId ( {
id : session.user.id ,
2026-03-04 19:20:57 +00:00
differenceInHours : 1 ,
2025-04-25 23:40:15 -07:00
} ) ;
2026-03-04 19:20:57 +00:00
if ( messageCount > entitlementsByUserType [ userType ] . maxMessagesPerHour ) {
2026-02-23 17:24:17 -08:00
return new ChatbotError ( "rate_limit:chat" ) . toResponse ( ) ;
2025-04-25 23:40:15 -07:00
}
2025-12-19 23:24:24 +00:00
const isToolApprovalFlow = Boolean ( messages ) ;
2025-03-04 17:25:46 -08:00
const chat = await getChatById ( { id } ) ;
2025-11-01 01:18:05 +01:00
let messagesFromDb : DBMessage [ ] = [ ] ;
2025-12-15 16:56:10 +00:00
let titlePromise : Promise < string > | null = null ;
2024-11-05 17:15:51 +03:00
2025-09-21 11:02:31 -07:00
if ( chat ) {
if ( chat . userId !== session . user . id ) {
2026-02-23 17:24:17 -08:00
return new ChatbotError ( "forbidden:chat" ) . toResponse ( ) ;
2025-09-21 11:02:31 -07:00
}
2026-03-20 09:37:02 +00:00
messagesFromDb = await getMessagesByChatId ( { id } ) ;
2025-12-19 23:24:24 +00:00
} else if ( message ? . role === "user" ) {
2025-05-01 17:47:48 -07:00
await saveChat ( {
id ,
userId : session.user.id ,
2025-12-15 16:56:10 +00:00
title : "New chat" ,
2025-05-01 17:47:48 -07:00
visibility : selectedVisibilityType ,
} ) ;
2025-12-15 16:56:10 +00:00
titlePromise = generateTitleFromUserMessage ( { message } ) ;
2025-03-04 17:25:46 -08:00
}
2024-11-05 17:15:51 +03:00
2026-03-20 09:37:02 +00:00
let uiMessages : ChatMessage [ ] ;
if ( isToolApprovalFlow && messages ) {
const dbMessages = convertToUIMessages ( messagesFromDb ) ;
const approvalStates = new Map (
messages . flatMap (
( m ) = >
m . parts
? . filter (
( p : Record < string , unknown > ) = >
p . state === "approval-responded" ||
p . state === "output-denied"
)
. map ( ( p : Record < string , unknown > ) = > [
String ( p . toolCallId ? ? "" ) ,
p ,
] ) ? ? [ ]
)
) ;
uiMessages = dbMessages . map ( ( msg ) = > ( {
. . . msg ,
parts : msg.parts.map ( ( part ) = > {
if (
"toolCallId" in part &&
approvalStates . has ( String ( part . toolCallId ) )
) {
return { . . . part , . . . approvalStates . get ( String ( part . toolCallId ) ) } ;
}
return part ;
} ) ,
} ) ) as ChatMessage [ ] ;
} else {
uiMessages = [
. . . convertToUIMessages ( messagesFromDb ) ,
message as ChatMessage ,
] ;
}
2025-04-26 01:09:01 -07:00
2025-04-29 16:18:46 -07:00
const { longitude , latitude , city , country } = geolocation ( request ) ;
const requestHints : RequestHints = {
longitude ,
latitude ,
city ,
country ,
} ;
2025-12-19 23:24:24 +00:00
if ( message ? . role === "user" ) {
await saveMessages ( {
messages : [
{
chatId : id ,
id : message.id ,
role : "user" ,
parts : message.parts ,
attachments : [ ] ,
createdAt : new Date ( ) ,
} ,
] ,
} ) ;
}
2025-02-19 19:09:03 -06:00
2026-03-20 09:37:02 +00:00
const modelConfig = chatModels . find ( ( m ) = > m . id === chatModel ) ;
const modelCapabilities = await getCapabilities ( ) ;
const capabilities = modelCapabilities [ chatModel ] ;
const isReasoningModel = capabilities ? . reasoning === true ;
const supportsTools = capabilities ? . tools === true ;
2026-01-15 16:06:42 +00:00
const modelMessages = await convertToModelMessages ( uiMessages ) ;
2025-05-01 12:36:52 -07:00
2025-07-03 02:26:34 -07:00
const stream = createUIMessageStream ( {
2025-12-19 23:24:24 +00:00
originalMessages : isToolApprovalFlow ? uiMessages : undefined ,
execute : async ( { writer : dataStream } ) = > {
2025-03-04 17:25:46 -08:00
const result = streamText ( {
2026-03-20 09:37:02 +00:00
model : getLanguageModel ( chatModel ) ,
system : systemPrompt ( { requestHints , supportsTools } ) ,
2026-01-15 16:06:42 +00:00
messages : modelMessages ,
2025-07-03 02:26:34 -07:00
stopWhen : stepCountIs ( 5 ) ,
2026-03-20 09:37:02 +00:00
experimental_activeTools :
isReasoningModel && ! supportsTools
? [ ]
: [
"getWeather" ,
"createDocument" ,
"editDocument" ,
"updateDocument" ,
"requestSuggestions" ,
] ,
providerOptions : {
. . . ( modelConfig ? . gatewayOrder && {
gateway : { order : modelConfig.gatewayOrder } ,
} ) ,
. . . ( modelConfig ? . reasoningEffort && {
openai : { reasoningEffort : modelConfig.reasoningEffort } ,
} ) ,
} ,
2025-03-04 17:25:46 -08:00
tools : {
getWeather ,
2026-03-20 09:37:02 +00:00
createDocument : createDocument ( {
session ,
dataStream ,
modelId : chatModel ,
} ) ,
editDocument : editDocument ( { dataStream , session } ) ,
updateDocument : updateDocument ( {
session ,
dataStream ,
modelId : chatModel ,
} ) ,
requestSuggestions : requestSuggestions ( {
session ,
dataStream ,
modelId : chatModel ,
} ) ,
2025-03-04 17:25:46 -08:00
} ,
experimental_telemetry : {
isEnabled : isProductionEnvironment ,
2025-09-21 11:02:31 -07:00
functionId : "stream-text" ,
2025-03-04 17:25:46 -08:00
} ,
} ) ;
2026-03-03 15:56:05 +00:00
dataStream . merge (
2026-03-13 13:12:33 -07:00
result . toUIMessageStream ( { sendReasoning : isReasoningModel } )
2026-03-03 15:56:05 +00:00
) ;
2025-03-04 17:25:46 -08:00
2026-01-15 16:06:42 +00:00
if ( titlePromise ) {
2026-05-18 15:08:46 +01:00
try {
const title = await titlePromise ;
dataStream . write ( { type : "data-chat-title" , data : title } ) ;
updateChatTitleById ( { chatId : id , title } ) ;
} catch ( _ ) {
/* non-fatal */
}
2026-01-15 16:06:42 +00:00
}
2025-07-03 02:26:34 -07:00
} ,
generateId : generateUUID ,
2025-12-19 23:24:24 +00:00
onFinish : async ( { messages : finishedMessages } ) = > {
if ( isToolApprovalFlow ) {
for ( const finishedMsg of finishedMessages ) {
const existingMsg = uiMessages . find ( ( m ) = > m . id === finishedMsg . id ) ;
if ( existingMsg ) {
await updateMessage ( {
id : finishedMsg.id ,
parts : finishedMsg.parts ,
} ) ;
} else {
await saveMessages ( {
messages : [
{
id : finishedMsg.id ,
role : finishedMsg.role ,
parts : finishedMsg.parts ,
createdAt : new Date ( ) ,
attachments : [ ] ,
chatId : id ,
} ,
] ,
} ) ;
}
}
} else if ( finishedMessages . length > 0 ) {
await saveMessages ( {
messages : finishedMessages.map ( ( currentMessage ) = > ( {
id : currentMessage.id ,
role : currentMessage.role ,
parts : currentMessage.parts ,
createdAt : new Date ( ) ,
attachments : [ ] ,
chatId : id ,
} ) ) ,
} ) ;
}
2025-03-04 17:25:46 -08:00
} ,
2026-03-02 16:01:11 +00:00
onError : ( error ) = > {
if (
error instanceof Error &&
error . message ? . includes (
2026-03-13 13:12:33 -07:00
"AI Gateway requires a valid credit card on file to service requests"
2026-03-02 16:01:11 +00:00
)
) {
return "AI Gateway requires a valid credit card on file to service requests. Please visit https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai%3Fmodal%3Dadd-credit-card to add a card and unlock your free credits." ;
}
return "Oops, an error occurred!" ;
} ,
2025-03-04 17:25:46 -08:00
} ) ;
2025-05-01 12:36:52 -07:00
2026-01-15 16:06:42 +00:00
return createUIMessageStreamResponse ( {
stream ,
async consumeSseStream ( { stream : sseStream } ) {
if ( ! process . env . REDIS_URL ) {
return ;
2025-12-19 23:24:24 +00:00
}
2026-01-15 16:06:42 +00:00
try {
const streamContext = getStreamContext ( ) ;
if ( streamContext ) {
const streamId = generateId ( ) ;
await createStreamId ( { streamId , chatId : id } ) ;
await streamContext . createNewResumableStream (
streamId ,
( ) = > sseStream
) ;
}
} catch ( _ ) {
2026-03-20 09:37:02 +00:00
/* non-critical */
2026-01-15 16:06:42 +00:00
}
} ,
} ) ;
2025-05-13 19:01:28 -07:00
} catch ( error ) {
2025-09-21 19:12:46 +01:00
const vercelId = request . headers . get ( "x-vercel-id" ) ;
2025-09-21 19:06:54 +01:00
2026-02-23 17:24:17 -08:00
if ( error instanceof ChatbotError ) {
2025-05-13 19:01:28 -07:00
return error . toResponse ( ) ;
}
2025-09-01 11:07:07 +01:00
2025-09-13 20:40:08 +01:00
if (
error instanceof Error &&
2025-09-21 13:11:54 +01:00
error . message ? . includes (
2025-09-21 11:02:31 -07:00
"AI Gateway requires a valid credit card on file to service requests"
2025-09-21 13:11:54 +01:00
)
2025-09-13 20:40:08 +01:00
) {
2026-02-23 17:24:17 -08:00
return new ChatbotError ( "bad_request:activate_gateway" ) . toResponse ( ) ;
2025-09-13 20:40:08 +01:00
}
2025-09-21 19:06:54 +01:00
console . error ( "Unhandled error in chat API:" , error , { vercelId } ) ;
2026-02-23 17:24:17 -08:00
return new ChatbotError ( "offline:chat" ) . toResponse ( ) ;
2025-03-04 17:25:46 -08:00
}
2024-10-11 18:00:22 +05:30
}
export async function DELETE ( request : Request ) {
const { searchParams } = new URL ( request . url ) ;
2025-09-21 11:02:31 -07:00
const id = searchParams . get ( "id" ) ;
2024-10-11 18:00:22 +05:30
if ( ! id ) {
2026-02-23 17:24:17 -08:00
return new ChatbotError ( "bad_request:api" ) . toResponse ( ) ;
2024-10-11 18:00:22 +05:30
}
2026-03-20 09:37:02 +00:00
const session = await auth ( ) ;
2024-10-11 18:00:22 +05:30
2025-05-13 19:01:28 -07:00
if ( ! session ? . user ) {
2026-02-23 17:24:17 -08:00
return new ChatbotError ( "unauthorized:chat" ) . toResponse ( ) ;
2024-10-11 18:00:22 +05:30
}
2025-05-13 19:01:28 -07:00
const chat = await getChatById ( { id } ) ;
2024-10-11 18:00:22 +05:30
2025-09-09 00:29:04 +02:00
if ( chat ? . userId !== session . user . id ) {
2026-02-23 17:24:17 -08:00
return new ChatbotError ( "forbidden:chat" ) . toResponse ( ) ;
2025-05-13 19:01:28 -07:00
}
2024-10-11 18:00:22 +05:30
2025-05-13 19:01:28 -07:00
const deletedChat = await deleteChatById ( { id } ) ;
2024-10-11 18:00:22 +05:30
2025-05-13 19:01:28 -07:00
return Response . json ( deletedChat , { status : 200 } ) ;
2024-10-11 18:00:22 +05:30
}