@itsoltech/basal-client 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.
@@ -0,0 +1,108 @@
1
+ import { QuestionSpec } from './questions.cjs';
2
+ import type { DecisionResponse, JsonValue, Question, MultiObjective } from './index.cjs';
3
+ /** Task automation is unknown (null) without a threshold certified for that task/error target. */
4
+ export type Automation = Record<string, boolean | null>;
5
+ export interface ClassifyAnswer<K extends string = string> {
6
+ type: 'classify';
7
+ experimental: true;
8
+ scores: Record<K, number>;
9
+ label?: K;
10
+ top_label?: K;
11
+ confidence?: number;
12
+ automated?: Automation;
13
+ labels?: K[];
14
+ set_confidence?: number;
15
+ }
16
+ export interface ClaimResult {
17
+ verdict: 'supported' | 'contradicted' | 'not_stated' | 'not_supported';
18
+ probabilities: Record<string, number>;
19
+ confidence: number;
20
+ automated: Automation;
21
+ }
22
+ export interface VerifyAnswer {
23
+ type: 'verify';
24
+ experimental: true;
25
+ claims: Record<string, ClaimResult>;
26
+ all_supported: boolean;
27
+ not_supported: string[];
28
+ }
29
+ /** Inclusive start/exclusive end count Unicode code points, not JavaScript UTF-16 units. */
30
+ export interface CompressionUnit {
31
+ start: number;
32
+ end: number;
33
+ probability: number;
34
+ }
35
+ export interface CompressAnswer {
36
+ type: 'compress';
37
+ experimental: true;
38
+ compressed: string;
39
+ kept: number[];
40
+ units: CompressionUnit[];
41
+ unit: 'sentence' | 'line' | 'paragraph';
42
+ original_chars: number;
43
+ compressed_chars: number;
44
+ ratio: number;
45
+ }
46
+ export interface ExtractField {
47
+ value: JsonValue;
48
+ confidence: number;
49
+ source: 'choice' | 'span';
50
+ calibration: 'validated' | 'per decision';
51
+ probabilities?: Record<string, number>;
52
+ present_probability?: number;
53
+ text?: string;
54
+ start?: number;
55
+ end?: number;
56
+ line_probability?: number;
57
+ span_probability?: number;
58
+ error?: string;
59
+ }
60
+ export interface ExtractAnswer {
61
+ type: 'extract';
62
+ experimental: true;
63
+ data: Record<string, JsonValue>;
64
+ fields: Record<string, ExtractField>;
65
+ }
66
+ export type TaskAnswer = ClassifyAnswer | VerifyAnswer | CompressAnswer | ExtractAnswer;
67
+ export interface ClassifyOptions {
68
+ multi_label?: boolean;
69
+ none?: boolean;
70
+ max_error?: number | readonly number[];
71
+ option_keys?: 'show' | 'hide';
72
+ threshold?: number;
73
+ min?: number;
74
+ max?: number;
75
+ objective?: MultiObjective;
76
+ }
77
+ export interface VerifyOptions {
78
+ binary?: boolean;
79
+ max_error?: number | readonly number[];
80
+ }
81
+ export interface CompressOptions {
82
+ unit?: 'sentence' | 'line' | 'paragraph';
83
+ keep_ratio?: number;
84
+ threshold?: number;
85
+ min_keep?: number;
86
+ }
87
+ declare abstract class TaskQuestion<A extends TaskAnswer> extends QuestionSpec<A> {
88
+ protected constructor(kind: A['type'], text: string, fields: Question);
89
+ read(response: DecisionResponse, id: string): A;
90
+ }
91
+ export declare class ClassifyQuestion<K extends string = string> extends TaskQuestion<ClassifyAnswer<K>> {
92
+ constructor(text: string, labels: Record<K, JsonValue> | readonly JsonValue[], options?: ClassifyOptions);
93
+ }
94
+ export declare class VerifyQuestion extends TaskQuestion<VerifyAnswer> {
95
+ constructor(text: string, claims: Record<string, JsonValue> | readonly JsonValue[], options?: VerifyOptions);
96
+ }
97
+ export declare class CompressQuestion extends TaskQuestion<CompressAnswer> {
98
+ constructor(text?: string, options?: CompressOptions);
99
+ }
100
+ export declare class ExtractQuestion extends TaskQuestion<ExtractAnswer> {
101
+ constructor(text: string, schema: Record<string, JsonValue>);
102
+ }
103
+ export declare function classify<const K extends string>(text: string, labels: Record<K, JsonValue>, options?: ClassifyOptions): ClassifyQuestion<K>;
104
+ export declare function classify(text: string, labels: readonly JsonValue[], options?: ClassifyOptions): ClassifyQuestion;
105
+ export declare function verify(text: string, claims: Record<string, JsonValue> | readonly JsonValue[], options?: VerifyOptions): VerifyQuestion;
106
+ export declare function compress(text?: string, options?: CompressOptions): CompressQuestion;
107
+ export declare function extract(text: string, schema: Record<string, JsonValue>): ExtractQuestion;
108
+ export {};
@@ -0,0 +1,166 @@
1
+ import type { DecisionRequest, JsonValue } from "./index.js";
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,182 @@
1
+ import { BasalProtocolError } from "./errors.js";
2
+ const encoder = new TextEncoder();
3
+ /** Path segment of a resource id. `.` and `..` would be resolved as dot segments by URL parsing. */
4
+ export function resourcePath(prefix, id, suffix = "") {
5
+ if (typeof id !== "string" || !id || id === "." || id === "..")
6
+ throw new TypeError("resource id must be a nonempty string");
7
+ return `${prefix}/${encodeURIComponent(id)}${suffix}`;
8
+ }
9
+ export function listPath(path, options) {
10
+ const query = new URLSearchParams();
11
+ if (options.after !== undefined) {
12
+ if (typeof options.after !== "string" || !options.after)
13
+ throw new TypeError("after must be a nonempty id");
14
+ query.set("after", options.after);
15
+ }
16
+ if (options.limit !== undefined) {
17
+ if (!Number.isSafeInteger(options.limit) || options.limit < 1)
18
+ throw new TypeError("limit must be a positive integer");
19
+ query.set("limit", String(options.limit));
20
+ }
21
+ if (options.purpose !== undefined) {
22
+ if (typeof options.purpose !== "string" || !options.purpose)
23
+ throw new TypeError("purpose must be a nonempty string");
24
+ query.set("purpose", options.purpose);
25
+ }
26
+ const encoded = query.toString();
27
+ return encoded ? `${path}?${encoded}` : path;
28
+ }
29
+ export function idempotencyHeader(key) {
30
+ if (key === undefined)
31
+ return {};
32
+ if (typeof key !== "string" || !/^[!-~]{1,128}$/.test(key)) {
33
+ throw new TypeError("idempotencyKey must have 1-128 visible ASCII characters");
34
+ }
35
+ return { "Idempotency-Key": key };
36
+ }
37
+ export function createBody(request) {
38
+ if (!request || typeof request !== "object" || typeof request.input_file_id !== "string" || !request.input_file_id) {
39
+ throw new TypeError("input_file_id must be a nonempty string");
40
+ }
41
+ // Extra caller fields are forwarded; the server refuses fields it does not know.
42
+ return { endpoint: "/v1/basal", completion_window: "24h", ...request };
43
+ }
44
+ function expiresFields(expires) {
45
+ if (expires === undefined)
46
+ return [];
47
+ if (!expires || expires.anchor !== "created_at" || !Number.isSafeInteger(expires.seconds) || expires.seconds < 0) {
48
+ throw new TypeError("expires_after needs anchor created_at and whole seconds");
49
+ }
50
+ return [["expires_after[anchor]", expires.anchor], ["expires_after[seconds]", String(expires.seconds)]];
51
+ }
52
+ /** Multipart form of an upload: `expires_after[...]`, then `purpose`, then the file part last (the server reserves the
53
+ * upload session at the file part, so expiry fields must precede it).
54
+ */
55
+ export function uploadForm(request) {
56
+ if (!request || typeof request !== "object")
57
+ throw new TypeError("upload request must be an object");
58
+ const { file, filename } = request;
59
+ if (request.purpose !== undefined && request.purpose !== "batch")
60
+ throw new TypeError("purpose must be batch");
61
+ // Control characters (CR/LF) would split the part header; a backslash is ambiguous in a quoted filename.
62
+ if (typeof filename !== "string" || !filename || encoder.encode(filename).length > 255 || /[\p{Cc}\\]/u.test(filename)) {
63
+ throw new TypeError("filename must have 1-255 UTF-8 bytes without control characters or backslashes");
64
+ }
65
+ const random = new Uint8Array(16);
66
+ globalThis.crypto.getRandomValues(random);
67
+ const boundary = `basal-${Array.from(random, b => b.toString(16).padStart(2, "0")).join("")}`;
68
+ let head = "";
69
+ for (const [name, value] of [...expiresFields(request.expires_after), ["purpose", "batch"]]) {
70
+ head += `--${boundary}\r\nContent-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`;
71
+ }
72
+ head += `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${filename.replaceAll('"', '\\"')}"\r\n`
73
+ + "Content-Type: application/jsonl\r\n\r\n";
74
+ const prefix = encoder.encode(head);
75
+ const suffix = encoder.encode(`\r\n--${boundary}--\r\n`);
76
+ const contentType = `multipart/form-data; boundary=${boundary}`;
77
+ if (file instanceof ReadableStream) {
78
+ if (file.locked)
79
+ throw new TypeError("upload stream is locked");
80
+ return { contentType, body: signal => framedStream(prefix, file, suffix, signal) };
81
+ }
82
+ let content;
83
+ if (file instanceof Blob || file instanceof ArrayBuffer)
84
+ content = file;
85
+ else if (file instanceof Uint8Array)
86
+ content = file.buffer instanceof ArrayBuffer ? file : new Uint8Array(file);
87
+ else
88
+ throw new TypeError("file must be a Uint8Array, ArrayBuffer, Blob or ReadableStream");
89
+ return { contentType, body: new Blob([prefix, content, suffix]) };
90
+ }
91
+ /** Streams the multipart prefix, the caller's chunks and the suffix without buffering the file.
92
+ * Fetch implementations may keep pulling a request body after abort, so `signal` cancels the source here.
93
+ */
94
+ function framedStream(prefix, source, suffix, signal) {
95
+ let reader;
96
+ let stage = "prefix";
97
+ const stop = (reason) => {
98
+ signal.removeEventListener("abort", onAbort);
99
+ if (stage === "finished" || stage === "stopped")
100
+ return Promise.resolve();
101
+ stage = "stopped";
102
+ return (reader ? reader.cancel(reason) : source.cancel(reason)).catch(() => { });
103
+ };
104
+ const onAbort = () => { void stop(signal.reason); };
105
+ signal.addEventListener("abort", onAbort, { once: true });
106
+ if (signal.aborted)
107
+ onAbort();
108
+ return new ReadableStream({
109
+ async pull(controller) {
110
+ if (stage === "stopped")
111
+ throw signal.reason ?? new Error("upload stopped");
112
+ if (stage === "prefix") {
113
+ stage = "file";
114
+ controller.enqueue(prefix);
115
+ return;
116
+ }
117
+ if (stage === "finished") {
118
+ controller.close();
119
+ return;
120
+ }
121
+ reader ??= source.getReader();
122
+ const { done, value } = await reader.read();
123
+ // A read cancelled by `stop` ends as done; it must not be framed as a complete file.
124
+ if (stage !== "file")
125
+ throw signal.reason ?? new Error("upload stopped");
126
+ if (!done) {
127
+ if (!(value instanceof Uint8Array)) {
128
+ const error = new TypeError("upload stream chunks must be Uint8Array");
129
+ await stop(error);
130
+ throw error;
131
+ }
132
+ controller.enqueue(value);
133
+ return;
134
+ }
135
+ reader.releaseLock();
136
+ reader = undefined;
137
+ stage = "finished";
138
+ signal.removeEventListener("abort", onAbort);
139
+ controller.enqueue(suffix);
140
+ },
141
+ cancel(reason) { return stop(reason); },
142
+ });
143
+ }
144
+ /** JSONL input file of native records (`method` POST and `url` /v1/basal are filled in). Never sends anything. */
145
+ export function batchInput(lines) {
146
+ const out = [];
147
+ for (const line of lines) {
148
+ if (!line || typeof line.custom_id !== "string" || !line.custom_id || !line.body || typeof line.body !== "object") {
149
+ throw new TypeError("each batch line needs a custom_id and a native request body");
150
+ }
151
+ out.push(JSON.stringify({ custom_id: line.custom_id, method: line.method ?? "POST", url: line.url ?? "/v1/basal", body: line.body }));
152
+ }
153
+ if (!out.length)
154
+ throw new TypeError("a batch input needs at least one line");
155
+ return encoder.encode(out.join("\n") + "\n");
156
+ }
157
+ /** Parse downloaded result bytes (output or error file) into lines. Never fetches anything. */
158
+ export function parseBatchResults(content) {
159
+ let text;
160
+ try {
161
+ text = new TextDecoder("utf-8", { fatal: true }).decode(content);
162
+ }
163
+ catch (cause) {
164
+ throw new BasalProtocolError("Batch result file is not UTF-8", { cause });
165
+ }
166
+ const lines = text.split("\n");
167
+ if (lines[lines.length - 1] === "")
168
+ lines.pop();
169
+ return lines.map((line, index) => {
170
+ let value;
171
+ try {
172
+ value = JSON.parse(line);
173
+ }
174
+ catch (cause) {
175
+ throw new BasalProtocolError(`Batch result line ${index + 1} is not JSON`, { cause });
176
+ }
177
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
178
+ throw new BasalProtocolError(`Batch result line ${index + 1} is not a JSON object`);
179
+ }
180
+ return value;
181
+ });
182
+ }
@@ -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
+ }
@@ -0,0 +1,23 @@
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
+ }
@@ -1,6 +1,12 @@
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";
2
3
  export * from "./questions.js";
3
4
  export * from "./media.js";
5
+ export * from "./tasks.js";
6
+ export * from "./errors.js";
7
+ export { batchInput, parseBatchResults } from "./batches.js";
8
+ export type { Batch, BatchErrorCode, BatchErrorEntry, BatchErrors, BatchInputLine, BatchList, BatchResultLine, BatchStatus, CreateBatchOptions, CreateBatchRequest, DeletedFile, DownloadOptions, ExpiresAfter, FileList, FileObject, FilePurpose, ListFilesOptions, ListOptions, ListPage, Metadata, RequestCounts, RequestOptions, TransferOptions, UploadContent, UploadFileRequest, } from "./batches.js";
9
+ import type { TaskAnswer } from "./tasks.js";
4
10
  /** Native Basal wire types. Criterion order is significant; do not sort keys. */
5
11
  export type JsonValue = string | number | boolean | null | JsonValue[] | {
6
12
  [key: string]: JsonValue;
@@ -23,7 +29,7 @@ export interface ToolRanking {
23
29
  }
24
30
  export type ToolDefinition = Record<string, JsonValue>;
25
31
  export interface Question {
26
- type?: "choice" | "noul" | "score" | "multi" | "act" | "tool";
32
+ type?: "choice" | "noul" | "score" | "multi" | "act" | "tool" | "classify" | "verify" | "compress" | "extract";
27
33
  instructions?: JsonValue;
28
34
  criteria?: Record<string, JsonValue> | JsonValue[];
29
35
  levels?: Record<string, JsonValue> | JsonValue[];
@@ -35,7 +41,7 @@ export interface Question {
35
41
  base?: "noul" | "choice" | "score";
36
42
  costs?: Record<string, number> | Record<string, Record<string, number>>;
37
43
  defer_action?: string;
38
- max_error?: number;
44
+ max_error?: number | readonly number[];
39
45
  objective?: MultiObjective;
40
46
  exclusive?: readonly string[];
41
47
  fallback?: string;
@@ -43,7 +49,17 @@ export interface Question {
43
49
  tools?: readonly ToolDefinition[];
44
50
  extra?: readonly ("answer_directly" | "ask_user" | "none")[];
45
51
  rank?: number;
52
+ description_chars?: number;
46
53
  multi?: boolean;
54
+ labels?: Record<string, JsonValue> | JsonValue[];
55
+ multi_label?: boolean;
56
+ none?: boolean;
57
+ claims?: Record<string, JsonValue> | JsonValue[];
58
+ binary?: boolean;
59
+ unit?: 'sentence' | 'line' | 'paragraph';
60
+ keep_ratio?: number;
61
+ min_keep?: number;
62
+ schema?: Record<string, JsonValue>;
47
63
  }
48
64
  export interface DecisionRequest {
49
65
  state: JsonValue;
@@ -122,7 +138,7 @@ export interface ActAnswer<K extends string = string, A extends string = string>
122
138
  max_error?: string;
123
139
  deferred?: boolean;
124
140
  }
125
- export type Answer = ChoiceAnswer | NoulAnswer | ScoreAnswer | MultiAnswer | ActAnswer | ToolAnswer;
141
+ export type Answer = ChoiceAnswer | NoulAnswer | ScoreAnswer | MultiAnswer | ActAnswer | ToolAnswer | TaskAnswer;
126
142
  export interface Usage {
127
143
  input_tokens: number;
128
144
  output_tokens: number;
@@ -152,21 +168,6 @@ export interface ModelMetadata {
152
168
  export interface ModelsResponse {
153
169
  models: ModelMetadata[];
154
170
  }
155
- export declare class BasalError extends Error {
156
- }
157
- /** Non-2xx response. Server body and Retry-After are available without logging them. */
158
- export declare class BasalHTTPError extends BasalError {
159
- readonly statusCode: number;
160
- readonly body: unknown;
161
- readonly retryAfter: string | null;
162
- constructor(statusCode: number, body: unknown, retryAfter: string | null);
163
- }
164
- export declare class BasalTransportError extends BasalError {
165
- }
166
- export declare class BasalProtocolError extends BasalError {
167
- }
168
- export declare class BasalResponseTooLargeError extends BasalError {
169
- }
170
171
  /** Web Fetch input accepted by browsers and server runtimes. */
171
172
  export type RequestInput = string | URL | Request;
172
173
  /** A transport adapter must honor signal/redirect and return a standard Web Response. */
@@ -190,7 +191,20 @@ export declare class BasalClient {
190
191
  private readonly fetch;
191
192
  constructor(options?: BasalClientOptions);
192
193
  private request;
194
+ private call;
193
195
  decide(request: DecisionRequest, signal?: AbortSignal): Promise<DecisionResponse>;
194
196
  decide<const Q extends QuestionSet>(state: JsonValue, questions: Q, options?: DecisionOptions): Promise<TypedDecisionResponse<Q>>;
195
197
  models(signal?: AbortSignal): Promise<ModelsResponse>;
198
+ /** Upload a JSONL input file (`purpose=batch`) from caller-owned bytes, a Blob or a stream; never fetches URLs. */
199
+ uploadFile(request: UploadFileRequest, options?: TransferOptions): Promise<FileObject>;
200
+ listFiles(options?: ListFilesOptions): Promise<FileList>;
201
+ getFile(fileId: string, options?: RequestOptions): Promise<FileObject>;
202
+ deleteFile(fileId: string, options?: RequestOptions): Promise<DeletedFile>;
203
+ /** Raw file bytes (JSONL), buffered up to `maxBytes` (default `maxResponseBytes`); never JSON-parsed. */
204
+ downloadFile(fileId: string, options?: DownloadOptions): Promise<Uint8Array>;
205
+ createBatch(request: CreateBatchRequest, options?: CreateBatchOptions): Promise<Batch>;
206
+ listBatches(options?: ListOptions): Promise<BatchList>;
207
+ getBatch(batchId: string, options?: RequestOptions): Promise<Batch>;
208
+ /** Request cancellation; the batch moves to `cancelling` and later `cancelled`. Does not wait. */
209
+ cancelBatch(batchId: string, options?: RequestOptions): Promise<Batch>;
196
210
  }