zorya-classifier 0.2.1 → 0.2.3

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 (139) hide show
  1. package/README.md +31 -17
  2. package/dist/app/classifier-config.d.ts +30 -0
  3. package/dist/app/classifier-config.js +46 -0
  4. package/dist/app/classifier-contracts.d.ts +32 -0
  5. package/dist/app/classifier-contracts.js +1 -0
  6. package/dist/app/classifier-runtime.d.ts +7 -0
  7. package/dist/app/classifier-runtime.js +71 -0
  8. package/dist/app/classifier-spatial.d.ts +31 -0
  9. package/dist/app/classifier-spatial.js +78 -0
  10. package/dist/app/cli-arguments.d.ts +2 -2
  11. package/dist/app/composed-classifier.d.ts +17 -0
  12. package/dist/app/composed-classifier.js +1 -0
  13. package/dist/app/create-classifier.d.ts +9 -33
  14. package/dist/app/create-classifier.js +53 -245
  15. package/dist/cli.js +1 -1
  16. package/dist/entities/classification.d.ts +83 -0
  17. package/dist/entities/classification.js +28 -0
  18. package/dist/entities/contracts.d.ts +6 -475
  19. package/dist/entities/contracts.js +6 -97
  20. package/dist/entities/decision.d.ts +211 -0
  21. package/dist/entities/decision.js +33 -0
  22. package/dist/entities/geometry.d.ts +37 -0
  23. package/dist/entities/geometry.js +26 -0
  24. package/dist/entities/image.d.ts +30 -0
  25. package/dist/entities/image.js +12 -0
  26. package/dist/entities/localization.d.ts +110 -0
  27. package/dist/entities/localization.js +13 -0
  28. package/dist/entities/object-proposals.d.ts +1 -1
  29. package/dist/entities/representation.d.ts +0 -183
  30. package/dist/entities/representation.js +1 -33
  31. package/dist/entities/segmentation-metadata.d.ts +70 -0
  32. package/dist/entities/segmentation-metadata.js +21 -1
  33. package/dist/entities/segmentation-verification.d.ts +1 -1
  34. package/dist/entities/segmentation.d.ts +5 -20
  35. package/dist/entities/validation.d.ts +2 -0
  36. package/dist/entities/validation.js +9 -0
  37. package/dist/entities/whole-object.d.ts +6 -21
  38. package/dist/features/choice-beam-layout.js +2 -7
  39. package/dist/features/proposals/presentation.d.ts +21 -0
  40. package/dist/features/proposals/presentation.js +122 -0
  41. package/dist/features/proposals/validation.d.ts +138 -0
  42. package/dist/features/proposals/validation.js +125 -0
  43. package/dist/features/segmentation/box-check.js +10 -23
  44. package/dist/features/segmentation/mask.js +2 -7
  45. package/dist/features/segmentation/region-result.d.ts +18 -0
  46. package/dist/features/segmentation/region-result.js +171 -0
  47. package/dist/features/segmentation/source-mask.d.ts +6 -0
  48. package/dist/features/segmentation/source-mask.js +32 -0
  49. package/dist/features/segmentation/validation.d.ts +9 -0
  50. package/dist/features/segmentation/validation.js +94 -0
  51. package/dist/infrastructure/decision-transport/errors.d.ts +8 -0
  52. package/dist/infrastructure/decision-transport/errors.js +34 -0
  53. package/dist/infrastructure/decision-transport/images.d.ts +1 -0
  54. package/dist/infrastructure/decision-transport/images.js +52 -0
  55. package/dist/infrastructure/{openrouter-common.d.ts → decision-transport/request.d.ts} +6 -15
  56. package/dist/infrastructure/decision-transport/request.js +162 -0
  57. package/dist/infrastructure/decision-transport/response.d.ts +1 -0
  58. package/dist/infrastructure/decision-transport/response.js +72 -0
  59. package/dist/infrastructure/decision-transport/route-identity.d.ts +11 -0
  60. package/dist/infrastructure/decision-transport/route-identity.js +23 -0
  61. package/dist/infrastructure/openrouter-vision.js +1 -1
  62. package/dist/infrastructure/openrouter.js +7 -19
  63. package/dist/infrastructure/raster.js +3 -8
  64. package/dist/infrastructure/segmentation/grabcut/index.d.ts +2 -2
  65. package/dist/infrastructure/segmentation/grabcut/index.js +3 -5
  66. package/dist/infrastructure/segmentation/grabcut/masks.d.ts +49 -0
  67. package/dist/infrastructure/segmentation/grabcut/masks.js +89 -0
  68. package/dist/infrastructure/segmentation/grabcut/model-asset.d.ts +4 -0
  69. package/dist/infrastructure/segmentation/grabcut/model-asset.js +4 -0
  70. package/dist/infrastructure/segmentation/grabcut/runtime.d.ts +70 -0
  71. package/dist/infrastructure/segmentation/grabcut/runtime.js +96 -0
  72. package/dist/infrastructure/segmentation/grabcut/worker-inference.d.ts +4 -43
  73. package/dist/infrastructure/segmentation/grabcut/worker-inference.js +4 -187
  74. package/dist/infrastructure/segmentation/mobile-sam/index.d.ts +3 -66
  75. package/dist/infrastructure/segmentation/mobile-sam/index.js +2 -478
  76. package/dist/infrastructure/segmentation/mobile-sam/input.d.ts +46 -0
  77. package/dist/infrastructure/segmentation/mobile-sam/input.js +83 -0
  78. package/dist/infrastructure/segmentation/mobile-sam/model-assets.d.ts +8 -0
  79. package/dist/infrastructure/segmentation/mobile-sam/model-assets.js +8 -0
  80. package/dist/infrastructure/segmentation/mobile-sam/object-proposals.d.ts +7 -0
  81. package/dist/infrastructure/segmentation/mobile-sam/object-proposals.js +124 -0
  82. package/dist/infrastructure/segmentation/mobile-sam/options.d.ts +43 -0
  83. package/dist/infrastructure/segmentation/mobile-sam/options.js +19 -0
  84. package/dist/infrastructure/segmentation/mobile-sam/proposal-masks.d.ts +26 -0
  85. package/dist/infrastructure/segmentation/mobile-sam/proposal-masks.js +71 -0
  86. package/dist/infrastructure/segmentation/mobile-sam/segmentation-masks.d.ts +112 -0
  87. package/dist/infrastructure/segmentation/mobile-sam/segmentation-masks.js +230 -0
  88. package/dist/infrastructure/segmentation/mobile-sam/segmenter.d.ts +22 -0
  89. package/dist/infrastructure/segmentation/mobile-sam/segmenter.js +268 -0
  90. package/dist/infrastructure/segmentation/mobile-sam/worker-expand.d.ts +7 -0
  91. package/dist/infrastructure/segmentation/mobile-sam/worker-expand.js +188 -0
  92. package/dist/infrastructure/segmentation/mobile-sam/worker-inference.d.ts +6 -149
  93. package/dist/infrastructure/segmentation/mobile-sam/worker-inference.js +6 -845
  94. package/dist/infrastructure/segmentation/mobile-sam/worker-models.d.ts +14 -0
  95. package/dist/infrastructure/segmentation/mobile-sam/worker-models.js +72 -0
  96. package/dist/infrastructure/segmentation/mobile-sam/worker-proposals.d.ts +7 -0
  97. package/dist/infrastructure/segmentation/mobile-sam/worker-proposals.js +159 -0
  98. package/dist/infrastructure/segmentation/mobile-sam/worker-segment.d.ts +4 -0
  99. package/dist/infrastructure/segmentation/mobile-sam/worker-segment.js +126 -0
  100. package/dist/infrastructure/segmentation/native-protocol.d.ts +59 -34
  101. package/dist/infrastructure/segmentation/native-protocol.js +26 -16
  102. package/dist/infrastructure/segmentation/proposal-presentation-input.d.ts +20 -0
  103. package/dist/infrastructure/segmentation/proposal-presentation-input.js +107 -0
  104. package/dist/infrastructure/segmentation/proposal-presentation-pixels.d.ts +8 -0
  105. package/dist/infrastructure/segmentation/proposal-presentation-pixels.js +93 -0
  106. package/dist/infrastructure/segmentation/proposal-presentation.js +10 -201
  107. package/dist/infrastructure/vision-chat/options.d.ts +62 -0
  108. package/dist/infrastructure/vision-chat/options.js +46 -0
  109. package/dist/infrastructure/vision-chat/request.d.ts +23 -0
  110. package/dist/infrastructure/vision-chat/request.js +109 -0
  111. package/dist/infrastructure/vision-chat/response.d.ts +3 -0
  112. package/dist/infrastructure/vision-chat/response.js +105 -0
  113. package/dist/infrastructure/vision-chat.d.ts +4 -61
  114. package/dist/infrastructure/vision-chat.js +8 -271
  115. package/dist/openai-compatible.js +1 -1
  116. package/dist/processes/extract-from-region.js +11 -216
  117. package/dist/processes/extract.d.ts +2 -8
  118. package/dist/processes/extract.js +12 -128
  119. package/dist/processes/proposal-location.js +5 -251
  120. package/docs/README.ru.md +50 -33
  121. package/docs/acceptance.md +6 -4
  122. package/docs/agents.ru.md +97 -0
  123. package/docs/api.md +4 -4
  124. package/docs/architecture.md +33 -7
  125. package/docs/architecture.ru.md +146 -0
  126. package/docs/benchmarks.md +3 -1
  127. package/docs/development.ru.md +141 -0
  128. package/docs/migrations/experimental-text-image-removal.ru.md +9 -0
  129. package/docs/provenance.json +288 -1
  130. package/docs/publishing.md +5 -3
  131. package/docs/refactoring-plan.ru.md +183 -0
  132. package/docs/v02-usage.md +8 -8
  133. package/docs/vision-results.md +1 -1
  134. package/package.json +5 -3
  135. package/dist/infrastructure/boundary-view.d.ts +0 -2
  136. package/dist/infrastructure/boundary-view.js +0 -263
  137. package/dist/infrastructure/openrouter-common.js +0 -317
  138. package/dist/infrastructure/text-representation.d.ts +0 -23
  139. package/dist/infrastructure/text-representation.js +0 -226
@@ -1,6 +1,4 @@
1
1
  import { z } from 'zod';
2
- import { ClassifierError } from '../entities/errors.js';
3
- import type { ErrorCode } from '../entities/errors.js';
4
2
  export declare const requestLimit: number;
5
3
  export declare const responseLimit: number;
6
4
  export declare const probability: z.ZodNumber;
@@ -19,29 +17,29 @@ export declare const requestSchema: z.ZodObject<{
19
17
  detail: "high";
20
18
  }>;
21
19
  }, "strict", z.ZodTypeAny, {
22
- type: "image_url";
23
20
  image_url: {
24
21
  url: string;
25
22
  detail: "high";
26
23
  };
27
- }, {
28
24
  type: "image_url";
25
+ }, {
29
26
  image_url: {
30
27
  url: string;
31
28
  detail: "high";
32
29
  };
33
- }>]>, "many">, (string | {
34
30
  type: "image_url";
31
+ }>]>, "many">, (string | {
35
32
  image_url: {
36
33
  url: string;
37
34
  detail: "high";
38
35
  };
39
- })[], (string | {
40
36
  type: "image_url";
37
+ })[], (string | {
41
38
  image_url: {
42
39
  url: string;
43
40
  detail: "high";
44
41
  };
42
+ type: "image_url";
45
43
  })[]>;
46
44
  questions: z.ZodEffects<z.ZodRecord<z.ZodEffects<z.ZodString, string, string>, z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
47
45
  type: z.ZodLiteral<"choice">;
@@ -86,11 +84,11 @@ export declare const requestSchema: z.ZodObject<{
86
84
  }>>;
87
85
  }, "strict", z.ZodTypeAny, {
88
86
  state: (string | {
89
- type: "image_url";
90
87
  image_url: {
91
88
  url: string;
92
89
  detail: "high";
93
90
  };
91
+ type: "image_url";
94
92
  })[];
95
93
  questions: Record<string, {
96
94
  type: "choice";
@@ -103,11 +101,11 @@ export declare const requestSchema: z.ZodObject<{
103
101
  }>;
104
102
  }, {
105
103
  state: (string | {
106
- type: "image_url";
107
104
  image_url: {
108
105
  url: string;
109
106
  detail: "high";
110
107
  };
108
+ type: "image_url";
111
109
  })[];
112
110
  questions: Record<string, {
113
111
  type: "choice";
@@ -119,12 +117,5 @@ export declare const requestSchema: z.ZodObject<{
119
117
  instructions: string;
120
118
  }>;
121
119
  }>;
122
- export declare function ownedError(code: ErrorCode, message: string, details?: Readonly<Record<string, unknown>>): ClassifierError;
123
- export declare function invalid(code: 'INVALID_INPUT' | 'INVALID_RESPONSE', message: string): ClassifierError;
124
- export declare function active(signal: AbortSignal): void;
125
- export declare function validated<T>(schema: z.ZodType<T, z.ZodTypeDef, unknown>, input: unknown, code: 'INVALID_INPUT' | 'INVALID_RESPONSE'): T;
126
- export declare function validateImage(url: string): void;
127
120
  /** Bounded owned snapshot; schema parsing never receives caller iterators or accessors. */
128
121
  export declare function preflightJson(input: unknown, limit: number): unknown;
129
- export declare function failure(error: unknown, signal: AbortSignal, cleanupFailed?: boolean): ClassifierError;
130
- export declare function readResponse(response: Response, signal: AbortSignal, limit: number): Promise<string>;
@@ -0,0 +1,162 @@
1
+ import { isProxy } from 'node:util/types';
2
+ import { z } from 'zod';
3
+ import { invalid, ownedError } from './errors.js';
4
+ export const requestLimit = 32 * 1024 * 1024;
5
+ export const responseLimit = 1024 * 1024;
6
+ export const probability = z.number().finite().min(0).max(1);
7
+ export const key = z
8
+ .string()
9
+ .min(1)
10
+ .max(128)
11
+ .refine((value) => value !== '__proto__');
12
+ const text = z.string().min(1).max(64_000);
13
+ const imageSchema = z
14
+ .object({
15
+ type: z.literal('image_url'),
16
+ image_url: z.object({ url: z.string().max(22_369_656), detail: z.literal('high') }).strict(),
17
+ })
18
+ .strict();
19
+ const questionSchema = z.discriminatedUnion('type', [
20
+ z
21
+ .object({
22
+ type: z.literal('choice'),
23
+ rankingSize: z.union([z.literal(1), z.literal(4)]).default(4),
24
+ instructions: text,
25
+ criteria: z
26
+ .record(key, text)
27
+ .refine((value) => Object.keys(value).length >= 2 && Object.keys(value).length <= 255),
28
+ })
29
+ .strict(),
30
+ z.object({ type: z.literal('noul'), instructions: text }).strict(),
31
+ ]);
32
+ export const requestSchema = z
33
+ .object({
34
+ state: z
35
+ .array(z.union([text, imageSchema]))
36
+ .min(1)
37
+ .max(64)
38
+ .refine((value) => value.filter((part) => typeof part !== 'string').length <= 9),
39
+ questions: z
40
+ .record(key, questionSchema)
41
+ .refine((value) => Object.keys(value).length >= 1 && Object.keys(value).length <= 16),
42
+ })
43
+ .strict();
44
+ /** Bounded owned snapshot; schema parsing never receives caller iterators or accessors. */
45
+ export function preflightJson(input, limit) {
46
+ let remaining = limit;
47
+ let nodes = 0;
48
+ const ancestors = new Set();
49
+ function charge(bytes) {
50
+ remaining -= bytes;
51
+ if (remaining < 0)
52
+ throw ownedError('RESOURCE_LIMIT', 'Decision request exceeds byte limit');
53
+ }
54
+ function string(value) {
55
+ charge(2);
56
+ for (let index = 0; index < value.length; index++) {
57
+ const code = value.charCodeAt(index);
58
+ if (code < 32)
59
+ charge([8, 9, 10, 12, 13].includes(code) ? 2 : 6);
60
+ else if (code === 34 || code === 92)
61
+ charge(2);
62
+ else if (code < 128)
63
+ charge(1);
64
+ else if (code < 2048)
65
+ charge(2);
66
+ else if (code >= 0xd800 && code <= 0xdbff) {
67
+ const next = value.charCodeAt(index + 1);
68
+ if (next >= 0xdc00 && next <= 0xdfff) {
69
+ charge(4);
70
+ index++;
71
+ }
72
+ else
73
+ charge(6);
74
+ }
75
+ else
76
+ charge(code >= 0xdc00 && code <= 0xdfff ? 6 : 3);
77
+ }
78
+ }
79
+ function visit(value, depth) {
80
+ if (++nodes > 10_000 || depth > 6)
81
+ throw invalid('INVALID_INPUT', 'Decision structure exceeds supported limits');
82
+ if (typeof value === 'string') {
83
+ string(value);
84
+ return value;
85
+ }
86
+ if (value === null || typeof value === 'boolean') {
87
+ charge(value === false ? 5 : 4);
88
+ return value;
89
+ }
90
+ if (typeof value === 'number' && Number.isFinite(value)) {
91
+ charge(String(value).length);
92
+ return value;
93
+ }
94
+ if (typeof value !== 'object' || value === null)
95
+ throw invalid('INVALID_INPUT', 'Unsupported decision value');
96
+ if (isProxy(value))
97
+ throw invalid('INVALID_INPUT', 'Decision input must contain plain values');
98
+ // Zod reads declared object fields even when they are not enumerable; JSON does not.
99
+ for (const name of [
100
+ 'state',
101
+ 'questions',
102
+ 'type',
103
+ 'image_url',
104
+ 'url',
105
+ 'detail',
106
+ 'instructions',
107
+ 'criteria',
108
+ ]) {
109
+ const descriptor = Object.getOwnPropertyDescriptor(value, name);
110
+ if (descriptor && !descriptor.enumerable)
111
+ throw invalid('INVALID_INPUT', 'Decision fields must be enumerable');
112
+ }
113
+ if (ancestors.has(value))
114
+ throw invalid('INVALID_INPUT', 'Circular decision structure');
115
+ ancestors.add(value);
116
+ charge(2);
117
+ if (Array.isArray(value)) {
118
+ const copy = [];
119
+ if (value.length > 64)
120
+ throw invalid('INVALID_INPUT', 'Decision array exceeds supported limits');
121
+ for (let index = 0; index < value.length; index++) {
122
+ if (index)
123
+ charge(1);
124
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
125
+ if (!descriptor || !Object.hasOwn(descriptor, 'value'))
126
+ throw invalid('INVALID_INPUT', 'Decision input must contain plain values');
127
+ copy.push(visit(descriptor.value, depth + 1));
128
+ }
129
+ ancestors.delete(value);
130
+ return copy;
131
+ }
132
+ else {
133
+ const copy = {};
134
+ const prototype = Object.getPrototypeOf(value);
135
+ if (prototype !== Object.prototype && prototype !== null)
136
+ throw invalid('INVALID_INPUT', 'Decision input must contain plain objects');
137
+ let count = 0;
138
+ for (const name in value) {
139
+ if (!Object.hasOwn(value, name))
140
+ continue;
141
+ if (++count > 255)
142
+ throw invalid('INVALID_INPUT', 'Decision object exceeds supported limits');
143
+ if (count > 1)
144
+ charge(1);
145
+ string(name);
146
+ charge(1);
147
+ const descriptor = Object.getOwnPropertyDescriptor(value, name);
148
+ if (!descriptor || !Object.hasOwn(descriptor, 'value'))
149
+ throw invalid('INVALID_INPUT', 'Decision input must contain plain values');
150
+ Object.defineProperty(copy, name, {
151
+ value: visit(descriptor.value, depth + 1),
152
+ enumerable: true,
153
+ configurable: true,
154
+ writable: true,
155
+ });
156
+ }
157
+ ancestors.delete(value);
158
+ return copy;
159
+ }
160
+ }
161
+ return visit(input, 0);
162
+ }
@@ -0,0 +1 @@
1
+ export declare function readResponse(response: Response, signal: AbortSignal, limit: number): Promise<string>;
@@ -0,0 +1,72 @@
1
+ import { active, failure, invalid, ownedError } from './errors.js';
2
+ function statusError(status) {
3
+ const code = status === 401 || status === 403
4
+ ? 'AUTHENTICATION'
5
+ : status === 413
6
+ ? 'RESOURCE_LIMIT'
7
+ : status === 415
8
+ ? 'UNSUPPORTED'
9
+ : 'PROVIDER';
10
+ return ownedError(code, 'Model provider rejected the request', { status });
11
+ }
12
+ export async function readResponse(response, signal, limit) {
13
+ const reader = response.body?.getReader();
14
+ if (!reader) {
15
+ active(signal);
16
+ if (!response.ok)
17
+ throw statusError(response.status);
18
+ throw invalid('INVALID_RESPONSE', 'Provider response has no body');
19
+ }
20
+ let cancelTask;
21
+ function cancel() {
22
+ cancelTask ??= reader.cancel().then(() => true, () => false);
23
+ }
24
+ signal.addEventListener('abort', cancel, { once: true });
25
+ let caught;
26
+ let output = '';
27
+ let complete = false;
28
+ try {
29
+ active(signal);
30
+ if (!response.ok)
31
+ throw statusError(response.status);
32
+ const length = response.headers.get('content-length');
33
+ if (length !== null && (!/^\d+$/u.test(length) || Number(length) > limit))
34
+ throw ownedError('RESOURCE_LIMIT', 'Provider response exceeds byte limit');
35
+ const storage = Buffer.allocUnsafe(limit);
36
+ let bytes = 0;
37
+ while (true) {
38
+ active(signal);
39
+ const next = await reader.read();
40
+ active(signal);
41
+ if (next.done)
42
+ break;
43
+ if (next.value.byteLength > limit - bytes)
44
+ throw ownedError('RESOURCE_LIMIT', 'Provider response exceeds byte limit');
45
+ storage.set(next.value, bytes);
46
+ bytes += next.value.byteLength;
47
+ }
48
+ complete = true;
49
+ try {
50
+ output = new TextDecoder('utf-8', { fatal: true }).decode(storage.subarray(0, bytes));
51
+ }
52
+ catch {
53
+ throw invalid('INVALID_RESPONSE', 'Provider returned invalid UTF-8');
54
+ }
55
+ }
56
+ catch (error) {
57
+ caught = error;
58
+ }
59
+ finally {
60
+ signal.removeEventListener('abort', cancel);
61
+ if (!complete)
62
+ cancel();
63
+ const released = cancelTask ? await cancelTask : true;
64
+ reader.releaseLock();
65
+ if (!released)
66
+ caught = failure(caught, signal, true);
67
+ }
68
+ if (caught !== undefined)
69
+ throw failure(caught, signal);
70
+ active(signal);
71
+ return output;
72
+ }
@@ -0,0 +1,11 @@
1
+ interface ResponseModelIdentity {
2
+ model: string;
3
+ responseModelIds?: readonly string[] | undefined;
4
+ }
5
+ export declare function acceptsResponseModel(value: string, identity: ResponseModelIdentity): boolean;
6
+ interface PinnedProviderIdentity {
7
+ provider?: string | undefined;
8
+ providerName?: string | undefined;
9
+ }
10
+ export declare function unexpectedResponseProvider(envelopeProvider: string | null | undefined, pinned: PinnedProviderIdentity): boolean;
11
+ export {};
@@ -0,0 +1,23 @@
1
+ function routeName(value) {
2
+ return value.toLowerCase().replace(/[^a-z0-9]/gu, '');
3
+ }
4
+ export function acceptsResponseModel(value, identity) {
5
+ if (value === identity.model || identity.responseModelIds?.includes(value))
6
+ return true;
7
+ if (!value.startsWith(identity.model + '-'))
8
+ return false;
9
+ const suffix = value.slice(identity.model.length + 1);
10
+ if (!/^\d{4}-?\d{2}-?\d{2}$/u.test(suffix))
11
+ return false;
12
+ const digits = suffix.replaceAll('-', ''), date = `${digits.slice(0, 4)}-${digits.slice(4, 6)}-${digits.slice(6, 8)}`;
13
+ const parsed = new Date(date + 'T00:00:00.000Z');
14
+ return Number.isFinite(parsed.valueOf()) && parsed.toISOString().slice(0, 10) === date;
15
+ }
16
+ export function unexpectedResponseProvider(envelopeProvider, pinned) {
17
+ if (!envelopeProvider)
18
+ return false;
19
+ const expected = pinned.providerName ?? pinned.provider;
20
+ if (expected === undefined)
21
+ return false;
22
+ return routeName(envelopeProvider) !== routeName(expected);
23
+ }
@@ -1,5 +1,5 @@
1
1
  import { createVisionChat, visionOptionsSchema } from './vision-chat.js';
2
- import { validated } from './openrouter-common.js';
2
+ import { validated } from './decision-transport/errors.js';
3
3
  import { z } from 'zod';
4
4
  const schema = visionOptionsSchema
5
5
  .omit({ endpoint: true, headers: true })
@@ -1,7 +1,11 @@
1
1
  import { z } from 'zod';
2
2
  import { ClassifierError } from '../entities/errors.js';
3
3
  import { acceptsChoiceMass } from '../shared/choice-mass.js';
4
- import { active, validated, ownedError, invalid, preflightJson, requestSchema, validateImage, failure, readResponse, requestLimit, responseLimit, probability, key, } from './openrouter-common.js';
4
+ import { active, failure, invalid, ownedError, validated } from './decision-transport/errors.js';
5
+ import { validateImage } from './decision-transport/images.js';
6
+ import { key, preflightJson, probability, requestLimit, requestSchema, responseLimit, } from './decision-transport/request.js';
7
+ import { readResponse } from './decision-transport/response.js';
8
+ import { acceptsResponseModel, unexpectedResponseProvider, } from './decision-transport/route-identity.js';
5
9
  const endpoint = 'https://openrouter.ai/api/alpha/decisions';
6
10
  const responseSchema = z
7
11
  .object({
@@ -59,9 +63,6 @@ const optionsSchema = legacyOptionsSchema
59
63
  probabilityRoundingStep: z.literal(0.01).optional(),
60
64
  })
61
65
  .strict();
62
- function routeName(value) {
63
- return value.toLowerCase().replace(/[^a-z0-9]/gu, '');
64
- }
65
66
  function serialize(input, maxBytes, options) {
66
67
  const envelope = {
67
68
  model: options.model,
@@ -83,18 +84,6 @@ function serialize(input, maxBytes, options) {
83
84
  validateImage(part.image_url.url);
84
85
  return { request, body };
85
86
  }
86
- function acceptedModel(value, options) {
87
- if (value === options.model || options.responseModelIds?.includes(value))
88
- return true;
89
- if (!value.startsWith(options.model + '-'))
90
- return false;
91
- const suffix = value.slice(options.model.length + 1);
92
- if (!/^\d{4}-?\d{2}-?\d{2}$/u.test(suffix))
93
- return false;
94
- const digits = suffix.replaceAll('-', ''), date = `${digits.slice(0, 4)}-${digits.slice(4, 6)}-${digits.slice(6, 8)}`;
95
- const parsed = new Date(date + 'T00:00:00.000Z');
96
- return Number.isFinite(parsed.valueOf()) && parsed.toISOString().slice(0, 10) === date;
97
- }
98
87
  function result(raw, request, response, durationMs, options) {
99
88
  let json;
100
89
  try {
@@ -104,10 +93,9 @@ function result(raw, request, response, durationMs, options) {
104
93
  throw invalid('INVALID_RESPONSE', 'Provider returned invalid JSON');
105
94
  }
106
95
  const value = validated(responseSchema, json, 'INVALID_RESPONSE');
107
- if (value.model && !acceptedModel(value.model, options))
96
+ if (value.model && !acceptsResponseModel(value.model, options))
108
97
  throw invalid('INVALID_RESPONSE', 'Provider returned an unexpected model');
109
- if (value.provider &&
110
- routeName(value.provider) !== routeName(options.providerName ?? options.provider))
98
+ if (value.provider && unexpectedResponseProvider(value.provider, options))
111
99
  throw invalid('INVALID_RESPONSE', 'Provider returned an unexpected route');
112
100
  const questionIds = Object.keys(request.questions);
113
101
  if (Object.keys(value.answers).length !== questionIds.length)
@@ -1,13 +1,8 @@
1
1
  import sharp from 'sharp';
2
2
  import { z } from 'zod';
3
3
  import { limits, parse, pixelRectSchema } from '../entities/contracts.js';
4
+ import { containsRect } from '../entities/geometry.js';
4
5
  import { ClassifierError, assertActive } from '../entities/errors.js';
5
- function contains(outer, inner) {
6
- return (inner.left >= outer.left &&
7
- inner.top >= outer.top &&
8
- inner.left + inner.width <= outer.left + outer.width &&
9
- inner.top + inner.height <= outer.top + outer.height);
10
- }
11
6
  async function normalizeImage(bytes, signal) {
12
7
  assertActive(signal);
13
8
  const pipeline = sharp(bytes, { limitInputPixels: limits.imagePixels, failOn: 'error' });
@@ -40,7 +35,7 @@ async function normalizeImage(bytes, signal) {
40
35
  }
41
36
  async function cropImage(image, region, signal, scale = 1) {
42
37
  assertActive(signal);
43
- if (!contains({ left: 0, top: 0, width: image.width, height: image.height }, region))
38
+ if (!containsRect({ left: 0, top: 0, width: image.width, height: image.height }, region))
44
39
  throw new ClassifierError('INVALID_INPUT', 'ROI lies outside oriented source image');
45
40
  const pipeline = sharp(image.bytes, { limitInputPixels: limits.imagePixels })
46
41
  .extract(region)
@@ -198,7 +193,7 @@ export function createVisionRepresentation(raster) {
198
193
  content: pixelRectSchema,
199
194
  }),
200
195
  }), json, 'INVALID_RESPONSE');
201
- if (!contains({ left: 0, top: 0, ...transform.sourceImageTransform.output }, transform.sourceImageTransform.content))
196
+ if (!containsRect({ left: 0, top: 0, ...transform.sourceImageTransform.output }, transform.sourceImageTransform.content))
202
197
  throw new ClassifierError('INVALID_RESPONSE', 'Vision raster content lies outside output');
203
198
  metadata.views.push({
204
199
  id: view.id,
@@ -7,11 +7,11 @@ declare const optionsSchema: z.ZodObject<{
7
7
  }, {
8
8
  assetPath: string;
9
9
  }>;
10
- /** @experimental Options for the unqualified GrabCut segmentation factory. */
10
+ /** Options for the GrabCut segmentation factory (caller-supplied pinned assets). */
11
11
  export type GrabCutSegmenterOptions = z.input<typeof optionsSchema>;
12
12
  /**
13
13
  * Explicit pinned OpenCV GrabCut adapter. The supplied native asset is never downloaded.
14
- * @experimental Native segmentation adapter; unqualified for general mask or object accuracy.
14
+ * Structural validation does not establish semantic mask or object quality.
15
15
  */
16
16
  export declare function createGrabCutSegmenter(options: GrabCutSegmenterOptions): Promise<SegmentationPort>;
17
17
  export {};
@@ -6,20 +6,18 @@ import { NativeChildPool } from '../native-child.js';
6
6
  import { nativeErrorReplySchema, nativeSegmentationReplySchema } from '../native-protocol.js';
7
7
  import { verifyPinnedAsset } from '../verify-asset.js';
8
8
  import { probeBackend } from '../probe-backend.js';
9
+ import { grabcutOpenCvArtifact } from './model-asset.js';
9
10
  const optionsSchema = z.object({ assetPath: z.string().min(1).max(4096) }).strict();
10
11
  /**
11
12
  * Explicit pinned OpenCV GrabCut adapter. The supplied native asset is never downloaded.
12
- * @experimental Native segmentation adapter; unqualified for general mask or object accuracy.
13
+ * Structural validation does not establish semantic mask or object quality.
13
14
  */
14
15
  export async function createGrabCutSegmenter(options) {
15
16
  const checked = optionsSchema.safeParse(options);
16
17
  if (!checked.success)
17
18
  throw new ClassifierError('INVALID_INPUT', 'Invalid GrabCut options');
18
19
  const parsed = checked.data;
19
- await verifyPinnedAsset(parsed.assetPath, {
20
- bytes: 10_964_323,
21
- sha256: '63366510248adf3a7eddf3e793dd825404efb7df3749f4d6f8557c7fa4ca8aa0',
22
- }, 'OpenCV.js');
20
+ await verifyPinnedAsset(parsed.assetPath, grabcutOpenCvArtifact, 'OpenCV.js');
23
21
  const workerUrl = new URL(import.meta.url.endsWith('.ts') ? '../native-worker.ts' : '../native-worker.js', import.meta.url);
24
22
  await probeBackend(workerUrl, 'grabcut', { assetPath: parsed.assetPath });
25
23
  const pool = new NativeChildPool({
@@ -0,0 +1,49 @@
1
+ import type { NativeSegmentationRequest } from '../native-protocol.js';
2
+ export declare function getBytes(mat: {
3
+ data?: unknown;
4
+ }, length: number): Uint8Array;
5
+ export declare function initializeGrabCutMask(input: {
6
+ flags: Uint8Array;
7
+ width: number;
8
+ height: number;
9
+ seedRegion: {
10
+ left: number;
11
+ top: number;
12
+ width: number;
13
+ height: number;
14
+ };
15
+ wholeRegion: {
16
+ left: number;
17
+ top: number;
18
+ width: number;
19
+ height: number;
20
+ };
21
+ roi: {
22
+ left: number;
23
+ top: number;
24
+ width: number;
25
+ height: number;
26
+ };
27
+ prompt: 'whole-region' | 'seed-region';
28
+ labels: {
29
+ background: number;
30
+ foreground: number;
31
+ probableBackground: number;
32
+ probableForeground: number;
33
+ };
34
+ }): void;
35
+ /** Run native GrabCut, then reacquire Mat.data before copying its post-call flags. */
36
+ export declare function runGrabCutAndCopyMask(mat: {
37
+ data?: unknown;
38
+ }, width: number, height: number, roi: {
39
+ left: number;
40
+ top: number;
41
+ width: number;
42
+ height: number;
43
+ }, runNative: () => void, labels: {
44
+ foreground: number;
45
+ probableForeground: number;
46
+ }): Uint8Array;
47
+ export declare function seededComponents(mask: Uint8Array, width: number, height: number, input: NativeSegmentationRequest & {
48
+ kind: 'grabcut';
49
+ }): Uint8Array;
@@ -0,0 +1,89 @@
1
+ import { z } from 'zod';
2
+ export function getBytes(mat, length) {
3
+ const bytes = z.instanceof(Uint8Array).parse(mat.data);
4
+ if (bytes.length !== length)
5
+ throw new Error('OpenCV matrix dimensions do not match');
6
+ return bytes;
7
+ }
8
+ export function initializeGrabCutMask(input) {
9
+ if (input.flags.length !== input.width * input.height)
10
+ throw new Error('GrabCut mask dimensions do not match source');
11
+ input.flags.fill(input.labels.background);
12
+ if (input.prompt === 'whole-region') {
13
+ for (let y = input.wholeRegion.top; y < input.wholeRegion.top + input.wholeRegion.height; y++)
14
+ for (let x = input.wholeRegion.left; x < input.wholeRegion.left + input.wholeRegion.width; x++)
15
+ input.flags[y * input.width + x] = input.labels.probableForeground;
16
+ }
17
+ else {
18
+ for (let y = input.roi.top; y < input.roi.top + input.roi.height; y++)
19
+ for (let x = input.roi.left; x < input.roi.left + input.roi.width; x++)
20
+ input.flags[y * input.width + x] = input.labels.probableBackground;
21
+ }
22
+ const seedLeft = Math.max(input.seedRegion.left, input.roi.left);
23
+ const seedTop = Math.max(input.seedRegion.top, input.roi.top);
24
+ const seedRight = Math.min(input.seedRegion.left + input.seedRegion.width, input.roi.left + input.roi.width);
25
+ const seedBottom = Math.min(input.seedRegion.top + input.seedRegion.height, input.roi.top + input.roi.height);
26
+ if (seedLeft >= seedRight || seedTop >= seedBottom)
27
+ throw new Error('GrabCut seed region does not intersect hard ROI');
28
+ for (let y = seedTop; y < seedBottom; y++)
29
+ for (let x = seedLeft; x < seedRight; x++)
30
+ input.flags[y * input.width + x] = input.labels.foreground;
31
+ }
32
+ /** Run native GrabCut, then reacquire Mat.data before copying its post-call flags. */
33
+ export function runGrabCutAndCopyMask(mat, width, height, roi, runNative, labels) {
34
+ runNative();
35
+ const flags = getBytes(mat, width * height);
36
+ const output = new Uint8Array(width * height);
37
+ for (let y = roi.top; y < roi.top + roi.height; y++) {
38
+ for (let x = roi.left; x < roi.left + roi.width; x++) {
39
+ const at = y * width + x;
40
+ const flag = flags[at];
41
+ if (flag === labels.foreground || flag === labels.probableForeground)
42
+ output[at] = 1;
43
+ }
44
+ }
45
+ return output;
46
+ }
47
+ export function seededComponents(mask, width, height, input) {
48
+ const visited = new Uint8Array(mask.length);
49
+ const output = new Uint8Array(mask.length);
50
+ const stack = [];
51
+ for (let start = 0; start < mask.length; start++) {
52
+ if (mask[start] !== 1 || visited[start] === 1)
53
+ continue;
54
+ stack.push(start);
55
+ visited[start] = 1;
56
+ const component = [];
57
+ let hitsSeed = false;
58
+ while (stack.length) {
59
+ const at = stack.pop();
60
+ if (at === undefined)
61
+ continue;
62
+ component.push(at);
63
+ const x = at % width;
64
+ const y = Math.floor(at / width);
65
+ if (x >= input.seedRegion.left &&
66
+ y >= input.seedRegion.top &&
67
+ x < input.seedRegion.left + input.seedRegion.width &&
68
+ y < input.seedRegion.top + input.seedRegion.height)
69
+ hitsSeed = true;
70
+ const neighbors = [at - 1, at + 1, at - width, at + width];
71
+ for (const neighbor of neighbors) {
72
+ if (neighbor < 0 ||
73
+ neighbor >= mask.length ||
74
+ visited[neighbor] === 1 ||
75
+ mask[neighbor] !== 1)
76
+ continue;
77
+ const nx = neighbor % width;
78
+ if (Math.abs(nx - x) + Math.abs(Math.floor(neighbor / width) - y) !== 1)
79
+ continue;
80
+ visited[neighbor] = 1;
81
+ stack.push(neighbor);
82
+ }
83
+ }
84
+ if (hitsSeed)
85
+ for (const index of component)
86
+ output[index] = 1;
87
+ }
88
+ return output;
89
+ }
@@ -0,0 +1,4 @@
1
+ export declare const grabcutOpenCvArtifact: Readonly<{
2
+ readonly bytes: 10964323;
3
+ readonly sha256: "63366510248adf3a7eddf3e793dd825404efb7df3749f4d6f8557c7fa4ca8aa0";
4
+ }>;
@@ -0,0 +1,4 @@
1
+ export const grabcutOpenCvArtifact = Object.freeze({
2
+ bytes: 10_964_323,
3
+ sha256: '63366510248adf3a7eddf3e793dd825404efb7df3749f4d6f8557c7fa4ca8aa0',
4
+ });