eq-chatbot-core 3.2.0__tar.gz → 3.2.1__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 (259) hide show
  1. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/CHANGELOG.md +106 -0
  2. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/PKG-INFO +2 -1
  3. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/RELEASE_NOTES.md +23 -0
  4. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/pyproject.toml +7 -0
  5. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/version.py +1 -1
  6. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/reports/latest.md +57 -54
  7. eq_chatbot_core-3.2.1/tests/unit/test_docs_extra_pyproject.py +61 -0
  8. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.claude/settings.local.json +0 -0
  9. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.github/workflows/ci.yml +0 -0
  10. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.gitignore +0 -0
  11. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/MILESTONES.md +0 -0
  12. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/PROJECT.md +0 -0
  13. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/REQUIREMENTS.md +0 -0
  14. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/ROADMAP.md +0 -0
  15. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/STATE.md +0 -0
  16. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/codebase/ARCHITECTURE.md +0 -0
  17. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/codebase/CONCERNS.md +0 -0
  18. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/codebase/CONVENTIONS.md +0 -0
  19. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/codebase/INTEGRATIONS.md +0 -0
  20. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/codebase/STACK.md +0 -0
  21. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/codebase/STRUCTURE.md +0 -0
  22. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/codebase/TESTING.md +0 -0
  23. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/config.json +0 -0
  24. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/00-codebase-cleanup/00-01-PLAN.md +0 -0
  25. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/00-codebase-cleanup/00-01-SUMMARY.md +0 -0
  26. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/00-codebase-cleanup/00-02-PLAN.md +0 -0
  27. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/00-codebase-cleanup/00-02-SUMMARY.md +0 -0
  28. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/00-codebase-cleanup/00-03-PLAN.md +0 -0
  29. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/00-codebase-cleanup/00-03-SUMMARY.md +0 -0
  30. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/00-codebase-cleanup/00-VERIFICATION.md +0 -0
  31. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/01-contracts-foundation/01-01-PLAN.md +0 -0
  32. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/01-contracts-foundation/01-01-SUMMARY.md +0 -0
  33. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/01-contracts-foundation/01-02-PLAN.md +0 -0
  34. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/01-contracts-foundation/01-02-SUMMARY.md +0 -0
  35. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/01-contracts-foundation/01-03-PLAN.md +0 -0
  36. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/01-contracts-foundation/01-03-SUMMARY.md +0 -0
  37. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/01-contracts-foundation/01-04-PLAN.md +0 -0
  38. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/01-contracts-foundation/01-04-SUMMARY.md +0 -0
  39. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/01-contracts-foundation/01-05-PLAN.md +0 -0
  40. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/01-contracts-foundation/01-05-SUMMARY.md +0 -0
  41. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/01-contracts-foundation/01-PATTERNS.md +0 -0
  42. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/01-contracts-foundation/01-RESEARCH.md +0 -0
  43. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/01-contracts-foundation/01-REVIEW-FIX.md +0 -0
  44. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/01-contracts-foundation/01-REVIEW.md +0 -0
  45. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/01-contracts-foundation/01-VALIDATION.md +0 -0
  46. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/01-contracts-foundation/01-VERIFICATION.md +0 -0
  47. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/02-openai-realtime-provider/02-01-PLAN.md +0 -0
  48. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/02-openai-realtime-provider/02-01-SUMMARY.md +0 -0
  49. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/02-openai-realtime-provider/02-02-PLAN.md +0 -0
  50. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/02-openai-realtime-provider/02-02-SUMMARY.md +0 -0
  51. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/02-openai-realtime-provider/02-03-PLAN.md +0 -0
  52. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/02-openai-realtime-provider/02-03-SUMMARY.md +0 -0
  53. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/02-openai-realtime-provider/02-CONTEXT.md +0 -0
  54. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/02-openai-realtime-provider/02-DISCUSSION-LOG.md +0 -0
  55. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/02-openai-realtime-provider/02-PATTERNS.md +0 -0
  56. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/02-openai-realtime-provider/02-RESEARCH.md +0 -0
  57. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/02-openai-realtime-provider/02-REVIEW.md +0 -0
  58. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/02-openai-realtime-provider/02-VALIDATION.md +0 -0
  59. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/02-openai-realtime-provider/02-VERIFICATION.md +0 -0
  60. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03-gemini-live-nova-sonic-stub/03-01-PLAN.md +0 -0
  61. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03-gemini-live-nova-sonic-stub/03-01-SUMMARY.md +0 -0
  62. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03-gemini-live-nova-sonic-stub/03-02-PLAN.md +0 -0
  63. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03-gemini-live-nova-sonic-stub/03-02-SUMMARY.md +0 -0
  64. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03-gemini-live-nova-sonic-stub/03-03-PLAN.md +0 -0
  65. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03-gemini-live-nova-sonic-stub/03-03-SUMMARY.md +0 -0
  66. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03-gemini-live-nova-sonic-stub/03-04-PLAN.md +0 -0
  67. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03-gemini-live-nova-sonic-stub/03-04-SUMMARY.md +0 -0
  68. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03-gemini-live-nova-sonic-stub/03-05-PLAN.md +0 -0
  69. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03-gemini-live-nova-sonic-stub/03-05-SUMMARY.md +0 -0
  70. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03-gemini-live-nova-sonic-stub/03-CONTEXT.md +0 -0
  71. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03-gemini-live-nova-sonic-stub/03-DISCUSSION-LOG.md +0 -0
  72. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03-gemini-live-nova-sonic-stub/03-HUMAN-UAT.md +0 -0
  73. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03-gemini-live-nova-sonic-stub/03-PATTERNS.md +0 -0
  74. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03-gemini-live-nova-sonic-stub/03-RESEARCH.md +0 -0
  75. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03-gemini-live-nova-sonic-stub/03-VALIDATION.md +0 -0
  76. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03-gemini-live-nova-sonic-stub/03-VERIFICATION.md +0 -0
  77. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03.1-elevenlabs-agents-realtime-provider/.gitkeep +0 -0
  78. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03.1-elevenlabs-agents-realtime-provider/03.1-01-PLAN.md +0 -0
  79. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03.1-elevenlabs-agents-realtime-provider/03.1-01-SUMMARY.md +0 -0
  80. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03.1-elevenlabs-agents-realtime-provider/03.1-02-PLAN.md +0 -0
  81. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03.1-elevenlabs-agents-realtime-provider/03.1-02-SUMMARY.md +0 -0
  82. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03.1-elevenlabs-agents-realtime-provider/03.1-03-PLAN.md +0 -0
  83. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03.1-elevenlabs-agents-realtime-provider/03.1-03-SUMMARY.md +0 -0
  84. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03.1-elevenlabs-agents-realtime-provider/03.1-CONTEXT.md +0 -0
  85. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03.1-elevenlabs-agents-realtime-provider/03.1-HUMAN-UAT.md +0 -0
  86. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03.1-elevenlabs-agents-realtime-provider/03.1-PATTERNS.md +0 -0
  87. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03.1-elevenlabs-agents-realtime-provider/03.1-RESEARCH.md +0 -0
  88. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03.1-elevenlabs-agents-realtime-provider/03.1-VALIDATION.md +0 -0
  89. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/phases/03.1-elevenlabs-agents-realtime-provider/03.1-VERIFICATION.md +0 -0
  90. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/research/ARCHITECTURE.md +0 -0
  91. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/research/FEATURES.md +0 -0
  92. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/research/PITFALLS.md +0 -0
  93. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/research/STACK.md +0 -0
  94. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.planning/research/SUMMARY.md +0 -0
  95. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.pre-commit-config.yaml +0 -0
  96. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/.project-tips +0 -0
  97. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/CLAUDE.md +0 -0
  98. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/LICENSE +0 -0
  99. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/MANIFEST.in +0 -0
  100. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/README.md +0 -0
  101. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/TASKS.md +0 -0
  102. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/docs/README.md +0 -0
  103. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/docs/cli.md +0 -0
  104. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/docs/mcp.md +0 -0
  105. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/docs/providers.md +0 -0
  106. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/docs/rag.md +0 -0
  107. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/docs/reasoning.md +0 -0
  108. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/docs/security.md +0 -0
  109. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/docs/server-mode.md +0 -0
  110. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/docs/superpowers/specs/2026-05-25-elevenlabs-realtime-provider-design.md +0 -0
  111. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/docs/testing.md +0 -0
  112. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/__init__.py +0 -0
  113. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/cli.py +0 -0
  114. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/data/__init__.py +0 -0
  115. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/data/capability_catalog.json +0 -0
  116. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/data/capability_overrides.json +0 -0
  117. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/data/config.toml.example +0 -0
  118. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/mcp/__init__.py +0 -0
  119. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/mcp/client.py +0 -0
  120. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/providers/__init__.py +0 -0
  121. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/providers/anthropic_provider.py +0 -0
  122. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/providers/base.py +0 -0
  123. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/providers/ionos_provider.py +0 -0
  124. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/providers/langdock_provider.py +0 -0
  125. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/providers/litellm_provider.py +0 -0
  126. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/providers/local_provider.py +0 -0
  127. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/providers/mammouth_provider.py +0 -0
  128. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/providers/melious_provider.py +0 -0
  129. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/providers/openai_compatible.py +0 -0
  130. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/providers/openai_provider.py +0 -0
  131. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/providers/openrouter_provider.py +0 -0
  132. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/providers/privatemode_provider.py +0 -0
  133. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/providers/stream_accumulator.py +0 -0
  134. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/providers/temperature_constraints.py +0 -0
  135. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/py.typed +0 -0
  136. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/rag/__init__.py +0 -0
  137. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/rag/chunker.py +0 -0
  138. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/rag/context_manager.py +0 -0
  139. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/rag/embedder.py +0 -0
  140. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/rag/retriever.py +0 -0
  141. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/realtime/__init__.py +0 -0
  142. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/realtime/abc.py +0 -0
  143. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/realtime/contracts.py +0 -0
  144. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/realtime/factory.py +0 -0
  145. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/realtime/mock.py +0 -0
  146. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/realtime/providers/__init__.py +0 -0
  147. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/realtime/providers/elevenlabs.py +0 -0
  148. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/realtime/providers/gemini_live.py +0 -0
  149. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/realtime/providers/openai.py +0 -0
  150. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/realtime/websocket_client.py +0 -0
  151. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/security/__init__.py +0 -0
  152. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/security/encryption.py +0 -0
  153. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/security/file_validator.py +0 -0
  154. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/security/injection.py +0 -0
  155. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/security/rate_limit.py +0 -0
  156. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/server/__init__.py +0 -0
  157. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/server/app.py +0 -0
  158. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/server/auth.py +0 -0
  159. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/server/lifecycle.py +0 -0
  160. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/server/models.py +0 -0
  161. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/server/streaming.py +0 -0
  162. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/services/__init__.py +0 -0
  163. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/services/capability_catalog.py +0 -0
  164. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/services/document_extractor.py +0 -0
  165. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/services/error_handler.py +0 -0
  166. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/services/knowledge_service.py +0 -0
  167. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/utils/__init__.py +0 -0
  168. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/utils/config.py +0 -0
  169. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/utils/image.py +0 -0
  170. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/utils/pdf.py +0 -0
  171. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/utils/secret_scrub.py +0 -0
  172. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/src/eq_chatbot_core/utils/url_validation.py +0 -0
  173. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/.env.example +0 -0
  174. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/INTEGRATION_TESTS.md +0 -0
  175. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/__init__.py +0 -0
  176. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/conftest.py +0 -0
  177. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/integration/__init__.py +0 -0
  178. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/integration/test_ionos_live.py +0 -0
  179. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/integration/test_litellm_live.py +0 -0
  180. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/integration/test_local_live.py +0 -0
  181. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/integration/test_mammouth_live.py +0 -0
  182. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/integration/test_mcp_live.py +0 -0
  183. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/integration/test_melious_live.py +0 -0
  184. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/integration/test_openai_live.py +0 -0
  185. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/integration/test_openrouter_live.py +0 -0
  186. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/integration/test_privatemode_live.py +0 -0
  187. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/integration/test_realtime_elevenlabs.py +0 -0
  188. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/integration/test_realtime_gemini_live.py +0 -0
  189. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/integration/test_realtime_openai_live.py +0 -0
  190. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/model_registry.py +0 -0
  191. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/__init__.py +0 -0
  192. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/realtime/__init__.py +0 -0
  193. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/realtime/conftest.py +0 -0
  194. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/realtime/test_contracts.py +0 -0
  195. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/realtime/test_factory.py +0 -0
  196. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/realtime/test_import_guard.py +0 -0
  197. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/realtime/test_mock.py +0 -0
  198. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/realtime/test_pyproject.py +0 -0
  199. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/realtime/test_realtime_elevenlabs.py +0 -0
  200. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/realtime/test_realtime_gemini.py +0 -0
  201. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/realtime/test_realtime_openai.py +0 -0
  202. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/realtime/test_websocket_client.py +0 -0
  203. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/server/__init__.py +0 -0
  204. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/server/test_app.py +0 -0
  205. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/server/test_auth.py +0 -0
  206. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/server/test_cli_serve.py +0 -0
  207. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/server/test_lifecycle.py +0 -0
  208. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/server/test_streaming.py +0 -0
  209. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_anthropic.py +0 -0
  210. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_anthropic_temperature_transport.py +0 -0
  211. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_capability_catalog.py +0 -0
  212. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_chunker.py +0 -0
  213. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_cli_api_key.py +0 -0
  214. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_cli_chat.py +0 -0
  215. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_cli_image.py +0 -0
  216. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_cli_listing_assets.py +0 -0
  217. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_config.py +0 -0
  218. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_context_manager.py +0 -0
  219. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_document_extractor.py +0 -0
  220. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_embedder.py +0 -0
  221. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_encryption.py +0 -0
  222. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_error_handler.py +0 -0
  223. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_exceptions.py +0 -0
  224. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_factory.py +0 -0
  225. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_file_validator.py +0 -0
  226. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_gpt5_temperature.py +0 -0
  227. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_injection.py +0 -0
  228. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_ionos.py +0 -0
  229. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_knowledge_service.py +0 -0
  230. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_langdock.py +0 -0
  231. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_langdock_agent_errors.py +0 -0
  232. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_langdock_agent_streaming.py +0 -0
  233. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_langdock_backends.py +0 -0
  234. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_langdock_google_models.py +0 -0
  235. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_langdock_http_error_detail.py +0 -0
  236. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_list_models_schema.py +0 -0
  237. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_litellm.py +0 -0
  238. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_local.py +0 -0
  239. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_mammouth.py +0 -0
  240. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_mcp.py +0 -0
  241. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_melious.py +0 -0
  242. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_openai.py +0 -0
  243. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_openai_image.py +0 -0
  244. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_openrouter.py +0 -0
  245. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_openrouter_error_handling.py +0 -0
  246. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_openrouter_image.py +0 -0
  247. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_pdf.py +0 -0
  248. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_privatemode.py +0 -0
  249. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_rate_limit.py +0 -0
  250. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_retriever.py +0 -0
  251. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_security_hardening.py +0 -0
  252. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_stream_accumulator.py +0 -0
  253. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_temperature_constraints.py +0 -0
  254. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_tool_normalization.py +0 -0
  255. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_url_validation.py +0 -0
  256. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_utils_image.py +0 -0
  257. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/tests/unit/test_websocket_is_connected.py +0 -0
  258. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/usage/AGENT.md +0 -0
  259. {eq_chatbot_core-3.2.0 → eq_chatbot_core-3.2.1}/uv.lock +0 -0
@@ -5,6 +5,112 @@ All notable changes to eq-chatbot-core will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.2.1] - 2026-08-24
9
+
10
+ ### Fixed
11
+
12
+ - **`.xlsx` conversion failed with an error about pandas.** The `[docs]` extra pulls
13
+ `markitdown[xlsx]`, which asks for a bare `openpyxl` and a bare `pandas` - no version on
14
+ either. The floor `openpyxl>=3.1.5` exists only inside pandas' own `excel` extra, and
15
+ markitdown does not request that extra, so nothing in the chain ever states it. A resolver
16
+ is therefore free to leave an older `openpyxl` in place, and Odoo pins exactly 3.1.2, so the
17
+ combination is not hypothetical - it is the normal case inside an Odoo install. Every
18
+ spreadsheet ended in:
19
+
20
+ XlsxConverter threw ImportError with message: Pandas requires version '3.1.5' or
21
+ newer of 'openpyxl' (version '3.1.2' currently installed).
22
+
23
+ Downstream that surfaces as "no text could be extracted", which names neither the library
24
+ nor the version. The `[docs]` extra now declares `openpyxl>=3.1.5,<4.0.0` itself.
25
+
26
+ Only `.xlsx` was affected - `.docx` goes through mammoth, `.pptx` through python-pptx and
27
+ `.pdf` through pymupdf, none of which route via pandas. Verified against all ten supported
28
+ extensions after the change.
29
+
30
+ ## [3.2.0] - 2026-08-23
31
+
32
+ ### Breaking
33
+
34
+ - **`anthropic` floor raised to >= 1.0.0; `httpx` is no longer a core dependency.**
35
+ The Anthropic SDK moved to `httpx2` in 1.0.0 and now rejects an `httpx` client outright
36
+ (`TypeError: Expected an instance of httpx2.Client`). Since `pyproject.toml` allowed
37
+ `<2.0.0`, a `pip install -U` would have silently broken the Anthropic provider. Both SDK
38
+ clients run on `httpx2` now, and the separate `httpx>=0.27,<1` dependency is gone — it
39
+ existed solely because of that one incompatibility.
40
+
41
+ The same SDK release **removed** `temperature`, `top_p` and `top_k` from `messages.create()`
42
+ and `messages.stream()`; passing one is a `TypeError`, not a warning. The SDK notes call this
43
+ "some minor breaking changes" — in practice every temperature-setting call failed. The new
44
+ helper `providers.temperature_constraints.apply_anthropic_temperature()` clamps the value and
45
+ routes it into `extra_body`. **Never write `params["temperature"]` for an Anthropic call again.**
46
+
47
+ ### Fixed
48
+
49
+ - **GPT-5 temperature support is not uniform — every call to `gpt-5`, `gpt-5.5` and the whole
50
+ current `gpt-5.6` tier failed with HTTP 400.** The constraint table carried a single permissive
51
+ `gpt-5` prefix declaring the entire family temperature-capable. Measured live on 23.08.2026:
52
+ `gpt-5` and `gpt-5-mini` refuse ("Only the default (1) value is supported"), `gpt-5.1`/`5.2`/`5.4`
53
+ accept, `gpt-5.5` and `gpt-5.6` refuse again. The generic prefix now says **no** and the
54
+ accepting generations are listed individually, so an untested future generation inherits "no
55
+ temperature" — omitting it never breaks, sending it does.
56
+
57
+ - **`get_temperature_constraints()` fell through to the permissive default for a prefixed model.**
58
+ `openai/gpt-5.6-luna` matched no entry, got 0.0-2.0, and was therefore sent a parameter the model
59
+ rejects. The function now strips the provider prefix itself instead of trusting every caller to.
60
+
61
+ - **LangDock agents really stream now.** `_agent_stream_completion` sent `"stream": false` and
62
+ yielded the finished answer as a single chunk — no streaming, and LangDock aborts non-streaming
63
+ requests after 100 seconds with HTTP 524, which an agent with tools or knowledge search reaches
64
+ easily. Both agent paths now go through `_agent_request_events()` and the AI SDK 5 data-stream
65
+ protocol; the synchronous path streams internally and assembles the answer itself. The parser
66
+ splits on the `data: ` marker rather than on line boundaries (LangDock glues events together) and
67
+ skips unparseable blocks instead of letting one kill a running answer.
68
+
69
+ - **The LangDock Google backend was entirely broken.** `list_models()` served a hand-maintained
70
+ catalogue (`gemini-2.5-flash`, `gemini-2.5-pro`) the gateway had retired — including the backend
71
+ default model, so every call without an explicit model ended in 400. The list now comes live from
72
+ the same endpoint the error came from. On top of that the streaming path silently yielded
73
+ **nothing**: the parser looks for SSE lines, but without `?alt=sse` LangDock answers with a JSON
74
+ array — no match, no chunks, no error. The parameter is now set.
75
+
76
+ - **Google and Codestral errors swallowed their own cause.** Both logged the response body and then
77
+ called `raise_for_status()`, so the caller saw only "Client error '400 Bad Request'" while the
78
+ actual reason — a retired model, a missing entitlement — sat in a log line. The shared
79
+ `_raise_http_error()` now appends the body without losing the typed mapping (401 stays
80
+ `AuthenticationError`, 429 `RateLimitError`).
81
+
82
+ - **The Agent API's HTTP 400 is translated.** It covers three entirely different cases: agent
83
+ unknown, agent not shared with the API key, or no model resolvable at all. The last happens when
84
+ the agent's model is set to "Auto" in LangDock — the auto-router works only in the chat UI, and a
85
+ top-level `model` demonstrably does not override it. The message now names the remedy in plain
86
+ words.
87
+
88
+ ### Changed
89
+
90
+ - **Every default model raised to the current generation.** Four were unusable or outdated:
91
+ `anthropic` pointed at `claude-sonnet-4-20250514` and `melious` at `minimax-428b-m3` — both
92
+ withdrawn — while `openai`, `mammouth` and `openrouter` still named `gpt-4o`. Now:
93
+ `gpt-5.6-luna`, `claude-sonnet-5`, `openai/gpt-5.6-luna`, `nemotron-3-nano-30b-a3b`. Three of
94
+ LangDock's four backend defaults were dead (`gpt-4o`, `claude-sonnet-4-20250514`,
95
+ `gemini-2.5-flash`).
96
+
97
+ A default model is the one unavoidable constant — everything else is resolved live. The new live
98
+ guards `TestProviderDefaultsAreLive` and `TestLangDockDefaultsAreLive` fail the moment a provider
99
+ stops serving one, and additionally check that it actually answers: being listed is not enough.
100
+
101
+ - **Dependency floors raised to the current release** (21 entries), among them
102
+ `sentence-transformers` 3 -> 6, `websockets` 13 -> 17.0.1, `pytest-asyncio` 0.24 -> 1.4,
103
+ `pytest` 8 -> 9.1.1, `mypy` 1.15 -> 2.3.1, `pytest-cov` 6 -> 7.1, `qdrant-client` 1.12 -> 1.19,
104
+ `fastapi` 0.115 -> 0.141, `uvicorn` 0.32 -> 0.52. An old floor is an open vulnerability waiting
105
+ for a fresh install. The suite runs green on all new majors without a code change.
106
+
107
+ - **Live coverage now spans all LangDock backends.** Only `openai` and `anthropic` were tested;
108
+ the Google backend speaks an entirely different protocol and was never touched live — which is
109
+ precisely why its defect went unnoticed. The Codestral path has no coverage any more: Mistral is
110
+ not of interest via LangDock.
111
+
112
+ **Test suite:** 1858 unit tests, 51 live tests (0 failures), mypy `--strict` clean.
113
+
8
114
  ## [3.1.1] - 2026-08-20
9
115
 
10
116
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: eq-chatbot-core
3
- Version: 3.2.0
3
+ Version: 3.2.1
4
4
  Summary: Core library for LLM chatbot integration with multi-provider support (OpenAI, Anthropic, LangDock, OpenRouter, Mammouth, Azure, Vertex AI, LiteLLM, IONOS, Local)
5
5
  Project-URL: Homepage, https://www.ownerp.com
6
6
  Project-URL: Documentation, https://github.com/equitania/eq-chatbot-core
@@ -39,6 +39,7 @@ Requires-Dist: ruff<1.0.0,>=0.16.4; extra == 'dev'
39
39
  Requires-Dist: twine<8.0.0,>=7.0.0; extra == 'dev'
40
40
  Provides-Extra: docs
41
41
  Requires-Dist: markitdown[docx,pdf,pptx,xlsx]<0.2.0,>=0.1.7; extra == 'docs'
42
+ Requires-Dist: openpyxl<4.0.0,>=3.1.5; extra == 'docs'
42
43
  Requires-Dist: pymupdf<2.0.0,>=1.28.2; extra == 'docs'
43
44
  Provides-Extra: image
44
45
  Requires-Dist: pillow<13.0,>=12.3.0; extra == 'image'
@@ -1,5 +1,28 @@
1
1
  # Release Notes
2
2
 
3
+ ## Version 3.2.1 (24.08.2026)
4
+
5
+ ### [FIX]
6
+
7
+ - **Die Umwandlung von `.xlsx` scheiterte mit einer Meldung über pandas.** Das `[docs]`-Extra
8
+ zieht `markitdown[xlsx]`, und das verlangt schlicht „openpyxl" und „pandas" — ohne jede
9
+ Fassungsangabe. Die Untergrenze `openpyxl>=3.1.5` steht allein im `excel`-Extra von pandas,
10
+ das markitdown nicht anfordert; in der ganzen Kette nennt sie also niemand. Ein Auflöser darf
11
+ damit eine ältere Fassung stehen lassen, und Odoo legt sich genau auf 3.1.2 fest — die
12
+ Kombination ist demnach kein Sonderfall, sondern der Regelfall in einer Odoo-Installation.
13
+ Jede Tabellenkalkulation endete mit:
14
+
15
+ XlsxConverter threw ImportError with message: Pandas requires version '3.1.5' or
16
+ newer of 'openpyxl' (version '3.1.2' currently installed).
17
+
18
+ Beim Aufrufer kommt davon „Es konnte kein Text extrahiert werden" an — eine Meldung, die
19
+ weder die Bibliothek noch die Fassung nennt. Das `[docs]`-Extra verlangt jetzt selbst
20
+ `openpyxl>=3.1.5,<4.0.0`.
21
+
22
+ Betroffen war ausschließlich `.xlsx`: `.docx` läuft über mammoth, `.pptx` über python-pptx
23
+ und `.pdf` über pymupdf, keines davon über pandas. Nach der Änderung gegen alle zehn
24
+ unterstützten Endungen geprüft.
25
+
3
26
  ## Version 3.2.0 (23.08.2026)
4
27
 
5
28
  ### ⚠️ Breaking
@@ -103,6 +103,13 @@ docs = [
103
103
  # Document-to-Markdown extraction for knowledge ingestion (services/document_extractor.py)
104
104
  "markitdown[docx,pdf,pptx,xlsx]>=0.1.7,<0.2.0",
105
105
  "pymupdf>=1.28.2,<2.0.0", # Embedded-image extraction from PDFs
106
+ # Stated here because nothing else states it. markitdown's [xlsx] extra asks for
107
+ # a bare "openpyxl" and a bare "pandas"; the floor openpyxl>=3.1.5 lives only in
108
+ # pandas' own "excel" extra, which markitdown does not request. So a resolver is
109
+ # free to leave an older openpyxl in place - and Odoo pins exactly 3.1.2 - after
110
+ # which every .xlsx conversion dies with "Pandas requires version '3.1.5' or
111
+ # newer of 'openpyxl'". The consumer sees "no text could be extracted".
112
+ "openpyxl>=3.1.5,<4.0.0",
106
113
  ]
107
114
  image = [
108
115
  # Floor 12.3.0 excludes CVE-2026-59205 (ImageCmsTransform buffer overflow),
@@ -1,3 +1,3 @@
1
1
  """Version information for eq-chatbot-core."""
2
2
 
3
- __version__ = "3.2.0"
3
+ __version__ = "3.2.1"
@@ -1,19 +1,19 @@
1
- # Test Report - 2026-08-23 20:11:01
1
+ # Test Report - 2026-08-24 15:08:24
2
2
 
3
- **eq_chatbot_core v3.1.1** | 8.99s | Python 3.13.12 | macOS-26.6.2-arm64-arm-64bit-Mach-O
3
+ **eq_chatbot_core v3.2.1** | 10.66s | Python 3.13.12 | macOS-26.6.2-arm64-arm-64bit-Mach-O
4
4
 
5
- > **Result: ALL PASSED - 1858 tests OK, 5 expected failures**
5
+ > **Result: ALL PASSED - 1861 tests OK, 5 expected failures**
6
6
 
7
- Command: `/Users/picard/gitbase/PyPi-Projects/eq_chatbot_core/.venv/lib/python3.13/site-packages/pytest/__main__.py tests/unit/ -q --no-header -p no:cacheprovider`
7
+ Command: `/Users/picard/gitbase/PyPi-Projects/eq_chatbot_core/.venv/lib/python3.13/site-packages/pytest/__main__.py tests/unit/ -q --no-header`
8
8
 
9
9
  ## Summary
10
10
 
11
11
  | Status | Count |
12
12
  |--------|-------|
13
- | Passed | 1858 |
13
+ | Passed | 1861 |
14
14
  | Failed | 0 |
15
15
  | XFailed (expected) | 5 |
16
- | **Total** | **1863** |
16
+ | **Total** | **1866** |
17
17
 
18
18
  ## Configuration Status
19
19
 
@@ -49,24 +49,24 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
49
49
  | Module | Test Model | Passed | Failed | Skipped | XFailed | Total | Duration |
50
50
  |--------|------------|--------|--------|---------|---------|-------|----------|
51
51
  | **Provider: OpenAI** | - | 47 | 0 | 0 | 0 | 47 | 0.09s |
52
- | **Provider: Anthropic** | - | 51 | 0 | 0 | 0 | 51 | 0.13s |
53
- | **Provider: LangDock** | - | 60 | 0 | 0 | 0 | 60 | 0.04s |
54
- | **Provider: OpenRouter** | - | 38 | 0 | 0 | 0 | 38 | 0.01s |
55
- | **Provider: Mammouth AI** | - | 41 | 0 | 0 | 0 | 41 | 0.01s |
52
+ | **Provider: Anthropic** | - | 51 | 0 | 0 | 0 | 51 | 0.11s |
53
+ | **Provider: LangDock** | - | 60 | 0 | 0 | 0 | 60 | 0.08s |
54
+ | **Provider: OpenRouter** | - | 38 | 0 | 0 | 0 | 38 | 0.06s |
55
+ | **Provider: Mammouth AI** | - | 41 | 0 | 0 | 0 | 41 | 0.06s |
56
56
  | **Provider: LiteLLM Gateway** | - | 30 | 0 | 0 | 0 | 30 | 0.02s |
57
- | **Provider: IONOS AI Model Hub** | - | 27 | 0 | 0 | 0 | 27 | 0.05s |
58
- | **Provider: Melious.ai (sovereign EU)** | - | 28 | 0 | 0 | 0 | 28 | 0.05s |
57
+ | **Provider: IONOS AI Model Hub** | - | 27 | 0 | 0 | 0 | 27 | 0.08s |
58
+ | **Provider: Melious.ai (sovereign EU)** | - | 28 | 0 | 0 | 0 | 28 | 0.04s |
59
59
  | **Provider: Privatemode.ai (end-to-end encrypted)** | - | 29 | 0 | 0 | 0 | 29 | 0.02s |
60
- | **Provider: Local (LM Studio / Ollama)** | - | 35 | 0 | 0 | 0 | 35 | 0.05s |
61
- | **Security** | - | 423 | 0 | 0 | 5 | 428 | 0.04s |
62
- | **RAG Pipeline** | - | 117 | 0 | 0 | 0 | 117 | 0.34s |
63
- | **Services & Core** | - | 166 | 0 | 0 | 0 | 166 | 0.09s |
64
- | **MCP Client** | - | 99 | 0 | 0 | 0 | 99 | 1.57s |
65
- | **Other** | - | 667 | 0 | 0 | 0 | 667 | 3.77s |
60
+ | **Provider: Local (LM Studio / Ollama)** | - | 35 | 0 | 0 | 0 | 35 | 0.04s |
61
+ | **Security** | - | 423 | 0 | 0 | 5 | 428 | 0.05s |
62
+ | **RAG Pipeline** | - | 117 | 0 | 0 | 0 | 117 | 0.42s |
63
+ | **Services & Core** | - | 166 | 0 | 0 | 0 | 166 | 0.13s |
64
+ | **MCP Client** | - | 99 | 0 | 0 | 0 | 99 | 1.53s |
65
+ | **Other** | - | 670 | 0 | 0 | 0 | 670 | 4.26s |
66
66
 
67
67
  ## Detailed Results
68
68
 
69
- ### Unit Tests (1858 passed, 5 xfailed)
69
+ ### Unit Tests (1861 passed, 5 xfailed)
70
70
 
71
71
  #### Provider: OpenAI (47 passed) - 0.09s
72
72
 
@@ -120,7 +120,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
120
120
  | `unit/test_openai.py::TestOpenAIProviderProperties::test_reasoning_model_no_temperature` | PASSED | <0.01s |
121
121
  | `unit/test_openai.py::TestOpenAIProviderProperties::test_gpt41_temperature_passthrough` | PASSED | <0.01s |
122
122
 
123
- #### Provider: Anthropic (51 passed) - 0.13s
123
+ #### Provider: Anthropic (51 passed) - 0.11s
124
124
 
125
125
  | Test | Status | Duration |
126
126
  |------|--------|----------|
@@ -130,7 +130,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
130
130
  | `unit/test_anthropic.py::TestAnthropicProviderInit::test_private_range_blocked` | PASSED | <0.01s |
131
131
  | `unit/test_anthropic.py::TestAnthropicProviderInit::test_non_http_scheme_blocked` | PASSED | <0.01s |
132
132
  | `unit/test_anthropic.py::TestAnthropicProviderInit::test_lazy_client_initialization` | PASSED | <0.01s |
133
- | `unit/test_anthropic.py::TestAnthropicProviderInit::test_client_property_creates_client` | PASSED | 0.09s |
133
+ | `unit/test_anthropic.py::TestAnthropicProviderInit::test_client_property_creates_client` | PASSED | 0.07s |
134
134
  | `unit/test_anthropic.py::TestAnthropicProviderInit::test_client_reuses_instance` | PASSED | <0.01s |
135
135
  | `unit/test_anthropic.py::TestSystemPromptExtraction::test_extract_single_system_prompt` | PASSED | <0.01s |
136
136
  | `unit/test_anthropic.py::TestSystemPromptExtraction::test_extract_multiple_system_prompts` | PASSED | <0.01s |
@@ -176,7 +176,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
176
176
  | `unit/test_anthropic.py::TestAnthropicProviderProperties::test_claude_temperature_clamped_to_max` | PASSED | <0.01s |
177
177
  | `unit/test_anthropic.py::TestAnthropicProviderProperties::test_repr` | PASSED | <0.01s |
178
178
 
179
- #### Provider: LangDock (60 passed) - 0.04s
179
+ #### Provider: LangDock (60 passed) - 0.08s
180
180
 
181
181
  | Test | Status | Duration |
182
182
  |------|--------|----------|
@@ -204,7 +204,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
204
204
  | `unit/test_langdock.py::TestLangDockTokenAPI::test_gpt5_uses_new_api` | PASSED | <0.01s |
205
205
  | `unit/test_langdock.py::TestLangDockTokenAPI::test_reasoning_models_use_new_api` | PASSED | <0.01s |
206
206
  | `unit/test_langdock.py::TestLangDockTokenAPI::test_gpt4_turbo_uses_legacy_api` | PASSED | <0.01s |
207
- | `unit/test_langdock.py::TestLangDockOpenAIChatCompletion::test_simple_completion` | PASSED | <0.01s |
207
+ | `unit/test_langdock.py::TestLangDockOpenAIChatCompletion::test_simple_completion` | PASSED | 0.05s |
208
208
  | `unit/test_langdock.py::TestLangDockOpenAIChatCompletion::test_completion_with_temperature` | PASSED | <0.01s |
209
209
  | `unit/test_langdock.py::TestLangDockOpenAIChatCompletion::test_completion_without_temperature_for_reasoning` | PASSED | <0.01s |
210
210
  | `unit/test_langdock.py::TestLangDockOpenAIChatCompletion::test_completion_with_reasoning_effort` | PASSED | <0.01s |
@@ -241,7 +241,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
241
241
  | `unit/test_langdock.py::TestLangDockSSRFGuard::test_localhost_base_url_accepted` | PASSED | <0.01s |
242
242
  | `unit/test_langdock.py::TestLangDockSSRFGuard::test_default_base_url_skips_validation` | PASSED | <0.01s |
243
243
 
244
- #### Provider: OpenRouter (38 passed) - 0.01s
244
+ #### Provider: OpenRouter (38 passed) - 0.06s
245
245
 
246
246
  | Test | Status | Duration |
247
247
  |------|--------|----------|
@@ -275,7 +275,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
275
275
  | `unit/test_openrouter.py::TestOpenRouterErrorHandling::test_rate_limit_error` | PASSED | <0.01s |
276
276
  | `unit/test_openrouter.py::TestOpenRouterErrorHandling::test_authentication_error` | PASSED | <0.01s |
277
277
  | `unit/test_openrouter.py::TestOpenRouterErrorHandling::test_context_length_error` | PASSED | <0.01s |
278
- | `unit/test_openrouter.py::TestOpenRouterErrorHandling::test_generic_error` | PASSED | <0.01s |
278
+ | `unit/test_openrouter.py::TestOpenRouterErrorHandling::test_generic_error` | PASSED | 0.05s |
279
279
  | `unit/test_openrouter.py::TestOpenRouterContextManager::test_close_client` | PASSED | <0.01s |
280
280
  | `unit/test_openrouter.py::TestOpenRouterContextManager::test_context_manager` | PASSED | <0.01s |
281
281
  | `unit/test_openrouter.py::TestOpenRouterFactoryIntegration::test_get_provider_returns_openrouter` | PASSED | <0.01s |
@@ -284,7 +284,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
284
284
  | `unit/test_openrouter.py::TestOpenRouterSSRFGuard::test_localhost_base_url_accepted` | PASSED | <0.01s |
285
285
  | `unit/test_openrouter.py::TestOpenRouterSSRFGuard::test_default_base_url_skips_validation` | PASSED | <0.01s |
286
286
 
287
- #### Provider: Mammouth AI (41 passed) - 0.01s
287
+ #### Provider: Mammouth AI (41 passed) - 0.06s
288
288
 
289
289
  | Test | Status | Duration |
290
290
  |------|--------|----------|
@@ -321,7 +321,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
321
321
  | `unit/test_mammouth.py::TestMammouthErrorHandling::test_authentication_error` | PASSED | <0.01s |
322
322
  | `unit/test_mammouth.py::TestMammouthErrorHandling::test_overloaded_error` | PASSED | <0.01s |
323
323
  | `unit/test_mammouth.py::TestMammouthErrorHandling::test_context_length_error` | PASSED | <0.01s |
324
- | `unit/test_mammouth.py::TestMammouthErrorHandling::test_generic_error` | PASSED | <0.01s |
324
+ | `unit/test_mammouth.py::TestMammouthErrorHandling::test_generic_error` | PASSED | 0.05s |
325
325
  | `unit/test_mammouth.py::TestMammouthContextManager::test_close_client` | PASSED | <0.01s |
326
326
  | `unit/test_mammouth.py::TestMammouthContextManager::test_context_manager` | PASSED | <0.01s |
327
327
  | `unit/test_mammouth.py::TestMammouthFactoryIntegration::test_get_provider_returns_mammouth` | PASSED | <0.01s |
@@ -365,7 +365,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
365
365
  | `unit/test_litellm.py::TestLiteLLMContextManager::test_close_closes_client` | PASSED | <0.01s |
366
366
  | `unit/test_litellm.py::TestLiteLLMContextManager::test_context_manager` | PASSED | <0.01s |
367
367
 
368
- #### Provider: IONOS AI Model Hub (27 passed) - 0.05s
368
+ #### Provider: IONOS AI Model Hub (27 passed) - 0.08s
369
369
 
370
370
  | Test | Status | Duration |
371
371
  |------|--------|----------|
@@ -382,7 +382,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
382
382
  | `unit/test_ionos.py::TestIonosProviderProperties::test_default_model_override` | PASSED | <0.01s |
383
383
  | `unit/test_ionos.py::TestIonosChatCompletion::test_simple_completion` | PASSED | <0.01s |
384
384
  | `unit/test_ionos.py::TestIonosChatCompletion::test_uses_default_model` | PASSED | <0.01s |
385
- | `unit/test_ionos.py::TestIonosChatCompletion::test_explicit_model_and_max_tokens` | PASSED | <0.01s |
385
+ | `unit/test_ionos.py::TestIonosChatCompletion::test_explicit_model_and_max_tokens` | PASSED | 0.04s |
386
386
  | `unit/test_ionos.py::TestIonosChatCompletion::test_tools_passed_through` | PASSED | <0.01s |
387
387
  | `unit/test_ionos.py::TestIonosChatCompletion::test_temperature_clamped` | PASSED | <0.01s |
388
388
  | `unit/test_ionos.py::TestIonosChatCompletion::test_tool_calls_parsed` | PASSED | <0.01s |
@@ -397,7 +397,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
397
397
  | `unit/test_ionos.py::TestIonosContextManager::test_close_closes_client` | PASSED | <0.01s |
398
398
  | `unit/test_ionos.py::TestIonosContextManager::test_context_manager` | PASSED | <0.01s |
399
399
 
400
- #### Provider: Melious.ai (sovereign EU) (28 passed) - 0.05s
400
+ #### Provider: Melious.ai (sovereign EU) (28 passed) - 0.04s
401
401
 
402
402
  | Test | Status | Duration |
403
403
  |------|--------|----------|
@@ -464,12 +464,12 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
464
464
  | `unit/test_privatemode.py::TestPrivatemodeErrorHandling::test_rate_limit_mapped` | PASSED | <0.01s |
465
465
  | `unit/test_privatemode.py::TestPrivatemodeErrorHandling::test_generic_error_mapped` | PASSED | <0.01s |
466
466
 
467
- #### Provider: Local (LM Studio / Ollama) (35 passed) - 0.05s
467
+ #### Provider: Local (LM Studio / Ollama) (35 passed) - 0.04s
468
468
 
469
469
  | Test | Status | Duration |
470
470
  |------|--------|----------|
471
471
  | `unit/test_local.py::TestLocalLLMProviderInit::test_default_initialization` | PASSED | <0.01s |
472
- | `unit/test_local.py::TestLocalLLMProviderInit::test_custom_base_url` | PASSED | 0.02s |
472
+ | `unit/test_local.py::TestLocalLLMProviderInit::test_custom_base_url` | PASSED | <0.01s |
473
473
  | `unit/test_local.py::TestLocalLLMProviderInit::test_ollama_url` | PASSED | <0.01s |
474
474
  | `unit/test_local.py::TestLocalLLMProviderInit::test_lm_studio_url` | PASSED | <0.01s |
475
475
  | `unit/test_local.py::TestLocalLLMProviderInit::test_custom_timeout` | PASSED | <0.01s |
@@ -504,7 +504,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
504
504
  | `unit/test_local.py::TestLocalLLMProviderClient::test_client_reuses_instance` | PASSED | <0.01s |
505
505
  | `unit/test_local.py::TestLocalLLMProviderClient::test_client_includes_auth_header` | PASSED | <0.01s |
506
506
 
507
- #### Security (423 passed, 5 xfailed) - 0.04s
507
+ #### Security (423 passed, 5 xfailed) - 0.05s
508
508
 
509
509
  | Test | Status | Duration | Detail |
510
510
  |------|--------|----------|--------|
@@ -937,7 +937,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
937
937
  | `unit/test_rate_limit.py::TestEnforceRateLimit::test_non_atomic_records_usage_when_allowed` | PASSED | <0.01s | |
938
938
  | `unit/test_rate_limit.py::TestEnforceRateLimit::test_non_atomic_does_not_record_when_denied` | PASSED | <0.01s | |
939
939
 
940
- #### RAG Pipeline (117 passed) - 0.34s
940
+ #### RAG Pipeline (117 passed) - 0.42s
941
941
 
942
942
  | Test | Status | Duration |
943
943
  |------|--------|----------|
@@ -1015,7 +1015,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
1015
1015
  | `unit/test_knowledge_service.py::TestKnowledgeExporter::test_export_to_langdock_filenames_cannot_escape_temp_dir` | PASSED | <0.01s |
1016
1016
  | `unit/test_knowledge_service.py::TestKnowledgeExporter::test_empty_records` | PASSED | <0.01s |
1017
1017
  | `unit/test_knowledge_service.py::TestExportRecord::test_create_export_record` | PASSED | <0.01s |
1018
- | `unit/test_retriever.py::TestRetrieveSuccess::test_retrieve_returns_retrieval_results` | PASSED | 0.32s |
1018
+ | `unit/test_retriever.py::TestRetrieveSuccess::test_retrieve_returns_retrieval_results` | PASSED | 0.41s |
1019
1019
  | `unit/test_retriever.py::TestRetrieveSuccess::test_retrieve_maps_payload_fields_correctly` | PASSED | <0.01s |
1020
1020
  | `unit/test_retriever.py::TestRetrieveSuccess::test_retrieve_calls_embedder_with_query` | PASSED | <0.01s |
1021
1021
  | `unit/test_retriever.py::TestRetrieveSuccess::test_retrieve_calls_qdrant_search` | PASSED | <0.01s |
@@ -1059,7 +1059,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
1059
1059
  | `unit/test_retriever.py::TestUpsert::test_upsert_qdrant_failure_raises_connection_error` | PASSED | <0.01s |
1060
1060
  | `unit/test_retriever.py::TestUpsert::test_upsert_handles_missing_optional_fields` | PASSED | <0.01s |
1061
1061
 
1062
- #### Services & Core (166 passed) - 0.09s
1062
+ #### Services & Core (166 passed) - 0.13s
1063
1063
 
1064
1064
  | Test | Status | Duration |
1065
1065
  |------|--------|----------|
@@ -1157,8 +1157,8 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
1157
1157
  | `unit/test_factory.py::TestFactoryCloudProviders::test_get_anthropic_provider` | PASSED | <0.01s |
1158
1158
  | `unit/test_factory.py::TestFactoryCloudProviders::test_get_langdock_provider` | PASSED | <0.01s |
1159
1159
  | `unit/test_factory.py::TestFactoryCloudProviders::test_get_openrouter_provider` | PASSED | <0.01s |
1160
- | `unit/test_factory.py::TestFactoryCloudProviders::test_get_ionos_provider` | PASSED | <0.01s |
1161
- | `unit/test_factory.py::TestFactoryCloudProviders::test_get_melious_provider` | PASSED | 0.05s |
1160
+ | `unit/test_factory.py::TestFactoryCloudProviders::test_get_ionos_provider` | PASSED | 0.06s |
1161
+ | `unit/test_factory.py::TestFactoryCloudProviders::test_get_melious_provider` | PASSED | 0.04s |
1162
1162
  | `unit/test_factory.py::TestFactoryCloudProviders::test_provider_name_case_insensitive` | PASSED | <0.01s |
1163
1163
  | `unit/test_factory.py::TestFactoryCloudProviders::test_custom_base_url` | PASSED | <0.01s |
1164
1164
  | `unit/test_factory.py::TestFactoryCloudProviders::test_custom_base_url_ssrf_blocked` | PASSED | <0.01s |
@@ -1167,7 +1167,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
1167
1167
  | `unit/test_factory.py::TestFactoryLocalProviders::test_get_lm_studio_alias` | PASSED | <0.01s |
1168
1168
  | `unit/test_factory.py::TestFactoryLocalProviders::test_get_lmstudio_alias` | PASSED | <0.01s |
1169
1169
  | `unit/test_factory.py::TestFactoryLocalProviders::test_get_ollama_alias` | PASSED | <0.01s |
1170
- | `unit/test_factory.py::TestFactoryLocalProviders::test_local_alias_custom_base_url_override` | PASSED | 0.02s |
1170
+ | `unit/test_factory.py::TestFactoryLocalProviders::test_local_alias_custom_base_url_override` | PASSED | <0.01s |
1171
1171
  | `unit/test_factory.py::TestFactoryLocalProviders::test_local_provider_with_api_key` | PASSED | <0.01s |
1172
1172
  | `unit/test_factory.py::TestFactoryLocalProviders::test_local_alias_case_insensitive` | PASSED | <0.01s |
1173
1173
  | `unit/test_factory.py::TestFactoryErrors::test_unknown_provider_raises_error` | PASSED | <0.01s |
@@ -1230,7 +1230,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
1230
1230
  | `unit/test_temperature_constraints.py::TestClampWithPrefixStrip::test_openrouter_o3_returns_none` | PASSED | <0.01s |
1231
1231
  | `unit/test_temperature_constraints.py::TestClampWithPrefixStrip::test_openrouter_unknown_passthrough` | PASSED | <0.01s |
1232
1232
 
1233
- #### MCP Client (99 passed) - 1.57s
1233
+ #### MCP Client (99 passed) - 1.53s
1234
1234
 
1235
1235
  | Test | Status | Duration |
1236
1236
  |------|--------|----------|
@@ -1254,12 +1254,12 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
1254
1254
  | `unit/test_mcp.py::TestMCPClientRequests::test_send_request_success` | PASSED | 0.05s |
1255
1255
  | `unit/test_mcp.py::TestMCPClientRequests::test_send_request_timeout` | PASSED | 1.01s |
1256
1256
  | `unit/test_mcp.py::TestMCPClientRequests::test_send_request_http_error` | PASSED | <0.01s |
1257
- | `unit/test_mcp.py::TestMCPClientRequests::test_send_request_increments_id` | PASSED | 0.06s |
1258
- | `unit/test_mcp.py::TestMCPClientToolOperations::test_list_tools_success` | PASSED | 0.06s |
1257
+ | `unit/test_mcp.py::TestMCPClientRequests::test_send_request_increments_id` | PASSED | 0.05s |
1258
+ | `unit/test_mcp.py::TestMCPClientToolOperations::test_list_tools_success` | PASSED | 0.05s |
1259
1259
  | `unit/test_mcp.py::TestMCPClientToolOperations::test_list_tools_empty` | PASSED | 0.06s |
1260
1260
  | `unit/test_mcp.py::TestMCPClientToolOperations::test_call_tool_success` | PASSED | 0.06s |
1261
1261
  | `unit/test_mcp.py::TestMCPClientToolOperations::test_call_tool_error` | PASSED | 0.06s |
1262
- | `unit/test_mcp.py::TestMCPClientToolOperations::test_get_tool_schema_found` | PASSED | 0.05s |
1262
+ | `unit/test_mcp.py::TestMCPClientToolOperations::test_get_tool_schema_found` | PASSED | 0.06s |
1263
1263
  | `unit/test_mcp.py::TestMCPClientToolOperations::test_get_tool_schema_not_found` | PASSED | 0.05s |
1264
1264
  | `unit/test_mcp.py::TestMCPClientToolOperations::test_call_tool_extracts_text_content` | PASSED | 0.06s |
1265
1265
  | `unit/test_mcp.py::TestMCPClientContextManager::test_context_manager_enter_exit` | PASSED | <0.01s |
@@ -1334,7 +1334,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
1334
1334
  | `unit/test_mcp.py::TestDNSRebindingProtection::test_mcpclient_pins_base_url_on_init` | PASSED | <0.01s |
1335
1335
  | `unit/test_mcp.py::TestDNSRebindingProtection::test_mcpclient_pins_endpoint_from_sse_event` | PASSED | <0.01s |
1336
1336
 
1337
- #### Other (667 passed) - 3.77s
1337
+ #### Other (670 passed) - 4.26s
1338
1338
 
1339
1339
  | Test | Status | Duration |
1340
1340
  |------|--------|----------|
@@ -1528,7 +1528,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
1528
1528
  | `unit/realtime/test_realtime_openai.py::TestConnectLifecycle::test_implements_contract` | PASSED | <0.01s |
1529
1529
  | `unit/realtime/test_realtime_openai.py::TestCloseLifecycle::test_close_does_not_raise_when_not_connected` | PASSED | <0.01s |
1530
1530
  | `unit/realtime/test_websocket_client.py::test_connect_with_backoff_3_failures_then_success` | PASSED | <0.01s |
1531
- | `unit/realtime/test_websocket_client.py::test_connect_with_backoff_all_failures_raises` | PASSED | 1.32s |
1531
+ | `unit/realtime/test_websocket_client.py::test_connect_with_backoff_all_failures_raises` | PASSED | 1.62s |
1532
1532
  | `unit/realtime/test_websocket_client.py::test_realtime_closed_error_fields` | PASSED | <0.01s |
1533
1533
  | `unit/realtime/test_websocket_client.py::test_realtime_rate_limit_error` | PASSED | <0.01s |
1534
1534
  | `unit/realtime/test_websocket_client.py::test_error_hierarchy` | PASSED | <0.01s |
@@ -1604,7 +1604,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
1604
1604
  | `unit/test_capability_catalog.py::TestProviderScoping::test_provider_hint_disambiguates` | PASSED | <0.01s |
1605
1605
  | `unit/test_capability_catalog.py::TestMetadataExposed::test_meta_and_providers_available` | PASSED | <0.01s |
1606
1606
  | `unit/test_capability_catalog.py::TestSnapshotAndRemote::test_from_snapshot_loads_known_model` | PASSED | <0.01s |
1607
- | `unit/test_capability_catalog.py::TestSnapshotAndRemote::test_from_remote_falls_back_to_snapshot` | PASSED | <0.01s |
1607
+ | `unit/test_capability_catalog.py::TestSnapshotAndRemote::test_from_remote_falls_back_to_snapshot` | PASSED | 0.09s |
1608
1608
  | `unit/test_cli_api_key.py::TestResolveApiKey::test_explicit_flag_wins_over_everything` | PASSED | <0.01s |
1609
1609
  | `unit/test_cli_api_key.py::TestResolveApiKey::test_provider_specific_env_used_when_no_flag` | PASSED | <0.01s |
1610
1610
  | `unit/test_cli_api_key.py::TestResolveApiKey::test_provider_specific_env_wins_over_generic` | PASSED | <0.01s |
@@ -1638,7 +1638,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
1638
1638
  | `unit/test_cli_image.py::TestImageCommand::test_prompt_file` | PASSED | <0.01s |
1639
1639
  | `unit/test_cli_image.py::TestImageCommand::test_custom_model_forwarded` | PASSED | <0.01s |
1640
1640
  | `unit/test_cli_image.py::TestImageCommand::test_openrouter_provider_accepted` | PASSED | <0.01s |
1641
- | `unit/test_cli_image.py::TestImageCommand::test_fit_option_resizes_image` | PASSED | 0.04s |
1641
+ | `unit/test_cli_image.py::TestImageCommand::test_fit_option_resizes_image` | PASSED | 0.02s |
1642
1642
  | `unit/test_cli_listing_assets.py::TestListingAssetsCommand::test_successful_batch_creates_files` | PASSED | <0.01s |
1643
1643
  | `unit/test_cli_listing_assets.py::TestListingAssetsCommand::test_dest_defaults_to_recipe_directory` | PASSED | <0.01s |
1644
1644
  | `unit/test_cli_listing_assets.py::TestListingAssetsCommand::test_provider_called_once_for_batch` | PASSED | <0.01s |
@@ -1694,6 +1694,9 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
1694
1694
  | `unit/test_config.py::TestConfigCLI::test_config_show_masks_keys` | PASSED | <0.01s |
1695
1695
  | `unit/test_config.py::TestConfigIntegration::test_test_provider_uses_config_key` | PASSED | <0.01s |
1696
1696
  | `unit/test_config.py::test_module_exposes_cli_resolvers` | PASSED | <0.01s |
1697
+ | `unit/test_docs_extra_pyproject.py::test_docs_extra_declares_openpyxl` | PASSED | <0.01s |
1698
+ | `unit/test_docs_extra_pyproject.py::test_the_floor_is_high_enough_for_pandas` | PASSED | <0.01s |
1699
+ | `unit/test_docs_extra_pyproject.py::test_the_extra_still_fences_off_the_next_major` | PASSED | <0.01s |
1697
1700
  | `unit/test_document_extractor.py::TestPlainTextExtraction::test_markdown_passthrough` | PASSED | <0.01s |
1698
1701
  | `unit/test_document_extractor.py::TestPlainTextExtraction::test_txt_passthrough` | PASSED | <0.01s |
1699
1702
  | `unit/test_document_extractor.py::TestPlainTextExtraction::test_latin1_fallback_warns` | PASSED | <0.01s |
@@ -1703,7 +1706,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
1703
1706
  | `unit/test_document_extractor.py::TestGuards::test_non_bytes_rejected` | PASSED | <0.01s |
1704
1707
  | `unit/test_document_extractor.py::TestGuards::test_supported_extensions_always_include_plain_text` | PASSED | <0.01s |
1705
1708
  | `unit/test_document_extractor.py::TestRichFormats::test_missing_markitdown_yields_warning_not_crash` | PASSED | <0.01s |
1706
- | `unit/test_document_extractor.py::TestRichFormats::test_html_extraction` | PASSED | 0.01s |
1709
+ | `unit/test_document_extractor.py::TestRichFormats::test_html_extraction` | PASSED | 0.05s |
1707
1710
  | `unit/test_embedder.py::TestOpenAIEmbedder::test_unknown_model_raises` | PASSED | <0.01s |
1708
1711
  | `unit/test_embedder.py::TestOpenAIEmbedder::test_dimensions_from_catalog` | PASSED | <0.01s |
1709
1712
  | `unit/test_embedder.py::TestLangDockEmbedder::test_region_sets_base_url` | PASSED | <0.01s |
@@ -1879,17 +1882,17 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
1879
1882
  | `unit/test_openrouter_image.py::TestOpenRouterImageGeneration::test_generate_image_custom_model` | PASSED | <0.01s |
1880
1883
  | `unit/test_openrouter_image.py::TestOpenRouterImageGeneration::test_generate_image_invalid_data_url_raises_error` | PASSED | <0.01s |
1881
1884
  | `unit/test_pdf.py::TestAvailability::test_available_when_pymupdf_installed` | PASSED | <0.01s |
1882
- | `unit/test_pdf.py::TestConversion::test_single_page_returns_one_png` | PASSED | 0.03s |
1885
+ | `unit/test_pdf.py::TestConversion::test_single_page_returns_one_png` | PASSED | 0.04s |
1883
1886
  | `unit/test_pdf.py::TestConversion::test_jpeg_format_selected` | PASSED | 0.03s |
1884
- | `unit/test_pdf.py::TestConversion::test_all_pages_converted` | PASSED | 0.06s |
1887
+ | `unit/test_pdf.py::TestConversion::test_all_pages_converted` | PASSED | 0.07s |
1885
1888
  | `unit/test_pdf.py::TestConversion::test_max_pages_truncates` | PASSED | 0.05s |
1886
1889
  | `unit/test_pdf.py::TestConversion::test_higher_dpi_produces_larger_image` | PASSED | 0.10s |
1887
1890
  | `unit/test_pdf.py::TestConversion::test_corrupt_pdf_returns_empty_list_instead_of_raising` | PASSED | <0.01s |
1888
1891
  | `unit/test_pdf.py::TestInputValidation::test_invalid_format_rejected` | PASSED | <0.01s |
1889
- | `unit/test_pdf.py::TestInputValidation::test_oversized_input_rejected` | PASSED | <0.01s |
1890
- | `unit/test_pdf.py::TestResourceClamping::test_page_count_clamped_to_hard_limit` | PASSED | 1.05s |
1892
+ | `unit/test_pdf.py::TestInputValidation::test_oversized_input_rejected` | PASSED | 0.01s |
1893
+ | `unit/test_pdf.py::TestResourceClamping::test_page_count_clamped_to_hard_limit` | PASSED | 1.07s |
1891
1894
  | `unit/test_pdf.py::TestResourceClamping::test_zero_max_pages_still_renders_one_page` | PASSED | 0.02s |
1892
- | `unit/test_pdf.py::TestResourceClamping::test_excessive_dpi_clamped` | PASSED | 0.67s |
1895
+ | `unit/test_pdf.py::TestResourceClamping::test_excessive_dpi_clamped` | PASSED | 0.69s |
1893
1896
  | `unit/test_pdf.py::TestResourceClamping::test_zero_dpi_clamped_to_minimum` | PASSED | <0.01s |
1894
1897
  | `unit/test_pdf.py::TestBase64Wrapper::test_returns_decodable_base64` | PASSED | 0.02s |
1895
1898
  | `unit/test_pdf.py::TestBase64Wrapper::test_matches_raw_conversion` | PASSED | 0.09s |
@@ -1971,7 +1974,7 @@ Resolved live from `tests/model_registry.py` against each provider's `list_model
1971
1974
  | `unit/test_url_validation.py::TestValidatingTransport::test_unresolvable_host_blocked` | PASSED | <0.01s |
1972
1975
  | `unit/test_url_validation.py::TestValidatingTransport::test_lan_mode_allows_private_but_not_metadata` | PASSED | <0.01s |
1973
1976
  | `unit/test_url_validation.py::TestClientLibraryChoice::test_default_transport_is_built_against_httpx2` | PASSED | <0.01s |
1974
- | `unit/test_url_validation.py::TestClientLibraryChoice::test_transport_can_still_be_built_against_httpx` | PASSED | <0.01s |
1977
+ | `unit/test_url_validation.py::TestClientLibraryChoice::test_transport_can_still_be_built_against_httpx` | PASSED | 0.01s |
1975
1978
  | `unit/test_url_validation.py::TestClientLibraryChoice::test_validating_transport_honours_the_same_choice` | PASSED | <0.01s |
1976
1979
  | `unit/test_url_validation.py::TestClientLibraryChoice::test_guard_still_fires_on_the_httpx_variant` | PASSED | <0.01s |
1977
1980
  | `unit/test_url_validation.py::TestClientLibraryChoice::test_anthropic_provider_builds_an_httpx2_client` | PASSED | <0.01s |
@@ -0,0 +1,61 @@
1
+ """Static check that the [docs] extra states the openpyxl floor itself.
2
+
3
+ Nothing else in the chain does. `markitdown[xlsx]` asks for a bare `openpyxl` and
4
+ a bare `pandas`; the floor `openpyxl>=3.1.5` lives only in pandas' own `excel`
5
+ extra, which markitdown does not request. So a resolver may leave an older
6
+ openpyxl in place - and Odoo pins exactly 3.1.2 - after which every `.xlsx`
7
+ conversion dies with "Pandas requires version '3.1.5' or newer of 'openpyxl'",
8
+ reaching the caller as "no text could be extracted".
9
+
10
+ Guarded by a test because the declaration looks redundant: markitdown already
11
+ brings openpyxl in, so the line invites deletion during a tidy-up. It is not
12
+ redundant, it is the only place the version is stated.
13
+ """
14
+
15
+ import tomllib
16
+ from pathlib import Path
17
+
18
+ import pytest
19
+
20
+ PYPROJECT = Path(__file__).parent.parent.parent / "pyproject.toml"
21
+ # The version pandas declares for its Excel support. Raising this is fine;
22
+ # dropping below it reopens the defect.
23
+ REQUIRED_FLOOR = (3, 1, 5)
24
+
25
+
26
+ def _docs_extra() -> list[str]:
27
+ with open(PYPROJECT, "rb") as handle:
28
+ data = tomllib.load(handle)
29
+ extras = data.get("project", {}).get("optional-dependencies", {})
30
+ assert "docs" in extras, "Missing [docs] extra in pyproject.toml"
31
+ return extras["docs"]
32
+
33
+
34
+ @pytest.mark.unit
35
+ def test_docs_extra_declares_openpyxl() -> None:
36
+ entries = [d for d in _docs_extra() if d.replace("-", "_").startswith("openpyxl")]
37
+ assert entries, (
38
+ "[docs] must declare openpyxl itself - markitdown[xlsx] requires it without a "
39
+ "version, and the >=3.1.5 floor pandas needs is stated nowhere else in the chain"
40
+ )
41
+ assert len(entries) == 1, f"Expected exactly one openpyxl entry, got: {entries}"
42
+
43
+
44
+ @pytest.mark.unit
45
+ def test_the_floor_is_high_enough_for_pandas() -> None:
46
+ """Asserted by intent: raising the floor is fine, dropping under it is not."""
47
+ entry = next(d for d in _docs_extra() if d.startswith("openpyxl"))
48
+ floor_text = entry.split(">=", 1)[1].split(",", 1)[0].strip()
49
+ floor = tuple(int(part) for part in floor_text.split("."))
50
+
51
+ assert floor >= REQUIRED_FLOOR, (
52
+ f"openpyxl floor {floor_text} is below the {'.'.join(map(str, REQUIRED_FLOOR))} "
53
+ "that pandas requires for .xlsx - spreadsheets would fail at conversion time"
54
+ )
55
+
56
+
57
+ @pytest.mark.unit
58
+ def test_the_extra_still_fences_off_the_next_major() -> None:
59
+ entry = next(d for d in _docs_extra() if d.startswith("openpyxl"))
60
+
61
+ assert "<4" in entry, f"openpyxl entry should cap the next major: {entry}"