Skip to content

Commit 85b29d5

Browse files
v0.5.2 (#33)
* fix(streaming): polyfill ReadableStream async iterator and text decoding * fix: include README.md, LICENSE and CHANGELOG.md in published package * v0.5.2 --------- Co-authored-by: Stainless Bot <[email protected]>
1 parent 387bd17 commit 85b29d5

File tree

4 files changed

+77
-14
lines changed

4 files changed

+77
-14
lines changed

build

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ rm -rf dist
1414
mkdir dist
1515
# Copy src to dist/src and build from dist/src into dist, so that
1616
# the source map for index.js.map will refer to ./src/index.ts etc
17-
cp -rp src dist
17+
cp -rp src README.md dist
18+
for file in LICENSE CHANGELOG.md; do
19+
if [ -e "${file}" ]; then cp "${file}" dist; fi
20+
done
1821
# this converts the export map paths for the dist directory
1922
# and does a few other minor things
2023
node scripts/make-dist-package-json.cjs > dist/package.json

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@anthropic-ai/sdk",
3-
"version": "0.5.1",
3+
"version": "0.5.2",
44
"description": "Client library for the Anthropic API",
55
"author": "Anthropic <[email protected]>",
66
"types": "dist/index.d.ts",

src/streaming.ts

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import type { Response } from '@anthropic-ai/sdk/_shims/fetch';
2+
23
import { APIResponse, Headers, createResponseHeaders } from './core';
4+
35
import { safeJSON } from '@anthropic-ai/sdk/core';
46
import { APIError } from '@anthropic-ai/sdk/error';
57

8+
type Bytes = string | ArrayBuffer | Uint8Array | Buffer | null | undefined;
9+
610
type ServerSentEvent = {
711
event: string | null;
812
data: string;
@@ -85,19 +89,11 @@ export class Stream<Item> implements AsyncIterable<Item>, APIResponse<Stream<Ite
8589
this.controller.abort();
8690
throw new Error(`Attempted to iterate over a response with no body`);
8791
}
88-
8992
const lineDecoder = new LineDecoder();
9093

91-
// @ts-ignore
92-
for await (const chunk of this.response.body) {
93-
let text;
94-
if (chunk instanceof Buffer) {
95-
text = chunk.toString();
96-
} else if ((chunk as any) instanceof Uint8Array) {
97-
text = Buffer.from(chunk).toString();
98-
} else {
99-
text = chunk;
100-
}
94+
const iter = readableStreamAsyncIterable<Bytes>(this.response.body);
95+
for await (const chunk of iter) {
96+
const text = decodeText(chunk);
10197

10298
for (const line of lineDecoder.decode(text)) {
10399
const sse = this.decoder.decode(line);
@@ -218,3 +214,67 @@ function partition(str: string, delimiter: string): [string, string, string] {
218214

219215
return [str, '', ''];
220216
}
217+
218+
let _textDecoder;
219+
function decodeText(bytes: Bytes): string {
220+
if (bytes == null) return '';
221+
if (typeof bytes === 'string') return bytes;
222+
223+
// Node:
224+
if (typeof Buffer !== 'undefined') {
225+
if (bytes instanceof Buffer) {
226+
return bytes.toString();
227+
}
228+
if (bytes instanceof Uint8Array) {
229+
return Buffer.from(bytes).toString();
230+
}
231+
232+
throw new Error(`Unexpected: received non-Uint8Array (${bytes.constructor.name}) in Node.`);
233+
}
234+
235+
// Browser
236+
if (typeof TextDecoder !== 'undefined') {
237+
if (bytes instanceof Uint8Array || bytes instanceof ArrayBuffer) {
238+
_textDecoder ??= new TextDecoder('utf8');
239+
return _textDecoder.decode(bytes);
240+
}
241+
242+
throw new Error(
243+
`Unexpected: received non-Uint8Array/ArrayBuffer (${
244+
(bytes as any).constructor.name
245+
}) in a web platform.`,
246+
);
247+
}
248+
249+
throw new Error(`Unexpected: neither Buffer nor TextDecoder are available as globals.`);
250+
}
251+
252+
/**
253+
* Most browsers don't yet have async iterable support for ReadableStream,
254+
* and Node has a very different way of reading bytes from its "ReadableStream".
255+
*
256+
* This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1624185965
257+
*
258+
* We make extensive use of "any" here to avoid pulling in either "node" or "dom" types
259+
* to library users' type scopes.
260+
*/
261+
function readableStreamAsyncIterable<T>(stream: any): AsyncIterableIterator<T> {
262+
if (stream[Symbol.asyncIterator]) {
263+
return stream[Symbol.asyncIterator];
264+
}
265+
266+
const reader = stream.getReader();
267+
268+
return {
269+
next() {
270+
return reader.read();
271+
},
272+
async return() {
273+
reader.releaseLock();
274+
return { done: true, value: undefined };
275+
},
276+
[Symbol.asyncIterator]() {
277+
return this;
278+
},
279+
};
280+
}

src/version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const VERSION = '0.5.1';
1+
export const VERSION = '0.5.2';

0 commit comments

Comments
 (0)