@juspay/neurolink 13.4.10 → 13.5.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/CHANGELOG.md +3 -4
- package/README.md +3 -2
- package/dist/browser/neurolink.min.js +419 -419
- package/dist/cli/commands/decide.js +25 -5
- package/dist/factories/providerDescriptors.d.ts +4 -3
- package/dist/factories/providerDescriptors.js +6 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/neurolink.d.ts +4 -2
- package/dist/neurolink.js +19 -4
- package/dist/providers/catalog/provider-catalog.schema.json +64 -3
- package/dist/providers/catalog/schema.js +81 -1
- package/dist/providers/catalog/xor.json +30 -10
- package/dist/providers/configuredDecision.d.ts +8 -1
- package/dist/providers/configuredDecision.js +22 -0
- package/dist/providers/decide/dialects/openaiDecisions.js +8 -0
- package/dist/providers/decide/dialects/systemOne.js +28 -2
- package/dist/providers/decide/guards.d.ts +6 -0
- package/dist/providers/decide/guards.js +22 -0
- package/dist/providers/systemOneDecision.d.ts +30 -7
- package/dist/providers/systemOneDecision.js +71 -14
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/decision.d.ts +117 -10
- package/dist/types/providerCatalog.d.ts +12 -1
- package/dist/types/providers.d.ts +2 -0
- package/dist/utils/decisionAnswers.d.ts +7 -1
- package/dist/utils/decisionAnswers.js +43 -2
- package/dist/utils/decisionLimits.d.ts +13 -1
- package/dist/utils/decisionLimits.js +52 -1
- package/dist/utils/decisionMedia.d.ts +4 -2
- package/dist/utils/decisionMedia.js +216 -14
- package/docs-site/static/search-index.json +24 -21
- package/package.json +1 -1
|
@@ -30,6 +30,11 @@ function encodeQuestion(question) {
|
|
|
30
30
|
})),
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
+
if (question.type === "value") {
|
|
34
|
+
// Unreachable through decide(): an entry on this dialect cannot list
|
|
35
|
+
// `value` in its questionTypes, so the pre-flight check refuses it first.
|
|
36
|
+
throw new Error("The openai-decisions dialect has no value question; it writes boolean, choice and score questions only.");
|
|
37
|
+
}
|
|
33
38
|
// The index is the label so a returned `value` and `label` can never
|
|
34
39
|
// disagree about which level it is; the rubric text rides in `description`.
|
|
35
40
|
return {
|
|
@@ -140,6 +145,9 @@ function readAnswer(question, raw) {
|
|
|
140
145
|
if (question.type === "choice") {
|
|
141
146
|
return raw.type === "choice" ? readChoice(question, raw) : null;
|
|
142
147
|
}
|
|
148
|
+
if (question.type === "value") {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
143
151
|
return raw.type === "score" ? readScore(question, raw) : null;
|
|
144
152
|
}
|
|
145
153
|
/** Number of answers carrying each asked name, so a repeated one can be refused. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { asNumber, asNumberMap,
|
|
1
|
+
import { asNumber, asNumberMap, asTextMap, isRecord } from "../guards.js";
|
|
2
2
|
/**
|
|
3
3
|
* Last-resort confidence, when no transport reported one.
|
|
4
4
|
*
|
|
@@ -43,11 +43,12 @@ function parseDecisionAnswer(raw, reportedConfidence) {
|
|
|
43
43
|
reportedConfidence ??
|
|
44
44
|
deriveConfidence(probabilities),
|
|
45
45
|
probabilities,
|
|
46
|
+
...(raw.unknown === true ? { unknown: true } : {}),
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
case "score": {
|
|
49
50
|
const score = asNumber(raw.score);
|
|
50
|
-
const legend =
|
|
51
|
+
const legend = asTextMap(raw.legend);
|
|
51
52
|
const probabilities = asNumberMap(raw.probabilities);
|
|
52
53
|
if (score === undefined || !legend || !probabilities) {
|
|
53
54
|
return null;
|
|
@@ -62,6 +63,21 @@ function parseDecisionAnswer(raw, reportedConfidence) {
|
|
|
62
63
|
probabilities,
|
|
63
64
|
};
|
|
64
65
|
}
|
|
66
|
+
case "value": {
|
|
67
|
+
const value = raw.value === null || typeof raw.value === "string"
|
|
68
|
+
? raw.value
|
|
69
|
+
: asNumber(raw.value);
|
|
70
|
+
const confidence = asNumber(raw.confidence) ?? reportedConfidence;
|
|
71
|
+
if (value === undefined || confidence === undefined) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
type: "value",
|
|
76
|
+
value,
|
|
77
|
+
confidence,
|
|
78
|
+
...(raw.unknown === true ? { unknown: true } : {}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
65
81
|
default:
|
|
66
82
|
return null;
|
|
67
83
|
}
|
|
@@ -78,6 +94,13 @@ function encodeQuestion(question) {
|
|
|
78
94
|
...(question.criteria ? { criteria: question.criteria } : {}),
|
|
79
95
|
};
|
|
80
96
|
}
|
|
97
|
+
if (question.type === "choice" && question.unknown !== undefined) {
|
|
98
|
+
// `unknown` goes out only when it is on: a server that does not know the
|
|
99
|
+
// field may refuse it even as false. A choice without it is the caller's
|
|
100
|
+
// own object, as every other question is.
|
|
101
|
+
const { unknown, ...rest } = question;
|
|
102
|
+
return unknown ? { ...rest, unknown: true } : rest;
|
|
103
|
+
}
|
|
81
104
|
return question;
|
|
82
105
|
}
|
|
83
106
|
export const systemOneDialect = {
|
|
@@ -98,6 +121,9 @@ export const systemOneDialect = {
|
|
|
98
121
|
...(media.video && mediaKeys.videoKey
|
|
99
122
|
? { [mediaKeys.videoKey]: media.video }
|
|
100
123
|
: {}),
|
|
124
|
+
...(media.audio && mediaKeys.audioKey
|
|
125
|
+
? { [mediaKeys.audioKey]: media.audio }
|
|
126
|
+
: {}),
|
|
101
127
|
};
|
|
102
128
|
},
|
|
103
129
|
readAnswers: (decoded, reportedConfidence) => {
|
|
@@ -2,3 +2,9 @@ export declare const isRecord: (v: unknown) => v is Record<string, unknown>;
|
|
|
2
2
|
export declare const asNumber: (v: unknown) => number | undefined;
|
|
3
3
|
export declare const asNumberMap: (v: unknown) => Record<string, number> | undefined;
|
|
4
4
|
export declare const asStringMap: (v: unknown) => Record<string, string> | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* A map whose values are shown as text: a string as is, anything else a JSON
|
|
7
|
+
* value can hold as its JSON text. A score legend echoes each level as the
|
|
8
|
+
* caller gave it, and a level may be a number or an object.
|
|
9
|
+
*/
|
|
10
|
+
export declare const asTextMap: (v: unknown) => Record<string, string> | undefined;
|
|
@@ -27,3 +27,25 @@ export const asStringMap = (v) => {
|
|
|
27
27
|
}
|
|
28
28
|
return out;
|
|
29
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* A map whose values are shown as text: a string as is, anything else a JSON
|
|
32
|
+
* value can hold as its JSON text. A score legend echoes each level as the
|
|
33
|
+
* caller gave it, and a level may be a number or an object.
|
|
34
|
+
*/
|
|
35
|
+
export const asTextMap = (v) => {
|
|
36
|
+
if (!isRecord(v)) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const out = {};
|
|
40
|
+
for (const [k, raw] of Object.entries(v)) {
|
|
41
|
+
if (typeof raw === "string") {
|
|
42
|
+
out[k] = raw;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (raw === undefined || typeof raw === "function") {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
out[k] = JSON.stringify(raw);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
};
|
|
@@ -22,6 +22,12 @@ export declare function describeValidationErrors(detail: readonly unknown[]): st
|
|
|
22
22
|
* belongs in an error message or a log, whatever the wording, so the
|
|
23
23
|
* configured key, anything shaped like a LiteLLM key, any long hex run and any
|
|
24
24
|
* embedded `data:` URL are all removed.
|
|
25
|
+
*
|
|
26
|
+
* Its request-validation errors also quote the request back (pydantic's
|
|
27
|
+
* `input_value=…`, the start of the state and questions), and its routing
|
|
28
|
+
* errors append the gateway's fallback table, which names every model group it
|
|
29
|
+
* serves. The first is the caller's data and the second is the gateway's, so
|
|
30
|
+
* both are cut.
|
|
25
31
|
*/
|
|
26
32
|
export declare function redactCredentials(message: string, apiKey: string): string;
|
|
27
33
|
/**
|
|
@@ -85,12 +91,14 @@ export declare abstract class SystemOneDecisionProvider extends BaseProvider {
|
|
|
85
91
|
*/
|
|
86
92
|
protected readRetryAfterMs(_headers: Headers): number | undefined;
|
|
87
93
|
/**
|
|
88
|
-
* The per-attempt timeout when the caller sets none
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
94
|
+
* The per-attempt timeout when the caller sets none: the descriptor's
|
|
95
|
+
* `decideMs`, plus `decideMsPerKStateTokens` for every full 1,000 estimated
|
|
96
|
+
* state tokens when it declares that, since a long state takes longer to read
|
|
97
|
+
* and a flat budget would cut off a request well inside the model's window. A
|
|
98
|
+
* provider whose latency grows another way overrides this. `stateTokens` is
|
|
99
|
+
* the local estimate, 0 when the descriptor sets no local limits.
|
|
92
100
|
*/
|
|
93
|
-
protected defaultTimeoutMs(_questionCount: number,
|
|
101
|
+
protected defaultTimeoutMs(_questionCount: number, stateTokens: number): number | undefined;
|
|
94
102
|
/**
|
|
95
103
|
* The part of a successful response that holds `answers`, `usage` and
|
|
96
104
|
* `model`. The default is the response itself; a vendor that wraps every
|
|
@@ -101,6 +109,12 @@ export declare abstract class SystemOneDecisionProvider extends BaseProvider {
|
|
|
101
109
|
/** Per-question confidence a transport reports outside the answer objects. */
|
|
102
110
|
protected reportedConfidence(_payload: unknown): Record<string, number>;
|
|
103
111
|
protected resolveResponseModel(payload: Record<string, unknown>, requestedModel: string): string;
|
|
112
|
+
/**
|
|
113
|
+
* The model this request goes to and is checked against: the call's own, else
|
|
114
|
+
* the instance's. A provider whose default cannot read some requests may pick
|
|
115
|
+
* another for them here.
|
|
116
|
+
*/
|
|
117
|
+
protected resolveRequestModel(request: DecisionRequest): string;
|
|
104
118
|
/** Extra fields for the credential-rejected warning. */
|
|
105
119
|
protected diagnosticContext(): Record<string, unknown>;
|
|
106
120
|
protected getProviderName(): AIProviderName;
|
|
@@ -126,8 +140,17 @@ export declare abstract class SystemOneDecisionProvider extends BaseProvider {
|
|
|
126
140
|
*/
|
|
127
141
|
decide(request: DecisionRequest): Promise<DecisionResult>;
|
|
128
142
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
143
|
+
* Refuse a question this provider does not read: a type it does not list in
|
|
144
|
+
* `decisionLimits.questionTypes` (absent means boolean, choice and score), or
|
|
145
|
+
* a choice or score past its per-question cap. Checked even when the limits
|
|
146
|
+
* are advisory, since a server that does not know a question type cannot
|
|
147
|
+
* answer it.
|
|
148
|
+
*/
|
|
149
|
+
private assertSupportedQuestions;
|
|
150
|
+
/**
|
|
151
|
+
* Turn the request's images, video and audio into wire form, or refuse it.
|
|
152
|
+
* Refused as a non-retryable `invalid_request`: resending the same media
|
|
153
|
+
* cannot help.
|
|
131
154
|
*/
|
|
132
155
|
private prepareMedia;
|
|
133
156
|
/**
|
|
@@ -6,7 +6,7 @@ import { ProviderError } from "../types/index.js";
|
|
|
6
6
|
import { prepareDecisionMedia } from "../utils/decisionMedia.js";
|
|
7
7
|
import { logger } from "../utils/logger.js";
|
|
8
8
|
import { redactUrlsInText } from "../utils/logSanitize.js";
|
|
9
|
-
import { estimateDecisionStateTokens, resolveDecisionLimitsReading, } from "../utils/decisionLimits.js";
|
|
9
|
+
import { estimateDecisionStateTokens, resolveDecisionLimitsReading, unsupportedDecisionQuestion, } from "../utils/decisionLimits.js";
|
|
10
10
|
import { systemOneDialect } from "./decide/dialects/systemOne.js";
|
|
11
11
|
import { asNumber, isRecord } from "./decide/guards.js";
|
|
12
12
|
/**
|
|
@@ -44,17 +44,31 @@ export function describeValidationErrors(detail) {
|
|
|
44
44
|
* belongs in an error message or a log, whatever the wording, so the
|
|
45
45
|
* configured key, anything shaped like a LiteLLM key, any long hex run and any
|
|
46
46
|
* embedded `data:` URL are all removed.
|
|
47
|
+
*
|
|
48
|
+
* Its request-validation errors also quote the request back (pydantic's
|
|
49
|
+
* `input_value=…`, the start of the state and questions), and its routing
|
|
50
|
+
* errors append the gateway's fallback table, which names every model group it
|
|
51
|
+
* serves. The first is the caller's data and the second is the gateway's, so
|
|
52
|
+
* both are cut.
|
|
47
53
|
*/
|
|
48
54
|
export function redactCredentials(message, apiKey) {
|
|
49
55
|
const withoutKey = apiKey
|
|
50
56
|
? message.split(apiKey).join("[redacted]")
|
|
51
57
|
: message;
|
|
52
|
-
return withoutKey
|
|
58
|
+
return (withoutKey
|
|
53
59
|
.replace(/\.?\s*Received API Key\s*=[\s\S]*$/, "")
|
|
60
|
+
.replace(/\.?\s*Received Model Group=[\s\S]*$/, "")
|
|
61
|
+
.replace(/\s*No fallback model group found[\s\S]*$/, "")
|
|
62
|
+
// Greedy: the quoted value may itself contain ", input_type=", so only the
|
|
63
|
+
// last one is a delimiter the server wrote.
|
|
64
|
+
.replace(/input_value=[\s\S]*(?=, input_type=)/g, "input_value=[redacted]")
|
|
65
|
+
// Cut short by the server, a quoted value has no input_type after it, and
|
|
66
|
+
// may run over several lines; everything after it is the value.
|
|
67
|
+
.replace(/input_value=(?!\[redacted\])[\s\S]*/g, "input_value=[redacted]")
|
|
54
68
|
.replace(/\bsk-[A-Za-z0-9._-]+/g, "[redacted]")
|
|
55
69
|
.replace(/\b[0-9a-f]{32,}\b/gi, "[redacted]")
|
|
56
70
|
.replace(/data:[a-z]+\/[a-z0-9.+-]+;base64,[A-Za-z0-9+/=]+/gi, "[media]")
|
|
57
|
-
.trim();
|
|
71
|
+
.trim());
|
|
58
72
|
}
|
|
59
73
|
/**
|
|
60
74
|
* Whether a failed `fetch` was one that refused a redirect. undici words every
|
|
@@ -151,13 +165,20 @@ export class SystemOneDecisionProvider extends BaseProvider {
|
|
|
151
165
|
return undefined;
|
|
152
166
|
}
|
|
153
167
|
/**
|
|
154
|
-
* The per-attempt timeout when the caller sets none
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
168
|
+
* The per-attempt timeout when the caller sets none: the descriptor's
|
|
169
|
+
* `decideMs`, plus `decideMsPerKStateTokens` for every full 1,000 estimated
|
|
170
|
+
* state tokens when it declares that, since a long state takes longer to read
|
|
171
|
+
* and a flat budget would cut off a request well inside the model's window. A
|
|
172
|
+
* provider whose latency grows another way overrides this. `stateTokens` is
|
|
173
|
+
* the local estimate, 0 when the descriptor sets no local limits.
|
|
158
174
|
*/
|
|
159
|
-
defaultTimeoutMs(_questionCount,
|
|
160
|
-
|
|
175
|
+
defaultTimeoutMs(_questionCount, stateTokens) {
|
|
176
|
+
const base = this.getDescriptorDecideMs();
|
|
177
|
+
const perK = PROVIDER_DESCRIPTORS_BY_NAME.get(this.providerName)?.timeouts
|
|
178
|
+
?.decideMsPerKStateTokens;
|
|
179
|
+
return base === undefined || perK === undefined
|
|
180
|
+
? base
|
|
181
|
+
: base + Math.floor(stateTokens / 1000) * perK;
|
|
161
182
|
}
|
|
162
183
|
/**
|
|
163
184
|
* The part of a successful response that holds `answers`, `usage` and
|
|
@@ -175,6 +196,14 @@ export class SystemOneDecisionProvider extends BaseProvider {
|
|
|
175
196
|
resolveResponseModel(payload, requestedModel) {
|
|
176
197
|
return typeof payload.model === "string" ? payload.model : requestedModel;
|
|
177
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* The model this request goes to and is checked against: the call's own, else
|
|
201
|
+
* the instance's. A provider whose default cannot read some requests may pick
|
|
202
|
+
* another for them here.
|
|
203
|
+
*/
|
|
204
|
+
resolveRequestModel(request) {
|
|
205
|
+
return request.model ?? this.modelName ?? this.getDefaultModel();
|
|
206
|
+
}
|
|
178
207
|
/** Extra fields for the credential-rejected warning. */
|
|
179
208
|
diagnosticContext() {
|
|
180
209
|
return {};
|
|
@@ -250,7 +279,8 @@ export class SystemOneDecisionProvider extends BaseProvider {
|
|
|
250
279
|
retryable: false,
|
|
251
280
|
});
|
|
252
281
|
}
|
|
253
|
-
const resolvedModel =
|
|
282
|
+
const resolvedModel = this.resolveRequestModel(request);
|
|
283
|
+
this.assertSupportedQuestions(request, resolvedModel);
|
|
254
284
|
const stateTokens = this.assertWithinDecisionLimits(request, resolvedModel, questionEntries.length);
|
|
255
285
|
const wireQuestions = Object.fromEntries(questionEntries.map(([id, question]) => [
|
|
256
286
|
id,
|
|
@@ -393,12 +423,39 @@ export class SystemOneDecisionProvider extends BaseProvider {
|
|
|
393
423
|
});
|
|
394
424
|
}
|
|
395
425
|
/**
|
|
396
|
-
*
|
|
397
|
-
*
|
|
426
|
+
* Refuse a question this provider does not read: a type it does not list in
|
|
427
|
+
* `decisionLimits.questionTypes` (absent means boolean, choice and score), or
|
|
428
|
+
* a choice or score past its per-question cap. Checked even when the limits
|
|
429
|
+
* are advisory, since a server that does not know a question type cannot
|
|
430
|
+
* answer it.
|
|
431
|
+
*/
|
|
432
|
+
assertSupportedQuestions(request, model) {
|
|
433
|
+
const descriptor = PROVIDER_DESCRIPTORS_BY_NAME.get(this.providerName);
|
|
434
|
+
const limits = descriptor
|
|
435
|
+
? resolveDecisionLimitsReading(descriptor, model)
|
|
436
|
+
: null;
|
|
437
|
+
for (const [id, question] of Object.entries(request.questions)) {
|
|
438
|
+
const problem = unsupportedDecisionQuestion(question, limits);
|
|
439
|
+
if (problem !== undefined) {
|
|
440
|
+
throw this.decisionError({
|
|
441
|
+
kind: "invalid_request",
|
|
442
|
+
message: `${this.vendorLabel()} cannot answer question "${id}": ${problem}.`,
|
|
443
|
+
retryable: false,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Turn the request's images, video and audio into wire form, or refuse it.
|
|
450
|
+
* Refused as a non-retryable `invalid_request`: resending the same media
|
|
451
|
+
* cannot help.
|
|
398
452
|
*/
|
|
399
453
|
async prepareMedia(request) {
|
|
400
454
|
const limits = PROVIDER_DESCRIPTORS_BY_NAME.get(this.providerName)?.decisionLimits;
|
|
401
|
-
const prepared = await prepareDecisionMedia(request, limits?.media, this.vendorLabel(), listMediaDecisionProviders({
|
|
455
|
+
const prepared = await prepareDecisionMedia(request, limits?.media, this.vendorLabel(), listMediaDecisionProviders({
|
|
456
|
+
video: request.video !== undefined,
|
|
457
|
+
audio: request.audio !== undefined,
|
|
458
|
+
}));
|
|
402
459
|
if (prepared.status === "refused") {
|
|
403
460
|
throw this.decisionError({
|
|
404
461
|
kind: "invalid_request",
|
|
@@ -423,7 +480,7 @@ export class SystemOneDecisionProvider extends BaseProvider {
|
|
|
423
480
|
if (bytes > limit) {
|
|
424
481
|
throw this.decisionError({
|
|
425
482
|
kind: "invalid_request",
|
|
426
|
-
message: `The request is ${bytes} bytes; ${this.vendorLabel()} accepts at most ${limit}. Send fewer or smaller images${media?.video ? ", or a shorter video" : ""}.`,
|
|
483
|
+
message: `The request is ${bytes} bytes; ${this.vendorLabel()} accepts at most ${limit}. Send fewer or smaller images${media?.video ? ", or a shorter video" : ""}${media?.audio ? ", or a shorter audio clip" : ""}.`,
|
|
427
484
|
retryable: false,
|
|
428
485
|
});
|
|
429
486
|
}
|
package/dist/types/cli.d.ts
CHANGED
|
@@ -1312,6 +1312,8 @@ export type CliDecideArgs = {
|
|
|
1312
1312
|
image?: string[];
|
|
1313
1313
|
/** One video file (or data: URL) for a provider that reads video. */
|
|
1314
1314
|
video?: string;
|
|
1315
|
+
/** One audio file (or data: URL) for a provider that reads audio. */
|
|
1316
|
+
audio?: string;
|
|
1315
1317
|
provider?: string;
|
|
1316
1318
|
model?: string;
|
|
1317
1319
|
timeout?: number;
|
package/dist/types/decision.d.ts
CHANGED
|
@@ -53,6 +53,13 @@ export type DecisionChoiceQuestion = {
|
|
|
53
53
|
type: "choice";
|
|
54
54
|
instructions: DecisionInput;
|
|
55
55
|
criteria: Readonly<Record<string, DecisionInput | null>>;
|
|
56
|
+
/**
|
|
57
|
+
* True adds an option named `unknown` the model picks when none of the
|
|
58
|
+
* criteria fits; the answer then carries `unknown: true`. Only a provider
|
|
59
|
+
* whose `decisionLimits.questionTypes` lists `"choice-unknown"` accepts it,
|
|
60
|
+
* and no criteria key may be named `unknown` alongside it.
|
|
61
|
+
*/
|
|
62
|
+
unknown?: boolean;
|
|
56
63
|
};
|
|
57
64
|
/**
|
|
58
65
|
* Rate against an ordered rubric. The answer is the **0-based index**, so a
|
|
@@ -63,7 +70,24 @@ export type DecisionScoreQuestion = {
|
|
|
63
70
|
instructions: DecisionInput;
|
|
64
71
|
criteria: readonly (DecisionInput | null)[];
|
|
65
72
|
};
|
|
66
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Extract one typed field from the state: an amount, a reference, a name. Only
|
|
75
|
+
* a provider whose `decisionLimits.questionTypes` lists `"value"` accepts it.
|
|
76
|
+
*/
|
|
77
|
+
export type DecisionValueQuestion = {
|
|
78
|
+
type: "value";
|
|
79
|
+
instructions: DecisionInput;
|
|
80
|
+
/** The type of the extracted value. Omitted means `string`. */
|
|
81
|
+
dtype?: "string" | "int" | "float";
|
|
82
|
+
/** A regular expression the extracted text must match. */
|
|
83
|
+
pattern?: string;
|
|
84
|
+
};
|
|
85
|
+
export type DecisionQuestion = DecisionBooleanQuestion | DecisionChoiceQuestion | DecisionScoreQuestion | DecisionValueQuestion;
|
|
86
|
+
/**
|
|
87
|
+
* What a provider may be asked, per question. `"choice-unknown"` is a `choice`
|
|
88
|
+
* question that sets `unknown: true`.
|
|
89
|
+
*/
|
|
90
|
+
export type DecisionQuestionType = DecisionQuestion["type"] | "choice-unknown";
|
|
67
91
|
/** Questions keyed by an id you choose; answers return under the same ids. */
|
|
68
92
|
export type DecisionQuestionMap = Readonly<Record<string, DecisionQuestion>>;
|
|
69
93
|
export type DecisionBooleanAnswer = {
|
|
@@ -79,24 +103,34 @@ export type DecisionChoiceAnswer = {
|
|
|
79
103
|
probabilities: Readonly<Record<string, number>>;
|
|
80
104
|
/** Calibrated certainty, 0–1, derived from the distribution. */
|
|
81
105
|
confidence: number;
|
|
106
|
+
/** True when the question set `unknown` and the model picked it. */
|
|
107
|
+
unknown?: boolean;
|
|
82
108
|
};
|
|
83
109
|
export type DecisionScoreAnswer = {
|
|
84
110
|
type: "score";
|
|
85
111
|
/** Probability-weighted level index; lands between levels. */
|
|
86
112
|
score: number;
|
|
87
|
-
/** Level index (as a string key) → the description supplied. */
|
|
113
|
+
/** Level index (as a string key) → the description supplied; a level that was not a string is given as its JSON text. */
|
|
88
114
|
legend: Readonly<Record<string, string>>;
|
|
89
115
|
/** Level index (as a string key) → probability. */
|
|
90
116
|
probabilities: Readonly<Record<string, number>>;
|
|
91
117
|
confidence: number;
|
|
92
118
|
};
|
|
93
|
-
export type
|
|
119
|
+
export type DecisionValueAnswer = {
|
|
120
|
+
type: "value";
|
|
121
|
+
/** The extracted value: a number for `int` / `float`, else a string. Null when the model abstained. */
|
|
122
|
+
value: string | number | null;
|
|
123
|
+
confidence: number;
|
|
124
|
+
/** True when the model found nothing to extract; `value` is then null. */
|
|
125
|
+
unknown?: boolean;
|
|
126
|
+
};
|
|
127
|
+
export type DecisionAnswer = DecisionBooleanAnswer | DecisionChoiceAnswer | DecisionScoreAnswer | DecisionValueAnswer;
|
|
94
128
|
export type DecisionAnswerMap = Readonly<Record<string, DecisionAnswer>>;
|
|
95
129
|
export type DecisionUsage = {
|
|
96
130
|
inputTokens: number;
|
|
97
131
|
outputTokens: number;
|
|
98
132
|
};
|
|
99
|
-
/** An image or
|
|
133
|
+
/** An image, video or audio clip as the caller supplies it: raw bytes, a file path, or a data URL. */
|
|
100
134
|
export type DecisionMediaSource = Buffer | string;
|
|
101
135
|
export type DecisionRequest = {
|
|
102
136
|
state: DecisionState;
|
|
@@ -109,6 +143,11 @@ export type DecisionRequest = {
|
|
|
109
143
|
images?: readonly DecisionMediaSource[];
|
|
110
144
|
/** One video, in the same forms. Sending images as well is allowed. */
|
|
111
145
|
video?: DecisionMediaSource;
|
|
146
|
+
/**
|
|
147
|
+
* One audio clip, in the same forms (a `data:audio/…;base64,` URL). Only a
|
|
148
|
+
* provider whose `decisionLimits.media.audio` is set accepts it.
|
|
149
|
+
*/
|
|
150
|
+
audio?: DecisionMediaSource;
|
|
112
151
|
/** Overrides the provider's configured model for this call only. */
|
|
113
152
|
model?: string;
|
|
114
153
|
signal?: AbortSignal;
|
|
@@ -127,7 +166,7 @@ export type DecisionResult = {
|
|
|
127
166
|
latencyMs: number;
|
|
128
167
|
/** Server-side time behind the edge; isolates model time from network. */
|
|
129
168
|
upstreamMs?: number;
|
|
130
|
-
/** Encoded size of the images and
|
|
169
|
+
/** Encoded size of the images, video and audio sent. Absent for a text-only request. */
|
|
131
170
|
mediaBytes?: number;
|
|
132
171
|
};
|
|
133
172
|
/**
|
|
@@ -147,6 +186,16 @@ export type DecisionLimits = {
|
|
|
147
186
|
maxStateTokens: number;
|
|
148
187
|
/** Absent when the provider has no cap on questions per request. */
|
|
149
188
|
maxQuestions?: number;
|
|
189
|
+
/**
|
|
190
|
+
* The question types the provider reads. Absent means `boolean`, `choice`
|
|
191
|
+
* and `score`; a request asking anything else is refused before any network
|
|
192
|
+
* call, whether or not the limits are advisory.
|
|
193
|
+
*/
|
|
194
|
+
questionTypes?: readonly DecisionQuestionType[];
|
|
195
|
+
/** Most levels one `score` question may have. Absent means no local cap. */
|
|
196
|
+
maxScoreLevels?: number;
|
|
197
|
+
/** Most options one `choice` question may have. Absent means no local cap. */
|
|
198
|
+
maxChoiceOptions?: number;
|
|
150
199
|
/**
|
|
151
200
|
* Tokens charged per non-ASCII character. The default estimate assumes ~4
|
|
152
201
|
* characters per token, which holds for English and is several times too
|
|
@@ -208,9 +257,15 @@ export type DecisionLimitsReading = {
|
|
|
208
257
|
maxStateTokens: number;
|
|
209
258
|
/**
|
|
210
259
|
* Absent when the provider caps a request by tokens (or bytes) rather
|
|
211
|
-
* than by question count — TypeSafe
|
|
260
|
+
* than by question count — TypeSafe; `Infinity` is never used.
|
|
212
261
|
*/
|
|
213
262
|
maxQuestions?: number;
|
|
263
|
+
/** Present only when the descriptor declares it; absent means `boolean`, `choice` and `score`. */
|
|
264
|
+
questionTypes?: readonly DecisionQuestionType[];
|
|
265
|
+
/** Most levels one `score` question may have; absent means no local cap. */
|
|
266
|
+
maxScoreLevels?: number;
|
|
267
|
+
/** Most options one `choice` question may have; absent means no local cap. */
|
|
268
|
+
maxChoiceOptions?: number;
|
|
214
269
|
nonAsciiTokensPerChar?: number;
|
|
215
270
|
/** Tokens charged per ASCII digit; see {@link DecisionLimits.digitTokensPerChar}. */
|
|
216
271
|
digitTokensPerChar?: number;
|
|
@@ -241,19 +296,36 @@ export type DecisionMediaLimits = {
|
|
|
241
296
|
video: boolean;
|
|
242
297
|
/** Ceiling on the encoded request body, in bytes. */
|
|
243
298
|
maxRequestBytes: number;
|
|
299
|
+
/** Whether one audio clip is accepted, and how much of it the model hears. Absent means no audio. */
|
|
300
|
+
audio?: DecisionAudioLimits;
|
|
301
|
+
};
|
|
302
|
+
export type DecisionAudioLimits = {
|
|
303
|
+
/**
|
|
304
|
+
* Seconds of audio the model reads. A clip whose length can be read from its
|
|
305
|
+
* header (WAV, FLAC, Ogg) and runs longer is refused, since the rest would be cut
|
|
306
|
+
* off without a word.
|
|
307
|
+
*/
|
|
308
|
+
maxSeconds: number;
|
|
244
309
|
};
|
|
245
310
|
/** An image's size in pixels, read from its header. */
|
|
246
311
|
export type DecisionImageDimensions = {
|
|
247
312
|
width: number;
|
|
248
313
|
height: number;
|
|
249
314
|
};
|
|
250
|
-
/** Media after preparation:
|
|
315
|
+
/** Media after preparation: images and video are `data:` URLs, ready for the wire. */
|
|
251
316
|
export type DecisionPreparedMedia = {
|
|
252
317
|
images: string[];
|
|
253
318
|
video?: string;
|
|
254
|
-
|
|
319
|
+
audio?: DecisionPreparedAudio;
|
|
320
|
+
/** Encoded size of everything above. */
|
|
255
321
|
bytes: number;
|
|
256
322
|
};
|
|
323
|
+
/** An audio clip as the wire takes it: raw base64 and the container it was sniffed as. */
|
|
324
|
+
export type DecisionPreparedAudio = {
|
|
325
|
+
data: string;
|
|
326
|
+
format: DecisionAudioFormat;
|
|
327
|
+
};
|
|
328
|
+
export type DecisionAudioFormat = "wav" | "flac" | "ogg" | "mp3";
|
|
257
329
|
/**
|
|
258
330
|
* Discriminated by a string literal, not a boolean: the package is also
|
|
259
331
|
* compiled without strictNullChecks, where a boolean discriminant does not
|
|
@@ -266,8 +338,8 @@ export type DecisionMediaResult = {
|
|
|
266
338
|
status: "refused";
|
|
267
339
|
message: string;
|
|
268
340
|
};
|
|
269
|
-
export type DecisionMediaKind = "image" | "video";
|
|
270
|
-
/** One image or
|
|
341
|
+
export type DecisionMediaKind = "image" | "video" | "audio";
|
|
342
|
+
/** One image, video or audio clip turned into a data URL, or the reason it could not be. Same literal-discriminant rule as above. */
|
|
271
343
|
export type DecisionMediaConversion = {
|
|
272
344
|
status: "ok";
|
|
273
345
|
dataUrl: string;
|
|
@@ -283,6 +355,21 @@ export type DecisionMediaFileRead = {
|
|
|
283
355
|
status: "error";
|
|
284
356
|
message: string;
|
|
285
357
|
};
|
|
358
|
+
/**
|
|
359
|
+
* A caller's media source resolved for one kind: bytes to identify (a Buffer
|
|
360
|
+
* or a file read from disk), a `data:` URL of that kind as given, or the reason
|
|
361
|
+
* it is refused.
|
|
362
|
+
*/
|
|
363
|
+
export type DecisionMediaSourceRead = {
|
|
364
|
+
status: "buffer";
|
|
365
|
+
buffer: Buffer;
|
|
366
|
+
} | {
|
|
367
|
+
status: "dataUrl";
|
|
368
|
+
dataUrl: string;
|
|
369
|
+
} | {
|
|
370
|
+
status: "error";
|
|
371
|
+
message: string;
|
|
372
|
+
};
|
|
286
373
|
/**
|
|
287
374
|
* Why a decision call failed, normalised across vendors. TypeSafe alone
|
|
288
375
|
* returns two different error envelopes, so a provider must flatten them.
|
|
@@ -318,6 +405,11 @@ export type DecisionChoiceReading = {
|
|
|
318
405
|
name: string;
|
|
319
406
|
probability: number;
|
|
320
407
|
}[];
|
|
408
|
+
/**
|
|
409
|
+
* True when the question set `unknown` and the model picked it: `choice` is
|
|
410
|
+
* then `"unknown"`, which is none of the question's own options.
|
|
411
|
+
*/
|
|
412
|
+
unknown: boolean;
|
|
321
413
|
};
|
|
322
414
|
export type DecisionScoreReading = {
|
|
323
415
|
score: number;
|
|
@@ -325,6 +417,12 @@ export type DecisionScoreReading = {
|
|
|
325
417
|
legend: Readonly<Record<string, string>>;
|
|
326
418
|
probabilities: Readonly<Record<string, number>>;
|
|
327
419
|
};
|
|
420
|
+
export type DecisionValueReading = {
|
|
421
|
+
value: string | number | null;
|
|
422
|
+
confidence: number;
|
|
423
|
+
/** True when the model abstained; `value` is then null. */
|
|
424
|
+
unknown: boolean;
|
|
425
|
+
};
|
|
328
426
|
/**
|
|
329
427
|
* What `NeuroLink.decide()` accepts: a decision request plus the usual
|
|
330
428
|
* provider/credential selection every inference type shares.
|
|
@@ -543,6 +641,8 @@ export type DecideMedia = {
|
|
|
543
641
|
imagesKey: string;
|
|
544
642
|
/** Written only when the request carries a video; required exactly when `decisionLimits.media.video` is true. */
|
|
545
643
|
videoKey?: string;
|
|
644
|
+
/** Written only when the request carries audio, as `{ data, format }`; required exactly when `decisionLimits.media.audio` is set. */
|
|
645
|
+
audioKey?: string;
|
|
546
646
|
};
|
|
547
647
|
/**
|
|
548
648
|
* Says when a decide provider's key may be used as the default decision
|
|
@@ -580,6 +680,13 @@ export type DecideAmbientKeyCredentialField = {
|
|
|
580
680
|
/** The field in that slice. Only `baseURL` is read today. */
|
|
581
681
|
field: "baseURL";
|
|
582
682
|
};
|
|
683
|
+
/** See `DecideBlock.capableModel`. */
|
|
684
|
+
export type DecideCapableModel = {
|
|
685
|
+
/** A key of the entry's `models.catalog`. */
|
|
686
|
+
model: string;
|
|
687
|
+
/** Question types that send a request to `model`; media always does. */
|
|
688
|
+
questionTypes: readonly DecisionQuestionType[];
|
|
689
|
+
};
|
|
583
690
|
/** What a dialect lays out as one request body. */
|
|
584
691
|
export type DecisionDialectBody = {
|
|
585
692
|
readonly state: DecisionState;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* src/lib/providers/catalog/<id>.json file per provider is the single
|
|
5
5
|
* source of truth; see docs/superpowers/plans/2026-08-28-provider-json-catalog-spec.md.
|
|
6
6
|
*/
|
|
7
|
-
import type { DecideAmbientKeyGuard, DecideDialectName, DecideMedia, DecisionErrorKind, DecisionLimits } from "./decision.js";
|
|
7
|
+
import type { DecideAmbientKeyGuard, DecideCapableModel, DecideDialectName, DecideMedia, DecisionErrorKind, DecisionLimits } from "./decision.js";
|
|
8
8
|
export type CatalogPricingPerMTok = {
|
|
9
9
|
input: number;
|
|
10
10
|
output: number;
|
|
@@ -338,6 +338,15 @@ export type DecideBlock = {
|
|
|
338
338
|
stateEncoding?: "text";
|
|
339
339
|
/** Body keys for images and video, after `model`, `state` and `questions`. Omitted means the body never carries media. */
|
|
340
340
|
media?: DecideMedia;
|
|
341
|
+
/**
|
|
342
|
+
* A model the request is sent to instead of the default when the call names
|
|
343
|
+
* no model, no model variable is set, and the request carries media or a
|
|
344
|
+
* question of one of `questionTypes`. Its `decisionLimits` row is the one
|
|
345
|
+
* checked. For a text default that cannot read those requests, whose server
|
|
346
|
+
* would otherwise hand them to this model behind its back, with a smaller
|
|
347
|
+
* window than the one checked. Omitted means the default model is always sent.
|
|
348
|
+
*/
|
|
349
|
+
capableModel?: DecideCapableModel;
|
|
341
350
|
/**
|
|
342
351
|
* Keeps a key found only in the environment from becoming the default
|
|
343
352
|
* decision provider when it plainly belongs to something else. Omitted means
|
|
@@ -368,6 +377,8 @@ export type DecideCatalogJson = {
|
|
|
368
377
|
};
|
|
369
378
|
timeouts?: {
|
|
370
379
|
decideMs?: number;
|
|
380
|
+
/** Added to `decideMs` per 1,000 estimated state tokens, for a model whose latency grows with the state. */
|
|
381
|
+
decideMsPerKStateTokens?: number;
|
|
371
382
|
};
|
|
372
383
|
decisionLimits?: DecisionLimits;
|
|
373
384
|
decide: DecideBlock;
|
|
@@ -2459,6 +2459,8 @@ export type ProviderDescriptor = {
|
|
|
2459
2459
|
generateMs?: number;
|
|
2460
2460
|
streamMs?: number;
|
|
2461
2461
|
decideMs?: number;
|
|
2462
|
+
/** Added to `decideMs` per 1,000 estimated state tokens. */
|
|
2463
|
+
decideMsPerKStateTokens?: number;
|
|
2462
2464
|
};
|
|
2463
2465
|
/**
|
|
2464
2466
|
* What a `decide` provider can read. Absent means the provider leaves size
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module utils/decisionAnswers
|
|
9
9
|
*/
|
|
10
|
-
import type { DecisionAnswerMap, DecisionBooleanGate, DecisionChoiceReading, DecisionQuestion, DecisionResult, DecisionScoreReading, ProviderDescriptor } from "../types/index.js";
|
|
10
|
+
import type { DecisionAnswerMap, DecisionBooleanGate, DecisionChoiceReading, DecisionQuestion, DecisionResult, DecisionScoreReading, DecisionValueReading, ProviderDescriptor } from "../types/index.js";
|
|
11
11
|
/**
|
|
12
12
|
* Read a yes/no answer as a probability.
|
|
13
13
|
*
|
|
@@ -24,6 +24,12 @@ export declare function readDecisionBoolean(answers: DecisionAnswerMap, id: stri
|
|
|
24
24
|
*/
|
|
25
25
|
export declare function readDecisionChoice(answers: DecisionAnswerMap, id: string): DecisionChoiceReading | undefined;
|
|
26
26
|
export declare function readDecisionScore(answers: DecisionAnswerMap, id: string): DecisionScoreReading | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Read an extracted value. `value` is null and `unknown` true when the model
|
|
29
|
+
* found nothing to extract, which is an answer, not a missing one: undefined
|
|
30
|
+
* still means the id is absent or the answer was another type.
|
|
31
|
+
*/
|
|
32
|
+
export declare function readDecisionValue(answers: DecisionAnswerMap, id: string): DecisionValueReading | undefined;
|
|
27
33
|
/**
|
|
28
34
|
* A yes/no answer carries no confidence of its own, so certainty has to be
|
|
29
35
|
* inferred from how far the probability sits from a coin flip. 0.5 → 0, and
|