@itsoltech/basal-client 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,196 @@
1
+ import { type QuestionSet, type TypedDecisionResponse, type DecisionOptions } from "./questions.cjs";
2
+ export * from "./questions.cjs";
3
+ export * from "./media.cjs";
4
+ /** Native Basal wire types. Criterion order is significant; do not sort keys. */
5
+ export type JsonValue = string | number | boolean | null | JsonValue[] | {
6
+ [key: string]: JsonValue;
7
+ };
8
+ export type Calibration = "validated" | "unvalidated" | "text";
9
+ export type MultiObjective = "threshold" | "exact" | "iou" | "f1";
10
+ export interface MediaInput {
11
+ type: "image" | "audio";
12
+ data: string;
13
+ }
14
+ export interface MediaUsage {
15
+ images: number;
16
+ audio: number;
17
+ media_tokens: number;
18
+ detail: "default" | "high";
19
+ }
20
+ export interface ToolRanking {
21
+ tool: string;
22
+ probability: number;
23
+ }
24
+ export type ToolDefinition = Record<string, JsonValue>;
25
+ export interface Question {
26
+ type?: "choice" | "noul" | "score" | "multi" | "act" | "tool";
27
+ instructions?: JsonValue;
28
+ criteria?: Record<string, JsonValue> | JsonValue[];
29
+ levels?: Record<string, JsonValue> | JsonValue[];
30
+ option_keys?: "show" | "hide";
31
+ evidence?: boolean;
32
+ threshold?: number;
33
+ min?: number;
34
+ max?: number;
35
+ base?: "noul" | "choice" | "score";
36
+ costs?: Record<string, number> | Record<string, Record<string, number>>;
37
+ defer_action?: string;
38
+ max_error?: number;
39
+ objective?: MultiObjective;
40
+ exclusive?: readonly string[];
41
+ fallback?: string;
42
+ fallback_threshold?: number;
43
+ tools?: readonly ToolDefinition[];
44
+ extra?: readonly ("answer_directly" | "ask_user" | "none")[];
45
+ rank?: number;
46
+ multi?: boolean;
47
+ }
48
+ export interface DecisionRequest {
49
+ state: JsonValue;
50
+ questions: Record<string, Question>;
51
+ model?: string;
52
+ facts?: "off" | "auto";
53
+ lang?: "pl" | "en";
54
+ media?: readonly MediaInput[];
55
+ detail?: "default" | "high";
56
+ }
57
+ export interface EvidenceSpan {
58
+ text: string;
59
+ start: number;
60
+ end: number;
61
+ probability: number;
62
+ }
63
+ interface BaseAnswer<K extends string = string> {
64
+ probabilities: Record<K, number>;
65
+ evidence?: EvidenceSpan[];
66
+ calibration?: Calibration;
67
+ }
68
+ export interface ChoiceAnswer<K extends string = string> extends BaseAnswer<K> {
69
+ type: "choice";
70
+ choice: K;
71
+ confidence: number;
72
+ }
73
+ export interface NoulAnswer extends BaseAnswer<"true" | "false"> {
74
+ type: "noul";
75
+ noul: number;
76
+ confidence: number;
77
+ }
78
+ export interface ScoreAnswer extends BaseAnswer {
79
+ type: "score";
80
+ score: number;
81
+ legend: Record<string, string>;
82
+ confidence: number;
83
+ }
84
+ export interface SelectionResult<K extends string = string> {
85
+ threshold?: number;
86
+ objective?: MultiObjective;
87
+ expected_score?: number | null;
88
+ constraints_applied?: JsonValue;
89
+ set_confidence: number;
90
+ }
91
+ export interface MultiAnswer<K extends string = string> extends BaseAnswer<K>, SelectionResult<K> {
92
+ type: "multi";
93
+ selected: K[];
94
+ }
95
+ export interface SingleToolAnswer extends BaseAnswer {
96
+ type: "tool";
97
+ multi?: false;
98
+ tool: string | null;
99
+ call: boolean;
100
+ confidence: number;
101
+ ranking?: ToolRanking[];
102
+ deferred?: boolean;
103
+ refused?: string;
104
+ max_error?: string;
105
+ }
106
+ export interface MultiToolAnswer extends BaseAnswer, SelectionResult {
107
+ type: "tool";
108
+ multi: true;
109
+ tools: string[];
110
+ call: boolean;
111
+ ranking?: ToolRanking[];
112
+ }
113
+ export type ToolAnswer = SingleToolAnswer | MultiToolAnswer;
114
+ export interface ActAnswer<K extends string = string, A extends string = string> extends BaseAnswer<K> {
115
+ type: "act";
116
+ action: A;
117
+ answer: K;
118
+ expected_costs: Record<A, number>;
119
+ confidence: number;
120
+ calibration: Calibration;
121
+ refused?: string;
122
+ max_error?: string;
123
+ deferred?: boolean;
124
+ }
125
+ export type Answer = ChoiceAnswer | NoulAnswer | ScoreAnswer | MultiAnswer | ActAnswer | ToolAnswer;
126
+ export interface Usage {
127
+ input_tokens: number;
128
+ output_tokens: number;
129
+ questions?: number;
130
+ branches?: number;
131
+ latency_ms?: number;
132
+ longest_prompt_tokens?: number;
133
+ second_order_branches?: number;
134
+ media?: MediaUsage;
135
+ }
136
+ export interface DecisionResponse {
137
+ model: string;
138
+ answers: Record<string, Answer>;
139
+ usage: Usage;
140
+ }
141
+ export interface ModelMetadata {
142
+ name: string;
143
+ description?: string;
144
+ release_date?: string;
145
+ mode?: string;
146
+ early_exit?: JsonValue[];
147
+ prompt_format?: number;
148
+ orders?: JsonValue;
149
+ temperature_per_prim?: Record<string, number>;
150
+ media?: ("image" | "audio")[];
151
+ }
152
+ export interface ModelsResponse {
153
+ models: ModelMetadata[];
154
+ }
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
+ /** Web Fetch input accepted by browsers and server runtimes. */
171
+ export type RequestInput = string | URL | Request;
172
+ /** A transport adapter must honor signal/redirect and return a standard Web Response. */
173
+ export type Fetch = (input: RequestInput, init?: RequestInit) => Promise<Response>;
174
+ export interface BasalClientOptions {
175
+ baseUrl?: string;
176
+ apiKey?: string;
177
+ timeoutMs?: number;
178
+ maxResponseBytes?: number;
179
+ /** Defaults to the runtime's native fetch; custom HTTP clients can supply a Fetch adapter. */
180
+ fetch?: Fetch;
181
+ }
182
+ /** Reusable fetch client. Server root URL, optional proxy prefix, without /v1.
183
+ * No automatic retries. AbortSignal cancels a request; timeout includes body reading.
184
+ */
185
+ export declare class BasalClient {
186
+ private readonly baseUrl;
187
+ private readonly headers;
188
+ private readonly timeoutMs;
189
+ private readonly maxResponseBytes;
190
+ private readonly fetch;
191
+ constructor(options?: BasalClientOptions);
192
+ private request;
193
+ decide(request: DecisionRequest, signal?: AbortSignal): Promise<DecisionResponse>;
194
+ decide<const Q extends QuestionSet>(state: JsonValue, questions: Q, options?: DecisionOptions): Promise<TypedDecisionResponse<Q>>;
195
+ models(signal?: AbortSignal): Promise<ModelsResponse>;
196
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.image = image;
4
+ exports.audio = audio;
5
+ function media(type, data) {
6
+ if (data instanceof Uint8Array) {
7
+ // Bounded chunks avoid the argument limit of spreading a whole image/audio file.
8
+ const chunks = [];
9
+ for (let offset = 0; offset < data.length; offset += 8192)
10
+ chunks.push(String.fromCharCode(...data.subarray(offset, offset + 8192)));
11
+ data = btoa(chunks.join(''));
12
+ }
13
+ if (typeof data !== 'string' || !data.trim() || /^https?:\/\//i.test(data))
14
+ throw new TypeError('media data must be bytes, base64 or a data URL');
15
+ return { type, data };
16
+ }
17
+ /** Inline image; never fetches a URL or reads a file. */
18
+ function image(data) { return media('image', data); }
19
+ /** Inline audio; never fetches a URL or reads a file. */
20
+ function audio(data) { return media('audio', data); }
@@ -0,0 +1,5 @@
1
+ import type { MediaInput } from './index.cjs';
2
+ /** Inline image; never fetches a URL or reads a file. */
3
+ export declare function image(data: Uint8Array | string): MediaInput;
4
+ /** Inline audio; never fetches a URL or reads a file. */
5
+ export declare function audio(data: Uint8Array | string): MediaInput;
@@ -0,0 +1,284 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ActQuestion = exports.ToolQuestion = exports.MultiQuestion = exports.ScoreQuestion = exports.NoulQuestion = exports.ChoiceQuestion = exports.QuestionSpec = void 0;
4
+ exports.choice = choice;
5
+ exports.noul = noul;
6
+ exports.score = score;
7
+ exports.multi = multi;
8
+ exports.act = act;
9
+ exports.tool = tool;
10
+ exports.buildRequest = buildRequest;
11
+ function text(value, name) {
12
+ if (typeof value !== 'string' || !value.trim())
13
+ throw new TypeError(`${name} must be a nonempty string`);
14
+ return value;
15
+ }
16
+ function criteria(values, min, max = Infinity) {
17
+ if (!values || typeof values !== 'object' || Array.isArray(values))
18
+ throw new TypeError('criteria must be an object');
19
+ const entries = Object.entries(values);
20
+ if (entries.length < min || entries.length > max)
21
+ throw new TypeError('invalid number of criteria');
22
+ for (const [key, value] of entries) {
23
+ text(key, 'criterion key');
24
+ text(value, 'criterion description');
25
+ }
26
+ return Object.fromEntries(entries);
27
+ }
28
+ function number(value, name, probability = false) {
29
+ if (typeof value !== 'number' || !Number.isFinite(value) || (probability && (value < 0 || value > 1))) {
30
+ throw new TypeError(`invalid ${name}`);
31
+ }
32
+ return value;
33
+ }
34
+ function evidence(options) {
35
+ if (options.evidence !== undefined && typeof options.evidence !== 'boolean')
36
+ throw new TypeError('evidence must be boolean');
37
+ return options.evidence ? { evidence: true } : {};
38
+ }
39
+ /** Immutable snapshot of a typed question. Use factories or the concrete constructors. */
40
+ class QuestionSpec {
41
+ wire;
42
+ constructor(kind, instructions, fields) {
43
+ this.wire = structuredClone({ ...fields, type: kind, instructions: text(instructions, 'instructions') });
44
+ }
45
+ toJSON() { return structuredClone(this.wire); }
46
+ /** Validate and retrieve an answer with this question's result type. */
47
+ read(response, id) {
48
+ const answer = response.answers?.[id];
49
+ if (!answer || answer.type !== this.wire.type || !answer.probabilities || typeof answer.probabilities !== 'object' || Array.isArray(answer.probabilities)) {
50
+ throw new TypeError('Basal returned a missing or mismatched answer');
51
+ }
52
+ const fields = {
53
+ choice: { choice: 'string', confidence: 'number' }, noul: { noul: 'number', confidence: 'number' },
54
+ score: { score: 'number', legend: 'object', confidence: 'number' },
55
+ multi: { selected: 'object', set_confidence: 'number' },
56
+ tool: answer.type === 'tool' && answer.multi ? { tools: 'object', call: 'boolean', set_confidence: 'number' } : { call: 'boolean', confidence: 'number' },
57
+ act: { action: 'string', answer: 'string', expected_costs: 'object', confidence: 'number', calibration: 'string' },
58
+ };
59
+ const data = answer;
60
+ for (const [field, kind] of Object.entries(fields[answer.type])) {
61
+ if (typeof data[field] !== kind || data[field] === null || (kind === 'number' && !Number.isFinite(data[field]))) {
62
+ throw new TypeError('Basal returned an invalid answer field');
63
+ }
64
+ }
65
+ for (const value of Object.values(answer.probabilities))
66
+ number(value, 'probability', true);
67
+ const expectedKeys = this.wire.type === 'noul' || (this.wire.type === 'act' && this.wire.criteria === undefined)
68
+ ? ['true', 'false'] : Array.isArray(this.wire.criteria)
69
+ ? this.wire.criteria.map((_, index) => String(index)) : Object.keys(this.wire.criteria ?? {});
70
+ if (expectedKeys.some(key => !Object.hasOwn(answer.probabilities, key)))
71
+ throw new TypeError('missing outcome probability');
72
+ if (answer.evidence !== undefined) {
73
+ if (!Array.isArray(answer.evidence))
74
+ throw new TypeError('invalid evidence');
75
+ for (const span of answer.evidence) {
76
+ if (!span || typeof span.text !== 'string' || !Number.isSafeInteger(span.start) || !Number.isSafeInteger(span.end)
77
+ || span.start < 0 || span.end < 0)
78
+ throw new TypeError('invalid evidence span');
79
+ number(span.probability, 'evidence probability', true);
80
+ }
81
+ }
82
+ if (answer.type === 'choice' && !Object.hasOwn(this.wire.criteria ?? {}, answer.choice))
83
+ throw new TypeError('unknown choice');
84
+ if (answer.type === 'multi' && (!Array.isArray(answer.selected) || answer.selected.some(k => typeof k !== 'string' || !Object.hasOwn(this.wire.criteria ?? {}, k))))
85
+ throw new TypeError('invalid selected labels');
86
+ if (answer.type === 'score' && (Array.isArray(answer.legend) || Object.values(answer.legend).some(v => typeof v !== 'string')))
87
+ throw new TypeError('invalid legend');
88
+ if (answer.type === 'act') {
89
+ if (!['validated', 'unvalidated', 'text'].includes(answer.calibration) || !Object.hasOwn(this.wire.costs ?? {}, answer.action) || !expectedKeys.includes(answer.answer))
90
+ throw new TypeError('invalid action');
91
+ if (Array.isArray(answer.expected_costs) || Object.keys(this.wire.costs ?? {}).some(key => !Object.hasOwn(answer.expected_costs, key)))
92
+ throw new TypeError('invalid expected costs');
93
+ for (const value of Object.values(answer.expected_costs))
94
+ number(value, 'expected cost');
95
+ }
96
+ if (answer.type === 'multi' || (answer.type === 'tool' && answer.multi)) {
97
+ validateSelection(answer, this.wire.objective ?? 'threshold');
98
+ }
99
+ if (answer.type === 'tool') {
100
+ if ((answer.multi ?? false) !== (this.wire.multi ?? false))
101
+ throw new TypeError('mismatched tool mode');
102
+ if (answer.multi) {
103
+ if (!Array.isArray(answer.tools) || answer.tools.some(k => typeof k !== 'string' || !Object.hasOwn(answer.probabilities, k)))
104
+ throw new TypeError('invalid selected tools');
105
+ }
106
+ else {
107
+ if (answer.tool === null ? answer.deferred !== true : typeof answer.tool !== 'string' || !Object.hasOwn(answer.probabilities, answer.tool))
108
+ throw new TypeError('invalid selected tool');
109
+ if (answer.deferred !== undefined && typeof answer.deferred !== 'boolean')
110
+ throw new TypeError('invalid deferred');
111
+ for (const key of ['refused', 'max_error'])
112
+ if (answer[key] !== undefined && typeof answer[key] !== 'string')
113
+ throw new TypeError('invalid tool metadata');
114
+ }
115
+ if (answer.ranking !== undefined) {
116
+ if (!Array.isArray(answer.ranking))
117
+ throw new TypeError('invalid ranking');
118
+ for (const entry of answer.ranking) {
119
+ if (!entry || typeof entry.tool !== 'string' || !Object.hasOwn(answer.probabilities, entry.tool))
120
+ throw new TypeError('invalid ranking entry');
121
+ number(entry.probability, 'ranking probability', true);
122
+ }
123
+ }
124
+ }
125
+ if (answer.type === 'act' && answer.deferred !== undefined && typeof answer.deferred !== 'boolean')
126
+ throw new TypeError('invalid deferred');
127
+ return { ...answer, ...(answer.type === 'multi' || answer.type === 'tool' ? {} : { evidence: answer.evidence ?? [] }) };
128
+ }
129
+ }
130
+ exports.QuestionSpec = QuestionSpec;
131
+ class ChoiceQuestion extends QuestionSpec {
132
+ constructor(instructions, values, options = {}) {
133
+ if (options.option_keys !== undefined && !['show', 'hide'].includes(options.option_keys))
134
+ throw new TypeError('invalid option_keys');
135
+ super('choice', instructions, { criteria: criteria(values, 2, 255), ...evidence(options), ...(options.option_keys === 'hide' ? { option_keys: 'hide' } : {}) });
136
+ }
137
+ }
138
+ exports.ChoiceQuestion = ChoiceQuestion;
139
+ class NoulQuestion extends QuestionSpec {
140
+ constructor(instructions, options = {}) { super('noul', instructions, evidence(options)); }
141
+ }
142
+ exports.NoulQuestion = NoulQuestion;
143
+ class ScoreQuestion extends QuestionSpec {
144
+ constructor(instructions, levels, options = {}) {
145
+ if (!Array.isArray(levels) || levels.length < 2 || levels.length > 255)
146
+ throw new TypeError('score requires 2–255 ordered levels');
147
+ super('score', instructions, { criteria: levels.map(v => text(v, 'level')), ...evidence(options) });
148
+ }
149
+ }
150
+ exports.ScoreQuestion = ScoreQuestion;
151
+ function selectionOptions(options, keys) {
152
+ const fields = {};
153
+ const objective = options.objective ?? 'threshold';
154
+ if (!['threshold', 'exact', 'iou', 'f1'].includes(objective))
155
+ throw new TypeError('invalid objective');
156
+ if (objective !== 'threshold')
157
+ fields.objective = objective;
158
+ if (options.threshold !== undefined) {
159
+ if (objective !== 'threshold')
160
+ throw new TypeError('threshold applies only to objective threshold');
161
+ fields.threshold = number(options.threshold, 'threshold', true);
162
+ }
163
+ for (const key of ['min', 'max']) {
164
+ const v = options[key];
165
+ if (v !== undefined) {
166
+ if (!Number.isSafeInteger(v) || v < 0 || (keys && v > keys.length))
167
+ throw new TypeError(`invalid ${key}`);
168
+ fields[key] = v;
169
+ }
170
+ }
171
+ if (options.min !== undefined && options.max !== undefined && options.min > options.max)
172
+ throw new TypeError('min exceeds max');
173
+ if (options.exclusive !== undefined) {
174
+ if (!Array.isArray(options.exclusive))
175
+ throw new TypeError('exclusive must be an array');
176
+ fields.exclusive = options.exclusive.map(k => { text(k, 'exclusive key'); if (keys && !keys.includes(k))
177
+ throw new TypeError('unknown exclusive key'); return k; });
178
+ }
179
+ if (options.fallback !== undefined) {
180
+ fields.fallback = text(options.fallback, 'fallback');
181
+ if (keys && !keys.includes(options.fallback))
182
+ throw new TypeError('unknown fallback');
183
+ }
184
+ if (options.fallback_threshold !== undefined) {
185
+ if (options.fallback === undefined)
186
+ throw new TypeError('fallback_threshold requires fallback');
187
+ fields.fallback_threshold = number(options.fallback_threshold, 'fallback_threshold', true);
188
+ }
189
+ return fields;
190
+ }
191
+ function validateSelection(answer, objective) {
192
+ number(answer.set_confidence, 'set_confidence', true);
193
+ if (objective === 'threshold')
194
+ number(answer.threshold, 'threshold', true);
195
+ else {
196
+ if (answer.objective !== objective)
197
+ throw new TypeError('mismatched objective');
198
+ if (answer.expected_score !== null && answer.expected_score !== undefined)
199
+ number(answer.expected_score, 'expected_score', true);
200
+ }
201
+ }
202
+ class MultiQuestion extends QuestionSpec {
203
+ constructor(instructions, values, options = {}) {
204
+ if ('evidence' in options)
205
+ throw new TypeError('multi does not support evidence');
206
+ super('multi', instructions, { criteria: criteria(values, 1), ...selectionOptions(options, Object.keys(values)) });
207
+ }
208
+ }
209
+ exports.MultiQuestion = MultiQuestion;
210
+ class ToolQuestion extends QuestionSpec {
211
+ constructor(instructions, tools, options = {}) {
212
+ if (!Array.isArray(tools) || tools.some(t => !t || typeof t !== 'object' || Array.isArray(t)))
213
+ throw new TypeError('tools must contain definition objects');
214
+ if (options.extra !== undefined && (!Array.isArray(options.extra) || options.extra.some(k => !['answer_directly', 'ask_user', 'none'].includes(k))))
215
+ throw new TypeError('invalid extra tool option');
216
+ if (options.multi !== undefined && typeof options.multi !== 'boolean')
217
+ throw new TypeError('invalid multi');
218
+ const fields = { tools, ...(options.extra === undefined ? {} : { extra: options.extra }) };
219
+ if (options.rank !== undefined) {
220
+ if (!Number.isSafeInteger(options.rank) || options.rank < 1)
221
+ throw new TypeError('rank must be a positive integer');
222
+ fields.rank = options.rank;
223
+ }
224
+ if (options.max_error !== undefined)
225
+ fields.max_error = number(options.max_error, 'max_error', true);
226
+ const selection = selectionOptions(options);
227
+ if (!options.multi && Object.keys(selection).length)
228
+ throw new TypeError('selection options require multi');
229
+ if (options.multi) {
230
+ fields.multi = true;
231
+ Object.assign(fields, selection);
232
+ }
233
+ super('tool', instructions, fields);
234
+ }
235
+ }
236
+ exports.ToolQuestion = ToolQuestion;
237
+ class ActQuestion extends QuestionSpec {
238
+ constructor(instructions, costs, options = {}) {
239
+ const optionsMap = options.criteria === undefined ? undefined : criteria(options.criteria, 2, 255);
240
+ const outcomes = Object.keys(optionsMap ?? { 'true': 'Yes', 'false': 'No' });
241
+ if (!costs || typeof costs !== 'object' || Array.isArray(costs) || !Object.keys(costs).length)
242
+ throw new TypeError('act requires a cost matrix');
243
+ const matrix = {};
244
+ for (const [action, row] of Object.entries(costs)) {
245
+ text(action, 'action');
246
+ if (!row || typeof row !== 'object' || Array.isArray(row) || Object.keys(row).length !== outcomes.length || outcomes.some(k => !Object.hasOwn(row, k)))
247
+ throw new TypeError('each cost row must cover every outcome exactly');
248
+ Object.defineProperty(matrix, action, { value: Object.fromEntries(Object.entries(row).map(([k, v]) => [k, number(v, 'cost')])), enumerable: true });
249
+ }
250
+ if (options.defer_action !== undefined && !Object.hasOwn(matrix, options.defer_action))
251
+ throw new TypeError('unknown defer_action');
252
+ super('act', instructions, { costs: matrix, ...evidence(options), ...(optionsMap ? { criteria: optionsMap } : {}),
253
+ ...(options.defer_action === undefined ? {} : { defer_action: options.defer_action }),
254
+ ...(options.max_error === undefined ? {} : { max_error: number(options.max_error, 'max_error', true) }) });
255
+ }
256
+ }
257
+ exports.ActQuestion = ActQuestion;
258
+ function choice(instructions, values, options) { return new ChoiceQuestion(instructions, values, options); }
259
+ function noul(instructions, options) { return new NoulQuestion(instructions, options); }
260
+ function score(instructions, levels, options) { return new ScoreQuestion(instructions, levels, options); }
261
+ function multi(instructions, values, options) { return new MultiQuestion(instructions, values, options); }
262
+ function act(instructions, costs, options) { return new ActQuestion(instructions, costs, options); }
263
+ function tool(instructions, tools, options) { return new ToolQuestion(instructions, tools, options); }
264
+ function buildRequest(state, questions, options) {
265
+ if (state === null || state === undefined)
266
+ throw new TypeError('state must not be null/undefined');
267
+ if (!questions || !Object.keys(questions).length)
268
+ throw new TypeError('questions must not be empty');
269
+ if (Object.getOwnPropertySymbols(questions).length)
270
+ throw new TypeError('question keys must be strings or numbers');
271
+ const entries = Object.entries(questions).map(([id, q]) => { text(id, 'question ID'); if (!(q instanceof QuestionSpec))
272
+ throw new TypeError('expected typed question'); return [id, q.toJSON()]; });
273
+ if (options.model !== undefined)
274
+ text(options.model, 'model');
275
+ if (options.facts !== undefined && !['off', 'auto'].includes(options.facts))
276
+ throw new TypeError('invalid facts');
277
+ if (options.lang !== undefined && !['pl', 'en'].includes(options.lang))
278
+ throw new TypeError('invalid lang');
279
+ if (options.detail !== undefined && !['default', 'high'].includes(options.detail))
280
+ throw new TypeError('invalid detail');
281
+ 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())))
282
+ throw new TypeError('invalid media');
283
+ 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 }) };
284
+ }
@@ -0,0 +1,85 @@
1
+ import type { ActAnswer, Answer, ChoiceAnswer, DecisionResponse, EvidenceSpan, JsonValue, MultiAnswer, NoulAnswer, Question, ScoreAnswer, ToolAnswer, ToolDefinition, MultiObjective, DecisionRequest } from './index.cjs';
2
+ /** Required result metadata on the typed path; missing spans become an empty array. */
3
+ export type TypedChoiceAnswer<K extends string = string> = ChoiceAnswer<K> & {
4
+ evidence: EvidenceSpan[];
5
+ };
6
+ export type TypedNoulAnswer = NoulAnswer & {
7
+ evidence: EvidenceSpan[];
8
+ };
9
+ export type TypedScoreAnswer = ScoreAnswer & {
10
+ evidence: EvidenceSpan[];
11
+ };
12
+ export type TypedMultiAnswer<K extends string = string> = Omit<MultiAnswer<K>, 'evidence'>;
13
+ export type TypedActAnswer<K extends string = string, A extends string = string> = ActAnswer<K, A> & {
14
+ evidence: EvidenceSpan[];
15
+ };
16
+ export interface EvidenceOptions {
17
+ evidence?: boolean;
18
+ }
19
+ export interface ChoiceOptions extends EvidenceOptions {
20
+ option_keys?: 'show' | 'hide';
21
+ }
22
+ export interface MultiOptions<K extends string = string> {
23
+ threshold?: number;
24
+ min?: number;
25
+ max?: number;
26
+ objective?: MultiObjective;
27
+ exclusive?: readonly K[];
28
+ fallback?: K;
29
+ fallback_threshold?: number;
30
+ }
31
+ export interface ToolOptions extends MultiOptions {
32
+ extra?: readonly ("answer_directly" | "ask_user" | "none")[];
33
+ rank?: number;
34
+ multi?: boolean;
35
+ max_error?: number;
36
+ }
37
+ export interface ActOptions<K extends string = string, A extends string = string> extends EvidenceOptions {
38
+ criteria?: Record<K, string>;
39
+ defer_action?: A;
40
+ max_error?: number;
41
+ }
42
+ export interface DecisionOptions extends Pick<DecisionRequest, 'model' | 'facts' | 'lang' | 'media' | 'detail'> {
43
+ signal?: AbortSignal;
44
+ }
45
+ /** Immutable snapshot of a typed question. Use factories or the concrete constructors. */
46
+ export declare abstract class QuestionSpec<A extends Answer = Answer> {
47
+ private readonly wire;
48
+ protected constructor(kind: A['type'], instructions: string, fields: Question);
49
+ toJSON(): Question;
50
+ /** Validate and retrieve an answer with this question's result type. */
51
+ read(response: DecisionResponse, id: string): A;
52
+ }
53
+ export declare class ChoiceQuestion<K extends string = string> extends QuestionSpec<TypedChoiceAnswer<K>> {
54
+ constructor(instructions: string, values: Record<K, string>, options?: ChoiceOptions);
55
+ }
56
+ export declare class NoulQuestion extends QuestionSpec<TypedNoulAnswer> {
57
+ constructor(instructions: string, options?: EvidenceOptions);
58
+ }
59
+ export declare class ScoreQuestion extends QuestionSpec<TypedScoreAnswer> {
60
+ constructor(instructions: string, levels: readonly string[], options?: EvidenceOptions);
61
+ }
62
+ export declare class MultiQuestion<K extends string = string> extends QuestionSpec<TypedMultiAnswer<K>> {
63
+ constructor(instructions: string, values: Record<K, string>, options?: MultiOptions<K>);
64
+ }
65
+ export declare class ToolQuestion extends QuestionSpec<ToolAnswer> {
66
+ constructor(instructions: string, tools: readonly ToolDefinition[], options?: ToolOptions);
67
+ }
68
+ export declare class ActQuestion<K extends string = 'true' | 'false', A extends string = string> extends QuestionSpec<TypedActAnswer<K, A>> {
69
+ constructor(instructions: string, costs: Record<A, Record<NoInfer<K>, number>>, options?: ActOptions<K, NoInfer<A>>);
70
+ }
71
+ export declare function choice<const K extends string>(instructions: string, values: Record<K, string>, options?: ChoiceOptions): ChoiceQuestion<K>;
72
+ export declare function noul(instructions: string, options?: EvidenceOptions): NoulQuestion;
73
+ export declare function score(instructions: string, levels: readonly string[], options?: EvidenceOptions): ScoreQuestion;
74
+ export declare function multi<const K extends string>(instructions: string, values: Record<K, string>, options?: MultiOptions<K>): MultiQuestion<K>;
75
+ export declare function act<const K extends string = 'true' | 'false', const A extends string = string>(instructions: string, costs: Record<A, Record<NoInfer<K>, number>>, options?: ActOptions<K, NoInfer<A>>): ActQuestion<K, A>;
76
+ export declare function tool(instructions: string, tools: readonly ToolDefinition[], options?: ToolOptions): ToolQuestion;
77
+ export type QuestionSet = Record<string, QuestionSpec>;
78
+ export type TypedDecisionResponse<Q extends QuestionSet> = Omit<DecisionResponse, 'answers'> & {
79
+ answers: {
80
+ [K in keyof Q]: Q[K] extends QuestionSpec<infer A> ? A : never;
81
+ };
82
+ answer<K extends keyof Q & (string | number)>(key: K): Q[K] extends QuestionSpec<infer A> ? A : never;
83
+ raw: DecisionResponse;
84
+ };
85
+ export declare function buildRequest(state: JsonValue, questions: QuestionSet, options: DecisionOptions): DecisionRequest;