zorya-classifier 0.0.0-stage → 0.2.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.
- package/LICENSE +21 -0
- package/NOTICE.md +13 -0
- package/README.md +139 -2
- package/dist/app/cli-arguments.d.ts +40 -0
- package/dist/app/cli-arguments.js +67 -0
- package/dist/app/cli-route.d.ts +3 -0
- package/dist/app/cli-route.js +40 -0
- package/dist/app/create-classifier.d.ts +33 -0
- package/dist/app/create-classifier.js +312 -0
- package/dist/app/extraction-files.d.ts +9 -0
- package/dist/app/extraction-files.js +28 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +77 -0
- package/dist/entities/boundary-view.d.ts +29 -0
- package/dist/entities/boundary-view.js +1 -0
- package/dist/entities/contracts.d.ts +471 -0
- package/dist/entities/contracts.js +97 -0
- package/dist/entities/cutout-raster.d.ts +7 -0
- package/dist/entities/cutout-raster.js +1 -0
- package/dist/entities/errors.d.ts +7 -0
- package/dist/entities/errors.js +16 -0
- package/dist/entities/object-proposals.d.ts +53 -0
- package/dist/entities/object-proposals.js +8 -0
- package/dist/entities/representation.d.ts +225 -0
- package/dist/entities/representation.js +33 -0
- package/dist/entities/segmentation-metadata.d.ts +11 -0
- package/dist/entities/segmentation-metadata.js +1 -0
- package/dist/entities/segmentation-verification.d.ts +30 -0
- package/dist/entities/segmentation-verification.js +1 -0
- package/dist/entities/segmentation.d.ts +159 -0
- package/dist/entities/segmentation.js +5 -0
- package/dist/entities/whole-object.d.ts +128 -0
- package/dist/entities/whole-object.js +7 -0
- package/dist/features/choice-beam-layout.d.ts +18 -0
- package/dist/features/choice-beam-layout.js +136 -0
- package/dist/features/choice.d.ts +4 -0
- package/dist/features/choice.js +83 -0
- package/dist/features/prompts.d.ts +8 -0
- package/dist/features/prompts.js +56 -0
- package/dist/features/segmentation/box-check.d.ts +10 -0
- package/dist/features/segmentation/box-check.js +299 -0
- package/dist/features/segmentation/mask.d.ts +6 -0
- package/dist/features/segmentation/mask.js +89 -0
- package/dist/features/segmentation/packed-mask.d.ts +5 -0
- package/dist/features/segmentation/packed-mask.js +53 -0
- package/dist/features/uniform-grid.d.ts +3 -0
- package/dist/features/uniform-grid.js +35 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +2 -0
- package/dist/infrastructure/boundary-view.d.ts +2 -0
- package/dist/infrastructure/boundary-view.js +263 -0
- package/dist/infrastructure/openrouter-common.d.ts +130 -0
- package/dist/infrastructure/openrouter-common.js +317 -0
- package/dist/infrastructure/openrouter-vision.d.ts +11 -0
- package/dist/infrastructure/openrouter-vision.js +17 -0
- package/dist/infrastructure/openrouter.d.ts +19 -0
- package/dist/infrastructure/openrouter.js +207 -0
- package/dist/infrastructure/raster.d.ts +11 -0
- package/dist/infrastructure/raster.js +216 -0
- package/dist/infrastructure/segmentation/box-presentation.d.ts +2 -0
- package/dist/infrastructure/segmentation/box-presentation.js +136 -0
- package/dist/infrastructure/segmentation/cutout-raster.d.ts +3 -0
- package/dist/infrastructure/segmentation/cutout-raster.js +79 -0
- package/dist/infrastructure/segmentation/grabcut/index.d.ts +13 -0
- package/dist/infrastructure/segmentation/grabcut/index.js +118 -0
- package/dist/infrastructure/segmentation/grabcut/worker-inference.d.ts +47 -0
- package/dist/infrastructure/segmentation/grabcut/worker-inference.js +260 -0
- package/dist/infrastructure/segmentation/index.d.ts +2 -0
- package/dist/infrastructure/segmentation/index.js +2 -0
- package/dist/infrastructure/segmentation/mobile-sam/index.d.ts +58 -0
- package/dist/infrastructure/segmentation/mobile-sam/index.js +472 -0
- package/dist/infrastructure/segmentation/mobile-sam/worker-inference.d.ts +149 -0
- package/dist/infrastructure/segmentation/mobile-sam/worker-inference.js +845 -0
- package/dist/infrastructure/segmentation/native-child.d.ts +23 -0
- package/dist/infrastructure/segmentation/native-child.js +297 -0
- package/dist/infrastructure/segmentation/native-protocol.d.ts +923 -0
- package/dist/infrastructure/segmentation/native-protocol.js +242 -0
- package/dist/infrastructure/segmentation/native-worker.d.ts +1 -0
- package/dist/infrastructure/segmentation/native-worker.js +93 -0
- package/dist/infrastructure/segmentation/probe-backend.d.ts +1 -0
- package/dist/infrastructure/segmentation/probe-backend.js +31 -0
- package/dist/infrastructure/segmentation/proposal-presentation.d.ts +3 -0
- package/dist/infrastructure/segmentation/proposal-presentation.js +371 -0
- package/dist/infrastructure/segmentation/verify-asset.d.ts +4 -0
- package/dist/infrastructure/segmentation/verify-asset.js +32 -0
- package/dist/infrastructure/text-representation.d.ts +23 -0
- package/dist/infrastructure/text-representation.js +226 -0
- package/dist/infrastructure/vision-chat.d.ts +64 -0
- package/dist/infrastructure/vision-chat.js +318 -0
- package/dist/openai-compatible.d.ts +8 -0
- package/dist/openai-compatible.js +11 -0
- package/dist/openrouter.d.ts +4 -0
- package/dist/openrouter.js +2 -0
- package/dist/processes/boundary-state.d.ts +12 -0
- package/dist/processes/boundary-state.js +196 -0
- package/dist/processes/classify.d.ts +3 -0
- package/dist/processes/classify.js +39 -0
- package/dist/processes/extract-from-region.d.ts +19 -0
- package/dist/processes/extract-from-region.js +415 -0
- package/dist/processes/extract.d.ts +25 -0
- package/dist/processes/extract.js +223 -0
- package/dist/processes/locate.d.ts +3 -0
- package/dist/processes/locate.js +274 -0
- package/dist/processes/proposal-location.d.ts +19 -0
- package/dist/processes/proposal-location.js +396 -0
- package/dist/processes/whole-object.d.ts +7 -0
- package/dist/processes/whole-object.js +272 -0
- package/dist/segmentation.d.ts +10 -0
- package/dist/segmentation.js +4 -0
- package/dist/shared/choice-mass.d.ts +2 -0
- package/dist/shared/choice-mass.js +19 -0
- package/dist/shared/gate.d.ts +11 -0
- package/dist/shared/gate.js +52 -0
- package/dist/shared/input-budget.d.ts +7 -0
- package/dist/shared/input-budget.js +21 -0
- package/dist/shared/scope.d.ts +12 -0
- package/dist/shared/scope.js +29 -0
- package/docs/0.2.0-plan.md +190 -0
- package/docs/README.ru.md +258 -0
- package/docs/acceptance.md +144 -0
- package/docs/api.md +49 -0
- package/docs/architecture.md +45 -0
- package/docs/benchmarks.md +63 -0
- package/docs/categorical-presence-port.md +26 -0
- package/docs/evidence/current-linux-check.json +15 -0
- package/docs/evidence/decisions-completion-v1-manifest.json +96 -0
- package/docs/evidence/decisions-completion-v1-report.json +1205 -0
- package/docs/evidence/decisions-selection-v1-manifest.json +96 -0
- package/docs/evidence/decisions-selection-v1-report.json +1457 -0
- package/docs/evidence/jev-pricing-2026-10-18.json +69 -0
- package/docs/evidence/linux-check.json +20 -0
- package/docs/evidence/linux-check.txt +205 -0
- package/docs/evidence/live-release-smoke-manifest.json +82 -0
- package/docs/evidence/live-release-smoke-provenance.json +9 -0
- package/docs/evidence/live-release-smoke-report.json +2136 -0
- package/docs/evidence/live-v1-manifest.json +84 -0
- package/docs/evidence/live-v1-provenance.json +9 -0
- package/docs/evidence/live-v1-report.json +15555 -0
- package/docs/evidence/luna-decisions-fine-probe.json +19 -0
- package/docs/evidence/luna-decisions-locator-smoke-manifest.json +39 -0
- package/docs/evidence/luna-decisions-locator-smoke-report.json +149 -0
- package/docs/evidence/pack-check.json +26 -0
- package/docs/evidence/protocol-probes.json +61 -0
- package/docs/evidence/rejected-vision-selection-v1-qwen-photo-dev-01.json +42 -0
- package/docs/evidence/rejected-vision-selection-v1-qwen-photo-dev-17.json +42 -0
- package/docs/evidence/rejected-vision-selection-v1-qwen-photo-heldout-09.json +42 -0
- package/docs/evidence/rejected-vision-selection-v2-haiku-photo-dev-16.json +42 -0
- package/docs/evidence/rejected-vision-selection-v2-haiku-support-heldout-016.json +42 -0
- package/docs/evidence/v02/baseline.json +1043 -0
- package/docs/evidence/v02/boundary-variants.md +17 -0
- package/docs/evidence/v02/box-confirmation-dev40/README.md +15 -0
- package/docs/evidence/v02/box-confirmation-dev40/component-refinement.json +246 -0
- package/docs/evidence/v02/box-confirmation-dev40/evaluation.json +1663 -0
- package/docs/evidence/v02/box-confirmation-dev40/provenance.json +130 -0
- package/docs/evidence/v02/box-confirmation-dev40/report.json +127 -0
- package/docs/evidence/v02/box-confirmation-dev40-freeze-20261020-v1.json +230 -0
- package/docs/evidence/v02/box-confirmation-method-selection-20261020-v1.json +85 -0
- package/docs/evidence/v02/box-heldout-cancelled-before-dispatch-20261021-v1.json +32 -0
- package/docs/evidence/v02/box-heldout-combined-seal-20261021-v1.json +89 -0
- package/docs/evidence/v02/box-heldout-evaluator-truth-seal-20261021-v1.json +61 -0
- package/docs/evidence/v02/box-heldout-exclusion-audit-20261020-v1.json +81 -0
- package/docs/evidence/v02/box-heldout-map-seal-20261021-v1.json +48 -0
- package/docs/evidence/v02/box-heldout-photo-seal-20261020-v1.json +62 -0
- package/docs/evidence/v02/box-heldout-run-freeze-20261021-v1.json +61 -0
- package/docs/evidence/v02/box-heldout-source-review-20261020-v1.json +134 -0
- package/docs/evidence/v02/box-heldout-ui-seal-20261020-v1.json +44 -0
- package/docs/evidence/v02/calculator-proof-root-review-20261020-v1.json +63 -0
- package/docs/evidence/v02/categorical-dev40-freeze.json +94 -0
- package/docs/evidence/v02/categorical-dev40-preserve.json +460 -0
- package/docs/evidence/v02/categorical-dev40-target-only.json +460 -0
- package/docs/evidence/v02/categorical-heldout-admission-20261020-v3.json +215 -0
- package/docs/evidence/v02/categorical-heldout-admission-20261020-v4.json +270 -0
- package/docs/evidence/v02/categorical-heldout-dispatch-authorization-20261020-v4.json +79 -0
- package/docs/evidence/v02/categorical-heldout-predispatch-20261020-v3.json +57 -0
- package/docs/evidence/v02/categorical-heldout-producer-source-map-v4.json +262 -0
- package/docs/evidence/v02/categorical-heldout-producer-source-map.json +263 -0
- package/docs/evidence/v02/categorical-heldout-run-freeze-20261020-v3.json +35 -0
- package/docs/evidence/v02/categorical-heldout-run-freeze-20261020-v4.json +35 -0
- package/docs/evidence/v02/categorical-heldout-v4/README.md +35 -0
- package/docs/evidence/v02/categorical-heldout-v4/candidate-evaluation.json +2812 -0
- package/docs/evidence/v02/categorical-heldout-v4/candidate-physical-receipts.json +15261 -0
- package/docs/evidence/v02/categorical-heldout-v4/candidate-provenance.json +131 -0
- package/docs/evidence/v02/categorical-heldout-v4/control-evaluation.json +855 -0
- package/docs/evidence/v02/categorical-heldout-v4/control-physical-receipts.json +4402 -0
- package/docs/evidence/v02/categorical-heldout-v4/control-provenance.json +31 -0
- package/docs/evidence/v02/categorical-heldout-v4/mask-normalized-exploratory.json +682 -0
- package/docs/evidence/v02/categorical-heldout-v4/paired-qualification.json +21 -0
- package/docs/evidence/v02/categorical-heldout-v4/report.json +258 -0
- package/docs/evidence/v02/categorical-presence-dev6.json +144 -0
- package/docs/evidence/v02/categorical-presence-dev6.md +18 -0
- package/docs/evidence/v02/categorical-vision-method-selection.json +90 -0
- package/docs/evidence/v02/checked-region-component-v1/README.md +7 -0
- package/docs/evidence/v02/checked-region-component-v1/evidence.json +311 -0
- package/docs/evidence/v02/checked-region-resource-interrupted-2b0d09f.json +102 -0
- package/docs/evidence/v02/checked-region-segmentation-resource-4df544c-receipt.json +167 -0
- package/docs/evidence/v02/checked-region-segmentation-resource-4df544c-root-review.json +58 -0
- package/docs/evidence/v02/checked-region-segmentation-resource-987ac32-receipt.json +158 -0
- package/docs/evidence/v02/ci-012b935.json +17 -0
- package/docs/evidence/v02/ci-0c365cb.json +372 -0
- package/docs/evidence/v02/ci-185bde8.json +32 -0
- package/docs/evidence/v02/ci-24f849f.json +95 -0
- package/docs/evidence/v02/ci-25c19cd.json +375 -0
- package/docs/evidence/v02/ci-2922caf.json +375 -0
- package/docs/evidence/v02/ci-29357d7.json +29 -0
- package/docs/evidence/v02/ci-32aa90d.json +24 -0
- package/docs/evidence/v02/ci-35e3064.json +376 -0
- package/docs/evidence/v02/ci-4331b1b.json +384 -0
- package/docs/evidence/v02/ci-43b7426.json +376 -0
- package/docs/evidence/v02/ci-450439c.json +373 -0
- package/docs/evidence/v02/ci-46283fe.json +29 -0
- package/docs/evidence/v02/ci-4c9c09f.json +34 -0
- package/docs/evidence/v02/ci-5579646.json +27 -0
- package/docs/evidence/v02/ci-6f9cc15.json +127 -0
- package/docs/evidence/v02/ci-7c1051f.json +27 -0
- package/docs/evidence/v02/ci-9837888.json +34 -0
- package/docs/evidence/v02/ci-987ac32.json +29 -0
- package/docs/evidence/v02/ci-9d00fe5.json +25 -0
- package/docs/evidence/v02/ci-9f85acc.json +127 -0
- package/docs/evidence/v02/ci-be275a6.json +201 -0
- package/docs/evidence/v02/ci-c107ddf.json +372 -0
- package/docs/evidence/v02/ci-c35f67d.json +127 -0
- package/docs/evidence/v02/ci-c5eddd8.json +32 -0
- package/docs/evidence/v02/ci-d2042e0.json +372 -0
- package/docs/evidence/v02/ci-d684259.json +375 -0
- package/docs/evidence/v02/ci-e0c190b.json +127 -0
- package/docs/evidence/v02/ci-e551253.json +372 -0
- package/docs/evidence/v02/ci-f9d4633.json +29 -0
- package/docs/evidence/v02/dev1-proposal-evaluation.md +13 -0
- package/docs/evidence/v02/dev2-mobile-sam-proposal-oracle.md +21 -0
- package/docs/evidence/v02/dev2-proposal-evaluation.md +21 -0
- package/docs/evidence/v02/fresh-development.md +16 -0
- package/docs/evidence/v02/fresh-native-numeric/artifact-hashes.json +9 -0
- package/docs/evidence/v02/fresh-native-numeric/evaluation-root-v1.json +568 -0
- package/docs/evidence/v02/fresh-native-numeric/run-manifest.json +342 -0
- package/docs/evidence/v02/fresh-native-numeric/summary.json +5654 -0
- package/docs/evidence/v02/fresh-native-outline-axis/artifact-hashes.json +9 -0
- package/docs/evidence/v02/fresh-native-outline-axis/evaluation-root-v1.json +568 -0
- package/docs/evidence/v02/fresh-native-outline-axis/run-manifest.json +378 -0
- package/docs/evidence/v02/fresh-native-outline-axis/summary.json +5605 -0
- package/docs/evidence/v02/fresh-text/README.md +23 -0
- package/docs/evidence/v02/fresh-text/ascii-contours-v1/classify-followup-run-manifest.json +405 -0
- package/docs/evidence/v02/fresh-text/ascii-contours-v1/classify-followup-summary.json +1646 -0
- package/docs/evidence/v02/fresh-text/ascii-contours-v1/evaluation.json +568 -0
- package/docs/evidence/v02/fresh-text/ascii-contours-v1/original-run-manifest.json +404 -0
- package/docs/evidence/v02/fresh-text/ascii-contours-v1/original-summary.json +4416 -0
- package/docs/evidence/v02/fresh-text/ascii-luma-v1/classify-followup-run-manifest.json +405 -0
- package/docs/evidence/v02/fresh-text/ascii-luma-v1/classify-followup-summary.json +1646 -0
- package/docs/evidence/v02/fresh-text/ascii-luma-v1/evaluation.json +568 -0
- package/docs/evidence/v02/fresh-text/ascii-luma-v1/original-run-manifest.json +404 -0
- package/docs/evidence/v02/fresh-text/ascii-luma-v1/original-summary.json +4647 -0
- package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/classify-followup-run-manifest.json +405 -0
- package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/classify-followup-summary.json +846 -0
- package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/evaluation.json +568 -0
- package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/original-run-manifest.json +404 -0
- package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/original-summary.json +1789 -0
- package/docs/evidence/v02/fresh-text/no-image-control/evaluation.json +568 -0
- package/docs/evidence/v02/fresh-text/no-image-control/original-run-manifest.json +404 -0
- package/docs/evidence/v02/fresh-text/no-image-control/original-summary.json +3953 -0
- package/docs/evidence/v02/fresh-text/palette-grid-v1/classify-followup-run-manifest.json +405 -0
- package/docs/evidence/v02/fresh-text/palette-grid-v1/classify-followup-summary.json +1646 -0
- package/docs/evidence/v02/fresh-text/palette-grid-v1/evaluation.json +568 -0
- package/docs/evidence/v02/fresh-text/palette-grid-v1/original-run-manifest.json +404 -0
- package/docs/evidence/v02/fresh-text/palette-grid-v1/original-summary.json +3974 -0
- package/docs/evidence/v02/fresh-text/pixel-rle-v1/classify-followup-run-manifest.json +405 -0
- package/docs/evidence/v02/fresh-text/pixel-rle-v1/classify-followup-summary.json +1646 -0
- package/docs/evidence/v02/fresh-text/pixel-rle-v1/evaluation.json +568 -0
- package/docs/evidence/v02/fresh-text/pixel-rle-v1/original-run-manifest.json +404 -0
- package/docs/evidence/v02/fresh-text/pixel-rle-v1/original-summary.json +4945 -0
- package/docs/evidence/v02/fresh-text/report.json +284 -0
- package/docs/evidence/v02/fresh-text/svg-contours-v1/classify-followup-run-manifest.json +405 -0
- package/docs/evidence/v02/fresh-text/svg-contours-v1/classify-followup-summary.json +846 -0
- package/docs/evidence/v02/fresh-text/svg-contours-v1/evaluation.json +568 -0
- package/docs/evidence/v02/fresh-text/svg-contours-v1/original-run-manifest.json +404 -0
- package/docs/evidence/v02/fresh-text/svg-contours-v1/original-summary.json +2189 -0
- package/docs/evidence/v02/heldout-evaluation-20261020.md +37 -0
- package/docs/evidence/v02/heldout-input-derivation.md +18 -0
- package/docs/evidence/v02/integration-0c365cb.json +90 -0
- package/docs/evidence/v02/integration-25c19cd.json +52 -0
- package/docs/evidence/v02/integration-35e3064.json +41 -0
- package/docs/evidence/v02/integration-4331b1b.json +42 -0
- package/docs/evidence/v02/integration-4c9c09f.json +46 -0
- package/docs/evidence/v02/integration-8010aad.json +48 -0
- package/docs/evidence/v02/integration-c1aa640.json +41 -0
- package/docs/evidence/v02/integration-eea1790.json +46 -0
- package/docs/evidence/v02/integration-fa82e65.json +42 -0
- package/docs/evidence/v02/jpeg-transport-offline.json +268 -0
- package/docs/evidence/v02/jpeg-transport-offline.md +7 -0
- package/docs/evidence/v02/local-archive-cleanup.json +59 -0
- package/docs/evidence/v02/native-base64-after.json +30 -0
- package/docs/evidence/v02/native-base64-before.json +32 -0
- package/docs/evidence/v02/native-checked-region-windows-46283fe.json +248 -0
- package/docs/evidence/v02/native-checked-region-windows-987ac32.json +141 -0
- package/docs/evidence/v02/native-grabcut-windows-4c494a3-run2.json +148 -0
- package/docs/evidence/v02/native-grabcut-windows-4c494a3-run3.json +148 -0
- package/docs/evidence/v02/native-grabcut-windows-4c494a3.json +56 -0
- package/docs/evidence/v02/native-proposal-windows-0c365cb.json +148 -0
- package/docs/evidence/v02/native-proposal-windows-450439c.json +156 -0
- package/docs/evidence/v02/native-proposal-windows-4c9c09f-retry1.json +147 -0
- package/docs/evidence/v02/native-proposal-windows-4c9c09f.json +52 -0
- package/docs/evidence/v02/native-region-windows-efc9318.json +163 -0
- package/docs/evidence/v02/native-windows-eea1790-baseline.json +198 -0
- package/docs/evidence/v02/native-windows-eea1790.json +228 -0
- package/docs/evidence/v02/ordinary-luna-axis/run-manifest.json +99 -0
- package/docs/evidence/v02/ordinary-luna-axis/summary.json +811 -0
- package/docs/evidence/v02/owned-temp-cleanup-20261020-v1.json +28 -0
- package/docs/evidence/v02/presentation-paired-20261020.json +1438 -0
- package/docs/evidence/v02/proposal-resource-soak-261dc23.json +201 -0
- package/docs/evidence/v02/proposal-resource-soak-261dc23.md +15 -0
- package/docs/evidence/v02/proposal-resource-soak-450439c-receipt.md +13 -0
- package/docs/evidence/v02/reconstructed-c03-box.json +41 -0
- package/docs/evidence/v02/region-seeded-dev-v1/README.md +17 -0
- package/docs/evidence/v02/region-seeded-dev-v1/evaluation.json +260 -0
- package/docs/evidence/v02/region-seeded-dev-v2/README.md +15 -0
- package/docs/evidence/v02/region-seeded-dev-v2/evaluation.json +232 -0
- package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/README.md +9 -0
- package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/control-identity-failure.json +22 -0
- package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/evaluation.json +160 -0
- package/docs/evidence/v02/region-seeded-dev-v3-control-r2/README.md +9 -0
- package/docs/evidence/v02/region-seeded-dev-v3-control-r2/attempt-accounting.json +55 -0
- package/docs/evidence/v02/region-seeded-dev-v3-control-r2/evaluation.json +136 -0
- package/docs/evidence/v02/region-seeded-development-control-r2-freeze-20261021.json +132 -0
- package/docs/evidence/v02/region-seeded-development-freeze-20261021-v1.json +167 -0
- package/docs/evidence/v02/region-seeded-development-freeze-20261021-v2.json +186 -0
- package/docs/evidence/v02/region-seeded-development-freeze-20261021-v3.json +205 -0
- package/docs/evidence/v02/region-seeded-heldout-cancelled-before-dispatch-20261021-v2.json +20 -0
- package/docs/evidence/v02/region-seeded-heldout-dispatch-authorization-20261021-v3.json +47 -0
- package/docs/evidence/v02/region-seeded-heldout-readiness-audit-20261021-v3.json +125 -0
- package/docs/evidence/v02/region-seeded-heldout-run-freeze-20261021-v2.json +234 -0
- package/docs/evidence/v02/region-seeded-heldout-run-freeze-20261021-v3.json +357 -0
- package/docs/evidence/v02/region-seeded-heldout-v3/README.md +19 -0
- package/docs/evidence/v02/region-seeded-heldout-v3/evaluation-summary.json +445 -0
- package/docs/evidence/v02/region-seeded-method-selection-20261021-v2.json +66 -0
- package/docs/evidence/v02/region-seeded-method-selection-20261021-v3.json +120 -0
- package/docs/evidence/v02/region-segmentation-resource-0ba4c87-receipt.json +156 -0
- package/docs/evidence/v02/resource-soak-linux-8010aad.json +125 -0
- package/docs/evidence/v02/screen-v2-terminal-evaluation.md +22 -0
- package/docs/evidence/v02/seed-outline/run-manifest.json +59 -0
- package/docs/evidence/v02/seed-outline/summary.json +487 -0
- package/docs/evidence/v02/seed-unbounded/run-manifest.json +80 -0
- package/docs/evidence/v02/seed-unbounded/summary.json +730 -0
- package/docs/evidence/v02/segmentation/README.md +21 -0
- package/docs/evidence/v02/segmentation/component-comparison-summary.json +240 -0
- package/docs/evidence/v02/segmentation/provenance/MOBILE-SAM-LICENSE.txt +201 -0
- package/docs/evidence/v02/segmentation/provenance/OPENCV-LICENSE.txt +202 -0
- package/docs/evidence/v02/segmentation/provenance/mobile-sam-download-receipt.json +29 -0
- package/docs/evidence/v02/segmentation/provenance/mobile-sam-graph-audit.json +214 -0
- package/docs/evidence/v02/segmentation/provenance/mobile-sam-model-metadata.json +166 -0
- package/docs/evidence/v02/split-choice-noul-dev6.json +72 -0
- package/docs/evidence/v02/split-choice-noul-dev6.md +11 -0
- package/docs/evidence/v02/supported-vision-screen-20261020.md +65 -0
- package/docs/evidence/v02/wesnoth-1.16.11-minimap-projection-source-audit.md +56 -0
- package/docs/evidence/v02/wesnoth-proof-root-review-20261020-v1.json +76 -0
- package/docs/evidence/vision-cost-reconciliation.json +37 -0
- package/docs/evidence/vision-model-summary.json +562 -0
- package/docs/evidence/vision-selection-v1-manifest.json +95 -0
- package/docs/evidence/vision-selection-v1-report.json +4598 -0
- package/docs/evidence/vision-selection-v2-manifest.json +95 -0
- package/docs/evidence/vision-selection-v2-report.json +1139 -0
- package/docs/evidence/windows-native.json +73 -0
- package/docs/evidence/windows-secrets.json +46 -0
- package/docs/operations.md +21 -0
- package/docs/plan.md +27 -0
- package/docs/provenance.json +32 -0
- package/docs/publishing.md +30 -0
- package/docs/research/text-transport-audit.md +66 -0
- package/docs/results.md +69 -0
- package/docs/v02-usage.md +162 -0
- package/docs/vision-results.md +78 -0
- package/package.json +114 -4
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { isProxy } from 'node:util/types';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { ClassifierError } from '../entities/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
|
+
const localErrors = new WeakSet();
|
|
45
|
+
export function ownedError(code, message, details = {}) {
|
|
46
|
+
const error = new ClassifierError(code, message, details);
|
|
47
|
+
localErrors.add(error);
|
|
48
|
+
return error;
|
|
49
|
+
}
|
|
50
|
+
export function invalid(code, message) {
|
|
51
|
+
return ownedError(code, message);
|
|
52
|
+
}
|
|
53
|
+
export function active(signal) {
|
|
54
|
+
if (!signal.aborted)
|
|
55
|
+
return;
|
|
56
|
+
const timeout = signal.reason instanceof ClassifierError && signal.reason.code === 'TIMEOUT';
|
|
57
|
+
throw ownedError(timeout ? 'TIMEOUT' : 'ABORTED', timeout ? 'Operation timed out' : 'Operation cancelled');
|
|
58
|
+
}
|
|
59
|
+
export function validated(schema, input, code) {
|
|
60
|
+
const parsed = schema.safeParse(input);
|
|
61
|
+
if (!parsed.success)
|
|
62
|
+
throw invalid(code, 'Decision schema validation failed');
|
|
63
|
+
return parsed.data;
|
|
64
|
+
}
|
|
65
|
+
function imageSize(bytes, format) {
|
|
66
|
+
if (format === 'png') {
|
|
67
|
+
if (bytes.length < 33 ||
|
|
68
|
+
!bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])) ||
|
|
69
|
+
bytes.readUInt32BE(8) !== 13 ||
|
|
70
|
+
bytes.toString('ascii', 12, 16) !== 'IHDR')
|
|
71
|
+
throw invalid('INVALID_INPUT', 'Invalid PNG image header');
|
|
72
|
+
return [bytes.readUInt32BE(16), bytes.readUInt32BE(20)];
|
|
73
|
+
}
|
|
74
|
+
if (bytes[0] !== 255 || bytes[1] !== 216)
|
|
75
|
+
throw invalid('INVALID_INPUT', 'Invalid JPEG image header');
|
|
76
|
+
let offset = 2;
|
|
77
|
+
while (offset + 3 < bytes.length) {
|
|
78
|
+
if (bytes[offset++] !== 255)
|
|
79
|
+
break;
|
|
80
|
+
while (bytes[offset] === 255)
|
|
81
|
+
offset++;
|
|
82
|
+
const marker = bytes[offset++];
|
|
83
|
+
if (marker === undefined || marker === 218 || marker === 217)
|
|
84
|
+
break;
|
|
85
|
+
if (offset + 2 > bytes.length)
|
|
86
|
+
break;
|
|
87
|
+
const length = bytes.readUInt16BE(offset);
|
|
88
|
+
if (length < 2 || offset + length > bytes.length)
|
|
89
|
+
break;
|
|
90
|
+
if ([192, 193, 194, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207].includes(marker)) {
|
|
91
|
+
if (length < 8)
|
|
92
|
+
break;
|
|
93
|
+
return [bytes.readUInt16BE(offset + 5), bytes.readUInt16BE(offset + 3)];
|
|
94
|
+
}
|
|
95
|
+
offset += length;
|
|
96
|
+
}
|
|
97
|
+
throw invalid('INVALID_INPUT', 'Invalid JPEG image dimensions');
|
|
98
|
+
}
|
|
99
|
+
export function validateImage(url) {
|
|
100
|
+
const prefix = /^data:image\/(png|jpeg);base64,/u.exec(url);
|
|
101
|
+
if (!prefix)
|
|
102
|
+
throw invalid('INVALID_INPUT', 'Only inline PNG or JPEG images are supported');
|
|
103
|
+
const encoded = url.slice(prefix[0].length);
|
|
104
|
+
const padding = encoded.endsWith('==') ? 2 : Number(encoded.endsWith('='));
|
|
105
|
+
if (!encoded.length ||
|
|
106
|
+
encoded.length % 4 !== 0 ||
|
|
107
|
+
!/^[A-Za-z0-9+/]+$/u.test(encoded.slice(0, encoded.length - padding)))
|
|
108
|
+
throw invalid('INVALID_INPUT', 'Invalid image base64');
|
|
109
|
+
const bytes = Buffer.from(encoded, 'base64');
|
|
110
|
+
if (bytes.length > 16 * 1024 * 1024 || bytes.toString('base64') !== encoded)
|
|
111
|
+
throw invalid('INVALID_INPUT', 'Invalid image byte representation');
|
|
112
|
+
const [width, height] = imageSize(bytes, prefix[1] ?? '');
|
|
113
|
+
if (width < 280 || height < 280 || width > 1568 || height > 1568)
|
|
114
|
+
throw invalid('INVALID_INPUT', 'Prepared image sides must be 280..1568 pixels');
|
|
115
|
+
}
|
|
116
|
+
/** Bounded owned snapshot; schema parsing never receives caller iterators or accessors. */
|
|
117
|
+
export function preflightJson(input, limit) {
|
|
118
|
+
let remaining = limit;
|
|
119
|
+
let nodes = 0;
|
|
120
|
+
const ancestors = new Set();
|
|
121
|
+
function charge(bytes) {
|
|
122
|
+
remaining -= bytes;
|
|
123
|
+
if (remaining < 0)
|
|
124
|
+
throw ownedError('RESOURCE_LIMIT', 'Decision request exceeds byte limit');
|
|
125
|
+
}
|
|
126
|
+
function string(value) {
|
|
127
|
+
charge(2);
|
|
128
|
+
for (let index = 0; index < value.length; index++) {
|
|
129
|
+
const code = value.charCodeAt(index);
|
|
130
|
+
if (code < 32)
|
|
131
|
+
charge([8, 9, 10, 12, 13].includes(code) ? 2 : 6);
|
|
132
|
+
else if (code === 34 || code === 92)
|
|
133
|
+
charge(2);
|
|
134
|
+
else if (code < 128)
|
|
135
|
+
charge(1);
|
|
136
|
+
else if (code < 2048)
|
|
137
|
+
charge(2);
|
|
138
|
+
else if (code >= 0xd800 && code <= 0xdbff) {
|
|
139
|
+
const next = value.charCodeAt(index + 1);
|
|
140
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
141
|
+
charge(4);
|
|
142
|
+
index++;
|
|
143
|
+
}
|
|
144
|
+
else
|
|
145
|
+
charge(6);
|
|
146
|
+
}
|
|
147
|
+
else
|
|
148
|
+
charge(code >= 0xdc00 && code <= 0xdfff ? 6 : 3);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function visit(value, depth) {
|
|
152
|
+
if (++nodes > 10_000 || depth > 6)
|
|
153
|
+
throw invalid('INVALID_INPUT', 'Decision structure exceeds supported limits');
|
|
154
|
+
if (typeof value === 'string') {
|
|
155
|
+
string(value);
|
|
156
|
+
return value;
|
|
157
|
+
}
|
|
158
|
+
if (value === null || typeof value === 'boolean') {
|
|
159
|
+
charge(value === false ? 5 : 4);
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
163
|
+
charge(String(value).length);
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
if (typeof value !== 'object' || value === null)
|
|
167
|
+
throw invalid('INVALID_INPUT', 'Unsupported decision value');
|
|
168
|
+
if (isProxy(value))
|
|
169
|
+
throw invalid('INVALID_INPUT', 'Decision input must contain plain values');
|
|
170
|
+
// Zod reads declared object fields even when they are not enumerable; JSON does not.
|
|
171
|
+
for (const name of [
|
|
172
|
+
'state',
|
|
173
|
+
'questions',
|
|
174
|
+
'type',
|
|
175
|
+
'image_url',
|
|
176
|
+
'url',
|
|
177
|
+
'detail',
|
|
178
|
+
'instructions',
|
|
179
|
+
'criteria',
|
|
180
|
+
]) {
|
|
181
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, name);
|
|
182
|
+
if (descriptor && !descriptor.enumerable)
|
|
183
|
+
throw invalid('INVALID_INPUT', 'Decision fields must be enumerable');
|
|
184
|
+
}
|
|
185
|
+
if (ancestors.has(value))
|
|
186
|
+
throw invalid('INVALID_INPUT', 'Circular decision structure');
|
|
187
|
+
ancestors.add(value);
|
|
188
|
+
charge(2);
|
|
189
|
+
if (Array.isArray(value)) {
|
|
190
|
+
const copy = [];
|
|
191
|
+
if (value.length > 64)
|
|
192
|
+
throw invalid('INVALID_INPUT', 'Decision array exceeds supported limits');
|
|
193
|
+
for (let index = 0; index < value.length; index++) {
|
|
194
|
+
if (index)
|
|
195
|
+
charge(1);
|
|
196
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
197
|
+
if (!descriptor || !Object.hasOwn(descriptor, 'value'))
|
|
198
|
+
throw invalid('INVALID_INPUT', 'Decision input must contain plain values');
|
|
199
|
+
copy.push(visit(descriptor.value, depth + 1));
|
|
200
|
+
}
|
|
201
|
+
ancestors.delete(value);
|
|
202
|
+
return copy;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
const copy = {};
|
|
206
|
+
const prototype = Object.getPrototypeOf(value);
|
|
207
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
208
|
+
throw invalid('INVALID_INPUT', 'Decision input must contain plain objects');
|
|
209
|
+
let count = 0;
|
|
210
|
+
for (const name in value) {
|
|
211
|
+
if (!Object.hasOwn(value, name))
|
|
212
|
+
continue;
|
|
213
|
+
if (++count > 255)
|
|
214
|
+
throw invalid('INVALID_INPUT', 'Decision object exceeds supported limits');
|
|
215
|
+
if (count > 1)
|
|
216
|
+
charge(1);
|
|
217
|
+
string(name);
|
|
218
|
+
charge(1);
|
|
219
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, name);
|
|
220
|
+
if (!descriptor || !Object.hasOwn(descriptor, 'value'))
|
|
221
|
+
throw invalid('INVALID_INPUT', 'Decision input must contain plain values');
|
|
222
|
+
Object.defineProperty(copy, name, {
|
|
223
|
+
value: visit(descriptor.value, depth + 1),
|
|
224
|
+
enumerable: true,
|
|
225
|
+
configurable: true,
|
|
226
|
+
writable: true,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
ancestors.delete(value);
|
|
230
|
+
return copy;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return visit(input, 0);
|
|
234
|
+
}
|
|
235
|
+
function statusError(status) {
|
|
236
|
+
const code = status === 401 || status === 403
|
|
237
|
+
? 'AUTHENTICATION'
|
|
238
|
+
: status === 413
|
|
239
|
+
? 'RESOURCE_LIMIT'
|
|
240
|
+
: status === 415
|
|
241
|
+
? 'UNSUPPORTED'
|
|
242
|
+
: 'PROVIDER';
|
|
243
|
+
return ownedError(code, 'Model provider rejected the request', { status });
|
|
244
|
+
}
|
|
245
|
+
export function failure(error, signal, cleanupFailed = false) {
|
|
246
|
+
const known = error instanceof ClassifierError && localErrors.has(error);
|
|
247
|
+
const incompleteCleanup = cleanupFailed || (known && error.details.cleanupFailed === true);
|
|
248
|
+
try {
|
|
249
|
+
active(signal);
|
|
250
|
+
}
|
|
251
|
+
catch (aborted) {
|
|
252
|
+
if (aborted instanceof ClassifierError)
|
|
253
|
+
return ownedError(aborted.code, aborted.message, incompleteCleanup ? { cleanupFailed: true } : {});
|
|
254
|
+
}
|
|
255
|
+
return ownedError(known ? error.code : 'PROVIDER', known ? error.message : 'Model provider transport failed', { ...(known ? error.details : {}), ...(incompleteCleanup ? { cleanupFailed: true } : {}) });
|
|
256
|
+
}
|
|
257
|
+
export async function readResponse(response, signal, limit) {
|
|
258
|
+
const reader = response.body?.getReader();
|
|
259
|
+
if (!reader) {
|
|
260
|
+
active(signal);
|
|
261
|
+
if (!response.ok)
|
|
262
|
+
throw statusError(response.status);
|
|
263
|
+
throw invalid('INVALID_RESPONSE', 'Provider response has no body');
|
|
264
|
+
}
|
|
265
|
+
let cancelTask;
|
|
266
|
+
function cancel() {
|
|
267
|
+
cancelTask ??= reader.cancel().then(() => true, () => false);
|
|
268
|
+
}
|
|
269
|
+
signal.addEventListener('abort', cancel, { once: true });
|
|
270
|
+
let caught;
|
|
271
|
+
let output = '';
|
|
272
|
+
let complete = false;
|
|
273
|
+
try {
|
|
274
|
+
active(signal);
|
|
275
|
+
if (!response.ok)
|
|
276
|
+
throw statusError(response.status);
|
|
277
|
+
const length = response.headers.get('content-length');
|
|
278
|
+
if (length !== null && (!/^\d+$/u.test(length) || Number(length) > limit))
|
|
279
|
+
throw ownedError('RESOURCE_LIMIT', 'Provider response exceeds byte limit');
|
|
280
|
+
const storage = Buffer.allocUnsafe(limit);
|
|
281
|
+
let bytes = 0;
|
|
282
|
+
while (true) {
|
|
283
|
+
active(signal);
|
|
284
|
+
const next = await reader.read();
|
|
285
|
+
active(signal);
|
|
286
|
+
if (next.done)
|
|
287
|
+
break;
|
|
288
|
+
if (next.value.byteLength > limit - bytes)
|
|
289
|
+
throw ownedError('RESOURCE_LIMIT', 'Provider response exceeds byte limit');
|
|
290
|
+
storage.set(next.value, bytes);
|
|
291
|
+
bytes += next.value.byteLength;
|
|
292
|
+
}
|
|
293
|
+
complete = true;
|
|
294
|
+
try {
|
|
295
|
+
output = new TextDecoder('utf-8', { fatal: true }).decode(storage.subarray(0, bytes));
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
throw invalid('INVALID_RESPONSE', 'Provider returned invalid UTF-8');
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
caught = error;
|
|
303
|
+
}
|
|
304
|
+
finally {
|
|
305
|
+
signal.removeEventListener('abort', cancel);
|
|
306
|
+
if (!complete)
|
|
307
|
+
cancel();
|
|
308
|
+
const released = cancelTask ? await cancelTask : true;
|
|
309
|
+
reader.releaseLock();
|
|
310
|
+
if (!released)
|
|
311
|
+
caught = failure(caught, signal, true);
|
|
312
|
+
}
|
|
313
|
+
if (caught !== undefined)
|
|
314
|
+
throw failure(caught, signal);
|
|
315
|
+
active(signal);
|
|
316
|
+
return output;
|
|
317
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { VisionChatOptions } from './vision-chat.js';
|
|
2
|
+
import type { DecisionPort } from '../entities/contracts.js';
|
|
3
|
+
export interface OpenRouterVisionOptions extends Omit<VisionChatOptions, 'endpoint' | 'headers' | 'apiKey' | 'provider'> {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
provider: string;
|
|
6
|
+
}
|
|
7
|
+
/** Optional OpenRouter routing wrapper over the ordinary chat-completions adapter. */
|
|
8
|
+
export declare function createOpenRouterVision(input: OpenRouterVisionOptions): DecisionPort;
|
|
9
|
+
export type OpenRouterTextOptions = OpenRouterVisionOptions;
|
|
10
|
+
/** Same strict chat protocol; rejects image parts before transport. */
|
|
11
|
+
export declare function createOpenRouterText(input: OpenRouterTextOptions): DecisionPort;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createVisionChat, visionOptionsSchema } from './vision-chat.js';
|
|
2
|
+
import { validated } from './openrouter-common.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
const schema = visionOptionsSchema
|
|
5
|
+
.omit({ endpoint: true, headers: true })
|
|
6
|
+
.extend({ apiKey: z.string().min(1), provider: z.string().min(1) })
|
|
7
|
+
.strict();
|
|
8
|
+
/** Optional OpenRouter routing wrapper over the ordinary chat-completions adapter. */
|
|
9
|
+
export function createOpenRouterVision(input) {
|
|
10
|
+
const options = validated(schema, input, 'INVALID_INPUT');
|
|
11
|
+
return createVisionChat({ ...options, endpoint: 'https://openrouter.ai/api/v1/chat/completions' });
|
|
12
|
+
}
|
|
13
|
+
/** Same strict chat protocol; rejects image parts before transport. */
|
|
14
|
+
export function createOpenRouterText(input) {
|
|
15
|
+
const options = validated(schema, input, 'INVALID_INPUT');
|
|
16
|
+
return createVisionChat({ ...options, endpoint: 'https://openrouter.ai/api/v1/chat/completions' }, 'text');
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DecisionPort } from '../entities/contracts.js';
|
|
2
|
+
export interface OpenRouterJevOptions {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
fetch?: typeof globalThis.fetch;
|
|
5
|
+
maxRequestBytes?: number;
|
|
6
|
+
maxResponseBytes?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface OpenRouterDecisionsOptions extends OpenRouterJevOptions {
|
|
9
|
+
model: string;
|
|
10
|
+
provider: string;
|
|
11
|
+
providerName?: string;
|
|
12
|
+
responseModelIds?: string[];
|
|
13
|
+
/** Only enable after confirming this wire quantization for the selected model. */
|
|
14
|
+
probabilityRoundingStep?: 0.01;
|
|
15
|
+
}
|
|
16
|
+
/** One pinned provider call; the caller owns the absolute deadline through its signal. */
|
|
17
|
+
export declare function createOpenRouterDecisions(input: OpenRouterDecisionsOptions): DecisionPort;
|
|
18
|
+
/** Optional historical integration; never an automatic fallback. */
|
|
19
|
+
export declare function createOpenRouterJev(input: OpenRouterJevOptions): DecisionPort;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ClassifierError } from '../entities/errors.js';
|
|
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';
|
|
5
|
+
const endpoint = 'https://openrouter.ai/api/alpha/decisions';
|
|
6
|
+
const responseSchema = z
|
|
7
|
+
.object({
|
|
8
|
+
id: z.string().min(1).max(512).nullable().optional(),
|
|
9
|
+
model: z.string().min(1).max(128).nullable().optional(),
|
|
10
|
+
provider: z.string().min(1).max(128).nullable().optional(),
|
|
11
|
+
usage: z
|
|
12
|
+
.object({
|
|
13
|
+
input_tokens: z.number().int().nonnegative().optional(),
|
|
14
|
+
output_tokens: z.number().int().nonnegative().optional(),
|
|
15
|
+
prompt_tokens: z.number().int().nonnegative().optional(),
|
|
16
|
+
completion_tokens: z.number().int().nonnegative().optional(),
|
|
17
|
+
total_tokens: z.number().int().nonnegative().optional(),
|
|
18
|
+
cost: z.number().finite().nonnegative().nullable().optional(),
|
|
19
|
+
})
|
|
20
|
+
.strict()
|
|
21
|
+
.nullable()
|
|
22
|
+
.optional(),
|
|
23
|
+
answers: z.record(key, z.discriminatedUnion('type', [
|
|
24
|
+
z
|
|
25
|
+
.object({
|
|
26
|
+
type: z.literal('choice'),
|
|
27
|
+
choice: key,
|
|
28
|
+
probabilities: z.record(key, probability),
|
|
29
|
+
confidence: probability.optional(),
|
|
30
|
+
})
|
|
31
|
+
.strict(),
|
|
32
|
+
z.object({ type: z.literal('noul'), noul: probability }).strict(),
|
|
33
|
+
])),
|
|
34
|
+
})
|
|
35
|
+
.strict();
|
|
36
|
+
const legacyOptionsSchema = z
|
|
37
|
+
.object({
|
|
38
|
+
apiKey: z
|
|
39
|
+
.string()
|
|
40
|
+
.min(1)
|
|
41
|
+
.max(4096)
|
|
42
|
+
.refine((value) => !/\s/u.test(value)),
|
|
43
|
+
fetch: z.custom((value) => typeof value === 'function').optional(),
|
|
44
|
+
maxRequestBytes: z.number().int().min(1).max(requestLimit).default(requestLimit),
|
|
45
|
+
maxResponseBytes: z.number().int().min(1).max(responseLimit).default(responseLimit),
|
|
46
|
+
})
|
|
47
|
+
.strict();
|
|
48
|
+
const identifier = z
|
|
49
|
+
.string()
|
|
50
|
+
.min(1)
|
|
51
|
+
.max(256)
|
|
52
|
+
.regex(/^[a-zA-Z0-9][a-zA-Z0-9._:/-]*$/u);
|
|
53
|
+
const optionsSchema = legacyOptionsSchema
|
|
54
|
+
.extend({
|
|
55
|
+
model: identifier,
|
|
56
|
+
provider: identifier,
|
|
57
|
+
providerName: z.string().min(1).max(256).optional(),
|
|
58
|
+
responseModelIds: z.array(identifier).max(8).optional(),
|
|
59
|
+
probabilityRoundingStep: z.literal(0.01).optional(),
|
|
60
|
+
})
|
|
61
|
+
.strict();
|
|
62
|
+
function routeName(value) {
|
|
63
|
+
return value.toLowerCase().replace(/[^a-z0-9]/gu, '');
|
|
64
|
+
}
|
|
65
|
+
function serialize(input, maxBytes, options) {
|
|
66
|
+
const envelope = {
|
|
67
|
+
model: options.model,
|
|
68
|
+
provider: { only: [options.provider], allow_fallbacks: false },
|
|
69
|
+
};
|
|
70
|
+
const snapshot = preflightJson(input, maxBytes - Buffer.byteLength(JSON.stringify(envelope)) + 1);
|
|
71
|
+
const request = validated(requestSchema, snapshot, 'INVALID_INPUT');
|
|
72
|
+
const questions = Object.fromEntries(Object.entries(request.questions).map(([id, question]) => [
|
|
73
|
+
id,
|
|
74
|
+
question.type === 'choice'
|
|
75
|
+
? { type: question.type, instructions: question.instructions, criteria: question.criteria }
|
|
76
|
+
: question,
|
|
77
|
+
]));
|
|
78
|
+
const body = JSON.stringify({ ...request, questions, ...envelope });
|
|
79
|
+
if (Buffer.byteLength(body) > maxBytes)
|
|
80
|
+
throw ownedError('RESOURCE_LIMIT', 'Decision request exceeds byte limit');
|
|
81
|
+
for (const part of request.state)
|
|
82
|
+
if (typeof part !== 'string')
|
|
83
|
+
validateImage(part.image_url.url);
|
|
84
|
+
return { request, body };
|
|
85
|
+
}
|
|
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
|
+
function result(raw, request, response, durationMs, options) {
|
|
99
|
+
let json;
|
|
100
|
+
try {
|
|
101
|
+
json = JSON.parse(raw);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
throw invalid('INVALID_RESPONSE', 'Provider returned invalid JSON');
|
|
105
|
+
}
|
|
106
|
+
const value = validated(responseSchema, json, 'INVALID_RESPONSE');
|
|
107
|
+
if (value.model && !acceptedModel(value.model, options))
|
|
108
|
+
throw invalid('INVALID_RESPONSE', 'Provider returned an unexpected model');
|
|
109
|
+
if (value.provider &&
|
|
110
|
+
routeName(value.provider) !== routeName(options.providerName ?? options.provider))
|
|
111
|
+
throw invalid('INVALID_RESPONSE', 'Provider returned an unexpected route');
|
|
112
|
+
const questionIds = Object.keys(request.questions);
|
|
113
|
+
if (Object.keys(value.answers).length !== questionIds.length)
|
|
114
|
+
throw invalid('INVALID_RESPONSE', 'Provider returned mismatched questions');
|
|
115
|
+
const answers = { ...value.answers };
|
|
116
|
+
for (const id of questionIds) {
|
|
117
|
+
const question = request.questions[id], answer = value.answers[id];
|
|
118
|
+
if (!question || !answer || question.type !== answer.type)
|
|
119
|
+
throw invalid('INVALID_RESPONSE', 'Provider returned mismatched answer types');
|
|
120
|
+
if (question.type === 'choice' && answer.type === 'choice') {
|
|
121
|
+
const keys = Object.keys(question.criteria);
|
|
122
|
+
const scores = answer.probabilities;
|
|
123
|
+
if (Object.keys(scores).length !== keys.length ||
|
|
124
|
+
keys.some((option) => !Object.hasOwn(scores, option)) ||
|
|
125
|
+
!Object.hasOwn(question.criteria, answer.choice) ||
|
|
126
|
+
scores[answer.choice] !== Math.max(...Object.values(scores)) ||
|
|
127
|
+
!acceptsChoiceMass(Object.values(scores), options.probabilityRoundingStep))
|
|
128
|
+
throw invalid('INVALID_RESPONSE', 'Provider returned an invalid choice distribution');
|
|
129
|
+
// Explicitly configured wire quantization; retain raw scores and native ties.
|
|
130
|
+
if (!acceptsChoiceMass(Object.values(scores)))
|
|
131
|
+
answers[id] = { ...answer, probabilityRoundingStep: 0.01 };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const requestId = value.id ?? response.headers.get('x-generation-id');
|
|
135
|
+
if (requestId !== null && requestId.length > 512)
|
|
136
|
+
throw invalid('INVALID_RESPONSE', 'Provider returned an invalid request identity');
|
|
137
|
+
return {
|
|
138
|
+
answers,
|
|
139
|
+
metadata: {
|
|
140
|
+
model: value.model ?? null,
|
|
141
|
+
provider: value.provider ?? null,
|
|
142
|
+
requestId,
|
|
143
|
+
costUsd: value.usage?.cost ?? null,
|
|
144
|
+
durationMs,
|
|
145
|
+
attempts: 1,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/** One pinned provider call; the caller owns the absolute deadline through its signal. */
|
|
150
|
+
export function createOpenRouterDecisions(input) {
|
|
151
|
+
const options = validated(optionsSchema, input, 'INVALID_INPUT');
|
|
152
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
153
|
+
return {
|
|
154
|
+
async decide(input, signal) {
|
|
155
|
+
active(signal);
|
|
156
|
+
let prepared;
|
|
157
|
+
try {
|
|
158
|
+
prepared = serialize(input, options.maxRequestBytes, options);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
active(signal);
|
|
162
|
+
if (error instanceof ClassifierError)
|
|
163
|
+
throw error;
|
|
164
|
+
throw invalid('INVALID_INPUT', 'Decision input could not be serialized');
|
|
165
|
+
}
|
|
166
|
+
active(signal);
|
|
167
|
+
const started = performance.now();
|
|
168
|
+
let response;
|
|
169
|
+
try {
|
|
170
|
+
response = await fetcher(endpoint, {
|
|
171
|
+
method: 'POST',
|
|
172
|
+
headers: {
|
|
173
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
174
|
+
'Content-Type': 'application/json',
|
|
175
|
+
},
|
|
176
|
+
body: prepared.body,
|
|
177
|
+
signal,
|
|
178
|
+
redirect: 'error',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
active(signal);
|
|
183
|
+
throw ownedError('PROVIDER', 'OpenRouter transport failed');
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
const raw = await readResponse(response, signal, options.maxResponseBytes);
|
|
187
|
+
const value = result(raw, prepared.request, response, Math.max(0, performance.now() - started), options);
|
|
188
|
+
active(signal);
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
throw failure(error, signal);
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/** Optional historical integration; never an automatic fallback. */
|
|
198
|
+
export function createOpenRouterJev(input) {
|
|
199
|
+
const { fetch: fetcher, ...options } = validated(legacyOptionsSchema, input, 'INVALID_INPUT');
|
|
200
|
+
return createOpenRouterDecisions({
|
|
201
|
+
...options,
|
|
202
|
+
...(fetcher ? { fetch: fetcher } : {}),
|
|
203
|
+
model: 'typesafe/jev-1.14',
|
|
204
|
+
provider: 'typesafe',
|
|
205
|
+
probabilityRoundingStep: 0.01,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RasterImage, RasterPort } from '../entities/contracts.js';
|
|
2
|
+
import type { ImageRepresentationPort } from '../entities/representation.js';
|
|
3
|
+
export declare function createSharpRaster(): RasterPort;
|
|
4
|
+
/** Decode an oriented raster into the canonical RGBA8 pixel layout used by adapters. */
|
|
5
|
+
export declare function readCanonicalPixels(image: RasterImage, signal: AbortSignal): Promise<{
|
|
6
|
+
data: Uint8Array;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}>;
|
|
10
|
+
/** Vision adapter that retains the existing sharp imageState layout for every supplied view. */
|
|
11
|
+
export declare function createVisionRepresentation(raster: RasterPort): ImageRepresentationPort;
|