@eigenpal/sdk 0.10.56 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/package.json +1 -1
- package/src/client.ts +118 -13
- package/src/generated/client.gen.ts +1 -1
- package/src/generated/index.ts +2 -2
- package/src/generated/sdk.gen.ts +101 -64
- package/src/generated/types.gen.ts +250 -62
- package/src/lib/files.ts +1 -1
- package/src/lib/upload-limits.ts +61 -0
- package/src/resources/files.ts +140 -17
- package/src/resources/runs.ts +1 -1
- package/src/runtime-config.ts +1 -1
- package/src/telemetry.ts +1 -1
package/src/lib/files.ts
CHANGED
|
@@ -132,7 +132,7 @@ function basename(path: string): string {
|
|
|
132
132
|
* so the body can be replayed if the SDK retries the request. (A consumed
|
|
133
133
|
* stream cannot be re-read; a Blob can.)
|
|
134
134
|
*/
|
|
135
|
-
async function resolveFileBlob(file: FileInput): Promise<{ blob: Blob; filename: string }> {
|
|
135
|
+
export async function resolveFileBlob(file: FileInput): Promise<{ blob: Blob; filename: string }> {
|
|
136
136
|
// `File` extends `Blob`, so this branch covers both.
|
|
137
137
|
if (typeof Blob !== 'undefined' && file instanceof Blob) {
|
|
138
138
|
return { blob: file, filename: (file as File).name || 'file' };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default to Vercel's ~4.5 MiB function body limit. Operators with a different
|
|
3
|
+
* ingress limit can override it; `null` keeps every run file on multipart.
|
|
4
|
+
*/
|
|
5
|
+
export const DEFAULT_MULTIPART_MAX_BYTES = Math.floor(4.5 * 1024 * 1024);
|
|
6
|
+
/** @deprecated Use {@link DEFAULT_MULTIPART_MAX_BYTES}. */
|
|
7
|
+
export const DIRECT_UPLOAD_BYTE_THRESHOLD = DEFAULT_MULTIPART_MAX_BYTES;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Conservative allowance for multipart boundaries, `target` / `input` /
|
|
11
|
+
* `overrides` / `metadata` parts, and Content-Disposition headers.
|
|
12
|
+
*/
|
|
13
|
+
export const MULTIPART_ENVELOPE_HEADROOM_BYTES = 256 * 1024;
|
|
14
|
+
|
|
15
|
+
/** Max aggregate file-content bytes that may ride in one multipart run request. */
|
|
16
|
+
export function multipartFileByteBudget(
|
|
17
|
+
multipartMaxBytes: number | null = DEFAULT_MULTIPART_MAX_BYTES
|
|
18
|
+
): number | null {
|
|
19
|
+
if (multipartMaxBytes === null) return null;
|
|
20
|
+
return Math.max(0, multipartMaxBytes - MULTIPART_ENVELOPE_HEADROOM_BYTES);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Choose which file keys must be pre-uploaded so remaining multipart file
|
|
25
|
+
* bytes plus envelope headroom stay under the configured multipart maximum.
|
|
26
|
+
*
|
|
27
|
+
* Prefers shedding the largest files first so smaller ones can stay on the
|
|
28
|
+
* single multipart round-trip when the aggregate still fits.
|
|
29
|
+
*/
|
|
30
|
+
export function keysRequiringPreUpload(
|
|
31
|
+
files: ReadonlyArray<{ key: string; size: number }>,
|
|
32
|
+
multipartMaxBytes: number | null = DEFAULT_MULTIPART_MAX_BYTES
|
|
33
|
+
): Set<string> {
|
|
34
|
+
const budget = multipartFileByteBudget(multipartMaxBytes);
|
|
35
|
+
const toPreUpload = new Set<string>();
|
|
36
|
+
if (budget === null) return toPreUpload;
|
|
37
|
+
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
if (file.size > budget) {
|
|
40
|
+
toPreUpload.add(file.key);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let multipartTotal = 0;
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
if (!toPreUpload.has(file.key)) multipartTotal += file.size;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const candidates = files
|
|
50
|
+
.filter((file) => !toPreUpload.has(file.key))
|
|
51
|
+
.slice()
|
|
52
|
+
.sort((a, b) => b.size - a.size || a.key.localeCompare(b.key));
|
|
53
|
+
|
|
54
|
+
for (const file of candidates) {
|
|
55
|
+
if (multipartTotal <= budget) break;
|
|
56
|
+
toPreUpload.add(file.key);
|
|
57
|
+
multipartTotal -= file.size;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return toPreUpload;
|
|
61
|
+
}
|
package/src/resources/files.ts
CHANGED
|
@@ -1,10 +1,52 @@
|
|
|
1
|
-
import type { OperationResult } from '../client';
|
|
1
|
+
import type { OperationResult, RequestDispatchOptions } from '../client';
|
|
2
2
|
import type { Client } from '../generated/client';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
filesContentGet,
|
|
5
|
+
filesDelete,
|
|
6
|
+
filesGet,
|
|
7
|
+
filesUploadsAbort,
|
|
8
|
+
filesUploadsComplete,
|
|
9
|
+
filesUploadsCreate,
|
|
10
|
+
} from '../generated/sdk.gen';
|
|
4
11
|
|
|
5
|
-
type Dispatch = <T>(
|
|
12
|
+
type Dispatch = <T>(
|
|
13
|
+
call: () => Promise<OperationResult<T>>,
|
|
14
|
+
options?: RequestDispatchOptions
|
|
15
|
+
) => Promise<T>;
|
|
6
16
|
type AnyResponse = any;
|
|
7
17
|
type SignalOptions = { signal?: AbortSignal };
|
|
18
|
+
type UploadOptions = SignalOptions & {
|
|
19
|
+
/** Required only when `file` is a nameless Blob. */
|
|
20
|
+
filename?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Tenant-scoped idempotency key for upload-session creation. Generated when
|
|
23
|
+
* omitted so SDK retries of a lost create response reuse the same reservation.
|
|
24
|
+
*/
|
|
25
|
+
idempotencyKey?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Optional lifecycle marker. Pass `run-input` for automatic `client.run`
|
|
28
|
+
* pre-uploads so the server can retain them for retries and reap them after 24 hours.
|
|
29
|
+
* Explicit `files.upload` callers should omit this (durable reusable file).
|
|
30
|
+
*/
|
|
31
|
+
purpose?: 'run-input';
|
|
32
|
+
onProgress?: (uploadedBytes: number, totalBytes: number) => void;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type CreateUploadInput = {
|
|
36
|
+
filename: string;
|
|
37
|
+
contentType: string;
|
|
38
|
+
size: number;
|
|
39
|
+
/** Compatible with server `CreateFileUploadSessionRequest.idempotencyKey`. */
|
|
40
|
+
idempotencyKey?: string;
|
|
41
|
+
purpose?: 'run-input';
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function newIdempotencyKey(): string {
|
|
45
|
+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
46
|
+
return crypto.randomUUID();
|
|
47
|
+
}
|
|
48
|
+
return `upload_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
49
|
+
}
|
|
8
50
|
|
|
9
51
|
export class FilesResource {
|
|
10
52
|
constructor(
|
|
@@ -12,15 +54,92 @@ export class FilesResource {
|
|
|
12
54
|
private readonly dispatch: Dispatch
|
|
13
55
|
) {}
|
|
14
56
|
|
|
15
|
-
async upload(file: Blob | File, options:
|
|
57
|
+
async upload(file: Blob | File, options: UploadOptions = {}): Promise<AnyResponse> {
|
|
58
|
+
const filename =
|
|
59
|
+
options.filename ??
|
|
60
|
+
(typeof File !== 'undefined' && file instanceof File ? file.name : undefined);
|
|
61
|
+
if (!filename) throw new Error('filename is required when uploading a Blob');
|
|
62
|
+
const idempotencyKey = options.idempotencyKey ?? newIdempotencyKey();
|
|
63
|
+
const negotiation = await this.createUpload(
|
|
64
|
+
{
|
|
65
|
+
filename,
|
|
66
|
+
contentType: file.type || 'application/octet-stream',
|
|
67
|
+
size: file.size,
|
|
68
|
+
idempotencyKey,
|
|
69
|
+
...(options.purpose ? { purpose: options.purpose } : {}),
|
|
70
|
+
},
|
|
71
|
+
options
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (negotiation.transport === 'presigned-put') {
|
|
75
|
+
const response = await fetch(negotiation.url, {
|
|
76
|
+
method: 'PUT',
|
|
77
|
+
headers: Object.fromEntries(
|
|
78
|
+
Object.entries((negotiation.headers ?? {}) as Record<string, string>).filter(
|
|
79
|
+
([name]) => name.toLowerCase() !== 'content-length'
|
|
80
|
+
)
|
|
81
|
+
),
|
|
82
|
+
body: file,
|
|
83
|
+
signal: options.signal,
|
|
84
|
+
});
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
await this.abortUpload(negotiation.uploadId).catch(() => undefined);
|
|
87
|
+
throw new Error(`Storage upload failed (${response.status}); retry the upload`);
|
|
88
|
+
}
|
|
89
|
+
options.onProgress?.(file.size, file.size);
|
|
90
|
+
return this.completeUpload(negotiation.uploadId, options);
|
|
91
|
+
}
|
|
92
|
+
|
|
16
93
|
const form = new FormData();
|
|
17
|
-
form.append('file', file);
|
|
94
|
+
form.append('file', file, filename);
|
|
95
|
+
if (options.purpose) form.append('purpose', options.purpose);
|
|
96
|
+
const uploaded = await this.dispatch(
|
|
97
|
+
() =>
|
|
98
|
+
this.client.post({
|
|
99
|
+
url: negotiation.url,
|
|
100
|
+
body: form as never,
|
|
101
|
+
bodySerializer: null,
|
|
102
|
+
headers: { 'Content-Type': null },
|
|
103
|
+
signal: options.signal,
|
|
104
|
+
}) as Promise<OperationResult<AnyResponse>>
|
|
105
|
+
);
|
|
106
|
+
options.onProgress?.(file.size, file.size);
|
|
107
|
+
return uploaded;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async createUpload(input: CreateUploadInput, options: SignalOptions = {}): Promise<AnyResponse> {
|
|
111
|
+
const body = {
|
|
112
|
+
filename: input.filename,
|
|
113
|
+
contentType: input.contentType,
|
|
114
|
+
size: input.size,
|
|
115
|
+
...(input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}),
|
|
116
|
+
...(input.purpose ? { purpose: input.purpose } : {}),
|
|
117
|
+
};
|
|
118
|
+
return this.dispatch(() =>
|
|
119
|
+
filesUploadsCreate({
|
|
120
|
+
client: this.client,
|
|
121
|
+
// Server accepts optional idempotencyKey; generated types may lag the schema.
|
|
122
|
+
body: body as never,
|
|
123
|
+
signal: options.signal,
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async completeUpload(uploadId: string, options: SignalOptions = {}): Promise<AnyResponse> {
|
|
18
129
|
return this.dispatch(() =>
|
|
19
|
-
|
|
130
|
+
filesUploadsComplete({
|
|
20
131
|
client: this.client,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
132
|
+
path: { uploadId },
|
|
133
|
+
signal: options.signal,
|
|
134
|
+
})
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async abortUpload(uploadId: string, options: SignalOptions = {}): Promise<AnyResponse> {
|
|
139
|
+
return this.dispatch(() =>
|
|
140
|
+
filesUploadsAbort({
|
|
141
|
+
client: this.client,
|
|
142
|
+
path: { uploadId },
|
|
24
143
|
signal: options.signal,
|
|
25
144
|
})
|
|
26
145
|
);
|
|
@@ -33,14 +152,18 @@ export class FilesResource {
|
|
|
33
152
|
}
|
|
34
153
|
|
|
35
154
|
async download(fileId: string, options: SignalOptions = {}): Promise<Blob> {
|
|
36
|
-
return this.dispatch(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
155
|
+
return this.dispatch(
|
|
156
|
+
async () => {
|
|
157
|
+
const response = await filesContentGet({
|
|
158
|
+
client: this.client,
|
|
159
|
+
path: { id: fileId },
|
|
160
|
+
parseAs: 'blob',
|
|
161
|
+
signal: options.signal,
|
|
162
|
+
});
|
|
163
|
+
return response as OperationResult<Blob>;
|
|
164
|
+
},
|
|
165
|
+
{ responseKind: 'binary' }
|
|
166
|
+
);
|
|
44
167
|
}
|
|
45
168
|
|
|
46
169
|
async delete(fileId: string, options: SignalOptions = {}): Promise<AnyResponse> {
|
package/src/resources/runs.ts
CHANGED
|
@@ -250,7 +250,7 @@ export class RunsReviewsResource {
|
|
|
250
250
|
return this.dispatch(
|
|
251
251
|
() =>
|
|
252
252
|
this.client.post({
|
|
253
|
-
url: '/
|
|
253
|
+
url: '/v1/runs/{id}/reviews/expected',
|
|
254
254
|
path: { id: runId },
|
|
255
255
|
body: formData,
|
|
256
256
|
bodySerializer: null,
|
package/src/runtime-config.ts
CHANGED
|
@@ -11,5 +11,5 @@ import type { CreateClientConfig } from './generated/client.gen';
|
|
|
11
11
|
*/
|
|
12
12
|
export const createClientConfig: CreateClientConfig = (config) => ({
|
|
13
13
|
...config,
|
|
14
|
-
baseUrl: config?.baseUrl ?? 'https://
|
|
14
|
+
baseUrl: config?.baseUrl ?? 'https://api.eigenpal.com',
|
|
15
15
|
});
|
package/src/telemetry.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
export const SDK_LANGUAGE = 'typescript';
|
|
20
20
|
// Rewritten at publish time by scripts/release/platform/render-sdk-versions.sh.
|
|
21
21
|
// Keep this string literal exactly stable — sed matches on it.
|
|
22
|
-
export const SDK_VERSION = '0.
|
|
22
|
+
export const SDK_VERSION = '0.11.1';
|
|
23
23
|
|
|
24
24
|
function detectRuntime(): string {
|
|
25
25
|
const g = globalThis as unknown as {
|