nexus-ai-pro 2.20.2 → 2.21.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 (93) hide show
  1. package/API_STABILITY.md +30 -0
  2. package/CHANGELOG.md +76 -1
  3. package/README.md +7 -6
  4. package/dist/adapter-kit/gather.d.ts +76 -0
  5. package/dist/adapter-kit/gather.js +147 -0
  6. package/dist/adapter-kit/index.d.ts +1 -0
  7. package/dist/adapter-kit/index.js +1 -0
  8. package/dist/adapter-kit/record.d.ts +3 -1
  9. package/dist/adapter-kit/record.js +20 -5
  10. package/dist/cli/adapters.d.ts +4 -3
  11. package/dist/cli/adapters.js +71 -2
  12. package/dist/cli/doctor.js +3 -0
  13. package/dist/cli.js +3 -1
  14. package/dist/core/config-builder.d.ts +1 -0
  15. package/dist/core/config-builder.js +1 -0
  16. package/dist/core/defaults.d.ts +34 -0
  17. package/dist/core/defaults.js +39 -0
  18. package/dist/decisions/completion.d.ts +10 -7
  19. package/dist/decisions/completion.js +80 -6
  20. package/dist/decisions/core.d.ts +88 -43
  21. package/dist/decisions/core.js +115 -28
  22. package/dist/decisions/errors.d.ts +25 -0
  23. package/dist/decisions/errors.js +12 -0
  24. package/dist/decisions/index.d.ts +6 -5
  25. package/dist/decisions/index.js +2 -1
  26. package/dist/decisions/integrations.d.ts +14 -5
  27. package/dist/decisions/integrations.js +16 -1
  28. package/dist/decisions/mock.d.ts +5 -3
  29. package/dist/decisions/mock.js +4 -1
  30. package/dist/decisions/pictures.d.ts +43 -0
  31. package/dist/decisions/pictures.js +103 -0
  32. package/dist/decisions/typesafe.d.ts +25 -5
  33. package/dist/decisions/typesafe.js +101 -7
  34. package/dist/doctor/index.d.ts +6 -9
  35. package/dist/doctor/index.js +41 -0
  36. package/dist/operations/adapters.d.ts +1 -0
  37. package/dist/operations/adapters.js +3 -1
  38. package/dist/protocols/a2a.js +22 -3
  39. package/dist/providers/openai.js +13 -0
  40. package/dist/server/deployments.d.ts +3 -2
  41. package/dist/server/deployments.js +6 -1
  42. package/dist/tracing/stores.d.ts +2 -0
  43. package/dist/tracing/stores.js +1 -0
  44. package/dist/tracing/tracer.d.ts +5 -2
  45. package/dist/tracing/tracer.js +6 -3
  46. package/dist/types/tracing.d.ts +6 -0
  47. package/dist/version.d.ts +1 -1
  48. package/dist/version.js +1 -1
  49. package/dist-cjs/adapter-kit/gather.d.ts +76 -0
  50. package/dist-cjs/adapter-kit/gather.js +152 -0
  51. package/dist-cjs/adapter-kit/index.d.ts +1 -0
  52. package/dist-cjs/adapter-kit/index.js +4 -1
  53. package/dist-cjs/adapter-kit/record.d.ts +3 -1
  54. package/dist-cjs/adapter-kit/record.js +20 -5
  55. package/dist-cjs/cli/adapters.d.ts +4 -3
  56. package/dist-cjs/cli/adapters.js +70 -1
  57. package/dist-cjs/cli/doctor.js +3 -0
  58. package/dist-cjs/core/config-builder.d.ts +1 -0
  59. package/dist-cjs/core/config-builder.js +5 -1
  60. package/dist-cjs/core/defaults.d.ts +34 -0
  61. package/dist-cjs/core/defaults.js +44 -0
  62. package/dist-cjs/decisions/completion.d.ts +10 -7
  63. package/dist-cjs/decisions/completion.js +79 -5
  64. package/dist-cjs/decisions/core.d.ts +88 -43
  65. package/dist-cjs/decisions/core.js +134 -46
  66. package/dist-cjs/decisions/errors.d.ts +25 -0
  67. package/dist-cjs/decisions/errors.js +16 -0
  68. package/dist-cjs/decisions/index.d.ts +6 -5
  69. package/dist-cjs/decisions/index.js +5 -1
  70. package/dist-cjs/decisions/integrations.d.ts +14 -5
  71. package/dist-cjs/decisions/integrations.js +16 -1
  72. package/dist-cjs/decisions/mock.d.ts +5 -3
  73. package/dist-cjs/decisions/mock.js +4 -1
  74. package/dist-cjs/decisions/pictures.d.ts +43 -0
  75. package/dist-cjs/decisions/pictures.js +109 -0
  76. package/dist-cjs/decisions/typesafe.d.ts +25 -5
  77. package/dist-cjs/decisions/typesafe.js +106 -12
  78. package/dist-cjs/doctor/index.d.ts +6 -9
  79. package/dist-cjs/doctor/index.js +41 -0
  80. package/dist-cjs/operations/adapters.d.ts +1 -0
  81. package/dist-cjs/operations/adapters.js +3 -1
  82. package/dist-cjs/protocols/a2a.js +22 -3
  83. package/dist-cjs/providers/openai.js +13 -0
  84. package/dist-cjs/server/deployments.d.ts +3 -2
  85. package/dist-cjs/server/deployments.js +6 -1
  86. package/dist-cjs/tracing/stores.d.ts +2 -0
  87. package/dist-cjs/tracing/stores.js +1 -0
  88. package/dist-cjs/tracing/tracer.d.ts +5 -2
  89. package/dist-cjs/tracing/tracer.js +6 -3
  90. package/dist-cjs/types/tracing.d.ts +6 -0
  91. package/dist-cjs/version.d.ts +1 -1
  92. package/dist-cjs/version.js +1 -1
  93. package/package.json +1 -1
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Which release's defaults apply where an option is left out: 2.x's, or a preview of 3.0's. 3.0 turns on
3
+ * three settings that 2.x keeps opt-in, because turning them on changes what an upgrade does; the
4
+ * preview turns on all three at once, so an application can run as 3.0 will before it upgrades. An
5
+ * option set explicitly always wins, under either.
6
+ */
7
+ /** A release whose defaults apply: `2.x`, the default, or `3.0`, a preview of the next major release's. */
8
+ export type NexusDefaults = '2.x' | '3.0';
9
+ /** One default that 3.0 changes: what it is, what 2.x does, what 3.0 does, and what to do first. */
10
+ export interface DefaultChange {
11
+ /** The setting, as its option is named. */
12
+ setting: string;
13
+ /** Where it is set. */
14
+ module: string;
15
+ /** What 2.x does when the option is left out. */
16
+ today: string;
17
+ /** What 3.0, and the preview, does when it is left out. */
18
+ next: string;
19
+ /** What to do before turning it on, if anything. */
20
+ before: string;
21
+ }
22
+ /** The defaults 3.0 changes, each turned on by the preview. */
23
+ export declare const DEFAULT_CHANGES: readonly DefaultChange[];
24
+ /**
25
+ * Chooses which release's defaults apply to everything created after it: `3.0` previews the next
26
+ * major release's, `2.x` returns to today's. Call it before creating a tracer, an operation store, or
27
+ * a deployment: each reads the defaults when it is created. Overrides `NEXUS_DEFAULTS`.
28
+ */
29
+ export declare function useDefaults(release: NexusDefaults): void;
30
+ /**
31
+ * The defaults in effect: as `useDefaults()` chose, else the `NEXUS_DEFAULTS` environment variable,
32
+ * `2.x` or `3.0`, else `2.x`. Throws for any other value of the variable, rather than ignore a typo.
33
+ */
34
+ export declare function nexusDefaults(): NexusDefaults;
@@ -0,0 +1,39 @@
1
+ export const DEFAULT_CHANGES = [
2
+ {
3
+ setting: 'Tracer incremental',
4
+ module: 'nexus-ai-pro/tracing',
5
+ today: 'false: a trace is written when its root run finishes',
6
+ next: 'true on a durable trace store: each run is written as it starts and again as it ends, so a run in a process that dies is not lost',
7
+ before: 'Nothing; close what a process that died left open with closeAbandonedRuns().',
8
+ },
9
+ {
10
+ setting: 'RedisOperationStore index',
11
+ module: 'nexus-ai-pro/operations',
12
+ today: 'false: finding queued work reads every record',
13
+ next: 'true where the client has zadd, zrem, and zrangebyscore: queued and leased work is kept in sorted sets',
14
+ before: 'Run 2.2 or later on every worker sharing the prefix, and call reindex() once.',
15
+ },
16
+ {
17
+ setting: 'Deployments bucketStrategy',
18
+ module: 'nexus-ai-pro/server',
19
+ today: 'fnv1a-v1 for a deployment first recorded',
20
+ next: 'hash-v2 for a deployment first recorded, which spreads sequential thread ids evenly; a recorded deployment keeps its own',
21
+ before: 'Nothing; a deployment already recorded keeps the strategy it records, and bucketing() changes one.',
22
+ },
23
+ ];
24
+ let chosen;
25
+ export function useDefaults(release) {
26
+ chosen = checked(release, 'useDefaults()');
27
+ }
28
+ export function nexusDefaults() {
29
+ if (chosen)
30
+ return chosen;
31
+ const variable = globalThis.process?.env
32
+ ?.NEXUS_DEFAULTS;
33
+ return variable === undefined || variable === '' ? '2.x' : checked(variable, 'NEXUS_DEFAULTS');
34
+ }
35
+ function checked(release, where) {
36
+ if (release === '2.x' || release === '3.0')
37
+ return release;
38
+ throw new Error(`${where} takes 2.x or 3.0, not ${JSON.stringify(release)}`);
39
+ }
@@ -1,8 +1,3 @@
1
- /**
2
- * Decisions from any completion model: each question asked on its own, answered with a one-token code,
3
- * and its probabilities read from the code token's log probabilities, or counted over samples where
4
- * the model reports none.
5
- */
6
1
  import type { CompletionRequest } from '../types/messages.js';
7
2
  import type { NexusResponse } from '../types/response.js';
8
3
  import { type DecisionProvider } from './core.js';
@@ -25,8 +20,14 @@ export interface CompletionDecisionsOptions {
25
20
  * a model that reports none or refuses to, which it then asks by sampling from the start.
26
21
  */
27
22
  method?: CompletionDecisionMethod;
28
- /** Calls per question, when sampling. Defaults to 8. */
23
+ /** Calls per question, when sampling, and per box. Defaults to 8. */
29
24
  samples?: number;
25
+ /**
26
+ * Whether the model reads pictures, so a decision about the pictures a state holds, or a box, may
27
+ * be asked of it. Defaults to `true`; set `false` for a text-only model, which such a decision then
28
+ * passes over to a fallback.
29
+ */
30
+ images?: boolean;
30
31
  /** The temperature samples are drawn at. Defaults to 1. */
31
32
  temperature?: number;
32
33
  /** Questions asked at once. Defaults to 8. */
@@ -41,7 +42,9 @@ export interface CompletionDecisionsOptions {
41
42
  * Its probabilities are read from the log probabilities of that token's alternatives where the model
42
43
  * reports them, the way TypeSafe AI's Jev reads its own; elsewhere the question is asked `samples`
43
44
  * times, and each answer counted. Sampled probabilities come in steps of one sample, and cost as many
44
- * calls.
45
+ * calls. The pictures a state holds are shown to the model after the state, each labelled by its
46
+ * path; a box is asked `samples` times, and is the median of each edge, its confidence how far the
47
+ * samples overlap it.
45
48
  */
46
49
  export declare function completionDecisions(client: DecisionCompletionClient, options: CompletionDecisionsOptions): DecisionProvider & {
47
50
  readonly sampledModels: ReadonlySet<string>;
@@ -1,14 +1,17 @@
1
- import { DecisionError, } from './core.js';
1
+ import { boxOverlap, DecisionError, } from './core.js';
2
+ import { replacePictures, statePictures } from './pictures.js';
2
3
  const LOGPROB_OPTIONS = 20;
3
4
  export function completionDecisions(client, options) {
4
5
  const method = options.method ?? 'auto';
6
+ const images = options.images !== false;
5
7
  const sampled = new Set();
6
8
  const info = {
7
9
  name: options.name ?? options.model,
8
10
  capabilities: {
9
- types: ['yes-no', 'choice', 'score'],
11
+ types: images ? ['yes-no', 'choice', 'score', 'box'] : ['yes-no', 'choice', 'score'],
10
12
  maxOptions: 255,
11
13
  maxLevels: 10,
14
+ images,
12
15
  defaultModel: options.model,
13
16
  },
14
17
  chargedByItsCalls: true,
@@ -21,7 +24,9 @@ export function completionDecisions(client, options) {
21
24
  messages: [
22
25
  {
23
26
  role: 'system',
24
- content: 'You answer one question about a state with exactly one of the codes given, and nothing else.',
27
+ content: question.type === 'box'
28
+ ? 'You answer where something is in a picture with exactly four numbers, and nothing else.'
29
+ : 'You answer one question about a state with exactly one of the codes given, and nothing else.',
25
30
  },
26
31
  { role: 'user', content: prompt },
27
32
  ],
@@ -29,6 +34,8 @@ export function completionDecisions(client, options) {
29
34
  ...(context.tenantId !== undefined ? { tenantId: context.tenantId } : {}),
30
35
  ...extra,
31
36
  });
37
+ if (question.type === 'box')
38
+ return askBox(request, model, usage);
32
39
  const readable = codes.length <= LOGPROB_OPTIONS && codes.every((code) => code.length === 1);
33
40
  if (method !== 'sampling' && readable && !sampled.has(model)) {
34
41
  let response;
@@ -64,6 +71,28 @@ export function completionDecisions(client, options) {
64
71
  }
65
72
  return distributionOf(question, codes, weights);
66
73
  };
74
+ const askBox = async (request, model, usage) => {
75
+ if (method === 'logprobs') {
76
+ throw new DecisionError('A box is read from several samples of the answer, not from log probabilities', 'DECISION_UNSUPPORTED');
77
+ }
78
+ const count = Math.max(1, Math.floor(options.samples ?? 8));
79
+ const readings = await Promise.all(Array.from({ length: count }, () => client.complete(request({ temperature: options.temperature ?? 1, maxTokens: 32 })).then((response) => {
80
+ usage.add(response);
81
+ return boxIn(response.content);
82
+ })));
83
+ const boxes = readings.filter((box) => box !== undefined);
84
+ if (boxes.length === 0) {
85
+ throw new DecisionError(`"${model}" answered none of ${count} samples with a box`, 'DECISION_FAILED');
86
+ }
87
+ const box = {
88
+ top: median(boxes.map((item) => item.top)),
89
+ bottom: median(boxes.map((item) => item.bottom)),
90
+ left: median(boxes.map((item) => item.left)),
91
+ right: median(boxes.map((item) => item.right)),
92
+ };
93
+ const agreement = boxes.reduce((sum, item) => sum + boxOverlap(item, box), 0) / count;
94
+ return { type: 'box', box, confidence: agreement };
95
+ };
67
96
  return {
68
97
  info,
69
98
  sampledModels: sampled,
@@ -112,6 +141,8 @@ class Totals {
112
141
  }
113
142
  }
114
143
  function codesOf(question) {
144
+ if (question.type === 'box')
145
+ return [];
115
146
  if (question.type === 'yes-no')
116
147
  return ['Y', 'N'];
117
148
  if (question.type === 'score')
@@ -121,10 +152,37 @@ function codesOf(question) {
121
152
  : question.options.map((_, index) => String(index + 1));
122
153
  }
123
154
  function promptOf(state, question, codes, instructions) {
124
- const text = typeof state === 'string' ? state : JSON.stringify(state, null, 2);
125
- const lines = [`State:\n${text}`, ''];
155
+ const pictures = typeof state === 'string' ? [] : statePictures(state);
156
+ const shown = pictures.length ? replacePictures(state, (picture) => `[picture \`${picture.path}\`]`) : state;
157
+ const text = typeof shown === 'string' ? shown : JSON.stringify(shown, null, 2);
158
+ const asked = questionLines(question, codes, instructions).join('\n');
159
+ if (pictures.length === 0)
160
+ return `State:\n${text}\n\n${asked}`;
161
+ return [
162
+ { type: 'text', text: `State:\n${text}\n\nThe pictures it holds, by path:` },
163
+ ...pictures.flatMap((picture) => [
164
+ { type: 'text', text: `\`${picture.path}\`:` },
165
+ imagePart(picture.part),
166
+ ]),
167
+ { type: 'text', text: asked },
168
+ ];
169
+ }
170
+ function imagePart(part) {
171
+ if (part.type === 'image')
172
+ return part;
173
+ if (part.type === 'asset')
174
+ return { type: 'image', source: { asset: part.asset } };
175
+ return part;
176
+ }
177
+ function questionLines(question, codes, instructions) {
178
+ const lines = [];
126
179
  if (instructions)
127
180
  lines.push(instructions, '');
181
+ if (question.type === 'box') {
182
+ lines.push(`Find: ${question.question}`);
183
+ lines.push('', "Answer with four numbers from 0 to 1, separated by spaces: the top, bottom, left, and right edges of a box around it, each a fraction of the picture's height or width from its top-left corner.");
184
+ return lines;
185
+ }
128
186
  if (question.type === 'yes-no') {
129
187
  lines.push(`Question: ${question.question ?? 'Is the statement true of the state?'}`);
130
188
  lines.push(`Y: yes${question.criteria?.yes ? `, ${question.criteria.yes}` : ''}`);
@@ -143,7 +201,21 @@ function promptOf(state, question, codes, instructions) {
143
201
  });
144
202
  }
145
203
  lines.push('', `Answer with one code: ${codes.join(', ')}.`);
146
- return lines.join('\n');
204
+ return lines;
205
+ }
206
+ function boxIn(content) {
207
+ const numbers = (content.match(/-?\d*\.?\d+/g) ?? []).slice(0, 4).map(Number);
208
+ if (numbers.length < 4 || numbers.some((value) => !(value >= 0 && value <= 1)))
209
+ return undefined;
210
+ const [top, bottom, left, right] = numbers;
211
+ return top <= bottom && left <= right ? { top, bottom, left, right } : undefined;
212
+ }
213
+ function median(values) {
214
+ const sorted = [...values].sort((a, b) => a - b);
215
+ const middle = Math.floor(sorted.length / 2);
216
+ return sorted.length % 2
217
+ ? sorted[middle]
218
+ : (sorted[middle - 1] + sorted[middle]) / 2;
147
219
  }
148
220
  function codeOf(token) {
149
221
  return token.replace(/^[\s"'`*(]+|[\s"'`*).:,]+$/g, '').toUpperCase();
@@ -184,6 +256,8 @@ function distributionOf(question, codes, weights) {
184
256
  return { type: 'yes-no', yes: share(0) };
185
257
  if (question.type === 'score')
186
258
  return { type: 'score', probabilities: codes.map((_, index) => share(index)) };
259
+ if (question.type === 'box')
260
+ throw new DecisionError('A box has no codes to weigh', 'DECISION_INVALID');
187
261
  return {
188
262
  type: 'choice',
189
263
  probabilities: Object.fromEntries(question.options.map((option, index) => [option.name, share(index)])),
@@ -1,13 +1,18 @@
1
1
  /**
2
- * Typed decisions: questions a model answers with a yes or no, a choice, or a score, each with its
3
- * probabilities and a confidence read from them, so software acts on an answer without parsing text.
2
+ * Typed decisions: questions a model answers with a yes or no, a choice, a score, or a box around
3
+ * something in a picture, each with a confidence read alike from any provider, so software acts on an
4
+ * answer without parsing text.
4
5
  */
5
6
  import type { CacheAdapter } from '../cache/adapters.js';
7
+ import type { ImageContent } from '../types/messages.js';
6
8
  import type { OperationLifecycleLike, ProviderCallContext } from '../types/lifecycle.js';
7
9
  import type { ResponseCost } from '../types/response.js';
8
10
  import type { RunKind } from '../types/tracing.js';
9
- /** The kinds of question there are: a yes or no, a choice among options, or a score on levels. */
10
- export type DecisionQuestionType = 'yes-no' | 'choice' | 'score';
11
+ /**
12
+ * The kinds of question there are: a yes or no, a choice among options, a score on levels, or a box
13
+ * around something in a picture.
14
+ */
15
+ export type DecisionQuestionType = 'yes-no' | 'choice' | 'score' | 'box';
11
16
  /** A question answered yes or no, with the probability that it is yes. */
12
17
  export interface YesNoQuestion {
13
18
  /** Discriminates the question. */
@@ -38,12 +43,40 @@ export interface ScoreQuestion {
38
43
  /** What each level means, lowest first. Two to ten. */
39
44
  levels: readonly string[];
40
45
  }
46
+ /** A question answered with where something is in a picture of the state: a box around it. */
47
+ export interface BoxQuestion {
48
+ /** Discriminates the question. */
49
+ type: 'box';
50
+ /**
51
+ * What to find, and in which picture, by its path in backticks, such as "the dent in `photo`". A box
52
+ * is given even for something the picture does not show: ask a yes or no beside it whether it does.
53
+ */
54
+ question: string;
55
+ }
41
56
  /** Any question. */
42
- export type DecisionQuestion = YesNoQuestion | ChoiceQuestion | ScoreQuestion;
57
+ export type DecisionQuestion = YesNoQuestion | ChoiceQuestion | ScoreQuestion | BoxQuestion;
43
58
  /** Questions by the names their answers come back under. */
44
59
  export type DecisionQuestions = Record<string, DecisionQuestion>;
45
- /** What every question of a decision is about: text, or any JSON value. */
60
+ /**
61
+ * What every question of a decision is about: text, or any JSON value. An object or a list may hold
62
+ * pictures, as image parts (`{ type: 'image', source }`) or image assets, anywhere in it; a question
63
+ * names each by its path in backticks, such as `photo` or `frames[2].frame`.
64
+ */
46
65
  export type DecisionState = string | Record<string, unknown> | readonly unknown[];
66
+ /**
67
+ * Where something is in a picture: each edge a fraction, from 0 to 1, of the picture's height (`top`,
68
+ * `bottom`) or width (`left`, `right`), measured from its top-left corner.
69
+ */
70
+ export interface DecisionBox {
71
+ /** The top edge, from the picture's top. */
72
+ top: number;
73
+ /** The bottom edge, from the picture's top: at least `top`. */
74
+ bottom: number;
75
+ /** The left edge, from the picture's left. */
76
+ left: number;
77
+ /** The right edge, from the picture's left: at least `left`. */
78
+ right: number;
79
+ }
47
80
  /** A yes-or-no question's answer. */
48
81
  export interface YesNoAnswer {
49
82
  /** Discriminates the answer. */
@@ -86,15 +119,27 @@ export interface ScoreAnswer {
86
119
  */
87
120
  confidence: number;
88
121
  }
122
+ /** A box question's answer. */
123
+ export interface BoxAnswer {
124
+ /** Discriminates the answer. */
125
+ type: 'box';
126
+ /** Where it is. */
127
+ box: DecisionBox;
128
+ /**
129
+ * How sure the answer is, from 0 to 1: the provider's own, such as TypeSafe AI's Jev gives, or how
130
+ * far several readings of the box agree, for a model asked more than once.
131
+ */
132
+ confidence: number;
133
+ }
89
134
  /** Any answer. */
90
- export type DecisionAnswer = YesNoAnswer | ChoiceAnswer | ScoreAnswer;
135
+ export type DecisionAnswer = YesNoAnswer | ChoiceAnswer | ScoreAnswer | BoxAnswer;
91
136
  /** The options of a choice question, as a union of their names. */
92
137
  type OptionsOf<O> = O extends readonly (infer K)[] ? K : keyof O;
93
138
  /** The answer a question gets: a choice's answer names only its options. */
94
139
  export type AnswerOf<Q> = Q extends YesNoQuestion ? YesNoAnswer : Q extends {
95
140
  type: 'choice';
96
141
  options: infer O;
97
- } ? ChoiceAnswer<Extract<OptionsOf<O>, string>> : Q extends ScoreQuestion ? ScoreAnswer : DecisionAnswer;
142
+ } ? ChoiceAnswer<Extract<OptionsOf<O>, string>> : Q extends ScoreQuestion ? ScoreAnswer : Q extends BoxQuestion ? BoxAnswer : DecisionAnswer;
98
143
  /** Answers by their questions' names, each typed by its question. */
99
144
  export type DecisionAnswers<Q extends DecisionQuestions> = {
100
145
  [K in keyof Q]: AnswerOf<Q[K]>;
@@ -152,12 +197,12 @@ export type DecisionProviderQuestion = YesNoQuestion | {
152
197
  name: string;
153
198
  description?: string;
154
199
  }>;
155
- } | ScoreQuestion;
200
+ } | ScoreQuestion | BoxQuestion;
156
201
  /** What a provider is asked. */
157
202
  export interface DecisionProviderRequest {
158
203
  /** The model to ask. */
159
204
  model: string;
160
- /** What every question is about. */
205
+ /** What every question is about, with any pictures it holds, which `statePictures()` lists. */
161
206
  state: DecisionState;
162
207
  /** The questions, by name. */
163
208
  questions: Record<string, DecisionProviderQuestion>;
@@ -172,6 +217,10 @@ export type DecisionDistribution = {
172
217
  } | {
173
218
  type: 'score';
174
219
  probabilities: readonly number[];
220
+ } | {
221
+ type: 'box';
222
+ box: DecisionBox;
223
+ confidence: number;
175
224
  };
176
225
  /** What a provider answers. */
177
226
  export interface DecisionProviderResult {
@@ -194,6 +243,8 @@ export interface DecisionProviderCapabilities {
194
243
  maxLevels: number;
195
244
  /** Most questions one decision may ask, when it has a limit. */
196
245
  maxQuestions?: number;
246
+ /** Whether it reads the pictures a state holds. A provider without it is not asked about a picture. */
247
+ images?: boolean;
197
248
  /** The model asked when a request names none. */
198
249
  defaultModel: string;
199
250
  /** The models it serves, when it lists them. */
@@ -232,30 +283,7 @@ export interface DecisionProvider {
232
283
  /** Answers every question of a decision. */
233
284
  decide(request: DecisionProviderRequest, context: DecisionCallContext): Promise<DecisionProviderResult>;
234
285
  }
235
- /** Why a decision failed. */
236
- export type DecisionErrorCode = 'DECISION_NO_PROVIDER' | 'DECISION_UNSUPPORTED' | 'DECISION_INVALID' | 'DECISION_FAILED';
237
- /** Raised for a decision no provider can serve, a question that is malformed, or a provider's failure. */
238
- export declare class DecisionError extends Error {
239
- /**
240
- * A stable code: `DECISION_NO_PROVIDER`, `DECISION_UNSUPPORTED` (a provider cannot take the
241
- * question), `DECISION_INVALID` (the request, or an answer, is malformed), or `DECISION_FAILED`.
242
- */
243
- readonly code: DecisionErrorCode;
244
- /** Whether asking again, or another provider, may help: a timeout or an overloaded service may. */
245
- readonly retryable: boolean;
246
- /** The HTTP status a provider answered with, when it did. */
247
- readonly status?: number | undefined;
248
- constructor(message: string,
249
- /**
250
- * A stable code: `DECISION_NO_PROVIDER`, `DECISION_UNSUPPORTED` (a provider cannot take the
251
- * question), `DECISION_INVALID` (the request, or an answer, is malformed), or `DECISION_FAILED`.
252
- */
253
- code: DecisionErrorCode,
254
- /** Whether asking again, or another provider, may help: a timeout or an overloaded service may. */
255
- retryable?: boolean,
256
- /** The HTTP status a provider answered with, when it did. */
257
- status?: number | undefined);
258
- }
286
+ export { DecisionError, type DecisionErrorCode } from './errors.js';
259
287
  /** A yes-or-no answer's confidence: how far its probability is from an even chance, `|2p - 1|`. */
260
288
  export declare function yesNoConfidence(probability: number): number;
261
289
  /**
@@ -275,6 +303,11 @@ export declare function scoreConfidence(probabilities: readonly number[]): numbe
275
303
  * `DECISION_INVALID` for probabilities that do not fit the question.
276
304
  */
277
305
  export declare function decisionAnswer(question: DecisionProviderQuestion, distribution: DecisionDistribution): DecisionAnswer;
306
+ /**
307
+ * How far two boxes overlap, from 0 to 1: the area they share over the area they cover together, the
308
+ * intersection over union by which a box is judged against where the thing is.
309
+ */
310
+ export declare function boxOverlap(a: DecisionBox, b: DecisionBox): number;
278
311
  /** What to do with an answer: act on it, have a person review it, or neither. */
279
312
  export type GateOutcome = 'act' | 'review' | 'abstain';
280
313
  /** The confidences a gate acts at, and has reviewed at. */
@@ -345,6 +378,7 @@ export interface CalibrationReport {
345
378
  /**
346
379
  * The probability an answer gives its own answer: a yes or no's `max(p, 1 - p)`, a choice's for its
347
380
  * option, and a score's for its level. Its confidence measures how far that is above an even spread.
381
+ * A box has no probabilities, so its confidence stands for one.
348
382
  */
349
383
  export declare function answerProbability(answer: DecisionAnswer): number;
350
384
  /**
@@ -355,11 +389,13 @@ export declare function answerProbability(answer: DecisionAnswer): number;
355
389
  */
356
390
  export declare function calibrate(samples: readonly CalibrationSample[], options?: CalibrateOptions): CalibrationReport;
357
391
  /**
358
- * Whether an answer is right: a yes-or-no or a choice by its answer, and a score by its most probable
359
- * level, or by its weighted score within `tolerance` levels when one is given.
392
+ * Whether an answer is right: a yes-or-no or a choice by its answer, a score by its most probable
393
+ * level, or by its weighted score within `tolerance` levels when one is given, and a box by how far it
394
+ * overlaps the expected one, `boxOverlap()`, at least `overlap` (one half by default).
360
395
  */
361
- export declare function answeredRight(answer: DecisionAnswer, expected: boolean | string | number, options?: {
396
+ export declare function answeredRight(answer: DecisionAnswer, expected: boolean | string | number | DecisionBox, options?: {
362
397
  tolerance?: number;
398
+ overlap?: number;
363
399
  }): boolean;
364
400
  /** A decision model's price, in US dollars per million tokens. */
365
401
  export interface DecisionPrice {
@@ -493,8 +529,11 @@ export interface DecisionConformanceCase {
493
529
  state: DecisionState;
494
530
  /** The question. */
495
531
  question: DecisionQuestion;
496
- /** The right answer: a yes or no, a choice's option, or a score's level. */
497
- expected: boolean | string | number;
532
+ /**
533
+ * The right answer: a yes or no, a choice's option, a score's level, or where a box should be, which
534
+ * an answer overlapping it by half or more matches.
535
+ */
536
+ expected: boolean | string | number | DecisionBox;
498
537
  }
499
538
  /** Options for `runDecisionConformance()`. */
500
539
  export interface DecisionConformanceOptions {
@@ -505,6 +544,11 @@ export interface DecisionConformanceOptions {
505
544
  * as it is told passes without them; a real model is held to them.
506
545
  */
507
546
  cases?: readonly DecisionConformanceCase[];
547
+ /**
548
+ * A picture for the checks of a provider that reads pictures or answers boxes: it is asked a yes or
549
+ * no about the picture, and a box around what it shows. Without one, those checks are skipped.
550
+ */
551
+ picture?: ImageContent;
508
552
  }
509
553
  /** How a provider did against the decision contract. */
510
554
  export interface DecisionConformanceReport {
@@ -514,12 +558,13 @@ export interface DecisionConformanceReport {
514
558
  passed: string[];
515
559
  /** Checks that failed, each with why. */
516
560
  failures: string[];
561
+ /** Checks that were not run, each with why, such as a box with no picture given to ask about. */
562
+ skipped: string[];
517
563
  }
518
564
  /**
519
565
  * Checks a provider against the decision contract: its capabilities are coherent; each question type
520
- * it declares comes back as probabilities that fit the question; several questions in one decision
521
- * each get their own answer; a stopped call is not answered; and every case is answered right. Never
522
- * throws; failures are listed.
566
+ * it declares comes back as probabilities, or a box, that fit the question; a provider that reads
567
+ * pictures answers about one; several questions in one decision each get their own answer; a stopped
568
+ * call is not answered; and every case is answered right. Never throws; failures are listed.
523
569
  */
524
570
  export declare function runDecisionConformance(provider: DecisionProvider, options?: DecisionConformanceOptions): Promise<DecisionConformanceReport>;
525
- export {};