kolega-code 0.6.0__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kolega_code-0.6.0 → kolega_code-0.7.0}/PKG-INFO +13 -3
- {kolega_code-0.6.0 → kolega_code-0.7.0}/README.md +7 -2
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/concepts/tools.md +8 -2
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/configuration/environment-variables.md +15 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/configuration/settings-and-api-keys.mdx +15 -3
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/index.mdx +3 -2
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/__init__.py +1 -1
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/__init__.py +1 -1
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_agent_tools_inventory.py +1 -0
- kolega_code-0.7.0/kolega_code/agent/tests/tool_backend/test_web_search_tool.py +366 -0
- kolega_code-0.7.0/kolega_code/agent/tool_backend/search_backends/__init__.py +83 -0
- kolega_code-0.7.0/kolega_code/agent/tool_backend/search_backends/base.py +58 -0
- kolega_code-0.7.0/kolega_code/agent/tool_backend/search_backends/duckduckgo.py +56 -0
- kolega_code-0.7.0/kolega_code/agent/tool_backend/search_backends/errors.py +23 -0
- kolega_code-0.7.0/kolega_code/agent/tool_backend/search_backends/firecrawl.py +117 -0
- kolega_code-0.7.0/kolega_code/agent/tool_backend/search_backends/models.py +28 -0
- kolega_code-0.7.0/kolega_code/agent/tool_backend/search_backends/searxng.py +62 -0
- kolega_code-0.7.0/kolega_code/agent/tool_backend/search_backends/tavily.py +70 -0
- kolega_code-0.7.0/kolega_code/agent/tool_backend/web_search_tool.py +114 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tools.py +29 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/app.py +99 -1
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/config.py +36 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/settings.py +13 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/tests/test_app.py +84 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/tests/test_cli_config.py +67 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/tests/test_settings.py +35 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/config.py +12 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/pyproject.toml +6 -1
- {kolega_code-0.6.0 → kolega_code-0.7.0}/uv.lock +829 -2
- {kolega_code-0.6.0 → kolega_code-0.7.0}/.env.example +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/.github/workflows/ci.yml +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/.github/workflows/docs.yml +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/.github/workflows/release.yml +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/.gitignore +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/CONTRIBUTING.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/LICENSE +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/RELEASING.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/SECURITY.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/conftest.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/.gitignore +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/README.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/astro.config.mjs +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/package-lock.json +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/package.json +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/public/favicon.svg +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/assets/demo.gif +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/assets/kolega-dark.svg +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/assets/kolega-light.svg +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/cli/ask.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/cli/doctor.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/cli/overview.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/cli/sessions.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/concepts/agents.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/concepts/how-it-works.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/configuration/providers-and-models.mdx +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/getting-started/installation.mdx +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/getting-started/quick-start.mdx +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/gigacode.mdx +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/hooks.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/skills.mdx +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/tui/composer.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/tui/interface.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/tui/modes.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/tui/sessions-and-resume.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/tui/slash-commands.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content.config.ts +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/styles/brand.css +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/docs/tsconfig.json +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/baseagent.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/browseragent.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/coder.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/common.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/compression.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/context.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/conversation.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/generalagent.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/investigationagent.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/orchestration/__init__.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/orchestration/budget.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/orchestration/errors.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/orchestration/executor.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/orchestration/guide.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/orchestration/journal.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/orchestration/runtime.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/orchestration/tests/__init__.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/orchestration/tests/test_orchestration.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/orchestration/types.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/planningagent.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_provider.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/auxiliary/compression/summary.system.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/auxiliary/compression/summary.user.md.j2 +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/auxiliary/terminal/output_summary.system.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/auxiliary/terminal/safety.system.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/auxiliary/tools/think_hard.system.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/extensions/cli/planning_questions.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/extensions/cli/shared_task_list.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/extensions/skills/catalog.md.j2 +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/system/agents/browser.md.j2 +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/system/agents/coder_cli.md.j2 +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/system/agents/general.md.j2 +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/system/agents/investigation.md.j2 +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/system/agents/planning.md.j2 +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/system/includes/agents_md_instructions.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/system/includes/environment_variables/workspace_env_vars.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/system/includes/frontend_guidance.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/system/includes/starter_templates/expo.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/system/includes/starter_templates/html_website.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/system/includes/starter_templates/mern_stack.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/system/includes/starter_templates/react_vite_shadcdn.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/user_tasks/cli/implement_plan.md.j2 +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompt_templates/user_tasks/cli/init_agents.md.j2 +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/prompts.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/__init__.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/__init__.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_anthropic_token_counting.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_billing_openai_cache.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_client.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_dashscope_mapping.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_error_boundary.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_exceptions.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_google_thought_signature.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_google_tool_signature_live.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_instrumented_client.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_instrumented_client_integration.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_langfuse_normalization.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_live_providers.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_model_specs.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_openai_cached_tokens.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_openai_cached_tokens_stream.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_openai_message_conversion.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_openai_token_counting.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_thinking_effort.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/llm/test_tool_execution_ids.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/services/__init__.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/services/test_browser.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/services/test_browser_parity.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/services/test_file_system.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/services/test_sandbox_terminal_input.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/services/test_terminal.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/services/test_terminal_buffer.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/services/test_terminal_state_serializer.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_base_agent.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_coder_attachments.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_coder_prompt_extensions.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_commands.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_duplicate_tool_results.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_empty_message_handling.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_event_emitter.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_general_agent.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_gigacode_gating.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_hooks_integration.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_html.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_parallel_tool_calls.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_permissions.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_planning_agent.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_prompt_provider.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_prompts.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_tool_registry.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/test_tools.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/__init__.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_agent_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_base_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_browser_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_build_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_create_file_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_edit_preview.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_glob_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_glob_tool_sandbox_parity.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_list_directory_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_read_file_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_replace_entire_file_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_replace_lines_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_search_and_replace_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_search_codebase_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_terminal_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_think_hard_integration.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_think_hard_streaming.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_web_fetch_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tests/tool_backend/test_workflow_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/agent_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/base_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/browser_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/build_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/create_file_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/edit_preview.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/glob_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/list_directory_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/memory_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/read_file_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/replace_entire_file_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/replace_lines_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/search_and_replace_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/search_codebase_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/streaming_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/terminal_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/think_hard_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/web_fetch_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/tool_backend/workflow_tool.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/agent/utils/commands.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/__init__.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/connection.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/file_index.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/main.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/mentions.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/messages.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/provider_registry.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/session_store.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/skills.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/slash_commands.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/tests/__init__.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/tests/test_connection.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/tests/test_file_index.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/tests/test_main.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/tests/test_mentions.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/tests/test_session_store.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/tests/test_skills.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/tests/test_slash_commands.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/tests/test_themes.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/tests/test_updater.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/theme.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/cli/updater.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/events.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/hooks/__init__.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/hooks/backends.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/hooks/config.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/hooks/dispatcher.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/hooks/events.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/hooks/matcher.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/hooks/outcome.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/hooks/tests/__init__.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/hooks/tests/test_hooks.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/llm/client.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/llm/exceptions.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/llm/instrumented_client.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/llm/models.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/llm/providers/__init__.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/llm/providers/anthropic.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/llm/providers/base.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/llm/providers/google.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/llm/providers/models.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/llm/providers/openai.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/llm/ratelimit.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/llm/specs.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/llm/tool_execution_ids.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/models/__init__.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/models/sandbox_terminal_state.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/permissions.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/runtime.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/sandbox/README.md +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/sandbox/__init__.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/sandbox/async_filesystem.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/sandbox/base.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/sandbox/browser.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/sandbox/event_loop.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/sandbox/filesystem.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/sandbox/local.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/sandbox/serializer.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/sandbox/terminal.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/sandbox/utils.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/services/base.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/services/browser.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/services/file_system.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/services/html.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/services/terminal.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/services/terminal_buffer.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/tools/__init__.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/tools/core.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/tools/definitions.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/tools/registry.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/tools/tests/__init__.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/kolega_code/tools/tests/test_definitions.py +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/run_tests.sh +0 -0
- {kolega_code-0.6.0 → kolega_code-0.7.0}/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.7.0
|
|
4
4
|
Summary: Local-first AI coding agent for the terminal
|
|
5
5
|
Project-URL: Homepage, https://kolega-ai.github.io/kolega-code/
|
|
6
6
|
Project-URL: Documentation, https://kolega-ai.github.io/kolega-code/
|
|
@@ -43,9 +43,13 @@ Classifier: Topic :: Utilities
|
|
|
43
43
|
Requires-Python: >=3.11
|
|
44
44
|
Requires-Dist: anthropic==0.101.0
|
|
45
45
|
Requires-Dist: beautifulsoup4==4.14.3
|
|
46
|
+
Requires-Dist: ddgs==9.14.4
|
|
47
|
+
Requires-Dist: firecrawl-py==4.28.2
|
|
46
48
|
Requires-Dist: google-genai==2.0.1
|
|
49
|
+
Requires-Dist: httpx==0.28.1
|
|
47
50
|
Requires-Dist: jinja2==3.1.6
|
|
48
51
|
Requires-Dist: langfuse==3.14.5
|
|
52
|
+
Requires-Dist: lxml-html-clean==0.4.4
|
|
49
53
|
Requires-Dist: nest-asyncio==1.6.0
|
|
50
54
|
Requires-Dist: openai==1.109.1
|
|
51
55
|
Requires-Dist: packaging==25.0
|
|
@@ -55,6 +59,7 @@ Requires-Dist: psutil==7.2.2
|
|
|
55
59
|
Requires-Dist: pydantic==2.13.4
|
|
56
60
|
Requires-Dist: python-dotenv==1.2.2
|
|
57
61
|
Requires-Dist: pyyaml==6.0.3
|
|
62
|
+
Requires-Dist: tavily-python==0.7.26
|
|
58
63
|
Requires-Dist: tenacity==9.1.4
|
|
59
64
|
Requires-Dist: textual==8.2.5
|
|
60
65
|
Requires-Dist: tiktoken==0.12.0
|
|
@@ -103,8 +108,9 @@ sessions, settings, and API keys stay on your own machine.
|
|
|
103
108
|
and applies precise edits.
|
|
104
109
|
- **Runs commands.** Executes shell commands and watches their output in a dedicated terminal view.
|
|
105
110
|
- **Plans before it builds.** Plan mode investigates and proposes; Build mode executes.
|
|
106
|
-
- **
|
|
107
|
-
|
|
111
|
+
- **Searches and browses the web.** The agent can search for relevant pages, fetch URLs
|
|
112
|
+
directly, or use the built-in browser agent (powered by Playwright) when a task needs
|
|
113
|
+
site interaction.
|
|
108
114
|
- **Dispatches sub-agents.** Hands work off to specialized agents and tracks their activity live.
|
|
109
115
|
- **Orchestrates workflows.** With [gigacode](https://kolega-ai.github.io/kolega-code/gigacode/),
|
|
110
116
|
it fans out many sub-agents in parallel.
|
|
@@ -187,6 +193,10 @@ Settings. Local session state lives under your platform's state directory unless
|
|
|
187
193
|
[Configuration docs](https://kolega-ai.github.io/kolega-code/configuration/settings-and-api-keys/)
|
|
188
194
|
for the full story.
|
|
189
195
|
|
|
196
|
+
The `web_search` tool uses DuckDuckGo by default without a key. To choose another backend,
|
|
197
|
+
set it in **Settings** or export `KOLEGA_CODE_WEB_SEARCH_BACKEND` as `firecrawl`, `tavily`,
|
|
198
|
+
or `searxng`; use `FIRECRAWL_API_KEY`, `TAVILY_API_KEY`, or `SEARXNG_BASE_URL` as needed.
|
|
199
|
+
|
|
190
200
|
## Requirements
|
|
191
201
|
|
|
192
202
|
- **Python 3.11+**
|
|
@@ -35,8 +35,9 @@ sessions, settings, and API keys stay on your own machine.
|
|
|
35
35
|
and applies precise edits.
|
|
36
36
|
- **Runs commands.** Executes shell commands and watches their output in a dedicated terminal view.
|
|
37
37
|
- **Plans before it builds.** Plan mode investigates and proposes; Build mode executes.
|
|
38
|
-
- **
|
|
39
|
-
|
|
38
|
+
- **Searches and browses the web.** The agent can search for relevant pages, fetch URLs
|
|
39
|
+
directly, or use the built-in browser agent (powered by Playwright) when a task needs
|
|
40
|
+
site interaction.
|
|
40
41
|
- **Dispatches sub-agents.** Hands work off to specialized agents and tracks their activity live.
|
|
41
42
|
- **Orchestrates workflows.** With [gigacode](https://kolega-ai.github.io/kolega-code/gigacode/),
|
|
42
43
|
it fans out many sub-agents in parallel.
|
|
@@ -119,6 +120,10 @@ Settings. Local session state lives under your platform's state directory unless
|
|
|
119
120
|
[Configuration docs](https://kolega-ai.github.io/kolega-code/configuration/settings-and-api-keys/)
|
|
120
121
|
for the full story.
|
|
121
122
|
|
|
123
|
+
The `web_search` tool uses DuckDuckGo by default without a key. To choose another backend,
|
|
124
|
+
set it in **Settings** or export `KOLEGA_CODE_WEB_SEARCH_BACKEND` as `firecrawl`, `tavily`,
|
|
125
|
+
or `searxng`; use `FIRECRAWL_API_KEY`, `TAVILY_API_KEY`, or `SEARXNG_BASE_URL` as needed.
|
|
126
|
+
|
|
122
127
|
## Requirements
|
|
123
128
|
|
|
124
129
|
- **Python 3.11+**
|
|
@@ -38,8 +38,14 @@ the [TUI](../../cli/overview/) or [`ask`](../../cli/ask/).
|
|
|
38
38
|
|
|
39
39
|
### Web
|
|
40
40
|
|
|
41
|
+
- `web_search` — search the web for relevant pages and return ranked results.
|
|
41
42
|
- `web_fetch` — fetch and parse a web page without a full browser.
|
|
42
43
|
|
|
44
|
+
Use `web_search` when you do not already know the right URL, then follow up with
|
|
45
|
+
`web_fetch` to read a result in depth. The default backend is keyless DuckDuckGo;
|
|
46
|
+
Firecrawl, Tavily, and self-hosted SearXNG can be selected in Settings or with
|
|
47
|
+
environment variables.
|
|
48
|
+
|
|
43
49
|
### Reasoning & memory
|
|
44
50
|
|
|
45
51
|
- `think_hard` — an extended-reasoning step that uses the
|
|
@@ -57,8 +63,8 @@ and `dispatch_general_agent`.
|
|
|
57
63
|
Tools are gated by mode. In a read-only context — like [Plan mode](../../tui/modes/)
|
|
58
64
|
or an investigation sub-agent — only non-mutating tools are available
|
|
59
65
|
(`list_directory`, `read_entire_file`, `read_file_section`, `search_codebase`,
|
|
60
|
-
`find_files_by_pattern`, `web_fetch`, `think_hard`, and reading
|
|
61
|
-
files and running commands require Build mode's full toolset.
|
|
66
|
+
`find_files_by_pattern`, `web_search`, `web_fetch`, `think_hard`, and reading
|
|
67
|
+
memory). Editing files and running commands require Build mode's full toolset.
|
|
62
68
|
|
|
63
69
|
This separation is what makes Plan mode safe to run against any codebase: the
|
|
64
70
|
planning agent can look but not touch.
|
{kolega_code-0.6.0 → kolega_code-0.7.0}/docs/src/content/docs/configuration/environment-variables.md
RENAMED
|
@@ -87,6 +87,18 @@ section.
|
|
|
87
87
|
| `KOLEGA_CODE_STATE_DIR` | Override where settings and sessions are stored |
|
|
88
88
|
| `KOLEGA_CODE_ENVIRONMENT` | Environment label attached to tracing/metadata (default `development`) |
|
|
89
89
|
|
|
90
|
+
## Web search
|
|
91
|
+
|
|
92
|
+
The `web_search` tool defaults to the keyless DuckDuckGo backend. Set a backend
|
|
93
|
+
explicitly when you want a cloud provider or a self-hosted SearXNG instance.
|
|
94
|
+
|
|
95
|
+
| Variable | Purpose |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| `KOLEGA_CODE_WEB_SEARCH_BACKEND` | Backend for `web_search`: `duckduckgo`, `firecrawl`, `tavily`, or `searxng` |
|
|
98
|
+
| `FIRECRAWL_API_KEY` | Optional Firecrawl key for higher rate limits |
|
|
99
|
+
| `TAVILY_API_KEY` | Tavily API key |
|
|
100
|
+
| `SEARXNG_BASE_URL` | Base URL for a self-hosted SearXNG instance |
|
|
101
|
+
|
|
90
102
|
## Telemetry (Langfuse)
|
|
91
103
|
|
|
92
104
|
Optional [Langfuse](https://langfuse.com/) tracing of LLM usage.
|
|
@@ -113,6 +125,9 @@ KOLEGA_CODE_PROVIDER=moonshot
|
|
|
113
125
|
KOLEGA_CODE_MODEL=kimi-k2.7-code
|
|
114
126
|
KOLEGA_CODE_THINKING_EFFORT=auto
|
|
115
127
|
|
|
128
|
+
# Optional: web search
|
|
129
|
+
KOLEGA_CODE_WEB_SEARCH_BACKEND=duckduckgo
|
|
130
|
+
|
|
116
131
|
# Optional: Langfuse tracing
|
|
117
132
|
LANGFUSE_HOST=https://us.cloud.langfuse.com
|
|
118
133
|
LANGFUSE_PUBLIC_KEY=
|
|
@@ -32,6 +32,13 @@ agents (planning, building, investigation, general, browser) their own provider,
|
|
|
32
32
|
model, and effort. Leave a role on **Default** to inherit the active model. See
|
|
33
33
|
[Per-agent models](../providers-and-models/#per-agent-models).
|
|
34
34
|
|
|
35
|
+
The **Web Search** section configures the `web_search` tool. DuckDuckGo is the
|
|
36
|
+
default and needs no key. Firecrawl can also run without a key, with an optional
|
|
37
|
+
key for higher rate limits. Tavily requires a key, and SearXNG requires the base
|
|
38
|
+
URL of your self-hosted instance. The same settings can be supplied with
|
|
39
|
+
[`KOLEGA_CODE_WEB_SEARCH_BACKEND`, `FIRECRAWL_API_KEY`, `TAVILY_API_KEY`, and
|
|
40
|
+
`SEARXNG_BASE_URL`](../environment-variables/#web-search).
|
|
41
|
+
|
|
35
42
|
## Where settings live
|
|
36
43
|
|
|
37
44
|
Settings are stored as JSON in your platform's state directory, in a file named
|
|
@@ -56,7 +63,8 @@ The file looks like this:
|
|
|
56
63
|
"active_thinking_effort": "auto",
|
|
57
64
|
"api_keys": {
|
|
58
65
|
"moonshot": "sk-...",
|
|
59
|
-
"deepseek": "sk-..."
|
|
66
|
+
"deepseek": "sk-...",
|
|
67
|
+
"tavily": "tvly-..."
|
|
60
68
|
},
|
|
61
69
|
"agent_models": {
|
|
62
70
|
"investigation": {
|
|
@@ -64,13 +72,17 @@ The file looks like this:
|
|
|
64
72
|
"model": "deepseek-v4-flash",
|
|
65
73
|
"thinking_effort": "high"
|
|
66
74
|
}
|
|
67
|
-
}
|
|
75
|
+
},
|
|
76
|
+
"web_search_backend": "tavily",
|
|
77
|
+
"web_search_base_url": null
|
|
68
78
|
}
|
|
69
79
|
```
|
|
70
80
|
|
|
71
81
|
`agent_models` holds per-agent overrides keyed by role (`planning`, `building`,
|
|
72
82
|
`investigation`, `general`, `browser`); a role that is absent inherits the active
|
|
73
83
|
model. The field is optional and older `schema_version` files load unchanged.
|
|
84
|
+
Cloud web-search keys are stored in `api_keys` under their backend names
|
|
85
|
+
(`firecrawl` or `tavily`); `web_search_base_url` is used by SearXNG.
|
|
74
86
|
|
|
75
87
|
<Aside type="caution" title="Keys are stored in plaintext">
|
|
76
88
|
API keys are written to `settings.json` in plaintext, but the file is created with
|
|
@@ -83,7 +95,7 @@ store keys on disk at all, supply them via
|
|
|
83
95
|
|
|
84
96
|
<FileTree>
|
|
85
97
|
- kolega-code/
|
|
86
|
-
- settings.json Provider, model, thinking effort, and API keys (0600)
|
|
98
|
+
- settings.json Provider, model, web search, thinking effort, and API keys (0600)
|
|
87
99
|
- sessions/
|
|
88
100
|
- <session-id>.json One file per saved session
|
|
89
101
|
</FileTree>
|
|
@@ -19,8 +19,9 @@ and your sessions, settings, and API keys are stored on your own machine.
|
|
|
19
19
|
dedicated terminal view in the UI.
|
|
20
20
|
- **Plans before it builds.** A read-only **Plan mode** produces a reviewable plan
|
|
21
21
|
and a shared task list; **Build mode** implements it.
|
|
22
|
-
- **
|
|
23
|
-
|
|
22
|
+
- **Searches and browses the web.** The agent can search for relevant pages,
|
|
23
|
+
fetch URLs directly, or use the built-in browser agent (powered by Playwright)
|
|
24
|
+
when a task needs site interaction.
|
|
24
25
|
- **Dispatches sub-agents.** For larger tasks, the main agent can hand off to
|
|
25
26
|
specialized agents (investigation, browser, coding, general) and track their
|
|
26
27
|
activity live.
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
from types import SimpleNamespace
|
|
2
|
+
from unittest.mock import AsyncMock, Mock, patch
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from kolega_code.agent.tool_backend.search_backends import (
|
|
8
|
+
DEFAULT_BACKEND,
|
|
9
|
+
SearchBackendError,
|
|
10
|
+
SearchBackendNotConfigured,
|
|
11
|
+
SearchBackendRateLimited,
|
|
12
|
+
SearchBackendUnavailable,
|
|
13
|
+
SearchResponse,
|
|
14
|
+
SearchResult,
|
|
15
|
+
available_backends,
|
|
16
|
+
backend_names,
|
|
17
|
+
build_search_backend,
|
|
18
|
+
get_backend_class,
|
|
19
|
+
)
|
|
20
|
+
from kolega_code.agent.tool_backend.search_backends.base import clamp_results
|
|
21
|
+
from kolega_code.agent.tool_backend.web_search_tool import WebSearchTool
|
|
22
|
+
from kolega_code.config import AgentConfig, ModelConfig, ModelProvider
|
|
23
|
+
|
|
24
|
+
_DUCK = "kolega_code.agent.tool_backend.search_backends.duckduckgo"
|
|
25
|
+
_TAVILY = "kolega_code.agent.tool_backend.search_backends.tavily"
|
|
26
|
+
_FIRE = "kolega_code.agent.tool_backend.search_backends.firecrawl"
|
|
27
|
+
_SEARX = "kolega_code.agent.tool_backend.search_backends.searxng"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# --------------------------------------------------------------------------- registry
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_registry_lists_all_backends() -> None:
|
|
34
|
+
assert set(backend_names()) == {"duckduckgo", "firecrawl", "tavily", "searxng"}
|
|
35
|
+
# Default backend is first in the TUI option list and is keyless.
|
|
36
|
+
labels = available_backends()
|
|
37
|
+
assert labels[0][1] == DEFAULT_BACKEND == "duckduckgo"
|
|
38
|
+
assert get_backend_class("duckduckgo").requires_api_key is False
|
|
39
|
+
# Optional-key model: Firecrawl is keyless-capable but accepts a key for higher limits;
|
|
40
|
+
# Tavily requires one; DuckDuckGo/SearXNG take no key.
|
|
41
|
+
assert get_backend_class("firecrawl").requires_api_key is False
|
|
42
|
+
assert get_backend_class("firecrawl").accepts_api_key is True
|
|
43
|
+
assert get_backend_class("tavily").requires_api_key is True
|
|
44
|
+
assert get_backend_class("tavily").accepts_api_key is True
|
|
45
|
+
assert get_backend_class("duckduckgo").accepts_api_key is False
|
|
46
|
+
assert get_backend_class("searxng").accepts_api_key is False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_build_unknown_backend_raises_typed_error() -> None:
|
|
50
|
+
with pytest.raises(SearchBackendError, match="Unknown web_search backend"):
|
|
51
|
+
build_search_backend("nope")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@pytest.mark.parametrize(("requested", "expected"), [(-3, 1), (0, 1), (5, 5), (50, 10), ("bad", 5)])
|
|
55
|
+
def test_clamp_results(requested, expected) -> None:
|
|
56
|
+
assert clamp_results(requested) == expected
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ------------------------------------------------------------------------- duckduckgo
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class _DummyDDGS:
|
|
63
|
+
def __init__(self, rows=None, exc=None):
|
|
64
|
+
self._rows = rows or []
|
|
65
|
+
self._exc = exc
|
|
66
|
+
|
|
67
|
+
def __enter__(self):
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
def __exit__(self, *args):
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
def text(self, query, max_results=5):
|
|
74
|
+
if self._exc:
|
|
75
|
+
raise self._exc
|
|
76
|
+
return self._rows
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@pytest.mark.asyncio
|
|
80
|
+
async def test_duckduckgo_maps_rows() -> None:
|
|
81
|
+
rows = [{"title": "T", "href": "https://a.test", "body": "snippet"}, {"title": "", "href": "", "body": "x"}]
|
|
82
|
+
with patch(f"{_DUCK}.DDGS", lambda: _DummyDDGS(rows=rows)):
|
|
83
|
+
response = await build_search_backend("duckduckgo").search("q", max_results=3)
|
|
84
|
+
assert response.backend == "duckduckgo"
|
|
85
|
+
# The second row has no href and is dropped.
|
|
86
|
+
assert [r.url for r in response.results] == ["https://a.test"]
|
|
87
|
+
assert response.results[0].snippet == "snippet"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@pytest.mark.asyncio
|
|
91
|
+
async def test_duckduckgo_rate_limit_mapped() -> None:
|
|
92
|
+
from ddgs.exceptions import RatelimitException
|
|
93
|
+
|
|
94
|
+
with patch(f"{_DUCK}.DDGS", lambda: _DummyDDGS(exc=RatelimitException("429"))):
|
|
95
|
+
with pytest.raises(SearchBackendRateLimited):
|
|
96
|
+
await build_search_backend("duckduckgo").search("q")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.mark.asyncio
|
|
100
|
+
async def test_duckduckgo_generic_error_unavailable() -> None:
|
|
101
|
+
with patch(f"{_DUCK}.DDGS", lambda: _DummyDDGS(exc=RuntimeError("boom"))):
|
|
102
|
+
with pytest.raises(SearchBackendUnavailable):
|
|
103
|
+
await build_search_backend("duckduckgo").search("q")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# ----------------------------------------------------------------------------- tavily
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pytest.mark.asyncio
|
|
110
|
+
async def test_tavily_requires_key() -> None:
|
|
111
|
+
with pytest.raises(SearchBackendNotConfigured):
|
|
112
|
+
await build_search_backend("tavily", api_key=None).search("q")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@pytest.mark.asyncio
|
|
116
|
+
async def test_tavily_success_with_answer() -> None:
|
|
117
|
+
payload = {
|
|
118
|
+
"answer": "the answer",
|
|
119
|
+
"results": [{"title": "T", "url": "https://a.test", "content": "body"}, {"url": ""}],
|
|
120
|
+
}
|
|
121
|
+
client = Mock()
|
|
122
|
+
client.search.return_value = payload
|
|
123
|
+
with patch(f"{_TAVILY}.TavilyClient", return_value=client):
|
|
124
|
+
response = await build_search_backend("tavily", api_key="k").search("q", max_results=2)
|
|
125
|
+
assert response.answer == "the answer"
|
|
126
|
+
assert [r.url for r in response.results] == ["https://a.test"]
|
|
127
|
+
# Key + count are passed through.
|
|
128
|
+
_, kwargs = client.search.call_args
|
|
129
|
+
assert kwargs["max_results"] == 2 and kwargs["include_answer"] is True
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@pytest.mark.asyncio
|
|
133
|
+
async def test_tavily_invalid_key_mapped_to_not_configured() -> None:
|
|
134
|
+
from tavily.errors import InvalidAPIKeyError
|
|
135
|
+
|
|
136
|
+
client = Mock()
|
|
137
|
+
client.search.side_effect = InvalidAPIKeyError("bad key")
|
|
138
|
+
with patch(f"{_TAVILY}.TavilyClient", return_value=client):
|
|
139
|
+
with pytest.raises(SearchBackendNotConfigured):
|
|
140
|
+
await build_search_backend("tavily", api_key="bad").search("q")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# --------------------------------------------------------------------------- firecrawl
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@pytest.mark.asyncio
|
|
147
|
+
async def test_firecrawl_keyless_uses_rest() -> None:
|
|
148
|
+
# No key -> direct REST (Firecrawl's free tier), no SDK involved.
|
|
149
|
+
payload = {
|
|
150
|
+
"success": True,
|
|
151
|
+
"data": {
|
|
152
|
+
"web": [
|
|
153
|
+
{"url": "https://a.test", "title": "T", "description": "desc"},
|
|
154
|
+
{"url": "", "title": "skip"},
|
|
155
|
+
]
|
|
156
|
+
},
|
|
157
|
+
}
|
|
158
|
+
with patch(f"{_FIRE}.httpx.AsyncClient", _post_client(_DummyResponse(200, payload))):
|
|
159
|
+
response = await build_search_backend("firecrawl", api_key=None).search("q", max_results=3)
|
|
160
|
+
assert response.backend == "firecrawl"
|
|
161
|
+
assert [r.url for r in response.results] == ["https://a.test"]
|
|
162
|
+
assert response.results[0].snippet == "desc"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@pytest.mark.asyncio
|
|
166
|
+
async def test_firecrawl_keyless_429_rate_limited() -> None:
|
|
167
|
+
with patch(f"{_FIRE}.httpx.AsyncClient", _post_client(_DummyResponse(429))):
|
|
168
|
+
with pytest.raises(SearchBackendRateLimited):
|
|
169
|
+
await build_search_backend("firecrawl", api_key=None).search("q")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@pytest.mark.asyncio
|
|
173
|
+
async def test_firecrawl_keyed_uses_sdk() -> None:
|
|
174
|
+
data = SimpleNamespace(
|
|
175
|
+
web=[
|
|
176
|
+
SimpleNamespace(url="https://a.test", title="T", description="desc", markdown="full"),
|
|
177
|
+
SimpleNamespace(url=None, title="skip", description="", markdown=""),
|
|
178
|
+
]
|
|
179
|
+
)
|
|
180
|
+
client = Mock()
|
|
181
|
+
client.search.return_value = data
|
|
182
|
+
with patch(f"{_FIRE}.Firecrawl", return_value=client):
|
|
183
|
+
response = await build_search_backend("firecrawl", api_key="fc").search("q", max_results=4)
|
|
184
|
+
assert [r.url for r in response.results] == ["https://a.test"]
|
|
185
|
+
assert response.results[0].snippet == "desc"
|
|
186
|
+
assert response.results[0].content == "full"
|
|
187
|
+
# limit is forwarded; firecrawl timeout is milliseconds.
|
|
188
|
+
_, kwargs = client.search.call_args
|
|
189
|
+
assert kwargs["limit"] == 4 and kwargs["timeout"] == int(WebSearchTool.SEARCH_TIMEOUT_SECONDS * 1000)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@pytest.mark.asyncio
|
|
193
|
+
async def test_firecrawl_401_mapped_to_not_configured() -> None:
|
|
194
|
+
client = Mock()
|
|
195
|
+
client.search.side_effect = RuntimeError("HTTP 401 Unauthorized")
|
|
196
|
+
with patch(f"{_FIRE}.Firecrawl", return_value=client):
|
|
197
|
+
with pytest.raises(SearchBackendNotConfigured):
|
|
198
|
+
await build_search_backend("firecrawl", api_key="fc").search("q")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# ----------------------------------------------------------------------------- searxng
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class _DummyResponse:
|
|
205
|
+
def __init__(self, status_code=200, payload=None, json_error=False):
|
|
206
|
+
self.status_code = status_code
|
|
207
|
+
self._payload = payload or {}
|
|
208
|
+
self._json_error = json_error
|
|
209
|
+
|
|
210
|
+
def json(self):
|
|
211
|
+
if self._json_error:
|
|
212
|
+
raise ValueError("not json")
|
|
213
|
+
return self._payload
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _searx_client(response=None, get_exc=None):
|
|
217
|
+
class _DummyAsyncClient:
|
|
218
|
+
def __init__(self, *args, **kwargs):
|
|
219
|
+
pass
|
|
220
|
+
|
|
221
|
+
async def __aenter__(self):
|
|
222
|
+
return self
|
|
223
|
+
|
|
224
|
+
async def __aexit__(self, *args):
|
|
225
|
+
return False
|
|
226
|
+
|
|
227
|
+
async def get(self, url, params=None):
|
|
228
|
+
if get_exc:
|
|
229
|
+
raise get_exc
|
|
230
|
+
return response
|
|
231
|
+
|
|
232
|
+
return _DummyAsyncClient
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _post_client(response=None, post_exc=None):
|
|
236
|
+
class _DummyAsyncClient:
|
|
237
|
+
def __init__(self, *args, **kwargs):
|
|
238
|
+
pass
|
|
239
|
+
|
|
240
|
+
async def __aenter__(self):
|
|
241
|
+
return self
|
|
242
|
+
|
|
243
|
+
async def __aexit__(self, *args):
|
|
244
|
+
return False
|
|
245
|
+
|
|
246
|
+
async def post(self, url, json=None, headers=None):
|
|
247
|
+
if post_exc:
|
|
248
|
+
raise post_exc
|
|
249
|
+
return response
|
|
250
|
+
|
|
251
|
+
return _DummyAsyncClient
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@pytest.mark.asyncio
|
|
255
|
+
async def test_searxng_requires_base_url() -> None:
|
|
256
|
+
with pytest.raises(SearchBackendNotConfigured):
|
|
257
|
+
await build_search_backend("searxng", base_url=None).search("q")
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@pytest.mark.asyncio
|
|
261
|
+
async def test_searxng_success() -> None:
|
|
262
|
+
payload = {"results": [{"title": "T", "url": "https://a.test", "content": "c"}, {"url": ""}]}
|
|
263
|
+
with patch(f"{_SEARX}.httpx.AsyncClient", _searx_client(_DummyResponse(200, payload))):
|
|
264
|
+
response = await build_search_backend("searxng", base_url="https://s.test/").search("q", max_results=5)
|
|
265
|
+
assert [r.url for r in response.results] == ["https://a.test"]
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@pytest.mark.asyncio
|
|
269
|
+
async def test_searxng_429_rate_limited() -> None:
|
|
270
|
+
with patch(f"{_SEARX}.httpx.AsyncClient", _searx_client(_DummyResponse(429))):
|
|
271
|
+
with pytest.raises(SearchBackendRateLimited):
|
|
272
|
+
await build_search_backend("searxng", base_url="https://s.test").search("q")
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@pytest.mark.asyncio
|
|
276
|
+
async def test_searxng_http_error_unavailable() -> None:
|
|
277
|
+
with patch(f"{_SEARX}.httpx.AsyncClient", _searx_client(_DummyResponse(503))):
|
|
278
|
+
with pytest.raises(SearchBackendUnavailable):
|
|
279
|
+
await build_search_backend("searxng", base_url="https://s.test").search("q")
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@pytest.mark.asyncio
|
|
283
|
+
async def test_searxng_timeout_unavailable() -> None:
|
|
284
|
+
with patch(f"{_SEARX}.httpx.AsyncClient", _searx_client(get_exc=httpx.TimeoutException("slow"))):
|
|
285
|
+
with pytest.raises(SearchBackendUnavailable):
|
|
286
|
+
await build_search_backend("searxng", base_url="https://s.test").search("q")
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
# ------------------------------------------------------------------------- WebSearchTool
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@pytest.fixture
|
|
293
|
+
def agent_config():
|
|
294
|
+
return AgentConfig(
|
|
295
|
+
anthropic_api_key="k",
|
|
296
|
+
long_context_config=ModelConfig(provider=ModelProvider.ANTHROPIC, model="m"),
|
|
297
|
+
fast_config=ModelConfig(provider=ModelProvider.ANTHROPIC, model="f"),
|
|
298
|
+
thinking_config=ModelConfig(provider=ModelProvider.ANTHROPIC, model="t"),
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
@pytest.fixture
|
|
303
|
+
def caller():
|
|
304
|
+
c = Mock()
|
|
305
|
+
c.agent_name = "coder"
|
|
306
|
+
c.current_tool_call_id = "call-1"
|
|
307
|
+
c.sub_agent = False
|
|
308
|
+
return c
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@pytest.fixture
|
|
312
|
+
def tool(tmp_path, agent_config, caller):
|
|
313
|
+
return WebSearchTool(tmp_path, "ws", "th", AsyncMock(), agent_config, caller)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@pytest.mark.asyncio
|
|
317
|
+
async def test_tool_empty_query_guard(tool) -> None:
|
|
318
|
+
assert (await tool.web_search(" ")).startswith("Error: Provide a non-empty search query.")
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@pytest.mark.asyncio
|
|
322
|
+
async def test_tool_formats_results_and_streams(tool) -> None:
|
|
323
|
+
response = SearchResponse(
|
|
324
|
+
query="cats",
|
|
325
|
+
backend="duckduckgo",
|
|
326
|
+
results=[SearchResult(title="Cats", url="https://cats.test", snippet="meow")],
|
|
327
|
+
)
|
|
328
|
+
backend = Mock()
|
|
329
|
+
backend.search = AsyncMock(return_value=response)
|
|
330
|
+
with patch("kolega_code.agent.tool_backend.web_search_tool.build_search_backend", return_value=backend):
|
|
331
|
+
result = await tool.web_search("cats", max_results=3)
|
|
332
|
+
# The URL appears as its own line (exact match, not a substring check).
|
|
333
|
+
assert any(line.strip() == "https://cats.test" for line in result.splitlines())
|
|
334
|
+
assert "Cats" in result
|
|
335
|
+
assert "meow" in result
|
|
336
|
+
# A final (is_complete) streaming update is emitted.
|
|
337
|
+
final = [c for c in tool.connection_manager.broadcast_event.call_args_list]
|
|
338
|
+
assert final, "expected streaming updates"
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@pytest.mark.asyncio
|
|
342
|
+
async def test_tool_empty_results_message(tool) -> None:
|
|
343
|
+
response = SearchResponse(query="zzz", backend="duckduckgo", results=[])
|
|
344
|
+
backend = Mock()
|
|
345
|
+
backend.search = AsyncMock(return_value=response)
|
|
346
|
+
with patch("kolega_code.agent.tool_backend.web_search_tool.build_search_backend", return_value=backend):
|
|
347
|
+
result = await tool.web_search("zzz")
|
|
348
|
+
assert result == "No results found for: zzz"
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@pytest.mark.asyncio
|
|
352
|
+
async def test_tool_not_configured_is_friendly(tool) -> None:
|
|
353
|
+
backend = Mock()
|
|
354
|
+
backend.search = AsyncMock(side_effect=SearchBackendNotConfigured("needs a key"))
|
|
355
|
+
with patch("kolega_code.agent.tool_backend.web_search_tool.build_search_backend", return_value=backend):
|
|
356
|
+
result = await tool.web_search("q")
|
|
357
|
+
assert result.startswith("Error:") and "Settings > Web Search" in result
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@pytest.mark.asyncio
|
|
361
|
+
async def test_tool_never_raises_on_unexpected(tool) -> None:
|
|
362
|
+
backend = Mock()
|
|
363
|
+
backend.search = AsyncMock(side_effect=RuntimeError("kaboom"))
|
|
364
|
+
with patch("kolega_code.agent.tool_backend.web_search_tool.build_search_backend", return_value=backend):
|
|
365
|
+
result = await tool.web_search("q")
|
|
366
|
+
assert result.startswith("Error: web_search failed:")
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Pluggable web-search backends for the ``web_search`` tool.
|
|
2
|
+
|
|
3
|
+
To add a backend: create a module with a ``SearchBackend`` subclass, import it below,
|
|
4
|
+
and add it to ``_BACKENDS``. The TUI Select options and config validation derive from
|
|
5
|
+
this registry, so no other wiring is needed.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Dict, List, Optional, Tuple, Type
|
|
11
|
+
|
|
12
|
+
from .base import DEFAULT_RESULTS, DEFAULT_TIMEOUT_SECONDS, SearchBackend, clamp_results
|
|
13
|
+
from .duckduckgo import DuckDuckGoBackend
|
|
14
|
+
from .errors import (
|
|
15
|
+
SearchBackendError,
|
|
16
|
+
SearchBackendNotConfigured,
|
|
17
|
+
SearchBackendRateLimited,
|
|
18
|
+
SearchBackendUnavailable,
|
|
19
|
+
)
|
|
20
|
+
from .firecrawl import FirecrawlBackend
|
|
21
|
+
from .models import SearchResponse, SearchResult
|
|
22
|
+
from .searxng import SearxngBackend
|
|
23
|
+
from .tavily import TavilyBackend
|
|
24
|
+
|
|
25
|
+
DEFAULT_BACKEND = "duckduckgo"
|
|
26
|
+
|
|
27
|
+
_BACKENDS: Dict[str, Type[SearchBackend]] = {
|
|
28
|
+
backend.name: backend
|
|
29
|
+
for backend in (DuckDuckGoBackend, FirecrawlBackend, TavilyBackend, SearxngBackend)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def backend_names() -> List[str]:
|
|
34
|
+
"""All registered backend names."""
|
|
35
|
+
return list(_BACKENDS)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def available_backends() -> List[Tuple[str, str]]:
|
|
39
|
+
"""``(label, name)`` pairs for the TUI Select, with the default backend first."""
|
|
40
|
+
ordered = [_BACKENDS[DEFAULT_BACKEND]] + [
|
|
41
|
+
backend for name, backend in _BACKENDS.items() if name != DEFAULT_BACKEND
|
|
42
|
+
]
|
|
43
|
+
return [(backend.label, backend.name) for backend in ordered]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_backend_class(name: str) -> Type[SearchBackend]:
|
|
47
|
+
"""Look up a backend class by name, or raise SearchBackendError for an unknown name."""
|
|
48
|
+
try:
|
|
49
|
+
return _BACKENDS[name]
|
|
50
|
+
except KeyError as exc:
|
|
51
|
+
valid = ", ".join(_BACKENDS)
|
|
52
|
+
raise SearchBackendError(f"Unknown web_search backend '{name}'. Valid: {valid}.") from exc
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def build_search_backend(
|
|
56
|
+
name: Optional[str],
|
|
57
|
+
*,
|
|
58
|
+
api_key: Optional[str] = None,
|
|
59
|
+
base_url: Optional[str] = None,
|
|
60
|
+
timeout: float = DEFAULT_TIMEOUT_SECONDS,
|
|
61
|
+
) -> SearchBackend:
|
|
62
|
+
"""Construct the configured backend instance."""
|
|
63
|
+
backend_cls = get_backend_class(name or DEFAULT_BACKEND)
|
|
64
|
+
return backend_cls(api_key=api_key, base_url=base_url, timeout=timeout)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
__all__ = [
|
|
68
|
+
"DEFAULT_BACKEND",
|
|
69
|
+
"DEFAULT_RESULTS",
|
|
70
|
+
"DEFAULT_TIMEOUT_SECONDS",
|
|
71
|
+
"SearchBackend",
|
|
72
|
+
"SearchBackendError",
|
|
73
|
+
"SearchBackendNotConfigured",
|
|
74
|
+
"SearchBackendRateLimited",
|
|
75
|
+
"SearchBackendUnavailable",
|
|
76
|
+
"SearchResponse",
|
|
77
|
+
"SearchResult",
|
|
78
|
+
"available_backends",
|
|
79
|
+
"backend_names",
|
|
80
|
+
"build_search_backend",
|
|
81
|
+
"clamp_results",
|
|
82
|
+
"get_backend_class",
|
|
83
|
+
]
|