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.
- {micro_cc-0.2.83/src/micro_cc.egg-info → micro_cc-0.2.85}/PKG-INFO +1 -1
- {micro_cc-0.2.83 → micro_cc-0.2.85}/pyproject.toml +8 -2
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/claude_loop_.py +46 -25
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/adapters/anthropic.py +19 -3
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/adapters/chat.py +27 -9
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/adapters/responses.py +10 -4
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/client.py +1 -0
- micro_cc-0.2.85/src/micro_cc/models/openrouter.py +248 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/registry.py +13 -8
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/types.py +7 -0
- micro_cc-0.2.85/src/micro_cc/self_heal_.py +207 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/graph-orchestration/SKILL.md +35 -13
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/start_headless_.py +12 -5
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/start_live_tui_.py +359 -9
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/monitor_.py +98 -3
- micro_cc-0.2.85/src/micro_cc/tools/monitor_schedule_.py +498 -0
- micro_cc-0.2.85/src/micro_cc/tools/monitor_schedule_runtime.py +109 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/search_tool_.py +4 -6
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/etype_handler_.py +6 -1
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/screen_cmds_.py +45 -4
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/command_registry.py +6 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/helpers.py +49 -9
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/hidden_prompts.py +31 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/hints.py +1 -1
- micro_cc-0.2.85/src/micro_cc/utils/self_reload_.py +154 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85/src/micro_cc.egg-info}/PKG-INFO +1 -1
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc.egg-info/SOURCES.txt +8 -1
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc.egg-info/entry_points.txt +1 -1
- {micro_cc-0.2.83 → micro_cc-0.2.85}/tests/test_model_consumers.py +14 -3
- {micro_cc-0.2.83 → micro_cc-0.2.85}/tests/test_models.py +28 -4
- micro_cc-0.2.85/tests/test_schedule.py +168 -0
- micro_cc-0.2.85/tests/test_self_reload.py +200 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/LICENSE +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/README.md +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/setup.cfg +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/__init__.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/browser/__init__.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/browser/_cookies.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/browser/_md_convert.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/browser/browser_manager.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/browser/simpletextbrowser.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/cache/__init__.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/cache/state_store.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/execute_tool.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/__init__.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/adapters/__init__.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/anthropic.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/foundry.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/litellm.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/ollama.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/openai.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/priming.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/models/schema.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/postgres_store/__init__.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/postgres_store/pg_store_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/postgres_store/schema_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/SKILL.md +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/ArsenalSC-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/BigShoulders-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Boldonse-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Boldonse-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/BricolageGrotesque-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/CrimsonPro-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/DMMono-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/DMMono-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/EricaOne-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/EricaOne-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/GeistMono-Bold.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/GeistMono-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/GeistMono-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Gloock-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Gloock-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/IBMPlexMono-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Italiana-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Italiana-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/JetBrainsMono-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Jura-Light.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Jura-Medium.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Jura-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/LibreBaskerville-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Lora-Bold.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Lora-Italic.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Lora-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Lora-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/NationalPark-Bold.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/NationalPark-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/NationalPark-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/NothingYouCouldDo-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Outfit-Bold.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Outfit-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Outfit-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/PixelifySans-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/PoiretOne-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/RedHatMono-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Silkscreen-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/SmoochSans-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Tektur-Medium.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Tektur-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/Tektur-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/WorkSans-Bold.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/WorkSans-Italic.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/WorkSans-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/WorkSans-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/YoungSerif-OFL.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/design/references/blueprint-tokens.md +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/github/SKILL.md +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/pptx/SKILL.md +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/pptx/brief.md +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/pptx/scripts/build_deck.js +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/pptx/scripts/deck_to_images.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/pptx/scripts/office/__init__.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/pptx/scripts/office/soffice.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/pptx/scripts/render_svg.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/pptx/svg-slides.md +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/skill_loader.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/system-design-review/REFERENCE.md +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/system-design-review/SKILL.md +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/system-design-review/scripts/design_graph.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/system-design-review/scripts/design_review.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/skills/xlsx/SKILL.md +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/__init__.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/ask_user_tool.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/bash_tool.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/browser_tool_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/computer_tool_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/file_tools_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/mcp_client_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/memory_tool_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/message_session_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/monitor_watch_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/search_history_tool_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/skill_tools_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/todo_tools_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/vision_tools_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tools/web_tools_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/__init__.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/alt_screen_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/detect_images_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/diff_viewer_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/keys_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/kill_ring_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/list_picker_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/md_render_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/message_row_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/multi_select_list_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/prompt_input_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/question_panel_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/scroll_view_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/self_terminal_setup_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/self_update_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/stack_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/stdin_buffer_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/terminal_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/text_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/text_utils_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/theme_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/truncated_text_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/undo_stack_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/tui_native/word_navigation_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/__init__.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/annotate_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/banner_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/claude_md_loader.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/claude_subscription.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/default_status_sh.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/history_mount_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/inbox_store_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/memory_store_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/msg_normalize_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/msg_store_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/project_files_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/session_ipc_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/settings_store_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/status_marker.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/subagent_tracker_.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/terminal_setup.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/tokenization_simple.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/tool_limits.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/utils/tool_result_storage.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/__init__.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/bridge.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/dist/assets/index-9B5FcRm2.css +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/dist/assets/index-CDG9HO3B.js +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/dist/index.html +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/history.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/launch.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/server.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc/webui/session.py +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc.egg-info/dependency_links.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc.egg-info/requires.txt +0 -0
- {micro_cc-0.2.83 → micro_cc-0.2.85}/src/micro_cc.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "micro-cc"
|
|
7
|
-
version = "0.2.
|
|
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
|
-
|
|
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_
|
|
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
|
|
150
|
-
#
|
|
151
|
-
# a tracked subagent, a
|
|
152
|
-
#
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
#
|
|
157
|
-
#
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
#
|
|
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
|
|
575
|
-
|
|
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
|
|
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
|
-
|
|
78
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
#
|
|
104
|
-
#
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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}
|
|
@@ -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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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):
|