openhands-sdk 1.44.0__tar.gz → 1.45.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.
Files changed (302) hide show
  1. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/PKG-INFO +4 -4
  2. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/acp_agent.py +313 -41
  3. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/acp_file_credentials.py +19 -0
  4. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/acp_tracing.py +1 -1
  5. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/base.py +6 -20
  6. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/response_dispatch.py +23 -1
  7. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/memory.py +11 -2
  8. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/prompt.py +5 -3
  9. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/sections/static.py +30 -4
  10. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/event_store.py +51 -10
  11. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/events_list_base.py +2 -2
  12. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/impl/local_conversation.py +76 -21
  13. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/impl/remote_conversation.py +9 -2
  14. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/secret_registry.py +54 -3
  15. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/state.py +12 -3
  16. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/extensions/installation/manager.py +2 -1
  17. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/git/git_changes.py +6 -8
  18. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/hooks/config.py +2 -1
  19. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/io/local.py +3 -1
  20. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/auth/credentials.py +8 -3
  21. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/llm_profile_store.py +2 -1
  22. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/mixins/fn_call_converter.py +9 -1
  23. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/model_features.py +4 -0
  24. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/verified_models.py +53 -1
  25. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/observability/laminar.py +16 -0
  26. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/discovery.py +2 -1
  27. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/fetch.py +2 -1
  28. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/format/__init__.py +5 -4
  29. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/format/agent_plugins.py +71 -23
  30. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/format/base.py +59 -0
  31. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/format/claude_code.py +9 -48
  32. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/installed.py +2 -1
  33. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/source.py +6 -2
  34. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/profiles/agent_profile.py +3 -3
  35. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/profiles/agent_profile_store.py +2 -1
  36. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/profiles/resolver.py +24 -17
  37. openhands_sdk-1.45.0/openhands/sdk/settings/acp_install_catalog.py +254 -0
  38. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/settings/acp_providers.py +263 -47
  39. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/settings/model.py +3 -1
  40. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/fetch.py +2 -1
  41. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/installed.py +2 -2
  42. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/skill.py +3 -3
  43. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/utils.py +4 -3
  44. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/subagent/load.py +14 -2
  45. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/registry.py +6 -1
  46. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/tool.py +16 -9
  47. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/command.py +21 -2
  48. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/path.py +48 -0
  49. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/remote/base.py +0 -1
  50. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands_sdk.egg-info/PKG-INFO +4 -4
  51. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands_sdk.egg-info/SOURCES.txt +2 -0
  52. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands_sdk.egg-info/requires.txt +3 -3
  53. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/pyproject.toml +4 -4
  54. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/__init__.py +0 -0
  55. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/__init__.py +0 -0
  56. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/acp_models.py +0 -0
  57. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/agent.py +0 -0
  58. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/critic_mixin.py +0 -0
  59. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/parallel_executor.py +0 -0
  60. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/utils.py +0 -0
  61. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/banner.py +0 -0
  62. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/__init__.py +0 -0
  63. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/agent_context.py +0 -0
  64. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/condenser/__init__.py +0 -0
  65. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/condenser/base.py +0 -0
  66. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
  67. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
  68. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
  69. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
  70. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/condenser/utils.py +0 -0
  71. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/__init__.py +0 -0
  72. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/presets.py +0 -0
  73. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/registry.py +0 -0
  74. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/section.py +0 -0
  75. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
  76. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
  77. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/sections/planning.py +0 -0
  78. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
  79. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
  80. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/__init__.py +0 -0
  81. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/manipulation_indices.py +0 -0
  82. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/properties/__init__.py +0 -0
  83. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/properties/base.py +0 -0
  84. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
  85. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
  86. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
  87. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
  88. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/view.py +0 -0
  89. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/__init__.py +0 -0
  90. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/base.py +0 -0
  91. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/cancellation.py +0 -0
  92. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/conversation.py +0 -0
  93. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
  94. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/exceptions.py +0 -0
  95. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
  96. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/goal/__init__.py +0 -0
  97. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/goal/controller.py +0 -0
  98. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/goal/judge.py +0 -0
  99. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/goal/prompts.py +0 -0
  100. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/goal/runner.py +0 -0
  101. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
  102. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/persistence_const.py +0 -0
  103. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/request.py +0 -0
  104. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
  105. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/response_utils.py +0 -0
  106. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/stuck_detector.py +0 -0
  107. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/title_utils.py +0 -0
  108. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/types.py +0 -0
  109. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
  110. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
  111. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
  112. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/credential.py +0 -0
  113. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/__init__.py +0 -0
  114. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/base.py +0 -0
  115. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/__init__.py +0 -0
  116. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
  117. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
  118. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
  119. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/api/client.py +0 -0
  120. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/api/critic.py +0 -0
  121. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
  122. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
  123. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
  124. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/result.py +0 -0
  125. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/__init__.py +0 -0
  126. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/acp_tool_call.py +0 -0
  127. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/base.py +0 -0
  128. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/condenser.py +0 -0
  129. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/conversation_error.py +0 -0
  130. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/conversation_state.py +0 -0
  131. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/error_classification.py +0 -0
  132. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/hook_execution.py +0 -0
  133. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/llm_completion_log.py +0 -0
  134. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
  135. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
  136. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
  137. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/llm_convertible/observation.py +0 -0
  138. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
  139. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
  140. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/resume_transcript.py +0 -0
  141. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/streaming_delta.py +0 -0
  142. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/token.py +0 -0
  143. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/types.py +0 -0
  144. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/user_action.py +0 -0
  145. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/extensions/__init__.py +0 -0
  146. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/extensions/fetch.py +0 -0
  147. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/extensions/installation/__init__.py +0 -0
  148. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/extensions/installation/info.py +0 -0
  149. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/extensions/installation/interface.py +0 -0
  150. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/extensions/installation/metadata.py +0 -0
  151. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/extensions/installation/utils.py +0 -0
  152. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/git/cached_repo.py +0 -0
  153. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/git/exceptions.py +0 -0
  154. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/git/git_commits.py +0 -0
  155. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/git/git_diff.py +0 -0
  156. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/git/models.py +0 -0
  157. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/git/utils.py +0 -0
  158. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/hooks/__init__.py +0 -0
  159. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
  160. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/hooks/executor.py +0 -0
  161. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/hooks/manager.py +0 -0
  162. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/hooks/types.py +0 -0
  163. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/io/__init__.py +0 -0
  164. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/io/base.py +0 -0
  165. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/io/cache.py +0 -0
  166. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/io/memory.py +0 -0
  167. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/__init__.py +0 -0
  168. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/auth/__init__.py +0 -0
  169. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/auth/openai.py +0 -0
  170. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/cleanup_profile.py +0 -0
  171. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/exceptions/__init__.py +0 -0
  172. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/exceptions/classifier.py +0 -0
  173. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/exceptions/mapping.py +0 -0
  174. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/exceptions/types.py +0 -0
  175. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/fallback_strategy.py +0 -0
  176. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/llm.py +0 -0
  177. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/llm_registry.py +0 -0
  178. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/llm_response.py +0 -0
  179. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/message.py +0 -0
  180. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
  181. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
  182. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/options/__init__.py +0 -0
  183. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/options/chat_options.py +0 -0
  184. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/options/common.py +0 -0
  185. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/options/responses_options.py +0 -0
  186. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/provider_connection_store.py +0 -0
  187. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/router/__init__.py +0 -0
  188. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/router/base.py +0 -0
  189. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
  190. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/router/impl/random.py +0 -0
  191. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/streaming.py +0 -0
  192. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/image_inline.py +0 -0
  193. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/image_resize.py +0 -0
  194. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
  195. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/metrics.py +0 -0
  196. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/model_info.py +0 -0
  197. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
  198. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
  199. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
  200. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
  201. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
  202. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
  203. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
  204. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/telemetry.py +0 -0
  205. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
  206. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
  207. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/logger/__init__.py +0 -0
  208. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/logger/logger.py +0 -0
  209. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/logger/rolling.py +0 -0
  210. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/marketplace/__init__.py +0 -0
  211. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/marketplace/registration.py +0 -0
  212. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/marketplace/registry.py +0 -0
  213. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/marketplace/types.py +0 -0
  214. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/mcp/__init__.py +0 -0
  215. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/mcp/client.py +0 -0
  216. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/mcp/config.py +0 -0
  217. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/mcp/definition.py +0 -0
  218. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/mcp/exceptions.py +0 -0
  219. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/mcp/tool.py +0 -0
  220. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/mcp/utils.py +0 -0
  221. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/observability/__init__.py +0 -0
  222. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/observability/utils.py +0 -0
  223. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/__init__.py +0 -0
  224. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
  225. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/loader.py +0 -0
  226. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/plugin.py +0 -0
  227. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/types.py +0 -0
  228. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/profiles/__init__.py +0 -0
  229. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/profiles/profile_refs.py +0 -0
  230. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/profiles/seed.py +0 -0
  231. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/py.typed +0 -0
  232. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/secret/__init__.py +0 -0
  233. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/secret/secrets.py +0 -0
  234. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/__init__.py +0 -0
  235. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/_shell_ast.py +0 -0
  236. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/analyzer.py +0 -0
  237. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/confirmation_policy.py +0 -0
  238. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
  239. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
  240. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
  241. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
  242. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
  243. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/ensemble.py +0 -0
  244. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/grayswan/__init__.py +0 -0
  245. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/grayswan/analyzer.py +0 -0
  246. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/grayswan/utils.py +0 -0
  247. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/llm_analyzer.py +0 -0
  248. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/risk.py +0 -0
  249. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/shell_parser.py +0 -0
  250. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/toolshield_helpers.py +0 -0
  251. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
  252. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/settings/__init__.py +0 -0
  253. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/settings/api_models.py +0 -0
  254. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/settings/metadata.py +0 -0
  255. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/__init__.py +0 -0
  256. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/exceptions.py +0 -0
  257. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/execute.py +0 -0
  258. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/trigger.py +0 -0
  259. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/types.py +0 -0
  260. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/subagent/__init__.py +0 -0
  261. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/subagent/registry.py +0 -0
  262. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/subagent/schema.py +0 -0
  263. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/testing/__init__.py +0 -0
  264. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/testing/test_llm.py +0 -0
  265. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/__init__.py +0 -0
  266. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/builtins/__init__.py +0 -0
  267. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/builtins/finish.py +0 -0
  268. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
  269. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
  270. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/builtins/think.py +0 -0
  271. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/builtins/vision_inspect.py +0 -0
  272. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/client_tool.py +0 -0
  273. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/defaults.py +0 -0
  274. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/schema.py +0 -0
  275. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/spec.py +0 -0
  276. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/__init__.py +0 -0
  277. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/async_executor.py +0 -0
  278. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/async_utils.py +0 -0
  279. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/cipher.py +0 -0
  280. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/datetime.py +0 -0
  281. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/deprecation.py +0 -0
  282. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/files.py +0 -0
  283. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/github.py +0 -0
  284. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/json.py +0 -0
  285. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/models.py +0 -0
  286. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/paging.py +0 -0
  287. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
  288. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/redact.py +0 -0
  289. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/truncate.py +0 -0
  290. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/visualize.py +0 -0
  291. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/__init__.py +0 -0
  292. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/base.py +0 -0
  293. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/local.py +0 -0
  294. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/models.py +0 -0
  295. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
  296. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
  297. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
  298. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/repo.py +0 -0
  299. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/workspace.py +0 -0
  300. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
  301. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands_sdk.egg-info/top_level.txt +0 -0
  302. {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/setup.cfg +0 -0
@@ -1,16 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-sdk
3
- Version: 1.44.0
3
+ Version: 1.45.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
7
7
  Project-URL: Documentation, https://docs.openhands.dev/sdk
8
8
  Project-URL: Bug Tracker, https://github.com/OpenHands/software-agent-sdk/issues
9
9
  Requires-Python: >=3.12
10
- Requires-Dist: agent-client-protocol>=0.10.1
10
+ Requires-Dist: agent-client-protocol<0.11.0,>=0.10.1
11
11
  Requires-Dist: deprecation>=2.1.0
12
12
  Requires-Dist: fakeredis[lua]>=2.32.1
13
- Requires-Dist: fastmcp>=3.0.0
13
+ Requires-Dist: fastmcp>=3.2.0
14
14
  Requires-Dist: filelock>=3.20.1
15
15
  Requires-Dist: httpx[socks]>=0.27.0
16
16
  Requires-Dist: jsonschema>=4.23.0
@@ -22,7 +22,7 @@ Requires-Dist: python-frontmatter>=1.1.0
22
22
  Requires-Dist: python-json-logger>=3.3.0
23
23
  Requires-Dist: tenacity>=9.1.2
24
24
  Requires-Dist: websockets>=12
25
- Requires-Dist: lmnr<0.8.0,>=0.7.56
25
+ Requires-Dist: lmnr<0.8.0,>=0.7.60
26
26
  Requires-Dist: tree-sitter>=0.25
27
27
  Requires-Dist: tree-sitter-bash>=0.25
28
28
  Provides-Extra: boto3
@@ -27,7 +27,14 @@ import threading
27
27
  import time
28
28
  import uuid
29
29
  import weakref
30
- from collections.abc import Awaitable, Callable, Collection, Generator, Iterable
30
+ from collections.abc import (
31
+ Awaitable,
32
+ Callable,
33
+ Collection,
34
+ Generator,
35
+ Iterable,
36
+ Sequence,
37
+ )
31
38
  from concurrent.futures import Future
32
39
  from pathlib import Path
33
40
  from typing import TYPE_CHECKING, Any, Final, Literal, NamedTuple
@@ -69,6 +76,7 @@ from openhands.sdk.agent.acp_file_credentials import (
69
76
  codex_auth_file,
70
77
  codex_auth_file_is_chatgpt,
71
78
  create_file_credential_lifecycle,
79
+ file_credential_looks_usable,
72
80
  write_secret_file,
73
81
  )
74
82
  from openhands.sdk.agent.acp_models import ACPModelInfo
@@ -94,11 +102,15 @@ from openhands.sdk.logger import get_logger
94
102
  from openhands.sdk.mcp.config import MCPServer
95
103
  from openhands.sdk.observability.laminar import maybe_init_laminar, observe
96
104
  from openhands.sdk.settings.acp_providers import (
105
+ ACP_PROVIDERS,
106
+ ACPEnvConflictSpec,
97
107
  ACPFileSecretSpec,
108
+ ACPProviderInfo,
98
109
  build_session_model_meta,
99
110
  default_acp_file_secrets,
100
111
  detect_acp_provider_by_agent_name,
101
112
  detect_acp_provider_by_command,
113
+ get_acp_provider,
102
114
  )
103
115
  from openhands.sdk.tool import Tool # noqa: TC002
104
116
  from openhands.sdk.tool.builtins.finish import FinishAction, FinishObservation
@@ -145,6 +157,10 @@ _ACP_CANCEL_DRAIN_TIMEOUT: float = float(
145
157
  )
146
158
 
147
159
  _ACP_AUTH_TIMEOUT: float = float(os.environ.get("ACP_AUTH_TIMEOUT", "30.0"))
160
+ _ACP_NPX_CACHE_WARM_TIMEOUT: float = float(
161
+ os.environ.get("ACP_NPX_CACHE_WARM_TIMEOUT", "300")
162
+ )
163
+ _ACP_VERSION_RE = re.compile(r"v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)")
148
164
 
149
165
  _ACP_PROMPT_RETRY_DELAYS: tuple[float, ...] = (5.0, 15.0, 30.0) # seconds
150
166
 
@@ -170,26 +186,6 @@ _RETRIABLE_SERVER_ERROR_CODES: frozenset[int] = frozenset({-32603})
170
186
  MAX_ACP_CONTENT_CHARS: int = 30_000
171
187
  _ACP_SUBPROCESS_LOG_LINE_CHARS = 4_000
172
188
 
173
- # Env vars that must be removed from the subprocess environment when a
174
- # particular "dominant" env var is present.
175
- #
176
- # Rationale: Claude Code's subscription auth uses CLAUDE_CODE_OAUTH_TOKEN, a
177
- # bearer validated against api.anthropic.com. A co-present ANTHROPIC_API_KEY
178
- # would take precedence over the token (silently bypassing the subscription),
179
- # and an ANTHROPIC_BASE_URL would route the bearer to a proxy that rejects it —
180
- # either silently breaks the intended OAuth auth. When the OAuth token is the
181
- # active credential we strip both so the subprocess authenticates with the
182
- # token against api.anthropic.com.
183
- #
184
- # Keyed on the credential itself (CLAUDE_CODE_OAUTH_TOKEN), NOT on
185
- # CLAUDE_CONFIG_DIR: the config dir is a *location* lever (data-dir isolation,
186
- # #1019) that is orthogonal to which credential is active. Keying the strip on
187
- # it wrongly fired during API-key isolation and missed the conflict when the
188
- # token arrived via env without isolation (#3588).
189
- _ENV_CONFLICT_MAP: dict[str, frozenset[str]] = {
190
- "CLAUDE_CODE_OAUTH_TOKEN": frozenset({"ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL"}),
191
- }
192
-
193
189
  # Number of trailing characters of an ACP session id retained in log lines
194
190
  # for correlation. ACP session ids are server-issued tokens whose possession
195
191
  # alone is enough to call ``session/load`` — they're effectively bearer
@@ -242,8 +238,9 @@ _STREAM_READER_LIMIT: int = 100 * 1024 * 1024 # 100 MiB
242
238
 
243
239
  # Minimum interval between on_activity heartbeat signals (seconds).
244
240
  # Throttled to avoid excessive calls while still keeping the idle timer
245
- # well below the ~20 min runtime-api kill threshold.
246
- _ACTIVITY_SIGNAL_INTERVAL: float = 30.0
241
+ # well below the ~20 min runtime-api kill threshold. Shared with the
242
+ # agent-server's streaming-delta heartbeat so both paths keep the same pace.
243
+ ACTIVITY_SIGNAL_INTERVAL: Final[float] = 30.0
247
244
 
248
245
  # ACP tool-call statuses that represent a terminal outcome. Non-terminal
249
246
  # statuses (``pending``, ``in_progress``) mean the call is still in flight
@@ -291,6 +288,44 @@ _AUTH_METHOD_ENV_MAP: dict[str, str] = {
291
288
  _GEMINI_OAUTH_PATH = Path(".gemini") / "oauth_creds.json"
292
289
 
293
290
 
291
+ def _preconfigured_credentials(
292
+ provider: ACPProviderInfo | None,
293
+ specs: Sequence[ACPFileSecretSpec],
294
+ env: dict[str, str],
295
+ ) -> list[str]:
296
+ """Credential sources already in place, named as they were supplied.
297
+
298
+ A server whose advertised auth methods we cannot perform may still be
299
+ authenticated out of band — from a credential file seeded onto disk, or
300
+ from the provider key it reads straight out of its environment. Both make
301
+ "no credential is available" the wrong thing to say. Read off the registry
302
+ record rather than provider names, so this holds for any provider.
303
+ """
304
+ present: list[str] = []
305
+ for spec in specs:
306
+ location = env.get(spec.env_var)
307
+ if not location:
308
+ continue
309
+ path = Path(location)
310
+ if spec.env_points_to == "dir":
311
+ path = path / spec.filename
312
+ if not path.is_file():
313
+ continue
314
+ # A file that exists but cannot authenticate is the case that most
315
+ # needs the warning, so presence alone is not enough where the SDK
316
+ # knows the format.
317
+ try:
318
+ text = path.read_text(encoding="utf-8")
319
+ except (OSError, UnicodeError):
320
+ continue
321
+ if file_credential_looks_usable(spec.secret_name, text):
322
+ present.append(spec.secret_name)
323
+ api_key_var = provider.api_key_env_var if provider is not None else None
324
+ if api_key_var and env.get(api_key_var):
325
+ present.append(api_key_var)
326
+ return present
327
+
328
+
294
329
  def _select_auth_method(
295
330
  auth_methods: list[Any],
296
331
  env: dict[str, str],
@@ -336,7 +371,11 @@ def _select_auth_method(
336
371
  return None
337
372
 
338
373
 
339
- def _auth_selection_failure_reason(auth_methods: list[Any], env: dict[str, str]) -> str:
374
+ def _auth_selection_failure_reason(
375
+ auth_methods: list[Any],
376
+ env: dict[str, str],
377
+ provider: ACPProviderInfo | None = None,
378
+ ) -> str:
340
379
  method_ids = {m.id for m in auth_methods}
341
380
  reasons: list[str] = []
342
381
  if "chat-gpt" in method_ids:
@@ -349,18 +388,113 @@ def _auth_selection_failure_reason(auth_methods: list[Any], env: dict[str, str])
349
388
  env.get(name) for name in ("CODEX_API_KEY", "OPENAI_API_KEY")
350
389
  ):
351
390
  reasons.append("CODEX_API_KEY and OPENAI_API_KEY are unset")
391
+ if provider is not None and provider.api_key_env_var:
392
+ if not env.get(provider.api_key_env_var):
393
+ reasons.append(f"{provider.api_key_env_var} is unset")
394
+ # ``type: "terminal"`` marks a login only an interactive TTY can complete,
395
+ # which the runtime does not have. Named so the log says why the method was
396
+ # never a candidate rather than implying a missing credential.
397
+ terminal_ids = sorted(
398
+ m.id for m in auth_methods if getattr(m, "type", None) == "terminal"
399
+ )
400
+ if terminal_ids:
401
+ reasons.append(
402
+ f"{', '.join(terminal_ids)} needs an interactive terminal, which the "
403
+ "headless runtime cannot provide"
404
+ )
352
405
  return "; ".join(reasons) or "no supported credential source is available"
353
406
 
354
407
 
355
- def _warn_auth_selection_failure(auth_methods: list[Any], env: dict[str, str]) -> None:
408
+ def _warn_auth_selection_failure(
409
+ auth_methods: list[Any],
410
+ env: dict[str, str],
411
+ provider: ACPProviderInfo | None = None,
412
+ ) -> None:
356
413
  logger.warning(
357
414
  "ACP server offers auth methods %s but no matching credential is available "
358
415
  "(%s) — session creation may fail",
359
416
  [m.id for m in auth_methods],
360
- _auth_selection_failure_reason(auth_methods, env),
417
+ _auth_selection_failure_reason(auth_methods, env, provider),
361
418
  )
362
419
 
363
420
 
421
+ def _log_acp_provider_version(agent_name: str, agent_version: str) -> None:
422
+ try:
423
+ provider = detect_acp_provider_by_agent_name(agent_name)
424
+ if provider is None:
425
+ return
426
+ pinned_version = None
427
+ for command_part in provider.default_command:
428
+ package, separator, version = command_part.rpartition("@")
429
+ if separator and package:
430
+ pinned_version = version
431
+ break
432
+ reported_version = next(
433
+ (
434
+ match.group(1)
435
+ for value in (agent_version, agent_name)
436
+ if (match := _ACP_VERSION_RE.search(value)) is not None
437
+ ),
438
+ None,
439
+ )
440
+ logger.info(
441
+ "ACP provider version: provider=%s, pinned_version=%r, "
442
+ "agent_name=%r, agent_version=%r, reported_version=%r",
443
+ provider.key,
444
+ pinned_version,
445
+ agent_name,
446
+ agent_version,
447
+ reported_version,
448
+ )
449
+ if reported_version is None:
450
+ logger.warning(
451
+ "Could not parse ACP provider version: provider=%s, "
452
+ "pinned_version=%r, agent_name=%r, agent_version=%r",
453
+ provider.key,
454
+ pinned_version,
455
+ agent_name,
456
+ agent_version,
457
+ )
458
+ elif pinned_version is not None and reported_version != pinned_version:
459
+ logger.warning(
460
+ "ACP provider version mismatch: provider=%s, pinned_version=%r, "
461
+ "reported_version=%r; provider was probably installed at runtime "
462
+ "via the npx fallback rather than preinstalled in the image",
463
+ provider.key,
464
+ pinned_version,
465
+ reported_version,
466
+ )
467
+ except Exception:
468
+ logger.warning(
469
+ "Could not verify ACP provider version: agent_name=%r, agent_version=%r",
470
+ agent_name,
471
+ agent_version,
472
+ )
473
+
474
+
475
+ def _npx_packages(command: list[str]) -> list[str]:
476
+ """Pinned npm specs an ``npx`` command installs, for the cache warm.
477
+
478
+ ``--package=<spec>`` flags when present — pi pins its ACP adapter and the
479
+ engine it spawns separately, and its positional argument is the bare binary
480
+ name, which would warm an unpinned package. Otherwise the first positional,
481
+ which is the package for the single-package providers.
482
+ """
483
+ if not command or command[0] != "npx":
484
+ return []
485
+ pinned = [
486
+ arg.removeprefix("--package=")
487
+ for arg in command[1:]
488
+ if arg.startswith("--package=")
489
+ ]
490
+ if pinned:
491
+ return pinned
492
+ for arg in command[1:]:
493
+ if not arg.startswith("-"):
494
+ return [arg]
495
+ return []
496
+
497
+
364
498
  def _with_codex_base_url(
365
499
  command: str, args: list[str], env: dict[str, str]
366
500
  ) -> dict[str, str]:
@@ -1429,13 +1563,13 @@ class _OpenHandsACPBridge:
1429
1563
  the server's idle_time grows unboundedly and the runtime-api kills
1430
1564
  the pod (default idle threshold ~20 min).
1431
1565
 
1432
- Throttled to at most once per _ACTIVITY_SIGNAL_INTERVAL seconds to
1566
+ Throttled to at most once per ACTIVITY_SIGNAL_INTERVAL seconds to
1433
1567
  avoid excessive overhead on chatty ACP servers.
1434
1568
  """
1435
1569
  if self.on_activity is None:
1436
1570
  return
1437
1571
  now = time.monotonic()
1438
- if now - self._last_activity_signal >= _ACTIVITY_SIGNAL_INTERVAL:
1572
+ if now - self._last_activity_signal >= ACTIVITY_SIGNAL_INTERVAL:
1439
1573
  self._last_activity_signal = now
1440
1574
  try:
1441
1575
  self.on_activity()
@@ -1552,8 +1686,8 @@ class ACPAgent(AgentBase):
1552
1686
  default=None,
1553
1687
  description=(
1554
1688
  "Provider registry key identifying which ACP CLI this agent runs "
1555
- "('claude-code', 'codex', 'gemini-cli', or 'custom'); None when the "
1556
- "agent is built directly rather than via ACPAgentSettings. Set by "
1689
+ "(any ACP_PROVIDERS key, or 'custom'); None when the agent is "
1690
+ "built directly rather than via ACPAgentSettings. Set by "
1557
1691
  "ACPAgentSettings.create_agent() from ACPAgentSettings.acp_server so "
1558
1692
  "the authoritative key survives onto the agent — and thus onto "
1559
1693
  "ConversationInfo.agent — because the launch command in acp_command "
@@ -1572,7 +1706,8 @@ class ACPAgent(AgentBase):
1572
1706
  "Session mode ID to set after creating a session. "
1573
1707
  "If None (default), auto-detected from the ACP server type: "
1574
1708
  "'bypassPermissions' for claude-agent-acp, "
1575
- "'agent-full-access' for codex-acp."
1709
+ "'agent-full-access' for codex-acp; a provider with no such mode "
1710
+ "(pi-acp) skips the call."
1576
1711
  ),
1577
1712
  )
1578
1713
  acp_prompt_timeout: float = Field(
@@ -1699,6 +1834,21 @@ class ACPAgent(AgentBase):
1699
1834
  ),
1700
1835
  )
1701
1836
 
1837
+ @field_validator("agent_context")
1838
+ @classmethod
1839
+ def _drop_project_skills(cls, value: AgentContext | None) -> AgentContext | None:
1840
+ """Clear ``load_project_skills`` — ACP CLIs read the repo themselves.
1841
+
1842
+ Claude Code, Codex and Gemini already ingest ``AGENTS.md`` / ``CLAUDE.md``
1843
+ and their own project skills from the session cwd, so loading them here
1844
+ too would put that content in the prompt twice. Normalised rather than
1845
+ rejected: callers legitimately set the flag on a shared context they also
1846
+ use for OpenHands agents (#4019).
1847
+ """
1848
+ if value is None or not value.load_project_skills:
1849
+ return value
1850
+ return value.model_copy(update={"load_project_skills": False})
1851
+
1702
1852
  def model_post_init(self, __context: object) -> None:
1703
1853
  super().model_post_init(__context)
1704
1854
  # Propagate the actual model name to the sentinel LLM and its
@@ -2325,6 +2475,59 @@ class ACPAgent(AgentBase):
2325
2475
  root = Path(state.workspace.working_dir) / ".openhands" / "acp" / subdir
2326
2476
  return Path(os.path.abspath(root))
2327
2477
 
2478
+ def _acp_npm_cache_dir(self, state: ConversationState) -> Path:
2479
+ if state.persistence_dir:
2480
+ root = Path(state.persistence_dir).parent
2481
+ else:
2482
+ root = Path(state.workspace.working_dir) / ".openhands"
2483
+ cache_dir = root / "npm-cache"
2484
+ cache_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
2485
+ return Path(os.path.abspath(cache_dir))
2486
+
2487
+ async def _warm_npx_cache(
2488
+ self,
2489
+ packages: Sequence[str],
2490
+ provider_key: str,
2491
+ env: dict[str, str],
2492
+ cwd: str,
2493
+ ) -> None:
2494
+ logger.info(
2495
+ "Warming ACP provider npx cache: provider=%s, packages=%s; "
2496
+ "first use may download the provider before session startup",
2497
+ provider_key,
2498
+ list(packages),
2499
+ )
2500
+ package_args = [arg for package in packages for arg in ("--package", package)]
2501
+ process = await asyncio.create_subprocess_exec(
2502
+ "npx",
2503
+ "--yes",
2504
+ "--prefer-offline",
2505
+ *package_args,
2506
+ "--",
2507
+ "node",
2508
+ "-e",
2509
+ "",
2510
+ cwd=cwd,
2511
+ env=env,
2512
+ stdin=asyncio.subprocess.DEVNULL,
2513
+ stdout=asyncio.subprocess.DEVNULL,
2514
+ stderr=asyncio.subprocess.DEVNULL,
2515
+ )
2516
+ try:
2517
+ await asyncio.wait_for(process.wait(), timeout=_ACP_NPX_CACHE_WARM_TIMEOUT)
2518
+ except TimeoutError as exc:
2519
+ process.kill()
2520
+ await process.wait()
2521
+ raise RuntimeError(
2522
+ "ACP provider cache warm timed out after "
2523
+ f"{_ACP_NPX_CACHE_WARM_TIMEOUT:.0f}s for {provider_key}"
2524
+ ) from exc
2525
+ if process.returncode:
2526
+ raise RuntimeError(
2527
+ f"ACP provider cache warm failed for {provider_key} "
2528
+ f"(exit code {process.returncode})"
2529
+ )
2530
+
2328
2531
  def _isolate_acp_data_dir(
2329
2532
  self, state: ConversationState, env: dict[str, str]
2330
2533
  ) -> None:
@@ -2351,8 +2554,9 @@ class ACPAgent(AgentBase):
2351
2554
  ``ANTHROPIC_API_KEY`` — API-key Claude gets the same per-conversation
2352
2555
  isolation (and pause/resume continuity) as OAuth Claude (#3588).
2353
2556
 
2354
- ``HOME`` (gemini-cli's only lever — it hard-codes ``~/.gemini`` and
2355
- ignores ``XDG``) has a wider blast radius than the surgical
2557
+ ``HOME`` (the only lever for gemini-cli, which hard-codes ``~/.gemini``
2558
+ and ignores ``XDG``, and for pi-acp, whose session map is hard-coded to
2559
+ ``~/.pi/pi-acp``) has a wider blast radius than the surgical
2356
2560
  ``CODEX_HOME`` / ``CLAUDE_CONFIG_DIR``: it also relocates the home dir
2357
2561
  seen by anything the CLI subprocess itself spawns (``git``, ``npm``,
2358
2562
  ``node``, shells — e.g. ``~/.gitconfig``, ``~/.npmrc``, the npm cache).
@@ -2372,6 +2576,42 @@ class ACPAgent(AgentBase):
2372
2576
  data_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
2373
2577
  env[env_var] = str(data_dir)
2374
2578
 
2579
+ def _resolved_provider(self) -> ACPProviderInfo | None:
2580
+ """The provider this agent runs, preferring the authoritative key.
2581
+
2582
+ ``acp_server`` is set by ``ACPAgentSettings.create_agent()`` and is the
2583
+ only identity that survives a custom or aliased ``acp_command``; the
2584
+ command is the fallback for an agent constructed directly.
2585
+ """
2586
+ return get_acp_provider(
2587
+ self.acp_server or ""
2588
+ ) or detect_acp_provider_by_command(self.acp_command)
2589
+
2590
+ def _strip_conflicting_env(self, env: dict[str, str]) -> None:
2591
+ """Remove env vars that would defeat this provider's own credential.
2592
+
2593
+ Scoped to the resolved provider's :attr:`ACPProviderInfo.env_conflicts`:
2594
+ the same variable is another provider's *credential* (a provider whose
2595
+ ``api_key_env_var`` is ``ANTHROPIC_API_KEY``), and stripping it there
2596
+ leaves it with nothing to authenticate with.
2597
+
2598
+ An unrecognised server keeps the conservative union: without an identity
2599
+ we cannot tell whose credential a dominant variable belongs to, and a
2600
+ directly-constructed ``ACPAgent`` running Claude Code is the case the
2601
+ rule was written for (#3588).
2602
+ """
2603
+ provider = self._resolved_provider()
2604
+ if provider is not None:
2605
+ specs: tuple[ACPEnvConflictSpec, ...] = provider.env_conflicts
2606
+ else:
2607
+ specs = tuple(
2608
+ spec for info in ACP_PROVIDERS.values() for spec in info.env_conflicts
2609
+ )
2610
+ for spec in specs:
2611
+ if spec.dominant in env:
2612
+ for name in spec.strip:
2613
+ env.pop(name, None)
2614
+
2375
2615
  def _materialise_file_secrets(
2376
2616
  self, state: ConversationState, env: dict[str, str]
2377
2617
  ) -> None:
@@ -2634,13 +2874,7 @@ class ACPAgent(AgentBase):
2634
2874
  # Strip CLAUDECODE so nested Claude Code instances don't refuse to start
2635
2875
  env.pop("CLAUDECODE", None)
2636
2876
 
2637
- # Strip env vars that conflict with an active auth mechanism: an active
2638
- # CLAUDE_CODE_OAUTH_TOKEN must not coexist with ANTHROPIC_API_KEY (which
2639
- # takes precedence) or ANTHROPIC_BASE_URL (proxies the bearer). See #3588.
2640
- for dominant, conflicts in _ENV_CONFLICT_MAP.items():
2641
- if dominant in env:
2642
- for conflict in conflicts:
2643
- env.pop(conflict, None)
2877
+ self._strip_conflicting_env(env)
2644
2878
 
2645
2879
  command = self.acp_command[0]
2646
2880
  args = list(self.acp_command[1:]) + list(self.acp_args)
@@ -2650,6 +2884,22 @@ class ACPAgent(AgentBase):
2650
2884
  env = _with_codex_base_url(command, args, env)
2651
2885
 
2652
2886
  working_dir = str(state.workspace.working_dir)
2887
+ provider = detect_acp_provider_by_command(self.acp_command)
2888
+ packages = _npx_packages(self.acp_command)
2889
+ if provider is not None and packages:
2890
+ env["npm_config_cache"] = str(self._acp_npm_cache_dir(state))
2891
+ try:
2892
+ self._executor.run_async(
2893
+ self._warm_npx_cache(packages, provider.key, env, working_dir),
2894
+ timeout=_ACP_NPX_CACHE_WARM_TIMEOUT + 5,
2895
+ )
2896
+ except Exception as error:
2897
+ logger.warning(
2898
+ "ACP provider npx cache warm failed: provider=%s; "
2899
+ "continuing with normal startup: %s",
2900
+ provider.key,
2901
+ error,
2902
+ )
2653
2903
 
2654
2904
  # Prior ACP session id — typically survives agent-server restarts via
2655
2905
  # ConversationState.agent_state (serialized into base_state.json).
@@ -2763,6 +3013,7 @@ class ACPAgent(AgentBase):
2763
3013
  agent_name,
2764
3014
  agent_version,
2765
3015
  )
3016
+ _log_acp_provider_version(agent_name, agent_version)
2766
3017
 
2767
3018
  # Translate any configured MCP servers into ACP protocol objects,
2768
3019
  # gating remote (http/sse) transports on what this server advertised
@@ -2830,7 +3081,28 @@ class ACPAgent(AgentBase):
2830
3081
  ) from exc
2831
3082
  await self._flush_file_credentials()
2832
3083
  else:
2833
- _warn_auth_selection_failure(auth_methods, env)
3084
+ # A server whose advertised methods we cannot perform may
3085
+ # still be authenticated out of band — from a seeded
3086
+ # credential file or the provider key it reads out of the
3087
+ # environment — so warning about a login we cannot do
3088
+ # would be noise whenever either is present.
3089
+ auth_provider = (
3090
+ self._resolved_provider()
3091
+ or detect_acp_provider_by_agent_name(agent_name)
3092
+ )
3093
+ configured = _preconfigured_credentials(
3094
+ auth_provider, self.acp_file_secrets, env
3095
+ )
3096
+ if configured:
3097
+ logger.info(
3098
+ "ACP server offers auth methods %s that cannot be "
3099
+ "performed here; using the already-configured "
3100
+ "credential(s) %s instead",
3101
+ [m.id for m in auth_methods],
3102
+ configured,
3103
+ )
3104
+ else:
3105
+ _warn_auth_selection_failure(auth_methods, env, auth_provider)
2834
3106
 
2835
3107
  # Resume the prior ACP session if we have its id. If the server
2836
3108
  # has forgotten it (state wiped, new host, etc.) fall through to
@@ -515,3 +515,22 @@ _FILE_CREDENTIAL_LIFECYCLES = {
515
515
 
516
516
  def supports_file_credential_binding(secret_name: str) -> bool:
517
517
  return secret_name in _FILE_CREDENTIAL_LIFECYCLES
518
+
519
+
520
+ _FILE_CREDENTIAL_VALIDATORS: dict[str, Callable[[str], bool]] = {
521
+ CODEX_AUTH_SECRET_NAME: is_valid_codex_auth,
522
+ }
523
+
524
+
525
+ def file_credential_looks_usable(secret_name: str, text: str) -> bool:
526
+ """Whether a materialised credential file is plausibly usable.
527
+
528
+ Only a *present* file can be checked here, and only for the few secrets the
529
+ SDK knows the shape of. An unrecognised secret gets the benefit of the
530
+ doubt: the CLI owns the format, and refusing to believe a credential we
531
+ cannot parse would warn on every provider we have not special-cased —
532
+ which is the noise this is meant to avoid. Returning ``True`` for unknown
533
+ names keeps that judgement with the provider.
534
+ """
535
+ validator = _FILE_CREDENTIAL_VALIDATORS.get(secret_name)
536
+ return True if validator is None else validator(text)
@@ -25,7 +25,7 @@ logger = get_logger(__name__)
25
25
 
26
26
  #: Marks a span as produced by an ACP agent rather than the native loop.
27
27
  AGENT_KIND_METADATA_KEY = "agent_kind"
28
- #: Which ACP CLI produced it ('claude-code', 'codex', 'gemini-cli', 'custom').
28
+ #: Which ACP CLI produced it: an ACP_PROVIDERS key, or 'custom'.
29
29
  ACP_SERVER_METADATA_KEY = "acp_server"
30
30
  #: Model the ACP CLI reported for the turn, when it reports one.
31
31
  ACP_MODEL_METADATA_KEY = "acp_model"
@@ -43,8 +43,8 @@ from openhands.sdk.tool.builtins.vision_inspect import (
43
43
  VisionInspectTool,
44
44
  has_vision_profile_available,
45
45
  )
46
- from openhands.sdk.utils.deprecation import deprecated
47
46
  from openhands.sdk.utils.models import DiscriminatedUnionMixin
47
+ from openhands.sdk.utils.path import get_user_persistence_dir
48
48
 
49
49
 
50
50
  if TYPE_CHECKING:
@@ -58,10 +58,11 @@ logger = get_logger(__name__)
58
58
 
59
59
 
60
60
  # -- SOUL.md loader -------------------------------------------------------
61
- # SOUL.md is the agent's identity file (~/.openhands/SOUL.md). When present
62
- # it replaces the default identity in the system prompt.
61
+ # SOUL.md is the agent's identity file, ``SOUL.md`` under the user persistence
62
+ # directory (~/.openhands/SOUL.md absent OH_PERSISTENCE_DIR). When present it
63
+ # replaces the default identity in the system prompt.
63
64
 
64
- _SOUL_PATH = os.path.join(os.path.expanduser("~"), ".openhands", "SOUL.md")
65
+ _SOUL_PATH = get_user_persistence_dir() / "SOUL.md"
65
66
  _DEFAULT_SOUL = (
66
67
  "You are OpenHands agent, a helpful AI assistant that can interact"
67
68
  " with a computer to solve tasks."
@@ -84,7 +85,7 @@ _PRESET_BY_FILENAME: dict[str, PromptPreset] = {
84
85
 
85
86
 
86
87
  def _load_soul_md() -> str:
87
- """Load ``~/.openhands/SOUL.md``, falling back to the built-in default."""
88
+ """Load ``SOUL.md`` from the user persistence dir, else the built-in default."""
88
89
  try:
89
90
  with open(_SOUL_PATH, encoding="utf-8") as f:
90
91
  content = f.read().strip()
@@ -735,21 +736,6 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
735
736
 
736
737
  return self
737
738
 
738
- @deprecated(
739
- deprecated_in="1.40.0",
740
- removed_in="1.45.0",
741
- details="Use model_dump(exclude_none=True) instead.",
742
- )
743
- def model_dump_succint(self, **kwargs):
744
- """Like model_dump, but excludes None fields by default."""
745
- if "exclude_none" not in kwargs:
746
- kwargs["exclude_none"] = True
747
- dumped = super().model_dump(**kwargs)
748
- # remove tool schema details for brevity
749
- if "tools" in dumped and isinstance(dumped["tools"], dict):
750
- dumped["tools"] = list(dumped["tools"].keys())
751
- return dumped
752
-
753
739
  def get_all_llms(self) -> Generator[LLM]:
754
740
  """Recursively yield unique *base-class* LLM objects reachable from `self`.
755
741
 
@@ -281,7 +281,7 @@ class ResponseDispatchMixin:
281
281
  """Create and emit a MessageEvent, running critic if configured."""
282
282
  msg_event = MessageEvent(
283
283
  source="agent",
284
- llm_message=message,
284
+ llm_message=self._mask_secrets(message, conversation),
285
285
  llm_response_id=llm_response.id,
286
286
  )
287
287
  if self.critic is not None and self.critic.mode == "finish_and_message":
@@ -293,6 +293,28 @@ class ResponseDispatchMixin:
293
293
  on_event(msg_event)
294
294
  return msg_event
295
295
 
296
+ @staticmethod
297
+ def _mask_secrets(message: Message, conversation: LocalConversation) -> Message:
298
+ """Return ``message`` with registered secret values masked in its text.
299
+
300
+ ``thinking_blocks`` and ``responses_reasoning_item`` are left alone:
301
+ they are signed provider payloads, and rewriting them invalidates the
302
+ signature replayed on the next request.
303
+ """
304
+ mask = conversation.state.secret_registry.mask_secrets_in_output
305
+ reasoning = message.reasoning_content
306
+ return message.model_copy(
307
+ update={
308
+ "content": [
309
+ part.model_copy(update={"text": mask(part.text)})
310
+ if isinstance(part, TextContent)
311
+ else part
312
+ for part in message.content
313
+ ],
314
+ "reasoning_content": mask(reasoning) if reasoning else reasoning,
315
+ }
316
+ )
317
+
296
318
  def _send_corrective_nudge(self, on_event: ConversationCallbackType) -> None:
297
319
  """Inject corrective feedback when no tool call and no content.
298
320