@itsoltech/basal-client 0.3.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/README.md +55 -1
- package/dist/cjs/batches.cjs +191 -0
- package/dist/cjs/batches.d.cts +166 -0
- package/dist/cjs/errors.cjs +31 -0
- package/dist/cjs/errors.d.cts +15 -0
- package/dist/cjs/index.cjs +118 -55
- package/dist/cjs/index.d.cts +17 -15
- package/dist/esm/batches.d.ts +166 -0
- package/dist/esm/batches.js +182 -0
- package/dist/esm/errors.d.ts +15 -0
- package/dist/esm/errors.js +23 -0
- package/dist/esm/index.d.ts +17 -15
- package/dist/esm/index.js +106 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @itsoltech/basal-client
|
|
2
2
|
|
|
3
|
-
TypeScript/JavaScript SDK for the native Basal API (`POST /v1/basal`). ESM and CommonJS, with type declarations.
|
|
3
|
+
TypeScript/JavaScript SDK for the native Basal API (`POST /v1/basal`) and the Batch API. ESM and CommonJS, with type declarations.
|
|
4
4
|
Node.js 22+ or modern browsers with `fetch` and `AbortController`. No runtime dependencies or GPU requirement.
|
|
5
5
|
|
|
6
6
|
This package is versioned independently. Release tag: `sdk-typescript-vX.Y.Z`.
|
|
@@ -75,6 +75,60 @@ validated before the promise resolves. Typed evidence defaults to an empty array
|
|
|
75
75
|
retains the native JSON without those defaults. The one-argument raw `decide(request, signal)` remains available.
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
## Batch API
|
|
79
|
+
|
|
80
|
+
Requires a Basal server with the Batch API enabled ([docs/BATCHES.md](https://github.com/itsoltech/basal-rs/blob/main/docs/BATCHES.md)).
|
|
81
|
+
Every Batch and Files route needs `apiKey` set to a batch token `<key_id>.<secret>`, also on loopback; the owner
|
|
82
|
+
of every file and batch comes from that token. Records are native `/v1/basal` requests; the SDK only transfers files and controls batches. It never polls:
|
|
83
|
+
call `getBatch` at an interval your application chooses and stop on `completed`, `failed`, `expired` or `cancelled`.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { BasalClient, batchInput, buildRequest, choice, parseBatchResults } from "@itsoltech/basal-client";
|
|
87
|
+
|
|
88
|
+
const client = new BasalClient({ baseUrl: "https://basal.example.com", apiKey: "<key_id>.<secret>" });
|
|
89
|
+
const body = buildRequest("Klient prosi o zwrot.", { department: choice("Dział?", { returns: "Zwroty", support: "Wsparcie" }) }, {});
|
|
90
|
+
const file = await client.uploadFile({ file: batchInput([{ custom_id: "ticket-1", body }]), filename: "tickets.jsonl" });
|
|
91
|
+
const batch = await client.createBatch({ input_file_id: file.id, metadata: { source: "tickets" } }, { idempotencyKey: "tickets-2026-10-20" });
|
|
92
|
+
// Later:
|
|
93
|
+
const current = await client.getBatch(batch.id);
|
|
94
|
+
if (current.status === "completed" && current.output_file_id) {
|
|
95
|
+
const lines = parseBatchResults(await client.downloadFile(current.output_file_id, { maxBytes: 256 * 1024 * 1024 }));
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
| Method | Route |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `uploadFile({file, filename, expires_after?}, {signal?, timeoutMs?})` | `POST /v1/files` |
|
|
102
|
+
| `listFiles({after?, limit?, purpose?, signal?})`, `getFile(id)`, `deleteFile(id)` | `/v1/files[/{id}]` |
|
|
103
|
+
| `downloadFile(id, {signal?, timeoutMs?, maxBytes?})` → `Uint8Array` | `GET /v1/files/{id}/content` |
|
|
104
|
+
| `createBatch({input_file_id, metadata?, output_expires_after?}, {idempotencyKey?, signal?})` | `POST /v1/batches` |
|
|
105
|
+
| `listBatches({after?, limit?, signal?})`, `getBatch(id)`, `cancelBatch(id)` | `/v1/batches[/{id}[/cancel]]` |
|
|
106
|
+
|
|
107
|
+
Uploads accept `Uint8Array`, `ArrayBuffer`, `Blob` (a browser `File`, or Node's `fs.openAsBlob`) or a
|
|
108
|
+
`ReadableStream<Uint8Array>`. They never fetch URLs or read paths. The form sends `expires_after[...]`
|
|
109
|
+
and `purpose=batch` before the file part, as the server requires. Bytes and Blobs are sent with a
|
|
110
|
+
Content-Length; a stream is sent chunked and needs streaming request bodies (`duplex: "half"`): Node.js,
|
|
111
|
+
or Chromium over HTTP/2. Otherwise pass a Blob. The SDK takes ownership of a stream: it is
|
|
112
|
+
consumed and cancelled on abort, timeout or an early server answer (e.g. 413). The filename (1–255 UTF-8 bytes)
|
|
113
|
+
must not contain control characters or backslashes; it is stored by the server as given.
|
|
114
|
+
|
|
115
|
+
`downloadFile` returns the raw bytes and never parses JSON. It buffers the body in memory and is bounded by
|
|
116
|
+
`maxResponseBytes` (8 MiB by default). For larger result files, raise `maxBytes` for that call, and
|
|
117
|
+
`timeoutMs` if needed, instead of raising the client-wide limit. `parseBatchResults` turns the bytes into result lines; their
|
|
118
|
+
`response.body` is the native answer (`DecisionResponse` for status 200, the native error body otherwise).
|
|
119
|
+
|
|
120
|
+
`createBatch` fills `endpoint: "/v1/basal"` and `completion_window: "24h"` (the only accepted values).
|
|
121
|
+
`metadata` keeps insertion order, except integer-like keys, which JavaScript enumerates first.
|
|
122
|
+
`idempotencyKey` (1–128 visible ASCII characters) makes a retried create return the same batch while its
|
|
123
|
+
metadata is retained. The SDK does not retry on its own. Resource ids and cursors are URL-encoded;
|
|
124
|
+
the server answers malformed ids like missing resources (404). `cancelBatch` returns the batch in
|
|
125
|
+
`cancelling`; cancellation ends asynchronously.
|
|
126
|
+
|
|
127
|
+
Errors use the same classes as the online API: `BasalHTTPError` with the
|
|
128
|
+
`{"error": {message, type, param, code}}` body and `retryAfter` (429/503), and
|
|
129
|
+
`BasalResponseTooLargeError` above the byte cap. Resource types preserve unknown fields at runtime, and
|
|
130
|
+
status/code enumerations accept future string values.
|
|
131
|
+
|
|
78
132
|
[Full documentation and publication setup](https://github.com/itsoltech/basal-rs/blob/main/docs/SDK.md).
|
|
79
133
|
|
|
80
134
|
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resourcePath = resourcePath;
|
|
4
|
+
exports.listPath = listPath;
|
|
5
|
+
exports.idempotencyHeader = idempotencyHeader;
|
|
6
|
+
exports.createBody = createBody;
|
|
7
|
+
exports.uploadForm = uploadForm;
|
|
8
|
+
exports.batchInput = batchInput;
|
|
9
|
+
exports.parseBatchResults = parseBatchResults;
|
|
10
|
+
const errors_cjs_1 = require("./errors.cjs");
|
|
11
|
+
const encoder = new TextEncoder();
|
|
12
|
+
/** Path segment of a resource id. `.` and `..` would be resolved as dot segments by URL parsing. */
|
|
13
|
+
function resourcePath(prefix, id, suffix = "") {
|
|
14
|
+
if (typeof id !== "string" || !id || id === "." || id === "..")
|
|
15
|
+
throw new TypeError("resource id must be a nonempty string");
|
|
16
|
+
return `${prefix}/${encodeURIComponent(id)}${suffix}`;
|
|
17
|
+
}
|
|
18
|
+
function listPath(path, options) {
|
|
19
|
+
const query = new URLSearchParams();
|
|
20
|
+
if (options.after !== undefined) {
|
|
21
|
+
if (typeof options.after !== "string" || !options.after)
|
|
22
|
+
throw new TypeError("after must be a nonempty id");
|
|
23
|
+
query.set("after", options.after);
|
|
24
|
+
}
|
|
25
|
+
if (options.limit !== undefined) {
|
|
26
|
+
if (!Number.isSafeInteger(options.limit) || options.limit < 1)
|
|
27
|
+
throw new TypeError("limit must be a positive integer");
|
|
28
|
+
query.set("limit", String(options.limit));
|
|
29
|
+
}
|
|
30
|
+
if (options.purpose !== undefined) {
|
|
31
|
+
if (typeof options.purpose !== "string" || !options.purpose)
|
|
32
|
+
throw new TypeError("purpose must be a nonempty string");
|
|
33
|
+
query.set("purpose", options.purpose);
|
|
34
|
+
}
|
|
35
|
+
const encoded = query.toString();
|
|
36
|
+
return encoded ? `${path}?${encoded}` : path;
|
|
37
|
+
}
|
|
38
|
+
function idempotencyHeader(key) {
|
|
39
|
+
if (key === undefined)
|
|
40
|
+
return {};
|
|
41
|
+
if (typeof key !== "string" || !/^[!-~]{1,128}$/.test(key)) {
|
|
42
|
+
throw new TypeError("idempotencyKey must have 1-128 visible ASCII characters");
|
|
43
|
+
}
|
|
44
|
+
return { "Idempotency-Key": key };
|
|
45
|
+
}
|
|
46
|
+
function createBody(request) {
|
|
47
|
+
if (!request || typeof request !== "object" || typeof request.input_file_id !== "string" || !request.input_file_id) {
|
|
48
|
+
throw new TypeError("input_file_id must be a nonempty string");
|
|
49
|
+
}
|
|
50
|
+
// Extra caller fields are forwarded; the server refuses fields it does not know.
|
|
51
|
+
return { endpoint: "/v1/basal", completion_window: "24h", ...request };
|
|
52
|
+
}
|
|
53
|
+
function expiresFields(expires) {
|
|
54
|
+
if (expires === undefined)
|
|
55
|
+
return [];
|
|
56
|
+
if (!expires || expires.anchor !== "created_at" || !Number.isSafeInteger(expires.seconds) || expires.seconds < 0) {
|
|
57
|
+
throw new TypeError("expires_after needs anchor created_at and whole seconds");
|
|
58
|
+
}
|
|
59
|
+
return [["expires_after[anchor]", expires.anchor], ["expires_after[seconds]", String(expires.seconds)]];
|
|
60
|
+
}
|
|
61
|
+
/** Multipart form of an upload: `expires_after[...]`, then `purpose`, then the file part last (the server reserves the
|
|
62
|
+
* upload session at the file part, so expiry fields must precede it).
|
|
63
|
+
*/
|
|
64
|
+
function uploadForm(request) {
|
|
65
|
+
if (!request || typeof request !== "object")
|
|
66
|
+
throw new TypeError("upload request must be an object");
|
|
67
|
+
const { file, filename } = request;
|
|
68
|
+
if (request.purpose !== undefined && request.purpose !== "batch")
|
|
69
|
+
throw new TypeError("purpose must be batch");
|
|
70
|
+
// Control characters (CR/LF) would split the part header; a backslash is ambiguous in a quoted filename.
|
|
71
|
+
if (typeof filename !== "string" || !filename || encoder.encode(filename).length > 255 || /[\p{Cc}\\]/u.test(filename)) {
|
|
72
|
+
throw new TypeError("filename must have 1-255 UTF-8 bytes without control characters or backslashes");
|
|
73
|
+
}
|
|
74
|
+
const random = new Uint8Array(16);
|
|
75
|
+
globalThis.crypto.getRandomValues(random);
|
|
76
|
+
const boundary = `basal-${Array.from(random, b => b.toString(16).padStart(2, "0")).join("")}`;
|
|
77
|
+
let head = "";
|
|
78
|
+
for (const [name, value] of [...expiresFields(request.expires_after), ["purpose", "batch"]]) {
|
|
79
|
+
head += `--${boundary}\r\nContent-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`;
|
|
80
|
+
}
|
|
81
|
+
head += `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${filename.replaceAll('"', '\\"')}"\r\n`
|
|
82
|
+
+ "Content-Type: application/jsonl\r\n\r\n";
|
|
83
|
+
const prefix = encoder.encode(head);
|
|
84
|
+
const suffix = encoder.encode(`\r\n--${boundary}--\r\n`);
|
|
85
|
+
const contentType = `multipart/form-data; boundary=${boundary}`;
|
|
86
|
+
if (file instanceof ReadableStream) {
|
|
87
|
+
if (file.locked)
|
|
88
|
+
throw new TypeError("upload stream is locked");
|
|
89
|
+
return { contentType, body: signal => framedStream(prefix, file, suffix, signal) };
|
|
90
|
+
}
|
|
91
|
+
let content;
|
|
92
|
+
if (file instanceof Blob || file instanceof ArrayBuffer)
|
|
93
|
+
content = file;
|
|
94
|
+
else if (file instanceof Uint8Array)
|
|
95
|
+
content = file.buffer instanceof ArrayBuffer ? file : new Uint8Array(file);
|
|
96
|
+
else
|
|
97
|
+
throw new TypeError("file must be a Uint8Array, ArrayBuffer, Blob or ReadableStream");
|
|
98
|
+
return { contentType, body: new Blob([prefix, content, suffix]) };
|
|
99
|
+
}
|
|
100
|
+
/** Streams the multipart prefix, the caller's chunks and the suffix without buffering the file.
|
|
101
|
+
* Fetch implementations may keep pulling a request body after abort, so `signal` cancels the source here.
|
|
102
|
+
*/
|
|
103
|
+
function framedStream(prefix, source, suffix, signal) {
|
|
104
|
+
let reader;
|
|
105
|
+
let stage = "prefix";
|
|
106
|
+
const stop = (reason) => {
|
|
107
|
+
signal.removeEventListener("abort", onAbort);
|
|
108
|
+
if (stage === "finished" || stage === "stopped")
|
|
109
|
+
return Promise.resolve();
|
|
110
|
+
stage = "stopped";
|
|
111
|
+
return (reader ? reader.cancel(reason) : source.cancel(reason)).catch(() => { });
|
|
112
|
+
};
|
|
113
|
+
const onAbort = () => { void stop(signal.reason); };
|
|
114
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
115
|
+
if (signal.aborted)
|
|
116
|
+
onAbort();
|
|
117
|
+
return new ReadableStream({
|
|
118
|
+
async pull(controller) {
|
|
119
|
+
if (stage === "stopped")
|
|
120
|
+
throw signal.reason ?? new Error("upload stopped");
|
|
121
|
+
if (stage === "prefix") {
|
|
122
|
+
stage = "file";
|
|
123
|
+
controller.enqueue(prefix);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (stage === "finished") {
|
|
127
|
+
controller.close();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
reader ??= source.getReader();
|
|
131
|
+
const { done, value } = await reader.read();
|
|
132
|
+
// A read cancelled by `stop` ends as done; it must not be framed as a complete file.
|
|
133
|
+
if (stage !== "file")
|
|
134
|
+
throw signal.reason ?? new Error("upload stopped");
|
|
135
|
+
if (!done) {
|
|
136
|
+
if (!(value instanceof Uint8Array)) {
|
|
137
|
+
const error = new TypeError("upload stream chunks must be Uint8Array");
|
|
138
|
+
await stop(error);
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
controller.enqueue(value);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
reader.releaseLock();
|
|
145
|
+
reader = undefined;
|
|
146
|
+
stage = "finished";
|
|
147
|
+
signal.removeEventListener("abort", onAbort);
|
|
148
|
+
controller.enqueue(suffix);
|
|
149
|
+
},
|
|
150
|
+
cancel(reason) { return stop(reason); },
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/** JSONL input file of native records (`method` POST and `url` /v1/basal are filled in). Never sends anything. */
|
|
154
|
+
function batchInput(lines) {
|
|
155
|
+
const out = [];
|
|
156
|
+
for (const line of lines) {
|
|
157
|
+
if (!line || typeof line.custom_id !== "string" || !line.custom_id || !line.body || typeof line.body !== "object") {
|
|
158
|
+
throw new TypeError("each batch line needs a custom_id and a native request body");
|
|
159
|
+
}
|
|
160
|
+
out.push(JSON.stringify({ custom_id: line.custom_id, method: line.method ?? "POST", url: line.url ?? "/v1/basal", body: line.body }));
|
|
161
|
+
}
|
|
162
|
+
if (!out.length)
|
|
163
|
+
throw new TypeError("a batch input needs at least one line");
|
|
164
|
+
return encoder.encode(out.join("\n") + "\n");
|
|
165
|
+
}
|
|
166
|
+
/** Parse downloaded result bytes (output or error file) into lines. Never fetches anything. */
|
|
167
|
+
function parseBatchResults(content) {
|
|
168
|
+
let text;
|
|
169
|
+
try {
|
|
170
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
171
|
+
}
|
|
172
|
+
catch (cause) {
|
|
173
|
+
throw new errors_cjs_1.BasalProtocolError("Batch result file is not UTF-8", { cause });
|
|
174
|
+
}
|
|
175
|
+
const lines = text.split("\n");
|
|
176
|
+
if (lines[lines.length - 1] === "")
|
|
177
|
+
lines.pop();
|
|
178
|
+
return lines.map((line, index) => {
|
|
179
|
+
let value;
|
|
180
|
+
try {
|
|
181
|
+
value = JSON.parse(line);
|
|
182
|
+
}
|
|
183
|
+
catch (cause) {
|
|
184
|
+
throw new errors_cjs_1.BasalProtocolError(`Batch result line ${index + 1} is not JSON`, { cause });
|
|
185
|
+
}
|
|
186
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
187
|
+
throw new errors_cjs_1.BasalProtocolError(`Batch result line ${index + 1} is not a JSON object`);
|
|
188
|
+
}
|
|
189
|
+
return value;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type { DecisionRequest, JsonValue } from "./index.cjs";
|
|
2
|
+
/** Batch and Files wire types (`contracts/batches/openapi.json`). Times are Unix seconds.
|
|
3
|
+
* Unknown response fields are preserved at runtime; `(string & {})` keeps enums open to future server values.
|
|
4
|
+
*/
|
|
5
|
+
export type FilePurpose = "batch" | "batch_output";
|
|
6
|
+
export interface FileObject {
|
|
7
|
+
id: string;
|
|
8
|
+
object: "file";
|
|
9
|
+
bytes: number;
|
|
10
|
+
created_at: number;
|
|
11
|
+
expires_at: number | null;
|
|
12
|
+
/** As uploaded, or `batch_<id>_output.jsonl` / `batch_<id>_error.jsonl` for results; never a server path. */
|
|
13
|
+
filename: string;
|
|
14
|
+
purpose: FilePurpose | (string & {});
|
|
15
|
+
/** Always `processed` (OpenAI compatibility). */
|
|
16
|
+
status: "processed" | (string & {});
|
|
17
|
+
status_details: string | null;
|
|
18
|
+
}
|
|
19
|
+
export interface DeletedFile {
|
|
20
|
+
id: string;
|
|
21
|
+
object: "file";
|
|
22
|
+
deleted: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface ListPage<T> {
|
|
25
|
+
object: "list";
|
|
26
|
+
data: T[];
|
|
27
|
+
first_id: string | null;
|
|
28
|
+
last_id: string | null;
|
|
29
|
+
has_more: boolean;
|
|
30
|
+
}
|
|
31
|
+
export type FileList = ListPage<FileObject>;
|
|
32
|
+
export type BatchList = ListPage<Batch>;
|
|
33
|
+
/** At most 16 pairs, keys of 1–64 and values of up to 512 characters, in insertion order (checked by the server).
|
|
34
|
+
* JavaScript enumerates integer-like keys first; use textual keys when order matters.
|
|
35
|
+
*/
|
|
36
|
+
export type Metadata = Record<string, string>;
|
|
37
|
+
export interface ExpiresAfter {
|
|
38
|
+
anchor: "created_at";
|
|
39
|
+
seconds: number;
|
|
40
|
+
}
|
|
41
|
+
export type BatchStatus = "validating" | "failed" | "in_progress" | "finalizing" | "completed" | "expired" | "cancelling" | "cancelled";
|
|
42
|
+
export type BatchErrorCode = "empty_file" | "empty_line" | "invalid_json_line" | "line_too_large" | "too_many_records" | "invalid_custom_id" | "duplicate_custom_id" | "invalid_method" | "invalid_url" | "stream_unsupported" | "invalid_body" | "model_not_found" | "mixed_models" | "budget_exceeded" | "host_budget_exceeded" | "unbounded_feature" | "input_changed" | "batch_expired" | "errors_truncated";
|
|
43
|
+
export interface RequestCounts {
|
|
44
|
+
total: number;
|
|
45
|
+
completed: number;
|
|
46
|
+
failed: number;
|
|
47
|
+
}
|
|
48
|
+
export interface BatchErrorEntry {
|
|
49
|
+
code: BatchErrorCode | (string & {});
|
|
50
|
+
message: string;
|
|
51
|
+
param: string | null;
|
|
52
|
+
line: number | null;
|
|
53
|
+
}
|
|
54
|
+
export interface BatchErrors {
|
|
55
|
+
object: "list";
|
|
56
|
+
data: BatchErrorEntry[];
|
|
57
|
+
}
|
|
58
|
+
export interface Batch {
|
|
59
|
+
id: string;
|
|
60
|
+
object: "batch";
|
|
61
|
+
endpoint: "/v1/basal" | (string & {});
|
|
62
|
+
model: string | null;
|
|
63
|
+
errors: BatchErrors | null;
|
|
64
|
+
input_file_id: string;
|
|
65
|
+
completion_window: "24h" | (string & {});
|
|
66
|
+
status: BatchStatus | (string & {});
|
|
67
|
+
output_file_id: string | null;
|
|
68
|
+
error_file_id: string | null;
|
|
69
|
+
created_at: number;
|
|
70
|
+
in_progress_at: number | null;
|
|
71
|
+
/** End of the 24h inference window; export may finish later. */
|
|
72
|
+
expires_at: number;
|
|
73
|
+
finalizing_at: number | null;
|
|
74
|
+
completed_at: number | null;
|
|
75
|
+
failed_at: number | null;
|
|
76
|
+
expired_at: number | null;
|
|
77
|
+
cancelling_at: number | null;
|
|
78
|
+
cancelled_at: number | null;
|
|
79
|
+
request_counts: RequestCounts;
|
|
80
|
+
metadata: Metadata | null;
|
|
81
|
+
}
|
|
82
|
+
/** `POST /v1/batches` body. `endpoint` and `completion_window` default to their only accepted values. */
|
|
83
|
+
export interface CreateBatchRequest {
|
|
84
|
+
input_file_id: string;
|
|
85
|
+
endpoint?: "/v1/basal";
|
|
86
|
+
completion_window?: "24h";
|
|
87
|
+
metadata?: Metadata | null;
|
|
88
|
+
/** Lifetime of each result file, anchored at its creation. */
|
|
89
|
+
output_expires_after?: ExpiresAfter | null;
|
|
90
|
+
}
|
|
91
|
+
/** One line of an input file; `body` is the native `/v1/basal` request (see `buildRequest`). */
|
|
92
|
+
export interface BatchInputLine {
|
|
93
|
+
custom_id: string;
|
|
94
|
+
method?: "POST";
|
|
95
|
+
url?: "/v1/basal";
|
|
96
|
+
body: DecisionRequest;
|
|
97
|
+
}
|
|
98
|
+
/** One line of a result file. `response.body` is the native answer: a `DecisionResponse` for status 200, otherwise
|
|
99
|
+
* the native error body. `error` is set when the record has no response (e.g. `batch_expired`, `result_too_large`).
|
|
100
|
+
*/
|
|
101
|
+
export interface BatchResultLine {
|
|
102
|
+
id: string;
|
|
103
|
+
custom_id: string;
|
|
104
|
+
response: {
|
|
105
|
+
status_code: number;
|
|
106
|
+
request_id: string;
|
|
107
|
+
body: {
|
|
108
|
+
[key: string]: JsonValue;
|
|
109
|
+
};
|
|
110
|
+
} | null;
|
|
111
|
+
error: {
|
|
112
|
+
code: string;
|
|
113
|
+
message: string;
|
|
114
|
+
} | null;
|
|
115
|
+
}
|
|
116
|
+
/** Common options of Batch and Files calls. */
|
|
117
|
+
export interface RequestOptions {
|
|
118
|
+
signal?: AbortSignal;
|
|
119
|
+
}
|
|
120
|
+
/** Upload and download options; `timeoutMs` replaces the client deadline for this call only. */
|
|
121
|
+
export interface TransferOptions extends RequestOptions {
|
|
122
|
+
timeoutMs?: number;
|
|
123
|
+
}
|
|
124
|
+
export interface DownloadOptions extends TransferOptions {
|
|
125
|
+
/** Byte cap of this download; defaults to the client's `maxResponseBytes`. The body is buffered in memory. */
|
|
126
|
+
maxBytes?: number;
|
|
127
|
+
}
|
|
128
|
+
export interface CreateBatchOptions extends RequestOptions {
|
|
129
|
+
/** 1–128 visible ASCII characters; a replay with the same request data returns the existing batch. */
|
|
130
|
+
idempotencyKey?: string;
|
|
131
|
+
}
|
|
132
|
+
export interface ListOptions extends RequestOptions {
|
|
133
|
+
after?: string;
|
|
134
|
+
limit?: number;
|
|
135
|
+
}
|
|
136
|
+
export interface ListFilesOptions extends ListOptions {
|
|
137
|
+
purpose?: FilePurpose;
|
|
138
|
+
}
|
|
139
|
+
/** Caller-owned upload content. The SDK never fetches URLs or reads paths; a stream is consumed and locked. */
|
|
140
|
+
export type UploadContent = Uint8Array | ArrayBuffer | Blob | ReadableStream<Uint8Array>;
|
|
141
|
+
export interface UploadFileRequest {
|
|
142
|
+
file: UploadContent;
|
|
143
|
+
/** 1–255 UTF-8 bytes without control characters or backslashes; stored as given, never used as a path. */
|
|
144
|
+
filename: string;
|
|
145
|
+
purpose?: "batch";
|
|
146
|
+
/** File lifetime (server default 30 days). */
|
|
147
|
+
expires_after?: ExpiresAfter;
|
|
148
|
+
}
|
|
149
|
+
/** Path segment of a resource id. `.` and `..` would be resolved as dot segments by URL parsing. */
|
|
150
|
+
export declare function resourcePath(prefix: string, id: string, suffix?: string): string;
|
|
151
|
+
export declare function listPath(path: string, options: ListFilesOptions): string;
|
|
152
|
+
export declare function idempotencyHeader(key: string | undefined): Record<string, string>;
|
|
153
|
+
export declare function createBody(request: CreateBatchRequest): CreateBatchRequest;
|
|
154
|
+
/** A multipart body; a stream body is created per request and stops its source when `signal` aborts. */
|
|
155
|
+
export interface Multipart {
|
|
156
|
+
contentType: string;
|
|
157
|
+
body: Blob | ((signal: AbortSignal) => ReadableStream<Uint8Array>);
|
|
158
|
+
}
|
|
159
|
+
/** Multipart form of an upload: `expires_after[...]`, then `purpose`, then the file part last (the server reserves the
|
|
160
|
+
* upload session at the file part, so expiry fields must precede it).
|
|
161
|
+
*/
|
|
162
|
+
export declare function uploadForm(request: UploadFileRequest): Multipart;
|
|
163
|
+
/** JSONL input file of native records (`method` POST and `url` /v1/basal are filled in). Never sends anything. */
|
|
164
|
+
export declare function batchInput(lines: Iterable<BatchInputLine>): Uint8Array;
|
|
165
|
+
/** Parse downloaded result bytes (output or error file) into lines. Never fetches anything. */
|
|
166
|
+
export declare function parseBatchResults(content: Uint8Array): BatchResultLine[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
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;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare class BasalError extends Error {
|
|
2
|
+
}
|
|
3
|
+
/** Non-2xx response. Server body and Retry-After are available without logging them. */
|
|
4
|
+
export declare class BasalHTTPError extends BasalError {
|
|
5
|
+
readonly statusCode: number;
|
|
6
|
+
readonly body: unknown;
|
|
7
|
+
readonly retryAfter: string | null;
|
|
8
|
+
constructor(statusCode: number, body: unknown, retryAfter: string | null);
|
|
9
|
+
}
|
|
10
|
+
export declare class BasalTransportError extends BasalError {
|
|
11
|
+
}
|
|
12
|
+
export declare class BasalProtocolError extends BasalError {
|
|
13
|
+
}
|
|
14
|
+
export declare class BasalResponseTooLargeError extends BasalError {
|
|
15
|
+
}
|