Skip to content

Commit 302df5e

Browse files
Normalize tuple-style headers in transport request mapping
Co-authored-by: Eric Allam <eric@trigger.dev>
1 parent 3766c73 commit 302df5e

File tree

2 files changed

+81
-1
lines changed

2 files changed

+81
-1
lines changed

packages/ai/src/chatTransport.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,74 @@ describe("TriggerChatTransport", function () {
323323
});
324324
});
325325

326+
it("normalizes tuple header arrays into request headers", async function () {
327+
let receivedTriggerBody: Record<string, unknown> | undefined;
328+
329+
const server = await startServer(function (req, res) {
330+
if (req.method === "POST" && req.url === "/api/v1/tasks/chat-task/trigger") {
331+
readJsonBody(req).then(function (body) {
332+
receivedTriggerBody = body;
333+
res.writeHead(200, {
334+
"content-type": "application/json",
335+
"x-trigger-jwt": "pk_run_tuple_headers",
336+
});
337+
res.end(JSON.stringify({ id: "run_tuple_headers" }));
338+
});
339+
return;
340+
}
341+
342+
if (req.method === "GET" && req.url === "/realtime/v1/streams/run_tuple_headers/chat-stream") {
343+
res.writeHead(200, {
344+
"content-type": "text/event-stream",
345+
});
346+
writeSSE(
347+
res,
348+
"1-0",
349+
JSON.stringify({ type: "text-start", id: "tuple_headers_1" })
350+
);
351+
writeSSE(
352+
res,
353+
"2-0",
354+
JSON.stringify({ type: "text-end", id: "tuple_headers_1" })
355+
);
356+
res.end();
357+
return;
358+
}
359+
360+
res.writeHead(404);
361+
res.end();
362+
});
363+
364+
const transport = new TriggerChatTransport({
365+
task: "chat-task",
366+
stream: "chat-stream",
367+
accessToken: "pk_trigger",
368+
baseURL: server.url,
369+
});
370+
371+
const stream = await transport.sendMessages({
372+
trigger: "submit-message",
373+
chatId: "chat-tuple-headers",
374+
messageId: undefined,
375+
messages: [],
376+
abortSignal: undefined,
377+
headers: [["x-tuple-header", "tuple-value"]] as unknown as Record<string, string>,
378+
});
379+
380+
const chunks = await readChunks(stream);
381+
expect(chunks).toHaveLength(2);
382+
383+
const payloadString = receivedTriggerBody?.payload as string;
384+
const payload = (JSON.parse(payloadString) as { json: Record<string, unknown> }).json;
385+
expect(payload.request).toEqual({
386+
body: null,
387+
headers: {
388+
"x-tuple-header": "tuple-value",
389+
},
390+
metadata: null,
391+
});
392+
});
393+
326394
it("returns null on reconnect when no active run exists", async function () {
327395
const transport = new TriggerChatTransport({
328396
task: "chat-task",

packages/ai/src/chatTransport.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,12 +389,24 @@ function resolveStreamKey<UI_MESSAGE extends UIMessage>(
389389
}
390390

391391
function normalizeHeaders(
392-
headers: Record<string, string> | Headers | undefined
392+
headers:
393+
| Record<string, string>
394+
| Headers
395+
| Array<[string, string]>
396+
| undefined
393397
): Record<string, string> | undefined {
394398
if (!headers) {
395399
return undefined;
396400
}
397401

402+
if (Array.isArray(headers)) {
403+
const result: Record<string, string> = {};
404+
for (const [key, value] of headers) {
405+
result[key] = value;
406+
}
407+
return result;
408+
}
409+
398410
if (isHeadersInstance(headers)) {
399411
const result: Record<string, string> = {};
400412
for (const [key, value] of headers.entries()) {

0 commit comments

Comments
 (0)