1- import type { Message , Task } from '@a2a-js/sdk'
1+ import type { DataPart , FilePart , Message , Part , Task , TextPart } from '@a2a-js/sdk'
22import { createLogger } from '@sim/logger'
33import { type NextRequest , NextResponse } from 'next/server'
44import { z } from 'zod'
@@ -10,11 +10,20 @@ export const dynamic = 'force-dynamic'
1010
1111const logger = createLogger ( 'A2ASendMessageAPI' )
1212
13+ const FileInputSchema = z . object ( {
14+ type : z . enum ( [ 'file' , 'url' ] ) ,
15+ data : z . string ( ) ,
16+ name : z . string ( ) ,
17+ mime : z . string ( ) . optional ( ) ,
18+ } )
19+
1320const A2ASendMessageSchema = z . object ( {
1421 agentUrl : z . string ( ) . min ( 1 , 'Agent URL is required' ) ,
1522 message : z . string ( ) . min ( 1 , 'Message is required' ) ,
1623 taskId : z . string ( ) . optional ( ) ,
1724 contextId : z . string ( ) . optional ( ) ,
25+ data : z . string ( ) . optional ( ) ,
26+ files : z . array ( FileInputSchema ) . optional ( ) ,
1827 apiKey : z . string ( ) . optional ( ) ,
1928} )
2029
@@ -51,18 +60,104 @@ export async function POST(request: NextRequest) {
5160 hasContextId : ! ! validatedData . contextId ,
5261 } )
5362
54- const client = await createA2AClient ( validatedData . agentUrl , validatedData . apiKey )
63+ let client
64+ try {
65+ client = await createA2AClient ( validatedData . agentUrl , validatedData . apiKey )
66+ logger . info ( `[${ requestId } ] A2A client created successfully` )
67+ } catch ( clientError ) {
68+ logger . error ( `[${ requestId } ] Failed to create A2A client:` , clientError )
69+ return NextResponse . json (
70+ {
71+ success : false ,
72+ error : `Failed to connect to agent: ${ clientError instanceof Error ? clientError . message : 'Unknown error' } ` ,
73+ } ,
74+ { status : 502 }
75+ )
76+ }
77+
78+ const parts : Part [ ] = [ ]
79+
80+ // Add text part
81+ const textPart : TextPart = { kind : 'text' , text : validatedData . message }
82+ parts . push ( textPart )
83+
84+ // Add data part if provided
85+ if ( validatedData . data ) {
86+ try {
87+ const parsedData = JSON . parse ( validatedData . data )
88+ const dataPart : DataPart = { kind : 'data' , data : parsedData }
89+ parts . push ( dataPart )
90+ } catch ( parseError ) {
91+ logger . warn ( `[${ requestId } ] Failed to parse data as JSON, skipping DataPart` , {
92+ error : parseError instanceof Error ? parseError . message : String ( parseError ) ,
93+ } )
94+ }
95+ }
96+
97+ // Add file parts if provided
98+ if ( validatedData . files && validatedData . files . length > 0 ) {
99+ for ( const file of validatedData . files ) {
100+ if ( file . type === 'url' ) {
101+ // URL-based file
102+ const filePart : FilePart = {
103+ kind : 'file' ,
104+ file : {
105+ name : file . name ,
106+ mimeType : file . mime ,
107+ uri : file . data ,
108+ } ,
109+ }
110+ parts . push ( filePart )
111+ } else if ( file . type === 'file' ) {
112+ // Base64 data URL file - extract bytes
113+ let bytes = file . data
114+ let mimeType = file . mime
115+
116+ // If it's a data URL, extract the base64 portion
117+ if ( file . data . startsWith ( 'data:' ) ) {
118+ const match = file . data . match ( / ^ d a t a : ( [ ^ ; ] + ) ; b a s e 6 4 , ( .+ ) $ / )
119+ if ( match ) {
120+ mimeType = mimeType || match [ 1 ]
121+ bytes = match [ 2 ]
122+ }
123+ }
124+
125+ const filePart : FilePart = {
126+ kind : 'file' ,
127+ file : {
128+ name : file . name ,
129+ mimeType : mimeType || 'application/octet-stream' ,
130+ bytes,
131+ } ,
132+ }
133+ parts . push ( filePart )
134+ }
135+ }
136+ }
55137
56138 const message : Message = {
57139 kind : 'message' ,
58140 messageId : crypto . randomUUID ( ) ,
59141 role : 'user' ,
60- parts : [ { kind : 'text' , text : validatedData . message } ] ,
142+ parts,
61143 ...( validatedData . taskId && { taskId : validatedData . taskId } ) ,
62144 ...( validatedData . contextId && { contextId : validatedData . contextId } ) ,
63145 }
64146
65- const result = await client . sendMessage ( { message } )
147+ let result
148+ try {
149+ result = await client . sendMessage ( { message } )
150+ logger . info ( `[${ requestId } ] A2A sendMessage completed` , { resultKind : result ?. kind } )
151+ } catch ( sendError ) {
152+ logger . error ( `[${ requestId } ] Failed to send A2A message:` , sendError )
153+ return NextResponse . json (
154+ {
155+ success : false ,
156+ error : `Failed to send message: ${ sendError instanceof Error ? sendError . message : 'Unknown error' } ` ,
157+ } ,
158+ { status : 502 }
159+ )
160+ }
66161
67162 if ( result . kind === 'message' ) {
68163 const responseMessage = result as Message
0 commit comments