@bendyline/gezel-inference 1.0.2 → 1.0.4
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/dist/catalog/chat-models/de/deepseek-v4.1-flash-552b-q2/manifest.json +120 -0
- package/dist/catalog/chat-models/de/deepseek-v4.1-flash-552b-q2/versions/1.0.0/manifest.json +21 -0
- package/dist/catalog/chat-models/gl/glm-5.3-flash-320b-q2/manifest.json +6 -5
- package/dist/catalog/chat-models/gl/glm-5.3-flash-320b-q2/versions/1.0.2/manifest.json +27 -0
- package/dist/catalog/chat-models/gl/glm-5.3-flash-320b-q2/versions/1.0.3/manifest.json +27 -0
- package/dist/catalog/chat-models/index.json +1 -1
- package/dist/catalog/chat-models/na/nanbeige4.2-3b-q4/manifest.json +1 -1
- package/dist/catalog/chat-models/ne/nemotron3.5-bolt-74b-iq2/manifest.json +157 -0
- package/dist/catalog/chat-models/ne/nemotron3.5-bolt-74b-iq2/versions/1.0.0/manifest.json +30 -0
- package/dist/catalog/chat-models/ne/nemotron3.5-bolt-e4b-q4/manifest.json +142 -0
- package/dist/catalog/chat-models/ne/nemotron3.5-bolt-e4b-q4/versions/1.0.0/manifest.json +14 -0
- package/dist/catalog/chat-models/ne/nemotron3.5-bolt-e4b-q4/versions/1.0.1/manifest.json +14 -0
- package/dist/catalog/chat-models/ne/nemotron3.5-super-vl-120b-nvfp4/manifest.json +156 -0
- package/dist/catalog/chat-models/ne/nemotron3.5-super-vl-120b-nvfp4/versions/1.0.0/manifest.json +28 -0
- package/dist/catalog/chat-models/raw-index.json +1 -1
- package/dist/catalog/image-models/an/animagine-xl-4/manifest.json +40 -0
- package/dist/catalog/image-models/an/animagine-xl-4/versions/4.0.0/manifest.json +10 -0
- package/dist/catalog/image-models/an/animagine-xl-4/versions/4.0.1/manifest.json +16 -0
- package/dist/catalog/image-models/fl/flux-2-klein-4b-q4/manifest.json +44 -0
- package/dist/catalog/image-models/fl/flux-2-klein-4b-q4/versions/1.0.0/manifest.json +23 -0
- package/dist/catalog/image-models/fl/flux-2-klein-4b-q4/versions/1.0.1/manifest.json +29 -0
- package/dist/catalog/image-models/index.json +1 -0
- package/dist/catalog/image-models/ir/iris-3b-f32/manifest.json +41 -0
- package/dist/catalog/image-models/ir/iris-3b-f32/versions/1.0.0/manifest.json +19 -0
- package/dist/catalog/image-models/ir/iris-3b-f32/versions/1.0.1/manifest.json +25 -0
- package/dist/catalog/image-models/ir/iris-3b-f32/versions/1.0.2/manifest.json +25 -0
- package/dist/catalog/image-models/kr/krea-2-turbo-q4/manifest.json +42 -0
- package/dist/catalog/image-models/kr/krea-2-turbo-q4/versions/1.0.0/manifest.json +23 -0
- package/dist/catalog/image-models/kr/krea-2-turbo-q4/versions/1.0.1/manifest.json +29 -0
- package/dist/catalog/image-models/kr/krea-2-turbo-q6/manifest.json +42 -0
- package/dist/catalog/image-models/kr/krea-2-turbo-q6/versions/1.0.0/manifest.json +23 -0
- package/dist/catalog/image-models/kr/krea-2-turbo-q6/versions/1.0.1/manifest.json +29 -0
- package/dist/catalog/image-models/kr/krea-2-turbo-q8/manifest.json +42 -0
- package/dist/catalog/image-models/kr/krea-2-turbo-q8/versions/1.0.0/manifest.json +23 -0
- package/dist/catalog/image-models/kr/krea-2-turbo-q8/versions/1.0.1/manifest.json +29 -0
- package/dist/catalog/image-models/mi/ming-image-0.1-design-bf16/manifest.json +41 -0
- package/dist/catalog/image-models/mi/ming-image-0.1-design-bf16/versions/1.0.0/manifest.json +31 -0
- package/dist/catalog/image-models/mi/ming-image-0.1-design-bf16/versions/1.0.1/manifest.json +50 -0
- package/dist/catalog/image-models/mi/ming-image-0.1-design-bf16/versions/1.0.2/manifest.json +50 -0
- package/dist/catalog/image-models/mo/moss-image-sft-q4/manifest.json +40 -0
- package/dist/catalog/image-models/mo/moss-image-sft-q4/versions/1.0.0/manifest.json +37 -0
- package/dist/catalog/image-models/mo/moss-image-sft-q4/versions/1.0.1/manifest.json +37 -0
- package/dist/catalog/image-models/raw-index.json +1 -0
- package/dist/catalog/image-models/sd/sd-1.5/manifest.json +40 -0
- package/dist/catalog/image-models/sd/sd-1.5/versions/1.0.0/manifest.json +10 -0
- package/dist/catalog/image-models/sd/sd-1.5/versions/1.0.1/manifest.json +16 -0
- package/dist/catalog/image-models/sd/sd-turbo/manifest.json +40 -0
- package/dist/catalog/image-models/sd/sd-turbo/versions/1.0.0/manifest.json +10 -0
- package/dist/catalog/image-models/sd/sd-turbo/versions/1.0.1/manifest.json +16 -0
- package/dist/catalog/image-models/sd/sdxl-base-1.0/manifest.json +39 -0
- package/dist/catalog/image-models/sd/sdxl-base-1.0/versions/1.0.0/manifest.json +10 -0
- package/dist/catalog/image-models/sd/sdxl-base-1.0/versions/1.0.1/manifest.json +16 -0
- package/dist/catalog/image-models/sd/sdxl-lightning-4step/manifest.json +40 -0
- package/dist/catalog/image-models/sd/sdxl-lightning-4step/versions/1.0.0/manifest.json +10 -0
- package/dist/catalog/image-models/sd/sdxl-lightning-4step/versions/1.0.1/manifest.json +16 -0
- package/dist/catalog/image-models/sd/sdxl-turbo/manifest.json +41 -0
- package/dist/catalog/image-models/sd/sdxl-turbo/versions/1.0.0/manifest.json +10 -0
- package/dist/catalog/image-models/sd/sdxl-turbo/versions/1.0.1/manifest.json +16 -0
- package/dist/catalog/knowledge-catalogs/be/bendyline-how-to-books/keywords.json +56 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-alaska-part-0001/keywords.json +12 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-nevada-part-0001/keywords.json +13 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-us-300-399-part-0001/keywords.json +14 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-us-347-349/keywords.json +17 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-utah-part-0001/keywords.json +12 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-utah-part-0002/keywords.json +12 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0001/keywords.json +12 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0002/keywords.json +12 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0003/keywords.json +12 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0004/keywords.json +12 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0005/keywords.json +12 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-wyoming-part-0001/keywords.json +12 -0
- package/dist/catalog/knowledge-catalogs/index.json +1 -1
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-aspnet-core-en/keywords.json +21 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-ai-platform-en/keywords.json +27 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-apps-compute-en/keywords.json +23 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-data-aiot-en/keywords.json +26 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-management-hybrid-en/keywords.json +28 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-network-security-en/keywords.json +29 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-dotnet-en/keywords.json +21 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-entity-framework-en/keywords.json +14 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-microsoft-365-en/keywords.json +20 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-powershell-en/keywords.json +24 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-sql-server-en/keywords.json +27 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-windows-apps-en/keywords.json +18 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-windows-server-en/keywords.json +28 -0
- package/dist/catalog/knowledge-catalogs/raw-index.json +1 -1
- package/dist/catalog/knowledge-catalogs/re/regional-american-south/keywords.json +1700 -0
- package/dist/catalog/knowledge-catalogs/re/regional-american-south/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-andes-amazon/keywords.json +750 -0
- package/dist/catalog/knowledge-catalogs/re/regional-andes-amazon/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-australia-nz/keywords.json +1281 -0
- package/dist/catalog/knowledge-catalogs/re/regional-australia-nz/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-brazil/keywords.json +1125 -0
- package/dist/catalog/knowledge-catalogs/re/regional-brazil/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-britain-ireland/keywords.json +3287 -0
- package/dist/catalog/knowledge-catalogs/re/regional-britain-ireland/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-california-southwest/keywords.json +1835 -0
- package/dist/catalog/knowledge-catalogs/re/regional-california-southwest/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-canada-alaska/keywords.json +1184 -0
- package/dist/catalog/knowledge-catalogs/re/regional-canada-alaska/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-central-eastern-europe/keywords.json +1503 -0
- package/dist/catalog/knowledge-catalogs/re/regional-central-eastern-europe/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-east-asia/keywords.json +2836 -0
- package/dist/catalog/knowledge-catalogs/re/regional-east-asia/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-east-southern-africa/keywords.json +813 -0
- package/dist/catalog/knowledge-catalogs/re/regional-east-southern-africa/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-mediterranean/keywords.json +2082 -0
- package/dist/catalog/knowledge-catalogs/re/regional-mediterranean/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-mexico-central-america/keywords.json +1481 -0
- package/dist/catalog/knowledge-catalogs/re/regional-mexico-central-america/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-middle-east/keywords.json +1272 -0
- package/dist/catalog/knowledge-catalogs/re/regional-middle-east/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-midwest/keywords.json +758 -0
- package/dist/catalog/knowledge-catalogs/re/regional-midwest/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-north-africa/keywords.json +896 -0
- package/dist/catalog/knowledge-catalogs/re/regional-north-africa/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northeast/keywords.json +1281 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northeast/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northern-eurasia/keywords.json +346 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northern-eurasia/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northern-europe/keywords.json +2688 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northern-europe/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-pacific-islands/keywords.json +973 -0
- package/dist/catalog/knowledge-catalogs/re/regional-pacific-islands/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-pacific-northwest/keywords.json +937 -0
- package/dist/catalog/knowledge-catalogs/re/regional-pacific-northwest/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-rockies-plains/keywords.json +608 -0
- package/dist/catalog/knowledge-catalogs/re/regional-rockies-plains/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-south-asia/keywords.json +633 -0
- package/dist/catalog/knowledge-catalogs/re/regional-south-asia/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-southeast-asia/keywords.json +2697 -0
- package/dist/catalog/knowledge-catalogs/re/regional-southeast-asia/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-southern-cone/keywords.json +606 -0
- package/dist/catalog/knowledge-catalogs/re/regional-southern-cone/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-west-central-africa/keywords.json +564 -0
- package/dist/catalog/knowledge-catalogs/re/regional-west-central-africa/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-western-europe/keywords.json +3247 -0
- package/dist/catalog/knowledge-catalogs/re/regional-western-europe-east/manifest.json +47 -0
- package/dist/catalog/knowledge-catalogs/re/regional-western-europe-east/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-western-europe-west/manifest.json +47 -0
- package/dist/catalog/knowledge-catalogs/re/regional-western-europe-west/versions/2026.10.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-arts/keywords.json +5437 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-arts/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-arts/versions/2026.10.1/manifest.json +181 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-astronomy/keywords.json +4009 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-astronomy/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-astronomy/versions/2026.10.1/manifest.json +181 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-biology/keywords.json +4009 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-biology/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-biology/versions/2026.10.1/manifest.json +217 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-business/keywords.json +5583 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-business/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-business/versions/2026.10.1/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-film-tv/keywords.json +6386 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-film-tv/manifest.json +2 -2
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-film-tv/versions/2026.10.1/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-food-drink/keywords.json +4009 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-food-drink/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-food-drink/versions/2026.10.1/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-games/keywords.json +5672 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-games/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-games/versions/2026.10.1/manifest.json +137 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-geography/manifest.json +68 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-geography/versions/2026.10.1/manifest.json +217 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-history/manifest.json +68 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-history/versions/2026.10.1/manifest.json +217 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-language/keywords.json +4009 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-language/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-language/versions/2026.10.1/manifest.json +141 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-law/keywords.json +4009 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-law/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-law/versions/2026.10.1/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-literature/keywords.json +5719 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-literature/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-literature/versions/2026.10.1/manifest.json +193 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-mathematics/keywords.json +4009 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-mathematics/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-mathematics/versions/2026.10.1/manifest.json +189 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-medicine/keywords.json +16009 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-medicine/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-medicine/versions/2026.10.1/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-military/keywords.json +5843 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-military/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-military/versions/2026.10.1/manifest.json +189 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-music/keywords.json +6264 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-music/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-music/versions/2026.10.1/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-arts/manifest.json +66 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-arts/versions/2026.10.1/manifest.json +209 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-business/manifest.json +61 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-business/versions/2026.10.1/manifest.json +189 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-entertainment/manifest.json +64 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-entertainment/versions/2026.10.1/manifest.json +201 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-military/manifest.json +60 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-military/versions/2026.10.1/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-other/manifest.json +55 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-other/versions/2026.10.1/manifest.json +165 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-politics/manifest.json +63 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-politics/versions/2026.10.1/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-religion/manifest.json +64 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-religion/versions/2026.10.1/manifest.json +201 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-science/manifest.json +65 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-science/versions/2026.10.1/manifest.json +205 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-sports/manifest.json +73 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-sports/versions/2026.10.1/manifest.json +237 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-politics/keywords.json +5447 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-politics/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-politics/versions/2026.10.1/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-religion-philosophy/keywords.json +4009 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-religion-philosophy/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-religion-philosophy/versions/2026.10.1/manifest.json +189 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-science/keywords.json +4009 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-science/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-science/versions/2026.10.1/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-sports/keywords.json +5209 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-sports/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-sports/versions/2026.10.1/manifest.json +193 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-technology/keywords.json +6537 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-technology/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-technology/versions/2026.10.1/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-transportation/keywords.json +5989 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-transportation/manifest.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-transportation/versions/2026.10.1/manifest.json +189 -0
- package/dist/catalog/provenance.json +2 -1
- package/dist/engines/native-file-manifest.json +198 -118
- package/dist/engines/native-manifest.d.ts +4 -4
- package/dist/engines/native-manifest.js +36 -34
- package/dist/engines/resolver.js +12 -1
- package/dist/fs/atomic.d.ts +4 -1
- package/dist/fs/atomic.js +7 -1
- package/dist/http/openai-compat/chat-target.d.ts +1 -1
- package/dist/http/openai-compat/chat-target.js +1 -1
- package/dist/http/openai-compat/engine-failure.js +7 -2
- package/dist/http/routes/v1-chat.js +1 -1
- package/dist/http/routes/v1-images.d.ts +44 -0
- package/dist/http/routes/v1-images.js +370 -0
- package/dist/http/routes/v1-knowledge.js +4 -0
- package/dist/http/routes/v1-responses.d.ts +1 -1
- package/dist/inference-service.d.ts +9 -1
- package/dist/inference-service.js +44 -11
- package/dist/knowledge/manager.d.ts +7 -0
- package/dist/knowledge/manager.js +32 -1
- package/dist/kokoro-lexicon/manifest.json +6 -3
- package/dist/models/image-fit.d.ts +83 -0
- package/dist/models/image-fit.js +163 -0
- package/dist/models/weight-facts.d.ts +36 -0
- package/dist/models/weight-facts.js +171 -0
- package/dist/perf/responsiveness.js +4 -3
- package/dist/providers/audio/kokoro-frontend.js +1 -1
- package/dist/providers/ds4/build-provider.js +34 -21
- package/dist/providers/ds4/dspark.d.ts +23 -5
- package/dist/providers/ds4/dspark.js +36 -8
- package/dist/providers/image/img2img-support.d.ts +40 -0
- package/dist/providers/image/img2img-support.js +74 -0
- package/dist/providers/image/local-engine.d.ts +31 -0
- package/dist/providers/image/local-engine.js +47 -0
- package/dist/providers/image/local-factory.d.ts +95 -0
- package/dist/providers/image/local-factory.js +270 -0
- package/dist/providers/image/mock.d.ts +23 -0
- package/dist/providers/image/mock.js +151 -0
- package/dist/providers/image/pull-registry.d.ts +106 -0
- package/dist/providers/image/pull-registry.js +227 -0
- package/dist/providers/image/sd-cpp.d.ts +200 -0
- package/dist/providers/image/sd-cpp.js +845 -0
- package/dist/providers/image/types.d.ts +235 -0
- package/dist/providers/image/types.js +20 -0
- package/dist/providers/llama-cpp/backend-health.d.ts +7 -0
- package/dist/providers/llama-cpp/backend-health.js +41 -1
- package/dist/providers/llama-cpp/build-provider.js +107 -56
- package/dist/providers/llama-cpp/cache-adapter.d.ts +7 -1
- package/dist/providers/llama-cpp/cache-adapter.js +9 -0
- package/dist/providers/llama-cpp/devices.d.ts +13 -2
- package/dist/providers/llama-cpp/devices.js +43 -6
- package/dist/providers/llama-cpp/models.js +4 -1
- package/dist/providers/llama-cpp/offload-planner.d.ts +23 -0
- package/dist/providers/llama-cpp/offload-planner.js +27 -0
- package/dist/providers/llama-cpp/provider.d.ts +14 -3
- package/dist/providers/llama-cpp/provider.js +35 -2
- package/dist/providers/mlx/provider.js +15 -5
- package/dist/providers/mlx/reasoning-stream.d.ts +15 -0
- package/dist/providers/mlx/reasoning-stream.js +35 -0
- package/dist/providers/native/capacity-broker.js +9 -3
- package/dist/providers/native/local-engine-runtime.js +15 -12
- package/dist/providers/native/supervisor.d.ts +8 -0
- package/dist/providers/native/supervisor.js +55 -5
- package/dist/providers/project-macro-loop-bail.js +5 -3
- package/dist/providers/provider-lifecycle.d.ts +21 -0
- package/dist/providers/provider-lifecycle.js +83 -0
- package/dist/providers/queue.d.ts +9 -2
- package/dist/providers/queue.js +17 -2
- package/dist/providers/retirement-gate.d.ts +20 -0
- package/dist/providers/retirement-gate.js +55 -0
- package/dist/providers/session-hooks.d.ts +4 -1
- package/dist/system/memory.js +6 -0
- package/dist/utils/image-meta.d.ts +12 -0
- package/dist/utils/image-meta.js +77 -0
- package/package.json +6 -6
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StableDiffusionCppProvider — talks to a local `sd-server` binary over
|
|
3
|
+
* HTTP. The server speaks an OpenAI-compatible `/v1/images/generations`
|
|
4
|
+
* shape with a few extensions (seed, sample_steps, negative_prompt,
|
|
5
|
+
* cfg_scale) supported by upstream sd.cpp.
|
|
6
|
+
*
|
|
7
|
+
* Lifecycle of the server itself is not the provider's concern — an
|
|
8
|
+
* Electron-side supervisor (Phase 3) spawns / idle-stops the binary and
|
|
9
|
+
* the provider simply hits whichever URL is configured. If the server
|
|
10
|
+
* isn't reachable, {@link generate} surfaces a clear error so the UI
|
|
11
|
+
* can prompt the user to enable image generation in Settings.
|
|
12
|
+
*
|
|
13
|
+
* Model management is filesystem-only: weights live under
|
|
14
|
+
* `<modelsRoot>/<id>/weights.*` alongside a resolved copy of the
|
|
15
|
+
* catalog manifest (so local state is self-describing even if the
|
|
16
|
+
* catalog entry later disappears).
|
|
17
|
+
*/
|
|
18
|
+
import { createHash } from 'node:crypto';
|
|
19
|
+
import { createReadStream } from 'node:fs';
|
|
20
|
+
import { mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
import { createAwakeTimeout, createLogger, imageModelDefaultSide } from '@bendyline/gezel';
|
|
23
|
+
import { resolveModelDirectory } from '../../models/model-id.js';
|
|
24
|
+
import { MODEL_HASH_READ_BUFFER_BYTES, assertModelStorePathSafe, findModelRoot, hashModelPayloadFiles, listOverlayModelIds, makeSharedModelReadable, modelExistsOnlyReadOnly, readOnlyModelError, verifyReadOnlyModelPayload, } from '../../models/storage-roots.js';
|
|
25
|
+
import { downloadWithRetry } from '../../utils/download-with-retry.js';
|
|
26
|
+
import { readImageMeta } from '../../utils/image-meta.js';
|
|
27
|
+
import { resolveImg2ImgSupport } from './img2img-support.js';
|
|
28
|
+
import { ImageCapacityError, ImageGenerationCancelledError, } from './types.js';
|
|
29
|
+
const log = createLogger('image');
|
|
30
|
+
const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
31
|
+
const DEFAULT_SAMPLE_STEPS = 20;
|
|
32
|
+
/**
|
|
33
|
+
* Model-specific step counts, from fast distilled models to the 50 of MOSS
|
|
34
|
+
* and Iris. Shared with `buildSdServerArgs`, which sets
|
|
35
|
+
* the same server launch default for endpoints that ignore request steps.
|
|
36
|
+
*/
|
|
37
|
+
export const MODEL_SAMPLE_STEP_DEFAULTS = new Map([
|
|
38
|
+
// FLUX.1 is still in the pinned catalog and on people's disks: keep its
|
|
39
|
+
// settings until no supported catalog offers it.
|
|
40
|
+
['flux-1-schnell-q4', 4],
|
|
41
|
+
['flux-2-klein-4b-q4', 4],
|
|
42
|
+
['sd-turbo', 4],
|
|
43
|
+
['sdxl-lightning-4step', 4],
|
|
44
|
+
['sdxl-turbo', 4],
|
|
45
|
+
// Krea 2 Turbo is an 8-step distilled model. Setting the launch
|
|
46
|
+
// `--steps` default matters more than for the others: the pinned
|
|
47
|
+
// sd-server doesn't honor the per-request `sample_steps`/`steps`
|
|
48
|
+
// override for these models, so without this the server falls back to
|
|
49
|
+
// its built-in 20-step default — 5× the render time for no quality
|
|
50
|
+
// gain, and on CPU that overruns the generate timeout entirely.
|
|
51
|
+
['krea-2-turbo-q4', 8],
|
|
52
|
+
['krea-2-turbo-q6', 8],
|
|
53
|
+
['krea-2-turbo-q8', 8],
|
|
54
|
+
['ming-image-0.1-design-bf16', 12],
|
|
55
|
+
['moss-image-sft-q4', 50],
|
|
56
|
+
// The publisher recommends 100; 50 renders the same at half the time
|
|
57
|
+
// (300 s to 148 s per 1024px image on a DGX Spark, image bench 2026-10-19).
|
|
58
|
+
['iris-3b-f32', 50],
|
|
59
|
+
]);
|
|
60
|
+
/**
|
|
61
|
+
* Classifier-free guidance (CFG) scale per model, applied to the
|
|
62
|
+
* `/sdapi/v1/txt2img` request. Distilled / Flux-flow models are trained
|
|
63
|
+
* to run at CFG 1 — their guidance is baked in (turbo/lightning) or
|
|
64
|
+
* carried by the separate Flux distilled-guidance value. Applying
|
|
65
|
+
* sd-server's higher default CFG to them blows out color and contrast
|
|
66
|
+
* (the oversaturated, "burned" look Krea 2 Turbo had before this).
|
|
67
|
+
* Models absent from the map (traditional SD 1.x / SDXL checkpoints)
|
|
68
|
+
* omit `cfg_scale` and inherit the server default, preserving their
|
|
69
|
+
* established behavior.
|
|
70
|
+
*/
|
|
71
|
+
export const MODEL_CFG_DEFAULTS = new Map([
|
|
72
|
+
['krea-2-turbo-q4', 1],
|
|
73
|
+
['krea-2-turbo-q6', 1],
|
|
74
|
+
['krea-2-turbo-q8', 1],
|
|
75
|
+
['flux-1-schnell-q4', 1],
|
|
76
|
+
['flux-1-dev-q4', 1],
|
|
77
|
+
['flux-2-klein-4b-q4', 1],
|
|
78
|
+
['sd-turbo', 1],
|
|
79
|
+
['sdxl-turbo', 1],
|
|
80
|
+
['sdxl-lightning-4step', 1],
|
|
81
|
+
['ming-image-0.1-design-bf16', 1],
|
|
82
|
+
['moss-image-sft-q4', 3],
|
|
83
|
+
['iris-3b-f32', 3],
|
|
84
|
+
// The model card's recommendation; the server's 7 over-saturates it.
|
|
85
|
+
['animagine-xl-4', 5],
|
|
86
|
+
]);
|
|
87
|
+
/**
|
|
88
|
+
* Whether the engine runs this model with guidance that reads a negative
|
|
89
|
+
* prompt. At CFG 1 the unconditional pass that carries it is skipped.
|
|
90
|
+
* Models absent from the map run at the server's default of 7.
|
|
91
|
+
*/
|
|
92
|
+
export function sdCppHonorsNegativePrompt(modelId) {
|
|
93
|
+
const cfg = MODEL_CFG_DEFAULTS.get(modelId);
|
|
94
|
+
return cfg === undefined || cfg > 1;
|
|
95
|
+
}
|
|
96
|
+
/** Pin samplers validated with the native engine for these models. */
|
|
97
|
+
export const MODEL_SAMPLER_DEFAULTS = new Map([
|
|
98
|
+
['ming-image-0.1-design-bf16', 'Euler'],
|
|
99
|
+
['moss-image-sft-q4', 'Euler'],
|
|
100
|
+
['iris-3b-f32', 'Euler'],
|
|
101
|
+
]);
|
|
102
|
+
/**
|
|
103
|
+
* Models whose VAE shows visible tile-boundary seams under
|
|
104
|
+
* stable-diffusion.cpp's `--vae-tiling` (Krea 2's Qwen-Image VAE is the
|
|
105
|
+
* known case — rainbow column streaks at ≤512px). `--vae-tiling` is a
|
|
106
|
+
* launch-time, size-agnostic flag: sd-server serves every request size
|
|
107
|
+
* from one process, so we can't gate it per-request on image size as
|
|
108
|
+
* we'd ideally like — we gate it per-model instead and skip it here.
|
|
109
|
+
* The trade-off is that very large renders of these models could hit a
|
|
110
|
+
* VAE allocation cap on memory-constrained GPUs; acceptable given these
|
|
111
|
+
* are high-VRAM models and seam-free output matters more.
|
|
112
|
+
*/
|
|
113
|
+
export const VAE_TILING_INCOMPATIBLE_MODELS = new Set([
|
|
114
|
+
'krea-2-turbo-q4',
|
|
115
|
+
'krea-2-turbo-q6',
|
|
116
|
+
'krea-2-turbo-q8',
|
|
117
|
+
]);
|
|
118
|
+
/**
|
|
119
|
+
* Models that render the same with EasyCache step reuse. MOSS took 205 s per
|
|
120
|
+
* 1024px image at 50 steps on a DGX Spark and 93 s with it, with renders a
|
|
121
|
+
* person could not tell apart (image bench, 2026-10-19). cache-dit and spectrum
|
|
122
|
+
* were as fast but changed text and detail.
|
|
123
|
+
*/
|
|
124
|
+
const EASYCACHE_MODELS = new Set(['moss-image-sft-q4']);
|
|
125
|
+
export function sdServerLoadProfile(modelId) {
|
|
126
|
+
const stepCache = EASYCACHE_MODELS.has(modelId) ? { stepCache: 'easycache' } : {};
|
|
127
|
+
if (modelId === 'iris-3b-f32') {
|
|
128
|
+
// Iris generates pixels directly: no VAE to tile. Upstream's Iris recipe
|
|
129
|
+
// enables diffusion flash attention for the 1024px sequence. The
|
|
130
|
+
// publisher stores F32 but runs with BF16 autocast, so only the diffusion
|
|
131
|
+
// weights convert at load, preserving the quantized encoder.
|
|
132
|
+
return {
|
|
133
|
+
vaeDecode: 'none',
|
|
134
|
+
defaultSidePx: imageModelDefaultSide(modelId),
|
|
135
|
+
bf16TensorPattern: '^model\\.diffusion_model\\.',
|
|
136
|
+
diffusionFlashAttention: true,
|
|
137
|
+
...stepCache,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// Tiling avoids VAE-decode OOM on large images, but produces visible
|
|
141
|
+
// tile-boundary seams with some VAEs — see VAE_TILING_INCOMPATIBLE_MODELS.
|
|
142
|
+
return {
|
|
143
|
+
vaeDecode: VAE_TILING_INCOMPATIBLE_MODELS.has(modelId) ? 'untiled' : 'tiled',
|
|
144
|
+
defaultSidePx: imageModelDefaultSide(modelId),
|
|
145
|
+
diffusionFlashAttention: false,
|
|
146
|
+
...stepCache,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
export class StableDiffusionCppProvider {
|
|
150
|
+
name = 'sd-cpp';
|
|
151
|
+
baseUrl;
|
|
152
|
+
modelsRoot;
|
|
153
|
+
storageRoots;
|
|
154
|
+
timeoutMs;
|
|
155
|
+
fetchImpl;
|
|
156
|
+
supervisor;
|
|
157
|
+
configured;
|
|
158
|
+
arbiter;
|
|
159
|
+
launchState;
|
|
160
|
+
defaultModelId;
|
|
161
|
+
renderTail = Promise.resolve();
|
|
162
|
+
pendingRenders = 0;
|
|
163
|
+
constructor(opts) {
|
|
164
|
+
this.baseUrl = opts.baseUrl.replace(/\/+$/, '');
|
|
165
|
+
this.modelsRoot = opts.modelsRoot;
|
|
166
|
+
this.storageRoots = opts.storageRoots ?? {
|
|
167
|
+
writableRoot: opts.modelsRoot,
|
|
168
|
+
readOnlyRoots: [],
|
|
169
|
+
};
|
|
170
|
+
this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
171
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
172
|
+
this.supervisor = opts.supervisor;
|
|
173
|
+
// Default true — historically the provider had no concept of
|
|
174
|
+
// "unconfigured", and most call sites pass an explicit URL or
|
|
175
|
+
// supervisor. The factory's loopback fallback flips this off.
|
|
176
|
+
this.configured = opts.configured ?? true;
|
|
177
|
+
this.launchState = opts.launchState;
|
|
178
|
+
this.defaultModelId = opts.defaultModelId;
|
|
179
|
+
// Only register an evictor when we own a supervisor. External-URL
|
|
180
|
+
// mode targets an sd-server the user runs themselves; we don't
|
|
181
|
+
// get to stop their process.
|
|
182
|
+
if (opts.arbiter && opts.supervisor) {
|
|
183
|
+
this.arbiter = opts.arbiter;
|
|
184
|
+
this.arbiter.registerEvictor('image', () => opts.supervisor.stop());
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async generate(input) {
|
|
188
|
+
if (input.signal?.aborted)
|
|
189
|
+
throw new ImageGenerationCancelledError();
|
|
190
|
+
// One render at a time: sd-server holds one model and renders serially,
|
|
191
|
+
// and cancelling a running render stops the engine, which must never
|
|
192
|
+
// take a second caller's render down with it. Each caller waits for
|
|
193
|
+
// every earlier caller, including one that left the queue early.
|
|
194
|
+
const previous = this.renderTail;
|
|
195
|
+
let release;
|
|
196
|
+
const mine = new Promise((resolve) => {
|
|
197
|
+
release = resolve;
|
|
198
|
+
});
|
|
199
|
+
this.renderTail = previous.then(() => mine);
|
|
200
|
+
const waiting = this.pendingRenders++ > 0;
|
|
201
|
+
try {
|
|
202
|
+
if (waiting) {
|
|
203
|
+
emitProgress(input, { phase: 'queued', step: 0, totalSteps: 0 });
|
|
204
|
+
await untilTurn(previous, input.signal);
|
|
205
|
+
}
|
|
206
|
+
return this.supervisor
|
|
207
|
+
? await this.supervisor.withRequest(() => this.generateInner(input), input.signal)
|
|
208
|
+
: await this.generateInner(input);
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
if (input.signal?.aborted && !(err instanceof ImageGenerationCancelledError)) {
|
|
212
|
+
throw new ImageGenerationCancelledError();
|
|
213
|
+
}
|
|
214
|
+
throw err;
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
this.pendingRenders--;
|
|
218
|
+
release();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async generateInner(input) {
|
|
222
|
+
const { signal } = input;
|
|
223
|
+
if (signal?.aborted)
|
|
224
|
+
throw new ImageGenerationCancelledError();
|
|
225
|
+
const modelId = input.model ?? (await this.resolveDefaultModelId());
|
|
226
|
+
const defaultDimension = imageModelDefaultSide(modelId);
|
|
227
|
+
const width = input.width ?? defaultDimension;
|
|
228
|
+
const height = input.height ?? defaultDimension;
|
|
229
|
+
const steps = input.steps ?? (await this.defaultSampleSteps(modelId));
|
|
230
|
+
const seed = input.seed ?? Math.floor(Math.random() * 2_147_483_647);
|
|
231
|
+
const started = Date.now();
|
|
232
|
+
log.info(`[sd-cpp] generate model=${modelId ?? 'default'} steps=${steps} explicitSteps=${input.steps !== undefined} size=${width}x${height}`);
|
|
233
|
+
// Model selection: sd-server serves one model per process. When a
|
|
234
|
+
// request targets a different installed model than the one currently
|
|
235
|
+
// loaded, point resolveLaunch at it (via the shared launchState) and
|
|
236
|
+
// stop the running server so the next ensureRunning relaunches with
|
|
237
|
+
// the requested model — instead of always serving whichever model
|
|
238
|
+
// sorts first. A no-op on the first generate (nothing running yet).
|
|
239
|
+
if (this.supervisor && this.launchState && modelId && this.launchState.modelId !== modelId) {
|
|
240
|
+
this.launchState.modelId = modelId;
|
|
241
|
+
await this.supervisor.stop();
|
|
242
|
+
}
|
|
243
|
+
// A render the caller abandons keeps running inside sd-server, which
|
|
244
|
+
// cannot interrupt one it has started, so cancelling stops the engine.
|
|
245
|
+
const request = new AbortController();
|
|
246
|
+
const stopRender = () => {
|
|
247
|
+
request.abort();
|
|
248
|
+
if (this.supervisor)
|
|
249
|
+
void this.supervisor.stop().catch(() => { });
|
|
250
|
+
};
|
|
251
|
+
signal?.addEventListener('abort', stopRender, { once: true });
|
|
252
|
+
let baseUrl = this.baseUrl;
|
|
253
|
+
// Engine log lines drive progress (sd-server's `|==> | 3/20 - 18.20s/it`
|
|
254
|
+
// bars) and explain a failed render. Only meaningful when we own the
|
|
255
|
+
// supervisor (external-URL mode has no log stream).
|
|
256
|
+
const recentLines = [];
|
|
257
|
+
let unsubscribeLogs;
|
|
258
|
+
if (this.supervisor) {
|
|
259
|
+
const track = input.onProgress ? progressTracker((p) => emitProgress(input, p)) : undefined;
|
|
260
|
+
unsubscribeLogs = this.supervisor.subscribeLogLines((line) => {
|
|
261
|
+
recentLines.push(line);
|
|
262
|
+
if (recentLines.length > RECENT_ENGINE_LINES)
|
|
263
|
+
recentLines.shift();
|
|
264
|
+
track?.(line);
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
let releaseGpu;
|
|
268
|
+
let timeout;
|
|
269
|
+
try {
|
|
270
|
+
if (this.supervisor) {
|
|
271
|
+
// Acquire the GPU slot BEFORE asking the supervisor to start.
|
|
272
|
+
// In `swap` policy this evicts a running llama-server so its
|
|
273
|
+
// VRAM is freed by the time sd-server tries to load weights.
|
|
274
|
+
// In `coexist` policy the call returns immediately. The arbiter
|
|
275
|
+
// is only set on the supervised path (see constructor).
|
|
276
|
+
if (this.arbiter)
|
|
277
|
+
releaseGpu = await this.arbiter.acquireLease('image');
|
|
278
|
+
if (!this.supervisor.currentBaseUrl()) {
|
|
279
|
+
emitProgress(input, { phase: 'loading_model', step: 0, totalSteps: 0 });
|
|
280
|
+
}
|
|
281
|
+
const launch = await this.supervisor.ensureRunning();
|
|
282
|
+
baseUrl = launch.baseUrl.replace(/\/+$/, '');
|
|
283
|
+
}
|
|
284
|
+
if (signal?.aborted)
|
|
285
|
+
throw new ImageGenerationCancelledError();
|
|
286
|
+
// Drive sd-server's A1111-compatible endpoints (`/sdapi/v1/txt2img`
|
|
287
|
+
// and `/sdapi/v1/img2img`). Unlike the OpenAI `/v1/images/*` shape
|
|
288
|
+
// — which reads only prompt/n/size and takes steps/seed/cfg from the
|
|
289
|
+
// server's launch defaults — these honor per-request `steps`,
|
|
290
|
+
// `seed`, `cfg_scale`, and `negative_prompt`, so caller overrides
|
|
291
|
+
// actually take effect. `cfg_scale` is set per model (distilled /
|
|
292
|
+
// Flux-flow models need CFG 1; others inherit the server default).
|
|
293
|
+
const cfg = modelId ? MODEL_CFG_DEFAULTS.get(modelId) : undefined;
|
|
294
|
+
let inputImages = input.inputImages ?? [];
|
|
295
|
+
// Capability gate: models whose architecture doesn't honor init
|
|
296
|
+
// latents on the pinned sd-server fall back to txt2img instead of
|
|
297
|
+
// silently producing an "edit" that never saw the source. The skip
|
|
298
|
+
// is reported on meta so every caller (route, MCP tool, chat
|
|
299
|
+
// bubble) can say so honestly.
|
|
300
|
+
let img2imgSkippedReason;
|
|
301
|
+
if (inputImages.length > 0) {
|
|
302
|
+
const installed = await this.readInstalledManifestFields(modelId);
|
|
303
|
+
const verdict = resolveImg2ImgSupport({
|
|
304
|
+
modelId,
|
|
305
|
+
explicit: installed?.supportsImg2Img,
|
|
306
|
+
weightsKind: installed?.weightsKind,
|
|
307
|
+
});
|
|
308
|
+
if (!verdict.supported) {
|
|
309
|
+
img2imgSkippedReason = verdict.reason ?? 'model does not support image editing (img2img)';
|
|
310
|
+
log.info(`[sd-cpp] dropping ${inputImages.length} input image(s): ${img2imgSkippedReason}`);
|
|
311
|
+
inputImages = [];
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const body = {
|
|
315
|
+
prompt: input.prompt,
|
|
316
|
+
negative_prompt: input.negativePrompt ?? '',
|
|
317
|
+
width,
|
|
318
|
+
height,
|
|
319
|
+
steps,
|
|
320
|
+
seed,
|
|
321
|
+
batch_size: 1,
|
|
322
|
+
};
|
|
323
|
+
if (cfg !== undefined)
|
|
324
|
+
body.cfg_scale = cfg;
|
|
325
|
+
const sampler = modelId ? MODEL_SAMPLER_DEFAULTS.get(modelId) : undefined;
|
|
326
|
+
if (sampler)
|
|
327
|
+
body.sampler_name = sampler;
|
|
328
|
+
let endpoint = `${baseUrl}/sdapi/v1/txt2img`;
|
|
329
|
+
// Img2img: a single source image drives `/sdapi/v1/img2img` via
|
|
330
|
+
// `init_images` (base64). stable-diffusion.cpp's img2img path takes
|
|
331
|
+
// one image, so we use the first and warn for any extras.
|
|
332
|
+
if (inputImages.length > 0) {
|
|
333
|
+
if (inputImages.length > 1) {
|
|
334
|
+
log.info(`[sd-cpp] received ${inputImages.length} input images; img2img uses the first only`);
|
|
335
|
+
}
|
|
336
|
+
const first = inputImages[0];
|
|
337
|
+
body.init_images = [Buffer.from(first.data).toString('base64')];
|
|
338
|
+
if (typeof input.strength === 'number')
|
|
339
|
+
body.denoising_strength = input.strength;
|
|
340
|
+
endpoint = `${baseUrl}/sdapi/v1/img2img`;
|
|
341
|
+
}
|
|
342
|
+
// The budget counts awake time only: a render that spans a laptop nap
|
|
343
|
+
// has not used the time the nap took.
|
|
344
|
+
timeout = createAwakeTimeout(this.timeoutMs);
|
|
345
|
+
const deadline = timeout.signal;
|
|
346
|
+
deadline.addEventListener('abort', stopRender, { once: true });
|
|
347
|
+
let res;
|
|
348
|
+
try {
|
|
349
|
+
res = await this.fetchImpl(endpoint, {
|
|
350
|
+
method: 'POST',
|
|
351
|
+
headers: { 'Content-Type': 'application/json' },
|
|
352
|
+
body: JSON.stringify(body),
|
|
353
|
+
signal: request.signal,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
if (signal?.aborted)
|
|
358
|
+
throw new ImageGenerationCancelledError();
|
|
359
|
+
if (deadline.aborted) {
|
|
360
|
+
throw new Error(`sd-server did not respond within ${this.timeoutMs / 1000}s`);
|
|
361
|
+
}
|
|
362
|
+
throw new Error(`sd-server unreachable at ${baseUrl}. Is the image engine enabled in Settings? (${err instanceof Error ? err.message : String(err)})`);
|
|
363
|
+
}
|
|
364
|
+
if (!res.ok) {
|
|
365
|
+
const text = await res.text().catch(() => '');
|
|
366
|
+
const shortfall = recentLines.find((line) => ENGINE_MEMORY_SHORTFALL.test(line));
|
|
367
|
+
if (shortfall) {
|
|
368
|
+
// The engine keeps the weights it loaded; stop it so the next
|
|
369
|
+
// launch is admitted against what is actually free.
|
|
370
|
+
if (this.supervisor)
|
|
371
|
+
await this.supervisor.stop().catch(() => { });
|
|
372
|
+
throw new ImageCapacityError(shortfall
|
|
373
|
+
.replace(/^\[[^\]]+\]\s*/, '')
|
|
374
|
+
.trim()
|
|
375
|
+
.slice(0, 200));
|
|
376
|
+
}
|
|
377
|
+
throw new Error(`sd-server returned ${res.status} ${res.statusText}: ${text.slice(0, 300)}`);
|
|
378
|
+
}
|
|
379
|
+
// A1111 txt2img/img2img return `{ images: [<base64 PNG>], … }`.
|
|
380
|
+
const payload = (await res.json());
|
|
381
|
+
const b64 = payload.images?.[0];
|
|
382
|
+
if (!b64)
|
|
383
|
+
throw new Error('sd-server response missing images[0]');
|
|
384
|
+
const png = Buffer.from(b64, 'base64');
|
|
385
|
+
// The engine rounds a size its network cannot take (SDXL to 64), so the
|
|
386
|
+
// picture's own header is the size the person got.
|
|
387
|
+
const rendered = readImageMeta(png);
|
|
388
|
+
return {
|
|
389
|
+
png,
|
|
390
|
+
meta: {
|
|
391
|
+
model: modelId ?? 'unknown',
|
|
392
|
+
seed,
|
|
393
|
+
steps,
|
|
394
|
+
widthPx: rendered?.width || width,
|
|
395
|
+
heightPx: rendered?.height || height,
|
|
396
|
+
durationMs: Date.now() - started,
|
|
397
|
+
...(img2imgSkippedReason ? { img2imgSkippedReason } : {}),
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
catch (err) {
|
|
402
|
+
if (signal?.aborted && !(err instanceof ImageGenerationCancelledError)) {
|
|
403
|
+
throw new ImageGenerationCancelledError();
|
|
404
|
+
}
|
|
405
|
+
throw err;
|
|
406
|
+
}
|
|
407
|
+
finally {
|
|
408
|
+
timeout?.dispose();
|
|
409
|
+
signal?.removeEventListener('abort', stopRender);
|
|
410
|
+
this.supervisor?.markUsed();
|
|
411
|
+
unsubscribeLogs?.();
|
|
412
|
+
releaseGpu?.();
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
async listInstalledModels() {
|
|
416
|
+
const entries = await listOverlayModelIds(this.storageRoots);
|
|
417
|
+
const out = [];
|
|
418
|
+
for (const id of entries) {
|
|
419
|
+
const root = await findModelRoot(this.storageRoots, id);
|
|
420
|
+
if (!root)
|
|
421
|
+
continue;
|
|
422
|
+
const metaPath = join(root, id, 'manifest.json');
|
|
423
|
+
try {
|
|
424
|
+
const raw = await readFile(metaPath, 'utf8');
|
|
425
|
+
const parsed = JSON.parse(raw);
|
|
426
|
+
if (!parsed.id || !parsed.name || !parsed.approxSizeBytes || !parsed.installedAt)
|
|
427
|
+
continue;
|
|
428
|
+
if (!(await verifyReadOnlyModelPayload(this.storageRoots, root, id, parsed.fileSha256))) {
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
out.push({
|
|
432
|
+
id: parsed.id,
|
|
433
|
+
name: parsed.name,
|
|
434
|
+
approxSizeBytes: parsed.approxSizeBytes,
|
|
435
|
+
installedAt: parsed.installedAt,
|
|
436
|
+
...(parsed.weightsKind ? { weightsKind: parsed.weightsKind } : {}),
|
|
437
|
+
...(typeof parsed.supportsImg2Img === 'boolean'
|
|
438
|
+
? { supportsImg2Img: parsed.supportsImg2Img }
|
|
439
|
+
: {}),
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
/* skip malformed entries */
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
out.sort((a, b) => a.name.localeCompare(b.name));
|
|
447
|
+
return out;
|
|
448
|
+
}
|
|
449
|
+
async *pullModel(id, spec, signal) {
|
|
450
|
+
const itemDir = join(this.modelsRoot, id);
|
|
451
|
+
await mkdir(this.modelsRoot, { recursive: true });
|
|
452
|
+
await assertModelStorePathSafe(this.modelsRoot, itemDir);
|
|
453
|
+
await mkdir(itemDir, { recursive: true });
|
|
454
|
+
await assertModelStorePathSafe(this.modelsRoot, itemDir);
|
|
455
|
+
// Total bytes across the unet + every auxiliary file. Driving the UI
|
|
456
|
+
// progress bar from this aggregate keeps multi-file pulls visually
|
|
457
|
+
// identical to single-file ones.
|
|
458
|
+
const totalAllBytes = spec.approxSizeBytes + spec.auxiliaryFiles.reduce((n, f) => n + f.approxSizeBytes, 0);
|
|
459
|
+
let writtenAll = 0;
|
|
460
|
+
const weightsFilename = extractFilename(spec.downloadUrl);
|
|
461
|
+
const weightsPath = join(itemDir, weightsFilename);
|
|
462
|
+
const downloadResult = yield* this.downloadFile({
|
|
463
|
+
url: spec.downloadUrl,
|
|
464
|
+
destPath: weightsPath,
|
|
465
|
+
expectedSha256: spec.sha256,
|
|
466
|
+
approxSizeBytes: spec.approxSizeBytes,
|
|
467
|
+
writtenSoFar: writtenAll,
|
|
468
|
+
totalAllBytes,
|
|
469
|
+
...(signal ? { signal } : {}),
|
|
470
|
+
});
|
|
471
|
+
if (downloadResult.kind === 'error') {
|
|
472
|
+
yield { type: 'error', error: downloadResult.error };
|
|
473
|
+
yield { type: 'done', id };
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
writtenAll = downloadResult.writtenAll;
|
|
477
|
+
const weightsActualSize = downloadResult.actualSize;
|
|
478
|
+
// Auxiliary files (VAE / clip_l / clip_g / t5xxl). Streamed sequentially
|
|
479
|
+
// so we don't fight the host's bandwidth across N parallel HF connections.
|
|
480
|
+
const auxiliaryRecords = [];
|
|
481
|
+
for (const aux of spec.auxiliaryFiles) {
|
|
482
|
+
const auxFilename = auxFilenameFor(aux);
|
|
483
|
+
const auxPath = join(itemDir, auxFilename);
|
|
484
|
+
const auxResult = yield* this.downloadFile({
|
|
485
|
+
url: aux.downloadUrl,
|
|
486
|
+
destPath: auxPath,
|
|
487
|
+
expectedSha256: aux.sha256,
|
|
488
|
+
approxSizeBytes: aux.approxSizeBytes,
|
|
489
|
+
writtenSoFar: writtenAll,
|
|
490
|
+
totalAllBytes,
|
|
491
|
+
...(signal ? { signal } : {}),
|
|
492
|
+
});
|
|
493
|
+
if (auxResult.kind === 'error') {
|
|
494
|
+
yield { type: 'error', error: `${aux.role}: ${auxResult.error}` };
|
|
495
|
+
yield { type: 'done', id };
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
writtenAll = auxResult.writtenAll;
|
|
499
|
+
auxiliaryRecords.push({ role: aux.role, filename: auxFilename });
|
|
500
|
+
}
|
|
501
|
+
const fileSha256 = await hashModelPayloadFiles(itemDir);
|
|
502
|
+
await writeFile(join(itemDir, 'manifest.json'), JSON.stringify({
|
|
503
|
+
id,
|
|
504
|
+
name: spec.name,
|
|
505
|
+
approxSizeBytes: totalAllBytes,
|
|
506
|
+
weightsFilename,
|
|
507
|
+
weightsKind: spec.weightsKind,
|
|
508
|
+
...(spec.supportsImg2Img !== undefined ? { supportsImg2Img: spec.supportsImg2Img } : {}),
|
|
509
|
+
weightsSizeBytes: weightsActualSize,
|
|
510
|
+
sha256: spec.sha256.toLowerCase(),
|
|
511
|
+
auxiliaryFiles: auxiliaryRecords,
|
|
512
|
+
fileSha256,
|
|
513
|
+
installedAt: new Date().toISOString(),
|
|
514
|
+
}, null, 2), 'utf8');
|
|
515
|
+
await makeSharedModelReadable(itemDir);
|
|
516
|
+
yield { type: 'progress', bytesWritten: writtenAll, totalBytes: totalAllBytes };
|
|
517
|
+
yield { type: 'done', id };
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Stream a single URL to disk with sha256 verification, reporting cumulative
|
|
521
|
+
* progress against `totalAllBytes` so multi-file pulls share one bar.
|
|
522
|
+
*
|
|
523
|
+
* Delegates the network plumbing to `downloadWithRetry` — that handles
|
|
524
|
+
* Range-based resume, exponential backoff, chunk-stall timeouts, and
|
|
525
|
+
* friendly error messages. This wrapper only owns the
|
|
526
|
+
* provider-specific concerns: sha256 verification, the
|
|
527
|
+
* `writtenSoFar`-aware progress translation, and the atomic rename
|
|
528
|
+
* after verify.
|
|
529
|
+
*/
|
|
530
|
+
async *downloadFile(opts) {
|
|
531
|
+
const { url, destPath, expectedSha256, approxSizeBytes, totalAllBytes, signal } = opts;
|
|
532
|
+
const baseWritten = opts.writtenSoFar;
|
|
533
|
+
const gen = downloadWithRetry({
|
|
534
|
+
url,
|
|
535
|
+
destPath,
|
|
536
|
+
approxSizeBytes,
|
|
537
|
+
fetchImpl: this.fetchImpl,
|
|
538
|
+
...(signal ? { signal } : {}),
|
|
539
|
+
});
|
|
540
|
+
let writtenThisFile = 0;
|
|
541
|
+
while (true) {
|
|
542
|
+
const next = await gen.next();
|
|
543
|
+
if (next.done) {
|
|
544
|
+
if (next.value.kind === 'ok') {
|
|
545
|
+
writtenThisFile = next.value.bytesWritten;
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
if (next.value.kind === 'aborted') {
|
|
549
|
+
// Leave .partial in place so a follow-up pull resumes.
|
|
550
|
+
return { kind: 'error', error: 'download aborted' };
|
|
551
|
+
}
|
|
552
|
+
return { kind: 'error', error: next.value.error };
|
|
553
|
+
}
|
|
554
|
+
const ev = next.value;
|
|
555
|
+
if (ev.type === 'progress') {
|
|
556
|
+
// Reproject per-file progress onto the multi-file aggregate.
|
|
557
|
+
writtenThisFile = ev.bytesWritten;
|
|
558
|
+
yield {
|
|
559
|
+
type: 'progress',
|
|
560
|
+
bytesWritten: baseWritten + ev.bytesWritten,
|
|
561
|
+
totalBytes: totalAllBytes,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
else if (ev.type === 'retrying') {
|
|
565
|
+
yield {
|
|
566
|
+
type: 'retrying',
|
|
567
|
+
attempt: ev.attempt,
|
|
568
|
+
maxAttempts: ev.maxAttempts,
|
|
569
|
+
delayMs: ev.delayMs,
|
|
570
|
+
reason: ev.reason,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// sha256 check — stream the .partial through the hasher rather than
|
|
575
|
+
// hashing while we write. Slightly more disk I/O on success, but it
|
|
576
|
+
// means the hasher stays correct across resume (Range requests
|
|
577
|
+
// would otherwise corrupt a per-chunk hasher with mid-file resumed
|
|
578
|
+
// bytes).
|
|
579
|
+
const partialPath = `${destPath}.partial`;
|
|
580
|
+
const hasher = createHash('sha256');
|
|
581
|
+
await new Promise((resolve, reject) => {
|
|
582
|
+
// Big read buffer so the hash doesn't starve in the busy daemon event
|
|
583
|
+
// loop. See MODEL_HASH_READ_BUFFER_BYTES.
|
|
584
|
+
const stream = createReadStream(partialPath, {
|
|
585
|
+
highWaterMark: MODEL_HASH_READ_BUFFER_BYTES,
|
|
586
|
+
});
|
|
587
|
+
stream.on('data', (chunk) => hasher.update(chunk));
|
|
588
|
+
stream.on('end', () => resolve());
|
|
589
|
+
stream.on('error', reject);
|
|
590
|
+
});
|
|
591
|
+
const actual = hasher.digest('hex');
|
|
592
|
+
if (actual !== expectedSha256.toLowerCase()) {
|
|
593
|
+
await rm(partialPath, { force: true });
|
|
594
|
+
return {
|
|
595
|
+
kind: 'error',
|
|
596
|
+
error: `sha256 mismatch: expected ${expectedSha256}, got ${actual}`,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
await rm(destPath, { force: true });
|
|
600
|
+
await rename(partialPath, destPath);
|
|
601
|
+
return { kind: 'ok', writtenAll: baseWritten + writtenThisFile, actualSize: writtenThisFile };
|
|
602
|
+
}
|
|
603
|
+
async deleteModel(id) {
|
|
604
|
+
const itemDir = resolveModelDirectory(this.modelsRoot, id);
|
|
605
|
+
if (await modelExistsOnlyReadOnly(this.storageRoots, id)) {
|
|
606
|
+
throw readOnlyModelError(id);
|
|
607
|
+
}
|
|
608
|
+
await rm(itemDir, { recursive: true, force: true });
|
|
609
|
+
}
|
|
610
|
+
async health() {
|
|
611
|
+
// The factory's default-loopback branch flips `configured: false` so
|
|
612
|
+
// we can return a distinct state instead of confusing the user with
|
|
613
|
+
// a generic "unreachable" — the engine isn't unreachable, it was
|
|
614
|
+
// never wired up. Without a binary or external URL, the provider
|
|
615
|
+
// points at 127.0.0.1:9081 by default; nothing's going to answer.
|
|
616
|
+
if (!this.configured) {
|
|
617
|
+
return {
|
|
618
|
+
status: 'not-configured',
|
|
619
|
+
baseUrl: this.baseUrl,
|
|
620
|
+
error: 'The image engine is not installed yet. Install it with `gezel native install` ' +
|
|
621
|
+
'(or from the desktop app), or set GEZEL_SD_SERVER_URL to a running sd-server.',
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
// When a supervisor is wired the engine is "configured but lazy" —
|
|
625
|
+
// the binary will spawn on the first generate. We could call
|
|
626
|
+
// ensureRunning() here, but probing eagerly defeats the lazy-start
|
|
627
|
+
// pattern and would cost a cold-start every time the user opens
|
|
628
|
+
// Settings. Surface ok and rely on the supervisor's internal
|
|
629
|
+
// health-watch.
|
|
630
|
+
if (this.supervisor) {
|
|
631
|
+
return { status: 'ok', baseUrl: this.baseUrl };
|
|
632
|
+
}
|
|
633
|
+
// Probe the configured URL with a short HEAD-style fetch. Most
|
|
634
|
+
// sd-server builds answer the root with a quick 404; what we care
|
|
635
|
+
// about is whether anything's listening at all. A network-level
|
|
636
|
+
// throw (ECONNREFUSED) is the unreachable signal.
|
|
637
|
+
const ctrl = new AbortController();
|
|
638
|
+
const timer = setTimeout(() => ctrl.abort(), 2000);
|
|
639
|
+
try {
|
|
640
|
+
const res = await this.fetchImpl(`${this.baseUrl}/`, {
|
|
641
|
+
method: 'GET',
|
|
642
|
+
signal: ctrl.signal,
|
|
643
|
+
});
|
|
644
|
+
// Any HTTP response — 200, 404, 405 — proves something is listening.
|
|
645
|
+
void res.body?.cancel?.().catch(() => { });
|
|
646
|
+
return { status: 'ok', baseUrl: this.baseUrl };
|
|
647
|
+
}
|
|
648
|
+
catch (err) {
|
|
649
|
+
const message = ctrl.signal.aborted
|
|
650
|
+
? `sd-server did not respond within 2s at ${this.baseUrl}`
|
|
651
|
+
: err instanceof Error
|
|
652
|
+
? err.message
|
|
653
|
+
: String(err);
|
|
654
|
+
return {
|
|
655
|
+
status: 'unreachable',
|
|
656
|
+
baseUrl: this.baseUrl,
|
|
657
|
+
error: message,
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
finally {
|
|
661
|
+
clearTimeout(timer);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
async shutdown() {
|
|
665
|
+
await this.supervisor?.stop();
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* The configured default when it is installed, else the first installed
|
|
669
|
+
* model id, else undefined. Stable ordering matches
|
|
670
|
+
* {@link listInstalledModels} so the default is deterministic.
|
|
671
|
+
*/
|
|
672
|
+
async resolveDefaultModelId() {
|
|
673
|
+
const models = await this.listInstalledModels();
|
|
674
|
+
if (this.defaultModelId && models.some((m) => m.id === this.defaultModelId)) {
|
|
675
|
+
return this.defaultModelId;
|
|
676
|
+
}
|
|
677
|
+
return models[0]?.id;
|
|
678
|
+
}
|
|
679
|
+
async defaultSampleSteps(modelId) {
|
|
680
|
+
if (!modelId)
|
|
681
|
+
return DEFAULT_SAMPLE_STEPS;
|
|
682
|
+
const fields = await this.readInstalledManifestFields(modelId);
|
|
683
|
+
if (typeof fields?.recommendedSteps === 'number' &&
|
|
684
|
+
Number.isInteger(fields.recommendedSteps) &&
|
|
685
|
+
fields.recommendedSteps > 0) {
|
|
686
|
+
return fields.recommendedSteps;
|
|
687
|
+
}
|
|
688
|
+
return MODEL_SAMPLE_STEP_DEFAULTS.get(modelId) ?? DEFAULT_SAMPLE_STEPS;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Read the installed `manifest.json` fields that steer per-model
|
|
692
|
+
* behavior. Returns null for unknown ids and unreadable/legacy
|
|
693
|
+
* metadata — callers fall back to their per-field defaults. (Older
|
|
694
|
+
* installed metadata did not persist catalog generation defaults or
|
|
695
|
+
* the img2img capability.)
|
|
696
|
+
*/
|
|
697
|
+
async readInstalledManifestFields(modelId) {
|
|
698
|
+
if (!modelId)
|
|
699
|
+
return null;
|
|
700
|
+
try {
|
|
701
|
+
const root = await findModelRoot(this.storageRoots, modelId);
|
|
702
|
+
if (!root)
|
|
703
|
+
return null;
|
|
704
|
+
const raw = await readFile(join(root, modelId, 'manifest.json'), 'utf8');
|
|
705
|
+
const parsed = JSON.parse(raw);
|
|
706
|
+
return {
|
|
707
|
+
...(typeof parsed.recommendedSteps === 'number'
|
|
708
|
+
? { recommendedSteps: parsed.recommendedSteps }
|
|
709
|
+
: {}),
|
|
710
|
+
...(parsed.weightsKind === 'checkpoint' || parsed.weightsKind === 'diffusion-model'
|
|
711
|
+
? { weightsKind: parsed.weightsKind }
|
|
712
|
+
: {}),
|
|
713
|
+
...(typeof parsed.supportsImg2Img === 'boolean'
|
|
714
|
+
? { supportsImg2Img: parsed.supportsImg2Img }
|
|
715
|
+
: {}),
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
catch {
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Parse a single line of sd-server's sampling progress output. The
|
|
725
|
+
* upstream stable-diffusion.cpp server prints lines shaped like:
|
|
726
|
+
*
|
|
727
|
+
* |==> | 1/20 - 18.20s/it
|
|
728
|
+
* |====> | 2/25 - 7.18it/s
|
|
729
|
+
*
|
|
730
|
+
* sometimes with a leading log prefix from our supervisor ("[sd-server] ").
|
|
731
|
+
* The rate flips to `it/s` once a step takes under a second, which on a
|
|
732
|
+
* fast GPU is every step of a turbo or SD 1.5 render. Returns the extracted
|
|
733
|
+
* step / total / per-step seconds when the line matches; otherwise null.
|
|
734
|
+
* Anchored on the fraction-with-suffix shape (`N/M - X.Ys/it`) so unrelated
|
|
735
|
+
* log noise, including the `MB/s` weight-load bar, can never trip a false
|
|
736
|
+
* match.
|
|
737
|
+
*/
|
|
738
|
+
/** Engine lines kept while a render runs, to explain a failure. */
|
|
739
|
+
const RECENT_ENGINE_LINES = 40;
|
|
740
|
+
/**
|
|
741
|
+
* sd-server's report that the render did not fit in graphics memory. It
|
|
742
|
+
* answers the request with a bare `generate_image returned no results`.
|
|
743
|
+
*/
|
|
744
|
+
const ENGINE_MEMORY_SHORTFALL = /cannot make enough memory available|out of (?:device )?memory|ErrorOutOfDeviceMemory|failed to allocate/i;
|
|
745
|
+
function emitProgress(input, progress) {
|
|
746
|
+
try {
|
|
747
|
+
input.onProgress?.(progress);
|
|
748
|
+
}
|
|
749
|
+
catch {
|
|
750
|
+
/* listener errors are not load-bearing — drop silently */
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
/** Resolve when `previous` settles, or reject when `signal` aborts first. */
|
|
754
|
+
function untilTurn(previous, signal) {
|
|
755
|
+
if (!signal)
|
|
756
|
+
return previous;
|
|
757
|
+
return new Promise((resolve, reject) => {
|
|
758
|
+
const onAbort = () => reject(new ImageGenerationCancelledError());
|
|
759
|
+
if (signal.aborted)
|
|
760
|
+
return onAbort();
|
|
761
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
762
|
+
void previous.then(() => {
|
|
763
|
+
signal.removeEventListener('abort', onAbort);
|
|
764
|
+
resolve();
|
|
765
|
+
});
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Parse one line of sd-server's weight-load bar, which it prints inside the
|
|
770
|
+
* first request after a launch because it loads weights lazily:
|
|
771
|
+
*
|
|
772
|
+
* |######### | 49/298 - 435.57MB/s
|
|
773
|
+
*/
|
|
774
|
+
export function parseWeightLoadProgress(line) {
|
|
775
|
+
const m = line.match(/(\d+)\s*\/\s*(\d+)\s*-\s*[\d.]+\s*(?:[KMG]?B)\/s/);
|
|
776
|
+
if (!m)
|
|
777
|
+
return null;
|
|
778
|
+
const step = Number.parseInt(m[1], 10);
|
|
779
|
+
const totalSteps = Number.parseInt(m[2], 10);
|
|
780
|
+
if (!Number.isFinite(step) || !Number.isFinite(totalSteps) || totalSteps <= 0)
|
|
781
|
+
return null;
|
|
782
|
+
return { phase: 'loading_model', step, totalSteps };
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Classify sd-server's progress bars into phases. Weight loads print a
|
|
786
|
+
* bytes-per-second bar. The first iteration bar is sampling; once it
|
|
787
|
+
* completes, the next is the tiled VAE decode, which reports tiles rather
|
|
788
|
+
* than steps. The step count the engine prints is used rather than the one
|
|
789
|
+
* requested, because some launch defaults override a request's steps.
|
|
790
|
+
*/
|
|
791
|
+
export function progressTracker(emit) {
|
|
792
|
+
let sampling = false;
|
|
793
|
+
let sampled = false;
|
|
794
|
+
return (line) => {
|
|
795
|
+
const load = parseWeightLoadProgress(line);
|
|
796
|
+
// The VAE's weights load lazily just before decoding; reporting that
|
|
797
|
+
// as loading the model would send the progress back a phase.
|
|
798
|
+
if (load)
|
|
799
|
+
return sampling ? undefined : emit(load);
|
|
800
|
+
const bar = parseSamplingProgress(line);
|
|
801
|
+
if (!bar)
|
|
802
|
+
return;
|
|
803
|
+
sampling = true;
|
|
804
|
+
emit({ ...bar, phase: sampled ? 'decoding' : 'sampling' });
|
|
805
|
+
if (!sampled && bar.step >= bar.totalSteps)
|
|
806
|
+
sampled = true;
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
export function parseSamplingProgress(line) {
|
|
810
|
+
const m = line.match(/(\d+)\s*\/\s*(\d+)\s*-\s*([\d.]+)(s\/it|it\/s)/);
|
|
811
|
+
if (!m)
|
|
812
|
+
return null;
|
|
813
|
+
const step = Number.parseInt(m[1], 10);
|
|
814
|
+
const totalSteps = Number.parseInt(m[2], 10);
|
|
815
|
+
const rate = Number.parseFloat(m[3]);
|
|
816
|
+
if (!Number.isFinite(step) || !Number.isFinite(totalSteps) || totalSteps <= 0)
|
|
817
|
+
return null;
|
|
818
|
+
const out = { step, totalSteps };
|
|
819
|
+
const secondsPerStep = m[4] === 'it/s' ? 1 / rate : rate;
|
|
820
|
+
if (Number.isFinite(secondsPerStep) && secondsPerStep > 0)
|
|
821
|
+
out.secondsPerStep = secondsPerStep;
|
|
822
|
+
return out;
|
|
823
|
+
}
|
|
824
|
+
function extractFilename(url) {
|
|
825
|
+
try {
|
|
826
|
+
const u = new URL(url);
|
|
827
|
+
const last = u.pathname.split('/').filter(Boolean).pop();
|
|
828
|
+
return last && last.length > 0 ? last : 'weights.bin';
|
|
829
|
+
}
|
|
830
|
+
catch {
|
|
831
|
+
return 'weights.bin';
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Filename used on disk for an auxiliary file. We want a deterministic
|
|
836
|
+
* `<role>.<ext>` shape (e.g. `vae.safetensors`) so the supervisor can build
|
|
837
|
+
* launch args without re-hitting the HF URL — but we still respect the
|
|
838
|
+
* upstream file extension so the engine sniffs the right format.
|
|
839
|
+
*/
|
|
840
|
+
function auxFilenameFor(aux) {
|
|
841
|
+
const upstream = extractFilename(aux.downloadUrl);
|
|
842
|
+
const dot = upstream.lastIndexOf('.');
|
|
843
|
+
const ext = dot > 0 ? upstream.slice(dot) : '';
|
|
844
|
+
return `${aux.role}${ext}`;
|
|
845
|
+
}
|