@trigger.dev/sdk 0.0.0-prerelease-20260220162801 → 0.0.0-prerelease-20260304181730
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commonjs/v3/ai.d.ts +245 -2
- package/dist/commonjs/v3/ai.js +384 -1
- package/dist/commonjs/v3/ai.js.map +1 -1
- package/dist/commonjs/v3/auth.d.ts +4 -0
- package/dist/commonjs/v3/auth.js.map +1 -1
- package/dist/commonjs/v3/chat-constants.d.ts +10 -0
- package/dist/commonjs/v3/chat-constants.js +14 -0
- package/dist/commonjs/v3/chat-constants.js.map +1 -0
- package/dist/commonjs/v3/chat-react.d.ts +42 -0
- package/dist/commonjs/v3/chat-react.js +63 -0
- package/dist/commonjs/v3/chat-react.js.map +1 -0
- package/dist/commonjs/v3/chat.d.ts +156 -0
- package/dist/commonjs/v3/chat.js +270 -0
- package/dist/commonjs/v3/chat.js.map +1 -0
- package/dist/commonjs/v3/chat.test.d.ts +1 -0
- package/dist/commonjs/v3/chat.test.js +1304 -0
- package/dist/commonjs/v3/chat.test.js.map +1 -0
- package/dist/commonjs/v3/runs.d.ts +4 -4
- package/dist/commonjs/v3/shared.js +10 -9
- package/dist/commonjs/v3/shared.js.map +1 -1
- package/dist/commonjs/v3/streams.d.ts +34 -2
- package/dist/commonjs/v3/streams.js +166 -2
- package/dist/commonjs/v3/streams.js.map +1 -1
- package/dist/commonjs/v3/wait.d.ts +2 -9
- package/dist/commonjs/v3/wait.js +7 -26
- package/dist/commonjs/v3/wait.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/esm/v3/ai.d.ts +245 -2
- package/dist/esm/v3/ai.js +385 -2
- package/dist/esm/v3/ai.js.map +1 -1
- package/dist/esm/v3/auth.d.ts +4 -0
- package/dist/esm/v3/auth.js.map +1 -1
- package/dist/esm/v3/chat-constants.d.ts +10 -0
- package/dist/esm/v3/chat-constants.js +11 -0
- package/dist/esm/v3/chat-constants.js.map +1 -0
- package/dist/esm/v3/chat-react.d.ts +42 -0
- package/dist/esm/v3/chat-react.js +60 -0
- package/dist/esm/v3/chat-react.js.map +1 -0
- package/dist/esm/v3/chat.d.ts +156 -0
- package/dist/esm/v3/chat.js +265 -0
- package/dist/esm/v3/chat.js.map +1 -0
- package/dist/esm/v3/chat.test.d.ts +1 -0
- package/dist/esm/v3/chat.test.js +1302 -0
- package/dist/esm/v3/chat.test.js.map +1 -0
- package/dist/esm/v3/shared.js +10 -9
- package/dist/esm/v3/shared.js.map +1 -1
- package/dist/esm/v3/streams.d.ts +34 -2
- package/dist/esm/v3/streams.js +167 -3
- package/dist/esm/v3/streams.js.map +1 -1
- package/dist/esm/v3/wait.d.ts +2 -9
- package/dist/esm/v3/wait.js +2 -22
- package/dist/esm/v3/wait.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +40 -5
|
@@ -0,0 +1,1304 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const chat_js_1 = require("./chat.js");
|
|
5
|
+
// Helper: encode text as SSE format
|
|
6
|
+
function sseEncode(chunks) {
|
|
7
|
+
return chunks.map((chunk, i) => `id: ${i}\ndata: ${JSON.stringify(chunk)}\n\n`).join("");
|
|
8
|
+
}
|
|
9
|
+
// Helper: create a ReadableStream from SSE text
|
|
10
|
+
function createSSEStream(sseText) {
|
|
11
|
+
const encoder = new TextEncoder();
|
|
12
|
+
return new ReadableStream({
|
|
13
|
+
start(controller) {
|
|
14
|
+
controller.enqueue(encoder.encode(sseText));
|
|
15
|
+
controller.close();
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
// Helper: create test UIMessages with unique IDs
|
|
20
|
+
let messageIdCounter = 0;
|
|
21
|
+
function createUserMessage(text) {
|
|
22
|
+
return {
|
|
23
|
+
id: `msg-user-${++messageIdCounter}`,
|
|
24
|
+
role: "user",
|
|
25
|
+
parts: [{ type: "text", text }],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function createAssistantMessage(text) {
|
|
29
|
+
return {
|
|
30
|
+
id: `msg-assistant-${++messageIdCounter}`,
|
|
31
|
+
role: "assistant",
|
|
32
|
+
parts: [{ type: "text", text }],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// Sample UIMessageChunks as the AI SDK would produce
|
|
36
|
+
const sampleChunks = [
|
|
37
|
+
{ type: "text-start", id: "part-1" },
|
|
38
|
+
{ type: "text-delta", id: "part-1", delta: "Hello" },
|
|
39
|
+
{ type: "text-delta", id: "part-1", delta: " world" },
|
|
40
|
+
{ type: "text-delta", id: "part-1", delta: "!" },
|
|
41
|
+
{ type: "text-end", id: "part-1" },
|
|
42
|
+
];
|
|
43
|
+
(0, vitest_1.describe)("TriggerChatTransport", () => {
|
|
44
|
+
let originalFetch;
|
|
45
|
+
(0, vitest_1.beforeEach)(() => {
|
|
46
|
+
originalFetch = global.fetch;
|
|
47
|
+
});
|
|
48
|
+
(0, vitest_1.afterEach)(() => {
|
|
49
|
+
global.fetch = originalFetch;
|
|
50
|
+
vitest_1.vi.restoreAllMocks();
|
|
51
|
+
});
|
|
52
|
+
(0, vitest_1.describe)("constructor", () => {
|
|
53
|
+
(0, vitest_1.it)("should create transport with required options", () => {
|
|
54
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
55
|
+
task: "my-chat-task",
|
|
56
|
+
accessToken: "test-token",
|
|
57
|
+
});
|
|
58
|
+
(0, vitest_1.expect)(transport).toBeInstanceOf(chat_js_1.TriggerChatTransport);
|
|
59
|
+
});
|
|
60
|
+
(0, vitest_1.it)("should accept optional configuration", () => {
|
|
61
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
62
|
+
task: "my-chat-task",
|
|
63
|
+
accessToken: "test-token",
|
|
64
|
+
baseURL: "https://custom.trigger.dev",
|
|
65
|
+
streamKey: "custom-stream",
|
|
66
|
+
headers: { "X-Custom": "value" },
|
|
67
|
+
});
|
|
68
|
+
(0, vitest_1.expect)(transport).toBeInstanceOf(chat_js_1.TriggerChatTransport);
|
|
69
|
+
});
|
|
70
|
+
(0, vitest_1.it)("should accept a function for accessToken", () => {
|
|
71
|
+
let tokenCallCount = 0;
|
|
72
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
73
|
+
task: "my-chat-task",
|
|
74
|
+
accessToken: () => {
|
|
75
|
+
tokenCallCount++;
|
|
76
|
+
return `dynamic-token-${tokenCallCount}`;
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
(0, vitest_1.expect)(transport).toBeInstanceOf(chat_js_1.TriggerChatTransport);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
(0, vitest_1.describe)("sendMessages", () => {
|
|
83
|
+
(0, vitest_1.it)("should trigger the task and return a ReadableStream of UIMessageChunks", async () => {
|
|
84
|
+
const triggerRunId = "run_abc123";
|
|
85
|
+
const publicToken = "pub_token_xyz";
|
|
86
|
+
// Mock fetch to handle both the trigger request and the SSE stream request
|
|
87
|
+
global.fetch = vitest_1.vi.fn().mockImplementation(async (url, init) => {
|
|
88
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
89
|
+
// Handle the task trigger request
|
|
90
|
+
if (urlStr.includes("/api/v1/tasks/") && urlStr.includes("/trigger")) {
|
|
91
|
+
return new Response(JSON.stringify({ id: triggerRunId }), {
|
|
92
|
+
status: 200,
|
|
93
|
+
headers: {
|
|
94
|
+
"content-type": "application/json",
|
|
95
|
+
"x-trigger-jwt": publicToken,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// Handle the SSE stream request
|
|
100
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
101
|
+
const sseText = sseEncode(sampleChunks);
|
|
102
|
+
return new Response(createSSEStream(sseText), {
|
|
103
|
+
status: 200,
|
|
104
|
+
headers: {
|
|
105
|
+
"content-type": "text/event-stream",
|
|
106
|
+
"X-Stream-Version": "v1",
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
111
|
+
});
|
|
112
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
113
|
+
task: "my-chat-task",
|
|
114
|
+
accessToken: "test-token",
|
|
115
|
+
baseURL: "https://api.test.trigger.dev",
|
|
116
|
+
});
|
|
117
|
+
const messages = [createUserMessage("Hello!")];
|
|
118
|
+
const stream = await transport.sendMessages({
|
|
119
|
+
trigger: "submit-message",
|
|
120
|
+
chatId: "chat-1",
|
|
121
|
+
messageId: undefined,
|
|
122
|
+
messages,
|
|
123
|
+
abortSignal: undefined,
|
|
124
|
+
});
|
|
125
|
+
(0, vitest_1.expect)(stream).toBeInstanceOf(ReadableStream);
|
|
126
|
+
// Read all chunks from the stream
|
|
127
|
+
const reader = stream.getReader();
|
|
128
|
+
const receivedChunks = [];
|
|
129
|
+
while (true) {
|
|
130
|
+
const { done, value } = await reader.read();
|
|
131
|
+
if (done)
|
|
132
|
+
break;
|
|
133
|
+
receivedChunks.push(value);
|
|
134
|
+
}
|
|
135
|
+
(0, vitest_1.expect)(receivedChunks).toHaveLength(sampleChunks.length);
|
|
136
|
+
(0, vitest_1.expect)(receivedChunks[0]).toEqual({ type: "text-start", id: "part-1" });
|
|
137
|
+
(0, vitest_1.expect)(receivedChunks[1]).toEqual({ type: "text-delta", id: "part-1", delta: "Hello" });
|
|
138
|
+
(0, vitest_1.expect)(receivedChunks[4]).toEqual({ type: "text-end", id: "part-1" });
|
|
139
|
+
});
|
|
140
|
+
(0, vitest_1.it)("should send the correct payload to the trigger API", async () => {
|
|
141
|
+
const fetchSpy = vitest_1.vi.fn().mockImplementation(async (url, init) => {
|
|
142
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
143
|
+
if (urlStr.includes("/api/v1/tasks/") && urlStr.includes("/trigger")) {
|
|
144
|
+
return new Response(JSON.stringify({ id: "run_test" }), {
|
|
145
|
+
status: 200,
|
|
146
|
+
headers: {
|
|
147
|
+
"content-type": "application/json",
|
|
148
|
+
"x-trigger-jwt": "pub_token",
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
153
|
+
return new Response(createSSEStream(""), {
|
|
154
|
+
status: 200,
|
|
155
|
+
headers: {
|
|
156
|
+
"content-type": "text/event-stream",
|
|
157
|
+
"X-Stream-Version": "v1",
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
162
|
+
});
|
|
163
|
+
global.fetch = fetchSpy;
|
|
164
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
165
|
+
task: "my-chat-task",
|
|
166
|
+
accessToken: "test-token",
|
|
167
|
+
baseURL: "https://api.test.trigger.dev",
|
|
168
|
+
});
|
|
169
|
+
const messages = [createUserMessage("Hello!")];
|
|
170
|
+
await transport.sendMessages({
|
|
171
|
+
trigger: "submit-message",
|
|
172
|
+
chatId: "chat-123",
|
|
173
|
+
messageId: undefined,
|
|
174
|
+
messages,
|
|
175
|
+
abortSignal: undefined,
|
|
176
|
+
metadata: { custom: "data" },
|
|
177
|
+
});
|
|
178
|
+
// Verify the trigger fetch call
|
|
179
|
+
const triggerCall = fetchSpy.mock.calls.find((call) => (typeof call[0] === "string" ? call[0] : call[0].toString()).includes("/trigger"));
|
|
180
|
+
(0, vitest_1.expect)(triggerCall).toBeDefined();
|
|
181
|
+
const triggerUrl = typeof triggerCall[0] === "string" ? triggerCall[0] : triggerCall[0].toString();
|
|
182
|
+
(0, vitest_1.expect)(triggerUrl).toContain("/api/v1/tasks/my-chat-task/trigger");
|
|
183
|
+
const triggerBody = JSON.parse(triggerCall[1]?.body);
|
|
184
|
+
const payload = triggerBody.payload;
|
|
185
|
+
(0, vitest_1.expect)(payload.messages).toEqual(messages);
|
|
186
|
+
(0, vitest_1.expect)(payload.chatId).toBe("chat-123");
|
|
187
|
+
(0, vitest_1.expect)(payload.trigger).toBe("submit-message");
|
|
188
|
+
(0, vitest_1.expect)(payload.metadata).toEqual({ custom: "data" });
|
|
189
|
+
});
|
|
190
|
+
(0, vitest_1.it)("should use the correct stream URL with custom streamKey", async () => {
|
|
191
|
+
const fetchSpy = vitest_1.vi.fn().mockImplementation(async (url) => {
|
|
192
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
193
|
+
if (urlStr.includes("/trigger")) {
|
|
194
|
+
return new Response(JSON.stringify({ id: "run_custom" }), {
|
|
195
|
+
status: 200,
|
|
196
|
+
headers: {
|
|
197
|
+
"content-type": "application/json",
|
|
198
|
+
"x-trigger-jwt": "token",
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
203
|
+
return new Response(createSSEStream(""), {
|
|
204
|
+
status: 200,
|
|
205
|
+
headers: {
|
|
206
|
+
"content-type": "text/event-stream",
|
|
207
|
+
"X-Stream-Version": "v1",
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
212
|
+
});
|
|
213
|
+
global.fetch = fetchSpy;
|
|
214
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
215
|
+
task: "my-task",
|
|
216
|
+
accessToken: "token",
|
|
217
|
+
baseURL: "https://api.test.trigger.dev",
|
|
218
|
+
streamKey: "my-custom-stream",
|
|
219
|
+
});
|
|
220
|
+
await transport.sendMessages({
|
|
221
|
+
trigger: "submit-message",
|
|
222
|
+
chatId: "chat-1",
|
|
223
|
+
messageId: undefined,
|
|
224
|
+
messages: [createUserMessage("test")],
|
|
225
|
+
abortSignal: undefined,
|
|
226
|
+
});
|
|
227
|
+
// Verify the stream URL uses the custom stream key
|
|
228
|
+
const streamCall = fetchSpy.mock.calls.find((call) => (typeof call[0] === "string" ? call[0] : call[0].toString()).includes("/realtime/v1/streams/"));
|
|
229
|
+
(0, vitest_1.expect)(streamCall).toBeDefined();
|
|
230
|
+
const streamUrl = typeof streamCall[0] === "string" ? streamCall[0] : streamCall[0].toString();
|
|
231
|
+
(0, vitest_1.expect)(streamUrl).toContain("/realtime/v1/streams/run_custom/my-custom-stream");
|
|
232
|
+
});
|
|
233
|
+
(0, vitest_1.it)("should include extra headers in stream requests", async () => {
|
|
234
|
+
const fetchSpy = vitest_1.vi.fn().mockImplementation(async (url) => {
|
|
235
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
236
|
+
if (urlStr.includes("/trigger")) {
|
|
237
|
+
return new Response(JSON.stringify({ id: "run_hdrs" }), {
|
|
238
|
+
status: 200,
|
|
239
|
+
headers: {
|
|
240
|
+
"content-type": "application/json",
|
|
241
|
+
"x-trigger-jwt": "token",
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
246
|
+
return new Response(createSSEStream(""), {
|
|
247
|
+
status: 200,
|
|
248
|
+
headers: {
|
|
249
|
+
"content-type": "text/event-stream",
|
|
250
|
+
"X-Stream-Version": "v1",
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
255
|
+
});
|
|
256
|
+
global.fetch = fetchSpy;
|
|
257
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
258
|
+
task: "my-task",
|
|
259
|
+
accessToken: "token",
|
|
260
|
+
baseURL: "https://api.test.trigger.dev",
|
|
261
|
+
headers: { "X-Custom-Header": "custom-value" },
|
|
262
|
+
});
|
|
263
|
+
await transport.sendMessages({
|
|
264
|
+
trigger: "submit-message",
|
|
265
|
+
chatId: "chat-1",
|
|
266
|
+
messageId: undefined,
|
|
267
|
+
messages: [createUserMessage("test")],
|
|
268
|
+
abortSignal: undefined,
|
|
269
|
+
});
|
|
270
|
+
// Verify the stream request includes custom headers
|
|
271
|
+
const streamCall = fetchSpy.mock.calls.find((call) => (typeof call[0] === "string" ? call[0] : call[0].toString()).includes("/realtime/v1/streams/"));
|
|
272
|
+
(0, vitest_1.expect)(streamCall).toBeDefined();
|
|
273
|
+
const requestHeaders = streamCall[1]?.headers;
|
|
274
|
+
(0, vitest_1.expect)(requestHeaders["X-Custom-Header"]).toBe("custom-value");
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
(0, vitest_1.describe)("reconnectToStream", () => {
|
|
278
|
+
(0, vitest_1.it)("should return null when no session exists for chatId", async () => {
|
|
279
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
280
|
+
task: "my-task",
|
|
281
|
+
accessToken: "token",
|
|
282
|
+
});
|
|
283
|
+
const result = await transport.reconnectToStream({
|
|
284
|
+
chatId: "nonexistent-chat",
|
|
285
|
+
});
|
|
286
|
+
(0, vitest_1.expect)(result).toBeNull();
|
|
287
|
+
});
|
|
288
|
+
(0, vitest_1.it)("should reconnect to an existing session", async () => {
|
|
289
|
+
const triggerRunId = "run_reconnect";
|
|
290
|
+
const publicToken = "pub_reconnect_token";
|
|
291
|
+
global.fetch = vitest_1.vi.fn().mockImplementation(async (url) => {
|
|
292
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
293
|
+
if (urlStr.includes("/trigger")) {
|
|
294
|
+
return new Response(JSON.stringify({ id: triggerRunId }), {
|
|
295
|
+
status: 200,
|
|
296
|
+
headers: {
|
|
297
|
+
"content-type": "application/json",
|
|
298
|
+
"x-trigger-jwt": publicToken,
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
303
|
+
const chunks = [
|
|
304
|
+
{ type: "text-start", id: "part-1" },
|
|
305
|
+
{ type: "text-delta", id: "part-1", delta: "Reconnected!" },
|
|
306
|
+
{ type: "text-end", id: "part-1" },
|
|
307
|
+
];
|
|
308
|
+
return new Response(createSSEStream(sseEncode(chunks)), {
|
|
309
|
+
status: 200,
|
|
310
|
+
headers: {
|
|
311
|
+
"content-type": "text/event-stream",
|
|
312
|
+
"X-Stream-Version": "v1",
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
317
|
+
});
|
|
318
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
319
|
+
task: "my-task",
|
|
320
|
+
accessToken: "token",
|
|
321
|
+
baseURL: "https://api.test.trigger.dev",
|
|
322
|
+
});
|
|
323
|
+
// First, send messages to establish a session
|
|
324
|
+
await transport.sendMessages({
|
|
325
|
+
trigger: "submit-message",
|
|
326
|
+
chatId: "chat-reconnect",
|
|
327
|
+
messageId: undefined,
|
|
328
|
+
messages: [createUserMessage("Hello")],
|
|
329
|
+
abortSignal: undefined,
|
|
330
|
+
});
|
|
331
|
+
// Now reconnect
|
|
332
|
+
const stream = await transport.reconnectToStream({
|
|
333
|
+
chatId: "chat-reconnect",
|
|
334
|
+
});
|
|
335
|
+
(0, vitest_1.expect)(stream).toBeInstanceOf(ReadableStream);
|
|
336
|
+
// Read the stream
|
|
337
|
+
const reader = stream.getReader();
|
|
338
|
+
const receivedChunks = [];
|
|
339
|
+
while (true) {
|
|
340
|
+
const { done, value } = await reader.read();
|
|
341
|
+
if (done)
|
|
342
|
+
break;
|
|
343
|
+
receivedChunks.push(value);
|
|
344
|
+
}
|
|
345
|
+
(0, vitest_1.expect)(receivedChunks.length).toBeGreaterThan(0);
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
(0, vitest_1.describe)("createChatTransport", () => {
|
|
349
|
+
(0, vitest_1.it)("should create a TriggerChatTransport instance", () => {
|
|
350
|
+
const transport = (0, chat_js_1.createChatTransport)({
|
|
351
|
+
task: "my-task",
|
|
352
|
+
accessToken: "token",
|
|
353
|
+
});
|
|
354
|
+
(0, vitest_1.expect)(transport).toBeInstanceOf(chat_js_1.TriggerChatTransport);
|
|
355
|
+
});
|
|
356
|
+
(0, vitest_1.it)("should pass options through to the transport", () => {
|
|
357
|
+
const transport = (0, chat_js_1.createChatTransport)({
|
|
358
|
+
task: "custom-task",
|
|
359
|
+
accessToken: "custom-token",
|
|
360
|
+
baseURL: "https://custom.example.com",
|
|
361
|
+
streamKey: "custom-key",
|
|
362
|
+
headers: { "X-Test": "value" },
|
|
363
|
+
});
|
|
364
|
+
(0, vitest_1.expect)(transport).toBeInstanceOf(chat_js_1.TriggerChatTransport);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
(0, vitest_1.describe)("publicAccessToken from trigger response", () => {
|
|
368
|
+
(0, vitest_1.it)("should use x-trigger-jwt from trigger response as the stream auth token", async () => {
|
|
369
|
+
const fetchSpy = vitest_1.vi.fn().mockImplementation(async (url, init) => {
|
|
370
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
371
|
+
if (urlStr.includes("/trigger")) {
|
|
372
|
+
// Return with x-trigger-jwt header — this public token should be
|
|
373
|
+
// used for the subsequent stream subscription request.
|
|
374
|
+
return new Response(JSON.stringify({ id: "run_pat" }), {
|
|
375
|
+
status: 200,
|
|
376
|
+
headers: {
|
|
377
|
+
"content-type": "application/json",
|
|
378
|
+
"x-trigger-jwt": "server-generated-public-token",
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
383
|
+
// Verify the Authorization header uses the server-generated token
|
|
384
|
+
const authHeader = init?.headers?.["Authorization"];
|
|
385
|
+
(0, vitest_1.expect)(authHeader).toBe("Bearer server-generated-public-token");
|
|
386
|
+
const chunks = [
|
|
387
|
+
{ type: "text-start", id: "p1" },
|
|
388
|
+
{ type: "text-end", id: "p1" },
|
|
389
|
+
];
|
|
390
|
+
return new Response(createSSEStream(sseEncode(chunks)), {
|
|
391
|
+
status: 200,
|
|
392
|
+
headers: {
|
|
393
|
+
"content-type": "text/event-stream",
|
|
394
|
+
"X-Stream-Version": "v1",
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
399
|
+
});
|
|
400
|
+
global.fetch = fetchSpy;
|
|
401
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
402
|
+
task: "my-task",
|
|
403
|
+
accessToken: "caller-token",
|
|
404
|
+
baseURL: "https://api.test.trigger.dev",
|
|
405
|
+
});
|
|
406
|
+
const stream = await transport.sendMessages({
|
|
407
|
+
trigger: "submit-message",
|
|
408
|
+
chatId: "chat-pat",
|
|
409
|
+
messageId: undefined,
|
|
410
|
+
messages: [createUserMessage("test")],
|
|
411
|
+
abortSignal: undefined,
|
|
412
|
+
});
|
|
413
|
+
// Consume the stream
|
|
414
|
+
const reader = stream.getReader();
|
|
415
|
+
while (true) {
|
|
416
|
+
const { done } = await reader.read();
|
|
417
|
+
if (done)
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
// Verify the stream subscription used the public token, not the caller token
|
|
421
|
+
const streamCall = fetchSpy.mock.calls.find((call) => (typeof call[0] === "string" ? call[0] : call[0].toString()).includes("/realtime/v1/streams/"));
|
|
422
|
+
(0, vitest_1.expect)(streamCall).toBeDefined();
|
|
423
|
+
const streamHeaders = streamCall[1]?.headers;
|
|
424
|
+
(0, vitest_1.expect)(streamHeaders["Authorization"]).toBe("Bearer server-generated-public-token");
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
(0, vitest_1.describe)("error handling", () => {
|
|
428
|
+
(0, vitest_1.it)("should propagate trigger API errors", async () => {
|
|
429
|
+
global.fetch = vitest_1.vi.fn().mockImplementation(async (url) => {
|
|
430
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
431
|
+
if (urlStr.includes("/trigger")) {
|
|
432
|
+
return new Response(JSON.stringify({ error: "Task not found" }), {
|
|
433
|
+
status: 404,
|
|
434
|
+
headers: { "content-type": "application/json" },
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
438
|
+
});
|
|
439
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
440
|
+
task: "nonexistent-task",
|
|
441
|
+
accessToken: "token",
|
|
442
|
+
baseURL: "https://api.test.trigger.dev",
|
|
443
|
+
});
|
|
444
|
+
await (0, vitest_1.expect)(transport.sendMessages({
|
|
445
|
+
trigger: "submit-message",
|
|
446
|
+
chatId: "chat-error",
|
|
447
|
+
messageId: undefined,
|
|
448
|
+
messages: [createUserMessage("test")],
|
|
449
|
+
abortSignal: undefined,
|
|
450
|
+
})).rejects.toThrow();
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
(0, vitest_1.describe)("abort signal", () => {
|
|
454
|
+
(0, vitest_1.it)("should close the stream gracefully when aborted", async () => {
|
|
455
|
+
let streamResolve;
|
|
456
|
+
const streamWait = new Promise((resolve) => {
|
|
457
|
+
streamResolve = resolve;
|
|
458
|
+
});
|
|
459
|
+
global.fetch = vitest_1.vi.fn().mockImplementation(async (url) => {
|
|
460
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
461
|
+
if (urlStr.includes("/trigger")) {
|
|
462
|
+
return new Response(JSON.stringify({ id: "run_abort" }), {
|
|
463
|
+
status: 200,
|
|
464
|
+
headers: {
|
|
465
|
+
"content-type": "application/json",
|
|
466
|
+
"x-trigger-jwt": "token",
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
471
|
+
// Create a slow stream that waits before sending data
|
|
472
|
+
const stream = new ReadableStream({
|
|
473
|
+
async start(controller) {
|
|
474
|
+
const encoder = new TextEncoder();
|
|
475
|
+
controller.enqueue(encoder.encode(`id: 0\ndata: ${JSON.stringify({ type: "text-start", id: "p1" })}\n\n`));
|
|
476
|
+
// Wait for the test to signal it's done
|
|
477
|
+
await streamWait;
|
|
478
|
+
controller.close();
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
return new Response(stream, {
|
|
482
|
+
status: 200,
|
|
483
|
+
headers: {
|
|
484
|
+
"content-type": "text/event-stream",
|
|
485
|
+
"X-Stream-Version": "v1",
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
490
|
+
});
|
|
491
|
+
const abortController = new AbortController();
|
|
492
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
493
|
+
task: "my-task",
|
|
494
|
+
accessToken: "token",
|
|
495
|
+
baseURL: "https://api.test.trigger.dev",
|
|
496
|
+
});
|
|
497
|
+
const stream = await transport.sendMessages({
|
|
498
|
+
trigger: "submit-message",
|
|
499
|
+
chatId: "chat-abort",
|
|
500
|
+
messageId: undefined,
|
|
501
|
+
messages: [createUserMessage("test")],
|
|
502
|
+
abortSignal: abortController.signal,
|
|
503
|
+
});
|
|
504
|
+
// Read the first chunk
|
|
505
|
+
const reader = stream.getReader();
|
|
506
|
+
const first = await reader.read();
|
|
507
|
+
(0, vitest_1.expect)(first.done).toBe(false);
|
|
508
|
+
// Abort and clean up
|
|
509
|
+
abortController.abort();
|
|
510
|
+
streamResolve?.();
|
|
511
|
+
// The stream should close — reading should return done
|
|
512
|
+
const next = await reader.read();
|
|
513
|
+
(0, vitest_1.expect)(next.done).toBe(true);
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
(0, vitest_1.describe)("multiple sessions", () => {
|
|
517
|
+
(0, vitest_1.it)("should track multiple chat sessions independently", async () => {
|
|
518
|
+
let callCount = 0;
|
|
519
|
+
global.fetch = vitest_1.vi.fn().mockImplementation(async (url) => {
|
|
520
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
521
|
+
if (urlStr.includes("/trigger")) {
|
|
522
|
+
callCount++;
|
|
523
|
+
return new Response(JSON.stringify({ id: `run_multi_${callCount}` }), {
|
|
524
|
+
status: 200,
|
|
525
|
+
headers: {
|
|
526
|
+
"content-type": "application/json",
|
|
527
|
+
"x-trigger-jwt": `token_${callCount}`,
|
|
528
|
+
},
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
532
|
+
return new Response(createSSEStream(""), {
|
|
533
|
+
status: 200,
|
|
534
|
+
headers: {
|
|
535
|
+
"content-type": "text/event-stream",
|
|
536
|
+
"X-Stream-Version": "v1",
|
|
537
|
+
},
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
541
|
+
});
|
|
542
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
543
|
+
task: "my-task",
|
|
544
|
+
accessToken: "token",
|
|
545
|
+
baseURL: "https://api.test.trigger.dev",
|
|
546
|
+
});
|
|
547
|
+
// Start two independent chat sessions
|
|
548
|
+
await transport.sendMessages({
|
|
549
|
+
trigger: "submit-message",
|
|
550
|
+
chatId: "session-a",
|
|
551
|
+
messageId: undefined,
|
|
552
|
+
messages: [createUserMessage("Hello A")],
|
|
553
|
+
abortSignal: undefined,
|
|
554
|
+
});
|
|
555
|
+
await transport.sendMessages({
|
|
556
|
+
trigger: "submit-message",
|
|
557
|
+
chatId: "session-b",
|
|
558
|
+
messageId: undefined,
|
|
559
|
+
messages: [createUserMessage("Hello B")],
|
|
560
|
+
abortSignal: undefined,
|
|
561
|
+
});
|
|
562
|
+
// Both sessions should be independently reconnectable
|
|
563
|
+
const streamA = await transport.reconnectToStream({ chatId: "session-a" });
|
|
564
|
+
const streamB = await transport.reconnectToStream({ chatId: "session-b" });
|
|
565
|
+
const streamC = await transport.reconnectToStream({ chatId: "nonexistent" });
|
|
566
|
+
(0, vitest_1.expect)(streamA).toBeInstanceOf(ReadableStream);
|
|
567
|
+
(0, vitest_1.expect)(streamB).toBeInstanceOf(ReadableStream);
|
|
568
|
+
(0, vitest_1.expect)(streamC).toBeNull();
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
(0, vitest_1.describe)("dynamic accessToken", () => {
|
|
572
|
+
(0, vitest_1.it)("should call the accessToken function for each sendMessages call", async () => {
|
|
573
|
+
let tokenCallCount = 0;
|
|
574
|
+
global.fetch = vitest_1.vi.fn().mockImplementation(async (url) => {
|
|
575
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
576
|
+
if (urlStr.includes("/trigger")) {
|
|
577
|
+
return new Response(JSON.stringify({ id: `run_dyn_${tokenCallCount}` }), {
|
|
578
|
+
status: 200,
|
|
579
|
+
headers: {
|
|
580
|
+
"content-type": "application/json",
|
|
581
|
+
"x-trigger-jwt": "stream-token",
|
|
582
|
+
},
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
586
|
+
const chunks = [
|
|
587
|
+
{ type: "text-start", id: "p1" },
|
|
588
|
+
{ type: "text-end", id: "p1" },
|
|
589
|
+
];
|
|
590
|
+
return new Response(createSSEStream(sseEncode(chunks)), {
|
|
591
|
+
status: 200,
|
|
592
|
+
headers: {
|
|
593
|
+
"content-type": "text/event-stream",
|
|
594
|
+
"X-Stream-Version": "v1",
|
|
595
|
+
},
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
599
|
+
});
|
|
600
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
601
|
+
task: "my-task",
|
|
602
|
+
accessToken: () => {
|
|
603
|
+
tokenCallCount++;
|
|
604
|
+
return `dynamic-token-${tokenCallCount}`;
|
|
605
|
+
},
|
|
606
|
+
baseURL: "https://api.test.trigger.dev",
|
|
607
|
+
});
|
|
608
|
+
// First call — the token function should be invoked
|
|
609
|
+
await transport.sendMessages({
|
|
610
|
+
trigger: "submit-message",
|
|
611
|
+
chatId: "chat-dyn-1",
|
|
612
|
+
messageId: undefined,
|
|
613
|
+
messages: [createUserMessage("first")],
|
|
614
|
+
abortSignal: undefined,
|
|
615
|
+
});
|
|
616
|
+
const firstCount = tokenCallCount;
|
|
617
|
+
(0, vitest_1.expect)(firstCount).toBeGreaterThanOrEqual(1);
|
|
618
|
+
// Second call — the token function should be invoked again
|
|
619
|
+
await transport.sendMessages({
|
|
620
|
+
trigger: "submit-message",
|
|
621
|
+
chatId: "chat-dyn-2",
|
|
622
|
+
messageId: undefined,
|
|
623
|
+
messages: [createUserMessage("second")],
|
|
624
|
+
abortSignal: undefined,
|
|
625
|
+
});
|
|
626
|
+
// Token function was called at least once more
|
|
627
|
+
(0, vitest_1.expect)(tokenCallCount).toBeGreaterThan(firstCount);
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
(0, vitest_1.describe)("body merging", () => {
|
|
631
|
+
(0, vitest_1.it)("should merge ChatRequestOptions.body into the task payload", async () => {
|
|
632
|
+
const fetchSpy = vitest_1.vi.fn().mockImplementation(async (url) => {
|
|
633
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
634
|
+
if (urlStr.includes("/trigger")) {
|
|
635
|
+
return new Response(JSON.stringify({ id: "run_body" }), {
|
|
636
|
+
status: 200,
|
|
637
|
+
headers: {
|
|
638
|
+
"content-type": "application/json",
|
|
639
|
+
"x-trigger-jwt": "token",
|
|
640
|
+
},
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
644
|
+
return new Response(createSSEStream(""), {
|
|
645
|
+
status: 200,
|
|
646
|
+
headers: {
|
|
647
|
+
"content-type": "text/event-stream",
|
|
648
|
+
"X-Stream-Version": "v1",
|
|
649
|
+
},
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
653
|
+
});
|
|
654
|
+
global.fetch = fetchSpy;
|
|
655
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
656
|
+
task: "my-task",
|
|
657
|
+
accessToken: "token",
|
|
658
|
+
baseURL: "https://api.test.trigger.dev",
|
|
659
|
+
});
|
|
660
|
+
await transport.sendMessages({
|
|
661
|
+
trigger: "submit-message",
|
|
662
|
+
chatId: "chat-body",
|
|
663
|
+
messageId: undefined,
|
|
664
|
+
messages: [createUserMessage("test")],
|
|
665
|
+
abortSignal: undefined,
|
|
666
|
+
body: { systemPrompt: "You are helpful", temperature: 0.7 },
|
|
667
|
+
});
|
|
668
|
+
const triggerCall = fetchSpy.mock.calls.find((call) => (typeof call[0] === "string" ? call[0] : call[0].toString()).includes("/trigger"));
|
|
669
|
+
const triggerBody = JSON.parse(triggerCall[1]?.body);
|
|
670
|
+
const payload = triggerBody.payload;
|
|
671
|
+
// body properties should be merged into the payload
|
|
672
|
+
(0, vitest_1.expect)(payload.systemPrompt).toBe("You are helpful");
|
|
673
|
+
(0, vitest_1.expect)(payload.temperature).toBe(0.7);
|
|
674
|
+
// Standard fields should still be present
|
|
675
|
+
(0, vitest_1.expect)(payload.chatId).toBe("chat-body");
|
|
676
|
+
(0, vitest_1.expect)(payload.trigger).toBe("submit-message");
|
|
677
|
+
});
|
|
678
|
+
});
|
|
679
|
+
(0, vitest_1.describe)("message types", () => {
|
|
680
|
+
(0, vitest_1.it)("should handle regenerate-message trigger", async () => {
|
|
681
|
+
const fetchSpy = vitest_1.vi.fn().mockImplementation(async (url) => {
|
|
682
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
683
|
+
if (urlStr.includes("/trigger")) {
|
|
684
|
+
return new Response(JSON.stringify({ id: "run_regen" }), {
|
|
685
|
+
status: 200,
|
|
686
|
+
headers: {
|
|
687
|
+
"content-type": "application/json",
|
|
688
|
+
"x-trigger-jwt": "token",
|
|
689
|
+
},
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
693
|
+
return new Response(createSSEStream(""), {
|
|
694
|
+
status: 200,
|
|
695
|
+
headers: {
|
|
696
|
+
"content-type": "text/event-stream",
|
|
697
|
+
"X-Stream-Version": "v1",
|
|
698
|
+
},
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
702
|
+
});
|
|
703
|
+
global.fetch = fetchSpy;
|
|
704
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
705
|
+
task: "my-task",
|
|
706
|
+
accessToken: "token",
|
|
707
|
+
baseURL: "https://api.test.trigger.dev",
|
|
708
|
+
});
|
|
709
|
+
const messages = [
|
|
710
|
+
createUserMessage("Hello!"),
|
|
711
|
+
createAssistantMessage("Hi there!"),
|
|
712
|
+
];
|
|
713
|
+
await transport.sendMessages({
|
|
714
|
+
trigger: "regenerate-message",
|
|
715
|
+
chatId: "chat-regen",
|
|
716
|
+
messageId: "msg-to-regen",
|
|
717
|
+
messages,
|
|
718
|
+
abortSignal: undefined,
|
|
719
|
+
});
|
|
720
|
+
// Verify the payload includes the regenerate trigger type and messageId
|
|
721
|
+
const triggerCall = fetchSpy.mock.calls.find((call) => (typeof call[0] === "string" ? call[0] : call[0].toString()).includes("/trigger"));
|
|
722
|
+
const triggerBody = JSON.parse(triggerCall[1]?.body);
|
|
723
|
+
const payload = triggerBody.payload;
|
|
724
|
+
(0, vitest_1.expect)(payload.trigger).toBe("regenerate-message");
|
|
725
|
+
(0, vitest_1.expect)(payload.messageId).toBe("msg-to-regen");
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
(0, vitest_1.describe)("lastEventId tracking", () => {
|
|
729
|
+
(0, vitest_1.it)("should pass lastEventId to SSE subscription on subsequent turns", async () => {
|
|
730
|
+
const controlChunk = {
|
|
731
|
+
type: "__trigger_waitpoint_ready",
|
|
732
|
+
tokenId: "wp_token_eid",
|
|
733
|
+
publicAccessToken: "wp_access_eid",
|
|
734
|
+
};
|
|
735
|
+
let triggerCallCount = 0;
|
|
736
|
+
const streamFetchCalls = [];
|
|
737
|
+
global.fetch = vitest_1.vi.fn().mockImplementation(async (url, init) => {
|
|
738
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
739
|
+
if (urlStr.includes("/api/v1/tasks/") && urlStr.includes("/trigger")) {
|
|
740
|
+
triggerCallCount++;
|
|
741
|
+
return new Response(JSON.stringify({ id: "run_eid" }), {
|
|
742
|
+
status: 200,
|
|
743
|
+
headers: {
|
|
744
|
+
"content-type": "application/json",
|
|
745
|
+
"x-trigger-jwt": "pub_token_eid",
|
|
746
|
+
},
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
if (urlStr.includes("/api/v1/waitpoints/tokens/") && urlStr.includes("/complete")) {
|
|
750
|
+
return new Response(JSON.stringify({ success: true }), {
|
|
751
|
+
status: 200,
|
|
752
|
+
headers: { "content-type": "application/json" },
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
756
|
+
streamFetchCalls.push({
|
|
757
|
+
url: urlStr,
|
|
758
|
+
headers: init?.headers ?? {},
|
|
759
|
+
});
|
|
760
|
+
const chunks = [
|
|
761
|
+
...sampleChunks,
|
|
762
|
+
{ type: "finish", id: "part-1" },
|
|
763
|
+
controlChunk,
|
|
764
|
+
];
|
|
765
|
+
return new Response(createSSEStream(sseEncode(chunks)), {
|
|
766
|
+
status: 200,
|
|
767
|
+
headers: {
|
|
768
|
+
"content-type": "text/event-stream",
|
|
769
|
+
"X-Stream-Version": "v1",
|
|
770
|
+
},
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
774
|
+
});
|
|
775
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
776
|
+
task: "my-task",
|
|
777
|
+
accessToken: "token",
|
|
778
|
+
baseURL: "https://api.test.trigger.dev",
|
|
779
|
+
});
|
|
780
|
+
// First message — triggers a new run
|
|
781
|
+
const stream1 = await transport.sendMessages({
|
|
782
|
+
trigger: "submit-message",
|
|
783
|
+
chatId: "chat-eid",
|
|
784
|
+
messageId: undefined,
|
|
785
|
+
messages: [createUserMessage("Hello")],
|
|
786
|
+
abortSignal: undefined,
|
|
787
|
+
});
|
|
788
|
+
const reader1 = stream1.getReader();
|
|
789
|
+
while (true) {
|
|
790
|
+
const { done } = await reader1.read();
|
|
791
|
+
if (done)
|
|
792
|
+
break;
|
|
793
|
+
}
|
|
794
|
+
// Second message — completes the waitpoint
|
|
795
|
+
const stream2 = await transport.sendMessages({
|
|
796
|
+
trigger: "submit-message",
|
|
797
|
+
chatId: "chat-eid",
|
|
798
|
+
messageId: undefined,
|
|
799
|
+
messages: [createUserMessage("Hello"), createAssistantMessage("Hi!"), createUserMessage("What's up?")],
|
|
800
|
+
abortSignal: undefined,
|
|
801
|
+
});
|
|
802
|
+
const reader2 = stream2.getReader();
|
|
803
|
+
while (true) {
|
|
804
|
+
const { done } = await reader2.read();
|
|
805
|
+
if (done)
|
|
806
|
+
break;
|
|
807
|
+
}
|
|
808
|
+
// The second stream subscription should include a Last-Event-ID header
|
|
809
|
+
(0, vitest_1.expect)(streamFetchCalls.length).toBe(2);
|
|
810
|
+
const secondStreamHeaders = streamFetchCalls[1].headers;
|
|
811
|
+
// SSEStreamSubscription passes lastEventId as the Last-Event-ID header
|
|
812
|
+
(0, vitest_1.expect)(secondStreamHeaders["Last-Event-ID"]).toBeDefined();
|
|
813
|
+
});
|
|
814
|
+
});
|
|
815
|
+
(0, vitest_1.describe)("AbortController cleanup", () => {
|
|
816
|
+
(0, vitest_1.it)("should terminate SSE connection after intercepting control chunk", async () => {
|
|
817
|
+
const controlChunk = {
|
|
818
|
+
type: "__trigger_waitpoint_ready",
|
|
819
|
+
tokenId: "wp_token_abort",
|
|
820
|
+
publicAccessToken: "wp_access_abort",
|
|
821
|
+
};
|
|
822
|
+
let streamAborted = false;
|
|
823
|
+
global.fetch = vitest_1.vi.fn().mockImplementation(async (url, init) => {
|
|
824
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
825
|
+
if (urlStr.includes("/trigger")) {
|
|
826
|
+
return new Response(JSON.stringify({ id: "run_abort_cleanup" }), {
|
|
827
|
+
status: 200,
|
|
828
|
+
headers: {
|
|
829
|
+
"content-type": "application/json",
|
|
830
|
+
"x-trigger-jwt": "pub_token",
|
|
831
|
+
},
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
835
|
+
// Track abort signal
|
|
836
|
+
const signal = init?.signal;
|
|
837
|
+
if (signal) {
|
|
838
|
+
signal.addEventListener("abort", () => {
|
|
839
|
+
streamAborted = true;
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
const chunks = [
|
|
843
|
+
...sampleChunks,
|
|
844
|
+
{ type: "finish", id: "part-1" },
|
|
845
|
+
controlChunk,
|
|
846
|
+
];
|
|
847
|
+
return new Response(createSSEStream(sseEncode(chunks)), {
|
|
848
|
+
status: 200,
|
|
849
|
+
headers: {
|
|
850
|
+
"content-type": "text/event-stream",
|
|
851
|
+
"X-Stream-Version": "v1",
|
|
852
|
+
},
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
856
|
+
});
|
|
857
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
858
|
+
task: "my-task",
|
|
859
|
+
accessToken: "token",
|
|
860
|
+
baseURL: "https://api.test.trigger.dev",
|
|
861
|
+
});
|
|
862
|
+
const stream = await transport.sendMessages({
|
|
863
|
+
trigger: "submit-message",
|
|
864
|
+
chatId: "chat-abort-cleanup",
|
|
865
|
+
messageId: undefined,
|
|
866
|
+
messages: [createUserMessage("Hello")],
|
|
867
|
+
abortSignal: undefined,
|
|
868
|
+
});
|
|
869
|
+
// Consume all chunks
|
|
870
|
+
const reader = stream.getReader();
|
|
871
|
+
while (true) {
|
|
872
|
+
const { done } = await reader.read();
|
|
873
|
+
if (done)
|
|
874
|
+
break;
|
|
875
|
+
}
|
|
876
|
+
// The internal AbortController should have aborted the fetch
|
|
877
|
+
(0, vitest_1.expect)(streamAborted).toBe(true);
|
|
878
|
+
});
|
|
879
|
+
});
|
|
880
|
+
(0, vitest_1.describe)("async accessToken", () => {
|
|
881
|
+
(0, vitest_1.it)("should accept an async function for accessToken", async () => {
|
|
882
|
+
let tokenCallCount = 0;
|
|
883
|
+
global.fetch = vitest_1.vi.fn().mockImplementation(async (url) => {
|
|
884
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
885
|
+
if (urlStr.includes("/trigger")) {
|
|
886
|
+
return new Response(JSON.stringify({ id: `run_async_${tokenCallCount}` }), {
|
|
887
|
+
status: 200,
|
|
888
|
+
headers: {
|
|
889
|
+
"content-type": "application/json",
|
|
890
|
+
"x-trigger-jwt": "stream-token",
|
|
891
|
+
},
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
895
|
+
const chunks = [
|
|
896
|
+
{ type: "text-start", id: "p1" },
|
|
897
|
+
{ type: "text-end", id: "p1" },
|
|
898
|
+
];
|
|
899
|
+
return new Response(createSSEStream(sseEncode(chunks)), {
|
|
900
|
+
status: 200,
|
|
901
|
+
headers: {
|
|
902
|
+
"content-type": "text/event-stream",
|
|
903
|
+
"X-Stream-Version": "v1",
|
|
904
|
+
},
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
908
|
+
});
|
|
909
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
910
|
+
task: "my-task",
|
|
911
|
+
accessToken: async () => {
|
|
912
|
+
tokenCallCount++;
|
|
913
|
+
// Simulate async work (e.g. server action)
|
|
914
|
+
await new Promise((r) => setTimeout(r, 1));
|
|
915
|
+
return `async-token-${tokenCallCount}`;
|
|
916
|
+
},
|
|
917
|
+
baseURL: "https://api.test.trigger.dev",
|
|
918
|
+
});
|
|
919
|
+
await transport.sendMessages({
|
|
920
|
+
trigger: "submit-message",
|
|
921
|
+
chatId: "chat-async",
|
|
922
|
+
messageId: undefined,
|
|
923
|
+
messages: [createUserMessage("Hello")],
|
|
924
|
+
abortSignal: undefined,
|
|
925
|
+
});
|
|
926
|
+
(0, vitest_1.expect)(tokenCallCount).toBe(1);
|
|
927
|
+
});
|
|
928
|
+
(0, vitest_1.it)("should resolve async token for waitpoint completion flow", async () => {
|
|
929
|
+
const controlChunk = {
|
|
930
|
+
type: "__trigger_waitpoint_ready",
|
|
931
|
+
tokenId: "wp_token_async",
|
|
932
|
+
publicAccessToken: "wp_access_async",
|
|
933
|
+
};
|
|
934
|
+
let tokenCallCount = 0;
|
|
935
|
+
let completeWaitpointCalled = false;
|
|
936
|
+
global.fetch = vitest_1.vi.fn().mockImplementation(async (url) => {
|
|
937
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
938
|
+
if (urlStr.includes("/api/v1/tasks/") && urlStr.includes("/trigger")) {
|
|
939
|
+
return new Response(JSON.stringify({ id: "run_async_wp" }), {
|
|
940
|
+
status: 200,
|
|
941
|
+
headers: {
|
|
942
|
+
"content-type": "application/json",
|
|
943
|
+
"x-trigger-jwt": "stream-token",
|
|
944
|
+
},
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
if (urlStr.includes("/api/v1/waitpoints/tokens/") && urlStr.includes("/complete")) {
|
|
948
|
+
completeWaitpointCalled = true;
|
|
949
|
+
return new Response(JSON.stringify({ success: true }), {
|
|
950
|
+
status: 200,
|
|
951
|
+
headers: { "content-type": "application/json" },
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
955
|
+
const chunks = [
|
|
956
|
+
...sampleChunks,
|
|
957
|
+
{ type: "finish", id: "part-1" },
|
|
958
|
+
controlChunk,
|
|
959
|
+
];
|
|
960
|
+
return new Response(createSSEStream(sseEncode(chunks)), {
|
|
961
|
+
status: 200,
|
|
962
|
+
headers: {
|
|
963
|
+
"content-type": "text/event-stream",
|
|
964
|
+
"X-Stream-Version": "v1",
|
|
965
|
+
},
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
969
|
+
});
|
|
970
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
971
|
+
task: "my-task",
|
|
972
|
+
accessToken: async () => {
|
|
973
|
+
tokenCallCount++;
|
|
974
|
+
await new Promise((r) => setTimeout(r, 1));
|
|
975
|
+
return `async-wp-token-${tokenCallCount}`;
|
|
976
|
+
},
|
|
977
|
+
baseURL: "https://api.test.trigger.dev",
|
|
978
|
+
});
|
|
979
|
+
// First message — triggers a new run (calls async token)
|
|
980
|
+
const stream1 = await transport.sendMessages({
|
|
981
|
+
trigger: "submit-message",
|
|
982
|
+
chatId: "chat-async-wp",
|
|
983
|
+
messageId: undefined,
|
|
984
|
+
messages: [createUserMessage("Hello")],
|
|
985
|
+
abortSignal: undefined,
|
|
986
|
+
});
|
|
987
|
+
const reader1 = stream1.getReader();
|
|
988
|
+
while (true) {
|
|
989
|
+
const { done } = await reader1.read();
|
|
990
|
+
if (done)
|
|
991
|
+
break;
|
|
992
|
+
}
|
|
993
|
+
const firstTokenCount = tokenCallCount;
|
|
994
|
+
// Second message — should complete waitpoint (does NOT call async token)
|
|
995
|
+
const stream2 = await transport.sendMessages({
|
|
996
|
+
trigger: "submit-message",
|
|
997
|
+
chatId: "chat-async-wp",
|
|
998
|
+
messageId: undefined,
|
|
999
|
+
messages: [createUserMessage("Hello"), createAssistantMessage("Hi!"), createUserMessage("More")],
|
|
1000
|
+
abortSignal: undefined,
|
|
1001
|
+
});
|
|
1002
|
+
const reader2 = stream2.getReader();
|
|
1003
|
+
while (true) {
|
|
1004
|
+
const { done } = await reader2.read();
|
|
1005
|
+
if (done)
|
|
1006
|
+
break;
|
|
1007
|
+
}
|
|
1008
|
+
// Token function should NOT have been called again for the waitpoint path
|
|
1009
|
+
(0, vitest_1.expect)(tokenCallCount).toBe(firstTokenCount);
|
|
1010
|
+
(0, vitest_1.expect)(completeWaitpointCalled).toBe(true);
|
|
1011
|
+
});
|
|
1012
|
+
});
|
|
1013
|
+
(0, vitest_1.describe)("single-run mode (waitpoint loop)", () => {
|
|
1014
|
+
(0, vitest_1.it)("should store waitpoint token from control chunk and not forward it to consumer", async () => {
|
|
1015
|
+
const controlChunk = {
|
|
1016
|
+
type: "__trigger_waitpoint_ready",
|
|
1017
|
+
tokenId: "wp_token_123",
|
|
1018
|
+
publicAccessToken: "wp_access_abc",
|
|
1019
|
+
};
|
|
1020
|
+
global.fetch = vitest_1.vi.fn().mockImplementation(async (url) => {
|
|
1021
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
1022
|
+
if (urlStr.includes("/trigger")) {
|
|
1023
|
+
return new Response(JSON.stringify({ id: "run_single" }), {
|
|
1024
|
+
status: 200,
|
|
1025
|
+
headers: {
|
|
1026
|
+
"content-type": "application/json",
|
|
1027
|
+
"x-trigger-jwt": "pub_token",
|
|
1028
|
+
},
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
1032
|
+
const chunks = [
|
|
1033
|
+
...sampleChunks,
|
|
1034
|
+
{ type: "finish", id: "part-1" },
|
|
1035
|
+
controlChunk,
|
|
1036
|
+
];
|
|
1037
|
+
return new Response(createSSEStream(sseEncode(chunks)), {
|
|
1038
|
+
status: 200,
|
|
1039
|
+
headers: {
|
|
1040
|
+
"content-type": "text/event-stream",
|
|
1041
|
+
"X-Stream-Version": "v1",
|
|
1042
|
+
},
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
1046
|
+
});
|
|
1047
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
1048
|
+
task: "my-task",
|
|
1049
|
+
accessToken: "token",
|
|
1050
|
+
baseURL: "https://api.test.trigger.dev",
|
|
1051
|
+
});
|
|
1052
|
+
const stream = await transport.sendMessages({
|
|
1053
|
+
trigger: "submit-message",
|
|
1054
|
+
chatId: "chat-single",
|
|
1055
|
+
messageId: undefined,
|
|
1056
|
+
messages: [createUserMessage("Hello")],
|
|
1057
|
+
abortSignal: undefined,
|
|
1058
|
+
});
|
|
1059
|
+
// Read all chunks — the control chunk should NOT appear
|
|
1060
|
+
const reader = stream.getReader();
|
|
1061
|
+
const receivedChunks = [];
|
|
1062
|
+
while (true) {
|
|
1063
|
+
const { done, value } = await reader.read();
|
|
1064
|
+
if (done)
|
|
1065
|
+
break;
|
|
1066
|
+
receivedChunks.push(value);
|
|
1067
|
+
}
|
|
1068
|
+
// All AI SDK chunks should be forwarded
|
|
1069
|
+
(0, vitest_1.expect)(receivedChunks.length).toBe(sampleChunks.length + 1); // +1 for the finish chunk
|
|
1070
|
+
// Control chunk should not be in the output
|
|
1071
|
+
(0, vitest_1.expect)(receivedChunks.every((c) => c.type !== "__trigger_waitpoint_ready")).toBe(true);
|
|
1072
|
+
});
|
|
1073
|
+
(0, vitest_1.it)("should complete waitpoint token on second message instead of triggering a new run", async () => {
|
|
1074
|
+
const controlChunk = {
|
|
1075
|
+
type: "__trigger_waitpoint_ready",
|
|
1076
|
+
tokenId: "wp_token_456",
|
|
1077
|
+
publicAccessToken: "wp_access_def",
|
|
1078
|
+
};
|
|
1079
|
+
let triggerCallCount = 0;
|
|
1080
|
+
let completeWaitpointCalled = false;
|
|
1081
|
+
global.fetch = vitest_1.vi.fn().mockImplementation(async (url, init) => {
|
|
1082
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
1083
|
+
if (urlStr.includes("/api/v1/tasks/") && urlStr.includes("/trigger")) {
|
|
1084
|
+
triggerCallCount++;
|
|
1085
|
+
return new Response(JSON.stringify({ id: "run_resume" }), {
|
|
1086
|
+
status: 200,
|
|
1087
|
+
headers: {
|
|
1088
|
+
"content-type": "application/json",
|
|
1089
|
+
"x-trigger-jwt": "pub_token",
|
|
1090
|
+
},
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
// Handle waitpoint token completion
|
|
1094
|
+
if (urlStr.includes("/api/v1/waitpoints/tokens/") && urlStr.includes("/complete")) {
|
|
1095
|
+
completeWaitpointCalled = true;
|
|
1096
|
+
return new Response(JSON.stringify({ success: true }), {
|
|
1097
|
+
status: 200,
|
|
1098
|
+
headers: { "content-type": "application/json" },
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
1102
|
+
const chunks = [
|
|
1103
|
+
...sampleChunks,
|
|
1104
|
+
{ type: "finish", id: "part-1" },
|
|
1105
|
+
controlChunk,
|
|
1106
|
+
];
|
|
1107
|
+
return new Response(createSSEStream(sseEncode(chunks)), {
|
|
1108
|
+
status: 200,
|
|
1109
|
+
headers: {
|
|
1110
|
+
"content-type": "text/event-stream",
|
|
1111
|
+
"X-Stream-Version": "v1",
|
|
1112
|
+
},
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
1116
|
+
});
|
|
1117
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
1118
|
+
task: "my-task",
|
|
1119
|
+
accessToken: "token",
|
|
1120
|
+
baseURL: "https://api.test.trigger.dev",
|
|
1121
|
+
});
|
|
1122
|
+
// First message — triggers a new run
|
|
1123
|
+
const stream1 = await transport.sendMessages({
|
|
1124
|
+
trigger: "submit-message",
|
|
1125
|
+
chatId: "chat-resume",
|
|
1126
|
+
messageId: undefined,
|
|
1127
|
+
messages: [createUserMessage("Hello")],
|
|
1128
|
+
abortSignal: undefined,
|
|
1129
|
+
});
|
|
1130
|
+
// Consume stream to capture the control chunk
|
|
1131
|
+
const reader1 = stream1.getReader();
|
|
1132
|
+
while (true) {
|
|
1133
|
+
const { done } = await reader1.read();
|
|
1134
|
+
if (done)
|
|
1135
|
+
break;
|
|
1136
|
+
}
|
|
1137
|
+
(0, vitest_1.expect)(triggerCallCount).toBe(1);
|
|
1138
|
+
// Second message — should complete the waitpoint instead of triggering
|
|
1139
|
+
const stream2 = await transport.sendMessages({
|
|
1140
|
+
trigger: "submit-message",
|
|
1141
|
+
chatId: "chat-resume",
|
|
1142
|
+
messageId: undefined,
|
|
1143
|
+
messages: [createUserMessage("Hello"), createAssistantMessage("Hi!"), createUserMessage("How are you?")],
|
|
1144
|
+
abortSignal: undefined,
|
|
1145
|
+
});
|
|
1146
|
+
// Consume second stream
|
|
1147
|
+
const reader2 = stream2.getReader();
|
|
1148
|
+
while (true) {
|
|
1149
|
+
const { done } = await reader2.read();
|
|
1150
|
+
if (done)
|
|
1151
|
+
break;
|
|
1152
|
+
}
|
|
1153
|
+
// Should NOT have triggered a second run
|
|
1154
|
+
(0, vitest_1.expect)(triggerCallCount).toBe(1);
|
|
1155
|
+
// Should have completed the waitpoint
|
|
1156
|
+
(0, vitest_1.expect)(completeWaitpointCalled).toBe(true);
|
|
1157
|
+
});
|
|
1158
|
+
(0, vitest_1.it)("should fall back to triggering a new run if stream closes without control chunk", async () => {
|
|
1159
|
+
let triggerCallCount = 0;
|
|
1160
|
+
global.fetch = vitest_1.vi.fn().mockImplementation(async (url) => {
|
|
1161
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
1162
|
+
if (urlStr.includes("/api/v1/tasks/") && urlStr.includes("/trigger")) {
|
|
1163
|
+
triggerCallCount++;
|
|
1164
|
+
return new Response(JSON.stringify({ id: `run_fallback_${triggerCallCount}` }), {
|
|
1165
|
+
status: 200,
|
|
1166
|
+
headers: {
|
|
1167
|
+
"content-type": "application/json",
|
|
1168
|
+
"x-trigger-jwt": "pub_token",
|
|
1169
|
+
},
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
1173
|
+
// No control chunk — stream just ends after the finish
|
|
1174
|
+
const chunks = [
|
|
1175
|
+
{ type: "text-start", id: "p1" },
|
|
1176
|
+
{ type: "text-delta", id: "p1", delta: "Hello" },
|
|
1177
|
+
{ type: "text-end", id: "p1" },
|
|
1178
|
+
];
|
|
1179
|
+
return new Response(createSSEStream(sseEncode(chunks)), {
|
|
1180
|
+
status: 200,
|
|
1181
|
+
headers: {
|
|
1182
|
+
"content-type": "text/event-stream",
|
|
1183
|
+
"X-Stream-Version": "v1",
|
|
1184
|
+
},
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
1188
|
+
});
|
|
1189
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
1190
|
+
task: "my-task",
|
|
1191
|
+
accessToken: "token",
|
|
1192
|
+
baseURL: "https://api.test.trigger.dev",
|
|
1193
|
+
});
|
|
1194
|
+
// First message
|
|
1195
|
+
const stream1 = await transport.sendMessages({
|
|
1196
|
+
trigger: "submit-message",
|
|
1197
|
+
chatId: "chat-fallback",
|
|
1198
|
+
messageId: undefined,
|
|
1199
|
+
messages: [createUserMessage("Hello")],
|
|
1200
|
+
abortSignal: undefined,
|
|
1201
|
+
});
|
|
1202
|
+
const reader1 = stream1.getReader();
|
|
1203
|
+
while (true) {
|
|
1204
|
+
const { done } = await reader1.read();
|
|
1205
|
+
if (done)
|
|
1206
|
+
break;
|
|
1207
|
+
}
|
|
1208
|
+
(0, vitest_1.expect)(triggerCallCount).toBe(1);
|
|
1209
|
+
// Second message — no waitpoint token stored, should trigger a new run
|
|
1210
|
+
await transport.sendMessages({
|
|
1211
|
+
trigger: "submit-message",
|
|
1212
|
+
chatId: "chat-fallback",
|
|
1213
|
+
messageId: undefined,
|
|
1214
|
+
messages: [createUserMessage("Hello"), createAssistantMessage("Hi!"), createUserMessage("Again")],
|
|
1215
|
+
abortSignal: undefined,
|
|
1216
|
+
});
|
|
1217
|
+
// Should have triggered a second run
|
|
1218
|
+
(0, vitest_1.expect)(triggerCallCount).toBe(2);
|
|
1219
|
+
});
|
|
1220
|
+
(0, vitest_1.it)("should fall back to new run when completing waitpoint fails", async () => {
|
|
1221
|
+
const controlChunk = {
|
|
1222
|
+
type: "__trigger_waitpoint_ready",
|
|
1223
|
+
tokenId: "wp_token_fail",
|
|
1224
|
+
publicAccessToken: "wp_access_fail",
|
|
1225
|
+
};
|
|
1226
|
+
let triggerCallCount = 0;
|
|
1227
|
+
global.fetch = vitest_1.vi.fn().mockImplementation(async (url) => {
|
|
1228
|
+
const urlStr = typeof url === "string" ? url : url.toString();
|
|
1229
|
+
if (urlStr.includes("/api/v1/tasks/") && urlStr.includes("/trigger")) {
|
|
1230
|
+
triggerCallCount++;
|
|
1231
|
+
return new Response(JSON.stringify({ id: `run_fail_${triggerCallCount}` }), {
|
|
1232
|
+
status: 200,
|
|
1233
|
+
headers: {
|
|
1234
|
+
"content-type": "application/json",
|
|
1235
|
+
"x-trigger-jwt": "pub_token",
|
|
1236
|
+
},
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
// Waitpoint completion fails
|
|
1240
|
+
if (urlStr.includes("/api/v1/waitpoints/tokens/") && urlStr.includes("/complete")) {
|
|
1241
|
+
return new Response(JSON.stringify({ error: "Token expired" }), {
|
|
1242
|
+
status: 400,
|
|
1243
|
+
headers: { "content-type": "application/json" },
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
if (urlStr.includes("/realtime/v1/streams/")) {
|
|
1247
|
+
// First call has control chunk, subsequent calls don't
|
|
1248
|
+
const chunks = [
|
|
1249
|
+
...sampleChunks,
|
|
1250
|
+
{ type: "finish", id: "part-1" },
|
|
1251
|
+
];
|
|
1252
|
+
if (triggerCallCount <= 1) {
|
|
1253
|
+
chunks.push(controlChunk);
|
|
1254
|
+
}
|
|
1255
|
+
return new Response(createSSEStream(sseEncode(chunks)), {
|
|
1256
|
+
status: 200,
|
|
1257
|
+
headers: {
|
|
1258
|
+
"content-type": "text/event-stream",
|
|
1259
|
+
"X-Stream-Version": "v1",
|
|
1260
|
+
},
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
throw new Error(`Unexpected fetch URL: ${urlStr}`);
|
|
1264
|
+
});
|
|
1265
|
+
const transport = new chat_js_1.TriggerChatTransport({
|
|
1266
|
+
task: "my-task",
|
|
1267
|
+
accessToken: "token",
|
|
1268
|
+
baseURL: "https://api.test.trigger.dev",
|
|
1269
|
+
});
|
|
1270
|
+
// First message
|
|
1271
|
+
const stream1 = await transport.sendMessages({
|
|
1272
|
+
trigger: "submit-message",
|
|
1273
|
+
chatId: "chat-fail",
|
|
1274
|
+
messageId: undefined,
|
|
1275
|
+
messages: [createUserMessage("Hello")],
|
|
1276
|
+
abortSignal: undefined,
|
|
1277
|
+
});
|
|
1278
|
+
const reader1 = stream1.getReader();
|
|
1279
|
+
while (true) {
|
|
1280
|
+
const { done } = await reader1.read();
|
|
1281
|
+
if (done)
|
|
1282
|
+
break;
|
|
1283
|
+
}
|
|
1284
|
+
(0, vitest_1.expect)(triggerCallCount).toBe(1);
|
|
1285
|
+
// Second message — waitpoint completion will fail, should fall back to new run
|
|
1286
|
+
const stream2 = await transport.sendMessages({
|
|
1287
|
+
trigger: "submit-message",
|
|
1288
|
+
chatId: "chat-fail",
|
|
1289
|
+
messageId: undefined,
|
|
1290
|
+
messages: [createUserMessage("Hello"), createAssistantMessage("Hi!"), createUserMessage("Again")],
|
|
1291
|
+
abortSignal: undefined,
|
|
1292
|
+
});
|
|
1293
|
+
const reader2 = stream2.getReader();
|
|
1294
|
+
while (true) {
|
|
1295
|
+
const { done } = await reader2.read();
|
|
1296
|
+
if (done)
|
|
1297
|
+
break;
|
|
1298
|
+
}
|
|
1299
|
+
// Should have triggered a second run as fallback
|
|
1300
|
+
(0, vitest_1.expect)(triggerCallCount).toBe(2);
|
|
1301
|
+
});
|
|
1302
|
+
});
|
|
1303
|
+
});
|
|
1304
|
+
//# sourceMappingURL=chat.test.js.map
|