@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.
- package/LICENSE +202 -0
- package/NOTICE +39 -0
- package/README.md +115 -2
- package/dist/cjs/index.cjs +216 -0
- package/dist/cjs/index.d.cts +196 -0
- package/dist/cjs/media.cjs +20 -0
- package/dist/cjs/media.d.cts +5 -0
- package/dist/cjs/questions.cjs +284 -0
- package/dist/cjs/questions.d.cts +85 -0
- package/dist/esm/index.d.ts +196 -0
- package/dist/esm/index.js +193 -0
- package/dist/esm/media.d.ts +5 -0
- package/dist/esm/media.js +16 -0
- package/dist/esm/questions.d.ts +85 -0
- package/dist/esm/questions.js +267 -0
- package/package.json +45 -4
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { type QuestionSet, type TypedDecisionResponse, type DecisionOptions } from "./questions.js";
|
|
2
|
+
export * from "./questions.js";
|
|
3
|
+
export * from "./media.js";
|
|
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,193 @@
|
|
|
1
|
+
import { buildRequest } from "./questions.js";
|
|
2
|
+
export * from "./questions.js";
|
|
3
|
+
export * from "./media.js";
|
|
4
|
+
export class BasalError extends Error {
|
|
5
|
+
}
|
|
6
|
+
/** Non-2xx response. Server body and Retry-After are available without logging them. */
|
|
7
|
+
export class BasalHTTPError extends BasalError {
|
|
8
|
+
statusCode;
|
|
9
|
+
body;
|
|
10
|
+
retryAfter;
|
|
11
|
+
constructor(statusCode, body, retryAfter) {
|
|
12
|
+
super(`Basal HTTP ${statusCode}`);
|
|
13
|
+
this.statusCode = statusCode;
|
|
14
|
+
this.body = body;
|
|
15
|
+
this.retryAfter = retryAfter;
|
|
16
|
+
this.name = "BasalHTTPError";
|
|
17
|
+
// Node's default inspection and JSON.stringify must not print reflected state or credentials.
|
|
18
|
+
Object.defineProperty(this, "body", { enumerable: false });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class BasalTransportError extends BasalError {
|
|
22
|
+
}
|
|
23
|
+
export class BasalProtocolError extends BasalError {
|
|
24
|
+
}
|
|
25
|
+
export class BasalResponseTooLargeError extends BasalError {
|
|
26
|
+
}
|
|
27
|
+
async function readBody(response, limit, signal) {
|
|
28
|
+
// Count bytes from the actual stream, including decompressed responses; never trust Content-Length alone.
|
|
29
|
+
const reader = response.body?.getReader();
|
|
30
|
+
if (!reader)
|
|
31
|
+
return "";
|
|
32
|
+
const cancel = () => { void reader.cancel().catch(() => { }); };
|
|
33
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
34
|
+
if (signal.aborted)
|
|
35
|
+
cancel();
|
|
36
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
37
|
+
const parts = [];
|
|
38
|
+
let bytes = 0;
|
|
39
|
+
let complete = false;
|
|
40
|
+
try {
|
|
41
|
+
for (;;) {
|
|
42
|
+
const { done, value } = await reader.read();
|
|
43
|
+
if (done) {
|
|
44
|
+
complete = true;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
bytes += value.byteLength;
|
|
48
|
+
if (bytes > limit) {
|
|
49
|
+
throw new BasalResponseTooLargeError(`Basal response exceeds ${limit} bytes`);
|
|
50
|
+
}
|
|
51
|
+
parts.push(decoder.decode(value, { stream: true }));
|
|
52
|
+
}
|
|
53
|
+
parts.push(decoder.decode());
|
|
54
|
+
return parts.join("");
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
signal.removeEventListener("abort", cancel);
|
|
58
|
+
// A decoder/read failure must also stop the underlying HTTP body, before releasing ownership.
|
|
59
|
+
if (!complete)
|
|
60
|
+
cancel();
|
|
61
|
+
reader.releaseLock();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Reusable fetch client. Server root URL, optional proxy prefix, without /v1.
|
|
65
|
+
* No automatic retries. AbortSignal cancels a request; timeout includes body reading.
|
|
66
|
+
*/
|
|
67
|
+
export class BasalClient {
|
|
68
|
+
baseUrl;
|
|
69
|
+
headers;
|
|
70
|
+
timeoutMs;
|
|
71
|
+
maxResponseBytes;
|
|
72
|
+
fetch;
|
|
73
|
+
constructor(options = {}) {
|
|
74
|
+
const url = new URL(options.baseUrl ?? "http://127.0.0.1:8000");
|
|
75
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.href.includes("?") || url.href.includes("#")) {
|
|
76
|
+
throw new TypeError("baseUrl must be an HTTP(S) server URL without credentials, query or fragment");
|
|
77
|
+
}
|
|
78
|
+
if (url.protocol !== "https:" && !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)) {
|
|
79
|
+
throw new TypeError("baseUrl requires HTTPS or loopback HTTP (e.g. an SSH tunnel)");
|
|
80
|
+
}
|
|
81
|
+
this.baseUrl = url.href.replace(/\/+$/, "") + "/";
|
|
82
|
+
this.timeoutMs = options.timeoutMs ?? 120_000;
|
|
83
|
+
if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0 || this.timeoutMs > 2_147_483_647) {
|
|
84
|
+
throw new TypeError("timeoutMs must be positive and at most 2147483647");
|
|
85
|
+
}
|
|
86
|
+
this.headers = { Accept: "application/json" };
|
|
87
|
+
this.maxResponseBytes = options.maxResponseBytes ?? 8 * 1024 * 1024;
|
|
88
|
+
if (!Number.isSafeInteger(this.maxResponseBytes) || this.maxResponseBytes <= 0) {
|
|
89
|
+
throw new TypeError("maxResponseBytes must be a positive safe integer");
|
|
90
|
+
}
|
|
91
|
+
this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
92
|
+
if (options.apiKey !== undefined) {
|
|
93
|
+
if (!options.apiKey || /[\r\n]/.test(options.apiKey))
|
|
94
|
+
throw new TypeError("apiKey must be a nonempty, single-line token");
|
|
95
|
+
this.headers.Authorization = `Bearer ${options.apiKey}`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async request(method, path, body, signal) {
|
|
99
|
+
// Serialization errors are local input errors, not failures of the HTTP transport.
|
|
100
|
+
const encoded = body === undefined ? undefined : JSON.stringify(body);
|
|
101
|
+
const controller = new AbortController();
|
|
102
|
+
const abort = () => controller.abort(signal?.reason);
|
|
103
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
104
|
+
if (signal?.aborted)
|
|
105
|
+
abort();
|
|
106
|
+
const timer = setTimeout(() => controller.abort(new Error("Basal request timed out")), this.timeoutMs);
|
|
107
|
+
let onAbort = () => { };
|
|
108
|
+
const aborted = new Promise((_, reject) => {
|
|
109
|
+
onAbort = () => reject(new BasalTransportError("Basal HTTP request aborted", { cause: controller.signal.reason }));
|
|
110
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
111
|
+
if (controller.signal.aborted)
|
|
112
|
+
onAbort();
|
|
113
|
+
});
|
|
114
|
+
const work = async () => {
|
|
115
|
+
let response;
|
|
116
|
+
let raw;
|
|
117
|
+
try {
|
|
118
|
+
response = await this.fetch(this.baseUrl + path, {
|
|
119
|
+
method, headers: body === undefined ? this.headers : { ...this.headers, "Content-Type": "application/json" },
|
|
120
|
+
...(encoded === undefined ? {} : { body: encoded }),
|
|
121
|
+
signal: controller.signal, redirect: "error", credentials: "omit", cache: "no-store",
|
|
122
|
+
});
|
|
123
|
+
if (response.redirected || ([301, 302, 303, 307, 308].includes(response.status) && response.headers.has("Location"))) {
|
|
124
|
+
await response.body?.cancel().catch(() => { });
|
|
125
|
+
throw new BasalTransportError("Basal transport returned a redirect");
|
|
126
|
+
}
|
|
127
|
+
raw = await readBody(response, this.maxResponseBytes, controller.signal);
|
|
128
|
+
}
|
|
129
|
+
catch (cause) {
|
|
130
|
+
if (cause instanceof BasalError)
|
|
131
|
+
throw cause;
|
|
132
|
+
throw new BasalTransportError("Basal HTTP transport failed", { cause });
|
|
133
|
+
}
|
|
134
|
+
let data;
|
|
135
|
+
try {
|
|
136
|
+
data = JSON.parse(raw);
|
|
137
|
+
}
|
|
138
|
+
catch (cause) {
|
|
139
|
+
if (response.ok)
|
|
140
|
+
throw new BasalProtocolError("Basal returned invalid JSON", { cause });
|
|
141
|
+
data = raw;
|
|
142
|
+
}
|
|
143
|
+
if (!response.ok)
|
|
144
|
+
throw new BasalHTTPError(response.status, data, response.headers.get("Retry-After"));
|
|
145
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
146
|
+
throw new BasalProtocolError("Basal returned a JSON value other than an object");
|
|
147
|
+
}
|
|
148
|
+
return data;
|
|
149
|
+
};
|
|
150
|
+
try {
|
|
151
|
+
if (controller.signal.aborted)
|
|
152
|
+
return await aborted;
|
|
153
|
+
return await Promise.race([work(), aborted]);
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
controller.signal.removeEventListener("abort", onAbort);
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
signal?.removeEventListener("abort", abort);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async decide(state, questionsOrSignal, options = {}) {
|
|
162
|
+
if (questionsOrSignal === undefined || ("aborted" in questionsOrSignal && typeof questionsOrSignal.addEventListener === "function")) {
|
|
163
|
+
return this.request("POST", "v1/basal", state, questionsOrSignal);
|
|
164
|
+
}
|
|
165
|
+
const questions = { ...questionsOrSignal };
|
|
166
|
+
const response = await this.request("POST", "v1/basal", buildRequest(state, questions, options), options.signal);
|
|
167
|
+
const parsed = new Map();
|
|
168
|
+
try {
|
|
169
|
+
if (typeof response.model !== "string" || !response.model || !response.usage || Array.isArray(response.usage)
|
|
170
|
+
|| !Number.isSafeInteger(response.usage.input_tokens) || response.usage.input_tokens < 0
|
|
171
|
+
|| !Number.isSafeInteger(response.usage.output_tokens) || response.usage.output_tokens < 0
|
|
172
|
+
|| !response.answers || Array.isArray(response.answers))
|
|
173
|
+
throw new TypeError("invalid decision envelope");
|
|
174
|
+
for (const [id, question] of Object.entries(questions))
|
|
175
|
+
parsed.set(id, question.read(response, id));
|
|
176
|
+
}
|
|
177
|
+
catch (cause) {
|
|
178
|
+
throw new BasalProtocolError("Invalid typed Basal answer", { cause });
|
|
179
|
+
}
|
|
180
|
+
const typed = { ...response, answers: Object.fromEntries(parsed) };
|
|
181
|
+
Object.defineProperty(typed, "raw", { value: response });
|
|
182
|
+
Object.defineProperty(typed, "answer", { value: (key) => {
|
|
183
|
+
if (typeof key !== "string" && typeof key !== "number")
|
|
184
|
+
throw new TypeError("answer key must be a string or number");
|
|
185
|
+
const answer = parsed.get(String(key));
|
|
186
|
+
if (answer === undefined)
|
|
187
|
+
throw new TypeError("answer key was not part of this response");
|
|
188
|
+
return answer;
|
|
189
|
+
} });
|
|
190
|
+
return typed;
|
|
191
|
+
}
|
|
192
|
+
models(signal) { return this.request("GET", "v1/models", undefined, signal); }
|
|
193
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { MediaInput } from './index.js';
|
|
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,16 @@
|
|
|
1
|
+
function media(type, data) {
|
|
2
|
+
if (data instanceof Uint8Array) {
|
|
3
|
+
// Bounded chunks avoid the argument limit of spreading a whole image/audio file.
|
|
4
|
+
const chunks = [];
|
|
5
|
+
for (let offset = 0; offset < data.length; offset += 8192)
|
|
6
|
+
chunks.push(String.fromCharCode(...data.subarray(offset, offset + 8192)));
|
|
7
|
+
data = btoa(chunks.join(''));
|
|
8
|
+
}
|
|
9
|
+
if (typeof data !== 'string' || !data.trim() || /^https?:\/\//i.test(data))
|
|
10
|
+
throw new TypeError('media data must be bytes, base64 or a data URL');
|
|
11
|
+
return { type, data };
|
|
12
|
+
}
|
|
13
|
+
/** Inline image; never fetches a URL or reads a file. */
|
|
14
|
+
export function image(data) { return media('image', data); }
|
|
15
|
+
/** Inline audio; never fetches a URL or reads a file. */
|
|
16
|
+
export function audio(data) { return media('audio', data); }
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ActAnswer, Answer, ChoiceAnswer, DecisionResponse, EvidenceSpan, JsonValue, MultiAnswer, NoulAnswer, Question, ScoreAnswer, ToolAnswer, ToolDefinition, MultiObjective, DecisionRequest } from './index.js';
|
|
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;
|