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,77 +1,35 @@
1
- import { z } from 'zod';
2
1
  import { classifySchema, locateSchema, parse } from '../entities/contracts.js';
3
2
  import { ClassifierError } from '../entities/errors.js';
4
3
  import { createSharpRaster } from '../infrastructure/raster.js';
5
4
  import { classifyInput } from '../processes/classify.js';
6
5
  import { locateInput } from '../processes/locate.js';
7
- import { OperationGate } from '../shared/gate.js';
8
- import { OperationScope } from '../shared/scope.js';
9
- import { InputBudget } from '../shared/input-budget.js';
10
6
  import { wholeObjectSchema } from '../entities/whole-object.js';
11
- import { locateWholeObjectInput } from '../processes/whole-object.js';
12
- import { preparedTextSchema, representationLimits } from '../entities/representation.js';
13
7
  import { extractSchema } from '../entities/segmentation.js';
14
- import { extractObject } from '../processes/extract.js';
15
- import { extractFromRegion } from '../processes/extract-from-region.js';
16
8
  import { createSharpCutoutRaster } from '../infrastructure/segmentation/cutout-raster.js';
17
- import { locateFromProposals } from '../processes/proposal-location.js';
18
9
  import { createSharpObjectProposalPresentation } from '../infrastructure/segmentation/proposal-presentation.js';
19
10
  import { createSegmentationBoxCheck } from '../features/segmentation/box-check.js';
20
11
  import { createSharpSegmentationBoxPresentation } from '../infrastructure/segmentation/box-presentation.js';
21
- const configSchema = z.object({
22
- timeoutMs: z.number().int().min(1).max(120000).default(45000),
23
- concurrency: z.number().int().min(1).max(4).default(2),
24
- maxQueue: z.number().int().min(0).max(32).default(16),
25
- maxInputBytes: z
26
- .number()
27
- .int()
28
- .min(1)
29
- .max(256 * 1024 * 1024)
30
- .default(64 * 1024 * 1024),
31
- });
32
- function signalOf(value) {
33
- if (value === undefined)
34
- return undefined;
35
- if (!(value instanceof AbortSignal))
36
- throw new ClassifierError('INVALID_INPUT', 'signal must be an AbortSignal');
37
- return value;
12
+ import { validateClassifierOptions } from './classifier-config.js';
13
+ import { createClassifierRuntime, parseOperationSignal } from './classifier-runtime.js';
14
+ import { createClassifierSpatial } from './classifier-spatial.js';
15
+ function prepareResearchOperationInput(rest, research) {
16
+ if (!research || rest.representation === undefined)
17
+ return { rest, port: undefined };
18
+ if (!research.acceptsRepresentation(rest.representation))
19
+ return { rest, port: undefined };
20
+ return {
21
+ rest: { ...rest, representation: { type: 'vision' } },
22
+ port: research.representationPort,
23
+ };
38
24
  }
39
25
  export function createClassifier(options) {
40
26
  return composeClassifier(options);
41
27
  }
42
- /** Internal benchmark composition. No research codec is exported by the npm entry points. */
43
- export function createResearchClassifier(options, experimental, boundaryView, intervalPolicy) {
44
- return composeClassifier(options, experimental, boundaryView, intervalPolicy);
45
- }
46
- function composeClassifier(options, experimental, boundaryView, intervalPolicy) {
47
- if (!options || typeof options.decision?.decide !== 'function')
48
- throw new ClassifierError('INVALID_INPUT', 'A DecisionPort is required');
49
- const segmenter = options.segmentation;
50
- const objectProposals = options.objectProposals;
51
- if (segmenter !== undefined &&
52
- (!segmenter ||
53
- typeof segmenter.segment !== 'function' ||
54
- (segmenter.expand !== undefined && typeof segmenter.expand !== 'function') ||
55
- typeof segmenter.settle !== 'function' ||
56
- typeof segmenter.dispose !== 'function'))
57
- throw new ClassifierError('INVALID_INPUT', 'segmentation must implement SegmentationPort');
58
- if (options.segmentationBoxCheck !== undefined &&
59
- (!options.segmentationBoxCheck || typeof options.segmentationBoxCheck.check !== 'function'))
60
- throw new ClassifierError('INVALID_INPUT', 'segmentationBoxCheck must implement its check port');
61
- if (objectProposals !== undefined &&
62
- (!objectProposals ||
63
- typeof objectProposals.propose !== 'function' ||
64
- typeof objectProposals.settle !== 'function' ||
65
- typeof objectProposals.dispose !== 'function'))
66
- throw new ClassifierError('INVALID_INPUT', 'objectProposals must implement ObjectProposalPort');
67
- const config = parse(configSchema, {
68
- timeoutMs: options.timeoutMs,
69
- concurrency: options.concurrency,
70
- maxQueue: options.maxQueue,
71
- maxInputBytes: options.maxInputBytes,
72
- });
73
- const gate = new OperationGate(config.concurrency, config.maxQueue);
74
- const inputBudget = new InputBudget(config.maxInputBytes);
28
+ /** Internal composition seam for benchmark helpers; not re-exported from public entry points. */
29
+ export function composeClassifier(options, research, boundaryView, intervalPolicy) {
30
+ const { config, ports } = validateClassifierOptions(options);
31
+ const { segmenter, objectProposals } = ports;
32
+ const runtime = createClassifierRuntime(config);
75
33
  const raster = createSharpRaster();
76
34
  const cutoutRaster = createSharpCutoutRaster();
77
35
  const boxCheck = segmenter
@@ -91,146 +49,46 @@ function composeClassifier(options, experimental, boundaryView, intervalPolicy)
91
49
  presentation: createSharpObjectProposalPresentation(),
92
50
  }
93
51
  : undefined;
94
- const owned = new Map();
95
- let disposed = false;
96
- const textRepresentation = experimental
97
- ? {
98
- async prepare(input) {
99
- const result = parse(preparedTextSchema, await experimental.prepare(input), 'INVALID_RESPONSE');
100
- const bytes = result.state.reduce((sum, part) => sum + Buffer.byteLength(part, 'utf8'), 0);
101
- if (bytes > representationLimits.totalBytes || bytes !== result.metadata.bytes)
102
- throw new ClassifierError('INVALID_RESPONSE', 'Representation byte accounting is inconsistent');
103
- return {
104
- state: result.state,
105
- metadata: {
106
- ...result.metadata,
107
- views: result.metadata.views.map(({ content, ...view }) => ({
108
- ...view,
109
- ...(content === undefined ? {} : { content }),
110
- })),
111
- },
112
- };
113
- },
114
- }
115
- : undefined;
116
- function representationFor(value) {
117
- if (value?.type !== 'experimental-text')
118
- return undefined;
119
- if (!textRepresentation)
120
- throw new ClassifierError('UNSUPPORTED', 'No text-image profile has passed release qualification yet');
121
- return textRepresentation;
122
- }
123
- async function locateWhole(input, signal, representation) {
124
- if (segmenter) {
125
- const result = await extractRegion(input, signal, representation);
126
- if (result.status === 'extracted')
127
- return result.location;
128
- if (result.status === 'unknown' && result.reason === 'segmentation-uncertain')
129
- return {
130
- status: 'unknown',
131
- reason: 'segmentation-uncertain',
132
- metadata: 'metadata' in result ? result.metadata : result.location.metadata,
133
- ...(result.verification ? { verification: result.verification } : {}),
134
- };
135
- return result;
136
- }
137
- if (proposalDependencies) {
138
- if (representation)
139
- throw new ClassifierError('UNSUPPORTED', 'Mask proposal selection requires vision input');
140
- return (await locateFromProposals(input, proposalDependencies, signal)).location;
141
- }
142
- return locateWholeObjectInput(input, options.decision, raster, signal, representation, boundaryView, intervalPolicy);
143
- }
144
- function extractRegion(input, signal, representation) {
145
- if (!segmenter || !boxCheck)
146
- throw new ClassifierError('UNSUPPORTED', 'Region extraction requires a SegmentationPort');
147
- if (representation)
148
- throw new ClassifierError('UNSUPPORTED', 'Checked segmentation requires vision input');
149
- return extractFromRegion({ ...input, signal }, {
150
- raster,
151
- image: cutoutRaster,
152
- segmenter,
153
- boxCheck,
154
- locateRegion: (source, { signal: operationSignal, ...request }) => locateInput({ ...request, image: source.bytes }, options.decision, raster, operationSignal, undefined, representation),
155
- });
156
- }
157
- async function run(signal, task) {
158
- if (disposed)
159
- throw new ClassifierError('DISPOSED', 'Classifier has been disposed');
160
- const scope = new OperationScope(config.timeoutMs, signal);
161
- const job = (async () => {
162
- let acquired = false;
163
- try {
164
- await gate.acquire(scope.signal);
165
- acquired = true;
166
- scope.guard();
167
- const result = await task(scope.signal);
168
- scope.guard();
169
- return result;
170
- }
171
- catch (error) {
172
- scope.guard();
173
- throw error;
174
- }
175
- finally {
176
- if (acquired)
177
- gate.release();
178
- scope.dispose();
179
- owned.delete(scope);
180
- }
181
- })();
182
- owned.set(scope, job);
183
- return job;
184
- }
52
+ const spatial = createClassifierSpatial({
53
+ getDecision: () => options.decision,
54
+ raster,
55
+ cutoutRaster,
56
+ segmenter,
57
+ boxCheck,
58
+ proposalDependencies,
59
+ boundaryView,
60
+ intervalPolicy,
61
+ });
185
62
  async function locate(input) {
186
- if (disposed)
187
- throw new ClassifierError('DISPOSED', 'Classifier has been disposed');
188
- gate.assertAvailable();
63
+ runtime.assertAcceptingOperations();
189
64
  if (!input || typeof input !== 'object')
190
65
  throw new ClassifierError('INVALID_INPUT', 'Expected locate input object');
191
66
  const { signal, ...rest } = input;
192
- const parent = signalOf(signal);
193
- if (rest.mode === 'whole-object') {
194
- const parsed = parse(wholeObjectSchema, rest);
195
- const representation = representationFor(parsed.representation);
196
- const release = inputBudget.reserve(parsed.image.byteLength);
197
- try {
198
- const snapshot = { ...parsed, image: new Uint8Array(parsed.image) };
199
- return await run(parent, (s) => locateWhole(snapshot, s, representation));
200
- }
201
- finally {
202
- release();
203
- }
204
- }
205
- const parsed = parse(locateSchema, rest);
206
- const representation = representationFor(parsed.representation);
207
- const release = inputBudget.reserve(parsed.image.byteLength);
208
- try {
209
- const snapshot = { ...parsed, image: new Uint8Array(parsed.image) };
210
- return await run(parent, (s) => locateInput(snapshot, options.decision, raster, s, options.refinement, representation));
211
- }
212
- finally {
213
- release();
67
+ const parent = parseOperationSignal(signal);
68
+ const { rest: rewritten, port } = prepareResearchOperationInput(rest, research);
69
+ if (rewritten.mode === 'whole-object') {
70
+ const parsed = parse(wholeObjectSchema, rewritten);
71
+ return runtime.withOwnedInput(parsed.image.byteLength, parent, () => ({ ...parsed, image: new Uint8Array(parsed.image) }), (snapshot, s) => spatial.locateWhole(snapshot, s, port));
214
72
  }
73
+ const parsed = parse(locateSchema, rewritten);
74
+ return runtime.withOwnedInput(parsed.image.byteLength, parent, () => ({ ...parsed, image: new Uint8Array(parsed.image) }), (snapshot, s) => locateInput(snapshot, options.decision, raster, s, options.refinement, port));
215
75
  }
216
76
  return {
217
77
  async classify(input) {
218
- if (disposed)
219
- throw new ClassifierError('DISPOSED', 'Classifier has been disposed');
220
- gate.assertAvailable();
78
+ runtime.assertAcceptingOperations();
221
79
  if (!input || typeof input !== 'object')
222
80
  throw new ClassifierError('INVALID_INPUT', 'Expected classify input object');
223
81
  const { signal, ...rest } = input;
224
- const parent = signalOf(signal);
225
- const parsed = parse(classifySchema, rest);
82
+ const parent = parseOperationSignal(signal);
83
+ const { rest: rewritten, port } = prepareResearchOperationInput(rest, research);
84
+ const parsed = parse(classifySchema, rewritten);
226
85
  const hasImage = typeof parsed.input !== 'string' && parsed.input.image !== undefined;
227
- const representation = hasImage ? representationFor(parsed.representation) : undefined;
86
+ const representation = hasImage ? port : undefined;
228
87
  const byteCount = typeof parsed.input === 'string'
229
88
  ? Buffer.byteLength(parsed.input)
230
89
  : (parsed.input.image?.byteLength ?? 0) + Buffer.byteLength(parsed.input.text ?? '');
231
- const release = inputBudget.reserve(byteCount);
232
- try {
233
- const media = typeof parsed.input === 'string'
90
+ return runtime.withOwnedInput(byteCount, parent, () => {
91
+ return typeof parsed.input === 'string'
234
92
  ? parsed.input
235
93
  : {
236
94
  ...parsed.input,
@@ -238,75 +96,25 @@ function composeClassifier(options, experimental, boundaryView, intervalPolicy)
238
96
  ? {}
239
97
  : { image: new Uint8Array(parsed.input.image) }),
240
98
  };
241
- return await run(parent, (s) => classifyInput({ ...parsed, input: media }, options.decision, raster, s, representation));
242
- }
243
- finally {
244
- release();
245
- }
99
+ }, (media, s) => classifyInput({ ...parsed, input: media }, options.decision, raster, s, representation));
246
100
  },
247
101
  locate,
248
102
  async extract(input) {
249
- if (disposed)
250
- throw new ClassifierError('DISPOSED', 'Classifier has been disposed');
251
- gate.assertAvailable();
103
+ runtime.assertAcceptingOperations();
252
104
  if (!input || typeof input !== 'object')
253
105
  throw new ClassifierError('INVALID_INPUT', 'Expected extract input object');
254
106
  const { signal, ...rest } = input;
255
- const parent = signalOf(signal);
256
- const parsed = parse(extractSchema, rest);
107
+ const parent = parseOperationSignal(signal);
108
+ const { rest: rewritten, port } = prepareResearchOperationInput(rest, research);
109
+ const parsed = parse(extractSchema, rewritten);
257
110
  if (!segmenter && !proposalDependencies)
258
111
  throw new ClassifierError('UNSUPPORTED', 'extract requires an explicit SegmentationPort or ObjectProposalPort');
259
- const representation = representationFor(parsed.representation);
260
- const release = inputBudget.reserve(parsed.image.byteLength);
261
- try {
262
- const snapshot = { ...parsed, image: new Uint8Array(parsed.image) };
263
- return await run(parent, (s) => {
264
- if (segmenter)
265
- return extractRegion(snapshot, s, representation);
266
- let prepared;
267
- // Per-operation ownership: no global selected-mask cache or second native inference.
268
- const selectedMask = {
269
- async segment() {
270
- if (!prepared?.mask || !prepared.segmentation)
271
- throw new ClassifierError('INVALID_RESPONSE', 'Proposal selection omitted its mask');
272
- return {
273
- status: 'segmented',
274
- mask: prepared.mask,
275
- metadata: prepared.segmentation,
276
- };
277
- },
278
- async settle() { },
279
- async dispose() { },
280
- };
281
- return extractObject({ ...snapshot, mode: 'whole-object', signal: s }, {
282
- raster,
283
- image: cutoutRaster,
284
- segmenter: selectedMask,
285
- locateWhole: async (source, { signal: requestSignal, ...request }) => {
286
- const wholeInput = {
287
- ...request,
288
- mode: 'whole-object',
289
- image: source.bytes,
290
- };
291
- if (!proposalDependencies)
292
- throw new ClassifierError('UNSUPPORTED', 'ObjectProposalPort is required');
293
- if (representation)
294
- throw new ClassifierError('UNSUPPORTED', 'Mask proposal selection requires vision input');
295
- prepared = await locateFromProposals(wholeInput, proposalDependencies, requestSignal);
296
- return prepared.location;
297
- },
298
- });
299
- });
300
- }
301
- finally {
302
- release();
303
- }
304
- },
305
- async dispose() {
306
- disposed = true;
307
- for (const scope of owned.keys())
308
- scope.abort();
309
- await Promise.allSettled(owned.values());
112
+ return runtime.withOwnedInput(parsed.image.byteLength, parent, () => ({ ...parsed, image: new Uint8Array(parsed.image) }), (snapshot, s) => {
113
+ if (segmenter)
114
+ return spatial.extractRegion(snapshot, s, port);
115
+ return spatial.extractWithProposals(snapshot, s, port);
116
+ });
310
117
  },
118
+ dispose: () => runtime.dispose(),
311
119
  };
312
120
  }
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { createMobileSamSegmenter, createGrabCutSegmenter } from './segmentation
8
8
  async function main() {
9
9
  const args = parseCliArguments(process.argv.slice(2));
10
10
  if (args.command === 'help') {
11
- console.log('zorya-classifier classify-text <text> <classes.json>\nzorya-classifier classify-image <image> <classes.json>\nzorya-classifier locate <image> <target> [--mode region|whole-object] (experimental, unqualified spatial search)\nzorya-classifier extract <image> <target> --output <prefix> --backend mobile-sam --encoder <path> --decoder <path> [--provider cpu|dml] (experimental extraction)\nzorya-classifier extract <image> <target> --output <prefix> --backend grabcut --opencv <path> (experimental extraction)\nSet ZORYA_MODEL. Default ZORYA_MODE=decisions; use chat for ordinary vision models/custom chat endpoints. OpenRouter: OPENROUTER_API_KEY + ZORYA_PROVIDER. Direct: ZORYA_ENDPOINT + optional ZORYA_API_KEY. Optional ZORYA_REASONING_EFFORT and ZORYA_RESPONSE_FORMAT. classify is supported; locate/extract/object backends are experimental and not qualified for general use. One classify request; up to three region or six whole-object requests. Extraction additionally runs the explicit local backend. Output files are never overwritten; native assets are never downloaded automatically.');
11
+ console.log('zorya-classifier classify-text <text> <classes.json>\nzorya-classifier classify-image <image> <classes.json>\nzorya-classifier locate <image> <target> [--mode region|whole-object] (spatial search; grid cells are not whole-object boxes)\nzorya-classifier extract <image> <target> --output <prefix> --backend mobile-sam --encoder <path> --decoder <path> [--provider cpu|dml] (extraction with caller-owned backend)\nzorya-classifier extract <image> <target> --output <prefix> --backend grabcut --opencv <path> (extraction with caller-owned backend)\nSet ZORYA_MODEL. Default ZORYA_MODE=decisions; use chat for ordinary vision models/custom chat endpoints. OpenRouter: OPENROUTER_API_KEY + ZORYA_PROVIDER. Direct: ZORYA_ENDPOINT + optional ZORYA_API_KEY. Optional ZORYA_REASONING_EFFORT and ZORYA_RESPONSE_FORMAT. classify is supported; locate/extract/object backends are available—model, provider and native assets remain caller-supplied. One classify request; up to three region or six whole-object requests. Extraction additionally runs the explicit local backend. Output files are never overwritten; native assets are never downloaded automatically.');
12
12
  return;
13
13
  }
14
14
  const { command, value, target } = args;
@@ -0,0 +1,83 @@
1
+ import { z } from 'zod';
2
+ export declare const classSchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ description: z.ZodString;
5
+ }, "strict", z.ZodTypeAny, {
6
+ id: string;
7
+ description: string;
8
+ }, {
9
+ id: string;
10
+ description: string;
11
+ }>;
12
+ export type ClassDefinition = z.infer<typeof classSchema>;
13
+ export declare const classifySchema: z.ZodObject<{
14
+ input: z.ZodUnion<[z.ZodString, z.ZodEffects<z.ZodObject<{
15
+ text: z.ZodOptional<z.ZodString>;
16
+ image: z.ZodOptional<z.ZodEffects<z.ZodType<Uint8Array<ArrayBufferLike>, z.ZodTypeDef, Uint8Array<ArrayBufferLike>>, Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>>>;
17
+ }, "strict", z.ZodTypeAny, {
18
+ text?: string | undefined;
19
+ image?: Uint8Array<ArrayBufferLike> | undefined;
20
+ }, {
21
+ text?: string | undefined;
22
+ image?: Uint8Array<ArrayBufferLike> | undefined;
23
+ }>, {
24
+ text?: string | undefined;
25
+ image?: Uint8Array<ArrayBufferLike> | undefined;
26
+ }, {
27
+ text?: string | undefined;
28
+ image?: Uint8Array<ArrayBufferLike> | undefined;
29
+ }>]>;
30
+ classes: z.ZodEffects<z.ZodArray<z.ZodObject<{
31
+ id: z.ZodString;
32
+ description: z.ZodString;
33
+ }, "strict", z.ZodTypeAny, {
34
+ id: string;
35
+ description: string;
36
+ }, {
37
+ id: string;
38
+ description: string;
39
+ }>, "many">, {
40
+ id: string;
41
+ description: string;
42
+ }[], {
43
+ id: string;
44
+ description: string;
45
+ }[]>;
46
+ instructions: z.ZodOptional<z.ZodString>;
47
+ representation: z.ZodOptional<z.ZodObject<{
48
+ type: z.ZodLiteral<"vision">;
49
+ }, "strict", z.ZodTypeAny, {
50
+ type: "vision";
51
+ }, {
52
+ type: "vision";
53
+ }>>;
54
+ }, "strict", z.ZodTypeAny, {
55
+ input: string | {
56
+ text?: string | undefined;
57
+ image?: Uint8Array<ArrayBufferLike> | undefined;
58
+ };
59
+ classes: {
60
+ id: string;
61
+ description: string;
62
+ }[];
63
+ instructions?: string | undefined;
64
+ representation?: {
65
+ type: "vision";
66
+ } | undefined;
67
+ }, {
68
+ input: string | {
69
+ text?: string | undefined;
70
+ image?: Uint8Array<ArrayBufferLike> | undefined;
71
+ };
72
+ classes: {
73
+ id: string;
74
+ description: string;
75
+ }[];
76
+ instructions?: string | undefined;
77
+ representation?: {
78
+ type: "vision";
79
+ } | undefined;
80
+ }>;
81
+ export type ClassifyInput = z.input<typeof classifySchema> & {
82
+ signal?: AbortSignal;
83
+ };
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ import { imageSchema, limits, representationSchema } from './image.js';
3
+ export const classSchema = z
4
+ .object({
5
+ id: z
6
+ .string()
7
+ .min(1)
8
+ .max(128)
9
+ .regex(/^[a-zA-Z][a-zA-Z0-9_-]*$/),
10
+ description: z.string().trim().min(1).max(300),
11
+ })
12
+ .strict();
13
+ const mediaInputSchema = z
14
+ .object({ text: z.string().trim().min(1).max(32000).optional(), image: imageSchema.optional() })
15
+ .strict()
16
+ .refine((v) => v.text !== undefined || v.image !== undefined, 'Provide text or image');
17
+ export const classifySchema = z
18
+ .object({
19
+ input: z.union([z.string().trim().min(1).max(32000), mediaInputSchema]),
20
+ classes: z
21
+ .array(classSchema)
22
+ .min(2)
23
+ .max(limits.maxClasses)
24
+ .refine((v) => new Set(v.map((c) => c.id)).size === v.length, 'Class IDs must be unique'),
25
+ instructions: z.string().trim().min(1).max(4000).optional(),
26
+ representation: representationSchema.optional(),
27
+ })
28
+ .strict();