@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.
@@ -14,48 +14,26 @@ 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.BasalResponseTooLargeError = exports.BasalProtocolError = exports.BasalTransportError = exports.BasalHTTPError = exports.BasalError = void 0;
17
+ exports.BasalClient = exports.parseBatchResults = 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");
19
21
  __exportStar(require("./questions.cjs"), exports);
20
22
  __exportStar(require("./media.cjs"), exports);
21
- class BasalError extends Error {
22
- }
23
- exports.BasalError = BasalError;
24
- /** Non-2xx response. Server body and Retry-After are available without logging them. */
25
- class BasalHTTPError extends BasalError {
26
- statusCode;
27
- body;
28
- retryAfter;
29
- constructor(statusCode, body, retryAfter) {
30
- super(`Basal HTTP ${statusCode}`);
31
- this.statusCode = statusCode;
32
- this.body = body;
33
- this.retryAfter = retryAfter;
34
- this.name = "BasalHTTPError";
35
- // Node's default inspection and JSON.stringify must not print reflected state or credentials.
36
- Object.defineProperty(this, "body", { enumerable: false });
37
- }
38
- }
39
- exports.BasalHTTPError = BasalHTTPError;
40
- class BasalTransportError extends BasalError {
41
- }
42
- exports.BasalTransportError = BasalTransportError;
43
- class BasalProtocolError extends BasalError {
44
- }
45
- exports.BasalProtocolError = BasalProtocolError;
46
- class BasalResponseTooLargeError extends BasalError {
47
- }
48
- exports.BasalResponseTooLargeError = BasalResponseTooLargeError;
23
+ __exportStar(require("./tasks.cjs"), exports);
24
+ __exportStar(require("./errors.cjs"), exports);
25
+ var batches_cjs_2 = require("./batches.cjs");
26
+ Object.defineProperty(exports, "batchInput", { enumerable: true, get: function () { return batches_cjs_2.batchInput; } });
27
+ Object.defineProperty(exports, "parseBatchResults", { enumerable: true, get: function () { return batches_cjs_2.parseBatchResults; } });
49
28
  async function readBody(response, limit, signal) {
50
29
  // Count bytes from the actual stream, including decompressed responses; never trust Content-Length alone.
51
30
  const reader = response.body?.getReader();
52
31
  if (!reader)
53
- return "";
32
+ return new Uint8Array();
54
33
  const cancel = () => { void reader.cancel().catch(() => { }); };
55
34
  signal.addEventListener("abort", cancel, { once: true });
56
35
  if (signal.aborted)
57
36
  cancel();
58
- const decoder = new TextDecoder("utf-8", { fatal: true });
59
37
  const parts = [];
60
38
  let bytes = 0;
61
39
  let complete = false;
@@ -68,20 +46,38 @@ async function readBody(response, limit, signal) {
68
46
  }
69
47
  bytes += value.byteLength;
70
48
  if (bytes > limit) {
71
- throw new BasalResponseTooLargeError(`Basal response exceeds ${limit} bytes`);
49
+ throw new errors_cjs_1.BasalResponseTooLargeError(`Basal response exceeds ${limit} bytes`);
72
50
  }
73
- parts.push(decoder.decode(value, { stream: true }));
51
+ parts.push(value);
74
52
  }
75
- parts.push(decoder.decode());
76
- return parts.join("");
77
53
  }
78
54
  finally {
79
55
  signal.removeEventListener("abort", cancel);
80
- // A decoder/read failure must also stop the underlying HTTP body, before releasing ownership.
56
+ // A read failure must also stop the underlying HTTP body, before releasing ownership.
81
57
  if (!complete)
82
58
  cancel();
83
59
  reader.releaseLock();
84
60
  }
61
+ if (parts.length === 1)
62
+ return parts[0];
63
+ const out = new Uint8Array(bytes);
64
+ let offset = 0;
65
+ for (const part of parts) {
66
+ out.set(part, offset);
67
+ offset += part.byteLength;
68
+ }
69
+ return out;
70
+ }
71
+ function positiveTimeout(value, name) {
72
+ if (!Number.isFinite(value) || value <= 0 || value > 2_147_483_647) {
73
+ throw new TypeError(`${name} must be positive and at most 2147483647`);
74
+ }
75
+ return value;
76
+ }
77
+ function byteLimit(value, name) {
78
+ if (!Number.isSafeInteger(value) || value <= 0)
79
+ throw new TypeError(`${name} must be a positive safe integer`);
80
+ return value;
85
81
  }
86
82
  /** Reusable fetch client. Server root URL, optional proxy prefix, without /v1.
87
83
  * No automatic retries. AbortSignal cancels a request; timeout includes body reading.
@@ -117,55 +113,79 @@ class BasalClient {
117
113
  this.headers.Authorization = `Bearer ${options.apiKey}`;
118
114
  }
119
115
  }
120
- async request(method, path, body, signal) {
121
- // Serialization errors are local input errors, not failures of the HTTP transport.
122
- const encoded = body === undefined ? undefined : JSON.stringify(body);
116
+ request(method, path, body, signal) {
117
+ return this.call({ method, path, json: body, signal });
118
+ }
119
+ async call(call) {
120
+ // Serialization and option errors are local input errors, not failures of the HTTP transport.
121
+ const encoded = call.json === undefined ? undefined : JSON.stringify(call.json);
122
+ const timeoutMs = call.timeoutMs === undefined ? this.timeoutMs : positiveTimeout(call.timeoutMs, "timeoutMs");
123
+ const limit = call.maxBytes === undefined ? this.maxResponseBytes : byteLimit(call.maxBytes, "maxBytes");
124
+ const headers = { ...this.headers, ...call.headers };
125
+ if (encoded !== undefined)
126
+ headers["Content-Type"] = "application/json";
127
+ const { signal } = call;
123
128
  const controller = new AbortController();
124
129
  const abort = () => controller.abort(signal?.reason);
125
130
  signal?.addEventListener("abort", abort, { once: true });
126
131
  if (signal?.aborted)
127
132
  abort();
128
- const timer = setTimeout(() => controller.abort(new Error("Basal request timed out")), this.timeoutMs);
133
+ const timer = setTimeout(() => controller.abort(new Error("Basal request timed out")), timeoutMs);
129
134
  let onAbort = () => { };
130
135
  const aborted = new Promise((_, reject) => {
131
- onAbort = () => reject(new BasalTransportError("Basal HTTP request aborted", { cause: controller.signal.reason }));
136
+ onAbort = () => reject(new errors_cjs_1.BasalTransportError("Basal HTTP request aborted", { cause: controller.signal.reason }));
132
137
  controller.signal.addEventListener("abort", onAbort, { once: true });
133
138
  if (controller.signal.aborted)
134
139
  onAbort();
135
140
  });
136
141
  const work = async () => {
137
142
  let response;
138
- let raw;
143
+ let content;
144
+ let raw = "";
139
145
  try {
140
- response = await this.fetch(this.baseUrl + path, {
141
- method, headers: body === undefined ? this.headers : { ...this.headers, "Content-Type": "application/json" },
142
- ...(encoded === undefined ? {} : { body: encoded }),
143
- signal: controller.signal, redirect: "error", credentials: "omit", cache: "no-store",
144
- });
146
+ const init = {
147
+ method: call.method, headers, signal: controller.signal, redirect: "error", credentials: "omit", cache: "no-store",
148
+ };
149
+ if (encoded !== undefined)
150
+ init.body = encoded;
151
+ else if (call.body instanceof Blob)
152
+ init.body = call.body;
153
+ else if (call.body !== undefined) {
154
+ // Streamed request bodies require half-duplex fetch (Node.js, Chromium over HTTP/2).
155
+ init.body = call.body(controller.signal);
156
+ init.duplex = "half";
157
+ }
158
+ response = await this.fetch(this.baseUrl + call.path, init);
145
159
  if (response.redirected || ([301, 302, 303, 307, 308].includes(response.status) && response.headers.has("Location"))) {
146
160
  await response.body?.cancel().catch(() => { });
147
- throw new BasalTransportError("Basal transport returned a redirect");
161
+ throw new errors_cjs_1.BasalTransportError("Basal transport returned a redirect");
148
162
  }
149
- raw = await readBody(response, this.maxResponseBytes, controller.signal);
163
+ // A per-download cap limits successful artifact bytes. Error envelopes retain the client's normal cap
164
+ // so a small artifact cap does not hide an actionable 404/429 and its Retry-After.
165
+ content = await readBody(response, response.ok ? limit : this.maxResponseBytes, controller.signal);
166
+ if (!call.bytes || !response.ok)
167
+ raw = new TextDecoder("utf-8", { fatal: true }).decode(content);
150
168
  }
151
169
  catch (cause) {
152
- if (cause instanceof BasalError)
170
+ if (cause instanceof errors_cjs_1.BasalError)
153
171
  throw cause;
154
- throw new BasalTransportError("Basal HTTP transport failed", { cause });
172
+ throw new errors_cjs_1.BasalTransportError("Basal HTTP transport failed", { cause });
155
173
  }
174
+ if (call.bytes && response.ok)
175
+ return content;
156
176
  let data;
157
177
  try {
158
178
  data = JSON.parse(raw);
159
179
  }
160
180
  catch (cause) {
161
181
  if (response.ok)
162
- throw new BasalProtocolError("Basal returned invalid JSON", { cause });
182
+ throw new errors_cjs_1.BasalProtocolError("Basal returned invalid JSON", { cause });
163
183
  data = raw;
164
184
  }
165
185
  if (!response.ok)
166
- throw new BasalHTTPError(response.status, data, response.headers.get("Retry-After"));
186
+ throw new errors_cjs_1.BasalHTTPError(response.status, data, response.headers.get("Retry-After"));
167
187
  if (data === null || typeof data !== "object" || Array.isArray(data)) {
168
- throw new BasalProtocolError("Basal returned a JSON value other than an object");
188
+ throw new errors_cjs_1.BasalProtocolError("Basal returned a JSON value other than an object");
169
189
  }
170
190
  return data;
171
191
  };
@@ -178,6 +198,9 @@ class BasalClient {
178
198
  controller.signal.removeEventListener("abort", onAbort);
179
199
  clearTimeout(timer);
180
200
  signal?.removeEventListener("abort", abort);
201
+ // An answer can arrive before a streamed upload is fully sent (e.g. 413); stop its source.
202
+ if (typeof call.body === "function")
203
+ controller.abort(new Error("Basal request finished"));
181
204
  }
182
205
  }
183
206
  async decide(state, questionsOrSignal, options = {}) {
@@ -197,7 +220,7 @@ class BasalClient {
197
220
  parsed.set(id, question.read(response, id));
198
221
  }
199
222
  catch (cause) {
200
- throw new BasalProtocolError("Invalid typed Basal answer", { cause });
223
+ throw new errors_cjs_1.BasalProtocolError("Invalid typed Basal answer", { cause });
201
224
  }
202
225
  const typed = { ...response, answers: Object.fromEntries(parsed) };
203
226
  Object.defineProperty(typed, "raw", { value: response });
@@ -212,5 +235,46 @@ class BasalClient {
212
235
  return typed;
213
236
  }
214
237
  models(signal) { return this.request("GET", "v1/models", undefined, signal); }
238
+ /** Upload a JSONL input file (`purpose=batch`) from caller-owned bytes, a Blob or a stream; never fetches URLs. */
239
+ async uploadFile(request, options = {}) {
240
+ const form = (0, batches_cjs_1.uploadForm)(request);
241
+ return await this.call({
242
+ method: "POST", path: "v1/files", body: form.body, headers: { "Content-Type": form.contentType },
243
+ signal: options.signal, timeoutMs: options.timeoutMs,
244
+ });
245
+ }
246
+ async listFiles(options = {}) {
247
+ return await this.call({ method: "GET", path: (0, batches_cjs_1.listPath)("v1/files", options), signal: options.signal });
248
+ }
249
+ async getFile(fileId, options = {}) {
250
+ return await this.call({ method: "GET", path: (0, batches_cjs_1.resourcePath)("v1/files", fileId), signal: options.signal });
251
+ }
252
+ async deleteFile(fileId, options = {}) {
253
+ return await this.call({ method: "DELETE", path: (0, batches_cjs_1.resourcePath)("v1/files", fileId), signal: options.signal });
254
+ }
255
+ /** Raw file bytes (JSONL), buffered up to `maxBytes` (default `maxResponseBytes`); never JSON-parsed. */
256
+ async downloadFile(fileId, options = {}) {
257
+ return await this.call({
258
+ method: "GET", path: (0, batches_cjs_1.resourcePath)("v1/files", fileId, "/content"), bytes: true,
259
+ headers: { Accept: "application/jsonl, application/json" },
260
+ signal: options.signal, timeoutMs: options.timeoutMs, maxBytes: options.maxBytes,
261
+ });
262
+ }
263
+ async createBatch(request, options = {}) {
264
+ const headers = (0, batches_cjs_1.idempotencyHeader)(options.idempotencyKey);
265
+ return await this.call({ method: "POST", path: "v1/batches", json: (0, batches_cjs_1.createBody)(request), headers, signal: options.signal });
266
+ }
267
+ async listBatches(options = {}) {
268
+ const { after, limit } = options;
269
+ const query = { ...(after === undefined ? {} : { after }), ...(limit === undefined ? {} : { limit }) };
270
+ return await this.call({ method: "GET", path: (0, batches_cjs_1.listPath)("v1/batches", query), signal: options.signal });
271
+ }
272
+ async getBatch(batchId, options = {}) {
273
+ return await this.call({ method: "GET", path: (0, batches_cjs_1.resourcePath)("v1/batches", batchId), signal: options.signal });
274
+ }
275
+ /** Request cancellation; the batch moves to `cancelling` and later `cancelled`. Does not wait. */
276
+ async cancelBatch(batchId, options = {}) {
277
+ return await this.call({ method: "POST", path: (0, batches_cjs_1.resourcePath)("v1/batches", batchId, "/cancel"), signal: options.signal });
278
+ }
215
279
  }
216
280
  exports.BasalClient = BasalClient;
@@ -1,6 +1,12 @@
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";
2
3
  export * from "./questions.cjs";
3
4
  export * from "./media.cjs";
5
+ export * from "./tasks.cjs";
6
+ export * from "./errors.cjs";
7
+ export { batchInput, parseBatchResults } from "./batches.cjs";
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.cjs";
9
+ import type { TaskAnswer } from "./tasks.cjs";
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
  }
@@ -40,13 +40,13 @@ function evidence(options) {
40
40
  class QuestionSpec {
41
41
  wire;
42
42
  constructor(kind, instructions, fields) {
43
- this.wire = structuredClone({ ...fields, type: kind, instructions: text(instructions, 'instructions') });
43
+ this.wire = structuredClone({ ...fields, type: kind, ...(['classify', 'verify', 'compress', 'extract'].includes(kind) ? (instructions ? { instructions } : {}) : { instructions: text(instructions, 'instructions') }) });
44
44
  }
45
45
  toJSON() { return structuredClone(this.wire); }
46
46
  /** Validate and retrieve an answer with this question's result type. */
47
47
  read(response, id) {
48
48
  const answer = response.answers?.[id];
49
- if (!answer || answer.type !== this.wire.type || !answer.probabilities || typeof answer.probabilities !== 'object' || Array.isArray(answer.probabilities)) {
49
+ if (!answer || !('probabilities' in answer) || answer.type !== this.wire.type || !answer.probabilities || typeof answer.probabilities !== 'object' || Array.isArray(answer.probabilities)) {
50
50
  throw new TypeError('Basal returned a missing or mismatched answer');
51
51
  }
52
52
  const fields = {
@@ -216,6 +216,11 @@ class ToolQuestion extends QuestionSpec {
216
216
  if (options.multi !== undefined && typeof options.multi !== 'boolean')
217
217
  throw new TypeError('invalid multi');
218
218
  const fields = { tools, ...(options.extra === undefined ? {} : { extra: options.extra }) };
219
+ if (options.description_chars !== undefined) {
220
+ if (!Number.isSafeInteger(options.description_chars) || options.description_chars < 0)
221
+ throw new TypeError('description_chars must be a non-negative safe integer');
222
+ fields.description_chars = options.description_chars;
223
+ }
219
224
  if (options.rank !== undefined) {
220
225
  if (!Number.isSafeInteger(options.rank) || options.rank < 1)
221
226
  throw new TypeError('rank must be a positive integer');
@@ -33,6 +33,8 @@ export interface ToolOptions extends MultiOptions {
33
33
  rank?: number;
34
34
  multi?: boolean;
35
35
  max_error?: number;
36
+ /** Tool-description truncation cap; omitted keeps the server default (300), zero disables truncation. */
37
+ description_chars?: number;
36
38
  }
37
39
  export interface ActOptions<K extends string = string, A extends string = string> extends EvidenceOptions {
38
40
  criteria?: Record<K, string>;
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExtractQuestion = exports.CompressQuestion = exports.VerifyQuestion = exports.ClassifyQuestion = void 0;
4
+ exports.classify = classify;
5
+ exports.verify = verify;
6
+ exports.compress = compress;
7
+ exports.extract = extract;
8
+ const questions_cjs_1 = require("./questions.cjs");
9
+ function object(v) {
10
+ if (!v || typeof v !== 'object' || Array.isArray(v))
11
+ throw new TypeError('expected task object');
12
+ return v;
13
+ }
14
+ function probability(v) { if (typeof v !== 'number' || !Number.isFinite(v) || v < 0 || v > 1)
15
+ throw new TypeError('invalid probability'); }
16
+ function probabilities(v) { for (const n of Object.values(object(v)))
17
+ probability(n); }
18
+ function automation(v) { if (Object.values(object(v)).some(n => n !== null && typeof n !== 'boolean'))
19
+ throw new TypeError('invalid automation'); }
20
+ function strings(v) { if (!Array.isArray(v) || v.some(k => typeof k !== 'string'))
21
+ throw new TypeError('expected string list'); }
22
+ function offset(v) { if (!Number.isSafeInteger(v) || v < 0)
23
+ throw new TypeError('invalid Unicode code point offset/count'); }
24
+ function span(v) { offset(v.start); offset(v.end); if (v.end < v.start)
25
+ throw new TypeError('reversed source span'); }
26
+ function risks(v) {
27
+ if (v === undefined)
28
+ return;
29
+ const a = Array.isArray(v) ? v : [v];
30
+ if (a.length > 64 || a.some(n => typeof n !== 'number' || !Number.isFinite(n) || n <= 0 || n >= 1))
31
+ throw new TypeError('invalid max_error');
32
+ }
33
+ function instructions(v) { if (typeof v !== 'string')
34
+ throw new TypeError('instructions must be a string'); return v; }
35
+ class TaskQuestion extends questions_cjs_1.QuestionSpec {
36
+ constructor(kind, text, fields) { super(kind, instructions(text), fields); }
37
+ read(response, id) {
38
+ const d = object(response.answers?.[id]);
39
+ if (d.type !== this.toJSON().type || d.experimental !== true)
40
+ throw new TypeError('Basal returned a missing or mismatched task');
41
+ if (d.type === 'classify') {
42
+ probabilities(d.scores);
43
+ const scores = object(d.scores);
44
+ if (this.toJSON().multi_label) {
45
+ strings(d.labels);
46
+ if (d.labels.some(k => !Object.hasOwn(scores, k)))
47
+ throw new TypeError('unknown label');
48
+ probability(d.set_confidence);
49
+ }
50
+ else {
51
+ if (typeof d.label !== 'string' || !Object.hasOwn(scores, d.label) || d.top_label !== d.label)
52
+ throw new TypeError('invalid label');
53
+ probability(d.confidence);
54
+ automation(d.automated);
55
+ }
56
+ }
57
+ else if (d.type === 'verify') {
58
+ const claims = object(d.claims);
59
+ for (const raw of Object.values(claims)) {
60
+ const c = object(raw);
61
+ const p = object(c.probabilities);
62
+ probabilities(p);
63
+ if (typeof c.verdict !== 'string' || !['supported', 'contradicted', 'not_stated', 'not_supported'].includes(c.verdict) || !Object.hasOwn(p, c.verdict))
64
+ throw new TypeError('invalid verdict');
65
+ probability(c.confidence);
66
+ automation(c.automated);
67
+ }
68
+ strings(d.not_supported);
69
+ if (typeof d.all_supported !== 'boolean' || d.not_supported.some(k => !Object.hasOwn(claims, k)))
70
+ throw new TypeError('invalid verification summary');
71
+ }
72
+ else if (d.type === 'compress') {
73
+ if (typeof d.compressed !== 'string' || !['sentence', 'line', 'paragraph'].includes(d.unit) || !Array.isArray(d.units) || !Array.isArray(d.kept))
74
+ throw new TypeError('invalid compression');
75
+ for (const u of d.units) {
76
+ const v = object(u);
77
+ span(v);
78
+ probability(v.probability);
79
+ }
80
+ for (const k of d.kept) {
81
+ offset(k);
82
+ if (k >= d.units.length)
83
+ throw new TypeError('invalid kept unit');
84
+ }
85
+ offset(d.original_chars);
86
+ offset(d.compressed_chars);
87
+ if (typeof d.ratio !== 'number' || !Number.isFinite(d.ratio) || d.ratio < 0)
88
+ throw new TypeError('invalid ratio');
89
+ }
90
+ else {
91
+ object(d.data);
92
+ for (const raw of Object.values(object(d.fields))) {
93
+ const f = object(raw);
94
+ if (!Object.hasOwn(f, 'value') || !['choice', 'span'].includes(f.source) || !['validated', 'per decision'].includes(f.calibration))
95
+ throw new TypeError('invalid extraction field');
96
+ probability(f.confidence);
97
+ if (f.probabilities !== undefined)
98
+ probabilities(f.probabilities);
99
+ for (const k of ['present_probability', 'line_probability', 'span_probability'])
100
+ if (f[k] !== undefined)
101
+ probability(f[k]);
102
+ if (f.text !== undefined) {
103
+ if (typeof f.text !== 'string')
104
+ throw new TypeError('invalid source text');
105
+ span(f);
106
+ }
107
+ }
108
+ }
109
+ return structuredClone(d);
110
+ }
111
+ }
112
+ class ClassifyQuestion extends TaskQuestion {
113
+ constructor(text, labels, options = {}) {
114
+ if (!labels || typeof labels !== 'object' || Object.keys(labels).length < 1 || Object.keys(labels).length > 256)
115
+ throw new TypeError('classify requires 1..256 labels');
116
+ risks(options.max_error);
117
+ for (const k of ['multi_label', 'none'])
118
+ if (options[k] !== undefined && typeof options[k] !== 'boolean')
119
+ throw new TypeError(`invalid ${k}`);
120
+ if (options.option_keys !== undefined && !['show', 'hide'].includes(options.option_keys))
121
+ throw new TypeError('invalid option_keys');
122
+ if (options.threshold !== undefined) {
123
+ probability(options.threshold);
124
+ if (options.objective !== undefined && options.objective !== 'threshold')
125
+ throw new TypeError('threshold requires threshold objective');
126
+ }
127
+ for (const k of ['min', 'max'])
128
+ if (options[k] !== undefined)
129
+ offset(options[k]);
130
+ if (options.min !== undefined && options.max !== undefined && options.min > options.max)
131
+ throw new TypeError('min exceeds max');
132
+ if (options.objective !== undefined && !['threshold', 'exact', 'iou', 'f1'].includes(options.objective))
133
+ throw new TypeError('invalid objective');
134
+ if (!options.multi_label && ['threshold', 'min', 'max', 'objective'].some(k => Object.hasOwn(options, k)))
135
+ throw new TypeError('selection options require multi_label');
136
+ super('classify', text, { labels: structuredClone(labels), ...options });
137
+ }
138
+ }
139
+ exports.ClassifyQuestion = ClassifyQuestion;
140
+ class VerifyQuestion extends TaskQuestion {
141
+ constructor(text, claims, options = {}) {
142
+ if (!claims || typeof claims !== 'object' || !Object.keys(claims).length)
143
+ throw new TypeError('verify requires claims');
144
+ risks(options.max_error);
145
+ if (options.binary !== undefined && typeof options.binary !== 'boolean')
146
+ throw new TypeError('invalid binary');
147
+ super('verify', text, { claims: structuredClone(claims), ...options });
148
+ }
149
+ }
150
+ exports.VerifyQuestion = VerifyQuestion;
151
+ class CompressQuestion extends TaskQuestion {
152
+ constructor(text = '', options = {}) {
153
+ if (options.unit !== undefined && !['sentence', 'line', 'paragraph'].includes(options.unit))
154
+ throw new TypeError('invalid unit');
155
+ if (options.keep_ratio !== undefined) {
156
+ probability(options.keep_ratio);
157
+ if (options.keep_ratio === 0)
158
+ throw new TypeError('keep_ratio must be positive');
159
+ }
160
+ if (options.threshold !== undefined)
161
+ probability(options.threshold);
162
+ if (options.min_keep !== undefined)
163
+ offset(options.min_keep);
164
+ super('compress', text, options);
165
+ }
166
+ }
167
+ exports.CompressQuestion = CompressQuestion;
168
+ class ExtractQuestion extends TaskQuestion {
169
+ constructor(text, schema) { object(schema); super('extract', text, { schema }); }
170
+ }
171
+ exports.ExtractQuestion = ExtractQuestion;
172
+ function classify(text, labels, options) { return new ClassifyQuestion(text, labels, options); }
173
+ function verify(text, claims, options) { return new VerifyQuestion(text, claims, options); }
174
+ function compress(text = '', options) { return new CompressQuestion(text, options); }
175
+ function extract(text, schema) { return new ExtractQuestion(text, schema); }