@convilyn/sdk 0.2.0 → 0.4.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 +66 -0
- package/LICENSE +201 -201
- package/README.md +1 -1
- package/dist/{chunk-UJHZKIP6.js → chunk-Z5IPYRO3.js} +206 -23
- package/dist/chunk-Z5IPYRO3.js.map +1 -0
- package/dist/cli.cjs +200 -20
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +204 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +144 -3
- package/dist/index.d.ts +144 -3
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-UJHZKIP6.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { VERSION, AuthError, APIError, guessContentType, Convilyn, JobFailedError, JobTimeoutError, HttpClient, resolveAuth, isGoalEventTerminal, WebSocketError, DEFAULT_BASE_URL, GoalJobFailedError, GoalJobTimeoutError, PlanRequiredError, QuotaExceededError } from './chunk-
|
|
2
|
+
import { VERSION, AuthError, APIError, guessContentType, Convilyn, JobFailedError, JobTimeoutError, HttpClient, resolveAuth, isGoalEventTerminal, WebSocketError, DEFAULT_BASE_URL, GoalJobFailedError, GoalJobTimeoutError, PlanRequiredError, QuotaExceededError } from './chunk-Z5IPYRO3.js';
|
|
3
3
|
import { Command, Option } from 'commander';
|
|
4
4
|
import { statSync, readFileSync, writeFileSync } from 'fs';
|
|
5
5
|
import { basename, parse, join } from 'path';
|
package/dist/index.cjs
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// src/errors.ts
|
|
4
|
+
var CONVILYN_ERROR_BRAND = /* @__PURE__ */ Symbol.for("@convilyn/sdk.ConvilynError");
|
|
4
5
|
var ConvilynError = class extends Error {
|
|
6
|
+
/** @internal brand marker — see {@link isConvilynError} */
|
|
7
|
+
[CONVILYN_ERROR_BRAND] = true;
|
|
5
8
|
constructor(message) {
|
|
6
9
|
super(message);
|
|
7
10
|
this.name = "ConvilynError";
|
|
8
11
|
}
|
|
9
12
|
};
|
|
13
|
+
function isConvilynError(err) {
|
|
14
|
+
return typeof err === "object" && err !== null && err[CONVILYN_ERROR_BRAND] === true;
|
|
15
|
+
}
|
|
10
16
|
var AuthError = class extends ConvilynError {
|
|
11
17
|
constructor(message) {
|
|
12
18
|
super(message);
|
|
@@ -107,14 +113,22 @@ var GoalJobTimeoutError = class extends ConvilynError {
|
|
|
107
113
|
jobSpecId;
|
|
108
114
|
elapsed;
|
|
109
115
|
timeout;
|
|
116
|
+
/**
|
|
117
|
+
* `'total'` — the overall `timeout` budget lapsed; `'idle'` —
|
|
118
|
+
* `idleTimeout` lapsed with no status/progress change (the job may still
|
|
119
|
+
* be healthy on a long phase; `retrieve()` before assuming failure).
|
|
120
|
+
*/
|
|
121
|
+
reason;
|
|
110
122
|
constructor(opts) {
|
|
123
|
+
const reason = opts.reason ?? "total";
|
|
111
124
|
super(
|
|
112
|
-
`GoalJob ${opts.jobSpecId} did not reach a terminal status within ${opts.timeout}s (elapsed ${opts.elapsed.toFixed(1)}s)`
|
|
125
|
+
reason === "idle" ? `GoalJob ${opts.jobSpecId} showed no status/progress change for ${opts.timeout}s (elapsed ${opts.elapsed.toFixed(1)}s total)` : `GoalJob ${opts.jobSpecId} did not reach a terminal status within ${opts.timeout}s (elapsed ${opts.elapsed.toFixed(1)}s)`
|
|
113
126
|
);
|
|
114
127
|
this.name = "GoalJobTimeoutError";
|
|
115
128
|
this.jobSpecId = opts.jobSpecId;
|
|
116
129
|
this.elapsed = opts.elapsed;
|
|
117
130
|
this.timeout = opts.timeout;
|
|
131
|
+
this.reason = reason;
|
|
118
132
|
}
|
|
119
133
|
};
|
|
120
134
|
var WebSocketError = class extends ConvilynError {
|
|
@@ -378,7 +392,7 @@ function resolveAuth(apiKey, options = {}) {
|
|
|
378
392
|
}
|
|
379
393
|
|
|
380
394
|
// src/version.ts
|
|
381
|
-
var VERSION = "0.
|
|
395
|
+
var VERSION = "0.4.0";
|
|
382
396
|
|
|
383
397
|
// src/internal/http.ts
|
|
384
398
|
var DEFAULT_BASE_URL = "https://api.convilyn.corenovus.com";
|
|
@@ -757,6 +771,29 @@ function parseGoalJob(wire) {
|
|
|
757
771
|
completedAt: toDateOrNull(wire.completedAt)
|
|
758
772
|
};
|
|
759
773
|
}
|
|
774
|
+
function parseArtifact(wire) {
|
|
775
|
+
return {
|
|
776
|
+
artifactId: String(wire.artifactId),
|
|
777
|
+
fileName: String(wire.fileName),
|
|
778
|
+
mimeType: String(wire.mimeType),
|
|
779
|
+
sizeBytes: Number(wire.sizeBytes ?? 0),
|
|
780
|
+
downloadUrl: wire.downloadUrl ?? null,
|
|
781
|
+
artifactType: wire.artifactType ?? null,
|
|
782
|
+
platform: wire.platform ?? null,
|
|
783
|
+
metadata: isRecord2(wire.metadata) ? wire.metadata : null,
|
|
784
|
+
isPrimary: Boolean(wire.isPrimary ?? false),
|
|
785
|
+
description: String(wire.description ?? "")
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
function parseArtifactDownload(wire) {
|
|
789
|
+
return {
|
|
790
|
+
downloadUrl: String(wire.downloadUrl),
|
|
791
|
+
fileName: String(wire.fileName),
|
|
792
|
+
sizeBytes: Number(wire.sizeBytes ?? 0),
|
|
793
|
+
mimeType: String(wire.mimeType),
|
|
794
|
+
expiresAt: toDate(wire.expiresAt)
|
|
795
|
+
};
|
|
796
|
+
}
|
|
760
797
|
function parseGoalEvent(wire) {
|
|
761
798
|
return {
|
|
762
799
|
type: wire.type,
|
|
@@ -809,6 +846,27 @@ function parseWorkflowSearchPage(wire) {
|
|
|
809
846
|
cursor: wire.cursor ?? null
|
|
810
847
|
};
|
|
811
848
|
}
|
|
849
|
+
function parseCatalogWorkflow(wire) {
|
|
850
|
+
return {
|
|
851
|
+
workflowId: String(wire.workflowId),
|
|
852
|
+
name: String(wire.name),
|
|
853
|
+
description: String(wire.description ?? ""),
|
|
854
|
+
icon: wire.icon ?? null,
|
|
855
|
+
supportedInputTypes: Array.isArray(wire.supportedInputTypes) ? wire.supportedInputTypes.map(String) : [],
|
|
856
|
+
supportedInputFormats: Array.isArray(wire.supportedInputFormats) ? wire.supportedInputFormats.map(String) : null,
|
|
857
|
+
category: String(wire.category ?? "goal_lane"),
|
|
858
|
+
requiredSlotCount: Number(wire.requiredSlotCount ?? 0),
|
|
859
|
+
subcategory: wire.subcategory ?? null,
|
|
860
|
+
skuGroup: wire.skuGroup ?? null,
|
|
861
|
+
status: String(wire.status ?? "active"),
|
|
862
|
+
supportedLocales: Array.isArray(wire.supportedLocales) ? wire.supportedLocales.map(String) : null,
|
|
863
|
+
maxInputSizeBytes: wire.maxInputSizeBytes ?? null,
|
|
864
|
+
minFileCount: wire.minFileCount ?? null,
|
|
865
|
+
tier: wire.tier ?? null,
|
|
866
|
+
// Tri-state: only an explicit false blocks a Free-tier run; absent stays null.
|
|
867
|
+
freeTierAllowed: wire.freeTierAllowed ?? null
|
|
868
|
+
};
|
|
869
|
+
}
|
|
812
870
|
function parseLikeResponse(wire) {
|
|
813
871
|
return {
|
|
814
872
|
liked: Boolean(wire.liked),
|
|
@@ -894,6 +952,52 @@ var Account = class {
|
|
|
894
952
|
}
|
|
895
953
|
};
|
|
896
954
|
|
|
955
|
+
// src/internal/download.ts
|
|
956
|
+
var MAX_DOWNLOAD_BYTES = 2 * 1024 ** 3;
|
|
957
|
+
async function downloadUrlToPath(http, url, to, maxBytes = MAX_DOWNLOAD_BYTES) {
|
|
958
|
+
const { lstat, open, unlink } = await import('fs/promises');
|
|
959
|
+
const st = await lstat(to).catch(() => null);
|
|
960
|
+
if (st?.isSymbolicLink()) {
|
|
961
|
+
throw new Error(
|
|
962
|
+
`Refusing to write to ${JSON.stringify(to)}: target is a symlink. Remove it or choose a non-symlink destination.`
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
const response = await http.externalGet(url);
|
|
966
|
+
const body = response.body;
|
|
967
|
+
const handle = await open(to, "w");
|
|
968
|
+
let written = 0;
|
|
969
|
+
try {
|
|
970
|
+
if (body == null) {
|
|
971
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
972
|
+
written = bytes.byteLength;
|
|
973
|
+
if (written > maxBytes) {
|
|
974
|
+
throw new Error(`Download exceeds the ${maxBytes}-byte cap`);
|
|
975
|
+
}
|
|
976
|
+
await handle.write(bytes);
|
|
977
|
+
} else {
|
|
978
|
+
const reader = body.getReader();
|
|
979
|
+
for (; ; ) {
|
|
980
|
+
const { done, value } = await reader.read();
|
|
981
|
+
if (done) {
|
|
982
|
+
break;
|
|
983
|
+
}
|
|
984
|
+
written += value.byteLength;
|
|
985
|
+
if (written > maxBytes) {
|
|
986
|
+
await reader.cancel().catch(() => void 0);
|
|
987
|
+
throw new Error(`Download exceeds the ${maxBytes}-byte cap`);
|
|
988
|
+
}
|
|
989
|
+
await handle.write(value);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
} catch (error) {
|
|
993
|
+
await handle.close().catch(() => void 0);
|
|
994
|
+
await unlink(to).catch(() => void 0);
|
|
995
|
+
throw error;
|
|
996
|
+
}
|
|
997
|
+
await handle.close();
|
|
998
|
+
return to;
|
|
999
|
+
}
|
|
1000
|
+
|
|
897
1001
|
// src/resources/convert.ts
|
|
898
1002
|
var DEFAULT_POLL_INTERVAL = 1;
|
|
899
1003
|
var DEFAULT_POLL_TIMEOUT = 300;
|
|
@@ -989,17 +1093,7 @@ var Convert = class {
|
|
|
989
1093
|
/** Download the first result file to `to` (Node) and return that path. */
|
|
990
1094
|
async downloadTo(job, { to }) {
|
|
991
1095
|
const url = await this.downloadUrl(job);
|
|
992
|
-
|
|
993
|
-
const st = await lstat(to).catch(() => null);
|
|
994
|
-
if (st?.isSymbolicLink()) {
|
|
995
|
-
throw new Error(
|
|
996
|
-
`Refusing to write to ${JSON.stringify(to)}: target is a symlink. Remove it or choose a non-symlink destination.`
|
|
997
|
-
);
|
|
998
|
-
}
|
|
999
|
-
const response = await this.#http.externalGet(url);
|
|
1000
|
-
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
1001
|
-
await writeFile(to, bytes);
|
|
1002
|
-
return to;
|
|
1096
|
+
return downloadUrlToPath(this.#http, url, to);
|
|
1003
1097
|
}
|
|
1004
1098
|
// ── private ────────────────────────────────────────────────────
|
|
1005
1099
|
#resolveSource(options) {
|
|
@@ -1154,6 +1248,19 @@ var Files = class {
|
|
|
1154
1248
|
const contentType = options.contentType ?? guessContentType(filename);
|
|
1155
1249
|
return this.#uploadWithBody(bytes, filename, contentType);
|
|
1156
1250
|
}
|
|
1251
|
+
/**
|
|
1252
|
+
* Delete an uploaded file's cloud copy (storage object + metadata record).
|
|
1253
|
+
*
|
|
1254
|
+
* Only the uploader can delete; a missing or unowned `fileId` surfaces as
|
|
1255
|
+
* a 404 `APIError` and a file attached to a still-running job as a 409
|
|
1256
|
+
* (`FILE_IN_USE`). The platform deletes input files automatically ~1 hour
|
|
1257
|
+
* after upload anyway — call this when you want the cloud copy gone
|
|
1258
|
+
* deterministically the moment your workflow is done (e.g. privacy-
|
|
1259
|
+
* sensitive documents on an edge device). Port of Python `files.delete`.
|
|
1260
|
+
*/
|
|
1261
|
+
async delete(fileId) {
|
|
1262
|
+
await this.#http.request("DELETE", `/api/v1/files/${fileId}`);
|
|
1263
|
+
}
|
|
1157
1264
|
async #uploadWithBody(body, filename, contentType) {
|
|
1158
1265
|
const size = body.byteLength;
|
|
1159
1266
|
const presign = await this.#presign(filename, size, contentType);
|
|
@@ -1354,7 +1461,8 @@ var Goals = class {
|
|
|
1354
1461
|
return this.#waitLoop(
|
|
1355
1462
|
jobSpecId,
|
|
1356
1463
|
options.timeout ?? DEFAULT_POLL_TIMEOUT2,
|
|
1357
|
-
options.pollInterval ?? DEFAULT_POLL_INTERVAL2
|
|
1464
|
+
options.pollInterval ?? DEFAULT_POLL_INTERVAL2,
|
|
1465
|
+
options.idleTimeout ?? null
|
|
1358
1466
|
);
|
|
1359
1467
|
}
|
|
1360
1468
|
/** Shortcut: {@link start} then {@link wait}. */
|
|
@@ -1362,7 +1470,8 @@ var Goals = class {
|
|
|
1362
1470
|
const job = await this.start(options);
|
|
1363
1471
|
return this.wait(job.jobSpecId, {
|
|
1364
1472
|
timeout: options.timeout,
|
|
1365
|
-
pollInterval: options.pollInterval
|
|
1473
|
+
pollInterval: options.pollInterval,
|
|
1474
|
+
idleTimeout: options.idleTimeout
|
|
1366
1475
|
});
|
|
1367
1476
|
}
|
|
1368
1477
|
/** Answer a single slot the agent is waiting on (sugar over {@link fillSlots}). */
|
|
@@ -1392,9 +1501,17 @@ var Goals = class {
|
|
|
1392
1501
|
});
|
|
1393
1502
|
return parseGoalJob(await response.json());
|
|
1394
1503
|
}
|
|
1395
|
-
/**
|
|
1504
|
+
/**
|
|
1505
|
+
* Confirm a slots-filled job, queueing it for execution.
|
|
1506
|
+
*
|
|
1507
|
+
* `expectedVersion` is optional — the server re-reads the current version
|
|
1508
|
+
* at submit time regardless, so most callers should simply omit it. The
|
|
1509
|
+
* request always carries a JSON object (`{}` when empty): older backend
|
|
1510
|
+
* builds declare the confirm body as a required parameter and reject a
|
|
1511
|
+
* body-less POST with 422 before the handler runs.
|
|
1512
|
+
*/
|
|
1396
1513
|
async confirm(jobSpecId, options = {}) {
|
|
1397
|
-
const json = options.expectedVersion != null ? { expectedVersion: options.expectedVersion } :
|
|
1514
|
+
const json = options.expectedVersion != null ? { expectedVersion: options.expectedVersion } : {};
|
|
1398
1515
|
await this.#http.request("POST", `/api/v1/jobs/goal/${jobSpecId}/confirm`, { json });
|
|
1399
1516
|
return this.#pollOnce(jobSpecId);
|
|
1400
1517
|
}
|
|
@@ -1412,6 +1529,41 @@ var Goals = class {
|
|
|
1412
1529
|
await this.#http.request("POST", `/api/v1/jobs/goal/${jobSpecId}/retry`, { json: body });
|
|
1413
1530
|
return this.#pollOnce(jobSpecId);
|
|
1414
1531
|
}
|
|
1532
|
+
/**
|
|
1533
|
+
* List a completed job's output artifacts with fresh download URLs.
|
|
1534
|
+
*
|
|
1535
|
+
* Available once the job is terminal-successful (`completed` or `partial`);
|
|
1536
|
+
* any other status surfaces the backend's 400 as an `APIError`. Each
|
|
1537
|
+
* artifact's `downloadUrl` is presigned and valid for one hour — re-call
|
|
1538
|
+
* this method for fresh URLs rather than persisting one.
|
|
1539
|
+
*/
|
|
1540
|
+
async artifacts(jobSpecId) {
|
|
1541
|
+
const response = await this.#http.request("GET", `/api/v1/jobs/goal/${jobSpecId}/artifacts`);
|
|
1542
|
+
const payload = await response.json();
|
|
1543
|
+
const rows = Array.isArray(payload.artifacts) ? payload.artifacts : [];
|
|
1544
|
+
return rows.map((row) => parseArtifact(row));
|
|
1545
|
+
}
|
|
1546
|
+
/**
|
|
1547
|
+
* Mint a fresh presigned download URL for one artifact. Prefer this over
|
|
1548
|
+
* reusing a stale {@link Artifact.downloadUrl} when more than an hour may
|
|
1549
|
+
* have passed since {@link artifacts}.
|
|
1550
|
+
*/
|
|
1551
|
+
async downloadArtifactUrl(jobSpecId, artifactId) {
|
|
1552
|
+
const response = await this.#http.request(
|
|
1553
|
+
"GET",
|
|
1554
|
+
`/api/v1/jobs/goal/${jobSpecId}/artifacts/${artifactId}/download`
|
|
1555
|
+
);
|
|
1556
|
+
return parseArtifactDownload(await response.json());
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Download one artifact to `to` (Node) and return that path. Mints a fresh
|
|
1560
|
+
* presigned URL first, then streams the body to disk (shares the size-cap
|
|
1561
|
+
* and symlink-refusal contract with `convert.downloadTo`).
|
|
1562
|
+
*/
|
|
1563
|
+
async downloadArtifactTo(jobSpecId, artifactId, { to }) {
|
|
1564
|
+
const info = await this.downloadArtifactUrl(jobSpecId, artifactId);
|
|
1565
|
+
return downloadUrlToPath(this.#http, info.downloadUrl, to);
|
|
1566
|
+
}
|
|
1415
1567
|
/**
|
|
1416
1568
|
* Stream goal execution events for a job. Opens a WebSocket, subscribes,
|
|
1417
1569
|
* then yields each {@link GoalEvent} in order; the iterator ends (and the
|
|
@@ -1466,11 +1618,13 @@ var Goals = class {
|
|
|
1466
1618
|
const response = await this.#http.request("GET", `/api/v1/jobs/goal/${jobSpecId}`);
|
|
1467
1619
|
return parseGoalJob(await response.json());
|
|
1468
1620
|
}
|
|
1469
|
-
async #waitLoop(jobSpecId, timeout, initialInterval) {
|
|
1621
|
+
async #waitLoop(jobSpecId, timeout, initialInterval, idleTimeout) {
|
|
1470
1622
|
const start = this.#now() / 1e3;
|
|
1471
1623
|
let interval = initialInterval;
|
|
1472
1624
|
let staleCount = 0;
|
|
1473
1625
|
let lastProgress = -1;
|
|
1626
|
+
let lastStatus = null;
|
|
1627
|
+
let lastChange = start;
|
|
1474
1628
|
for (; ; ) {
|
|
1475
1629
|
const job = await this.#pollOnce(jobSpecId);
|
|
1476
1630
|
if (isGoalJobTerminal(job)) {
|
|
@@ -1479,6 +1633,10 @@ var Goals = class {
|
|
|
1479
1633
|
if (goalJobNeedsInput(job)) {
|
|
1480
1634
|
return job;
|
|
1481
1635
|
}
|
|
1636
|
+
if (job.status !== lastStatus) {
|
|
1637
|
+
lastStatus = job.status;
|
|
1638
|
+
lastChange = this.#now() / 1e3;
|
|
1639
|
+
}
|
|
1482
1640
|
if (job.progress === lastProgress) {
|
|
1483
1641
|
staleCount += 1;
|
|
1484
1642
|
if (staleCount >= STALE_PROGRESS_BACKOFF_AFTER2) {
|
|
@@ -1487,12 +1645,17 @@ var Goals = class {
|
|
|
1487
1645
|
}
|
|
1488
1646
|
} else {
|
|
1489
1647
|
lastProgress = job.progress;
|
|
1648
|
+
lastChange = this.#now() / 1e3;
|
|
1490
1649
|
staleCount = 0;
|
|
1491
1650
|
}
|
|
1492
|
-
const
|
|
1651
|
+
const now = this.#now() / 1e3;
|
|
1652
|
+
const elapsed = now - start;
|
|
1493
1653
|
if (elapsed + interval > timeout) {
|
|
1494
1654
|
throw new GoalJobTimeoutError({ jobSpecId, elapsed, timeout });
|
|
1495
1655
|
}
|
|
1656
|
+
if (idleTimeout != null && now - lastChange + interval > idleTimeout) {
|
|
1657
|
+
throw new GoalJobTimeoutError({ jobSpecId, elapsed, timeout: idleTimeout, reason: "idle" });
|
|
1658
|
+
}
|
|
1496
1659
|
await this.#sleep(interval);
|
|
1497
1660
|
}
|
|
1498
1661
|
}
|
|
@@ -1540,6 +1703,20 @@ var Workflows = class {
|
|
|
1540
1703
|
constructor(http) {
|
|
1541
1704
|
this.#http = http;
|
|
1542
1705
|
}
|
|
1706
|
+
/**
|
|
1707
|
+
* List the platform's built-in AI-workflow catalog.
|
|
1708
|
+
*
|
|
1709
|
+
* Distinct from {@link search}, which browses the user-published community
|
|
1710
|
+
* marketplace — the catalog is the curated set of built-in workflows
|
|
1711
|
+
* runnable directly via `goals.start({ workflowId })`. Anonymous callers
|
|
1712
|
+
* are allowed; deprecated entries are filtered out server-side.
|
|
1713
|
+
*/
|
|
1714
|
+
async catalog() {
|
|
1715
|
+
const response = await this.#http.request("GET", "/api/v1/workflows/catalog");
|
|
1716
|
+
const payload = await response.json();
|
|
1717
|
+
const rows = Array.isArray(payload.workflows) ? payload.workflows : [];
|
|
1718
|
+
return rows.map((row) => parseCatalogWorkflow(row));
|
|
1719
|
+
}
|
|
1543
1720
|
/** Browse the public community listing (one page + a `cursor`). */
|
|
1544
1721
|
async search(options = {}) {
|
|
1545
1722
|
const params = {
|
|
@@ -1610,6 +1787,9 @@ var Convilyn = class {
|
|
|
1610
1787
|
#http;
|
|
1611
1788
|
constructor(options = {}) {
|
|
1612
1789
|
const auth = options.auth ?? resolveAuth(options.apiKey);
|
|
1790
|
+
if (options.timeout !== void 0 && options.timeout <= 0) {
|
|
1791
|
+
throw new TypeError(`timeout must be a positive number of seconds (got ${options.timeout})`);
|
|
1792
|
+
}
|
|
1613
1793
|
this.#http = new HttpClient({
|
|
1614
1794
|
auth,
|
|
1615
1795
|
baseUrl: options.baseUrl,
|
|
@@ -1641,7 +1821,10 @@ function resolveRetryPolicy(maxRetries, retryPolicy) {
|
|
|
1641
1821
|
if (maxRetries == null) {
|
|
1642
1822
|
return new ExponentialBackoffRetry();
|
|
1643
1823
|
}
|
|
1644
|
-
if (maxRetries
|
|
1824
|
+
if (maxRetries < 0) {
|
|
1825
|
+
throw new TypeError(`maxRetries must be >= 0 (got ${maxRetries})`);
|
|
1826
|
+
}
|
|
1827
|
+
if (maxRetries === 0) {
|
|
1645
1828
|
return new NoRetry();
|
|
1646
1829
|
}
|
|
1647
1830
|
return new ExponentialBackoffRetry({ maxAttempts: maxRetries });
|
|
@@ -1671,6 +1854,7 @@ exports.VERSION = VERSION;
|
|
|
1671
1854
|
exports.WebSocketError = WebSocketError;
|
|
1672
1855
|
exports.goalJobNeedsInput = goalJobNeedsInput;
|
|
1673
1856
|
exports.isConvertJobTerminal = isConvertJobTerminal;
|
|
1857
|
+
exports.isConvilynError = isConvilynError;
|
|
1674
1858
|
exports.isGoalEventTerminal = isGoalEventTerminal;
|
|
1675
1859
|
exports.isGoalEventType = isGoalEventType;
|
|
1676
1860
|
exports.isGoalJobTerminal = isGoalJobTerminal;
|