kolega-code 0.8.0__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 (312) hide show
  1. {kolega_code-0.8.0 → kolega_code-0.8.1}/PKG-INFO +1 -1
  2. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/__init__.py +4 -1
  3. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/__init__.py +6 -1
  4. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/baseagent.py +14 -0
  5. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/browseragent.py +2 -0
  6. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/coder.py +2 -0
  7. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/conversation.py +16 -2
  8. kolega_code-0.8.1/kolega_code/agent/errors.py +9 -0
  9. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/generalagent.py +2 -0
  10. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/investigationagent.py +2 -0
  11. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/planningagent.py +2 -0
  12. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_chatgpt_oauth_provider.py +136 -0
  13. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_error_boundary.py +3 -2
  14. kolega_code-0.8.1/kolega_code/agent/tests/llm/test_openai_responses_provider.py +163 -0
  15. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_thinking_effort.py +5 -1
  16. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_base_agent.py +68 -0
  17. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_image_compat.py +56 -0
  18. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_agent_tool.py +29 -3
  19. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/agent_tool.py +2 -0
  20. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/llm/client.py +6 -2
  21. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/llm/instrumented_client.py +3 -3
  22. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/llm/models.py +78 -0
  23. kolega_code-0.8.1/kolega_code/llm/providers/chatgpt_oauth.py +103 -0
  24. kolega_code-0.8.1/kolega_code/llm/providers/openai_responses.py +60 -0
  25. kolega_code-0.8.0/kolega_code/llm/providers/chatgpt_oauth.py → kolega_code-0.8.1/kolega_code/llm/providers/responses_common.py +142 -103
  26. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/llm/specs.py +18 -8
  27. {kolega_code-0.8.0 → kolega_code-0.8.1}/pyproject.toml +1 -1
  28. {kolega_code-0.8.0 → kolega_code-0.8.1}/uv.lock +2 -2
  29. {kolega_code-0.8.0 → kolega_code-0.8.1}/.env.example +0 -0
  30. {kolega_code-0.8.0 → kolega_code-0.8.1}/.github/workflows/ci.yml +0 -0
  31. {kolega_code-0.8.0 → kolega_code-0.8.1}/.github/workflows/docs.yml +0 -0
  32. {kolega_code-0.8.0 → kolega_code-0.8.1}/.github/workflows/release.yml +0 -0
  33. {kolega_code-0.8.0 → kolega_code-0.8.1}/.gitignore +0 -0
  34. {kolega_code-0.8.0 → kolega_code-0.8.1}/CONTRIBUTING.md +0 -0
  35. {kolega_code-0.8.0 → kolega_code-0.8.1}/LICENSE +0 -0
  36. {kolega_code-0.8.0 → kolega_code-0.8.1}/README.md +0 -0
  37. {kolega_code-0.8.0 → kolega_code-0.8.1}/RELEASING.md +0 -0
  38. {kolega_code-0.8.0 → kolega_code-0.8.1}/SECURITY.md +0 -0
  39. {kolega_code-0.8.0 → kolega_code-0.8.1}/conftest.py +0 -0
  40. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/.gitignore +0 -0
  41. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/README.md +0 -0
  42. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/astro.config.mjs +0 -0
  43. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/package-lock.json +0 -0
  44. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/package.json +0 -0
  45. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/public/favicon.svg +0 -0
  46. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/assets/demo.gif +0 -0
  47. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/assets/kolega-dark.svg +0 -0
  48. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/assets/kolega-light.svg +0 -0
  49. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/cli/ask.md +0 -0
  50. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/cli/doctor.md +0 -0
  51. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/cli/overview.md +0 -0
  52. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/cli/sessions.md +0 -0
  53. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/concepts/agents.md +0 -0
  54. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/concepts/how-it-works.md +0 -0
  55. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/concepts/tools.md +0 -0
  56. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/configuration/environment-variables.md +0 -0
  57. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/configuration/providers-and-models.mdx +0 -0
  58. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/configuration/settings-and-api-keys.mdx +0 -0
  59. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/configuration/sign-in-with-chatgpt.mdx +0 -0
  60. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/getting-started/installation.mdx +0 -0
  61. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/getting-started/quick-start.mdx +0 -0
  62. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/gigacode.mdx +0 -0
  63. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/hooks.md +0 -0
  64. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/index.mdx +0 -0
  65. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/skills.mdx +0 -0
  66. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/tui/composer.md +0 -0
  67. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/tui/interface.md +0 -0
  68. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/tui/modes.md +0 -0
  69. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/tui/sessions-and-resume.md +0 -0
  70. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content/docs/tui/slash-commands.md +0 -0
  71. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/content.config.ts +0 -0
  72. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/src/styles/brand.css +0 -0
  73. {kolega_code-0.8.0 → kolega_code-0.8.1}/docs/tsconfig.json +0 -0
  74. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/common.py +0 -0
  75. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/compression.py +0 -0
  76. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/context.py +0 -0
  77. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/orchestration/__init__.py +0 -0
  78. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/orchestration/budget.py +0 -0
  79. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/orchestration/errors.py +0 -0
  80. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/orchestration/executor.py +0 -0
  81. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/orchestration/guide.py +0 -0
  82. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/orchestration/journal.py +0 -0
  83. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/orchestration/runtime.py +0 -0
  84. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/orchestration/tests/__init__.py +0 -0
  85. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/orchestration/tests/test_orchestration.py +0 -0
  86. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/orchestration/types.py +0 -0
  87. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_provider.py +0 -0
  88. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/auxiliary/compression/summary.system.md +0 -0
  89. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/auxiliary/compression/summary.user.md.j2 +0 -0
  90. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/auxiliary/terminal/output_summary.system.md +0 -0
  91. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/auxiliary/terminal/safety.system.md +0 -0
  92. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/auxiliary/tools/think_hard.system.md +0 -0
  93. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/extensions/cli/planning_questions.md +0 -0
  94. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/extensions/cli/shared_task_list.md +0 -0
  95. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/extensions/skills/catalog.md.j2 +0 -0
  96. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/agents/browser.md.j2 +0 -0
  97. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/agents/coder_cli.md.j2 +0 -0
  98. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/agents/general.md.j2 +0 -0
  99. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/agents/investigation.md.j2 +0 -0
  100. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/agents/planning.md.j2 +0 -0
  101. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/agents_md_instructions.md +0 -0
  102. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/environment_variables/workspace_env_vars.md +0 -0
  103. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/frontend_guidance.md +0 -0
  104. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/expo.md +0 -0
  105. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/html_website.md +0 -0
  106. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/mern_stack.md +0 -0
  107. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/react_vite_shadcdn.md +0 -0
  108. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/user_tasks/cli/implement_plan.md.j2 +0 -0
  109. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/user_tasks/cli/init_agents.md.j2 +0 -0
  110. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/prompts.py +0 -0
  111. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/__init__.py +0 -0
  112. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/compaction_helpers.py +0 -0
  113. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/__init__.py +0 -0
  114. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_anthropic_token_counting.py +0 -0
  115. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_billing_openai_cache.py +0 -0
  116. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_chatgpt_live.py +0 -0
  117. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_client.py +0 -0
  118. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_dashscope_mapping.py +0 -0
  119. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_exceptions.py +0 -0
  120. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_google_thought_signature.py +0 -0
  121. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_google_tool_signature_live.py +0 -0
  122. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_instrumented_client.py +0 -0
  123. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_instrumented_client_integration.py +0 -0
  124. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_langfuse_normalization.py +0 -0
  125. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_live_providers.py +0 -0
  126. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_model_specs.py +0 -0
  127. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_openai_cached_tokens.py +0 -0
  128. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_openai_cached_tokens_stream.py +0 -0
  129. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_openai_message_conversion.py +0 -0
  130. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_openai_token_counting.py +0 -0
  131. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_supports_vision.py +0 -0
  132. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_tool_execution_ids.py +0 -0
  133. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/services/__init__.py +0 -0
  134. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_browser.py +0 -0
  135. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_browser_parity.py +0 -0
  136. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_file_system.py +0 -0
  137. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_sandbox_terminal_input.py +0 -0
  138. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_terminal.py +0 -0
  139. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_terminal_buffer.py +0 -0
  140. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_terminal_state_serializer.py +0 -0
  141. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_agent_tools_inventory.py +0 -0
  142. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_coder_attachments.py +0 -0
  143. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_coder_prompt_extensions.py +0 -0
  144. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_commands.py +0 -0
  145. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compaction_integration.py +0 -0
  146. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compaction_persistence.py +0 -0
  147. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compaction_subagent.py +0 -0
  148. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compaction_tui_events.py +0 -0
  149. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compress_command.py +0 -0
  150. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compression.py +0 -0
  151. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_conversation_compaction.py +0 -0
  152. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_duplicate_tool_results.py +0 -0
  153. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_empty_message_handling.py +0 -0
  154. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_event_emitter.py +0 -0
  155. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_general_agent.py +0 -0
  156. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_gigacode_gating.py +0 -0
  157. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_hooks_integration.py +0 -0
  158. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_html.py +0 -0
  159. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_parallel_tool_calls.py +0 -0
  160. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_permissions.py +0 -0
  161. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_planning_agent.py +0 -0
  162. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_prompt_provider.py +0 -0
  163. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_prompts.py +0 -0
  164. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_read_image_tool.py +0 -0
  165. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_tool_registry.py +0 -0
  166. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/test_tools.py +0 -0
  167. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/__init__.py +0 -0
  168. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_base_tool.py +0 -0
  169. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_browser_tool.py +0 -0
  170. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_build_tool.py +0 -0
  171. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_create_file_tool.py +0 -0
  172. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_edit_preview.py +0 -0
  173. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_glob_tool.py +0 -0
  174. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_glob_tool_sandbox_parity.py +0 -0
  175. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_list_directory_tool.py +0 -0
  176. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_read_file_tool.py +0 -0
  177. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_replace_entire_file_tool.py +0 -0
  178. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_replace_lines_tool.py +0 -0
  179. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_search_and_replace_tool.py +0 -0
  180. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_search_codebase_tool.py +0 -0
  181. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_terminal_tool.py +0 -0
  182. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_think_hard_integration.py +0 -0
  183. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_think_hard_streaming.py +0 -0
  184. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_web_fetch_tool.py +0 -0
  185. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_web_search_tool.py +0 -0
  186. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_workflow_tool.py +0 -0
  187. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/base_tool.py +0 -0
  188. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/browser_tool.py +0 -0
  189. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/build_tool.py +0 -0
  190. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/create_file_tool.py +0 -0
  191. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/edit_preview.py +0 -0
  192. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/glob_tool.py +0 -0
  193. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/list_directory_tool.py +0 -0
  194. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/memory_tool.py +0 -0
  195. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/read_file_tool.py +0 -0
  196. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/read_image_tool.py +0 -0
  197. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/replace_entire_file_tool.py +0 -0
  198. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/replace_lines_tool.py +0 -0
  199. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_and_replace_tool.py +0 -0
  200. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/__init__.py +0 -0
  201. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/base.py +0 -0
  202. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/duckduckgo.py +0 -0
  203. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/errors.py +0 -0
  204. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/firecrawl.py +0 -0
  205. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/models.py +0 -0
  206. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/searxng.py +0 -0
  207. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/tavily.py +0 -0
  208. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_codebase_tool.py +0 -0
  209. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/streaming_tool.py +0 -0
  210. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/terminal_tool.py +0 -0
  211. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/think_hard_tool.py +0 -0
  212. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/web_fetch_tool.py +0 -0
  213. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/web_search_tool.py +0 -0
  214. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/workflow_tool.py +0 -0
  215. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/tools.py +0 -0
  216. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/agent/utils/commands.py +0 -0
  217. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/auth/__init__.py +0 -0
  218. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/auth/_jwt.py +0 -0
  219. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/auth/chatgpt_oauth.py +0 -0
  220. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/auth/constants.py +0 -0
  221. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/auth/tests/__init__.py +0 -0
  222. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/auth/tests/test_chatgpt_oauth.py +0 -0
  223. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/auth/tests/test_tokens.py +0 -0
  224. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/auth/tokens.py +0 -0
  225. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/__init__.py +0 -0
  226. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/app.py +0 -0
  227. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/clipboard_image.py +0 -0
  228. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/config.py +0 -0
  229. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/connection.py +0 -0
  230. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/file_index.py +0 -0
  231. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/main.py +0 -0
  232. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/mentions.py +0 -0
  233. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/messages.py +0 -0
  234. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/provider_registry.py +0 -0
  235. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/session_store.py +0 -0
  236. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/settings.py +0 -0
  237. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/skills.py +0 -0
  238. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/slash_commands.py +0 -0
  239. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/__init__.py +0 -0
  240. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/test_app.py +0 -0
  241. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/test_app_compaction_restore.py +0 -0
  242. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/test_app_image_vision_warning.py +0 -0
  243. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/test_app_model_switch_vision.py +0 -0
  244. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/test_cli_config.py +0 -0
  245. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/test_clipboard_image.py +0 -0
  246. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/test_connection.py +0 -0
  247. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/test_file_index.py +0 -0
  248. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/test_main.py +0 -0
  249. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/test_mentions.py +0 -0
  250. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/test_provider_registry.py +0 -0
  251. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/test_session_store.py +0 -0
  252. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/test_settings.py +0 -0
  253. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/test_skills.py +0 -0
  254. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/test_slash_commands.py +0 -0
  255. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/test_themes.py +0 -0
  256. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/tests/test_updater.py +0 -0
  257. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/theme.py +0 -0
  258. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/cli/updater.py +0 -0
  259. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/config.py +0 -0
  260. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/events.py +0 -0
  261. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/hooks/__init__.py +0 -0
  262. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/hooks/backends.py +0 -0
  263. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/hooks/config.py +0 -0
  264. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/hooks/dispatcher.py +0 -0
  265. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/hooks/events.py +0 -0
  266. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/hooks/matcher.py +0 -0
  267. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/hooks/outcome.py +0 -0
  268. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/hooks/tests/__init__.py +0 -0
  269. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/hooks/tests/test_hooks.py +0 -0
  270. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/llm/exceptions.py +0 -0
  271. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/llm/providers/__init__.py +0 -0
  272. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/llm/providers/anthropic.py +0 -0
  273. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/llm/providers/base.py +0 -0
  274. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/llm/providers/google.py +0 -0
  275. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/llm/providers/models.py +0 -0
  276. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/llm/providers/openai.py +0 -0
  277. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/llm/ratelimit.py +0 -0
  278. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/llm/tool_execution_ids.py +0 -0
  279. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/models/__init__.py +0 -0
  280. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/models/sandbox_terminal_state.py +0 -0
  281. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/permissions.py +0 -0
  282. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/runtime.py +0 -0
  283. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/sandbox/README.md +0 -0
  284. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/sandbox/__init__.py +0 -0
  285. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/sandbox/async_filesystem.py +0 -0
  286. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/sandbox/base.py +0 -0
  287. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/sandbox/browser.py +0 -0
  288. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/sandbox/event_loop.py +0 -0
  289. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/sandbox/filesystem.py +0 -0
  290. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/sandbox/local.py +0 -0
  291. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/sandbox/serializer.py +0 -0
  292. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/sandbox/terminal.py +0 -0
  293. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/sandbox/utils.py +0 -0
  294. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/services/base.py +0 -0
  295. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/services/browser.py +0 -0
  296. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/services/file_system.py +0 -0
  297. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/services/html.py +0 -0
  298. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/services/terminal.py +0 -0
  299. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/services/terminal_buffer.py +0 -0
  300. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/tools/__init__.py +0 -0
  301. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/tools/core.py +0 -0
  302. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/tools/definitions.py +0 -0
  303. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/tools/registry.py +0 -0
  304. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/tools/tests/__init__.py +0 -0
  305. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/tools/tests/test_definitions.py +0 -0
  306. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/utils/__init__.py +0 -0
  307. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/utils/images.py +0 -0
  308. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/utils/tests/__init__.py +0 -0
  309. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/utils/tests/test_images.py +0 -0
  310. {kolega_code-0.8.0 → kolega_code-0.8.1}/kolega_code/utils/tests/test_images_ascii.py +0 -0
  311. {kolega_code-0.8.0 → kolega_code-0.8.1}/run_tests.sh +0 -0
  312. {kolega_code-0.8.0 → kolega_code-0.8.1}/scripts/install-kolega-code.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kolega-code
3
- Version: 0.8.0
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/
@@ -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.8.0"
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.8.0"
47
+ __version__ = "0.8.1"
@@ -12,6 +12,7 @@ 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
@@ -119,6 +120,7 @@ class BaseAgent(LogMixin):
119
120
  sub_agent_recorder: Optional[Any] = None,
120
121
  hook_dispatcher: Optional[HookDispatcher] = None,
121
122
  context: Optional[AgentContext] = None,
123
+ max_iterations: Optional[int] = None,
122
124
  ) -> None:
123
125
  """
124
126
  Initialize a new BaseAgent instance.
@@ -203,7 +205,11 @@ class BaseAgent(LogMixin):
203
205
  if hook_dispatcher is not None:
204
206
  context.hook_dispatcher = hook_dispatcher
205
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
+
206
211
  self.context = context
212
+ self.max_iterations = max_iterations
207
213
 
208
214
  # Flat attributes kept for compatibility with subclasses, tools, and hosts.
209
215
  self.project_path = context.workspace.project_path
@@ -1282,7 +1288,15 @@ class BaseAgent(LogMixin):
1282
1288
 
1283
1289
  stop_reason = None
1284
1290
  stop_overrides = 0
1291
+ iterations = 0
1285
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
+
1286
1300
  self.mark_cache_checkpoint()
1287
1301
 
1288
1302
  try:
@@ -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
@@ -16,6 +16,7 @@ from kolega_code.llm.models import (
16
16
  Message,
17
17
  MessageHistory,
18
18
  RedactedThinkingBlock,
19
+ ResponsesReasoningBlock,
19
20
  TextBlock,
20
21
  ThinkingBlock,
21
22
  ToolCall,
@@ -113,8 +114,21 @@ def replace_image_blocks_with_placeholders(messages: List[Message], model_name:
113
114
  return result
114
115
 
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
+
116
124
  def _preserve_reasoning_block(block: Any, *, source_provider: str, target_provider: str) -> bool:
117
- return bool(source_provider) and source_provider == target_provider
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
+ )
118
132
 
119
133
 
120
134
  def _reasoning_placeholder(block: Any, source_provider: str) -> TextBlock:
@@ -134,7 +148,7 @@ def _adapt_content_blocks_for_provider(
134
148
  if isinstance(block, ImageBlock) and not supports_vision:
135
149
  adapted.append(TextBlock(text=_image_placeholder(block.media_type, target_model)))
136
150
  changed = True
137
- elif isinstance(block, (ThinkingBlock, RedactedThinkingBlock)):
151
+ elif isinstance(block, (ThinkingBlock, RedactedThinkingBlock, ResponsesReasoningBlock)):
138
152
  if _preserve_reasoning_block(block, source_provider=source_provider, target_provider=target_provider):
139
153
  adapted.append(block)
140
154
  else:
@@ -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."""
@@ -47,6 +47,7 @@ class GeneralAgent(BaseAgent):
47
47
  usage_recorder: Optional[Any] = None,
48
48
  sub_agent_recorder: Optional[Any] = None,
49
49
  hook_dispatcher: Optional[Any] = None,
50
+ max_iterations: Optional[int] = None,
50
51
  ) -> None:
51
52
  """
52
53
  Initialize a new GeneralAgent instance.
@@ -99,6 +100,7 @@ class GeneralAgent(BaseAgent):
99
100
  usage_recorder=usage_recorder,
100
101
  sub_agent_recorder=sub_agent_recorder,
101
102
  hook_dispatcher=hook_dispatcher,
103
+ max_iterations=max_iterations,
102
104
  )
103
105
 
104
106
  # Full coder-style toolset, minus sub-agent dispatch (a dispatched agent
@@ -45,6 +45,7 @@ class InvestigationAgent(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 InvestigationAgent instance.
@@ -97,6 +98,7 @@ class InvestigationAgent(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(
@@ -44,6 +44,7 @@ class PlanningAgent(BaseAgent):
44
44
  usage_recorder: Optional[Any] = None,
45
45
  sub_agent_recorder: Optional[Any] = None,
46
46
  hook_dispatcher: Optional[Any] = None,
47
+ max_iterations: Optional[int] = None,
47
48
  ) -> None:
48
49
  super().__init__(
49
50
  project_path,
@@ -70,6 +71,7 @@ class PlanningAgent(BaseAgent):
70
71
  usage_recorder=usage_recorder,
71
72
  sub_agent_recorder=sub_agent_recorder,
72
73
  hook_dispatcher=hook_dispatcher,
74
+ max_iterations=max_iterations,
73
75
  )
74
76
 
75
77
  self._completed_plan: Optional[str] = None
@@ -13,6 +13,7 @@ from kolega_code.llm.models import (
13
13
  ImageBlock,
14
14
  Message,
15
15
  MessageHistory,
16
+ ResponsesReasoningBlock,
16
17
  TextBlock,
17
18
  ToolCall,
18
19
  ToolDefinition,
@@ -149,6 +150,32 @@ def test_to_responses_input_multiple_tool_outputs_before_image_followups():
149
150
  assert any(part.get("type") == "input_image" for part in items[2]["content"])
150
151
 
151
152
 
153
+ def test_to_responses_input_resends_reasoning_before_tool_call():
154
+ # A prior assistant turn carrying captured reasoning must resend it as a
155
+ # reasoning item that *precedes* the function_call it belongs to.
156
+ history = MessageHistory(
157
+ [
158
+ Message(
159
+ role="assistant",
160
+ content=[
161
+ ResponsesReasoningBlock(encrypted_content="ENC", summary=["thinking..."], item_id="rs_1"),
162
+ ToolCall(id="call_1", name="read_file", input={"path": "a.py"}),
163
+ ],
164
+ ),
165
+ ]
166
+ )
167
+ items = to_responses_input(history)
168
+ assert items[0] == {
169
+ "type": "reasoning",
170
+ "summary": [{"type": "summary_text", "text": "thinking..."}],
171
+ "encrypted_content": "ENC",
172
+ }
173
+ # The opaque server id is intentionally not resent (matches Codex, store=false).
174
+ assert "id" not in items[0]
175
+ assert items[1]["type"] == "function_call"
176
+ assert items[1]["call_id"] == "call_1"
177
+
178
+
152
179
  def test_responses_tools_flattens_function_shape():
153
180
  tool = ToolDefinition(
154
181
  name="read_file",
@@ -231,6 +258,9 @@ async def test_responses_stream_wrapper_text_tools_and_usage():
231
258
  assert tool_calls[0].input == {"path": "a.py"}
232
259
  assert message.usage_metadata["prompt_tokens"] == 12
233
260
  assert message.usage_metadata["completion_tokens"] == 4
261
+ # Messages must be tagged with the real provider so history adaptation treats
262
+ # them as openai_chatgpt (not the api-key "openai" provider).
263
+ assert message.usage_metadata["provider"] == "openai_chatgpt"
234
264
 
235
265
 
236
266
  @pytest.mark.asyncio
@@ -313,6 +343,86 @@ async def test_responses_stream_wrapper_max_tokens_stop_reason():
313
343
  assert message.stop_reason == "max_tokens"
314
344
 
315
345
 
346
+ @pytest.mark.asyncio
347
+ async def test_responses_stream_wrapper_captures_reasoning_for_continuity():
348
+ # With include=["reasoning.encrypted_content"], the backend emits a completed
349
+ # reasoning item carrying the encrypted blob. We capture it (leading the
350
+ # assistant message) so it round-trips into the next request for continuity.
351
+ completed = _ns(output=[], usage=None, status="completed", incomplete_details=None)
352
+ events = [
353
+ _ns(
354
+ type="response.output_item.done",
355
+ item=_ns(
356
+ type="reasoning",
357
+ id="rs_1",
358
+ encrypted_content="ENC",
359
+ summary=[_ns(type="summary_text", text="planning")],
360
+ ),
361
+ ),
362
+ _ns(
363
+ type="response.output_item.done",
364
+ item=_ns(type="function_call", id="fc_1", call_id="call_1", name="read_file", arguments='{"path": "a.py"}'),
365
+ ),
366
+ _ns(type="response.completed", response=completed),
367
+ ]
368
+ wrapper = ResponsesStreamWrapper(_FakeStream(events))
369
+ async with wrapper as stream:
370
+ async for _chunk in stream:
371
+ pass
372
+ message = await wrapper.get_final_message()
373
+
374
+ # Reasoning leads the assistant content so it precedes the tool call on resend.
375
+ assert isinstance(message.content[0], ResponsesReasoningBlock)
376
+ assert message.content[0].encrypted_content == "ENC"
377
+ assert message.content[0].summary == ["planning"]
378
+
379
+ items = to_responses_input(MessageHistory([message]))
380
+ assert items[0]["type"] == "reasoning"
381
+ assert items[0]["encrypted_content"] == "ENC"
382
+ assert items[1]["type"] == "function_call"
383
+
384
+
385
+ @pytest.mark.asyncio
386
+ async def test_responses_stream_wrapper_reasoning_from_final_output_fallback():
387
+ # Some backends populate reasoning only on the final response output, not via
388
+ # output_item.done events. The fallback path must still capture it.
389
+ completed = _ns(
390
+ output=[
391
+ _ns(type="reasoning", id="rs_1", encrypted_content="ENC2", summary=[]),
392
+ _ns(type="message", content=[_ns(type="output_text", text="hi")]),
393
+ ],
394
+ usage=None,
395
+ status="completed",
396
+ incomplete_details=None,
397
+ )
398
+ wrapper = ResponsesStreamWrapper(_FakeStream([_ns(type="response.completed", response=completed)]))
399
+ async with wrapper as stream:
400
+ async for _chunk in stream:
401
+ pass
402
+ message = await wrapper.get_final_message()
403
+ reasoning = [b for b in message.content if isinstance(b, ResponsesReasoningBlock)]
404
+ assert reasoning and reasoning[0].encrypted_content == "ENC2"
405
+ assert reasoning[0].summary == []
406
+
407
+
408
+ @pytest.mark.asyncio
409
+ async def test_responses_stream_wrapper_skips_reasoning_without_encrypted_content():
410
+ # A reasoning item lacking encrypted_content is useless for continuity (and
411
+ # would be rejected on resend), so it must be dropped.
412
+ completed = _ns(output=[], usage=None, status="completed", incomplete_details=None)
413
+ events = [
414
+ _ns(type="response.output_item.done", item=_ns(type="reasoning", id="rs_1", encrypted_content=None, summary=[])),
415
+ _ns(type="response.output_text.delta", delta="ok"),
416
+ _ns(type="response.completed", response=completed),
417
+ ]
418
+ wrapper = ResponsesStreamWrapper(_FakeStream(events))
419
+ async with wrapper as stream:
420
+ async for _chunk in stream:
421
+ pass
422
+ message = await wrapper.get_final_message()
423
+ assert not [b for b in message.content if isinstance(b, ResponsesReasoningBlock)]
424
+
425
+
316
426
  # --- provider request building (no network) -------------------------------------
317
427
 
318
428
 
@@ -347,12 +457,18 @@ async def test_provider_generate_builds_codex_shaped_request():
347
457
  )
348
458
 
349
459
  assert message.get_text_content() == "hi"
460
+ assert message.usage_metadata["provider"] == "openai_chatgpt"
350
461
  kwargs = fake.last_kwargs
351
462
  assert kwargs["model"] == "gpt-5.5"
352
463
  assert kwargs["store"] is False
353
464
  assert kwargs["stream"] is True # backend is SSE-only; generate streams too
354
465
  assert kwargs["instructions"] == "sys"
466
+ # summary="auto" so the backend streams a reasoning summary for the thinking
467
+ # display; this is independent of continuity, which rides on `include` below.
355
468
  assert kwargs["reasoning"] == {"effort": "high", "summary": "auto"}
469
+ # Reasoning continuity: ask the backend for the encrypted reasoning blob so it
470
+ # can be resent next turn (matches Codex; the cause of the long-thinking gap).
471
+ assert kwargs["include"] == ["reasoning.encrypted_content"]
356
472
  assert kwargs["tool_choice"] == "auto"
357
473
  assert kwargs["parallel_tool_calls"] is False
358
474
  assert "prompt_cache_key" in kwargs
@@ -361,6 +477,26 @@ async def test_provider_generate_builds_codex_shaped_request():
361
477
  assert kwargs["input"] == [{"role": "user", "content": [{"type": "input_text", "text": "hello"}]}]
362
478
 
363
479
 
480
+ @pytest.mark.asyncio
481
+ async def test_provider_generate_omits_reasoning_and_include_without_thinking():
482
+ provider = ChatGPTOAuthProvider(token_manager=ChatGPTTokenManager(_tokens()))
483
+ completed = _ns(
484
+ output=[_ns(type="message", content=[_ns(type="output_text", text="hi")])],
485
+ usage=None,
486
+ status="completed",
487
+ incomplete_details=None,
488
+ )
489
+ fake = _FakeResponses(_FakeStream([_ns(type="response.completed", response=completed)]))
490
+ provider.async_client = _ns(responses=fake)
491
+ await provider.generate(
492
+ MessageHistory([Message(role="user", content=[TextBlock(text="hello")])]),
493
+ params=GenerationParams(), # no thinking effort
494
+ model="gpt-5.5",
495
+ )
496
+ assert "reasoning" not in fake.last_kwargs
497
+ assert "include" not in fake.last_kwargs
498
+
499
+
364
500
  @pytest.mark.asyncio
365
501
  async def test_provider_stream_returns_wrapper():
366
502
  provider = ChatGPTOAuthProvider(token_manager=ChatGPTTokenManager(_tokens()))
@@ -318,8 +318,9 @@ class TestProviderInitialization:
318
318
 
319
319
  def test_provider_initialization_with_error(self):
320
320
  """Test that provider initialization errors are properly wrapped."""
321
- # Mock the provider class to raise an error during initialization
322
- with patch("kolega_code.llm.client.OpenAIProvider") as MockProvider:
321
+ # The api-key `openai` provider now uses the Responses provider, so mock
322
+ # that class to raise during initialization.
323
+ with patch("kolega_code.llm.client.OpenAIResponsesProvider") as MockProvider:
323
324
  MockProvider.side_effect = RuntimeError("Provider init failed")
324
325
 
325
326
  with pytest.raises(LLMInternalServerError) as exc_info:
@@ -0,0 +1,163 @@
1
+ """Tests for the api-key OpenAI Responses provider and its routing.
2
+
3
+ gpt-5.x reject ``function tools + reasoning_effort`` on Chat Completions, so the
4
+ api-key ``openai`` provider routes to the Responses API. The OpenAI-compatible
5
+ providers (fireworks, xai, …) keep using the Chat Completions ``OpenAIProvider``.
6
+ """
7
+
8
+ import types
9
+
10
+ import pytest
11
+
12
+ from kolega_code.llm.client import LLMClient
13
+ from kolega_code.llm.models import (
14
+ Message,
15
+ MessageHistory,
16
+ ResponsesReasoningBlock,
17
+ TextBlock,
18
+ ToolDefinition,
19
+ ToolParameter,
20
+ )
21
+ from kolega_code.llm.providers.models import GenerationParams
22
+ from kolega_code.llm.providers.openai import OpenAIProvider
23
+ from kolega_code.llm.providers.openai_responses import OpenAIResponsesProvider
24
+
25
+
26
+ def _ns(**kwargs):
27
+ return types.SimpleNamespace(**kwargs)
28
+
29
+
30
+ class _FakeStream:
31
+ def __init__(self, events):
32
+ self._events = events
33
+
34
+ def __aiter__(self):
35
+ async def gen():
36
+ for event in self._events:
37
+ yield event
38
+
39
+ return gen()
40
+
41
+
42
+ class _FakeResponses:
43
+ def __init__(self, result):
44
+ self._result = result
45
+ self.last_kwargs = None
46
+
47
+ async def create(self, **kwargs):
48
+ self.last_kwargs = kwargs
49
+ return self._result
50
+
51
+
52
+ # --- routing --------------------------------------------------------------------
53
+
54
+
55
+ def test_llmclient_routes_openai_to_responses_provider():
56
+ client = LLMClient(provider="openai", api_key="sk-test")
57
+ assert isinstance(client.provider, OpenAIResponsesProvider)
58
+ assert client.provider.provider_name == "openai"
59
+
60
+
61
+ def test_llmclient_routes_compatible_provider_to_chat_completions():
62
+ client = LLMClient(provider="fireworks", api_key="sk-test")
63
+ assert isinstance(client.provider, OpenAIProvider)
64
+ assert not isinstance(client.provider, OpenAIResponsesProvider)
65
+
66
+
67
+ # --- request building (no network) ----------------------------------------------
68
+
69
+
70
+ def test_build_request_is_responses_shaped_with_reasoning():
71
+ provider = OpenAIResponsesProvider(api_key="sk-test")
72
+ tool = ToolDefinition(
73
+ name="read_file",
74
+ description="Read a file",
75
+ parameters=[ToolParameter(name="path", type="string", description="path", required=True)],
76
+ )
77
+ params = GenerationParams(tools=[tool], thinking="high", max_completion_tokens=256, temperature=0.5)
78
+ request = provider._build_request(
79
+ MessageHistory([Message(role="user", content=[TextBlock(text="hello")])]),
80
+ Message(role="system", content=[TextBlock(text="sys")]),
81
+ params,
82
+ {"model": "gpt-5.5"},
83
+ )
84
+
85
+ assert request["model"] == "gpt-5.5"
86
+ assert request["stream"] is True
87
+ assert request["store"] is False
88
+ assert request["tool_choice"] == "auto"
89
+ assert request["instructions"] == "sys"
90
+ assert request["reasoning"] == {"effort": "high", "summary": "auto"}
91
+ assert request["include"] == ["reasoning.encrypted_content"]
92
+ assert request["tools"][0]["type"] == "function"
93
+ assert request["tools"][0]["name"] == "read_file"
94
+ assert request["input"] == [{"role": "user", "content": [{"type": "input_text", "text": "hello"}]}]
95
+ # The Responses path must NOT send Chat-Completions-only fields.
96
+ assert "temperature" not in request
97
+ assert "max_completion_tokens" not in request
98
+ assert "max_output_tokens" not in request
99
+
100
+
101
+ def test_build_request_default_model_and_no_reasoning_without_thinking():
102
+ provider = OpenAIResponsesProvider(api_key="sk-test")
103
+ request = provider._build_request(
104
+ MessageHistory([Message(role="user", content=[TextBlock(text="hi")])]),
105
+ None,
106
+ GenerationParams(),
107
+ {},
108
+ )
109
+ assert request["model"] == "gpt-5.5"
110
+ assert "reasoning" not in request
111
+ assert "include" not in request
112
+
113
+
114
+ # --- generate / stream (no network) ---------------------------------------------
115
+
116
+
117
+ @pytest.mark.asyncio
118
+ async def test_generate_tags_openai_provider_and_sends_include():
119
+ provider = OpenAIResponsesProvider(api_key="sk-test")
120
+ completed = _ns(
121
+ output=[_ns(type="message", content=[_ns(type="output_text", text="hi")])],
122
+ usage=_ns(input_tokens=3, output_tokens=2, total_tokens=5, input_tokens_details=None),
123
+ status="completed",
124
+ incomplete_details=None,
125
+ )
126
+ fake = _FakeResponses(_FakeStream([_ns(type="response.completed", response=completed)]))
127
+ provider.async_client = _ns(responses=fake)
128
+
129
+ msg = await provider.generate(
130
+ MessageHistory([Message(role="user", content=[TextBlock(text="hello")])]),
131
+ params=GenerationParams(thinking="medium"),
132
+ model="gpt-5.5",
133
+ )
134
+
135
+ assert msg.get_text_content() == "hi"
136
+ assert msg.usage_metadata["provider"] == "openai"
137
+ assert fake.last_kwargs["include"] == ["reasoning.encrypted_content"]
138
+ assert fake.last_kwargs["reasoning"] == {"effort": "medium", "summary": "auto"}
139
+
140
+
141
+ @pytest.mark.asyncio
142
+ async def test_stream_captures_reasoning_for_continuity():
143
+ provider = OpenAIResponsesProvider(api_key="sk-test")
144
+ completed = _ns(output=[], usage=None, status="completed", incomplete_details=None)
145
+ events = [
146
+ _ns(type="response.output_item.done", item=_ns(type="reasoning", id="rs_1", encrypted_content="ENC", summary=[])),
147
+ _ns(type="response.output_text.delta", delta="hi"),
148
+ _ns(type="response.completed", response=completed),
149
+ ]
150
+ provider.async_client = _ns(responses=_FakeResponses(_FakeStream(events)))
151
+
152
+ wrapper = await provider.stream(
153
+ MessageHistory([Message(role="user", content=[TextBlock(text="x")])]),
154
+ params=GenerationParams(thinking="high"),
155
+ model="gpt-5.5",
156
+ )
157
+ async with wrapper as stream:
158
+ async for _chunk in stream:
159
+ pass
160
+ message = await wrapper.get_final_message()
161
+
162
+ assert isinstance(message.content[0], ResponsesReasoningBlock)
163
+ assert message.content[0].encrypted_content == "ENC"
@@ -149,13 +149,17 @@ def test_google_gemini_3_pro_uses_thinking_level() -> None:
149
149
 
150
150
 
151
151
  def test_openai_reasoning_effort_is_sent_for_reasoning_models() -> None:
152
+ # The api-key `openai` provider's gpt-5.x models now use the Responses API
153
+ # (served by OpenAIResponsesProvider), so effort serializes to a nested
154
+ # reasoning object rather than a flat reasoning_effort field.
152
155
  provider = OpenAIProvider(api_key="test-key")
153
156
  generation_params = provider._prepare_generation_params(GenerationParams(thinking="high"))
154
157
  generation_params["model"] = "gpt-5.5"
155
158
 
156
159
  provider._apply_thinking_params(generation_params, GenerationParams(thinking="high"))
157
160
 
158
- assert generation_params["reasoning_effort"] == "high"
161
+ assert generation_params["reasoning"] == {"effort": "high", "summary": "auto"}
162
+ assert "reasoning_effort" not in generation_params
159
163
 
160
164
 
161
165
  def test_numeric_thinking_budget_is_rejected() -> None: