@@ -165,24 +165,69 @@ async function waitForPath(filePath: string, timeoutMs = 60_000): Promise<void>
|
165 | 165 | throw new Error(`timed out waiting for ${filePath}`); |
166 | 166 | } |
167 | 167 | |
168 | | -async function waitForChatFinalText(params: { |
| 168 | +function extractAssistantTexts(messages: unknown[]): string[] { |
| 169 | +const texts: string[] = []; |
| 170 | +for (const entry of messages) { |
| 171 | +if (!entry || typeof entry !== "object") { |
| 172 | +continue; |
| 173 | +} |
| 174 | +if ((entry as { role?: unknown }).role !== "assistant") { |
| 175 | +continue; |
| 176 | +} |
| 177 | +const text = extractFirstTextBlock(entry); |
| 178 | +if (typeof text === "string" && text.trim().length > 0) { |
| 179 | +texts.push(text); |
| 180 | +} |
| 181 | +} |
| 182 | +return texts; |
| 183 | +} |
| 184 | + |
| 185 | +function formatAssistantTextPreview(texts: string[], maxChars = 800): string { |
| 186 | +const combined = texts.join("\n\n").trim(); |
| 187 | +if (!combined) { |
| 188 | +return "<none>"; |
| 189 | +} |
| 190 | +return combined.length > maxChars ? `${combined.slice(0, maxChars)}...` : combined; |
| 191 | +} |
| 192 | + |
| 193 | +async function waitForTrajectoryExportInstructionText(params: { |
| 194 | +client: GatewayClient; |
169 | 195 | events: EventFrame[]; |
| 196 | +expectedText: string; |
170 | 197 | runId: string; |
| 198 | +sessionKey: string; |
171 | 199 | timeoutMs: number; |
172 | 200 | }): Promise<string> { |
173 | 201 | const deadline = Date.now() + params.timeoutMs; |
| 202 | +let lastAssistantTexts: string[] = []; |
174 | 203 | while (Date.now() < deadline) { |
175 | | -const text = params.events |
| 204 | +const eventText = params.events |
176 | 205 | .map((event) => extractChatFinalText(event, params.runId)) |
177 | 206 | .find(Boolean); |
178 | | -if (text) { |
179 | | -return text; |
| 207 | +if (eventText) { |
| 208 | +return eventText; |
| 209 | +} |
| 210 | +const history: { messages?: unknown[] } = await params.client.request( |
| 211 | +"chat.history", |
| 212 | +{ |
| 213 | +sessionKey: params.sessionKey, |
| 214 | +limit: 24, |
| 215 | +}, |
| 216 | +{ timeoutMs: 10_000 }, |
| 217 | +); |
| 218 | +lastAssistantTexts = extractAssistantTexts(history.messages ?? []); |
| 219 | +const historyText = lastAssistantTexts.find((text) => text.includes(params.expectedText)); |
| 220 | +if (historyText) { |
| 221 | +return historyText; |
180 | 222 | } |
181 | 223 | await new Promise((resolve) => { |
182 | | -setTimeout(resolve, 50); |
| 224 | +setTimeout(resolve, 500); |
183 | 225 | }); |
184 | 226 | } |
185 | | -throw new Error(`timed out waiting for chat final for ${params.runId}`); |
| 227 | +throw new Error( |
| 228 | +`timed out waiting for trajectory export instruction text for ${params.runId}; ` + |
| 229 | +`events=${params.events.length}; assistantTexts=${formatAssistantTextPreview(lastAssistantTexts)}`, |
| 230 | +); |
186 | 231 | } |
187 | 232 | |
188 | 233 | function extractChatFinalText(event: EventFrame, runId: string): string | undefined { |
@@ -377,9 +422,12 @@ describeLive("gateway live trajectory export", () => {
|
377 | 422 | const finalText = |
378 | 423 | typeof exportResponse?.message === "object" |
379 | 424 | ? extractFirstTextBlock(exportResponse.message) |
380 | | - : await waitForChatFinalText({ |
| 425 | + : await waitForTrajectoryExportInstructionText({ |
| 426 | + client, |
381 | 427 | events: gatewayEvents, |
| 428 | +expectedText: "Trajectory exports can include", |
382 | 429 | runId: exportRunId, |
| 430 | + sessionKey, |
383 | 431 | timeoutMs: 60_000, |
384 | 432 | }); |
385 | 433 | expect(finalText).toContain("Trajectory exports can include"); |
|