@juspay/neurolink 13.4.9 → 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.
Files changed (35) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/README.md +14 -4
  3. package/dist/browser/neurolink.min.js +419 -419
  4. package/dist/cli/commands/decide.js +25 -5
  5. package/dist/factories/providerDescriptors.d.ts +4 -3
  6. package/dist/factories/providerDescriptors.js +6 -4
  7. package/dist/hitl/hitlManager.d.ts +1 -0
  8. package/dist/hitl/hitlManager.js +2 -1
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.js +1 -1
  11. package/dist/neurolink.d.ts +4 -2
  12. package/dist/neurolink.js +36 -10
  13. package/dist/providers/catalog/provider-catalog.schema.json +64 -3
  14. package/dist/providers/catalog/schema.js +81 -1
  15. package/dist/providers/catalog/xor.json +30 -10
  16. package/dist/providers/configuredDecision.d.ts +8 -1
  17. package/dist/providers/configuredDecision.js +22 -0
  18. package/dist/providers/decide/dialects/openaiDecisions.js +8 -0
  19. package/dist/providers/decide/dialects/systemOne.js +28 -2
  20. package/dist/providers/decide/guards.d.ts +6 -0
  21. package/dist/providers/decide/guards.js +22 -0
  22. package/dist/providers/systemOneDecision.d.ts +30 -7
  23. package/dist/providers/systemOneDecision.js +71 -14
  24. package/dist/types/cli.d.ts +2 -0
  25. package/dist/types/decision.d.ts +117 -10
  26. package/dist/types/providerCatalog.d.ts +12 -1
  27. package/dist/types/providers.d.ts +2 -0
  28. package/dist/utils/decisionAnswers.d.ts +7 -1
  29. package/dist/utils/decisionAnswers.js +43 -2
  30. package/dist/utils/decisionLimits.d.ts +13 -1
  31. package/dist/utils/decisionLimits.js +52 -1
  32. package/dist/utils/decisionMedia.d.ts +4 -2
  33. package/dist/utils/decisionMedia.js +216 -14
  34. package/docs-site/static/search-index.json +24 -21
  35. package/package.json +1 -1
@@ -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
- export type DecisionQuestion = DecisionBooleanQuestion | DecisionChoiceQuestion | DecisionScoreQuestion;
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 DecisionAnswer = DecisionBooleanAnswer | DecisionChoiceAnswer | DecisionScoreAnswer;
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 video as the caller supplies it: raw bytes, a file path, or a data URL. */
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 video sent. Absent for a text-only request. */
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 and XOR; `Infinity` is never used.
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: everything is a `data:` URL, ready for the wire. */
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
- /** Encoded size of every URL above. */
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 video turned into a data URL, or the reason it could not be. Same literal-discriminant rule as above. */
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
@@ -34,7 +34,13 @@ export function readDecisionChoice(answers, id) {
34
34
  const ranked = Object.entries(probabilities)
35
35
  .map(([name, probability]) => ({ name, probability }))
36
36
  .sort((a, b) => b.probability - a.probability);
37
- return { choice, confidence, probabilities, ranked };
37
+ return {
38
+ choice,
39
+ confidence,
40
+ probabilities,
41
+ ranked,
42
+ unknown: answer.unknown === true,
43
+ };
38
44
  }
39
45
  export function readDecisionScore(answers, id) {
40
46
  const answer = answers[id];
@@ -44,6 +50,22 @@ export function readDecisionScore(answers, id) {
44
50
  const { score, confidence, legend, probabilities } = answer;
45
51
  return { score, confidence, legend, probabilities };
46
52
  }
53
+ /**
54
+ * Read an extracted value. `value` is null and `unknown` true when the model
55
+ * found nothing to extract, which is an answer, not a missing one: undefined
56
+ * still means the id is absent or the answer was another type.
57
+ */
58
+ export function readDecisionValue(answers, id) {
59
+ const answer = answers[id];
60
+ if (answer?.type !== "value") {
61
+ return undefined;
62
+ }
63
+ return {
64
+ value: answer.value,
65
+ confidence: answer.confidence,
66
+ unknown: answer.unknown === true || answer.value === null,
67
+ };
68
+ }
47
69
  /**
48
70
  * A yes/no answer carries no confidence of its own, so certainty has to be
49
71
  * inferred from how far the probability sits from a coin flip. 0.5 → 0, and
@@ -136,6 +158,15 @@ export function stripHostAnswers(result, hostIdByWireId) {
136
158
  return { ...result, answers };
137
159
  }
138
160
  const isRecordValue = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
161
+ function compilesAsRegex(pattern) {
162
+ try {
163
+ new RegExp(pattern);
164
+ return true;
165
+ }
166
+ catch {
167
+ return false;
168
+ }
169
+ }
139
170
  /**
140
171
  * Whether `value` is something a decision model accepts as content: a
141
172
  * string, number or boolean, or an array or plain object of the same.
@@ -198,7 +229,17 @@ function isWellFormedDecisionQuestion(value) {
198
229
  case "choice":
199
230
  return (isRecordValue(value.criteria) &&
200
231
  Object.keys(value.criteria).length >= 2 &&
201
- Object.values(value.criteria).every((rubric) => rubric === null || isDecisionInput(rubric)));
232
+ Object.values(value.criteria).every((rubric) => rubric === null || isDecisionInput(rubric)) &&
233
+ (value.unknown === undefined || typeof value.unknown === "boolean"));
234
+ case "value":
235
+ return ((value.dtype === undefined ||
236
+ value.dtype === "string" ||
237
+ value.dtype === "int" ||
238
+ value.dtype === "float") &&
239
+ (value.pattern === undefined ||
240
+ (typeof value.pattern === "string" &&
241
+ value.pattern.length > 0 &&
242
+ compilesAsRegex(value.pattern))));
202
243
  case "score":
203
244
  return (Array.isArray(value.criteria) &&
204
245
  value.criteria.length >= 2 &&
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * @module utils/decisionLimits
13
13
  */
14
- import type { DecisionLimits, DecisionLimitsReading, DecisionState, ProviderDescriptor } from "../types/index.js";
14
+ import type { DecisionLimits, DecisionLimitsReading, DecisionQuestion, DecisionQuestionType, DecisionState, ProviderDescriptor } from "../types/index.js";
15
15
  /**
16
16
  * Estimate how many tokens a decision model will read from `state`.
17
17
  *
@@ -37,3 +37,15 @@ export declare function estimateDecisionStateTokens(state: DecisionState, limits
37
37
  * limits.
38
38
  */
39
39
  export declare function resolveDecisionLimitsReading(descriptor: Pick<ProviderDescriptor, "name" | "decisionLimits">, model: string): DecisionLimitsReading | null;
40
+ /** What a provider reads when its limits list no `questionTypes`. */
41
+ export declare const DEFAULT_DECISION_QUESTION_TYPES: readonly DecisionQuestionType[];
42
+ /** The type a question needs a provider to read; a `choice` with `unknown` on needs `choice-unknown`. */
43
+ export declare function decisionQuestionTypeOf(question: DecisionQuestion): DecisionQuestionType;
44
+ /**
45
+ * Why a provider with these limits cannot be asked `question`, or undefined
46
+ * when it can. Applies whether or not the limits are advisory: a question type
47
+ * the server does not know is refused outright, never answered from a guess.
48
+ * `limits` is null for a provider that declares no limits at all, which reads
49
+ * the default types and has no per-question caps.
50
+ */
51
+ export declare function unsupportedDecisionQuestion(question: DecisionQuestion, limits: Pick<DecisionLimitsReading, "questionTypes" | "maxScoreLevels" | "maxChoiceOptions"> | null): string | undefined;
@@ -94,10 +94,19 @@ export function resolveDecisionLimitsReading(descriptor, model) {
94
94
  model,
95
95
  maxStateTokens: modelLimits?.maxStateTokens ?? limits.maxStateTokens,
96
96
  // Absent for a provider that caps by tokens or bytes rather than by
97
- // question count (TypeSafe, XOR).
97
+ // question count (TypeSafe).
98
98
  ...(limits.maxQuestions !== undefined
99
99
  ? { maxQuestions: limits.maxQuestions }
100
100
  : {}),
101
+ ...(limits.questionTypes !== undefined
102
+ ? { questionTypes: [...limits.questionTypes] }
103
+ : {}),
104
+ ...(limits.maxScoreLevels !== undefined
105
+ ? { maxScoreLevels: limits.maxScoreLevels }
106
+ : {}),
107
+ ...(limits.maxChoiceOptions !== undefined
108
+ ? { maxChoiceOptions: limits.maxChoiceOptions }
109
+ : {}),
101
110
  ...(nonAsciiTokensPerChar !== undefined ? { nonAsciiTokensPerChar } : {}),
102
111
  // Not model-overridable — `models` only carries `maxStateTokens` and
103
112
  // `nonAsciiTokensPerChar` — so these three come from the base limits only.
@@ -114,3 +123,45 @@ export function resolveDecisionLimitsReading(descriptor, model) {
114
123
  enforcedLocally: limits.advisory !== true,
115
124
  };
116
125
  }
126
+ /** What a provider reads when its limits list no `questionTypes`. */
127
+ export const DEFAULT_DECISION_QUESTION_TYPES = ["boolean", "choice", "score"];
128
+ /** The type a question needs a provider to read; a `choice` with `unknown` on needs `choice-unknown`. */
129
+ export function decisionQuestionTypeOf(question) {
130
+ return question.type === "choice" && question.unknown === true
131
+ ? "choice-unknown"
132
+ : question.type;
133
+ }
134
+ /**
135
+ * Why a provider with these limits cannot be asked `question`, or undefined
136
+ * when it can. Applies whether or not the limits are advisory: a question type
137
+ * the server does not know is refused outright, never answered from a guess.
138
+ * `limits` is null for a provider that declares no limits at all, which reads
139
+ * the default types and has no per-question caps.
140
+ */
141
+ export function unsupportedDecisionQuestion(question, limits) {
142
+ const types = limits?.questionTypes ?? DEFAULT_DECISION_QUESTION_TYPES;
143
+ const type = decisionQuestionTypeOf(question);
144
+ if (!types.includes(type)) {
145
+ return type === "choice-unknown"
146
+ ? "a choice with unknown: true is not accepted"
147
+ : `the question type "${type}" is not accepted (accepted: ${types.join(", ")})`;
148
+ }
149
+ if (question.type === "choice") {
150
+ const keys = Object.keys(question.criteria);
151
+ if (question.unknown === true && keys.includes("unknown")) {
152
+ return 'a choice with unknown: true cannot also name an option "unknown"';
153
+ }
154
+ const options = keys.length + (question.unknown === true ? 1 : 0);
155
+ const max = limits?.maxChoiceOptions;
156
+ if (max !== undefined && options > max) {
157
+ return `a choice may have at most ${max} options${question.unknown === true ? ", counting unknown" : ""}; this one has ${options}`;
158
+ }
159
+ }
160
+ if (question.type === "score") {
161
+ const max = limits?.maxScoreLevels;
162
+ if (max !== undefined && question.criteria.length > max) {
163
+ return `a score may have at most ${max} levels; this one has ${question.criteria.length}`;
164
+ }
165
+ }
166
+ return undefined;
167
+ }
@@ -1,9 +1,11 @@
1
1
  import type { DecisionMediaLimits, DecisionMediaResult, DecisionMediaSource } from "../types/index.js";
2
2
  /**
3
- * Turn the caller's images and video into `data:` URLs, or say why not.
4
- * Runs before any request, so every refusal costs nothing and none is retried.
3
+ * Turn the caller's images and video into `data:` URLs, and an audio clip into
4
+ * `{ data, format }`, or say why not. Runs before any request, so every
5
+ * refusal costs nothing and none is retried.
5
6
  */
6
7
  export declare function prepareDecisionMedia(request: {
7
8
  images?: readonly DecisionMediaSource[];
8
9
  video?: DecisionMediaSource;
10
+ audio?: DecisionMediaSource;
9
11
  }, limits: DecisionMediaLimits | undefined, vendor: string, alternatives: readonly string[]): Promise<DecisionMediaResult>;