@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.
@@ -0,0 +1,300 @@
1
+ import { BasalBatchResultError, BasalProtocolError } from "./errors.js";
2
+ /** Client-side bounds of `batches.create({requests})`, equal to the server defaults; a server may be configured lower. */
3
+ export const MAX_BATCH_REQUESTS = 50_000;
4
+ export const MAX_BATCH_INPUT_BYTES = 200_000_000;
5
+ export const TERMINAL_BATCH_STATUSES = Object.freeze(["completed", "failed", "expired", "cancelled"]);
6
+ const encoder = new TextEncoder();
7
+ /** Path segment of a resource id. `.` and `..` would be resolved as dot segments by URL parsing. */
8
+ export function resourcePath(prefix, id, suffix = "") {
9
+ if (typeof id !== "string" || !id || id === "." || id === "..")
10
+ throw new TypeError("resource id must be a nonempty string");
11
+ return `${prefix}/${encodeURIComponent(id)}${suffix}`;
12
+ }
13
+ export function listPath(path, options) {
14
+ const query = new URLSearchParams();
15
+ if (options.after !== undefined) {
16
+ if (typeof options.after !== "string" || !options.after)
17
+ throw new TypeError("after must be a nonempty id");
18
+ query.set("after", options.after);
19
+ }
20
+ if (options.limit !== undefined) {
21
+ if (!Number.isSafeInteger(options.limit) || options.limit < 1)
22
+ throw new TypeError("limit must be a positive integer");
23
+ query.set("limit", String(options.limit));
24
+ }
25
+ if (options.purpose !== undefined) {
26
+ if (typeof options.purpose !== "string" || !options.purpose)
27
+ throw new TypeError("purpose must be a nonempty string");
28
+ query.set("purpose", options.purpose);
29
+ }
30
+ const encoded = query.toString();
31
+ return encoded ? `${path}?${encoded}` : path;
32
+ }
33
+ export function idempotencyHeader(key) {
34
+ if (key === undefined)
35
+ return {};
36
+ if (typeof key !== "string" || !/^[!-~]{1,128}$/.test(key)) {
37
+ throw new TypeError("idempotencyKey must have 1-128 visible ASCII characters");
38
+ }
39
+ return { "Idempotency-Key": key };
40
+ }
41
+ export function createBody(request) {
42
+ if (!request || typeof request !== "object" || typeof request.input_file_id !== "string" || !request.input_file_id) {
43
+ throw new TypeError("input_file_id must be a nonempty string");
44
+ }
45
+ // Extra caller fields are forwarded; the server refuses fields it does not know.
46
+ return { endpoint: "/v1/basal", completion_window: "24h", ...request };
47
+ }
48
+ function expiresFields(expires) {
49
+ if (expires === undefined)
50
+ return [];
51
+ if (!expires || expires.anchor !== "created_at" || !Number.isSafeInteger(expires.seconds) || expires.seconds < 0) {
52
+ throw new TypeError("expires_after needs anchor created_at and whole seconds");
53
+ }
54
+ return [["expires_after[anchor]", expires.anchor], ["expires_after[seconds]", String(expires.seconds)]];
55
+ }
56
+ /** Multipart form of an upload: `expires_after[...]`, then `purpose`, then the file part last (the server reserves the
57
+ * upload session at the file part, so expiry fields must precede it).
58
+ */
59
+ export function uploadForm(request) {
60
+ if (!request || typeof request !== "object")
61
+ throw new TypeError("upload request must be an object");
62
+ const { file, filename } = request;
63
+ if (request.purpose !== undefined && request.purpose !== "batch")
64
+ throw new TypeError("purpose must be batch");
65
+ // Control characters (CR/LF) would split the part header; a backslash is ambiguous in a quoted filename.
66
+ if (typeof filename !== "string" || !filename || encoder.encode(filename).length > 255 || /[\p{Cc}\\]/u.test(filename)) {
67
+ throw new TypeError("filename must have 1-255 UTF-8 bytes without control characters or backslashes");
68
+ }
69
+ const random = new Uint8Array(16);
70
+ globalThis.crypto.getRandomValues(random);
71
+ const boundary = `basal-${Array.from(random, b => b.toString(16).padStart(2, "0")).join("")}`;
72
+ let head = "";
73
+ for (const [name, value] of [...expiresFields(request.expires_after), ["purpose", "batch"]]) {
74
+ head += `--${boundary}\r\nContent-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`;
75
+ }
76
+ head += `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${filename.replaceAll('"', '\\"')}"\r\n`
77
+ + "Content-Type: application/jsonl\r\n\r\n";
78
+ const prefix = encoder.encode(head);
79
+ const suffix = encoder.encode(`\r\n--${boundary}--\r\n`);
80
+ const contentType = `multipart/form-data; boundary=${boundary}`;
81
+ if (file instanceof ReadableStream) {
82
+ if (file.locked)
83
+ throw new TypeError("upload stream is locked");
84
+ return { contentType, body: signal => framedStream(prefix, file, suffix, signal) };
85
+ }
86
+ let content;
87
+ if (file instanceof Blob || file instanceof ArrayBuffer)
88
+ content = file;
89
+ else if (file instanceof Uint8Array)
90
+ content = file.buffer instanceof ArrayBuffer ? file : new Uint8Array(file);
91
+ else
92
+ throw new TypeError("file must be a Uint8Array, ArrayBuffer, Blob or ReadableStream");
93
+ return { contentType, body: new Blob([prefix, content, suffix]) };
94
+ }
95
+ /** Streams the multipart prefix, the caller's chunks and the suffix without buffering the file.
96
+ * Fetch implementations may keep pulling a request body after abort, so `signal` cancels the source here.
97
+ */
98
+ function framedStream(prefix, source, suffix, signal) {
99
+ let reader;
100
+ let stage = "prefix";
101
+ const stop = (reason) => {
102
+ signal.removeEventListener("abort", onAbort);
103
+ if (stage === "finished" || stage === "stopped")
104
+ return Promise.resolve();
105
+ stage = "stopped";
106
+ return (reader ? reader.cancel(reason) : source.cancel(reason)).catch(() => { });
107
+ };
108
+ const onAbort = () => { void stop(signal.reason); };
109
+ signal.addEventListener("abort", onAbort, { once: true });
110
+ if (signal.aborted)
111
+ onAbort();
112
+ return new ReadableStream({
113
+ async pull(controller) {
114
+ if (stage === "stopped")
115
+ throw signal.reason ?? new Error("upload stopped");
116
+ if (stage === "prefix") {
117
+ stage = "file";
118
+ controller.enqueue(prefix);
119
+ return;
120
+ }
121
+ if (stage === "finished") {
122
+ controller.close();
123
+ return;
124
+ }
125
+ reader ??= source.getReader();
126
+ const { done, value } = await reader.read();
127
+ // A read cancelled by `stop` ends as done; it must not be framed as a complete file.
128
+ if (stage !== "file")
129
+ throw signal.reason ?? new Error("upload stopped");
130
+ if (!done) {
131
+ if (!(value instanceof Uint8Array)) {
132
+ const error = new TypeError("upload stream chunks must be Uint8Array");
133
+ await stop(error);
134
+ throw error;
135
+ }
136
+ controller.enqueue(value);
137
+ return;
138
+ }
139
+ reader.releaseLock();
140
+ reader = undefined;
141
+ stage = "finished";
142
+ signal.removeEventListener("abort", onAbort);
143
+ controller.enqueue(suffix);
144
+ },
145
+ cancel(reason) { return stop(reason); },
146
+ });
147
+ }
148
+ /** JSONL input file of native records (`method` POST and `url` /v1/basal are filled in). Never sends anything. */
149
+ export function batchInput(lines) {
150
+ const out = [];
151
+ for (const line of lines) {
152
+ if (!line || typeof line.custom_id !== "string" || !line.custom_id || !line.body || typeof line.body !== "object") {
153
+ throw new TypeError("each batch line needs a custom_id and a native request body");
154
+ }
155
+ out.push(JSON.stringify({ custom_id: line.custom_id, method: line.method ?? "POST", url: line.url ?? "/v1/basal", body: line.body }));
156
+ }
157
+ if (!out.length)
158
+ throw new TypeError("a batch input needs at least one line");
159
+ return encoder.encode(out.join("\n") + "\n");
160
+ }
161
+ /** Parse downloaded result bytes (output or error file) into lines. Never fetches anything. */
162
+ export function parseBatchResults(content) {
163
+ let text;
164
+ try {
165
+ text = new TextDecoder("utf-8", { fatal: true }).decode(content);
166
+ }
167
+ catch (cause) {
168
+ throw new BasalProtocolError("Batch result file is not UTF-8", { cause });
169
+ }
170
+ const lines = text.split("\n");
171
+ if (lines[lines.length - 1] === "")
172
+ lines.pop();
173
+ return lines.map((line, index) => {
174
+ let value;
175
+ try {
176
+ value = JSON.parse(line);
177
+ }
178
+ catch (cause) {
179
+ throw new BasalProtocolError(`Batch result line ${index + 1} is not JSON`, { cause });
180
+ }
181
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
182
+ throw new BasalProtocolError(`Batch result line ${index + 1} is not a JSON object`);
183
+ }
184
+ return value;
185
+ });
186
+ }
187
+ const LINE_KEYS = new Set(["custom_id", "method", "url", "body"]);
188
+ /** Validated JSONL of `batches.create({requests})`: nonempty, supported envelope, unique ids and the bounds above.
189
+ * The result is held in memory (at most `MAX_BATCH_INPUT_BYTES`). Never sends anything.
190
+ */
191
+ export function serializeBatchRequests(requests) {
192
+ if (!requests || typeof requests !== "object" || typeof requests[Symbol.iterator] !== "function") {
193
+ throw new TypeError("requests must be an iterable of batch records");
194
+ }
195
+ const parts = [];
196
+ const seen = new Set();
197
+ let bytes = 0;
198
+ for (const line of requests) {
199
+ const at = `requests[${parts.length}]`;
200
+ if (parts.length >= MAX_BATCH_REQUESTS)
201
+ throw new TypeError(`requests must have at most ${MAX_BATCH_REQUESTS} records`);
202
+ if (!line || typeof line !== "object" || Array.isArray(line))
203
+ throw new TypeError(`${at} must be an object`);
204
+ for (const key of Object.keys(line)) {
205
+ if (!LINE_KEYS.has(key))
206
+ throw new TypeError(`${at} has unsupported field ${JSON.stringify(key)}`);
207
+ }
208
+ const { custom_id: id, body } = line;
209
+ // Lone surrogates have no UTF-8 form; the server compares ids byte for byte.
210
+ if (typeof id !== "string" || !id || /\p{Cs}/u.test(id) || encoder.encode(id).length > 256) {
211
+ throw new TypeError(`${at}.custom_id must be a string of 1-256 UTF-8 bytes`);
212
+ }
213
+ if (seen.has(id))
214
+ throw new TypeError(`${at}.custom_id duplicates an earlier record`);
215
+ seen.add(id);
216
+ if (line.method !== undefined && line.method !== "POST")
217
+ throw new TypeError(`${at}.method must be POST`);
218
+ if (line.url !== undefined && line.url !== "/v1/basal")
219
+ throw new TypeError(`${at}.url must be /v1/basal`);
220
+ if (!body || typeof body !== "object" || Array.isArray(body))
221
+ throw new TypeError(`${at}.body must be a native request object`);
222
+ if (body.stream === true)
223
+ throw new TypeError(`${at}.body.stream must not be true`);
224
+ let json;
225
+ try {
226
+ json = JSON.stringify({ custom_id: id, method: "POST", url: "/v1/basal", body });
227
+ }
228
+ catch (cause) {
229
+ throw new TypeError(`${at}.body is not serializable as JSON`, { cause });
230
+ }
231
+ const encoded = encoder.encode(json + "\n");
232
+ bytes += encoded.byteLength;
233
+ if (bytes > MAX_BATCH_INPUT_BYTES)
234
+ throw new TypeError(`requests exceed ${MAX_BATCH_INPUT_BYTES} bytes of JSONL`);
235
+ parts.push(encoded);
236
+ }
237
+ if (!parts.length)
238
+ throw new TypeError("requests must contain at least one record");
239
+ return new Blob(parts, { type: "application/jsonl" });
240
+ }
241
+ /** Result lines of one downloaded file, decoded and checked one line at a time (no list of all lines). A final LF is
242
+ * optional; any other empty line, invalid UTF-8 (a BOM included), JSON or envelope raises `BasalBatchResultError`.
243
+ * Unknown fields are preserved.
244
+ */
245
+ export function* readBatchResultLines(content, fileId) {
246
+ const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
247
+ let start = 0;
248
+ for (let number = 1; start < content.byteLength; number++) {
249
+ // UTF-8 never encodes another code point with byte 0x0A, so splitting bytes at LF is exact.
250
+ let end = content.indexOf(0x0a, start);
251
+ if (end < 0)
252
+ end = content.byteLength;
253
+ const bytes = content.subarray(start, end);
254
+ start = end + 1;
255
+ let text;
256
+ try {
257
+ text = decoder.decode(bytes);
258
+ }
259
+ catch (cause) {
260
+ throw new BasalBatchResultError(fileId, number, "is not UTF-8", { cause });
261
+ }
262
+ let value;
263
+ try {
264
+ value = JSON.parse(text);
265
+ }
266
+ catch (cause) {
267
+ throw new BasalBatchResultError(fileId, number, "is not JSON", { cause });
268
+ }
269
+ const problem = envelopeProblem(value);
270
+ if (problem)
271
+ throw new BasalBatchResultError(fileId, number, problem);
272
+ yield value;
273
+ }
274
+ }
275
+ function isObject(value) {
276
+ return value !== null && typeof value === "object" && !Array.isArray(value);
277
+ }
278
+ function envelopeProblem(value) {
279
+ if (!isObject(value))
280
+ return "is not a JSON object";
281
+ if (typeof value.id !== "string" || !value.id)
282
+ return "has no string id";
283
+ if (typeof value.custom_id !== "string" || !value.custom_id)
284
+ return "has no string custom_id";
285
+ const { response, error } = value;
286
+ if (response === undefined || error === undefined)
287
+ return "lacks response or error";
288
+ if (response !== null && (!isObject(response) || !Number.isSafeInteger(response.status_code)
289
+ || response.status_code < 100 || response.status_code > 599
290
+ || typeof response.request_id !== "string" || !response.request_id || !isObject(response.body)))
291
+ return "has an invalid response";
292
+ if (error !== null && (!isObject(error) || typeof error.code !== "string" || !error.code || typeof error.message !== "string")) {
293
+ return "has an invalid error";
294
+ }
295
+ if (response === null && error === null)
296
+ return "has neither response nor error";
297
+ if (response !== null && error !== null)
298
+ return "has both response and error";
299
+ return undefined;
300
+ }
@@ -0,0 +1,59 @@
1
+ import type { Batch, FileObject } from "./batches.js";
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
+ }
@@ -0,0 +1,85 @@
1
+ export class BasalError extends Error {
2
+ }
3
+ /** Non-2xx response. Server body and Retry-After are available without logging them. */
4
+ export class BasalHTTPError extends BasalError {
5
+ statusCode;
6
+ body;
7
+ retryAfter;
8
+ constructor(statusCode, body, retryAfter) {
9
+ super(`Basal HTTP ${statusCode}`);
10
+ this.statusCode = statusCode;
11
+ this.body = body;
12
+ this.retryAfter = retryAfter;
13
+ this.name = "BasalHTTPError";
14
+ // Node's default inspection and JSON.stringify must not print reflected state or credentials.
15
+ Object.defineProperty(this, "body", { enumerable: false });
16
+ }
17
+ }
18
+ export class BasalTransportError extends BasalError {
19
+ }
20
+ export class BasalProtocolError extends BasalError {
21
+ }
22
+ export class BasalResponseTooLargeError extends BasalError {
23
+ }
24
+ /** `batches.create({requests})` uploaded the input file, but creating the batch failed (HTTP, transport or abort).
25
+ * The SDK neither retries nor deletes the file. The create may still have succeeded on the server, so resume with
26
+ * the same parameters, `{input_file_id: error.fileId}` and `{idempotencyKey: error.idempotencyKey}`; the original
27
+ * failure is in `cause`.
28
+ */
29
+ export class BasalBatchSubmissionError extends BasalError {
30
+ file;
31
+ idempotencyKey;
32
+ fileId;
33
+ constructor(file, idempotencyKey, options) {
34
+ super(`Basal batch creation failed after uploading input file ${file.id}`, options);
35
+ this.file = file;
36
+ this.idempotencyKey = idempotencyKey;
37
+ this.name = "BasalBatchSubmissionError";
38
+ this.fileId = file.id;
39
+ }
40
+ }
41
+ /** `batches.wait` reached its deadline. The batch keeps running on the server; nothing is cancelled.
42
+ * `lastBatch` is the last retrieved state (null if no retrieve finished) and is non-enumerable, like `BasalHTTPError.body`.
43
+ */
44
+ export class BasalBatchTimeoutError extends BasalError {
45
+ batchId;
46
+ lastBatch;
47
+ timeoutMs;
48
+ constructor(batchId, lastBatch, timeoutMs, options) {
49
+ super(`Basal batch did not reach a terminal status within ${timeoutMs} ms`, options);
50
+ this.batchId = batchId;
51
+ this.lastBatch = lastBatch;
52
+ this.timeoutMs = timeoutMs;
53
+ this.name = "BasalBatchTimeoutError";
54
+ Object.defineProperty(this, "lastBatch", { enumerable: false });
55
+ }
56
+ }
57
+ /** `batches.results` was called for a batch that is not `completed`, `failed`, `expired` or `cancelled`.
58
+ * `batch` (non-enumerable) is the retrieved state; `status` is its raw status.
59
+ */
60
+ export class BasalBatchNotReadyError extends BasalError {
61
+ batchId;
62
+ batch;
63
+ status;
64
+ constructor(batchId, batch) {
65
+ super("Basal batch has not reached a terminal status");
66
+ this.batchId = batchId;
67
+ this.batch = batch;
68
+ this.name = "BasalBatchNotReadyError";
69
+ this.status = String(batch.status);
70
+ Object.defineProperty(this, "batch", { enumerable: false });
71
+ }
72
+ }
73
+ /** A result file line that is not UTF-8, not JSON or not a result envelope. `line` is 1-based; the content is not
74
+ * copied into the error.
75
+ */
76
+ export class BasalBatchResultError extends BasalProtocolError {
77
+ fileId;
78
+ line;
79
+ constructor(fileId, line, reason, options) {
80
+ super(`Batch result file ${fileId} line ${line} ${reason}`, options);
81
+ this.fileId = fileId;
82
+ this.line = line;
83
+ this.name = "BasalBatchResultError";
84
+ }
85
+ }
@@ -1,7 +1,13 @@
1
1
  import { type QuestionSet, type TypedDecisionResponse, type DecisionOptions } from "./questions.js";
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.js";
3
+ import { Batches, Files } from "./resources.js";
2
4
  export * from "./questions.js";
3
5
  export * from "./media.js";
4
6
  export * from "./tasks.js";
7
+ export * from "./errors.js";
8
+ export { batchInput, MAX_BATCH_INPUT_BYTES, MAX_BATCH_REQUESTS, parseBatchResults, TERMINAL_BATCH_STATUSES, } from "./batches.js";
9
+ export { Batches, Files } from "./resources.js";
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.js";
5
11
  import type { TaskAnswer } from "./tasks.js";
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
  }