@@ -5,153 +5,155 @@ import { buildAppContext, triageAgent } from "../ai";
55import { record , setAttributes } from "../lib/tracing" ;
66import { validateWebsite } from "../lib/website-utils" ;
77
8- // Accept both content string and parts array formats (like Midday)
98const AgentRequestSchema = t . Object ( {
10- websiteId : t . String ( ) ,
11- message : t . Object ( {
12- id : t . Optional ( t . String ( ) ) ,
13- role : t . Union ( [ t . Literal ( "user" ) , t . Literal ( "assistant" ) ] ) ,
14- content : t . Optional ( t . String ( ) ) ,
15- parts : t . Optional (
16- t . Array (
17- t . Object ( {
18- type : t . String ( ) ,
19- text : t . Optional ( t . String ( ) ) ,
20- } )
21- )
22- ) ,
23- text : t . Optional ( t . String ( ) ) , // SDK sometimes sends text directly
24- } ) ,
25- id : t . Optional ( t . String ( ) ) ,
26- timezone : t . Optional ( t . String ( ) ) ,
9+ websiteId : t . String ( ) ,
10+ message : t . Object ( {
11+ id : t . Optional ( t . String ( ) ) ,
12+ role : t . Union ( [ t . Literal ( "user" ) , t . Literal ( "assistant" ) ] ) ,
13+ content : t . Optional ( t . String ( ) ) ,
14+ parts : t . Optional (
15+ t . Array (
16+ t . Object ( {
17+ type : t . String ( ) ,
18+ text : t . Optional ( t . String ( ) ) ,
19+ } )
20+ )
21+ ) ,
22+ text : t . Optional ( t . String ( ) ) , // SDK sometimes sends text directly
23+ } ) ,
24+ id : t . Optional ( t . String ( ) ) ,
25+ timezone : t . Optional ( t . String ( ) ) ,
2726} ) ;
2827
2928interface UIMessage {
30- id : string ;
31- role : "user" | "assistant" ;
32- parts : Array < { type : "text" ; text : string } > ;
29+ id : string ;
30+ role : "user" | "assistant" ;
31+ parts : Array < { type : "text" ; text : string } > ;
3332}
3433
3534interface IncomingMessage {
36- id ?: string ;
37- role : "user" | "assistant" ;
38- content ?: string ;
39- parts ?: Array < { type : string ; text ?: string } > ;
40- text ?: string ;
35+ id ?: string ;
36+ role : "user" | "assistant" ;
37+ content ?: string ;
38+ parts ?: Array < { type : string ; text ?: string } > ;
39+ text ?: string ;
4140}
4241
4342function toUIMessage ( msg : IncomingMessage ) : UIMessage {
44- // If already has parts, use them
45- if ( msg . parts && msg . parts . length > 0 ) {
46- return {
47- id : msg . id ?? crypto . randomUUID ( ) ,
48- role : msg . role ,
49- parts : msg . parts . map ( ( p ) => ( { type : "text" , text : p . text ?? "" } ) ) ,
50- } ;
51- }
52-
53- // Extract text from content or text field
54- const text = msg . content ?? msg . text ?? "" ;
55-
56- return {
57- id : msg . id ?? crypto . randomUUID ( ) ,
58- role : msg . role ,
59- parts : [ { type : "text" , text } ] ,
60- } ;
43+ if ( msg . parts && msg . parts . length > 0 ) {
44+ return {
45+ id : msg . id ?? crypto . randomUUID ( ) ,
46+ role : msg . role ,
47+ parts : msg . parts . map ( ( p ) => ( { type : "text" , text : p . text ?? "" } ) ) ,
48+ } ;
49+ }
50+
51+ // Extract text from content or text field
52+ const text = msg . content ?? msg . text ?? "" ;
53+
54+ return {
55+ id : msg . id ?? crypto . randomUUID ( ) ,
56+ role : msg . role ,
57+ parts : [ { type : "text" , text } ] ,
58+ } ;
6159}
6260
6361export const agent = new Elysia ( { prefix : "/v1/agent" } )
64- . derive ( async ( { request } ) => {
65- const session = await auth . api . getSession ( { headers : request . headers } ) ;
66- return { user : session ?. user ?? null } ;
67- } )
68- . onBeforeHandle ( ( { user } ) => {
69- if ( ! user ) {
70- return new Response (
71- JSON . stringify ( {
72- success : false ,
73- error : "Authentication required" ,
74- code : "AUTH_REQUIRED" ,
75- } ) ,
76- { status : 401 , headers : { "Content-Type" : "application/json" } }
77- ) ;
78- }
79- } )
80- . post (
81- "/chat" ,
82- function agentChat ( { body, user, request } ) {
83- return record ( "agentChat" , async ( ) => {
84- const chatId = body . id ?? crypto . randomUUID ( ) ;
85-
86- setAttributes ( {
87- "agent.website_id" : body . websiteId ,
88- "agent.user_id" : user ?. id ?? "unknown" ,
89- "agent.chat_id" : chatId ,
90- } ) ;
91-
92- try {
93- const websiteValidation = await validateWebsite ( body . websiteId ) ;
94- if ( ! ( websiteValidation . success && websiteValidation . website ) ) {
95- return new Response (
96- JSON . stringify ( {
97- error : websiteValidation . error ?? "Website not found" ,
98- } ) ,
99- { status : 404 , headers : { "Content-Type" : "application/json" } }
100- ) ;
101- }
102-
103- const { website } = websiteValidation ;
104-
105- let authorized = website . isPublic ;
106- if ( ! authorized ) {
107- if ( website . organizationId ) {
108- const { success } = await websitesApi . hasPermission ( {
109- headers : request . headers ,
110- body : { permissions : { website : [ "read" ] } } ,
111- } ) ;
112- authorized = success ;
113- } else {
114- authorized = website . userId === user ?. id ;
115- }
116- }
117-
118- if ( ! authorized ) {
119- return new Response ( JSON . stringify ( { error : "Unauthorized" } ) , {
120- status : 403 ,
121- headers : { "Content-Type" : "application/json" } ,
122- } ) ;
123- }
124-
125- const appContext = buildAppContext (
126- user ?. id ?? "anonymous" ,
127- body . websiteId ,
128- website . domain ?? "unknown" ,
129- body . timezone ?? "UTC"
130- ) ;
131-
132- const message = toUIMessage ( body . message ) ;
133-
134- return triageAgent . toUIMessageStream ( {
135- message,
136- strategy : "auto" ,
137- maxRounds : 5 ,
138- maxSteps : 20 ,
139- context : appContext ,
140- experimental_transform : smoothStream ( {
141- chunking : "word" ,
142- } ) ,
143- sendSources : true ,
144- } ) ;
145- } catch ( error ) {
146- console . error ( "Agent chat error:" , error ) ;
147- return new Response (
148- JSON . stringify ( {
149- error : error instanceof Error ? error . message : "Unknown error" ,
150- } ) ,
151- { status : 500 , headers : { "Content-Type" : "application/json" } }
152- ) ;
153- }
154- } ) ;
155- } ,
156- { body : AgentRequestSchema }
157- ) ;
62+ . derive ( async ( { request } ) => {
63+ const session = await auth . api . getSession ( { headers : request . headers } ) ;
64+ return { user : session ?. user ?? null } ;
65+ } )
66+ . onBeforeHandle ( ( { user, set } ) => {
67+ if ( ! user ) {
68+ set . status = 401 ;
69+ return {
70+ success : false ,
71+ error : "Authentication required" ,
72+ code : "AUTH_REQUIRED" ,
73+ } ;
74+ }
75+ } )
76+ . post (
77+ "/chat" ,
78+ function agentChat ( { body, user, request } ) {
79+ return record ( "agentChat" , async ( ) => {
80+ const chatId = body . id ?? crypto . randomUUID ( ) ;
81+
82+ setAttributes ( {
83+ "agent.website_id" : body . websiteId ,
84+ "agent.user_id" : user ?. id ?? "unknown" ,
85+ "agent.chat_id" : chatId ,
86+ } ) ;
87+
88+ try {
89+ const websiteValidation = await validateWebsite ( body . websiteId ) ;
90+ if ( ! ( websiteValidation . success && websiteValidation . website ) ) {
91+ return new Response (
92+ JSON . stringify ( {
93+ error : websiteValidation . error ?? "Website not found" ,
94+ } ) ,
95+ { status : 404 , headers : { "Content-Type" : "application/json" } }
96+ ) ;
97+ }
98+
99+ const { website } = websiteValidation ;
100+
101+ let authorized = website . isPublic ;
102+ if ( ! authorized ) {
103+ if ( website . organizationId ) {
104+ const { success } = await websitesApi . hasPermission ( {
105+ headers : request . headers ,
106+ body : { permissions : { website : [ "read" ] } } ,
107+ } ) ;
108+ authorized = success ;
109+ } else {
110+ authorized = website . userId === user ?. id ;
111+ }
112+ }
113+
114+ if ( ! authorized ) {
115+ return new Response ( JSON . stringify ( { error : "Unauthorized" } ) , {
116+ status : 403 ,
117+ headers : { "Content-Type" : "application/json" } ,
118+ } ) ;
119+ }
120+
121+ const appContext = buildAppContext (
122+ user ?. id ?? "anonymous" ,
123+ body . websiteId ,
124+ website . domain ?? "unknown" ,
125+ body . timezone ?? "UTC"
126+ ) ;
127+
128+ const message = toUIMessage ( body . message ) ;
129+
130+ // Update context with chatId for memory retrieval
131+ const contextWithChatId = {
132+ ...appContext ,
133+ chatId,
134+ } ;
135+
136+ return triageAgent . toUIMessageStream ( {
137+ message,
138+ strategy : "auto" ,
139+ maxRounds : 5 ,
140+ maxSteps : 20 ,
141+ context : contextWithChatId ,
142+ experimental_transform : smoothStream ( {
143+ chunking : "word" ,
144+ } ) ,
145+ sendSources : true ,
146+ } ) ;
147+ } catch ( error ) {
148+ console . error ( "Agent chat error:" , error ) ;
149+ return new Response (
150+ JSON . stringify ( {
151+ error : error instanceof Error ? error . message : "Unknown error" ,
152+ } ) ,
153+ { status : 500 , headers : { "Content-Type" : "application/json" } }
154+ ) ;
155+ }
156+ } ) ;
157+ } ,
158+ { body : AgentRequestSchema }
159+ ) ;
0 commit comments