@gradio/client 2.3.1 → 2.5.0
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 +21 -0
- package/README.md +4 -2
- package/dist/__vite-browser-external-Cgmn0awE.js +7 -0
- package/dist/browser.js +1949 -1963
- package/dist/client.d.ts +5 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/helpers/api_info.d.ts.map +1 -1
- package/dist/helpers/data.d.ts.map +1 -1
- package/dist/helpers/init_helpers.d.ts +11 -2
- package/dist/helpers/init_helpers.d.ts.map +1 -1
- package/dist/index.cjs +2584 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2471 -2466
- package/dist/index.min.js +1962 -0
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/duplicate.d.ts.map +1 -1
- package/dist/utils/predict.d.ts.map +1 -1
- package/dist/utils/run_history.d.ts +64 -0
- package/dist/utils/run_history.d.ts.map +1 -0
- package/dist/utils/stream.d.ts.map +1 -1
- package/dist/utils/submit.d.ts.map +1 -1
- package/package.json +11 -4
- package/scripts/verify_dist.mjs +46 -0
- package/src/client.ts +54 -5
- package/src/constants.ts +10 -0
- package/src/helpers/api_info.ts +46 -19
- package/src/helpers/data.ts +8 -3
- package/src/helpers/init_helpers.ts +54 -7
- package/src/index.ts +12 -0
- package/src/test/api_info.test.ts +77 -12
- package/src/test/data.test.ts +26 -3
- package/src/test/init.test.ts +21 -3
- package/src/test/init_helpers.test.ts +67 -1
- package/src/test/refresh.test.ts +31 -0
- package/src/test/run_history.test.ts +345 -0
- package/src/test/submit.test.ts +1 -3
- package/src/types.ts +29 -0
- package/src/utils/duplicate.ts +2 -0
- package/src/utils/predict.ts +14 -1
- package/src/utils/run_history.ts +520 -0
- package/src/utils/stream.ts +17 -4
- package/src/utils/submit.ts +105 -12
- package/vite.config.ts +20 -4
- package/dist/__vite-browser-external-DYxpcVy9.js +0 -4
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
clear_run_history,
|
|
5
|
+
consume_run_history_replay,
|
|
6
|
+
delete_run_history,
|
|
7
|
+
on_run_history_change,
|
|
8
|
+
read_run_history,
|
|
9
|
+
run_history_url,
|
|
10
|
+
stage_run_history_replay,
|
|
11
|
+
start_run_history,
|
|
12
|
+
update_run_history,
|
|
13
|
+
update_run_inputs
|
|
14
|
+
} from "../utils/run_history";
|
|
15
|
+
|
|
16
|
+
const app_id = "app-under-test";
|
|
17
|
+
const other_app = "some-other-app";
|
|
18
|
+
const scope = { app_id };
|
|
19
|
+
const other_scope = { app_id: other_app };
|
|
20
|
+
const replacement_apps = Array.from(
|
|
21
|
+
{ length: 8 },
|
|
22
|
+
(_, index) => `replacement-app-${index}`
|
|
23
|
+
);
|
|
24
|
+
const in_browser = typeof window !== "undefined";
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
clear_run_history(scope);
|
|
28
|
+
clear_run_history(other_scope);
|
|
29
|
+
for (const replacement_app of replacement_apps) {
|
|
30
|
+
clear_run_history({ app_id: replacement_app });
|
|
31
|
+
}
|
|
32
|
+
vi.useRealTimers();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe.skipIf(!in_browser)("run history", () => {
|
|
36
|
+
test("stores runs per app id with the page and inputs", () => {
|
|
37
|
+
const id = start_run_history({
|
|
38
|
+
app_id,
|
|
39
|
+
endpoint: "/predict",
|
|
40
|
+
api_name: "/predict",
|
|
41
|
+
fn_index: 0,
|
|
42
|
+
inputs: ["hello"],
|
|
43
|
+
input_components: [
|
|
44
|
+
{
|
|
45
|
+
type: "textbox",
|
|
46
|
+
component_class_id: "textbox-id",
|
|
47
|
+
props: { label: "Prompt" }
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const runs = read_run_history(scope);
|
|
53
|
+
expect(runs).toHaveLength(1);
|
|
54
|
+
expect(runs[0]).toMatchObject({
|
|
55
|
+
id,
|
|
56
|
+
endpoint: "/predict",
|
|
57
|
+
api_name: "/predict",
|
|
58
|
+
inputs: ["hello"],
|
|
59
|
+
status: "running",
|
|
60
|
+
input_components: [
|
|
61
|
+
{
|
|
62
|
+
type: "textbox",
|
|
63
|
+
component_class_id: "textbox-id",
|
|
64
|
+
props: { label: "Prompt" }
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
page: `${window.location.pathname}${window.location.search}`
|
|
68
|
+
});
|
|
69
|
+
expect(read_run_history(other_scope)).toEqual([]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("replaces inputs with their upload-processed values", () => {
|
|
73
|
+
const id = start_run_history({
|
|
74
|
+
app_id,
|
|
75
|
+
endpoint: "/predict",
|
|
76
|
+
api_name: "/predict",
|
|
77
|
+
fn_index: 0,
|
|
78
|
+
inputs: [new File(["hello"], "hello.txt")]
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
update_run_inputs(scope, id, [
|
|
82
|
+
{ path: "/tmp/hello.txt", url: "/gradio_api/file=/tmp/hello.txt" }
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
expect(read_run_history(scope)[0].inputs).toEqual([
|
|
86
|
+
{ path: "/tmp/hello.txt", url: "/gradio_api/file=/tmp/hello.txt" }
|
|
87
|
+
]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("updates outputs and completion state", () => {
|
|
91
|
+
const id = start_run_history({
|
|
92
|
+
app_id,
|
|
93
|
+
endpoint: "/predict",
|
|
94
|
+
api_name: "/predict",
|
|
95
|
+
fn_index: 0,
|
|
96
|
+
inputs: ["hello"]
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
update_run_history(scope, id, {
|
|
100
|
+
type: "data",
|
|
101
|
+
endpoint: "/predict",
|
|
102
|
+
fn_index: 0,
|
|
103
|
+
data: ["hello hello"]
|
|
104
|
+
});
|
|
105
|
+
update_run_history(scope, id, {
|
|
106
|
+
type: "status",
|
|
107
|
+
endpoint: "/predict",
|
|
108
|
+
fn_index: 0,
|
|
109
|
+
queue: false,
|
|
110
|
+
stage: "complete",
|
|
111
|
+
time: new Date("2025-05-16T21:45:00Z")
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(read_run_history(scope)[0]).toMatchObject({
|
|
115
|
+
outputs: ["hello hello"],
|
|
116
|
+
status: "completed",
|
|
117
|
+
completed_at: "2025-05-16T21:45:00.000Z"
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("records the runtime reported by the server", () => {
|
|
122
|
+
const id = start_run_history({
|
|
123
|
+
app_id,
|
|
124
|
+
endpoint: "/predict",
|
|
125
|
+
api_name: "/predict",
|
|
126
|
+
fn_index: 0,
|
|
127
|
+
inputs: ["hello"]
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
update_run_history(scope, id, {
|
|
131
|
+
type: "status",
|
|
132
|
+
endpoint: "/predict",
|
|
133
|
+
fn_index: 0,
|
|
134
|
+
queue: true,
|
|
135
|
+
stage: "complete",
|
|
136
|
+
cache_duration: 1.25,
|
|
137
|
+
time: new Date("2025-05-16T21:45:00Z")
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(read_run_history(scope)[0]).toMatchObject({
|
|
141
|
+
duration_ms: 1250,
|
|
142
|
+
completed_at: "2025-05-16T21:45:00.000Z"
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("records failed runs and their error message", () => {
|
|
147
|
+
const id = start_run_history({
|
|
148
|
+
app_id,
|
|
149
|
+
endpoint: 3,
|
|
150
|
+
api_name: "Function 3",
|
|
151
|
+
fn_index: 3,
|
|
152
|
+
inputs: [{ circular: null }]
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
update_run_history(scope, id, {
|
|
156
|
+
type: "status",
|
|
157
|
+
endpoint: "/predict",
|
|
158
|
+
fn_index: 3,
|
|
159
|
+
queue: true,
|
|
160
|
+
stage: "error",
|
|
161
|
+
message: "generation failed"
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(read_run_history(scope)[0]).toMatchObject({
|
|
165
|
+
status: "failed",
|
|
166
|
+
error: "generation failed"
|
|
167
|
+
});
|
|
168
|
+
expect(read_run_history(scope)[0].duration_ms).toBeGreaterThanOrEqual(0);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("keeps only the newest 100 runs", () => {
|
|
172
|
+
for (let index = 0; index < 105; index++) {
|
|
173
|
+
start_run_history({
|
|
174
|
+
app_id,
|
|
175
|
+
endpoint: "/predict",
|
|
176
|
+
api_name: "/predict",
|
|
177
|
+
fn_index: 0,
|
|
178
|
+
inputs: [index]
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const runs = read_run_history(scope);
|
|
183
|
+
expect(runs).toHaveLength(100);
|
|
184
|
+
expect(runs[0].inputs).toEqual([104]);
|
|
185
|
+
expect(runs.at(-1)?.inputs).toEqual([5]);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("deletes an individual run", () => {
|
|
189
|
+
const first = start_run_history({
|
|
190
|
+
app_id,
|
|
191
|
+
endpoint: "/predict",
|
|
192
|
+
api_name: "/predict",
|
|
193
|
+
fn_index: 0,
|
|
194
|
+
inputs: ["first"]
|
|
195
|
+
});
|
|
196
|
+
start_run_history({
|
|
197
|
+
app_id,
|
|
198
|
+
endpoint: "/predict",
|
|
199
|
+
api_name: "/predict",
|
|
200
|
+
fn_index: 0,
|
|
201
|
+
inputs: ["second"]
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
delete_run_history(scope, first!);
|
|
205
|
+
|
|
206
|
+
expect(read_run_history(scope)).toHaveLength(1);
|
|
207
|
+
expect(read_run_history(scope)[0].inputs).toEqual(["second"]);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("notifies this tab when runs are added, deleted or cleared", () => {
|
|
211
|
+
let notified = 0;
|
|
212
|
+
const unsubscribe = on_run_history_change(() => notified++);
|
|
213
|
+
|
|
214
|
+
const id = start_run_history({
|
|
215
|
+
app_id,
|
|
216
|
+
endpoint: "/predict",
|
|
217
|
+
api_name: "/predict",
|
|
218
|
+
fn_index: 0,
|
|
219
|
+
inputs: ["hello"]
|
|
220
|
+
});
|
|
221
|
+
expect(notified).toBe(1);
|
|
222
|
+
|
|
223
|
+
// Progress updates are not structural, so they must not notify.
|
|
224
|
+
update_run_history(scope, id, {
|
|
225
|
+
type: "status",
|
|
226
|
+
endpoint: "/predict",
|
|
227
|
+
fn_index: 0,
|
|
228
|
+
queue: true,
|
|
229
|
+
stage: "complete"
|
|
230
|
+
});
|
|
231
|
+
expect(notified).toBe(1);
|
|
232
|
+
|
|
233
|
+
delete_run_history(scope, id!);
|
|
234
|
+
expect(notified).toBe(2);
|
|
235
|
+
|
|
236
|
+
clear_run_history(scope);
|
|
237
|
+
expect(notified).toBe(3);
|
|
238
|
+
|
|
239
|
+
unsubscribe();
|
|
240
|
+
start_run_history({
|
|
241
|
+
app_id,
|
|
242
|
+
endpoint: "/predict",
|
|
243
|
+
api_name: "/predict",
|
|
244
|
+
fn_index: 0,
|
|
245
|
+
inputs: ["bye"]
|
|
246
|
+
});
|
|
247
|
+
expect(notified).toBe(3);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("keeps a separate history per app id", () => {
|
|
251
|
+
start_run_history({
|
|
252
|
+
app_id,
|
|
253
|
+
endpoint: "/predict",
|
|
254
|
+
api_name: "/predict",
|
|
255
|
+
fn_index: 0,
|
|
256
|
+
inputs: ["mine"]
|
|
257
|
+
});
|
|
258
|
+
start_run_history({
|
|
259
|
+
app_id: other_app,
|
|
260
|
+
endpoint: "/predict",
|
|
261
|
+
api_name: "/predict",
|
|
262
|
+
fn_index: 0,
|
|
263
|
+
inputs: ["theirs"]
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect(read_run_history(scope)[0].inputs).toEqual(["mine"]);
|
|
267
|
+
expect(read_run_history(other_scope)[0].inputs).toEqual(["theirs"]);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("keeps a separate history per user of the same app", () => {
|
|
271
|
+
const ada = { app_id, username: "ada" };
|
|
272
|
+
const grace = { app_id, username: "grace" };
|
|
273
|
+
|
|
274
|
+
start_run_history({
|
|
275
|
+
...ada,
|
|
276
|
+
endpoint: "/predict",
|
|
277
|
+
api_name: "/predict",
|
|
278
|
+
fn_index: 0,
|
|
279
|
+
inputs: ["ada's secret"]
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(read_run_history(grace)).toEqual([]);
|
|
283
|
+
expect(read_run_history(scope)).toEqual([]);
|
|
284
|
+
expect(read_run_history(ada)[0].inputs).toEqual(["ada's secret"]);
|
|
285
|
+
|
|
286
|
+
clear_run_history(ada);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("removes staged replays when pruning an old app", () => {
|
|
290
|
+
vi.useFakeTimers();
|
|
291
|
+
vi.setSystemTime(new Date("2025-01-01T00:00:00Z"));
|
|
292
|
+
start_run_history({
|
|
293
|
+
app_id: other_app,
|
|
294
|
+
endpoint: "/predict",
|
|
295
|
+
api_name: "/predict",
|
|
296
|
+
fn_index: 0,
|
|
297
|
+
inputs: ["old"]
|
|
298
|
+
});
|
|
299
|
+
stage_run_history_replay(other_scope, read_run_history(other_scope)[0]);
|
|
300
|
+
|
|
301
|
+
for (const [index, replacement_app] of replacement_apps.entries()) {
|
|
302
|
+
vi.setSystemTime(new Date(Date.UTC(2025, 0, index + 2)));
|
|
303
|
+
start_run_history({
|
|
304
|
+
app_id: replacement_app,
|
|
305
|
+
endpoint: "/predict",
|
|
306
|
+
api_name: "/predict",
|
|
307
|
+
fn_index: 0,
|
|
308
|
+
inputs: [index]
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
expect(read_run_history(other_scope)).toEqual([]);
|
|
313
|
+
expect(consume_run_history_replay(other_scope)).toBeNull();
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe("run history url", () => {
|
|
318
|
+
test("joins the app root and the api prefix", () => {
|
|
319
|
+
expect(run_history_url("http://localhost:7860")).toBe(
|
|
320
|
+
"http://localhost:7860/gradio_api/runs"
|
|
321
|
+
);
|
|
322
|
+
expect(run_history_url("http://localhost:7860/my-app/")).toBe(
|
|
323
|
+
"http://localhost:7860/my-app/gradio_api/runs"
|
|
324
|
+
);
|
|
325
|
+
expect(run_history_url("http://localhost:7860", "/api")).toBe(
|
|
326
|
+
"http://localhost:7860/api/runs"
|
|
327
|
+
);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
describe.skipIf(in_browser)("run history outside the browser", () => {
|
|
332
|
+
test("is a no-op without storage", () => {
|
|
333
|
+
expect(
|
|
334
|
+
start_run_history({
|
|
335
|
+
app_id,
|
|
336
|
+
endpoint: "/predict",
|
|
337
|
+
api_name: "/predict",
|
|
338
|
+
fn_index: 0,
|
|
339
|
+
inputs: ["hello"]
|
|
340
|
+
})
|
|
341
|
+
).toBeNull();
|
|
342
|
+
expect(read_run_history(scope)).toEqual([]);
|
|
343
|
+
expect(consume_run_history_replay(scope)).toBeNull();
|
|
344
|
+
});
|
|
345
|
+
});
|
package/src/test/submit.test.ts
CHANGED
|
@@ -162,8 +162,6 @@ describe("predict error handling", () => {
|
|
|
162
162
|
1000,
|
|
163
163
|
"predict() never settled for an unknown endpoint"
|
|
164
164
|
)
|
|
165
|
-
).rejects.toThrow(
|
|
166
|
-
"There is no endpoint matching that name of fn_index matching that number."
|
|
167
|
-
);
|
|
165
|
+
).rejects.toThrow('No endpoint matching "nonexistent_endpoint" was found');
|
|
168
166
|
});
|
|
169
167
|
});
|
package/src/types.ts
CHANGED
|
@@ -36,6 +36,8 @@ export interface EndpointInfo<T extends ApiData | JsApiData> {
|
|
|
36
36
|
parameters: T[];
|
|
37
37
|
returns: T[];
|
|
38
38
|
type?: DependencyTypes;
|
|
39
|
+
/** Set when the endpoint's function takes a `gr.OAuthToken`. */
|
|
40
|
+
oauth_token?: "required" | "optional";
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
export interface ApiInfo<T extends ApiData | JsApiData> {
|
|
@@ -209,6 +211,12 @@ export interface Config {
|
|
|
209
211
|
pwa?: boolean;
|
|
210
212
|
i18n_translations?: Record<string, Record<string, string>> | null;
|
|
211
213
|
mcp_server?: boolean;
|
|
214
|
+
/**
|
|
215
|
+
* Whether the app permits its runs being saved in the browser. Set from
|
|
216
|
+
* `run_history` on `launch()`; absent on apps served by older versions of
|
|
217
|
+
* Gradio, which is treated as permitted.
|
|
218
|
+
*/
|
|
219
|
+
run_history?: boolean;
|
|
212
220
|
}
|
|
213
221
|
|
|
214
222
|
// todo: DRY up types
|
|
@@ -301,6 +309,7 @@ export interface Payload {
|
|
|
301
309
|
time?: Date;
|
|
302
310
|
event_data?: unknown;
|
|
303
311
|
trigger_id?: number | null;
|
|
312
|
+
oauth_token?: string;
|
|
304
313
|
}
|
|
305
314
|
|
|
306
315
|
export interface PostResponse {
|
|
@@ -323,6 +332,11 @@ export interface DuplicateOptions extends ClientOptions {
|
|
|
323
332
|
|
|
324
333
|
export interface ClientOptions {
|
|
325
334
|
token?: `hf_${string}`;
|
|
335
|
+
/**
|
|
336
|
+
* @deprecated Use `token` instead. Kept as an alias so that code written
|
|
337
|
+
* for older versions of the client keeps working.
|
|
338
|
+
*/
|
|
339
|
+
hf_token?: `hf_${string}`;
|
|
326
340
|
status_callback?: SpaceStatusCallback | null;
|
|
327
341
|
auth?: [string, string] | null;
|
|
328
342
|
with_null_state?: boolean;
|
|
@@ -332,6 +346,21 @@ export interface ClientOptions {
|
|
|
332
346
|
session_hash?: string;
|
|
333
347
|
cookies?: string;
|
|
334
348
|
credentials?: RequestCredentials;
|
|
349
|
+
/**
|
|
350
|
+
* A Hugging Face token passed to the app's own code, for endpoints whose
|
|
351
|
+
* function takes a `gr.OAuthToken`. Unlike `token`, which only authenticates
|
|
352
|
+
* you to the app, this lets the app act on your behalf, so it is sent only to
|
|
353
|
+
* endpoints that declare they need it.
|
|
354
|
+
*/
|
|
355
|
+
oauth_token?: string;
|
|
356
|
+
/**
|
|
357
|
+
* Whether to save each call's inputs and outputs in this browser's local
|
|
358
|
+
* storage, so they can be reviewed on the app's run history page. Defaults
|
|
359
|
+
* to true, and only ever applies in a browser: in Node there is no storage
|
|
360
|
+
* to write to and nothing is recorded. The app can turn it off for everyone
|
|
361
|
+
* with `run_history=False` on `launch()`, which takes precedence over this.
|
|
362
|
+
*/
|
|
363
|
+
record_history?: boolean;
|
|
335
364
|
}
|
|
336
365
|
|
|
337
366
|
export interface FileData {
|
package/src/utils/duplicate.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { Client } from "../client";
|
|
|
8
8
|
import { SPACE_METADATA_ERROR_MSG } from "../constants";
|
|
9
9
|
import {
|
|
10
10
|
get_cookie_header,
|
|
11
|
+
normalise_token_option,
|
|
11
12
|
parse_and_set_cookies
|
|
12
13
|
} from "../helpers/init_helpers";
|
|
13
14
|
import { process_endpoint } from "../helpers/api_info";
|
|
@@ -16,6 +17,7 @@ export async function duplicate(
|
|
|
16
17
|
app_reference: string,
|
|
17
18
|
options: DuplicateOptions
|
|
18
19
|
): Promise<Client> {
|
|
20
|
+
normalise_token_option(options);
|
|
19
21
|
const { token, private: _private, hardware, timeout, auth } = options;
|
|
20
22
|
|
|
21
23
|
if (hardware && !hardware_types.includes(hardware)) {
|
package/src/utils/predict.ts
CHANGED
|
@@ -37,7 +37,20 @@ export async function predict<T = unknown>(
|
|
|
37
37
|
|
|
38
38
|
if (message.type === "status") {
|
|
39
39
|
if (message.stage === "error") {
|
|
40
|
-
|
|
40
|
+
// Throw a real `Error` (rather than the raw status object) so that
|
|
41
|
+
// uncaught failures surface a readable message instead of
|
|
42
|
+
// crashing Node with `ERR_UNHANDLED_REJECTION ... reason "#<Object>"`.
|
|
43
|
+
// The status fields are preserved on the error for callers that
|
|
44
|
+
// inspect them.
|
|
45
|
+
const { message: error_message, ...status } = message;
|
|
46
|
+
const error = new Error(
|
|
47
|
+
(typeof error_message === "string"
|
|
48
|
+
? error_message
|
|
49
|
+
: error_message && JSON.stringify(error_message)) ||
|
|
50
|
+
"An unknown error occurred while making a prediction."
|
|
51
|
+
);
|
|
52
|
+
Object.assign(error, status);
|
|
53
|
+
throw error;
|
|
41
54
|
}
|
|
42
55
|
if (message.stage === "complete") {
|
|
43
56
|
status_complete = true;
|