@@ -146,6 +146,139 @@ describe("TriggerChatTransport", function () {
146146 expect ( stream ) . toBeNull ( ) ;
147147 } ) ;
148148
149+ it ( "supports custom payload mapping and trigger options resolver" , async function ( ) {
150+ let receivedTriggerBody : Record < string , unknown > | undefined ;
151+ let receivedResolverChatId : string | undefined ;
152+ let receivedResolverHeader : string | undefined ;
153+
154+ const server = await startServer ( function ( req , res ) {
155+ if ( req . method === "POST" && req . url === "/api/v1/tasks/chat-task/trigger" ) {
156+ readJsonBody ( req ) . then ( function ( body ) {
157+ receivedTriggerBody = body ;
158+ res . writeHead ( 200 , {
159+ "content-type" : "application/json" ,
160+ "x-trigger-jwt" : "pk_run_789" ,
161+ } ) ;
162+ res . end ( JSON . stringify ( { id : "run_789" } ) ) ;
163+ } ) ;
164+ return ;
165+ }
166+
167+ if ( req . method === "GET" && req . url === "/realtime/v1/streams/run_789/chat-stream" ) {
168+ res . writeHead ( 200 , {
169+ "content-type" : "text/event-stream" ,
170+ } ) ;
171+ writeSSE (
172+ res ,
173+ "1-0" ,
174+ JSON . stringify ( { type : "text-start" , id : "mapped_1" } )
175+ ) ;
176+ writeSSE (
177+ res ,
178+ "2-0" ,
179+ JSON . stringify ( { type : "text-end" , id : "mapped_1" } )
180+ ) ;
181+ res . end ( ) ;
182+ return ;
183+ }
184+
185+ res . writeHead ( 404 ) ;
186+ res . end ( ) ;
187+ } ) ;
188+
189+ const transport = new TriggerChatTransport <
190+ UIMessage ,
191+ {
192+ prompt : string ;
193+ chatId : string ;
194+ sourceHeader : string | undefined ;
195+ }
196+ > ( {
197+ task : "chat-task" ,
198+ stream : "chat-stream" ,
199+ accessToken : "pk_trigger" ,
200+ baseURL : server . url ,
201+ payloadMapper : function payloadMapper ( request ) {
202+ const firstMessage = request . messages [ 0 ] ;
203+ const firstPart = firstMessage ?. parts [ 0 ] ;
204+ const prompt =
205+ firstPart && firstPart . type === "text"
206+ ? firstPart . text
207+ : "" ;
208+
209+ return {
210+ prompt,
211+ chatId : request . chatId ,
212+ sourceHeader : request . request . headers ?. [ "x-source" ] ,
213+ } ;
214+ } ,
215+ triggerOptions : function triggerOptions ( request ) {
216+ receivedResolverChatId = request . chatId ;
217+ receivedResolverHeader = request . request . headers ?. [ "x-source" ] ;
218+
219+ return {
220+ queue : "chat-queue" ,
221+ concurrencyKey : `chat-${ request . chatId } ` ,
222+ idempotencyKey : `idem-${ request . chatId } ` ,
223+ ttl : "30m" ,
224+ tags : [ "chat" , "mapped" ] ,
225+ metadata : {
226+ requester : request . request . headers ?. [ "x-source" ] ?? "unknown" ,
227+ } ,
228+ priority : 50 ,
229+ } ;
230+ } ,
231+ } ) ;
232+
233+ const stream = await transport . sendMessages ( {
234+ trigger : "submit-message" ,
235+ chatId : "chat-mapped" ,
236+ messageId : undefined ,
237+ messages : [
238+ {
239+ id : "mapped-user" ,
240+ role : "user" ,
241+ parts : [ { type : "text" , text : "Map me" } ] ,
242+ } satisfies UIMessage ,
243+ ] ,
244+ abortSignal : undefined ,
245+ headers : {
246+ "x-source" : "sdk-test" ,
247+ } ,
248+ } ) ;
249+
250+ const chunks = await readChunks ( stream ) ;
251+ expect ( chunks ) . toHaveLength ( 2 ) ;
252+ expect ( chunks [ 0 ] ) . toMatchObject ( {
253+ chunk : { type : "text-start" , id : "mapped_1" } ,
254+ } ) ;
255+ expect ( chunks [ 1 ] ) . toMatchObject ( {
256+ chunk : { type : "text-end" , id : "mapped_1" } ,
257+ } ) ;
258+
259+ expect ( receivedResolverChatId ) . toBe ( "chat-mapped" ) ;
260+ expect ( receivedResolverHeader ) . toBe ( "sdk-test" ) ;
261+
262+ expect ( receivedTriggerBody ) . toBeDefined ( ) ;
263+ const payloadString = receivedTriggerBody ?. payload as string ;
264+ const payload = ( JSON . parse ( payloadString ) as { json : Record < string , unknown > } ) . json ;
265+ expect ( payload ) . toEqual ( {
266+ prompt : "Map me" ,
267+ chatId : "chat-mapped" ,
268+ sourceHeader : "sdk-test" ,
269+ } ) ;
270+
271+ const options = ( receivedTriggerBody ?. options ?? { } ) as Record < string , unknown > ;
272+ expect ( options . queue ) . toEqual ( { name : "chat-queue" } ) ;
273+ expect ( options . concurrencyKey ) . toBe ( "chat-chat-mapped" ) ;
274+ expect ( options . ttl ) . toBe ( "30m" ) ;
275+ expect ( options . tags ) . toEqual ( [ "chat" , "mapped" ] ) ;
276+ expect ( options . metadata ) . toEqual ( { requester : "sdk-test" } ) ;
277+ expect ( options . priority ) . toBe ( 50 ) ;
278+ expect ( typeof options . idempotencyKey ) . toBe ( "string" ) ;
279+ expect ( ( options . idempotencyKey as string ) . length ) . toBe ( 64 ) ;
280+ } ) ;
281+
149282 it ( "reconnects active streams using tracked lastEventId" , async function ( ) {
150283 let reconnectLastEventId : string | undefined ;
151284 let firstStreamResponse : ServerResponse < IncomingMessage > | undefined ;
0 commit comments