|
1 | 1 | import { describe, it, expect } from 'vitest' |
2 | 2 | import { stopStreamProcessing } from './utils/streaming' |
| 3 | +import { streamText } from './streaming' |
| 4 | + |
| 5 | +function iterableFromArray<T>(arr: T[]): AsyncIterable<T> { |
| 6 | + return { |
| 7 | + [Symbol.asyncIterator]() { |
| 8 | + let i = 0 |
| 9 | + return { |
| 10 | + next: async () => |
| 11 | + i < arr.length |
| 12 | + ? { value: arr[i++], done: false } |
| 13 | + : { value: undefined, done: true }, |
| 14 | + } |
| 15 | + }, |
| 16 | + } |
| 17 | +} |
3 | 18 |
|
4 | 19 | describe('stopStreamProcessing', () => { |
5 | 20 | it('replaces response body with an empty closed stream', async () => { |
@@ -59,3 +74,73 @@ describe('stopStreamProcessing', () => { |
59 | 74 | expect(response.body).toBeInstanceOf(ReadableStream) |
60 | 75 | }) |
61 | 76 | }) |
| 77 | + |
| 78 | +describe('streamText', () => { |
| 79 | + it('emits stream_done when the stream ends', async () => { |
| 80 | + // Simulate a normal completion (not timing out) |
| 81 | + const chunks = [ |
| 82 | + { type: 'response.output_text.delta', delta: "I'm glad you asked!" }, |
| 83 | + { |
| 84 | + type: 'response.output_text.delta', |
| 85 | + delta: ' Here are a few universally nice things that', |
| 86 | + }, |
| 87 | + { |
| 88 | + type: 'response.output_text.delta', |
| 89 | + delta: ' could have happened today:\n\n', |
| 90 | + }, |
| 91 | + { |
| 92 | + type: 'response.output_text.delta', |
| 93 | + delta: '- Someone smiled at a stranger, brightening', |
| 94 | + }, |
| 95 | + { type: 'response.output_text.delta', delta: ' their day\n' }, |
| 96 | + { |
| 97 | + type: 'response.output_text.delta', |
| 98 | + delta: '- A teacher helped a student understand a', |
| 99 | + }, |
| 100 | + { type: 'response.output_text.delta', delta: ' difficult concept\n' }, |
| 101 | + { |
| 102 | + type: 'response.output_text.delta', |
| 103 | + delta: '- A kind person paid for someone’s coffee', |
| 104 | + }, |
| 105 | + { type: 'response.output_text.delta', delta: ' in line\n' }, |
| 106 | + { |
| 107 | + type: 'response.output_text.delta', |
| 108 | + delta: '- A pet reunited with its owner at a local', |
| 109 | + }, |
| 110 | + { type: 'response.output_text.delta', delta: ' animal shelter\n' }, |
| 111 | + { |
| 112 | + type: 'response.output_text.delta', |
| 113 | + delta: '- A friend reached out just to say hello\n\n', |
| 114 | + }, |
| 115 | + { |
| 116 | + type: 'response.output_text.delta', |
| 117 | + delta: 'Would you like to hear some real, uplifting', |
| 118 | + }, |
| 119 | + { type: 'response.output_text.delta', delta: ' news from today?' }, |
| 120 | + { type: 'response.output_text.delta', delta: ' Just let me know!' }, |
| 121 | + { |
| 122 | + type: 'response.tool_call_completed', |
| 123 | + response: { |
| 124 | + /* ...omitted... */ |
| 125 | + }, |
| 126 | + }, |
| 127 | + { |
| 128 | + type: 'response.completed', |
| 129 | + response: { |
| 130 | + /* ...omitted... */ |
| 131 | + }, |
| 132 | + }, |
| 133 | + ] |
| 134 | + const response = streamText(iterableFromArray(chunks)) |
| 135 | + const reader = response.body!.getReader() |
| 136 | + let result = '' |
| 137 | + let done = false |
| 138 | + while (!done) { |
| 139 | + const { value, done: d } = await reader.read() |
| 140 | + if (value) result += new TextDecoder().decode(value) |
| 141 | + done = d |
| 142 | + } |
| 143 | + // Should include t:{...stream_done...} |
| 144 | + expect(result).toMatch(/t:{\"type\":\"stream_done\"}/) |
| 145 | + }) |
| 146 | +}) |
0 commit comments