deepagents-code 0.1.0__tar.gz → 0.1.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.
- deepagents_code-0.1.1/CHANGELOG.md +17 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/PKG-INFO +4 -4
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/README.md +3 -3
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/_version.py +2 -2
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/app.tcss +2 -2
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/integrations/sandbox_factory.py +1 -1
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/main.py +1 -1
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/model_config.py +1 -1
- deepagents_code-0.1.1/deepagents_code/tools.py +406 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/pyproject.toml +1 -1
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/scripts/install.sh +2 -2
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_main.py +1 -1
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_messages.py +1 -1
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_sandbox_factory.py +1 -1
- deepagents_code-0.1.1/tests/unit_tests/tools/test_fetch_url.py +474 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/uv.lock +4 -4
- deepagents_code-0.1.0/CHANGELOG.md +0 -10
- deepagents_code-0.1.0/deepagents_code/tools.py +0 -159
- deepagents_code-0.1.0/tests/unit_tests/tools/test_fetch_url.py +0 -72
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/.gitignore +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/DEV.md +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/Makefile +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/THREAT_MODEL.md +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/__init__.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/__main__.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/_ask_user_types.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/_cli_context.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/_constants.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/_debug.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/_env_vars.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/_git.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/_server_config.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/_session_stats.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/_testing_models.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/_textual_patches.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/agent.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/app.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/ask_user.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/auth_store.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/built_in_skills/__init__.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/built_in_skills/remember/SKILL.md +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/built_in_skills/skill-creator/SKILL.md +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/built_in_skills/skill-creator/scripts/init_skill.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/built_in_skills/skill-creator/scripts/quick_validate.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/clipboard.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/command_registry.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/config.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/configurable_model.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/default_agent_prompt.md +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/editor.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/event_bus.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/extras_info.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/file_ops.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/formatting.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/hooks.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/input.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/integrations/__init__.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/integrations/sandbox_provider.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/iterm_cursor_guide.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/local_context.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/mcp_auth.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/mcp_commands.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/mcp_providers/__init__.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/mcp_providers/_registry.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/mcp_providers/base.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/mcp_providers/github.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/mcp_providers/slack.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/mcp_tools.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/mcp_trust.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/media_utils.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/non_interactive.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/notifications.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/offload.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/onboarding.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/output.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/project_utils.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/py.typed +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/remote_client.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/server.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/server_graph.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/server_manager.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/sessions.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/skills/__init__.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/skills/commands.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/skills/invocation.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/skills/load.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/state_migration.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/subagents.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/system_prompt.md +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/terminal_capabilities.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/terminal_escape.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/textual_adapter.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/theme.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/token_state.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/tool_display.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/ui.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/unicode_security.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/update_check.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/__init__.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/_links.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/agent_selector.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/approval.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/ask_user.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/auth.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/autocomplete.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/chat_input.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/diff.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/history.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/launch_init.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/loading.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/mcp_viewer.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/message_store.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/messages.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/model_selector.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/notification_center.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/notification_detail.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/notification_settings.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/status.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/theme_selector.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/thread_selector.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/tool_renderers.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/tool_widgets.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/update_available.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/update_progress.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/widgets/welcome.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/examples/skills/arxiv-search/SKILL.md +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/examples/skills/arxiv-search/arxiv_search.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/examples/skills/langgraph-docs/SKILL.md +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/examples/skills/skill-creator/SKILL.md +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/examples/skills/skill-creator/scripts/init_skill.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/examples/skills/skill-creator/scripts/quick_validate.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/examples/skills/web-research/SKILL.md +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/images/tui.png +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/scripts/check_imports.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/scripts/debug_server.sh +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/README.md +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/integration_tests/__init__.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/integration_tests/benchmarks/__init__.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/integration_tests/benchmarks/test_codspeed_import_benchmarks.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/integration_tests/benchmarks/test_startup_benchmarks.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/integration_tests/conftest.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/integration_tests/test_acp_mode.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/integration_tests/test_compact_resume.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/integration_tests/test_sandbox_factory.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/integration_tests/test_sandbox_operations.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/__init__.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/conftest.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/skills/__init__.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/skills/test_commands.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/skills/test_load.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/skills/test_skills_json.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_agent.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_agent_friendly.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_agent_selector.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_app.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_approval.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_args.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_ask_user.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_ask_user_middleware.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_auth_store.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_auth_widgets.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_autocomplete.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_charset.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_chat_input.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_clipboard.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_command_registry.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_compact_tool.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_config.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_configurable_model.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_debug.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_editor.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_end_to_end.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_env_vars.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_event_bus.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_exception_handling.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_extras_info.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_file_ops.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_formatting.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_git.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_history.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_hooks.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_imports.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_input_parsing.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_iterm_cursor_guide.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_launch_init.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_links.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_loading.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_local_context.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_main_acp_mode.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_main_args.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_mcp_auth.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_mcp_commands.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_mcp_tools.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_mcp_trust.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_mcp_viewer.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_media_utils.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_message_store.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_model_config.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_model_selector.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_model_switch.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_non_interactive.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_notification_center.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_notification_detail.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_notifications.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_offload.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_offload_dict_messages.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_onboarding.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_output.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_reload.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_remote_client.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_server.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_server_config.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_server_graph.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_server_helpers.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_server_manager.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_session_stats.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_sessions.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_shell_allow_list.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_skill_invocation.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_startup_fast_paths.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_state_migration.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_status.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_subagents.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_terminal_capabilities.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_terminal_escape.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_textual_adapter.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_textual_patches.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_theme.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_thread_selector.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_token_tracker.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_tool_display.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_ui.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_unicode_security.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_update_available.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_update_check.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_update_progress.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_version.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/test_welcome.py +0 -0
- {deepagents_code-0.1.0 → deepagents_code-0.1.1}/tests/unit_tests/tools/__init__.py +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Deep Agents Code Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.1](https://github.com/langchain-ai/deepagents/compare/deepagents-code==0.1.0...deepagents-code==0.1.1) (2026-05-16)
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
* Correct LangSmith sandbox working directory ([#3415](https://github.com/langchain-ai/deepagents/issues/3415)) ([b0e8d83](https://github.com/langchain-ai/deepagents/commit/b0e8d83f97a2a698268173a839000c84e8368324))
|
|
8
|
+
* Guard `fetch_url` against SSRF ([#3411](https://github.com/langchain-ai/deepagents/issues/3411)) ([54d8521](https://github.com/langchain-ai/deepagents/commit/54d8521976940dfe147ead4b56565360241335be))
|
|
9
|
+
|
|
10
|
+
## [0.1.0](https://github.com/langchain-ai/deepagents/compare/deepagents-code==0.0.1...deepagents-code==0.1.0) (2026-05-12)
|
|
11
|
+
|
|
12
|
+
Hello world! Ported from `libs/cli`.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
`deepagents-code` was forked from `deepagents-cli` at v0.1.0 (2026-05-12).
|
|
17
|
+
For history prior to the fork, see [the `deepagents-cli` changelog](https://github.com/langchain-ai/deepagents/blob/main/libs/cli/CHANGELOG.md).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepagents-code
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Terminal interface for Deep Agents - interactive AI agent with file operations, shell access, and sub-agent capabilities.
|
|
5
5
|
Project-URL: Homepage, https://docs.langchain.com/oss/python/deepagents/overview
|
|
6
6
|
Project-URL: Documentation, https://reference.langchain.com/python/deepagents/
|
|
@@ -145,13 +145,13 @@ Description-Content-Type: text/markdown
|
|
|
145
145
|
## Quick Install
|
|
146
146
|
|
|
147
147
|
```bash
|
|
148
|
-
curl -LsSf https://langch.in/
|
|
148
|
+
curl -LsSf https://langch.in/dcode | bash
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
```bash
|
|
152
152
|
# With model provider extras
|
|
153
153
|
# OpenAI, Anthropic, and Gemini are included by default
|
|
154
|
-
DEEPAGENTS_EXTRAS="nvidia,ollama" curl -LsSf https://langch.in/
|
|
154
|
+
DEEPAGENTS_EXTRAS="nvidia,ollama" curl -LsSf https://langch.in/dcode | bash
|
|
155
155
|
```
|
|
156
156
|
|
|
157
157
|
Or install directly with `uv`:
|
|
@@ -184,7 +184,7 @@ The fastest way to start using Deep Agents. `deepagents-code` is a pre-built cod
|
|
|
184
184
|
|
|
185
185
|
## 📖 Resources
|
|
186
186
|
|
|
187
|
-
- **[
|
|
187
|
+
- **[Documentation](https://docs.langchain.com/oss/python/deepagents/code/overview)**
|
|
188
188
|
- **[Changelog](https://github.com/langchain-ai/deepagents/blob/main/libs/code/CHANGELOG.md)**
|
|
189
189
|
- **[Source code](https://github.com/langchain-ai/deepagents/tree/main/libs/code)**
|
|
190
190
|
- **[Deep Agents SDK](https://github.com/langchain-ai/deepagents)** — underlying agent harness
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
## Quick Install
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
curl -LsSf https://langch.in/
|
|
15
|
+
curl -LsSf https://langch.in/dcode | bash
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
# With model provider extras
|
|
20
20
|
# OpenAI, Anthropic, and Gemini are included by default
|
|
21
|
-
DEEPAGENTS_EXTRAS="nvidia,ollama" curl -LsSf https://langch.in/
|
|
21
|
+
DEEPAGENTS_EXTRAS="nvidia,ollama" curl -LsSf https://langch.in/dcode | bash
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
Or install directly with `uv`:
|
|
@@ -51,7 +51,7 @@ The fastest way to start using Deep Agents. `deepagents-code` is a pre-built cod
|
|
|
51
51
|
|
|
52
52
|
## 📖 Resources
|
|
53
53
|
|
|
54
|
-
- **[
|
|
54
|
+
- **[Documentation](https://docs.langchain.com/oss/python/deepagents/code/overview)**
|
|
55
55
|
- **[Changelog](https://github.com/langchain-ai/deepagents/blob/main/libs/code/CHANGELOG.md)**
|
|
56
56
|
- **[Source code](https://github.com/langchain-ai/deepagents/tree/main/libs/code)**
|
|
57
57
|
- **[Deep Agents SDK](https://github.com/langchain-ai/deepagents)** — underlying agent harness
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
# Keep the `x-release-please-version` annotation — release-please uses it to
|
|
4
4
|
# bump `__version__` in sync with `pyproject.toml` on every release PR.
|
|
5
|
-
__version__ = "0.1.
|
|
5
|
+
__version__ = "0.1.1" # x-release-please-version
|
|
6
6
|
|
|
7
|
-
DOCS_URL = "https://docs.langchain.com/oss/python/deepagents/
|
|
7
|
+
DOCS_URL = "https://docs.langchain.com/oss/python/deepagents/code"
|
|
8
8
|
"""URL for `deepagents-code` documentation."""
|
|
9
9
|
|
|
10
10
|
PYPI_URL = "https://pypi.org/pypi/deepagents-code/json"
|
|
@@ -24,7 +24,7 @@ Screen {
|
|
|
24
24
|
/* Chat area - main scrollable messages area */
|
|
25
25
|
#chat {
|
|
26
26
|
height: 1fr;
|
|
27
|
-
padding: 1 2;
|
|
27
|
+
padding: 1 2 0 2;
|
|
28
28
|
background: $background;
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -44,7 +44,7 @@ Screen {
|
|
|
44
44
|
/* Bottom app container - holds ChatInput (now inside scroll) */
|
|
45
45
|
#bottom-app-container {
|
|
46
46
|
height: auto;
|
|
47
|
-
margin-top:
|
|
47
|
+
margin-top: 0;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/* Input area */
|
{deepagents_code-0.1.0 → deepagents_code-0.1.1}/deepagents_code/integrations/sandbox_factory.py
RENAMED
|
@@ -73,7 +73,7 @@ def _run_sandbox_setup(backend: SandboxBackendProtocol, setup_script_path: str)
|
|
|
73
73
|
_PROVIDER_TO_WORKING_DIR = {
|
|
74
74
|
"agentcore": "/tmp", # noqa: S108 # AgentCore Code Interpreter working directory
|
|
75
75
|
"daytona": "/home/daytona",
|
|
76
|
-
"langsmith": "/
|
|
76
|
+
"langsmith": "/root", # `$HOME` in the LangSmith sandbox
|
|
77
77
|
"modal": "/workspace",
|
|
78
78
|
"runloop": "/home/user",
|
|
79
79
|
}
|
|
@@ -1521,7 +1521,7 @@ def _check_mcp_project_trust(*, trust_flag: bool = False) -> bool | None:
|
|
|
1521
1521
|
from rich.console import Console as _Console
|
|
1522
1522
|
|
|
1523
1523
|
docs_url = (
|
|
1524
|
-
"https://docs.langchain.com/oss/python/deepagents/
|
|
1524
|
+
"https://docs.langchain.com/oss/python/deepagents/code/"
|
|
1525
1525
|
"mcp-tools#project-level-trust"
|
|
1526
1526
|
)
|
|
1527
1527
|
prompt_console = _Console(stderr=True)
|
|
@@ -94,7 +94,7 @@ def resolve_env_var(name: str) -> str | None:
|
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
PROVIDERS_DOCS_URL = (
|
|
97
|
-
"https://docs.langchain.com/oss/python/deepagents/
|
|
97
|
+
"https://docs.langchain.com/oss/python/deepagents/code/providers#provider-reference"
|
|
98
98
|
)
|
|
99
99
|
"""Public docs page for configuring model providers.
|
|
100
100
|
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
"""Custom tools for the agent."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import contextlib
|
|
6
|
+
import ipaddress
|
|
7
|
+
import logging
|
|
8
|
+
import socket
|
|
9
|
+
import threading
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
11
|
+
from urllib.parse import urljoin, urlparse
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from collections.abc import Iterator
|
|
15
|
+
|
|
16
|
+
from tavily import TavilyClient
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
_UNSET = object()
|
|
21
|
+
_tavily_client: TavilyClient | object | None = _UNSET
|
|
22
|
+
|
|
23
|
+
_ALLOWED_URL_SCHEMES = frozenset({"http", "https"})
|
|
24
|
+
_MAX_FETCH_REDIRECTS = 5
|
|
25
|
+
|
|
26
|
+
# Module-level lock guarding the urllib3 connection-factory monkeypatch used by
|
|
27
|
+
# `_pinned_dns`. The patch is process-global, so serializing fetches keeps
|
|
28
|
+
# concurrent calls from clobbering each other's pinned IP set.
|
|
29
|
+
_dns_pin_lock = threading.Lock()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _UrlValidationError(ValueError):
|
|
33
|
+
"""Raised by `_validate_url` for scheme/DNS/SSRF-blocked URLs.
|
|
34
|
+
|
|
35
|
+
Distinguishes intentional SSRF-guard rejections from incidental
|
|
36
|
+
`ValueError`s raised elsewhere in the fetch path (e.g., markdown
|
|
37
|
+
conversion).
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _is_blocked_ip(ip: ipaddress.IPv4Address | ipaddress.IPv6Address) -> bool:
|
|
42
|
+
"""Return True if `ip` belongs to a non-publicly-routable range.
|
|
43
|
+
|
|
44
|
+
Rejects: private (RFC1918/ULA), loopback, link-local (including cloud
|
|
45
|
+
IMDS at `169.254.169.254`), reserved, multicast, unspecified
|
|
46
|
+
(`0.0.0.0`/`::`), and anything `ipaddress` does not consider globally
|
|
47
|
+
routable (catches benchmarking, documentation, and similar ranges the
|
|
48
|
+
explicit predicates miss).
|
|
49
|
+
|
|
50
|
+
IPv4-mapped IPv6 (`::ffff:a.b.c.d`) and 6to4 (`2002::/16`) are unwrapped
|
|
51
|
+
to their underlying IPv4 address before the checks so that private
|
|
52
|
+
space tunneled inside an IPv6 wrapper is still caught — e.g.,
|
|
53
|
+
`::ffff:127.0.0.1` and `2002:a9fe:a9fe::1` (6to4 over IMDS) both
|
|
54
|
+
evaluate as blocked.
|
|
55
|
+
"""
|
|
56
|
+
if isinstance(ip, ipaddress.IPv6Address):
|
|
57
|
+
if ip.ipv4_mapped is not None:
|
|
58
|
+
ip = ip.ipv4_mapped
|
|
59
|
+
elif ip.sixtofour is not None:
|
|
60
|
+
ip = ip.sixtofour
|
|
61
|
+
return (
|
|
62
|
+
not ip.is_global
|
|
63
|
+
or ip.is_private
|
|
64
|
+
or ip.is_loopback
|
|
65
|
+
or ip.is_link_local
|
|
66
|
+
or ip.is_reserved
|
|
67
|
+
or ip.is_multicast
|
|
68
|
+
or ip.is_unspecified
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _validate_url(url: str) -> list[str]:
|
|
73
|
+
"""Reject URLs that target private/internal/metadata addresses.
|
|
74
|
+
|
|
75
|
+
Resolves the URL's hostname and rejects any URL whose hostname resolves
|
|
76
|
+
to a private, loopback, link-local (includes cloud IMDS at
|
|
77
|
+
`169.254.169.254`), reserved, multicast, or unspecified IP — including
|
|
78
|
+
such addresses wrapped in IPv4-mapped IPv6 (`::ffff:...`) or 6to4
|
|
79
|
+
(`2002::/16`). This is the SSRF guard required because the URL is
|
|
80
|
+
supplied by an LLM agent and may originate from prompt-injected content.
|
|
81
|
+
|
|
82
|
+
Note:
|
|
83
|
+
This function resolves DNS once. The HTTP client must be pinned to
|
|
84
|
+
the returned IP list (see `_pinned_dns`) to close the TOCTOU window
|
|
85
|
+
against attacker-controlled DNS (rebinding).
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
url: Candidate URL to validate.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
The list of validated IP strings the hostname resolves to.
|
|
92
|
+
|
|
93
|
+
Callers should pin the outgoing connection to one of these IPs.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
_UrlValidationError: If the URL is malformed, uses a disallowed
|
|
97
|
+
scheme, fails DNS resolution, or resolves to a blocked address.
|
|
98
|
+
"""
|
|
99
|
+
parsed = urlparse(url)
|
|
100
|
+
if parsed.scheme not in _ALLOWED_URL_SCHEMES:
|
|
101
|
+
msg = f"URL scheme not allowed: {parsed.scheme!r} (must be http or https)"
|
|
102
|
+
raise _UrlValidationError(msg)
|
|
103
|
+
|
|
104
|
+
hostname = parsed.hostname
|
|
105
|
+
if not hostname:
|
|
106
|
+
msg = "URL is missing a hostname"
|
|
107
|
+
raise _UrlValidationError(msg)
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
encoded_hostname = hostname.encode("idna").decode("ascii")
|
|
111
|
+
except UnicodeError as exc:
|
|
112
|
+
msg = f"Could not encode hostname {hostname!r} as IDNA: {exc}"
|
|
113
|
+
raise _UrlValidationError(msg) from exc
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
infos = socket.getaddrinfo(
|
|
117
|
+
encoded_hostname,
|
|
118
|
+
None,
|
|
119
|
+
type=socket.SOCK_STREAM,
|
|
120
|
+
proto=socket.IPPROTO_TCP,
|
|
121
|
+
)
|
|
122
|
+
except socket.gaierror as exc:
|
|
123
|
+
msg = f"Could not resolve hostname {hostname!r}: {exc}"
|
|
124
|
+
raise _UrlValidationError(msg) from exc
|
|
125
|
+
|
|
126
|
+
validated_ips: list[str] = []
|
|
127
|
+
for info in infos:
|
|
128
|
+
# `sockaddr[0]` may include an IPv6 scope id (`fe80::1%eth0`); strip
|
|
129
|
+
# it before parsing so `ipaddress.ip_address` never raises.
|
|
130
|
+
raw_ip = str(info[4][0]).split("%", 1)[0]
|
|
131
|
+
ip = ipaddress.ip_address(raw_ip)
|
|
132
|
+
if _is_blocked_ip(ip):
|
|
133
|
+
logger.warning(
|
|
134
|
+
"SSRF guard blocked URL %r: hostname %r resolves to %s",
|
|
135
|
+
url,
|
|
136
|
+
hostname,
|
|
137
|
+
ip,
|
|
138
|
+
)
|
|
139
|
+
msg = (
|
|
140
|
+
f"URL hostname {hostname!r} resolves to blocked address {ip} "
|
|
141
|
+
"(private, loopback, link-local, reserved, or non-global range)"
|
|
142
|
+
)
|
|
143
|
+
raise _UrlValidationError(msg)
|
|
144
|
+
validated_ips.append(raw_ip)
|
|
145
|
+
|
|
146
|
+
if not validated_ips:
|
|
147
|
+
msg = f"Hostname {hostname!r} resolved to no addresses"
|
|
148
|
+
raise _UrlValidationError(msg)
|
|
149
|
+
|
|
150
|
+
return validated_ips
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@contextlib.contextmanager
|
|
154
|
+
def _pinned_dns(hostname: str, allowed_ips: list[str]) -> Iterator[None]:
|
|
155
|
+
"""Force outgoing urllib3 connections for `hostname` to use `allowed_ips`.
|
|
156
|
+
|
|
157
|
+
Patches `urllib3.util.connection.create_connection` for the duration of
|
|
158
|
+
the context so that `requests` cannot re-resolve `hostname` to a
|
|
159
|
+
different IP than the one `_validate_url` vetted (defends against DNS
|
|
160
|
+
rebinding TOCTOU). The patch is process-global, so the module lock
|
|
161
|
+
serializes concurrent fetches.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
hostname: The exact hostname (already IDNA-encoded by the caller)
|
|
165
|
+
whose resolution must be pinned.
|
|
166
|
+
allowed_ips: The IPs `_validate_url` confirmed are safe to connect
|
|
167
|
+
to. Tried in order; the first that accepts the connection wins.
|
|
168
|
+
"""
|
|
169
|
+
from urllib3.util import connection as urllib3_connection
|
|
170
|
+
|
|
171
|
+
with _dns_pin_lock:
|
|
172
|
+
original = urllib3_connection.create_connection
|
|
173
|
+
|
|
174
|
+
def patched(
|
|
175
|
+
address: tuple[str, int], *args: Any, **kwargs: Any
|
|
176
|
+
) -> socket.socket:
|
|
177
|
+
host, port = address[0], address[1]
|
|
178
|
+
if host != hostname:
|
|
179
|
+
return original(address, *args, **kwargs)
|
|
180
|
+
last_exc: OSError | None = None
|
|
181
|
+
for ip in allowed_ips:
|
|
182
|
+
try:
|
|
183
|
+
return original((ip, port), *args, **kwargs)
|
|
184
|
+
except OSError as exc:
|
|
185
|
+
last_exc = exc
|
|
186
|
+
assert last_exc is not None # noqa: S101 # loop body guarantees this
|
|
187
|
+
raise last_exc
|
|
188
|
+
|
|
189
|
+
urllib3_connection.create_connection = patched # type: ignore[assignment] # signature matches at runtime
|
|
190
|
+
try:
|
|
191
|
+
yield
|
|
192
|
+
finally:
|
|
193
|
+
urllib3_connection.create_connection = original
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _get_tavily_client() -> TavilyClient | None:
|
|
197
|
+
"""Get or initialize the lazy Tavily client singleton.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
TavilyClient instance, or None if API key is not configured.
|
|
201
|
+
"""
|
|
202
|
+
global _tavily_client # noqa: PLW0603 # Module-level cache requires global statement
|
|
203
|
+
if _tavily_client is not _UNSET:
|
|
204
|
+
return _tavily_client # type: ignore[return-value] # narrowed by sentinel check
|
|
205
|
+
|
|
206
|
+
from deepagents_code.config import settings
|
|
207
|
+
|
|
208
|
+
if settings.has_tavily:
|
|
209
|
+
from tavily import TavilyClient as _TavilyClient
|
|
210
|
+
|
|
211
|
+
_tavily_client = _TavilyClient(api_key=settings.tavily_api_key)
|
|
212
|
+
else:
|
|
213
|
+
_tavily_client = None
|
|
214
|
+
return _tavily_client
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def web_search( # noqa: ANN201 # Return type depends on dynamic tool configuration
|
|
218
|
+
query: str,
|
|
219
|
+
max_results: int = 5,
|
|
220
|
+
topic: Literal["general", "news", "finance"] = "general",
|
|
221
|
+
include_raw_content: bool = False,
|
|
222
|
+
):
|
|
223
|
+
"""Search the web using Tavily for current information and documentation.
|
|
224
|
+
|
|
225
|
+
This tool searches the web and returns relevant results. After receiving results,
|
|
226
|
+
you MUST synthesize the information into a natural, helpful response for the user.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
query: The search query (be specific and detailed)
|
|
230
|
+
max_results: Number of results to return (default: 5)
|
|
231
|
+
topic: Search topic type - "general" for most queries, "news" for current events
|
|
232
|
+
include_raw_content: Include full page content (warning: uses more tokens)
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Dictionary containing:
|
|
236
|
+
- results: List of search results, each with:
|
|
237
|
+
- title: Page title
|
|
238
|
+
- url: Page URL
|
|
239
|
+
- content: Relevant excerpt from the page
|
|
240
|
+
- score: Relevance score (0-1)
|
|
241
|
+
- query: The original search query
|
|
242
|
+
|
|
243
|
+
IMPORTANT: After using this tool:
|
|
244
|
+
1. Read through the 'content' field of each result
|
|
245
|
+
2. Extract relevant information that answers the user's question
|
|
246
|
+
3. Synthesize this into a clear, natural language response
|
|
247
|
+
4. Cite sources by mentioning the page titles or URLs
|
|
248
|
+
5. NEVER show the raw JSON to the user - always provide a formatted response
|
|
249
|
+
"""
|
|
250
|
+
try:
|
|
251
|
+
import requests
|
|
252
|
+
from tavily import (
|
|
253
|
+
BadRequestError,
|
|
254
|
+
InvalidAPIKeyError,
|
|
255
|
+
MissingAPIKeyError,
|
|
256
|
+
UsageLimitExceededError,
|
|
257
|
+
)
|
|
258
|
+
from tavily.errors import ForbiddenError, TimeoutError as TavilyTimeoutError
|
|
259
|
+
except ImportError as exc:
|
|
260
|
+
return {"error": f"Required package not installed: {exc.name}."}
|
|
261
|
+
|
|
262
|
+
client = _get_tavily_client()
|
|
263
|
+
if client is None:
|
|
264
|
+
return {
|
|
265
|
+
"error": "Tavily API key not configured. "
|
|
266
|
+
"Please set TAVILY_API_KEY environment variable.",
|
|
267
|
+
"query": query,
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
return client.search(
|
|
272
|
+
query,
|
|
273
|
+
max_results=max_results,
|
|
274
|
+
include_raw_content=include_raw_content,
|
|
275
|
+
topic=topic,
|
|
276
|
+
)
|
|
277
|
+
except (
|
|
278
|
+
requests.exceptions.RequestException,
|
|
279
|
+
ValueError,
|
|
280
|
+
TypeError,
|
|
281
|
+
# Tavily-specific exceptions
|
|
282
|
+
BadRequestError,
|
|
283
|
+
ForbiddenError,
|
|
284
|
+
InvalidAPIKeyError,
|
|
285
|
+
MissingAPIKeyError,
|
|
286
|
+
TavilyTimeoutError,
|
|
287
|
+
UsageLimitExceededError,
|
|
288
|
+
) as e:
|
|
289
|
+
return {"error": f"Web search error: {e!s}", "query": query}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def fetch_url(url: str, timeout: int = 30) -> dict[str, Any]:
|
|
293
|
+
"""Fetch content from a URL and convert HTML to markdown format.
|
|
294
|
+
|
|
295
|
+
This tool fetches web page content and converts it to clean markdown text,
|
|
296
|
+
making it easy to read and process HTML content. After receiving the markdown,
|
|
297
|
+
you MUST synthesize the information into a natural, helpful response for the user.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
url: The URL to fetch (must be a valid HTTP/HTTPS URL)
|
|
301
|
+
timeout: Request timeout in seconds (default: 30)
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Dictionary containing:
|
|
305
|
+
- success: Whether the request succeeded
|
|
306
|
+
- url: The final URL after redirects
|
|
307
|
+
- markdown_content: The page content converted to markdown
|
|
308
|
+
- status_code: HTTP status code
|
|
309
|
+
- content_length: Length of the markdown content in characters
|
|
310
|
+
|
|
311
|
+
IMPORTANT: After using this tool:
|
|
312
|
+
1. Read through the markdown content
|
|
313
|
+
2. Extract relevant information that answers the user's question
|
|
314
|
+
3. Synthesize this into a clear, natural language response
|
|
315
|
+
4. NEVER show the raw markdown to the user unless specifically requested
|
|
316
|
+
"""
|
|
317
|
+
try:
|
|
318
|
+
import requests
|
|
319
|
+
from markdownify import markdownify
|
|
320
|
+
except ImportError as exc:
|
|
321
|
+
return {"error": f"Required package not installed: {exc.name}."}
|
|
322
|
+
|
|
323
|
+
try:
|
|
324
|
+
response = _fetch_with_redirects(url, timeout=timeout)
|
|
325
|
+
except _UrlValidationError as e:
|
|
326
|
+
return {
|
|
327
|
+
"error": f"Fetch URL error: {e!s}",
|
|
328
|
+
"url": url,
|
|
329
|
+
"category": "validation",
|
|
330
|
+
}
|
|
331
|
+
except requests.exceptions.TooManyRedirects as e:
|
|
332
|
+
return {"error": f"Fetch URL error: {e!s}", "url": url, "category": "redirects"}
|
|
333
|
+
except requests.exceptions.RequestException as e:
|
|
334
|
+
return {"error": f"Fetch URL error: {e!s}", "url": url, "category": "network"}
|
|
335
|
+
|
|
336
|
+
markdown_content = markdownify(response.text)
|
|
337
|
+
return {
|
|
338
|
+
"url": str(response.url),
|
|
339
|
+
"markdown_content": markdown_content,
|
|
340
|
+
"status_code": response.status_code,
|
|
341
|
+
"content_length": len(markdown_content),
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _fetch_with_redirects(url: str, *, timeout: int) -> Any: # noqa: ANN401 # requests.Response, but kept dynamic to avoid eager import
|
|
346
|
+
"""Fetch `url`, re-validating each redirect hop against the SSRF guard.
|
|
347
|
+
|
|
348
|
+
Each hop is validated by `_validate_url` and its connection pinned to
|
|
349
|
+
the validated IP via `_pinned_dns`. Caps at `_MAX_FETCH_REDIRECTS`
|
|
350
|
+
redirects (so up to `_MAX_FETCH_REDIRECTS + 1` total hops counting the
|
|
351
|
+
initial request). Network/HTTP errors propagate as
|
|
352
|
+
`requests.exceptions.RequestException` (or its subclasses).
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
url: Initial URL to fetch.
|
|
356
|
+
timeout: Per-request timeout in seconds.
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
The final `requests.Response` for the non-redirect terminal hop.
|
|
360
|
+
|
|
361
|
+
Raises:
|
|
362
|
+
_UrlValidationError: If any hop fails SSRF validation or returns a
|
|
363
|
+
3xx without a `Location` header.
|
|
364
|
+
requests.exceptions.TooManyRedirects: If the redirect cap is exceeded.
|
|
365
|
+
"""
|
|
366
|
+
import requests
|
|
367
|
+
|
|
368
|
+
current_url = url
|
|
369
|
+
session = requests.Session()
|
|
370
|
+
# DNS pinning only protects the direct target connection. Environment
|
|
371
|
+
# proxies resolve the target separately, so they must be disabled here.
|
|
372
|
+
session.trust_env = False
|
|
373
|
+
for _hop in range(_MAX_FETCH_REDIRECTS + 1):
|
|
374
|
+
validated_ips = _validate_url(current_url)
|
|
375
|
+
hostname = urlparse(current_url).hostname
|
|
376
|
+
# `_validate_url` raises if hostname is missing, so this is non-None.
|
|
377
|
+
assert hostname is not None # noqa: S101 # invariant from _validate_url
|
|
378
|
+
encoded_hostname = hostname.encode("idna").decode("ascii")
|
|
379
|
+
|
|
380
|
+
with _pinned_dns(encoded_hostname, validated_ips):
|
|
381
|
+
response = session.get(
|
|
382
|
+
current_url,
|
|
383
|
+
timeout=timeout,
|
|
384
|
+
headers={"User-Agent": "Mozilla/5.0 (compatible; DeepAgents/1.0)"},
|
|
385
|
+
allow_redirects=False,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# 300-399 covers every redirect class. `requests.Response.is_redirect`
|
|
389
|
+
# also checks for a `Location` header, which would hide malformed 3xx
|
|
390
|
+
# responses — so we check the raw status code instead.
|
|
391
|
+
if 300 <= response.status_code < 400: # noqa: PLR2004 # HTTP redirect class
|
|
392
|
+
location = response.headers.get("Location")
|
|
393
|
+
if not location:
|
|
394
|
+
msg = (
|
|
395
|
+
f"Redirect response (status {response.status_code}) at "
|
|
396
|
+
f"{current_url!r} is missing a Location header"
|
|
397
|
+
)
|
|
398
|
+
raise _UrlValidationError(msg)
|
|
399
|
+
current_url = urljoin(current_url, location)
|
|
400
|
+
continue
|
|
401
|
+
|
|
402
|
+
response.raise_for_status()
|
|
403
|
+
return response
|
|
404
|
+
|
|
405
|
+
msg = f"Exceeded {_MAX_FETCH_REDIRECTS} redirects starting from {url!r}"
|
|
406
|
+
raise requests.exceptions.TooManyRedirects(msg)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "deepagents-code"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.1"
|
|
8
8
|
description = "Terminal interface for Deep Agents - interactive AI agent with file operations, shell access, and sub-agent capabilities."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -45,7 +45,7 @@ cleanup() {
|
|
|
45
45
|
if [ $exit_code -ne 0 ]; then
|
|
46
46
|
echo "" >&2
|
|
47
47
|
log_error "Installation failed (exit code ${exit_code}). See errors above."
|
|
48
|
-
log_error "For help, visit: https://docs.langchain.com/oss/python/deepagents/
|
|
48
|
+
log_error "For help, visit: https://docs.langchain.com/oss/python/deepagents/code/overview"
|
|
49
49
|
fi
|
|
50
50
|
}
|
|
51
51
|
trap cleanup EXIT
|
|
@@ -436,4 +436,4 @@ echo ""
|
|
|
436
436
|
printf "${GREEN}✔${NC} Setup complete. Run: ${BOLD}deepagents${NC}\n"
|
|
437
437
|
echo ""
|
|
438
438
|
echo "For help and support, see the Deep Agents Code docs:"
|
|
439
|
-
echo " https://docs.langchain.com/oss/python/deepagents/
|
|
439
|
+
echo " https://docs.langchain.com/oss/python/deepagents/code/overview"
|
|
@@ -1034,7 +1034,7 @@ class TestCheckMcpProjectTrustPrompt:
|
|
|
1034
1034
|
captured = capsys.readouterr()
|
|
1035
1035
|
flattened = captured.err.replace("\n", "")
|
|
1036
1036
|
assert (
|
|
1037
|
-
"https://docs.langchain.com/oss/python/deepagents/
|
|
1037
|
+
"https://docs.langchain.com/oss/python/deepagents/code/"
|
|
1038
1038
|
"mcp-tools#project-level-trust" in flattened
|
|
1039
1039
|
)
|
|
1040
1040
|
assert "Learn more:" in captured.err
|
|
@@ -79,7 +79,7 @@ class TestErrorMessageMarkupSafety:
|
|
|
79
79
|
"""Pre-built `Content` with `link` spans passes through to render output."""
|
|
80
80
|
from textual.style import Style as TStyle
|
|
81
81
|
|
|
82
|
-
url = "https://docs.langchain.com/oss/python/deepagents/
|
|
82
|
+
url = "https://docs.langchain.com/oss/python/deepagents/code/providers"
|
|
83
83
|
body = Content.assemble(
|
|
84
84
|
"see ",
|
|
85
85
|
(url, TStyle(underline=True, link=url)),
|