@itsoltech/basal-client 0.2.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.
package/README.md CHANGED
@@ -103,14 +103,28 @@ Usage exposes `longest_prompt_tokens`, `second_order_branches` and media counts,
103
103
  soft tokens and detail. Discovery exposes `prompt_format`, `orders`, enabled
104
104
  media and calibration temperatures. Model names remain strings: use exact names
105
105
  from model discovery, not a hardcoded model enumeration. Keep old models and
106
- raw JSON access available. These SDKs cover server-supported format-2 decisions;
107
- upstream 2.1 task types are not implemented by this server.
106
+ raw JSON access available. The server validates each model’s capabilities.
107
+ Basal 2.1 task helpers are described below.
108
108
 
109
109
  ```typescript
110
110
  import { BasalClient, image, tool, multi } from "@itsoltech/basal-client";
111
111
  const response = await new BasalClient().decide("Inspect the supplied material.", {
112
- route: tool("Choose a tool.", [{name:"inspect", description:"Inspect material", inputSchema:{type:"object"}}], {extra:["none"], rank:2}),
112
+ route: tool("Choose a tool.", [{name:"inspect", description:"Inspect material", inputSchema:{type:"object"}}], {extra:["none"], rank:2, description_chars:120}),
113
113
  labels: multi("Select visible objects.", {animal:"An animal", vehicle:"A vehicle"}, {objective:"f1"}),
114
114
  }, {model:"basal-omni-1.0-mini", lang:"en", media:[image(new Uint8Array(await file.arrayBuffer()))]});
115
115
  console.log(response.answers.route, response.answers.labels.expected_score);
116
116
  ```
117
+
118
+ Tool descriptions are normalized by the server. Omit `description_chars`
119
+ (`DescriptionChars` in .NET) to keep the default cap of 300 Unicode code points;
120
+ use `0` to disable truncation. Descriptions longer than a positive cap are
121
+ shortened at a word boundary where possible, with an ellipsis appended. The SDK forwards this option
122
+ without changing the supplied tool definitions.
123
+
124
+ ## Experimental Basal 2.1 tasks
125
+
126
+ Typed classify, verify, compress and extract factories preserve nullable task automation,
127
+ Unicode code point offsets and nested JSON extraction values. Task answers do not share
128
+ the primitive probabilities field. See [task semantics](../../docs/TASKS.md) and
129
+ [SDK examples](../../docs/SDK.md#zadania-basal-21). These source changes require a server
130
+ with the experimental task layer; installing an SDK does not update the inference server.
@@ -18,6 +18,7 @@ exports.BasalClient = exports.BasalResponseTooLargeError = exports.BasalProtocol
18
18
  const questions_cjs_1 = require("./questions.cjs");
19
19
  __exportStar(require("./questions.cjs"), exports);
20
20
  __exportStar(require("./media.cjs"), exports);
21
+ __exportStar(require("./tasks.cjs"), exports);
21
22
  class BasalError extends Error {
22
23
  }
23
24
  exports.BasalError = BasalError;
@@ -1,6 +1,8 @@
1
1
  import { type QuestionSet, type TypedDecisionResponse, type DecisionOptions } from "./questions.cjs";
2
2
  export * from "./questions.cjs";
3
3
  export * from "./media.cjs";
4
+ export * from "./tasks.cjs";
5
+ import type { TaskAnswer } from "./tasks.cjs";
4
6
  /** Native Basal wire types. Criterion order is significant; do not sort keys. */
5
7
  export type JsonValue = string | number | boolean | null | JsonValue[] | {
6
8
  [key: string]: JsonValue;
@@ -23,7 +25,7 @@ export interface ToolRanking {
23
25
  }
24
26
  export type ToolDefinition = Record<string, JsonValue>;
25
27
  export interface Question {
26
- type?: "choice" | "noul" | "score" | "multi" | "act" | "tool";
28
+ type?: "choice" | "noul" | "score" | "multi" | "act" | "tool" | "classify" | "verify" | "compress" | "extract";
27
29
  instructions?: JsonValue;
28
30
  criteria?: Record<string, JsonValue> | JsonValue[];
29
31
  levels?: Record<string, JsonValue> | JsonValue[];
@@ -35,7 +37,7 @@ export interface Question {
35
37
  base?: "noul" | "choice" | "score";
36
38
  costs?: Record<string, number> | Record<string, Record<string, number>>;
37
39
  defer_action?: string;
38
- max_error?: number;
40
+ max_error?: number | readonly number[];
39
41
  objective?: MultiObjective;
40
42
  exclusive?: readonly string[];
41
43
  fallback?: string;
@@ -43,7 +45,17 @@ export interface Question {
43
45
  tools?: readonly ToolDefinition[];
44
46
  extra?: readonly ("answer_directly" | "ask_user" | "none")[];
45
47
  rank?: number;
48
+ description_chars?: number;
46
49
  multi?: boolean;
50
+ labels?: Record<string, JsonValue> | JsonValue[];
51
+ multi_label?: boolean;
52
+ none?: boolean;
53
+ claims?: Record<string, JsonValue> | JsonValue[];
54
+ binary?: boolean;
55
+ unit?: 'sentence' | 'line' | 'paragraph';
56
+ keep_ratio?: number;
57
+ min_keep?: number;
58
+ schema?: Record<string, JsonValue>;
47
59
  }
48
60
  export interface DecisionRequest {
49
61
  state: JsonValue;
@@ -122,7 +134,7 @@ export interface ActAnswer<K extends string = string, A extends string = string>
122
134
  max_error?: string;
123
135
  deferred?: boolean;
124
136
  }
125
- export type Answer = ChoiceAnswer | NoulAnswer | ScoreAnswer | MultiAnswer | ActAnswer | ToolAnswer;
137
+ export type Answer = ChoiceAnswer | NoulAnswer | ScoreAnswer | MultiAnswer | ActAnswer | ToolAnswer | TaskAnswer;
126
138
  export interface Usage {
127
139
  input_tokens: number;
128
140
  output_tokens: number;
@@ -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); }
@@ -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 {};
@@ -1,6 +1,8 @@
1
1
  import { type QuestionSet, type TypedDecisionResponse, type DecisionOptions } from "./questions.js";
2
2
  export * from "./questions.js";
3
3
  export * from "./media.js";
4
+ export * from "./tasks.js";
5
+ import type { TaskAnswer } from "./tasks.js";
4
6
  /** Native Basal wire types. Criterion order is significant; do not sort keys. */
5
7
  export type JsonValue = string | number | boolean | null | JsonValue[] | {
6
8
  [key: string]: JsonValue;
@@ -23,7 +25,7 @@ export interface ToolRanking {
23
25
  }
24
26
  export type ToolDefinition = Record<string, JsonValue>;
25
27
  export interface Question {
26
- type?: "choice" | "noul" | "score" | "multi" | "act" | "tool";
28
+ type?: "choice" | "noul" | "score" | "multi" | "act" | "tool" | "classify" | "verify" | "compress" | "extract";
27
29
  instructions?: JsonValue;
28
30
  criteria?: Record<string, JsonValue> | JsonValue[];
29
31
  levels?: Record<string, JsonValue> | JsonValue[];
@@ -35,7 +37,7 @@ export interface Question {
35
37
  base?: "noul" | "choice" | "score";
36
38
  costs?: Record<string, number> | Record<string, Record<string, number>>;
37
39
  defer_action?: string;
38
- max_error?: number;
40
+ max_error?: number | readonly number[];
39
41
  objective?: MultiObjective;
40
42
  exclusive?: readonly string[];
41
43
  fallback?: string;
@@ -43,7 +45,17 @@ export interface Question {
43
45
  tools?: readonly ToolDefinition[];
44
46
  extra?: readonly ("answer_directly" | "ask_user" | "none")[];
45
47
  rank?: number;
48
+ description_chars?: number;
46
49
  multi?: boolean;
50
+ labels?: Record<string, JsonValue> | JsonValue[];
51
+ multi_label?: boolean;
52
+ none?: boolean;
53
+ claims?: Record<string, JsonValue> | JsonValue[];
54
+ binary?: boolean;
55
+ unit?: 'sentence' | 'line' | 'paragraph';
56
+ keep_ratio?: number;
57
+ min_keep?: number;
58
+ schema?: Record<string, JsonValue>;
47
59
  }
48
60
  export interface DecisionRequest {
49
61
  state: JsonValue;
@@ -122,7 +134,7 @@ export interface ActAnswer<K extends string = string, A extends string = string>
122
134
  max_error?: string;
123
135
  deferred?: boolean;
124
136
  }
125
- export type Answer = ChoiceAnswer | NoulAnswer | ScoreAnswer | MultiAnswer | ActAnswer | ToolAnswer;
137
+ export type Answer = ChoiceAnswer | NoulAnswer | ScoreAnswer | MultiAnswer | ActAnswer | ToolAnswer | TaskAnswer;
126
138
  export interface Usage {
127
139
  input_tokens: number;
128
140
  output_tokens: number;
package/dist/esm/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { buildRequest } from "./questions.js";
2
2
  export * from "./questions.js";
3
3
  export * from "./media.js";
4
+ export * from "./tasks.js";
4
5
  export class BasalError extends Error {
5
6
  }
6
7
  /** Non-2xx response. Server body and Retry-After are available without logging them. */
@@ -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>;
@@ -30,13 +30,13 @@ 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 = {
@@ -201,6 +201,11 @@ export class ToolQuestion extends QuestionSpec {
201
201
  if (options.multi !== undefined && typeof options.multi !== 'boolean')
202
202
  throw new TypeError('invalid multi');
203
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
+ }
204
209
  if (options.rank !== undefined) {
205
210
  if (!Number.isSafeInteger(options.rank) || options.rank < 1)
206
211
  throw new TypeError('rank must be a positive integer');
@@ -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.2.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",