abstractcore 2.19.0__tar.gz → 2.19.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. {abstractcore-2.19.0 → abstractcore-2.19.2}/PKG-INFO +10 -10
  2. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/apps/extractor.py +2 -2
  3. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/apps/judge.py +2 -2
  4. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/assets/model_downloads_catalog.json +25 -5
  5. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/capabilities/registry.py +6 -3
  6. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/compression/pil_text_renderer.py +1 -1
  7. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/config/capability_defaults.py +191 -32
  8. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/config/engines.py +59 -34
  9. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/config/main.py +14 -18
  10. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/config/manager.py +94 -2
  11. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/config/model_catalog.py +2 -2
  12. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/config/model_materializer.py +26 -8
  13. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/config/recommendations.py +20 -2
  14. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/config/route_engines.py +54 -7
  15. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/config/vision_config.py +3 -1
  16. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/core/generate_contract.py +47 -1
  17. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/download.py +2 -2
  18. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/embeddings/manager.py +3 -4
  19. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/auto_handler.py +4 -1
  20. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/processors/pdf_processor.py +2 -2
  21. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/base.py +2 -2
  22. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/huggingface_provider.py +35 -20
  23. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/mlx_provider.py +4 -3
  24. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/openai_provider.py +3 -3
  25. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/registry.py +31 -21
  26. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/server/app.py +5 -1
  27. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/server/camera_endpoints.py +4 -1
  28. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/server/host_routes.py +2 -2
  29. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/server/vision_endpoints.py +8 -8
  30. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/browser_tools.py +13 -6
  31. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/common_tools.py +4 -4
  32. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/comms_tools.py +93 -24
  33. abstractcore-2.19.2/abstractcore/utils/install_settings.py +140 -0
  34. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/version.py +1 -1
  35. abstractcore-2.19.2/abstractcore/utils/windows_dll.py +159 -0
  36. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore.egg-info/PKG-INFO +10 -10
  37. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore.egg-info/SOURCES.txt +2 -0
  38. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore.egg-info/requires.txt +13 -9
  39. {abstractcore-2.19.0 → abstractcore-2.19.2}/pyproject.toml +15 -10
  40. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_generate_contract.py +30 -0
  41. abstractcore-2.19.2/tests/test_install_hints_no_bare_packages.py +200 -0
  42. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_install_settings.py +18 -0
  43. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_multimodal_generate_output.py +42 -0
  44. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_packaging_extras.py +1 -1
  45. abstractcore-2.19.0/abstractcore/utils/install_settings.py +0 -43
  46. {abstractcore-2.19.0 → abstractcore-2.19.2}/LICENSE +0 -0
  47. {abstractcore-2.19.0 → abstractcore-2.19.2}/README.md +0 -0
  48. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/__init__.py +0 -0
  49. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/apps/__init__.py +0 -0
  50. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/apps/__main__.py +0 -0
  51. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/apps/app_config_utils.py +0 -0
  52. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/apps/deepsearch.py +0 -0
  53. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/apps/intent.py +0 -0
  54. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/apps/summarizer.py +0 -0
  55. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/architectures/__init__.py +0 -0
  56. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/architectures/detection.py +0 -0
  57. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/architectures/enums.py +0 -0
  58. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/architectures/response_postprocessing.py +0 -0
  59. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/architectures/thinking_controls.py +0 -0
  60. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/assets/OCRA.ttf +0 -0
  61. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/assets/OCRB.ttf +0 -0
  62. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/assets/OCRBL.ttf +0 -0
  63. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/assets/README.md +0 -0
  64. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/assets/abstractframework_identity.json +0 -0
  65. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/assets/architecture_formats.json +0 -0
  66. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/assets/bloc-schema.jsonld +0 -0
  67. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/assets/model_capabilities.json +0 -0
  68. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/assets/model_capabilities.schema.json +0 -0
  69. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/assets/model_downloads_catalog.schema.json +0 -0
  70. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/assets/semantic_models.md +0 -0
  71. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/assets/session_schema.json +0 -0
  72. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/capabilities/__init__.py +0 -0
  73. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/capabilities/errors.py +0 -0
  74. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/capabilities/host.py +0 -0
  75. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/capabilities/music_selectors.py +0 -0
  76. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/capabilities/scene3d_selectors.py +0 -0
  77. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/capabilities/types.py +0 -0
  78. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/capabilities/vision_catalog.py +0 -0
  79. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/compression/__init__.py +0 -0
  80. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/compression/analytics.py +0 -0
  81. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/compression/cache.py +0 -0
  82. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/compression/config.py +0 -0
  83. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/compression/exceptions.py +0 -0
  84. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/compression/glyph_processor.py +0 -0
  85. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/compression/optimizer.py +0 -0
  86. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/compression/orchestrator.py +0 -0
  87. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/compression/quality.py +0 -0
  88. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/compression/text_formatter.py +0 -0
  89. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/compression/vision_compressor.py +0 -0
  90. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/config/__init__.py +0 -0
  91. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/config/host_jobs.py +0 -0
  92. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/config/model_verify.py +0 -0
  93. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/config/models_engines_cli.py +0 -0
  94. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/config/provider_profiles.py +0 -0
  95. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/console/__init__.py +0 -0
  96. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/console/theme_sync.py +0 -0
  97. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/console/themes.py +0 -0
  98. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/console/web.py +0 -0
  99. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/core/__init__.py +0 -0
  100. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/core/bloc_kv.py +0 -0
  101. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/core/bloc_metadata.py +0 -0
  102. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/core/cached_session.py +0 -0
  103. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/core/degeneration.py +0 -0
  104. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/core/endpoint_damping.py +0 -0
  105. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/core/enums.py +0 -0
  106. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/core/factory.py +0 -0
  107. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/core/file_blocs.py +0 -0
  108. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/core/file_boxes.py +0 -0
  109. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/core/interface.py +0 -0
  110. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/core/multimodal_generation.py +0 -0
  111. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/core/output_specs.py +0 -0
  112. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/core/retry.py +0 -0
  113. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/core/session.py +0 -0
  114. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/core/types.py +0 -0
  115. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/embeddings/__init__.py +0 -0
  116. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/embeddings/models.py +0 -0
  117. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/endpoint/__init__.py +0 -0
  118. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/endpoint/app.py +0 -0
  119. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/events/__init__.py +0 -0
  120. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/exceptions/__init__.py +0 -0
  121. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/mcp/__init__.py +0 -0
  122. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/mcp/client.py +0 -0
  123. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/mcp/factory.py +0 -0
  124. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/mcp/naming.py +0 -0
  125. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/mcp/stdio_client.py +0 -0
  126. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/mcp/tool_source.py +0 -0
  127. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/__init__.py +0 -0
  128. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/base.py +0 -0
  129. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/capabilities.py +0 -0
  130. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/delivery.py +0 -0
  131. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/enrichment.py +0 -0
  132. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/handlers/__init__.py +0 -0
  133. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/handlers/anthropic_handler.py +0 -0
  134. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/handlers/local_handler.py +0 -0
  135. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/handlers/openai_handler.py +0 -0
  136. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/openai_parts.py +0 -0
  137. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/pdf_routing.py +0 -0
  138. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/processors/__init__.py +0 -0
  139. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/processors/audio_processor.py +0 -0
  140. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/processors/direct_pdf_processor.py +0 -0
  141. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/processors/glyph_pdf_processor.py +0 -0
  142. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/processors/image_processor.py +0 -0
  143. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/processors/office_processor.py +0 -0
  144. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/processors/text_processor.py +0 -0
  145. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/processors/video_processor.py +0 -0
  146. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/types.py +0 -0
  147. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/utils/__init__.py +0 -0
  148. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/utils/image_scaler.py +0 -0
  149. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/utils/video_frames.py +0 -0
  150. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/media/vision_fallback.py +0 -0
  151. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/processing/__init__.py +0 -0
  152. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/processing/basic_deepsearch.py +0 -0
  153. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/processing/basic_extractor.py +0 -0
  154. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/processing/basic_intent.py +0 -0
  155. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/processing/basic_judge.py +0 -0
  156. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/processing/basic_summarizer.py +0 -0
  157. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/__init__.py +0 -0
  158. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/_http.py +0 -0
  159. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/anthropic_provider.py +0 -0
  160. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/endpoint_context.py +0 -0
  161. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/generation_cancel.py +0 -0
  162. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/generation_progress.py +0 -0
  163. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/hf_residency.py +0 -0
  164. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/lmstudio_provider.py +0 -0
  165. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/mlx_model_rules.py +0 -0
  166. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/mlx_native_cache.py +0 -0
  167. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/mlx_native_session.py +0 -0
  168. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/mlx_prefill_observer.py +0 -0
  169. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/mlx_qwen4.py +0 -0
  170. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/mlx_residency.py +0 -0
  171. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/mlx_runtime.py +0 -0
  172. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/mlx_vision_addon.py +0 -0
  173. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/model_capabilities.py +0 -0
  174. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/model_config_fingerprint.py +0 -0
  175. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/ollama_provider.py +0 -0
  176. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/openai_compatible_provider.py +0 -0
  177. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/openrouter_provider.py +0 -0
  178. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/portkey_provider.py +0 -0
  179. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/process_residency.py +0 -0
  180. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/speculation.py +0 -0
  181. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/streaming.py +0 -0
  182. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/tokenizer_fingerprint.py +0 -0
  183. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/vllm_provider.py +0 -0
  184. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/providers/weights_fingerprint.py +0 -0
  185. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/py.typed +0 -0
  186. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/server/__init__.py +0 -0
  187. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/server/__main__.py +0 -0
  188. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/server/audio_endpoints.py +0 -0
  189. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/server/capability_generation.py +0 -0
  190. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/server/console_routes.py +0 -0
  191. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/server/credentials.py +0 -0
  192. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/server/first_run.py +0 -0
  193. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/server/scene3d_endpoints.py +0 -0
  194. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/server/session_routes.py +0 -0
  195. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/structured/__init__.py +0 -0
  196. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/structured/handler.py +0 -0
  197. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/structured/retry.py +0 -0
  198. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/structured/schema_compat.py +0 -0
  199. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/__init__.py +0 -0
  200. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/abstractignore.py +0 -0
  201. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/arg_canonicalizer.py +0 -0
  202. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/arg_coercion.py +0 -0
  203. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/code_analysis.py +0 -0
  204. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/core.py +0 -0
  205. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/email_digests.py +0 -0
  206. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/fetch_url_ssrf.py +0 -0
  207. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/handler.py +0 -0
  208. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/inventory.py +0 -0
  209. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/parser.py +0 -0
  210. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/process_tree.py +0 -0
  211. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/registry.py +0 -0
  212. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/risk_facts.py +0 -0
  213. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/shell_session.py +0 -0
  214. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/shell_tools.py +0 -0
  215. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/syntax_rewriter.py +0 -0
  216. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/tag_rewriter.py +0 -0
  217. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/telegram_tdlib.py +0 -0
  218. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/telegram_tools.py +0 -0
  219. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/tools/wire_naming.py +0 -0
  220. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/__init__.py +0 -0
  221. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/async_stream.py +0 -0
  222. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/cli.py +0 -0
  223. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/context_calibration.py +0 -0
  224. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/context_estimate.py +0 -0
  225. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/data_registry.py +0 -0
  226. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/display.py +0 -0
  227. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/file_filters.py +0 -0
  228. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/host_profile.py +0 -0
  229. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/hostinfo.py +0 -0
  230. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/identity.py +0 -0
  231. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/jsonish.py +0 -0
  232. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/memory.py +0 -0
  233. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/message_preprocessor.py +0 -0
  234. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/model_cache.py +0 -0
  235. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/model_fit.py +0 -0
  236. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/residency.py +0 -0
  237. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/self_fixes.py +0 -0
  238. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/structured_logging.py +0 -0
  239. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/token_utils.py +0 -0
  240. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/trace_export.py +0 -0
  241. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/truncation.py +0 -0
  242. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/vlm_token_calculator.py +0 -0
  243. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore/utils/workspace_paths.py +0 -0
  244. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore.egg-info/dependency_links.txt +0 -0
  245. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore.egg-info/entry_points.txt +0 -0
  246. {abstractcore-2.19.0 → abstractcore-2.19.2}/abstractcore.egg-info/top_level.txt +0 -0
  247. {abstractcore-2.19.0 → abstractcore-2.19.2}/setup.cfg +0 -0
  248. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_abstractendpoint_singleton_provider.py +0 -0
  249. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_agentic_cli_compatibility.py +0 -0
  250. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_basic_session.py +0 -0
  251. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_bloc_kv.py +0 -0
  252. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_bloc_kv_cache_dtype.py +0 -0
  253. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_bloc_kv_endpoint.py +0 -0
  254. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_bloc_kv_engine_fingerprint.py +0 -0
  255. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_bloc_kv_model_config_fingerprint.py +0 -0
  256. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_bloc_kv_tokenizer_fingerprint.py +0 -0
  257. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_bloc_kv_weights_fingerprint.py +0 -0
  258. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_bloc_metadata_json_extract.py +0 -0
  259. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_cached_session_kv_mode.py +0 -0
  260. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_cached_session_kv_thinking_prefix.py +0 -0
  261. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_capabilities_registry.py +0 -0
  262. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_capabilities_registry_preferred_backends_from_config.py +0 -0
  263. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_complete_integration.py +0 -0
  264. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_comprehensive_events.py +0 -0
  265. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_console_web.py +0 -0
  266. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_core_components.py +0 -0
  267. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_display_rule.py +0 -0
  268. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_email_digests.py +0 -0
  269. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_embedding_model_token_warnings.py +0 -0
  270. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_enhanced_prompt.py +0 -0
  271. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_environment_variable_tool_call_tags.py +0 -0
  272. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_factory.py +0 -0
  273. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_file_bloc_store_unit.py +0 -0
  274. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_file_boxes_cached_session.py +0 -0
  275. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_final_accuracy.py +0 -0
  276. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_final_comprehensive.py +0 -0
  277. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_final_graceful_errors.py +0 -0
  278. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_generate_response_reasoning_property_unit.py +0 -0
  279. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_generate_with_outputs.py +0 -0
  280. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_graceful_fallback.py +0 -0
  281. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_import_debug.py +0 -0
  282. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_import_safety.py +0 -0
  283. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_incremental_thinking_tag_stripper.py +0 -0
  284. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_integrated_functionality.py +0 -0
  285. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_llms_sources.py +0 -0
  286. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_mcp_integration.py +0 -0
  287. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_mcp_stdio_client.py +0 -0
  288. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_openai_prompt_cache_key_passthrough.py +0 -0
  289. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_output_specs.py +0 -0
  290. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_packaging_assets.py +0 -0
  291. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_prompt_cache_api.py +0 -0
  292. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_prompt_cache_bloc_composition.py +0 -0
  293. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_prompt_cache_bloc_e2e_gate.py +0 -0
  294. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_prompt_cache_control_plane.py +0 -0
  295. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_prompt_cache_prepare_modules_thinking_http.py +0 -0
  296. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_retry_observability.py +0 -0
  297. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_retry_strategy.py +0 -0
  298. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_seed_determinism.py +0 -0
  299. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_seed_temperature_basic.py +0 -0
  300. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_sensory_prompting.py +0 -0
  301. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_server_prompt_cache_control_plane_proxy.py +0 -0
  302. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_server_prompt_cache_key_passthrough.py +0 -0
  303. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_session_reasoning_persistence_unit.py +0 -0
  304. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_structured_output_truncation_retry.py +0 -0
  305. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_text_only_model_experience.py +0 -0
  306. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_unload_memory.py +0 -0
  307. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_user_scenario_validation.py +0 -0
  308. {abstractcore-2.19.0 → abstractcore-2.19.2}/tests/test_voice_host_credentials.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: abstractcore
3
- Version: 2.19.0
3
+ Version: 2.19.2
4
4
  Summary: Unified interface to all LLM providers with essential infrastructure for tool calling, streaming, and model management
5
5
  Author-email: Laurent-Philippe Albou <contact@abstractcore.ai>
6
6
  Maintainer-email: Laurent-Philippe Albou <contact@abstractcore.ai>
@@ -31,7 +31,7 @@ Description-Content-Type: text/markdown
31
31
  License-File: LICENSE
32
32
  Requires-Dist: pydantic<3.0.0,>=2.0.0
33
33
  Requires-Dist: httpx<1.0.0,>=0.24.0
34
- Requires-Dist: openai<2.0.0,>=1.0.0
34
+ Requires-Dist: openai<3.0.0,>=1.0.0
35
35
  Requires-Dist: anthropic<1.0.0,>=0.25.0
36
36
  Requires-Dist: tiktoken<1.0.0,>=0.5.0
37
37
  Requires-Dist: requests<3.0.0,>=2.25.0
@@ -48,8 +48,8 @@ Requires-Dist: fastapi<1.0.0,>=0.100.0
48
48
  Requires-Dist: uvicorn[standard]<1.0.0,>=0.23.0
49
49
  Requires-Dist: python-multipart<1.0.0,>=0.0.6
50
50
  Requires-Dist: sse-starlette<2.0.0,>=1.6.0
51
- Requires-Dist: abstractvoice>=0.13.0
52
- Requires-Dist: abstractvision>=0.3.31
51
+ Requires-Dist: abstractvoice>=0.13.2
52
+ Requires-Dist: abstractvision>=0.3.33
53
53
  Requires-Dist: abstractmusic>=0.1.15; python_version >= "3.10"
54
54
  Requires-Dist: abstract3d>=0.1.0; python_version >= "3.10"
55
55
  Provides-Extra: apple
@@ -67,26 +67,26 @@ Requires-Dist: accelerate>=1.0.0; extra == "apple"
67
67
  Requires-Dist: sentence-transformers<6.0.0,>=5.1.0; extra == "apple"
68
68
  Requires-Dist: numpy<3.0.0,>=2.1.0; extra == "apple"
69
69
  Requires-Dist: Pillow<13.0.0,>=12.1.1; extra == "apple"
70
- Requires-Dist: abstractvoice[all-apple]>=0.13.0; extra == "apple"
70
+ Requires-Dist: abstractvoice[all-apple]>=0.13.2; extra == "apple"
71
71
  Requires-Dist: omnivoice>=0.1.5; extra == "apple"
72
- Requires-Dist: abstractvision[all-apple]>=0.3.31; extra == "apple"
72
+ Requires-Dist: abstractvision[all-apple]>=0.3.33; extra == "apple"
73
73
  Requires-Dist: abstractmusic[all-apple]>=0.1.15; extra == "apple"
74
74
  Requires-Dist: playwright<2.0.0,>=1.49.0; extra == "apple"
75
75
  Provides-Extra: gpu
76
- Requires-Dist: vllm<1.0.0,>=0.6.0; extra == "gpu"
76
+ Requires-Dist: vllm<1.0.0,>=0.6.0; sys_platform == "linux" and extra == "gpu"
77
77
  Requires-Dist: outlines>=0.1.0; extra == "gpu"
78
78
  Requires-Dist: transformers<6.0.0,>=4.57.1; extra == "gpu"
79
79
  Requires-Dist: torch<3.0.0,>=2.6.0; extra == "gpu"
80
80
  Requires-Dist: torchvision>=0.17.0; extra == "gpu"
81
81
  Requires-Dist: torchaudio>=2.1.0; extra == "gpu"
82
82
  Requires-Dist: sentencepiece<1.0.0,>=0.1.99; extra == "gpu"
83
- Requires-Dist: llama-cpp-python<1.0.0,>=0.3.23; extra == "gpu"
83
+ Requires-Dist: llama-cpp-python<1.0.0,>=0.3.23; sys_platform != "win32" and extra == "gpu"
84
84
  Requires-Dist: sentence-transformers<6.0.0,>=5.1.0; extra == "gpu"
85
85
  Requires-Dist: numpy<3.0.0,>=1.20.0; python_version < "3.13" and extra == "gpu"
86
86
  Requires-Dist: numpy<3.0.0,>=2.1.0; python_version >= "3.13" and extra == "gpu"
87
- Requires-Dist: abstractvoice[all-gpu]>=0.13.0; extra == "gpu"
87
+ Requires-Dist: abstractvoice[all-gpu]>=0.13.2; extra == "gpu"
88
88
  Requires-Dist: omnivoice>=0.1.5; extra == "gpu"
89
- Requires-Dist: abstractvision[all-gpu]>=0.3.31; extra == "gpu"
89
+ Requires-Dist: abstractvision[all-gpu]>=0.3.33; extra == "gpu"
90
90
  Requires-Dist: abstractmusic[all-gpu]>=0.1.15; extra == "gpu"
91
91
  Requires-Dist: playwright<2.0.0,>=1.49.0; extra == "gpu"
92
92
  Provides-Extra: remote
@@ -225,7 +225,7 @@ Default model setup:
225
225
  '--format',
226
226
  choices=format_choices,
227
227
  default='json-ld',
228
- help='Output format: json-ld (JSON-LD graph), triples (RDF SUBJECT PREDICATE OBJECT), json (simple JSON)' + (', yaml' if YAML_AVAILABLE else ' - install PyYAML for YAML support')
228
+ help='Output format: json-ld (JSON-LD graph), triples (RDF SUBJECT PREDICATE OBJECT), json (simple JSON)' + (', yaml' if YAML_AVAILABLE else ' - yaml needs PyYAML (part of the light install; repair with: pip install -U abstractcore)')
229
229
  )
230
230
 
231
231
  parser.add_argument(
@@ -513,7 +513,7 @@ Default model setup:
513
513
  )
514
514
  else: # yaml
515
515
  if not YAML_AVAILABLE:
516
- print("Error: PyYAML is required for YAML output format. Install with: pip install PyYAML")
516
+ print("Error: PyYAML is required for YAML output format; it is part of the light install. Repair with: pip install -U abstractcore")
517
517
  sys.exit(1)
518
518
  formatted_output = yaml.dump(result, default_flow_style=False, indent=2, sort_keys=False)
519
519
 
@@ -352,7 +352,7 @@ Default model setup:
352
352
  '--format',
353
353
  choices=format_choices,
354
354
  default='json',
355
- help='Output format: json (structured), plain (human-readable)' + (', yaml' if YAML_AVAILABLE else ' - install PyYAML for YAML support')
355
+ help='Output format: json (structured), plain (human-readable)' + (', yaml' if YAML_AVAILABLE else ' - yaml needs PyYAML (part of the light install; repair with: pip install -U abstractcore)')
356
356
  )
357
357
 
358
358
  parser.add_argument(
@@ -623,7 +623,7 @@ Default model setup:
623
623
  formatted_output = format_assessment_plain(assessment)
624
624
  elif args.format == 'yaml':
625
625
  if not YAML_AVAILABLE:
626
- print("Error: PyYAML is required for YAML output format. Install with: pip install PyYAML")
626
+ print("Error: PyYAML is required for YAML output format; it is part of the light install. Repair with: pip install -U abstractcore")
627
627
  sys.exit(1)
628
628
  formatted_output = yaml.dump(assessment, default_flow_style=False, indent=2, sort_keys=False)
629
629
  else:
@@ -1089,8 +1089,8 @@
1089
1089
  "artifacts": [
1090
1090
  {
1091
1091
  "provider": "lmstudio",
1092
- "artifact": "qwen/qwen3.5-9b@4bit",
1093
- "quant": "4bit",
1092
+ "artifact": "qwen/qwen3.5-9b@q4_k_m",
1093
+ "quant": "q4_k_m",
1094
1094
  "download_bytes": null,
1095
1095
  "size_source": "unknown",
1096
1096
  "verified": true,
@@ -4078,7 +4078,7 @@
4078
4078
  {
4079
4079
  "id": "flux.2-klein-4b",
4080
4080
  "family": "flux.2",
4081
- "display_name": "FLUX.2 [klein] 4B (8-bit)",
4081
+ "display_name": "FLUX.2 [klein] 4B",
4082
4082
  "vendor": "Black Forest Labs",
4083
4083
  "params_total": 4000000000,
4084
4084
  "params_active": null,
@@ -4098,6 +4098,26 @@
4098
4098
  "size_source": "catalog",
4099
4099
  "verified": true,
4100
4100
  "recommended": true
4101
+ },
4102
+ {
4103
+ "provider": "diffusers",
4104
+ "artifact": "black-forest-labs/FLUX.2-klein-4B",
4105
+ "quant": "bf16",
4106
+ "download_bytes": 23740007447,
4107
+ "size_source": "catalog",
4108
+ "verified": true,
4109
+ "recommended": true,
4110
+ "upstream": {
4111
+ "method": "hf_api",
4112
+ "checked": "2026-09-29",
4113
+ "revision": "e7b7dc27f91deacad38e78976d1f2b499d76a294"
4114
+ },
4115
+ "note": "23,740,007,447 bytes: the whole repo (25 files) at revision e7b7dc27, read with the Hugging Face model API on 2026-09-29; `models download diffusers` fetches it whole, including the 7.75 GB single-file checkpoint (flux-2-klein-4b.safetensors) the Diffusers pipeline does not load. The pipeline's weights are 14.9 GiB in float16 (text encoder 7.49 GiB, transformer 7.22 GiB, VAE 0.16 GiB).",
4116
+ "resident": {
4117
+ "bytes": 8912896000,
4118
+ "source": "nvidia-smi GPU memory peak 7.8-8.3 GiB (the largest, 8.3 GiB, is the figure) of real AbstractVision Diffusers text-to-image generations at 768x768, 4 steps, float16, about 17 s each, with the model CPU offload AbstractVision turns on by itself when the 14.9 GiB of float16 weights do not fit the GPU's free memory (moved whole, the pipeline runs out of CUDA memory on this card); system RAM holds the idle weights, about 15 GiB: Linux, NVIDIA Quadro RTX 5000 16 GB (Turing, sm_75), driver 595, abstractvision 0.3.32 source (commit 281756f), 2026-09-29 (framework backlog 0989)",
4119
+ "measured_with": "AbstractVision/diffusers on CUDA in float16 at 768x768 with model CPU offload, about 15 GiB of idle weights waiting in system RAM"
4120
+ }
4101
4121
  }
4102
4122
  ],
4103
4123
  "capabilities_override": {
@@ -4105,7 +4125,7 @@
4105
4125
  "vision": false,
4106
4126
  "image_generation": true
4107
4127
  },
4108
- "notes": "The AbstractFramework fresh-install image-generation default (output.image)."
4128
+ "notes": "The AbstractFramework image-generation recommendation (output.image): the 8-bit MLX-Gen build on Apple silicon, the Diffusers repo on an NVIDIA GPU (CUDA)."
4109
4129
  },
4110
4130
  {
4111
4131
  "id": "supertonic-3",
@@ -4316,7 +4336,7 @@
4316
4336
  "voice",
4317
4337
  "speech-to-text"
4318
4338
  ],
4319
- "starter": false,
4339
+ "starter": true,
4320
4340
  "artifacts": [
4321
4341
  {
4322
4342
  "provider": "huggingface",
@@ -647,9 +647,12 @@ class CapabilityRegistry:
647
647
  if cap in {"voice", "audio", "vision", "music", "scene3d", "3d"}:
648
648
  return "pip install -U abstractcore"
649
649
  if cap == "camera":
650
- # camera (seat: camera): no abstractcore extra exists yet — the
651
- # plugin package is the install unit.
652
- return 'pip install "abstractcamera"'
650
+ # camera (seat: camera): no install setting ships the camera plugin, and a
651
+ # bare-package install is never advised (operator ruling 2026-09-29).
652
+ return (
653
+ "Camera is not available with AbstractCore's install settings (light, apple, gpu): "
654
+ "the camera plugin (abstractcamera) is not part of them"
655
+ )
653
656
  return None
654
657
 
655
658
  def _select_backend_id(self, capability: str) -> str:
@@ -57,7 +57,7 @@ class PILTextRenderer:
57
57
  from PIL import Image, ImageDraw, ImageFont
58
58
  except ImportError as e:
59
59
  raise RenderingError(
60
- f"PIL/Pillow not available: {e}. Install with: pip install pillow"
60
+ f"PIL/Pillow not available: {e}. Pillow is part of the light install; repair with: pip install -U abstractcore"
61
61
  )
62
62
 
63
63
  def _estimate_text_capacity(self, config: RenderingConfig, fonts: dict) -> int:
@@ -290,7 +290,15 @@ def clean_capability_route_default(value: Any) -> CapabilityRouteDefault:
290
290
  )
291
291
 
292
292
 
293
- RECOMMENDED_SEED_VERSION = "recommended-v1"
293
+ RECOMMENDED_SEED_VERSION = "recommended-v2"
294
+
295
+ # What each seed version added to the one before it, applied once to a store
296
+ # an earlier seed wrote (`upgrade_recommended_seed`). v2 (AbstractCore 2.19.2):
297
+ # speech input, so a fresh install transcribes locally instead of falling
298
+ # through to OpenAI without a key.
299
+ RECOMMENDED_SEED_ADDITIONS: Tuple[Tuple[str, Tuple[str, ...]], ...] = (
300
+ ("recommended-v2", ("input.voice",)),
301
+ )
294
302
 
295
303
  # THE RECOMMENDED MODEL PER CAPABILITY: one table, every surface.
296
304
  #
@@ -317,11 +325,27 @@ RECOMMENDED_SEED_VERSION = "recommended-v1"
317
325
  # them ONLY when the config file does not exist yet -- never merged into an
318
326
  # existing store (an operator who cleared a route meant it), and never on the
319
327
  # corrupt-file fallback. `apply-recommended` and `models download
320
- # --recommended` act on the same starter set. Rows with `starter=False`
321
- # (speech input, music) are recommendations every listing shows, which no
322
- # writer applies on its own: the operator sets them (`abstractcore config
328
+ # --recommended` act on the same starter set: text, voice output, speech
329
+ # input, image and video. Rows with `starter=False` (music) are
330
+ # recommendations every listing shows, which no writer applies on its own: the operator sets them (`abstractcore config
323
331
  # set-default`). Text stores at input.text (the canonical storage key;
324
332
  # output.text derives).
333
+ #
334
+ # PER-ACCELERATOR PICKS (`by_accelerator`): a row whose best engine differs by
335
+ # the host's accelerator carries the other pick IN THE ROW, keyed by the host
336
+ # probe's `accelerator` (`cuda`, `metal`, ...). `route`/`download` stay the
337
+ # portable pick every other host starts from; `_full_recommendation` swaps in
338
+ # the accelerator's pick BEFORE the engine-support filter and the memory gate,
339
+ # so the pick is judged by the same rules as every other row. The row stays
340
+ # the one place its recommendation lives.
341
+ @dataclass(frozen=True)
342
+ class AcceleratorPick:
343
+ """The route and download that replace a row's portable pick on one accelerator."""
344
+
345
+ route: CapabilityRouteDefault
346
+ download: Mapping[str, str]
347
+
348
+
325
349
  @dataclass(frozen=True)
326
350
  class RecommendedModel:
327
351
  """One capability's recommendation: the route, its download, starter or not."""
@@ -329,20 +353,31 @@ class RecommendedModel:
329
353
  route: CapabilityRouteDefault
330
354
  download: Mapping[str, str]
331
355
  starter: bool
356
+ by_accelerator: Mapping[str, AcceleratorPick] = field(default_factory=dict)
357
+
358
+ def pick_for(self, host: Mapping[str, Any]) -> Tuple[CapabilityRouteDefault, Mapping[str, str]]:
359
+ """`(route, download)` for `host`: its accelerator's pick, else the portable one."""
360
+
361
+ pick = self.by_accelerator.get(str(host.get("accelerator") or ""))
362
+ return (pick.route, pick.download) if pick is not None else (self.route, self.download)
332
363
 
333
364
 
334
365
  RECOMMENDED_MODELS: Dict[str, RecommendedModel] = {
335
366
  # Text: the 4-BIT quantized build (operator ruling 2026-08-01). The ROUTE
336
367
  # stores the bare LM Studio id because that is what the server serves when
337
368
  # a single quant is installed; the 4-bit choice is pinned by the download
338
- # artifact reference, which is what actually fetches the weights.
369
+ # artifact reference, which is what actually fetches the weights. The
370
+ # variant is LM Studio's GGUF Q4_K_M: LM Studio is recommended only off
371
+ # Apple silicon (Linux, Windows), where its catalog has no `4bit` (MLX)
372
+ # variant -- `lms get qwen/qwen3.5-9b@4bit` fails there with "Cannot find
373
+ # variant 4bit" (measured on Linux + NVIDIA, framework backlog 0989).
339
374
  "input.text": RecommendedModel(
340
375
  route=CapabilityRouteDefault(
341
376
  provider="lmstudio", model="qwen/qwen3.5-9b",
342
377
  options={"speculation": {"mode": "native_mtp", "num_draft_tokens": 2,
343
378
  "require_acceleration": False}},
344
379
  ),
345
- download={"provider": "lmstudio", "artifact": "qwen/qwen3.5-9b@4bit"},
380
+ download={"provider": "lmstudio", "artifact": "qwen/qwen3.5-9b@q4_k_m"},
346
381
  starter=True,
347
382
  ),
348
383
  "output.voice": RecommendedModel(
@@ -350,10 +385,26 @@ RECOMMENDED_MODELS: Dict[str, RecommendedModel] = {
350
385
  download={"provider": "supertonic", "artifact": "supertonic-3"},
351
386
  starter=True,
352
387
  ),
388
+ # Image: FLUX.2 [klein] 4B. Apple silicon (and the portable pick): the
389
+ # 8-bit MLX-Gen build. An NVIDIA GPU (`cuda`): the same model's Diffusers
390
+ # repo on AbstractVision's `diffusers` backend (the route provider the
391
+ # image lane maps to that backend; device `auto` resolves to CUDA, float16).
392
+ # Its 14.9 GiB of float16 weights do not fit a 16 GB card whole, so
393
+ # AbstractVision (>= 0.3.32, `cpu_offload="auto"`) loads it with model CPU
394
+ # offload there: measured 768x768 in ~17 s with an 8.3 GiB GPU peak on a
395
+ # Quadro RTX 5000 16 GB (framework backlog 0989; the catalog artifact's
396
+ # `resident` is that figure, so the memory gate judges it). Processor-only
397
+ # hosts keep the portable pick and report it unavailable.
353
398
  "output.image": RecommendedModel(
354
399
  route=CapabilityRouteDefault(provider="mlx-gen", model="AbstractFramework/flux.2-klein-4b-8bit"),
355
400
  download={"provider": "mlx-gen", "artifact": "AbstractFramework/flux.2-klein-4b-8bit"},
356
401
  starter=True,
402
+ by_accelerator={
403
+ "cuda": AcceleratorPick(
404
+ route=CapabilityRouteDefault(provider="diffusers", model="black-forest-labs/FLUX.2-klein-4B"),
405
+ download={"provider": "diffusers", "artifact": "black-forest-labs/FLUX.2-klein-4B"},
406
+ ),
407
+ },
357
408
  ),
358
409
  # Video: Wan2.2 TI2V-5B, ONE checkpoint for text-to-video AND
359
410
  # image-to-video, so the modality cell answers both tasks. It is the only
@@ -371,11 +422,14 @@ RECOMMENDED_MODELS: Dict[str, RecommendedModel] = {
371
422
  # an NVIDIA GPU, the processor elsewhere -- it has no Metal backend) with
372
423
  # AbstractVoice's own default model, `base`. faster-whisper resolves
373
424
  # `base` to the Hugging Face repo below in the standard Hugging Face cache,
374
- # which is where `models download huggingface <repo>` puts it.
425
+ # which is where `models download huggingface <repo>` puts it. A STARTER
426
+ # row (2.19.2, framework rehearsal 0.6.3): without it a fresh install's
427
+ # speech-to-text fell through to OpenAI and failed for want of an API key,
428
+ # although the installer ships faster-whisper (`abstractvoice[stt]`).
375
429
  "input.voice": RecommendedModel(
376
430
  route=CapabilityRouteDefault(provider="faster-whisper", model="base"),
377
431
  download={"provider": "huggingface", "artifact": "Systran/faster-whisper-base"},
378
- starter=False,
432
+ starter=True,
379
433
  ),
380
434
  # Music: AbstractMusic's `acestep` backend (Diffusers AceStepPipeline on
381
435
  # PyTorch: CUDA, Apple MPS in bfloat16, or the processor in float32) with
@@ -393,7 +447,7 @@ RECOMMENDED_MODELS: Dict[str, RecommendedModel] = {
393
447
  # `apply-recommended` and `models download --recommended` act on. Derived,
394
448
  # never edited: `RECOMMENDED_MODELS` is the one place a recommendation lives.
395
449
  # Like the table, never read directly for a host answer: use the host-aware
396
- # functions below.
450
+ # functions below. They hold the PORTABLE picks (no `by_accelerator` swap).
397
451
  RECOMMENDED_CAPABILITY_DEFAULT_ROUTES: Dict[str, CapabilityRouteDefault] = {
398
452
  key: rec.route for key, rec in RECOMMENDED_MODELS.items() if rec.starter
399
453
  }
@@ -408,6 +462,8 @@ RECOMMENDED_MODEL_DOWNLOADS: Dict[str, Dict[str, str]] = {
408
462
  # ONE owner): the unified-memory tier on Apple silicon (operator ruling
409
463
  # 2026-09-24), the portable LM Studio build elsewhere, and the same model's
410
464
  # Ollama build where LM Studio has no build (Intel Macs);
465
+ # - a row with a pick for the host's accelerator (`by_accelerator`) uses it:
466
+ # image generation on an NVIDIA GPU is Diffusers, not MLX-Gen;
411
467
  # - every row whose engine cannot run on the host is DROPPED, never written
412
468
  # as a route that fails at first use. It is reported instead, with the
413
469
  # reason, by `recommended_unavailable_routes()` -- the grid then shows the
@@ -443,6 +499,10 @@ RECOMMENDED_MODEL_DOWNLOADS: Dict[str, Dict[str, str]] = {
443
499
  # acestep abstractmusic's ACE-Step backend is a Diffusers pipeline on
444
500
  # PyTorch (abstractmusic/backends/acestep.py): Apple-silicon
445
501
  # macOS, x86_64/arm64 Linux, x86_64 Windows.
502
+ # diffusers abstractvision's Diffusers backend is PyTorch
503
+ # (abstractvision/backends/huggingface_diffusers.py), the same
504
+ # builds as acestep. Recommended on `cuda` hosts only
505
+ # (`RECOMMENDED_MODELS["output.image"].by_accelerator`).
446
506
  _RECOMMENDED_PROVIDER_ENGINE = {
447
507
  "mlx": "mlx",
448
508
  "mlx-gen": "mlx",
@@ -451,12 +511,14 @@ _RECOMMENDED_PROVIDER_ENGINE = {
451
511
  "supertonic": "onnxruntime",
452
512
  "faster-whisper": "ctranslate2",
453
513
  "acestep": "torch",
514
+ "diffusers": "torch",
454
515
  }
455
516
  # How a provider that runs ON another engine words the engine's refusal.
456
517
  _ENGINE_VIA = {
457
518
  "supertonic": "Supertonic voice runs on ONNX Runtime (CPU), and ",
458
519
  "faster-whisper": "faster-whisper speech input runs on CTranslate2, and ",
459
520
  "acestep": "ACE-Step music generation runs on PyTorch, and ",
521
+ "diffusers": "Diffusers image generation runs on PyTorch, and ",
460
522
  }
461
523
 
462
524
  # CONFIGURED routes whose engine runs INSIDE this process (AbstractCore's MLX
@@ -470,8 +532,9 @@ _ENGINE_VIA = {
470
532
  _IN_PROCESS_PROVIDERS = frozenset({"mlx", "mlx-gen", "supertonic"})
471
533
 
472
534
  # What an operator can do instead, per route, appended to the reason. Provider
473
- # ids are abstractvision's own (`diffusers`, `sdcpp`); the curated catalog has
474
- # no artifact for them yet, which is why nothing is recommended in their place.
535
+ # ids are abstractvision's own (`diffusers`, `sdcpp`). An NVIDIA GPU gets the
536
+ # Diffusers image pick itself (`by_accelerator`); a processor-only host does not
537
+ # (no measured processor run vouches for it), so the sentence names the engines.
475
538
  # Video has NO local alternative off Apple silicon today: abstractvision's
476
539
  # Diffusers text-to-video is disabled for its only model (CogVideoX-2b,
477
540
  # `_TEMPORARILY_DISABLED_LOCAL_DIFFUSERS_TASKS`) and has no image-to-video,
@@ -494,11 +557,6 @@ _UNAVAILABLE_NEXT_STEP = {
494
557
  "input.text to a cloud provider or to a text server on another machine (lmstudio, ollama or "
495
558
  "openai-compatible, with its base_url)"
496
559
  ),
497
- "output.image": (
498
- "set output.image to an image engine this host runs: diffusers (included with abstractcore[gpu]), "
499
- "sdcpp (stable-diffusion.cpp, included with abstractcore[apple] and abstractcore[gpu]) or a cloud "
500
- "image provider"
501
- ),
502
560
  "output.video": _VIDEO_NO_LOCAL_ENGINE,
503
561
  # Engine ids are abstractvoice's (`transformers-asr`, `openai`).
504
562
  "input.voice": (
@@ -508,6 +566,27 @@ _UNAVAILABLE_NEXT_STEP = {
508
566
  # Backend ids are abstractmusic's (`acemusic`, `elevenlabs-music`).
509
567
  "output.music": "set output.music to a cloud music backend (acemusic or elevenlabs-music, with its API key)",
510
568
  }
569
+ # output.image names only the three settings (operator ruling 2026-09-29). Apple
570
+ # silicon never reaches this (MLX-Gen runs there), nor does an NVIDIA GPU (its
571
+ # pick is Diffusers, which runs there). Linux: abstractcore[gpu] ships
572
+ # both local image engines; Windows x86_64: abstractcore[gpu] ships diffusers
573
+ # (stable-diffusion.cpp is a source build there, marked out; backlog 0988). An Intel
574
+ # Mac or Windows on ARM has no setting that installs them (`uv pip compile` of
575
+ # abstractcore[gpu] for x86_64-apple-darwin fails on torch). One sentence covers every
576
+ # non-Apple host so a machine-class row ("Linux or Windows ...") stays exact.
577
+ _UNAVAILABLE_NEXT_STEP["output.image"] = (
578
+ "set output.image to a local image engine: diffusers, included with abstractcore[gpu] on Linux and "
579
+ "Windows, or sdcpp (stable-diffusion.cpp), included with abstractcore[gpu] on Linux (on an Intel Mac or "
580
+ "Windows on ARM they are not available with AbstractCore's install settings), or to a cloud image provider"
581
+ )
582
+
583
+
584
+ def _unavailable_next_step(key: Optional[str], host: Mapping[str, Any]) -> Optional[str]:
585
+ """What an operator can do instead of an unavailable recommended row."""
586
+
587
+ return _UNAVAILABLE_NEXT_STEP.get(key or "")
588
+
589
+
511
590
  # Next step when the engine runs but the model does not fit (fit-gated rows).
512
591
  _TOO_LARGE_NEXT_STEP = {
513
592
  "output.image": "use a Mac with more unified memory, or a cloud image provider",
@@ -520,6 +599,12 @@ _TOO_LARGE_NEXT_STEP = {
520
599
  "with its API key)"
521
600
  ),
522
601
  }
602
+ # The same, where the host's accelerator words it differently (the key is
603
+ # `(accelerator, route)`): an NVIDIA GPU's image pick is judged against GPU
604
+ # memory, not a Mac's unified memory.
605
+ _TOO_LARGE_NEXT_STEP_BY_ACCELERATOR = {
606
+ ("cuda", "output.image"): "use an NVIDIA GPU with more memory, or a cloud image provider",
607
+ }
523
608
  # Recommended rows written only where the catalog's fit estimate (the same one
524
609
  # the model browser's "fits this computer" filter uses: `fits` or `tight`)
525
610
  # says the model fits this host's memory.
@@ -606,7 +691,8 @@ def _fit_gate_sentence(
606
691
  if verdict in ("fits", "tight"):
607
692
  return None
608
693
  name = got["row"].get("display_name") or got["row"].get("id")
609
- next_step = f"; {_TOO_LARGE_NEXT_STEP[key]}" if key in _TOO_LARGE_NEXT_STEP else ""
694
+ step = _TOO_LARGE_NEXT_STEP_BY_ACCELERATOR.get((str(host.get("accelerator") or ""), key)) or _TOO_LARGE_NEXT_STEP.get(key)
695
+ next_step = f"; {step}" if step else ""
610
696
  if verdict == "unknown" or not isinstance(fit.get("need_bytes"), int) or not isinstance(fit.get("usable_bytes"), int):
611
697
  return f"{name} needs a lot of memory and this computer's memory could not be measured{next_step}"
612
698
  # "measured" only where the seed carries a measured run-time peak
@@ -666,8 +752,9 @@ def _unavailable_reasons(
666
752
  for key, route in routes.items():
667
753
  reason = recommended_route_unavailable_reason(route.provider, host, key)
668
754
  if reason:
669
- if key in _UNAVAILABLE_NEXT_STEP:
670
- reason = f"{reason}; {_UNAVAILABLE_NEXT_STEP[key]}"
755
+ next_step = _unavailable_next_step(key, host)
756
+ if next_step:
757
+ reason = f"{reason}; {next_step}"
671
758
  out[key] = reason
672
759
  continue
673
760
  if key in _FIT_GATED_ROUTES:
@@ -682,9 +769,19 @@ def _host_or_probe(host: Optional[Mapping[str, Any]]) -> Dict[str, Any]:
682
769
  return dict(host)
683
770
  from ..utils.host_profile import host_profile
684
771
 
685
- # The LIGHT reading: os / arch / Apple silicon / RAM, no GPU tool, no
686
- # torch, no mlx (the import-time config seed runs this).
687
- return host_profile(light=True)
772
+ # The LIGHT reading first: os / arch / Apple silicon / RAM, no GPU tool,
773
+ # no torch, no mlx. It answers Apple silicon whole. Off Apple silicon it
774
+ # reads every GPU as `none`, and there the accelerator decides a
775
+ # recommendation (an NVIDIA GPU's image pick, `by_accelerator`), so the
776
+ # FULL probe answers (cached for a few seconds; nvidia-smi). On the light
777
+ # reading alone apply-recommended, the grid and `models download
778
+ # --recommended` would treat a CUDA host as processor-only and never
779
+ # offer its image pick (framework backlog 0989). The import-time seed never comes here: it passes the light reading
780
+ # itself (`seed_recommended_capability_defaults`).
781
+ light = host_profile(light=True)
782
+ if light.get("accelerator") == "metal":
783
+ return light
784
+ return host_profile()
688
785
 
689
786
 
690
787
  def _full_recommendation(
@@ -698,15 +795,15 @@ def _full_recommendation(
698
795
 
699
796
  from .model_catalog import recommended_text_model
700
797
 
701
- table = {key: rec for key, rec in RECOMMENDED_MODELS.items() if rec.starter or not starter_only}
798
+ table = {key: rec.pick_for(host) for key, rec in RECOMMENDED_MODELS.items() if rec.starter or not starter_only}
702
799
  routes = {
703
800
  key: CapabilityRouteDefault(
704
- provider=rec.route.provider, model=rec.route.model, base_url=rec.route.base_url,
705
- reasoning=rec.route.reasoning, options=dict(rec.route.options),
801
+ provider=route.provider, model=route.model, base_url=route.base_url,
802
+ reasoning=route.reasoning, options=dict(route.options),
706
803
  )
707
- for key, rec in table.items()
804
+ for key, (route, _download) in table.items()
708
805
  }
709
- downloads = {key: dict(rec.download) for key, rec in table.items()}
806
+ downloads = {key: dict(download) for key, (_route, download) in table.items()}
710
807
  pick = recommended_text_model(host, fit=False)
711
808
  routes["input.text"] = CapabilityRouteDefault(
712
809
  provider=pick["provider"], model=pick["model"], options=dict(pick.get("options") or {})
@@ -743,9 +840,11 @@ def image_input_unavailable_reason(
743
840
  def recommended_unavailable_routes(host: Optional[Mapping[str, Any]] = None) -> Dict[str, Dict[str, str]]:
744
841
  """Recommended rows this host cannot run: `{key: {provider, model, reason}}`.
745
842
 
746
- On every host that is not Apple silicon `output.image` and `output.video`
747
- are here (their only recommended engine, MLX-Gen, is Apple silicon only;
748
- the curated catalog has no other local image or video artifact). On Apple
843
+ On every host that is not Apple silicon `output.video` is here (its only
844
+ recommended engine, MLX-Gen, is Apple silicon only), and so is
845
+ `output.image` except on an NVIDIA GPU, whose image pick is Diffusers
846
+ (`by_accelerator`; there it is here only where the catalog says it does
847
+ not fit the GPU's memory). On Apple
749
848
  silicon the memory-gated rows (`_FIT_GATED_ROUTES`: image, video) are here
750
849
  where the catalog says their model does not fit: `output.image` on an 8 GB
751
850
  Mac (FLUX.2 klein 4B needs ~8.5 GiB), `output.video` below 32 GiB of
@@ -837,8 +936,8 @@ def configured_routes_unavailable(
837
936
  pick = recommended.get(rec_key) if rec_key else None
838
937
  if pick is not None:
839
938
  reason = f"{reason}; this computer's recommended route is {pick.provider}/{pick.model}"
840
- elif rec_key in _UNAVAILABLE_NEXT_STEP:
841
- reason = f"{reason}; {_UNAVAILABLE_NEXT_STEP[rec_key]}"
939
+ elif _unavailable_next_step(rec_key, profile):
940
+ reason = f"{reason}; {_unavailable_next_step(rec_key, profile)}"
842
941
  out[key] = {"provider": provider, "model": model, "reason": reason}
843
942
  return out
844
943
 
@@ -904,6 +1003,7 @@ def recommended_model_downloads(host: Optional[Mapping[str, Any]] = None) -> Dic
904
1003
  RECOMMENDED_SELECTORS: Dict[str, str] = {
905
1004
  "text": "input.text",
906
1005
  "voice": "output.voice",
1006
+ "stt": "input.voice",
907
1007
  "image": "output.image",
908
1008
  "video": "output.video",
909
1009
  }
@@ -1073,7 +1173,16 @@ def seed_recommended_capability_defaults(
1073
1173
  provenance marker so surfaces can label the values as recommended. The
1074
1174
  recommendation is this host's (Apple silicon: the unified-memory tier); a
1075
1175
  route this host cannot run is left unset (`recommended_unavailable_routes`).
1176
+
1177
+ With no `host` it reads the LIGHT host profile only: it runs when the
1178
+ config store is first created, on import, where no GPU tool or engine may
1179
+ load. The light reading sees no CUDA, so a fresh NVIDIA install gets no
1180
+ image route here; `apply-recommended` (full probe) writes it.
1076
1181
  """
1182
+ if host is None:
1183
+ from ..utils.host_profile import host_profile
1184
+
1185
+ host = host_profile(light=True)
1077
1186
  for key, route in recommended_capability_default_routes(host).items():
1078
1187
  existing = config.routes.get(key)
1079
1188
  if existing is None or not existing.configured():
@@ -1086,6 +1195,56 @@ def seed_recommended_capability_defaults(
1086
1195
  return config
1087
1196
 
1088
1197
 
1198
+ def upgrade_recommended_seed(
1199
+ config: CapabilityDefaultsConfig, *, host: Optional[Mapping[str, Any]] = None
1200
+ ) -> bool:
1201
+ """Add the rows later seed versions introduced to a store an earlier seed wrote.
1202
+
1203
+ Only a store stamped by an earlier seed (`seeded` is an older
1204
+ `recommended-v*`) is touched, and only the rows the newer versions added
1205
+ (`RECOMMENDED_SEED_ADDITIONS`) that are EMPTY in it: a route the operator
1206
+ set is never replaced, and a store that was never seeded is never
1207
+ changed. The recommendation is this host's (a row this host cannot run is
1208
+ left unset), read from the light host profile like the seed itself. The
1209
+ result lives in memory, stamped with the current version, exactly like a
1210
+ fresh seed: the next save of the store persists it. Returns whether the
1211
+ config changed.
1212
+ """
1213
+
1214
+ seeded = str(config.seeded or "")
1215
+ if not seeded.startswith("recommended-v") or seeded == RECOMMENDED_SEED_VERSION:
1216
+ return False
1217
+ try:
1218
+ current = int(seeded[len("recommended-v"):])
1219
+ except ValueError:
1220
+ return False
1221
+ keys = [
1222
+ key
1223
+ for version, added in RECOMMENDED_SEED_ADDITIONS
1224
+ if int(version[len("recommended-v"):]) > current
1225
+ for key in added
1226
+ ]
1227
+ if not keys:
1228
+ return False
1229
+ if host is None:
1230
+ from ..utils.host_profile import host_profile
1231
+
1232
+ host = host_profile(light=True)
1233
+ runnable = recommended_capability_default_routes(host)
1234
+ for key in keys:
1235
+ route = runnable.get(key)
1236
+ existing = config.routes.get(key)
1237
+ if route is None or (existing is not None and existing.configured()):
1238
+ continue
1239
+ config.routes[key] = CapabilityRouteDefault(
1240
+ provider=route.provider, model=route.model,
1241
+ base_url=route.base_url, reasoning=route.reasoning,
1242
+ options=dict(route.options),
1243
+ )
1244
+ config.seeded = RECOMMENDED_SEED_VERSION
1245
+ return True
1246
+
1247
+
1089
1248
  def capability_defaults_from_dict(value: Any) -> CapabilityDefaultsConfig:
1090
1249
  if isinstance(value, CapabilityDefaultsConfig):
1091
1250
  return value