henchman-ai 0.1.14__tar.gz → 0.1.16__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 (387) hide show
  1. henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/140365231305696/b0c5ce5844ad8acc/.rag.lock +0 -0
  2. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/PKG-INFO +1 -1
  3. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/pyproject.toml +1 -1
  4. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/app.py +8 -4
  5. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/commands/builtins.py +3 -1
  6. henchman_ai-0.1.16/src/henchman/cli/commands/model.py +285 -0
  7. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/providers/anthropic.py +106 -58
  8. henchman_ai-0.1.16/src/henchman/utils/ratelimit.py +71 -0
  9. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/utils/tokens.py +1 -0
  10. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/version.py +1 -1
  11. henchman_ai-0.1.16/tests/MagicMock/mock.rag.cache_dir/125572362825280/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/link_lists.bin +0 -0
  12. henchman_ai-0.1.16/tests/MagicMock/mock.rag.cache_dir/125572765096208/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/link_lists.bin +0 -0
  13. henchman_ai-0.1.16/tests/MagicMock/mock.rag.cache_dir/125572822401392/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/link_lists.bin +0 -0
  14. henchman_ai-0.1.16/tests/MagicMock/mock.rag.cache_dir/125572896176320/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/link_lists.bin +0 -0
  15. henchman_ai-0.1.16/tests/MagicMock/mock.rag.cache_dir/135520285986352/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/link_lists.bin +0 -0
  16. henchman_ai-0.1.16/tests/MagicMock/mock.rag.cache_dir/135520286228656/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/link_lists.bin +0 -0
  17. henchman_ai-0.1.16/tests/MagicMock/mock.rag.cache_dir/135520287933552/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/link_lists.bin +0 -0
  18. henchman_ai-0.1.16/tests/MagicMock/mock.rag.cache_dir/135520588106160/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/link_lists.bin +0 -0
  19. henchman_ai-0.1.16/tests/__init__.py +0 -0
  20. henchman_ai-0.1.16/tests/cli/__init__.py +0 -0
  21. henchman_ai-0.1.16/tests/cli/commands/test_model.py +323 -0
  22. henchman_ai-0.1.16/tests/cli/commands/test_model_integration.py +189 -0
  23. henchman_ai-0.1.16/tests/cli/commands/test_plan_branch.py +138 -0
  24. henchman_ai-0.1.16/tests/cli/test_console_confirm.py +77 -0
  25. henchman_ai-0.1.16/tests/config/__init__.py +0 -0
  26. henchman_ai-0.1.16/tests/core/__init__.py +0 -0
  27. henchman_ai-0.1.16/tests/integration/test_anthropic_functional.py +29 -0
  28. henchman_ai-0.1.16/tests/mcp/__init__.py +0 -0
  29. henchman_ai-0.1.16/tests/providers/__init__.py +0 -0
  30. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/providers/test_anthropic.py +1 -0
  31. henchman_ai-0.1.16/tests/providers/test_anthropic_ratelimit.py +116 -0
  32. henchman_ai-0.1.16/tests/providers/test_anthropic_validation.py +123 -0
  33. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/test_version.py +1 -1
  34. henchman_ai-0.1.16/tests/tools/__init__.py +0 -0
  35. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/.github/copilot-instructions.md +0 -0
  36. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/.github/workflows/ci.yml +0 -0
  37. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/.github/workflows/docs.yml +0 -0
  38. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/.github/workflows/publish.yml +0 -0
  39. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/.gitignore +0 -0
  40. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/ALPHA_TEST_LOG.md +0 -0
  41. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/BETA_TESTING_ISSUES.md +0 -0
  42. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/BETA_TESTING_ISSUES2.md +0 -0
  43. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/BETA_TESTING_ISSUES3.md +0 -0
  44. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/CHANGELOG.md +0 -0
  45. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/CONTRIBUTING.md +0 -0
  46. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/LICENSE +0 -0
  47. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/MagicMock/mock.rag.cache_dir/125050554038512/b0c5ce5844ad8acc/.rag.lock +0 -0
  48. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/MagicMock/mock.rag.cache_dir/125050555035824/b0c5ce5844ad8acc/.rag.lock +0 -0
  49. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/MagicMock/mock.rag.cache_dir/125050555125200/b0c5ce5844ad8acc/.rag.lock +0 -0
  50. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/MagicMock/mock.rag.cache_dir/125051893219152/b0c5ce5844ad8acc/.rag.lock +0 -0
  51. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/MagicMock/mock.rag.cache_dir/125080879298512/b0c5ce5844ad8acc/.rag.lock +0 -0
  52. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/MagicMock/mock.rag.cache_dir/125080879878832/b0c5ce5844ad8acc/.rag.lock +0 -0
  53. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/MagicMock/mock.rag.cache_dir/125080880913088/b0c5ce5844ad8acc/.rag.lock +0 -0
  54. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/MagicMock/mock.rag.cache_dir/125081401798976/b0c5ce5844ad8acc/.rag.lock +0 -0
  55. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/MagicMock/mock.rag.cache_dir/125246043105824/b0c5ce5844ad8acc/.rag.lock +0 -0
  56. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/MagicMock/mock.rag.cache_dir/125246043924496/b0c5ce5844ad8acc/.rag.lock +0 -0
  57. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/MagicMock/mock.rag.cache_dir/125246044001472/b0c5ce5844ad8acc/.rag.lock +0 -0
  58. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/MagicMock/mock.rag.cache_dir/125247627445840/b0c5ce5844ad8acc/.rag.lock +0 -0
  59. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/131782863223120 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/126367803948160}/b0c5ce5844ad8acc/.rag.lock +0 -0
  60. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/131782952758032 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/126368206535376}/b0c5ce5844ad8acc/.rag.lock +0 -0
  61. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/131782953186608 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/126368218825504}/b0c5ce5844ad8acc/.rag.lock +0 -0
  62. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/131783467925184 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/126368326698240}/b0c5ce5844ad8acc/.rag.lock +0 -0
  63. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/131953655609296 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/129118288064016}/b0c5ce5844ad8acc/.rag.lock +0 -0
  64. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/131953656855104 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/129118289735760}/b0c5ce5844ad8acc/.rag.lock +0 -0
  65. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/131954146008512 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/129118290807072}/b0c5ce5844ad8acc/.rag.lock +0 -0
  66. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/131954155550192 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/129118485247328}/b0c5ce5844ad8acc/.rag.lock +0 -0
  67. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/133226809006208 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/131782863223120}/b0c5ce5844ad8acc/.rag.lock +0 -0
  68. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/133226810375248 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/131782952758032}/b0c5ce5844ad8acc/.rag.lock +0 -0
  69. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/133226812410672 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/131782953186608}/b0c5ce5844ad8acc/.rag.lock +0 -0
  70. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/133227143444000 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/131783467925184}/b0c5ce5844ad8acc/.rag.lock +0 -0
  71. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/133999527639808 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/131953655609296}/b0c5ce5844ad8acc/.rag.lock +0 -0
  72. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/133999929687216 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/131953656855104}/b0c5ce5844ad8acc/.rag.lock +0 -0
  73. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/133999955332368 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/131954146008512}/b0c5ce5844ad8acc/.rag.lock +0 -0
  74. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/134000027622176 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/131954155550192}/b0c5ce5844ad8acc/.rag.lock +0 -0
  75. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/134508512171968 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/133226809006208}/b0c5ce5844ad8acc/.rag.lock +0 -0
  76. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/134508512231200 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/133226810375248}/b0c5ce5844ad8acc/.rag.lock +0 -0
  77. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/134508512485616 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/133226812410672}/b0c5ce5844ad8acc/.rag.lock +0 -0
  78. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/134508855764000 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/133227143444000}/b0c5ce5844ad8acc/.rag.lock +0 -0
  79. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/134604944477792 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/133999527639808}/b0c5ce5844ad8acc/.rag.lock +0 -0
  80. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/134604945877536 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/133999929687216}/b0c5ce5844ad8acc/.rag.lock +0 -0
  81. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/134604946059776 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/133999955332368}/b0c5ce5844ad8acc/.rag.lock +0 -0
  82. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/134605456585744 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/134000027622176}/b0c5ce5844ad8acc/.rag.lock +0 -0
  83. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/134618534097648 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/134508512171968}/b0c5ce5844ad8acc/.rag.lock +0 -0
  84. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/134618535070656 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/134508512231200}/b0c5ce5844ad8acc/.rag.lock +0 -0
  85. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/134619942883056 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/134508512485616}/b0c5ce5844ad8acc/.rag.lock +0 -0
  86. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/134620110571280 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/134508855764000}/b0c5ce5844ad8acc/.rag.lock +0 -0
  87. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/138555538485184 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/134604944477792}/b0c5ce5844ad8acc/.rag.lock +0 -0
  88. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/138555538874752 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/134604945877536}/b0c5ce5844ad8acc/.rag.lock +0 -0
  89. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/138555539451408 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/134604946059776}/b0c5ce5844ad8acc/.rag.lock +0 -0
  90. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/138556062045760 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/134605456585744}/b0c5ce5844ad8acc/.rag.lock +0 -0
  91. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/139088593563008 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/134618534097648}/b0c5ce5844ad8acc/.rag.lock +0 -0
  92. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/139088594086144 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/134618535070656}/b0c5ce5844ad8acc/.rag.lock +0 -0
  93. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/139088594201216 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/134619942883056}/b0c5ce5844ad8acc/.rag.lock +0 -0
  94. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/139088659820192 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/134620110571280}/b0c5ce5844ad8acc/.rag.lock +0 -0
  95. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/139391159286624 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/134745693453872}/b0c5ce5844ad8acc/.rag.lock +0 -0
  96. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/139391160298352 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/134745694538112}/b0c5ce5844ad8acc/.rag.lock +0 -0
  97. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/139391160361968 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/134746088611312}/b0c5ce5844ad8acc/.rag.lock +0 -0
  98. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/139391778658240 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/134746194079344}/b0c5ce5844ad8acc/.rag.lock +0 -0
  99. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/140323476876816 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/138555538485184}/b0c5ce5844ad8acc/.rag.lock +0 -0
  100. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/140323493157376 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/138555538874752}/b0c5ce5844ad8acc/.rag.lock +0 -0
  101. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/140323629475472 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/138555539451408}/b0c5ce5844ad8acc/.rag.lock +0 -0
  102. {henchman_ai-0.1.14/MagicMock/mock.rag.cache_dir/140324020609984 → henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/138556062045760}/b0c5ce5844ad8acc/.rag.lock +0 -0
  103. /henchman_ai-0.1.14/tests/MagicMock/mock.rag.cache_dir/125572362825280/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/link_lists.bin → /henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/139088593563008/b0c5ce5844ad8acc/.rag.lock +0 -0
  104. /henchman_ai-0.1.14/tests/MagicMock/mock.rag.cache_dir/125572765096208/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/link_lists.bin → /henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/139088594086144/b0c5ce5844ad8acc/.rag.lock +0 -0
  105. /henchman_ai-0.1.14/tests/MagicMock/mock.rag.cache_dir/125572822401392/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/link_lists.bin → /henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/139088594201216/b0c5ce5844ad8acc/.rag.lock +0 -0
  106. /henchman_ai-0.1.14/tests/MagicMock/mock.rag.cache_dir/125572896176320/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/link_lists.bin → /henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/139088659820192/b0c5ce5844ad8acc/.rag.lock +0 -0
  107. /henchman_ai-0.1.14/tests/MagicMock/mock.rag.cache_dir/135520285986352/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/link_lists.bin → /henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/139391159286624/b0c5ce5844ad8acc/.rag.lock +0 -0
  108. /henchman_ai-0.1.14/tests/MagicMock/mock.rag.cache_dir/135520286228656/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/link_lists.bin → /henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/139391160298352/b0c5ce5844ad8acc/.rag.lock +0 -0
  109. /henchman_ai-0.1.14/tests/MagicMock/mock.rag.cache_dir/135520287933552/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/link_lists.bin → /henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/139391160361968/b0c5ce5844ad8acc/.rag.lock +0 -0
  110. /henchman_ai-0.1.14/tests/MagicMock/mock.rag.cache_dir/135520588106160/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/link_lists.bin → /henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/139391778658240/b0c5ce5844ad8acc/.rag.lock +0 -0
  111. /henchman_ai-0.1.14/tests/__init__.py → /henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/140323476876816/b0c5ce5844ad8acc/.rag.lock +0 -0
  112. /henchman_ai-0.1.14/tests/cli/__init__.py → /henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/140323493157376/b0c5ce5844ad8acc/.rag.lock +0 -0
  113. /henchman_ai-0.1.14/tests/config/__init__.py → /henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/140323629475472/b0c5ce5844ad8acc/.rag.lock +0 -0
  114. /henchman_ai-0.1.14/tests/core/__init__.py → /henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/140324020609984/b0c5ce5844ad8acc/.rag.lock +0 -0
  115. /henchman_ai-0.1.14/tests/mcp/__init__.py → /henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/140365102460192/b0c5ce5844ad8acc/.rag.lock +0 -0
  116. /henchman_ai-0.1.14/tests/providers/__init__.py → /henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/140365132965952/b0c5ce5844ad8acc/.rag.lock +0 -0
  117. /henchman_ai-0.1.14/tests/tools/__init__.py → /henchman_ai-0.1.16/MagicMock/mock.rag.cache_dir/140365156415680/b0c5ce5844ad8acc/.rag.lock +0 -0
  118. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/PROJECT_PLAN.md +0 -0
  119. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/README.md +0 -0
  120. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/README_enhanced.md +0 -0
  121. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/RELEASE_CHECKLIST.md +0 -0
  122. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/TASK_COMPLETION_SUMMARY.md +0 -0
  123. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/debug_compaction.py +0 -0
  124. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/docs/api.md +0 -0
  125. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/docs/configuration.md +0 -0
  126. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/docs/extensions.md +0 -0
  127. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/docs/getting-started.md +0 -0
  128. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/docs/index.md +0 -0
  129. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/docs/mcp.md +0 -0
  130. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/docs/providers.md +0 -0
  131. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/docs/tools.md +0 -0
  132. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/evals/README.md +0 -0
  133. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/evals/__init__.py +0 -0
  134. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/evals/conftest.py +0 -0
  135. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/evals/helpers.py +0 -0
  136. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/evals/test_answer_vs_action.py +0 -0
  137. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/evals/test_coding_tasks.py +0 -0
  138. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/evals/test_edit_precision.py +0 -0
  139. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/evals/test_skills_memory.py +0 -0
  140. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/evals/test_tool_selection.py +0 -0
  141. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/fix_repl.py +0 -0
  142. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/fix_repl_simple.py +0 -0
  143. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/mkdocs.yml +0 -0
  144. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/replace_method.py +0 -0
  145. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/reproduce_400_error.py +0 -0
  146. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/run_interactive_tests.py +0 -0
  147. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/scripts/build_docs.py +0 -0
  148. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/scripts/ci.sh +0 -0
  149. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/scripts/run_evals.sh +0 -0
  150. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/__init__.py +0 -0
  151. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/__main__.py +0 -0
  152. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/__init__.py +0 -0
  153. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/commands/__init__.py +0 -0
  154. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/commands/chat.py +0 -0
  155. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/commands/extensions.py +0 -0
  156. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/commands/mcp.py +0 -0
  157. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/commands/plan.py +0 -0
  158. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/commands/rag.py +0 -0
  159. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/commands/skill.py +0 -0
  160. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/commands/unlimited.py +0 -0
  161. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/console.py +0 -0
  162. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/input.py +0 -0
  163. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/json_output.py +0 -0
  164. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/prompts.py +0 -0
  165. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/repl.py +0 -0
  166. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/repl.py.backup +0 -0
  167. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/cli/repl.py.backup2 +0 -0
  168. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/config/__init__.py +0 -0
  169. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/config/context.py +0 -0
  170. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/config/schema.py +0 -0
  171. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/config/settings.py +0 -0
  172. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/core/__init__.py +0 -0
  173. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/core/agent.py +0 -0
  174. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/core/agent.py.backup +0 -0
  175. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/core/events.py +0 -0
  176. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/core/session.py +0 -0
  177. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/core/turn.py +0 -0
  178. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/extensions/__init__.py +0 -0
  179. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/extensions/base.py +0 -0
  180. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/extensions/manager.py +0 -0
  181. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/mcp/__init__.py +0 -0
  182. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/mcp/client.py +0 -0
  183. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/mcp/config.py +0 -0
  184. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/mcp/manager.py +0 -0
  185. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/mcp/tool.py +0 -0
  186. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/providers/__init__.py +0 -0
  187. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/providers/base.py +0 -0
  188. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/providers/deepseek.py +0 -0
  189. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/providers/ollama.py +0 -0
  190. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/providers/openai_compat.py +0 -0
  191. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/providers/openai_compat.py.backup +0 -0
  192. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/providers/registry.py +0 -0
  193. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/rag/__init__.py +0 -0
  194. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/rag/chunker.py +0 -0
  195. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/rag/concurrency.py +0 -0
  196. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/rag/embedder.py +0 -0
  197. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/rag/indexer.py +0 -0
  198. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/rag/repo_id.py +0 -0
  199. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/rag/store.py +0 -0
  200. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/rag/system.py +0 -0
  201. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/skills/__init__.py +0 -0
  202. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/skills/executor.py +0 -0
  203. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/skills/learner.py +0 -0
  204. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/skills/models.py +0 -0
  205. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/skills/store.py +0 -0
  206. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/tools/__init__.py +0 -0
  207. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/tools/base.py +0 -0
  208. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/tools/builtins/__init__.py +0 -0
  209. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/tools/builtins/ask_user.py +0 -0
  210. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/tools/builtins/file_edit.py +0 -0
  211. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/tools/builtins/file_read.py +0 -0
  212. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/tools/builtins/file_write.py +0 -0
  213. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/tools/builtins/glob_tool.py +0 -0
  214. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/tools/builtins/grep.py +0 -0
  215. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/tools/builtins/ls.py +0 -0
  216. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/tools/builtins/rag_search.py +0 -0
  217. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/tools/builtins/shell.py +0 -0
  218. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/tools/builtins/web_fetch.py +0 -0
  219. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/tools/builtins/web_search.py +0 -0
  220. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/tools/registry.py +0 -0
  221. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/utils/__init__.py +0 -0
  222. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/utils/compaction.py +0 -0
  223. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/utils/retry.py +0 -0
  224. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/src/henchman/utils/validation.py +0 -0
  225. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/test_compaction.py +0 -0
  226. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/test_compaction_fix.py +0 -0
  227. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/test_fixes.py +0 -0
  228. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/test_output.txt +0 -0
  229. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/test_run.py +0 -0
  230. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572362825280/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/data_level0.bin +0 -0
  231. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572362825280/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/header.bin +0 -0
  232. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572362825280/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/length.bin +0 -0
  233. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572362825280/b0c5ce5844ad8acc/chroma/chroma.sqlite3 +0 -0
  234. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572362825280/b0c5ce5844ad8acc/manifest.json +0 -0
  235. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572765096208/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/data_level0.bin +0 -0
  236. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572765096208/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/header.bin +0 -0
  237. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572765096208/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/length.bin +0 -0
  238. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572765096208/b0c5ce5844ad8acc/chroma/chroma.sqlite3 +0 -0
  239. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572765096208/b0c5ce5844ad8acc/manifest.json +0 -0
  240. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572822401392/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/data_level0.bin +0 -0
  241. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572822401392/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/header.bin +0 -0
  242. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572822401392/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/length.bin +0 -0
  243. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572822401392/b0c5ce5844ad8acc/chroma/chroma.sqlite3 +0 -0
  244. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572822401392/b0c5ce5844ad8acc/manifest.json +0 -0
  245. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572896176320/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/data_level0.bin +0 -0
  246. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572896176320/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/header.bin +0 -0
  247. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572896176320/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/length.bin +0 -0
  248. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572896176320/b0c5ce5844ad8acc/chroma/chroma.sqlite3 +0 -0
  249. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/125572896176320/b0c5ce5844ad8acc/manifest.json +0 -0
  250. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520285986352/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/data_level0.bin +0 -0
  251. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520285986352/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/header.bin +0 -0
  252. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520285986352/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/length.bin +0 -0
  253. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520285986352/b0c5ce5844ad8acc/chroma/chroma.sqlite3 +0 -0
  254. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520285986352/b0c5ce5844ad8acc/manifest.json +0 -0
  255. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520286228656/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/data_level0.bin +0 -0
  256. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520286228656/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/header.bin +0 -0
  257. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520286228656/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/length.bin +0 -0
  258. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520286228656/b0c5ce5844ad8acc/chroma/chroma.sqlite3 +0 -0
  259. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520286228656/b0c5ce5844ad8acc/manifest.json +0 -0
  260. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520287933552/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/data_level0.bin +0 -0
  261. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520287933552/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/header.bin +0 -0
  262. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520287933552/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/length.bin +0 -0
  263. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520287933552/b0c5ce5844ad8acc/chroma/chroma.sqlite3 +0 -0
  264. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520287933552/b0c5ce5844ad8acc/manifest.json +0 -0
  265. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520588106160/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/data_level0.bin +0 -0
  266. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520588106160/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/header.bin +0 -0
  267. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520588106160/b0c5ce5844ad8acc/chroma/88b10860-7f3a-42c9-a3aa-20e09850b445/length.bin +0 -0
  268. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520588106160/b0c5ce5844ad8acc/chroma/chroma.sqlite3 +0 -0
  269. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/MagicMock/mock.rag.cache_dir/135520588106160/b0c5ce5844ad8acc/manifest.json +0 -0
  270. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/commands/test_plan.py +0 -0
  271. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/commands/test_session_resume_id.py +0 -0
  272. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/commands/test_skill.py +0 -0
  273. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/commands/test_skill_extended.py +0 -0
  274. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/commands/test_unlimited.py +0 -0
  275. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_app.py +0 -0
  276. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_app_extended.py +0 -0
  277. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_builtins.py +0 -0
  278. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_chat_command.py +0 -0
  279. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_cli_smoke.py +0 -0
  280. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_commands.py +0 -0
  281. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_commands_repro.py +0 -0
  282. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_console.py +0 -0
  283. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_enhanced_tool_display.py +0 -0
  284. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_input.py +0 -0
  285. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_input_bindings.py +0 -0
  286. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_interrupt.py +0 -0
  287. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_json_output.py +0 -0
  288. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_keyboard_fixes.py +0 -0
  289. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_keyboard_integration.py +0 -0
  290. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_keyboard_interrupt.py +0 -0
  291. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_keyboard_verification.py +0 -0
  292. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_loop_protection.py +0 -0
  293. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_mcp_command.py +0 -0
  294. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_repl.py +0 -0
  295. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_repl_attribute_fix.py +0 -0
  296. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_repl_startup_message.py +0 -0
  297. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/cli/test_repl_toolbar.py +0 -0
  298. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/config/test_context.py +0 -0
  299. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/config/test_schema.py +0 -0
  300. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/config/test_settings.py +0 -0
  301. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/conftest.py +0 -0
  302. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/core/test_automatic_compaction.py +0 -0
  303. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/core/test_events.py +0 -0
  304. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/core/test_session.py +0 -0
  305. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/core/test_session_manager.py +0 -0
  306. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/core/test_streaming_tool_calls.py +0 -0
  307. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/core/test_turn_state.py +0 -0
  308. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/e2e/test_context_safety.py +0 -0
  309. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/e2e/test_plan_mode_workflow.py +0 -0
  310. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/e2e/test_tool_fix.py +0 -0
  311. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/empty_message_validation/test_empty_messages.py +0 -0
  312. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/extensions/__init__.py +0 -0
  313. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/extensions/test_base.py +0 -0
  314. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/extensions/test_command.py +0 -0
  315. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/extensions/test_manager.py +0 -0
  316. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/integration/test_context_limits.py +0 -0
  317. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/integration/test_tool_integration.py +0 -0
  318. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/mcp/test_client.py +0 -0
  319. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/mcp/test_config.py +0 -0
  320. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/mcp/test_manager.py +0 -0
  321. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/mcp/test_tool.py +0 -0
  322. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/providers/test_413_error_handling.py +0 -0
  323. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/providers/test_base.py +0 -0
  324. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/providers/test_deepseek.py +0 -0
  325. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/providers/test_ollama.py +0 -0
  326. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/providers/test_openai_compat.py +0 -0
  327. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/providers/test_registry.py +0 -0
  328. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/rag/__init__.py +0 -0
  329. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/rag/test_chunker.py +0 -0
  330. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/rag/test_concurrency.py +0 -0
  331. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/rag/test_concurrency_smoke.py +0 -0
  332. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/rag/test_embedder.py +0 -0
  333. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/rag/test_indexer.py +0 -0
  334. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/rag/test_rag_command.py +0 -0
  335. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/rag/test_rag_concurrency_integration.py +0 -0
  336. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/rag/test_rag_search_tool.py +0 -0
  337. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/rag/test_repo_id.py +0 -0
  338. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/rag/test_store.py +0 -0
  339. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/rag/test_system.py +0 -0
  340. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/skills/test_executor.py +0 -0
  341. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/skills/test_learner.py +0 -0
  342. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/skills/test_markdown_skills.py +0 -0
  343. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/skills/test_models.py +0 -0
  344. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/skills/test_store.py +0 -0
  345. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/skills/test_store_extended.py +0 -0
  346. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/smoke/test_escape_key_behavior.py +0 -0
  347. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/smoke/test_large_file_handling.py +0 -0
  348. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/test_coverage_suite.py +0 -0
  349. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/test_main.py +0 -0
  350. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/tools/test_ask_user_tool.py +0 -0
  351. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/tools/test_base.py +0 -0
  352. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/tools/test_directory_tools.py +0 -0
  353. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/tools/test_file_tools.py +0 -0
  354. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/tools/test_grep_tool.py +0 -0
  355. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/tools/test_plan_mode_enforcement.py +0 -0
  356. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/tools/test_registry.py +0 -0
  357. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/tools/test_shell_advanced.py +0 -0
  358. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/tools/test_shell_tool.py +0 -0
  359. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/tools/test_web_fetch_tool.py +0 -0
  360. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/ui_integration/INTERACTIVE_SESSION_TESTS.md +0 -0
  361. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/ui_integration/__init__.py +0 -0
  362. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/ui_integration/conftest.py +0 -0
  363. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/ui_integration/test_agent.py +0 -0
  364. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/ui_integration/test_compaction_llm.py +0 -0
  365. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/ui_integration/test_events.py +0 -0
  366. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/ui_integration/test_llm.py +0 -0
  367. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/ui_integration/test_mcp.py +0 -0
  368. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/ui_integration/test_plan_mode.py +0 -0
  369. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/ui_integration/test_repl_e2e.py +0 -0
  370. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/ui_integration/test_repl_integration.py +0 -0
  371. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/ui_integration/test_session.py +0 -0
  372. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/ui_integration/test_skills.py +0 -0
  373. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/ui_integration/test_slash_commands.py +0 -0
  374. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/ui_integration/test_tokens_llm.py +0 -0
  375. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/ui_integration/test_tool_calls.py +0 -0
  376. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/ui_integration/test_tool_integration.py +0 -0
  377. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/utils/test_compaction.py +0 -0
  378. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/utils/test_compaction_edge_cases.py +0 -0
  379. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/utils/test_compaction_validation.py +0 -0
  380. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/utils/test_multi_turn_tool_calls.py +0 -0
  381. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/utils/test_protected_zone.py +0 -0
  382. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/utils/test_retry.py +0 -0
  383. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/utils/test_summarization.py +0 -0
  384. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/utils/test_tiktoken_integration.py +0 -0
  385. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/utils/test_token_counter_extended.py +0 -0
  386. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/utils/test_tool_sequence_compaction.py +0 -0
  387. {henchman_ai-0.1.14 → henchman_ai-0.1.16}/tests/utils/test_validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: henchman-ai
3
- Version: 0.1.14
3
+ Version: 0.1.16
4
4
  Summary: A model-agnostic AI agent CLI - your AI henchman for the terminal
5
5
  Project-URL: Homepage, https://github.com/MGPowerlytics/henchman-ai
6
6
  Project-URL: Repository, https://github.com/MGPowerlytics/henchman-ai
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "henchman-ai"
7
- version = "0.1.14"
7
+ version = "0.1.16"
8
8
  description = "A model-agnostic AI agent CLI - your AI henchman for the terminal"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -43,13 +43,17 @@ def _get_provider() -> ModelProvider:
43
43
  registry = get_default_registry()
44
44
 
45
45
  provider_name = settings.providers.default or "deepseek"
46
- provider_settings = getattr(settings.providers, provider_name, None)
46
+ provider_settings = getattr(settings.providers, provider_name, {})
47
47
 
48
- if provider_settings:
48
+ if isinstance(provider_settings, dict):
49
+ # Ensure api_key is handled correctly (backward compatibility or env var)
50
+ kwargs = provider_settings.copy()
51
+ if not kwargs.get("api_key"):
52
+ kwargs["api_key"] = os.environ.get("ANTHROPIC_API_KEY") if provider_name == "anthropic" else os.environ.get("HENCHMAN_API_KEY")
53
+
49
54
  return registry.create(
50
55
  provider_name,
51
- api_key=getattr(provider_settings, "api_key", None) or "",
52
- model=getattr(provider_settings, "model", None),
56
+ **kwargs
53
57
  )
54
58
  except Exception: # pragma: no cover
55
59
  pass
@@ -8,6 +8,7 @@ from __future__ import annotations
8
8
  from henchman.cli.commands import Command, CommandContext
9
9
  from henchman.cli.commands.chat import ChatCommand
10
10
  from henchman.cli.commands.mcp import McpCommand
11
+ from henchman.cli.commands.model import ModelCommand
11
12
  from henchman.cli.commands.plan import PlanCommand
12
13
  from henchman.cli.commands.rag import RagCommand
13
14
  from henchman.cli.commands.skill import SkillCommand
@@ -57,10 +58,10 @@ class HelpCommand(Command):
57
58
  ctx.console.print(" /skill - Manage and execute learned skills")
58
59
  ctx.console.print(" /chat - Manage chat sessions (save, list, resume)")
59
60
  ctx.console.print(" /mcp - Manage MCP server connections")
61
+ ctx.console.print(" /model - Show or change model/provider")
60
62
  ctx.console.print(" /quit - Exit the CLI")
61
63
  ctx.console.print(" /clear - Clear the screen")
62
64
  ctx.console.print(" /tools - List available tools")
63
- ctx.console.print(" /model - Show or change the model")
64
65
  ctx.console.print("")
65
66
 
66
67
 
@@ -212,6 +213,7 @@ def get_builtin_commands() -> list[Command]:
212
213
  ToolsCommand(),
213
214
  ChatCommand(),
214
215
  McpCommand(),
216
+ ModelCommand(),
215
217
  PlanCommand(),
216
218
  RagCommand(),
217
219
  SkillCommand(),
@@ -0,0 +1,285 @@
1
+ """Model and provider management commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import TYPE_CHECKING
7
+
8
+ from henchman.cli.commands import Command, CommandContext
9
+ from henchman.config import load_settings
10
+ from henchman.providers import get_default_registry
11
+
12
+ if TYPE_CHECKING:
13
+ from henchman.providers.base import ModelProvider
14
+
15
+
16
+ class ModelCommand(Command):
17
+ """Show or change the model and provider."""
18
+
19
+ @property
20
+ def name(self) -> str:
21
+ """Command name.
22
+
23
+ Returns:
24
+ Command name string.
25
+ """
26
+ return "model"
27
+
28
+ @property
29
+ def description(self) -> str:
30
+ """Command description.
31
+
32
+ Returns:
33
+ Description string.
34
+ """
35
+ return "Show or change the model and provider"
36
+
37
+ @property
38
+ def usage(self) -> str:
39
+ """Command usage.
40
+
41
+ Returns:
42
+ Usage string.
43
+ """
44
+ return "/model [list|set <provider> [<model>]]"
45
+
46
+ async def execute(self, ctx: CommandContext) -> None:
47
+ """Execute the model command.
48
+
49
+ Args:
50
+ ctx: Command context.
51
+ """
52
+ args = ctx.args
53
+ if not args:
54
+ await self._show_current(ctx)
55
+ elif args[0] == "list":
56
+ await self._list_providers(ctx)
57
+ elif args[0] == "set" and len(args) >= 2:
58
+ await self._set_provider(ctx, args[1], args[2] if len(args) > 2 else None)
59
+ else:
60
+ ctx.console.print(f"[yellow]Usage: {self.usage}[/]")
61
+
62
+ async def _show_current(self, ctx: CommandContext) -> None:
63
+ """Show current provider and model.
64
+
65
+ Args:
66
+ ctx: Command context.
67
+ """
68
+ if not ctx.agent:
69
+ ctx.console.print("[yellow]No active agent. Cannot show current model.[/]")
70
+ return
71
+
72
+ provider = ctx.agent.provider
73
+ settings = load_settings()
74
+ registry = get_default_registry()
75
+
76
+ ctx.console.print("\n[bold blue]Current Configuration[/]\n")
77
+ ctx.console.print(f" Provider: [cyan]{provider.name}[/]")
78
+
79
+ # Show model if available
80
+ if hasattr(provider, "default_model"):
81
+ ctx.console.print(f" Model: [cyan]{provider.default_model}[/]")
82
+
83
+ # Show available providers
84
+ available = registry.list_providers()
85
+ ctx.console.print(f"\n Available providers: [dim]{', '.join(available)}[/]")
86
+ ctx.console.print(f"\n Use [cyan]/model list[/] to see all providers")
87
+ ctx.console.print(f" Use [cyan]/model set <provider> [model][/] to switch")
88
+ ctx.console.print("")
89
+
90
+ async def _list_providers(self, ctx: CommandContext) -> None:
91
+ """List all available providers and models.
92
+
93
+ Args:
94
+ ctx: Command context.
95
+ """
96
+ registry = get_default_registry()
97
+ providers = registry.list_providers()
98
+
99
+ ctx.console.print("\n[bold blue]Available Providers[/]\n")
100
+
101
+ for provider_name in sorted(providers):
102
+ try:
103
+ provider_class = registry.get(provider_name)
104
+
105
+ # Get example configuration
106
+ example_config = self._get_example_config(provider_name)
107
+
108
+ ctx.console.print(f" [cyan]{provider_name}[/]")
109
+ if hasattr(provider_class, "__doc__") and provider_class.__doc__:
110
+ doc_lines = provider_class.__doc__.strip().split('\n')
111
+ first_line = doc_lines[0].strip()
112
+ ctx.console.print(f" [dim]{first_line}[/]")
113
+
114
+ if example_config:
115
+ ctx.console.print(f" [yellow]Config:[/] {example_config}")
116
+
117
+ # Show environment variables needed
118
+ env_vars = self._get_env_vars(provider_name)
119
+ if env_vars:
120
+ ctx.console.print(f" [yellow]Env vars:[/] {env_vars}")
121
+
122
+ ctx.console.print("")
123
+ except Exception as e:
124
+ ctx.console.print(f" [red]{provider_name}[/] - Error: {e}")
125
+
126
+ async def _set_provider(
127
+ self,
128
+ ctx: CommandContext,
129
+ provider_name: str,
130
+ model_name: str | None = None
131
+ ) -> None:
132
+ """Switch to a different provider.
133
+
134
+ Args:
135
+ ctx: Command context.
136
+ provider_name: Name of the provider to switch to.
137
+ model_name: Optional model name to use.
138
+
139
+ Raises:
140
+ ValueError: If provider cannot be created.
141
+ """
142
+ if not ctx.repl:
143
+ ctx.console.print("[yellow]Cannot switch providers without REPL context.[/]")
144
+ return
145
+
146
+ try:
147
+ # Get registry and create new provider
148
+ registry = get_default_registry()
149
+
150
+ if provider_name not in registry.list_providers():
151
+ ctx.console.print(f"[red]Provider '{provider_name}' not found.[/]")
152
+ ctx.console.print(f"Available providers: {', '.join(registry.list_providers())}")
153
+ return
154
+
155
+ # Try to get API key from environment or settings
156
+ api_key = self._get_api_key_for_provider(provider_name)
157
+
158
+ # Create provider instance
159
+ provider_kwargs = {"api_key": api_key or ""}
160
+ if model_name:
161
+ provider_kwargs["model"] = model_name
162
+
163
+ new_provider = registry.create(provider_name, **provider_kwargs)
164
+
165
+ # Test the provider with a simple call
166
+ ctx.console.print(f"[dim]Testing {provider_name} connection...[/]")
167
+ try:
168
+ # Simple test to verify provider works
169
+ if hasattr(new_provider, "default_model"):
170
+ ctx.console.print(f"[green]✓ Connected to {provider_name}[/]")
171
+ if model_name:
172
+ ctx.console.print(f"[green]✓ Using model: {model_name}[/]")
173
+ else:
174
+ ctx.console.print(f"[green]✓ Using default model: {new_provider.default_model}[/]")
175
+ else:
176
+ ctx.console.print(f"[green]✓ Connected to {provider_name}[/]")
177
+ except Exception as e:
178
+ ctx.console.print(f"[yellow]⚠ Connection test failed: {e}[/]")
179
+ ctx.console.print("[yellow]Provider created but may not work correctly.[/]")
180
+
181
+ # Update the agent with new provider
182
+ old_provider = ctx.agent.provider
183
+ ctx.agent.provider = new_provider
184
+
185
+ # Update REPL's provider reference
186
+ ctx.repl.provider = new_provider
187
+
188
+ ctx.console.print(f"\n[bold green]✓ Switched from {old_provider.name} to {new_provider.name}[/]")
189
+
190
+ # Show any configuration needed
191
+ if not api_key:
192
+ env_var = self._get_env_var_name(provider_name)
193
+ ctx.console.print(f"\n[yellow]⚠ No API key found for {provider_name}[/]")
194
+ ctx.console.print(f" Set environment variable: [cyan]{env_var}=your-api-key[/]")
195
+ ctx.console.print(f" Or configure in [cyan]~/.henchman/settings.yaml[/]:")
196
+ ctx.console.print(f" providers:")
197
+ ctx.console.print(f" {provider_name}:")
198
+ ctx.console.print(f" api_key: your-api-key")
199
+
200
+ except Exception as e:
201
+ ctx.console.print(f"[red]Failed to switch provider: {e}[/]")
202
+ ctx.console.print("[dim]Check that the provider is properly configured.[/]")
203
+
204
+ def _get_example_config(self, provider_name: str) -> str:
205
+ """Get example configuration for a provider.
206
+
207
+ Args:
208
+ provider_name: Name of the provider.
209
+
210
+ Returns:
211
+ Example configuration string.
212
+ """
213
+ examples = {
214
+ "deepseek": "deepseek-chat (default), deepseek-coder",
215
+ "openai": "gpt-4-turbo, gpt-3.5-turbo",
216
+ "anthropic": "claude-3-opus, claude-3-sonnet",
217
+ "ollama": "llama2, mistral, codellama",
218
+ }
219
+ return examples.get(provider_name, "Check provider documentation")
220
+
221
+ def _get_env_vars(self, provider_name: str) -> str:
222
+ """Get environment variables needed for a provider.
223
+
224
+ Args:
225
+ provider_name: Name of the provider.
226
+
227
+ Returns:
228
+ Environment variable names.
229
+ """
230
+ env_vars = {
231
+ "deepseek": "DEEPSEEK_API_KEY",
232
+ "openai": "OPENAI_API_KEY",
233
+ "anthropic": "ANTHROPIC_API_KEY",
234
+ "ollama": "OLLAMA_HOST (optional, defaults to http://localhost:11434)",
235
+ }
236
+ return env_vars.get(provider_name, "Check provider documentation")
237
+
238
+ def _get_env_var_name(self, provider_name: str) -> str:
239
+ """Get the environment variable name for a provider's API key.
240
+
241
+ Args:
242
+ provider_name: Name of the provider.
243
+
244
+ Returns:
245
+ Environment variable name.
246
+ """
247
+ mapping = {
248
+ "deepseek": "DEEPSEEK_API_KEY",
249
+ "openai": "OPENAI_API_KEY",
250
+ "anthropic": "ANTHROPIC_API_KEY",
251
+ "ollama": "OLLAMA_API_KEY", # Ollama doesn't usually need API key
252
+ }
253
+ return mapping.get(provider_name, f"{provider_name.upper()}_API_KEY")
254
+
255
+ def _get_api_key_for_provider(self, provider_name: str) -> str | None:
256
+ """Get API key for a provider from environment or settings.
257
+
258
+ Args:
259
+ provider_name: Name of the provider.
260
+
261
+ Returns:
262
+ API key if found, None otherwise.
263
+ """
264
+ # Try environment variables first
265
+ env_var = self._get_env_var_name(provider_name)
266
+ api_key = os.environ.get(env_var)
267
+
268
+ if api_key:
269
+ return api_key
270
+
271
+ # Try generic HENCHMAN_API_KEY
272
+ api_key = os.environ.get("HENCHMAN_API_KEY")
273
+ if api_key:
274
+ return api_key
275
+
276
+ # Try settings
277
+ try:
278
+ settings = load_settings()
279
+ provider_settings = getattr(settings.providers, provider_name, None)
280
+ if provider_settings and hasattr(provider_settings, "api_key"):
281
+ return provider_settings.api_key
282
+ except Exception:
283
+ pass
284
+
285
+ return None
@@ -4,12 +4,13 @@ This provider uses the Anthropic SDK to communicate with Claude models.
4
4
  Unlike OpenAI-compatible APIs, Anthropic has its own message format.
5
5
  """
6
6
 
7
+ import asyncio
7
8
  import json
8
9
  import os
9
10
  from collections.abc import AsyncIterator
10
11
  from typing import Any
11
12
 
12
- from anthropic import AsyncAnthropic
13
+ from anthropic import AsyncAnthropic, RateLimitError
13
14
 
14
15
  from henchman.providers.base import (
15
16
  FinishReason,
@@ -19,11 +20,14 @@ from henchman.providers.base import (
19
20
  ToolCall,
20
21
  ToolDeclaration,
21
22
  )
23
+ from henchman.utils.ratelimit import AsyncRateLimiter
24
+ from henchman.utils.tokens import TokenCounter
22
25
 
23
26
  __all__ = ["AnthropicProvider"]
24
27
 
25
28
  # Available Claude models
26
29
  ANTHROPIC_MODELS = [
30
+ "claude-opus-4-6",
27
31
  "claude-sonnet-4-20250514",
28
32
  "claude-3-7-sonnet-20250219",
29
33
  "claude-3-5-sonnet-20241022",
@@ -50,6 +54,8 @@ class AnthropicProvider(ModelProvider):
50
54
  api_key: str | None = None,
51
55
  model: str = "claude-sonnet-4-20250514",
52
56
  max_tokens: int = 8192,
57
+ tokens_per_minute: int = 30000,
58
+ max_retries: int = 3,
53
59
  ) -> None:
54
60
  """Initialize the Anthropic provider.
55
61
 
@@ -57,11 +63,15 @@ class AnthropicProvider(ModelProvider):
57
63
  api_key: API key for authentication. Defaults to ANTHROPIC_API_KEY env var.
58
64
  model: Default model to use.
59
65
  max_tokens: Maximum tokens in response.
66
+ tokens_per_minute: Maximum tokens per minute (rate limit).
67
+ max_retries: Maximum number of retries for rate limits.
60
68
  """
61
69
  self.api_key = api_key or os.getenv("ANTHROPIC_API_KEY", "")
62
70
  self.default_model = model
63
71
  self.max_tokens = max_tokens
72
+ self.max_retries = max_retries
64
73
  self._client = AsyncAnthropic(api_key=self.api_key or "placeholder")
74
+ self._rate_limiter = AsyncRateLimiter(tokens_per_minute)
65
75
 
66
76
  @property
67
77
  def name(self) -> str:
@@ -187,6 +197,7 @@ class AnthropicProvider(ModelProvider):
187
197
  # All other messages must have non-empty content
188
198
  if not (message.content or '').strip():
189
199
  raise ValueError(f"Message with role '{message.role}' cannot have empty content")
200
+
190
201
  system_prompt, formatted_messages = self._format_messages(messages)
191
202
 
192
203
  params: dict[str, Any] = {
@@ -202,62 +213,99 @@ class AnthropicProvider(ModelProvider):
202
213
  if tools:
203
214
  params["tools"] = [self._format_tool(t) for t in tools]
204
215
 
205
- async with self._client.messages.stream(**params) as stream:
206
- pending_tool_calls: dict[str, dict[str, Any]] = {}
207
- current_tool_id: str | None = None
208
-
209
- async for event in stream:
210
- content: str | None = None
211
- thinking: str | None = None
212
- tool_calls: list[ToolCall] | None = None
213
- finish_reason: FinishReason | None = None
214
-
215
- if event.type == "content_block_start":
216
- block = event.content_block
217
- if block.type == "tool_use":
218
- current_tool_id = block.id
219
- pending_tool_calls[block.id] = {
220
- "id": block.id,
221
- "name": block.name,
222
- "arguments": "",
223
- }
224
-
225
- elif event.type == "content_block_delta":
226
- delta = event.delta
227
- if delta.type == "text_delta":
228
- content = delta.text
229
- elif delta.type == "thinking_delta":
230
- thinking = delta.thinking
231
- elif delta.type == "input_json_delta" and current_tool_id:
232
- pending_tool_calls[current_tool_id]["arguments"] += delta.partial_json
233
-
234
- elif event.type == "content_block_stop":
235
- current_tool_id = None
236
-
237
- elif event.type == "message_delta":
238
- finish_reason = self._parse_finish_reason(event.delta.stop_reason)
239
-
240
- # Emit completed tool calls
241
- if finish_reason == FinishReason.TOOL_CALLS and pending_tool_calls:
242
- tool_calls = []
243
- for tc_data in pending_tool_calls.values():
244
- try:
245
- arguments = json.loads(tc_data["arguments"]) if tc_data["arguments"] else {}
246
- except json.JSONDecodeError:
247
- arguments = {}
248
- tool_calls.append(
249
- ToolCall(
250
- id=tc_data["id"],
251
- name=tc_data["name"],
252
- arguments=arguments,
253
- )
216
+ input_tokens = TokenCounter.count_messages(messages, model=self.default_model)
217
+ retries = 0
218
+ while True:
219
+ try:
220
+ # Rate limiting: wait for capacity based on input tokens
221
+ await self._rate_limiter.wait_for_capacity(input_tokens)
222
+
223
+ total_output_tokens = 0
224
+ async with self._client.messages.stream(**params) as stream:
225
+ pending_tool_calls: dict[str, dict[str, Any]] = {}
226
+ current_tool_id: str | None = None
227
+
228
+ async for event in stream:
229
+ content: str | None = None
230
+ thinking: str | None = None
231
+ tool_calls: list[ToolCall] | None = None
232
+ finish_reason: FinishReason | None = None
233
+
234
+ if event.type == "content_block_start":
235
+ block = event.content_block
236
+ if block.type == "tool_use":
237
+ current_tool_id = block.id
238
+ pending_tool_calls[block.id] = {
239
+ "id": block.id,
240
+ "name": block.name,
241
+ "arguments": "",
242
+ }
243
+
244
+ elif event.type == "content_block_delta":
245
+ delta = event.delta
246
+ if delta.type == "text_delta":
247
+ content = delta.text
248
+ total_output_tokens += TokenCounter.count_text(content, model=self.default_model)
249
+ elif delta.type == "thinking_delta":
250
+ thinking = delta.thinking
251
+ total_output_tokens += TokenCounter.count_text(thinking, model=self.default_model)
252
+ elif delta.type == "input_json_delta" and current_tool_id:
253
+ pending_tool_calls[current_tool_id]["arguments"] += delta.partial_json
254
+ # Note: we don't count JSON tokens precisely here as they come in,
255
+ # but we could count the delta text.
256
+ total_output_tokens += TokenCounter.count_text(delta.partial_json, model=self.default_model)
257
+
258
+ elif event.type == "content_block_stop":
259
+ current_tool_id = None
260
+
261
+ elif event.type == "message_delta":
262
+ finish_reason = self._parse_finish_reason(event.delta.stop_reason)
263
+
264
+ # Emit completed tool calls
265
+ if finish_reason == FinishReason.TOOL_CALLS and pending_tool_calls:
266
+ tool_calls = []
267
+ for tc_data in pending_tool_calls.values():
268
+ try:
269
+ arguments = json.loads(tc_data["arguments"]) if tc_data["arguments"] else {}
270
+ except json.JSONDecodeError:
271
+ arguments = {}
272
+ tool_calls.append(
273
+ ToolCall(
274
+ id=tc_data["id"],
275
+ name=tc_data["name"],
276
+ arguments=arguments,
277
+ )
278
+ )
279
+
280
+ # Only yield if we have meaningful content
281
+ if content is not None or thinking is not None or tool_calls or finish_reason:
282
+ yield StreamChunk(
283
+ content=content,
284
+ tool_calls=tool_calls,
285
+ finish_reason=finish_reason,
286
+ thinking=thinking,
254
287
  )
255
288
 
256
- # Only yield if we have meaningful content
257
- if content is not None or thinking is not None or tool_calls or finish_reason:
258
- yield StreamChunk(
259
- content=content,
260
- tool_calls=tool_calls,
261
- finish_reason=finish_reason,
262
- thinking=thinking,
263
- )
289
+ # Record final usage
290
+ await self._rate_limiter.add_usage(input_tokens + total_output_tokens)
291
+ break # Success, exit retry loop
292
+
293
+ except RateLimitError as e:
294
+ retries += 1
295
+ if retries > self.max_retries:
296
+ raise
297
+
298
+ # Hit rate limit, wait and retry
299
+ # Extract wait time from headers if available, otherwise use exponential backoff
300
+ wait_time = 5.0 * (2 ** (retries - 1)) # Default backoff
301
+
302
+ # Log to console if possible
303
+ from rich.console import Console
304
+ Console().print(f"[yellow]Rate limit reached (429). Retrying in {wait_time:.1f}s... (Attempt {retries}/{self.max_retries})[/yellow]")
305
+
306
+ await asyncio.sleep(wait_time)
307
+ # After sleeping, we loop back and try again.
308
+ # The rate limiter's wait_for_capacity will be called again if we move it inside the loop,
309
+ # but we already called it once. However, Anthropic says we are OVER the limit,
310
+ # so we should probably record that usage or just wait.
311
+ # Let's move the wait_for_capacity INSIDE the retry loop.
@@ -0,0 +1,71 @@
1
+ """Rate limiting utilities for API providers."""
2
+
3
+ import asyncio
4
+ import time
5
+ from collections import deque
6
+
7
+
8
+ class AsyncRateLimiter:
9
+ """An asynchronous rate limiter using a sliding window.
10
+
11
+ Tracks token usage and provides a way to wait until enough capacity
12
+ is available.
13
+ """
14
+
15
+ def __init__(self, tokens_per_minute: int) -> None:
16
+ """Initialize the rate limiter.
17
+
18
+ Args:
19
+ tokens_per_minute: Maximum tokens allowed per 60-second window.
20
+ """
21
+ self.tokens_per_minute = tokens_per_minute
22
+ # Each entry is (timestamp, token_count)
23
+ self.usage: deque[tuple[float, int]] = deque()
24
+ self._lock = asyncio.Lock()
25
+
26
+ async def _clean_old_usage(self) -> None:
27
+ """Remove usage entries older than 60 seconds."""
28
+ now = time.time()
29
+ while self.usage and self.usage[0][0] < now - 60:
30
+ self.usage.popleft()
31
+
32
+ def _get_current_usage(self) -> int:
33
+ """Calculate the total tokens used in the last 60 seconds."""
34
+ return sum(tokens for _, tokens in self.usage)
35
+
36
+ async def wait_for_capacity(self, tokens: int) -> None:
37
+ """Wait until the specified number of tokens can be used.
38
+
39
+ Args:
40
+ tokens: The number of tokens to be used.
41
+ """
42
+ if tokens > self.tokens_per_minute:
43
+ # If a single request is larger than the limit, we can't really
44
+ # satisfy it, but we'll just wait for the window to be completely clear.
45
+ tokens = self.tokens_per_minute
46
+
47
+ async with self._lock:
48
+ while True:
49
+ await self._clean_old_usage()
50
+ current_usage = self._get_current_usage()
51
+
52
+ if current_usage + tokens <= self.tokens_per_minute:
53
+ break
54
+
55
+ # Wait until the oldest entry expires
56
+ if self.usage:
57
+ sleep_time = self.usage[0][0] + 60.1 - time.time()
58
+ if sleep_time > 0:
59
+ await asyncio.sleep(sleep_time)
60
+ else:
61
+ # Should not happen if current_usage > 0, but safety first
62
+ await asyncio.sleep(1)
63
+
64
+ async def add_usage(self, tokens: int) -> None:
65
+ """Record token usage.
66
+
67
+ Args:
68
+ tokens: The number of tokens used.
69
+ """
70
+ async with self._lock:
71
+ self.usage.append((time.time(), tokens))
@@ -27,6 +27,7 @@ MODEL_LIMITS: dict[str, int] = {
27
27
  "gpt-4": 8192,
28
28
  "gpt-3.5-turbo": 16385,
29
29
  # Anthropic models (these use different tokenization but we estimate)
30
+ "claude-opus-4-6": 200000,
30
31
  "claude-sonnet-4-20250514": 200000,
31
32
  "claude-3-7-sonnet-20250219": 200000,
32
33
  "claude-3-5-sonnet-20241022": 200000,
@@ -1,6 +1,6 @@
1
1
  """Version information for Henchman-AI."""
2
2
 
3
- VERSION_TUPLE = (0, 1, 13)
3
+ VERSION_TUPLE = (0, 1, 16)
4
4
  VERSION = ".".join(str(v) for v in VERSION_TUPLE)
5
5
 
6
6
  __all__ = ["VERSION", "VERSION_TUPLE"]