kolega-code 0.7.2__tar.gz → 0.8.0__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 (310) hide show
  1. {kolega_code-0.7.2 → kolega_code-0.8.0}/.env.example +1 -0
  2. {kolega_code-0.7.2 → kolega_code-0.8.0}/PKG-INFO +2 -1
  3. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/concepts/agents.md +2 -2
  4. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/concepts/tools.md +7 -4
  5. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/configuration/environment-variables.md +5 -0
  6. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/configuration/providers-and-models.mdx +15 -4
  7. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/tui/modes.md +2 -0
  8. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/__init__.py +1 -1
  9. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/__init__.py +1 -1
  10. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/baseagent.py +42 -17
  11. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/conversation.py +203 -2
  12. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/investigationagent.py +2 -2
  13. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/planningagent.py +3 -1
  14. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/agents/investigation.md.j2 +1 -1
  15. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/agents/planning.md.j2 +5 -2
  16. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_chatgpt_oauth_provider.py +60 -0
  17. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_client.py +143 -0
  18. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_langfuse_normalization.py +17 -0
  19. kolega_code-0.8.0/kolega_code/agent/tests/llm/test_model_specs.py +51 -0
  20. kolega_code-0.8.0/kolega_code/agent/tests/llm/test_openai_message_conversion.py +140 -0
  21. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_openai_token_counting.py +25 -13
  22. kolega_code-0.8.0/kolega_code/agent/tests/llm/test_supports_vision.py +56 -0
  23. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_thinking_effort.py +26 -0
  24. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_agent_tools_inventory.py +18 -2
  25. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_base_agent.py +82 -0
  26. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_coder_attachments.py +175 -12
  27. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_compaction_persistence.py +3 -1
  28. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_conversation_compaction.py +32 -1
  29. kolega_code-0.8.0/kolega_code/agent/tests/test_image_compat.py +420 -0
  30. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_planning_agent.py +26 -2
  31. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_prompts.py +2 -0
  32. kolega_code-0.8.0/kolega_code/agent/tests/test_read_image_tool.py +161 -0
  33. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_tool_registry.py +4 -1
  34. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_tools.py +7 -0
  35. kolega_code-0.8.0/kolega_code/agent/tool_backend/read_image_tool.py +35 -0
  36. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/workflow_tool.py +3 -1
  37. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tools.py +42 -0
  38. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/app.py +368 -43
  39. kolega_code-0.8.0/kolega_code/cli/clipboard_image.py +152 -0
  40. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/main.py +17 -0
  41. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/mentions.py +20 -0
  42. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/messages.py +13 -0
  43. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/provider_registry.py +6 -1
  44. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/session_store.py +7 -2
  45. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/slash_commands.py +2 -0
  46. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/tests/test_app.py +246 -14
  47. kolega_code-0.8.0/kolega_code/cli/tests/test_app_compaction_restore.py +144 -0
  48. kolega_code-0.8.0/kolega_code/cli/tests/test_app_image_vision_warning.py +563 -0
  49. kolega_code-0.8.0/kolega_code/cli/tests/test_app_model_switch_vision.py +214 -0
  50. kolega_code-0.8.0/kolega_code/cli/tests/test_clipboard_image.py +185 -0
  51. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/tests/test_mentions.py +30 -0
  52. kolega_code-0.8.0/kolega_code/cli/tests/test_provider_registry.py +19 -0
  53. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/tests/test_session_store.py +24 -0
  54. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/llm/instrumented_client.py +2 -2
  55. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/llm/models.py +195 -76
  56. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/llm/providers/anthropic.py +1 -1
  57. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/llm/providers/chatgpt_oauth.py +39 -2
  58. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/llm/providers/openai.py +33 -8
  59. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/llm/specs.py +129 -11
  60. kolega_code-0.8.0/kolega_code/utils/__init__.py +1 -0
  61. kolega_code-0.8.0/kolega_code/utils/images.py +140 -0
  62. kolega_code-0.8.0/kolega_code/utils/tests/__init__.py +0 -0
  63. kolega_code-0.8.0/kolega_code/utils/tests/test_images.py +90 -0
  64. kolega_code-0.8.0/kolega_code/utils/tests/test_images_ascii.py +97 -0
  65. {kolega_code-0.7.2 → kolega_code-0.8.0}/pyproject.toml +2 -1
  66. {kolega_code-0.7.2 → kolega_code-0.8.0}/uv.lock +91 -2
  67. kolega_code-0.7.2/kolega_code/agent/tests/llm/test_model_specs.py +0 -31
  68. kolega_code-0.7.2/kolega_code/agent/tests/llm/test_openai_message_conversion.py +0 -30
  69. {kolega_code-0.7.2 → kolega_code-0.8.0}/.github/workflows/ci.yml +0 -0
  70. {kolega_code-0.7.2 → kolega_code-0.8.0}/.github/workflows/docs.yml +0 -0
  71. {kolega_code-0.7.2 → kolega_code-0.8.0}/.github/workflows/release.yml +0 -0
  72. {kolega_code-0.7.2 → kolega_code-0.8.0}/.gitignore +0 -0
  73. {kolega_code-0.7.2 → kolega_code-0.8.0}/CONTRIBUTING.md +0 -0
  74. {kolega_code-0.7.2 → kolega_code-0.8.0}/LICENSE +0 -0
  75. {kolega_code-0.7.2 → kolega_code-0.8.0}/README.md +0 -0
  76. {kolega_code-0.7.2 → kolega_code-0.8.0}/RELEASING.md +0 -0
  77. {kolega_code-0.7.2 → kolega_code-0.8.0}/SECURITY.md +0 -0
  78. {kolega_code-0.7.2 → kolega_code-0.8.0}/conftest.py +0 -0
  79. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/.gitignore +0 -0
  80. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/README.md +0 -0
  81. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/astro.config.mjs +0 -0
  82. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/package-lock.json +0 -0
  83. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/package.json +0 -0
  84. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/public/favicon.svg +0 -0
  85. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/assets/demo.gif +0 -0
  86. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/assets/kolega-dark.svg +0 -0
  87. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/assets/kolega-light.svg +0 -0
  88. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/cli/ask.md +0 -0
  89. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/cli/doctor.md +0 -0
  90. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/cli/overview.md +0 -0
  91. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/cli/sessions.md +0 -0
  92. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/concepts/how-it-works.md +0 -0
  93. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/configuration/settings-and-api-keys.mdx +0 -0
  94. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/configuration/sign-in-with-chatgpt.mdx +0 -0
  95. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/getting-started/installation.mdx +0 -0
  96. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/getting-started/quick-start.mdx +0 -0
  97. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/gigacode.mdx +0 -0
  98. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/hooks.md +0 -0
  99. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/index.mdx +0 -0
  100. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/skills.mdx +0 -0
  101. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/tui/composer.md +0 -0
  102. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/tui/interface.md +0 -0
  103. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/tui/sessions-and-resume.md +0 -0
  104. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content/docs/tui/slash-commands.md +0 -0
  105. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/content.config.ts +0 -0
  106. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/src/styles/brand.css +0 -0
  107. {kolega_code-0.7.2 → kolega_code-0.8.0}/docs/tsconfig.json +0 -0
  108. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/browseragent.py +0 -0
  109. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/coder.py +0 -0
  110. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/common.py +0 -0
  111. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/compression.py +0 -0
  112. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/context.py +0 -0
  113. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/generalagent.py +0 -0
  114. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/orchestration/__init__.py +0 -0
  115. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/orchestration/budget.py +0 -0
  116. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/orchestration/errors.py +0 -0
  117. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/orchestration/executor.py +0 -0
  118. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/orchestration/guide.py +0 -0
  119. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/orchestration/journal.py +0 -0
  120. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/orchestration/runtime.py +0 -0
  121. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/orchestration/tests/__init__.py +0 -0
  122. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/orchestration/tests/test_orchestration.py +0 -0
  123. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/orchestration/types.py +0 -0
  124. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_provider.py +0 -0
  125. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/auxiliary/compression/summary.system.md +0 -0
  126. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/auxiliary/compression/summary.user.md.j2 +0 -0
  127. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/auxiliary/terminal/output_summary.system.md +0 -0
  128. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/auxiliary/terminal/safety.system.md +0 -0
  129. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/auxiliary/tools/think_hard.system.md +0 -0
  130. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/extensions/cli/planning_questions.md +0 -0
  131. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/extensions/cli/shared_task_list.md +0 -0
  132. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/extensions/skills/catalog.md.j2 +0 -0
  133. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/agents/browser.md.j2 +0 -0
  134. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/agents/coder_cli.md.j2 +0 -0
  135. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/agents/general.md.j2 +0 -0
  136. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/includes/agents_md_instructions.md +0 -0
  137. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/includes/environment_variables/workspace_env_vars.md +0 -0
  138. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/includes/frontend_guidance.md +0 -0
  139. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/includes/starter_templates/expo.md +0 -0
  140. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/includes/starter_templates/html_website.md +0 -0
  141. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/includes/starter_templates/mern_stack.md +0 -0
  142. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/includes/starter_templates/react_vite_shadcdn.md +0 -0
  143. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/user_tasks/cli/implement_plan.md.j2 +0 -0
  144. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/user_tasks/cli/init_agents.md.j2 +0 -0
  145. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/prompts.py +0 -0
  146. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/__init__.py +0 -0
  147. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/compaction_helpers.py +0 -0
  148. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/__init__.py +0 -0
  149. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_anthropic_token_counting.py +0 -0
  150. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_billing_openai_cache.py +0 -0
  151. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_chatgpt_live.py +0 -0
  152. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_dashscope_mapping.py +0 -0
  153. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_error_boundary.py +0 -0
  154. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_exceptions.py +0 -0
  155. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_google_thought_signature.py +0 -0
  156. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_google_tool_signature_live.py +0 -0
  157. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_instrumented_client.py +0 -0
  158. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_instrumented_client_integration.py +0 -0
  159. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_live_providers.py +0 -0
  160. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_openai_cached_tokens.py +0 -0
  161. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_openai_cached_tokens_stream.py +0 -0
  162. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_tool_execution_ids.py +0 -0
  163. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/services/__init__.py +0 -0
  164. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/services/test_browser.py +0 -0
  165. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/services/test_browser_parity.py +0 -0
  166. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/services/test_file_system.py +0 -0
  167. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/services/test_sandbox_terminal_input.py +0 -0
  168. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/services/test_terminal.py +0 -0
  169. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/services/test_terminal_buffer.py +0 -0
  170. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/services/test_terminal_state_serializer.py +0 -0
  171. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_coder_prompt_extensions.py +0 -0
  172. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_commands.py +0 -0
  173. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_compaction_integration.py +0 -0
  174. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_compaction_subagent.py +0 -0
  175. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_compaction_tui_events.py +0 -0
  176. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_compress_command.py +0 -0
  177. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_compression.py +0 -0
  178. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_duplicate_tool_results.py +0 -0
  179. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_empty_message_handling.py +0 -0
  180. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_event_emitter.py +0 -0
  181. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_general_agent.py +0 -0
  182. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_gigacode_gating.py +0 -0
  183. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_hooks_integration.py +0 -0
  184. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_html.py +0 -0
  185. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_parallel_tool_calls.py +0 -0
  186. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_permissions.py +0 -0
  187. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/test_prompt_provider.py +0 -0
  188. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/__init__.py +0 -0
  189. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_agent_tool.py +0 -0
  190. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_base_tool.py +0 -0
  191. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_browser_tool.py +0 -0
  192. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_build_tool.py +0 -0
  193. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_create_file_tool.py +0 -0
  194. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_edit_preview.py +0 -0
  195. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_glob_tool.py +0 -0
  196. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_glob_tool_sandbox_parity.py +0 -0
  197. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_list_directory_tool.py +0 -0
  198. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_read_file_tool.py +0 -0
  199. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_replace_entire_file_tool.py +0 -0
  200. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_replace_lines_tool.py +0 -0
  201. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_search_and_replace_tool.py +0 -0
  202. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_search_codebase_tool.py +0 -0
  203. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_terminal_tool.py +0 -0
  204. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_think_hard_integration.py +0 -0
  205. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_think_hard_streaming.py +0 -0
  206. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_web_fetch_tool.py +0 -0
  207. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_web_search_tool.py +0 -0
  208. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_workflow_tool.py +0 -0
  209. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/agent_tool.py +0 -0
  210. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/base_tool.py +0 -0
  211. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/browser_tool.py +0 -0
  212. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/build_tool.py +0 -0
  213. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/create_file_tool.py +0 -0
  214. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/edit_preview.py +0 -0
  215. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/glob_tool.py +0 -0
  216. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/list_directory_tool.py +0 -0
  217. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/memory_tool.py +0 -0
  218. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/read_file_tool.py +0 -0
  219. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/replace_entire_file_tool.py +0 -0
  220. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/replace_lines_tool.py +0 -0
  221. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_and_replace_tool.py +0 -0
  222. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_backends/__init__.py +0 -0
  223. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_backends/base.py +0 -0
  224. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_backends/duckduckgo.py +0 -0
  225. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_backends/errors.py +0 -0
  226. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_backends/firecrawl.py +0 -0
  227. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_backends/models.py +0 -0
  228. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_backends/searxng.py +0 -0
  229. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_backends/tavily.py +0 -0
  230. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_codebase_tool.py +0 -0
  231. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/streaming_tool.py +0 -0
  232. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/terminal_tool.py +0 -0
  233. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/think_hard_tool.py +0 -0
  234. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/web_fetch_tool.py +0 -0
  235. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/web_search_tool.py +0 -0
  236. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/agent/utils/commands.py +0 -0
  237. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/auth/__init__.py +0 -0
  238. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/auth/_jwt.py +0 -0
  239. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/auth/chatgpt_oauth.py +0 -0
  240. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/auth/constants.py +0 -0
  241. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/auth/tests/__init__.py +0 -0
  242. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/auth/tests/test_chatgpt_oauth.py +0 -0
  243. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/auth/tests/test_tokens.py +0 -0
  244. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/auth/tokens.py +0 -0
  245. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/__init__.py +0 -0
  246. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/config.py +0 -0
  247. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/connection.py +0 -0
  248. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/file_index.py +0 -0
  249. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/settings.py +0 -0
  250. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/skills.py +0 -0
  251. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/tests/__init__.py +0 -0
  252. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/tests/test_cli_config.py +0 -0
  253. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/tests/test_connection.py +0 -0
  254. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/tests/test_file_index.py +0 -0
  255. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/tests/test_main.py +0 -0
  256. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/tests/test_settings.py +0 -0
  257. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/tests/test_skills.py +0 -0
  258. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/tests/test_slash_commands.py +0 -0
  259. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/tests/test_themes.py +0 -0
  260. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/tests/test_updater.py +0 -0
  261. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/theme.py +0 -0
  262. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/cli/updater.py +0 -0
  263. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/config.py +0 -0
  264. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/events.py +0 -0
  265. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/hooks/__init__.py +0 -0
  266. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/hooks/backends.py +0 -0
  267. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/hooks/config.py +0 -0
  268. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/hooks/dispatcher.py +0 -0
  269. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/hooks/events.py +0 -0
  270. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/hooks/matcher.py +0 -0
  271. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/hooks/outcome.py +0 -0
  272. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/hooks/tests/__init__.py +0 -0
  273. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/hooks/tests/test_hooks.py +0 -0
  274. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/llm/client.py +0 -0
  275. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/llm/exceptions.py +0 -0
  276. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/llm/providers/__init__.py +0 -0
  277. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/llm/providers/base.py +0 -0
  278. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/llm/providers/google.py +0 -0
  279. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/llm/providers/models.py +0 -0
  280. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/llm/ratelimit.py +0 -0
  281. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/llm/tool_execution_ids.py +0 -0
  282. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/models/__init__.py +0 -0
  283. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/models/sandbox_terminal_state.py +0 -0
  284. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/permissions.py +0 -0
  285. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/runtime.py +0 -0
  286. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/sandbox/README.md +0 -0
  287. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/sandbox/__init__.py +0 -0
  288. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/sandbox/async_filesystem.py +0 -0
  289. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/sandbox/base.py +0 -0
  290. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/sandbox/browser.py +0 -0
  291. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/sandbox/event_loop.py +0 -0
  292. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/sandbox/filesystem.py +0 -0
  293. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/sandbox/local.py +0 -0
  294. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/sandbox/serializer.py +0 -0
  295. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/sandbox/terminal.py +0 -0
  296. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/sandbox/utils.py +0 -0
  297. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/services/base.py +0 -0
  298. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/services/browser.py +0 -0
  299. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/services/file_system.py +0 -0
  300. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/services/html.py +0 -0
  301. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/services/terminal.py +0 -0
  302. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/services/terminal_buffer.py +0 -0
  303. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/tools/__init__.py +0 -0
  304. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/tools/core.py +0 -0
  305. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/tools/definitions.py +0 -0
  306. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/tools/registry.py +0 -0
  307. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/tools/tests/__init__.py +0 -0
  308. {kolega_code-0.7.2 → kolega_code-0.8.0}/kolega_code/tools/tests/test_definitions.py +0 -0
  309. {kolega_code-0.7.2 → kolega_code-0.8.0}/run_tests.sh +0 -0
  310. {kolega_code-0.7.2 → kolega_code-0.8.0}/scripts/install-kolega-code.sh +0 -0
@@ -7,6 +7,7 @@ OPENAI_API_KEY=
7
7
  # (no key needed). Optional override of the OAuth client id, for testing only:
8
8
  # KOLEGA_CODE_CHATGPT_CLIENT_ID=
9
9
  GOOGLE_API_KEY=
10
+ FIREWORKS_API_KEY=
10
11
  MOONSHOT_API_KEY=
11
12
  DEEPSEEK_API_KEY=
12
13
  ZAI_API_KEY=
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kolega-code
3
- Version: 0.7.2
3
+ Version: 0.8.0
4
4
  Summary: Local-first AI coding agent for the terminal
5
5
  Project-URL: Homepage, https://kolega-ai.github.io/kolega-code/
6
6
  Project-URL: Documentation, https://kolega-ai.github.io/kolega-code/
@@ -54,6 +54,7 @@ Requires-Dist: nest-asyncio==1.6.0
54
54
  Requires-Dist: openai==1.109.1
55
55
  Requires-Dist: packaging==25.0
56
56
  Requires-Dist: pathspec==1.1.1
57
+ Requires-Dist: pillow==12.2.0
57
58
  Requires-Dist: playwright==1.59.0
58
59
  Requires-Dist: psutil==7.2.2
59
60
  Requires-Dist: pydantic==2.13.4
@@ -12,10 +12,10 @@ agent can hand off to the others when a task benefits from focus.
12
12
  | Agent | Specialty | Can edit files? |
13
13
  | --- | --- | --- |
14
14
  | **Coder** | The main, general-purpose coding agent. Full toolset. | Yes |
15
- | **Investigation** | Read-only exploration of the codebase. | No |
15
+ | **Investigation** | Read-only exploration of the codebase. Can run investigative commands. | No |
16
16
  | **Browser** | Web navigation and interaction via Playwright. | No (browser tools only) |
17
17
  | **General** | A flexible agent that can read and dispatch other agents. | Limited |
18
- | **Planning** | Drives [Plan mode](../../tui/modes/): investigates and writes a plan, read-only. | No |
18
+ | **Planning** | Drives [Plan mode](../../tui/modes/): investigates and writes a plan. Can run commands but cannot edit files. | No |
19
19
 
20
20
  ## Dispatching sub-agents
21
21
 
@@ -61,13 +61,16 @@ and `dispatch_general_agent`.
61
61
  ## Read-only vs. full access
62
62
 
63
63
  Tools are gated by mode. In a read-only context — like [Plan mode](../../tui/modes/)
64
- or an investigation sub-agent — only non-mutating tools are available
64
+ or an investigation sub-agent — the agent can read and search the codebase
65
65
  (`list_directory`, `read_entire_file`, `read_file_section`, `search_codebase`,
66
66
  `find_files_by_pattern`, `web_search`, `web_fetch`, `think_hard`, and reading
67
- memory). Editing files and running commands require Build mode's full toolset.
67
+ memory) **and** run shell commands to investigate. Editing files still requires
68
+ Build mode's full toolset.
68
69
 
69
- This separation is what makes Plan mode safe to run against any codebase: the
70
- planning agent can look but not touch.
70
+ This separation is what keeps Plan mode safe to run against any codebase: the
71
+ planning agent can look and run investigative commands, but it has no file-edit
72
+ tools. Shell commands are further gated by the active permission mode — in `ask`
73
+ they prompt before running.
71
74
 
72
75
  In the Textual TUI, Build mode defaults to `ask` permission mode. Shell commands
73
76
  and file edits must be approved before they run unless you switch to `auto` or
@@ -118,6 +118,7 @@ starting point:
118
118
  # Pick one provider's key (or several)
119
119
  MOONSHOT_API_KEY=
120
120
  DEEPSEEK_API_KEY=
121
+ FIREWORKS_API_KEY=
121
122
  ANTHROPIC_API_KEY=
122
123
 
123
124
  # Optional: choose models per role
@@ -125,6 +126,10 @@ KOLEGA_CODE_PROVIDER=moonshot
125
126
  KOLEGA_CODE_MODEL=kimi-k2.7-code
126
127
  KOLEGA_CODE_THINKING_EFFORT=auto
127
128
 
129
+ # Fireworks example:
130
+ # KOLEGA_CODE_PROVIDER=fireworks
131
+ # KOLEGA_CODE_MODEL=accounts/fireworks/models/glm-5p2
132
+
128
133
  # Optional: web search
129
134
  KOLEGA_CODE_WEB_SEARCH_BACKEND=duckduckgo
130
135
 
@@ -45,11 +45,21 @@ with thinking effort `auto` (enabled) requests route to **K2.7 Code**, and with
45
45
  (disabled) they route to **K2.6**. Set `KIMI_CODING_API_KEY` to your Kimi Coding Plan key.
46
46
  </Aside>
47
47
 
48
+ <Aside type="tip">
49
+ **Fireworks.ai.** The `fireworks` provider targets Fireworks' OpenAI-compatible
50
+ serverless endpoint (`https://api.fireworks.ai/inference/v1`) with `FIREWORKS_API_KEY`.
51
+ Fireworks model IDs use resource names such as `accounts/fireworks/models/glm-5p2`.
52
+ Kolega Code exposes GLM-5.2, GLM-5.1, Kimi K2.7 Code, DeepSeek V4 Pro/Flash,
53
+ MiniMax M3, and Qwen 3.7 Plus through the Settings UI and CLI. Fireworks reasoning
54
+ models return `reasoning_content`; Kolega Code exposes `none`, `low`, `medium`,
55
+ `high`, and `max` as `reasoning_effort` values for these models.
56
+ </Aside>
57
+
48
58
  <Aside type="note">
49
- The **Settings tab** in the TUI currently offers a curated shortlist
50
- **Moonshot (Kimi K2.7 Code and Kimi K2.6)** and **DeepSeek (DeepSeek V4 Pro)**.
51
- The full provider list above is available through environment variables and CLI
52
- flags. See [Settings & API Keys](../settings-and-api-keys/).
59
+ The **Settings tab** in the TUI is generated from Kolega Code's model registry, so
60
+ new entries in the supported model catalog appear there automatically. You can also
61
+ select any supported provider/model through environment variables and CLI flags.
62
+ See [Settings & API Keys](../settings-and-api-keys/).
53
63
  </Aside>
54
64
 
55
65
  ## Model roles
@@ -153,6 +163,7 @@ models in the TUI.
153
163
  | Moonshot `kimi-k2.6` | `auto`, `none` | `auto` |
154
164
  | Z.AI `glm-5.2` | `high`, `max` | `max` |
155
165
  | Z.AI `glm-5.1` | `auto`, `none` | `auto` |
166
+ | Fireworks serverless reasoning models | `none`, `low`, `medium`, `high`, `max` | `medium` |
156
167
  | Google `gemini-2.5-pro` | `auto`, `low`, `medium`, `high` | `medium` |
157
168
  | OpenAI reasoning models | `low`, `medium`, `high` | `medium` |
158
169
 
@@ -35,6 +35,8 @@ produces a plan.
35
35
  While planning, the agent can:
36
36
 
37
37
  - Read and search the codebase.
38
+ - Run shell commands to investigate (e.g. `git log`, `grep`, running tests) —
39
+ subject to the current permission mode, so commands prompt for approval in `ask`.
38
40
  - Maintain a shared **task list** (visible in the Planning tab).
39
41
  - Write a structured plan, which appears in the **Planning** tab.
40
42
 
@@ -148,4 +148,4 @@ __all__ = [
148
148
  "parse_git_status_output",
149
149
  ]
150
150
 
151
- __version__ = "0.7.2"
151
+ __version__ = "0.8.0"
@@ -39,4 +39,4 @@ __all__ = [
39
39
  "ToolExtension",
40
40
  ]
41
41
 
42
- __version__ = "0.7.2"
42
+ __version__ = "0.8.0"
@@ -15,7 +15,7 @@ from .compression import CompactionResult, HistoryCompressor
15
15
  from kolega_code.config import AgentConfig, ModelProvider
16
16
  from kolega_code.events import AgentConnectionManager
17
17
  from .context import AgentContext, AgentServices, Telemetry, WorkspaceInfo
18
- from .conversation import Conversation
18
+ from .conversation import Conversation, adapt_history_for_provider
19
19
  from kolega_code.events import AgentEventEmitter
20
20
  from kolega_code.hooks import (
21
21
  NO_OP_DISPATCHER,
@@ -34,7 +34,7 @@ from kolega_code.llm.exceptions import (
34
34
  )
35
35
  from kolega_code.llm.models import ImageBlock, Message, MessageHistory, TextBlock, ToolCall, ToolResult
36
36
  from kolega_code.llm.providers.models import TokenCount
37
- from kolega_code.llm.specs import get_model_specs
37
+ from kolega_code.llm.specs import get_model_specs, supports_vision as model_supports_vision
38
38
  from kolega_code.permissions import (
39
39
  PermissionDecision,
40
40
  PermissionMode,
@@ -90,10 +90,6 @@ class BaseAgent(LogMixin):
90
90
  long_content_tool_calls = ["create_file", "replace_entire_file"]
91
91
  max_tool_result_chars_in_history = 100_000
92
92
  skill_content_pattern = re.compile(r'<skill_content name="[^"]+">')
93
- deepseek_image_unsupported_message = (
94
- "DeepSeek V4 Pro does not support image input via the DeepSeek API. "
95
- "Remove the image or switch to a vision-capable model for this request."
96
- )
97
93
 
98
94
  def __init__(
99
95
  self,
@@ -270,6 +266,11 @@ class BaseAgent(LogMixin):
270
266
  self.model_context_length = model_specs["context_length"]
271
267
  self.model_completion_tokens = model_specs["max_completion_tokens"]
272
268
  self.model_default_temperature = model_specs.get("default_temperature", 1.0)
269
+ # Whether this agent's primary model can accept image input. Read by the
270
+ # ToolCollection read_image tool gate (so non-vision models never see the
271
+ # tool) and used by _unsupported_attachment_message to reject image
272
+ # attachments for non-vision models with a clear message.
273
+ self.supports_vision = bool(model_specs.get("supports_vision", False))
273
274
 
274
275
  self.llm = context.create_llm_client(agent_name=self.agent_name)
275
276
 
@@ -365,6 +366,26 @@ class BaseAgent(LogMixin):
365
366
  """
366
367
  return self.conversation.repaired(messages)
367
368
 
369
+ def _history_for_llm(self) -> MessageHistory:
370
+ """Build the message history to send to the LLM for this turn.
371
+
372
+ Compaction-aware and tool-call-repaired. For non-vision models, any
373
+ ``ImageBlock`` carried over from earlier turns (user attachments or
374
+ ``read_image`` tool results) is replaced with a text placeholder on this
375
+ request copy only — the stored history is never mutated, so switching
376
+ back to a vision-capable model restores the images.
377
+ """
378
+ effective = self.get_effective_history_for_llm()
379
+ fixed = self.fix_incomplete_tool_calls(list(effective))
380
+ provider = getattr(self.primary_model_config.provider, "value", self.primary_model_config.provider)
381
+ fixed = adapt_history_for_provider(
382
+ fixed,
383
+ target_provider=str(provider),
384
+ target_model=self.primary_model_config.model,
385
+ supports_vision=self.supports_vision,
386
+ )
387
+ return MessageHistory(fixed)
388
+
368
389
  def mark_cache_checkpoint(self) -> None:
369
390
  """
370
391
  Mark the last message in history for caching and remove cache_control from all other messages.
@@ -480,18 +501,22 @@ class BaseAgent(LogMixin):
480
501
  # ------------------------------------------------------------------
481
502
 
482
503
  def _unsupported_attachment_message(self, attachments: Optional[List[Dict[str, Any]]]) -> Optional[str]:
504
+ if not any(attachment.get("type") == "image" for attachment in attachments or []):
505
+ return None
506
+
483
507
  provider = getattr(
484
508
  self.primary_model_config.provider,
485
509
  "value",
486
510
  self.primary_model_config.provider,
487
511
  )
488
- if provider != ModelProvider.DEEPSEEK.value:
512
+ if model_supports_vision(provider, self.primary_model_config.model):
489
513
  return None
490
514
 
491
- if any(attachment.get("type") == "image" for attachment in attachments or []):
492
- return self.deepseek_image_unsupported_message
493
-
494
- return None
515
+ return (
516
+ f"{self.primary_model_config.model} does not support image input. "
517
+ "Your message was not sent to the model. "
518
+ "Remove the image attachment or switch to a vision-capable model with /model."
519
+ )
495
520
 
496
521
  def _attachment_blocks(self, attachments: Optional[List[Dict[str, Any]]]) -> List[Any]:
497
522
  """Convert attachment payloads into content blocks for a user message."""
@@ -518,9 +543,9 @@ class BaseAgent(LogMixin):
518
543
 
519
544
  async def count_current_context(self) -> TokenCount:
520
545
  self._sanitize_oversized_tool_results()
521
- # Fix history before counting to get accurate count for what LLM will see
522
- effective = self.get_effective_history_for_llm()
523
- fixed_history = MessageHistory(self.fix_incomplete_tool_calls(list(effective)))
546
+ # History sent to the LLM (and to token counting): tool-call-repaired and,
547
+ # for non-vision models, stripped of image blocks from earlier turns.
548
+ fixed_history = self._history_for_llm()
524
549
  token_count = await self.llm.count_tokens(
525
550
  system=self.system_prompt,
526
551
  messages=fixed_history,
@@ -1304,9 +1329,9 @@ class BaseAgent(LogMixin):
1304
1329
  response_uuid = str(uuid.uuid4())
1305
1330
  thinking_uuid = str(uuid.uuid4())
1306
1331
 
1307
- # Fix history before sending to LLM to ensure valid tool call sequences
1308
- effective = self.get_effective_history_for_llm()
1309
- fixed_history = MessageHistory(self.fix_incomplete_tool_calls(list(effective)))
1332
+ # History sent to the LLM: tool-call-repaired and, for non-vision
1333
+ # models, stripped of image blocks carried over from earlier turns.
1334
+ fixed_history = self._history_for_llm()
1310
1335
 
1311
1336
  async with await self.llm.stream(
1312
1337
  system=self.system_prompt,
@@ -11,11 +11,192 @@ import logging
11
11
  import re
12
12
  from typing import Any, Dict, List, Optional
13
13
 
14
- from kolega_code.llm.models import Message, MessageHistory, TextBlock, ToolCall, ToolResult
14
+ from kolega_code.llm.models import (
15
+ ImageBlock,
16
+ Message,
17
+ MessageHistory,
18
+ RedactedThinkingBlock,
19
+ TextBlock,
20
+ ThinkingBlock,
21
+ ToolCall,
22
+ ToolResult,
23
+ )
15
24
 
16
25
  logger = logging.getLogger(__name__)
17
26
 
18
27
 
28
+ def _image_placeholder(media_type: str, model_name: str) -> str:
29
+ """Text substituted for an image block when the active model can't see images."""
30
+ return f"[An image ({media_type}) was shared earlier in this thread but is not visible to {model_name}.]"
31
+
32
+
33
+ def count_image_blocks(messages: List[Message]) -> int:
34
+ """Count ``ImageBlock`` instances across messages, including ones nested in tool results."""
35
+ count = 0
36
+ for message in messages:
37
+ if not isinstance(message.content, list):
38
+ continue
39
+ for block in message.content:
40
+ if isinstance(block, ImageBlock):
41
+ count += 1
42
+ elif isinstance(block, ToolResult) and isinstance(block.content, list):
43
+ count += sum(1 for inner in block.content if isinstance(inner, ImageBlock))
44
+ return count
45
+
46
+
47
+ def _provider_value(provider: Any) -> str:
48
+ value = getattr(provider, "value", provider)
49
+ return str(value or "")
50
+
51
+
52
+ def _message_with_content(message: Message, content: List[Any]) -> Message:
53
+ return Message(
54
+ role=message.role,
55
+ content=content,
56
+ stop_reason=message.stop_reason,
57
+ tool_calls=message.tool_calls,
58
+ usage_metadata=message.usage_metadata,
59
+ )
60
+
61
+
62
+ def _tool_result_with_content(tool_result: ToolResult, content: List[Any]) -> ToolResult:
63
+ return ToolResult(
64
+ tool_use_id=tool_result.tool_use_id,
65
+ content=content,
66
+ name=tool_result.name,
67
+ is_error=tool_result.is_error,
68
+ cache_checkpoint=tool_result.cache_checkpoint,
69
+ execution_id=tool_result.execution_id,
70
+ )
71
+
72
+
73
+ def replace_image_blocks_with_placeholders(messages: List[Message], model_name: str) -> List[Message]:
74
+ """Return a new message list with every ``ImageBlock`` replaced by a text placeholder.
75
+
76
+ Non-mutating: messages without images are returned as-is. When a replacement
77
+ occurs, new ``Message``/``ToolResult`` objects are built so the caller's stored
78
+ history is never altered — switching back to a vision-capable model restores
79
+ the original images. ``ImageBlock``s nested inside a ``ToolResult.content`` list
80
+ (e.g. from the ``read_image`` tool) are handled too.
81
+ """
82
+ result: List[Message] = []
83
+ for message in messages:
84
+ if not isinstance(message.content, list):
85
+ result.append(message)
86
+ continue
87
+ new_blocks: List[Any] = []
88
+ changed = False
89
+ for block in message.content:
90
+ if isinstance(block, ImageBlock):
91
+ new_blocks.append(TextBlock(text=_image_placeholder(block.media_type, model_name)))
92
+ changed = True
93
+ elif isinstance(block, ToolResult) and isinstance(block.content, list):
94
+ inner_new: List[Any] = []
95
+ inner_changed = False
96
+ for inner in block.content:
97
+ if isinstance(inner, ImageBlock):
98
+ inner_new.append(TextBlock(text=_image_placeholder(inner.media_type, model_name)))
99
+ inner_changed = True
100
+ else:
101
+ inner_new.append(inner)
102
+ if inner_changed:
103
+ new_blocks.append(_tool_result_with_content(block, inner_new))
104
+ changed = True
105
+ else:
106
+ new_blocks.append(block)
107
+ else:
108
+ new_blocks.append(block)
109
+ if changed:
110
+ result.append(_message_with_content(message, new_blocks))
111
+ else:
112
+ result.append(message)
113
+ return result
114
+
115
+
116
+ def _preserve_reasoning_block(block: Any, *, source_provider: str, target_provider: str) -> bool:
117
+ return bool(source_provider) and source_provider == target_provider
118
+
119
+
120
+ def _reasoning_placeholder(block: Any, source_provider: str) -> TextBlock:
121
+ source = source_provider or "unknown provider"
122
+ if isinstance(block, RedactedThinkingBlock):
123
+ return TextBlock(text=f"[Prior redacted reasoning from {source} omitted for compatibility.]")
124
+ return TextBlock(text=f"[Prior reasoning from {source} omitted for compatibility.]")
125
+
126
+
127
+ def _adapt_content_blocks_for_provider(
128
+ blocks: List[Any], *, source_provider: str, target_provider: str, target_model: str, supports_vision: bool
129
+ ) -> tuple[List[Any], bool]:
130
+ adapted: List[Any] = []
131
+ changed = False
132
+
133
+ for block in blocks:
134
+ if isinstance(block, ImageBlock) and not supports_vision:
135
+ adapted.append(TextBlock(text=_image_placeholder(block.media_type, target_model)))
136
+ changed = True
137
+ elif isinstance(block, (ThinkingBlock, RedactedThinkingBlock)):
138
+ if _preserve_reasoning_block(block, source_provider=source_provider, target_provider=target_provider):
139
+ adapted.append(block)
140
+ else:
141
+ adapted.append(_reasoning_placeholder(block, source_provider))
142
+ changed = True
143
+ elif isinstance(block, ToolResult) and isinstance(block.content, list):
144
+ inner, inner_changed = _adapt_content_blocks_for_provider(
145
+ block.content,
146
+ source_provider=source_provider,
147
+ target_provider=target_provider,
148
+ target_model=target_model,
149
+ supports_vision=supports_vision,
150
+ )
151
+ if inner_changed:
152
+ adapted.append(_tool_result_with_content(block, inner))
153
+ changed = True
154
+ else:
155
+ adapted.append(block)
156
+ else:
157
+ adapted.append(block)
158
+
159
+ return adapted, changed
160
+
161
+
162
+ def adapt_history_for_provider(
163
+ messages: List[Message], *, target_provider: str, target_model: str, supports_vision: bool
164
+ ) -> List[Message]:
165
+ """Return a request-safe history for the target provider without mutating storage.
166
+
167
+ Provider-managed reasoning blocks are not portable across providers, but
168
+ reasoning produced by a provider is safe to send back to the same provider.
169
+ Preserve same-provider reasoning; convert foreign or unknown reasoning
170
+ blocks to text placeholders. Image blocks are preserved for vision models
171
+ and replaced with placeholders for non-vision models, matching the previous
172
+ image compatibility behavior.
173
+ """
174
+ target_provider = _provider_value(target_provider)
175
+ result: List[Message] = []
176
+ changed_any = False
177
+
178
+ for message in messages:
179
+ if not isinstance(message.content, list):
180
+ result.append(message)
181
+ continue
182
+
183
+ source_provider = _provider_value((message.usage_metadata or {}).get("provider"))
184
+ adapted_content, changed = _adapt_content_blocks_for_provider(
185
+ message.content,
186
+ source_provider=source_provider,
187
+ target_provider=target_provider,
188
+ target_model=target_model,
189
+ supports_vision=supports_vision,
190
+ )
191
+ if changed:
192
+ result.append(_message_with_content(message, adapted_content))
193
+ changed_any = True
194
+ else:
195
+ result.append(message)
196
+
197
+ return result if changed_any else messages
198
+
199
+
19
200
  class Conversation:
20
201
  """Message history plus the invariants that keep it valid for providers."""
21
202
 
@@ -31,8 +212,11 @@ class Conversation:
31
212
  # Compaction state: ``summary`` stands in for ``history[:compacted_through]``
32
213
  # in the effective view; messages from ``compacted_through`` onward are kept
33
214
  # verbatim. ``summary is None`` / ``compacted_through == 0`` means uncompacted.
215
+ # ``compacted_history_length`` is the history length captured when compaction
216
+ # ran, so the UI can place the summary after the retained tail on restore.
34
217
  self.summary: Optional[Message] = None
35
218
  self.compacted_through: int = 0
219
+ self.compacted_history_length: int = 0
36
220
  self.max_tool_result_chars = max_tool_result_chars
37
221
 
38
222
  # ------------------------------------------------------------------
@@ -51,6 +235,7 @@ class Conversation:
51
235
  self._history = value if isinstance(value, MessageHistory) else MessageHistory(list(value))
52
236
  self.summary = None
53
237
  self.compacted_through = 0
238
+ self.compacted_history_length = 0
54
239
 
55
240
  @property
56
241
  def last_compression_index(self) -> Optional[int]:
@@ -66,6 +251,7 @@ class Conversation:
66
251
  if value is None:
67
252
  self.summary = None
68
253
  self.compacted_through = 0
254
+ self.compacted_history_length = 0
69
255
  else:
70
256
  self.compacted_through = value + 1
71
257
 
@@ -74,6 +260,15 @@ class Conversation:
74
260
  self._history = MessageHistory([])
75
261
  self.summary = None
76
262
  self.compacted_through = 0
263
+ self.compacted_history_length = 0
264
+
265
+ def has_image_blocks(self) -> bool:
266
+ """True if the effective (compaction-aware) history still carries any images.
267
+
268
+ Images folded into a compaction summary are already gone, so this reflects
269
+ only what would actually be sent to the model.
270
+ """
271
+ return count_image_blocks(list(self.effective_history())) > 0
77
272
 
78
273
  # ------------------------------------------------------------------
79
274
  # Appending
@@ -439,6 +634,7 @@ class Conversation:
439
634
  summary if isinstance(summary, Message) else Message(role="user", content=[TextBlock(text=str(summary))])
440
635
  )
441
636
  self.compacted_through = len(self._history)
637
+ self.compacted_history_length = len(self._history)
442
638
 
443
639
  def apply_compaction(self, summary_text: str, split_point: int) -> None:
444
640
  """Record ``summary_text`` as standing in for ``history[:split_point]``.
@@ -448,6 +644,7 @@ class Conversation:
448
644
  """
449
645
  self.summary = Message(role="user", content=[TextBlock(text=summary_text)])
450
646
  self.compacted_through = max(0, min(split_point, len(self._history)))
647
+ self.compacted_history_length = len(self._history)
451
648
 
452
649
  def compaction_split_point(self, *, keep_recent: int, min_prefix: int) -> Optional[int]:
453
650
  """Index where the verbatim recent tail should begin.
@@ -500,13 +697,15 @@ class Conversation:
500
697
  return {
501
698
  "summary": self.summary.get_text_content() if self.summary is not None else "",
502
699
  "compacted_through": self.compacted_through,
700
+ "compacted_history_length": self.compacted_history_length,
503
701
  }
504
702
 
505
703
  def restore_compaction(self, data: Optional[Dict[str, Any]]) -> None:
506
704
  """Restore a compaction boundary saved by ``dump_compaction``.
507
705
 
508
706
  Call AFTER ``restore`` (which resets compaction). Assigns the boundary
509
- fields directly so it does not trip the history setter's reset.
707
+ fields directly so it does not trip the history setter's reset. Old
708
+ sessions without ``compacted_history_length`` fall back to 0.
510
709
  """
511
710
  data = data or {}
512
711
  text = (data.get("summary") or "").strip()
@@ -514,6 +713,8 @@ class Conversation:
514
713
  if text and through > 0:
515
714
  self.summary = Message(role="user", content=[TextBlock(text=text)])
516
715
  self.compacted_through = min(through, len(self._history))
716
+ self.compacted_history_length = int(data.get("compacted_history_length") or 0)
517
717
  else:
518
718
  self.summary = None
519
719
  self.compacted_through = 0
720
+ self.compacted_history_length = 0
@@ -6,7 +6,7 @@ from kolega_code.config import AgentConfig
6
6
  from kolega_code.events import AgentConnectionManager
7
7
  from kolega_code.llm.models import Message, TextBlock
8
8
  from .prompt_provider import AgentMode, AgentType, PromptExtension
9
- from .tools import ToolCollection
9
+ from .tools import ToolCollection, ToolCollectionConfig
10
10
 
11
11
 
12
12
  class InvestigationAgent(BaseAgent):
@@ -106,7 +106,7 @@ class InvestigationAgent(BaseAgent):
106
106
  self.connection_manager,
107
107
  self.config,
108
108
  caller=self,
109
- read_only=True,
109
+ tool_config=ToolCollectionConfig(read_only=True, custom_tool_groups=["command_tools"]),
110
110
  filesystem=self.filesystem,
111
111
  terminal_manager=self.terminal_manager,
112
112
  browser_manager=self.browser_manager,
@@ -92,7 +92,9 @@ class PlanningAgent(BaseAgent):
92
92
  self.connection_manager,
93
93
  self.config,
94
94
  caller=self,
95
- tool_config=ToolCollectionConfig(read_only=True, custom_tool_groups=["planning_tools"]),
95
+ tool_config=ToolCollectionConfig(
96
+ read_only=True, custom_tool_groups=["planning_tools", "command_tools"]
97
+ ),
96
98
  filesystem=self.filesystem,
97
99
  terminal_manager=self.terminal_manager,
98
100
  browser_manager=self.browser_manager,
@@ -46,7 +46,7 @@ to take further action, so your last message should contain the task result.
46
46
 
47
47
  ## **Running Commands**
48
48
 
49
- When requesting a command to be run, you will be asked to judge if it is appropriate to run without the USER's permission. A command is unsafe if it may have some destructive side-effects. Example unsafe side-effects include: deleting files, mutating state, installing system dependencies, making external requests, etc. You must NEVER NEVER run a command automatically if it could be unsafe. You cannot allow the USER to override your judgement on this. If a command is unsafe, do not run it automatically, even if the USER wants you to.
49
+ You may run shell commands (e.g. `git log`, `grep`, build/lint/test commands) to investigate the codebase. Never use commands to modify the project no editing files, installing dependencies, committing, pushing, or other side effects. Prefer read-only tools when they suffice. Commands are subject to the active permission mode and may require approval.
50
50
 
51
51
  ## **Communication Guidelines**
52
52
 
@@ -13,10 +13,13 @@ Here is useful information about the environment:
13
13
 
14
14
  ## Operating Mode
15
15
 
16
- You are in planning mode. Do not implement code changes, create files, edit files, run shell commands, start servers, or perform other mutating actions.
17
- Use read-only tools to inspect the repository and reduce ambiguity. If the host provides shared task-list tools, keep that list current enough that another agent could see what has been considered and what remains.
16
+ You are in planning mode. Do not implement code changes, create files, edit files, start servers, or perform other mutating actions.
17
+ You may run shell commands (e.g. `git log`, `grep`, build/lint/test commands) to investigate the codebase and reduce ambiguity, but never use them to modify the project no editing files, installing dependencies, committing, pushing, or other side effects. Prefer read-only tools when they suffice, and use `exec_command` only to inspect or validate.
18
+ Use read-only tools and investigative commands to inspect the repository and reduce ambiguity. If the host provides shared task-list tools, keep that list current enough that another agent could see what has been considered and what remains.
18
19
 
19
20
  When the plan is decision complete, call `write_plan` with the final markdown plan. Do not call `write_plan` while major product or implementation choices are still unresolved.
21
+ If a planning turn ends with a complete new or revised implementation plan, submit it through `write_plan`; do not only print the plan in a normal assistant response.
22
+ When user answers, constraints, or follow-up discussion change the plan, the next `write_plan` payload must be a complete replacement plan that incorporates those decisions and can be implemented without reading earlier discussion.
20
23
 
21
24
  If an important decision cannot be derived from the repository, ask the user a concise question instead of guessing. Otherwise choose conservative defaults that match the codebase.
22
25