zorya-classifier 0.0.0-stage → 0.1.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 (91) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +11 -0
  3. package/README.md +127 -2
  4. package/dist/app/cli-route.d.ts +3 -0
  5. package/dist/app/cli-route.js +40 -0
  6. package/dist/app/create-classifier.d.ts +14 -0
  7. package/dist/app/create-classifier.js +100 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +33 -0
  10. package/dist/entities/contracts.d.ts +395 -0
  11. package/dist/entities/contracts.js +90 -0
  12. package/dist/entities/errors.d.ts +7 -0
  13. package/dist/entities/errors.js +16 -0
  14. package/dist/features/choice-beam-layout.d.ts +18 -0
  15. package/dist/features/choice-beam-layout.js +136 -0
  16. package/dist/features/choice.d.ts +4 -0
  17. package/dist/features/choice.js +83 -0
  18. package/dist/features/prompts.d.ts +8 -0
  19. package/dist/features/prompts.js +56 -0
  20. package/dist/features/uniform-grid.d.ts +3 -0
  21. package/dist/features/uniform-grid.js +35 -0
  22. package/dist/index.d.ts +5 -0
  23. package/dist/index.js +2 -0
  24. package/dist/infrastructure/openrouter-common.d.ts +130 -0
  25. package/dist/infrastructure/openrouter-common.js +317 -0
  26. package/dist/infrastructure/openrouter-vision.d.ts +8 -0
  27. package/dist/infrastructure/openrouter-vision.js +12 -0
  28. package/dist/infrastructure/openrouter.d.ts +19 -0
  29. package/dist/infrastructure/openrouter.js +207 -0
  30. package/dist/infrastructure/raster.d.ts +2 -0
  31. package/dist/infrastructure/raster.js +121 -0
  32. package/dist/infrastructure/vision-chat.d.ts +64 -0
  33. package/dist/infrastructure/vision-chat.js +309 -0
  34. package/dist/openai-compatible.d.ts +5 -0
  35. package/dist/openai-compatible.js +7 -0
  36. package/dist/openrouter.d.ts +4 -0
  37. package/dist/openrouter.js +2 -0
  38. package/dist/processes/classify.d.ts +2 -0
  39. package/dist/processes/classify.js +21 -0
  40. package/dist/processes/locate.d.ts +2 -0
  41. package/dist/processes/locate.js +201 -0
  42. package/dist/shared/choice-mass.d.ts +2 -0
  43. package/dist/shared/choice-mass.js +19 -0
  44. package/dist/shared/gate.d.ts +11 -0
  45. package/dist/shared/gate.js +52 -0
  46. package/dist/shared/scope.d.ts +12 -0
  47. package/dist/shared/scope.js +29 -0
  48. package/docs/README.ru.md +245 -0
  49. package/docs/acceptance.md +31 -0
  50. package/docs/api.md +45 -0
  51. package/docs/architecture.md +32 -0
  52. package/docs/benchmarks.md +53 -0
  53. package/docs/evidence/current-linux-check.json +15 -0
  54. package/docs/evidence/decisions-completion-v1-manifest.json +96 -0
  55. package/docs/evidence/decisions-completion-v1-report.json +1205 -0
  56. package/docs/evidence/decisions-selection-v1-manifest.json +96 -0
  57. package/docs/evidence/decisions-selection-v1-report.json +1457 -0
  58. package/docs/evidence/jev-pricing-2026-10-18.json +69 -0
  59. package/docs/evidence/linux-check.json +20 -0
  60. package/docs/evidence/linux-check.txt +205 -0
  61. package/docs/evidence/live-release-smoke-manifest.json +82 -0
  62. package/docs/evidence/live-release-smoke-provenance.json +9 -0
  63. package/docs/evidence/live-release-smoke-report.json +2136 -0
  64. package/docs/evidence/live-v1-manifest.json +84 -0
  65. package/docs/evidence/live-v1-provenance.json +9 -0
  66. package/docs/evidence/live-v1-report.json +15555 -0
  67. package/docs/evidence/luna-decisions-fine-probe.json +19 -0
  68. package/docs/evidence/luna-decisions-locator-smoke-manifest.json +39 -0
  69. package/docs/evidence/luna-decisions-locator-smoke-report.json +149 -0
  70. package/docs/evidence/pack-check.json +26 -0
  71. package/docs/evidence/protocol-probes.json +61 -0
  72. package/docs/evidence/rejected-vision-selection-v1-qwen-photo-dev-01.json +42 -0
  73. package/docs/evidence/rejected-vision-selection-v1-qwen-photo-dev-17.json +42 -0
  74. package/docs/evidence/rejected-vision-selection-v1-qwen-photo-heldout-09.json +42 -0
  75. package/docs/evidence/rejected-vision-selection-v2-haiku-photo-dev-16.json +42 -0
  76. package/docs/evidence/rejected-vision-selection-v2-haiku-support-heldout-016.json +42 -0
  77. package/docs/evidence/vision-cost-reconciliation.json +37 -0
  78. package/docs/evidence/vision-model-summary.json +562 -0
  79. package/docs/evidence/vision-selection-v1-manifest.json +95 -0
  80. package/docs/evidence/vision-selection-v1-report.json +4598 -0
  81. package/docs/evidence/vision-selection-v2-manifest.json +95 -0
  82. package/docs/evidence/vision-selection-v2-report.json +1139 -0
  83. package/docs/evidence/windows-native.json +73 -0
  84. package/docs/evidence/windows-secrets.json +46 -0
  85. package/docs/operations.md +15 -0
  86. package/docs/plan.md +25 -0
  87. package/docs/provenance.json +32 -0
  88. package/docs/publishing.md +21 -0
  89. package/docs/results.md +69 -0
  90. package/docs/vision-results.md +51 -0
  91. package/package.json +89 -4
@@ -0,0 +1,395 @@
1
+ import { z } from 'zod';
2
+ export declare const limits: Readonly<{
3
+ imageBytes: number;
4
+ imagePixels: 16777216;
5
+ bodyBytes: number;
6
+ maxClasses: 255;
7
+ maxImages: 9;
8
+ }>;
9
+ export declare const pixelRectSchema: z.ZodObject<{
10
+ left: z.ZodNumber;
11
+ top: z.ZodNumber;
12
+ width: z.ZodNumber;
13
+ height: z.ZodNumber;
14
+ }, "strict", z.ZodTypeAny, {
15
+ left: number;
16
+ top: number;
17
+ width: number;
18
+ height: number;
19
+ }, {
20
+ left: number;
21
+ top: number;
22
+ width: number;
23
+ height: number;
24
+ }>;
25
+ export type PixelRect = z.infer<typeof pixelRectSchema>;
26
+ export interface GridCell {
27
+ id: string;
28
+ column: number;
29
+ row: number;
30
+ region: PixelRect;
31
+ xId?: string;
32
+ yId?: string;
33
+ kind?: 'cell';
34
+ }
35
+ export interface GridPanel {
36
+ id: string;
37
+ region: PixelRect;
38
+ cells: readonly GridCell[];
39
+ }
40
+ export declare const classSchema: z.ZodObject<{
41
+ id: z.ZodString;
42
+ description: z.ZodString;
43
+ }, "strict", z.ZodTypeAny, {
44
+ id: string;
45
+ description: string;
46
+ }, {
47
+ id: string;
48
+ description: string;
49
+ }>;
50
+ export type ClassDefinition = z.infer<typeof classSchema>;
51
+ export declare const classifySchema: z.ZodObject<{
52
+ input: z.ZodUnion<[z.ZodString, z.ZodEffects<z.ZodObject<{
53
+ text: z.ZodOptional<z.ZodString>;
54
+ image: z.ZodOptional<z.ZodEffects<z.ZodType<Uint8Array<ArrayBufferLike>, z.ZodTypeDef, Uint8Array<ArrayBufferLike>>, Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>>>;
55
+ }, "strict", z.ZodTypeAny, {
56
+ text?: string | undefined;
57
+ image?: Uint8Array<ArrayBufferLike> | undefined;
58
+ }, {
59
+ text?: string | undefined;
60
+ image?: Uint8Array<ArrayBufferLike> | undefined;
61
+ }>, {
62
+ text?: string | undefined;
63
+ image?: Uint8Array<ArrayBufferLike> | undefined;
64
+ }, {
65
+ text?: string | undefined;
66
+ image?: Uint8Array<ArrayBufferLike> | undefined;
67
+ }>]>;
68
+ classes: z.ZodEffects<z.ZodArray<z.ZodObject<{
69
+ id: z.ZodString;
70
+ description: z.ZodString;
71
+ }, "strict", z.ZodTypeAny, {
72
+ id: string;
73
+ description: string;
74
+ }, {
75
+ id: string;
76
+ description: string;
77
+ }>, "many">, {
78
+ id: string;
79
+ description: string;
80
+ }[], {
81
+ id: string;
82
+ description: string;
83
+ }[]>;
84
+ instructions: z.ZodOptional<z.ZodString>;
85
+ }, "strict", z.ZodTypeAny, {
86
+ input: string | {
87
+ text?: string | undefined;
88
+ image?: Uint8Array<ArrayBufferLike> | undefined;
89
+ };
90
+ classes: {
91
+ id: string;
92
+ description: string;
93
+ }[];
94
+ instructions?: string | undefined;
95
+ }, {
96
+ input: string | {
97
+ text?: string | undefined;
98
+ image?: Uint8Array<ArrayBufferLike> | undefined;
99
+ };
100
+ classes: {
101
+ id: string;
102
+ description: string;
103
+ }[];
104
+ instructions?: string | undefined;
105
+ }>;
106
+ export type ClassifyInput = z.input<typeof classifySchema> & {
107
+ signal?: AbortSignal;
108
+ };
109
+ export declare const locateSchema: z.ZodObject<{
110
+ image: z.ZodEffects<z.ZodType<Uint8Array<ArrayBufferLike>, z.ZodTypeDef, Uint8Array<ArrayBufferLike>>, Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>>;
111
+ target: z.ZodString;
112
+ exclusions: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
113
+ roi: z.ZodOptional<z.ZodObject<{
114
+ left: z.ZodNumber;
115
+ top: z.ZodNumber;
116
+ width: z.ZodNumber;
117
+ height: z.ZodNumber;
118
+ }, "strict", z.ZodTypeAny, {
119
+ left: number;
120
+ top: number;
121
+ width: number;
122
+ height: number;
123
+ }, {
124
+ left: number;
125
+ top: number;
126
+ width: number;
127
+ height: number;
128
+ }>>;
129
+ }, "strict", z.ZodTypeAny, {
130
+ image: Uint8Array<ArrayBufferLike>;
131
+ target: string;
132
+ exclusions: string[];
133
+ roi?: {
134
+ left: number;
135
+ top: number;
136
+ width: number;
137
+ height: number;
138
+ } | undefined;
139
+ }, {
140
+ image: Uint8Array<ArrayBufferLike>;
141
+ target: string;
142
+ exclusions?: string[] | undefined;
143
+ roi?: {
144
+ left: number;
145
+ top: number;
146
+ width: number;
147
+ height: number;
148
+ } | undefined;
149
+ }>;
150
+ export type LocateInput = z.input<typeof locateSchema> & {
151
+ signal?: AbortSignal;
152
+ };
153
+ export type ImagePart = {
154
+ type: 'image_url';
155
+ image_url: {
156
+ url: string;
157
+ detail: 'high';
158
+ };
159
+ };
160
+ export type DecisionState = (string | ImagePart)[];
161
+ export type ChoiceQuestion = {
162
+ type: 'choice';
163
+ rankingSize?: 1 | 4;
164
+ instructions: string;
165
+ criteria: Record<string, string>;
166
+ };
167
+ type NoulQuestion = {
168
+ type: 'noul';
169
+ instructions: string;
170
+ };
171
+ type DecisionQuestion = ChoiceQuestion | NoulQuestion;
172
+ export type DecisionRequest = {
173
+ state: DecisionState;
174
+ questions: Record<string, DecisionQuestion>;
175
+ };
176
+ export interface DecisionMetadata {
177
+ model: string | null;
178
+ provider: string | null;
179
+ requestId: string | null;
180
+ durationMs: number;
181
+ costUsd: number | null;
182
+ attempts: number;
183
+ }
184
+ declare const choiceAnswerSchema: z.ZodObject<{
185
+ type: z.ZodLiteral<"choice">;
186
+ choice: z.ZodString;
187
+ probabilities: z.ZodRecord<z.ZodString, z.ZodNumber>;
188
+ confidence: z.ZodOptional<z.ZodNumber>;
189
+ probabilityRoundingStep: z.ZodOptional<z.ZodLiteral<0.01>>;
190
+ }, "strip", z.ZodTypeAny, {
191
+ type: "choice";
192
+ choice: string;
193
+ probabilities: Record<string, number>;
194
+ confidence?: number | undefined;
195
+ probabilityRoundingStep?: 0.01 | undefined;
196
+ }, {
197
+ type: "choice";
198
+ choice: string;
199
+ probabilities: Record<string, number>;
200
+ confidence?: number | undefined;
201
+ probabilityRoundingStep?: 0.01 | undefined;
202
+ }>;
203
+ export type ChoiceAnswer = z.infer<typeof choiceAnswerSchema>;
204
+ export declare const decisionResultSchema: z.ZodObject<{
205
+ answers: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
206
+ type: z.ZodLiteral<"choice">;
207
+ choice: z.ZodString;
208
+ probabilities: z.ZodRecord<z.ZodString, z.ZodNumber>;
209
+ confidence: z.ZodOptional<z.ZodNumber>;
210
+ probabilityRoundingStep: z.ZodOptional<z.ZodLiteral<0.01>>;
211
+ }, "strip", z.ZodTypeAny, {
212
+ type: "choice";
213
+ choice: string;
214
+ probabilities: Record<string, number>;
215
+ confidence?: number | undefined;
216
+ probabilityRoundingStep?: 0.01 | undefined;
217
+ }, {
218
+ type: "choice";
219
+ choice: string;
220
+ probabilities: Record<string, number>;
221
+ confidence?: number | undefined;
222
+ probabilityRoundingStep?: 0.01 | undefined;
223
+ }>, z.ZodObject<{
224
+ type: z.ZodLiteral<"ranked-choice">;
225
+ ranking: z.ZodArray<z.ZodString, "many">;
226
+ }, "strict", z.ZodTypeAny, {
227
+ type: "ranked-choice";
228
+ ranking: string[];
229
+ }, {
230
+ type: "ranked-choice";
231
+ ranking: string[];
232
+ }>, z.ZodObject<{
233
+ type: z.ZodLiteral<"presence">;
234
+ presence: z.ZodEnum<["present", "absent", "unknown"]>;
235
+ }, "strict", z.ZodTypeAny, {
236
+ type: "presence";
237
+ presence: "unknown" | "present" | "absent";
238
+ }, {
239
+ type: "presence";
240
+ presence: "unknown" | "present" | "absent";
241
+ }>, z.ZodObject<{
242
+ type: z.ZodLiteral<"noul">;
243
+ noul: z.ZodNumber;
244
+ }, "strip", z.ZodTypeAny, {
245
+ type: "noul";
246
+ noul: number;
247
+ }, {
248
+ type: "noul";
249
+ noul: number;
250
+ }>]>>;
251
+ metadata: z.ZodObject<{
252
+ model: z.ZodNullable<z.ZodString>;
253
+ provider: z.ZodNullable<z.ZodString>;
254
+ requestId: z.ZodNullable<z.ZodString>;
255
+ durationMs: z.ZodNumber;
256
+ costUsd: z.ZodNullable<z.ZodNumber>;
257
+ attempts: z.ZodNumber;
258
+ }, "strip", z.ZodTypeAny, {
259
+ model: string | null;
260
+ provider: string | null;
261
+ requestId: string | null;
262
+ durationMs: number;
263
+ costUsd: number | null;
264
+ attempts: number;
265
+ }, {
266
+ model: string | null;
267
+ provider: string | null;
268
+ requestId: string | null;
269
+ durationMs: number;
270
+ costUsd: number | null;
271
+ attempts: number;
272
+ }>;
273
+ }, "strip", z.ZodTypeAny, {
274
+ answers: Record<string, {
275
+ type: "choice";
276
+ choice: string;
277
+ probabilities: Record<string, number>;
278
+ confidence?: number | undefined;
279
+ probabilityRoundingStep?: 0.01 | undefined;
280
+ } | {
281
+ type: "ranked-choice";
282
+ ranking: string[];
283
+ } | {
284
+ type: "presence";
285
+ presence: "unknown" | "present" | "absent";
286
+ } | {
287
+ type: "noul";
288
+ noul: number;
289
+ }>;
290
+ metadata: {
291
+ model: string | null;
292
+ provider: string | null;
293
+ requestId: string | null;
294
+ durationMs: number;
295
+ costUsd: number | null;
296
+ attempts: number;
297
+ };
298
+ }, {
299
+ answers: Record<string, {
300
+ type: "choice";
301
+ choice: string;
302
+ probabilities: Record<string, number>;
303
+ confidence?: number | undefined;
304
+ probabilityRoundingStep?: 0.01 | undefined;
305
+ } | {
306
+ type: "ranked-choice";
307
+ ranking: string[];
308
+ } | {
309
+ type: "presence";
310
+ presence: "unknown" | "present" | "absent";
311
+ } | {
312
+ type: "noul";
313
+ noul: number;
314
+ }>;
315
+ metadata: {
316
+ model: string | null;
317
+ provider: string | null;
318
+ requestId: string | null;
319
+ durationMs: number;
320
+ costUsd: number | null;
321
+ attempts: number;
322
+ };
323
+ }>;
324
+ export type DecisionResult = z.infer<typeof decisionResultSchema>;
325
+ export interface DecisionPort {
326
+ decide(request: DecisionRequest, signal: AbortSignal): Promise<DecisionResult>;
327
+ }
328
+ export interface ChoiceSelection {
329
+ choice: string;
330
+ nativeChoice: string | null;
331
+ probabilities: Record<string, number> | null;
332
+ ranking?: string[];
333
+ confidence?: number;
334
+ probabilityRoundingStep?: 0.01;
335
+ tied: string[];
336
+ }
337
+ export interface ClassificationResult extends ChoiceSelection {
338
+ metadata: DecisionMetadata;
339
+ }
340
+ interface LocalizationMetadata {
341
+ image: {
342
+ width: number;
343
+ height: number;
344
+ coordinateSpace: 'oriented-source-pixels';
345
+ };
346
+ stages: DecisionMetadata[];
347
+ durationMs: number;
348
+ knownCostUsd: number;
349
+ unknownCostCount: number;
350
+ }
351
+ export type LocalizationResult = ({
352
+ status: 'located';
353
+ region: PixelRect;
354
+ normalizedRegion: PixelRect;
355
+ selection: ChoiceSelection;
356
+ method: 'three-choice-grid-v1';
357
+ refinement?: RefinementResult;
358
+ } | {
359
+ status: 'not-found';
360
+ reason: 'absent-in-evaluated-region';
361
+ } | {
362
+ status: 'unknown';
363
+ reason: 'uncertain-presence' | 'candidate-union-empty' | 'contradictory-evidence' | 'layout-limit';
364
+ }) & {
365
+ metadata: LocalizationMetadata;
366
+ };
367
+ export type RefinementResult = {
368
+ status: 'refined';
369
+ region: PixelRect;
370
+ method: string;
371
+ } | {
372
+ status: 'unknown';
373
+ reason: string;
374
+ };
375
+ export interface RefinementPort {
376
+ refine(input: {
377
+ image: Uint8Array;
378
+ target: string;
379
+ region: PixelRect;
380
+ signal: AbortSignal;
381
+ }): Promise<RefinementResult>;
382
+ }
383
+ export declare function parse<T>(schema: z.ZodType<T, z.ZodTypeDef, unknown>, value: unknown, code?: 'INVALID_INPUT' | 'INVALID_RESPONSE'): T;
384
+ export interface RasterImage {
385
+ bytes: Uint8Array;
386
+ width: number;
387
+ height: number;
388
+ }
389
+ export interface RasterPort {
390
+ normalize(bytes: Uint8Array, signal: AbortSignal): Promise<RasterImage>;
391
+ crop(image: RasterImage, region: PixelRect, signal: AbortSignal, scale?: number): Promise<RasterImage>;
392
+ imageState(image: RasterImage, signal: AbortSignal): Promise<DecisionState>;
393
+ grid(image: RasterImage, region: PixelRect, cells: readonly GridCell[], labels: ReadonlyMap<string, string>, signal: AbortSignal): Promise<RasterImage>;
394
+ }
395
+ export {};
@@ -0,0 +1,90 @@
1
+ import { z } from 'zod';
2
+ import { ClassifierError } from './errors.js';
3
+ export const limits = Object.freeze({
4
+ imageBytes: 16 * 1024 * 1024,
5
+ imagePixels: 16_777_216,
6
+ bodyBytes: 32 * 1024 * 1024,
7
+ maxClasses: 255,
8
+ maxImages: 9,
9
+ });
10
+ export const pixelRectSchema = z
11
+ .object({
12
+ left: z.number().int().nonnegative().safe(),
13
+ top: z.number().int().nonnegative().safe(),
14
+ width: z.number().int().positive().max(16384),
15
+ height: z.number().int().positive().max(16384),
16
+ })
17
+ .strict();
18
+ export const classSchema = z
19
+ .object({
20
+ id: z
21
+ .string()
22
+ .min(1)
23
+ .max(128)
24
+ .regex(/^[a-zA-Z][a-zA-Z0-9_-]*$/),
25
+ description: z.string().trim().min(1).max(300),
26
+ })
27
+ .strict();
28
+ const imageSchema = z
29
+ .custom((value) => value instanceof Uint8Array)
30
+ .refine((v) => v.byteLength > 0 && v.byteLength <= limits.imageBytes, 'Expected 1..16MiB image bytes');
31
+ const mediaInputSchema = z
32
+ .object({ text: z.string().trim().min(1).max(32000).optional(), image: imageSchema.optional() })
33
+ .strict()
34
+ .refine((v) => v.text !== undefined || v.image !== undefined, 'Provide text or image');
35
+ export const classifySchema = z
36
+ .object({
37
+ input: z.union([z.string().trim().min(1).max(32000), mediaInputSchema]),
38
+ classes: z
39
+ .array(classSchema)
40
+ .min(2)
41
+ .max(limits.maxClasses)
42
+ .refine((v) => new Set(v.map((c) => c.id)).size === v.length, 'Class IDs must be unique'),
43
+ instructions: z.string().trim().min(1).max(4000).optional(),
44
+ })
45
+ .strict();
46
+ export const locateSchema = z
47
+ .object({
48
+ image: imageSchema,
49
+ target: z.string().trim().min(1).max(2000),
50
+ exclusions: z.array(z.string().trim().min(1).max(500)).max(16).default([]),
51
+ roi: pixelRectSchema.optional(),
52
+ })
53
+ .strict();
54
+ const choiceAnswerSchema = z.object({
55
+ type: z.literal('choice'),
56
+ choice: z.string(),
57
+ probabilities: z.record(z.number().finite().min(0).max(1)),
58
+ confidence: z.number().finite().min(0).max(1).optional(),
59
+ probabilityRoundingStep: z.literal(0.01).optional(),
60
+ });
61
+ const answersSchema = z.record(z.discriminatedUnion('type', [
62
+ choiceAnswerSchema,
63
+ z
64
+ .object({
65
+ type: z.literal('ranked-choice'),
66
+ ranking: z.array(z.string().min(1).max(128)).min(1).max(4),
67
+ })
68
+ .strict(),
69
+ z
70
+ .object({ type: z.literal('presence'), presence: z.enum(['present', 'absent', 'unknown']) })
71
+ .strict(),
72
+ z.object({ type: z.literal('noul'), noul: z.number().finite().min(0).max(1) }),
73
+ ]));
74
+ const metadataSchema = z.object({
75
+ model: z.string().nullable(),
76
+ provider: z.string().nullable(),
77
+ requestId: z.string().nullable(),
78
+ durationMs: z.number().finite().nonnegative(),
79
+ costUsd: z.number().finite().nonnegative().nullable(),
80
+ attempts: z.number().int().positive(),
81
+ });
82
+ export const decisionResultSchema = z.object({ answers: answersSchema, metadata: metadataSchema });
83
+ export function parse(schema, value, code = 'INVALID_INPUT') {
84
+ const result = schema.safeParse(value);
85
+ if (!result.success)
86
+ throw new ClassifierError(code, 'Schema validation failed', {
87
+ paths: result.error.issues.map((v) => v.path.join('.')),
88
+ });
89
+ return result.data;
90
+ }
@@ -0,0 +1,7 @@
1
+ export type ErrorCode = 'INVALID_INPUT' | 'INVALID_RESPONSE' | 'TIMEOUT' | 'ABORTED' | 'CAPACITY' | 'DISPOSED' | 'PROVIDER' | 'AUTHENTICATION' | 'UNSUPPORTED' | 'RESOURCE_LIMIT';
2
+ export declare class ClassifierError extends Error {
3
+ readonly code: ErrorCode;
4
+ readonly details: Readonly<Record<string, unknown>>;
5
+ constructor(code: ErrorCode, message: string, details?: Readonly<Record<string, unknown>>);
6
+ }
7
+ export declare function assertActive(signal: AbortSignal): void;
@@ -0,0 +1,16 @@
1
+ export class ClassifierError extends Error {
2
+ code;
3
+ details;
4
+ constructor(code, message, details = {}) {
5
+ super(message);
6
+ this.code = code;
7
+ this.details = details;
8
+ this.name = 'ClassifierError';
9
+ }
10
+ }
11
+ export function assertActive(signal) {
12
+ if (signal.aborted)
13
+ throw signal.reason instanceof ClassifierError
14
+ ? signal.reason
15
+ : new ClassifierError('ABORTED', 'Operation cancelled');
16
+ }
@@ -0,0 +1,18 @@
1
+ import type { PixelRect } from '../entities/contracts.js';
2
+ import type { ChoiceAnswer as CategoricalAnswer, GridCell, GridPanel as GridDecisionPanel } from '../entities/contracts.js';
3
+ type ChoiceMap = Pick<CategoricalAnswer, 'choice' | 'probabilities'> | {
4
+ choice: string;
5
+ ranking: readonly string[];
6
+ };
7
+ type PanelGeometry = Pick<GridDecisionPanel, 'id' | 'region' | 'cells'>;
8
+ type RankedCell = {
9
+ cell: GridCell;
10
+ probability: number | null;
11
+ };
12
+ /** Native Choice is validated by its consumer; this helper never normalizes raw probabilities. */
13
+ export declare function rankChoiceBeamCells(cells: readonly GridCell[], answer: ChoiceMap): RankedCell[];
14
+ /** S7 coarse first pass owns every requested source pixel once, including tiny or narrow ROIs. */
15
+ export declare function choiceBeamFirstCells(region: PixelRect): GridCell[];
16
+ /** Four independent padded source ROIs; an impossible bounded layout returns no panels. */
17
+ export declare function choiceBeamPanels(region: PixelRect, coarseCells: readonly GridCell[], answer: ChoiceMap, accuracyPx: number): PanelGeometry[];
18
+ export {};
@@ -0,0 +1,136 @@
1
+ import { pixelRectSchema } from '../entities/contracts.js';
2
+ import { uniformCoordinateCells } from './uniform-grid.js';
3
+ const sourceKey = (region) => `${region.left}:${region.top}:${region.width}:${region.height}`;
4
+ function assertRegion(region) {
5
+ if (!pixelRectSchema.safeParse(region).success ||
6
+ !Number.isSafeInteger(region.left + region.width) ||
7
+ !Number.isSafeInteger(region.top + region.height))
8
+ throw new RangeError('Choice beam requires safe positive source-pixel rectangles');
9
+ }
10
+ function contains(outer, inner) {
11
+ return (inner.left >= outer.left &&
12
+ inner.top >= outer.top &&
13
+ inner.left + inner.width <= outer.left + outer.width &&
14
+ inner.top + inner.height <= outer.top + outer.height);
15
+ }
16
+ /** Native Choice is validated by its consumer; this helper never normalizes raw probabilities. */
17
+ export function rankChoiceBeamCells(cells, answer) {
18
+ if (cells.length > 196)
19
+ throw new RangeError('Choice beam exceeds its distinct-cell budget');
20
+ if (!cells.length)
21
+ return [];
22
+ if ('ranking' in answer) {
23
+ if (answer.ranking.length !== Math.min(4, cells.length) ||
24
+ new Set(answer.ranking).size !== answer.ranking.length ||
25
+ answer.ranking[0] !== answer.choice)
26
+ throw new RangeError('Choice beam needs a complete top-four closed ranking');
27
+ const meanings = new Map();
28
+ for (const cell of cells) {
29
+ assertRegion(cell.region);
30
+ const key = sourceKey(cell.region);
31
+ if (!cell.id || (meanings.has(cell.id) && meanings.get(cell.id) !== key))
32
+ throw new RangeError('Conflicting cell IDs');
33
+ meanings.set(cell.id, key);
34
+ }
35
+ const seen = new Set(), ranked = [];
36
+ for (const id of answer.ranking) {
37
+ const cell = cells.find((value) => value.id === id);
38
+ if (!cell)
39
+ throw new RangeError('Ranking contains an unknown cell');
40
+ const key = sourceKey(cell.region);
41
+ if (!seen.has(key)) {
42
+ seen.add(key);
43
+ ranked.push({ cell, probability: null });
44
+ }
45
+ }
46
+ return ranked;
47
+ }
48
+ const meanings = new Map();
49
+ const ranked = cells.map((cell, order) => {
50
+ assertRegion(cell.region);
51
+ const probability = answer.probabilities[cell.id], key = sourceKey(cell.region);
52
+ if (!cell.id ||
53
+ !Object.hasOwn(answer.probabilities, cell.id) ||
54
+ probability === undefined ||
55
+ !Number.isFinite(probability) ||
56
+ probability < 0 ||
57
+ probability > 1 ||
58
+ (meanings.has(cell.id) && meanings.get(cell.id) !== key))
59
+ throw new RangeError('Choice beam needs complete finite probabilities and canonical cell IDs');
60
+ meanings.set(cell.id, key);
61
+ return { cell, probability, order };
62
+ });
63
+ const selected = ranked.find((item) => item.cell.id === answer.choice), maximum = Math.max(...ranked.map((item) => item.probability));
64
+ if (!selected || selected.probability !== maximum)
65
+ throw new RangeError('Choice beam requires a native spatial maximum');
66
+ ranked.sort((a, b) => b.probability - a.probability || a.order - b.order);
67
+ const seen = new Set(), result = [];
68
+ for (const item of ranked) {
69
+ const key = sourceKey(item.cell.region);
70
+ if (seen.has(key))
71
+ continue;
72
+ seen.add(key);
73
+ result.push({ cell: item.cell, probability: item.probability });
74
+ if (result.length === 4)
75
+ break;
76
+ }
77
+ return result;
78
+ }
79
+ /** S7 coarse first pass owns every requested source pixel once, including tiny or narrow ROIs. */
80
+ export function choiceBeamFirstCells(region) {
81
+ assertRegion(region);
82
+ return uniformCoordinateCells(region, 4, 4);
83
+ }
84
+ function contextRegion(region, cell) {
85
+ const padX = Math.ceil(cell.width / 2), padY = Math.ceil(cell.height / 2), left = Math.max(region.left, cell.left - padX), top = Math.max(region.top, cell.top - padY);
86
+ return {
87
+ left,
88
+ top,
89
+ width: Math.min(region.left + region.width, cell.left + cell.width + padX) - left,
90
+ height: Math.min(region.top + region.height, cell.top + cell.height + padY) - top,
91
+ };
92
+ }
93
+ function panelLayout(region, accuracy) {
94
+ const desiredColumns = Math.ceil(region.width / accuracy), desiredRows = Math.ceil(region.height / accuracy);
95
+ let best;
96
+ for (let columns = 1; columns <= Math.min(49, desiredColumns); columns++) {
97
+ for (let rows = 1; rows <= Math.min(Math.floor(49 / columns), desiredRows); rows++) {
98
+ const side = Math.max(Math.ceil(region.width / columns), Math.ceil(region.height / rows));
99
+ if (side > 256)
100
+ continue;
101
+ const imbalance = Math.abs(region.width / columns - region.height / rows), count = columns * rows;
102
+ if (!best ||
103
+ side < best.side ||
104
+ (side === best.side && imbalance < best.imbalance) ||
105
+ (side === best.side && imbalance === best.imbalance && count > best.count))
106
+ best = { columns, rows, side, imbalance, count };
107
+ }
108
+ }
109
+ return best;
110
+ }
111
+ /** Four independent padded source ROIs; an impossible bounded layout returns no panels. */
112
+ export function choiceBeamPanels(region, coarseCells, answer, accuracyPx) {
113
+ assertRegion(region);
114
+ if (!Number.isSafeInteger(accuracyPx) || accuracyPx < 1 || accuracyPx > 64)
115
+ throw new RangeError('Choice beam accuracy must be a bounded positive integer');
116
+ if (coarseCells.length > 16)
117
+ throw new RangeError('Choice beam first pass exceeds16 cells');
118
+ for (const cell of coarseCells) {
119
+ assertRegion(cell.region);
120
+ if (!contains(region, cell.region))
121
+ throw new RangeError('Choice beam coarse cells must fit the requested source ROI');
122
+ }
123
+ const canonical = new Map(), panels = [];
124
+ for (const { cell } of rankChoiceBeamCells(coarseCells, answer)) {
125
+ const roi = contextRegion(region, cell.region), layout = panelLayout(roi, accuracyPx);
126
+ if (!layout)
127
+ return [];
128
+ const id = `p${panels.length}`, cells = uniformCoordinateCells(roi, layout.columns, layout.rows).map((fine) => {
129
+ const key = sourceKey(fine.region), canonicalId = canonical.get(key) ?? `${id}:${fine.id}`;
130
+ canonical.set(key, canonicalId);
131
+ return { ...fine, id: canonicalId };
132
+ });
133
+ panels.push({ id, region: roi, cells });
134
+ }
135
+ return panels;
136
+ }
@@ -0,0 +1,4 @@
1
+ import type { ChoiceSelection, DecisionRequest, DecisionResult } from '../entities/contracts.js';
2
+ export declare function validateDecision(request: DecisionRequest, value: unknown): DecisionResult;
3
+ export declare function selection(result: DecisionResult, keys: readonly string[], rankingSize?: number): ChoiceSelection;
4
+ export declare function presence(result: DecisionResult): number;