2026-02-13 23:49:55 -08:00
import { performance } from "node:perf_hooks" ;
2026-02-13 22:43:55 -08:00
import { z } from "zod" ;
import type { FastifyInstance } from "fastify" ;
import { prisma } from "./db.js" ;
import { requireAdmin } from "./auth.js" ;
2026-02-13 23:15:12 -08:00
import { env } from "./env.js" ;
2026-05-02 19:21:06 -07:00
import { buildComparableAttachments } from "./llm/message-content.js" ;
2026-02-13 22:43:55 -08:00
import { runMultiplex } from "./llm/multiplexer.js" ;
import { runMultiplexStream } from "./llm/streaming.js" ;
2026-02-14 21:00:30 -08:00
import { getModelCatalogSnapshot } from "./llm/model-catalog.js" ;
2026-02-14 21:27:44 -08:00
import { openaiClient } from "./llm/providers.js" ;
2026-02-13 23:49:55 -08:00
import { exaClient } from "./search/exa.js" ;
2026-05-02 19:21:06 -07:00
import type { ChatAttachment } from "./llm/types.js" ;
2026-02-13 22:43:55 -08:00
2026-02-13 23:15:12 -08:00
type IncomingChatMessage = {
role : "system" | "user" | "assistant" | "tool" ;
content : string ;
name? : string ;
2026-05-02 19:21:06 -07:00
attachments? : ChatAttachment [ ] ;
2026-02-13 23:15:12 -08:00
} ;
2026-03-10 17:40:22 -07:00
function sameMessage (
2026-05-02 19:21:06 -07:00
a : { role : string ; content : string ; name? : string | null ; metadata? : unknown } ,
b : { role : string ; content : string ; name? : string | null ; attachments? : ChatAttachment [ ] }
2026-03-10 17:40:22 -07:00
) {
2026-05-02 19:21:06 -07:00
const existingAttachments = JSON . stringify ( buildComparableAttachments ( ( a . metadata as Record < string , unknown > | null ) ? . attachments ? ? null ) ) ;
const incomingAttachments = JSON . stringify ( b . attachments ? ? [ ] ) ;
return (
a . role === b . role &&
a . content === b . content &&
( a . name ? ? null ) === ( b . name ? ? null ) &&
existingAttachments === incomingAttachments
) ;
2026-02-13 23:15:12 -08:00
}
2026-03-10 17:40:22 -07:00
function isToolCallLogMetadata ( value : unknown ) {
if ( ! value || typeof value !== "object" || Array . isArray ( value ) ) return false ;
const record = value as Record < string , unknown > ;
return record . kind === "tool_call" ;
}
function isToolCallLogMessage ( message : { role : string ; metadata : unknown } ) {
return message . role === "tool" && isToolCallLogMetadata ( message . metadata ) ;
}
2026-02-13 23:15:12 -08:00
async function storeNonAssistantMessages ( chatId : string , messages : IncomingChatMessage [ ] ) {
const incoming = messages . filter ( ( m ) = > m . role !== "assistant" ) ;
if ( ! incoming . length ) return ;
const existing = await prisma . message . findMany ( {
where : { chatId } ,
orderBy : { createdAt : "asc" } ,
2026-03-10 17:40:22 -07:00
select : { role : true , content : true , name : true , metadata : true } ,
2026-02-13 23:15:12 -08:00
} ) ;
2026-03-10 17:40:22 -07:00
const existingNonAssistant = existing . filter ( ( m ) = > m . role !== "assistant" && ! isToolCallLogMessage ( m ) ) ;
2026-02-13 23:15:12 -08:00
let sharedPrefix = 0 ;
const max = Math . min ( existingNonAssistant . length , incoming . length ) ;
2026-03-10 17:40:22 -07:00
while ( sharedPrefix < max && sameMessage ( existingNonAssistant [ sharedPrefix ] , incoming [ sharedPrefix ] ) ) {
2026-02-13 23:15:12 -08:00
sharedPrefix += 1 ;
}
if ( sharedPrefix === incoming . length ) return ;
const toInsert = sharedPrefix === existingNonAssistant . length ? incoming . slice ( existingNonAssistant . length ) : incoming ;
if ( ! toInsert . length ) return ;
await prisma . message . createMany ( {
data : toInsert.map ( ( m ) = > ( {
chatId ,
role : m.role as any ,
content : m.content ,
name : m.name ,
2026-05-02 19:21:06 -07:00
metadata : m.attachments?.length ? ( { attachments : m.attachments } as any ) : undefined ,
2026-02-13 23:15:12 -08:00
} ) ) ,
} ) ;
}
2026-05-02 19:21:06 -07:00
const MAX_CHAT_ATTACHMENTS = 8 ;
const MAX_IMAGE_ATTACHMENT_BYTES = 6 * 1024 * 1024 ;
const MAX_TEXT_ATTACHMENT_CHARS = 200 _000 ;
const MAX_IMAGE_DATA_URL_CHARS = 8 _500_000 ;
const ChatAttachmentSchema = z . discriminatedUnion ( "kind" , [
z . object ( {
kind : z.literal ( "image" ) ,
id : z.string ( ) . trim ( ) . min ( 1 ) . max ( 128 ) ,
filename : z.string ( ) . trim ( ) . min ( 1 ) . max ( 255 ) ,
mimeType : z.enum ( [ "image/png" , "image/jpeg" ] ) ,
sizeBytes : z.number ( ) . int ( ) . positive ( ) . max ( MAX_IMAGE_ATTACHMENT_BYTES ) ,
dataUrl : z
. string ( )
. max ( MAX_IMAGE_DATA_URL_CHARS )
. regex ( /^data:image\/(?:png|jpeg);base64,[a-z0-9+/=\s]+$/i , "Invalid image data URL" ) ,
} ) ,
z . object ( {
kind : z.literal ( "text" ) ,
id : z.string ( ) . trim ( ) . min ( 1 ) . max ( 128 ) ,
filename : z.string ( ) . trim ( ) . min ( 1 ) . max ( 255 ) ,
mimeType : z.string ( ) . trim ( ) . min ( 1 ) . max ( 127 ) ,
sizeBytes : z.number ( ) . int ( ) . positive ( ) . max ( 8 * 1024 * 1024 ) ,
text : z.string ( ) . max ( MAX_TEXT_ATTACHMENT_CHARS ) ,
truncated : z.boolean ( ) . optional ( ) ,
} ) ,
] ) ;
const CompletionMessageSchema = z
. object ( {
role : z.enum ( [ "system" , "user" , "assistant" , "tool" ] ) ,
content : z.string ( ) ,
name : z.string ( ) . optional ( ) ,
attachments : z.array ( ChatAttachmentSchema ) . max ( MAX_CHAT_ATTACHMENTS ) . optional ( ) ,
} )
. superRefine ( ( value , ctx ) = > {
if ( value . attachments ? . length && value . role === "tool" ) {
ctx . addIssue ( {
code : z.ZodIssueCode.custom ,
message : "Tool messages cannot include attachments." ,
path : [ "attachments" ] ,
} ) ;
}
} ) ;
function mergeAttachmentsIntoMetadata ( metadata : unknown , attachments? : ChatAttachment [ ] ) {
if ( ! attachments ? . length ) return metadata as any ;
if ( ! metadata || typeof metadata !== "object" || Array . isArray ( metadata ) ) {
return { attachments } ;
}
return {
. . . ( metadata as Record < string , unknown > ) ,
attachments ,
} ;
}
2026-02-14 01:53:34 -08:00
const SearchRunBody = z . object ( {
query : z.string ( ) . trim ( ) . min ( 1 ) . optional ( ) ,
title : z.string ( ) . trim ( ) . min ( 1 ) . optional ( ) ,
type : z . enum ( [ "auto" , "fast" , "deep" , "instant" ] ) . optional ( ) ,
numResults : z.number ( ) . int ( ) . min ( 1 ) . max ( 25 ) . optional ( ) ,
includeDomains : z.array ( z . string ( ) . trim ( ) . min ( 1 ) ) . max ( 50 ) . optional ( ) ,
excludeDomains : z.array ( z . string ( ) . trim ( ) . min ( 1 ) ) . max ( 50 ) . optional ( ) ,
} ) ;
function mapSearchResultRow ( searchId : string , result : any , index : number ) {
return {
searchId ,
rank : index ,
title : result.title ? ? null ,
url : result.url ,
publishedDate : result.publishedDate ? ? null ,
author : result.author ? ? null ,
text : result.text ? ? null ,
highlights : Array.isArray ( result . highlights ) ? ( result . highlights as any ) : null ,
highlightScores : Array.isArray ( result . highlightScores ) ? ( result . highlightScores as any ) : null ,
score : typeof result . score === "number" ? result.score : null ,
favicon : result.favicon ? ? null ,
image : result.image ? ? null ,
} ;
}
function mapSearchResultPreview ( result : any , index : number ) {
return {
id : ` preview-result- ${ index } ` ,
createdAt : new Date ( ) . toISOString ( ) ,
rank : index ,
title : result.title ? ? null ,
url : result.url ,
publishedDate : result.publishedDate ? ? null ,
author : result.author ? ? null ,
text : result.text ? ? null ,
highlights : Array.isArray ( result . highlights ) ? ( result . highlights as any ) : null ,
highlightScores : Array.isArray ( result . highlightScores ) ? ( result . highlightScores as any ) : null ,
score : typeof result . score === "number" ? result.score : null ,
favicon : result.favicon ? ? null ,
image : result.image ? ? null ,
} ;
}
2026-05-02 16:48:01 -07:00
function truncateContextPart ( value : string | null | undefined , maxLength : number ) {
const trimmed = value ? . trim ( ) ;
if ( ! trimmed ) return null ;
if ( trimmed . length <= maxLength ) return trimmed ;
return ` ${ trimmed . slice ( 0 , maxLength - 1 ) . trimEnd ( ) } ... ` ;
}
2026-02-14 01:53:34 -08:00
function parseAnswerText ( answerResponse : any ) {
if ( typeof answerResponse ? . answer === "string" ) return answerResponse . answer ;
if ( answerResponse ? . answer ) return JSON . stringify ( answerResponse . answer , null , 2 ) ;
return null ;
}
2026-02-14 21:27:44 -08:00
function normalizeSuggestedTitle ( raw : string , fallback : string ) {
const oneLine = raw
. replace ( /\r?\n+/g , " " )
. replace ( /^['"`\s]+|['"`\s]+$/g , "" )
. replace ( /\s+/g , " " )
. trim ( ) ;
const fromRaw = oneLine || fallback ;
const words = fromRaw . split ( /\s+/ ) . filter ( Boolean ) ;
return words . slice ( 0 , 4 ) . join ( " " ) . slice ( 0 , 64 ) . trim ( ) || fallback ;
}
async function generateChatTitle ( content : string ) {
const systemPrompt =
"You create short chat titles. Return exactly one line, maximum 4 words, no quotes, no trailing punctuation." ;
const userPrompt = ` User request: \ n ${ content } \ n \ nTitle: ` ;
2026-05-02 21:44:32 -07:00
const response = await openaiClient ( ) . responses . create ( {
2026-02-14 21:27:44 -08:00
model : "gpt-4.1-mini" ,
temperature : 0 ,
2026-05-02 21:44:32 -07:00
max_output_tokens : 20 ,
instructions : systemPrompt ,
input : userPrompt ,
store : false ,
2026-02-14 21:27:44 -08:00
} ) ;
2026-05-02 21:44:32 -07:00
return response . output_text ? ? "" ;
2026-02-14 21:27:44 -08:00
}
2026-02-14 01:53:34 -08:00
function normalizeUrlForMatch ( input : string | null | undefined ) {
if ( ! input ) return "" ;
try {
const parsed = new URL ( input ) ;
parsed . hash = "" ;
const normalized = parsed . toString ( ) ;
return normalized . endsWith ( "/" ) ? normalized . slice ( 0 , - 1 ) : normalized ;
} catch {
return input . trim ( ) . replace ( /\/$/ , "" ) ;
}
}
2026-05-02 16:48:01 -07:00
function buildSearchChatContext ( search : any ) {
const query = truncateContextPart ( search . query , 500 ) ? ? truncateContextPart ( search . title , 500 ) ? ? "Untitled search" ;
const lines : string [ ] = [
"You are Sybil. The user started this chat from a saved web search. Use the search answer and result context below when answering follow-up questions. If the context is insufficient, say so and use available tools when appropriate." ,
"" ,
` Search query: ${ query } ` ,
] ;
const answer = truncateContextPart ( search . answerText , 6000 ) ;
if ( answer ) {
lines . push ( "" , "Search answer:" , answer ) ;
}
if ( Array . isArray ( search . answerCitations ) && search . answerCitations . length ) {
lines . push ( "" , "Answer citations:" ) ;
for ( const [ index , citation ] of search . answerCitations . slice ( 0 , 8 ) . entries ( ) ) {
const title = truncateContextPart ( citation ? . title , 160 ) ;
const url = truncateContextPart ( citation ? . url ? ? citation ? . id , 400 ) ;
if ( title || url ) {
lines . push ( ` ${ index + 1 } . ${ [ title , url ] . filter ( Boolean ) . join ( " - " ) } ` ) ;
}
}
}
if ( Array . isArray ( search . results ) && search . results . length ) {
lines . push ( "" , "Search results:" ) ;
for ( const result of search . results . slice ( 0 , 10 ) ) {
const title = truncateContextPart ( result . title , 180 ) ? ? result . url ;
const url = truncateContextPart ( result . url , 500 ) ;
const published = truncateContextPart ( result . publishedDate , 80 ) ;
const author = truncateContextPart ( result . author , 120 ) ;
const text = truncateContextPart ( result . text , 1000 ) ;
const highlights = Array . isArray ( result . highlights )
? result . highlights
. map ( ( highlight : unknown ) = > truncateContextPart ( typeof highlight === "string" ? highlight : null , 360 ) )
. filter ( Boolean )
: [ ] ;
lines . push ( ` ${ result . rank + 1 } . ${ title } ` ) ;
if ( url ) lines . push ( ` URL: ${ url } ` ) ;
if ( published || author ) lines . push ( ` Source detail: ${ [ published , author ] . filter ( Boolean ) . join ( " - " ) } ` ) ;
if ( text ) lines . push ( ` Text: ${ text } ` ) ;
for ( const highlight of highlights . slice ( 0 , 2 ) ) {
lines . push ( ` Highlight: ${ highlight } ` ) ;
}
}
}
return lines . join ( "\n" ) ;
}
2026-02-14 01:53:34 -08:00
function buildSseHeaders ( originHeader : string | undefined ) {
const origin = originHeader && originHeader !== "null" ? originHeader : "*" ;
const headers : Record < string , string > = {
"Content-Type" : "text/event-stream; charset=utf-8" ,
"Cache-Control" : "no-cache, no-transform" ,
Connection : "keep-alive" ,
"X-Accel-Buffering" : "no" ,
"Access-Control-Allow-Origin" : origin ,
Vary : "Origin" ,
} ;
if ( origin !== "*" ) {
headers [ "Access-Control-Allow-Credentials" ] = "true" ;
}
return headers ;
}
2026-02-13 22:43:55 -08:00
export async function registerRoutes ( app : FastifyInstance ) {
2026-02-14 01:10:27 -08:00
app . get ( "/health" , { logLevel : "silent" } , async ( ) = > ( { ok : true } ) ) ;
2026-02-13 22:43:55 -08:00
2026-02-13 23:15:12 -08:00
app . get ( "/v1/auth/session" , async ( req ) = > {
requireAdmin ( req ) ;
return { authenticated : true , mode : env.ADMIN_TOKEN ? "token" : "open" } ;
} ) ;
2026-02-14 21:00:30 -08:00
app . get ( "/v1/models" , async ( req ) = > {
requireAdmin ( req ) ;
return { providers : getModelCatalogSnapshot ( ) } ;
} ) ;
2026-02-13 22:43:55 -08:00
app . get ( "/v1/chats" , async ( req ) = > {
requireAdmin ( req ) ;
const chats = await prisma . chat . findMany ( {
orderBy : { updatedAt : "desc" } ,
take : 100 ,
2026-02-14 22:06:30 -08:00
select : {
id : true ,
title : true ,
createdAt : true ,
updatedAt : true ,
initiatedProvider : true ,
initiatedModel : true ,
lastUsedProvider : true ,
lastUsedModel : true ,
} ,
2026-02-13 22:43:55 -08:00
} ) ;
return { chats } ;
} ) ;
app . post ( "/v1/chats" , async ( req ) = > {
requireAdmin ( req ) ;
2026-05-02 23:48:01 -07:00
const Body = z
. object ( {
title : z.string ( ) . optional ( ) ,
provider : z.enum ( [ "openai" , "anthropic" , "xai" ] ) . optional ( ) ,
model : z.string ( ) . trim ( ) . min ( 1 ) . optional ( ) ,
messages : z.array ( CompletionMessageSchema ) . optional ( ) ,
} )
. superRefine ( ( value , ctx ) = > {
if ( value . provider && ! value . model ) {
ctx . addIssue ( {
code : z.ZodIssueCode.custom ,
message : "model is required when provider is supplied" ,
path : [ "model" ] ,
} ) ;
}
if ( ! value . provider && value . model ) {
ctx . addIssue ( {
code : z.ZodIssueCode.custom ,
message : "provider is required when model is supplied" ,
path : [ "provider" ] ,
} ) ;
}
} ) ;
const parsed = Body . safeParse ( req . body ? ? { } ) ;
if ( ! parsed . success ) return app . httpErrors . badRequest ( parsed . error . message ) ;
const body = parsed . data ;
2026-02-14 22:06:30 -08:00
const chat = await prisma . chat . create ( {
2026-05-02 23:48:01 -07:00
data : {
title : body.title ,
initiatedProvider : body.provider as any ,
initiatedModel : body.model ,
lastUsedProvider : body.provider as any ,
lastUsedModel : body.model ,
messages : body.messages?.length
? {
create : body.messages.map ( ( message ) = > ( {
role : message.role as any ,
content : message.content ,
name : message.name ,
metadata : message.attachments?.length ? ( { attachments : message.attachments } as any ) : undefined ,
} ) ) ,
}
: undefined ,
} ,
2026-02-14 22:06:30 -08:00
select : {
id : true ,
title : true ,
createdAt : true ,
updatedAt : true ,
initiatedProvider : true ,
initiatedModel : true ,
lastUsedProvider : true ,
lastUsedModel : true ,
} ,
} ) ;
2026-02-13 22:43:55 -08:00
return { chat } ;
} ) ;
2026-02-14 21:27:44 -08:00
app . patch ( "/v1/chats/:chatId" , async ( req ) = > {
requireAdmin ( req ) ;
const Params = z . object ( { chatId : z.string ( ) } ) ;
const Body = z . object ( { title : z.string ( ) . trim ( ) . min ( 1 ) } ) ;
const { chatId } = Params . parse ( req . params ) ;
const body = Body . parse ( req . body ? ? { } ) ;
const updated = await prisma . chat . updateMany ( {
where : { id : chatId } ,
data : { title : body.title } ,
} ) ;
if ( updated . count === 0 ) return app . httpErrors . notFound ( "chat not found" ) ;
const chat = await prisma . chat . findUnique ( {
where : { id : chatId } ,
2026-02-14 22:06:30 -08:00
select : {
id : true ,
title : true ,
createdAt : true ,
updatedAt : true ,
initiatedProvider : true ,
initiatedModel : true ,
lastUsedProvider : true ,
lastUsedModel : true ,
} ,
2026-02-14 21:27:44 -08:00
} ) ;
if ( ! chat ) return app . httpErrors . notFound ( "chat not found" ) ;
return { chat } ;
} ) ;
app . post ( "/v1/chats/title/suggest" , async ( req ) = > {
requireAdmin ( req ) ;
const Body = z . object ( {
chatId : z.string ( ) ,
content : z.string ( ) . trim ( ) . min ( 1 ) ,
} ) ;
const body = Body . parse ( req . body ? ? { } ) ;
const existing = await prisma . chat . findUnique ( {
where : { id : body.chatId } ,
2026-02-14 22:06:30 -08:00
select : {
id : true ,
title : true ,
createdAt : true ,
updatedAt : true ,
initiatedProvider : true ,
initiatedModel : true ,
lastUsedProvider : true ,
lastUsedModel : true ,
} ,
2026-02-14 21:27:44 -08:00
} ) ;
if ( ! existing ) return app . httpErrors . notFound ( "chat not found" ) ;
if ( existing . title ? . trim ( ) ) return { chat : existing } ;
const fallback = body . content . split ( /\r?\n/ ) [ 0 ] ? . trim ( ) . slice ( 0 , 48 ) || "New chat" ;
const suggestedRaw = await generateChatTitle ( body . content ) ;
const title = normalizeSuggestedTitle ( suggestedRaw , fallback ) ;
const chat = await prisma . chat . update ( {
where : { id : body.chatId } ,
data : { title } ,
2026-02-14 22:06:30 -08:00
select : {
id : true ,
title : true ,
createdAt : true ,
updatedAt : true ,
initiatedProvider : true ,
initiatedModel : true ,
lastUsedProvider : true ,
lastUsedModel : true ,
} ,
2026-02-14 21:27:44 -08:00
} ) ;
return { chat } ;
} ) ;
2026-02-14 01:10:27 -08:00
app . delete ( "/v1/chats/:chatId" , async ( req ) = > {
requireAdmin ( req ) ;
const Params = z . object ( { chatId : z.string ( ) } ) ;
const { chatId } = Params . parse ( req . params ) ;
req . log . info ( { chatId } , "delete chat requested" ) ;
const result = await prisma . chat . deleteMany ( { where : { id : chatId } } ) ;
if ( result . count === 0 ) {
req . log . warn ( { chatId } , "delete chat target not found" ) ;
return app . httpErrors . notFound ( "chat not found" ) ;
}
req . log . info ( { chatId } , "chat deleted" ) ;
return { deleted : true } ;
} ) ;
2026-02-13 23:49:55 -08:00
app . get ( "/v1/searches" , async ( req ) = > {
requireAdmin ( req ) ;
const searches = await prisma . search . findMany ( {
orderBy : { updatedAt : "desc" } ,
take : 100 ,
select : { id : true , title : true , query : true , createdAt : true , updatedAt : true } ,
} ) ;
return { searches } ;
} ) ;
app . post ( "/v1/searches" , async ( req ) = > {
requireAdmin ( req ) ;
const Body = z . object ( { title : z.string ( ) . optional ( ) , query : z.string ( ) . optional ( ) } ) ;
const body = Body . parse ( req . body ? ? { } ) ;
const title = body . title ? . trim ( ) || body . query ? . trim ( ) ? . slice ( 0 , 80 ) ;
const query = body . query ? . trim ( ) || null ;
const search = await prisma . search . create ( {
data : {
title : title || null ,
query ,
} ,
select : { id : true , title : true , query : true , createdAt : true , updatedAt : true } ,
} ) ;
return { search } ;
} ) ;
2026-02-14 01:10:27 -08:00
app . delete ( "/v1/searches/:searchId" , async ( req ) = > {
requireAdmin ( req ) ;
const Params = z . object ( { searchId : z.string ( ) } ) ;
const { searchId } = Params . parse ( req . params ) ;
req . log . info ( { searchId } , "delete search requested" ) ;
const result = await prisma . search . deleteMany ( { where : { id : searchId } } ) ;
if ( result . count === 0 ) {
req . log . warn ( { searchId } , "delete search target not found" ) ;
return app . httpErrors . notFound ( "search not found" ) ;
}
req . log . info ( { searchId } , "search deleted" ) ;
return { deleted : true } ;
} ) ;
2026-02-13 23:49:55 -08:00
app . get ( "/v1/searches/:searchId" , async ( req ) = > {
requireAdmin ( req ) ;
const Params = z . object ( { searchId : z.string ( ) } ) ;
const { searchId } = Params . parse ( req . params ) ;
const search = await prisma . search . findUnique ( {
where : { id : searchId } ,
include : { results : { orderBy : { rank : "asc" } } } ,
} ) ;
if ( ! search ) return app . httpErrors . notFound ( "search not found" ) ;
return { search } ;
} ) ;
2026-05-02 16:48:01 -07:00
app . post ( "/v1/searches/:searchId/chat" , async ( req ) = > {
requireAdmin ( req ) ;
const Params = z . object ( { searchId : z.string ( ) } ) ;
const Body = z . object ( { title : z.string ( ) . optional ( ) } ) ;
const { searchId } = Params . parse ( req . params ) ;
const body = Body . parse ( req . body ? ? { } ) ;
const search = await prisma . search . findUnique ( {
where : { id : searchId } ,
include : { results : { orderBy : { rank : "asc" } } } ,
} ) ;
if ( ! search ) return app . httpErrors . notFound ( "search not found" ) ;
const fallbackTitle = search . query ? . trim ( ) || search . title ? . trim ( ) || "Search results" ;
const title = body . title ? . trim ( ) || ` Search: ${ fallbackTitle . slice ( 0 , 72 ) } ` ;
const context = buildSearchChatContext ( search ) ;
const chat = await prisma . chat . create ( {
data : {
title ,
messages : {
create : {
role : "system" as any ,
content : context ,
metadata : {
kind : "search_context" ,
searchId : search.id ,
query : search.query ,
resultCount : search.results.length ,
} ,
} ,
} ,
} ,
select : {
id : true ,
title : true ,
createdAt : true ,
updatedAt : true ,
initiatedProvider : true ,
initiatedModel : true ,
lastUsedProvider : true ,
lastUsedModel : true ,
} ,
} ) ;
return { chat } ;
} ) ;
2026-02-13 23:49:55 -08:00
app . post ( "/v1/searches/:searchId/run" , async ( req ) = > {
requireAdmin ( req ) ;
const Params = z . object ( { searchId : z.string ( ) } ) ;
const { searchId } = Params . parse ( req . params ) ;
2026-02-14 01:53:34 -08:00
const body = SearchRunBody . parse ( req . body ? ? { } ) ;
2026-02-13 23:49:55 -08:00
const existing = await prisma . search . findUnique ( {
where : { id : searchId } ,
select : { id : true , query : true } ,
} ) ;
if ( ! existing ) return app . httpErrors . notFound ( "search not found" ) ;
const query = body . query ? . trim ( ) || existing . query ? . trim ( ) ;
if ( ! query ) return app . httpErrors . badRequest ( "query is required" ) ;
const startedAt = performance . now ( ) ;
try {
2026-02-14 00:14:10 -08:00
const exa = exaClient ( ) ;
const [ searchOutcome , answerOutcome ] = await Promise . allSettled ( [
exa . searchAndContents ( query , {
type : body . type ? ? "auto" ,
numResults : body.numResults ? ? 10 ,
includeDomains : body.includeDomains ,
excludeDomains : body.excludeDomains ,
text : { maxCharacters : 1200 } ,
highlights : {
query ,
maxCharacters : 320 ,
numSentences : 2 ,
highlightsPerUrl : 2 ,
} ,
moderation : true ,
userLocation : "US" ,
} as any ) ,
exa . answer ( query , {
text : true ,
model : "exa" ,
userLocation : "US" ,
} ) ,
] ) ;
const searchResponse = searchOutcome . status === "fulfilled" ? searchOutcome.value : null ;
const answerResponse = answerOutcome . status === "fulfilled" ? answerOutcome.value : null ;
const searchError = searchOutcome . status === "rejected" ? searchOutcome . reason ? . message ? ? String ( searchOutcome . reason ) : null ;
const answerError = answerOutcome . status === "rejected" ? answerOutcome . reason ? . message ? ? String ( answerOutcome . reason ) : null ;
2026-02-13 23:49:55 -08:00
const latencyMs = Math . round ( performance . now ( ) - startedAt ) ;
const normalizedTitle = body . title ? . trim ( ) || query . slice ( 0 , 80 ) ;
2026-02-14 01:53:34 -08:00
const rows = ( searchResponse ? . results ? ? [ ] ) . map ( ( result : any , index : number ) = > mapSearchResultRow ( searchId , result , index ) ) ;
const answerText = parseAnswerText ( answerResponse ) ;
2026-02-13 23:49:55 -08:00
await prisma . $transaction ( async ( tx ) = > {
await tx . search . update ( {
where : { id : searchId } ,
data : {
query ,
title : normalizedTitle ,
2026-02-14 00:14:10 -08:00
requestId : searchResponse?.requestId ? ? null ,
rawResponse : searchResponse as any ,
2026-02-13 23:49:55 -08:00
latencyMs ,
2026-02-14 00:14:10 -08:00
error : searchError ,
answerText ,
answerRequestId : answerResponse?.requestId ? ? null ,
answerCitations : ( answerResponse ? . citations as any ) ? ? null ,
answerRawResponse : answerResponse as any ,
answerError ,
2026-02-13 23:49:55 -08:00
} ,
} ) ;
await tx . searchResult . deleteMany ( { where : { searchId } } ) ;
if ( rows . length ) {
await tx . searchResult . createMany ( { data : rows as any } ) ;
}
} ) ;
2026-02-14 00:14:10 -08:00
if ( searchError && answerError ) {
throw app . httpErrors . badGateway ( ` Exa search and answer failed: ${ searchError } ; ${ answerError } ` ) ;
}
2026-02-13 23:49:55 -08:00
const search = await prisma . search . findUnique ( {
where : { id : searchId } ,
include : { results : { orderBy : { rank : "asc" } } } ,
} ) ;
if ( ! search ) return app . httpErrors . notFound ( "search not found" ) ;
return { search } ;
} catch ( err : any ) {
await prisma . search . update ( {
where : { id : searchId } ,
data : {
latencyMs : Math.round ( performance . now ( ) - startedAt ) ,
error : err?.message ? ? String ( err ) ,
} ,
} ) ;
throw err ;
}
} ) ;
2026-02-14 01:53:34 -08:00
app . post ( "/v1/searches/:searchId/run/stream" , async ( req , reply ) = > {
requireAdmin ( req ) ;
const Params = z . object ( { searchId : z.string ( ) } ) ;
const { searchId } = Params . parse ( req . params ) ;
const body = SearchRunBody . parse ( req . body ? ? { } ) ;
const existing = await prisma . search . findUnique ( {
where : { id : searchId } ,
select : { id : true , query : true } ,
} ) ;
if ( ! existing ) return app . httpErrors . notFound ( "search not found" ) ;
const query = body . query ? . trim ( ) || existing . query ? . trim ( ) ;
if ( ! query ) return app . httpErrors . badRequest ( "query is required" ) ;
const startedAt = performance . now ( ) ;
const normalizedTitle = body . title ? . trim ( ) || query . slice ( 0 , 80 ) ;
reply . raw . writeHead ( 200 , buildSseHeaders ( typeof req . headers . origin === "string" ? req.headers.origin : undefined ) ) ;
const send = ( event : string , data : any ) = > {
if ( reply . raw . writableEnded ) return ;
reply . raw . write ( ` event: ${ event } \ n ` ) ;
reply . raw . write ( ` data: ${ JSON . stringify ( data ) } \ n \ n ` ) ;
} ;
try {
const exa = exaClient ( ) ;
const searchPromise = exa . search ( query , {
type : body . type ? ? "auto" ,
numResults : body.numResults ? ? 10 ,
includeDomains : body.includeDomains ,
excludeDomains : body.excludeDomains ,
moderation : true ,
userLocation : "US" ,
contents : false ,
} as any ) ;
const answerPromise = exa . answer ( query , {
text : true ,
model : "exa" ,
userLocation : "US" ,
} ) ;
let searchResponse : any | null = null ;
let answerResponse : any | null = null ;
let enrichedResults : any [ ] | null = null ;
let searchError : string | null = null ;
let answerError : string | null = null ;
const searchSettled = searchPromise . then (
async ( value ) = > {
searchResponse = value ;
const previewResults = ( value ? . results ? ? [ ] ) . map ( ( result : any , index : number ) = > mapSearchResultPreview ( result , index ) ) ;
send ( "search_results" , {
requestId : value?.requestId ? ? null ,
results : previewResults ,
} ) ;
const urls = ( value ? . results ? ? [ ] ) . map ( ( result : any ) = > result ? . url ) . filter ( ( url : string | undefined ) = > typeof url === "string" ) ;
if ( ! urls . length ) return ;
try {
const contentsResponse = await exa . getContents ( urls , {
text : { maxCharacters : 1200 } ,
highlights : {
query ,
maxCharacters : 320 ,
numSentences : 2 ,
highlightsPerUrl : 2 ,
} ,
} as any ) ;
const byUrl = new Map < string , any > ( ) ;
for ( const contentItem of contentsResponse ? . results ? ? [ ] ) {
byUrl . set ( normalizeUrlForMatch ( contentItem ? . url ) , contentItem ) ;
}
enrichedResults = ( value ? . results ? ? [ ] ) . map ( ( result : any ) = > {
const contentItem = byUrl . get ( normalizeUrlForMatch ( result ? . url ) ) ;
if ( ! contentItem ) return result ;
return {
. . . result ,
text : contentItem.text ? ? result . text ? ? null ,
highlights : Array.isArray ( contentItem . highlights ) ? contentItem.highlights : result.highlights ? ? null ,
highlightScores : Array.isArray ( contentItem . highlightScores ) ? contentItem.highlightScores : result.highlightScores ? ? null ,
} ;
} ) ;
send ( "search_results" , {
requestId : value?.requestId ? ? null ,
results : enrichedResults.map ( ( result : any , index : number ) = > mapSearchResultPreview ( result , index ) ) ,
} ) ;
} catch {
// keep preview results if content enrichment fails
}
} ,
( reason ) = > {
searchError = reason ? . message ? ? String ( reason ) ;
send ( "search_error" , { error : searchError } ) ;
}
) ;
const answerSettled = answerPromise . then (
( value ) = > {
answerResponse = value ;
send ( "answer" , {
answerText : parseAnswerText ( value ) ,
answerRequestId : value?.requestId ? ? null ,
answerCitations : ( value ? . citations as any ) ? ? null ,
} ) ;
} ,
( reason ) = > {
answerError = reason ? . message ? ? String ( reason ) ;
send ( "answer_error" , { error : answerError } ) ;
}
) ;
await Promise . all ( [ searchSettled , answerSettled ] ) ;
const latencyMs = Math . round ( performance . now ( ) - startedAt ) ;
const persistedResults = enrichedResults ? ? searchResponse ? . results ? ? [ ] ;
const rows = persistedResults . map ( ( result : any , index : number ) = > mapSearchResultRow ( searchId , result , index ) ) ;
const answerText = parseAnswerText ( answerResponse ) ;
await prisma . $transaction ( async ( tx ) = > {
await tx . search . update ( {
where : { id : searchId } ,
data : {
query ,
title : normalizedTitle ,
requestId : searchResponse?.requestId ? ? null ,
rawResponse : searchResponse as any ,
latencyMs ,
error : searchError ,
answerText ,
answerRequestId : answerResponse?.requestId ? ? null ,
answerCitations : ( answerResponse ? . citations as any ) ? ? null ,
answerRawResponse : answerResponse as any ,
answerError ,
} ,
} ) ;
await tx . searchResult . deleteMany ( { where : { searchId } } ) ;
if ( rows . length ) {
await tx . searchResult . createMany ( { data : rows as any } ) ;
}
} ) ;
const search = await prisma . search . findUnique ( {
where : { id : searchId } ,
include : { results : { orderBy : { rank : "asc" } } } ,
} ) ;
if ( ! search ) {
send ( "error" , { message : "search not found" } ) ;
} else {
send ( "done" , { search } ) ;
}
} catch ( err : any ) {
await prisma . search . update ( {
where : { id : searchId } ,
data : {
query ,
title : normalizedTitle ,
latencyMs : Math.round ( performance . now ( ) - startedAt ) ,
error : err?.message ? ? String ( err ) ,
} ,
} ) ;
send ( "error" , { message : err?.message ? ? String ( err ) } ) ;
} finally {
reply . raw . end ( ) ;
}
return reply ;
} ) ;
2026-02-13 22:43:55 -08:00
app . get ( "/v1/chats/:chatId" , async ( req ) = > {
requireAdmin ( req ) ;
const Params = z . object ( { chatId : z.string ( ) } ) ;
const { chatId } = Params . parse ( req . params ) ;
const chat = await prisma . chat . findUnique ( {
where : { id : chatId } ,
include : { messages : { orderBy : { createdAt : "asc" } } , calls : { orderBy : { createdAt : "desc" } } } ,
} ) ;
if ( ! chat ) return app . httpErrors . notFound ( "chat not found" ) ;
return { chat } ;
} ) ;
app . post ( "/v1/chats/:chatId/messages" , async ( req ) = > {
requireAdmin ( req ) ;
const Params = z . object ( { chatId : z.string ( ) } ) ;
const Body = z . object ( {
role : z.enum ( [ "system" , "user" , "assistant" , "tool" ] ) ,
content : z.string ( ) ,
name : z.string ( ) . optional ( ) ,
metadata : z.unknown ( ) . optional ( ) ,
2026-05-02 19:21:06 -07:00
attachments : z.array ( ChatAttachmentSchema ) . max ( MAX_CHAT_ATTACHMENTS ) . optional ( ) ,
2026-02-13 22:43:55 -08:00
} ) ;
const { chatId } = Params . parse ( req . params ) ;
2026-05-02 23:48:01 -07:00
const parsed = Body . safeParse ( req . body ) ;
if ( ! parsed . success ) return app . httpErrors . badRequest ( parsed . error . message ) ;
const body = parsed . data ;
2026-02-13 22:43:55 -08:00
const msg = await prisma . message . create ( {
data : {
chatId ,
role : body.role as any ,
content : body.content ,
name : body.name ,
2026-05-02 19:21:06 -07:00
metadata : mergeAttachmentsIntoMetadata ( body . metadata , body . attachments ) as any ,
2026-02-13 22:43:55 -08:00
} ,
} ) ;
return { message : msg } ;
} ) ;
// Main: create a completion via provider+model and store everything.
app . post ( "/v1/chat-completions" , async ( req ) = > {
requireAdmin ( req ) ;
const Body = z . object ( {
chatId : z.string ( ) . optional ( ) ,
provider : z.enum ( [ "openai" , "anthropic" , "xai" ] ) ,
model : z.string ( ) . min ( 1 ) ,
2026-05-02 19:21:06 -07:00
messages : z.array ( CompletionMessageSchema ) ,
2026-02-13 22:43:55 -08:00
temperature : z.number ( ) . min ( 0 ) . max ( 2 ) . optional ( ) ,
maxTokens : z.number ( ) . int ( ) . positive ( ) . optional ( ) ,
} ) ;
2026-05-02 23:48:01 -07:00
const parsed = Body . safeParse ( req . body ) ;
if ( ! parsed . success ) return app . httpErrors . badRequest ( parsed . error . message ) ;
const body = parsed . data ;
2026-02-13 22:43:55 -08:00
// ensure chat exists if provided
if ( body . chatId ) {
const exists = await prisma . chat . findUnique ( { where : { id : body.chatId } , select : { id : true } } ) ;
if ( ! exists ) return app . httpErrors . notFound ( "chat not found" ) ;
}
2026-02-13 23:15:12 -08:00
// Store only new non-assistant messages to avoid duplicate history entries.
2026-02-13 22:43:55 -08:00
if ( body . chatId ) {
2026-02-13 23:15:12 -08:00
await storeNonAssistantMessages ( body . chatId , body . messages ) ;
2026-02-13 22:43:55 -08:00
}
const result = await runMultiplex ( body ) ;
return {
chatId : body.chatId ? ? null ,
. . . result ,
} ;
} ) ;
// Streaming SSE endpoint.
app . post ( "/v1/chat-completions/stream" , async ( req , reply ) = > {
requireAdmin ( req ) ;
2026-05-02 23:48:01 -07:00
const Body = z
. object ( {
chatId : z.string ( ) . optional ( ) ,
persist : z.boolean ( ) . optional ( ) ,
provider : z.enum ( [ "openai" , "anthropic" , "xai" ] ) ,
model : z.string ( ) . min ( 1 ) ,
messages : z.array ( CompletionMessageSchema ) ,
temperature : z.number ( ) . min ( 0 ) . max ( 2 ) . optional ( ) ,
maxTokens : z.number ( ) . int ( ) . positive ( ) . optional ( ) ,
} )
. superRefine ( ( value , ctx ) = > {
if ( value . persist === false && value . chatId ) {
ctx . addIssue ( {
code : z.ZodIssueCode.custom ,
message : "chatId must be omitted when persist is false" ,
path : [ "chatId" ] ,
} ) ;
}
} ) ;
2026-02-13 22:43:55 -08:00
2026-05-02 23:48:01 -07:00
const parsed = Body . safeParse ( req . body ) ;
if ( ! parsed . success ) return app . httpErrors . badRequest ( parsed . error . message ) ;
const body = parsed . data ;
2026-02-13 22:43:55 -08:00
// ensure chat exists if provided
if ( body . chatId ) {
const exists = await prisma . chat . findUnique ( { where : { id : body.chatId } , select : { id : true } } ) ;
if ( ! exists ) return app . httpErrors . notFound ( "chat not found" ) ;
}
2026-02-13 23:15:12 -08:00
// Store only new non-assistant messages to avoid duplicate history entries.
2026-05-02 23:48:01 -07:00
if ( body . persist !== false && body . chatId ) {
2026-02-13 23:15:12 -08:00
await storeNonAssistantMessages ( body . chatId , body . messages ) ;
2026-02-13 22:43:55 -08:00
}
2026-02-14 01:53:34 -08:00
reply . raw . writeHead ( 200 , buildSseHeaders ( typeof req . headers . origin === "string" ? req.headers.origin : undefined ) ) ;
2026-05-02 23:09:39 -07:00
reply . raw . flushHeaders ( ) ;
2026-02-13 22:43:55 -08:00
const send = ( event : string , data : any ) = > {
reply . raw . write ( ` event: ${ event } \ n ` ) ;
reply . raw . write ( ` data: ${ JSON . stringify ( data ) } \ n \ n ` ) ;
} ;
for await ( const ev of runMultiplexStream ( body ) ) {
if ( ev . type === "meta" ) send ( "meta" , ev ) ;
2026-03-02 16:13:34 -08:00
else if ( ev . type === "tool_call" ) send ( "tool_call" , ev . event ) ;
2026-02-13 22:43:55 -08:00
else if ( ev . type === "delta" ) send ( "delta" , ev ) ;
else if ( ev . type === "done" ) send ( "done" , ev ) ;
else if ( ev . type === "error" ) send ( "error" , ev ) ;
}
reply . raw . end ( ) ;
return reply ;
} ) ;
}