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.
- {kolega_code-0.7.2 → kolega_code-0.8.1}/.env.example +1 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/PKG-INFO +2 -1
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/concepts/agents.md +2 -2
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/concepts/tools.md +7 -4
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/configuration/environment-variables.md +5 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/configuration/providers-and-models.mdx +15 -4
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/tui/modes.md +2 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/__init__.py +4 -1
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/__init__.py +6 -1
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/baseagent.py +56 -17
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/browseragent.py +2 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/coder.py +2 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/conversation.py +217 -2
- kolega_code-0.8.1/kolega_code/agent/errors.py +9 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/generalagent.py +2 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/investigationagent.py +4 -2
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/planningagent.py +5 -1
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/agents/investigation.md.j2 +1 -1
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/agents/planning.md.j2 +5 -2
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_chatgpt_oauth_provider.py +196 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_client.py +143 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_error_boundary.py +3 -2
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_langfuse_normalization.py +17 -0
- kolega_code-0.8.1/kolega_code/agent/tests/llm/test_model_specs.py +51 -0
- kolega_code-0.8.1/kolega_code/agent/tests/llm/test_openai_message_conversion.py +140 -0
- kolega_code-0.8.1/kolega_code/agent/tests/llm/test_openai_responses_provider.py +163 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_openai_token_counting.py +25 -13
- kolega_code-0.8.1/kolega_code/agent/tests/llm/test_supports_vision.py +56 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_thinking_effort.py +31 -1
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_agent_tools_inventory.py +18 -2
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_base_agent.py +150 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_coder_attachments.py +175 -12
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compaction_persistence.py +3 -1
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_conversation_compaction.py +32 -1
- kolega_code-0.8.1/kolega_code/agent/tests/test_image_compat.py +476 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_planning_agent.py +26 -2
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_prompts.py +2 -0
- kolega_code-0.8.1/kolega_code/agent/tests/test_read_image_tool.py +161 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_tool_registry.py +4 -1
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_tools.py +7 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_agent_tool.py +29 -3
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/agent_tool.py +2 -0
- kolega_code-0.8.1/kolega_code/agent/tool_backend/read_image_tool.py +35 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/workflow_tool.py +3 -1
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tools.py +42 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/app.py +368 -43
- kolega_code-0.8.1/kolega_code/cli/clipboard_image.py +152 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/main.py +17 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/mentions.py +20 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/messages.py +13 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/provider_registry.py +6 -1
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/session_store.py +7 -2
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/slash_commands.py +2 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_app.py +246 -14
- kolega_code-0.8.1/kolega_code/cli/tests/test_app_compaction_restore.py +144 -0
- kolega_code-0.8.1/kolega_code/cli/tests/test_app_image_vision_warning.py +563 -0
- kolega_code-0.8.1/kolega_code/cli/tests/test_app_model_switch_vision.py +214 -0
- kolega_code-0.8.1/kolega_code/cli/tests/test_clipboard_image.py +185 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_mentions.py +30 -0
- kolega_code-0.8.1/kolega_code/cli/tests/test_provider_registry.py +19 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_session_store.py +24 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/client.py +6 -2
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/instrumented_client.py +3 -3
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/models.py +273 -76
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/providers/anthropic.py +1 -1
- kolega_code-0.8.1/kolega_code/llm/providers/chatgpt_oauth.py +103 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/providers/openai.py +33 -8
- kolega_code-0.8.1/kolega_code/llm/providers/openai_responses.py +60 -0
- 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
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/specs.py +147 -19
- kolega_code-0.8.1/kolega_code/utils/__init__.py +1 -0
- kolega_code-0.8.1/kolega_code/utils/images.py +140 -0
- kolega_code-0.8.1/kolega_code/utils/tests/__init__.py +0 -0
- kolega_code-0.8.1/kolega_code/utils/tests/test_images.py +90 -0
- kolega_code-0.8.1/kolega_code/utils/tests/test_images_ascii.py +97 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/pyproject.toml +2 -1
- {kolega_code-0.7.2 → kolega_code-0.8.1}/uv.lock +91 -2
- kolega_code-0.7.2/kolega_code/agent/tests/llm/test_model_specs.py +0 -31
- kolega_code-0.7.2/kolega_code/agent/tests/llm/test_openai_message_conversion.py +0 -30
- {kolega_code-0.7.2 → kolega_code-0.8.1}/.github/workflows/ci.yml +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/.github/workflows/docs.yml +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/.github/workflows/release.yml +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/.gitignore +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/CONTRIBUTING.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/LICENSE +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/README.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/RELEASING.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/SECURITY.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/conftest.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/.gitignore +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/README.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/astro.config.mjs +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/package-lock.json +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/package.json +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/public/favicon.svg +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/assets/demo.gif +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/assets/kolega-dark.svg +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/assets/kolega-light.svg +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/cli/ask.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/cli/doctor.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/cli/overview.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/cli/sessions.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/concepts/how-it-works.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/configuration/settings-and-api-keys.mdx +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/configuration/sign-in-with-chatgpt.mdx +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/getting-started/installation.mdx +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/getting-started/quick-start.mdx +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/gigacode.mdx +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/hooks.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/index.mdx +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/skills.mdx +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/tui/composer.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/tui/interface.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/tui/sessions-and-resume.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/tui/slash-commands.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content.config.ts +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/styles/brand.css +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/docs/tsconfig.json +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/common.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/compression.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/context.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/budget.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/errors.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/executor.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/guide.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/journal.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/runtime.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/tests/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/tests/test_orchestration.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/orchestration/types.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_provider.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/auxiliary/compression/summary.system.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/auxiliary/compression/summary.user.md.j2 +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/auxiliary/terminal/output_summary.system.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/auxiliary/terminal/safety.system.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/auxiliary/tools/think_hard.system.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/extensions/cli/planning_questions.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/extensions/cli/shared_task_list.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/extensions/skills/catalog.md.j2 +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/agents/browser.md.j2 +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/agents/coder_cli.md.j2 +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/agents/general.md.j2 +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/agents_md_instructions.md +0 -0
- {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
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/frontend_guidance.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/expo.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/html_website.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/mern_stack.md +0 -0
- {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
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/user_tasks/cli/implement_plan.md.j2 +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompt_templates/user_tasks/cli/init_agents.md.j2 +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/prompts.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/compaction_helpers.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_anthropic_token_counting.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_billing_openai_cache.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_chatgpt_live.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_dashscope_mapping.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_exceptions.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_google_thought_signature.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_google_tool_signature_live.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_instrumented_client.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_instrumented_client_integration.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_live_providers.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_openai_cached_tokens.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_openai_cached_tokens_stream.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/llm/test_tool_execution_ids.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/services/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_browser.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_browser_parity.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_file_system.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_sandbox_terminal_input.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_terminal.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_terminal_buffer.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/services/test_terminal_state_serializer.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_coder_prompt_extensions.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_commands.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compaction_integration.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compaction_subagent.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compaction_tui_events.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compress_command.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_compression.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_duplicate_tool_results.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_empty_message_handling.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_event_emitter.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_general_agent.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_gigacode_gating.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_hooks_integration.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_html.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_parallel_tool_calls.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_permissions.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/test_prompt_provider.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_base_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_browser_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_build_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_create_file_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_edit_preview.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_glob_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_glob_tool_sandbox_parity.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_list_directory_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_read_file_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_replace_entire_file_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_replace_lines_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_search_and_replace_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_search_codebase_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_terminal_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_think_hard_integration.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_think_hard_streaming.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_web_fetch_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_web_search_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tests/tool_backend/test_workflow_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/base_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/browser_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/build_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/create_file_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/edit_preview.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/glob_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/list_directory_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/memory_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/read_file_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/replace_entire_file_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/replace_lines_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_and_replace_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/base.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/duckduckgo.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/errors.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/firecrawl.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/models.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/searxng.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_backends/tavily.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/search_codebase_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/streaming_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/terminal_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/think_hard_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/web_fetch_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/tool_backend/web_search_tool.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/agent/utils/commands.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/auth/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/auth/_jwt.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/auth/chatgpt_oauth.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/auth/constants.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/auth/tests/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/auth/tests/test_chatgpt_oauth.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/auth/tests/test_tokens.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/auth/tokens.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/config.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/connection.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/file_index.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/settings.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/skills.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_cli_config.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_connection.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_file_index.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_main.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_settings.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_skills.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_slash_commands.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_themes.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/tests/test_updater.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/theme.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/cli/updater.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/config.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/events.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/backends.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/config.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/dispatcher.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/events.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/matcher.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/outcome.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/tests/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/hooks/tests/test_hooks.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/exceptions.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/providers/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/providers/base.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/providers/google.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/providers/models.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/ratelimit.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/llm/tool_execution_ids.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/models/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/models/sandbox_terminal_state.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/permissions.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/runtime.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/README.md +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/async_filesystem.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/base.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/browser.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/event_loop.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/filesystem.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/local.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/serializer.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/terminal.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/sandbox/utils.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/services/base.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/services/browser.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/services/file_system.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/services/html.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/services/terminal.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/services/terminal_buffer.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/tools/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/tools/core.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/tools/definitions.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/tools/registry.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/tools/tests/__init__.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/kolega_code/tools/tests/test_definitions.py +0 -0
- {kolega_code-0.7.2 → kolega_code-0.8.1}/run_tests.sh +0 -0
- {kolega_code-0.7.2 → 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.
|
|
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
|
|
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 —
|
|
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)
|
|
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
|
|
70
|
-
planning agent can look but
|
|
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
|
{kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/configuration/environment-variables.md
RENAMED
|
@@ -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
|
|
{kolega_code-0.7.2 → kolega_code-0.8.1}/docs/src/content/docs/configuration/providers-and-models.mdx
RENAMED
|
@@ -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
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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.
|
|
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.
|
|
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
|
|
518
|
+
if model_supports_vision(provider, self.primary_model_config.model):
|
|
489
519
|
return None
|
|
490
520
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
-
#
|
|
522
|
-
|
|
523
|
-
fixed_history =
|
|
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
|
-
#
|
|
1308
|
-
|
|
1309
|
-
fixed_history =
|
|
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
|
|
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
|