@itsoltech/basal-client 0.3.0 → 0.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/README.md +129 -1
- package/dist/cjs/batches.cjs +312 -0
- package/dist/cjs/batches.d.cts +214 -0
- package/dist/cjs/errors.cjs +97 -0
- package/dist/cjs/errors.d.cts +59 -0
- package/dist/cjs/index.cjs +131 -55
- package/dist/cjs/index.d.cts +23 -15
- package/dist/cjs/resources.cjs +206 -0
- package/dist/cjs/resources.d.cts +48 -0
- package/dist/esm/batches.d.ts +214 -0
- package/dist/esm/batches.js +300 -0
- package/dist/esm/errors.d.ts +59 -0
- package/dist/esm/errors.js +85 -0
- package/dist/esm/index.d.ts +23 -15
- package/dist/esm/index.js +114 -40
- package/dist/esm/resources.d.ts +48 -0
- package/dist/esm/resources.js +201 -0
- package/package.json +1 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BasalBatchResultError = exports.BasalBatchNotReadyError = exports.BasalBatchTimeoutError = exports.BasalBatchSubmissionError = exports.BasalResponseTooLargeError = exports.BasalProtocolError = exports.BasalTransportError = exports.BasalHTTPError = exports.BasalError = void 0;
|
|
4
|
+
class BasalError extends Error {
|
|
5
|
+
}
|
|
6
|
+
exports.BasalError = BasalError;
|
|
7
|
+
/** Non-2xx response. Server body and Retry-After are available without logging them. */
|
|
8
|
+
class BasalHTTPError extends BasalError {
|
|
9
|
+
statusCode;
|
|
10
|
+
body;
|
|
11
|
+
retryAfter;
|
|
12
|
+
constructor(statusCode, body, retryAfter) {
|
|
13
|
+
super(`Basal HTTP ${statusCode}`);
|
|
14
|
+
this.statusCode = statusCode;
|
|
15
|
+
this.body = body;
|
|
16
|
+
this.retryAfter = retryAfter;
|
|
17
|
+
this.name = "BasalHTTPError";
|
|
18
|
+
// Node's default inspection and JSON.stringify must not print reflected state or credentials.
|
|
19
|
+
Object.defineProperty(this, "body", { enumerable: false });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.BasalHTTPError = BasalHTTPError;
|
|
23
|
+
class BasalTransportError extends BasalError {
|
|
24
|
+
}
|
|
25
|
+
exports.BasalTransportError = BasalTransportError;
|
|
26
|
+
class BasalProtocolError extends BasalError {
|
|
27
|
+
}
|
|
28
|
+
exports.BasalProtocolError = BasalProtocolError;
|
|
29
|
+
class BasalResponseTooLargeError extends BasalError {
|
|
30
|
+
}
|
|
31
|
+
exports.BasalResponseTooLargeError = BasalResponseTooLargeError;
|
|
32
|
+
/** `batches.create({requests})` uploaded the input file, but creating the batch failed (HTTP, transport or abort).
|
|
33
|
+
* The SDK neither retries nor deletes the file. The create may still have succeeded on the server, so resume with
|
|
34
|
+
* the same parameters, `{input_file_id: error.fileId}` and `{idempotencyKey: error.idempotencyKey}`; the original
|
|
35
|
+
* failure is in `cause`.
|
|
36
|
+
*/
|
|
37
|
+
class BasalBatchSubmissionError extends BasalError {
|
|
38
|
+
file;
|
|
39
|
+
idempotencyKey;
|
|
40
|
+
fileId;
|
|
41
|
+
constructor(file, idempotencyKey, options) {
|
|
42
|
+
super(`Basal batch creation failed after uploading input file ${file.id}`, options);
|
|
43
|
+
this.file = file;
|
|
44
|
+
this.idempotencyKey = idempotencyKey;
|
|
45
|
+
this.name = "BasalBatchSubmissionError";
|
|
46
|
+
this.fileId = file.id;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.BasalBatchSubmissionError = BasalBatchSubmissionError;
|
|
50
|
+
/** `batches.wait` reached its deadline. The batch keeps running on the server; nothing is cancelled.
|
|
51
|
+
* `lastBatch` is the last retrieved state (null if no retrieve finished) and is non-enumerable, like `BasalHTTPError.body`.
|
|
52
|
+
*/
|
|
53
|
+
class BasalBatchTimeoutError extends BasalError {
|
|
54
|
+
batchId;
|
|
55
|
+
lastBatch;
|
|
56
|
+
timeoutMs;
|
|
57
|
+
constructor(batchId, lastBatch, timeoutMs, options) {
|
|
58
|
+
super(`Basal batch did not reach a terminal status within ${timeoutMs} ms`, options);
|
|
59
|
+
this.batchId = batchId;
|
|
60
|
+
this.lastBatch = lastBatch;
|
|
61
|
+
this.timeoutMs = timeoutMs;
|
|
62
|
+
this.name = "BasalBatchTimeoutError";
|
|
63
|
+
Object.defineProperty(this, "lastBatch", { enumerable: false });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.BasalBatchTimeoutError = BasalBatchTimeoutError;
|
|
67
|
+
/** `batches.results` was called for a batch that is not `completed`, `failed`, `expired` or `cancelled`.
|
|
68
|
+
* `batch` (non-enumerable) is the retrieved state; `status` is its raw status.
|
|
69
|
+
*/
|
|
70
|
+
class BasalBatchNotReadyError extends BasalError {
|
|
71
|
+
batchId;
|
|
72
|
+
batch;
|
|
73
|
+
status;
|
|
74
|
+
constructor(batchId, batch) {
|
|
75
|
+
super("Basal batch has not reached a terminal status");
|
|
76
|
+
this.batchId = batchId;
|
|
77
|
+
this.batch = batch;
|
|
78
|
+
this.name = "BasalBatchNotReadyError";
|
|
79
|
+
this.status = String(batch.status);
|
|
80
|
+
Object.defineProperty(this, "batch", { enumerable: false });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.BasalBatchNotReadyError = BasalBatchNotReadyError;
|
|
84
|
+
/** A result file line that is not UTF-8, not JSON or not a result envelope. `line` is 1-based; the content is not
|
|
85
|
+
* copied into the error.
|
|
86
|
+
*/
|
|
87
|
+
class BasalBatchResultError extends BasalProtocolError {
|
|
88
|
+
fileId;
|
|
89
|
+
line;
|
|
90
|
+
constructor(fileId, line, reason, options) {
|
|
91
|
+
super(`Batch result file ${fileId} line ${line} ${reason}`, options);
|
|
92
|
+
this.fileId = fileId;
|
|
93
|
+
this.line = line;
|
|
94
|
+
this.name = "BasalBatchResultError";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
exports.BasalBatchResultError = BasalBatchResultError;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Batch, FileObject } from "./batches.cjs";
|
|
2
|
+
export declare class BasalError extends Error {
|
|
3
|
+
}
|
|
4
|
+
/** Non-2xx response. Server body and Retry-After are available without logging them. */
|
|
5
|
+
export declare class BasalHTTPError extends BasalError {
|
|
6
|
+
readonly statusCode: number;
|
|
7
|
+
readonly body: unknown;
|
|
8
|
+
readonly retryAfter: string | null;
|
|
9
|
+
constructor(statusCode: number, body: unknown, retryAfter: string | null);
|
|
10
|
+
}
|
|
11
|
+
export declare class BasalTransportError extends BasalError {
|
|
12
|
+
}
|
|
13
|
+
export declare class BasalProtocolError extends BasalError {
|
|
14
|
+
}
|
|
15
|
+
export declare class BasalResponseTooLargeError extends BasalError {
|
|
16
|
+
}
|
|
17
|
+
/** `batches.create({requests})` uploaded the input file, but creating the batch failed (HTTP, transport or abort).
|
|
18
|
+
* The SDK neither retries nor deletes the file. The create may still have succeeded on the server, so resume with
|
|
19
|
+
* the same parameters, `{input_file_id: error.fileId}` and `{idempotencyKey: error.idempotencyKey}`; the original
|
|
20
|
+
* failure is in `cause`.
|
|
21
|
+
*/
|
|
22
|
+
export declare class BasalBatchSubmissionError extends BasalError {
|
|
23
|
+
readonly file: FileObject;
|
|
24
|
+
readonly idempotencyKey: string;
|
|
25
|
+
readonly fileId: string;
|
|
26
|
+
constructor(file: FileObject, idempotencyKey: string, options: {
|
|
27
|
+
cause: unknown;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** `batches.wait` reached its deadline. The batch keeps running on the server; nothing is cancelled.
|
|
31
|
+
* `lastBatch` is the last retrieved state (null if no retrieve finished) and is non-enumerable, like `BasalHTTPError.body`.
|
|
32
|
+
*/
|
|
33
|
+
export declare class BasalBatchTimeoutError extends BasalError {
|
|
34
|
+
readonly batchId: string;
|
|
35
|
+
readonly lastBatch: Batch | null;
|
|
36
|
+
readonly timeoutMs: number;
|
|
37
|
+
constructor(batchId: string, lastBatch: Batch | null, timeoutMs: number, options?: {
|
|
38
|
+
cause: unknown;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** `batches.results` was called for a batch that is not `completed`, `failed`, `expired` or `cancelled`.
|
|
42
|
+
* `batch` (non-enumerable) is the retrieved state; `status` is its raw status.
|
|
43
|
+
*/
|
|
44
|
+
export declare class BasalBatchNotReadyError extends BasalError {
|
|
45
|
+
readonly batchId: string;
|
|
46
|
+
readonly batch: Batch;
|
|
47
|
+
readonly status: string;
|
|
48
|
+
constructor(batchId: string, batch: Batch);
|
|
49
|
+
}
|
|
50
|
+
/** A result file line that is not UTF-8, not JSON or not a result envelope. `line` is 1-based; the content is not
|
|
51
|
+
* copied into the error.
|
|
52
|
+
*/
|
|
53
|
+
export declare class BasalBatchResultError extends BasalProtocolError {
|
|
54
|
+
readonly fileId: string;
|
|
55
|
+
readonly line: number;
|
|
56
|
+
constructor(fileId: string, line: number, reason: string, options?: {
|
|
57
|
+
cause: unknown;
|
|
58
|
+
});
|
|
59
|
+
}
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -14,49 +14,33 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.BasalClient = exports.
|
|
17
|
+
exports.BasalClient = exports.Files = exports.Batches = exports.TERMINAL_BATCH_STATUSES = exports.parseBatchResults = exports.MAX_BATCH_REQUESTS = exports.MAX_BATCH_INPUT_BYTES = exports.batchInput = void 0;
|
|
18
18
|
const questions_cjs_1 = require("./questions.cjs");
|
|
19
|
+
const errors_cjs_1 = require("./errors.cjs");
|
|
20
|
+
const batches_cjs_1 = require("./batches.cjs");
|
|
21
|
+
const resources_cjs_1 = require("./resources.cjs");
|
|
19
22
|
__exportStar(require("./questions.cjs"), exports);
|
|
20
23
|
__exportStar(require("./media.cjs"), exports);
|
|
21
24
|
__exportStar(require("./tasks.cjs"), exports);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
exports.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
this.statusCode = statusCode;
|
|
33
|
-
this.body = body;
|
|
34
|
-
this.retryAfter = retryAfter;
|
|
35
|
-
this.name = "BasalHTTPError";
|
|
36
|
-
// Node's default inspection and JSON.stringify must not print reflected state or credentials.
|
|
37
|
-
Object.defineProperty(this, "body", { enumerable: false });
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
exports.BasalHTTPError = BasalHTTPError;
|
|
41
|
-
class BasalTransportError extends BasalError {
|
|
42
|
-
}
|
|
43
|
-
exports.BasalTransportError = BasalTransportError;
|
|
44
|
-
class BasalProtocolError extends BasalError {
|
|
45
|
-
}
|
|
46
|
-
exports.BasalProtocolError = BasalProtocolError;
|
|
47
|
-
class BasalResponseTooLargeError extends BasalError {
|
|
48
|
-
}
|
|
49
|
-
exports.BasalResponseTooLargeError = BasalResponseTooLargeError;
|
|
25
|
+
__exportStar(require("./errors.cjs"), exports);
|
|
26
|
+
var batches_cjs_2 = require("./batches.cjs");
|
|
27
|
+
Object.defineProperty(exports, "batchInput", { enumerable: true, get: function () { return batches_cjs_2.batchInput; } });
|
|
28
|
+
Object.defineProperty(exports, "MAX_BATCH_INPUT_BYTES", { enumerable: true, get: function () { return batches_cjs_2.MAX_BATCH_INPUT_BYTES; } });
|
|
29
|
+
Object.defineProperty(exports, "MAX_BATCH_REQUESTS", { enumerable: true, get: function () { return batches_cjs_2.MAX_BATCH_REQUESTS; } });
|
|
30
|
+
Object.defineProperty(exports, "parseBatchResults", { enumerable: true, get: function () { return batches_cjs_2.parseBatchResults; } });
|
|
31
|
+
Object.defineProperty(exports, "TERMINAL_BATCH_STATUSES", { enumerable: true, get: function () { return batches_cjs_2.TERMINAL_BATCH_STATUSES; } });
|
|
32
|
+
var resources_cjs_2 = require("./resources.cjs");
|
|
33
|
+
Object.defineProperty(exports, "Batches", { enumerable: true, get: function () { return resources_cjs_2.Batches; } });
|
|
34
|
+
Object.defineProperty(exports, "Files", { enumerable: true, get: function () { return resources_cjs_2.Files; } });
|
|
50
35
|
async function readBody(response, limit, signal) {
|
|
51
36
|
// Count bytes from the actual stream, including decompressed responses; never trust Content-Length alone.
|
|
52
37
|
const reader = response.body?.getReader();
|
|
53
38
|
if (!reader)
|
|
54
|
-
return
|
|
39
|
+
return new Uint8Array();
|
|
55
40
|
const cancel = () => { void reader.cancel().catch(() => { }); };
|
|
56
41
|
signal.addEventListener("abort", cancel, { once: true });
|
|
57
42
|
if (signal.aborted)
|
|
58
43
|
cancel();
|
|
59
|
-
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
60
44
|
const parts = [];
|
|
61
45
|
let bytes = 0;
|
|
62
46
|
let complete = false;
|
|
@@ -69,20 +53,38 @@ async function readBody(response, limit, signal) {
|
|
|
69
53
|
}
|
|
70
54
|
bytes += value.byteLength;
|
|
71
55
|
if (bytes > limit) {
|
|
72
|
-
throw new BasalResponseTooLargeError(`Basal response exceeds ${limit} bytes`);
|
|
56
|
+
throw new errors_cjs_1.BasalResponseTooLargeError(`Basal response exceeds ${limit} bytes`);
|
|
73
57
|
}
|
|
74
|
-
parts.push(
|
|
58
|
+
parts.push(value);
|
|
75
59
|
}
|
|
76
|
-
parts.push(decoder.decode());
|
|
77
|
-
return parts.join("");
|
|
78
60
|
}
|
|
79
61
|
finally {
|
|
80
62
|
signal.removeEventListener("abort", cancel);
|
|
81
|
-
// A
|
|
63
|
+
// A read failure must also stop the underlying HTTP body, before releasing ownership.
|
|
82
64
|
if (!complete)
|
|
83
65
|
cancel();
|
|
84
66
|
reader.releaseLock();
|
|
85
67
|
}
|
|
68
|
+
if (parts.length === 1)
|
|
69
|
+
return parts[0];
|
|
70
|
+
const out = new Uint8Array(bytes);
|
|
71
|
+
let offset = 0;
|
|
72
|
+
for (const part of parts) {
|
|
73
|
+
out.set(part, offset);
|
|
74
|
+
offset += part.byteLength;
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
function positiveTimeout(value, name) {
|
|
79
|
+
if (!Number.isFinite(value) || value <= 0 || value > 2_147_483_647) {
|
|
80
|
+
throw new TypeError(`${name} must be positive and at most 2147483647`);
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
function byteLimit(value, name) {
|
|
85
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
86
|
+
throw new TypeError(`${name} must be a positive safe integer`);
|
|
87
|
+
return value;
|
|
86
88
|
}
|
|
87
89
|
/** Reusable fetch client. Server root URL, optional proxy prefix, without /v1.
|
|
88
90
|
* No automatic retries. AbortSignal cancels a request; timeout includes body reading.
|
|
@@ -93,6 +95,10 @@ class BasalClient {
|
|
|
93
95
|
timeoutMs;
|
|
94
96
|
maxResponseBytes;
|
|
95
97
|
fetch;
|
|
98
|
+
/** Files API under resource names: `create`, `retrieve`, `list`, `delete`, `content`. */
|
|
99
|
+
files;
|
|
100
|
+
/** Batch API under resource names, with `create({requests})`, `wait` and `results`. */
|
|
101
|
+
batches;
|
|
96
102
|
constructor(options = {}) {
|
|
97
103
|
const url = new URL(options.baseUrl ?? "http://127.0.0.1:8000");
|
|
98
104
|
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.href.includes("?") || url.href.includes("#")) {
|
|
@@ -117,56 +123,82 @@ class BasalClient {
|
|
|
117
123
|
throw new TypeError("apiKey must be a nonempty, single-line token");
|
|
118
124
|
this.headers.Authorization = `Bearer ${options.apiKey}`;
|
|
119
125
|
}
|
|
126
|
+
this.files = new resources_cjs_1.Files(this);
|
|
127
|
+
this.batches = new resources_cjs_1.Batches(this);
|
|
128
|
+
}
|
|
129
|
+
request(method, path, body, signal) {
|
|
130
|
+
return this.call({ method, path, json: body, signal });
|
|
120
131
|
}
|
|
121
|
-
async
|
|
122
|
-
// Serialization errors are local input errors, not failures of the HTTP transport.
|
|
123
|
-
const encoded =
|
|
132
|
+
async call(call) {
|
|
133
|
+
// Serialization and option errors are local input errors, not failures of the HTTP transport.
|
|
134
|
+
const encoded = call.json === undefined ? undefined : JSON.stringify(call.json);
|
|
135
|
+
const timeoutMs = call.timeoutMs === undefined ? this.timeoutMs : positiveTimeout(call.timeoutMs, "timeoutMs");
|
|
136
|
+
const limit = call.maxBytes === undefined ? this.maxResponseBytes : byteLimit(call.maxBytes, "maxBytes");
|
|
137
|
+
const headers = { ...this.headers, ...call.headers };
|
|
138
|
+
if (encoded !== undefined)
|
|
139
|
+
headers["Content-Type"] = "application/json";
|
|
140
|
+
const { signal } = call;
|
|
124
141
|
const controller = new AbortController();
|
|
125
142
|
const abort = () => controller.abort(signal?.reason);
|
|
126
143
|
signal?.addEventListener("abort", abort, { once: true });
|
|
127
144
|
if (signal?.aborted)
|
|
128
145
|
abort();
|
|
129
|
-
const timer = setTimeout(() => controller.abort(new Error("Basal request timed out")),
|
|
146
|
+
const timer = setTimeout(() => controller.abort(new Error("Basal request timed out")), timeoutMs);
|
|
130
147
|
let onAbort = () => { };
|
|
131
148
|
const aborted = new Promise((_, reject) => {
|
|
132
|
-
onAbort = () => reject(new BasalTransportError("Basal HTTP request aborted", { cause: controller.signal.reason }));
|
|
149
|
+
onAbort = () => reject(new errors_cjs_1.BasalTransportError("Basal HTTP request aborted", { cause: controller.signal.reason }));
|
|
133
150
|
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
134
151
|
if (controller.signal.aborted)
|
|
135
152
|
onAbort();
|
|
136
153
|
});
|
|
137
154
|
const work = async () => {
|
|
138
155
|
let response;
|
|
139
|
-
let
|
|
156
|
+
let content;
|
|
157
|
+
let raw = "";
|
|
140
158
|
try {
|
|
141
|
-
|
|
142
|
-
method, headers:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
159
|
+
const init = {
|
|
160
|
+
method: call.method, headers, signal: controller.signal, redirect: "error", credentials: "omit", cache: "no-store",
|
|
161
|
+
};
|
|
162
|
+
if (encoded !== undefined)
|
|
163
|
+
init.body = encoded;
|
|
164
|
+
else if (call.body instanceof Blob)
|
|
165
|
+
init.body = call.body;
|
|
166
|
+
else if (call.body !== undefined) {
|
|
167
|
+
// Streamed request bodies require half-duplex fetch (Node.js, Chromium over HTTP/2).
|
|
168
|
+
init.body = call.body(controller.signal);
|
|
169
|
+
init.duplex = "half";
|
|
170
|
+
}
|
|
171
|
+
response = await this.fetch(this.baseUrl + call.path, init);
|
|
146
172
|
if (response.redirected || ([301, 302, 303, 307, 308].includes(response.status) && response.headers.has("Location"))) {
|
|
147
173
|
await response.body?.cancel().catch(() => { });
|
|
148
|
-
throw new BasalTransportError("Basal transport returned a redirect");
|
|
174
|
+
throw new errors_cjs_1.BasalTransportError("Basal transport returned a redirect");
|
|
149
175
|
}
|
|
150
|
-
|
|
176
|
+
// A per-download cap limits successful artifact bytes. Error envelopes retain the client's normal cap
|
|
177
|
+
// so a small artifact cap does not hide an actionable 404/429 and its Retry-After.
|
|
178
|
+
content = await readBody(response, response.ok ? limit : this.maxResponseBytes, controller.signal);
|
|
179
|
+
if (!call.bytes || !response.ok)
|
|
180
|
+
raw = new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
151
181
|
}
|
|
152
182
|
catch (cause) {
|
|
153
|
-
if (cause instanceof BasalError)
|
|
183
|
+
if (cause instanceof errors_cjs_1.BasalError)
|
|
154
184
|
throw cause;
|
|
155
|
-
throw new BasalTransportError("Basal HTTP transport failed", { cause });
|
|
185
|
+
throw new errors_cjs_1.BasalTransportError("Basal HTTP transport failed", { cause });
|
|
156
186
|
}
|
|
187
|
+
if (call.bytes && response.ok)
|
|
188
|
+
return content;
|
|
157
189
|
let data;
|
|
158
190
|
try {
|
|
159
191
|
data = JSON.parse(raw);
|
|
160
192
|
}
|
|
161
193
|
catch (cause) {
|
|
162
194
|
if (response.ok)
|
|
163
|
-
throw new BasalProtocolError("Basal returned invalid JSON", { cause });
|
|
195
|
+
throw new errors_cjs_1.BasalProtocolError("Basal returned invalid JSON", { cause });
|
|
164
196
|
data = raw;
|
|
165
197
|
}
|
|
166
198
|
if (!response.ok)
|
|
167
|
-
throw new BasalHTTPError(response.status, data, response.headers.get("Retry-After"));
|
|
199
|
+
throw new errors_cjs_1.BasalHTTPError(response.status, data, response.headers.get("Retry-After"));
|
|
168
200
|
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
169
|
-
throw new BasalProtocolError("Basal returned a JSON value other than an object");
|
|
201
|
+
throw new errors_cjs_1.BasalProtocolError("Basal returned a JSON value other than an object");
|
|
170
202
|
}
|
|
171
203
|
return data;
|
|
172
204
|
};
|
|
@@ -179,6 +211,9 @@ class BasalClient {
|
|
|
179
211
|
controller.signal.removeEventListener("abort", onAbort);
|
|
180
212
|
clearTimeout(timer);
|
|
181
213
|
signal?.removeEventListener("abort", abort);
|
|
214
|
+
// An answer can arrive before a streamed upload is fully sent (e.g. 413); stop its source.
|
|
215
|
+
if (typeof call.body === "function")
|
|
216
|
+
controller.abort(new Error("Basal request finished"));
|
|
182
217
|
}
|
|
183
218
|
}
|
|
184
219
|
async decide(state, questionsOrSignal, options = {}) {
|
|
@@ -198,7 +233,7 @@ class BasalClient {
|
|
|
198
233
|
parsed.set(id, question.read(response, id));
|
|
199
234
|
}
|
|
200
235
|
catch (cause) {
|
|
201
|
-
throw new BasalProtocolError("Invalid typed Basal answer", { cause });
|
|
236
|
+
throw new errors_cjs_1.BasalProtocolError("Invalid typed Basal answer", { cause });
|
|
202
237
|
}
|
|
203
238
|
const typed = { ...response, answers: Object.fromEntries(parsed) };
|
|
204
239
|
Object.defineProperty(typed, "raw", { value: response });
|
|
@@ -213,5 +248,46 @@ class BasalClient {
|
|
|
213
248
|
return typed;
|
|
214
249
|
}
|
|
215
250
|
models(signal) { return this.request("GET", "v1/models", undefined, signal); }
|
|
251
|
+
/** Upload a JSONL input file (`purpose=batch`) from caller-owned bytes, a Blob or a stream; never fetches URLs. */
|
|
252
|
+
async uploadFile(request, options = {}) {
|
|
253
|
+
const form = (0, batches_cjs_1.uploadForm)(request);
|
|
254
|
+
return await this.call({
|
|
255
|
+
method: "POST", path: "v1/files", body: form.body, headers: { "Content-Type": form.contentType },
|
|
256
|
+
signal: options.signal, timeoutMs: options.timeoutMs,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
async listFiles(options = {}) {
|
|
260
|
+
return await this.call({ method: "GET", path: (0, batches_cjs_1.listPath)("v1/files", options), signal: options.signal });
|
|
261
|
+
}
|
|
262
|
+
async getFile(fileId, options = {}) {
|
|
263
|
+
return await this.call({ method: "GET", path: (0, batches_cjs_1.resourcePath)("v1/files", fileId), signal: options.signal });
|
|
264
|
+
}
|
|
265
|
+
async deleteFile(fileId, options = {}) {
|
|
266
|
+
return await this.call({ method: "DELETE", path: (0, batches_cjs_1.resourcePath)("v1/files", fileId), signal: options.signal });
|
|
267
|
+
}
|
|
268
|
+
/** Raw file bytes (JSONL), buffered up to `maxBytes` (default `maxResponseBytes`); never JSON-parsed. */
|
|
269
|
+
async downloadFile(fileId, options = {}) {
|
|
270
|
+
return await this.call({
|
|
271
|
+
method: "GET", path: (0, batches_cjs_1.resourcePath)("v1/files", fileId, "/content"), bytes: true,
|
|
272
|
+
headers: { Accept: "application/jsonl, application/json" },
|
|
273
|
+
signal: options.signal, timeoutMs: options.timeoutMs, maxBytes: options.maxBytes,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
async createBatch(request, options = {}) {
|
|
277
|
+
const headers = (0, batches_cjs_1.idempotencyHeader)(options.idempotencyKey);
|
|
278
|
+
return await this.call({ method: "POST", path: "v1/batches", json: (0, batches_cjs_1.createBody)(request), headers, signal: options.signal });
|
|
279
|
+
}
|
|
280
|
+
async listBatches(options = {}) {
|
|
281
|
+
const { after, limit } = options;
|
|
282
|
+
const query = { ...(after === undefined ? {} : { after }), ...(limit === undefined ? {} : { limit }) };
|
|
283
|
+
return await this.call({ method: "GET", path: (0, batches_cjs_1.listPath)("v1/batches", query), signal: options.signal });
|
|
284
|
+
}
|
|
285
|
+
async getBatch(batchId, options = {}) {
|
|
286
|
+
return await this.call({ method: "GET", path: (0, batches_cjs_1.resourcePath)("v1/batches", batchId), signal: options.signal });
|
|
287
|
+
}
|
|
288
|
+
/** Request cancellation; the batch moves to `cancelling` and later `cancelled`. Does not wait. */
|
|
289
|
+
async cancelBatch(batchId, options = {}) {
|
|
290
|
+
return await this.call({ method: "POST", path: (0, batches_cjs_1.resourcePath)("v1/batches", batchId, "/cancel"), signal: options.signal });
|
|
291
|
+
}
|
|
216
292
|
}
|
|
217
293
|
exports.BasalClient = BasalClient;
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { type QuestionSet, type TypedDecisionResponse, type DecisionOptions } from "./questions.cjs";
|
|
2
|
+
import { type Batch, type BatchList, type CreateBatchOptions, type CreateBatchRequest, type DeletedFile, type DownloadOptions, type FileList, type FileObject, type ListFilesOptions, type ListOptions, type RequestOptions, type TransferOptions, type UploadFileRequest } from "./batches.cjs";
|
|
3
|
+
import { Batches, Files } from "./resources.cjs";
|
|
2
4
|
export * from "./questions.cjs";
|
|
3
5
|
export * from "./media.cjs";
|
|
4
6
|
export * from "./tasks.cjs";
|
|
7
|
+
export * from "./errors.cjs";
|
|
8
|
+
export { batchInput, MAX_BATCH_INPUT_BYTES, MAX_BATCH_REQUESTS, parseBatchResults, TERMINAL_BATCH_STATUSES, } from "./batches.cjs";
|
|
9
|
+
export { Batches, Files } from "./resources.cjs";
|
|
10
|
+
export type { Batch, BatchCreateCommonParams, BatchCreateFromFileParams, BatchCreateFromRequestsParams, BatchCreateOptions, BatchCreateParams, BatchErrorCode, BatchErrorEntry, BatchErrors, BatchInputLine, BatchList, BatchResultLine, BatchResultsOptions, BatchStatus, BatchWaitOptions, CreateBatchOptions, CreateBatchRequest, DeletedFile, DownloadOptions, ExpiresAfter, FileList, FileObject, FilePurpose, ListFilesOptions, ListOptions, ListPage, Metadata, RequestCounts, RequestOptions, TransferOptions, UploadContent, UploadFileRequest, } from "./batches.cjs";
|
|
5
11
|
import type { TaskAnswer } from "./tasks.cjs";
|
|
6
12
|
/** Native Basal wire types. Criterion order is significant; do not sort keys. */
|
|
7
13
|
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
@@ -164,21 +170,6 @@ export interface ModelMetadata {
|
|
|
164
170
|
export interface ModelsResponse {
|
|
165
171
|
models: ModelMetadata[];
|
|
166
172
|
}
|
|
167
|
-
export declare class BasalError extends Error {
|
|
168
|
-
}
|
|
169
|
-
/** Non-2xx response. Server body and Retry-After are available without logging them. */
|
|
170
|
-
export declare class BasalHTTPError extends BasalError {
|
|
171
|
-
readonly statusCode: number;
|
|
172
|
-
readonly body: unknown;
|
|
173
|
-
readonly retryAfter: string | null;
|
|
174
|
-
constructor(statusCode: number, body: unknown, retryAfter: string | null);
|
|
175
|
-
}
|
|
176
|
-
export declare class BasalTransportError extends BasalError {
|
|
177
|
-
}
|
|
178
|
-
export declare class BasalProtocolError extends BasalError {
|
|
179
|
-
}
|
|
180
|
-
export declare class BasalResponseTooLargeError extends BasalError {
|
|
181
|
-
}
|
|
182
173
|
/** Web Fetch input accepted by browsers and server runtimes. */
|
|
183
174
|
export type RequestInput = string | URL | Request;
|
|
184
175
|
/** A transport adapter must honor signal/redirect and return a standard Web Response. */
|
|
@@ -200,9 +191,26 @@ export declare class BasalClient {
|
|
|
200
191
|
private readonly timeoutMs;
|
|
201
192
|
private readonly maxResponseBytes;
|
|
202
193
|
private readonly fetch;
|
|
194
|
+
/** Files API under resource names: `create`, `retrieve`, `list`, `delete`, `content`. */
|
|
195
|
+
readonly files: Files;
|
|
196
|
+
/** Batch API under resource names, with `create({requests})`, `wait` and `results`. */
|
|
197
|
+
readonly batches: Batches;
|
|
203
198
|
constructor(options?: BasalClientOptions);
|
|
204
199
|
private request;
|
|
200
|
+
private call;
|
|
205
201
|
decide(request: DecisionRequest, signal?: AbortSignal): Promise<DecisionResponse>;
|
|
206
202
|
decide<const Q extends QuestionSet>(state: JsonValue, questions: Q, options?: DecisionOptions): Promise<TypedDecisionResponse<Q>>;
|
|
207
203
|
models(signal?: AbortSignal): Promise<ModelsResponse>;
|
|
204
|
+
/** Upload a JSONL input file (`purpose=batch`) from caller-owned bytes, a Blob or a stream; never fetches URLs. */
|
|
205
|
+
uploadFile(request: UploadFileRequest, options?: TransferOptions): Promise<FileObject>;
|
|
206
|
+
listFiles(options?: ListFilesOptions): Promise<FileList>;
|
|
207
|
+
getFile(fileId: string, options?: RequestOptions): Promise<FileObject>;
|
|
208
|
+
deleteFile(fileId: string, options?: RequestOptions): Promise<DeletedFile>;
|
|
209
|
+
/** Raw file bytes (JSONL), buffered up to `maxBytes` (default `maxResponseBytes`); never JSON-parsed. */
|
|
210
|
+
downloadFile(fileId: string, options?: DownloadOptions): Promise<Uint8Array>;
|
|
211
|
+
createBatch(request: CreateBatchRequest, options?: CreateBatchOptions): Promise<Batch>;
|
|
212
|
+
listBatches(options?: ListOptions): Promise<BatchList>;
|
|
213
|
+
getBatch(batchId: string, options?: RequestOptions): Promise<Batch>;
|
|
214
|
+
/** Request cancellation; the batch moves to `cancelling` and later `cancelled`. Does not wait. */
|
|
215
|
+
cancelBatch(batchId: string, options?: RequestOptions): Promise<Batch>;
|
|
208
216
|
}
|