Skip to content

Commit 58edbb0

Browse files
committed
💚 Fix VCR module (#133)
1 parent 5e08456 commit 58edbb0

File tree

7 files changed

+229
-83
lines changed

7 files changed

+229
-83
lines changed

‎packages/inference/src/HfInference.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1048,7 +1048,9 @@ export class HfInference {
10481048
throw new Error(`Server response contains error: ${response.status}`);
10491049
}
10501050
if (response.headers.get("content-type") !== "text/event-stream") {
1051-
throw new Error(`Server does not support event stream content type`);
1051+
throw new Error(
1052+
`Server does not support event stream content type, it returned ` + response.headers.get("content-type")
1053+
);
10521054
}
10531055

10541056
const reader = response.body.getReader();
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export function base64FromBytes(arr: Uint8Array): string {
2+
if (globalThis.Buffer) {
3+
return globalThis.Buffer.from(arr).toString("base64");
4+
} else {
5+
const bin: string[] = [];
6+
arr.forEach((byte) => {
7+
bin.push(String.fromCharCode(byte));
8+
});
9+
return globalThis.btoa(bin.join(""));
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { pick } from "./pick";
2+
import { typedInclude } from "./typed-include";
3+
4+
/**
5+
* Return copy of object, omitting blocklisted array of props
6+
*/
7+
export function omit<T, K extends keyof T>(o: T, props: K[] | K): Pick<T, Exclude<keyof T, K>> {
8+
const propsArr = Array.isArray(props) ? props : [props];
9+
const letsKeep = (Object.keys(o) as (keyof T)[]).filter((prop) => !typedInclude(propsArr, prop));
10+
return pick(o, letsKeep);
11+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* Return copy of object, only keeping allowlisted properties.
3+
*
4+
* This doesn't add {p: undefined} anymore, for props not in the o object.
5+
*/
6+
export function pick<T, K extends keyof T>(o: T, props: K[] | ReadonlyArray<K>): Pick<T, K> {
7+
// inspired by stackoverflow.com/questions/25553910/one-liner-to-take-some-properties-from-object-in-es-6
8+
return Object.assign(
9+
{},
10+
...props.map((prop) => {
11+
if (o[prop] !== undefined) {
12+
return { [prop]: o[prop] };
13+
}
14+
})
15+
);
16+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function typedInclude<V, T extends V>(arr: readonly T[], v: V): v is T {
2+
return arr.includes(v as T);
3+
}

‎packages/inference/test/tapes.json

Lines changed: 164 additions & 73 deletions
Large diffs are not rendered by default.

‎packages/inference/test/vcr.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import { base64FromBytes } from "../src/utils/base64FromBytes";
12
import { isBackend, isFrontend } from "../src/utils/env-predicates";
3+
import { omit } from "../src/utils/omit";
24

35
const TAPES_FILE = "./tapes.json";
6+
const BASE64_PREFIX = "data:application/octet-stream;base64,";
47

58
enum MODE {
69
RECORD = "record",
@@ -28,9 +31,7 @@ if (process.env.VCR_MODE) {
2831

2932
const originalFetch = globalThis.fetch;
3033

31-
globalThis.fetch = (...args) => {
32-
return vcr(originalFetch, args[0], args[1]);
33-
};
34+
globalThis.fetch = (...args) => vcr(originalFetch, args[0], args[1]);
3435

3536
/**
3637
* Represents a recorded HTTP request
@@ -51,9 +52,9 @@ interface Tape {
5152
};
5253
}
5354

54-
function tapeToResponse(tape: Tape) {
55+
async function tapeToResponse(tape: Tape) {
5556
return new Response(
56-
Uint8Array.from(atob(tape.response.body), (c) => c.charCodeAt(0)),
57+
tape.response.body?.startsWith(BASE64_PREFIX) ? (await originalFetch(tape.response.body)).body : tape.response.body,
5758
{
5859
status: tape.response.status,
5960
statusText: tape.response.statusText,
@@ -133,21 +134,32 @@ async function vcr(
133134
const response = await originalFetch(input, init);
134135

135136
if (VCR_MODE === MODE.RECORD || VCR_MODE === MODE.CACHE) {
137+
const isText =
138+
response.headers.get("Content-Type")?.includes("json") || response.headers.get("Content-Type")?.includes("text");
136139
const arrayBuffer = await response.arrayBuffer();
137-
const headers: Record<string, string> = {};
138-
response.headers.forEach((value, key) => (headers[key] = value));
139140

140141
const tape: Tape = {
141142
url,
142143
init: {
143-
headers: init.headers,
144+
headers: omit(init.headers as Record<string, string>, "Authorization"),
144145
method: init.method,
145146
},
146147
response: {
147148
// Truncating the body to 30KB to avoid having huge files
148-
body: arrayBuffer.byteLength > 30_000 ? "" : Buffer.from(arrayBuffer).toString("base64"),
149+
body:
150+
arrayBuffer.byteLength > 30_000
151+
? ""
152+
: isText
153+
? new TextDecoder().decode(arrayBuffer)
154+
: BASE64_PREFIX + base64FromBytes(new Uint8Array(arrayBuffer)),
149155
status: response.status,
150156
statusText: response.statusText,
157+
headers: Object.fromEntries(
158+
// Remove varying headers as much as possible
159+
[...response.headers.entries()].filter(
160+
([key]) => key !== "date" && key !== "content-length" && !key.startsWith("x-")
161+
)
162+
),
151163
},
152164
};
153165
tapes[hash] = tape;

0 commit comments

Comments
 (0)