micro-cc 0.2.83__tar.gz → 0.2.85__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 (225) hide show
  1. {micro_cc-0.2.83/src/micro_cc.egg-info → micro_cc-0.2.85}/PKG-INFO +1 -1
  2. {micro_cc-0.2.83 → micro_cc-0.2.85}/pyproject.toml +8 -2
  3. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/claude_loop_.py +46 -25
  4. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/adapters/anthropic.py +19 -3
  5. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/adapters/chat.py +27 -9
  6. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/adapters/responses.py +10 -4
  7. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/client.py +1 -0
  8. micro_cc-0.2.85/src/micro_cc/models/openrouter.py +248 -0
  9. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/registry.py +13 -8
  10. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/types.py +7 -0
  11. micro_cc-0.2.85/src/micro_cc/self_heal_.py +207 -0
  12. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/graph-orchestration/SKILL.md +35 -13
  13. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/start_headless_.py +12 -5
  14. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/start_live_tui_.py +359 -9
  15. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/monitor_.py +98 -3
  16. micro_cc-0.2.85/src/micro_cc/tools/monitor_schedule_.py +498 -0
  17. micro_cc-0.2.85/src/micro_cc/tools/monitor_schedule_runtime.py +109 -0
  18. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/search_tool_.py +4 -6
  19. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/etype_handler_.py +6 -1
  20. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/screen_cmds_.py +45 -4
  21. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/command_registry.py +6 -0
  22. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/helpers.py +49 -9
  23. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/hidden_prompts.py +31 -0
  24. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/hints.py +1 -1
  25. micro_cc-0.2.85/src/micro_cc/utils/self_reload_.py +154 -0
  26. {micro_cc-0.2.83 → micro_cc-0.2.85/src/micro_cc.egg-info}/PKG-INFO +1 -1
  27. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc.egg-info/SOURCES.txt +8 -1
  28. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc.egg-info/entry_points.txt +1 -1
  29. {micro_cc-0.2.83 → micro_cc-0.2.85}/tests/test_model_consumers.py +14 -3
  30. {micro_cc-0.2.83 → micro_cc-0.2.85}/tests/test_models.py +28 -4
  31. micro_cc-0.2.85/tests/test_schedule.py +168 -0
  32. micro_cc-0.2.85/tests/test_self_reload.py +200 -0
  33. {micro_cc-0.2.83 → micro_cc-0.2.85}/LICENSE +0 -0
  34. {micro_cc-0.2.83 → micro_cc-0.2.85}/README.md +0 -0
  35. {micro_cc-0.2.83 → micro_cc-0.2.85}/setup.cfg +0 -0
  36. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/__init__.py +0 -0
  37. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/browser/__init__.py +0 -0
  38. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/browser/_cookies.py +0 -0
  39. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/browser/_md_convert.py +0 -0
  40. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/browser/browser_manager.py +0 -0
  41. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/browser/simpletextbrowser.py +0 -0
  42. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/cache/__init__.py +0 -0
  43. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/cache/state_store.py +0 -0
  44. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/execute_tool.py +0 -0
  45. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/__init__.py +0 -0
  46. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/adapters/__init__.py +0 -0
  47. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/anthropic.py +0 -0
  48. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/foundry.py +0 -0
  49. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/litellm.py +0 -0
  50. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/ollama.py +0 -0
  51. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/openai.py +0 -0
  52. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/priming.py +0 -0
  53. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/schema.py +0 -0
  54. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/postgres_store/__init__.py +0 -0
  55. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/postgres_store/pg_store_.py +0 -0
  56. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/postgres_store/schema_.py +0 -0
  57. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/SKILL.md +0 -0
  58. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/ArsenalSC-OFL.txt +0 -0
  59. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
  60. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
  61. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/BigShoulders-OFL.txt +0 -0
  62. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
  63. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Boldonse-OFL.txt +0 -0
  64. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Boldonse-Regular.ttf +0 -0
  65. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  66. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/BricolageGrotesque-OFL.txt +0 -0
  67. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  68. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
  69. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
  70. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/CrimsonPro-OFL.txt +0 -0
  71. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
  72. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/DMMono-OFL.txt +0 -0
  73. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/DMMono-Regular.ttf +0 -0
  74. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/EricaOne-OFL.txt +0 -0
  75. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/EricaOne-Regular.ttf +0 -0
  76. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/GeistMono-Bold.ttf +0 -0
  77. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/GeistMono-OFL.txt +0 -0
  78. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/GeistMono-Regular.ttf +0 -0
  79. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Gloock-OFL.txt +0 -0
  80. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Gloock-Regular.ttf +0 -0
  81. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
  82. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/IBMPlexMono-OFL.txt +0 -0
  83. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
  84. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
  85. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
  86. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
  87. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
  88. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  89. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  90. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  91. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-OFL.txt +0 -0
  92. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  93. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  94. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  95. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Italiana-OFL.txt +0 -0
  96. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Italiana-Regular.ttf +0 -0
  97. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  98. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/JetBrainsMono-OFL.txt +0 -0
  99. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  100. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Jura-Light.ttf +0 -0
  101. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Jura-Medium.ttf +0 -0
  102. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Jura-OFL.txt +0 -0
  103. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/LibreBaskerville-OFL.txt +0 -0
  104. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
  105. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Lora-Bold.ttf +0 -0
  106. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  107. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Lora-Italic.ttf +0 -0
  108. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Lora-OFL.txt +0 -0
  109. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Lora-Regular.ttf +0 -0
  110. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/NationalPark-Bold.ttf +0 -0
  111. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/NationalPark-OFL.txt +0 -0
  112. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/NationalPark-Regular.ttf +0 -0
  113. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/NothingYouCouldDo-OFL.txt +0 -0
  114. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  115. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Outfit-Bold.ttf +0 -0
  116. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Outfit-OFL.txt +0 -0
  117. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Outfit-Regular.ttf +0 -0
  118. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
  119. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/PixelifySans-OFL.txt +0 -0
  120. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/PoiretOne-OFL.txt +0 -0
  121. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
  122. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
  123. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/RedHatMono-OFL.txt +0 -0
  124. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
  125. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Silkscreen-OFL.txt +0 -0
  126. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
  127. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
  128. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/SmoochSans-OFL.txt +0 -0
  129. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Tektur-Medium.ttf +0 -0
  130. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Tektur-OFL.txt +0 -0
  131. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Tektur-Regular.ttf +0 -0
  132. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/WorkSans-Bold.ttf +0 -0
  133. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
  134. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/WorkSans-Italic.ttf +0 -0
  135. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/WorkSans-OFL.txt +0 -0
  136. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/WorkSans-Regular.ttf +0 -0
  137. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/YoungSerif-OFL.txt +0 -0
  138. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
  139. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/references/blueprint-tokens.md +0 -0
  140. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/github/SKILL.md +0 -0
  141. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/pptx/SKILL.md +0 -0
  142. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/pptx/brief.md +0 -0
  143. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/pptx/scripts/build_deck.js +0 -0
  144. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/pptx/scripts/deck_to_images.py +0 -0
  145. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/pptx/scripts/office/__init__.py +0 -0
  146. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/pptx/scripts/office/soffice.py +0 -0
  147. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/pptx/scripts/render_svg.py +0 -0
  148. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/pptx/svg-slides.md +0 -0
  149. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/skill_loader.py +0 -0
  150. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/system-design-review/REFERENCE.md +0 -0
  151. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/system-design-review/SKILL.md +0 -0
  152. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/system-design-review/scripts/design_graph.py +0 -0
  153. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/system-design-review/scripts/design_review.py +0 -0
  154. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/xlsx/SKILL.md +0 -0
  155. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/__init__.py +0 -0
  156. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/ask_user_tool.py +0 -0
  157. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/bash_tool.py +0 -0
  158. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/browser_tool_.py +0 -0
  159. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/computer_tool_.py +0 -0
  160. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/file_tools_.py +0 -0
  161. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/mcp_client_.py +0 -0
  162. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/memory_tool_.py +0 -0
  163. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/message_session_.py +0 -0
  164. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/monitor_watch_.py +0 -0
  165. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/search_history_tool_.py +0 -0
  166. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/skill_tools_.py +0 -0
  167. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/todo_tools_.py +0 -0
  168. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/vision_tools_.py +0 -0
  169. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/web_tools_.py +0 -0
  170. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/__init__.py +0 -0
  171. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/alt_screen_.py +0 -0
  172. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/detect_images_.py +0 -0
  173. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/diff_viewer_.py +0 -0
  174. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/keys_.py +0 -0
  175. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/kill_ring_.py +0 -0
  176. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/list_picker_.py +0 -0
  177. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/md_render_.py +0 -0
  178. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/message_row_.py +0 -0
  179. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/multi_select_list_.py +0 -0
  180. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/prompt_input_.py +0 -0
  181. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/question_panel_.py +0 -0
  182. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/scroll_view_.py +0 -0
  183. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/self_terminal_setup_.py +0 -0
  184. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/self_update_.py +0 -0
  185. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/stack_.py +0 -0
  186. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/stdin_buffer_.py +0 -0
  187. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/terminal_.py +0 -0
  188. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/text_.py +0 -0
  189. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/text_utils_.py +0 -0
  190. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/theme_.py +0 -0
  191. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/truncated_text_.py +0 -0
  192. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/undo_stack_.py +0 -0
  193. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/word_navigation_.py +0 -0
  194. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/__init__.py +0 -0
  195. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/annotate_.py +0 -0
  196. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/banner_.py +0 -0
  197. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/claude_md_loader.py +0 -0
  198. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/claude_subscription.py +0 -0
  199. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/default_status_sh.py +0 -0
  200. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/history_mount_.py +0 -0
  201. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/inbox_store_.py +0 -0
  202. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/memory_store_.py +0 -0
  203. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/msg_normalize_.py +0 -0
  204. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/msg_store_.py +0 -0
  205. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/project_files_.py +0 -0
  206. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/session_ipc_.py +0 -0
  207. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/settings_store_.py +0 -0
  208. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/status_marker.py +0 -0
  209. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/subagent_tracker_.py +0 -0
  210. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/terminal_setup.py +0 -0
  211. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/tokenization_simple.py +0 -0
  212. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/tool_limits.py +0 -0
  213. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/tool_result_storage.py +0 -0
  214. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/__init__.py +0 -0
  215. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/bridge.py +0 -0
  216. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/dist/assets/index-9B5FcRm2.css +0 -0
  217. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/dist/assets/index-CDG9HO3B.js +0 -0
  218. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/dist/index.html +0 -0
  219. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/history.py +0 -0
  220. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/launch.py +0 -0
  221. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/server.py +0 -0
  222. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/session.py +0 -0
  223. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc.egg-info/dependency_links.txt +0 -0
  224. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc.egg-info/requires.txt +0 -0
  225. {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: micro-cc
3
- Version: 0.2.83
3
+ Version: 0.2.85
4
4
  Summary: Harness that gives frontier models full system access — shell, filesystem, browser, MCP — running directly on the metal.
5
5
  Author: George Salapa
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "micro-cc"
7
- version = "0.2.83"
7
+ version = "0.2.85"
8
8
  description = "Harness that gives frontier models full system access — shell, filesystem, browser, MCP — running directly on the metal."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -51,7 +51,13 @@ dependencies = [
51
51
  postgres = ["psycopg[binary]>=3.2"]
52
52
 
53
53
  [project.scripts]
54
- microcc = "micro_cc.start_live_tui_:start_"
54
+ # The interactive entry point goes through the supervisor (self_heal_.main),
55
+ # not start_live_tui_ directly: the harness is plain source the model can edit,
56
+ # so a bad self-edit can stop the app booting — and the only process that could
57
+ # report that is the one that won't start. self_heal_ catches a boot failure,
58
+ # reinstalls the published wheel over the broken tree and retries, bounded.
59
+ # headless stays direct — it's a one-shot run, not a long-lived UI to supervise.
60
+ microcc = "micro_cc.self_heal_:main"
55
61
  microcc-headless = "micro_cc.start_headless_:start_headless_"
56
62
 
57
63
  [tool.setuptools.packages.find]
@@ -13,10 +13,8 @@ from micro_cc.utils.tool_limits import cap_for, MAX_TOOL_RESULTS_PER_MESSAGE
13
13
  from micro_cc.utils.tool_result_storage import persist_and_preview
14
14
  from micro_cc.models.schema import function_to_schema
15
15
  from micro_cc.skills.skill_loader import get_skill_summary
16
- from micro_cc.tools.bash_tool import bash_, list_background_processes
16
+ from micro_cc.tools.bash_tool import bash_
17
17
  from micro_cc.tools.monitor_ import monitor_
18
- from micro_cc.tools.monitor_watch_ import list_watches as list_monitor_watches
19
- from micro_cc.utils import subagent_tracker_
20
18
  from micro_cc.tools.file_tools_ import read_, write_, edit_, glob_, grep_
21
19
  from micro_cc.tools.search_tool_ import search_tools, list_mcps
22
20
  from micro_cc.tools.todo_tools_ import todo_tool_
@@ -145,25 +143,20 @@ async def claude_loop(
145
143
  search_history_,
146
144
  read_skill, list_skills, list_mcps,
147
145
  ask_user_question_tool_,
146
+ monitor_,
148
147
  ]
149
- # monitor_ is conditional, not a permanent fixture here: it's only worth
150
- # a schema slot on a turn where there's actually something to monitor —
151
- # a tracked subagent, a bash_-backgrounded process still alive, or an
152
- # already-running monitor_(action="watch") stream. All three are cheap
153
- # (in-memory dict lookups, one small JSON file read — no network, no
154
- # subprocess), so checking every turn costs nothing on the common case
155
- # where none of them apply and monitor_ simply doesn't appear. This is
156
- # deliberately NOT the search_tools discoverable path (see
157
- # search_tool_.py's comment by monitor_'s old registration there) —
158
- # the model would have to think to search for it, and either there's
159
- # nothing to monitor (in which case the tool would be a no-op anyway)
160
- # or one of the checks below already put it in scope.
161
- if (
162
- list_background_processes()
163
- or subagent_tracker_.read_tracked(project_dir)["subagents"]
164
- or list_monitor_watches()
165
- ):
166
- default_tools.append(monitor_)
148
+ # monitor_ is a PERMANENT core tool (like memory_), not conditionally
149
+ # injected. It used to appear only when something already existed to
150
+ # monitor — a tracked subagent, a live backgrounded process, an active
151
+ # watch — but that made its create-style actions unreachable on a fresh
152
+ # session: you cannot schedule "wake me in 20 minutes" (action="schedule"),
153
+ # start a watch (action="watch"), or set anything up if the tool isn't in
154
+ # scope precisely *because* nothing exists yet. Same reasoning as memory_:
155
+ # a tool whose job is to CREATE things has to be reachable when there's
156
+ # nothing. The read actions ("check"/"list"/"schedules") self-report empty,
157
+ # so an always-present monitor_ costs one schema slot and nothing else.
158
+ # Its own docstring is the discovery surface; no search_tools registration
159
+ # needed (see search_tool_.py's comment by monitor_'s old registration).
167
160
  tool_schemas = []
168
161
  for tool in default_tools:
169
162
  tool_schemas.append(function_to_schema(tool))
@@ -570,11 +563,24 @@ whole conversation history, including compacted turns.
570
563
  for failure in new_mcp_failures:
571
564
  yield {"type": "error", "message": failure}
572
565
 
566
+ # A tool_use block whose streamed arguments didn't parse as
567
+ # JSON (adapter sets ContentBlock.parse_error rather than
568
+ # raising or inventing {} args — see models/adapters/*.py)
569
+ # must never execute with wrong/empty input. Excluded from
570
+ # local/mcp below and handled the same way as an unknown
571
+ # tool name: an is_error tool_result telling the model what
572
+ # broke, so it can retry, instead of the exception
573
+ # propagating out of the stream and killing the whole turn.
574
+ malformed_by_id = {tb.id: tb.parse_error for tb in tool_use_blocks if tb.parse_error}
575
+
573
576
  # Split into: local tools, client-side MCP tools, truly unknown
574
- local_tool_blocks = [tb for tb in tool_use_blocks if tb.name in tools]
575
- mcp_tool_blocks = [tb for tb in tool_use_blocks if tb.name in mcp_routing]
577
+ local_tool_blocks = [tb for tb in tool_use_blocks
578
+ if tb.name in tools and tb.id not in malformed_by_id]
579
+ mcp_tool_blocks = [tb for tb in tool_use_blocks
580
+ if tb.name in mcp_routing and tb.id not in malformed_by_id]
576
581
  unknown_blocks = [tb for tb in tool_use_blocks
577
- if tb.name not in tools and tb.name not in mcp_routing]
582
+ if tb.name not in tools and tb.name not in mcp_routing
583
+ and tb.id not in malformed_by_id]
578
584
 
579
585
  # Hallucinated tool names get an error tool_result instead of
580
586
  # being executed — folded into the single turn message below
@@ -716,6 +722,20 @@ whole conversation history, including compacted turns.
716
722
  tool_result_blocks = []
717
723
  persisted_this_turn = set()
718
724
  for tool_block in tool_use_blocks:
725
+ if tool_block.id in malformed_by_id:
726
+ tool_result_blocks.append(
727
+ {
728
+ "type": "tool_result",
729
+ "tool_use_id": tool_block.id,
730
+ "content": (
731
+ f"Error: arguments for tool '{tool_block.name}' were not valid JSON "
732
+ f"({malformed_by_id[tool_block.id]}). Retry the call with valid JSON input."
733
+ ),
734
+ "is_error": True,
735
+ }
736
+ )
737
+ continue
738
+
719
739
  if tool_block.id in unknown_ids:
720
740
  tool_result_blocks.append(
721
741
  {
@@ -854,7 +874,8 @@ whole conversation history, including compacted turns.
854
874
  break
855
875
  block = tool_result_blocks[i]
856
876
  tb = by_id.get(block["tool_use_id"])
857
- if tb is None or tb.id in persisted_this_turn or tb.id in unknown_ids or tb.id in ask_errors_by_id:
877
+ if (tb is None or tb.id in persisted_this_turn or tb.id in unknown_ids
878
+ or tb.id in ask_errors_by_id or tb.id in malformed_by_id):
858
879
  continue
859
880
  old_len = len(block["content"])
860
881
  block["content"] = persist_and_preview(
@@ -74,8 +74,21 @@ async def stream_response(raw_stream, unstealth=None) -> ResponseStream:
74
74
 
75
75
  elif event.type == "content_block_stop":
76
76
  if current_tool:
77
- args = json.loads(current_tool["input_json"]) if current_tool["input_json"] else {}
78
- tool_blocks.append(current_tool | {"input": args})
77
+ parse_error = ""
78
+ try:
79
+ args = json.loads(current_tool["input_json"]) if current_tool["input_json"] else {}
80
+ except json.JSONDecodeError as e:
81
+ # Never invent arguments for a tool call whose streamed
82
+ # JSON didn't parse — but don't let this raise either:
83
+ # that used to propagate out of the whole generator and
84
+ # take down the entire turn (claude_loop_'s outer
85
+ # except-and-break) over one bad tool call. Flagging it
86
+ # on the block instead lets claude_loop_ hand the model
87
+ # an is_error tool_result and continue, same treatment
88
+ # as a hallucinated tool name.
89
+ args = {}
90
+ parse_error = str(e)
91
+ tool_blocks.append(current_tool | {"input": args, "parse_error": parse_error})
79
92
  current_tool = None
80
93
 
81
94
  elif event.type == "message_delta":
@@ -90,6 +103,9 @@ async def stream_response(raw_stream, unstealth=None) -> ResponseStream:
90
103
  blocks.append(ContentBlock(type="text", text=text))
91
104
  for tb in tool_blocks:
92
105
  name = unstealth(tb["name"]) if unstealth else tb["name"]
93
- blocks.append(ContentBlock(type="tool_use", name=name, input=tb["input"], id=tb["id"]))
106
+ blocks.append(ContentBlock(
107
+ type="tool_use", name=name, input=tb["input"], id=tb["id"],
108
+ parse_error=tb.get("parse_error", ""),
109
+ ))
94
110
 
95
111
  yield {"type": "response", "response": Response(content=blocks, usage=usage), "usage": usage}
@@ -1,4 +1,4 @@
1
- """OpenAI Chat Completions output, including LiteLLM/Ollama reasoning.
1
+ """OpenAI Chat Completions output, including LiteLLM/Ollama/OpenRouter reasoning.
2
2
 
3
3
  The wire protocol is shared; only reasoning extraction varies by provider.
4
4
  These functions consume streams in the caller's task without prefetch/retry.
@@ -9,13 +9,14 @@ from typing import Literal
9
9
 
10
10
  from micro_cc.models.types import ContentBlock, Response, ResponseStream, Usage
11
11
 
12
- Reasoning = Literal["none", "litellm", "ollama"]
12
+ Reasoning = Literal["none", "litellm", "ollama", "openrouter"]
13
13
 
14
14
 
15
15
  def _reasoning_text(obj, reasoning: Reasoning) -> str:
16
16
  if reasoning == "litellm":
17
17
  return getattr(obj, "reasoning_content", None) or ""
18
- if reasoning == "ollama":
18
+ if reasoning in ("ollama", "openrouter"):
19
+ # Same plain "reasoning" string field on both.
19
20
  text = getattr(obj, "reasoning", None)
20
21
  if text:
21
22
  return text
@@ -56,11 +57,15 @@ def response(raw, *, reasoning: Reasoning = "none") -> Response:
56
57
  if msg.content:
57
58
  blocks.append(ContentBlock(type="text", text=msg.content))
58
59
  for tc in msg.tool_calls or []:
60
+ parse_error = ""
59
61
  try:
60
62
  args = json.loads(tc.function.arguments)
61
- except (json.JSONDecodeError, TypeError):
63
+ except (json.JSONDecodeError, TypeError) as e:
62
64
  args = {}
63
- blocks.append(ContentBlock(type="tool_use", name=tc.function.name, input=args, id=tc.id))
65
+ parse_error = str(e)
66
+ blocks.append(ContentBlock(
67
+ type="tool_use", name=tc.function.name, input=args, id=tc.id, parse_error=parse_error,
68
+ ))
64
69
  return Response(content=blocks, usage=_usage(raw))
65
70
 
66
71
 
@@ -100,9 +105,22 @@ async def stream_response(raw_stream, *, reasoning: Reasoning = "none") -> Respo
100
105
  if text:
101
106
  blocks.append(ContentBlock(type="text", text=text))
102
107
  for tc in tool_calls.values():
103
- # As before, malformed streamed arguments fail rather than execute a
104
- # partial call with invented arguments. No retry after visible deltas.
105
- args = json.loads(tc["args"]) if tc["args"] else {}
106
- blocks.append(ContentBlock(type="tool_use", name=tc["name"], input=args, id=tc["id"]))
108
+ # Malformed streamed arguments never execute as a partial call with
109
+ # invented input — but parse failure is flagged on the block
110
+ # (parse_error) rather than raised: raising here used to propagate
111
+ # out of this whole generator and kill the entire turn (claude_loop_'s
112
+ # outer except-and-break) over one bad tool call. claude_loop_ instead
113
+ # hands the model an is_error tool_result and continues, same
114
+ # treatment as a hallucinated tool name.
115
+ parse_error = ""
116
+ args = {}
117
+ if tc["args"]:
118
+ try:
119
+ args = json.loads(tc["args"])
120
+ except json.JSONDecodeError as e:
121
+ parse_error = str(e)
122
+ blocks.append(ContentBlock(
123
+ type="tool_use", name=tc["name"], input=args, id=tc["id"], parse_error=parse_error,
124
+ ))
107
125
  result = Response(content=blocks, usage=usage)
108
126
  yield {"type": "response", "response": result, "usage": usage}
@@ -19,12 +19,14 @@ def response(resp) -> Response:
19
19
  if getattr(part, "type", None) == "output_text":
20
20
  blocks.append(ContentBlock(type="text", text=part.text))
21
21
  elif item.type == "function_call":
22
+ parse_error = ""
22
23
  try:
23
24
  args = json.loads(item.arguments)
24
- except (json.JSONDecodeError, TypeError):
25
+ except (json.JSONDecodeError, TypeError) as e:
25
26
  args = {}
27
+ parse_error = str(e)
26
28
  blocks.append(ContentBlock(
27
- type="tool_use", name=item.name, input=args, id=item.call_id,
29
+ type="tool_use", name=item.name, input=args, id=item.call_id, parse_error=parse_error,
28
30
  ))
29
31
 
30
32
  usage = {}
@@ -65,10 +67,14 @@ async def stream_response(resp_stream) -> ResponseStream:
65
67
  if text:
66
68
  blocks.append(ContentBlock(type="text", text=text))
67
69
  for tc in tool_calls:
70
+ parse_error = ""
68
71
  try:
69
72
  args = json.loads(tc.arguments)
70
- except (json.JSONDecodeError, TypeError):
73
+ except (json.JSONDecodeError, TypeError) as e:
71
74
  args = {}
72
- blocks.append(ContentBlock(type="tool_use", name=tc.name, input=args, id=tc.call_id))
75
+ parse_error = str(e)
76
+ blocks.append(ContentBlock(
77
+ type="tool_use", name=tc.name, input=args, id=tc.call_id, parse_error=parse_error,
78
+ ))
73
79
 
74
80
  yield {"type": "response", "response": Response(content=blocks, usage=usage), "usage": usage}
@@ -10,6 +10,7 @@ _PROVIDERS = {
10
10
  "Foundry": ("foundry", "f_model_call"),
11
11
  "LiteLLM": ("litellm", "l_model_call"),
12
12
  "OpenAI": ("openai", "oai_model_call"),
13
+ "OpenRouter": ("openrouter", "or_model_call"),
13
14
  "Ollama": ("ollama", "o_model_call"),
14
15
  }
15
16
 
@@ -0,0 +1,248 @@
1
+ """OpenRouter requests via its OpenAI-compatible endpoint.
2
+
3
+ No fixed alias table (models/registry.py) — the model is whatever
4
+ "author/slug" the user pasted at /login. resolve() still runs but just
5
+ passes it through unchanged. Reasoning uses OpenRouter's unified `reasoning`
6
+ param instead of a provider-specific toggle.
7
+
8
+ Env vars:
9
+ OPENROUTER_API_KEY: API key (openrouter.ai/keys)
10
+ OPENROUTER_BASE_URL: default https://openrouter.ai/api/v1
11
+ """
12
+
13
+ from typing import List, Dict, Any, Optional, Union
14
+ from openai import AsyncOpenAI
15
+ from dotenv import load_dotenv
16
+ import asyncio
17
+ import json
18
+ import os
19
+
20
+ from micro_cc.models.adapters import chat
21
+ from micro_cc.models.priming import prime_stream
22
+ from micro_cc.models.schema import tools_to_chat
23
+ from micro_cc.models.registry import DEFAULT_MODEL, resolve, supports_thinking
24
+
25
+ load_dotenv(os.path.expanduser("~/.micro-cc/.env"))
26
+ load_dotenv()
27
+
28
+ FIRST_EVENT_TIMEOUT = 60
29
+
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Converters — Anthropic format -> OpenAI chat format
33
+ # ---------------------------------------------------------------------------
34
+
35
+
36
+ def _msgs_to_openai(anthropic_msgs):
37
+ """Anthropic-format message list -> OpenAI chat format. Thinking blocks
38
+ are dropped — the model re-derives reasoning each turn instead."""
39
+ out = []
40
+ for msg in anthropic_msgs:
41
+ role = msg.get("role")
42
+ content = msg.get("content", "")
43
+
44
+ if role == "system":
45
+ out.append({"role": "system", "content": content})
46
+ continue
47
+
48
+ if role == "assistant" and isinstance(content, list):
49
+ text_parts = []
50
+ tool_calls = []
51
+ for block in content:
52
+ if not isinstance(block, dict):
53
+ continue
54
+ btype = block.get("type")
55
+ if btype == "text":
56
+ text_parts.append(block["text"])
57
+ elif btype == "tool_use":
58
+ tool_calls.append({
59
+ "id": block["id"],
60
+ "type": "function",
61
+ "function": {
62
+ "name": block["name"],
63
+ "arguments": json.dumps(block["input"]),
64
+ },
65
+ })
66
+
67
+ m = {
68
+ "role": "assistant",
69
+ "content": "\n".join(text_parts) if text_parts else None,
70
+ }
71
+ if tool_calls:
72
+ m["tool_calls"] = tool_calls
73
+ out.append(m)
74
+ continue
75
+
76
+ if role == "user" and isinstance(content, list):
77
+ tool_results = []
78
+ other_parts = []
79
+
80
+ for block in content:
81
+ if not isinstance(block, dict):
82
+ continue
83
+ btype = block.get("type")
84
+ if btype == "tool_result":
85
+ tr_content = block.get("content", "")
86
+ if isinstance(tr_content, list):
87
+ tool_results.append({
88
+ "role": "tool",
89
+ "tool_call_id": block["tool_use_id"],
90
+ "content": "[image attached — see following message]",
91
+ })
92
+ for item in tr_content:
93
+ if item.get("type") == "image":
94
+ source = item.get("source", {})
95
+ media = source.get("media_type", "image/jpeg")
96
+ other_parts.append({
97
+ "type": "image_url",
98
+ "image_url": {"url": f"data:{media};base64,{source.get('data', '')}"},
99
+ })
100
+ else:
101
+ tool_results.append({
102
+ "role": "tool",
103
+ "tool_call_id": block["tool_use_id"],
104
+ "content": tr_content,
105
+ })
106
+ elif btype == "text":
107
+ other_parts.append({"type": "text", "text": block["text"]})
108
+ elif btype == "image":
109
+ source = block.get("source", {})
110
+ media = source.get("media_type", "image/jpeg")
111
+ other_parts.append({
112
+ "type": "image_url",
113
+ "image_url": {
114
+ "url": f"data:{media};base64,{source.get('data', '')}",
115
+ },
116
+ })
117
+ elif btype == "document":
118
+ other_parts.append({
119
+ "type": "text",
120
+ "text": "[PDF document attached]",
121
+ })
122
+
123
+ out.extend(tool_results)
124
+
125
+ if other_parts:
126
+ if len(other_parts) == 1 and other_parts[0].get("type") == "text":
127
+ out.append({"role": "user", "content": other_parts[0]["text"]})
128
+ else:
129
+ out.append({"role": "user", "content": other_parts})
130
+ continue
131
+
132
+ out.append({"role": role, "content": content})
133
+
134
+ return out
135
+
136
+
137
+ # ---------------------------------------------------------------------------
138
+ # Main entry point — same signature pattern as models/ollama.py
139
+ # ---------------------------------------------------------------------------
140
+
141
+
142
+ async def or_model_call(
143
+ input: Union[List[Dict[str, Any]], str],
144
+ model=DEFAULT_MODEL,
145
+ encoded_image: Optional[Union[str, List[str]]] = None,
146
+ tools: Optional[List[Dict[str, Any]]] = None,
147
+ stream: bool = False,
148
+ thinking: bool = False,
149
+ max_tokens: int = 120000,
150
+ client_timeout: int = 480,
151
+ pdf: str = None,
152
+ retries: int = 3,
153
+ ):
154
+ base_url = os.getenv("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1")
155
+ client = AsyncOpenAI(
156
+ base_url=base_url,
157
+ api_key=os.getenv("OPENROUTER_API_KEY", ""),
158
+ timeout=client_timeout,
159
+ )
160
+ base_sleep = 2
161
+
162
+ alias = model
163
+ model = resolve(alias, "openrouter")
164
+
165
+ # ---- Build messages ----
166
+ if pdf:
167
+ messages = [{"role": "user", "content": f"[PDF document attached]\n\n{input}"}]
168
+ elif encoded_image:
169
+ content = []
170
+ imgs = [encoded_image] if isinstance(encoded_image, str) else encoded_image
171
+ for img in imgs:
172
+ content.append({
173
+ "type": "image_url",
174
+ "image_url": {"url": f"data:image/jpeg;base64,{img}"},
175
+ })
176
+ content.append({"type": "text", "text": input})
177
+ messages = [{"role": "user", "content": content}]
178
+ elif isinstance(input, str):
179
+ messages = [{"role": "user", "content": input}]
180
+ else:
181
+ messages = _msgs_to_openai(input)
182
+
183
+ # ---- API parameters ----
184
+ api_params = {
185
+ "model": model,
186
+ "max_tokens": max_tokens,
187
+ "stream": stream,
188
+ "messages": messages,
189
+ }
190
+
191
+ if stream:
192
+ api_params["stream_options"] = {"include_usage": True}
193
+
194
+ # Non-standard params (vLLM/HF-ecosystem, not part of OpenAI's own API
195
+ # surface) must ride in extra_body — the openai SDK's typed
196
+ # chat.completions.create() raises a TypeError on an unknown top-level
197
+ # kwarg before a request is even sent, it doesn't pass it through.
198
+ extra_body = {}
199
+
200
+ if thinking and supports_thinking(alias):
201
+ # OpenRouter's unified reasoning param — a no-op on unsupported models.
202
+ extra_body["reasoning"] = {"effort": "high"}
203
+
204
+ # Sampling-level guard against the literal token-repetition loop some
205
+ # open-weight reasoning models (DeepSeek in particular) fall into under
206
+ # sustained deliberation — short tokens ("Go." / "OK." / "Write.")
207
+ # cycling instead of terminating. repetition_penalty is the
208
+ # provider-native lever for exactly this failure mode (>1.0 penalizes
209
+ # tokens already seen in the generation so far). A model that doesn't
210
+ # support it (not in its OpenRouter supported_parameters) just ignores
211
+ # the field rather than erroring. 1.1 is a conservative default — tune
212
+ # here if retesting shows it's too weak/strong.
213
+ extra_body["repetition_penalty"] = 1.1
214
+
215
+ if extra_body:
216
+ api_params["extra_body"] = extra_body
217
+
218
+ if tools:
219
+ openrouter_tools = tools_to_chat(tools)
220
+ if openrouter_tools:
221
+ api_params["tools"] = openrouter_tools
222
+ api_params["tool_choice"] = "auto"
223
+
224
+ # ---- Call with retries ----
225
+ for attempt in range(retries):
226
+ try:
227
+ response = await client.chat.completions.create(**api_params)
228
+ if stream:
229
+ return await prime_stream(
230
+ chat.stream_response(response, reasoning="openrouter"), FIRST_EVENT_TIMEOUT
231
+ )
232
+ else:
233
+ return chat.response(response, reasoning="openrouter")
234
+
235
+ except Exception as e:
236
+ import traceback
237
+ print(f"\n[openrouter model_call]: {e}", flush=True)
238
+ traceback.print_exc()
239
+ if attempt < retries - 1:
240
+ sleep_time = base_sleep * (2 ** attempt)
241
+ print(
242
+ f"\n[openrouter model_call]: Retrying in {sleep_time}s (attempt {attempt + 1}/{retries})..."
243
+ )
244
+ await asyncio.sleep(sleep_time)
245
+ else:
246
+ print(f"\n[openrouter model_call]: Failed after {retries} attempts")
247
+
248
+ return None
@@ -89,18 +89,23 @@ DEFAULT_TRIM_BUDGET = 100_000
89
89
  # for output (max_output, where known).
90
90
  PROMPT_RESERVE = 20_000
91
91
 
92
+ # Per-backend fallback for trim_budget_for when `alias` isn't in MODELS —
93
+ # OpenRouter aliases are free-text (never registered), so this is what they
94
+ # always get instead of the conservative DEFAULT_TRIM_BUDGET. Ollama isn't
95
+ # here: it derives its own budget from OLLAMA_NUM_CTX (start_live_tui_.py's
96
+ # _ollama_trim_budget) and never calls this function.
97
+ BACKEND_DEFAULT_TRIM_BUDGET = {
98
+ "openrouter": 1_000_000,
99
+ }
92
100
 
93
- def trim_budget_for(alias: str) -> int:
94
- """Per-model transcript trim budget: context_window - max_output -
95
- PROMPT_RESERVE, floored at DEFAULT_TRIM_BUDGET so an alias with no
96
- context_window entry (unregistered, or an Ollama model — those are
97
- sized from OLLAMA_NUM_CTX instead, see start_live_._ollama_trim_budget)
98
- doesn't get starved down to something smaller than the old flat default.
99
- """
101
+
102
+ def trim_budget_for(alias: str, backend: str | None = None) -> int:
103
+ """Per-model trim budget: context_window - max_output - PROMPT_RESERVE,
104
+ floored at DEFAULT_TRIM_BUDGET or BACKEND_DEFAULT_TRIM_BUDGET[backend]."""
100
105
  entry = MODELS.get(alias)
101
106
  window = entry.get("context_window") if entry else None
102
107
  if not window:
103
- return DEFAULT_TRIM_BUDGET
108
+ return BACKEND_DEFAULT_TRIM_BUDGET.get(backend, DEFAULT_TRIM_BUDGET)
104
109
  max_output = entry.get("max_output", 0) if entry else 0
105
110
  return max(DEFAULT_TRIM_BUDGET, window - max_output - PROMPT_RESERVE)
106
111
 
@@ -19,6 +19,13 @@ class ContentBlock:
19
19
  name: str = ""
20
20
  input: dict = field(default_factory=dict)
21
21
  id: str = ""
22
+ # Set (to the JSONDecodeError message) when a tool_use block's streamed
23
+ # arguments didn't parse as JSON. `input` is left {} rather than the
24
+ # model's raw, invented-looking partial text — but the block still
25
+ # carries id/name so claude_loop_ can pair it with an is_error
26
+ # tool_result instead of dropping it and violating the API's "one
27
+ # tool_result per tool_use" contract.
28
+ parse_error: str = ""
22
29
 
23
30
 
24
31
  class Usage(TypedDict, total=False):