@itsoltech/basal-client 0.1.0 → 0.3.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.
@@ -30,19 +30,20 @@ function evidence(options) {
30
30
  export class QuestionSpec {
31
31
  wire;
32
32
  constructor(kind, instructions, fields) {
33
- this.wire = structuredClone({ ...fields, type: kind, instructions: text(instructions, 'instructions') });
33
+ this.wire = structuredClone({ ...fields, type: kind, ...(['classify', 'verify', 'compress', 'extract'].includes(kind) ? (instructions ? { instructions } : {}) : { instructions: text(instructions, 'instructions') }) });
34
34
  }
35
35
  toJSON() { return structuredClone(this.wire); }
36
36
  /** Validate and retrieve an answer with this question's result type. */
37
37
  read(response, id) {
38
38
  const answer = response.answers?.[id];
39
- if (!answer || answer.type !== this.wire.type || !answer.probabilities || typeof answer.probabilities !== 'object' || Array.isArray(answer.probabilities)) {
39
+ if (!answer || !('probabilities' in answer) || answer.type !== this.wire.type || !answer.probabilities || typeof answer.probabilities !== 'object' || Array.isArray(answer.probabilities)) {
40
40
  throw new TypeError('Basal returned a missing or mismatched answer');
41
41
  }
42
42
  const fields = {
43
43
  choice: { choice: 'string', confidence: 'number' }, noul: { noul: 'number', confidence: 'number' },
44
44
  score: { score: 'number', legend: 'object', confidence: 'number' },
45
- multi: { selected: 'object', threshold: 'number', set_confidence: 'number' },
45
+ multi: { selected: 'object', set_confidence: 'number' },
46
+ tool: answer.type === 'tool' && answer.multi ? { tools: 'object', call: 'boolean', set_confidence: 'number' } : { call: 'boolean', confidence: 'number' },
46
47
  act: { action: 'string', answer: 'string', expected_costs: 'object', confidence: 'number', calibration: 'string' },
47
48
  };
48
49
  const data = answer;
@@ -75,21 +76,52 @@ export class QuestionSpec {
75
76
  if (answer.type === 'score' && (Array.isArray(answer.legend) || Object.values(answer.legend).some(v => typeof v !== 'string')))
76
77
  throw new TypeError('invalid legend');
77
78
  if (answer.type === 'act') {
78
- if (!['validated', 'unvalidated'].includes(answer.calibration) || !Object.hasOwn(this.wire.costs ?? {}, answer.action) || !expectedKeys.includes(answer.answer))
79
+ if (!['validated', 'unvalidated', 'text'].includes(answer.calibration) || !Object.hasOwn(this.wire.costs ?? {}, answer.action) || !expectedKeys.includes(answer.answer))
79
80
  throw new TypeError('invalid action');
80
81
  if (Array.isArray(answer.expected_costs) || Object.keys(this.wire.costs ?? {}).some(key => !Object.hasOwn(answer.expected_costs, key)))
81
82
  throw new TypeError('invalid expected costs');
82
83
  for (const value of Object.values(answer.expected_costs))
83
84
  number(value, 'expected cost');
84
85
  }
85
- return { ...answer, ...(answer.type === 'multi' ? {} : { evidence: answer.evidence ?? [] }) };
86
+ if (answer.type === 'multi' || (answer.type === 'tool' && answer.multi)) {
87
+ validateSelection(answer, this.wire.objective ?? 'threshold');
88
+ }
89
+ if (answer.type === 'tool') {
90
+ if ((answer.multi ?? false) !== (this.wire.multi ?? false))
91
+ throw new TypeError('mismatched tool mode');
92
+ if (answer.multi) {
93
+ if (!Array.isArray(answer.tools) || answer.tools.some(k => typeof k !== 'string' || !Object.hasOwn(answer.probabilities, k)))
94
+ throw new TypeError('invalid selected tools');
95
+ }
96
+ else {
97
+ if (answer.tool === null ? answer.deferred !== true : typeof answer.tool !== 'string' || !Object.hasOwn(answer.probabilities, answer.tool))
98
+ throw new TypeError('invalid selected tool');
99
+ if (answer.deferred !== undefined && typeof answer.deferred !== 'boolean')
100
+ throw new TypeError('invalid deferred');
101
+ for (const key of ['refused', 'max_error'])
102
+ if (answer[key] !== undefined && typeof answer[key] !== 'string')
103
+ throw new TypeError('invalid tool metadata');
104
+ }
105
+ if (answer.ranking !== undefined) {
106
+ if (!Array.isArray(answer.ranking))
107
+ throw new TypeError('invalid ranking');
108
+ for (const entry of answer.ranking) {
109
+ if (!entry || typeof entry.tool !== 'string' || !Object.hasOwn(answer.probabilities, entry.tool))
110
+ throw new TypeError('invalid ranking entry');
111
+ number(entry.probability, 'ranking probability', true);
112
+ }
113
+ }
114
+ }
115
+ if (answer.type === 'act' && answer.deferred !== undefined && typeof answer.deferred !== 'boolean')
116
+ throw new TypeError('invalid deferred');
117
+ return { ...answer, ...(answer.type === 'multi' || answer.type === 'tool' ? {} : { evidence: answer.evidence ?? [] }) };
86
118
  }
87
119
  }
88
120
  export class ChoiceQuestion extends QuestionSpec {
89
121
  constructor(instructions, values, options = {}) {
90
122
  if (options.option_keys !== undefined && !['show', 'hide'].includes(options.option_keys))
91
123
  throw new TypeError('invalid option_keys');
92
- super('choice', instructions, { criteria: criteria(values, 2, 10), ...evidence(options), ...(options.option_keys === 'hide' ? { option_keys: 'hide' } : {}) });
124
+ super('choice', instructions, { criteria: criteria(values, 2, 255), ...evidence(options), ...(options.option_keys === 'hide' ? { option_keys: 'hide' } : {}) });
93
125
  }
94
126
  }
95
127
  export class NoulQuestion extends QuestionSpec {
@@ -97,34 +129,103 @@ export class NoulQuestion extends QuestionSpec {
97
129
  }
98
130
  export class ScoreQuestion extends QuestionSpec {
99
131
  constructor(instructions, levels, options = {}) {
100
- if (!Array.isArray(levels) || levels.length < 2 || levels.length > 10)
101
- throw new TypeError('score requires 2–10 ordered levels');
132
+ if (!Array.isArray(levels) || levels.length < 2 || levels.length > 255)
133
+ throw new TypeError('score requires 2–255 ordered levels');
102
134
  super('score', instructions, { criteria: levels.map(v => text(v, 'level')), ...evidence(options) });
103
135
  }
104
136
  }
137
+ function selectionOptions(options, keys) {
138
+ const fields = {};
139
+ const objective = options.objective ?? 'threshold';
140
+ if (!['threshold', 'exact', 'iou', 'f1'].includes(objective))
141
+ throw new TypeError('invalid objective');
142
+ if (objective !== 'threshold')
143
+ fields.objective = objective;
144
+ if (options.threshold !== undefined) {
145
+ if (objective !== 'threshold')
146
+ throw new TypeError('threshold applies only to objective threshold');
147
+ fields.threshold = number(options.threshold, 'threshold', true);
148
+ }
149
+ for (const key of ['min', 'max']) {
150
+ const v = options[key];
151
+ if (v !== undefined) {
152
+ if (!Number.isSafeInteger(v) || v < 0 || (keys && v > keys.length))
153
+ throw new TypeError(`invalid ${key}`);
154
+ fields[key] = v;
155
+ }
156
+ }
157
+ if (options.min !== undefined && options.max !== undefined && options.min > options.max)
158
+ throw new TypeError('min exceeds max');
159
+ if (options.exclusive !== undefined) {
160
+ if (!Array.isArray(options.exclusive))
161
+ throw new TypeError('exclusive must be an array');
162
+ fields.exclusive = options.exclusive.map(k => { text(k, 'exclusive key'); if (keys && !keys.includes(k))
163
+ throw new TypeError('unknown exclusive key'); return k; });
164
+ }
165
+ if (options.fallback !== undefined) {
166
+ fields.fallback = text(options.fallback, 'fallback');
167
+ if (keys && !keys.includes(options.fallback))
168
+ throw new TypeError('unknown fallback');
169
+ }
170
+ if (options.fallback_threshold !== undefined) {
171
+ if (options.fallback === undefined)
172
+ throw new TypeError('fallback_threshold requires fallback');
173
+ fields.fallback_threshold = number(options.fallback_threshold, 'fallback_threshold', true);
174
+ }
175
+ return fields;
176
+ }
177
+ function validateSelection(answer, objective) {
178
+ number(answer.set_confidence, 'set_confidence', true);
179
+ if (objective === 'threshold')
180
+ number(answer.threshold, 'threshold', true);
181
+ else {
182
+ if (answer.objective !== objective)
183
+ throw new TypeError('mismatched objective');
184
+ if (answer.expected_score !== null && answer.expected_score !== undefined)
185
+ number(answer.expected_score, 'expected_score', true);
186
+ }
187
+ }
105
188
  export class MultiQuestion extends QuestionSpec {
106
189
  constructor(instructions, values, options = {}) {
107
190
  if ('evidence' in options)
108
191
  throw new TypeError('multi does not support evidence');
109
- const fields = { criteria: criteria(values, 1) };
110
- if (options.threshold !== undefined)
111
- fields.threshold = number(options.threshold, 'threshold', true);
112
- for (const key of ['min', 'max']) {
113
- const value = options[key];
114
- if (value !== undefined) {
115
- if (!Number.isInteger(value) || value < 0 || value > Object.keys(values).length)
116
- throw new TypeError(`invalid ${key}`);
117
- fields[key] = value;
118
- }
192
+ super('multi', instructions, { criteria: criteria(values, 1), ...selectionOptions(options, Object.keys(values)) });
193
+ }
194
+ }
195
+ export class ToolQuestion extends QuestionSpec {
196
+ constructor(instructions, tools, options = {}) {
197
+ if (!Array.isArray(tools) || tools.some(t => !t || typeof t !== 'object' || Array.isArray(t)))
198
+ throw new TypeError('tools must contain definition objects');
199
+ if (options.extra !== undefined && (!Array.isArray(options.extra) || options.extra.some(k => !['answer_directly', 'ask_user', 'none'].includes(k))))
200
+ throw new TypeError('invalid extra tool option');
201
+ if (options.multi !== undefined && typeof options.multi !== 'boolean')
202
+ throw new TypeError('invalid multi');
203
+ const fields = { tools, ...(options.extra === undefined ? {} : { extra: options.extra }) };
204
+ if (options.description_chars !== undefined) {
205
+ if (!Number.isSafeInteger(options.description_chars) || options.description_chars < 0)
206
+ throw new TypeError('description_chars must be a non-negative safe integer');
207
+ fields.description_chars = options.description_chars;
208
+ }
209
+ if (options.rank !== undefined) {
210
+ if (!Number.isSafeInteger(options.rank) || options.rank < 1)
211
+ throw new TypeError('rank must be a positive integer');
212
+ fields.rank = options.rank;
213
+ }
214
+ if (options.max_error !== undefined)
215
+ fields.max_error = number(options.max_error, 'max_error', true);
216
+ const selection = selectionOptions(options);
217
+ if (!options.multi && Object.keys(selection).length)
218
+ throw new TypeError('selection options require multi');
219
+ if (options.multi) {
220
+ fields.multi = true;
221
+ Object.assign(fields, selection);
119
222
  }
120
- if (options.min !== undefined && options.max !== undefined && options.min > options.max)
121
- throw new TypeError('min exceeds max');
122
- super('multi', instructions, fields);
223
+ super('tool', instructions, fields);
123
224
  }
124
225
  }
125
226
  export class ActQuestion extends QuestionSpec {
126
227
  constructor(instructions, costs, options = {}) {
127
- const optionsMap = options.criteria === undefined ? undefined : criteria(options.criteria, 2, 10);
228
+ const optionsMap = options.criteria === undefined ? undefined : criteria(options.criteria, 2, 255);
128
229
  const outcomes = Object.keys(optionsMap ?? { 'true': 'Yes', 'false': 'No' });
129
230
  if (!costs || typeof costs !== 'object' || Array.isArray(costs) || !Object.keys(costs).length)
130
231
  throw new TypeError('act requires a cost matrix');
@@ -147,6 +248,7 @@ export function noul(instructions, options) { return new NoulQuestion(instructio
147
248
  export function score(instructions, levels, options) { return new ScoreQuestion(instructions, levels, options); }
148
249
  export function multi(instructions, values, options) { return new MultiQuestion(instructions, values, options); }
149
250
  export function act(instructions, costs, options) { return new ActQuestion(instructions, costs, options); }
251
+ export function tool(instructions, tools, options) { return new ToolQuestion(instructions, tools, options); }
150
252
  export function buildRequest(state, questions, options) {
151
253
  if (state === null || state === undefined)
152
254
  throw new TypeError('state must not be null/undefined');
@@ -160,5 +262,11 @@ export function buildRequest(state, questions, options) {
160
262
  text(options.model, 'model');
161
263
  if (options.facts !== undefined && !['off', 'auto'].includes(options.facts))
162
264
  throw new TypeError('invalid facts');
163
- return { state, questions: Object.fromEntries(entries), ...(options.model === undefined ? {} : { model: options.model }), ...(options.facts === undefined ? {} : { facts: options.facts }) };
265
+ if (options.lang !== undefined && !['pl', 'en'].includes(options.lang))
266
+ throw new TypeError('invalid lang');
267
+ if (options.detail !== undefined && !['default', 'high'].includes(options.detail))
268
+ throw new TypeError('invalid detail');
269
+ if (options.media !== undefined && (!Array.isArray(options.media) || options.media.length < 1 || options.media.length > 8 || options.media.some(m => !m || !['image', 'audio'].includes(m.type) || typeof m.data !== 'string' || !m.data.trim())))
270
+ throw new TypeError('invalid media');
271
+ return { state, questions: Object.fromEntries(entries), ...(options.lang === undefined ? {} : { lang: options.lang }), ...(options.detail === undefined ? {} : { detail: options.detail }), ...(options.media === undefined ? {} : { media: structuredClone(options.media) }), ...(options.model === undefined ? {} : { model: options.model }), ...(options.facts === undefined ? {} : { facts: options.facts }) };
164
272
  }
@@ -0,0 +1,108 @@
1
+ import { QuestionSpec } from './questions.js';
2
+ import type { DecisionResponse, JsonValue, Question, MultiObjective } from './index.js';
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,164 @@
1
+ import { QuestionSpec } from './questions.js';
2
+ function object(v) {
3
+ if (!v || typeof v !== 'object' || Array.isArray(v))
4
+ throw new TypeError('expected task object');
5
+ return v;
6
+ }
7
+ function probability(v) { if (typeof v !== 'number' || !Number.isFinite(v) || v < 0 || v > 1)
8
+ throw new TypeError('invalid probability'); }
9
+ function probabilities(v) { for (const n of Object.values(object(v)))
10
+ probability(n); }
11
+ function automation(v) { if (Object.values(object(v)).some(n => n !== null && typeof n !== 'boolean'))
12
+ throw new TypeError('invalid automation'); }
13
+ function strings(v) { if (!Array.isArray(v) || v.some(k => typeof k !== 'string'))
14
+ throw new TypeError('expected string list'); }
15
+ function offset(v) { if (!Number.isSafeInteger(v) || v < 0)
16
+ throw new TypeError('invalid Unicode code point offset/count'); }
17
+ function span(v) { offset(v.start); offset(v.end); if (v.end < v.start)
18
+ throw new TypeError('reversed source span'); }
19
+ function risks(v) {
20
+ if (v === undefined)
21
+ return;
22
+ const a = Array.isArray(v) ? v : [v];
23
+ if (a.length > 64 || a.some(n => typeof n !== 'number' || !Number.isFinite(n) || n <= 0 || n >= 1))
24
+ throw new TypeError('invalid max_error');
25
+ }
26
+ function instructions(v) { if (typeof v !== 'string')
27
+ throw new TypeError('instructions must be a string'); return v; }
28
+ class TaskQuestion extends QuestionSpec {
29
+ constructor(kind, text, fields) { super(kind, instructions(text), fields); }
30
+ read(response, id) {
31
+ const d = object(response.answers?.[id]);
32
+ if (d.type !== this.toJSON().type || d.experimental !== true)
33
+ throw new TypeError('Basal returned a missing or mismatched task');
34
+ if (d.type === 'classify') {
35
+ probabilities(d.scores);
36
+ const scores = object(d.scores);
37
+ if (this.toJSON().multi_label) {
38
+ strings(d.labels);
39
+ if (d.labels.some(k => !Object.hasOwn(scores, k)))
40
+ throw new TypeError('unknown label');
41
+ probability(d.set_confidence);
42
+ }
43
+ else {
44
+ if (typeof d.label !== 'string' || !Object.hasOwn(scores, d.label) || d.top_label !== d.label)
45
+ throw new TypeError('invalid label');
46
+ probability(d.confidence);
47
+ automation(d.automated);
48
+ }
49
+ }
50
+ else if (d.type === 'verify') {
51
+ const claims = object(d.claims);
52
+ for (const raw of Object.values(claims)) {
53
+ const c = object(raw);
54
+ const p = object(c.probabilities);
55
+ probabilities(p);
56
+ if (typeof c.verdict !== 'string' || !['supported', 'contradicted', 'not_stated', 'not_supported'].includes(c.verdict) || !Object.hasOwn(p, c.verdict))
57
+ throw new TypeError('invalid verdict');
58
+ probability(c.confidence);
59
+ automation(c.automated);
60
+ }
61
+ strings(d.not_supported);
62
+ if (typeof d.all_supported !== 'boolean' || d.not_supported.some(k => !Object.hasOwn(claims, k)))
63
+ throw new TypeError('invalid verification summary');
64
+ }
65
+ else if (d.type === 'compress') {
66
+ if (typeof d.compressed !== 'string' || !['sentence', 'line', 'paragraph'].includes(d.unit) || !Array.isArray(d.units) || !Array.isArray(d.kept))
67
+ throw new TypeError('invalid compression');
68
+ for (const u of d.units) {
69
+ const v = object(u);
70
+ span(v);
71
+ probability(v.probability);
72
+ }
73
+ for (const k of d.kept) {
74
+ offset(k);
75
+ if (k >= d.units.length)
76
+ throw new TypeError('invalid kept unit');
77
+ }
78
+ offset(d.original_chars);
79
+ offset(d.compressed_chars);
80
+ if (typeof d.ratio !== 'number' || !Number.isFinite(d.ratio) || d.ratio < 0)
81
+ throw new TypeError('invalid ratio');
82
+ }
83
+ else {
84
+ object(d.data);
85
+ for (const raw of Object.values(object(d.fields))) {
86
+ const f = object(raw);
87
+ if (!Object.hasOwn(f, 'value') || !['choice', 'span'].includes(f.source) || !['validated', 'per decision'].includes(f.calibration))
88
+ throw new TypeError('invalid extraction field');
89
+ probability(f.confidence);
90
+ if (f.probabilities !== undefined)
91
+ probabilities(f.probabilities);
92
+ for (const k of ['present_probability', 'line_probability', 'span_probability'])
93
+ if (f[k] !== undefined)
94
+ probability(f[k]);
95
+ if (f.text !== undefined) {
96
+ if (typeof f.text !== 'string')
97
+ throw new TypeError('invalid source text');
98
+ span(f);
99
+ }
100
+ }
101
+ }
102
+ return structuredClone(d);
103
+ }
104
+ }
105
+ export class ClassifyQuestion extends TaskQuestion {
106
+ constructor(text, labels, options = {}) {
107
+ if (!labels || typeof labels !== 'object' || Object.keys(labels).length < 1 || Object.keys(labels).length > 256)
108
+ throw new TypeError('classify requires 1..256 labels');
109
+ risks(options.max_error);
110
+ for (const k of ['multi_label', 'none'])
111
+ if (options[k] !== undefined && typeof options[k] !== 'boolean')
112
+ throw new TypeError(`invalid ${k}`);
113
+ if (options.option_keys !== undefined && !['show', 'hide'].includes(options.option_keys))
114
+ throw new TypeError('invalid option_keys');
115
+ if (options.threshold !== undefined) {
116
+ probability(options.threshold);
117
+ if (options.objective !== undefined && options.objective !== 'threshold')
118
+ throw new TypeError('threshold requires threshold objective');
119
+ }
120
+ for (const k of ['min', 'max'])
121
+ if (options[k] !== undefined)
122
+ offset(options[k]);
123
+ if (options.min !== undefined && options.max !== undefined && options.min > options.max)
124
+ throw new TypeError('min exceeds max');
125
+ if (options.objective !== undefined && !['threshold', 'exact', 'iou', 'f1'].includes(options.objective))
126
+ throw new TypeError('invalid objective');
127
+ if (!options.multi_label && ['threshold', 'min', 'max', 'objective'].some(k => Object.hasOwn(options, k)))
128
+ throw new TypeError('selection options require multi_label');
129
+ super('classify', text, { labels: structuredClone(labels), ...options });
130
+ }
131
+ }
132
+ export class VerifyQuestion extends TaskQuestion {
133
+ constructor(text, claims, options = {}) {
134
+ if (!claims || typeof claims !== 'object' || !Object.keys(claims).length)
135
+ throw new TypeError('verify requires claims');
136
+ risks(options.max_error);
137
+ if (options.binary !== undefined && typeof options.binary !== 'boolean')
138
+ throw new TypeError('invalid binary');
139
+ super('verify', text, { claims: structuredClone(claims), ...options });
140
+ }
141
+ }
142
+ export class CompressQuestion extends TaskQuestion {
143
+ constructor(text = '', options = {}) {
144
+ if (options.unit !== undefined && !['sentence', 'line', 'paragraph'].includes(options.unit))
145
+ throw new TypeError('invalid unit');
146
+ if (options.keep_ratio !== undefined) {
147
+ probability(options.keep_ratio);
148
+ if (options.keep_ratio === 0)
149
+ throw new TypeError('keep_ratio must be positive');
150
+ }
151
+ if (options.threshold !== undefined)
152
+ probability(options.threshold);
153
+ if (options.min_keep !== undefined)
154
+ offset(options.min_keep);
155
+ super('compress', text, options);
156
+ }
157
+ }
158
+ export class ExtractQuestion extends TaskQuestion {
159
+ constructor(text, schema) { object(schema); super('extract', text, { schema }); }
160
+ }
161
+ export function classify(text, labels, options) { return new ClassifyQuestion(text, labels, options); }
162
+ export function verify(text, claims, options) { return new VerifyQuestion(text, claims, options); }
163
+ export function compress(text = '', options) { return new CompressQuestion(text, options); }
164
+ export function extract(text, schema) { return new ExtractQuestion(text, schema); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itsoltech/basal-client",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "TypeScript and JavaScript client for the native Basal HTTP API",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.cjs",