forked from anthropics/anthropic-sdk-typescript
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathuploads.ts
81 lines (71 loc) · 3.03 KB
/
uploads.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import type { Readable } from 'node:stream';
import type { RequestOptions } from './core';
import { type BodyInit } from '@anthropic-ai/sdk/_shims/fetch';
import { FormData } from '@anthropic-ai/sdk/_shims/formdata';
import { getMultipartRequestOptions } from '@anthropic-ai/sdk/_shims/getMultipartRequestOptions';
import { isUploadable } from '@anthropic-ai/sdk/_shims/uploadable';
import { toFile } from '@anthropic-ai/sdk/_shims/toFile';
import { fileFromPath } from '@anthropic-ai/sdk/_shims/fileFromPath';
export { toFile, fileFromPath };
type MultipartBody = {
__multipartBody__: Readable | BodyInit;
};
export const isMultipartBody = (body: any): body is MultipartBody =>
typeof body === 'object' && body?.__multipartBody__ != null;
/**
* Returns a multipart/form-data request if any part of the given request body contains a File / Blob value.
* Otherwise returns the request as is.
*/
export const maybeMultipartFormRequestOptions = async <T extends {} = Record<string, unknown>>(
opts: RequestOptions<T>,
): Promise<RequestOptions<T | MultipartBody>> => {
if (!hasUploadableValue(opts.body)) return opts;
const form = await createForm(opts.body);
return getMultipartRequestOptions(form, opts);
};
export const multipartFormRequestOptions = async <T extends {} = Record<string, unknown>>(
opts: RequestOptions<T>,
): Promise<RequestOptions<T | MultipartBody>> => {
const form = await createForm(opts.body);
return getMultipartRequestOptions(form, opts);
};
export const createForm = async <T = Record<string, unknown>>(body: T | undefined): Promise<FormData> => {
const form = new FormData();
await Promise.all(Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value)));
return form;
};
const hasUploadableValue = (value: unknown): boolean => {
if (isUploadable(value)) return true;
if (Array.isArray(value)) return value.some(hasUploadableValue);
if (value && typeof value === 'object') {
for (const k in value) {
if (hasUploadableValue((value as any)[k])) return true;
}
}
return false;
};
const addFormValue = async (form: FormData, key: string, value: unknown): Promise<void> => {
if (value === undefined) return;
if (value == null) {
throw new TypeError(
`Received null for "${key}"; to pass null in FormData, you must use the string 'null'`,
);
}
// TODO: make nested formats configurable
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
form.append(key, value);
} else if (isUploadable(value)) {
const file = await toFile(value);
form.append(key, file);
} else if (Array.isArray(value)) {
await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry)));
} else if (typeof value === 'object') {
await Promise.all(
Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)),
);
} else {
throw new TypeError(
`Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`,
);
}
};