openhands-sdk 1.46.0__tar.gz → 1.48.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 (303) hide show
  1. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/PKG-INFO +1 -1
  2. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/acp_agent.py +90 -4
  3. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/agent.py +58 -18
  4. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/response_dispatch.py +29 -4
  5. openhands_sdk-1.48.0/openhands/sdk/agent/stream_context.py +294 -0
  6. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/utils.py +1 -88
  7. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +2 -11
  8. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/impl/local_conversation.py +28 -6
  9. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/impl/remote_conversation.py +177 -71
  10. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/secret_registry.py +66 -0
  11. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/title_utils.py +24 -7
  12. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/hooks/executor.py +1 -2
  13. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/cleanup_profile.py +4 -10
  14. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/exceptions/__init__.py +2 -0
  15. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/exceptions/classifier.py +22 -0
  16. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/llm.py +78 -29
  17. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/retry_mixin.py +16 -2
  18. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/telemetry.py +14 -20
  19. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/profiles/agent_profile.py +24 -0
  20. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/profiles/resolver.py +7 -0
  21. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/settings/model.py +23 -1
  22. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/builtins/vision_inspect.py +1 -3
  23. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/models.py +5 -0
  24. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/redact.py +1 -1
  25. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +46 -3
  26. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/remote/base.py +25 -2
  27. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +110 -46
  28. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands_sdk.egg-info/PKG-INFO +1 -1
  29. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands_sdk.egg-info/SOURCES.txt +2 -0
  30. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/pyproject.toml +2 -2
  31. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/__init__.py +0 -0
  32. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/__init__.py +0 -0
  33. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/acp_file_credentials.py +0 -0
  34. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/acp_models.py +0 -0
  35. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/acp_tracing.py +0 -0
  36. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/base.py +0 -0
  37. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/critic_mixin.py +0 -0
  38. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/parallel_executor.py +0 -0
  39. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/banner.py +0 -0
  40. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/__init__.py +0 -0
  41. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/agent_context.py +0 -0
  42. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/condenser/__init__.py +0 -0
  43. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/condenser/base.py +0 -0
  44. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
  45. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
  46. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
  47. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/condenser/utils.py +0 -0
  48. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/memory.py +0 -0
  49. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/__init__.py +0 -0
  50. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/presets.py +0 -0
  51. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/prompt.py +0 -0
  52. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/registry.py +0 -0
  53. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/section.py +0 -0
  54. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
  55. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
  56. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/sections/planning.py +0 -0
  57. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/sections/static.py +0 -0
  58. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
  59. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
  60. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/__init__.py +0 -0
  61. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/manipulation_indices.py +0 -0
  62. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/properties/__init__.py +0 -0
  63. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/properties/base.py +0 -0
  64. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
  65. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
  66. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
  67. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
  68. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/view.py +0 -0
  69. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/__init__.py +0 -0
  70. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/base.py +0 -0
  71. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/cancellation.py +0 -0
  72. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/conversation.py +0 -0
  73. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
  74. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/event_store.py +0 -0
  75. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/events_list_base.py +0 -0
  76. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/exceptions.py +0 -0
  77. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
  78. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/goal/__init__.py +0 -0
  79. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/goal/controller.py +0 -0
  80. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/goal/judge.py +0 -0
  81. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/goal/prompts.py +0 -0
  82. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/goal/runner.py +0 -0
  83. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
  84. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/persistence_const.py +0 -0
  85. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/request.py +0 -0
  86. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
  87. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/response_utils.py +0 -0
  88. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/state.py +0 -0
  89. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/stuck_detector.py +0 -0
  90. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/types.py +0 -0
  91. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
  92. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
  93. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
  94. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/credential.py +0 -0
  95. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/__init__.py +0 -0
  96. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/base.py +0 -0
  97. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/__init__.py +0 -0
  98. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
  99. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
  100. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
  101. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/api/client.py +0 -0
  102. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/api/critic.py +0 -0
  103. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
  104. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
  105. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
  106. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/result.py +0 -0
  107. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/__init__.py +0 -0
  108. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/acp_tool_call.py +0 -0
  109. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/base.py +0 -0
  110. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/condenser.py +0 -0
  111. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/conversation_error.py +0 -0
  112. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/conversation_state.py +0 -0
  113. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/error_classification.py +0 -0
  114. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/hook_execution.py +0 -0
  115. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/llm_completion_log.py +0 -0
  116. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
  117. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
  118. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
  119. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/llm_convertible/observation.py +0 -0
  120. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
  121. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
  122. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/resume_transcript.py +0 -0
  123. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/streaming_delta.py +0 -0
  124. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/token.py +0 -0
  125. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/types.py +0 -0
  126. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/user_action.py +0 -0
  127. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/extensions/__init__.py +0 -0
  128. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/extensions/fetch.py +0 -0
  129. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/extensions/installation/__init__.py +0 -0
  130. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/extensions/installation/info.py +0 -0
  131. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/extensions/installation/interface.py +0 -0
  132. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/extensions/installation/manager.py +0 -0
  133. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/extensions/installation/metadata.py +0 -0
  134. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/extensions/installation/utils.py +0 -0
  135. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/git/cached_repo.py +0 -0
  136. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/git/exceptions.py +0 -0
  137. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/git/git_changes.py +0 -0
  138. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/git/git_commits.py +0 -0
  139. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/git/git_diff.py +0 -0
  140. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/git/models.py +0 -0
  141. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/git/utils.py +0 -0
  142. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/hooks/__init__.py +0 -0
  143. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/hooks/config.py +0 -0
  144. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
  145. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/hooks/manager.py +0 -0
  146. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/hooks/types.py +0 -0
  147. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/io/__init__.py +0 -0
  148. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/io/base.py +0 -0
  149. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/io/cache.py +0 -0
  150. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/io/local.py +0 -0
  151. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/io/memory.py +0 -0
  152. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/__init__.py +0 -0
  153. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/auth/__init__.py +0 -0
  154. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/auth/credentials.py +0 -0
  155. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/auth/openai.py +0 -0
  156. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/exceptions/mapping.py +0 -0
  157. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/exceptions/types.py +0 -0
  158. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/fallback_strategy.py +0 -0
  159. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/llm_profile_store.py +0 -0
  160. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/llm_registry.py +0 -0
  161. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/llm_response.py +0 -0
  162. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/message.py +0 -0
  163. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
  164. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
  165. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
  166. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/options/__init__.py +0 -0
  167. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/options/chat_options.py +0 -0
  168. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/options/common.py +0 -0
  169. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/options/responses_options.py +0 -0
  170. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/provider_connection_store.py +0 -0
  171. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/router/__init__.py +0 -0
  172. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/router/base.py +0 -0
  173. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
  174. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/router/impl/random.py +0 -0
  175. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/streaming.py +0 -0
  176. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/image_inline.py +0 -0
  177. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/image_resize.py +0 -0
  178. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
  179. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/metrics.py +0 -0
  180. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/model_features.py +0 -0
  181. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/model_info.py +0 -0
  182. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
  183. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
  184. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
  185. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
  186. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
  187. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
  188. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
  189. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/verified_models.py +0 -0
  190. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
  191. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/logger/__init__.py +0 -0
  192. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/logger/logger.py +0 -0
  193. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/logger/rolling.py +0 -0
  194. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/marketplace/__init__.py +0 -0
  195. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/marketplace/registration.py +0 -0
  196. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/marketplace/registry.py +0 -0
  197. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/marketplace/types.py +0 -0
  198. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/mcp/__init__.py +0 -0
  199. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/mcp/client.py +0 -0
  200. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/mcp/config.py +0 -0
  201. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/mcp/definition.py +0 -0
  202. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/mcp/exceptions.py +0 -0
  203. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/mcp/tool.py +0 -0
  204. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/mcp/utils.py +0 -0
  205. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/observability/__init__.py +0 -0
  206. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/observability/laminar.py +0 -0
  207. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/observability/utils.py +0 -0
  208. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/__init__.py +0 -0
  209. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/discovery.py +0 -0
  210. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/fetch.py +0 -0
  211. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/format/__init__.py +0 -0
  212. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/format/agent_plugins.py +0 -0
  213. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/format/base.py +0 -0
  214. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/format/claude_code.py +0 -0
  215. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
  216. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/installed.py +0 -0
  217. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/loader.py +0 -0
  218. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/plugin.py +0 -0
  219. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/source.py +0 -0
  220. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/types.py +0 -0
  221. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/profiles/__init__.py +0 -0
  222. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/profiles/agent_profile_store.py +0 -0
  223. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/profiles/profile_refs.py +0 -0
  224. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/profiles/seed.py +0 -0
  225. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/py.typed +0 -0
  226. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/secret/__init__.py +0 -0
  227. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/secret/secrets.py +0 -0
  228. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/__init__.py +0 -0
  229. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/_shell_ast.py +0 -0
  230. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/analyzer.py +0 -0
  231. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/confirmation_policy.py +0 -0
  232. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
  233. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
  234. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
  235. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
  236. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
  237. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/ensemble.py +0 -0
  238. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/grayswan/__init__.py +0 -0
  239. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/grayswan/analyzer.py +0 -0
  240. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/grayswan/utils.py +0 -0
  241. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/llm_analyzer.py +0 -0
  242. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/risk.py +0 -0
  243. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/shell_parser.py +0 -0
  244. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/toolshield_helpers.py +0 -0
  245. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
  246. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/settings/__init__.py +0 -0
  247. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/settings/acp_install_catalog.py +0 -0
  248. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/settings/acp_providers.py +0 -0
  249. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/settings/api_models.py +0 -0
  250. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/settings/metadata.py +0 -0
  251. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/__init__.py +0 -0
  252. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/exceptions.py +0 -0
  253. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/execute.py +0 -0
  254. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/fetch.py +0 -0
  255. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/installed.py +0 -0
  256. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/skill.py +0 -0
  257. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/trigger.py +0 -0
  258. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/types.py +0 -0
  259. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/utils.py +0 -0
  260. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/subagent/__init__.py +0 -0
  261. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/subagent/load.py +0 -0
  262. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/subagent/registry.py +0 -0
  263. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/subagent/schema.py +0 -0
  264. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/testing/__init__.py +0 -0
  265. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/testing/test_llm.py +0 -0
  266. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/__init__.py +0 -0
  267. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/builtins/__init__.py +0 -0
  268. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/builtins/finish.py +0 -0
  269. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
  270. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
  271. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/builtins/think.py +0 -0
  272. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/client_tool.py +0 -0
  273. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/defaults.py +0 -0
  274. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/registry.py +0 -0
  275. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/schema.py +0 -0
  276. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/spec.py +0 -0
  277. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/tool.py +0 -0
  278. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/__init__.py +0 -0
  279. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/async_executor.py +0 -0
  280. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/async_utils.py +0 -0
  281. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/cipher.py +0 -0
  282. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/command.py +0 -0
  283. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/datetime.py +0 -0
  284. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/deprecation.py +0 -0
  285. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/files.py +0 -0
  286. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/github.py +0 -0
  287. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/json.py +0 -0
  288. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/paging.py +0 -0
  289. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/path.py +0 -0
  290. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
  291. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/truncate.py +0 -0
  292. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/visualize.py +0 -0
  293. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/__init__.py +0 -0
  294. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/base.py +0 -0
  295. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/local.py +0 -0
  296. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/models.py +0 -0
  297. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
  298. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/repo.py +0 -0
  299. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/workspace.py +0 -0
  300. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
  301. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands_sdk.egg-info/requires.txt +0 -0
  302. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands_sdk.egg-info/top_level.txt +0 -0
  303. {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-sdk
3
- Version: 1.46.0
3
+ Version: 1.48.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
@@ -82,6 +82,7 @@ from openhands.sdk.agent.acp_file_credentials import (
82
82
  from openhands.sdk.agent.acp_models import ACPModelInfo
83
83
  from openhands.sdk.agent.acp_tracing import ACPTurnTrace
84
84
  from openhands.sdk.agent.base import AgentBase
85
+ from openhands.sdk.agent.stream_context import StreamContext
85
86
  from openhands.sdk.context import AgentContext
86
87
  from openhands.sdk.conversation.state import ConversationExecutionStatus
87
88
  from openhands.sdk.credential import (
@@ -1926,6 +1927,10 @@ class ACPAgent(AgentBase):
1926
1927
  _suffix_install_state: str = PrivateAttr(default="unused")
1927
1928
  _installed_suffix: str | None = PrivateAttr(default=None)
1928
1929
  _restart_session_on_next_turn: bool = PrivateAttr(default=False)
1930
+ # Stream identity for the turn in flight; see stream_context.py. Held on
1931
+ # the agent rather than threaded through the finalizers because the ACP
1932
+ # turn already resolves through four of them.
1933
+ _stream: StreamContext | None = PrivateAttr(default=None)
1929
1934
  _resumed_existing_session: bool = PrivateAttr(default=False)
1930
1935
  _file_credential_lifecycles: dict[str, ACPFileCredentialLifecycle] = PrivateAttr(
1931
1936
  default_factory=dict
@@ -1957,7 +1962,7 @@ class ACPAgent(AgentBase):
1957
1962
  self._file_credential_bindings[secret_name] = binding
1958
1963
 
1959
1964
  def restart_for_updated_credentials(self, secret_names: Collection[str]) -> None:
1960
- configured = {spec.secret_name for spec in self.acp_file_secrets}
1965
+ configured = {spec.secret_name for spec in self._active_file_secrets()}
1961
1966
  with self._file_credential_lock:
1962
1967
  self._replace_file_credentials_on_next_materialisation.update(
1963
1968
  configured.intersection(secret_names)
@@ -2452,7 +2457,7 @@ class ACPAgent(AgentBase):
2452
2457
  (their values are file blobs, not env vars the subprocess can reference
2453
2458
  by name).
2454
2459
  """
2455
- configured = {spec.secret_name for spec in self.acp_file_secrets}
2460
+ configured = {spec.secret_name for spec in self._active_file_secrets()}
2456
2461
  if not configured:
2457
2462
  return set()
2458
2463
  return set(state.secret_registry.secret_sources) & configured
@@ -2587,6 +2592,42 @@ class ACPAgent(AgentBase):
2587
2592
  self.acp_server or ""
2588
2593
  ) or detect_acp_provider_by_command(self.acp_command)
2589
2594
 
2595
+ def _active_file_secrets(self) -> list[ACPFileSecretSpec]:
2596
+ """The file-secret specs that apply to the provider this agent runs.
2597
+
2598
+ Drops the specs that are *another registered provider's* reserved
2599
+ credential and keeps everything else, so a harness added upstream cannot
2600
+ change how this provider's conversation treats a secret carrying the new
2601
+ reserved name (see #4923). A name several providers share stays: it is
2602
+ this provider's too.
2603
+
2604
+ Deliberately no provenance test. :attr:`acp_file_secrets` defaults to the
2605
+ union across the registry, but a persisted conversation carries whatever
2606
+ that union was when it was written, so comparing against today's default
2607
+ would read an older list as a caller override and silently stop scoping
2608
+ after an upgrade. Filtering by ownership needs no such distinction, and a
2609
+ spec for a CLI outside the registry is owned by nobody and always applies.
2610
+
2611
+ An unrecognised server keeps every spec, matching
2612
+ :meth:`_strip_conflicting_env`: without an identity we cannot tell whose
2613
+ credential a reserved name belongs to.
2614
+ """
2615
+ provider = self._resolved_provider()
2616
+ if provider is None:
2617
+ return list(self.acp_file_secrets)
2618
+ own = {spec.secret_name for spec in provider.file_secrets}
2619
+ owned_elsewhere = {
2620
+ spec.secret_name
2621
+ for key, info in ACP_PROVIDERS.items()
2622
+ if key != provider.key
2623
+ for spec in info.file_secrets
2624
+ } - own
2625
+ return [
2626
+ spec
2627
+ for spec in self.acp_file_secrets
2628
+ if spec.secret_name not in owned_elsewhere
2629
+ ]
2630
+
2590
2631
  def _strip_conflicting_env(self, env: dict[str, str]) -> None:
2591
2632
  """Remove env vars that would defeat this provider's own credential.
2592
2633
 
@@ -2615,7 +2656,7 @@ class ACPAgent(AgentBase):
2615
2656
  def _materialise_file_secrets(
2616
2657
  self, state: ConversationState, env: dict[str, str]
2617
2658
  ) -> None:
2618
- for spec in self.acp_file_secrets:
2659
+ for spec in self._active_file_secrets():
2619
2660
  name = spec.secret_name
2620
2661
  with self._file_credential_lock:
2621
2662
  replace_existing = (
@@ -3091,7 +3132,7 @@ class ACPAgent(AgentBase):
3091
3132
  or detect_acp_provider_by_agent_name(agent_name)
3092
3133
  )
3093
3134
  configured = _preconfigured_credentials(
3094
- auth_provider, self.acp_file_secrets, env
3135
+ auth_provider, self._active_file_secrets(), env
3095
3136
  )
3096
3137
  if configured:
3097
3138
  logger.info(
@@ -3688,7 +3729,13 @@ class ACPAgent(AgentBase):
3688
3729
  # completed turn for eval/remote consumers, matching #2190.
3689
3730
  finish_action = FinishAction(message=response_text)
3690
3731
  tc_id = str(uuid.uuid4())
3732
+ # An ACP turn's streamed text lands here, not in a MessageEvent, so
3733
+ # this is the event that retires the stream's slot.
3734
+ minted: dict[str, Any] = {}
3735
+ if self._stream is not None and (item_id := self._stream.claim()):
3736
+ minted["id"] = item_id
3691
3737
  action_event = ActionEvent(
3738
+ **minted,
3692
3739
  source="agent",
3693
3740
  thought=[],
3694
3741
  reasoning_content=thought_text or None,
@@ -3704,6 +3751,8 @@ class ACPAgent(AgentBase):
3704
3751
  llm_response_id=str(uuid.uuid4()),
3705
3752
  )
3706
3753
  on_event(action_event)
3754
+ if self._stream is not None and minted:
3755
+ self._stream.commit()
3707
3756
  on_event(
3708
3757
  ObservationEvent(
3709
3758
  observation=FinishObservation.from_text(text=response_text),
@@ -3865,6 +3914,19 @@ class ACPAgent(AgentBase):
3865
3914
  (``LocalConversation.arun``) goes through :meth:`astep`, which
3866
3915
  avoids the cross-thread state-lock deadlock described in #3348.
3867
3916
  """
3917
+ with StreamContext.open(conversation, on_token) as stream:
3918
+ self._stream = stream
3919
+ try:
3920
+ self._step(conversation, on_event, stream.token_callback)
3921
+ finally:
3922
+ self._stream = None
3923
+
3924
+ def _step(
3925
+ self,
3926
+ conversation: LocalConversation,
3927
+ on_event: ConversationCallbackType,
3928
+ on_token: ConversationTokenCallbackType | None = None,
3929
+ ) -> None:
3868
3930
  state = conversation.state
3869
3931
 
3870
3932
  if self._restart_session_on_next_turn:
@@ -3933,6 +3995,8 @@ class ACPAgent(AgentBase):
3933
3995
  )
3934
3996
  time.sleep(delay)
3935
3997
  self._cancel_inflight_tool_calls()
3998
+ if self._stream is not None:
3999
+ self._stream.new_attempt()
3936
4000
  self._reset_client_for_turn(
3937
4001
  on_token,
3938
4002
  on_event,
@@ -3963,6 +4027,8 @@ class ACPAgent(AgentBase):
3963
4027
  )
3964
4028
  time.sleep(delay)
3965
4029
  self._cancel_inflight_tool_calls()
4030
+ if self._stream is not None:
4031
+ self._stream.new_attempt()
3966
4032
  self._reset_client_for_turn(
3967
4033
  on_token,
3968
4034
  on_event,
@@ -4023,6 +4089,22 @@ class ACPAgent(AgentBase):
4023
4089
  supplied by ``LocalConversation.arun`` is responsible for taking
4024
4090
  the state lock around each individual event.
4025
4091
  """
4092
+ with StreamContext.open(conversation, on_token) as stream:
4093
+ self._stream = stream
4094
+ try:
4095
+ await self._astep(
4096
+ conversation, on_event, stream.token_callback, prompt_message
4097
+ )
4098
+ finally:
4099
+ self._stream = None
4100
+
4101
+ async def _astep(
4102
+ self,
4103
+ conversation: LocalConversation,
4104
+ on_event: ConversationCallbackType,
4105
+ on_token: ConversationTokenCallbackType | None = None,
4106
+ prompt_message: MessageEvent | None = None,
4107
+ ) -> None:
4026
4108
  state = conversation.state
4027
4109
 
4028
4110
  if self._restart_session_on_next_turn:
@@ -4099,6 +4181,8 @@ class ACPAgent(AgentBase):
4099
4181
  )
4100
4182
  await asyncio.sleep(delay)
4101
4183
  self._cancel_inflight_tool_calls()
4184
+ if self._stream is not None:
4185
+ self._stream.new_attempt()
4102
4186
  self._reset_client_for_turn(
4103
4187
  on_token,
4104
4188
  on_event,
@@ -4126,6 +4210,8 @@ class ACPAgent(AgentBase):
4126
4210
  )
4127
4211
  await asyncio.sleep(delay)
4128
4212
  self._cancel_inflight_tool_calls()
4213
+ if self._stream is not None:
4214
+ self._stream.new_attempt()
4129
4215
  self._reset_client_for_turn(
4130
4216
  on_token,
4131
4217
  on_event,
@@ -4,7 +4,7 @@ import json
4
4
  import re
5
5
  from collections.abc import Callable
6
6
  from dataclasses import dataclass, field
7
- from typing import TYPE_CHECKING
7
+ from typing import TYPE_CHECKING, Any
8
8
 
9
9
  from pydantic import PrivateAttr, ValidationError, model_validator
10
10
 
@@ -18,11 +18,10 @@ from openhands.sdk.agent.response_dispatch import (
18
18
  ResponseDispatchMixin,
19
19
  classify_response,
20
20
  )
21
+ from openhands.sdk.agent.stream_context import StreamContext
21
22
  from openhands.sdk.agent.utils import (
22
- amake_llm_completion,
23
23
  aprepare_llm_messages,
24
24
  fix_malformed_tool_arguments,
25
- make_llm_completion,
26
25
  normalize_tool_call,
27
26
  parse_tool_call_arguments,
28
27
  prepare_llm_messages,
@@ -535,7 +534,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
535
534
  system_prompt=TextContent(text=self.static_system_message),
536
535
  # Tools are stored as ToolDefinition objects and converted to
537
536
  # OpenAI format with security_risk parameter during LLM completion.
538
- # See make_llm_completion() in agent/utils.py for details.
537
+ # Agent calls always expose security risk prediction in tool schemas.
539
538
  tools=list(self.tools_map.values()),
540
539
  dynamic_context=TextContent(text=dynamic_context)
541
540
  if dynamic_context
@@ -639,6 +638,15 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
639
638
  conversation: LocalConversation,
640
639
  on_event: ConversationCallbackType,
641
640
  on_token: ConversationTokenCallbackType | None = None,
641
+ ) -> None:
642
+ with StreamContext.open(conversation, on_token) as stream:
643
+ self._step(conversation, on_event, stream)
644
+
645
+ def _step(
646
+ self,
647
+ conversation: LocalConversation,
648
+ on_event: ConversationCallbackType,
649
+ stream: StreamContext,
642
650
  ) -> None:
643
651
  state = conversation.state
644
652
  # Check for pending actions (implicit confirmation)
@@ -717,11 +725,12 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
717
725
  )
718
726
 
719
727
  try:
720
- llm_response = make_llm_completion(
721
- self.llm,
722
- _messages,
728
+ llm_response = self.llm.generate(
729
+ messages=_messages,
723
730
  tools=list(self.tools_map.values()),
724
- on_token=on_token,
731
+ store=False,
732
+ add_security_risk_prediction=True,
733
+ on_token=stream.token_callback,
725
734
  call_context=call_context,
726
735
  )
727
736
  except FunctionCallValidationError as e:
@@ -807,11 +816,11 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
807
816
  match response_type:
808
817
  case LLMResponseType.TOOL_CALLS:
809
818
  self._handle_tool_calls(
810
- message, llm_response, conversation, state, on_event
819
+ message, llm_response, conversation, state, on_event, stream
811
820
  )
812
821
  case LLMResponseType.CONTENT:
813
822
  self._handle_content_response(
814
- message, llm_response, conversation, state, on_event
823
+ message, llm_response, conversation, state, on_event, stream
815
824
  )
816
825
  case LLMResponseType.REASONING_ONLY | LLMResponseType.EMPTY:
817
826
  self._handle_no_content_response(
@@ -820,6 +829,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
820
829
  conversation,
821
830
  state,
822
831
  on_event,
832
+ stream,
823
833
  response_type=response_type,
824
834
  )
825
835
 
@@ -833,12 +843,21 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
833
843
  """Async variant of :meth:`step`.
834
844
 
835
845
  The LLM completion is performed asynchronously via
836
- :func:`amake_llm_completion`. Tool dispatch uses
846
+ :meth:`LLM.agenerate`. Tool dispatch uses
837
847
  :meth:`_aexecute_actions` which runs each tool call in its own
838
848
  thread via :func:`asyncio.loop.run_in_executor` and schedules
839
849
  parallel calls with :func:`asyncio.gather`, keeping the event
840
850
  loop responsive during blocking tool I/O.
841
851
  """
852
+ with StreamContext.open(conversation, on_token) as stream:
853
+ await self._astep(conversation, on_event, stream)
854
+
855
+ async def _astep(
856
+ self,
857
+ conversation: LocalConversation,
858
+ on_event: ConversationCallbackType,
859
+ stream: StreamContext,
860
+ ) -> None:
842
861
  state = conversation.state
843
862
  # Check for pending actions (implicit confirmation)
844
863
  pending_actions = ConversationState.get_unmatched_actions(state.active_branch())
@@ -914,11 +933,12 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
914
933
  # and state snapshots aren't blocked for the whole response. No-op
915
934
  # unless the run loop holds the lock (e.g. direct astep() in tests).
916
935
  async with conversation._released_state_lock_during_io():
917
- llm_response = await amake_llm_completion(
918
- self.llm,
919
- _messages,
936
+ llm_response = await self.llm.agenerate(
937
+ messages=_messages,
920
938
  tools=list(self.tools_map.values()),
921
- on_token=on_token,
939
+ store=False,
940
+ add_security_risk_prediction=True,
941
+ on_token=stream.token_callback,
922
942
  call_context=call_context,
923
943
  )
924
944
  except FunctionCallValidationError as e:
@@ -1006,11 +1026,11 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
1006
1026
  match response_type:
1007
1027
  case LLMResponseType.TOOL_CALLS:
1008
1028
  await self._ahandle_tool_calls(
1009
- message, llm_response, conversation, state, on_event
1029
+ message, llm_response, conversation, state, on_event, stream
1010
1030
  )
1011
1031
  case LLMResponseType.CONTENT:
1012
1032
  self._handle_content_response(
1013
- message, llm_response, conversation, state, on_event
1033
+ message, llm_response, conversation, state, on_event, stream
1014
1034
  )
1015
1035
  case LLMResponseType.REASONING_ONLY | LLMResponseType.EMPTY:
1016
1036
  self._handle_no_content_response(
@@ -1019,6 +1039,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
1019
1039
  conversation,
1020
1040
  state,
1021
1041
  on_event,
1042
+ stream,
1022
1043
  response_type=response_type,
1023
1044
  )
1024
1045
 
@@ -1153,6 +1174,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
1153
1174
  reasoning_content: str | None = None,
1154
1175
  thinking_blocks: list[ThinkingBlock | RedactedThinkingBlock] | None = None,
1155
1176
  responses_reasoning_item: ReasoningItemModel | None = None,
1177
+ stream: StreamContext | None = None,
1156
1178
  ) -> None:
1157
1179
  try:
1158
1180
  json.loads(tool_call.arguments)
@@ -1168,7 +1190,11 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
1168
1190
  }
1169
1191
  )
1170
1192
 
1193
+ minted: dict[str, Any] = {}
1194
+ if stream is not None and (item_id := stream.claim()):
1195
+ minted["id"] = item_id
1171
1196
  tc_event = ActionEvent(
1197
+ **minted,
1172
1198
  source="agent",
1173
1199
  thought=thought or [],
1174
1200
  reasoning_content=reasoning_content,
@@ -1181,6 +1207,8 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
1181
1207
  action=None,
1182
1208
  )
1183
1209
  on_event(tc_event)
1210
+ if stream is not None and minted:
1211
+ stream.commit()
1184
1212
  error_event = AgentErrorEvent(
1185
1213
  error=error,
1186
1214
  tool_name=tool_name,
@@ -1207,6 +1235,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
1207
1235
  reasoning_content: str | None = None,
1208
1236
  thinking_blocks: list[ThinkingBlock | RedactedThinkingBlock] | None = None,
1209
1237
  responses_reasoning_item: ReasoningItemModel | None = None,
1238
+ stream: StreamContext | None = None,
1210
1239
  ) -> ActionEvent | None:
1211
1240
  """Converts a tool call into an ActionEvent, validating arguments.
1212
1241
 
@@ -1249,6 +1278,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
1249
1278
  reasoning_content=reasoning_content,
1250
1279
  thinking_blocks=thinking_blocks,
1251
1280
  responses_reasoning_item=responses_reasoning_item,
1281
+ stream=stream,
1252
1282
  )
1253
1283
  return
1254
1284
 
@@ -1311,11 +1341,19 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
1311
1341
  reasoning_content=reasoning_content,
1312
1342
  thinking_blocks=thinking_blocks,
1313
1343
  responses_reasoning_item=responses_reasoning_item,
1344
+ stream=stream,
1314
1345
  )
1315
1346
  return
1316
1347
 
1317
- # Create initial action event
1348
+ # Create initial action event. Claimed here rather than at the call
1349
+ # site so an error path, which never builds this event, leaves the slot
1350
+ # open for the abort in StreamContext.__exit__.
1351
+ minted: dict[str, Any] = {}
1352
+ if stream is not None and (item_id := stream.claim()):
1353
+ minted["id"] = item_id
1354
+
1318
1355
  action_event = ActionEvent(
1356
+ **minted,
1319
1357
  action=action,
1320
1358
  thought=thought or [],
1321
1359
  reasoning_content=reasoning_content,
@@ -1339,6 +1377,8 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
1339
1377
  )
1340
1378
 
1341
1379
  on_event(action_event)
1380
+ if stream is not None and minted:
1381
+ stream.commit()
1342
1382
  return action_event
1343
1383
 
1344
1384
  def _execute_action_event(
@@ -9,8 +9,9 @@ Contains:
9
9
  from __future__ import annotations
10
10
 
11
11
  from enum import StrEnum
12
- from typing import TYPE_CHECKING
12
+ from typing import TYPE_CHECKING, Any
13
13
 
14
+ from openhands.sdk.agent.stream_context import StreamContext
14
15
  from openhands.sdk.conversation.state import ConversationExecutionStatus
15
16
  from openhands.sdk.event import MessageEvent
16
17
  from openhands.sdk.llm import LLMResponse, Message, TextContent
@@ -106,6 +107,7 @@ class ResponseDispatchMixin:
106
107
  list[ThinkingBlock | RedactedThinkingBlock] | None
107
108
  ) = None,
108
109
  responses_reasoning_item: ReasoningItemModel | None = None,
110
+ stream: StreamContext | None = None,
109
111
  ) -> ActionEvent | None: ...
110
112
 
111
113
  def _execute_actions(
@@ -147,6 +149,7 @@ class ResponseDispatchMixin:
147
149
  conversation: LocalConversation,
148
150
  state: ConversationState,
149
151
  on_event: ConversationCallbackType,
152
+ stream: StreamContext | None = None,
150
153
  ) -> None:
151
154
  """Handle LLM response containing tool calls."""
152
155
  if not all(isinstance(c, TextContent) for c in message.content):
@@ -172,6 +175,9 @@ class ResponseDispatchMixin:
172
175
  responses_reasoning_item=(
173
176
  message.responses_reasoning_item if i == 0 else None
174
177
  ),
178
+ # The streamed text is this action's thought, so the first
179
+ # action event is what retires the slot.
180
+ stream=stream if i == 0 else None,
175
181
  )
176
182
  if action_event is None:
177
183
  continue
@@ -192,6 +198,7 @@ class ResponseDispatchMixin:
192
198
  conversation: LocalConversation,
193
199
  state: ConversationState,
194
200
  on_event: ConversationCallbackType,
201
+ stream: StreamContext | None = None,
195
202
  ) -> None:
196
203
  """Async variant of :meth:`_handle_tool_calls`.
197
204
 
@@ -222,6 +229,9 @@ class ResponseDispatchMixin:
222
229
  responses_reasoning_item=(
223
230
  message.responses_reasoning_item if i == 0 else None
224
231
  ),
232
+ # The streamed text is this action's thought, so the first
233
+ # action event is what retires the slot.
234
+ stream=stream if i == 0 else None,
225
235
  )
226
236
  if action_event is None:
227
237
  continue
@@ -242,9 +252,10 @@ class ResponseDispatchMixin:
242
252
  conversation: LocalConversation,
243
253
  state: ConversationState,
244
254
  on_event: ConversationCallbackType,
255
+ stream: StreamContext | None = None,
245
256
  ) -> None:
246
257
  """Handle LLM response with text content — finishes conversation."""
247
- self._emit_message_event(message, llm_response, conversation, on_event)
258
+ self._emit_message_event(message, llm_response, conversation, on_event, stream)
248
259
  self._maybe_emit_vllm_tokens(llm_response, on_event)
249
260
  logger.debug("LLM produced a message response - awaits user input")
250
261
  state.execution_status = ConversationExecutionStatus.FINISHED
@@ -256,6 +267,7 @@ class ResponseDispatchMixin:
256
267
  conversation: LocalConversation,
257
268
  state: ConversationState, # noqa: ARG002
258
269
  on_event: ConversationCallbackType,
270
+ stream: StreamContext | None = None,
259
271
  *,
260
272
  response_type: LLMResponseType,
261
273
  ) -> None:
@@ -267,7 +279,7 @@ class ResponseDispatchMixin:
267
279
  """
268
280
  if response_type is LLMResponseType.EMPTY:
269
281
  logger.warning("LLM produced empty response - continuing agent loop")
270
- self._emit_message_event(message, llm_response, conversation, on_event)
282
+ self._emit_message_event(message, llm_response, conversation, on_event, stream)
271
283
  self._maybe_emit_vllm_tokens(llm_response, on_event)
272
284
  self._send_corrective_nudge(on_event)
273
285
 
@@ -277,9 +289,18 @@ class ResponseDispatchMixin:
277
289
  llm_response: LLMResponse,
278
290
  conversation: LocalConversation,
279
291
  on_event: ConversationCallbackType,
292
+ stream: StreamContext | None = None,
280
293
  ) -> MessageEvent:
281
- """Create and emit a MessageEvent, running critic if configured."""
294
+ """Create and emit a MessageEvent, running critic if configured.
295
+
296
+ The message carries the id its own stream minted, so a client holding
297
+ an open slot retires it on ``frame.event.id == slot.item_id``.
298
+ """
299
+ minted: dict[str, Any] = {}
300
+ if stream is not None and (item_id := stream.claim()):
301
+ minted["id"] = item_id
282
302
  msg_event = MessageEvent(
303
+ **minted,
283
304
  source="agent",
284
305
  llm_message=self._mask_secrets(message, conversation),
285
306
  llm_response_id=llm_response.id,
@@ -291,6 +312,10 @@ class ResponseDispatchMixin:
291
312
  update={"critic_result": critic_result}
292
313
  )
293
314
  on_event(msg_event)
315
+ # Retired only now: on_event can raise while persisting, and a slot
316
+ # retired before that leaves the client holding it open forever.
317
+ if stream is not None and minted:
318
+ stream.commit()
294
319
  return msg_event
295
320
 
296
321
  @staticmethod