openhands-sdk 1.29.2__tar.gz → 1.30.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.
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/PKG-INFO +1 -1
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/__init__.py +2 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/agent.py +119 -2
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/base.py +75 -25
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/utils.py +8 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/agent_context.py +29 -31
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +10 -0
- openhands_sdk-1.30.0/openhands/sdk/context/prompts/presets.py +109 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/sections/__init__.py +4 -1
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/sections/dynamic.py +1 -2
- openhands_sdk-1.29.2/openhands/sdk/agent/prompts/system_prompt_planning.j2 → openhands_sdk-1.30.0/openhands/sdk/context/prompts/sections/planning.py +40 -2
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/sections/static.py +10 -5
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/base.py +20 -1
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/conversation.py +12 -1
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/impl/local_conversation.py +108 -28
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/impl/remote_conversation.py +13 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/persistence_const.py +2 -1
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/request.py +9 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/title_utils.py +5 -1
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/types.py +29 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/__init__.py +7 -1
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/auth/openai.py +17 -2
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/exceptions/__init__.py +4 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/exceptions/classifier.py +38 -21
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/exceptions/mapping.py +7 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/exceptions/types.py +14 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/llm.py +91 -9
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/llm_profile_store.py +28 -1
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/options/chat_options.py +21 -4
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/options/responses_options.py +18 -3
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/router/base.py +9 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/retry_mixin.py +9 -1
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/marketplace/registration.py +11 -2
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/mcp/tool.py +30 -4
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/observability/laminar.py +23 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/plugin/__init__.py +9 -0
- openhands_sdk-1.30.0/openhands/sdk/plugin/discovery.py +203 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/plugin/types.py +10 -1
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/profiles/__init__.py +14 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/profiles/agent_profile.py +35 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/profiles/agent_profile_store.py +131 -2
- openhands_sdk-1.30.0/openhands/sdk/profiles/profile_refs.py +149 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/profiles/resolver.py +3 -3
- openhands_sdk-1.30.0/openhands/sdk/profiles/seed.py +71 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/settings/model.py +8 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/subagent/__init__.py +4 -1
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/subagent/load.py +45 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/subagent/schema.py +10 -1
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/testing/test_llm.py +4 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/redact.py +14 -10
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands_sdk.egg-info/PKG-INFO +1 -1
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands_sdk.egg-info/SOURCES.txt +6 -30
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/pyproject.toml +1 -1
- openhands_sdk-1.29.2/openhands/sdk/agent/prompts/in_context_learning_example.j2 +0 -175
- openhands_sdk-1.29.2/openhands/sdk/agent/prompts/in_context_learning_example_suffix.j2 +0 -3
- openhands_sdk-1.29.2/openhands/sdk/agent/prompts/model_specific/anthropic_claude.j2 +0 -3
- openhands_sdk-1.29.2/openhands/sdk/agent/prompts/model_specific/google_gemini.j2 +0 -1
- openhands_sdk-1.29.2/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5-codex.j2 +0 -2
- openhands_sdk-1.29.2/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5.j2 +0 -18
- openhands_sdk-1.29.2/openhands/sdk/agent/prompts/security_policy.j2 +0 -25
- openhands_sdk-1.29.2/openhands/sdk/agent/prompts/security_risk_assessment.j2 +0 -31
- openhands_sdk-1.29.2/openhands/sdk/agent/prompts/self_documentation.j2 +0 -15
- openhands_sdk-1.29.2/openhands/sdk/agent/prompts/system_prompt.j2 +0 -152
- openhands_sdk-1.29.2/openhands/sdk/agent/prompts/system_prompt_interactive.j2 +0 -14
- openhands_sdk-1.29.2/openhands/sdk/agent/prompts/system_prompt_long_horizon.j2 +0 -40
- openhands_sdk-1.29.2/openhands/sdk/agent/prompts/system_prompt_tech_philosophy.j2 +0 -122
- openhands_sdk-1.29.2/openhands/sdk/context/prompts/presets.py +0 -71
- openhands_sdk-1.29.2/openhands/sdk/context/prompts/templates/system_message_suffix.j2 +0 -52
- openhands_sdk-1.29.2/openhands/sdk/profiles/profile_refs.py +0 -173
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/acp_agent.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/acp_models.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/critic_mixin.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/parallel_executor.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/agent/response_dispatch.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/banner.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/condenser/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/condenser/base.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/condenser/utils.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/prompt.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/registry.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/section.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/manipulation_indices.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/properties/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/properties/base.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/context/view/view.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/cancellation.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/event_store.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/events_list_base.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/exceptions.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/goal/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/goal/controller.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/goal/judge.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/goal/prompts.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/goal/runner.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/response_utils.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/secret_registry.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/state.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/stuck_detector.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/base.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/api/client.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/api/critic.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/critic/result.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/acp_tool_call.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/base.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/condenser.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/conversation_error.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/conversation_state.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/hook_execution.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/llm_completion_log.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/llm_convertible/observation.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/resume_transcript.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/streaming_delta.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/token.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/types.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/event/user_action.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/extensions/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/extensions/fetch.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/extensions/installation/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/extensions/installation/info.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/extensions/installation/interface.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/extensions/installation/manager.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/extensions/installation/metadata.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/extensions/installation/utils.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/git/cached_repo.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/git/exceptions.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/git/git_changes.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/git/git_diff.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/git/models.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/git/utils.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/hooks/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/hooks/config.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/hooks/executor.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/hooks/manager.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/hooks/types.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/io/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/io/base.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/io/cache.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/io/local.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/io/memory.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/auth/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/auth/credentials.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/fallback_strategy.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/llm_registry.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/llm_response.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/message.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/options/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/options/common.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/router/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/router/impl/random.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/streaming.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/image_inline.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/image_resize.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/metrics.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/model_features.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/model_info.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/telemetry.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/verified_models.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/logger/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/logger/logger.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/logger/rolling.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/marketplace/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/marketplace/registry.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/marketplace/types.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/mcp/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/mcp/client.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/mcp/definition.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/mcp/exceptions.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/mcp/utils.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/observability/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/observability/utils.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/plugin/fetch.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/plugin/installed.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/plugin/loader.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/plugin/plugin.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/plugin/source.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/py.typed +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/secret/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/secret/secrets.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/_shell_ast.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/analyzer.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/confirmation_policy.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/ensemble.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/grayswan/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/grayswan/analyzer.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/grayswan/utils.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/llm_analyzer.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/risk.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/security/shell_parser.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/settings/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/settings/acp_providers.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/settings/api_models.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/settings/metadata.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/exceptions.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/execute.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/fetch.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/installed.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/skill.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/trigger.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/types.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/skills/utils.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/subagent/registry.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/testing/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/builtins/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/builtins/finish.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/builtins/think.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/client_tool.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/registry.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/schema.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/spec.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/tool/tool.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/async_executor.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/async_utils.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/cipher.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/command.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/datetime.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/deprecation.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/github.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/json.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/models.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/paging.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/path.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/truncate.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/utils/visualize.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/base.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/local.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/models.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/remote/base.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/repo.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands/sdk/workspace/workspace.py +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands_sdk.egg-info/requires.txt +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/openhands_sdk.egg-info/top_level.txt +0 -0
- {openhands_sdk-1.29.2 → openhands_sdk-1.30.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.30.0
|
|
4
4
|
Summary: OpenHands SDK - Core functionality for building AI agents
|
|
5
5
|
Project-URL: Source, https://github.com/OpenHands/software-agent-sdk
|
|
6
6
|
Project-URL: Homepage, https://github.com/OpenHands/software-agent-sdk
|
|
@@ -85,6 +85,7 @@ from openhands.sdk.skills import (
|
|
|
85
85
|
)
|
|
86
86
|
from openhands.sdk.subagent import (
|
|
87
87
|
agent_definition_to_factory,
|
|
88
|
+
discover_agents,
|
|
88
89
|
load_agents_from_dir,
|
|
89
90
|
load_project_agents,
|
|
90
91
|
load_user_agents,
|
|
@@ -197,6 +198,7 @@ __all__ = [
|
|
|
197
198
|
"load_project_agents",
|
|
198
199
|
"load_user_agents",
|
|
199
200
|
"load_agents_from_dir",
|
|
201
|
+
"discover_agents",
|
|
200
202
|
"agent_definition_to_factory",
|
|
201
203
|
"load_project_skills",
|
|
202
204
|
"load_skills_from_dir",
|
|
@@ -27,7 +27,7 @@ from openhands.sdk.agent.utils import (
|
|
|
27
27
|
parse_tool_call_arguments,
|
|
28
28
|
prepare_llm_messages,
|
|
29
29
|
)
|
|
30
|
-
from openhands.sdk.context.prompts.presets import create_registry
|
|
30
|
+
from openhands.sdk.context.prompts.presets import PromptPreset, create_registry
|
|
31
31
|
from openhands.sdk.conversation import (
|
|
32
32
|
CancellationToken,
|
|
33
33
|
ConversationCallbackType,
|
|
@@ -51,6 +51,7 @@ from openhands.sdk.event.condenser import (
|
|
|
51
51
|
CondensationRequest,
|
|
52
52
|
)
|
|
53
53
|
from openhands.sdk.llm import (
|
|
54
|
+
LLM,
|
|
54
55
|
LLMResponse,
|
|
55
56
|
Message,
|
|
56
57
|
MessageToolCall,
|
|
@@ -61,9 +62,11 @@ from openhands.sdk.llm import (
|
|
|
61
62
|
)
|
|
62
63
|
from openhands.sdk.llm.exceptions import (
|
|
63
64
|
FunctionCallValidationError,
|
|
65
|
+
LLMContentPolicyViolationError,
|
|
64
66
|
LLMContextWindowExceedError,
|
|
65
67
|
LLMMalformedConversationHistoryError,
|
|
66
68
|
)
|
|
69
|
+
from openhands.sdk.llm.router.base import RouterLLM
|
|
67
70
|
from openhands.sdk.logger import get_logger
|
|
68
71
|
from openhands.sdk.observability.laminar import (
|
|
69
72
|
maybe_init_laminar,
|
|
@@ -78,6 +81,7 @@ from openhands.sdk.tool import (
|
|
|
78
81
|
|
|
79
82
|
|
|
80
83
|
if TYPE_CHECKING:
|
|
84
|
+
from openhands.sdk.llm.llm import LLMCallContext
|
|
81
85
|
from openhands.sdk.tool import ToolDefinition
|
|
82
86
|
from openhands.sdk.mcp.tool import MCPToolDefinition
|
|
83
87
|
from openhands.sdk.tool.builtins import (
|
|
@@ -112,6 +116,36 @@ def _tool_has_summary_param(tool: ToolDefinition) -> bool:
|
|
|
112
116
|
INIT_STATE_PREFIX_SCAN_WINDOW = 3
|
|
113
117
|
|
|
114
118
|
|
|
119
|
+
def _latest_user_message_contains_image(messages: list[Message]) -> bool:
|
|
120
|
+
for message in reversed(messages):
|
|
121
|
+
if message.role == "user":
|
|
122
|
+
return message.contains_image
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _non_multimodal_image_message(model: str) -> Message:
|
|
127
|
+
return Message(
|
|
128
|
+
role="assistant",
|
|
129
|
+
content=[
|
|
130
|
+
TextContent(
|
|
131
|
+
text=(
|
|
132
|
+
"I received your image, but the currently selected model "
|
|
133
|
+
f"({model}) does not support image understanding. Please "
|
|
134
|
+
"switch to a multimodal model to analyze the image."
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
],
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _should_handle_non_multimodal_image_input(
|
|
142
|
+
llm: LLM, messages: list[Message]
|
|
143
|
+
) -> bool:
|
|
144
|
+
if isinstance(llm, RouterLLM):
|
|
145
|
+
return False
|
|
146
|
+
return _latest_user_message_contains_image(messages) and not llm.vision_is_active()
|
|
147
|
+
|
|
148
|
+
|
|
115
149
|
@dataclass(frozen=True, slots=True)
|
|
116
150
|
class _ActionBatch:
|
|
117
151
|
"""Immutable result of preparing a batch of actions for execution.
|
|
@@ -473,7 +507,10 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
473
507
|
secret_infos = state.secret_registry.get_secret_infos()
|
|
474
508
|
|
|
475
509
|
ctx = self._build_prompt_context(additional_secret_infos=secret_infos)
|
|
476
|
-
|
|
510
|
+
# The dynamic tier is preset-independent; fall back to the default tier for a
|
|
511
|
+
# custom Jinja template (preset None), as before.
|
|
512
|
+
preset = self._prompt_preset or PromptPreset.DEFAULT
|
|
513
|
+
return create_registry(preset).build(ctx).dynamic
|
|
477
514
|
|
|
478
515
|
def _execute_actions(
|
|
479
516
|
self,
|
|
@@ -571,6 +608,10 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
571
608
|
"skipping hook check for legacy conversation state."
|
|
572
609
|
)
|
|
573
610
|
|
|
611
|
+
# Build per-conversation context once and thread it through all
|
|
612
|
+
# LLM calls in this step (avoids shared mutable state on the LLM).
|
|
613
|
+
call_context: LLMCallContext = conversation.get_llm_call_context()
|
|
614
|
+
|
|
574
615
|
# Prepare LLM messages from the cached, incrementally-maintained view.
|
|
575
616
|
# See https://github.com/OpenHands/software-agent-sdk/issues/3053.
|
|
576
617
|
_messages_or_condensation = prepare_llm_messages(
|
|
@@ -584,6 +625,20 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
584
625
|
|
|
585
626
|
_messages = _messages_or_condensation
|
|
586
627
|
|
|
628
|
+
if _should_handle_non_multimodal_image_input(self.llm, _messages):
|
|
629
|
+
logger.info(
|
|
630
|
+
"Image input received while selected model does not support vision: %s",
|
|
631
|
+
self.llm.model,
|
|
632
|
+
)
|
|
633
|
+
on_event(
|
|
634
|
+
MessageEvent(
|
|
635
|
+
source="agent",
|
|
636
|
+
llm_message=_non_multimodal_image_message(self.llm.model),
|
|
637
|
+
)
|
|
638
|
+
)
|
|
639
|
+
state.execution_status = ConversationExecutionStatus.FINISHED
|
|
640
|
+
return
|
|
641
|
+
|
|
587
642
|
logger.debug(
|
|
588
643
|
"Sending messages to LLM: "
|
|
589
644
|
f"{json.dumps([m.model_dump() for m in _messages[1:]], indent=2)}"
|
|
@@ -595,6 +650,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
595
650
|
_messages,
|
|
596
651
|
tools=list(self.tools_map.values()),
|
|
597
652
|
on_token=on_token,
|
|
653
|
+
call_context=call_context,
|
|
598
654
|
)
|
|
599
655
|
except FunctionCallValidationError as e:
|
|
600
656
|
logger.warning(f"LLM generated malformed function call: {e}")
|
|
@@ -607,6 +663,28 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
607
663
|
)
|
|
608
664
|
on_event(error_message)
|
|
609
665
|
return
|
|
666
|
+
except LLMContentPolicyViolationError as e:
|
|
667
|
+
# Content-policy blocks are deterministic; nudge the model and let the
|
|
668
|
+
# run loop continue instead of emitting a fatal error.
|
|
669
|
+
logger.warning(f"LLM output blocked by content filter: {e}")
|
|
670
|
+
on_event(
|
|
671
|
+
MessageEvent(
|
|
672
|
+
source="user",
|
|
673
|
+
llm_message=Message(
|
|
674
|
+
role="user",
|
|
675
|
+
content=[
|
|
676
|
+
TextContent(
|
|
677
|
+
text=(
|
|
678
|
+
"Your previous response was blocked by the "
|
|
679
|
+
"model's content filter. Please continue, "
|
|
680
|
+
"rephrasing to avoid the flagged content."
|
|
681
|
+
)
|
|
682
|
+
)
|
|
683
|
+
],
|
|
684
|
+
),
|
|
685
|
+
)
|
|
686
|
+
)
|
|
687
|
+
return
|
|
610
688
|
except LLMMalformedConversationHistoryError as e:
|
|
611
689
|
# The provider rejected the current message history as structurally
|
|
612
690
|
# invalid (for example, broken tool_use/tool_result pairing). Route
|
|
@@ -712,6 +790,8 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
712
790
|
"skipping hook check for legacy conversation state."
|
|
713
791
|
)
|
|
714
792
|
|
|
793
|
+
call_context: LLMCallContext = conversation.get_llm_call_context()
|
|
794
|
+
|
|
715
795
|
# Prepare LLM messages from the cached, incrementally-maintained view.
|
|
716
796
|
# See https://github.com/OpenHands/software-agent-sdk/issues/3053.
|
|
717
797
|
_messages_or_condensation = await aprepare_llm_messages(
|
|
@@ -724,6 +804,20 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
724
804
|
|
|
725
805
|
_messages = _messages_or_condensation
|
|
726
806
|
|
|
807
|
+
if _should_handle_non_multimodal_image_input(self.llm, _messages):
|
|
808
|
+
logger.info(
|
|
809
|
+
"Image input received while selected model does not support vision: %s",
|
|
810
|
+
self.llm.model,
|
|
811
|
+
)
|
|
812
|
+
on_event(
|
|
813
|
+
MessageEvent(
|
|
814
|
+
source="agent",
|
|
815
|
+
llm_message=_non_multimodal_image_message(self.llm.model),
|
|
816
|
+
)
|
|
817
|
+
)
|
|
818
|
+
state.execution_status = ConversationExecutionStatus.FINISHED
|
|
819
|
+
return
|
|
820
|
+
|
|
727
821
|
logger.debug(
|
|
728
822
|
"Sending messages to LLM: "
|
|
729
823
|
f"{json.dumps([m.model_dump() for m in _messages[1:]], indent=2)}"
|
|
@@ -735,6 +829,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
735
829
|
_messages,
|
|
736
830
|
tools=list(self.tools_map.values()),
|
|
737
831
|
on_token=on_token,
|
|
832
|
+
call_context=call_context,
|
|
738
833
|
)
|
|
739
834
|
except FunctionCallValidationError as e:
|
|
740
835
|
logger.warning(f"LLM generated malformed function call: {e}")
|
|
@@ -747,6 +842,28 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
747
842
|
)
|
|
748
843
|
on_event(error_message)
|
|
749
844
|
return
|
|
845
|
+
except LLMContentPolicyViolationError as e:
|
|
846
|
+
# Content-policy blocks are deterministic; nudge the model and let the
|
|
847
|
+
# run loop continue instead of emitting a fatal error.
|
|
848
|
+
logger.warning(f"LLM output blocked by content filter: {e}")
|
|
849
|
+
on_event(
|
|
850
|
+
MessageEvent(
|
|
851
|
+
source="user",
|
|
852
|
+
llm_message=Message(
|
|
853
|
+
role="user",
|
|
854
|
+
content=[
|
|
855
|
+
TextContent(
|
|
856
|
+
text=(
|
|
857
|
+
"Your previous response was blocked by the "
|
|
858
|
+
"model's content filter. Please continue, "
|
|
859
|
+
"rephrasing to avoid the flagged content."
|
|
860
|
+
)
|
|
861
|
+
)
|
|
862
|
+
],
|
|
863
|
+
),
|
|
864
|
+
)
|
|
865
|
+
)
|
|
866
|
+
return
|
|
750
867
|
except LLMMalformedConversationHistoryError as e:
|
|
751
868
|
# The provider rejected the current message history as
|
|
752
869
|
# structurally invalid (for example, broken
|
|
@@ -9,6 +9,7 @@ from abc import ABC, abstractmethod
|
|
|
9
9
|
from collections import Counter
|
|
10
10
|
from collections.abc import Generator, Iterable, Sequence
|
|
11
11
|
from concurrent.futures import ThreadPoolExecutor
|
|
12
|
+
from pathlib import Path
|
|
12
13
|
from typing import TYPE_CHECKING, Any, Literal
|
|
13
14
|
|
|
14
15
|
from pydantic import (
|
|
@@ -25,7 +26,7 @@ from pydantic import (
|
|
|
25
26
|
|
|
26
27
|
from openhands.sdk.context.agent_context import AgentContext
|
|
27
28
|
from openhands.sdk.context.condenser import CondenserBase
|
|
28
|
-
from openhands.sdk.context.prompts.presets import create_registry
|
|
29
|
+
from openhands.sdk.context.prompts.presets import PromptPreset, create_registry
|
|
29
30
|
from openhands.sdk.context.prompts.prompt import render_template
|
|
30
31
|
from openhands.sdk.context.prompts.section import Platform, PromptContext
|
|
31
32
|
from openhands.sdk.critic.base import CriticBase
|
|
@@ -110,12 +111,21 @@ _DEFAULT_SOUL = (
|
|
|
110
111
|
" with a computer to solve tasks."
|
|
111
112
|
)
|
|
112
113
|
|
|
113
|
-
# Built-in prompt dir. The registry only stands in for
|
|
114
|
-
# subclass with its own prompts/
|
|
114
|
+
# Built-in prompt dir. The registry only stands in for built-in prompts here; a
|
|
115
|
+
# subclass with its own prompts/ keeps the Jinja render path.
|
|
115
116
|
_BUILTIN_PROMPT_DIR = os.path.realpath(
|
|
116
117
|
os.path.join(os.path.dirname(__file__), "prompts")
|
|
117
118
|
)
|
|
118
119
|
|
|
120
|
+
# Built-in ``system_prompt_filename`` values are back-compat sentinels (the .j2 files
|
|
121
|
+
# were removed) that select a registry preset. ``system_prompt_planning.j2`` keeps its
|
|
122
|
+
# historical name so ``get_planning_agent`` needs no change. Any other filename -- or a
|
|
123
|
+
# subclass's own ``prompt_dir`` -- falls through to the Jinja escape hatch.
|
|
124
|
+
_PRESET_BY_FILENAME: dict[str, PromptPreset] = {
|
|
125
|
+
"system_prompt.j2": PromptPreset.DEFAULT,
|
|
126
|
+
"system_prompt_planning.j2": PromptPreset.PLANNING,
|
|
127
|
+
}
|
|
128
|
+
|
|
119
129
|
|
|
120
130
|
def _load_soul_md() -> str:
|
|
121
131
|
"""Load ``~/.openhands/SOUL.md``, falling back to the built-in default."""
|
|
@@ -248,10 +258,14 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
248
258
|
security_policy_filename: str = Field(
|
|
249
259
|
default="security_policy.j2",
|
|
250
260
|
description=(
|
|
251
|
-
"Security policy
|
|
252
|
-
"-
|
|
253
|
-
"
|
|
254
|
-
"
|
|
261
|
+
"Security policy filename. The default 'security_policy.j2' is a "
|
|
262
|
+
"back-compat sentinel (the file was removed) that selects the built-in "
|
|
263
|
+
"default policy from the prompt registry -- it is not loaded from disk. "
|
|
264
|
+
"Any other value names a custom policy file whose contents are inserted "
|
|
265
|
+
"verbatim (NOT rendered as a Jinja template). Can be either:\n"
|
|
266
|
+
"- A relative filename (e.g., 'custom_security_policy.md') loaded from "
|
|
267
|
+
"the agent's prompts directory\n"
|
|
268
|
+
"- An absolute path (e.g., '/path/to/custom_security_policy.md')\n"
|
|
255
269
|
"- Empty string to disable security policy"
|
|
256
270
|
),
|
|
257
271
|
)
|
|
@@ -456,6 +470,18 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
456
470
|
"""Returns the name of the Agent."""
|
|
457
471
|
return self.__class__.__name__
|
|
458
472
|
|
|
473
|
+
@property
|
|
474
|
+
def _prompt_preset(self) -> PromptPreset | None:
|
|
475
|
+
"""The registry preset for this agent's built-in prompt.
|
|
476
|
+
|
|
477
|
+
``None`` means "take the Jinja escape hatch": a subclass with its own
|
|
478
|
+
``prompt_dir``, or a ``system_prompt_filename`` that is not a known built-in
|
|
479
|
+
sentinel (e.g. a custom relative name or an absolute path).
|
|
480
|
+
"""
|
|
481
|
+
if os.path.realpath(self.prompt_dir) != _BUILTIN_PROMPT_DIR:
|
|
482
|
+
return None
|
|
483
|
+
return _PRESET_BY_FILENAME.get(self.system_prompt_filename)
|
|
484
|
+
|
|
459
485
|
@property
|
|
460
486
|
def static_system_message(self) -> str:
|
|
461
487
|
"""Compute the static portion of the system message.
|
|
@@ -464,12 +490,11 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
464
490
|
per-conversation context. This static portion can be cached and reused
|
|
465
491
|
across conversations for better prompt caching efficiency.
|
|
466
492
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
``
|
|
470
|
-
``
|
|
471
|
-
|
|
472
|
-
custom ``security_policy_filename`` renders so its policy file is included.
|
|
493
|
+
Built-in prompts (the ``default`` and ``planning`` presets) are assembled from
|
|
494
|
+
the typed section registry, which also resolves a custom
|
|
495
|
+
``security_policy_filename``. Escape hatches keep the Jinja path: an inline
|
|
496
|
+
``system_prompt`` is returned verbatim; a custom ``system_prompt_filename`` or
|
|
497
|
+
subclass ``prompt_dir`` renders its own template.
|
|
473
498
|
|
|
474
499
|
Returns:
|
|
475
500
|
The static system prompt without dynamic context.
|
|
@@ -477,22 +502,17 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
477
502
|
if self.system_prompt is not None:
|
|
478
503
|
return self.system_prompt
|
|
479
504
|
|
|
480
|
-
# Escape hatch: custom
|
|
481
|
-
#
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
if (
|
|
485
|
-
self.system_prompt_filename != "system_prompt.j2"
|
|
486
|
-
or os.path.realpath(self.prompt_dir) != _BUILTIN_PROMPT_DIR
|
|
487
|
-
or self.security_policy_filename != "security_policy.j2"
|
|
488
|
-
):
|
|
505
|
+
# Escape hatch: a custom filename or a subclass's own prompt_dir renders its
|
|
506
|
+
# own Jinja template; everything else (incl. custom policies) uses the registry.
|
|
507
|
+
preset = self._prompt_preset
|
|
508
|
+
if preset is None:
|
|
489
509
|
return render_template(
|
|
490
510
|
prompt_dir=self.prompt_dir,
|
|
491
511
|
template_name=self.system_prompt_filename,
|
|
492
512
|
**self._resolved_template_kwargs(),
|
|
493
513
|
)
|
|
494
514
|
|
|
495
|
-
return create_registry().build(self._build_prompt_context()).static
|
|
515
|
+
return create_registry(preset).build(self._build_prompt_context()).static
|
|
496
516
|
|
|
497
517
|
def _resolved_template_kwargs(self) -> dict[str, object]:
|
|
498
518
|
"""Resolve the system-prompt template kwargs.
|
|
@@ -525,6 +545,24 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
525
545
|
template_kwargs["model_variant"] = spec.variant
|
|
526
546
|
return template_kwargs
|
|
527
547
|
|
|
548
|
+
def _read_custom_security_policy(self) -> str | None:
|
|
549
|
+
"""Raw contents of a custom security policy file -- inserted verbatim, NOT
|
|
550
|
+
rendered as a Jinja template.
|
|
551
|
+
|
|
552
|
+
Returns ``None`` -- so ``SecuritySection`` keeps its built-in default policy
|
|
553
|
+
-- when ``security_policy_filename`` is the default sentinel
|
|
554
|
+
``"security_policy.j2"`` (a string only; the file was removed, so it is never
|
|
555
|
+
read) or ``""`` (an empty *filename*, which disables the policy). A configured
|
|
556
|
+
file whose own contents are empty still returns ``""`` (an empty custom
|
|
557
|
+
policy), not ``None``.
|
|
558
|
+
|
|
559
|
+
Relative names resolve against ``prompt_dir``; absolute paths are used as-is.
|
|
560
|
+
"""
|
|
561
|
+
filename = self.security_policy_filename
|
|
562
|
+
if not filename or filename == "security_policy.j2":
|
|
563
|
+
return None
|
|
564
|
+
return (Path(self.prompt_dir) / filename).read_text(encoding="utf-8")
|
|
565
|
+
|
|
528
566
|
def _build_prompt_context(
|
|
529
567
|
self,
|
|
530
568
|
additional_secret_infos: list[dict[str, str | None]] | None = None,
|
|
@@ -578,8 +616,17 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
578
616
|
# secrets (additional_secret_infos), matching what <CUSTOM_SECRETS> shows.
|
|
579
617
|
secret_names = tuple(name for name, _ in secret_infos if name)
|
|
580
618
|
|
|
619
|
+
template_kwargs = self._resolved_template_kwargs()
|
|
620
|
+
# A custom security policy's content for SecuritySection (registry path only).
|
|
621
|
+
policy_content = self._read_custom_security_policy()
|
|
622
|
+
if policy_content is not None:
|
|
623
|
+
template_kwargs = {
|
|
624
|
+
**template_kwargs,
|
|
625
|
+
"security_policy_content": policy_content,
|
|
626
|
+
}
|
|
627
|
+
|
|
581
628
|
return PromptContext(
|
|
582
|
-
template_kwargs=
|
|
629
|
+
template_kwargs=template_kwargs,
|
|
583
630
|
tool_names=tuple(t.name for t in self.tools),
|
|
584
631
|
platform=Platform.current(),
|
|
585
632
|
working_dir=None,
|
|
@@ -613,7 +660,10 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
613
660
|
"""
|
|
614
661
|
if not self.agent_context:
|
|
615
662
|
return None
|
|
616
|
-
|
|
663
|
+
# The dynamic tier is preset-independent, so a custom Jinja template (preset
|
|
664
|
+
# None) still gets the default dynamic block, exactly as before.
|
|
665
|
+
preset = self._prompt_preset or PromptPreset.DEFAULT
|
|
666
|
+
return create_registry(preset).build(self._build_prompt_context()).dynamic
|
|
617
667
|
|
|
618
668
|
def init_state(
|
|
619
669
|
self,
|
|
@@ -31,6 +31,7 @@ from openhands.sdk.tool import Action, ToolDefinition
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
if TYPE_CHECKING:
|
|
34
|
+
from openhands.sdk.llm.llm import LLMCallContext
|
|
34
35
|
from openhands.sdk.llm.streaming import AnyTokenCallbackType
|
|
35
36
|
|
|
36
37
|
|
|
@@ -604,6 +605,7 @@ def make_llm_completion(
|
|
|
604
605
|
messages: list[Message],
|
|
605
606
|
tools: list[ToolDefinition] | None = None,
|
|
606
607
|
on_token: ConversationTokenCallbackType | None = None,
|
|
608
|
+
call_context: LLMCallContext | None = None,
|
|
607
609
|
) -> LLMResponse:
|
|
608
610
|
"""Make an LLM completion call with the provided messages and tools.
|
|
609
611
|
|
|
@@ -612,6 +614,7 @@ def make_llm_completion(
|
|
|
612
614
|
messages: The messages to send to the LLM
|
|
613
615
|
tools: Optional list of tools to provide to the LLM
|
|
614
616
|
on_token: Optional callback for streaming token updates
|
|
617
|
+
call_context: Per-conversation context for cache/session affinity.
|
|
615
618
|
|
|
616
619
|
Returns:
|
|
617
620
|
LLMResponse from the LLM completion call
|
|
@@ -636,6 +639,7 @@ def make_llm_completion(
|
|
|
636
639
|
store=False,
|
|
637
640
|
add_security_risk_prediction=True,
|
|
638
641
|
on_token=on_token,
|
|
642
|
+
call_context=call_context,
|
|
639
643
|
)
|
|
640
644
|
else:
|
|
641
645
|
return llm.completion(
|
|
@@ -643,6 +647,7 @@ def make_llm_completion(
|
|
|
643
647
|
tools=tools or [],
|
|
644
648
|
add_security_risk_prediction=True,
|
|
645
649
|
on_token=on_token,
|
|
650
|
+
call_context=call_context,
|
|
646
651
|
)
|
|
647
652
|
|
|
648
653
|
|
|
@@ -686,6 +691,7 @@ async def amake_llm_completion(
|
|
|
686
691
|
messages: list[Message],
|
|
687
692
|
tools: list[ToolDefinition] | None = None,
|
|
688
693
|
on_token: AnyTokenCallbackType | None = None,
|
|
694
|
+
call_context: LLMCallContext | None = None,
|
|
689
695
|
) -> LLMResponse:
|
|
690
696
|
"""Async variant of :func:`make_llm_completion`."""
|
|
691
697
|
if llm.uses_responses_api():
|
|
@@ -696,6 +702,7 @@ async def amake_llm_completion(
|
|
|
696
702
|
store=False,
|
|
697
703
|
add_security_risk_prediction=True,
|
|
698
704
|
on_token=on_token,
|
|
705
|
+
call_context=call_context,
|
|
699
706
|
)
|
|
700
707
|
else:
|
|
701
708
|
return await llm.acompletion(
|
|
@@ -703,4 +710,5 @@ async def amake_llm_completion(
|
|
|
703
710
|
tools=tools or [],
|
|
704
711
|
add_security_risk_prediction=True,
|
|
705
712
|
on_token=on_token,
|
|
713
|
+
call_context=call_context,
|
|
706
714
|
)
|
|
@@ -16,6 +16,8 @@ from pydantic import (
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
from openhands.sdk.context.prompts import render_template
|
|
19
|
+
from openhands.sdk.context.prompts.presets import create_registry
|
|
20
|
+
from openhands.sdk.context.prompts.section import PromptContext
|
|
19
21
|
from openhands.sdk.llm import Message, TextContent
|
|
20
22
|
from openhands.sdk.llm.utils.model_prompt_spec import get_model_prompt_spec
|
|
21
23
|
from openhands.sdk.logger import get_logger
|
|
@@ -41,8 +43,8 @@ PROMPT_DIR = pathlib.Path(__file__).parent / "prompts" / "templates"
|
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
class ResolvedDynamicData(NamedTuple):
|
|
44
|
-
"""Dynamic-tier inputs resolved once,
|
|
45
|
-
|
|
46
|
+
"""Dynamic-tier inputs resolved once, fed into the section registry
|
|
47
|
+
(skills gated by model family, secrets merged)."""
|
|
46
48
|
|
|
47
49
|
repo_skills: list[Skill]
|
|
48
50
|
available_skills_prompt: str
|
|
@@ -116,7 +118,8 @@ class AgentContext(BaseModel):
|
|
|
116
118
|
default_factory=list,
|
|
117
119
|
description=(
|
|
118
120
|
"Marketplace registrations for plugin resolution. Registrations with "
|
|
119
|
-
"auto_load=True are resolved by
|
|
121
|
+
"auto_load=True or a list of plugin names are resolved by "
|
|
122
|
+
"LocalConversation at startup."
|
|
120
123
|
),
|
|
121
124
|
json_schema_extra={"acp_compatible": True},
|
|
122
125
|
)
|
|
@@ -147,8 +150,8 @@ class AgentContext(BaseModel):
|
|
|
147
150
|
json_schema_extra={"acp_compatible": True},
|
|
148
151
|
)
|
|
149
152
|
current_datetime: datetime | str | None = Field(
|
|
150
|
-
# Timezone-aware local "now"
|
|
151
|
-
#
|
|
153
|
+
# Timezone-aware local "now"; get_formatted_datetime renders it to the
|
|
154
|
+
# minute for the prompt.
|
|
152
155
|
default_factory=lambda: datetime.now().astimezone(),
|
|
153
156
|
description=(
|
|
154
157
|
"Current date and time information to provide to the agent. "
|
|
@@ -263,13 +266,17 @@ class AgentContext(BaseModel):
|
|
|
263
266
|
|
|
264
267
|
Returns:
|
|
265
268
|
Formatted datetime string, or None if current_datetime is not set.
|
|
266
|
-
If current_datetime is a datetime object, it's formatted as
|
|
269
|
+
If current_datetime is a datetime object, it's formatted as
|
|
270
|
+
"YYYY-MM-DDTHH:MM" (no seconds, microseconds, or UTC offset).
|
|
267
271
|
If current_datetime is already a string, it's returned as-is.
|
|
268
272
|
"""
|
|
269
273
|
if self.current_datetime is None:
|
|
270
274
|
return None
|
|
271
275
|
if isinstance(self.current_datetime, datetime):
|
|
272
|
-
|
|
276
|
+
# Local wall-clock to the minute: drop seconds, microseconds, offset.
|
|
277
|
+
return self.current_datetime.replace(tzinfo=None).isoformat(
|
|
278
|
+
timespec="minutes"
|
|
279
|
+
)
|
|
273
280
|
return self.current_datetime
|
|
274
281
|
|
|
275
282
|
def _partition_skills(self) -> tuple[list[Skill], list[Skill]]:
|
|
@@ -327,27 +334,16 @@ class AgentContext(BaseModel):
|
|
|
327
334
|
data = self._resolve_dynamic_data(
|
|
328
335
|
llm_model, llm_model_canonical, additional_secret_infos
|
|
329
336
|
)
|
|
330
|
-
|
|
331
|
-
data.
|
|
332
|
-
|
|
333
|
-
or
|
|
334
|
-
or
|
|
335
|
-
|
|
337
|
+
ctx = PromptContext(
|
|
338
|
+
now=data.formatted_datetime,
|
|
339
|
+
repo_skills=tuple((s.name, s.content) for s in data.repo_skills),
|
|
340
|
+
available_skills_prompt=data.available_skills_prompt or None,
|
|
341
|
+
custom_suffix=self.system_message_suffix or None,
|
|
342
|
+
secret_infos=tuple(
|
|
343
|
+
(info["name"] or "", info["description"]) for info in data.secret_infos
|
|
344
|
+
),
|
|
336
345
|
)
|
|
337
|
-
|
|
338
|
-
formatted_text = render_template(
|
|
339
|
-
prompt_dir=str(PROMPT_DIR),
|
|
340
|
-
template_name="system_message_suffix.j2",
|
|
341
|
-
repo_skills=data.repo_skills,
|
|
342
|
-
system_message_suffix=self.system_message_suffix or "",
|
|
343
|
-
secret_infos=data.secret_infos,
|
|
344
|
-
available_skills_prompt=data.available_skills_prompt,
|
|
345
|
-
current_datetime=data.formatted_datetime,
|
|
346
|
-
).strip()
|
|
347
|
-
return formatted_text
|
|
348
|
-
elif self.system_message_suffix and self.system_message_suffix.strip():
|
|
349
|
-
return self.system_message_suffix.strip()
|
|
350
|
-
return None
|
|
346
|
+
return create_registry().build(ctx).dynamic
|
|
351
347
|
|
|
352
348
|
def _resolve_dynamic_data(
|
|
353
349
|
self,
|
|
@@ -433,9 +429,10 @@ class AgentContext(BaseModel):
|
|
|
433
429
|
this adapter only emits prompt-only context. Unsupported AgentContext
|
|
434
430
|
fields are rejected by :meth:`validate_acp_compatibility`.
|
|
435
431
|
|
|
436
|
-
The rendering reuses :meth:`get_system_message_suffix
|
|
437
|
-
|
|
438
|
-
identical prompt layout as the regular agent. This
|
|
432
|
+
The rendering reuses :meth:`get_system_message_suffix`, which assembles
|
|
433
|
+
the dynamic-tier sections via the shared prompt registry, so that ACP
|
|
434
|
+
agents receive the identical prompt layout as the regular agent. This
|
|
435
|
+
includes the
|
|
439
436
|
``<CUSTOM_SECRETS>`` block when secrets are present, informing the ACP
|
|
440
437
|
subprocess which environment variables are available. The actual secret
|
|
441
438
|
values are injected into the subprocess environment by
|
|
@@ -455,7 +452,8 @@ class AgentContext(BaseModel):
|
|
|
455
452
|
"""
|
|
456
453
|
self.validate_acp_compatibility()
|
|
457
454
|
# No model-specific skill filtering for ACP — delegate to the shared
|
|
458
|
-
#
|
|
455
|
+
# builder, whose dynamic-tier sections also emit the <CUSTOM_SECRETS>
|
|
456
|
+
# block from secrets.
|
|
459
457
|
return self.get_system_message_suffix(
|
|
460
458
|
additional_secret_infos=additional_secret_infos
|
|
461
459
|
)
|
|
@@ -80,6 +80,16 @@ class LLMSummarizingCondenser(RollingCondenser):
|
|
|
80
80
|
)
|
|
81
81
|
return self
|
|
82
82
|
|
|
83
|
+
@model_validator(mode="after")
|
|
84
|
+
def _disable_streaming_for_summary(self):
|
|
85
|
+
# Summaries are consumed whole with no on_token callback, which a
|
|
86
|
+
# streaming LLM requires. Disable streaming once so every summary path
|
|
87
|
+
# is covered. model_copy is non-mutating and shares usage_id/metrics,
|
|
88
|
+
# so summary tokens stay attributed to the conversation.
|
|
89
|
+
if self.llm.stream:
|
|
90
|
+
self.llm = self.llm.model_copy(update={"stream": False})
|
|
91
|
+
return self
|
|
92
|
+
|
|
83
93
|
def handles_condensation_requests(self) -> bool:
|
|
84
94
|
return True
|
|
85
95
|
|