Skip to content

Commit 213f241

Browse files
lgrammelsshader
andauthoredMay 30, 2024··
fix (core,streams): support ResponseInit variants (#1766)
Co-authored-by: Sarah Shader <sarahshader@gmail.com>
1 parent 49f5a92 commit 213f241

File tree

6 files changed

+135
-67
lines changed

6 files changed

+135
-67
lines changed
 

‎.changeset/eighty-kings-attend.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'ai': patch
3+
---
4+
5+
fix (core,streams): support ResponseInit variants

‎packages/core/core/generate-text/stream-text.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
AsyncIterableStream,
2323
createAsyncIterableStream,
2424
} from '../util/async-iterable-stream';
25+
import { prepareResponseHeaders } from '../util/prepare-response-headers';
2526
import { retryWithExponentialBackoff } from '../util/retry-with-exponential-backoff';
2627
import { runToolsTransformation } from './run-tools-transformation';
2728
import { TokenUsage } from './token-usage';
@@ -600,10 +601,9 @@ Non-text-delta events are ignored.
600601
toTextStreamResponse(init?: ResponseInit): Response {
601602
return new Response(this.textStream.pipeThrough(new TextEncoderStream()), {
602603
status: init?.status ?? 200,
603-
headers: {
604-
'Content-Type': 'text/plain; charset=utf-8',
605-
...init?.headers,
606-
},
604+
headers: prepareResponseHeaders(init, {
605+
contentType: 'text/plain; charset=utf-8',
606+
}),
607607
});
608608
}
609609
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { expect, it } from 'vitest';
2+
import { prepareResponseHeaders } from './prepare-response-headers';
3+
4+
it('should set Content-Type header if not present', () => {
5+
const headers = prepareResponseHeaders(
6+
{},
7+
{ contentType: 'application/json' },
8+
);
9+
10+
expect(headers.get('Content-Type')).toBe('application/json');
11+
});
12+
13+
it('should not overwrite existing Content-Type header', () => {
14+
const headers = prepareResponseHeaders(
15+
{ headers: { 'Content-Type': 'text/html' } },
16+
{ contentType: 'application/json' },
17+
);
18+
19+
expect(headers.get('Content-Type')).toBe('text/html');
20+
});
21+
22+
it('should handle undefined init', () => {
23+
const headers = prepareResponseHeaders(undefined, {
24+
contentType: 'application/json',
25+
});
26+
27+
expect(headers.get('Content-Type')).toBe('application/json');
28+
});
29+
30+
it('should handle init headers as Headers object', () => {
31+
const headers = prepareResponseHeaders(
32+
{ headers: new Headers({ init: 'foo' }) },
33+
{ contentType: 'application/json' },
34+
);
35+
36+
expect(headers.get('init')).toBe('foo');
37+
expect(headers.get('Content-Type')).toBe('application/json');
38+
});
39+
40+
it('should handle Response object headers', () => {
41+
const initHeaders = { init: 'foo' };
42+
const response = new Response(null, {
43+
headers: { ...initHeaders, extra: 'bar' },
44+
});
45+
46+
const headers = prepareResponseHeaders(response, {
47+
contentType: 'application/json',
48+
});
49+
50+
expect(headers.get('init')).toBe('foo');
51+
expect(headers.get('extra')).toBe('bar');
52+
expect(headers.get('Content-Type')).toBe('application/json');
53+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export function prepareResponseHeaders(
2+
init: ResponseInit | undefined,
3+
{ contentType }: { contentType: string },
4+
) {
5+
const headers = new Headers(init?.headers ?? {});
6+
7+
if (!headers.has('Content-Type')) {
8+
headers.set('Content-Type', contentType);
9+
}
10+
11+
return headers;
12+
}
+57-59
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,60 @@
1-
import { describe, it, expect } from 'vitest';
1+
import { expect, it } from 'vitest';
22
import { splitArray } from './split-array';
33

4-
describe('splitArray', () => {
5-
it('should split an array into chunks of the specified size', () => {
6-
const array = [1, 2, 3, 4, 5];
7-
const size = 2;
8-
const result = splitArray(array, size);
9-
expect(result).toEqual([[1, 2], [3, 4], [5]]);
10-
});
11-
12-
it('should return an empty array when the input array is empty', () => {
13-
const array: number[] = [];
14-
const size = 2;
15-
const result = splitArray(array, size);
16-
expect(result).toEqual([]);
17-
});
18-
19-
it('should return the original array when the chunk size is greater than the array length', () => {
20-
const array = [1, 2, 3];
21-
const size = 5;
22-
const result = splitArray(array, size);
23-
expect(result).toEqual([[1, 2, 3]]);
24-
});
25-
26-
it('should return the original array when the chunk size is equal to the array length', () => {
27-
const array = [1, 2, 3];
28-
const size = 3;
29-
const result = splitArray(array, size);
30-
expect(result).toEqual([[1, 2, 3]]);
31-
});
32-
33-
it('should handle chunk size of 1 correctly', () => {
34-
const array = [1, 2, 3];
35-
const size = 1;
36-
const result = splitArray(array, size);
37-
expect(result).toEqual([[1], [2], [3]]);
38-
});
39-
40-
it('should throw an error for chunk size of 0', () => {
41-
const array = [1, 2, 3];
42-
const size = 0;
43-
expect(() => splitArray(array, size)).toThrow(
44-
'chunkSize must be greater than 0',
45-
);
46-
});
47-
48-
it('should throw an error for negative chunk size', () => {
49-
const array = [1, 2, 3];
50-
const size = -1;
51-
expect(() => splitArray(array, size)).toThrow(
52-
'chunkSize must be greater than 0',
53-
);
54-
});
55-
56-
it('should handle non-integer chunk size by flooring the size', () => {
57-
const array = [1, 2, 3, 4, 5];
58-
const size = 2.5;
59-
const result = splitArray(array, Math.floor(size));
60-
expect(result).toEqual([[1, 2], [3, 4], [5]]);
61-
});
4+
it('should split an array into chunks of the specified size', () => {
5+
const array = [1, 2, 3, 4, 5];
6+
const size = 2;
7+
const result = splitArray(array, size);
8+
expect(result).toEqual([[1, 2], [3, 4], [5]]);
9+
});
10+
11+
it('should return an empty array when the input array is empty', () => {
12+
const array: number[] = [];
13+
const size = 2;
14+
const result = splitArray(array, size);
15+
expect(result).toEqual([]);
16+
});
17+
18+
it('should return the original array when the chunk size is greater than the array length', () => {
19+
const array = [1, 2, 3];
20+
const size = 5;
21+
const result = splitArray(array, size);
22+
expect(result).toEqual([[1, 2, 3]]);
23+
});
24+
25+
it('should return the original array when the chunk size is equal to the array length', () => {
26+
const array = [1, 2, 3];
27+
const size = 3;
28+
const result = splitArray(array, size);
29+
expect(result).toEqual([[1, 2, 3]]);
30+
});
31+
32+
it('should handle chunk size of 1 correctly', () => {
33+
const array = [1, 2, 3];
34+
const size = 1;
35+
const result = splitArray(array, size);
36+
expect(result).toEqual([[1], [2], [3]]);
37+
});
38+
39+
it('should throw an error for chunk size of 0', () => {
40+
const array = [1, 2, 3];
41+
const size = 0;
42+
expect(() => splitArray(array, size)).toThrow(
43+
'chunkSize must be greater than 0',
44+
);
45+
});
46+
47+
it('should throw an error for negative chunk size', () => {
48+
const array = [1, 2, 3];
49+
const size = -1;
50+
expect(() => splitArray(array, size)).toThrow(
51+
'chunkSize must be greater than 0',
52+
);
53+
});
54+
55+
it('should handle non-integer chunk size by flooring the size', () => {
56+
const array = [1, 2, 3, 4, 5];
57+
const size = 2.5;
58+
const result = splitArray(array, Math.floor(size));
59+
expect(result).toEqual([[1, 2], [3, 4], [5]]);
6260
});

‎packages/core/streams/streaming-text-response.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { mergeStreams } from '../core/util/merge-streams';
2+
import { prepareResponseHeaders } from '../core/util/prepare-response-headers';
23
import { StreamData } from './stream-data';
34

45
/**
@@ -15,10 +16,9 @@ export class StreamingTextResponse extends Response {
1516
super(processedStream as any, {
1617
...init,
1718
status: 200,
18-
headers: {
19-
'Content-Type': 'text/plain; charset=utf-8',
20-
...init?.headers,
21-
},
19+
headers: prepareResponseHeaders(init, {
20+
contentType: 'text/plain; charset=utf-8',
21+
}),
2222
});
2323
}
2424
}

0 commit comments

Comments
 (0)
Please sign in to comment.