kolega-code 0.7.2__tar.gz → 0.8.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. {kolega_code-0.7.2 → kolega_code-0.8.1}/.env.example +1 -0
  2. {kolega_code-0.7.2 → kolega_code-0.8.1}/PKG-INFO +2 -1
  3. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/concepts/agents.md +2 -2
  4. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/concepts/tools.md +7 -4
  5. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/configuration/environment-variables.md +5 -0
  6. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/configuration/providers-and-models.mdx +15 -4
  7. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/tui/modes.md +2 -0
  8. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/__init__.py +4 -1
  9. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/__init__.py +6 -1
  10. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/baseagent.py +56 -17
  11. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/browseragent.py +2 -0
  12. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/coder.py +2 -0
  13. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/conversation.py +217 -2
  14. kolega_code-0.8.1/kolega_code/agent/errors.py +9 -0
  15. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/generalagent.py +2 -0
  16. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/investigationagent.py +4 -2
  17. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/planningagent.py +5 -1
  18. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/agents/investigation.md.j2 +1 -1
  19. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/agents/planning.md.j2 +5 -2
  20. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_chatgpt_oauth_provider.py +196 -0
  21. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_client.py +143 -0
  22. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_error_boundary.py +3 -2
  23. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_langfuse_normalization.py +17 -0
  24. kolega_code-0.8.1/kolega_code/agent/tests/llm/test_model_specs.py +51 -0
  25. kolega_code-0.8.1/kolega_code/agent/tests/llm/test_openai_message_conversion.py +140 -0
  26. kolega_code-0.8.1/kolega_code/agent/tests/llm/test_openai_responses_provider.py +163 -0
  27. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_openai_token_counting.py +25 -13
  28. kolega_code-0.8.1/kolega_code/agent/tests/llm/test_supports_vision.py +56 -0
  29. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_thinking_effort.py +31 -1
  30. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_agent_tools_inventory.py +18 -2
  31. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_base_agent.py +150 -0
  32. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_coder_attachments.py +175 -12
  33. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compaction_persistence.py +3 -1
  34. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_conversation_compaction.py +32 -1
  35. kolega_code-0.8.1/kolega_code/agent/tests/test_image_compat.py +476 -0
  36. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_planning_agent.py +26 -2
  37. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_prompts.py +2 -0
  38. kolega_code-0.8.1/kolega_code/agent/tests/test_read_image_tool.py +161 -0
  39. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_tool_registry.py +4 -1
  40. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_tools.py +7 -0
  41. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_agent_tool.py +29 -3
  42. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/agent_tool.py +2 -0
  43. kolega_code-0.8.1/kolega_code/agent/tool_backend/read_image_tool.py +35 -0
  44. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/workflow_tool.py +3 -1
  45. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tools.py +42 -0
  46. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/app.py +368 -43
  47. kolega_code-0.8.1/kolega_code/cli/clipboard_image.py +152 -0
  48. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/main.py +17 -0
  49. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/mentions.py +20 -0
  50. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/messages.py +13 -0
  51. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/provider_registry.py +6 -1
  52. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/session_store.py +7 -2
  53. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/slash_commands.py +2 -0
  54. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_app.py +246 -14
  55. kolega_code-0.8.1/kolega_code/cli/tests/test_app_compaction_restore.py +144 -0
  56. kolega_code-0.8.1/kolega_code/cli/tests/test_app_image_vision_warning.py +563 -0
  57. kolega_code-0.8.1/kolega_code/cli/tests/test_app_model_switch_vision.py +214 -0
  58. kolega_code-0.8.1/kolega_code/cli/tests/test_clipboard_image.py +185 -0
  59. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_mentions.py +30 -0
  60. kolega_code-0.8.1/kolega_code/cli/tests/test_provider_registry.py +19 -0
  61. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_session_store.py +24 -0
  62. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/client.py +6 -2
  63. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/instrumented_client.py +3 -3
  64. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/models.py +273 -76
  65. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/providers/anthropic.py +1 -1
  66. kolega_code-0.8.1/kolega_code/llm/providers/chatgpt_oauth.py +103 -0
  67. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/providers/openai.py +33 -8
  68. kolega_code-0.8.1/kolega_code/llm/providers/openai_responses.py +60 -0
  69. kolega_code-0.7.2/kolega_code/llm/providers/chatgpt_oauth.py → kolega_code-0.8.1/kolega_code/llm/providers/responses_common.py +181 -105
  70. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/specs.py +147 -19
  71. kolega_code-0.8.1/kolega_code/utils/__init__.py +1 -0
  72. kolega_code-0.8.1/kolega_code/utils/images.py +140 -0
  73. kolega_code-0.8.1/kolega_code/utils/tests/__init__.py +0 -0
  74. kolega_code-0.8.1/kolega_code/utils/tests/test_images.py +90 -0
  75. kolega_code-0.8.1/kolega_code/utils/tests/test_images_ascii.py +97 -0
  76. {kolega_code-0.7.2 → kolega_code-0.8.1}/pyproject.toml +2 -1
  77. {kolega_code-0.7.2 → kolega_code-0.8.1}/uv.lock +91 -2
  78. kolega_code-0.7.2/kolega_code/agent/tests/llm/test_model_specs.py +0 -31
  79. kolega_code-0.7.2/kolega_code/agent/tests/llm/test_openai_message_conversion.py +0 -30
  80. {kolega_code-0.7.2 → kolega_code-0.8.1}/.github/workflows/ci.yml +0 -0
  81. {kolega_code-0.7.2 → kolega_code-0.8.1}/.github/workflows/docs.yml +0 -0
  82. {kolega_code-0.7.2 → kolega_code-0.8.1}/.github/workflows/release.yml +0 -0
  83. {kolega_code-0.7.2 → kolega_code-0.8.1}/.gitignore +0 -0
  84. {kolega_code-0.7.2 → kolega_code-0.8.1}/CONTRIBUTING.md +0 -0
  85. {kolega_code-0.7.2 → kolega_code-0.8.1}/LICENSE +0 -0
  86. {kolega_code-0.7.2 → kolega_code-0.8.1}/README.md +0 -0
  87. {kolega_code-0.7.2 → kolega_code-0.8.1}/RELEASING.md +0 -0
  88. {kolega_code-0.7.2 → kolega_code-0.8.1}/SECURITY.md +0 -0
  89. {kolega_code-0.7.2 → kolega_code-0.8.1}/conftest.py +0 -0
  90. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/.gitignore +0 -0
  91. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/README.md +0 -0
  92. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/astro.config.mjs +0 -0
  93. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/package-lock.json +0 -0
  94. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/package.json +0 -0
  95. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/public/favicon.svg +0 -0
  96. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/assets/demo.gif +0 -0
  97. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/assets/kolega-dark.svg +0 -0
  98. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/assets/kolega-light.svg +0 -0
  99. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/cli/ask.md +0 -0
  100. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/cli/doctor.md +0 -0
  101. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/cli/overview.md +0 -0
  102. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/cli/sessions.md +0 -0
  103. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/concepts/how-it-works.md +0 -0
  104. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/configuration/settings-and-api-keys.mdx +0 -0
  105. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/configuration/sign-in-with-chatgpt.mdx +0 -0
  106. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/getting-started/installation.mdx +0 -0
  107. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/getting-started/quick-start.mdx +0 -0
  108. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/gigacode.mdx +0 -0
  109. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/hooks.md +0 -0
  110. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/index.mdx +0 -0
  111. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/skills.mdx +0 -0
  112. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/tui/composer.md +0 -0
  113. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/tui/interface.md +0 -0
  114. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/tui/sessions-and-resume.md +0 -0
  115. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/tui/slash-commands.md +0 -0
  116. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content.config.ts +0 -0
  117. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/styles/brand.css +0 -0
  118. {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/tsconfig.json +0 -0
  119. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/common.py +0 -0
  120. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/compression.py +0 -0
  121. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/context.py +0 -0
  122. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/__init__.py +0 -0
  123. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/budget.py +0 -0
  124. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/errors.py +0 -0
  125. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/executor.py +0 -0
  126. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/guide.py +0 -0
  127. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/journal.py +0 -0
  128. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/runtime.py +0 -0
  129. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/tests/__init__.py +0 -0
  130. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/tests/test_orchestration.py +0 -0
  131. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/types.py +0 -0
  132. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_provider.py +0 -0
  133. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/auxiliary/compression/summary.system.md +0 -0
  134. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/auxiliary/compression/summary.user.md.j2 +0 -0
  135. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/auxiliary/terminal/output_summary.system.md +0 -0
  136. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/auxiliary/terminal/safety.system.md +0 -0
  137. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/auxiliary/tools/think_hard.system.md +0 -0
  138. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/extensions/cli/planning_questions.md +0 -0
  139. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/extensions/cli/shared_task_list.md +0 -0
  140. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/extensions/skills/catalog.md.j2 +0 -0
  141. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/agents/browser.md.j2 +0 -0
  142. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/agents/coder_cli.md.j2 +0 -0
  143. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/agents/general.md.j2 +0 -0
  144. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/agents_md_instructions.md +0 -0
  145. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/environment_variables/workspace_env_vars.md +0 -0
  146. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/frontend_guidance.md +0 -0
  147. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/expo.md +0 -0
  148. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/html_website.md +0 -0
  149. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/mern_stack.md +0 -0
  150. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/react_vite_shadcdn.md +0 -0
  151. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/user_tasks/cli/implement_plan.md.j2 +0 -0
  152. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/user_tasks/cli/init_agents.md.j2 +0 -0
  153. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompts.py +0 -0
  154. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/__init__.py +0 -0
  155. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/compaction_helpers.py +0 -0
  156. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/__init__.py +0 -0
  157. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_anthropic_token_counting.py +0 -0
  158. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_billing_openai_cache.py +0 -0
  159. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_chatgpt_live.py +0 -0
  160. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_dashscope_mapping.py +0 -0
  161. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_exceptions.py +0 -0
  162. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_google_thought_signature.py +0 -0
  163. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_google_tool_signature_live.py +0 -0
  164. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_instrumented_client.py +0 -0
  165. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_instrumented_client_integration.py +0 -0
  166. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_live_providers.py +0 -0
  167. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_openai_cached_tokens.py +0 -0
  168. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_openai_cached_tokens_stream.py +0 -0
  169. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_tool_execution_ids.py +0 -0
  170. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/services/__init__.py +0 -0
  171. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_browser.py +0 -0
  172. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_browser_parity.py +0 -0
  173. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_file_system.py +0 -0
  174. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_sandbox_terminal_input.py +0 -0
  175. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_terminal.py +0 -0
  176. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_terminal_buffer.py +0 -0
  177. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_terminal_state_serializer.py +0 -0
  178. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_coder_prompt_extensions.py +0 -0
  179. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_commands.py +0 -0
  180. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compaction_integration.py +0 -0
  181. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compaction_subagent.py +0 -0
  182. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compaction_tui_events.py +0 -0
  183. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compress_command.py +0 -0
  184. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compression.py +0 -0
  185. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_duplicate_tool_results.py +0 -0
  186. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_empty_message_handling.py +0 -0
  187. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_event_emitter.py +0 -0
  188. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_general_agent.py +0 -0
  189. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_gigacode_gating.py +0 -0
  190. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_hooks_integration.py +0 -0
  191. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_html.py +0 -0
  192. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_parallel_tool_calls.py +0 -0
  193. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_permissions.py +0 -0
  194. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_prompt_provider.py +0 -0
  195. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/__init__.py +0 -0
  196. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_base_tool.py +0 -0
  197. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_browser_tool.py +0 -0
  198. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_build_tool.py +0 -0
  199. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_create_file_tool.py +0 -0
  200. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_edit_preview.py +0 -0
  201. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_glob_tool.py +0 -0
  202. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_glob_tool_sandbox_parity.py +0 -0
  203. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_list_directory_tool.py +0 -0
  204. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_read_file_tool.py +0 -0
  205. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_replace_entire_file_tool.py +0 -0
  206. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_replace_lines_tool.py +0 -0
  207. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_search_and_replace_tool.py +0 -0
  208. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_search_codebase_tool.py +0 -0
  209. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_terminal_tool.py +0 -0
  210. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_think_hard_integration.py +0 -0
  211. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_think_hard_streaming.py +0 -0
  212. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_web_fetch_tool.py +0 -0
  213. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_web_search_tool.py +0 -0
  214. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_workflow_tool.py +0 -0
  215. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/base_tool.py +0 -0
  216. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/browser_tool.py +0 -0
  217. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/build_tool.py +0 -0
  218. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/create_file_tool.py +0 -0
  219. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/edit_preview.py +0 -0
  220. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/glob_tool.py +0 -0
  221. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/list_directory_tool.py +0 -0
  222. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/memory_tool.py +0 -0
  223. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/read_file_tool.py +0 -0
  224. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/replace_entire_file_tool.py +0 -0
  225. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/replace_lines_tool.py +0 -0
  226. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_and_replace_tool.py +0 -0
  227. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/__init__.py +0 -0
  228. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/base.py +0 -0
  229. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/duckduckgo.py +0 -0
  230. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/errors.py +0 -0
  231. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/firecrawl.py +0 -0
  232. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/models.py +0 -0
  233. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/searxng.py +0 -0
  234. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/tavily.py +0 -0
  235. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_codebase_tool.py +0 -0
  236. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/streaming_tool.py +0 -0
  237. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/terminal_tool.py +0 -0
  238. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/think_hard_tool.py +0 -0
  239. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/web_fetch_tool.py +0 -0
  240. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/web_search_tool.py +0 -0
  241. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/utils/commands.py +0 -0
  242. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/auth/__init__.py +0 -0
  243. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/auth/_jwt.py +0 -0
  244. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/auth/chatgpt_oauth.py +0 -0
  245. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/auth/constants.py +0 -0
  246. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/auth/tests/__init__.py +0 -0
  247. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/auth/tests/test_chatgpt_oauth.py +0 -0
  248. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/auth/tests/test_tokens.py +0 -0
  249. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/auth/tokens.py +0 -0
  250. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/__init__.py +0 -0
  251. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/config.py +0 -0
  252. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/connection.py +0 -0
  253. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/file_index.py +0 -0
  254. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/settings.py +0 -0
  255. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/skills.py +0 -0
  256. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/__init__.py +0 -0
  257. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_cli_config.py +0 -0
  258. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_connection.py +0 -0
  259. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_file_index.py +0 -0
  260. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_main.py +0 -0
  261. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_settings.py +0 -0
  262. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_skills.py +0 -0
  263. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_slash_commands.py +0 -0
  264. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_themes.py +0 -0
  265. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_updater.py +0 -0
  266. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/theme.py +0 -0
  267. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/updater.py +0 -0
  268. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/config.py +0 -0
  269. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/events.py +0 -0
  270. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/__init__.py +0 -0
  271. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/backends.py +0 -0
  272. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/config.py +0 -0
  273. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/dispatcher.py +0 -0
  274. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/events.py +0 -0
  275. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/matcher.py +0 -0
  276. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/outcome.py +0 -0
  277. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/tests/__init__.py +0 -0
  278. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/tests/test_hooks.py +0 -0
  279. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/exceptions.py +0 -0
  280. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/providers/__init__.py +0 -0
  281. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/providers/base.py +0 -0
  282. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/providers/google.py +0 -0
  283. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/providers/models.py +0 -0
  284. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/ratelimit.py +0 -0
  285. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/tool_execution_ids.py +0 -0
  286. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/models/__init__.py +0 -0
  287. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/models/sandbox_terminal_state.py +0 -0
  288. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/permissions.py +0 -0
  289. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/runtime.py +0 -0
  290. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/README.md +0 -0
  291. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/__init__.py +0 -0
  292. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/async_filesystem.py +0 -0
  293. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/base.py +0 -0
  294. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/browser.py +0 -0
  295. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/event_loop.py +0 -0
  296. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/filesystem.py +0 -0
  297. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/local.py +0 -0
  298. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/serializer.py +0 -0
  299. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/terminal.py +0 -0
  300. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/utils.py +0 -0
  301. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/services/base.py +0 -0
  302. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/services/browser.py +0 -0
  303. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/services/file_system.py +0 -0
  304. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/services/html.py +0 -0
  305. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/services/terminal.py +0 -0
  306. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/services/terminal_buffer.py +0 -0
  307. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/tools/__init__.py +0 -0
  308. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/tools/core.py +0 -0
  309. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/tools/definitions.py +0 -0
  310. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/tools/registry.py +0 -0
  311. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/tools/tests/__init__.py +0 -0
  312. {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/tools/tests/test_definitions.py +0 -0
  313. {kolega_code-0.7.2 → kolega_code-0.8.1}/run_tests.sh +0 -0
  314. {kolega_code-0.7.2 → kolega_code-0.8.1}/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.1
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
 
@@ -18,6 +18,7 @@ from .agent.planningagent import PlanningAgent
18
18
  from .agent.compression import HistoryCompressor
19
19
  from .agent.context import AgentContext, AgentServices, Telemetry, WorkspaceInfo
20
20
  from .agent.conversation import Conversation
21
+ from .agent.errors import AgentError, MaxAgentIterationsExceeded
21
22
  from .events import AgentEventEmitter
22
23
 
23
24
  # Configuration
@@ -86,6 +87,8 @@ __all__ = [
86
87
  "AgentContext",
87
88
  "AgentServices",
88
89
  "Telemetry",
90
+ "AgentError",
91
+ "MaxAgentIterationsExceeded",
89
92
  "WorkspaceInfo",
90
93
  "Conversation",
91
94
  "HistoryCompressor",
@@ -148,4 +151,4 @@ __all__ = [
148
151
  "parse_git_status_output",
149
152
  ]
150
153
 
151
- __version__ = "0.7.2"
154
+ __version__ = "0.8.1"
@@ -8,6 +8,9 @@ from .browseragent import BrowserAgent
8
8
  from .generalagent import GeneralAgent
9
9
  from .planningagent import PlanningAgent
10
10
 
11
+ # Export agent errors
12
+ from .errors import AgentError, MaxAgentIterationsExceeded
13
+
11
14
  # Export agent models
12
15
  from kolega_code.events import AgentStatus, AgentEvent
13
16
 
@@ -25,6 +28,8 @@ __all__ = [
25
28
  "BrowserAgent",
26
29
  "GeneralAgent",
27
30
  "PlanningAgent",
31
+ "AgentError",
32
+ "MaxAgentIterationsExceeded",
28
33
  "AgentStatus",
29
34
  "AgentEvent",
30
35
  "AgentConfig",
@@ -39,4 +44,4 @@ __all__ = [
39
44
  "ToolExtension",
40
45
  ]
41
46
 
42
- __version__ = "0.7.2"
47
+ __version__ = "0.8.1"
@@ -12,10 +12,11 @@ from typing import Any, AsyncGenerator, Dict, List, Optional
12
12
 
13
13
  from .common import LogMixin
14
14
  from .compression import CompactionResult, HistoryCompressor
15
+ from .errors import MaxAgentIterationsExceeded
15
16
  from kolega_code.config import AgentConfig, ModelProvider
16
17
  from kolega_code.events import AgentConnectionManager
17
18
  from .context import AgentContext, AgentServices, Telemetry, WorkspaceInfo
18
- from .conversation import Conversation
19
+ from .conversation import Conversation, adapt_history_for_provider
19
20
  from kolega_code.events import AgentEventEmitter
20
21
  from kolega_code.hooks import (
21
22
  NO_OP_DISPATCHER,
@@ -34,7 +35,7 @@ from kolega_code.llm.exceptions import (
34
35
  )
35
36
  from kolega_code.llm.models import ImageBlock, Message, MessageHistory, TextBlock, ToolCall, ToolResult
36
37
  from kolega_code.llm.providers.models import TokenCount
37
- from kolega_code.llm.specs import get_model_specs
38
+ from kolega_code.llm.specs import get_model_specs, supports_vision as model_supports_vision
38
39
  from kolega_code.permissions import (
39
40
  PermissionDecision,
40
41
  PermissionMode,
@@ -90,10 +91,6 @@ class BaseAgent(LogMixin):
90
91
  long_content_tool_calls = ["create_file", "replace_entire_file"]
91
92
  max_tool_result_chars_in_history = 100_000
92
93
  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
94
 
98
95
  def __init__(
99
96
  self,
@@ -123,6 +120,7 @@ class BaseAgent(LogMixin):
123
120
  sub_agent_recorder: Optional[Any] = None,
124
121
  hook_dispatcher: Optional[HookDispatcher] = None,
125
122
  context: Optional[AgentContext] = None,
123
+ max_iterations: Optional[int] = None,
126
124
  ) -> None:
127
125
  """
128
126
  Initialize a new BaseAgent instance.
@@ -207,7 +205,11 @@ class BaseAgent(LogMixin):
207
205
  if hook_dispatcher is not None:
208
206
  context.hook_dispatcher = hook_dispatcher
209
207
 
208
+ if max_iterations is not None and max_iterations < 1:
209
+ raise ValueError("max_iterations must be a positive integer or None")
210
+
210
211
  self.context = context
212
+ self.max_iterations = max_iterations
211
213
 
212
214
  # Flat attributes kept for compatibility with subclasses, tools, and hosts.
213
215
  self.project_path = context.workspace.project_path
@@ -270,6 +272,11 @@ class BaseAgent(LogMixin):
270
272
  self.model_context_length = model_specs["context_length"]
271
273
  self.model_completion_tokens = model_specs["max_completion_tokens"]
272
274
  self.model_default_temperature = model_specs.get("default_temperature", 1.0)
275
+ # Whether this agent's primary model can accept image input. Read by the
276
+ # ToolCollection read_image tool gate (so non-vision models never see the
277
+ # tool) and used by _unsupported_attachment_message to reject image
278
+ # attachments for non-vision models with a clear message.
279
+ self.supports_vision = bool(model_specs.get("supports_vision", False))
273
280
 
274
281
  self.llm = context.create_llm_client(agent_name=self.agent_name)
275
282
 
@@ -365,6 +372,26 @@ class BaseAgent(LogMixin):
365
372
  """
366
373
  return self.conversation.repaired(messages)
367
374
 
375
+ def _history_for_llm(self) -> MessageHistory:
376
+ """Build the message history to send to the LLM for this turn.
377
+
378
+ Compaction-aware and tool-call-repaired. For non-vision models, any
379
+ ``ImageBlock`` carried over from earlier turns (user attachments or
380
+ ``read_image`` tool results) is replaced with a text placeholder on this
381
+ request copy only — the stored history is never mutated, so switching
382
+ back to a vision-capable model restores the images.
383
+ """
384
+ effective = self.get_effective_history_for_llm()
385
+ fixed = self.fix_incomplete_tool_calls(list(effective))
386
+ provider = getattr(self.primary_model_config.provider, "value", self.primary_model_config.provider)
387
+ fixed = adapt_history_for_provider(
388
+ fixed,
389
+ target_provider=str(provider),
390
+ target_model=self.primary_model_config.model,
391
+ supports_vision=self.supports_vision,
392
+ )
393
+ return MessageHistory(fixed)
394
+
368
395
  def mark_cache_checkpoint(self) -> None:
369
396
  """
370
397
  Mark the last message in history for caching and remove cache_control from all other messages.
@@ -480,18 +507,22 @@ class BaseAgent(LogMixin):
480
507
  # ------------------------------------------------------------------
481
508
 
482
509
  def _unsupported_attachment_message(self, attachments: Optional[List[Dict[str, Any]]]) -> Optional[str]:
510
+ if not any(attachment.get("type") == "image" for attachment in attachments or []):
511
+ return None
512
+
483
513
  provider = getattr(
484
514
  self.primary_model_config.provider,
485
515
  "value",
486
516
  self.primary_model_config.provider,
487
517
  )
488
- if provider != ModelProvider.DEEPSEEK.value:
518
+ if model_supports_vision(provider, self.primary_model_config.model):
489
519
  return None
490
520
 
491
- if any(attachment.get("type") == "image" for attachment in attachments or []):
492
- return self.deepseek_image_unsupported_message
493
-
494
- return None
521
+ return (
522
+ f"{self.primary_model_config.model} does not support image input. "
523
+ "Your message was not sent to the model. "
524
+ "Remove the image attachment or switch to a vision-capable model with /model."
525
+ )
495
526
 
496
527
  def _attachment_blocks(self, attachments: Optional[List[Dict[str, Any]]]) -> List[Any]:
497
528
  """Convert attachment payloads into content blocks for a user message."""
@@ -518,9 +549,9 @@ class BaseAgent(LogMixin):
518
549
 
519
550
  async def count_current_context(self) -> TokenCount:
520
551
  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)))
552
+ # History sent to the LLM (and to token counting): tool-call-repaired and,
553
+ # for non-vision models, stripped of image blocks from earlier turns.
554
+ fixed_history = self._history_for_llm()
524
555
  token_count = await self.llm.count_tokens(
525
556
  system=self.system_prompt,
526
557
  messages=fixed_history,
@@ -1257,7 +1288,15 @@ class BaseAgent(LogMixin):
1257
1288
 
1258
1289
  stop_reason = None
1259
1290
  stop_overrides = 0
1291
+ iterations = 0
1260
1292
  while stop_reason not in ["end_turn", "max_tokens", "stop_sequence"]:
1293
+ iterations += 1
1294
+ if self.max_iterations is not None and iterations > self.max_iterations:
1295
+ raise MaxAgentIterationsExceeded(
1296
+ f"Agent '{self.agent_name}' exceeded max_iterations={self.max_iterations} "
1297
+ "without reaching a terminal stop reason"
1298
+ )
1299
+
1261
1300
  self.mark_cache_checkpoint()
1262
1301
 
1263
1302
  try:
@@ -1304,9 +1343,9 @@ class BaseAgent(LogMixin):
1304
1343
  response_uuid = str(uuid.uuid4())
1305
1344
  thinking_uuid = str(uuid.uuid4())
1306
1345
 
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)))
1346
+ # History sent to the LLM: tool-call-repaired and, for non-vision
1347
+ # models, stripped of image blocks carried over from earlier turns.
1348
+ fixed_history = self._history_for_llm()
1310
1349
 
1311
1350
  async with await self.llm.stream(
1312
1351
  system=self.system_prompt,
@@ -45,6 +45,7 @@ class BrowserAgent(BaseAgent):
45
45
  usage_recorder: Optional[Any] = None,
46
46
  sub_agent_recorder: Optional[Any] = None,
47
47
  hook_dispatcher: Optional[Any] = None,
48
+ max_iterations: Optional[int] = None,
48
49
  ) -> None:
49
50
  """
50
51
  Initialize a new BrowserAgent instance.
@@ -97,6 +98,7 @@ class BrowserAgent(BaseAgent):
97
98
  usage_recorder=usage_recorder,
98
99
  sub_agent_recorder=sub_agent_recorder,
99
100
  hook_dispatcher=hook_dispatcher,
101
+ max_iterations=max_iterations,
100
102
  )
101
103
 
102
104
  self.tool_collection = ToolCollection(
@@ -49,6 +49,7 @@ class CoderAgent(BaseAgent, LogMixin):
49
49
  usage_recorder: Optional[Any] = None,
50
50
  sub_agent_recorder: Optional[Any] = None,
51
51
  hook_dispatcher: Optional[Any] = None,
52
+ max_iterations: Optional[int] = None,
52
53
  ) -> None:
53
54
  """
54
55
  Initialize a new CoderAgent instance.
@@ -104,6 +105,7 @@ class CoderAgent(BaseAgent, LogMixin):
104
105
  usage_recorder=usage_recorder,
105
106
  sub_agent_recorder=sub_agent_recorder,
106
107
  hook_dispatcher=hook_dispatcher,
108
+ max_iterations=max_iterations,
107
109
  )
108
110
 
109
111
  # Configure tool collection with custom coder agent tools
@@ -11,11 +11,206 @@ 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
+ ResponsesReasoningBlock,
20
+ TextBlock,
21
+ ThinkingBlock,
22
+ ToolCall,
23
+ ToolResult,
24
+ )
15
25
 
16
26
  logger = logging.getLogger(__name__)
17
27
 
18
28
 
29
+ def _image_placeholder(media_type: str, model_name: str) -> str:
30
+ """Text substituted for an image block when the active model can't see images."""
31
+ return f"[An image ({media_type}) was shared earlier in this thread but is not visible to {model_name}.]"
32
+
33
+
34
+ def count_image_blocks(messages: List[Message]) -> int:
35
+ """Count ``ImageBlock`` instances across messages, including ones nested in tool results."""
36
+ count = 0
37
+ for message in messages:
38
+ if not isinstance(message.content, list):
39
+ continue
40
+ for block in message.content:
41
+ if isinstance(block, ImageBlock):
42
+ count += 1
43
+ elif isinstance(block, ToolResult) and isinstance(block.content, list):
44
+ count += sum(1 for inner in block.content if isinstance(inner, ImageBlock))
45
+ return count
46
+
47
+
48
+ def _provider_value(provider: Any) -> str:
49
+ value = getattr(provider, "value", provider)
50
+ return str(value or "")
51
+
52
+
53
+ def _message_with_content(message: Message, content: List[Any]) -> Message:
54
+ return Message(
55
+ role=message.role,
56
+ content=content,
57
+ stop_reason=message.stop_reason,
58
+ tool_calls=message.tool_calls,
59
+ usage_metadata=message.usage_metadata,
60
+ )
61
+
62
+
63
+ def _tool_result_with_content(tool_result: ToolResult, content: List[Any]) -> ToolResult:
64
+ return ToolResult(
65
+ tool_use_id=tool_result.tool_use_id,
66
+ content=content,
67
+ name=tool_result.name,
68
+ is_error=tool_result.is_error,
69
+ cache_checkpoint=tool_result.cache_checkpoint,
70
+ execution_id=tool_result.execution_id,
71
+ )
72
+
73
+
74
+ def replace_image_blocks_with_placeholders(messages: List[Message], model_name: str) -> List[Message]:
75
+ """Return a new message list with every ``ImageBlock`` replaced by a text placeholder.
76
+
77
+ Non-mutating: messages without images are returned as-is. When a replacement
78
+ occurs, new ``Message``/``ToolResult`` objects are built so the caller's stored
79
+ history is never altered — switching back to a vision-capable model restores
80
+ the original images. ``ImageBlock``s nested inside a ``ToolResult.content`` list
81
+ (e.g. from the ``read_image`` tool) are handled too.
82
+ """
83
+ result: List[Message] = []
84
+ for message in messages:
85
+ if not isinstance(message.content, list):
86
+ result.append(message)
87
+ continue
88
+ new_blocks: List[Any] = []
89
+ changed = False
90
+ for block in message.content:
91
+ if isinstance(block, ImageBlock):
92
+ new_blocks.append(TextBlock(text=_image_placeholder(block.media_type, model_name)))
93
+ changed = True
94
+ elif isinstance(block, ToolResult) and isinstance(block.content, list):
95
+ inner_new: List[Any] = []
96
+ inner_changed = False
97
+ for inner in block.content:
98
+ if isinstance(inner, ImageBlock):
99
+ inner_new.append(TextBlock(text=_image_placeholder(inner.media_type, model_name)))
100
+ inner_changed = True
101
+ else:
102
+ inner_new.append(inner)
103
+ if inner_changed:
104
+ new_blocks.append(_tool_result_with_content(block, inner_new))
105
+ changed = True
106
+ else:
107
+ new_blocks.append(block)
108
+ else:
109
+ new_blocks.append(block)
110
+ if changed:
111
+ result.append(_message_with_content(message, new_blocks))
112
+ else:
113
+ result.append(message)
114
+ return result
115
+
116
+
117
+ # Groups of providers whose reasoning is mutually replayable. The api-key
118
+ # `openai` and the ChatGPT-subscription `openai_chatgpt` are both the OpenAI
119
+ # Responses API, so encrypted reasoning produced by one replays cleanly to the
120
+ # other — the one exception to "only replay reasoning to the same provider".
121
+ _REASONING_COMPATIBLE_GROUPS: tuple[frozenset[str], ...] = (frozenset({"openai", "openai_chatgpt"}),)
122
+
123
+
124
+ def _preserve_reasoning_block(block: Any, *, source_provider: str, target_provider: str) -> bool:
125
+ if not source_provider:
126
+ return False
127
+ if source_provider == target_provider:
128
+ return True
129
+ return any(
130
+ source_provider in group and target_provider in group for group in _REASONING_COMPATIBLE_GROUPS
131
+ )
132
+
133
+
134
+ def _reasoning_placeholder(block: Any, source_provider: str) -> TextBlock:
135
+ source = source_provider or "unknown provider"
136
+ if isinstance(block, RedactedThinkingBlock):
137
+ return TextBlock(text=f"[Prior redacted reasoning from {source} omitted for compatibility.]")
138
+ return TextBlock(text=f"[Prior reasoning from {source} omitted for compatibility.]")
139
+
140
+
141
+ def _adapt_content_blocks_for_provider(
142
+ blocks: List[Any], *, source_provider: str, target_provider: str, target_model: str, supports_vision: bool
143
+ ) -> tuple[List[Any], bool]:
144
+ adapted: List[Any] = []
145
+ changed = False
146
+
147
+ for block in blocks:
148
+ if isinstance(block, ImageBlock) and not supports_vision:
149
+ adapted.append(TextBlock(text=_image_placeholder(block.media_type, target_model)))
150
+ changed = True
151
+ elif isinstance(block, (ThinkingBlock, RedactedThinkingBlock, ResponsesReasoningBlock)):
152
+ if _preserve_reasoning_block(block, source_provider=source_provider, target_provider=target_provider):
153
+ adapted.append(block)
154
+ else:
155
+ adapted.append(_reasoning_placeholder(block, source_provider))
156
+ changed = True
157
+ elif isinstance(block, ToolResult) and isinstance(block.content, list):
158
+ inner, inner_changed = _adapt_content_blocks_for_provider(
159
+ block.content,
160
+ source_provider=source_provider,
161
+ target_provider=target_provider,
162
+ target_model=target_model,
163
+ supports_vision=supports_vision,
164
+ )
165
+ if inner_changed:
166
+ adapted.append(_tool_result_with_content(block, inner))
167
+ changed = True
168
+ else:
169
+ adapted.append(block)
170
+ else:
171
+ adapted.append(block)
172
+
173
+ return adapted, changed
174
+
175
+
176
+ def adapt_history_for_provider(
177
+ messages: List[Message], *, target_provider: str, target_model: str, supports_vision: bool
178
+ ) -> List[Message]:
179
+ """Return a request-safe history for the target provider without mutating storage.
180
+
181
+ Provider-managed reasoning blocks are not portable across providers, but
182
+ reasoning produced by a provider is safe to send back to the same provider.
183
+ Preserve same-provider reasoning; convert foreign or unknown reasoning
184
+ blocks to text placeholders. Image blocks are preserved for vision models
185
+ and replaced with placeholders for non-vision models, matching the previous
186
+ image compatibility behavior.
187
+ """
188
+ target_provider = _provider_value(target_provider)
189
+ result: List[Message] = []
190
+ changed_any = False
191
+
192
+ for message in messages:
193
+ if not isinstance(message.content, list):
194
+ result.append(message)
195
+ continue
196
+
197
+ source_provider = _provider_value((message.usage_metadata or {}).get("provider"))
198
+ adapted_content, changed = _adapt_content_blocks_for_provider(
199
+ message.content,
200
+ source_provider=source_provider,
201
+ target_provider=target_provider,
202
+ target_model=target_model,
203
+ supports_vision=supports_vision,
204
+ )
205
+ if changed:
206
+ result.append(_message_with_content(message, adapted_content))
207
+ changed_any = True
208
+ else:
209
+ result.append(message)
210
+
211
+ return result if changed_any else messages
212
+
213
+
19
214
  class Conversation:
20
215
  """Message history plus the invariants that keep it valid for providers."""
21
216
 
@@ -31,8 +226,11 @@ class Conversation:
31
226
  # Compaction state: ``summary`` stands in for ``history[:compacted_through]``
32
227
  # in the effective view; messages from ``compacted_through`` onward are kept
33
228
  # verbatim. ``summary is None`` / ``compacted_through == 0`` means uncompacted.
229
+ # ``compacted_history_length`` is the history length captured when compaction
230
+ # ran, so the UI can place the summary after the retained tail on restore.
34
231
  self.summary: Optional[Message] = None
35
232
  self.compacted_through: int = 0
233
+ self.compacted_history_length: int = 0
36
234
  self.max_tool_result_chars = max_tool_result_chars
37
235
 
38
236
  # ------------------------------------------------------------------
@@ -51,6 +249,7 @@ class Conversation:
51
249
  self._history = value if isinstance(value, MessageHistory) else MessageHistory(list(value))
52
250
  self.summary = None
53
251
  self.compacted_through = 0
252
+ self.compacted_history_length = 0
54
253
 
55
254
  @property
56
255
  def last_compression_index(self) -> Optional[int]:
@@ -66,6 +265,7 @@ class Conversation:
66
265
  if value is None:
67
266
  self.summary = None
68
267
  self.compacted_through = 0
268
+ self.compacted_history_length = 0
69
269
  else:
70
270
  self.compacted_through = value + 1
71
271
 
@@ -74,6 +274,15 @@ class Conversation:
74
274
  self._history = MessageHistory([])
75
275
  self.summary = None
76
276
  self.compacted_through = 0
277
+ self.compacted_history_length = 0
278
+
279
+ def has_image_blocks(self) -> bool:
280
+ """True if the effective (compaction-aware) history still carries any images.
281
+
282
+ Images folded into a compaction summary are already gone, so this reflects
283
+ only what would actually be sent to the model.
284
+ """
285
+ return count_image_blocks(list(self.effective_history())) > 0
77
286
 
78
287
  # ------------------------------------------------------------------
79
288
  # Appending
@@ -439,6 +648,7 @@ class Conversation:
439
648
  summary if isinstance(summary, Message) else Message(role="user", content=[TextBlock(text=str(summary))])
440
649
  )
441
650
  self.compacted_through = len(self._history)
651
+ self.compacted_history_length = len(self._history)
442
652
 
443
653
  def apply_compaction(self, summary_text: str, split_point: int) -> None:
444
654
  """Record ``summary_text`` as standing in for ``history[:split_point]``.
@@ -448,6 +658,7 @@ class Conversation:
448
658
  """
449
659
  self.summary = Message(role="user", content=[TextBlock(text=summary_text)])
450
660
  self.compacted_through = max(0, min(split_point, len(self._history)))
661
+ self.compacted_history_length = len(self._history)
451
662
 
452
663
  def compaction_split_point(self, *, keep_recent: int, min_prefix: int) -> Optional[int]:
453
664
  """Index where the verbatim recent tail should begin.
@@ -500,13 +711,15 @@ class Conversation:
500
711
  return {
501
712
  "summary": self.summary.get_text_content() if self.summary is not None else "",
502
713
  "compacted_through": self.compacted_through,
714
+ "compacted_history_length": self.compacted_history_length,
503
715
  }
504
716
 
505
717
  def restore_compaction(self, data: Optional[Dict[str, Any]]) -> None:
506
718
  """Restore a compaction boundary saved by ``dump_compaction``.
507
719
 
508
720
  Call AFTER ``restore`` (which resets compaction). Assigns the boundary
509
- fields directly so it does not trip the history setter's reset.
721
+ fields directly so it does not trip the history setter's reset. Old
722
+ sessions without ``compacted_history_length`` fall back to 0.
510
723
  """
511
724
  data = data or {}
512
725
  text = (data.get("summary") or "").strip()
@@ -514,6 +727,8 @@ class Conversation:
514
727
  if text and through > 0:
515
728
  self.summary = Message(role="user", content=[TextBlock(text=text)])
516
729
  self.compacted_through = min(through, len(self._history))
730
+ self.compacted_history_length = int(data.get("compacted_history_length") or 0)
517
731
  else:
518
732
  self.summary = None
519
733
  self.compacted_through = 0
734
+ self.compacted_history_length = 0
@@ -0,0 +1,9 @@
1
+ """Exceptions raised by agent runtime loops."""
2
+
3
+
4
+ class AgentError(RuntimeError):
5
+ """Base class for catchable agent runtime errors."""
6
+
7
+
8
+ class MaxAgentIterationsExceeded(AgentError):
9
+ """Raised when an opt-in agent loop iteration cap is exceeded."""